naracli 1.0.32 → 1.0.40
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/README.md +9 -6
- package/dist/nara-cli-bundle.cjs +92393 -88258
- package/dist/zk/answer_proof.wasm +0 -0
- package/dist/zk/answer_proof_final.zkey +0 -0
- package/package.json +8 -4
- package/src/cli/commands/agent.ts +46 -8
- package/src/cli/commands/config.ts +31 -13
- package/src/cli/commands/quest.ts +151 -15
- package/src/cli/commands/zkid.ts +5 -5
- package/src/cli/utils/agent-config.ts +127 -22
- package/src/cli/utils/wallet.ts +9 -7
- package/src/tests/agent.test.ts +193 -0
- package/src/tests/config.test.ts +199 -0
- package/src/tests/helpers.ts +20 -0
- package/src/tests/quest-referral.test.ts +15 -22
- package/src/tests/quest.test.ts +1 -1
- package/src/tests/validation.test.ts +80 -0
- package/src/tests/zkid.test.ts +6 -6
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naracli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.40",
|
|
4
4
|
"description": "CLI for the Nara chain (Solana-compatible)",
|
|
5
5
|
"module": "index.ts",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"cli": "node --require ./bin/env-loader.cjs --import tsx bin/nara-cli.ts",
|
|
14
14
|
"build": "node scripts/build.cjs",
|
|
15
|
-
"test": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/quest.test.ts src/tests/skills.test.ts src/tests/zkid.test.ts src/tests/agent-registry.test.ts",
|
|
15
|
+
"test": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/validation.test.ts src/tests/config.test.ts src/tests/agent.test.ts src/tests/quest.test.ts src/tests/skills.test.ts src/tests/zkid.test.ts src/tests/agent-registry.test.ts",
|
|
16
|
+
"test:validation": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/validation.test.ts",
|
|
17
|
+
"test:config": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/config.test.ts",
|
|
18
|
+
"test:agent-cli": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/agent.test.ts",
|
|
16
19
|
"test:quest": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/quest.test.ts",
|
|
17
20
|
"test:skills": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/skills.test.ts",
|
|
18
21
|
"test:zkid": "node --require ./bin/env-loader.cjs --import tsx --test --experimental-test-isolation=none --test-reporter spec src/tests/zkid.test.ts",
|
|
@@ -53,7 +56,8 @@
|
|
|
53
56
|
"bs58": "^6.0.0",
|
|
54
57
|
"commander": "^12.1.0",
|
|
55
58
|
"ed25519-hd-key": "^1.3.0",
|
|
56
|
-
"nara-sdk": "^1.0.
|
|
59
|
+
"nara-sdk": "^1.0.43",
|
|
57
60
|
"picocolors": "^1.1.1"
|
|
58
|
-
}
|
|
61
|
+
},
|
|
62
|
+
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
|
59
63
|
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import type { GlobalOptions } from "../types";
|
|
16
16
|
import {
|
|
17
17
|
registerAgent,
|
|
18
|
-
|
|
18
|
+
registerAgentWithReferral,
|
|
19
19
|
getAgentInfo,
|
|
20
20
|
getAgentMemory,
|
|
21
21
|
setBio,
|
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
transferAgentAuthority,
|
|
26
26
|
deleteAgent,
|
|
27
27
|
logActivity,
|
|
28
|
+
logActivityWithReferral,
|
|
29
|
+
setReferral as setReferralOnChain,
|
|
28
30
|
} from "nara-sdk";
|
|
29
31
|
import { readFileSync } from "node:fs";
|
|
30
32
|
import { addAgentId } from "../utils/agent-config";
|
|
@@ -32,22 +34,25 @@ import { validateName } from "../utils/validation";
|
|
|
32
34
|
|
|
33
35
|
// ─── Command handlers ────────────────────────────────────────────
|
|
34
36
|
|
|
35
|
-
async function handleAgentRegister(agentId: string, options: GlobalOptions) {
|
|
37
|
+
async function handleAgentRegister(agentId: string, options: GlobalOptions & { referral?: string }) {
|
|
36
38
|
validateName(agentId, "Agent ID");
|
|
37
39
|
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
38
40
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
39
41
|
const wallet = await loadWallet(options.wallet);
|
|
40
42
|
|
|
41
43
|
if (!options.json) printInfo(`Registering agent "${agentId}"...`);
|
|
42
|
-
const result =
|
|
44
|
+
const result = options.referral
|
|
45
|
+
? await registerAgentWithReferral(connection, wallet, agentId, options.referral)
|
|
46
|
+
: await registerAgent(connection, wallet, agentId);
|
|
43
47
|
if (!options.json) printSuccess(`Agent "${agentId}" registered!`);
|
|
44
|
-
addAgentId(agentId);
|
|
48
|
+
addAgentId(agentId, rpcUrl);
|
|
45
49
|
|
|
46
50
|
if (options.json) {
|
|
47
|
-
formatOutput({ agentId, signature: result.signature, agentPubkey: result.agentPubkey.toBase58() }, true);
|
|
51
|
+
formatOutput({ agentId, referral: options.referral ?? null, signature: result.signature, agentPubkey: result.agentPubkey.toBase58() }, true);
|
|
48
52
|
} else {
|
|
49
53
|
console.log(` Transaction: ${result.signature}`);
|
|
50
54
|
console.log(` Agent PDA: ${result.agentPubkey.toBase58()}`);
|
|
55
|
+
if (options.referral) console.log(` Referral: ${options.referral}`);
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
|
|
@@ -225,6 +230,22 @@ async function handleAgentDelete(agentId: string, options: GlobalOptions) {
|
|
|
225
230
|
}
|
|
226
231
|
}
|
|
227
232
|
|
|
233
|
+
async function handleAgentSetReferral(agentId: string, referralAgentId: string, options: GlobalOptions) {
|
|
234
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
235
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
236
|
+
const wallet = await loadWallet(options.wallet);
|
|
237
|
+
|
|
238
|
+
if (!options.json) printInfo(`Setting referral for "${agentId}" to "${referralAgentId}"...`);
|
|
239
|
+
const signature = await setReferralOnChain(connection, wallet, agentId, referralAgentId);
|
|
240
|
+
if (!options.json) printSuccess(`Referral set on-chain!`);
|
|
241
|
+
|
|
242
|
+
if (options.json) {
|
|
243
|
+
formatOutput({ agentId, referral: referralAgentId, signature }, true);
|
|
244
|
+
} else {
|
|
245
|
+
console.log(` Transaction: ${signature}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
228
249
|
async function handleAgentLog(
|
|
229
250
|
agentId: string,
|
|
230
251
|
activity: string,
|
|
@@ -238,7 +259,9 @@ async function handleAgentLog(
|
|
|
238
259
|
const referral = options.referral;
|
|
239
260
|
|
|
240
261
|
if (!options.json) printInfo(`Logging activity for "${agentId}"...`);
|
|
241
|
-
const signature =
|
|
262
|
+
const signature = referral
|
|
263
|
+
? await logActivityWithReferral(connection, wallet, agentId, model, activity, log, referral)
|
|
264
|
+
: await logActivity(connection, wallet, agentId, model, activity, log);
|
|
242
265
|
if (!options.json) printSuccess("Activity logged!");
|
|
243
266
|
|
|
244
267
|
if (options.json) {
|
|
@@ -259,10 +282,11 @@ export function registerAgentCommands(program: Command): void {
|
|
|
259
282
|
agent
|
|
260
283
|
.command("register <agent-id>")
|
|
261
284
|
.description("Register a new agent on-chain")
|
|
262
|
-
.
|
|
285
|
+
.option("--referral <agent-id>", "Referral agent ID")
|
|
286
|
+
.action(async (agentId: string, opts: { referral?: string }, cmd: Command) => {
|
|
263
287
|
try {
|
|
264
288
|
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
265
|
-
await handleAgentRegister(agentId, globalOpts);
|
|
289
|
+
await handleAgentRegister(agentId, { ...globalOpts, ...opts });
|
|
266
290
|
} catch (error: any) {
|
|
267
291
|
printError(error.message);
|
|
268
292
|
process.exit(1);
|
|
@@ -381,6 +405,20 @@ export function registerAgentCommands(program: Command): void {
|
|
|
381
405
|
}
|
|
382
406
|
});
|
|
383
407
|
|
|
408
|
+
// agent set-referral
|
|
409
|
+
agent
|
|
410
|
+
.command("set-referral <agent-id> <referral-agent-id>")
|
|
411
|
+
.description("Set referral agent on-chain")
|
|
412
|
+
.action(async (agentId: string, referralAgentId: string, _opts: any, cmd: Command) => {
|
|
413
|
+
try {
|
|
414
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
415
|
+
await handleAgentSetReferral(agentId, referralAgentId, globalOpts);
|
|
416
|
+
} catch (error: any) {
|
|
417
|
+
printError(error.message);
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
384
422
|
// agent log
|
|
385
423
|
agent
|
|
386
424
|
.command("log <agent-id> <activity> <log>")
|
|
@@ -3,32 +3,50 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
loadGlobalConfig,
|
|
8
|
+
saveGlobalConfig,
|
|
9
|
+
loadNetworkConfig,
|
|
10
|
+
rpcUrlToNetworkName,
|
|
11
|
+
} from "../utils/agent-config";
|
|
12
|
+
import { getRpcUrl } from "../utils/wallet";
|
|
7
13
|
import { printError, printSuccess, formatOutput } from "../utils/output";
|
|
8
14
|
import { DEFAULT_RPC_URL } from "nara-sdk";
|
|
9
15
|
import type { GlobalOptions } from "../types";
|
|
10
16
|
|
|
11
17
|
function handleConfigGet(options: GlobalOptions) {
|
|
12
|
-
const
|
|
18
|
+
const globalConfig = loadGlobalConfig();
|
|
19
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
20
|
+
const networkConfig = loadNetworkConfig(rpcUrl);
|
|
21
|
+
const networkName = rpcUrlToNetworkName(rpcUrl);
|
|
22
|
+
|
|
13
23
|
const data = {
|
|
14
|
-
rpc_url:
|
|
15
|
-
wallet:
|
|
16
|
-
rpc_url_custom: !!
|
|
17
|
-
wallet_custom: !!
|
|
24
|
+
rpc_url: globalConfig.rpc_url ?? DEFAULT_RPC_URL,
|
|
25
|
+
wallet: globalConfig.wallet ?? "~/.config/nara/id.json",
|
|
26
|
+
rpc_url_custom: !!globalConfig.rpc_url,
|
|
27
|
+
wallet_custom: !!globalConfig.wallet,
|
|
28
|
+
network: networkName,
|
|
29
|
+
agent_ids: networkConfig.agent_ids,
|
|
30
|
+
zk_ids: networkConfig.zk_ids,
|
|
18
31
|
};
|
|
19
32
|
|
|
20
33
|
if (options.json) {
|
|
21
34
|
formatOutput(data, true);
|
|
22
35
|
} else {
|
|
23
36
|
console.log("");
|
|
24
|
-
console.log(` RPC URL:
|
|
25
|
-
console.log(` Wallet:
|
|
37
|
+
console.log(` RPC URL: ${data.rpc_url}${data.rpc_url_custom ? "" : " (default)"}`);
|
|
38
|
+
console.log(` Wallet: ${data.wallet}${data.wallet_custom ? "" : " (default)"}`);
|
|
39
|
+
console.log(` Network: ${networkName}`);
|
|
40
|
+
if (networkConfig.agent_ids.length > 0)
|
|
41
|
+
console.log(` Agents: ${networkConfig.agent_ids.join(", ")}`);
|
|
42
|
+
if (networkConfig.zk_ids.length > 0)
|
|
43
|
+
console.log(` ZK IDs: ${networkConfig.zk_ids.join(", ")}`);
|
|
26
44
|
console.log("");
|
|
27
45
|
}
|
|
28
46
|
}
|
|
29
47
|
|
|
30
48
|
function handleConfigSet(key: string, value: string, options: GlobalOptions) {
|
|
31
|
-
const config =
|
|
49
|
+
const config = loadGlobalConfig();
|
|
32
50
|
|
|
33
51
|
switch (key) {
|
|
34
52
|
case "rpc-url":
|
|
@@ -41,18 +59,18 @@ function handleConfigSet(key: string, value: string, options: GlobalOptions) {
|
|
|
41
59
|
throw new Error(`Unknown config key: "${key}". Valid keys: rpc-url, wallet`);
|
|
42
60
|
}
|
|
43
61
|
|
|
44
|
-
|
|
62
|
+
saveGlobalConfig(config);
|
|
45
63
|
if (!options.json) printSuccess(`Config "${key}" set to "${value}"`);
|
|
46
64
|
if (options.json) formatOutput({ key, value }, true);
|
|
47
65
|
}
|
|
48
66
|
|
|
49
67
|
function handleConfigReset(key: string | undefined, options: GlobalOptions) {
|
|
50
|
-
const config =
|
|
68
|
+
const config = loadGlobalConfig();
|
|
51
69
|
|
|
52
70
|
if (!key) {
|
|
53
71
|
delete config.rpc_url;
|
|
54
72
|
delete config.wallet;
|
|
55
|
-
|
|
73
|
+
saveGlobalConfig(config);
|
|
56
74
|
if (!options.json) printSuccess("All config reset to defaults");
|
|
57
75
|
} else {
|
|
58
76
|
switch (key) {
|
|
@@ -65,7 +83,7 @@ function handleConfigReset(key: string | undefined, options: GlobalOptions) {
|
|
|
65
83
|
default:
|
|
66
84
|
throw new Error(`Unknown config key: "${key}". Valid keys: rpc-url, wallet`);
|
|
67
85
|
}
|
|
68
|
-
|
|
86
|
+
saveGlobalConfig(config);
|
|
69
87
|
if (!options.json) printSuccess(`Config "${key}" reset to default`);
|
|
70
88
|
}
|
|
71
89
|
|
|
@@ -20,9 +20,13 @@ import {
|
|
|
20
20
|
submitAnswer,
|
|
21
21
|
submitAnswerViaRelay,
|
|
22
22
|
parseQuestReward,
|
|
23
|
+
stake as questStake,
|
|
24
|
+
unstake as questUnstake,
|
|
25
|
+
getStakeInfo,
|
|
23
26
|
type ActivityLog,
|
|
27
|
+
type StakeInfo,
|
|
24
28
|
} from "nara-sdk";
|
|
25
|
-
import {
|
|
29
|
+
import { loadNetworkConfig } from "../utils/agent-config";
|
|
26
30
|
|
|
27
31
|
const DEFAULT_QUEST_RELAY_URL = process.env.QUEST_RELAY_URL || "https://quest-api.nara.build/";
|
|
28
32
|
|
|
@@ -34,9 +38,12 @@ const QUEST_ERRORS: Record<number, string> = {
|
|
|
34
38
|
6003: "invalidProof",
|
|
35
39
|
6004: "invalidDeadline",
|
|
36
40
|
6005: "insufficientReward",
|
|
37
|
-
6006: "
|
|
38
|
-
6007: "
|
|
39
|
-
6008: "
|
|
41
|
+
6006: "questionTooLong",
|
|
42
|
+
6007: "alreadyAnswered",
|
|
43
|
+
6008: "invalidMinRewardCount",
|
|
44
|
+
6009: "invalidMaxRewardCount",
|
|
45
|
+
6010: "unstakeNotReady",
|
|
46
|
+
6011: "insufficientStakeBalance",
|
|
40
47
|
};
|
|
41
48
|
|
|
42
49
|
function anchorErrorCode(err: any): string {
|
|
@@ -59,7 +66,7 @@ function formatTimeRemaining(seconds: number): string {
|
|
|
59
66
|
|
|
60
67
|
// ─── Command: quest get ──────────────────────────────────────────
|
|
61
68
|
async function handleQuestGet(options: GlobalOptions) {
|
|
62
|
-
const rpcUrl =
|
|
69
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
63
70
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
64
71
|
|
|
65
72
|
let wallet: Keypair;
|
|
@@ -85,9 +92,8 @@ async function handleQuestGet(options: GlobalOptions) {
|
|
|
85
92
|
return;
|
|
86
93
|
}
|
|
87
94
|
|
|
88
|
-
const data = {
|
|
95
|
+
const data: Record<string, any> = {
|
|
89
96
|
round: quest.round,
|
|
90
|
-
questionId: quest.questionId,
|
|
91
97
|
question: quest.question,
|
|
92
98
|
difficulty: quest.difficulty,
|
|
93
99
|
rewardPerWinner: `${quest.rewardPerWinner} NARA`,
|
|
@@ -97,6 +103,8 @@ async function handleQuestGet(options: GlobalOptions) {
|
|
|
97
103
|
deadline: new Date(quest.deadline * 1000).toLocaleString(),
|
|
98
104
|
timeRemaining: formatTimeRemaining(quest.timeRemaining),
|
|
99
105
|
expired: quest.expired,
|
|
106
|
+
stakeRequirement: `${quest.stakeRequirement} NARA`,
|
|
107
|
+
minWinnerStake: `${quest.minWinnerStake} NARA`,
|
|
100
108
|
};
|
|
101
109
|
|
|
102
110
|
if (options.json) {
|
|
@@ -111,6 +119,10 @@ async function handleQuestGet(options: GlobalOptions) {
|
|
|
111
119
|
console.log(
|
|
112
120
|
` Reward slots: ${quest.winnerCount}/${quest.rewardCount} (${quest.remainingSlots} remaining)`
|
|
113
121
|
);
|
|
122
|
+
if (quest.stakeRequirement > 0) {
|
|
123
|
+
console.log(` Stake requirement: ${quest.stakeRequirement} NARA`);
|
|
124
|
+
console.log(` Min winner stake: ${quest.minWinnerStake} NARA`);
|
|
125
|
+
}
|
|
114
126
|
console.log(` Deadline: ${new Date(quest.deadline * 1000).toLocaleString()}`);
|
|
115
127
|
if (quest.timeRemaining > 0) {
|
|
116
128
|
console.log(` Time remaining: ${formatTimeRemaining(quest.timeRemaining)}`);
|
|
@@ -124,15 +136,16 @@ async function handleQuestGet(options: GlobalOptions) {
|
|
|
124
136
|
// ─── Command: quest answer ───────────────────────────────────────
|
|
125
137
|
async function handleQuestAnswer(
|
|
126
138
|
answer: string,
|
|
127
|
-
options: GlobalOptions & { relay?: string; agent?: string; model?: string; referral?: string }
|
|
139
|
+
options: GlobalOptions & { relay?: string; agent?: string; model?: string; referral?: string; stake?: string }
|
|
128
140
|
) {
|
|
129
|
-
const rpcUrl =
|
|
141
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
130
142
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
131
143
|
const wallet = await loadWallet(options.wallet);
|
|
132
|
-
const
|
|
133
|
-
const configAgentId =
|
|
144
|
+
const networkConfig = loadNetworkConfig(rpcUrl);
|
|
145
|
+
const configAgentId = networkConfig.agent_ids[0];
|
|
134
146
|
const agent = options.agent ?? "naracli";
|
|
135
147
|
const model = options.model ?? "";
|
|
148
|
+
const referral = options.referral;
|
|
136
149
|
|
|
137
150
|
// 1. Fetch quest info
|
|
138
151
|
let quest;
|
|
@@ -165,7 +178,7 @@ async function handleQuestAnswer(
|
|
|
165
178
|
|
|
166
179
|
let proof;
|
|
167
180
|
try {
|
|
168
|
-
proof = await generateProof(answer, quest.answerHash, wallet.publicKey);
|
|
181
|
+
proof = await generateProof(answer, quest.answerHash, wallet.publicKey, quest.round);
|
|
169
182
|
} catch (err: any) {
|
|
170
183
|
if (err.message?.includes("Assert Failed")) {
|
|
171
184
|
printError("Wrong answer");
|
|
@@ -207,9 +220,10 @@ async function handleQuestAnswer(
|
|
|
207
220
|
try {
|
|
208
221
|
let activityLog: ActivityLog | undefined;
|
|
209
222
|
if (configAgentId) {
|
|
210
|
-
activityLog = { agentId: configAgentId, activity: "PoMI", model, log: "", referralAgentId:
|
|
223
|
+
activityLog = { agentId: configAgentId, activity: "PoMI", model, log: "", referralAgentId: referral };
|
|
211
224
|
}
|
|
212
|
-
const
|
|
225
|
+
const stakeOpt = options.stake === "auto" ? "auto" : options.stake ? parseFloat(options.stake) : undefined;
|
|
226
|
+
const result = await submitAnswer(connection, wallet, proof.solana, agent, model, stakeOpt !== undefined ? { stake: stakeOpt } : undefined, activityLog);
|
|
213
227
|
printSuccess("Answer submitted!");
|
|
214
228
|
console.log(` Transaction: ${result.signature}`);
|
|
215
229
|
await handleReward(connection, result.signature, options);
|
|
@@ -219,6 +233,78 @@ async function handleQuestAnswer(
|
|
|
219
233
|
}
|
|
220
234
|
}
|
|
221
235
|
|
|
236
|
+
// ─── Command: quest stake ────────────────────────────────────────
|
|
237
|
+
async function handleQuestStake(amount: string, options: GlobalOptions) {
|
|
238
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
239
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
240
|
+
const wallet = await loadWallet(options.wallet);
|
|
241
|
+
|
|
242
|
+
const n = parseFloat(amount);
|
|
243
|
+
if (isNaN(n) || n <= 0) {
|
|
244
|
+
printError("Amount must be a positive number");
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!options.json) printInfo(`Staking ${n} NARA...`);
|
|
249
|
+
const signature = await questStake(connection, wallet, n);
|
|
250
|
+
if (!options.json) printSuccess(`Staked ${n} NARA!`);
|
|
251
|
+
|
|
252
|
+
if (options.json) {
|
|
253
|
+
formatOutput({ amount: n, signature }, true);
|
|
254
|
+
} else {
|
|
255
|
+
console.log(` Transaction: ${signature}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ─── Command: quest unstake ─────────────────────────────────────
|
|
260
|
+
async function handleQuestUnstake(amount: string, options: GlobalOptions) {
|
|
261
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
262
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
263
|
+
const wallet = await loadWallet(options.wallet);
|
|
264
|
+
|
|
265
|
+
const n = parseFloat(amount);
|
|
266
|
+
if (isNaN(n) || n <= 0) {
|
|
267
|
+
printError("Amount must be a positive number");
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!options.json) printInfo(`Unstaking ${n} NARA...`);
|
|
272
|
+
const signature = await questUnstake(connection, wallet, n);
|
|
273
|
+
if (!options.json) printSuccess(`Unstaked ${n} NARA!`);
|
|
274
|
+
|
|
275
|
+
if (options.json) {
|
|
276
|
+
formatOutput({ amount: n, signature }, true);
|
|
277
|
+
} else {
|
|
278
|
+
console.log(` Transaction: ${signature}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ─── Command: quest stake-info ──────────────────────────────────
|
|
283
|
+
async function handleQuestStakeInfo(options: GlobalOptions) {
|
|
284
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
285
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
286
|
+
const wallet = await loadWallet(options.wallet);
|
|
287
|
+
|
|
288
|
+
const stakeInfo = await getStakeInfo(connection, wallet.publicKey);
|
|
289
|
+
if (!stakeInfo) {
|
|
290
|
+
if (options.json) {
|
|
291
|
+
formatOutput({ staked: false, amount: 0 }, true);
|
|
292
|
+
} else {
|
|
293
|
+
printInfo("No stake record found");
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (options.json) {
|
|
299
|
+
formatOutput({ staked: true, amount: stakeInfo.amount, stakeRound: stakeInfo.stakeRound }, true);
|
|
300
|
+
} else {
|
|
301
|
+
console.log("");
|
|
302
|
+
console.log(` Staked: ${stakeInfo.amount} NARA`);
|
|
303
|
+
console.log(` Stake round: ${stakeInfo.stakeRound}`);
|
|
304
|
+
console.log("");
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
222
308
|
// ─── Parse reward from transaction ───────────────────────────────
|
|
223
309
|
async function handleReward(
|
|
224
310
|
connection: Connection,
|
|
@@ -279,6 +365,12 @@ function handleSubmitError(err: any) {
|
|
|
279
365
|
case "poolNotActive":
|
|
280
366
|
printError("No active quest at the moment");
|
|
281
367
|
break;
|
|
368
|
+
case "unstakeNotReady":
|
|
369
|
+
printError("Cannot unstake until round advances or deadline passes");
|
|
370
|
+
break;
|
|
371
|
+
case "insufficientStakeBalance":
|
|
372
|
+
printError("Unstake amount exceeds staked balance");
|
|
373
|
+
break;
|
|
282
374
|
default:
|
|
283
375
|
printError(`Failed to submit answer: ${err.message ?? String(err)}`);
|
|
284
376
|
if (err.logs) {
|
|
@@ -317,11 +409,55 @@ export function registerQuestCommands(program: Command): void {
|
|
|
317
409
|
.option("--agent <name>", "Agent identifier (default: naracli)")
|
|
318
410
|
.option("--model <name>", "Model identifier")
|
|
319
411
|
.option("--referral <agent-id>", "Referral agent ID")
|
|
412
|
+
.option("--stake [amount]", 'Stake NARA before answering ("auto" to top-up to requirement, or a number)')
|
|
320
413
|
.action(async (answer: string, opts: any, cmd: Command) => {
|
|
321
414
|
try {
|
|
322
415
|
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
323
416
|
const relayUrl = opts.relay === true ? DEFAULT_QUEST_RELAY_URL : opts.relay;
|
|
324
|
-
|
|
417
|
+
const stakeVal = opts.stake === true ? "auto" : opts.stake;
|
|
418
|
+
await handleQuestAnswer(answer, { ...globalOpts, relay: relayUrl, agent: opts.agent, model: opts.model, referral: opts.referral, stake: stakeVal });
|
|
419
|
+
} catch (error: any) {
|
|
420
|
+
printError(error.message);
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// quest stake
|
|
426
|
+
quest
|
|
427
|
+
.command("stake <amount>")
|
|
428
|
+
.description("Stake NARA to participate in quests")
|
|
429
|
+
.action(async (amount: string, _opts: any, cmd: Command) => {
|
|
430
|
+
try {
|
|
431
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
432
|
+
await handleQuestStake(amount, globalOpts);
|
|
433
|
+
} catch (error: any) {
|
|
434
|
+
printError(error.message);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// quest unstake
|
|
440
|
+
quest
|
|
441
|
+
.command("unstake <amount>")
|
|
442
|
+
.description("Unstake NARA (available after round advances or deadline passes)")
|
|
443
|
+
.action(async (amount: string, _opts: any, cmd: Command) => {
|
|
444
|
+
try {
|
|
445
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
446
|
+
await handleQuestUnstake(amount, globalOpts);
|
|
447
|
+
} catch (error: any) {
|
|
448
|
+
printError(error.message);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// quest stake-info
|
|
454
|
+
quest
|
|
455
|
+
.command("stake-info")
|
|
456
|
+
.description("Get your current quest stake info")
|
|
457
|
+
.action(async (_opts: any, cmd: Command) => {
|
|
458
|
+
try {
|
|
459
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
460
|
+
await handleQuestStakeInfo(globalOpts);
|
|
325
461
|
} catch (error: any) {
|
|
326
462
|
printError(error.message);
|
|
327
463
|
process.exit(1);
|
package/src/cli/commands/zkid.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
ZKID_DENOMINATIONS,
|
|
28
28
|
} from "nara-sdk";
|
|
29
29
|
import BN from "bn.js";
|
|
30
|
-
import { addZkId,
|
|
30
|
+
import { addZkId, loadNetworkConfig } from "../utils/agent-config";
|
|
31
31
|
|
|
32
32
|
// ─── Denomination helpers ────────────────────────────────────────
|
|
33
33
|
|
|
@@ -73,7 +73,7 @@ async function handleZkIdCreate(name: string, options: GlobalOptions) {
|
|
|
73
73
|
if (!options.json) printInfo(`Registering ZK ID "${name}"...`);
|
|
74
74
|
const signature = await createZkId(connection, wallet, name, idSecret);
|
|
75
75
|
if (!options.json) printSuccess(`ZK ID "${name}" registered!`);
|
|
76
|
-
addZkId(name);
|
|
76
|
+
addZkId(name, rpcUrl);
|
|
77
77
|
|
|
78
78
|
if (options.json) {
|
|
79
79
|
formatOutput({ name, signature }, true);
|
|
@@ -149,12 +149,12 @@ async function handleZkIdScan(
|
|
|
149
149
|
if (name) {
|
|
150
150
|
names = [name];
|
|
151
151
|
} else {
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
152
|
+
const networkConfig = loadNetworkConfig(rpcUrl);
|
|
153
|
+
if (networkConfig.zk_ids.length === 0) {
|
|
154
154
|
printError("No ZK IDs in config. Provide a name or create a ZK ID first.");
|
|
155
155
|
process.exit(1);
|
|
156
156
|
}
|
|
157
|
-
names =
|
|
157
|
+
names = networkConfig.zk_ids;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
const allResults: Array<{ name: string; deposits: any[] }> = [];
|