@clanker-chain/clanker-cli 2026.9.12 → 2026.9.13
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/bin/chain-identity.mjs +40 -62
- package/bin/clanker.mjs +50 -12
- package/lib/doctor.mjs +20 -7
- package/lib/fund.mjs +61 -24
- package/lib/login.mjs +200 -0
- package/lib/operator-key.mjs +2 -3
- package/lib/operator-signer.mjs +284 -0
- package/lib/pair.mjs +8 -20
- package/lib/privy-client.mjs +419 -0
- package/lib/privy-constants.mjs +20 -0
- package/lib/privy-hpke.mjs +101 -0
- package/lib/privy-session.mjs +151 -0
- package/lib/profile.mjs +1 -1
- package/lib/resolve.mjs +11 -6
- package/lib/setup.mjs +122 -16
- package/package.json +6 -1
package/bin/chain-identity.mjs
CHANGED
|
@@ -3,41 +3,14 @@
|
|
|
3
3
|
* On-chain identity commands (ClankerIdentity contract).
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
createPublicClient,
|
|
8
|
-
createWalletClient,
|
|
9
|
-
defineChain,
|
|
10
|
-
http,
|
|
11
|
-
} from "viem";
|
|
12
6
|
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
13
7
|
import { clankerIdentityAbi } from "../lib/clanker-identity-abi.mjs";
|
|
14
8
|
import { assertSafeBotLabel, writeBotKeyFiles } from "../lib/keys.mjs";
|
|
15
9
|
import { harnessSnippet, writeOperator } from "../lib/profile.mjs";
|
|
16
10
|
import { labelToId } from "../lib/identity-query.mjs";
|
|
11
|
+
import { resolveOperatorSigner } from "../lib/operator-signer.mjs";
|
|
17
12
|
import { resolveOperatorKey } from "../lib/resolve.mjs";
|
|
18
13
|
|
|
19
|
-
async function chainFromRpc(rpc) {
|
|
20
|
-
const publicClient = createPublicClient({ transport: http(rpc) });
|
|
21
|
-
const id = await publicClient.getChainId();
|
|
22
|
-
return defineChain({
|
|
23
|
-
id,
|
|
24
|
-
name: `clanker-chain-${id}`,
|
|
25
|
-
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
26
|
-
rpcUrls: { default: { http: [rpc] } },
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function walletFromKey(rpc, key) {
|
|
31
|
-
const account = privateKeyToAccount(key);
|
|
32
|
-
const transport = http(rpc);
|
|
33
|
-
const chain = await chainFromRpc(rpc);
|
|
34
|
-
return {
|
|
35
|
-
account,
|
|
36
|
-
public: createPublicClient({ chain, transport }),
|
|
37
|
-
wallet: createWalletClient({ account, chain, transport }),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
14
|
async function waitOk(pub, hash) {
|
|
42
15
|
const receipt = await pub.waitForTransactionReceipt({ hash });
|
|
43
16
|
if (receipt.status !== "success") {
|
|
@@ -48,6 +21,7 @@ async function waitOk(pub, hash) {
|
|
|
48
21
|
|
|
49
22
|
/**
|
|
50
23
|
* Shared resolve for mutating chain commands (Anvil-guarded).
|
|
24
|
+
* Prefer resolveOperatorSigner for Privy + local.
|
|
51
25
|
* @param {string[]} argv
|
|
52
26
|
*/
|
|
53
27
|
export function resolveForWrite(argv) {
|
|
@@ -55,15 +29,15 @@ export function resolveForWrite(argv) {
|
|
|
55
29
|
}
|
|
56
30
|
|
|
57
31
|
export async function chainMintOperator(label, argv, opts = {}) {
|
|
58
|
-
const
|
|
59
|
-
const
|
|
32
|
+
const signer = await resolveOperatorSigner(argv, opts);
|
|
33
|
+
const pub = signer.publicClient;
|
|
60
34
|
const operatorFee = await pub.readContract({
|
|
61
|
-
address: network.registry,
|
|
35
|
+
address: signer.network.registry,
|
|
62
36
|
abi: clankerIdentityAbi,
|
|
63
37
|
functionName: "operatorFee",
|
|
64
38
|
});
|
|
65
|
-
const hash = await
|
|
66
|
-
address: network.registry,
|
|
39
|
+
const hash = await signer.writeContract({
|
|
40
|
+
address: signer.network.registry,
|
|
67
41
|
abi: clankerIdentityAbi,
|
|
68
42
|
functionName: "registerOperator",
|
|
69
43
|
args: [label],
|
|
@@ -72,23 +46,27 @@ export async function chainMintOperator(label, argv, opts = {}) {
|
|
|
72
46
|
await waitOk(pub, hash);
|
|
73
47
|
|
|
74
48
|
writeOperator(
|
|
75
|
-
{
|
|
76
|
-
|
|
49
|
+
{
|
|
50
|
+
label,
|
|
51
|
+
owner: signer.address,
|
|
52
|
+
key: opts.keyPointer ?? signer.keyPointer,
|
|
53
|
+
},
|
|
54
|
+
opts.home ?? signer.network.home,
|
|
77
55
|
);
|
|
78
56
|
|
|
79
57
|
return {
|
|
80
58
|
ok: true,
|
|
81
59
|
label,
|
|
82
60
|
operator_id: labelToId(label),
|
|
83
|
-
owner: address,
|
|
61
|
+
owner: signer.address,
|
|
84
62
|
tx: hash,
|
|
85
63
|
};
|
|
86
64
|
}
|
|
87
65
|
|
|
88
66
|
export async function chainMintBot(botLabel, operatorLabel, argv, opts = {}) {
|
|
89
67
|
assertSafeBotLabel(botLabel);
|
|
90
|
-
const
|
|
91
|
-
const
|
|
68
|
+
const signer = await resolveOperatorSigner(argv, opts);
|
|
69
|
+
const pub = signer.publicClient;
|
|
92
70
|
let botPrivateKey;
|
|
93
71
|
for (let i = 0; i < argv.length; i += 1) {
|
|
94
72
|
const a = argv[i];
|
|
@@ -114,12 +92,12 @@ export async function chainMintBot(botLabel, operatorLabel, argv, opts = {}) {
|
|
|
114
92
|
);
|
|
115
93
|
|
|
116
94
|
const botFee = await pub.readContract({
|
|
117
|
-
address: network.registry,
|
|
95
|
+
address: signer.network.registry,
|
|
118
96
|
abi: clankerIdentityAbi,
|
|
119
97
|
functionName: "botFee",
|
|
120
98
|
});
|
|
121
|
-
const hash = await
|
|
122
|
-
address: network.registry,
|
|
99
|
+
const hash = await signer.writeContract({
|
|
100
|
+
address: signer.network.registry,
|
|
123
101
|
abi: clankerIdentityAbi,
|
|
124
102
|
functionName: "registerBot",
|
|
125
103
|
args: [operatorIdBytes, botLabel, botAccount.address],
|
|
@@ -130,7 +108,7 @@ export async function chainMintBot(botLabel, operatorLabel, argv, opts = {}) {
|
|
|
130
108
|
const channelsMqtt = harnessSnippet({
|
|
131
109
|
botId: botLabel,
|
|
132
110
|
operatorId: operatorLabel,
|
|
133
|
-
network,
|
|
111
|
+
network: signer.network,
|
|
134
112
|
});
|
|
135
113
|
|
|
136
114
|
return {
|
|
@@ -146,11 +124,11 @@ export async function chainMintBot(botLabel, operatorLabel, argv, opts = {}) {
|
|
|
146
124
|
}
|
|
147
125
|
|
|
148
126
|
export async function chainRotateBotKey(botLabel, newKey, argv, opts = {}) {
|
|
149
|
-
const
|
|
150
|
-
const
|
|
127
|
+
const signer = await resolveOperatorSigner(argv, opts);
|
|
128
|
+
const pub = signer.publicClient;
|
|
151
129
|
const botIdBytes = labelToId(botLabel);
|
|
152
|
-
const hash = await
|
|
153
|
-
address: network.registry,
|
|
130
|
+
const hash = await signer.writeContract({
|
|
131
|
+
address: signer.network.registry,
|
|
154
132
|
abi: clankerIdentityAbi,
|
|
155
133
|
functionName: "rotateBotKey",
|
|
156
134
|
args: [botIdBytes, newKey],
|
|
@@ -160,11 +138,11 @@ export async function chainRotateBotKey(botLabel, newKey, argv, opts = {}) {
|
|
|
160
138
|
}
|
|
161
139
|
|
|
162
140
|
export async function chainRevokeBot(botLabel, argv, opts = {}) {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
141
|
+
const signer = await resolveOperatorSigner(argv, opts);
|
|
142
|
+
const pub = signer.publicClient;
|
|
165
143
|
const botIdBytes = labelToId(botLabel);
|
|
166
|
-
const hash = await
|
|
167
|
-
address: network.registry,
|
|
144
|
+
const hash = await signer.writeContract({
|
|
145
|
+
address: signer.network.registry,
|
|
168
146
|
abi: clankerIdentityAbi,
|
|
169
147
|
functionName: "revokeBot",
|
|
170
148
|
args: [botIdBytes],
|
|
@@ -174,11 +152,11 @@ export async function chainRevokeBot(botLabel, argv, opts = {}) {
|
|
|
174
152
|
}
|
|
175
153
|
|
|
176
154
|
export async function chainProposeOperatorTransfer(label, newOwner, argv, opts = {}) {
|
|
177
|
-
const
|
|
178
|
-
const
|
|
155
|
+
const signer = await resolveOperatorSigner(argv, opts);
|
|
156
|
+
const pub = signer.publicClient;
|
|
179
157
|
const id = labelToId(label);
|
|
180
|
-
const hash = await
|
|
181
|
-
address: network.registry,
|
|
158
|
+
const hash = await signer.writeContract({
|
|
159
|
+
address: signer.network.registry,
|
|
182
160
|
abi: clankerIdentityAbi,
|
|
183
161
|
functionName: "proposeOperatorTransfer",
|
|
184
162
|
args: [id, newOwner],
|
|
@@ -188,11 +166,11 @@ export async function chainProposeOperatorTransfer(label, newOwner, argv, opts =
|
|
|
188
166
|
}
|
|
189
167
|
|
|
190
168
|
export async function chainAcceptOperatorTransfer(label, argv, opts = {}) {
|
|
191
|
-
const
|
|
192
|
-
const
|
|
169
|
+
const signer = await resolveOperatorSigner(argv, opts);
|
|
170
|
+
const pub = signer.publicClient;
|
|
193
171
|
const id = labelToId(label);
|
|
194
|
-
const hash = await
|
|
195
|
-
address: network.registry,
|
|
172
|
+
const hash = await signer.writeContract({
|
|
173
|
+
address: signer.network.registry,
|
|
196
174
|
abi: clankerIdentityAbi,
|
|
197
175
|
functionName: "acceptOperatorTransfer",
|
|
198
176
|
args: [id],
|
|
@@ -201,12 +179,12 @@ export async function chainAcceptOperatorTransfer(label, argv, opts = {}) {
|
|
|
201
179
|
writeOperator(
|
|
202
180
|
{
|
|
203
181
|
label,
|
|
204
|
-
owner: address,
|
|
205
|
-
key: opts.keyPointer ?? keyPointer,
|
|
182
|
+
owner: signer.address,
|
|
183
|
+
key: opts.keyPointer ?? signer.keyPointer,
|
|
206
184
|
},
|
|
207
|
-
opts.home ?? network.home,
|
|
185
|
+
opts.home ?? signer.network.home,
|
|
208
186
|
);
|
|
209
|
-
return { ok: true, label, owner: address, tx: hash };
|
|
187
|
+
return { ok: true, label, owner: signer.address, tx: hash };
|
|
210
188
|
}
|
|
211
189
|
|
|
212
190
|
export { labelToId };
|
package/bin/clanker.mjs
CHANGED
|
@@ -24,9 +24,11 @@ import {
|
|
|
24
24
|
resolvePreferredOperator,
|
|
25
25
|
} from "../lib/identity-query.mjs";
|
|
26
26
|
import { resolveForRead, resolveOperatorKey, resolveReadIdentity } from "../lib/resolve.mjs";
|
|
27
|
+
import { resolveOperatorSigner } from "../lib/operator-signer.mjs";
|
|
27
28
|
import { runSetup } from "../lib/setup.mjs";
|
|
28
29
|
import { runDoctor } from "../lib/doctor.mjs";
|
|
29
30
|
import { runFund } from "../lib/fund.mjs";
|
|
31
|
+
import { runLogin, runLogout } from "../lib/login.mjs";
|
|
30
32
|
import {
|
|
31
33
|
assessMintBudget,
|
|
32
34
|
formatEthTrim,
|
|
@@ -170,8 +172,10 @@ function usage() {
|
|
|
170
172
|
console.log(`clanker - clanker-chain helper CLI
|
|
171
173
|
|
|
172
174
|
Usage:
|
|
173
|
-
clanker setup [--preset sepolia|local] [--operator <label>] [--address 0x…] [--key-file path] [--force]
|
|
174
|
-
clanker
|
|
175
|
+
clanker setup [--preset sepolia|local] [--operator <label>] [--address 0x…] [--skip-key] [--bot-key path] [--key-file path] [--force]
|
|
176
|
+
clanker login [--no-open] [--operator <label>] [--json]
|
|
177
|
+
clanker logout [--json]
|
|
178
|
+
clanker fund [--address 0x…] [--no-open] [--timeout ms] [--json]
|
|
175
179
|
clanker doctor [--json]
|
|
176
180
|
clanker init --preset sepolia|local [--force]
|
|
177
181
|
clanker whoami [--json] [--operator <label>] [--address 0x…] [--with-bots]
|
|
@@ -197,7 +201,7 @@ Profile:
|
|
|
197
201
|
~/.clanker/operator.json label + owner + optional key pointer (never raw hex)
|
|
198
202
|
~/.clanker/keys/ bot keys (also written to ~/.openclaw/keys/)
|
|
199
203
|
|
|
200
|
-
Humans: \`clanker
|
|
204
|
+
Humans: \`clanker login\` (email vault) or \`clanker setup --generate-key\`, then \`fund\` / \`doctor\` / \`whoami\`.
|
|
201
205
|
Pairing (Policy): both operators run \`clanker pair add\` before DMs deliver on the hub.
|
|
202
206
|
Mutates print a plan and confirm unless --yes or --json.
|
|
203
207
|
whoami is fast by default; pass --with-bots to enrich child bots (or use \`clanker bots\`).
|
|
@@ -415,7 +419,8 @@ async function main() {
|
|
|
415
419
|
error: err.message,
|
|
416
420
|
because: "setup could not write a valid local profile",
|
|
417
421
|
try: [
|
|
418
|
-
"clanker
|
|
422
|
+
"clanker login",
|
|
423
|
+
"clanker setup --preset sepolia --operator org.you --yes --force",
|
|
419
424
|
"clanker fund",
|
|
420
425
|
"clanker doctor",
|
|
421
426
|
],
|
|
@@ -424,6 +429,36 @@ async function main() {
|
|
|
424
429
|
return;
|
|
425
430
|
}
|
|
426
431
|
|
|
432
|
+
if (cmd === "login") {
|
|
433
|
+
try {
|
|
434
|
+
const { exitCode } = await runLogin(rest);
|
|
435
|
+
process.exit(exitCode);
|
|
436
|
+
} catch (err) {
|
|
437
|
+
exitCliError({
|
|
438
|
+
error: err.message,
|
|
439
|
+
because: "login needs Privy CLI access enabled and /authorize approval",
|
|
440
|
+
try: [
|
|
441
|
+
"Open the printed URL and Approve",
|
|
442
|
+
"Privy dashboard: Enable CLI and agent access → Verification URI https://clanker-chain.com/authorize",
|
|
443
|
+
"clanker setup --generate-key # local key door instead",
|
|
444
|
+
],
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (cmd === "logout") {
|
|
450
|
+
try {
|
|
451
|
+
const { exitCode } = await runLogout(rest);
|
|
452
|
+
process.exit(exitCode);
|
|
453
|
+
} catch (err) {
|
|
454
|
+
exitCliError({
|
|
455
|
+
error: err.message,
|
|
456
|
+
because: "logout could not clear the Privy session",
|
|
457
|
+
try: ["clanker logout"],
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
427
462
|
if (cmd === "fund") {
|
|
428
463
|
try {
|
|
429
464
|
const { exitCode } = await runFund(rest);
|
|
@@ -581,21 +616,21 @@ async function main() {
|
|
|
581
616
|
let planRows;
|
|
582
617
|
let preview;
|
|
583
618
|
try {
|
|
584
|
-
preview =
|
|
619
|
+
preview = await resolveOperatorSigner(flags);
|
|
585
620
|
planRows = [
|
|
586
621
|
["action", "registerOperator"],
|
|
587
622
|
["label", label],
|
|
588
623
|
["owner", preview.address],
|
|
589
624
|
["rpc", preview.network.rpc],
|
|
590
625
|
["registry", preview.network.registry ?? "(none)"],
|
|
591
|
-
["
|
|
626
|
+
["signer", preview.source],
|
|
592
627
|
];
|
|
593
628
|
} catch (err) {
|
|
594
629
|
exitCliError({
|
|
595
630
|
error: err.message,
|
|
596
|
-
because: "operator mint needs a
|
|
631
|
+
because: "operator mint needs a signer on this network",
|
|
597
632
|
try: [
|
|
598
|
-
"
|
|
633
|
+
"clanker login",
|
|
599
634
|
"clanker operator mint " + label + " --key-file ~/.clanker/op.key --yes",
|
|
600
635
|
"clanker fund",
|
|
601
636
|
"clanker doctor",
|
|
@@ -736,14 +771,15 @@ async function main() {
|
|
|
736
771
|
let preview = null;
|
|
737
772
|
if (!operatorLabel) {
|
|
738
773
|
try {
|
|
739
|
-
preview =
|
|
774
|
+
preview = await resolveOperatorSigner(flags);
|
|
740
775
|
const inferred = await resolveOperatorLabel(flags, preview.address, preview.network);
|
|
741
776
|
operatorLabel = inferred.label;
|
|
742
777
|
} catch (err) {
|
|
743
778
|
exitCliError({
|
|
744
779
|
error: err.message,
|
|
745
|
-
because: "bot mint needs an operator label or a resolvable
|
|
780
|
+
because: "bot mint needs an operator label or a resolvable signer",
|
|
746
781
|
try: [
|
|
782
|
+
"clanker login",
|
|
747
783
|
`clanker bot mint ${botLabel} <operator_label> --yes`,
|
|
748
784
|
"clanker setup",
|
|
749
785
|
],
|
|
@@ -752,12 +788,13 @@ async function main() {
|
|
|
752
788
|
}
|
|
753
789
|
if (!preview) {
|
|
754
790
|
try {
|
|
755
|
-
preview =
|
|
791
|
+
preview = await resolveOperatorSigner(flags);
|
|
756
792
|
} catch (err) {
|
|
757
793
|
exitCliError({
|
|
758
794
|
error: err.message,
|
|
759
|
-
because: "bot mint needs a
|
|
795
|
+
because: "bot mint needs a signer on this network",
|
|
760
796
|
try: [
|
|
797
|
+
"clanker login",
|
|
761
798
|
`clanker bot mint ${botLabel} ${operatorLabel} --key-file ~/.clanker/op.key --yes`,
|
|
762
799
|
"clanker fund",
|
|
763
800
|
],
|
|
@@ -770,6 +807,7 @@ async function main() {
|
|
|
770
807
|
["operator", operatorLabel],
|
|
771
808
|
["owner", preview.address],
|
|
772
809
|
["registry", preview.network.registry ?? "(none)"],
|
|
810
|
+
["signer", preview.source],
|
|
773
811
|
];
|
|
774
812
|
try {
|
|
775
813
|
if (preview.network.registry) {
|
package/lib/doctor.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "./profile.mjs";
|
|
13
13
|
import { detectSetupHints, formatSetupDetectTable } from "./setup-detect.mjs";
|
|
14
14
|
import { publicClientFromRpc } from "./identity-query.mjs";
|
|
15
|
+
import { loadPrivySession } from "./privy-session.mjs";
|
|
15
16
|
import {
|
|
16
17
|
assessMintBudget,
|
|
17
18
|
budgetToJson,
|
|
@@ -96,17 +97,24 @@ export async function runDoctorChecks(opts = {}) {
|
|
|
96
97
|
});
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
const privySession = loadPrivySession(home);
|
|
101
|
+
const hasPrivy =
|
|
102
|
+
Boolean(operator?.key?.type === "privy" && operator.key.value) ||
|
|
103
|
+
Boolean(privySession?.accessToken && privySession?.walletId);
|
|
99
104
|
const hasKey =
|
|
100
105
|
Boolean(operator?.key?.type === "keyFile" && operator.key.value) ||
|
|
101
106
|
Boolean(operator?.key?.type === "env" && operator.key.value) ||
|
|
102
|
-
Boolean(env.OPERATOR_PRIVATE_KEY)
|
|
107
|
+
Boolean(env.OPERATOR_PRIVATE_KEY) ||
|
|
108
|
+
hasPrivy;
|
|
103
109
|
checks.push({
|
|
104
110
|
id: "signing",
|
|
105
111
|
ok: true,
|
|
106
112
|
level: hasKey ? "pass" : "warn",
|
|
107
|
-
message:
|
|
108
|
-
? "
|
|
109
|
-
:
|
|
113
|
+
message: hasPrivy
|
|
114
|
+
? "Privy session available (mint/pair/rotate via email vault)"
|
|
115
|
+
: hasKey
|
|
116
|
+
? "signing key pointer available (mint/pair/rotate OK)"
|
|
117
|
+
: "read-only profile — whoami/bots/fund OK; mint/pair/rotate need clanker login or a key pointer",
|
|
110
118
|
});
|
|
111
119
|
|
|
112
120
|
checks.push({
|
|
@@ -293,17 +301,22 @@ export async function runDoctor(argv = [], opts = {}) {
|
|
|
293
301
|
} else if (report.budget && !report.budget.funded && !report.budget.local) {
|
|
294
302
|
console.log(c.dim("Mint: fund the operator address first (clanker fund)"));
|
|
295
303
|
} else {
|
|
296
|
-
console.log(
|
|
304
|
+
console.log(
|
|
305
|
+
c.dim(
|
|
306
|
+
"Mint/pair/rotate: need clanker login or a signing key (non-Anvil owner on public RPC)",
|
|
307
|
+
),
|
|
308
|
+
);
|
|
297
309
|
}
|
|
298
310
|
|
|
299
311
|
if (!report.readyWhoami) {
|
|
300
|
-
nextHint(["clanker setup"]);
|
|
312
|
+
nextHint(["clanker setup", "clanker login"]);
|
|
301
313
|
} else if (report.budget && !report.budget.funded && !report.budget.local) {
|
|
302
314
|
nextHint(["clanker fund", "clanker doctor"]);
|
|
303
315
|
} else if (!report.readyMint) {
|
|
304
316
|
nextHint([
|
|
317
|
+
"clanker login",
|
|
305
318
|
"clanker whoami",
|
|
306
|
-
"
|
|
319
|
+
"mint/pair/rotate need login or --key-file",
|
|
307
320
|
]);
|
|
308
321
|
} else {
|
|
309
322
|
nextHint(["clanker whoami", "clanker bots"]);
|
package/lib/fund.mjs
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { getAddress } from "viem";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
clankerHome,
|
|
9
|
+
loadConfig,
|
|
10
|
+
loadOperator,
|
|
11
|
+
isLocalRpc,
|
|
12
|
+
PRESETS,
|
|
13
|
+
} from "./profile.mjs";
|
|
8
14
|
import { publicClientFromRpc } from "./identity-query.mjs";
|
|
9
15
|
import {
|
|
10
16
|
assessMintBudget,
|
|
@@ -53,10 +59,12 @@ export function parseFundFlags(argv) {
|
|
|
53
59
|
let pollMs = DEFAULT_FUND_POLL_MS;
|
|
54
60
|
let noOpen = false;
|
|
55
61
|
let json = false;
|
|
62
|
+
let address = null;
|
|
56
63
|
for (let i = 0; i < argv.length; i += 1) {
|
|
57
64
|
const a = argv[i];
|
|
58
65
|
if (a === "--no-open") noOpen = true;
|
|
59
66
|
else if (a === "--json") json = true;
|
|
67
|
+
else if (a === "--address" && argv[i + 1]) address = argv[++i];
|
|
60
68
|
else if (a === "--timeout" && argv[i + 1]) {
|
|
61
69
|
timeoutMs = Number(argv[++i]);
|
|
62
70
|
} else if (a === "--poll" && argv[i + 1]) {
|
|
@@ -69,7 +77,29 @@ export function parseFundFlags(argv) {
|
|
|
69
77
|
if (!Number.isFinite(pollMs) || pollMs < 100) {
|
|
70
78
|
throw new Error("--poll must be >= 100 (ms)");
|
|
71
79
|
}
|
|
72
|
-
|
|
80
|
+
if (address && !/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
81
|
+
throw new Error("--address must be 0x + 40 hex");
|
|
82
|
+
}
|
|
83
|
+
return { timeoutMs, pollMs, noOpen, json, address };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {{ operator?: { key?: object|null, label?: string|null }|null, canSign?: boolean }} opts
|
|
88
|
+
*/
|
|
89
|
+
function fundNextHints(opts = {}) {
|
|
90
|
+
const canSign = opts.canSign ?? Boolean(opts.operator?.key);
|
|
91
|
+
if (canSign) {
|
|
92
|
+
return [
|
|
93
|
+
"clanker doctor",
|
|
94
|
+
`clanker operator mint ${opts.operator?.label ?? "<label>"} --yes`,
|
|
95
|
+
"clanker bot mint <bot_label> --yes",
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
return [
|
|
99
|
+
"clanker doctor",
|
|
100
|
+
"clanker whoami",
|
|
101
|
+
"mint/pair/rotate need a signing key or stay on /join",
|
|
102
|
+
];
|
|
73
103
|
}
|
|
74
104
|
|
|
75
105
|
/**
|
|
@@ -94,16 +124,31 @@ export async function runFund(argv = [], opts = {}) {
|
|
|
94
124
|
const now = opts.now ?? (() => Date.now());
|
|
95
125
|
const openUrlImpl = opts.openUrlImpl ?? openUrl;
|
|
96
126
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
127
|
+
const sepolia = PRESETS.sepolia;
|
|
128
|
+
let owner;
|
|
129
|
+
let rpc;
|
|
130
|
+
let registry;
|
|
131
|
+
let label = operator?.label ?? null;
|
|
103
132
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
133
|
+
if (flags.address) {
|
|
134
|
+
owner = getAddress(flags.address);
|
|
135
|
+
rpc = config?.chainRpcUrl ?? sepolia.chainRpcUrl;
|
|
136
|
+
registry = config?.registryAddress ?? sepolia.registryAddress;
|
|
137
|
+
} else {
|
|
138
|
+
if (!config) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
"config.json missing — run clanker setup, or pass --address 0x… to fund before setup",
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (!operator?.owner || !/^0x[0-9a-fA-F]{40}$/.test(operator.owner)) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
"operator.json incomplete — run clanker setup, or pass --address 0x…",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
owner = getAddress(operator.owner);
|
|
149
|
+
rpc = config.chainRpcUrl ?? "";
|
|
150
|
+
registry = config.registryAddress;
|
|
151
|
+
}
|
|
107
152
|
|
|
108
153
|
if (isLocalRpc(rpc)) {
|
|
109
154
|
const payload = {
|
|
@@ -138,7 +183,7 @@ export async function runFund(argv = [], opts = {}) {
|
|
|
138
183
|
registry,
|
|
139
184
|
owner,
|
|
140
185
|
rpc,
|
|
141
|
-
operatorLabel:
|
|
186
|
+
operatorLabel: label,
|
|
142
187
|
});
|
|
143
188
|
|
|
144
189
|
let budget = await assess();
|
|
@@ -154,7 +199,7 @@ export async function runFund(argv = [], opts = {}) {
|
|
|
154
199
|
console.log(c.bold("clanker fund"));
|
|
155
200
|
console.log("");
|
|
156
201
|
console.log(`owner: ${owner}`);
|
|
157
|
-
console.log(`label: ${
|
|
202
|
+
console.log(`label: ${label ?? "(none)"}`);
|
|
158
203
|
console.log(`registry: ${registry}`);
|
|
159
204
|
console.log(`budget: ${formatBudgetSummary(budget)}`);
|
|
160
205
|
console.log(`faucet: ${budget.faucetUrl}`);
|
|
@@ -165,11 +210,7 @@ export async function runFund(argv = [], opts = {}) {
|
|
|
165
210
|
if (budget.funded) {
|
|
166
211
|
if (!flags.json) {
|
|
167
212
|
console.log(c.green("Already funded for remaining mint fees + gas."));
|
|
168
|
-
nextHint(
|
|
169
|
-
"clanker doctor",
|
|
170
|
-
`clanker operator mint ${operator.label ?? "<label>"} --yes`,
|
|
171
|
-
"clanker bot mint <bot_label> --yes",
|
|
172
|
-
]);
|
|
213
|
+
nextHint(fundNextHints({ operator, canSign: Boolean(operator?.key) }));
|
|
173
214
|
}
|
|
174
215
|
return { exitCode: 0, funded: true, budget };
|
|
175
216
|
}
|
|
@@ -218,11 +259,7 @@ export async function runFund(argv = [], opts = {}) {
|
|
|
218
259
|
} else {
|
|
219
260
|
console.log("");
|
|
220
261
|
console.log(c.green("Funded. Ready to mint."));
|
|
221
|
-
nextHint(
|
|
222
|
-
"clanker doctor",
|
|
223
|
-
`clanker operator mint ${operator.label ?? "<label>"} --yes`,
|
|
224
|
-
"clanker bot mint <bot_label> --yes",
|
|
225
|
-
]);
|
|
262
|
+
nextHint(fundNextHints({ operator, canSign: Boolean(operator?.key) }));
|
|
226
263
|
}
|
|
227
264
|
return { exitCode: 0, funded: true, budget };
|
|
228
265
|
}
|
|
@@ -243,7 +280,7 @@ export async function runFund(argv = [], opts = {}) {
|
|
|
243
280
|
nextHint([
|
|
244
281
|
`Open ${budget.faucetUrl} and claim again`,
|
|
245
282
|
`Backup: ${budget.backupFaucetUrl}`,
|
|
246
|
-
"clanker fund",
|
|
283
|
+
flags.address ? `clanker fund --address ${owner}` : "clanker fund",
|
|
247
284
|
]);
|
|
248
285
|
}
|
|
249
286
|
return { exitCode: 1, funded: false, budget, timedOut: true };
|