@hula-privacy/mixer 0.2.0 → 0.3.0
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/dist/index.d.mts +933 -0
- package/dist/index.d.ts +933 -0
- package/dist/index.js +2587 -0
- package/dist/index.mjs +2480 -0
- package/package.json +1 -8
- package/src/api.ts +5 -1
- package/src/crypto.ts +32 -12
- package/src/idl.ts +838 -0
- package/src/merkle.ts +3 -1
- package/src/transaction.ts +3 -4
- package/src/types.ts +2 -0
- package/src/utxo.ts +26 -11
- package/src/wallet.ts +238 -36
package/src/merkle.ts
CHANGED
|
@@ -193,7 +193,9 @@ export async function fetchMerkleRoot(
|
|
|
193
193
|
): Promise<bigint> {
|
|
194
194
|
const client = getRelayerClient(relayerUrl);
|
|
195
195
|
const tree = await client.getTree(treeIndex);
|
|
196
|
-
|
|
196
|
+
// Handle hex strings with or without 0x prefix
|
|
197
|
+
const rootStr = tree.root.startsWith("0x") ? tree.root : `0x${tree.root}`;
|
|
198
|
+
return BigInt(rootStr);
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
/**
|
package/src/transaction.ts
CHANGED
|
@@ -189,6 +189,7 @@ export async function buildTransaction(
|
|
|
189
189
|
};
|
|
190
190
|
|
|
191
191
|
// Create encrypted notes for outputs with encryption keys
|
|
192
|
+
// Note: leafIndex is omitted to save space - recipient looks it up via commitment
|
|
192
193
|
const encryptedNotes: Buffer[] = [];
|
|
193
194
|
for (let i = 0; i < outputs.length; i++) {
|
|
194
195
|
const output = outputs[i];
|
|
@@ -200,7 +201,7 @@ export async function buildTransaction(
|
|
|
200
201
|
value: utxo.value,
|
|
201
202
|
mintTokenAddress: utxo.mintTokenAddress,
|
|
202
203
|
secret: utxo.secret,
|
|
203
|
-
leafIndex
|
|
204
|
+
// leafIndex omitted - recipient queries relayer with computed commitment
|
|
204
205
|
},
|
|
205
206
|
output.encryptionPubKey
|
|
206
207
|
);
|
|
@@ -217,7 +218,7 @@ export async function buildTransaction(
|
|
|
217
218
|
value: changeUtxo.value,
|
|
218
219
|
mintTokenAddress: changeUtxo.mintTokenAddress,
|
|
219
220
|
secret: changeUtxo.secret,
|
|
220
|
-
leafIndex
|
|
221
|
+
// leafIndex omitted - we already know it locally
|
|
221
222
|
},
|
|
222
223
|
walletKeys.encryptionKeyPair.publicKey
|
|
223
224
|
);
|
|
@@ -460,5 +461,3 @@ export function toAnchorPublicInputs(builtTx: BuiltTransaction): {
|
|
|
460
461
|
fee: new BN(builtTx.publicInputs.fee.toString()),
|
|
461
462
|
};
|
|
462
463
|
}
|
|
463
|
-
|
|
464
|
-
|
package/src/types.ts
CHANGED
|
@@ -302,6 +302,8 @@ export interface HulaSDKConfig {
|
|
|
302
302
|
rpcUrl: string;
|
|
303
303
|
/** Relayer API endpoint */
|
|
304
304
|
relayerUrl: string;
|
|
305
|
+
/** Program ID for the Hula Privacy program */
|
|
306
|
+
programId?: PublicKey;
|
|
305
307
|
/** Path to circuit WASM file */
|
|
306
308
|
circuitWasmPath?: string;
|
|
307
309
|
/** Path to circuit zkey file */
|
package/src/utxo.ts
CHANGED
|
@@ -11,9 +11,11 @@ import {
|
|
|
11
11
|
deserializeEncryptedNote,
|
|
12
12
|
generateSpendingKey,
|
|
13
13
|
hexToBytes,
|
|
14
|
+
pubkeyToBigInt,
|
|
14
15
|
} from "./crypto";
|
|
15
16
|
import { DOMAIN_NULLIFIER } from "./constants";
|
|
16
17
|
import type { UTXO, WalletKeys, SerializableUTXO, NoteData } from "./types";
|
|
18
|
+
import { PublicKey } from "@solana/web3.js";
|
|
17
19
|
|
|
18
20
|
// ============================================================================
|
|
19
21
|
// Commitment & Nullifier Computation
|
|
@@ -176,31 +178,42 @@ export function scanNotesForUTXOs(
|
|
|
176
178
|
const decrypted = decryptNote(encryptedNote, walletKeys.encryptionKeyPair.secretKey);
|
|
177
179
|
if (!decrypted) continue;
|
|
178
180
|
|
|
179
|
-
//
|
|
181
|
+
// Get the mint address from the note metadata
|
|
182
|
+
const mintAddress = note.mint;
|
|
183
|
+
const mintTokenAddress = pubkeyToBigInt(new PublicKey(mintAddress));
|
|
184
|
+
|
|
185
|
+
// Compute commitment to find the leafIndex
|
|
180
186
|
const commitment = computeCommitment(
|
|
181
187
|
decrypted.value,
|
|
182
|
-
|
|
188
|
+
mintTokenAddress,
|
|
183
189
|
walletKeys.owner,
|
|
184
190
|
decrypted.secret
|
|
185
191
|
);
|
|
186
192
|
|
|
187
|
-
//
|
|
193
|
+
// Find leafIndex by matching commitment in on-chain data
|
|
194
|
+
let foundLeafIndex: number | undefined;
|
|
188
195
|
const treeCommitments = commitments.get(note.treeIndex);
|
|
189
196
|
if (treeCommitments) {
|
|
190
|
-
const onChainCommitment
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
for (const [leafIndex, onChainCommitment] of treeCommitments.entries()) {
|
|
198
|
+
if (onChainCommitment === commitment) {
|
|
199
|
+
foundLeafIndex = leafIndex;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
194
202
|
}
|
|
195
203
|
}
|
|
196
204
|
|
|
205
|
+
if (foundLeafIndex === undefined) {
|
|
206
|
+
// Commitment not found on-chain yet - skip for now
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
197
210
|
foundUTXOs.push({
|
|
198
211
|
value: decrypted.value.toString(),
|
|
199
|
-
mintTokenAddress:
|
|
212
|
+
mintTokenAddress: mintTokenAddress.toString(),
|
|
200
213
|
owner: walletKeys.owner.toString(),
|
|
201
214
|
secret: decrypted.secret.toString(),
|
|
202
215
|
commitment: commitment.toString(),
|
|
203
|
-
leafIndex:
|
|
216
|
+
leafIndex: foundLeafIndex,
|
|
204
217
|
treeIndex: note.treeIndex,
|
|
205
218
|
mint: note.mint,
|
|
206
219
|
spent: false,
|
|
@@ -261,8 +274,10 @@ export async function syncUTXOs(
|
|
|
261
274
|
commitments
|
|
262
275
|
);
|
|
263
276
|
|
|
277
|
+
const allUTXOs = [...newUTXOs, ...existingUTXOs];
|
|
278
|
+
|
|
264
279
|
// Check which existing UTXOs have been spent
|
|
265
|
-
const unspentUTXOs =
|
|
280
|
+
const unspentUTXOs = allUTXOs.filter(u => !u.spent);
|
|
266
281
|
const nullifiersToCheck: string[] = [];
|
|
267
282
|
const utxoByNullifier = new Map<string, string>();
|
|
268
283
|
|
|
@@ -273,7 +288,7 @@ export async function syncUTXOs(
|
|
|
273
288
|
commitment,
|
|
274
289
|
utxo.leafIndex
|
|
275
290
|
);
|
|
276
|
-
const nullifierStr = nullifier.toString();
|
|
291
|
+
const nullifierStr = nullifier.toString(16).padStart(64, '0');
|
|
277
292
|
nullifiersToCheck.push(nullifierStr);
|
|
278
293
|
utxoByNullifier.set(nullifierStr, utxo.commitment); // Use commitment as ID
|
|
279
294
|
}
|
package/src/wallet.ts
CHANGED
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
* High-level wallet abstraction that manages keys, UTXOs, and syncing
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { PublicKey, Connection, Keypair, type TransactionInstruction } from "@solana/web3.js";
|
|
8
|
-
import
|
|
7
|
+
import { PublicKey, Connection, Keypair, SystemProgram, ComputeBudgetProgram, Transaction, type TransactionInstruction } from "@solana/web3.js";
|
|
8
|
+
import { AnchorProvider, Program, Wallet } from "@coral-xyz/anchor";
|
|
9
|
+
import HulaPrivacyIdl from "./idl";
|
|
10
|
+
|
|
11
|
+
// Compute budget for ZK proof verification (needs more than default 200k)
|
|
12
|
+
const COMPUTE_UNITS = 400_000;
|
|
13
|
+
const COMPUTE_UNIT_PRICE = 1; // microlamports per CU
|
|
9
14
|
|
|
10
15
|
import {
|
|
11
16
|
initPoseidon,
|
|
@@ -40,7 +45,9 @@ import type {
|
|
|
40
45
|
export class HulaWallet {
|
|
41
46
|
private connection: Connection;
|
|
42
47
|
private relayerUrl: string;
|
|
48
|
+
private programId: PublicKey;
|
|
43
49
|
private keys: WalletKeys;
|
|
50
|
+
private signer?: Keypair;
|
|
44
51
|
private utxos: SerializableUTXO[] = [];
|
|
45
52
|
private lastSyncedSlot: string = "0";
|
|
46
53
|
private initialized: boolean = false;
|
|
@@ -48,11 +55,15 @@ export class HulaWallet {
|
|
|
48
55
|
private constructor(
|
|
49
56
|
connection: Connection,
|
|
50
57
|
relayerUrl: string,
|
|
51
|
-
|
|
58
|
+
programId: PublicKey,
|
|
59
|
+
keys: WalletKeys,
|
|
60
|
+
signer?: Keypair
|
|
52
61
|
) {
|
|
53
62
|
this.connection = connection;
|
|
54
63
|
this.relayerUrl = relayerUrl;
|
|
64
|
+
this.programId = programId;
|
|
55
65
|
this.keys = keys;
|
|
66
|
+
this.signer = signer;
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
/**
|
|
@@ -63,12 +74,38 @@ export class HulaWallet {
|
|
|
63
74
|
|
|
64
75
|
const connection = new Connection(config.rpcUrl, "confirmed");
|
|
65
76
|
const relayerUrl = config.relayerUrl;
|
|
77
|
+
const programId = config.programId ?? new PublicKey("tnJ9AAxNau3BRBTe7NzXpv8DeYyiogvGd8YPnCiuarA");
|
|
66
78
|
setDefaultRelayerUrl(relayerUrl);
|
|
67
79
|
|
|
68
80
|
const spendingKey = generateSpendingKey();
|
|
69
81
|
const keys = deriveKeys(spendingKey);
|
|
70
82
|
|
|
71
|
-
const wallet = new HulaWallet(connection, relayerUrl, keys);
|
|
83
|
+
const wallet = new HulaWallet(connection, relayerUrl, programId, keys);
|
|
84
|
+
wallet.initialized = true;
|
|
85
|
+
return wallet;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create a wallet from a Solana keypair
|
|
90
|
+
*
|
|
91
|
+
* Derives the spending key deterministically from the keypair's secret key.
|
|
92
|
+
*/
|
|
93
|
+
static async fromKeypair(
|
|
94
|
+
keypair: Keypair,
|
|
95
|
+
config: HulaSDKConfig
|
|
96
|
+
): Promise<HulaWallet> {
|
|
97
|
+
await initPoseidon();
|
|
98
|
+
|
|
99
|
+
const connection = new Connection(config.rpcUrl, "confirmed");
|
|
100
|
+
const relayerUrl = config.relayerUrl;
|
|
101
|
+
const programId = config.programId ?? new PublicKey("tnJ9AAxNau3BRBTe7NzXpv8DeYyiogvGd8YPnCiuarA");
|
|
102
|
+
setDefaultRelayerUrl(relayerUrl);
|
|
103
|
+
|
|
104
|
+
// Derive spending key from keypair's secret key
|
|
105
|
+
const spendingKey = deriveSpendingKeyFromSignature(keypair.secretKey);
|
|
106
|
+
const keys = deriveKeys(spendingKey);
|
|
107
|
+
|
|
108
|
+
const wallet = new HulaWallet(connection, relayerUrl, programId, keys, keypair);
|
|
72
109
|
wallet.initialized = true;
|
|
73
110
|
return wallet;
|
|
74
111
|
}
|
|
@@ -84,11 +121,12 @@ export class HulaWallet {
|
|
|
84
121
|
|
|
85
122
|
const connection = new Connection(config.rpcUrl, "confirmed");
|
|
86
123
|
const relayerUrl = config.relayerUrl;
|
|
124
|
+
const programId = config.programId ?? new PublicKey("tnJ9AAxNau3BRBTe7NzXpv8DeYyiogvGd8YPnCiuarA");
|
|
87
125
|
setDefaultRelayerUrl(relayerUrl);
|
|
88
126
|
|
|
89
127
|
const keys = deriveKeys(spendingKey);
|
|
90
128
|
|
|
91
|
-
const wallet = new HulaWallet(connection, relayerUrl, keys);
|
|
129
|
+
const wallet = new HulaWallet(connection, relayerUrl, programId, keys);
|
|
92
130
|
wallet.initialized = true;
|
|
93
131
|
return wallet;
|
|
94
132
|
}
|
|
@@ -106,12 +144,13 @@ export class HulaWallet {
|
|
|
106
144
|
|
|
107
145
|
const connection = new Connection(config.rpcUrl, "confirmed");
|
|
108
146
|
const relayerUrl = config.relayerUrl;
|
|
147
|
+
const programId = config.programId ?? new PublicKey("tnJ9AAxNau3BRBTe7NzXpv8DeYyiogvGd8YPnCiuarA");
|
|
109
148
|
setDefaultRelayerUrl(relayerUrl);
|
|
110
149
|
|
|
111
150
|
const spendingKey = deriveSpendingKeyFromSignature(signature);
|
|
112
151
|
const keys = deriveKeys(spendingKey);
|
|
113
152
|
|
|
114
|
-
const wallet = new HulaWallet(connection, relayerUrl, keys);
|
|
153
|
+
const wallet = new HulaWallet(connection, relayerUrl, programId, keys);
|
|
115
154
|
wallet.initialized = true;
|
|
116
155
|
return wallet;
|
|
117
156
|
}
|
|
@@ -130,12 +169,19 @@ export class HulaWallet {
|
|
|
130
169
|
/**
|
|
131
170
|
* Get wallet's owner hash as hex string
|
|
132
171
|
*/
|
|
133
|
-
|
|
134
|
-
return this.keys.owner.toString(16).padStart(64, "0");
|
|
172
|
+
getOwnerHash(): string {
|
|
173
|
+
return "0x" + this.keys.owner.toString(16).padStart(64, "0");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get wallet's encryption public key as hex string
|
|
178
|
+
*/
|
|
179
|
+
getEncryptionPublicKey(): string {
|
|
180
|
+
return "0x" + Buffer.from(this.keys.encryptionKeyPair.publicKey).toString("hex");
|
|
135
181
|
}
|
|
136
182
|
|
|
137
183
|
/**
|
|
138
|
-
* Get wallet's encryption public key
|
|
184
|
+
* Get wallet's encryption public key as bytes
|
|
139
185
|
*/
|
|
140
186
|
get encryptionPublicKey(): Uint8Array {
|
|
141
187
|
return this.keys.encryptionKeyPair.publicKey;
|
|
@@ -155,6 +201,27 @@ export class HulaWallet {
|
|
|
155
201
|
return this.keys;
|
|
156
202
|
}
|
|
157
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Get the connection
|
|
206
|
+
*/
|
|
207
|
+
getConnection(): Connection {
|
|
208
|
+
return this.connection;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get the signer keypair (if available)
|
|
213
|
+
*/
|
|
214
|
+
getSigner(): Keypair | undefined {
|
|
215
|
+
return this.signer;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Set the signer keypair
|
|
220
|
+
*/
|
|
221
|
+
setSigner(signer: Keypair): void {
|
|
222
|
+
this.signer = signer;
|
|
223
|
+
}
|
|
224
|
+
|
|
158
225
|
// ============================================================================
|
|
159
226
|
// UTXO Management
|
|
160
227
|
// ============================================================================
|
|
@@ -259,15 +326,21 @@ export class HulaWallet {
|
|
|
259
326
|
|
|
260
327
|
/**
|
|
261
328
|
* Deposit tokens into the privacy pool
|
|
329
|
+
*
|
|
330
|
+
* @param mint - Token mint address
|
|
331
|
+
* @param amount - Amount in raw token units
|
|
332
|
+
* @returns Transaction signature
|
|
262
333
|
*/
|
|
263
334
|
async deposit(
|
|
264
335
|
mint: PublicKey,
|
|
265
336
|
amount: bigint
|
|
266
|
-
): Promise<{
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
337
|
+
): Promise<string> {
|
|
338
|
+
if (!this.signer) {
|
|
339
|
+
throw new Error("No signer available. Use fromKeypair() or setSigner() to set a signer.");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Build the transaction with ZK proof
|
|
343
|
+
const builtTx = await buildTransaction(
|
|
271
344
|
{
|
|
272
345
|
mint,
|
|
273
346
|
depositAmount: amount,
|
|
@@ -277,36 +350,76 @@ export class HulaWallet {
|
|
|
277
350
|
this.relayerUrl
|
|
278
351
|
);
|
|
279
352
|
|
|
353
|
+
// Build accounts and pre-instructions
|
|
280
354
|
const { accounts, preInstructions, inputTreeIndex } = buildTransactionAccounts(
|
|
281
|
-
|
|
282
|
-
|
|
355
|
+
builtTx,
|
|
356
|
+
this.signer.publicKey,
|
|
283
357
|
mint,
|
|
284
358
|
amount,
|
|
285
359
|
0n
|
|
286
360
|
);
|
|
287
361
|
|
|
288
|
-
|
|
362
|
+
// Add compute budget instructions for ZK proof verification
|
|
363
|
+
const allPreInstructions = [
|
|
364
|
+
ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }),
|
|
365
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: COMPUTE_UNIT_PRICE }),
|
|
366
|
+
...preInstructions,
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
// Setup Anchor program
|
|
370
|
+
const wallet = new Wallet(this.signer);
|
|
371
|
+
const provider = new AnchorProvider(this.connection, wallet, { commitment: "confirmed" });
|
|
372
|
+
|
|
373
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
374
|
+
const program = new Program(HulaPrivacyIdl as any, provider);
|
|
375
|
+
|
|
376
|
+
// Convert public inputs to Anchor format
|
|
377
|
+
const publicInputs = toAnchorPublicInputs(builtTx);
|
|
378
|
+
|
|
379
|
+
// Submit the transaction
|
|
380
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
381
|
+
const signature = await (program.methods as any)
|
|
382
|
+
.transact(
|
|
383
|
+
inputTreeIndex,
|
|
384
|
+
Array.from(builtTx.proof),
|
|
385
|
+
publicInputs,
|
|
386
|
+
builtTx.encryptedNotes
|
|
387
|
+
)
|
|
388
|
+
.accounts(accounts)
|
|
389
|
+
.preInstructions(allPreInstructions)
|
|
390
|
+
.signers([this.signer])
|
|
391
|
+
.rpc();
|
|
392
|
+
|
|
393
|
+
return signature;
|
|
289
394
|
}
|
|
290
395
|
|
|
291
396
|
/**
|
|
292
|
-
* Transfer tokens privately
|
|
397
|
+
* Transfer tokens privately to another user
|
|
398
|
+
*
|
|
399
|
+
* @param mint - Token mint address
|
|
400
|
+
* @param amount - Amount in raw token units
|
|
401
|
+
* @param recipientOwner - Recipient's owner hash (bigint)
|
|
402
|
+
* @param recipientEncryptionPubKey - Recipient's encryption public key
|
|
403
|
+
* @returns Transaction signature
|
|
293
404
|
*/
|
|
294
405
|
async transfer(
|
|
295
406
|
mint: PublicKey,
|
|
296
407
|
amount: bigint,
|
|
297
408
|
recipientOwner: bigint,
|
|
298
409
|
recipientEncryptionPubKey: Uint8Array
|
|
299
|
-
): Promise<{
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
410
|
+
): Promise<string> {
|
|
411
|
+
if (!this.signer) {
|
|
412
|
+
throw new Error("No signer available. Use fromKeypair() or setSigner() to set a signer.");
|
|
413
|
+
}
|
|
414
|
+
|
|
303
415
|
const mintBigInt = pubkeyToBigInt(mint);
|
|
304
416
|
const unspent = this.getUnspentUTXOs().filter(
|
|
305
417
|
u => u.mintTokenAddress === mintBigInt
|
|
306
418
|
);
|
|
307
419
|
const inputUtxos = selectUTXOs(unspent, amount, mintBigInt);
|
|
308
420
|
|
|
309
|
-
|
|
421
|
+
// Build the transaction with ZK proof
|
|
422
|
+
const builtTx = await buildTransaction(
|
|
310
423
|
{
|
|
311
424
|
mint,
|
|
312
425
|
inputUtxos,
|
|
@@ -322,35 +435,83 @@ export class HulaWallet {
|
|
|
322
435
|
this.relayerUrl
|
|
323
436
|
);
|
|
324
437
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
438
|
+
// Build accounts and pre-instructions
|
|
439
|
+
const { accounts, preInstructions, inputTreeIndex } = buildTransactionAccounts(
|
|
440
|
+
builtTx,
|
|
441
|
+
this.signer.publicKey,
|
|
328
442
|
mint,
|
|
329
443
|
0n,
|
|
330
444
|
0n
|
|
331
445
|
);
|
|
332
446
|
|
|
333
|
-
|
|
447
|
+
// Add compute budget instructions for ZK proof verification
|
|
448
|
+
const allPreInstructions = [
|
|
449
|
+
ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }),
|
|
450
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: COMPUTE_UNIT_PRICE }),
|
|
451
|
+
...preInstructions,
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
// Setup Anchor program
|
|
455
|
+
const wallet = new Wallet(this.signer);
|
|
456
|
+
const provider = new AnchorProvider(this.connection, wallet, { commitment: "confirmed" });
|
|
457
|
+
|
|
458
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
459
|
+
const program = new Program(HulaPrivacyIdl as any, provider);
|
|
460
|
+
|
|
461
|
+
// Convert public inputs to Anchor format
|
|
462
|
+
const publicInputs = toAnchorPublicInputs(builtTx);
|
|
463
|
+
|
|
464
|
+
// Submit the transaction
|
|
465
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
466
|
+
const signature = await (program.methods as any)
|
|
467
|
+
.transact(
|
|
468
|
+
inputTreeIndex,
|
|
469
|
+
Array.from(builtTx.proof),
|
|
470
|
+
publicInputs,
|
|
471
|
+
builtTx.encryptedNotes
|
|
472
|
+
)
|
|
473
|
+
.accounts(accounts)
|
|
474
|
+
.preInstructions(allPreInstructions)
|
|
475
|
+
.signers([this.signer])
|
|
476
|
+
.rpc();
|
|
477
|
+
|
|
478
|
+
// Mark input UTXOs as spent locally
|
|
479
|
+
for (const utxo of inputUtxos) {
|
|
480
|
+
const serialized = this.utxos.find(u => u.commitment === utxo.commitment.toString());
|
|
481
|
+
if (serialized) {
|
|
482
|
+
serialized.spent = true;
|
|
483
|
+
serialized.spentTx = signature;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return signature;
|
|
334
488
|
}
|
|
335
489
|
|
|
336
490
|
/**
|
|
337
491
|
* Withdraw tokens from the privacy pool to a public address
|
|
492
|
+
*
|
|
493
|
+
* @param mint - Token mint address
|
|
494
|
+
* @param amount - Amount in raw token units
|
|
495
|
+
* @param recipient - Public key to receive the tokens
|
|
496
|
+
* @returns Transaction signature
|
|
338
497
|
*/
|
|
339
498
|
async withdraw(
|
|
340
499
|
mint: PublicKey,
|
|
341
500
|
amount: bigint,
|
|
342
501
|
recipient: PublicKey
|
|
343
|
-
): Promise<{
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
502
|
+
): Promise<string> {
|
|
503
|
+
if (!this.signer) {
|
|
504
|
+
throw new Error("No signer available. Use fromKeypair() or setSigner() to set a signer.");
|
|
505
|
+
}
|
|
506
|
+
|
|
347
507
|
const mintBigInt = pubkeyToBigInt(mint);
|
|
348
508
|
const unspent = this.getUnspentUTXOs().filter(
|
|
349
509
|
u => u.mintTokenAddress === mintBigInt
|
|
350
510
|
);
|
|
351
511
|
const inputUtxos = selectUTXOs(unspent, amount, mintBigInt);
|
|
352
512
|
|
|
353
|
-
|
|
513
|
+
// Build the transaction with ZK proof
|
|
514
|
+
const builtTx = await buildTransaction(
|
|
354
515
|
{
|
|
355
516
|
mint,
|
|
356
517
|
inputUtxos,
|
|
@@ -361,16 +522,57 @@ export class HulaWallet {
|
|
|
361
522
|
this.relayerUrl
|
|
362
523
|
);
|
|
363
524
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
525
|
+
// Build accounts and pre-instructions
|
|
526
|
+
const { accounts, preInstructions, inputTreeIndex } = buildTransactionAccounts(
|
|
527
|
+
builtTx,
|
|
528
|
+
this.signer.publicKey,
|
|
367
529
|
mint,
|
|
368
530
|
0n,
|
|
369
531
|
amount,
|
|
370
532
|
recipient
|
|
371
533
|
);
|
|
372
534
|
|
|
373
|
-
|
|
535
|
+
// Add compute budget instructions for ZK proof verification
|
|
536
|
+
const allPreInstructions = [
|
|
537
|
+
ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }),
|
|
538
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: COMPUTE_UNIT_PRICE }),
|
|
539
|
+
...preInstructions,
|
|
540
|
+
];
|
|
541
|
+
|
|
542
|
+
// Setup Anchor program
|
|
543
|
+
const wallet = new Wallet(this.signer);
|
|
544
|
+
const provider = new AnchorProvider(this.connection, wallet, { commitment: "confirmed" });
|
|
545
|
+
|
|
546
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
547
|
+
const program = new Program(HulaPrivacyIdl as any, provider);
|
|
548
|
+
|
|
549
|
+
// Convert public inputs to Anchor format
|
|
550
|
+
const publicInputs = toAnchorPublicInputs(builtTx);
|
|
551
|
+
|
|
552
|
+
// Submit the transaction
|
|
553
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
554
|
+
const signature = await (program.methods as any)
|
|
555
|
+
.transact(
|
|
556
|
+
inputTreeIndex,
|
|
557
|
+
Array.from(builtTx.proof),
|
|
558
|
+
publicInputs,
|
|
559
|
+
builtTx.encryptedNotes
|
|
560
|
+
)
|
|
561
|
+
.accounts(accounts)
|
|
562
|
+
.preInstructions(allPreInstructions)
|
|
563
|
+
.signers([this.signer])
|
|
564
|
+
.rpc();
|
|
565
|
+
|
|
566
|
+
// Mark input UTXOs as spent locally
|
|
567
|
+
for (const utxo of inputUtxos) {
|
|
568
|
+
const serialized = this.utxos.find(u => u.commitment === utxo.commitment.toString());
|
|
569
|
+
if (serialized) {
|
|
570
|
+
serialized.spent = true;
|
|
571
|
+
serialized.spentTx = signature;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return signature;
|
|
374
576
|
}
|
|
375
577
|
|
|
376
578
|
/**
|