naracli 1.0.17 → 1.0.22

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.
Files changed (41) hide show
  1. package/README.md +101 -114
  2. package/bin/nara-cli.ts +0 -20
  3. package/dist/nara-cli.mjs +49930 -2222
  4. package/index.ts +10 -58
  5. package/package.json +7 -6
  6. package/src/cli/commands/quest.ts +8 -7
  7. package/src/cli/commands/skills.ts +491 -0
  8. package/src/cli/commands/skillsInstall.ts +793 -0
  9. package/src/cli/commands/wallet.ts +13 -114
  10. package/src/cli/commands/zkid.ts +410 -0
  11. package/src/cli/index.ts +215 -9
  12. package/src/cli/prompts/searchMultiselect.ts +297 -0
  13. package/src/cli/types.ts +0 -138
  14. package/src/cli/utils/transaction.ts +1 -1
  15. package/src/cli/utils/validation.ts +0 -40
  16. package/src/cli/utils/wallet.ts +3 -1
  17. package/src/tests/helpers.ts +78 -0
  18. package/src/tests/skills.e2e.test.ts +126 -0
  19. package/src/tests/skills.test.ts +192 -0
  20. package/src/tests/test_skill.md +18 -0
  21. package/src/tests/zkid.e2e.test.ts +128 -0
  22. package/src/tests/zkid.test.ts +153 -0
  23. package/src/types/snarkjs.d.ts +4 -1
  24. package/dist/quest/nara_quest.json +0 -534
  25. package/dist/zk/answer_proof.wasm +0 -0
  26. package/dist/zk/answer_proof_final.zkey +0 -0
  27. package/src/cli/commands/config.ts +0 -125
  28. package/src/cli/commands/migrate.ts +0 -270
  29. package/src/cli/commands/pool.ts +0 -364
  30. package/src/cli/commands/swap.ts +0 -349
  31. package/src/cli/quest/nara_quest.json +0 -534
  32. package/src/cli/quest/nara_quest_types.ts +0 -540
  33. package/src/cli/zk/answer_proof.wasm +0 -0
  34. package/src/cli/zk/answer_proof_final.zkey +0 -0
  35. package/src/client.ts +0 -96
  36. package/src/config.ts +0 -132
  37. package/src/constants.ts +0 -35
  38. package/src/migrate.ts +0 -222
  39. package/src/pool.ts +0 -259
  40. package/src/quest.ts +0 -387
  41. package/src/swap.ts +0 -608
@@ -66,46 +66,6 @@ export function validateNonNegativeNumber(
66
66
  return num;
67
67
  }
68
68
 
