nara-sdk 1.0.21 → 1.0.25

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/index.ts CHANGED
@@ -56,7 +56,9 @@ export {
56
56
  scanClaimableDeposits,
57
57
  withdraw,
58
58
  transferZkId,
59
+ transferZkIdByCommitment,
59
60
  deriveIdSecret,
61
+ computeIdCommitment,
60
62
  isValidRecipient,
61
63
  generateValidRecipient,
62
64
  ZKID_DENOMINATIONS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nara-sdk",
3
- "version": "1.0.21",
3
+ "version": "1.0.25",
4
4
  "description": "SDK for the Nara chain (Solana-compatible)",
5
5
  "module": "index.ts",
6
6
  "main": "index.ts",
package/src/quest.ts CHANGED
@@ -13,8 +13,7 @@ import { Program, AnchorProvider, Wallet } from "@coral-xyz/anchor";
13
13
  import type { NaraQuest } from "./idls/nara_quest_types";
14
14
  import { DEFAULT_QUEST_PROGRAM_ID } from "./constants";
15
15
 
16
- import { createRequire } from "module";
17
- const _require = createRequire(import.meta.url);
16
+ import naraQuestIdl from "./idls/nara_quest.json";
18
17
 
19
18
  // ─── ZK constants ────────────────────────────────────────────────
20
19
  const BN254_FIELD =
@@ -23,7 +22,9 @@ const BN254_FIELD =
23
22
  import { fileURLToPath } from "url";
24
23
  import { dirname, join, resolve } from "path";
25
24
  import { existsSync } from "fs";
26
- const __dirname = dirname(fileURLToPath(import.meta.url));
25
+ const __dirname: string = import.meta.url
26
+ ? dirname(fileURLToPath(import.meta.url))
27
+ : eval("__dirname") as string;
27
28
 
28
29
  function findZkFile(name: string): string {
29
30
  const srcPath = join(__dirname, "zk", name);
@@ -162,7 +163,7 @@ function createProgram(
162
163
  wallet: Keypair,
163
164
  programId?: string
164
165
  ): Program<NaraQuest> {
165
- const idl = _require("./idls/nara_quest.json");
166
+ const idl = naraQuestIdl;
166
167
  const pid = programId ?? DEFAULT_QUEST_PROGRAM_ID;
167
168
  const idlWithPid = { ...idl, address: pid };
168
169
  const provider = new AnchorProvider(
package/src/skills.ts CHANGED
@@ -13,8 +13,7 @@ import { Program, AnchorProvider, Wallet } from "@coral-xyz/anchor";
13
13
  import type { NaraSkillsHub } from "./idls/nara_skills_hub";
14
14
  import { DEFAULT_SKILLS_PROGRAM_ID } from "./constants";
15
15
 
16
- import { createRequire } from "module";
17
- const _require = createRequire(import.meta.url);
16
+ import naraSkillsIdl from "./idls/nara_skills_hub.json";
18
17
 
19
18
  // ─── Constants ───────────────────────────────────────────────────
20
19
 
@@ -27,6 +26,42 @@ const BUFFER_HEADER_SIZE = 80;
27
26
  /** SkillContent account header size: 8 discriminator + 32 skill */
28
27
  const CONTENT_HEADER_SIZE = 40;
29
28
 
29
+ // ─── Helpers ─────────────────────────────────────────────────────
30
+
31
+ /** Send a transaction and poll until confirmed, without using WebSocket. */
32
+ async function sendAndConfirmTx(
33
+ connection: Connection,
34
+ tx: anchor.web3.Transaction,
35
+ signers: anchor.web3.Signer[]
36
+ ): Promise<string> {
37
+ const { blockhash, lastValidBlockHeight } =
38
+ await connection.getLatestBlockhash("confirmed");
39
+ tx.recentBlockhash = blockhash;
40
+ tx.feePayer = signers[0]!.publicKey;
41
+ tx.sign(...signers);
42
+
43
+ const rawTx = tx.serialize();
44
+ const sig = await connection.sendRawTransaction(rawTx, {
45
+ skipPreflight: false,
46
+ });
47
+
48
+ while (true) {
49
+ const { value } = await connection.getSignatureStatuses([sig]);
50
+ const status = value[0];
51
+ if (status) {
52
+ if (status.err) throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
53
+ if (status.confirmationStatus === "confirmed" || status.confirmationStatus === "finalized") {
54
+ return sig;
55
+ }
56
+ }
57
+ const currentHeight = await connection.getBlockHeight("confirmed");
58
+ if (currentHeight > lastValidBlockHeight) {
59
+ throw new Error(`Transaction expired (blockhash no longer valid): ${sig}`);
60
+ }
61
+ await new Promise((r) => setTimeout(r, 1000));
62
+ }
63
+ }
64
+
30
65
  // ─── Types ───────────────────────────────────────────────────────
31
66
 
32
67
  export interface SkillRecord {
@@ -68,7 +103,7 @@ function createProgram(
68
103
  wallet: Keypair,
69
104
  programId?: string
70
105
  ): Program<NaraSkillsHub> {
71
- const idl = _require("./idls/nara_skills_hub.json");
106
+ const idl = naraSkillsIdl;
72
107
  const pid = programId ?? DEFAULT_SKILLS_PROGRAM_ID;
73
108
  const idlWithPid = { ...idl, address: pid };
74
109
  const provider = new AnchorProvider(connection, new Wallet(wallet), {
@@ -378,10 +413,7 @@ export async function uploadSkillContent(
378
413
  programId: program.programId,
379
414
  })
380
415
  );
381
- await anchor.web3.sendAndConfirmTransaction(connection, createBufferTx, [
382
- wallet,
383
- bufferKeypair,
384
- ]);
416
+ await sendAndConfirmTx(connection, createBufferTx, [wallet, bufferKeypair]);
385
417
 
386
418
  // ── Step 2: init_buffer ───────────────────────────────────────
387
419
  await program.methods
@@ -426,10 +458,7 @@ export async function uploadSkillContent(
426
458
  programId: program.programId,
427
459
  })
428
460
  );
429
- await anchor.web3.sendAndConfirmTransaction(connection, createContentTx, [
430
- wallet,
431
- contentKeypair,
432
- ]);
461
+ await sendAndConfirmTx(connection, createContentTx, [wallet, contentKeypair]);
433
462
 
434
463
  // ── Step 5: finalize ──────────────────────────────────────────
435
464
  if (isUpdate) {
package/src/zkid.ts CHANGED
@@ -18,10 +18,17 @@ import nacl from "tweetnacl";
18
18
  import BN from "bn.js";
19
19
  import type { NaraZk } from "./idls/nara_zk";
20
20
  import { DEFAULT_ZKID_PROGRAM_ID } from "./constants";
21
+ import naraZkIdl from "./idls/nara_zk.json";
21
22
  import { createRequire } from "module";
22
23
 
23
- const _require = createRequire(import.meta.url);
24
- const __dirname = dirname(fileURLToPath(import.meta.url));
24
+ // _require is used only for snarkjs (external, loaded at runtime).
25
+ // Supports both ESM (tsx dev) and esbuild CJS bundle (import.meta.url is "" — falsy)
26
+ const _require: NodeRequire = import.meta.url
27
+ ? createRequire(import.meta.url)
28
+ : eval("require") as NodeRequire;
29
+ const __dirname: string = import.meta.url
30
+ ? dirname(fileURLToPath(import.meta.url))
31
+ : eval("__dirname") as string;
25
32
 
26
33
  // ─── Constants ────────────────────────────────────────────────────────────────
27
34
 
@@ -174,7 +181,7 @@ function createProgram(
174
181
  wallet: Keypair,
175
182
  programId?: string
176
183
  ): Program<NaraZk> {
177
- const idl = _require("./idls/nara_zk.json");
184
+ const idl = naraZkIdl;
178
185
  const pid = programId ?? DEFAULT_ZKID_PROGRAM_ID;
179
186
  const idlWithPid = { ...idl, address: pid };
180
187
  const provider = new AnchorProvider(connection, new Wallet(wallet), {
@@ -188,7 +195,7 @@ function createReadProgram(
188
195
  connection: Connection,
189
196
  programId?: string
190
197
  ): Program<NaraZk> {
191
- const idl = _require("./idls/nara_zk.json");
198
+ const idl = naraZkIdl;
192
199
  const pid = programId ?? DEFAULT_ZKID_PROGRAM_ID;
193
200
  const idlWithPid = { ...idl, address: pid };
194
201
  const provider = new AnchorProvider(
@@ -549,3 +556,61 @@ export async function transferZkId(
549
556
  .accounts({ payer: payer.publicKey } as any)
550
557
  .rpc();
551
558
  }
559
+
560
+ /**
561
+ * Derive the idCommitment (public) from a Keypair + name.
562
+ *
563
+ * The new owner can share this hex string without revealing their idSecret.
564
+ * Returns a 64-char hex string (32 bytes, big-endian).
565
+ */
566
+ export async function computeIdCommitment(keypair: Keypair, name: string): Promise<string> {
567
+ const idSecret = await deriveIdSecret(keypair, name);
568
+ const commitment = await poseidonHash([idSecret]);
569
+ return bigIntToBytes32BE(commitment).toString("hex");
570
+ }
571
+
572
+ /**
573
+ * Transfer ZK ID ownership using the new owner's idCommitment directly.
574
+ *
575
+ * Unlike transferZkId (which takes newIdSecret), this function accepts the
576
+ * commitment hex produced by computeIdCommitment(), so the new owner never
577
+ * needs to share their secret.
578
+ *
579
+ * @param currentIdSecret - Current owner's idSecret (from deriveIdSecret)
580
+ * @param newIdCommitment - New owner's commitment as bigint (parse from hex)
581
+ */
582
+ export async function transferZkIdByCommitment(
583
+ connection: Connection,
584
+ payer: Keypair,
585
+ name: string,
586
+ currentIdSecret: bigint,
587
+ newIdCommitment: bigint,
588
+ options?: ZkIdOptions
589
+ ): Promise<string> {
590
+ const program = createProgram(connection, payer, options?.programId);
591
+ const nameHashBuf = computeNameHash(name);
592
+
593
+ const [zkIdPda] = findZkIdPda(nameHashBuf, new PublicKey(options?.programId ?? DEFAULT_ZKID_PROGRAM_ID));
594
+ const zkId = await program.account.zkIdAccount.fetch(zkIdPda);
595
+ const currentCommitmentField = bytes32ToBigInt(
596
+ Buffer.from(zkId.idCommitment as number[])
597
+ );
598
+
599
+ const newCommitmentBuf = bigIntToBytes32BE(newIdCommitment);
600
+
601
+ const input = {
602
+ idSecret: currentIdSecret.toString(),
603
+ idCommitment: currentCommitmentField.toString(),
604
+ };
605
+ const { proof } = await silentProve(input, OWNERSHIP_WASM, OWNERSHIP_ZKEY);
606
+ const packedProof = packProof(proof);
607
+
608
+ return await program.methods
609
+ .transferZkId(
610
+ toBytes32(nameHashBuf),
611
+ toBytes32(newCommitmentBuf),
612
+ packedProof
613
+ )
614
+ .accounts({ payer: payer.publicKey } as any)
615
+ .rpc();
616
+ }