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.
- package/README.md +101 -114
- package/bin/nara-cli.ts +0 -20
- package/dist/nara-cli.mjs +49930 -2222
- package/index.ts +10 -58
- package/package.json +7 -6
- package/src/cli/commands/quest.ts +8 -7
- package/src/cli/commands/skills.ts +491 -0
- package/src/cli/commands/skillsInstall.ts +793 -0
- package/src/cli/commands/wallet.ts +13 -114
- package/src/cli/commands/zkid.ts +410 -0
- package/src/cli/index.ts +215 -9
- package/src/cli/prompts/searchMultiselect.ts +297 -0
- package/src/cli/types.ts +0 -138
- package/src/cli/utils/transaction.ts +1 -1
- package/src/cli/utils/validation.ts +0 -40
- package/src/cli/utils/wallet.ts +3 -1
- package/src/tests/helpers.ts +78 -0
- package/src/tests/skills.e2e.test.ts +126 -0
- package/src/tests/skills.test.ts +192 -0
- package/src/tests/test_skill.md +18 -0
- package/src/tests/zkid.e2e.test.ts +128 -0
- package/src/tests/zkid.test.ts +153 -0
- package/src/types/snarkjs.d.ts +4 -1
- package/dist/quest/nara_quest.json +0 -534
- package/dist/zk/answer_proof.wasm +0 -0
- package/dist/zk/answer_proof_final.zkey +0 -0
- package/src/cli/commands/config.ts +0 -125
- package/src/cli/commands/migrate.ts +0 -270
- package/src/cli/commands/pool.ts +0 -364
- package/src/cli/commands/swap.ts +0 -349
- package/src/cli/quest/nara_quest.json +0 -534
- package/src/cli/quest/nara_quest_types.ts +0 -540
- package/src/cli/zk/answer_proof.wasm +0 -0
- package/src/cli/zk/answer_proof_final.zkey +0 -0
- package/src/client.ts +0 -96
- package/src/config.ts +0 -132
- package/src/constants.ts +0 -35
- package/src/migrate.ts +0 -222
- package/src/pool.ts +0 -259
- package/src/quest.ts +0 -387
- package/src/swap.ts +0 -608
|
@@ -21,8 +21,9 @@ import { join } from "node:path";
|
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
import { mkdir, writeFile, access } from "node:fs/promises";
|
|
23
23
|
import bs58 from "bs58";
|
|
24
|
-
import { NaraSDK } from "
|
|
25
|
-
|
|
24
|
+
import { NaraSDK } from "nara-sdk";
|
|
25
|
+
|
|
26
|
+
const _DEFAULT_WALLET_PATH = process.env.WALLET_PATH || "~/.config/nara/id.json";
|
|
26
27
|
import { loadWallet, getRpcUrl } from "../utils/wallet";
|
|
27
28
|
import { validatePublicKey, validatePositiveNumber } from "../utils/validation";
|
|
28
29
|
import {
|
|
@@ -85,108 +86,6 @@ export function registerWalletCommands(program: Command): void {
|
|
|
85
86
|
}
|
|
86
87
|
});
|
|
87
88
|
|
|
88
|
-
// wallet address
|
|
89
|
-
wallet
|
|
90
|
-
.command("address")
|
|
91
|
-
.description("Show wallet address")
|
|
92
|
-
.action(async (options: GlobalOptions) => {
|
|
93
|
-
try {
|
|
94
|
-
await handleWalletAddress(options);
|
|
95
|
-
} catch (error: any) {
|
|
96
|
-
printError(error.message);
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// wallet balance
|
|
102
|
-
wallet
|
|
103
|
-
.command("balance")
|
|
104
|
-
.description("Check NSO balance")
|
|
105
|
-
.argument("[address]", "Wallet address (optional, defaults to current wallet)")
|
|
106
|
-
.action(async (address: string | undefined, options: WalletBalanceOptions) => {
|
|
107
|
-
try {
|
|
108
|
-
await handleWalletBalance(address, options);
|
|
109
|
-
} catch (error: any) {
|
|
110
|
-
printError(error.message);
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// wallet token-balance
|
|
116
|
-
wallet
|
|
117
|
-
.command("token-balance <token-address>")
|
|
118
|
-
.description("Check token balance")
|
|
119
|
-
.option("--owner <address>", "Owner address (optional, defaults to current wallet)")
|
|
120
|
-
.action(
|
|
121
|
-
async (
|
|
122
|
-
tokenAddress: string,
|
|
123
|
-
options: Omit<TokenBalanceOptions, "tokenAddress">
|
|
124
|
-
) => {
|
|
125
|
-
try {
|
|
126
|
-
await handleTokenBalance(tokenAddress, options);
|
|
127
|
-
} catch (error: any) {
|
|
128
|
-
printError(error.message);
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
// wallet tx-status
|
|
135
|
-
wallet
|
|
136
|
-
.command("tx-status <signature>")
|
|
137
|
-
.description("Check transaction status")
|
|
138
|
-
.action(
|
|
139
|
-
async (signature: string, options: Omit<TxStatusOptions, "signature">) => {
|
|
140
|
-
try {
|
|
141
|
-
await handleTxStatus(signature, options);
|
|
142
|
-
} catch (error: any) {
|
|
143
|
-
printError(error.message);
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
// wallet transfer
|
|
150
|
-
wallet
|
|
151
|
-
.command("transfer <to> <amount>")
|
|
152
|
-
.description("Transfer NSO to another wallet")
|
|
153
|
-
.option("-e, --export-tx", "Export unsigned transaction", false)
|
|
154
|
-
.action(
|
|
155
|
-
async (
|
|
156
|
-
to: string,
|
|
157
|
-
amount: string,
|
|
158
|
-
options: Omit<TransferSolOptions, "to" | "amount">
|
|
159
|
-
) => {
|
|
160
|
-
try {
|
|
161
|
-
await handleTransferSol(to, amount, options);
|
|
162
|
-
} catch (error: any) {
|
|
163
|
-
printError(error.message);
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
// wallet transfer-token
|
|
170
|
-
wallet
|
|
171
|
-
.command("transfer-token <token-address> <to> <amount>")
|
|
172
|
-
.description("Transfer tokens to another wallet")
|
|
173
|
-
.option("--decimals <number>", "Token decimals", "6")
|
|
174
|
-
.option("-e, --export-tx", "Export unsigned transaction", false)
|
|
175
|
-
.action(
|
|
176
|
-
async (
|
|
177
|
-
tokenAddress: string,
|
|
178
|
-
to: string,
|
|
179
|
-
amount: string,
|
|
180
|
-
options: Omit<TransferTokenOptions, "tokenAddress" | "to" | "amount">
|
|
181
|
-
) => {
|
|
182
|
-
try {
|
|
183
|
-
await handleTransferToken(tokenAddress, to, amount, options);
|
|
184
|
-
} catch (error: any) {
|
|
185
|
-
printError(error.message);
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
);
|
|
190
89
|
}
|
|
191
90
|
|
|
192
91
|
/**
|
|
@@ -194,7 +93,7 @@ export function registerWalletCommands(program: Command): void {
|
|
|
194
93
|
* @param address Wallet address
|
|
195
94
|
* @param options Command options
|
|
196
95
|
*/
|
|
197
|
-
async function handleWalletBalance(
|
|
96
|
+
export async function handleWalletBalance(
|
|
198
97
|
address: string | undefined,
|
|
199
98
|
options: WalletBalanceOptions
|
|
200
99
|
): Promise<void> {
|
|
@@ -236,7 +135,7 @@ async function handleWalletBalance(
|
|
|
236
135
|
console.log(JSON.stringify(output, null, 2));
|
|
237
136
|
} else {
|
|
238
137
|
console.log(`\nWallet: ${pubkey.toBase58()}`);
|
|
239
|
-
console.log(`Balance: ${balanceSOL.toFixed(4)}
|
|
138
|
+
console.log(`Balance: ${balanceSOL.toFixed(4)} NARA (${balance.toLocaleString()} lamports)`);
|
|
240
139
|
}
|
|
241
140
|
}
|
|
242
141
|
|
|
@@ -245,7 +144,7 @@ async function handleWalletBalance(
|
|
|
245
144
|
* @param tokenAddress Token address
|
|
246
145
|
* @param options Command options
|
|
247
146
|
*/
|
|
248
|
-
async function handleTokenBalance(
|
|
147
|
+
export async function handleTokenBalance(
|
|
249
148
|
tokenAddress: string,
|
|
250
149
|
options: Omit<TokenBalanceOptions, "tokenAddress">
|
|
251
150
|
): Promise<void> {
|
|
@@ -329,7 +228,7 @@ async function handleTokenBalance(
|
|
|
329
228
|
* @param signature Transaction signature
|
|
330
229
|
* @param options Command options
|
|
331
230
|
*/
|
|
332
|
-
async function handleTxStatus(
|
|
231
|
+
export async function handleTxStatus(
|
|
333
232
|
signature: string,
|
|
334
233
|
options: Omit<TxStatusOptions, "signature">
|
|
335
234
|
): Promise<void> {
|
|
@@ -399,7 +298,7 @@ async function handleTxStatus(
|
|
|
399
298
|
console.log(`Time: ${date.toISOString()}`);
|
|
400
299
|
}
|
|
401
300
|
if (output.fee) {
|
|
402
|
-
console.log(`Fee: ${output.fee / LAMPORTS_PER_SOL}
|
|
301
|
+
console.log(`Fee: ${output.fee / LAMPORTS_PER_SOL} NARA`);
|
|
403
302
|
}
|
|
404
303
|
if (output.error) {
|
|
405
304
|
console.log(`Error: ${JSON.stringify(output.error)}`);
|
|
@@ -416,7 +315,7 @@ async function handleTxStatus(
|
|
|
416
315
|
* @param amount Amount in SOL
|
|
417
316
|
* @param options Command options
|
|
418
317
|
*/
|
|
419
|
-
async function handleTransferSol(
|
|
318
|
+
export async function handleTransferSol(
|
|
420
319
|
to: string,
|
|
421
320
|
amount: string,
|
|
422
321
|
options: Omit<TransferSolOptions, "to" | "amount">
|
|
@@ -434,7 +333,7 @@ async function handleTransferSol(
|
|
|
434
333
|
const lamports = Math.floor(amountSOL * LAMPORTS_PER_SOL);
|
|
435
334
|
|
|
436
335
|
printInfo(`To: ${recipient.toBase58()}`);
|
|
437
|
-
printInfo(`Amount: ${amountSOL}
|
|
336
|
+
printInfo(`Amount: ${amountSOL} NARA`);
|
|
438
337
|
|
|
439
338
|
// Initialize SDK
|
|
440
339
|
const sdk = new NaraSDK({
|
|
@@ -482,7 +381,7 @@ async function handleTransferSol(
|
|
|
482
381
|
console.log(`\nTransfer Details:`);
|
|
483
382
|
console.log(` From: ${wallet.publicKey.toBase58()}`);
|
|
484
383
|
console.log(` To: ${recipient.toBase58()}`);
|
|
485
|
-
console.log(` Amount: ${amountSOL}
|
|
384
|
+
console.log(` Amount: ${amountSOL} NARA`);
|
|
486
385
|
printTransactionResult(txResult, false);
|
|
487
386
|
}
|
|
488
387
|
}
|
|
@@ -494,7 +393,7 @@ async function handleTransferSol(
|
|
|
494
393
|
* @param amount Amount in tokens
|
|
495
394
|
* @param options Command options
|
|
496
395
|
*/
|
|
497
|
-
async function handleTransferToken(
|
|
396
|
+
export async function handleTransferToken(
|
|
498
397
|
tokenAddress: string,
|
|
499
398
|
to: string,
|
|
500
399
|
amount: string,
|
|
@@ -709,7 +608,7 @@ async function handleWalletImport(options: {
|
|
|
709
608
|
* Handle wallet address command
|
|
710
609
|
* @param options Command options
|
|
711
610
|
*/
|
|
712
|
-
async function handleWalletAddress(options: GlobalOptions): Promise<void> {
|
|
611
|
+
export async function handleWalletAddress(options: GlobalOptions): Promise<void> {
|
|
713
612
|
const wallet = await loadWallet(options.wallet);
|
|
714
613
|
|
|
715
614
|
if (options.json) {
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZK ID commands - interact with nara-zk anonymous identity protocol
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { Connection, PublicKey } from "@solana/web3.js";
|
|
7
|
+
import { loadWallet, getRpcUrl } from "../utils/wallet";
|
|
8
|
+
import {
|
|
9
|
+
printError,
|
|
10
|
+
printInfo,
|
|
11
|
+
printSuccess,
|
|
12
|
+
printWarning,
|
|
13
|
+
formatOutput,
|
|
14
|
+
} from "../utils/output";
|
|
15
|
+
import type { GlobalOptions } from "../types";
|
|
16
|
+
import {
|
|
17
|
+
createZkId,
|
|
18
|
+
getZkIdInfo,
|
|
19
|
+
deposit,
|
|
20
|
+
scanClaimableDeposits,
|
|
21
|
+
withdraw,
|
|
22
|
+
transferZkIdByCommitment,
|
|
23
|
+
deriveIdSecret,
|
|
24
|
+
computeIdCommitment,
|
|
25
|
+
isValidRecipient,
|
|
26
|
+
generateValidRecipient,
|
|
27
|
+
ZKID_DENOMINATIONS,
|
|
28
|
+
} from "nara-sdk";
|
|
29
|
+
import BN from "bn.js";
|
|
30
|
+
|
|
31
|
+
// ─── Denomination helpers ────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const VALID_AMOUNTS = [1, 10, 100, 1000, 10000, 100000] as const;
|
|
34
|
+
type ValidAmount = (typeof VALID_AMOUNTS)[number];
|
|
35
|
+
|
|
36
|
+
const DENOM_MAP: Record<ValidAmount, BN> = {
|
|
37
|
+
1: ZKID_DENOMINATIONS.NARA_1,
|
|
38
|
+
10: ZKID_DENOMINATIONS.NARA_10,
|
|
39
|
+
100: ZKID_DENOMINATIONS.NARA_100,
|
|
40
|
+
1000: ZKID_DENOMINATIONS.NARA_1000,
|
|
41
|
+
10000: ZKID_DENOMINATIONS.NARA_10000,
|
|
42
|
+
100000: ZKID_DENOMINATIONS.NARA_100000,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function parseDenomination(amount: string): BN {
|
|
46
|
+
const n = parseInt(amount, 10);
|
|
47
|
+
if (!VALID_AMOUNTS.includes(n as ValidAmount)) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Invalid amount "${amount}". Valid denominations: ${VALID_AMOUNTS.join(", ")} NARA`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return DENOM_MAP[n as ValidAmount];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── Command handlers ────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
async function handleZkIdCreate(name: string, options: GlobalOptions) {
|
|
58
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
59
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
60
|
+
const wallet = await loadWallet(options.wallet);
|
|
61
|
+
|
|
62
|
+
if (!options.json) printInfo(`Deriving idSecret for "${name}"...`);
|
|
63
|
+
const idSecret = await deriveIdSecret(wallet, name);
|
|
64
|
+
|
|
65
|
+
// Check if already exists
|
|
66
|
+
const existing = await getZkIdInfo(connection, name);
|
|
67
|
+
if (existing) {
|
|
68
|
+
printWarning(`ZK ID "${name}" already exists (depositCount: ${existing.depositCount})`);
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!options.json) printInfo(`Registering ZK ID "${name}"...`);
|
|
73
|
+
const signature = await createZkId(connection, wallet, name, idSecret);
|
|
74
|
+
if (!options.json) printSuccess(`ZK ID "${name}" registered!`);
|
|
75
|
+
|
|
76
|
+
if (options.json) {
|
|
77
|
+
formatOutput({ name, signature }, true);
|
|
78
|
+
} else {
|
|
79
|
+
console.log(` Transaction: ${signature}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function handleZkIdInfo(name: string, options: GlobalOptions) {
|
|
84
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
85
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
86
|
+
|
|
87
|
+
const info = await getZkIdInfo(connection, name);
|
|
88
|
+
if (!info) {
|
|
89
|
+
if (options.json) {
|
|
90
|
+
formatOutput({ name, exists: false }, true);
|
|
91
|
+
} else {
|
|
92
|
+
printWarning(`ZK ID "${name}" does not exist`);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const data = {
|
|
98
|
+
name,
|
|
99
|
+
depositCount: info.depositCount,
|
|
100
|
+
commitmentStartIndex: info.commitmentStartIndex,
|
|
101
|
+
idCommitment: Buffer.from(info.idCommitment).toString("hex"),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (options.json) {
|
|
105
|
+
formatOutput(data, true);
|
|
106
|
+
} else {
|
|
107
|
+
console.log("");
|
|
108
|
+
console.log(` Name: ${name}`);
|
|
109
|
+
console.log(` Deposit count: ${info.depositCount}`);
|
|
110
|
+
console.log(` Commitment start index: ${info.commitmentStartIndex}`);
|
|
111
|
+
console.log(` ID commitment: ${data.idCommitment}`);
|
|
112
|
+
console.log("");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function handleZkIdDeposit(
|
|
117
|
+
name: string,
|
|
118
|
+
amount: string,
|
|
119
|
+
options: GlobalOptions
|
|
120
|
+
) {
|
|
121
|
+
const denomination = parseDenomination(amount);
|
|
122
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
123
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
124
|
+
const wallet = await loadWallet(options.wallet);
|
|
125
|
+
|
|
126
|
+
if (!options.json) printInfo(`Depositing ${amount} NARA into ZK ID "${name}"...`);
|
|
127
|
+
const signature = await deposit(connection, wallet, name, denomination);
|
|
128
|
+
if (!options.json) printSuccess("Deposit complete!");
|
|
129
|
+
|
|
130
|
+
if (options.json) {
|
|
131
|
+
formatOutput({ name, amount: parseInt(amount), signature }, true);
|
|
132
|
+
} else {
|
|
133
|
+
console.log(` Transaction: ${signature}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function handleZkIdScan(name: string, options: GlobalOptions) {
|
|
138
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
139
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
140
|
+
const wallet = await loadWallet(options.wallet);
|
|
141
|
+
|
|
142
|
+
if (!options.json) printInfo(`Scanning claimable deposits for "${name}"...`);
|
|
143
|
+
const idSecret = await deriveIdSecret(wallet, name);
|
|
144
|
+
const claimable = await scanClaimableDeposits(connection, name, idSecret);
|
|
145
|
+
|
|
146
|
+
if (options.json) {
|
|
147
|
+
formatOutput(
|
|
148
|
+
{
|
|
149
|
+
name,
|
|
150
|
+
count: claimable.length,
|
|
151
|
+
deposits: claimable.map((d) => ({
|
|
152
|
+
leafIndex: d.leafIndex.toString(),
|
|
153
|
+
depositIndex: d.depositIndex,
|
|
154
|
+
denomination: d.denomination.toString(),
|
|
155
|
+
nara: Number(d.denomination) / 1e9,
|
|
156
|
+
})),
|
|
157
|
+
},
|
|
158
|
+
true
|
|
159
|
+
);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (claimable.length === 0) {
|
|
164
|
+
printInfo("No claimable deposits found");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log(`\n Found ${claimable.length} claimable deposit(s):`);
|
|
169
|
+
claimable.forEach((d, i) => {
|
|
170
|
+
const nara = Number(d.denomination) / 1e9;
|
|
171
|
+
console.log(
|
|
172
|
+
` [${i}] ${nara} NARA leafIndex=${d.leafIndex} depositIndex=${d.depositIndex}`
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
console.log("");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function handleZkIdWithdraw(
|
|
179
|
+
name: string,
|
|
180
|
+
options: GlobalOptions & { recipient?: string }
|
|
181
|
+
) {
|
|
182
|
+
// Validate recipient early (before any network calls)
|
|
183
|
+
let recipient: PublicKey | undefined;
|
|
184
|
+
if (options.recipient) {
|
|
185
|
+
try {
|
|
186
|
+
recipient = new PublicKey(options.recipient);
|
|
187
|
+
} catch {
|
|
188
|
+
throw new Error(`Invalid recipient address: ${options.recipient}`);
|
|
189
|
+
}
|
|
190
|
+
if (!isValidRecipient(recipient)) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
"Recipient address is not a valid BN254 field element. " +
|
|
193
|
+
"Use `zkid withdraw` without --recipient to auto-generate a valid address."
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
199
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
200
|
+
const wallet = await loadWallet(options.wallet);
|
|
201
|
+
|
|
202
|
+
if (!options.json) printInfo(`Scanning deposits for "${name}"...`);
|
|
203
|
+
const idSecret = await deriveIdSecret(wallet, name);
|
|
204
|
+
const claimable = await scanClaimableDeposits(connection, name, idSecret);
|
|
205
|
+
|
|
206
|
+
if (claimable.length === 0) {
|
|
207
|
+
printWarning("No claimable deposits found");
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Auto-generate recipient if not provided
|
|
212
|
+
if (!recipient) {
|
|
213
|
+
const kp = generateValidRecipient();
|
|
214
|
+
recipient = kp.publicKey;
|
|
215
|
+
if (!options.json) printInfo(`Auto-generated recipient: ${recipient.toBase58()}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const depositInfo = claimable[0]!;
|
|
219
|
+
const nara = Number(depositInfo.denomination) / 1e9;
|
|
220
|
+
if (!options.json) printInfo(`Withdrawing ${nara} NARA (depositIndex=${depositInfo.depositIndex})...`);
|
|
221
|
+
|
|
222
|
+
const signature = await withdraw(
|
|
223
|
+
connection,
|
|
224
|
+
wallet,
|
|
225
|
+
name,
|
|
226
|
+
idSecret,
|
|
227
|
+
depositInfo,
|
|
228
|
+
recipient
|
|
229
|
+
);
|
|
230
|
+
if (!options.json) printSuccess("Withdrawal complete!");
|
|
231
|
+
|
|
232
|
+
if (options.json) {
|
|
233
|
+
formatOutput(
|
|
234
|
+
{
|
|
235
|
+
name,
|
|
236
|
+
recipient: recipient.toBase58(),
|
|
237
|
+
nara,
|
|
238
|
+
depositIndex: depositInfo.depositIndex,
|
|
239
|
+
signature,
|
|
240
|
+
},
|
|
241
|
+
true
|
|
242
|
+
);
|
|
243
|
+
} else {
|
|
244
|
+
console.log(` Recipient: ${recipient.toBase58()}`);
|
|
245
|
+
console.log(` Transaction: ${signature}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function handleZkIdCommitment(name: string, options: GlobalOptions) {
|
|
250
|
+
const wallet = await loadWallet(options.wallet);
|
|
251
|
+
const commitment = await computeIdCommitment(wallet, name);
|
|
252
|
+
|
|
253
|
+
if (options.json) {
|
|
254
|
+
formatOutput({ name, idCommitment: commitment }, true);
|
|
255
|
+
} else {
|
|
256
|
+
console.log("");
|
|
257
|
+
console.log(` ZK ID name: ${name}`);
|
|
258
|
+
console.log(` ID commitment: ${commitment}`);
|
|
259
|
+
console.log("");
|
|
260
|
+
printInfo("Share this commitment with the current owner to transfer the ZK ID.");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function handleZkIdTransfer(
|
|
265
|
+
name: string,
|
|
266
|
+
newIdCommitmentHex: string,
|
|
267
|
+
options: GlobalOptions
|
|
268
|
+
) {
|
|
269
|
+
// Parse hex commitment
|
|
270
|
+
let newIdCommitment: bigint;
|
|
271
|
+
try {
|
|
272
|
+
if (!/^[0-9a-fA-F]{64}$/.test(newIdCommitmentHex)) {
|
|
273
|
+
throw new Error("must be a 64-char hex string");
|
|
274
|
+
}
|
|
275
|
+
newIdCommitment = BigInt("0x" + newIdCommitmentHex);
|
|
276
|
+
} catch (e: any) {
|
|
277
|
+
throw new Error(`Invalid id-commitment: ${e.message}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
281
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
282
|
+
const wallet = await loadWallet(options.wallet);
|
|
283
|
+
|
|
284
|
+
if (!options.json) printInfo(`Generating ownership proof for "${name}"...`);
|
|
285
|
+
const currentIdSecret = await deriveIdSecret(wallet, name);
|
|
286
|
+
|
|
287
|
+
if (!options.json) printInfo("Transferring ZK ID ownership...");
|
|
288
|
+
const signature = await transferZkIdByCommitment(
|
|
289
|
+
connection,
|
|
290
|
+
wallet,
|
|
291
|
+
name,
|
|
292
|
+
currentIdSecret,
|
|
293
|
+
newIdCommitment
|
|
294
|
+
);
|
|
295
|
+
if (!options.json) printSuccess("ZK ID ownership transferred!");
|
|
296
|
+
|
|
297
|
+
if (options.json) {
|
|
298
|
+
formatOutput({ name, newIdCommitment: newIdCommitmentHex, signature }, true);
|
|
299
|
+
} else {
|
|
300
|
+
console.log(` New commitment: ${newIdCommitmentHex}`);
|
|
301
|
+
console.log(` Transaction: ${signature}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ─── Register commands ───────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
export function registerZkIdCommands(program: Command): void {
|
|
308
|
+
const zkid = program
|
|
309
|
+
.command("zkid")
|
|
310
|
+
.description("ZK ID commands (anonymous identity protocol)");
|
|
311
|
+
|
|
312
|
+
// zkid create
|
|
313
|
+
zkid
|
|
314
|
+
.command("create <name>")
|
|
315
|
+
.description("Register a new ZK ID on-chain")
|
|
316
|
+
.action(async (name: string, _opts: any, cmd: Command) => {
|
|
317
|
+
try {
|
|
318
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
319
|
+
await handleZkIdCreate(name, globalOpts);
|
|
320
|
+
} catch (error: any) {
|
|
321
|
+
printError(error.message);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// zkid info
|
|
327
|
+
zkid
|
|
328
|
+
.command("info <name>")
|
|
329
|
+
.description("Get ZK ID account info (read-only)")
|
|
330
|
+
.action(async (name: string, _opts: any, cmd: Command) => {
|
|
331
|
+
try {
|
|
332
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
333
|
+
await handleZkIdInfo(name, globalOpts);
|
|
334
|
+
} catch (error: any) {
|
|
335
|
+
printError(error.message);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// zkid deposit
|
|
341
|
+
zkid
|
|
342
|
+
.command("deposit <name> <amount>")
|
|
343
|
+
.description(`Deposit fixed-denomination NARA into a ZK ID (valid: ${VALID_AMOUNTS.join(", ")})`)
|
|
344
|
+
.action(async (name: string, amount: string, _opts: any, cmd: Command) => {
|
|
345
|
+
try {
|
|
346
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
347
|
+
await handleZkIdDeposit(name, amount, globalOpts);
|
|
348
|
+
} catch (error: any) {
|
|
349
|
+
printError(error.message);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// zkid scan
|
|
355
|
+
zkid
|
|
356
|
+
.command("scan <name>")
|
|
357
|
+
.description("Scan claimable deposits for a ZK ID (requires wallet)")
|
|
358
|
+
.action(async (name: string, _opts: any, cmd: Command) => {
|
|
359
|
+
try {
|
|
360
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
361
|
+
await handleZkIdScan(name, globalOpts);
|
|
362
|
+
} catch (error: any) {
|
|
363
|
+
printError(error.message);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// zkid withdraw
|
|
369
|
+
zkid
|
|
370
|
+
.command("withdraw <name>")
|
|
371
|
+
.description("Anonymously withdraw the first claimable deposit")
|
|
372
|
+
.option("--recipient <address>", "Recipient address (must be a valid BN254 field element)")
|
|
373
|
+
.action(async (name: string, opts: { recipient?: string }, cmd: Command) => {
|
|
374
|
+
try {
|
|
375
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
376
|
+
await handleZkIdWithdraw(name, { ...globalOpts, ...opts });
|
|
377
|
+
} catch (error: any) {
|
|
378
|
+
printError(error.message);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// zkid id-commitment
|
|
384
|
+
zkid
|
|
385
|
+
.command("id-commitment <name>")
|
|
386
|
+
.description("Derive and display your idCommitment (share with current owner to receive transfer)")
|
|
387
|
+
.action(async (name: string, _opts: any, cmd: Command) => {
|
|
388
|
+
try {
|
|
389
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
390
|
+
await handleZkIdCommitment(name, globalOpts);
|
|
391
|
+
} catch (error: any) {
|
|
392
|
+
printError(error.message);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// zkid transfer
|
|
398
|
+
zkid
|
|
399
|
+
.command("transfer <name> <new-id-commitment>")
|
|
400
|
+
.description("Transfer ZK ID ownership to the holder of a new idCommitment")
|
|
401
|
+
.action(async (name: string, newIdCommitment: string, _opts: any, cmd: Command) => {
|
|
402
|
+
try {
|
|
403
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
404
|
+
await handleZkIdTransfer(name, newIdCommitment, globalOpts);
|
|
405
|
+
} catch (error: any) {
|
|
406
|
+
printError(error.message);
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|