69
- /**
70
- * Validate swap mode string
71
- * @param mode Mode string
72
- * @returns Validated mode string
73
- * @throws Error if invalid
74
- */
75
- export function validateSwapMode(mode: string): string {
76
- const validModes = ["exact-in", "partial-fill", "exact-out"];
77
- const normalized = mode.toLowerCase();
78
-
79
- if (!validModes.includes(normalized)) {
80
- throw new Error(
81
- `Invalid swap mode: ${mode}. Must be one of: ${validModes.join(", ")}`
82
- );
83
- }
84
-
85
- return normalized;
86
- }
87
-
88
- /**
89
- * Validate direction string
90
- * @param direction Direction string
91
- * @returns Validated direction string
92
- * @throws Error if invalid
93
- */
94
- export function validateDirection(direction: string): string {
95
- const validDirections = ["buy", "sell"];
96
- const normalized = direction.toLowerCase();
97
-
98
- if (!validDirections.includes(normalized)) {
99
- throw new Error(
100
- `Invalid direction: ${direction}. Must be one of: ${validDirections.join(
101
- ", "
102
- )}`
103
- );
104
- }
105
-
106
- return normalized;
107
- }
108
-
109
69
  /**
110
70
  * Validate required option
111
71
  * @param value Option value
@@ -5,7 +5,9 @@
5
5
  import { Keypair } from "@solana/web3.js";
6
6
  import { join } from "node:path";
7
7
  import { homedir } from "node:os";
8
- import { DEFAULT_RPC_URL, DEFAULT_WALLET_PATH as _DEFAULT_WALLET_PATH } from "../../constants";
8
+ import { DEFAULT_RPC_URL } from "nara-sdk";
9
+
10
+ const _DEFAULT_WALLET_PATH = process.env.WALLET_PATH || "~/.config/nara/id.json";
9
11
 
10
12
  /**
11
13
  * Resolve wallet path (expand ~ to home directory)
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Test helpers - run the CLI as a subprocess and capture output
3
+ */
4
+
5
+ import { spawn } from "node:child_process";
6
+ import { join, dirname } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { readFileSync, existsSync } from "node:fs";
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const CLI = join(__dirname, "../../bin/nara-cli.ts");
12
+ const ROOT = join(__dirname, "../../");
13
+ const ENV_FILE = join(ROOT, ".env");
14
+
15
+ /** Parse .env file into an object (simple key=value, skips comments) */
16
+ function loadEnvFile(path: string): Record<string, string> {
17
+ if (!existsSync(path)) return {};
18
+ const env: Record<string, string> = {};
19
+ for (const line of readFileSync(path, "utf8").split("\n")) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed || trimmed.startsWith("#")) continue;
22
+ const idx = trimmed.indexOf("=");
23
+ if (idx < 0) continue;
24
+ const key = trimmed.slice(0, idx).trim();
25
+ const val = trimmed.slice(idx + 1).trim();
26
+ if (key) env[key] = val;
27
+ }
28
+ return env;
29
+ }
30
+
31
+ const ENV = loadEnvFile(ENV_FILE);
32
+
33
+ export interface CliResult {
34
+ stdout: string;
35
+ stderr: string;
36
+ exitCode: number;
37
+ }
38
+
39
+ /**
40
+ * Run the CLI with given args, stream stdout/stderr to parent in real-time
41
+ * while also capturing them for assertions.
42
+ */
43
+ export function runCli(args: string[], extraEnv: Record<string, string> = {}): Promise<CliResult> {
44
+ return new Promise((resolve) => {
45
+ const child = spawn("npx", ["tsx", CLI, ...args], {
46
+ env: { ...process.env, ...ENV, ...extraEnv },
47
+ timeout: 120_000,
48
+ });
49
+
50
+ let stdout = "";
51
+ let stderr = "";
52
+
53
+ child.stdout.setEncoding("utf8");
54
+ child.stderr.setEncoding("utf8");
55
+
56
+ child.stdout.on("data", (chunk: string) => {
57
+ stdout += chunk;
58
+ process.stdout.write(chunk);
59
+ });
60
+
61
+ child.stderr.on("data", (chunk: string) => {
62
+ stderr += chunk;
63
+ process.stderr.write(chunk);
64
+ });
65
+
66
+ child.on("close", (code) => {
67
+ resolve({ stdout, stderr, exitCode: code ?? 1 });
68
+ });
69
+ });
70
+ }
71
+
72
+ /** Whether a wallet is configured (for write-command tests) */
73
+ export const hasWallet = !!(ENV.PRIVATE_KEY || process.env.PRIVATE_KEY);
74
+
75
+ /** Generate a unique test resource name using a timestamp */
76
+ export function uniqueName(prefix: string): string {
77
+ return `${prefix}-${Date.now().toString(36)}`;
78
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * End-to-end tests for `skills` commands (hits real testnet)
3
+ *
4
+ * Requires PRIVATE_KEY in .env and sufficient NARA balance.
5
+ * Run: npm run test:e2e
6
+ */
7
+
8
+ import { describe, it, before, after } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { writeFileSync, unlinkSync, mkdtempSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+ import { runCli, hasWallet, uniqueName } from "./helpers.js";
14
+
15
+ if (!hasWallet) {
16
+ console.log("Skipping skills e2e tests: PRIVATE_KEY not set");
17
+ process.exit(0);
18
+ }
19
+
20
+ // ─── Shared state across tests ────────────────────────────────────
21
+
22
+ const SKILL_NAME = uniqueName("e2e-skill");
23
+ const AUTHOR = "e2e-test-author";
24
+ let tmpDir: string;
25
+ let contentFile: string;
26
+
27
+ // ─── Setup ────────────────────────────────────────────────────────
28
+
29
+ before(() => {
30
+ tmpDir = mkdtempSync(join(tmpdir(), "naracli-test-"));
31
+ contentFile = join(tmpDir, "skill-content.md");
32
+ writeFileSync(contentFile, `# ${SKILL_NAME}\n\nThis is e2e test content.\n`);
33
+ console.log(`\nUsing skill name: ${SKILL_NAME}`);
34
+ });
35
+
36
+ // ─── Cleanup ──────────────────────────────────────────────────────
37
+
38
+ after(async () => {
39
+ try { unlinkSync(contentFile); } catch {}
40
+ // Delete on-chain skill (reclaim rent)
41
+ const { exitCode, stderr } = await runCli(["skills", "delete", SKILL_NAME]);
42
+ if (exitCode !== 0) {
43
+ console.warn(` Warning: failed to delete skill "${SKILL_NAME}": ${stderr}`);
44
+ } else {
45
+ console.log(` Cleaned up skill "${SKILL_NAME}"`);
46
+ }
47
+ });
48
+
49
+ // ─── Tests ────────────────────────────────────────────────────────
50
+
51
+ describe(`skills e2e (name=${SKILL_NAME})`, () => {
52
+ it("register skill on-chain", async () => {
53
+ const { stdout, stderr, exitCode } = await runCli([
54
+ "skills", "register", SKILL_NAME, AUTHOR,
55
+ ]);
56
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
57
+ assert.ok(stdout.includes("registered") || stdout.includes("Transaction"), `stdout: ${stdout}`);
58
+ });
59
+
60
+ it("get skill returns correct name and author", async () => {
61
+ const { stdout, exitCode, stderr } = await runCli([
62
+ "skills", "get", "--json", SKILL_NAME,
63
+ ]);
64
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
65
+ const json = JSON.parse(stdout);
66
+ assert.equal(json.name, SKILL_NAME);
67
+ assert.equal(json.author, AUTHOR);
68
+ assert.equal(json.version, 0);
69
+ assert.equal(json.description, null);
70
+ });
71
+
72
+ it("set-description updates description", async () => {
73
+ const desc = "An e2e test skill";
74
+ const { exitCode, stderr } = await runCli([
75
+ "skills", "set-description", SKILL_NAME, desc,
76
+ ]);
77
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
78
+
79
+ const { stdout } = await runCli(["skills", "get", "--json", SKILL_NAME]);
80
+ const json = JSON.parse(stdout);
81
+ assert.equal(json.description, desc);
82
+ });
83
+
84
+ it("set-metadata updates metadata", async () => {
85
+ const meta = JSON.stringify({ env: "e2e", version: "1.0.0" });
86
+ const { exitCode, stderr } = await runCli([
87
+ "skills", "set-metadata", SKILL_NAME, meta,
88
+ ]);
89
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
90
+
91
+ const { stdout } = await runCli(["skills", "get", "--json", SKILL_NAME]);
92
+ const json = JSON.parse(stdout);
93
+ assert.equal(json.metadata, meta);
94
+ });
95
+
96
+ it("upload content and read back matches original", async () => {
97
+ const { exitCode, stderr } = await runCli([
98
+ "skills", "upload", SKILL_NAME, contentFile,
99
+ ]);
100
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
101
+
102
+ // Read back via skills content
103
+ const { stdout: contentOut, exitCode: cExit } = await runCli([
104
+ "skills", "content", SKILL_NAME,
105
+ ]);
106
+ assert.equal(cExit, 0);
107
+ assert.ok(contentOut.includes(SKILL_NAME), `content mismatch: ${contentOut}`);
108
+ });
109
+
110
+ it("skills content --json includes size field", async () => {
111
+ const { stdout, exitCode } = await runCli([
112
+ "skills", "content", "--json", SKILL_NAME,
113
+ ]);
114
+ assert.equal(exitCode, 0);
115
+ const json = JSON.parse(stdout);
116
+ assert.ok(typeof json.size === "number" && json.size > 0);
117
+ assert.ok(typeof json.content === "string" && json.content.length > 0);
118
+ });
119
+
120
+ it("get skill shows updated version after upload", async () => {
121
+ const { stdout, exitCode } = await runCli(["skills", "get", "--json", SKILL_NAME]);
122
+ assert.equal(exitCode, 0);
123
+ const json = JSON.parse(stdout);
124
+ assert.ok(json.version >= 1, `expected version >= 1, got ${json.version}`);
125
+ });
126
+ });
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Tests for `skills` CLI commands
3
+ *
4
+ * Run: npm test
5
+ */
6
+
7
+ import { describe, it, after } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import { join, dirname } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { runCli, hasWallet, uniqueName } from "./helpers.js";
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const TEST_SKILL_FILE = join(__dirname, "test_skill.md");
15
+
16
+ // ─── Help output ──────────────────────────────────────────────────
17
+
18
+ describe("skills --help", () => {
19
+ it("shows all subcommands", async () => {
20
+ const { stdout, exitCode } = await runCli(["skills", "--help"]);
21
+ assert.equal(exitCode, 0);
22
+ for (const cmd of ["register", "get", "content", "set-description", "set-metadata", "upload", "transfer", "close-buffer", "delete"]) {
23
+ assert.ok(stdout.includes(cmd), `missing command: ${cmd}`);
24
+ }
25
+ });
26
+
27
+ it("skills register --help shows <name> and <author>", async () => {
28
+ const { stdout, exitCode } = await runCli(["skills", "register", "--help"]);
29
+ assert.equal(exitCode, 0);
30
+ assert.ok(stdout.includes("<name>"));
31
+ assert.ok(stdout.includes("<author>"));
32
+ });
33
+
34
+ it("skills upload --help shows <file>", async () => {
35
+ const { stdout, exitCode } = await runCli(["skills", "upload", "--help"]);
36
+ assert.equal(exitCode, 0);
37
+ assert.ok(stdout.includes("<file>"));
38
+ });
39
+
40
+ it("skills content --help shows --hex option", async () => {
41
+ const { stdout, exitCode } = await runCli(["skills", "content", "--help"]);
42
+ assert.equal(exitCode, 0);
43
+ assert.ok(stdout.includes("--hex"));
44
+ });
45
+ });
46
+
47
+ // ─── Argument validation (no chain needed) ────────────────────────
48
+
49
+ describe("skills argument errors", () => {
50
+ it("skills get with no name exits non-zero", async () => {
51
+ const { exitCode } = await runCli(["skills", "get"]);
52
+ assert.notEqual(exitCode, 0);
53
+ });
54
+
55
+ it("skills register with missing author exits non-zero", async () => {
56
+ const { exitCode } = await runCli(["skills", "register", "myskill"]);
57
+ assert.notEqual(exitCode, 0);
58
+ });
59
+
60
+ it("skills set-metadata rejects invalid JSON", async () => {
61
+ if (!hasWallet) return;
62
+ const { stderr, exitCode } = await runCli(["skills", "set-metadata", "anyskill", "not-valid-json"]);
63
+ assert.equal(exitCode, 1);
64
+ assert.ok(stderr.includes("Invalid JSON"), `stderr: ${stderr}`);
65
+ });
66
+
67
+ it("skills transfer rejects invalid public key", async () => {
68
+ if (!hasWallet) return;
69
+ const { stderr, exitCode } = await runCli(["skills", "transfer", "anyskill", "not-a-pubkey"]);
70
+ assert.equal(exitCode, 1);
71
+ assert.ok(stderr.includes("Invalid public key"), `stderr: ${stderr}`);
72
+ });
73
+
74
+ it("skills upload with non-existent file fails", async () => {
75
+ if (!hasWallet) return;
76
+ const { exitCode, stderr } = await runCli(["skills", "upload", "anyskill", "/tmp/__no_such_file__.bin"]);
77
+ assert.equal(exitCode, 1);
78
+ assert.ok(stderr.length > 0);
79
+ });
80
+ });
81
+
82
+ // ─── Read-only chain queries ──────────────────────────────────────
83
+
84
+ describe("skills read-only queries", () => {
85
+ it("skills get non-existent skill exits with error", async () => {
86
+ const { exitCode, stderr } = await runCli(["skills", "get", "definitely-does-not-exist-xyz123"]);
87
+ assert.equal(exitCode, 1);
88
+ assert.ok(stderr.length > 0);
89
+ });
90
+ });
91
+
92
+ // ─── Success cases (requires wallet) ─────────────────────────────
93
+
94
+ describe("skills success cases", () => {
95
+ const SKILL_NAME = uniqueName("unit-skill");
96
+ const AUTHOR = "unit-test-author";
97
+
98
+ after(async () => {
99
+ if (!hasWallet) return;
100
+ await runCli(["skills", "delete", SKILL_NAME]);
101
+ });
102
+
103
+ it("register a new skill on-chain", async () => {
104
+ if (!hasWallet) return;
105
+ const { stdout, stderr, exitCode } = await runCli([
106
+ "skills", "register", SKILL_NAME, AUTHOR,
107
+ ]);
108
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
109
+ assert.ok(stdout.includes("Transaction"), `stdout: ${stdout}`);
110
+ });
111
+
112
+ it("get --json returns correct name, author and initial state", async () => {
113
+ if (!hasWallet) return;
114
+ const { stdout, exitCode, stderr } = await runCli([
115
+ "skills", "get", "--json", SKILL_NAME,
116
+ ]);
117
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
118
+ const json = JSON.parse(stdout);
119
+ assert.equal(json.name, SKILL_NAME);
120
+ assert.equal(json.author, AUTHOR);
121
+ assert.equal(json.version, 0);
122
+ assert.equal(json.description, null);
123
+ assert.equal(json.metadata, null);
124
+ assert.ok(typeof json.authority === "string" && json.authority.length > 0);
125
+ assert.ok(typeof json.createdAt === "string");
126
+ });
127
+
128
+ it("set-description updates description on-chain", async () => {
129
+ if (!hasWallet) return;
130
+ const desc = "A unit-test skill description";
131
+ const { exitCode, stderr } = await runCli([
132
+ "skills", "set-description", SKILL_NAME, desc,
133
+ ]);
134
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
135
+
136
+ const { stdout } = await runCli(["skills", "get", "--json", SKILL_NAME]);
137
+ assert.equal(JSON.parse(stdout).description, desc);
138
+ });
139
+
140
+ it("set-metadata updates metadata on-chain", async () => {
141
+ if (!hasWallet) return;
142
+ const meta = JSON.stringify({ env: "unit-test", ok: true });
143
+ const { exitCode, stderr } = await runCli([
144
+ "skills", "set-metadata", SKILL_NAME, meta,
145
+ ]);
146
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
147
+
148
+ const { stdout } = await runCli(["skills", "get", "--json", SKILL_NAME]);
149
+ assert.equal(JSON.parse(stdout).metadata, meta);
150
+ });
151
+
152
+ it("upload test_skill.md uploads content on-chain", async () => {
153
+ if (!hasWallet) return;
154
+ const { stdout, stderr, exitCode } = await runCli([
155
+ "skills", "upload", SKILL_NAME, TEST_SKILL_FILE,
156
+ ]);
157
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
158
+ assert.ok(stdout.includes("Transaction") || stdout.includes("Finalize"), `stdout: ${stdout}`);
159
+ });
160
+
161
+ it("get --json shows version=1 and non-null updatedAt after upload", async () => {
162
+ if (!hasWallet) return;
163
+ const { stdout, exitCode, stderr } = await runCli([
164
+ "skills", "get", "--json", SKILL_NAME,
165
+ ]);
166
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
167
+ const json = JSON.parse(stdout);
168
+ assert.equal(json.version, 1);
169
+ assert.ok(json.updatedAt !== null, "updatedAt should be set after content upload");
170
+ });
171
+
172
+ it("content --json reads back uploaded file", async () => {
173
+ if (!hasWallet) return;
174
+ const { stdout, exitCode, stderr } = await runCli([
175
+ "skills", "content", "--json", SKILL_NAME,
176
+ ]);
177
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
178
+ const json = JSON.parse(stdout);
179
+ assert.ok(typeof json.size === "number" && json.size > 0);
180
+ assert.ok(typeof json.content === "string" && json.content.length > 0);
181
+ });
182
+
183
+ it("delete removes the skill from chain", async () => {
184
+ if (!hasWallet) return;
185
+ const { exitCode, stderr } = await runCli(["skills", "delete", SKILL_NAME]);
186
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
187
+
188
+ // Verify it's gone
189
+ const { exitCode: getExit } = await runCli(["skills", "get", SKILL_NAME]);
190
+ assert.equal(getExit, 1);
191
+ });
192
+ });
@@ -0,0 +1,18 @@
1
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
2
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
3
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
4
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
5
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
6
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
7
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
8
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
9
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
10
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
11
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
12
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
13
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
14
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
15
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
16
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
17
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
18
+ TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL TEST SKILL
@@ -0,0 +1,128 @@
1
+ /**
2
+ * End-to-end tests for `zkid` commands (hits real testnet)
3
+ *
4
+ * Requires PRIVATE_KEY in .env and sufficient NARA balance.
5
+ * Run: npm run test:e2e
6
+ */
7
+
8
+ import { describe, it } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { runCli, hasWallet, uniqueName } from "./helpers.js";
11
+
12
+ if (!hasWallet) {
13
+ console.log("Skipping zkid e2e tests: PRIVATE_KEY not set");
14
+ process.exit(0);
15
+ }
16
+
17
+ // ─── Shared state ─────────────────────────────────────────────────
18
+
19
+ const ZKID_NAME = uniqueName("e2e-zkid");
20
+ console.log(`\nUsing ZK ID name: ${ZKID_NAME}`);
21
+
22
+ // ─── Tests ────────────────────────────────────────────────────────
23
+
24
+ describe(`zkid e2e (name=${ZKID_NAME})`, () => {
25
+ // ── id-commitment (pure crypto, no chain) ──────────────────────
26
+
27
+ it("id-commitment outputs a deterministic 64-char hex", async () => {
28
+ const r1 = await runCli(["zkid", "id-commitment", "--json", ZKID_NAME]);
29
+ const r2 = await runCli(["zkid", "id-commitment", "--json", ZKID_NAME]);
30
+ assert.equal(r1.exitCode, 0, `stderr: ${r1.stderr}`);
31
+ assert.equal(r2.exitCode, 0);
32
+ const c1 = JSON.parse(r1.stdout);
33
+ const c2 = JSON.parse(r2.stdout);
34
+ assert.match(c1.idCommitment, /^[0-9a-f]{64}$/);
35
+ assert.equal(c1.idCommitment, c2.idCommitment, "commitment not deterministic");
36
+ });
37
+
38
+ // ── create ─────────────────────────────────────────────────────
39
+
40
+ it("create registers a new ZK ID on-chain", async () => {
41
+ const { stdout, stderr, exitCode } = await runCli([
42
+ "zkid", "create", ZKID_NAME,
43
+ ]);
44
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
45
+ assert.ok(stdout.includes("registered") || stdout.includes("Transaction"), `stdout: ${stdout}`);
46
+ });
47
+
48
+ it("create again exits 0 with warning (already exists)", async () => {
49
+ const { stdout, exitCode } = await runCli(["zkid", "create", ZKID_NAME]);
50
+ assert.equal(exitCode, 0);
51
+ assert.ok(stdout.includes("already exists"), `stdout: ${stdout}`);
52
+ });
53
+
54
+ // ── info ───────────────────────────────────────────────────────
55
+
56
+ it("info returns correct fields for existing ZK ID", async () => {
57
+ const { stdout, exitCode, stderr } = await runCli([
58
+ "zkid", "info", "--json", ZKID_NAME,
59
+ ]);
60
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
61
+ const json = JSON.parse(stdout);
62
+ assert.equal(json.name, ZKID_NAME);
63
+ assert.equal(json.depositCount, 0);
64
+ assert.equal(json.commitmentStartIndex, 0);
65
+ assert.match(json.idCommitment, /^[0-9a-f]{64}$/);
66
+ });
67
+
68
+ it("id-commitment matches the commitment stored on-chain", async () => {
69
+ const commitResult = await runCli(["zkid", "id-commitment", "--json", ZKID_NAME]);
70
+ const infoResult = await runCli(["zkid", "info", "--json", ZKID_NAME]);
71
+ assert.equal(commitResult.exitCode, 0);
72
+ assert.equal(infoResult.exitCode, 0);
73
+ const localCommitment = JSON.parse(commitResult.stdout).idCommitment;
74
+ const chainCommitment = JSON.parse(infoResult.stdout).idCommitment;
75
+ assert.equal(localCommitment, chainCommitment, "local commitment != on-chain commitment");
76
+ });
77
+
78
+ // ── deposit ────────────────────────────────────────────────────
79
+
80
+ it("deposit 1 NARA into the ZK ID", async () => {
81
+ const { stdout, stderr, exitCode } = await runCli([
82
+ "zkid", "deposit", ZKID_NAME, "1",
83
+ ]);
84
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
85
+ assert.ok(stdout.includes("Transaction"), `stdout: ${stdout}`);
86
+ });
87
+
88
+ it("info shows depositCount=1 after deposit", async () => {
89
+ const { stdout, exitCode } = await runCli(["zkid", "info", "--json", ZKID_NAME]);
90
+ assert.equal(exitCode, 0);
91
+ const json = JSON.parse(stdout);
92
+ assert.equal(json.depositCount, 1);
93
+ });
94
+
95
+ // ── scan ───────────────────────────────────────────────────────
96
+
97
+ it("scan finds 1 claimable deposit", async () => {
98
+ const { stdout, exitCode, stderr } = await runCli([
99
+ "zkid", "scan", "--json", ZKID_NAME,
100
+ ]);
101
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
102
+ const json = JSON.parse(stdout);
103
+ assert.equal(json.count, 1);
104
+ assert.equal(json.deposits[0].nara, 1);
105
+ });
106
+
107
+ // ── withdraw ───────────────────────────────────────────────────
108
+
109
+ it("withdraw sends funds to auto-generated recipient", async () => {
110
+ const { stdout, stderr, exitCode } = await runCli([
111
+ "zkid", "withdraw", "--json", ZKID_NAME,
112
+ ]);
113
+ assert.equal(exitCode, 0, `stderr: ${stderr}`);
114
+ const json = JSON.parse(stdout);
115
+ assert.ok(json.recipient, "no recipient in output");
116
+ assert.ok(json.signature, "no signature in output");
117
+ assert.equal(json.nara, 1);
118
+ });
119
+
120
+ it("scan shows 0 claimable deposits after withdrawal", async () => {
121
+ const { stdout, exitCode } = await runCli([
122
+ "zkid", "scan", "--json", ZKID_NAME,
123
+ ]);
124
+ assert.equal(exitCode, 0);
125
+ const json = JSON.parse(stdout);
126
+ assert.equal(json.count, 0, `expected 0, got ${json.count}`);
127
+ });
128
+ });