testprivacycash-evm 1.2.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 (52) hide show
  1. package/.github/workflows/npm-publish.yml +55 -0
  2. package/README.md +17 -0
  3. package/circuits/transaction2.wasm +0 -0
  4. package/circuits/transaction2.zkey +0 -0
  5. package/dist/balance.d.ts +10 -0
  6. package/dist/balance.js +57 -0
  7. package/dist/deposit.d.ts +10 -0
  8. package/dist/deposit.js +345 -0
  9. package/dist/index.d.ts +10 -0
  10. package/dist/index.js +8 -0
  11. package/dist/utils/ERCPool.abi.json +929 -0
  12. package/dist/utils/EtherPool.abi.json +798 -0
  13. package/dist/utils/constants.d.ts +8 -0
  14. package/dist/utils/constants.js +11 -0
  15. package/dist/utils/db.d.ts +17 -0
  16. package/dist/utils/db.js +276 -0
  17. package/dist/utils/encryption.d.ts +8 -0
  18. package/dist/utils/encryption.js +34 -0
  19. package/dist/utils/keypair.d.ts +9 -0
  20. package/dist/utils/keypair.js +19 -0
  21. package/dist/utils/logger.d.ts +9 -0
  22. package/dist/utils/logger.js +35 -0
  23. package/dist/utils/networkConfig.d.ts +54 -0
  24. package/dist/utils/networkConfig.js +126 -0
  25. package/dist/utils/prover.d.ts +15 -0
  26. package/dist/utils/prover.js +30 -0
  27. package/dist/utils/remoteConfig.d.ts +21 -0
  28. package/dist/utils/remoteConfig.js +24 -0
  29. package/dist/utils/utils.d.ts +83 -0
  30. package/dist/utils/utils.js +275 -0
  31. package/dist/utils/utxo.d.ts +20 -0
  32. package/dist/utils/utxo.js +55 -0
  33. package/dist/withdraw.d.ts +12 -0
  34. package/dist/withdraw.js +253 -0
  35. package/package.json +37 -0
  36. package/src/balance.ts +74 -0
  37. package/src/deposit.ts +406 -0
  38. package/src/index.ts +16 -0
  39. package/src/utils/ERCPool.abi.json +929 -0
  40. package/src/utils/EtherPool.abi.json +798 -0
  41. package/src/utils/constants.ts +15 -0
  42. package/src/utils/db.ts +311 -0
  43. package/src/utils/encryption.ts +40 -0
  44. package/src/utils/keypair.ts +24 -0
  45. package/src/utils/logger.ts +42 -0
  46. package/src/utils/networkConfig.ts +169 -0
  47. package/src/utils/prover.ts +39 -0
  48. package/src/utils/remoteConfig.ts +49 -0
  49. package/src/utils/utils.ts +387 -0
  50. package/src/utils/utxo.ts +74 -0
  51. package/src/withdraw.ts +351 -0
  52. package/tsconfig.json +29 -0
