@hula-privacy/mixer 0.1.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/README.md +290 -0
- package/package.json +52 -0
- package/src/api.ts +276 -0
- package/src/constants.ts +108 -0
- package/src/crypto.ts +293 -0
- package/src/index.ts +185 -0
- package/src/merkle.ts +220 -0
- package/src/proof.ts +251 -0
- package/src/transaction.ts +464 -0
- package/src/types.ts +331 -0
- package/src/utxo.ts +358 -0
- package/src/wallet.ts +475 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction building for Hula Privacy Protocol
|
|
3
|
+
*
|
|
4
|
+
* Builds privacy transactions with ZK proofs
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { PublicKey, SystemProgram, type TransactionInstruction } from "@solana/web3.js";
|
|
8
|
+
import {
|
|
9
|
+
getAssociatedTokenAddressSync,
|
|
10
|
+
createAssociatedTokenAccountIdempotentInstruction,
|
|
11
|
+
} from "@solana/spl-token";
|
|
12
|
+
import { BN } from "@coral-xyz/anchor";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
PROGRAM_ID,
|
|
16
|
+
TOKEN_2022_PROGRAM_ID,
|
|
17
|
+
NUM_INPUT_UTXOS,
|
|
18
|
+
NUM_OUTPUT_UTXOS,
|
|
19
|
+
MERKLE_TREE_DEPTH,
|
|
20
|
+
getPoolPDA,
|
|
21
|
+
getMerkleTreePDA,
|
|
22
|
+
getVaultPDA,
|
|
23
|
+
getNullifierPDA,
|
|
24
|
+
} from "./constants";
|
|
25
|
+
import {
|
|
26
|
+
bigIntToBytes32,
|
|
27
|
+
pubkeyToBigInt,
|
|
28
|
+
encryptNote,
|
|
29
|
+
serializeEncryptedNote,
|
|
30
|
+
} from "./crypto";
|
|
31
|
+
import { computeMerklePathFromLeaves, fetchMerkleRoot, getCurrentTreeIndex, getNextLeafIndex } from "./merkle";
|
|
32
|
+
import { computeCommitment, computeNullifier, createUTXO, createDummyUTXO } from "./utxo";
|
|
33
|
+
import { generateProof } from "./proof";
|
|
34
|
+
import { getRelayerClient } from "./api";
|
|
35
|
+
import type {
|
|
36
|
+
UTXO,
|
|
37
|
+
WalletKeys,
|
|
38
|
+
TransactionRequest,
|
|
39
|
+
BuiltTransaction,
|
|
40
|
+
CircuitInputs,
|
|
41
|
+
OutputSpec,
|
|
42
|
+
} from "./types";
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Transaction Builder
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build a privacy transaction
|
|
50
|
+
*
|
|
51
|
+
* This handles all transaction types: deposits, transfers, and withdrawals.
|
|
52
|
+
*/
|
|
53
|
+
export async function buildTransaction(
|
|
54
|
+
request: TransactionRequest,
|
|
55
|
+
walletKeys: WalletKeys,
|
|
56
|
+
relayerUrl?: string
|
|
57
|
+
): Promise<BuiltTransaction> {
|
|
58
|
+
const client = getRelayerClient(relayerUrl);
|
|
59
|
+
|
|
60
|
+
// Get current pool state
|
|
61
|
+
const pool = await client.getPool();
|
|
62
|
+
const currentTreeIndex = pool.currentTreeIndex;
|
|
63
|
+
|
|
64
|
+
// Validate inputs
|
|
65
|
+
const depositAmount = request.depositAmount ?? 0n;
|
|
66
|
+
const withdrawAmount = request.withdrawAmount ?? 0n;
|
|
67
|
+
const fee = request.fee ?? 0n;
|
|
68
|
+
const inputUtxos = request.inputUtxos ?? [];
|
|
69
|
+
const outputs = request.outputs ?? [];
|
|
70
|
+
|
|
71
|
+
if (depositAmount === 0n && inputUtxos.length === 0) {
|
|
72
|
+
throw new Error("Either depositAmount or inputUtxos is required");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (withdrawAmount > 0n && !request.recipient) {
|
|
76
|
+
throw new Error("recipient is required when withdrawing");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Determine input tree (all inputs must be from same tree)
|
|
80
|
+
let inputTreeIndex = currentTreeIndex;
|
|
81
|
+
if (inputUtxos.length > 0) {
|
|
82
|
+
inputTreeIndex = inputUtxos[0].treeIndex;
|
|
83
|
+
for (const utxo of inputUtxos) {
|
|
84
|
+
if (utxo.treeIndex !== inputTreeIndex) {
|
|
85
|
+
throw new Error("All input UTXOs must be from the same tree");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Fetch leaves from input tree for merkle path computation
|
|
91
|
+
const leaves = await client.getCommitmentsForTree(inputTreeIndex);
|
|
92
|
+
|
|
93
|
+
// Get merkle root from input tree
|
|
94
|
+
let merkleRoot: bigint;
|
|
95
|
+
if (inputUtxos.length > 0) {
|
|
96
|
+
merkleRoot = await fetchMerkleRoot(inputTreeIndex, relayerUrl);
|
|
97
|
+
} else {
|
|
98
|
+
// Pure deposit - use current tree root
|
|
99
|
+
merkleRoot = await fetchMerkleRoot(currentTreeIndex, relayerUrl);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Get next leaf index for output tree
|
|
103
|
+
const nextLeafIndex = await getNextLeafIndex(currentTreeIndex, relayerUrl);
|
|
104
|
+
|
|
105
|
+
// Calculate totals
|
|
106
|
+
const totalInputValue = inputUtxos.reduce((sum, u) => sum + u.value, 0n);
|
|
107
|
+
const totalAvailable = totalInputValue + depositAmount;
|
|
108
|
+
|
|
109
|
+
// Build output UTXOs
|
|
110
|
+
const mintBigInt = pubkeyToBigInt(request.mint);
|
|
111
|
+
const outputUtxos: UTXO[] = [];
|
|
112
|
+
let totalOutputValue = 0n;
|
|
113
|
+
let outputIndex = 0;
|
|
114
|
+
|
|
115
|
+
for (const output of outputs) {
|
|
116
|
+
const owner = output.owner === "self" ? walletKeys.owner : output.owner;
|
|
117
|
+
const utxo = createUTXO(
|
|
118
|
+
output.amount,
|
|
119
|
+
mintBigInt,
|
|
120
|
+
owner,
|
|
121
|
+
nextLeafIndex + outputIndex,
|
|
122
|
+
currentTreeIndex
|
|
123
|
+
);
|
|
124
|
+
outputUtxos.push(utxo);
|
|
125
|
+
totalOutputValue += output.amount;
|
|
126
|
+
outputIndex++;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Calculate change
|
|
130
|
+
const totalOut = withdrawAmount + fee + totalOutputValue;
|
|
131
|
+
const changeAmount = totalAvailable - totalOut;
|
|
132
|
+
|
|
133
|
+
if (changeAmount < 0n) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Insufficient value. Have ${totalAvailable} (inputs: ${totalInputValue}, deposit: ${depositAmount}), ` +
|
|
136
|
+
`need ${totalOut} (outputs: ${totalOutputValue}, withdraw: ${withdrawAmount}, fee: ${fee})`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Add change output if needed
|
|
141
|
+
if (changeAmount > 0n) {
|
|
142
|
+
if (outputUtxos.length >= NUM_OUTPUT_UTXOS) {
|
|
143
|
+
throw new Error("No output slot available for change");
|
|
144
|
+
}
|
|
145
|
+
const changeUtxo = createUTXO(
|
|
146
|
+
changeAmount,
|
|
147
|
+
mintBigInt,
|
|
148
|
+
walletKeys.owner,
|
|
149
|
+
nextLeafIndex + outputIndex,
|
|
150
|
+
currentTreeIndex
|
|
151
|
+
);
|
|
152
|
+
outputUtxos.push(changeUtxo);
|
|
153
|
+
totalOutputValue += changeAmount;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Pad outputs to NUM_OUTPUT_UTXOS
|
|
157
|
+
while (outputUtxos.length < NUM_OUTPUT_UTXOS) {
|
|
158
|
+
outputUtxos.push(createDummyUTXO());
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Build circuit inputs
|
|
162
|
+
const circuitInputs = buildCircuitInputs(
|
|
163
|
+
merkleRoot,
|
|
164
|
+
inputUtxos,
|
|
165
|
+
outputUtxos,
|
|
166
|
+
walletKeys,
|
|
167
|
+
leaves,
|
|
168
|
+
depositAmount,
|
|
169
|
+
withdrawAmount,
|
|
170
|
+
request.recipient ?? PublicKey.default,
|
|
171
|
+
request.mint,
|
|
172
|
+
fee
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Generate ZK proof
|
|
176
|
+
const { proof } = await generateProof(circuitInputs);
|
|
177
|
+
|
|
178
|
+
// Build public inputs for on-chain verifier
|
|
179
|
+
const nullifiers = circuitInputs.nullifiers.map(n => BigInt(n));
|
|
180
|
+
const publicInputs = {
|
|
181
|
+
merkleRoot: Array.from(bigIntToBytes32(merkleRoot)),
|
|
182
|
+
nullifiers: nullifiers.map(n => Array.from(bigIntToBytes32(n))),
|
|
183
|
+
outputCommitments: outputUtxos.map(u => Array.from(bigIntToBytes32(u.commitment))),
|
|
184
|
+
publicDeposit: depositAmount,
|
|
185
|
+
publicWithdraw: withdrawAmount,
|
|
186
|
+
recipient: request.recipient ?? PublicKey.default,
|
|
187
|
+
mintTokenAddress: request.mint,
|
|
188
|
+
fee,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Create encrypted notes for outputs with encryption keys
|
|
192
|
+
const encryptedNotes: Buffer[] = [];
|
|
193
|
+
for (let i = 0; i < outputs.length; i++) {
|
|
194
|
+
const output = outputs[i];
|
|
195
|
+
const utxo = outputUtxos[i];
|
|
196
|
+
|
|
197
|
+
if (output.encryptionPubKey && utxo.value > 0n) {
|
|
198
|
+
const encrypted = encryptNote(
|
|
199
|
+
{
|
|
200
|
+
value: utxo.value,
|
|
201
|
+
mintTokenAddress: utxo.mintTokenAddress,
|
|
202
|
+
secret: utxo.secret,
|
|
203
|
+
leafIndex: utxo.leafIndex,
|
|
204
|
+
},
|
|
205
|
+
output.encryptionPubKey
|
|
206
|
+
);
|
|
207
|
+
encryptedNotes.push(Buffer.from(serializeEncryptedNote(encrypted)));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Add encrypted note for change (to self)
|
|
212
|
+
if (changeAmount > 0n) {
|
|
213
|
+
const changeIndex = outputs.length;
|
|
214
|
+
const changeUtxo = outputUtxos[changeIndex];
|
|
215
|
+
const encrypted = encryptNote(
|
|
216
|
+
{
|
|
217
|
+
value: changeUtxo.value,
|
|
218
|
+
mintTokenAddress: changeUtxo.mintTokenAddress,
|
|
219
|
+
secret: changeUtxo.secret,
|
|
220
|
+
leafIndex: changeUtxo.leafIndex,
|
|
221
|
+
},
|
|
222
|
+
walletKeys.encryptionKeyPair.publicKey
|
|
223
|
+
);
|
|
224
|
+
encryptedNotes.push(Buffer.from(serializeEncryptedNote(encrypted)));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
proof,
|
|
229
|
+
publicInputs,
|
|
230
|
+
encryptedNotes,
|
|
231
|
+
outputUtxos: outputUtxos.filter(u => u.value > 0n),
|
|
232
|
+
nullifiers,
|
|
233
|
+
inputTreeIndex,
|
|
234
|
+
outputTreeIndex: currentTreeIndex,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Build circuit inputs from transaction data
|
|
240
|
+
*/
|
|
241
|
+
function buildCircuitInputs(
|
|
242
|
+
merkleRoot: bigint,
|
|
243
|
+
inputUtxos: UTXO[],
|
|
244
|
+
outputUtxos: UTXO[],
|
|
245
|
+
walletKeys: WalletKeys,
|
|
246
|
+
leaves: bigint[],
|
|
247
|
+
depositAmount: bigint,
|
|
248
|
+
withdrawAmount: bigint,
|
|
249
|
+
recipient: PublicKey,
|
|
250
|
+
mint: PublicKey,
|
|
251
|
+
fee: bigint
|
|
252
|
+
): CircuitInputs {
|
|
253
|
+
const inputSpendingKeys: string[] = [];
|
|
254
|
+
const inputValues: string[] = [];
|
|
255
|
+
const inputSecrets: string[] = [];
|
|
256
|
+
const inputLeafIndices: string[] = [];
|
|
257
|
+
const inputPathElements: string[][] = [];
|
|
258
|
+
const inputPathIndices: number[][] = [];
|
|
259
|
+
const nullifiers: string[] = [];
|
|
260
|
+
|
|
261
|
+
for (let i = 0; i < NUM_INPUT_UTXOS; i++) {
|
|
262
|
+
if (i < inputUtxos.length) {
|
|
263
|
+
const utxo = inputUtxos[i];
|
|
264
|
+
inputSpendingKeys.push(walletKeys.spendingKey.toString());
|
|
265
|
+
inputValues.push(utxo.value.toString());
|
|
266
|
+
inputSecrets.push(utxo.secret.toString());
|
|
267
|
+
inputLeafIndices.push(utxo.leafIndex.toString());
|
|
268
|
+
|
|
269
|
+
// Compute merkle path
|
|
270
|
+
const { pathElements, pathIndices } = computeMerklePathFromLeaves(
|
|
271
|
+
utxo.leafIndex,
|
|
272
|
+
leaves
|
|
273
|
+
);
|
|
274
|
+
inputPathElements.push(pathElements.map(e => e.toString()));
|
|
275
|
+
inputPathIndices.push(pathIndices);
|
|
276
|
+
|
|
277
|
+
// Compute nullifier
|
|
278
|
+
const nullifier = computeNullifier(
|
|
279
|
+
walletKeys.spendingKey,
|
|
280
|
+
utxo.commitment,
|
|
281
|
+
utxo.leafIndex
|
|
282
|
+
);
|
|
283
|
+
nullifiers.push(nullifier.toString());
|
|
284
|
+
} else {
|
|
285
|
+
// Dummy input
|
|
286
|
+
inputSpendingKeys.push("0");
|
|
287
|
+
inputValues.push("0");
|
|
288
|
+
inputSecrets.push("0");
|
|
289
|
+
inputLeafIndices.push("0");
|
|
290
|
+
inputPathElements.push(Array(MERKLE_TREE_DEPTH).fill("0"));
|
|
291
|
+
inputPathIndices.push(Array(MERKLE_TREE_DEPTH).fill(0));
|
|
292
|
+
nullifiers.push("0");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
merkleRoot: merkleRoot.toString(),
|
|
298
|
+
nullifiers,
|
|
299
|
+
outputCommitments: outputUtxos.map(u => u.commitment.toString()),
|
|
300
|
+
publicDeposit: depositAmount.toString(),
|
|
301
|
+
publicWithdraw: withdrawAmount.toString(),
|
|
302
|
+
recipient: pubkeyToBigInt(recipient).toString(),
|
|
303
|
+
mintTokenAddress: pubkeyToBigInt(mint).toString(),
|
|
304
|
+
fee: fee.toString(),
|
|
305
|
+
|
|
306
|
+
inputSpendingKeys,
|
|
307
|
+
inputValues,
|
|
308
|
+
inputSecrets,
|
|
309
|
+
inputLeafIndices,
|
|
310
|
+
inputPathElements,
|
|
311
|
+
inputPathIndices,
|
|
312
|
+
|
|
313
|
+
outputValues: outputUtxos.map(u => u.value.toString()),
|
|
314
|
+
outputOwners: outputUtxos.map(u => u.owner.toString()),
|
|
315
|
+
outputSecrets: outputUtxos.map(u => u.secret.toString()),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// Solana Instruction Building
|
|
321
|
+
// ============================================================================
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Build Solana transaction accounts and instructions
|
|
325
|
+
*
|
|
326
|
+
* This creates the account list and any pre-instructions needed for the transaction.
|
|
327
|
+
*/
|
|
328
|
+
export function buildTransactionAccounts(
|
|
329
|
+
builtTx: BuiltTransaction,
|
|
330
|
+
payer: PublicKey,
|
|
331
|
+
mint: PublicKey,
|
|
332
|
+
depositAmount: bigint,
|
|
333
|
+
withdrawAmount: bigint,
|
|
334
|
+
recipient?: PublicKey
|
|
335
|
+
): {
|
|
336
|
+
accounts: {
|
|
337
|
+
payer: PublicKey;
|
|
338
|
+
mint: PublicKey;
|
|
339
|
+
pool: PublicKey;
|
|
340
|
+
inputTree: PublicKey;
|
|
341
|
+
merkleTree: PublicKey;
|
|
342
|
+
vault: PublicKey;
|
|
343
|
+
depositorTokenAccount: PublicKey | null;
|
|
344
|
+
depositor: PublicKey | null;
|
|
345
|
+
recipientTokenAccount: PublicKey | null;
|
|
346
|
+
feeRecipientTokenAccount: PublicKey | null;
|
|
347
|
+
nullifierAccount0: PublicKey | null;
|
|
348
|
+
nullifierAccount1: PublicKey | null;
|
|
349
|
+
tokenProgram: PublicKey;
|
|
350
|
+
systemProgram: PublicKey;
|
|
351
|
+
};
|
|
352
|
+
preInstructions: TransactionInstruction[];
|
|
353
|
+
inputTreeIndex: number | null;
|
|
354
|
+
} {
|
|
355
|
+
const [poolPda] = getPoolPDA();
|
|
356
|
+
const [vaultPda] = getVaultPDA(mint);
|
|
357
|
+
const [inputTreePda] = getMerkleTreePDA(builtTx.inputTreeIndex);
|
|
358
|
+
const [outputTreePda] = getMerkleTreePDA(builtTx.outputTreeIndex);
|
|
359
|
+
|
|
360
|
+
const preInstructions: TransactionInstruction[] = [];
|
|
361
|
+
|
|
362
|
+
// Depositor token account
|
|
363
|
+
let depositorTokenAccount: PublicKey | null = null;
|
|
364
|
+
let depositor: PublicKey | null = null;
|
|
365
|
+
if (depositAmount > 0n) {
|
|
366
|
+
depositorTokenAccount = getAssociatedTokenAddressSync(
|
|
367
|
+
mint,
|
|
368
|
+
payer,
|
|
369
|
+
false,
|
|
370
|
+
TOKEN_2022_PROGRAM_ID
|
|
371
|
+
);
|
|
372
|
+
depositor = payer;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Recipient token account
|
|
376
|
+
let recipientTokenAccount: PublicKey | null = null;
|
|
377
|
+
if (withdrawAmount > 0n && recipient) {
|
|
378
|
+
recipientTokenAccount = getAssociatedTokenAddressSync(
|
|
379
|
+
mint,
|
|
380
|
+
recipient,
|
|
381
|
+
false,
|
|
382
|
+
TOKEN_2022_PROGRAM_ID
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
// Create ATA if needed
|
|
386
|
+
const createAtaIx = createAssociatedTokenAccountIdempotentInstruction(
|
|
387
|
+
payer,
|
|
388
|
+
recipientTokenAccount,
|
|
389
|
+
recipient,
|
|
390
|
+
mint,
|
|
391
|
+
TOKEN_2022_PROGRAM_ID
|
|
392
|
+
);
|
|
393
|
+
preInstructions.push(createAtaIx);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Nullifier PDAs
|
|
397
|
+
const nullifierPdas: (PublicKey | null)[] = [];
|
|
398
|
+
for (const nullifier of builtTx.nullifiers) {
|
|
399
|
+
if (nullifier === 0n) {
|
|
400
|
+
nullifierPdas.push(null);
|
|
401
|
+
} else {
|
|
402
|
+
const nullifierBytes = bigIntToBytes32(nullifier);
|
|
403
|
+
const [nullifierPda] = getNullifierPDA(nullifierBytes);
|
|
404
|
+
nullifierPdas.push(nullifierPda);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Determine if we need to pass input tree index
|
|
409
|
+
const inputTreeIndex = builtTx.inputTreeIndex !== builtTx.outputTreeIndex
|
|
410
|
+
? builtTx.inputTreeIndex
|
|
411
|
+
: null;
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
accounts: {
|
|
415
|
+
payer,
|
|
416
|
+
mint,
|
|
417
|
+
pool: poolPda,
|
|
418
|
+
inputTree: inputTreePda,
|
|
419
|
+
merkleTree: outputTreePda,
|
|
420
|
+
vault: vaultPda,
|
|
421
|
+
depositorTokenAccount,
|
|
422
|
+
depositor,
|
|
423
|
+
recipientTokenAccount,
|
|
424
|
+
feeRecipientTokenAccount: null, // TODO: Add fee recipient support
|
|
425
|
+
nullifierAccount0: nullifierPdas[0] ?? null,
|
|
426
|
+
nullifierAccount1: nullifierPdas[1] ?? null,
|
|
427
|
+
tokenProgram: TOKEN_2022_PROGRAM_ID,
|
|
428
|
+
systemProgram: SystemProgram.programId,
|
|
429
|
+
},
|
|
430
|
+
preInstructions,
|
|
431
|
+
inputTreeIndex,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ============================================================================
|
|
436
|
+
// Helper Types for Anchor
|
|
437
|
+
// ============================================================================
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Convert built transaction to Anchor-compatible format
|
|
441
|
+
*/
|
|
442
|
+
export function toAnchorPublicInputs(builtTx: BuiltTransaction): {
|
|
443
|
+
merkleRoot: number[];
|
|
444
|
+
nullifiers: number[][];
|
|
445
|
+
outputCommitments: number[][];
|
|
446
|
+
publicDeposit: BN;
|
|
447
|
+
publicWithdraw: BN;
|
|
448
|
+
recipient: PublicKey;
|
|
449
|
+
mintTokenAddress: PublicKey;
|
|
450
|
+
fee: BN;
|
|
451
|
+
} {
|
|
452
|
+
return {
|
|
453
|
+
merkleRoot: builtTx.publicInputs.merkleRoot,
|
|
454
|
+
nullifiers: builtTx.publicInputs.nullifiers,
|
|
455
|
+
outputCommitments: builtTx.publicInputs.outputCommitments,
|
|
456
|
+
publicDeposit: new BN(builtTx.publicInputs.publicDeposit.toString()),
|
|
457
|
+
publicWithdraw: new BN(builtTx.publicInputs.publicWithdraw.toString()),
|
|
458
|
+
recipient: builtTx.publicInputs.recipient,
|
|
459
|
+
mintTokenAddress: builtTx.publicInputs.mintTokenAddress,
|
|
460
|
+
fee: new BN(builtTx.publicInputs.fee.toString()),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
|