naracli 1.0.94 → 1.0.96

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naracli",
3
- "version": "1.0.94",
3
+ "version": "1.0.96",
4
4
  "description": "CLI for the Nara chain (Solana-compatible)",
5
5
  "homepage": "https://nara.build",
6
6
  "repository": {
@@ -67,7 +67,7 @@
67
67
  "bs58": "^6.0.0",
68
68
  "commander": "^12.1.0",
69
69
  "ed25519-hd-key": "^1.3.0",
70
- "nara-sdk": "^1.0.84",
70
+ "nara-sdk": "^1.0.85",
71
71
  "picocolors": "^1.1.1"
72
72
  },
73
73
  "packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
@@ -18,6 +18,7 @@ import {
18
18
  registerAgent,
19
19
  registerAgentWithReferral,
20
20
  getAgentInfo,
21
+ listAgentsByAuthority,
21
22
  getAgentMemory,
22
23
  setBio,
23
24
  setMetadata,
@@ -278,7 +279,6 @@ async function handleAgentGet(agentId: string, options: GlobalOptions) {
278
279
  console.log(` Link: ${submitTweetIntent}`);
279
280
  console.log(` Then run: npx naracli agent submit-tweet <tweet-url>`);
280
281
  console.log(` Credits are based on likes, bookmarks, retweets, and quotes.`);
281
- console.log(` Submit daily without breaking the streak to multiply your rewards!`);
282
282
  console.log("");
283
283
  } else {
284
284
  // Not bound — show bind tip
@@ -517,6 +517,35 @@ async function handleAgentRecover(agentId: string, options: GlobalOptions) {
517
517
  }
518
518
  }
519
519
 
520
+ async function handleAgentList(options: GlobalOptions) {
521
+ const rpcUrl = getRpcUrl(options.rpcUrl);
522
+ const wallet = await loadWallet(options.wallet);
523
+ const connection = new Connection(rpcUrl, "confirmed");
524
+
525
+ const agentIds = await listAgentsByAuthority(connection, wallet.publicKey);
526
+
527
+ if (options.json) {
528
+ formatOutput({ authority: wallet.publicKey.toBase58(), count: agentIds.length, agentIds }, true);
529
+ return;
530
+ }
531
+
532
+ if (agentIds.length === 0) {
533
+ printWarning(`No agents found for ${wallet.publicKey.toBase58()}`);
534
+ return;
535
+ }
536
+
537
+ const networkConfig = loadNetworkConfig(rpcUrl, wallet.publicKey.toBase58());
538
+ const savedId = networkConfig.agent_id;
539
+
540
+ console.log("");
541
+ console.log(` Authority: ${wallet.publicKey.toBase58()}`);
542
+ console.log(` Agents (${agentIds.length}):`);
543
+ for (const id of agentIds) {
544
+ console.log(` - ${id}${id === savedId ? " (saved locally)" : ""}`);
545
+ }
546
+ console.log("");
547
+ }
548
+
520
549
  async function handleAgentClear(options: GlobalOptions) {
521
550
  const rpcUrl = getRpcUrl(options.rpcUrl);
522
551
  let pubkey: string | undefined;
@@ -610,6 +639,13 @@ async function handleAgentSubmitTweet(agentId: string, tweetId: bigint, tweetUrl
610
639
  const connection = new Connection(rpcUrl, "confirmed");
611
640
  const wallet = await loadWallet(options.wallet);
612
641
 
642
+ // Require Twitter binding before submitting a tweet
643
+ const twitter = await getAgentTwitter(connection, agentId).catch(() => null);
644
+ if (!twitter) {
645
+ printError(`Agent "${agentId}" has not bound a Twitter account yet. Run "npx naracli agent bind-twitter" first.`);
646
+ process.exit(1);
647
+ }
648
+
613
649
  // Check if tweet has already been used
614
650
  const existing = await getTweetRecord(connection, tweetId);
615
651
  if (existing) {
@@ -907,6 +943,20 @@ export function registerAgentCommands(program: Command): void {
907
943
  }
908
944
  });
909
945
 
946
+ // agent list
947
+ agent
948
+ .command("list")
949
+ .description("List all agent IDs registered to this wallet's authority")
950
+ .action(async (_opts: any, cmd: Command) => {
951
+ try {
952
+ const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
953
+ await handleAgentList(globalOpts);
954
+ } catch (error: any) {
955
+ printError(error.message);
956
+ process.exit(1);
957
+ }
958
+ });
959
+
910
960
  // agent clear
911
961
  agent
912
962
  .command("clear")
@@ -26,7 +26,6 @@ import {
26
26
  unstake as questUnstake,
27
27
  getStakeInfo,
28
28
  type ActivityLog,
29
- type StakeInfo,
30
29
  type QuestOptions,
31
30
  } from "nara-sdk";
32
31
  import { loadNetworkConfig } from "../utils/agent-config";
@@ -59,6 +58,7 @@ const QUEST_ERRORS: Record<number, string> = {
59
58
  6009: "invalidMaxRewardCount",
60
59
  6010: "unstakeNotReady",
61
60
  6011: "insufficientStakeBalance",
61
+ 6024: "noCredits",
62
62
  };
63
63
 
64
64
  function anchorErrorCode(err: any): string {
@@ -70,6 +70,9 @@ function anchorErrorCode(err: any): string {
70
70
  return "";
71
71
  }
72
72
 
73
+ const STAKE_DEPRECATION_NOTICE =
74
+ "PoMI has been upgraded to Boost PoMI — mining is now gated by boost credits, not stake. The stake channel is being phased out; please unstake your NARA soon.";
75
+
73
76
  // ─── Helpers ─────────────────────────────────────────────────────
74
77
  function formatTimeRemaining(seconds: number): string {
75
78
  if (seconds <= 0) return "expired";
@@ -80,7 +83,7 @@ function formatTimeRemaining(seconds: number): string {
80
83
  }
81
84
 
82
85
  // ─── Command: quest get ──────────────────────────────────────────
83
- async function handleQuestGet(options: GlobalOptions & { verbose?: boolean }) {
86
+ async function handleQuestGet(options: GlobalOptions) {
84
87
  const rpcUrl = getRpcUrl(options.rpcUrl);
85
88
  const connection = new Connection(rpcUrl, "confirmed");
86
89
 
@@ -107,15 +110,12 @@ async function handleQuestGet(options: GlobalOptions & { verbose?: boolean }) {
107
110
  return;
108
111
  }
109
112
 
110
- // Stake is always required to answer quests
111
- const stakeRequired = true;
112
-
113
- // Fetch free credits (stake-free answer quota)
114
- let freeCredits = 0;
113
+ // Fetch Boost PoMI credits (required to submit an answer)
114
+ let boostCredits = 0;
115
115
  try {
116
116
  const stakeInfo = await getStakeInfo(connection, wallet.publicKey);
117
117
  if (stakeInfo) {
118
- freeCredits = stakeInfo.freeCredits;
118
+ boostCredits = stakeInfo.boostCredits;
119
119
  }
120
120
  } catch {
121
121
  // Ignore — wallet may not have a stake record
@@ -126,21 +126,13 @@ async function handleQuestGet(options: GlobalOptions & { verbose?: boolean }) {
126
126
  question: quest.question,
127
127
  difficulty: quest.difficulty,
128
128
  totalReward: `${quest.totalReward} NARA`,
129
- stakeRewardPerWinner: `${quest.stakeRewardPerWinner} NARA`,
130
- stakeRewardSlots: `${quest.stakeWinnerCount}/${quest.stakeRewardCount}`,
131
- stakeRemainingSlots: quest.stakeRemainingSlots,
132
- creditRewardPerWinner: `${quest.creditRewardPerWinner} NARA`,
133
- creditRewardSlots: `${quest.creditWinnerCount}/${quest.creditRewardCount}`,
134
- creditRemainingSlots: quest.creditRemainingSlots,
129
+ rewardPerWinner: `${quest.stakeRewardPerWinner} NARA`,
130
+ rewardSlots: `${quest.stakeWinnerCount}/${quest.stakeRewardCount}`,
131
+ remainingSlots: quest.stakeRemainingSlots,
135
132
  deadline: new Date(quest.deadline * 1000).toLocaleString(),
136
133
  timeRemaining: formatTimeRemaining(quest.timeRemaining),
137
134
  expired: quest.expired,
138
- stakeRequired,
139
- stakeRequirement: stakeRequired ? `${quest.effectiveStakeRequirement.toFixed(9).replace(/\.?0+$/, "")} NARA` : "0 NARA",
140
- stakeHigh: `${quest.stakeHigh} NARA`,
141
- stakeLow: `${quest.stakeLow} NARA`,
142
- avgParticipantStake: `${quest.avgParticipantStake} NARA`,
143
- freeCredits,
135
+ boostCredits,
144
136
  };
145
137
 
146
138
  if (options.json) {
@@ -152,19 +144,9 @@ async function handleQuestGet(options: GlobalOptions & { verbose?: boolean }) {
152
144
  console.log(` Difficulty: ${quest.difficulty}`);
153
145
  console.log(` Total reward: ${quest.totalReward} NARA`);
154
146
  console.log(
155
- ` Stake reward: ${quest.stakeRewardPerWinner} NARA/winner, ${quest.stakeWinnerCount}/${quest.stakeRewardCount} (${quest.stakeRemainingSlots} remaining)`
156
- );
157
- console.log(
158
- ` Boost reward: ${quest.creditRewardPerWinner} NARA/winner, ${quest.creditWinnerCount}/${quest.creditRewardCount} (${quest.creditRemainingSlots} remaining)`
147
+ ` Boost PoMI reward: ${quest.stakeRewardPerWinner} NARA/winner, ${quest.stakeWinnerCount}/${quest.stakeRewardCount} (${quest.stakeRemainingSlots} remaining)`
159
148
  );
160
- if (stakeRequired) {
161
- console.log(` Stake requirement: ${quest.effectiveStakeRequirement.toFixed(9).replace(/\.?0+$/, "")} NARA (decays ${quest.stakeHigh} → ${quest.stakeLow})`);
162
- } else if (options.verbose) {
163
- console.log(` Stake requirement: none (${quest.effectiveStakeRequirement.toFixed(9).replace(/\.?0+$/, "")} NARA, decays ${quest.stakeHigh} → ${quest.stakeLow})`);
164
- } else {
165
- console.log(` Stake requirement: none`);
166
- }
167
- console.log(` Boost credits: ${freeCredits}`);
149
+ console.log(` Boost credits: ${boostCredits}${boostCredits === 0 ? " (required to answer — acquire credits first)" : ""}`);
168
150
  console.log(` Deadline: ${new Date(quest.deadline * 1000).toLocaleString()}`);
169
151
  if (quest.timeRemaining > 0) {
170
152
  console.log(` Time remaining: ${formatTimeRemaining(quest.timeRemaining)}`);
@@ -206,7 +188,6 @@ async function handleQuestConfig(options: GlobalOptions) {
206
188
  stakeBpsLow,
207
189
  decayMs: config.decayMs,
208
190
  minQuestInterval: config.minQuestInterval,
209
- freeStakeMultiplier: config.freeStakeMultiplier,
210
191
  };
211
192
 
212
193
  if (options.json) {
@@ -221,7 +202,6 @@ async function handleQuestConfig(options: GlobalOptions) {
221
202
  console.log(` Stake BPS Low: ${stakeBpsLow / 100}% (${stakeBpsLow} BPS)`);
222
203
  console.log(` Decay (ms): ${data.decayMs}`);
223
204
  console.log(` Min Quest Interval: ${data.minQuestInterval}s`);
224
- console.log(` Free Stake Multiplier: ${data.freeStakeMultiplier}x`);
225
205
  console.log("");
226
206
  }
227
207
  }
@@ -229,7 +209,7 @@ async function handleQuestConfig(options: GlobalOptions) {
229
209
  // ─── Command: quest answer ───────────────────────────────────────
230
210
  async function handleQuestAnswer(
231
211
  answer: string,
232
- options: GlobalOptions & { relay?: string; agent?: string; model?: string; referral?: string; stake?: string }
212
+ options: GlobalOptions & { relay?: string; agent?: string; model?: string; referral?: string }
233
213
  ) {
234
214
  const rpcUrl = getRpcUrl(options.rpcUrl);
235
215
  const connection = new Connection(rpcUrl, "confirmed");
@@ -315,8 +295,7 @@ async function handleQuestAnswer(
315
295
  if (configAgentId) {
316
296
  activityLog = { agentId: configAgentId, activity: "PoMI", model, log: "", referralAgentId: referral };
317
297
  }
318
- const stakeOpt = options.stake === "auto" ? "auto" : options.stake ? parseFloat(options.stake) : undefined;
319
- const result = await submitAnswer(connection, wallet, proof.solana, agent, model, stakeOpt !== undefined ? { stake: stakeOpt } : undefined, activityLog);
298
+ const result = await submitAnswer(connection, wallet, proof.solana, agent, model, undefined, activityLog);
320
299
  printSuccess("Answer submitted!");
321
300
  console.log(` Transaction: ${result.signature}`);
322
301
  await handleReward(connection, result.signature, options);
@@ -338,12 +317,13 @@ async function handleQuestStake(amount: string, options: GlobalOptions) {
338
317
  process.exit(1);
339
318
  }
340
319
 
320
+ if (!options.json) printWarning(STAKE_DEPRECATION_NOTICE);
341
321
  if (!options.json) printInfo(`Staking ${n} NARA...`);
342
322
  const signature = await questStake(connection, wallet, n);
343
323
  if (!options.json) printSuccess(`Staked ${n} NARA!`);
344
324
 
345
325
  if (options.json) {
346
- formatOutput({ amount: n, signature }, true);
326
+ formatOutput({ amount: n, signature, notice: STAKE_DEPRECATION_NOTICE }, true);
347
327
  } else {
348
328
  console.log(` Transaction: ${signature}`);
349
329
  }
@@ -361,12 +341,13 @@ async function handleQuestUnstake(amount: string, options: GlobalOptions) {
361
341
  process.exit(1);
362
342
  }
363
343
 
344
+ if (!options.json) printWarning(STAKE_DEPRECATION_NOTICE);
364
345
  if (!options.json) printInfo(`Unstaking ${n} NARA...`);
365
346
  const signature = await questUnstake(connection, wallet, n);
366
347
  if (!options.json) printSuccess(`Unstaked ${n} NARA!`);
367
348
 
368
349
  if (options.json) {
369
- formatOutput({ amount: n, signature }, true);
350
+ formatOutput({ amount: n, signature, notice: STAKE_DEPRECATION_NOTICE }, true);
370
351
  } else {
371
352
  console.log(` Transaction: ${signature}`);
372
353
  }
@@ -389,12 +370,20 @@ async function handleQuestStakeInfo(options: GlobalOptions) {
389
370
  }
390
371
 
391
372
  if (options.json) {
392
- formatOutput({ staked: true, amount: stakeInfo.amount, stakeRound: stakeInfo.stakeRound }, true);
373
+ formatOutput({
374
+ staked: true,
375
+ amount: stakeInfo.amount,
376
+ stakeRound: stakeInfo.stakeRound,
377
+ boostCredits: stakeInfo.boostCredits,
378
+ notice: stakeInfo.amount > 0 ? STAKE_DEPRECATION_NOTICE : undefined,
379
+ }, true);
393
380
  } else {
394
381
  console.log("");
395
382
  console.log(` Staked: ${stakeInfo.amount} NARA`);
396
383
  console.log(` Stake round: ${stakeInfo.stakeRound}`);
384
+ console.log(` Boost credits: ${stakeInfo.boostCredits}`);
397
385
  console.log("");
386
+ if (stakeInfo.amount > 0) printWarning(STAKE_DEPRECATION_NOTICE);
398
387
  }
399
388
  }
400
389
 
@@ -464,6 +453,9 @@ function handleSubmitError(err: any) {
464
453
  case "insufficientStakeBalance":
465
454
  printError("Unstake amount exceeds staked balance");
466
455
  break;
456
+ case "noCredits":
457
+ printError("Boost PoMI requires boost credits. Acquire credits before submitting an answer.");
458
+ break;
467
459
  default:
468
460
  printError(`Failed to submit answer: ${err.message ?? String(err)}`);
469
461
  if (err.logs) {
@@ -483,12 +475,11 @@ export function registerQuestCommands(program: Command): void {
483
475
  // quest get
484
476
  quest
485
477
  .command("get")
486
- .description("Get current quest info (question, deadline, difficulty, stake requirement)")
487
- .option("-v, --verbose", "Show stake details even when not required")
488
- .action(async (opts: any, cmd: Command) => {
478
+ .description("Get current quest info (question, deadline, difficulty, boost credits)")
479
+ .action(async (_opts: any, cmd: Command) => {
489
480
  try {
490
481
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
491
- await handleQuestGet({ ...globalOpts, verbose: opts.verbose });
482
+ await handleQuestGet(globalOpts);
492
483
  } catch (error: any) {
493
484
  printError(error.message);
494
485
  process.exit(1);
@@ -498,18 +489,16 @@ export function registerQuestCommands(program: Command): void {
498
489
  // quest answer
499
490
  quest
500
491
  .command("answer <answer>")
501
- .description("Submit a quest answer with ZK proof. Generates a proof locally and submits on-chain. Use --relay when balance is 0 (gasless). Always pass --agent and --model for reward tracking.")
492
+ .description("Submit a quest answer with ZK proof. Requires boost credits. Use --relay when balance is 0 (gasless). Always pass --agent and --model for reward tracking.")
502
493
  .option("--relay [url]", `Submit via gasless relay (default: ${DEFAULT_QUEST_RELAY_URL}, backup: https://quest2-api.nara.build/)`)
503
494
  .option("--agent <name>", "Agent/platform type: claude-code, cursor, chatgpt, openclaw, etc. (default: naracli)")
504
495
  .option("--model <name>", "AI model used: claude-opus-4-6, claude-sonnet-4-6, gpt-4o, etc.")
505
496
  .option("--referral <agent-id>", "Referral agent ID for earning referral points")
506
- .option("--stake [amount]", 'Stake NARA in the same tx ("auto" to top-up to requirement, or an exact amount)')
507
497
  .action(async (answer: string, opts: any, cmd: Command) => {
508
498
  try {
509
499
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
510
500
  const relayUrl = opts.relay === true ? DEFAULT_QUEST_RELAY_URL : opts.relay;
511
- const stakeVal = opts.stake === true ? "auto" : opts.stake;
512
- await handleQuestAnswer(answer, { ...globalOpts, relay: relayUrl, agent: opts.agent, model: opts.model, referral: opts.referral, stake: stakeVal });
501
+ await handleQuestAnswer(answer, { ...globalOpts, relay: relayUrl, agent: opts.agent, model: opts.model, referral: opts.referral });
513
502
  } catch (error: any) {
514
503
  printError(error.message);
515
504
  process.exit(1);
@@ -519,7 +508,7 @@ export function registerQuestCommands(program: Command): void {
519
508
  // quest config
520
509
  quest
521
510
  .command("config")
522
- .description("Show quest program config (reward counts, stake params, decay, intervals)")
511
+ .description("Show quest program config (reward counts, decay, intervals)")
523
512
  .action(async (_opts: any, cmd: Command) => {
524
513
  try {
525
514
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
@@ -561,7 +550,7 @@ export function registerQuestCommands(program: Command): void {
561
550
  // quest stake-info
562
551
  quest
563
552
  .command("stake-info")
564
- .description("Get your current quest stake info")
553
+ .description("Get your current quest stake info (stake balance + boost credits)")
565
554
  .action(async (_opts: any, cmd: Command) => {
566
555
  try {
567
556
  const globalOpts = cmd.optsWithGlobals() as GlobalOptions;