@@ -0,0 +1,15 @@
1
+
2
+ import { BASE_NETWORK } from './networkConfig.js';
3
+
4
+ // Backward-compatible re-exports — all resolve to the Base network.
5
+ // For multi-chain support, pass a `network` (NetworkConfig | chainId) to SDK functions.
6
+ export const CONTRACT_ADDRESS = BASE_NETWORK.etherPoolAddress;
7
+ export const FEE_RECIPIENT_ADDRESS = BASE_NETWORK.feeRecipientAddress;
8
+ export const INDEXER_URL = BASE_NETWORK.indexerUrl;
9
+ export const BASE_SEPOLIA_RPC = BASE_NETWORK.rpcUrl;
10
+
11
+ export const SIGN_PRIVACY_MESSAGE = 'Privacy Money account sign in';
12
+
13
+ export const PRIVATE_USDC_CONTRACT_ADDRESS = BASE_NETWORK.usdcPoolAddress;
14
+ export const USDC_CONTRACT_ADDRESS = BASE_NETWORK.usdcTokenAddress;
15
+ export const USDC_DECIMALS = 6;
@@ -0,0 +1,311 @@
1
+
2
+ export interface StorageAdapter {
3
+ setup(): Promise<void>;
4
+ set(key: string, value: any): Promise<void>;
5
+ get(key: string): Promise<any | null>;
6
+ getAll(): Promise<any[]>;
7
+ }
8
+
9
+ const isBrowser = typeof window !== 'undefined' && typeof indexedDB !== 'undefined';
10
+ const isNodeRuntime =
11
+ typeof process !== 'undefined' &&
12
+ typeof process.versions !== 'undefined' &&
13
+ !!process.versions.node;
14
+ const isBunRuntime =
15
+ typeof process !== 'undefined' &&
16
+ typeof process.versions !== 'undefined' &&
17
+ !!(process.versions as any).bun;
18
+ const isDenoRuntime = typeof (globalThis as any).Deno !== 'undefined';
19
+
20
+ export class UniversalStorage {
21
+ private adapter!: StorageAdapter;
22
+
23
+ constructor(
24
+ private dbName: string,
25
+ private storeName: string
26
+ ) { }
27
+
28
+ async init(): Promise<UniversalStorage> {
29
+ if (isBrowser) {
30
+ this.adapter = new BrowserIDBAdapter(this.dbName, this.storeName);
31
+ await this.adapter.setup();
32
+ } else {
33
+ this.adapter = await this.createServerAdapter();
34
+ }
35
+ return this;
36
+ }
37
+
38
+ private async createServerAdapter(): Promise<StorageAdapter> {
39
+ // Prefer sqlite on Node.js when available because it is durable and scalable.
40
+ if (isNodeRuntime) {
41
+ const sqliteAdapter = new NodeSQLiteAdapter(this.dbName);
42
+ try {
43
+ await sqliteAdapter.setup();
44
+ return sqliteAdapter;
45
+ } catch (error: any) {
46
+ if (!isSqliteUnavailableError(error)) {
47
+ throw error;
48
+ }
49
+ }
50
+ }
51
+
52
+ // Bun/Deno/other runtimes can still persist using a JSON file.
53
+ const fileAdapter = new ServerFileAdapter(this.dbName, this.storeName);
54
+ try {
55
+ await fileAdapter.setup();
56
+ return fileAdapter;
57
+ } catch {
58
+ // Last resort for restricted server runtimes (e.g. no filesystem access).
59
+ const memoryAdapter = new ServerMemoryAdapter();
60
+ await memoryAdapter.setup();
61
+ return memoryAdapter;
62
+ }
63
+ }
64
+
65
+ async set(key: string, value: any): Promise<void> {
66
+ return await this.adapter.set(key, value);
67
+ }
68
+
69
+ async get<T = any>(key: string): Promise<T | null> {
70
+ return await this.adapter.get(key);
71
+ }
72
+
73
+ async getAll<T = any>(): Promise<T[]> {
74
+ return await this.adapter.getAll();
75
+ }
76
+ }
77
+
78
+ class BrowserIDBAdapter implements StorageAdapter {
79
+ private db: IDBDatabase | null = null;
80
+
81
+ constructor(private dbName: string, private storeName: string) { }
82
+
83
+ setup(): Promise<void> {
84
+ return new Promise((resolve, reject) => {
85
+ const request = indexedDB.open(this.dbName, 3);
86
+ request.onupgradeneeded = (e: any) => {
87
+ const db = e.target.result as IDBDatabase;
88
+ if (!db.objectStoreNames.contains(this.storeName)) {
89
+ db.createObjectStore(this.storeName);
90
+ }
91
+ };
92
+ request.onsuccess = (e: any) => {
93
+ this.db = e.target.result;
94
+ resolve();
95
+ };
96
+ request.onerror = () => reject(new Error("IndexedDB initialization failed"));
97
+ });
98
+ }
99
+
100
+ async set(key: string, value: any): Promise<void> {
101
+ if (!this.db) throw new Error("Database not initialized");
102
+ const tx = this.db.transaction(this.storeName, 'readwrite');
103
+ const store = tx.objectStore(this.storeName);
104
+ return new Promise((res, rej) => {
105
+ const req = store.put(value, key);
106
+ tx.oncomplete = () => res();
107
+ tx.onerror = () => rej(tx.error);
108
+ });
109
+ }
110
+
111
+ async get(key: string): Promise<any | null> {
112
+ if (!this.db) throw new Error("Database not initialized");
113
+ const tx = this.db.transaction(this.storeName, 'readonly');
114
+ const store = tx.objectStore(this.storeName);
115
+ return new Promise((res) => {
116
+ const req = store.get(key);
117
+ req.onsuccess = () => res(req.result || null);
118
+ });
119
+ }
120
+
121
+ async getAll(): Promise<any[]> {
122
+ if (!this.db) throw new Error("Database not initialized");
123
+ const tx = this.db.transaction(this.storeName, 'readonly');
124
+ const store = tx.objectStore(this.storeName);
125
+ return new Promise((res) => {
126
+ const req = store.getAll();
127
+ req.onsuccess = () => res(req.result || []);
128
+ });
129
+ }
130
+ }
131
+
132
+ class NodeSQLiteAdapter implements StorageAdapter {
133
+ private db: any;
134
+ private fileName: string;
135
+
136
+ constructor(dbName: string) {
137
+ this.fileName = `./cache/${dbName}.db`;
138
+ }
139
+
140
+ async setup(): Promise<void> {
141
+ const sqlite3 = loadNodeSqlite();
142
+ const { Database } = sqlite3;
143
+ this.db = new Database(this.fileName);
144
+ await this.run('CREATE TABLE IF NOT EXISTS storage (key TEXT PRIMARY KEY, value TEXT)');
145
+ }
146
+
147
+ private run(sql: string, ...params: any[]): Promise<void> {
148
+ return new Promise((resolve, reject) => {
149
+ this.db.run(sql, params, (err: any) => {
150
+ if (err) reject(err);
151
+ else resolve();
152
+ });
153
+ });
154
+ }
155
+
156
+ private queryGet(sql: string, ...params: any[]): Promise<any> {
157
+ return new Promise((resolve, reject) => {
158
+ this.db.get(sql, params, (err: any, row: any) => {
159
+ if (err) reject(err);
160
+ else resolve(row);
161
+ });
162
+ });
163
+ }
164
+
165
+ private queryAll(sql: string, ...params: any[]): Promise<any[]> {
166
+ return new Promise((resolve, reject) => {
167
+ this.db.all(sql, params, (err: any, rows: any[]) => {
168
+ if (err) reject(err);
169
+ else resolve(rows);
170
+ });
171
+ });
172
+ }
173
+
174
+ async set(key: string, value: any): Promise<void> {
175
+ await this.run('INSERT OR REPLACE INTO storage (key, value) VALUES (?, ?)', key, JSON.stringify(value));
176
+ }
177
+
178
+ async get(key: string): Promise<any | null> {
179
+ const row = await this.queryGet('SELECT value FROM storage WHERE key = ?', key);
180
+ return row ? JSON.parse(row.value) : null;
181
+ }
182
+
183
+ async getAll(): Promise<any[]> {
184
+ const rows = await this.queryAll('SELECT value FROM storage');
185
+ return rows.map((row: any) => JSON.parse(row.value));
186
+ }
187
+ }
188
+
189
+ function loadNodeSqlite(): any {
190
+ // Use indirect eval so bundlers don't try to include sqlite3 in browser builds.
191
+ const nodeRequire = (0, eval)('typeof require !== "undefined" ? require : undefined');
192
+ if (!nodeRequire) {
193
+ const error = new Error('sqlite3 is only available when Node.js require is present');
194
+ (error as any).code = 'SQLITE_UNAVAILABLE';
195
+ throw error;
196
+ }
197
+ try {
198
+ return nodeRequire('sqlite3');
199
+ } catch (error: any) {
200
+ if (error && /Cannot find module 'sqlite3'|Cannot find module \"sqlite3\"/i.test(String(error.message))) {
201
+ error.code = 'SQLITE_UNAVAILABLE';
202
+ }
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ function isSqliteUnavailableError(error: any): boolean {
208
+ return error?.code === 'SQLITE_UNAVAILABLE';
209
+ }
210
+
211
+ class ServerFileAdapter implements StorageAdapter {
212
+ private readonly fileName: string;
213
+ private data: Record<string, any> = {};
214
+
215
+ constructor(dbName: string, storeName: string) {
216
+ this.fileName = `./cache/${dbName}.${storeName}.json`;
217
+ }
218
+
219
+ async setup(): Promise<void> {
220
+ await ensureCacheDir();
221
+ const raw = await readFileSafe(this.fileName);
222
+ this.data = raw ? JSON.parse(raw) : {};
223
+ }
224
+
225
+ async set(key: string, value: any): Promise<void> {
226
+ this.data[key] = value;
227
+ await writeFileSafe(this.fileName, JSON.stringify(this.data));
228
+ }
229
+
230
+ async get(key: string): Promise<any | null> {
231
+ return key in this.data ? this.data[key] : null;
232
+ }
233
+
234
+ async getAll(): Promise<any[]> {
235
+ return Object.values(this.data);
236
+ }
237
+ }
238
+
239
+ class ServerMemoryAdapter implements StorageAdapter {
240
+ private data = new Map<string, any>();
241
+
242
+ async setup(): Promise<void> {
243
+ return;
244
+ }
245
+
246
+ async set(key: string, value: any): Promise<void> {
247
+ this.data.set(key, value);
248
+ }
249
+
250
+ async get(key: string): Promise<any | null> {
251
+ return this.data.has(key) ? this.data.get(key) : null;
252
+ }
253
+
254
+ async getAll(): Promise<any[]> {
255
+ return Array.from(this.data.values());
256
+ }
257
+ }
258
+
259
+ async function ensureCacheDir(): Promise<void> {
260
+ if (isDenoRuntime) {
261
+ await (globalThis as any).Deno.mkdir('./cache', { recursive: true });
262
+ return;
263
+ }
264
+
265
+ if (isNodeRuntime || isBunRuntime) {
266
+ const fs = await import('node:fs/promises');
267
+ await fs.mkdir('./cache', { recursive: true });
268
+ return;
269
+ }
270
+
271
+ throw new Error('Filesystem storage is not available in this runtime');
272
+ }
273
+
274
+ async function readFileSafe(path: string): Promise<string | null> {
275
+ try {
276
+ if (isDenoRuntime) {
277
+ return await (globalThis as any).Deno.readTextFile(path);
278
+ }
279
+
280
+ if (isNodeRuntime || isBunRuntime) {
281
+ const fs = await import('node:fs/promises');
282
+ return await fs.readFile(path, 'utf8');
283
+ }
284
+ } catch (error: any) {
285
+ if (isMissingFileError(error)) {
286
+ return null;
287
+ }
288
+ throw error;
289
+ }
290
+
291
+ throw new Error('Filesystem storage is not available in this runtime');
292
+ }
293
+
294
+ async function writeFileSafe(path: string, content: string): Promise<void> {
295
+ if (isDenoRuntime) {
296
+ await (globalThis as any).Deno.writeTextFile(path, content);
297
+ return;
298
+ }
299
+
300
+ if (isNodeRuntime || isBunRuntime) {
301
+ const fs = await import('node:fs/promises');
302
+ await fs.writeFile(path, content, 'utf8');
303
+ return;
304
+ }
305
+
306
+ throw new Error('Filesystem storage is not available in this runtime');
307
+ }
308
+
309
+ function isMissingFileError(error: any): boolean {
310
+ return error?.code === 'ENOENT' || error?.name === 'NotFound';
311
+ }
@@ -0,0 +1,40 @@
1
+ import crypto from 'crypto';
2
+ import { ethers } from 'ethers';
3
+ import { Keypair } from './keypair.js';
4
+
5
+ export function deriveKeys(signature: string) {
6
+ const encryptionKeyHex = ethers.utils.keccak256(signature);
7
+ const encryptionKey = Buffer.from(encryptionKeyHex.slice(2), 'hex');
8
+ const utxoPrivateKey = ethers.utils.keccak256(encryptionKey);
9
+ const keypair = new Keypair(utxoPrivateKey);
10
+ return { encryptionKey, utxoPrivateKey, keypair };
11
+ }
12
+
13
+ // export async function signMessage(signer: ethers.Signer) {
14
+ // const signature = await signer.signMessage(SIGN_IN_MESSAGe);
15
+ // return deriveKeys(signature);
16
+ // }
17
+
18
+ export function encrypt(data: string | Buffer, encryptionKey: Buffer): string {
19
+ const dataBuffer = typeof data === 'string' ? Buffer.from(data) : data;
20
+ const iv = crypto.randomBytes(12);
21
+ const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey as any, iv as any);
22
+ const encrypted = Buffer.concat([cipher.update(dataBuffer as any), cipher.final()] as any[]);
23
+ const authTag = cipher.getAuthTag();
24
+ const result = Buffer.concat([iv, authTag, encrypted] as any[]);
25
+ return '0x' + result.toString('hex');
26
+ }
27
+
28
+ export function decrypt(encryptedData: string | Buffer, encryptionKey: Buffer): Buffer {
29
+ const buf = typeof encryptedData === 'string'
30
+ ? Buffer.from(encryptedData.replace(/^0x/, ''), 'hex')
31
+ : (encryptedData as any);
32
+
33
+ const iv = buf.slice(0, 12);
34
+ const authTag = buf.slice(12, 28);
35
+ const data = buf.slice(28);
36
+
37
+ const decipher = crypto.createDecipheriv('aes-256-gcm', encryptionKey as any, iv as any);
38
+ decipher.setAuthTag(authTag as any);
39
+ return Buffer.concat([decipher.update(data as any), decipher.final()] as any[]);
40
+ }
@@ -0,0 +1,24 @@
1
+ import { BigNumber, ethers } from 'ethers';
2
+ import { poseidonHash, toFixedHex } from './utils.js';
3
+
4
+ export class Keypair {
5
+ public privkey: string;
6
+ public pubkey: BigNumber;
7
+
8
+ constructor(privkey: string = ethers.Wallet.createRandom().privateKey) {
9
+ this.privkey = privkey;
10
+ this.pubkey = poseidonHash([this.privkey]);
11
+ }
12
+
13
+ toString(): string {
14
+ return toFixedHex(this.pubkey);
15
+ }
16
+
17
+ address(): string {
18
+ return this.toString();
19
+ }
20
+
21
+ sign(commitment: any, merklePath: any): BigNumber {
22
+ return poseidonHash([this.privkey, commitment, merklePath]);
23
+ }
24
+ }
@@ -0,0 +1,42 @@
1
+ // log level
2
+ export type LogLevel = "debug" | "info" | "warn" | "error";
3
+
4
+ export type LoggerFn = (level: LogLevel, message: string) => void;
5
+
6
+ const defaultLogger: LoggerFn = (level, message) => {
7
+ const prefix = `[${level.toUpperCase()}]`;
8
+ console.log(prefix, message);
9
+ };
10
+
11
+ let userLogger: LoggerFn = defaultLogger;
12
+
13
+ export function setLogger(logger: LoggerFn) {
14
+ userLogger = logger;
15
+ }
16
+
17
+ function argToStr(args: unknown[]) {
18
+ return args.map(arg => {
19
+ if (typeof arg === "object" && arg !== null) {
20
+ try {
21
+ return JSON.stringify(arg);
22
+ } catch {
23
+ return String(arg);
24
+ }
25
+ }
26
+ return String(arg);
27
+ }).join(" ");
28
+ }
29
+ export const logger = {
30
+ debug: (...args: unknown[]) => {
31
+ userLogger('debug', argToStr(args))
32
+ },
33
+ info: (...args: unknown[]) => {
34
+ userLogger('info', argToStr(args))
35
+ },
36
+ warn: (...args: unknown[]) => {
37
+ userLogger('warn', argToStr(args))
38
+ },
39
+ error: (...args: unknown[]) => {
40
+ userLogger('error', argToStr(args))
41
+ },
42
+ }
@@ -0,0 +1,169 @@
1
+ export type Erc20Token = 'usdc' | 'usdt';
2
+ export type NativeToken = 'eth' | 'bnb';
3
+ export type PrivacyToken = NativeToken | Erc20Token;
4
+ export type SupportedChain = 'base' | 'eth' | 'bnb';
5
+
6
+ const EVM_INDEXER_URL = process.env.NEXT_PUBLIC_EVM_INDEXER_URL || process.env.EVM_INDEXER_URL || 'https://evm.privacycash.org';
7
+
8
+ function getIndexerRpcUrl(chain: SupportedChain) {
9
+ return `${EVM_INDEXER_URL.replace(/\/$/, '')}/rpc/${chain}`;
10
+ }
11
+
12
+ export interface NetworkConfig {
13
+ chainId: number;
14
+ /** Short identifier used in API calls and DB table prefixes. */
15
+ chainKey: SupportedChain;
16
+ rpcUrl: string;
17
+ indexerUrl: string;
18
+ etherPoolAddress: string;
19
+ usdcPoolAddress: string;
20
+ usdcTokenAddress: string;
21
+ usdcDecimals: number;
22
+ usdtPoolAddress: string;
23
+ usdtTokenAddress: string;
24
+ usdtDecimals: number;
25
+ feeRecipientAddress: string;
26
+ /** Namespace prefix for local UTXO cache files. Base keeps legacy names. */
27
+ cachePrefix: string;
28
+ /** Average block time in ms — used to scale confirmation polling timeouts. */
29
+ blockTimeMs: number;
30
+ /** Native token used by this chain. */
31
+ nativeToken?: NativeToken;
32
+ /** Human-readable native token symbol used in logs and errors. */
33
+ nativeSymbol?: 'ETH' | 'BNB';
34
+ /** Optional block explorer base URL. */
35
+ blockExplorerUrl?: string;
36
+ }
37
+
38
+ export const BASE_NETWORK: NetworkConfig = {
39
+ chainId: 8453,
40
+ chainKey: 'base',
41
+ rpcUrl: process.env.NEXT_PUBLIC_BASE_RPC || process.env.BASE_RPC || getIndexerRpcUrl('base'),
42
+ indexerUrl: EVM_INDEXER_URL,
43
+ etherPoolAddress: '0x7F673790C08Ddf27c0Aa6fa9526CCC8dAaB081Ec',
44
+ usdcPoolAddress: '0xe91dd4AB03909f5CEb87f42B4308B222995a905b',
45
+ usdcTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
46
+ usdcDecimals: 6,
47
+ usdtPoolAddress: '',
48
+ usdtTokenAddress: '',
49
+ usdtDecimals: 6,
50
+ feeRecipientAddress: '0x8D772A68f2327409a7bb3F96f549297AEdf9312B',
51
+ cachePrefix: 'base',
52
+ blockTimeMs: 2000,
53
+ nativeToken: 'eth',
54
+ nativeSymbol: 'ETH',
55
+ };
56
+
57
+ export const ETH_NETWORK: NetworkConfig = {
58
+ chainId: 1,
59
+ chainKey: 'eth',
60
+ rpcUrl: process.env.NEXT_PUBLIC_ETH_RPC || process.env.ETH_RPC || getIndexerRpcUrl('eth'),
61
+ indexerUrl: EVM_INDEXER_URL,
62
+ etherPoolAddress: process.env.NEXT_PUBLIC_ETH_ETHER_POOL_ADDRESS || '0x77A10AE3E513c2D73D73eb52212c6918C8830dd0',
63
+ usdcPoolAddress: '',
64
+ usdcTokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
65
+ usdcDecimals: 6,
66
+ usdtPoolAddress: process.env.NEXT_PUBLIC_ETH_USDT_POOL_ADDRESS || '0xC88F4dF2B6EdDd6B6Bdf95A0177f50C90Fa7527f',
67
+ usdtTokenAddress: process.env.NEXT_PUBLIC_ETH_USDT_TOKEN_ADDRESS || '0xdAC17F958D2ee523a2206206994597C13D831ec7',
68
+ usdtDecimals: 6,
69
+ feeRecipientAddress: process.env.NEXT_PUBLIC_ETH_FEE_RECIPIENT_ADDRESS || '0x8D772A68f2327409a7bb3F96f549297AEdf9312B',
70
+ cachePrefix: 'eth',
71
+ blockTimeMs: 12000,
72
+ nativeToken: 'eth',
73
+ nativeSymbol: 'ETH',
74
+ };
75
+
76
+ const BNB_ALCHEMY_RPC = process.env.ALCHEMY_KEY
77
+ ? `https://bnb-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`
78
+ : getIndexerRpcUrl('bnb');
79
+
80
+ export const BNB_NETWORK: NetworkConfig = {
81
+ chainId: 56,
82
+ chainKey: 'bnb',
83
+ rpcUrl: process.env.NEXT_PUBLIC_BNB_RPC || process.env.BNB_RPC || BNB_ALCHEMY_RPC,
84
+ indexerUrl: EVM_INDEXER_URL,
85
+ etherPoolAddress: '0x22D8509E7AF58b1EaFB311f8F76E81dC3a391F77',
86
+ usdcPoolAddress: '',
87
+ usdcTokenAddress: '',
88
+ usdcDecimals: 6,
89
+ usdtPoolAddress: '',
90
+ usdtTokenAddress: '',
91
+ usdtDecimals: 18,
92
+ feeRecipientAddress: '0x8D772A68f2327409a7bb3F96f549297AEdf9312B',
93
+ cachePrefix: 'bnb',
94
+ blockTimeMs: 1000,
95
+ nativeToken: 'bnb',
96
+ nativeSymbol: 'BNB',
97
+ blockExplorerUrl: 'https://bscscan.com/',
98
+ };
99
+
100
+ export const NETWORKS: Record<number, NetworkConfig> = {
101
+ 8453: BASE_NETWORK,
102
+ 1: ETH_NETWORK,
103
+ 56: BNB_NETWORK,
104
+ };
105
+
106
+ export function getNetworkConfig(chainId: number): NetworkConfig {
107
+ const config = NETWORKS[chainId];
108
+ if (!config) {
109
+ throw new Error(
110
+ `Unsupported chain ID: ${chainId}. Supported: ${Object.keys(NETWORKS).join(', ')}`,
111
+ );
112
+ }
113
+ return config;
114
+ }
115
+
116
+ /** Returns the default network driven by NEXT_PUBLIC_CHAIN_ID env var, falling back to Base. */
117
+ export function getDefaultNetworkConfig(): NetworkConfig {
118
+ const chainId = Number(process.env.NEXT_PUBLIC_CHAIN_ID) || 8453;
119
+ return getNetworkConfig(chainId);
120
+ }
121
+
122
+ /** Resolve a NetworkConfig from an object, a chain ID number, or the env-driven default. */
123
+ export function resolveNetwork(network?: NetworkConfig | number): NetworkConfig {
124
+ if (network === undefined || network === null) return getDefaultNetworkConfig();
125
+ if (typeof network === 'number') return getNetworkConfig(network);
126
+ return network;
127
+ }
128
+
129
+ /** Resolve an omitted token to the selected chain's native token and reject cross-chain native symbols. */
130
+ export function resolvePrivacyToken(net: NetworkConfig, token?: PrivacyToken): PrivacyToken {
131
+ const nativeToken: NativeToken = net.nativeToken ?? (net.chainKey === 'bnb' ? 'bnb' : 'eth');
132
+ const resolved = token ?? nativeToken;
133
+ if ((resolved === 'eth' || resolved === 'bnb') && resolved !== nativeToken) {
134
+ throw new Error(`${resolved.toUpperCase()} is not supported on ${net.chainKey}`);
135
+ }
136
+ return resolved;
137
+ }
138
+
139
+ export function isNativeToken(net: NetworkConfig, token: PrivacyToken): boolean {
140
+ const nativeToken: NativeToken = net.nativeToken ?? (net.chainKey === 'bnb' ? 'bnb' : 'eth');
141
+ return token === nativeToken;
142
+ }
143
+
144
+ export function getErc20TokenConfig(net: NetworkConfig, token: PrivacyToken) {
145
+ if (isNativeToken(net, token)) return null;
146
+ if (token === 'eth' || token === 'bnb') {
147
+ throw new Error(`${token.toUpperCase()} is not supported on ${net.chainKey}`);
148
+ }
149
+ const config = token === 'usdc'
150
+ ? {
151
+ token,
152
+ symbol: 'USDC' as const,
153
+ poolAddress: net.usdcPoolAddress,
154
+ tokenAddress: net.usdcTokenAddress,
155
+ decimals: net.usdcDecimals,
156
+ }
157
+ : {
158
+ token,
159
+ symbol: 'USDT' as const,
160
+ poolAddress: net.usdtPoolAddress,
161
+ tokenAddress: net.usdtTokenAddress,
162
+ decimals: net.usdtDecimals,
163
+ };
164
+
165
+ if (!config.poolAddress || !config.tokenAddress) {
166
+ throw new Error(`${token.toUpperCase()} is not supported on ${net.chainKey}`);
167
+ }
168
+ return config;
169
+ }
@@ -0,0 +1,39 @@
1
+ // @ts-ignore
2
+ import { utils } from 'ffjavascript';
3
+ import { toFixedHex } from './utils.js';
4
+
5
+ /**
6
+ * Generates a Zero-Knowledge proof for a transaction.
7
+ * Works across Node.js, Bun, Deno, and Browser — fully in-memory, no tmp files.
8
+ *
9
+ * Bun/Deno use single-threaded mode: passing { singleThread: true } to both
10
+ * wtnsCalcOptions (5th arg) and proverOptions (6th arg) of groth16.fullProve
11
+ * prevents snarkjs from spawning web-worker threads entirely, avoiding the
12
+ * Bun v1.3.x crash where worker threads dispatch plain Error objects to
13
+ * EventTarget.dispatchEvent (which requires a proper Event instance in Bun).
14
+ */
15
+ export async function prove(input: any, keyBasePath: string) {
16
+ // @ts-ignore
17
+ const useSingleThread = typeof Bun !== 'undefined' || typeof Deno !== 'undefined';
18
+ const singleThreadOpts = useSingleThread ? { singleThread: true } : undefined;
19
+
20
+ // @ts-ignore
21
+ const snarkjs = await import('snarkjs');
22
+ const { proof } = await snarkjs.groth16.fullProve(
23
+ utils.stringifyBigInts(input),
24
+ `${keyBasePath}.wasm`,
25
+ `${keyBasePath}.zkey`,
26
+ undefined, // logger
27
+ singleThreadOpts, // wtnsCalcOptions: controls witness calculation threading
28
+ singleThreadOpts, // proverOptions: controls proving threading
29
+ );
30
+
31
+ const pA = [toFixedHex(proof.pi_a[0]), toFixedHex(proof.pi_a[1])];
32
+ const pB = [
33
+ [toFixedHex(proof.pi_b[0][1]), toFixedHex(proof.pi_b[0][0])],
34
+ [toFixedHex(proof.pi_b[1][1]), toFixedHex(proof.pi_b[1][0])],
35
+ ];
36
+ const pC = [toFixedHex(proof.pi_c[0]), toFixedHex(proof.pi_c[1])];
37
+
38
+ return { pA, pB, pC };
39
+ }
@@ -0,0 +1,49 @@
1
+ import { logger } from './logger.js';
2
+ import { NetworkConfig, PrivacyToken, resolveNetwork } from './networkConfig.js';
3
+
4
+ export interface FeeSnapshot {
5
+ id: string;
6
+ chain: 'eth';
7
+ feeRateBps: number;
8
+ validFrom: number;
9
+ expiresAt: number;
10
+ rentFeeUnits: Partial<Record<PrivacyToken, string>>;
11
+ rentFees?: Partial<Record<PrivacyToken, number>>;
12
+ }
13
+
14
+ export interface RemoteConfig {
15
+ prices: Partial<Record<PrivacyToken, number>>;
16
+ minimum_withdrawal: Record<PrivacyToken, number>;
17
+ minimum_deposit: Record<PrivacyToken, number>;
18
+ rent_fees: Record<PrivacyToken, number>;
19
+ fee_rate: number;
20
+ feeSnapshot?: FeeSnapshot | null;
21
+ }
22
+
23
+ const CACHE_TTL_MS = 60 * 1000; // 1 minute
24
+
25
+ type CacheEntry = { config: RemoteConfig; expiresAt: number };
26
+ const configCache = new Map<number, CacheEntry>();
27
+
28
+ export async function getRemoteConfig(network?: NetworkConfig | number, options?: { forceRefresh?: boolean }): Promise<RemoteConfig> {
29
+ const net = resolveNetwork(network);
30
+ const now = Date.now();
31
+ const cached = configCache.get(net.chainId);
32
+ if (!options?.forceRefresh && cached && now < cached.expiresAt) return cached.config;
33
+
34
+ if (!net.indexerUrl) {
35
+ throw new Error(`No indexer URL configured for chain ${net.chainId} (${net.chainKey})`);
36
+ }
37
+
38
+ const configUrl = new URL(`${net.indexerUrl.replace(/\/$/, '')}/config`);
39
+ configUrl.searchParams.set('chain', net.chainKey);
40
+
41
+ const res = await fetch(configUrl.toString());
42
+ if (!res.ok) {
43
+ throw new Error(`Failed to fetch remote config: HTTP ${res.status}`);
44
+ }
45
+ const data = await res.json() as RemoteConfig;
46
+ configCache.set(net.chainId, { config: data, expiresAt: now + CACHE_TTL_MS });
47
+ logger.debug('Remote config loaded successfully');
48
+ return data;
49
+ }