@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.
- package/CHANGELOG.md +1476 -0
- package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +17 -0
- package/assets/renderer-scaffold/test/MyRenderer.t.sol +51 -2
- package/dist/flags.d.ts +7 -0
- package/dist/flags.d.ts.map +1 -1
- package/dist/flags.js +19 -0
- package/dist/flags.js.map +1 -1
- package/dist/jsonout.d.ts +37 -0
- package/dist/jsonout.d.ts.map +1 -0
- package/dist/jsonout.js +68 -0
- package/dist/jsonout.js.map +1 -0
- package/dist/main.js +791 -42
- package/dist/main.js.map +1 -1
- package/dist/ownerops.d.ts +42 -1
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +238 -53
- package/dist/ownerops.js.map +1 -1
- package/dist/resume.d.ts +96 -0
- package/dist/resume.d.ts.map +1 -0
- package/dist/resume.js +95 -0
- package/dist/resume.js.map +1 -0
- package/dist/scaffold.d.ts +10 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +52 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/served.d.ts +46 -0
- package/dist/served.d.ts.map +1 -0
- package/dist/served.js +65 -0
- package/dist/served.js.map +1 -0
- package/package.json +7 -6
- package/skill/SKILL.md +7 -4
- package/skill/reference/code-projects.md +8 -6
- package/skill/reference/operating.md +2 -0
- package/skill/reference/setup.md +2 -1
- package/skill/reference/troubleshooting.md +13 -0
package/dist/ownerops.d.ts
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 { 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`).
|
package/dist/ownerops.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
138
|
+
return null; // cold lane — nothing broadcast
|
|
129
139
|
await reindexIfKnown(address, flags);
|
|
130
|
-
return
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
254
|
+
return v;
|
|
233
255
|
}
|
|
234
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
479
|
-
|
|
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,
|
|
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
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
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
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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
|
|
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
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
1178
|
-
dim(` ${collection ? '' : `→ artifacts[].key "${key}"; /data
|
|
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
|