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 +2 -0
- package/package.json +1 -1
- package/src/quest.ts +5 -4
- package/src/skills.ts +40 -11
- package/src/zkid.ts +69 -4
package/index.ts
CHANGED
package/package.json
CHANGED
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
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 =
|
|
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 =
|
|
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
|
+
}
|