nova-privacy-sdk 1.0.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.
Files changed (69) hide show
  1. package/.github/workflows/npm-publish.yml +55 -0
  2. package/PUBLISH.md +122 -0
  3. package/README.md +177 -0
  4. package/__tests__/e2e.test.ts +56 -0
  5. package/__tests__/e2espl.test.ts +73 -0
  6. package/__tests__/encryption.test.ts +1635 -0
  7. package/circuit2/transaction2.wasm +0 -0
  8. package/circuit2/transaction2.zkey +0 -0
  9. package/dist/config.d.ts +9 -0
  10. package/dist/config.js +12 -0
  11. package/dist/deposit.d.ts +18 -0
  12. package/dist/deposit.js +392 -0
  13. package/dist/depositSPL.d.ts +20 -0
  14. package/dist/depositSPL.js +448 -0
  15. package/dist/exportUtils.d.ts +11 -0
  16. package/dist/exportUtils.js +11 -0
  17. package/dist/getUtxos.d.ts +29 -0
  18. package/dist/getUtxos.js +294 -0
  19. package/dist/getUtxosSPL.d.ts +33 -0
  20. package/dist/getUtxosSPL.js +395 -0
  21. package/dist/index.d.ts +125 -0
  22. package/dist/index.js +302 -0
  23. package/dist/models/keypair.d.ts +26 -0
  24. package/dist/models/keypair.js +43 -0
  25. package/dist/models/utxo.d.ts +49 -0
  26. package/dist/models/utxo.js +85 -0
  27. package/dist/utils/address_lookup_table.d.ts +9 -0
  28. package/dist/utils/address_lookup_table.js +45 -0
  29. package/dist/utils/constants.d.ts +31 -0
  30. package/dist/utils/constants.js +62 -0
  31. package/dist/utils/encryption.d.ts +107 -0
  32. package/dist/utils/encryption.js +376 -0
  33. package/dist/utils/logger.d.ts +9 -0
  34. package/dist/utils/logger.js +35 -0
  35. package/dist/utils/merkle_tree.d.ts +92 -0
  36. package/dist/utils/merkle_tree.js +186 -0
  37. package/dist/utils/node-shim.d.ts +5 -0
  38. package/dist/utils/node-shim.js +5 -0
  39. package/dist/utils/prover.d.ts +36 -0
  40. package/dist/utils/prover.js +147 -0
  41. package/dist/utils/utils.d.ts +69 -0
  42. package/dist/utils/utils.js +182 -0
  43. package/dist/withdraw.d.ts +21 -0
  44. package/dist/withdraw.js +270 -0
  45. package/dist/withdrawSPL.d.ts +23 -0
  46. package/dist/withdrawSPL.js +306 -0
  47. package/package.json +77 -0
  48. package/setup-git.sh +51 -0
  49. package/setup-github.sh +36 -0
  50. package/src/config.ts +22 -0
  51. package/src/deposit.ts +487 -0
  52. package/src/depositSPL.ts +567 -0
  53. package/src/exportUtils.ts +13 -0
  54. package/src/getUtxos.ts +396 -0
  55. package/src/getUtxosSPL.ts +528 -0
  56. package/src/index.ts +350 -0
  57. package/src/models/keypair.ts +52 -0
  58. package/src/models/utxo.ts +106 -0
  59. package/src/utils/address_lookup_table.ts +78 -0
  60. package/src/utils/constants.ts +84 -0
  61. package/src/utils/encryption.ts +464 -0
  62. package/src/utils/logger.ts +42 -0
  63. package/src/utils/merkle_tree.ts +207 -0
  64. package/src/utils/node-shim.ts +6 -0
  65. package/src/utils/prover.ts +222 -0
  66. package/src/utils/utils.ts +242 -0
  67. package/src/withdraw.ts +332 -0
  68. package/src/withdrawSPL.ts +394 -0
  69. package/tsconfig.json +28 -0
