bstest001 0.0.4 → 0.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bstest001",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -17,7 +17,8 @@
17
17
  "ethers": "^5.0.0",
18
18
  "ffjavascript": "^0.2.36",
19
19
  "poseidon-lite": "^0.3.0",
20
- "snarkjs": "^0.7.5"
20
+ "snarkjs": "^0.7.5",
21
+ "sqlite3": "^6.0.1"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@types/node": "^25.3.3",
package/src/balance.ts CHANGED
@@ -34,7 +34,8 @@ export async function getBalance({ provider: rawProvider, signature, address }:
34
34
  const unspent = await findUnspentUtxos({
35
35
  etherPool,
36
36
  encryptionKey,
37
- keypair
37
+ keypair,
38
+ address
38
39
  });
39
40
 
40
41
  logger.debug(`Unspent UTXOs: ${unspent.length}`);
package/src/deposit.ts CHANGED
@@ -63,7 +63,7 @@ export async function deposit({ provider: rawProvider, depositAmountInput, keyBa
63
63
  const unspent = await findUnspentUtxos({
64
64
  etherPool,
65
65
  encryptionKey,
66
- keypair
66
+ keypair, address
67
67
  });
68
68
  logger.debug(`Unspent UTXOs found: ${unspent.length}`);
69
69
 
@@ -0,0 +1,151 @@
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
+ export class UniversalStorage {
11
+ private adapter!: StorageAdapter;
12
+
13
+ constructor(
14
+ private dbName: string = 'PrivacyCash',
15
+ private storeName: string = 'evm'
16
+ ) { }
17
+
18
+ async init(): Promise<UniversalStorage> {
19
+ if (isBrowser) {
20
+ this.adapter = new BrowserIDBAdapter(this.dbName, this.storeName);
21
+ } else {
22
+ // Dynamically load adapter in Node.js environment
23
+ this.adapter = new NodeSQLiteAdapter(this.dbName);
24
+ }
25
+ await this.adapter.setup();
26
+ return this;
27
+ }
28
+
29
+ async set(key: string, value: any): Promise<void> {
30
+ return await this.adapter.set(key, value);
31
+ }
32
+
33
+ async get<T = any>(key: string): Promise<T | null> {
34
+ return await this.adapter.get(key);
35
+ }
36
+
37
+ async getAll<T = any>(): Promise<T[]> {
38
+ return await this.adapter.getAll();
39
+ }
40
+ }
41
+
42
+ class BrowserIDBAdapter implements StorageAdapter {
43
+ private db: IDBDatabase | null = null;
44
+
45
+ constructor(private dbName: string, private storeName: string) { }
46
+
47
+ setup(): Promise<void> {
48
+ return new Promise((resolve, reject) => {
49
+ const request = indexedDB.open(this.dbName, 1);
50
+ request.onupgradeneeded = (e: any) => {
51
+ const db = e.target.result as IDBDatabase;
52
+ if (!db.objectStoreNames.contains(this.storeName)) {
53
+ db.createObjectStore(this.storeName);
54
+ }
55
+ };
56
+ request.onsuccess = (e: any) => {
57
+ this.db = e.target.result;
58
+ resolve();
59
+ };
60
+ request.onerror = () => reject(new Error("IndexedDB initialization failed"));
61
+ });
62
+ }
63
+
64
+ async set(key: string, value: any): Promise<void> {
65
+ if (!this.db) throw new Error("Database not initialized");
66
+ const tx = this.db.transaction(this.storeName, 'readwrite');
67
+ const store = tx.objectStore(this.storeName);
68
+ return new Promise((res, rej) => {
69
+ const req = store.put(value, key);
70
+ tx.oncomplete = () => res();
71
+ tx.onerror = () => rej(tx.error);
72
+ });
73
+ }
74
+
75
+ async get(key: string): Promise<any | null> {
76
+ if (!this.db) throw new Error("Database not initialized");
77
+ const tx = this.db.transaction(this.storeName, 'readonly');
78
+ const store = tx.objectStore(this.storeName);
79
+ return new Promise((res) => {
80
+ const req = store.get(key);
81
+ req.onsuccess = () => res(req.result || null);
82
+ });
83
+ }
84
+
85
+ async getAll(): Promise<any[]> {
86
+ if (!this.db) throw new Error("Database not initialized");
87
+ const tx = this.db.transaction(this.storeName, 'readonly');
88
+ const store = tx.objectStore(this.storeName);
89
+ return new Promise((res) => {
90
+ const req = store.getAll();
91
+ req.onsuccess = () => res(req.result || []);
92
+ });
93
+ }
94
+ }
95
+
96
+ class NodeSQLiteAdapter implements StorageAdapter {
97
+ private db: any;
98
+ private fileName: string;
99
+
100
+ constructor(dbName: string) {
101
+ this.fileName = `./cache/${dbName}.db`;
102
+ }
103
+
104
+ async setup(): Promise<void> {
105
+ const sqlite3 = await import('sqlite3');
106
+ const { Database } = sqlite3.default;
107
+ this.db = new Database(this.fileName);
108
+ await this.run('CREATE TABLE IF NOT EXISTS storage (key TEXT PRIMARY KEY, value TEXT)');
109
+ }
110
+
111
+ private run(sql: string, ...params: any[]): Promise<void> {
112
+ return new Promise((resolve, reject) => {
113
+ this.db.run(sql, params, (err: any) => {
114
+ if (err) reject(err);
115
+ else resolve();
116
+ });
117
+ });
118
+ }
119
+
120
+ private queryGet(sql: string, ...params: any[]): Promise<any> {
121
+ return new Promise((resolve, reject) => {
122
+ this.db.get(sql, params, (err: any, row: any) => {
123
+ if (err) reject(err);
124
+ else resolve(row);
125
+ });
126
+ });
127
+ }
128
+
129
+ private queryAll(sql: string, ...params: any[]): Promise<any[]> {
130
+ return new Promise((resolve, reject) => {
131
+ this.db.all(sql, params, (err: any, rows: any[]) => {
132
+ if (err) reject(err);
133
+ else resolve(rows);
134
+ });
135
+ });
136
+ }
137
+
138
+ async set(key: string, value: any): Promise<void> {
139
+ await this.run('INSERT OR REPLACE INTO storage (key, value) VALUES (?, ?)', key, JSON.stringify(value));
140
+ }
141
+
142
+ async get(key: string): Promise<any | null> {
143
+ const row = await this.queryGet('SELECT value FROM storage WHERE key = ?', key);
144
+ return row ? JSON.parse(row.value) : null;
145
+ }
146
+
147
+ async getAll(): Promise<any[]> {
148
+ const rows = await this.queryAll('SELECT value FROM storage');
149
+ return rows.map((row: any) => JSON.parse(row.value));
150
+ }
151
+ }
@@ -2,10 +2,13 @@ import crypto from 'crypto';
2
2
  import { BigNumber, ethers } from 'ethers';
3
3
  import { poseidon1, poseidon2, poseidon3, poseidon4 } from 'poseidon-lite';
4
4
  import { INDEXER_URL } from './constants.js';
5
+ import { UniversalStorage } from './db.js';
5
6
  import { logger } from './logger.js';
6
7
  import { prove } from './prover.js';
7
8
  import { Utxo } from './utxo.js';
8
9
 
10
+ const storage = new UniversalStorage('PrivacyCashDB', 'evm');
11
+
9
12
  export const FIELD_SIZE = BigNumber.from(
10
13
  '21888242871839275222246405745257275088548364400416034343698204186575808495617',
11
14
  );
@@ -230,51 +233,86 @@ export async function prepareTransaction({
230
233
  }
231
234
 
232
235
  export async function findUnspentUtxos({
236
+ address,
233
237
  etherPool,
234
238
  encryptionKey,
235
239
  keypair,
240
+ start = 0,
236
241
  }: {
242
+ address: string;
237
243
  etherPool: ethers.Contract;
238
244
  encryptionKey: Buffer;
239
245
  keypair: any;
246
+ start?: number;
240
247
  }) {
241
- let start = 0
242
- const limit = 1000
243
- let hasMore = true
244
- const candidates: { utxo: Utxo; nullifier: string }[] = []
248
+ await storage.init();
249
+ const offsetKey = `offset_${address}`;
250
+ const knownKey = `keo_${address}`;
251
+ const cachedOffset = (await storage.get(offsetKey)) || start;
252
+ const cachedKnown = (await storage.get(knownKey)) || [];
253
+ let knownEncryptedOutputs = new Map<string, number>();
254
+ const candidates: { utxo: Utxo; nullifier: string }[] = [];
255
+
256
+ // print number of knownEncryptedOutputs
257
+ logger.debug(`Cached offset: ${cachedOffset}`);
258
+ logger.debug(`Cached known encrypted outputs: ${cachedKnown.length}`);
259
+
260
+ // Process cached known encrypted outputs
261
+ for (const item of cachedKnown) {
262
+ knownEncryptedOutputs.set(item.encrypted, item.index);
263
+ try {
264
+ const utxo = Utxo.decrypt(encryptionKey, item.encrypted, item.index, keypair);
265
+ if (!utxo.amount.isZero()) {
266
+ utxo.index = item.index;
267
+ const nullifier = toFixedHex(utxo.getNullifier());
268
+ candidates.push({ utxo, nullifier });
269
+ }
270
+ } catch {
271
+ // Decryption failed — this output belongs to someone else
272
+ }
273
+ }
245
274
 
275
+ let startIndex = cachedOffset;
276
+ const limit = 1000;
277
+ let hasMore = true;
246
278
  while (hasMore) {
247
- let url = `${INDEXER_URL}/get_encrypted?start=${start}&end=${start + limit}`
248
- logger.debug(`Fetching encrypted UTXOs from indexer: ${url}`)
249
- const res = await fetch(url)
279
+ let url = `${INDEXER_URL}/get_encrypted?start=${startIndex}&end=${startIndex + limit}`;
280
+ logger.debug(`Fetching encrypted UTXOs from indexer: ${url}`);
281
+ const res = await fetch(url);
250
282
  if (!res.ok) {
251
- throw new Error(`Failed to fetch encrypted UTXOs: ${res.statusText}`)
283
+ throw new Error(`Failed to fetch encrypted UTXOs: ${res.statusText}`);
252
284
  }
253
- const data = await res.json()
254
- const { encrypted_outputs, hasMore: more, start: realStart } = data
285
+ const data = await res.json();
286
+ const { encrypted_outputs, hasMore: more, start: realStart } = data;
255
287
 
256
288
  if (encrypted_outputs && encrypted_outputs.length > 0) {
257
289
  encrypted_outputs.forEach((encryptedOutput: string, i: number) => {
258
- const index = (realStart ?? start) + i
259
- try {
260
- const utxo = Utxo.decrypt(encryptionKey, encryptedOutput, Number(index), keypair)
261
- if (!utxo.amount.isZero()) {
262
- utxo.index = index
263
- const nullifier = toFixedHex(utxo.getNullifier())
264
- candidates.push({ utxo, nullifier })
290
+ const index = (realStart ?? startIndex) + i;
291
+ if (!knownEncryptedOutputs.has(encryptedOutput)) {
292
+ try {
293
+ const utxo = Utxo.decrypt(encryptionKey, encryptedOutput, index, keypair);
294
+ if (!utxo.amount.isZero()) {
295
+ knownEncryptedOutputs.set(encryptedOutput, index);
296
+ utxo.index = index;
297
+ const nullifier = toFixedHex(utxo.getNullifier());
298
+ candidates.push({ utxo, nullifier });
299
+ }
300
+ } catch {
301
+ // Decryption failed — this output belongs to someone else
265
302
  }
266
- } catch {
267
- // Decryption failed — this output belongs to someone else
268
303
  }
269
- })
304
+ });
270
305
  }
271
306
 
272
- hasMore = more
273
- start += limit
307
+ hasMore = more;
308
+ startIndex += encrypted_outputs.length;
274
309
  }
310
+ // Cache the updated offset and known encrypted outputs
311
+ await storage.set(offsetKey, startIndex);
312
+ await storage.set(knownKey, Array.from(knownEncryptedOutputs.entries()).map(([encrypted, index]) => ({ encrypted, index })));
275
313
 
276
314
  // log candidates length
277
- logger.debug(`Found ${candidates.length} candidate UTXOs, checking which are unspent...`)
315
+ logger.debug(`Found ${candidates.length} candidate UTXOs, checking which are unspent...`);
278
316
 
279
317
  const unspent: Utxo[] = []
280
318
  const spentCheckChunkSize = 256
package/src/withdraw.ts CHANGED
@@ -8,12 +8,13 @@ import { Utxo } from './utils/utxo.js';
8
8
 
9
9
  const FLAT_FEE = ethers.utils.parseEther(RENT_FEE.toString())
10
10
 
11
- export async function withdraw({ provider: rawProvider, withdrawAmountInput, recipient, keyBasePath, signature }: {
11
+ export async function withdraw({ provider: rawProvider, withdrawAmountInput, recipient, keyBasePath, signature, address }: {
12
12
  provider: any,
13
13
  withdrawAmountInput: number,
14
14
  recipient: string,
15
15
  keyBasePath: string,
16
16
  signature: string,
17
+ address: string
17
18
  }) {
18
19
  const provider: ethers.providers.Provider = rawProvider?.request
19
20
  ? new ethers.providers.Web3Provider(rawProvider)
@@ -40,7 +41,8 @@ export async function withdraw({ provider: rawProvider, withdrawAmountInput, rec
40
41
  const unspent = await findUnspentUtxos({
41
42
  etherPool,
42
43
  encryptionKey,
43
- keypair
44
+ keypair,
45
+ address
44
46
  });
45
47
  logger.debug(`Unspent UTXOs found: ${unspent.length}`);
46
48