@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.
@@ -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 { key, address, network, keyPointer } = resolveOperatorKey(argv, opts);
59
- const { public: pub, wallet } = await walletFromKey(network.rpc, key);
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 wallet.writeContract({
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
- { label, owner: address, key: opts.keyPointer ?? keyPointer },
76
- opts.home ?? network.home,
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 { key, network } = resolveOperatorKey(argv, opts);
91
- const { public: pub, wallet } = await walletFromKey(network.rpc, key);
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 wallet.writeContract({
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 { key, network } = resolveOperatorKey(argv, opts);
150
- const { public: pub, wallet } = await walletFromKey(network.rpc, key);
127
+ const signer = await resolveOperatorSigner(argv, opts);
128
+ const pub = signer.publicClient;
151
129
  const botIdBytes = labelToId(botLabel);
152
- const hash = await wallet.writeContract({
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 { key, network } = resolveOperatorKey(argv, opts);
164
- const { public: pub, wallet } = await walletFromKey(network.rpc, key);
141
+ const signer = await resolveOperatorSigner(argv, opts);
142
+ const pub = signer.publicClient;
165
143
  const botIdBytes = labelToId(botLabel);
166
- const hash = await wallet.writeContract({
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 { key, network } = resolveOperatorKey(argv, opts);
178
- const { public: pub, wallet } = await walletFromKey(network.rpc, key);
155
+ const signer = await resolveOperatorSigner(argv, opts);
156
+ const pub = signer.publicClient;
179
157
  const id = labelToId(label);
180
- const hash = await wallet.writeContract({
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 { key, address, network, keyPointer } = resolveOperatorKey(argv, opts);
192
- const { public: pub, wallet } = await walletFromKey(network.rpc, key);
169
+ const signer = await resolveOperatorSigner(argv, opts);
170
+ const pub = signer.publicClient;
193
171
  const id = labelToId(label);
194
- const hash = await wallet.writeContract({
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 fund [--no-open] [--timeout ms] [--json]
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 setup\` then \`clanker fund\` / \`doctor\` / \`whoami\`.
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 setup --preset sepolia --operator org.you --address 0x… --yes --force",
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 = resolveOperatorKey(flags);
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
- ["key", preview.source],
626
+ ["signer", preview.source],
592
627
  ];
593
628
  } catch (err) {
594
629
  exitCliError({
595
630
  error: err.message,
596
- because: "operator mint needs a signing key on this network",
631
+ because: "operator mint needs a signer on this network",
597
632
  try: [
598
- "export OPERATOR_PRIVATE_KEY=0x…",
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 = resolveOperatorKey(flags);
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 signing key",
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 = resolveOperatorKey(flags);
791
+ preview = await resolveOperatorSigner(flags);
756
792
  } catch (err) {
757
793
  exitCliError({
758
794
  error: err.message,
759
- because: "bot mint needs a signing key on this network",
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: hasKey
108
- ? "signing key pointer available (mint/revoke OK)"
109
- : "read-only profile — whoami/bots OK; mint needs --key-file or OPERATOR_PRIVATE_KEY",
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(c.dim("Mint/revoke: need signing key (and non-Anvil owner on public RPC)"));
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
- "clanker setup --key-file ~/.clanker/op.key --force # to enable mint",
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 { clankerHome, loadConfig, loadOperator, isLocalRpc } from "./profile.mjs";
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
- return { timeoutMs, pollMs, noOpen, json };
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
- if (!config) {
98
- throw new Error("config.json missing — run clanker setup");
99
- }
100
- if (!operator?.owner || !/^0x[0-9a-fA-F]{40}$/.test(operator.owner)) {
101
- throw new Error("operator.json incomplete — run clanker setup");
102
- }
127
+ const sepolia = PRESETS.sepolia;
128
+ let owner;
129
+ let rpc;
130
+ let registry;
131
+ let label = operator?.label ?? null;
103
132
 
104
- const rpc = config.chainRpcUrl ?? "";
105
- const registry = config.registryAddress;
106
- const owner = getAddress(operator.owner);
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: operator.label,
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: ${operator.label ?? "(none)"}`);
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 };