package/src/index.ts ADDED
@@ -0,0 +1,350 @@
1
+ import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, VersionedTransaction } from '@solana/web3.js';
2
+ import { deposit } from './deposit.js';
3
+ import { getBalanceFromUtxos, getUtxos, localstorageKey } from './getUtxos.js';
4
+ import { getBalanceFromUtxosSPL, getUtxosSPL } from './getUtxosSPL.js';
5
+
6
+ import { LSK_ENCRYPTED_OUTPUTS, LSK_FETCH_OFFSET, SplList, TokenList, tokens, USDC_MINT } from './utils/constants.js';
7
+ import { logger, type LoggerFn, setLogger } from './utils/logger.js';
8
+ import { EncryptionService } from './utils/encryption.js';
9
+ import { WasmFactory } from '@lightprotocol/hasher.rs';
10
+ import bs58 from 'bs58'
11
+ import { withdraw } from './withdraw.js';
12
+ import { LocalStorage } from "node-localstorage";
13
+ import path from 'node:path'
14
+ import { depositSPL } from './depositSPL.js';
15
+ import { withdrawSPL } from './withdrawSPL.js';
16
+ import { getAssociatedTokenAddress } from '@solana/spl-token';
17
+
18
+ let storage = new LocalStorage(path.join(process.cwd(), "cache"));
19
+
20
+ export class PrivacyCash {
21
+ private connection: Connection
22
+ public publicKey: PublicKey
23
+ private encryptionService: EncryptionService
24
+ private keypair: Keypair
25
+ private isRuning?: boolean = false
26
+ private status: string = ''
27
+ constructor({ RPC_url, owner, enableDebug }: {
28
+ RPC_url: string,
29
+ owner: string | number[] | Uint8Array | Keypair,
30
+ enableDebug?: boolean
31
+ }) {
32
+ let keypair = getSolanaKeypair(owner)
33
+ if (!keypair) {
34
+ throw new Error('param "owner" is not a valid Private Key or Keypair')
35
+ }
36
+ this.keypair = keypair
37
+ this.connection = new Connection(RPC_url, 'confirmed')
38
+ this.publicKey = keypair.publicKey
39
+ this.encryptionService = new EncryptionService();
40
+ this.encryptionService.deriveEncryptionKeyFromWallet(this.keypair);
41
+ if (!enableDebug) {
42
+ this.startStatusRender()
43
+ this.setLogger((level, message) => {
44
+ if (level == 'info') {
45
+ this.status = message
46
+ } else if (level == 'error') {
47
+ console.log('error message: ', message)
48
+ }
49
+ })
50
+ }
51
+ }
52
+
53
+ setLogger(loger: LoggerFn) {
54
+ setLogger(loger)
55
+ return this
56
+ }
57
+
58
+ /**
59
+ * Clears the cache of utxos.
60
+ *
61
+ * By default, downloaded utxos will be cached in the local storage. Thus the next time when you makes another
62
+ * deposit or withdraw or getPrivateBalance, the SDK only fetches the utxos that are not in the cache.
63
+ *
64
+ * This method clears the cache of utxos.
65
+ */
66
+ async clearCache() {
67
+ if (!this.publicKey) {
68
+ return this
69
+ }
70
+ storage.removeItem(LSK_FETCH_OFFSET + localstorageKey(this.publicKey))
71
+ storage.removeItem(LSK_ENCRYPTED_OUTPUTS + localstorageKey(this.publicKey))
72
+ // spl
73
+ for (let token of tokens) {
74
+ let ata = await getAssociatedTokenAddress(
75
+ token.pubkey,
76
+ this.publicKey
77
+ );
78
+ storage.removeItem(LSK_FETCH_OFFSET + localstorageKey(ata))
79
+ storage.removeItem(LSK_ENCRYPTED_OUTPUTS + localstorageKey(ata))
80
+ }
81
+ return this
82
+ }
83
+
84
+ /**
85
+ * Deposit SOL to the Privacy Cash.
86
+ *
87
+ * Lamports is the amount of SOL in lamports. e.g. if you want to deposit 0.01 SOL (10000000 lamports), call deposit({ lamports: 10000000 })
88
+ */
89
+ async deposit({ lamports }: {
90
+ lamports: number
91
+ }) {
92
+ this.isRuning = true
93
+ logger.info('start depositting')
94
+ let lightWasm = await WasmFactory.getInstance()
95
+ let res = await deposit({
96
+ lightWasm,
97
+ amount_in_lamports: lamports,
98
+ connection: this.connection,
99
+ encryptionService: this.encryptionService,
100
+ publicKey: this.publicKey,
101
+ transactionSigner: async (tx: VersionedTransaction) => {
102
+ tx.sign([this.keypair])
103
+ return tx
104
+ },
105
+ keyBasePath: path.join(import.meta.dirname, '..', 'circuit2', 'transaction2'),
106
+ storage
107
+ })
108
+ this.isRuning = false
109
+ return res
110
+ }
111
+
112
+ /**
113
+ * Deposit USDC to the Privacy Cash.
114
+ */
115
+ async depositUSDC({ base_units }: {
116
+ base_units: number
117
+ }) {
118
+ this.isRuning = true
119
+ logger.info('start depositting')
120
+ let lightWasm = await WasmFactory.getInstance()
121
+ let res = await depositSPL({
122
+ mintAddress: USDC_MINT,
123
+ lightWasm,
124
+ base_units: base_units,
125
+ connection: this.connection,
126
+ encryptionService: this.encryptionService,
127
+ publicKey: this.publicKey,
128
+ transactionSigner: async (tx: VersionedTransaction) => {
129
+ tx.sign([this.keypair])
130
+ return tx
131
+ },
132
+ keyBasePath: path.join(import.meta.dirname, '..', 'circuit2', 'transaction2'),
133
+ storage
134
+ })
135
+ this.isRuning = false
136
+ return res
137
+ }
138
+
139
+ /**
140
+ * Withdraw SOL from the Privacy Cash.
141
+ *
142
+ * Lamports is the amount of SOL in lamports. e.g. if you want to withdraw 0.01 SOL (10000000 lamports), call withdraw({ lamports: 10000000 })
143
+ */
144
+ async withdraw({ lamports, recipientAddress }: {
145
+ lamports: number,
146
+ recipientAddress?: string
147
+ }) {
148
+ this.isRuning = true
149
+ logger.info('start withdrawing')
150
+ let lightWasm = await WasmFactory.getInstance()
151
+ let recipient = recipientAddress ? new PublicKey(recipientAddress) : this.publicKey
152
+ let res = await withdraw({
153
+ lightWasm,
154
+ amount_in_lamports: lamports,
155
+ connection: this.connection,
156
+ encryptionService: this.encryptionService,
157
+ publicKey: this.publicKey,
158
+ recipient,
159
+ keyBasePath: path.join(import.meta.dirname, '..', 'circuit2', 'transaction2'),
160
+ storage
161
+ })
162
+ logger.debug(`Withdraw successful. Recipient ${recipient} received ${res.amount_in_lamports / LAMPORTS_PER_SOL} SOL, with ${res.fee_in_lamports / LAMPORTS_PER_SOL} SOL relayers fees`)
163
+ this.isRuning = false
164
+ return res
165
+ }
166
+
167
+ /**
168
+ * Withdraw USDC from the Privacy Cash.
169
+ *
170
+ * base_units is the amount of USDC in base unit. e.g. if you want to withdraw 1 USDC (1,000,000 base unit), call withdraw({ base_units: 1000000, recipientAddress: 'some_address' })
171
+ */
172
+ async withdrawUSDC({ base_units, recipientAddress }: {
173
+ base_units: number,
174
+ recipientAddress?: string
175
+ }) {
176
+ this.isRuning = true
177
+ logger.info('start withdrawing')
178
+ let lightWasm = await WasmFactory.getInstance()
179
+ let recipient = recipientAddress ? new PublicKey(recipientAddress) : this.publicKey
180
+ let res = await withdrawSPL({
181
+ mintAddress: USDC_MINT,
182
+ lightWasm,
183
+ base_units,
184
+ connection: this.connection,
185
+ encryptionService: this.encryptionService,
186
+ publicKey: this.publicKey,
187
+ recipient,
188
+ keyBasePath: path.join(import.meta.dirname, '..', 'circuit2', 'transaction2'),
189
+ storage
190
+ })
191
+ logger.debug(`Withdraw successful. Recipient ${recipient} received ${base_units} USDC units`)
192
+ this.isRuning = false
193
+ return res
194
+ }
195
+
196
+ /**
197
+ * Returns the amount of lamports current wallet has in Privacy Cash.
198
+ */
199
+ async getPrivateBalance(abortSignal?: AbortSignal) {
200
+ logger.info('getting private balance')
201
+ this.isRuning = true
202
+ let utxos = await getUtxos({ publicKey: this.publicKey, connection: this.connection, encryptionService: this.encryptionService, storage, abortSignal })
203
+ this.isRuning = false
204
+ return getBalanceFromUtxos(utxos)
205
+ }
206
+
207
+ /**
208
+ * Returns the amount of base unites current wallet has in Privacy Cash.
209
+ */
210
+ async getPrivateBalanceUSDC() {
211
+ logger.info('getting private balance')
212
+ this.isRuning = true
213
+ let utxos = await getUtxosSPL({ publicKey: this.publicKey, connection: this.connection, encryptionService: this.encryptionService, storage, mintAddress: USDC_MINT })
214
+ this.isRuning = false
215
+ return getBalanceFromUtxosSPL(utxos)
216
+ }
217
+
218
+ /**
219
+ * Returns the amount of base unites current wallet has in Privacy Cash.
220
+ */
221
+ async getPrivateBalanceSpl(mintAddress: PublicKey | string) {
222
+ this.isRuning = true
223
+ let utxos = await getUtxosSPL({
224
+ publicKey: this.publicKey,
225
+ connection: this.connection,
226
+ encryptionService: this.encryptionService,
227
+ storage,
228
+ mintAddress
229
+ })
230
+ this.isRuning = false
231
+ return getBalanceFromUtxosSPL(utxos)
232
+ }
233
+
234
+ /**
235
+ * Returns true if the code is running in a browser.
236
+ */
237
+ isBrowser() {
238
+ return typeof window !== "undefined"
239
+ }
240
+
241
+ async startStatusRender() {
242
+ let frames = ['-', '\\', '|', '/'];
243
+ let i = 0
244
+ while (true) {
245
+ if (this.isRuning) {
246
+ let k = i % frames.length
247
+ i++
248
+ stdWrite(this.status, frames[k])
249
+ }
250
+ await new Promise(r => setTimeout(r, 250));
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Deposit SPL to the Privacy Cash.
256
+ */
257
+ async depositSPL({ base_units, mintAddress, amount }: {
258
+ base_units?: number,
259
+ amount?: number,
260
+ mintAddress: PublicKey | string
261
+ }) {
262
+ this.isRuning = true
263
+ logger.info('start depositting')
264
+ let lightWasm = await WasmFactory.getInstance()
265
+ let res = await depositSPL({
266
+ lightWasm,
267
+ base_units,
268
+ amount,
269
+ connection: this.connection,
270
+ encryptionService: this.encryptionService,
271
+ publicKey: this.publicKey,
272
+ transactionSigner: async (tx: VersionedTransaction) => {
273
+ tx.sign([this.keypair])
274
+ return tx
275
+ },
276
+ keyBasePath: path.join(import.meta.dirname, '..', 'circuit2', 'transaction2'),
277
+ storage,
278
+ mintAddress
279
+ })
280
+ this.isRuning = false
281
+ return res
282
+ }
283
+
284
+ /**
285
+ * Withdraw SPL from the Privacy Cash.
286
+ */
287
+ async withdrawSPL({ base_units, mintAddress, recipientAddress, amount }: {
288
+ base_units?: number,
289
+ amount?: number,
290
+ mintAddress: PublicKey | string,
291
+ recipientAddress?: string
292
+ }) {
293
+ this.isRuning = true
294
+ logger.info('start withdrawing')
295
+ let lightWasm = await WasmFactory.getInstance()
296
+ let recipient = recipientAddress ? new PublicKey(recipientAddress) : this.publicKey
297
+
298
+ let res = await withdrawSPL({
299
+ lightWasm,
300
+ base_units,
301
+ amount,
302
+ connection: this.connection,
303
+ encryptionService: this.encryptionService,
304
+ publicKey: this.publicKey,
305
+ recipient,
306
+ keyBasePath: path.join(import.meta.dirname, '..', 'circuit2', 'transaction2'),
307
+ storage,
308
+ mintAddress
309
+ })
310
+ logger.debug(`Withdraw successful. Recipient ${recipient} received ${base_units} USDC units`)
311
+ this.isRuning = false
312
+ return res
313
+ }
314
+
315
+
316
+ }
317
+
318
+ function getSolanaKeypair(
319
+ secret: string | number[] | Uint8Array | Keypair
320
+ ): Keypair | null {
321
+ try {
322
+ if (secret instanceof Keypair) {
323
+ return secret;
324
+ }
325
+
326
+ let keyArray: Uint8Array;
327
+
328
+ if (typeof secret === "string") {
329
+ keyArray = bs58.decode(secret);
330
+ } else if (secret instanceof Uint8Array) {
331
+ keyArray = secret;
332
+ } else {
333
+ // number[]
334
+ keyArray = Uint8Array.from(secret);
335
+ }
336
+
337
+ if (keyArray.length !== 32 && keyArray.length !== 64) {
338
+ return null;
339
+ }
340
+ return Keypair.fromSecretKey(keyArray);
341
+ } catch {
342
+ return null;
343
+ }
344
+ }
345
+
346
+ function stdWrite(status: string, frame: string) {
347
+ let blue = "\x1b[34m";
348
+ let reset = "\x1b[0m";
349
+ process.stdout.write(`${frame}status: ${blue}${status}${reset}\r`);
350
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Keypair module for ZK Cash
3
+ *
4
+ * Provides cryptographic keypair functionality for the ZK Cash system
5
+ * Based on: https://github.com/tornadocash/tornado-nova
6
+ */
7
+
8
+ import BN from 'bn.js';
9
+ import { ethers } from 'ethers';
10
+ import * as hasher from '@lightprotocol/hasher.rs';
11
+
12
+ // Field size constant
13
+ const FIELD_SIZE = new BN(
14
+ '21888242871839275222246405745257275088548364400416034343698204186575808495617'
15
+ );
16
+
17
+ /**
18
+ * Simplified version of Keypair
19
+ */
20
+ export class Keypair {
21
+ public privkey: BN;
22
+ public pubkey: BN;
23
+ private lightWasm: hasher.LightWasm;
24
+
25
+ constructor(privkeyHex: string, lightWasm: hasher.LightWasm) {
26
+ const rawDecimal = BigInt(privkeyHex);
27
+ this.privkey = new BN((rawDecimal % BigInt(FIELD_SIZE.toString())).toString());
28
+ this.lightWasm = lightWasm;
29
+ // TODO: lazily compute pubkey
30
+ this.pubkey = new BN(this.lightWasm.poseidonHashString([this.privkey.toString()]));
31
+ }
32
+
33
+ /**
34
+ * Sign a message using keypair private key
35
+ *
36
+ * @param {string|number|BigNumber} commitment a hex string with commitment
37
+ * @param {string|number|BigNumber} merklePath a hex string with merkle path
38
+ * @returns {BigNumber} a hex string with signature
39
+ */
40
+ sign(commitment: string, merklePath: string): string {
41
+ return this.lightWasm.poseidonHashString([this.privkey.toString(), commitment, merklePath]);
42
+ }
43
+
44
+ static async generateNew(lightWasm: hasher.LightWasm): Promise<Keypair> {
45
+ // Tornado Cash Nova uses ethers.js to generate a random private key
46
+ // We can't generate Solana keypairs because it won't fit in the field size
47
+ // It's OK to use ethereum secret keys, because the secret key is only used for the proof generation.
48
+ // Namely, it's used to guarantee the uniqueness of the nullifier.
49
+ const wallet = ethers.Wallet.createRandom();
50
+ return new Keypair(wallet.privateKey, lightWasm);
51
+ }
52
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * UTXO (Unspent Transaction Output) module for ZK Cash
3
+ *
4
+ * Provides UTXO functionality for the ZK Cash system
5
+ * Based on: https://github.com/tornadocash/tornado-nova
6
+ */
7
+
8
+ import BN from 'bn.js';
9
+ import { Keypair } from './keypair.js';
10
+ import * as hasher from '@lightprotocol/hasher.rs';
11
+ import { ethers } from 'ethers';
12
+ import { getMintAddressField } from '../utils/utils.js';
13
+ import { PublicKey } from '@solana/web3.js';
14
+ /**
15
+ * Simplified Utxo class inspired by Tornado Cash Nova
16
+ * Based on: https://github.com/tornadocash/tornado-nova/blob/f9264eeffe48bf5e04e19d8086ee6ec58cdf0d9e/src/utxo.js
17
+ */
18
+ export class Utxo {
19
+ amount: BN;
20
+ blinding: BN;
21
+ keypair: Keypair;
22
+ index: number;
23
+ mintAddress: string;
24
+ version: 'v1' | 'v2';
25
+ private lightWasm: hasher.LightWasm;
26
+
27
+ constructor({
28
+ lightWasm,
29
+ amount = new BN(0),
30
+ /**
31
+ * Tornado nova doesn't use solana eddsa with curve 25519 but their own "keypair"
32
+ * which is:
33
+ * - private key: random [31;u8]
34
+ * - public key: PoseidonHash(privateKey)
35
+ *
36
+ * Generate a new keypair for each UTXO
37
+ */
38
+ keypair,
39
+ blinding = new BN(Math.floor(Math.random() * 1000000000)), // Use fixed value for consistency instead of randomBN()
40
+ index = 0,
41
+ mintAddress = '11111111111111111111111111111112', // Default to Solana native SOL mint address,
42
+ version = 'v2'
43
+ }: {
44
+ lightWasm: hasher.LightWasm,
45
+ amount?: BN | number | string,
46
+ keypair?: Keypair,
47
+ blinding?: BN | number | string,
48
+ index?: number,
49
+ mintAddress?: string,
50
+ version?: 'v1' | 'v2'
51
+ }) {
52
+ this.amount = new BN(amount.toString());
53
+ this.blinding = new BN(blinding.toString());
54
+ this.lightWasm = lightWasm;
55
+ this.keypair = keypair || new Keypair(ethers.Wallet.createRandom().privateKey, lightWasm);
56
+ this.index = index;
57
+ this.mintAddress = mintAddress;
58
+ this.version = version;
59
+ }
60
+
61
+ async getCommitment(): Promise<string> {
62
+ // return this.lightWasm.poseidonHashString([this.amount.toString(), this.keypair.pubkey.toString(), this.blinding.toString(), this.mintAddress]);
63
+ const mintAddressField = getMintAddressField(new PublicKey(this.mintAddress));
64
+ return this.lightWasm.poseidonHashString([
65
+ this.amount.toString(),
66
+ this.keypair.pubkey.toString(),
67
+ this.blinding.toString(),
68
+ mintAddressField
69
+ ]);
70
+ }
71
+
72
+ async getNullifier(): Promise<string> {
73
+ const commitmentValue = await this.getCommitment();
74
+ const signature = this.keypair.sign(commitmentValue, new BN(this.index).toString());
75
+
76
+ return this.lightWasm.poseidonHashString([commitmentValue, new BN(this.index).toString(), signature]);
77
+ }
78
+
79
+ /**
80
+ * Log all the UTXO's public properties and derived values in JSON format
81
+ * @returns Promise that resolves once all logging is complete
82
+ */
83
+ async log(): Promise<void> {
84
+ // Prepare the UTXO data object
85
+ const utxoData: any = {
86
+ amount: this.amount.toString(),
87
+ blinding: this.blinding.toString(),
88
+ index: this.index,
89
+ mintAddress: this.mintAddress,
90
+ keypair: {
91
+ pubkey: this.keypair.pubkey.toString()
92
+ }
93
+ };
94
+
95
+ // Add derived values
96
+ try {
97
+ utxoData.commitment = await this.getCommitment();
98
+ utxoData.nullifier = await this.getNullifier();
99
+ } catch (error: any) {
100
+ utxoData.error = error.message;
101
+ }
102
+
103
+ // Output as formatted JSON
104
+ console.log(JSON.stringify(utxoData, null, 2));
105
+ }
106
+ }
@@ -0,0 +1,78 @@
1
+ import {
2
+ Connection,
3
+ Keypair,
4
+ PublicKey,
5
+ SystemProgram,
6
+ AddressLookupTableProgram,
7
+ Transaction,
8
+ sendAndConfirmTransaction,
9
+ ComputeBudgetProgram,
10
+ VersionedTransaction,
11
+ TransactionMessage
12
+ } from '@solana/web3.js';
13
+ import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token';
14
+ import { logger } from './logger.js';
15
+
16
+
17
+ /**
18
+ * Helper function to use an existing ALT (recommended for production)
19
+ * Use create_alt.ts to create the ALT once, then hardcode the address and use this function
20
+ */
21
+ export async function useExistingALT(
22
+ connection: Connection,
23
+ altAddress: PublicKey
24
+ ): Promise<{ value: any } | null> {
25
+ try {
26
+ logger.debug(`Using existing ALT: ${altAddress.toString()}`);
27
+ const altAccount = await connection.getAddressLookupTable(altAddress);
28
+
29
+ if (altAccount.value) {
30
+ logger.debug(`✅ ALT found with ${altAccount.value.state.addresses.length} addresses`);
31
+ } else {
32
+ logger.error('❌ ALT not found');
33
+ }
34
+
35
+ return altAccount;
36
+ } catch (error) {
37
+ console.error('Error getting existing ALT:', error);
38
+ return null;
39
+ }
40
+ }
41
+
42
+
43
+ export function getProtocolAddressesWithMint(
44
+ programId: PublicKey,
45
+ authority: PublicKey,
46
+ treeAta: PublicKey,
47
+ feeRecipient: PublicKey,
48
+ feeRecipientAta: PublicKey
49
+ ): PublicKey[] {
50
+ // Derive global config PDA
51
+ const [globalConfigAccount] = PublicKey.findProgramAddressSync(
52
+ [Buffer.from('global_config')],
53
+ programId
54
+ );
55
+
56
+ // Derive tree accounts
57
+ const [treeAccount] = PublicKey.findProgramAddressSync(
58
+ [Buffer.from('merkle_tree')],
59
+ programId
60
+ );
61
+
62
+ return [
63
+ // Core program accounts (constant)
64
+ programId,
65
+ treeAccount,
66
+ treeAta,
67
+ globalConfigAccount,
68
+ authority,
69
+ feeRecipient,
70
+ feeRecipientAta,
71
+
72
+ // System programs (constant)
73
+ SystemProgram.programId,
74
+ ComputeBudgetProgram.programId,
75
+ ASSOCIATED_TOKEN_PROGRAM_ID,
76
+ TOKEN_PROGRAM_ID,
77
+ ];
78
+ }
@@ -0,0 +1,84 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import BN from 'bn.js';
3
+
4
+ export const FIELD_SIZE = new BN('21888242871839275222246405745257275088548364400416034343698204186575808495617')
5
+
6
+ export const PROGRAM_ID = process.env.NEXT_PUBLIC_PROGRAM_ID ? new PublicKey(process.env.NEXT_PUBLIC_PROGRAM_ID) : new PublicKey('SHLD11qQaY6h2N3ozXUWJEJitk1zGm9tvvAC7rjxpsp');
7
+
8
+ export const FEE_RECIPIENT = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM')
9
+
10
+ export const FETCH_UTXOS_GROUP_SIZE = 20_000
11
+
12
+ export const TRANSACT_IX_DISCRIMINATOR = Buffer.from([217, 149, 130, 143, 221, 52, 252, 119]);
13
+
14
+ export const TRANSACT_SPL_IX_DISCRIMINATOR = Buffer.from([154, 66, 244, 204, 78, 225, 163, 151]);
15
+
16
+ export const MERKLE_TREE_DEPTH = 26;
17
+
18
+ export const ALT_ADDRESS = process.env.NEXT_PUBLIC_ALT_ADDRESS ? new PublicKey(process.env.NEXT_PUBLIC_ALT_ADDRESS) : new PublicKey('HEN49U2ySJ85Vc78qprSW9y6mFDhs1NczRxyppNHjofe');
19
+
20
+ export const RELAYER_API_URL = process.env.NEXT_PUBLIC_RELAYER_API_URL ?? 'https://api3.privacycash.org';
21
+
22
+ export const SIGN_MESSAGE = `Privacy Money account sign in`
23
+
24
+ // localStorage cache keys
25
+ export const LSK_FETCH_OFFSET = 'fetch_offset'
26
+ export const LSK_ENCRYPTED_OUTPUTS = 'encrypted_outputs'
27
+
28
+ export const USDC_MINT = process.env.NEXT_PUBLIC_USDC_MINT ? new PublicKey(process.env.NEXT_PUBLIC_USDC_MINT) : new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v')
29
+
30
+ // Tree addresses for SOL (NOVASOL), NOVA, and legacy SOL
31
+ export const SOL_TREE_ADDRESS = process.env.NEXT_PUBLIC_SOL_TREE ? new PublicKey(process.env.NEXT_PUBLIC_SOL_TREE) : new PublicKey('DUnk81NBY7gUYRekT7GAwTU7jvabiVVwcMKZkRDs3dW9')
32
+ export const NOVA_TREE_ADDRESS = process.env.NEXT_PUBLIC_NOVA_TREE ? new PublicKey(process.env.NEXT_PUBLIC_NOVA_TREE) : new PublicKey('9CJ8jwAXJ7GrYtrMmD4gXBpTC59DMX7a91yMVpz25enZ')
33
+ // NOVASOL is the SOL tree used in Nova Privacy program
34
+ export const NOVASOL_TREE_ADDRESS = process.env.NEXT_PUBLIC_NOVASOL_TREE ? new PublicKey(process.env.NEXT_PUBLIC_NOVASOL_TREE) : new PublicKey('FZfbN2fY3aj7hTwh87dKMfuVwpaEipY3TuGpmYJnsWcG')
35
+ export const NOVA_MINT = process.env.NEXT_PUBLIC_NOVA_MINT ? new PublicKey(process.env.NEXT_PUBLIC_NOVA_MINT) : new PublicKey('3SkFJRqMPTKZLqKK1MmY2mvAm711FGAtJ9ZbL6r1coin')
36
+
37
+ const tokenList = ['sol', 'usdc', 'usdt', 'zec', 'ore', 'nova'] as const;
38
+ export type TokenList = typeof tokenList[number];
39
+ const splList = ['usdc', 'usdt', 'zec', 'ore', 'nova'] as const;
40
+ export type SplList = typeof splList[number];
41
+ export type Token = {
42
+ name: TokenList
43
+ prefix: string
44
+ units_per_token: number
45
+ pubkey: PublicKey
46
+ }
47
+ export const tokens: Token[] = [
48
+ {
49
+ name: 'sol',
50
+ pubkey: new PublicKey('So11111111111111111111111111111111111111112'),
51
+ prefix: '',
52
+ units_per_token: 1e9
53
+ },
54
+ {
55
+ name: 'usdc',
56
+ pubkey: process.env.NEXT_PUBLIC_USDC_MINT ? new PublicKey(process.env.NEXT_PUBLIC_USDC_MINT) : new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
57
+ prefix: 'usdc_',
58
+ units_per_token: 1e6
59
+ },
60
+ {
61
+ name: 'usdt',
62
+ pubkey: process.env.NEXT_PUBLIC_USDT_MINT ? new PublicKey(process.env.NEXT_PUBLIC_USDT_MINT) : new PublicKey('Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'),
63
+ prefix: 'usdt_',
64
+ units_per_token: 1e6
65
+ },
66
+ {
67
+ name: 'zec',
68
+ pubkey: process.env.NEXT_PUBLIC_ZEC_MINT ? new PublicKey(process.env.NEXT_PUBLIC_ZEC_MINT) : new PublicKey('A7bdiYdS5GjqGFtxf17ppRHtDKPkkRqbKtR27dxvQXaS'),
69
+ prefix: 'zec_',
70
+ units_per_token: 1e8
71
+ },
72
+ {
73
+ name: 'ore',
74
+ pubkey: process.env.NEXT_PUBLIC_ORE_MINT ? new PublicKey(process.env.NEXT_PUBLIC_ORE_MINT) : new PublicKey('oreoU2P8bN6jkk3jbaiVxYnG1dCXcYxwhwyK9jSybcp'),
75
+ prefix: 'ore_',
76
+ units_per_token: 1e11
77
+ },
78
+ {
79
+ name: 'nova',
80
+ pubkey: NOVA_MINT,
81
+ prefix: 'nova_',
82
+ units_per_token: 1e9
83
+ },
84
+ ]