@permissionless-technologies/upp-sdk 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/LICENSE +661 -0
- package/README.md +194 -0
- package/dist/asp-TXSAFFD3.cjs +53 -0
- package/dist/asp-TXSAFFD3.cjs.map +1 -0
- package/dist/asp-ZA3RGN7G.js +4 -0
- package/dist/asp-ZA3RGN7G.js.map +1 -0
- package/dist/babyjubjub-2MGQVCKB.js +5 -0
- package/dist/babyjubjub-2MGQVCKB.js.map +1 -0
- package/dist/babyjubjub-MWZLJOVZ.cjs +66 -0
- package/dist/babyjubjub-MWZLJOVZ.cjs.map +1 -0
- package/dist/chunk-2JQISXBD.js +150 -0
- package/dist/chunk-2JQISXBD.js.map +1 -0
- package/dist/chunk-3HQ7A6ZM.cjs +61 -0
- package/dist/chunk-3HQ7A6ZM.cjs.map +1 -0
- package/dist/chunk-5AKBSMEQ.cjs +1008 -0
- package/dist/chunk-5AKBSMEQ.cjs.map +1 -0
- package/dist/chunk-5V5HSN6Y.js +81 -0
- package/dist/chunk-5V5HSN6Y.js.map +1 -0
- package/dist/chunk-BH24DZ5S.cjs +91 -0
- package/dist/chunk-BH24DZ5S.cjs.map +1 -0
- package/dist/chunk-C7QQOJ7T.cjs +67 -0
- package/dist/chunk-C7QQOJ7T.cjs.map +1 -0
- package/dist/chunk-ERQE57IA.cjs +404 -0
- package/dist/chunk-ERQE57IA.cjs.map +1 -0
- package/dist/chunk-EUP7MBAH.cjs +165 -0
- package/dist/chunk-EUP7MBAH.cjs.map +1 -0
- package/dist/chunk-G7VZBCD6.cjs +35 -0
- package/dist/chunk-G7VZBCD6.cjs.map +1 -0
- package/dist/chunk-GQV47S3N.cjs +10 -0
- package/dist/chunk-GQV47S3N.cjs.map +1 -0
- package/dist/chunk-GXZ3MTCQ.cjs +527 -0
- package/dist/chunk-GXZ3MTCQ.cjs.map +1 -0
- package/dist/chunk-JWNXBALH.cjs +57 -0
- package/dist/chunk-JWNXBALH.cjs.map +1 -0
- package/dist/chunk-KIKBPJXJ.cjs +348 -0
- package/dist/chunk-KIKBPJXJ.cjs.map +1 -0
- package/dist/chunk-NCW4AE7L.js +8 -0
- package/dist/chunk-NCW4AE7L.js.map +1 -0
- package/dist/chunk-NDM5EJEV.cjs +70 -0
- package/dist/chunk-NDM5EJEV.cjs.map +1 -0
- package/dist/chunk-NUIQHTSA.js +489 -0
- package/dist/chunk-NUIQHTSA.js.map +1 -0
- package/dist/chunk-OQDSHMXU.js +1002 -0
- package/dist/chunk-OQDSHMXU.js.map +1 -0
- package/dist/chunk-P37MRZ73.js +58 -0
- package/dist/chunk-P37MRZ73.js.map +1 -0
- package/dist/chunk-PWHOUQOZ.js +335 -0
- package/dist/chunk-PWHOUQOZ.js.map +1 -0
- package/dist/chunk-S4B7GYLN.js +112 -0
- package/dist/chunk-S4B7GYLN.js.map +1 -0
- package/dist/chunk-SGZZL5AC.js +59 -0
- package/dist/chunk-SGZZL5AC.js.map +1 -0
- package/dist/chunk-SQKBT2SH.cjs +122 -0
- package/dist/chunk-SQKBT2SH.cjs.map +1 -0
- package/dist/chunk-TSF6HEVS.cjs +201 -0
- package/dist/chunk-TSF6HEVS.cjs.map +1 -0
- package/dist/chunk-V23OSL25.js +48 -0
- package/dist/chunk-V23OSL25.js.map +1 -0
- package/dist/chunk-W77GRBO4.js +53 -0
- package/dist/chunk-W77GRBO4.js.map +1 -0
- package/dist/chunk-XV72HNHN.js +399 -0
- package/dist/chunk-XV72HNHN.js.map +1 -0
- package/dist/chunk-YOWDERVC.js +186 -0
- package/dist/chunk-YOWDERVC.js.map +1 -0
- package/dist/chunk-Z6ZWNWWR.js +30 -0
- package/dist/chunk-Z6ZWNWWR.js.map +1 -0
- package/dist/chunk-ZKZV6OI3.cjs +165 -0
- package/dist/chunk-ZKZV6OI3.cjs.map +1 -0
- package/dist/chunk-ZU6J7KMY.js +159 -0
- package/dist/chunk-ZU6J7KMY.js.map +1 -0
- package/dist/core/index.cjs +300 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +9 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +11 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-BBzvvrhG.d.ts +757 -0
- package/dist/index-BGvapsJy.d.cts +2811 -0
- package/dist/index-C-jSNw6j.d.cts +757 -0
- package/dist/index-ChGaGPzP.d.ts +2811 -0
- package/dist/index.cjs +3652 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +3112 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer/index.cjs +58 -0
- package/dist/indexer/index.cjs.map +1 -0
- package/dist/indexer/index.d.cts +206 -0
- package/dist/indexer/index.d.ts +206 -0
- package/dist/indexer/index.js +5 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/keccak-m31-B_AqBbRF.d.cts +70 -0
- package/dist/keccak-m31-B_AqBbRF.d.ts +70 -0
- package/dist/keys/index.cjs +68 -0
- package/dist/keys/index.cjs.map +1 -0
- package/dist/keys/index.d.cts +158 -0
- package/dist/keys/index.d.ts +158 -0
- package/dist/keys/index.js +7 -0
- package/dist/keys/index.js.map +1 -0
- package/dist/merkle-7KS2EHRF.js +5 -0
- package/dist/merkle-7KS2EHRF.js.map +1 -0
- package/dist/merkle-HGDC6OB4.cjs +30 -0
- package/dist/merkle-HGDC6OB4.cjs.map +1 -0
- package/dist/merkle-mteVOlDf.d.cts +188 -0
- package/dist/merkle-mteVOlDf.d.ts +188 -0
- package/dist/poseidon-UHTJLWQM.js +7 -0
- package/dist/poseidon-UHTJLWQM.js.map +1 -0
- package/dist/poseidon-WHJSZSNP.cjs +45 -0
- package/dist/poseidon-WHJSZSNP.cjs.map +1 -0
- package/dist/proof-5OECB3RQ.cjs +45 -0
- package/dist/proof-5OECB3RQ.cjs.map +1 -0
- package/dist/proof-C4YBP6RY.js +4 -0
- package/dist/proof-C4YBP6RY.js.map +1 -0
- package/dist/react/index.cjs +2641 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +757 -0
- package/dist/react/index.d.ts +757 -0
- package/dist/react/index.js +2598 -0
- package/dist/react/index.js.map +1 -0
- package/dist/transfer-2UDHDS7Q.cjs +37 -0
- package/dist/transfer-2UDHDS7Q.cjs.map +1 -0
- package/dist/transfer-BlmbO-Rd.d.ts +1270 -0
- package/dist/transfer-DKZuJnRM.d.cts +1270 -0
- package/dist/transfer-KTCXKHS4.js +8 -0
- package/dist/transfer-KTCXKHS4.js.map +1 -0
- package/dist/types-CJSbxv4q.d.cts +143 -0
- package/dist/types-mLybMxNR.d.ts +143 -0
- package/dist/utils/index.cjs +178 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +88 -0
- package/dist/utils/index.d.ts +88 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +119 -0
- package/src/contracts/interfaces/IASPRegistry.sol +36 -0
- package/src/contracts/interfaces/IUniversalPrivatePool.sol +260 -0
- package/src/contracts/interfaces/IVerifiers.sol +68 -0
- package/src/deployments/11155111.json +19 -0
- package/src/deployments/31337.json +19 -0
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
import { createAutoAdapter } from './chunk-XV72HNHN.js';
|
|
2
|
+
import { keccak256, toHex } from 'viem';
|
|
3
|
+
|
|
4
|
+
async function computeSearchTag(sdk, masterViewingSecret, masterViewingPubKey, ephemeralPubkey, debug = false) {
|
|
5
|
+
const dvk = await sdk.deriveDecryptionViewingKey(
|
|
6
|
+
masterViewingSecret,
|
|
7
|
+
masterViewingPubKey,
|
|
8
|
+
ephemeralPubkey.x
|
|
9
|
+
);
|
|
10
|
+
const viewingSharedSecret = await sdk.computeSharedSecret(dvk, ephemeralPubkey);
|
|
11
|
+
const searchTagHash = await sdk.poseidon([viewingSharedSecret.x, 0n]);
|
|
12
|
+
const searchTag = searchTagHash & (1n << 64n) - 1n;
|
|
13
|
+
if (debug) {
|
|
14
|
+
console.log(`[computeSearchTag] viewingSharedSecret.x: ${viewingSharedSecret.x}`);
|
|
15
|
+
console.log(`[computeSearchTag] searchTagHash: ${searchTagHash}`);
|
|
16
|
+
console.log(`[computeSearchTag] searchTag (lower 64 bits): ${searchTag}`);
|
|
17
|
+
}
|
|
18
|
+
return searchTag;
|
|
19
|
+
}
|
|
20
|
+
async function matchesSearchTag(eventSearchTag, sdk, masterViewingSecret, masterViewingPubKey, ephemeralPubkey, debug = false) {
|
|
21
|
+
const expectedTag = await computeSearchTag(
|
|
22
|
+
sdk,
|
|
23
|
+
masterViewingSecret,
|
|
24
|
+
masterViewingPubKey,
|
|
25
|
+
ephemeralPubkey,
|
|
26
|
+
debug
|
|
27
|
+
);
|
|
28
|
+
if (debug) {
|
|
29
|
+
console.log(`[matchesSearchTag] eventSearchTag: ${eventSearchTag}, expectedTag: ${expectedTag}, match: ${eventSearchTag === expectedTag}`);
|
|
30
|
+
}
|
|
31
|
+
return eventSearchTag === expectedTag;
|
|
32
|
+
}
|
|
33
|
+
async function tryDecryptNote(sdk, keys, ephemeralPubkey, encryptedNote, expectedCommitment, debug = false) {
|
|
34
|
+
try {
|
|
35
|
+
const dvk = await sdk.deriveDecryptionViewingKey(
|
|
36
|
+
keys.masterViewingSecret,
|
|
37
|
+
keys.masterViewingPubKey,
|
|
38
|
+
ephemeralPubkey.x
|
|
39
|
+
);
|
|
40
|
+
const viewingSharedSecret = await sdk.computeSharedSecret(dvk, ephemeralPubkey);
|
|
41
|
+
const keyMaterial = keccak256(toHex(viewingSharedSecret.x, { size: 32 }));
|
|
42
|
+
const keyBytes = hexToBytes(keyMaterial);
|
|
43
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
44
|
+
"raw",
|
|
45
|
+
keyBytes.buffer.slice(keyBytes.byteOffset, keyBytes.byteOffset + keyBytes.byteLength),
|
|
46
|
+
{ name: "AES-GCM", length: 256 },
|
|
47
|
+
false,
|
|
48
|
+
["decrypt"]
|
|
49
|
+
);
|
|
50
|
+
const encryptedBytes = hexToBytes(encryptedNote);
|
|
51
|
+
const nonce = encryptedBytes.slice(0, 12);
|
|
52
|
+
const ciphertext = encryptedBytes.slice(12);
|
|
53
|
+
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, ciphertext);
|
|
54
|
+
const plaintextBytes = new Uint8Array(plaintext);
|
|
55
|
+
if (debug) {
|
|
56
|
+
console.log(`[tryDecryptNote] Decrypted plaintext length: ${plaintextBytes.length} bytes`);
|
|
57
|
+
}
|
|
58
|
+
const amount = bytesToBigint(plaintextBytes.slice(0, 32));
|
|
59
|
+
const blinding = bytesToBigint(plaintextBytes.slice(32, 64));
|
|
60
|
+
const origin = bytesToBigint(plaintextBytes.slice(64, 84));
|
|
61
|
+
const token = bytesToBigint(plaintextBytes.slice(84, 104));
|
|
62
|
+
const spendingSharedSecret = await sdk.computeSharedSecret(keys.spendingSecret, ephemeralPubkey);
|
|
63
|
+
const stealthScalar = await sdk.poseidon([spendingSharedSecret.x, spendingSharedSecret.y]);
|
|
64
|
+
const stealthOffset = await sdk.privateToPublic(stealthScalar);
|
|
65
|
+
const oneTimePubkey = await sdk.addPoints(keys.spendingPubkey, stealthOffset);
|
|
66
|
+
const subOrder = await sdk.getSubOrder();
|
|
67
|
+
const oneTimeSecret = (keys.spendingSecret + stealthScalar) % subOrder;
|
|
68
|
+
const computedCommitment = await sdk.poseidon([
|
|
69
|
+
amount,
|
|
70
|
+
oneTimePubkey.x,
|
|
71
|
+
oneTimePubkey.y,
|
|
72
|
+
blinding,
|
|
73
|
+
origin,
|
|
74
|
+
token
|
|
75
|
+
]);
|
|
76
|
+
if (debug) {
|
|
77
|
+
console.log(`[tryDecryptNote] Decrypted values:`);
|
|
78
|
+
console.log(` amount: ${amount}`);
|
|
79
|
+
console.log(` blinding: ${blinding}`);
|
|
80
|
+
console.log(` origin: 0x${origin.toString(16).padStart(40, "0")}`);
|
|
81
|
+
console.log(` token: 0x${token.toString(16).padStart(40, "0")}`);
|
|
82
|
+
console.log(` oneTimePubkey.x: ${oneTimePubkey.x}`);
|
|
83
|
+
console.log(` oneTimePubkey.y: ${oneTimePubkey.y}`);
|
|
84
|
+
console.log(` computedCommitment: ${computedCommitment}`);
|
|
85
|
+
console.log(` expectedCommitment: ${expectedCommitment}`);
|
|
86
|
+
console.log(` match: ${computedCommitment === expectedCommitment}`);
|
|
87
|
+
}
|
|
88
|
+
if (computedCommitment !== expectedCommitment) {
|
|
89
|
+
if (debug) {
|
|
90
|
+
console.log(`[tryDecryptNote] Commitment mismatch - not our note`);
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return { amount, blinding, oneTimeSecret, oneTimePubkey, origin, token };
|
|
95
|
+
} catch (e) {
|
|
96
|
+
if (debug) {
|
|
97
|
+
console.log(`[tryDecryptNote] Decryption exception:`, e instanceof Error ? e.message : e);
|
|
98
|
+
console.log(`[tryDecryptNote] encryptedNote hex length: ${encryptedNote.length} chars = ${(encryptedNote.length - 2) / 2} bytes`);
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function unpackNoteData(packed, debug = false) {
|
|
104
|
+
try {
|
|
105
|
+
const bytes = hexToBytes(packed);
|
|
106
|
+
if (bytes.length < 100) {
|
|
107
|
+
if (debug) {
|
|
108
|
+
console.log(`[unpackNoteData] Packed data too short: ${bytes.length} bytes`);
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const searchTag = bytesToBigint(bytes.slice(0, 8));
|
|
113
|
+
const ephemeralPubkeyX = bytesToBigint(bytes.slice(8, 40));
|
|
114
|
+
const ephemeralPubkeyY = bytesToBigint(bytes.slice(40, 72));
|
|
115
|
+
const encryptedNote = "0x" + Array.from(bytes.slice(72)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
116
|
+
if (debug) {
|
|
117
|
+
console.log(`[unpackNoteData] Total packed bytes: ${bytes.length}, header: 72, encryptedNote: ${bytes.length - 72}`);
|
|
118
|
+
console.log(`[unpackNoteData] searchTag=${searchTag}, ephemeralPubkeyX=${ephemeralPubkeyX}`);
|
|
119
|
+
}
|
|
120
|
+
return { searchTag, ephemeralPubkeyX, ephemeralPubkeyY, encryptedNote };
|
|
121
|
+
} catch (e) {
|
|
122
|
+
if (debug) {
|
|
123
|
+
console.log(`[unpackNoteData] Failed to unpack:`, e);
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function hexToBytes(hex) {
|
|
129
|
+
const h = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
130
|
+
const bytes = new Uint8Array(h.length / 2);
|
|
131
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
132
|
+
bytes[i] = parseInt(h.substr(i * 2, 2), 16);
|
|
133
|
+
}
|
|
134
|
+
return bytes;
|
|
135
|
+
}
|
|
136
|
+
function bytesToBigint(bytes) {
|
|
137
|
+
let result = 0n;
|
|
138
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
139
|
+
const byte = bytes[i];
|
|
140
|
+
if (byte !== void 0) {
|
|
141
|
+
result = result << 8n | BigInt(byte);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
var DEFAULT_BATCH_SIZE = 200;
|
|
147
|
+
var MIN_BATCH_SIZE = 50;
|
|
148
|
+
var MAX_BATCH_SIZE = 2e3;
|
|
149
|
+
var BATCH_DELAY_MS = 100;
|
|
150
|
+
var DEFAULT_LIVE_SYNC_INTERVAL_MS = 3e4;
|
|
151
|
+
var DEFAULT_REQUESTS_PER_SECOND = 20;
|
|
152
|
+
var DEFAULT_MAX_CONCURRENT = 10;
|
|
153
|
+
var STORAGE_KEY_SYNC = "sync";
|
|
154
|
+
var STORAGE_KEY_NOTES = "notes";
|
|
155
|
+
var STORAGE_KEY_NULLIFIERS = "nullifiers";
|
|
156
|
+
var COMMITMENT_INSERTED_EVENT = {
|
|
157
|
+
type: "event",
|
|
158
|
+
name: "CommitmentInserted",
|
|
159
|
+
inputs: [
|
|
160
|
+
{ indexed: true, name: "commitment", type: "bytes32" },
|
|
161
|
+
{ indexed: false, name: "leafIndex", type: "uint256" },
|
|
162
|
+
{ indexed: false, name: "timestamp", type: "uint256" }
|
|
163
|
+
]
|
|
164
|
+
};
|
|
165
|
+
var STEALTH_TRANSACT_EVENT = {
|
|
166
|
+
type: "event",
|
|
167
|
+
name: "StealthTransact",
|
|
168
|
+
inputs: [
|
|
169
|
+
{ indexed: true, name: "commitment", type: "bytes32" },
|
|
170
|
+
{ indexed: true, name: "searchTag", type: "uint64" },
|
|
171
|
+
{ indexed: false, name: "ephemeralPubkeyX", type: "uint256" },
|
|
172
|
+
{ indexed: false, name: "ephemeralPubkeyY", type: "uint256" },
|
|
173
|
+
{ indexed: false, name: "encryptedNote", type: "bytes" }
|
|
174
|
+
]
|
|
175
|
+
};
|
|
176
|
+
var SHIELDED_EVENT = {
|
|
177
|
+
type: "event",
|
|
178
|
+
name: "Shielded",
|
|
179
|
+
inputs: [
|
|
180
|
+
{ indexed: true, name: "token", type: "address" },
|
|
181
|
+
{ indexed: true, name: "depositor", type: "address" },
|
|
182
|
+
{ indexed: true, name: "commitment", type: "bytes32" },
|
|
183
|
+
{ indexed: false, name: "leafIndex", type: "uint256" },
|
|
184
|
+
{ indexed: false, name: "encryptedNote", type: "bytes" }
|
|
185
|
+
]
|
|
186
|
+
};
|
|
187
|
+
var TRANSFERRED_EVENT = {
|
|
188
|
+
type: "event",
|
|
189
|
+
name: "Transferred",
|
|
190
|
+
inputs: [
|
|
191
|
+
{ indexed: true, name: "nullifier", type: "bytes32" },
|
|
192
|
+
{ indexed: true, name: "outputCommitment1", type: "bytes32" },
|
|
193
|
+
{ indexed: true, name: "outputCommitment2", type: "bytes32" },
|
|
194
|
+
{ indexed: false, name: "encryptedNote1", type: "bytes" },
|
|
195
|
+
{ indexed: false, name: "encryptedNote2", type: "bytes" }
|
|
196
|
+
]
|
|
197
|
+
};
|
|
198
|
+
var NULLIFIED_EVENT = {
|
|
199
|
+
type: "event",
|
|
200
|
+
name: "Nullified",
|
|
201
|
+
inputs: [{ indexed: true, name: "nullifier", type: "bytes32" }]
|
|
202
|
+
};
|
|
203
|
+
var WITHDRAWN_EVENT = {
|
|
204
|
+
type: "event",
|
|
205
|
+
name: "Withdrawn",
|
|
206
|
+
inputs: [
|
|
207
|
+
{ indexed: true, name: "token", type: "address" },
|
|
208
|
+
{ indexed: true, name: "recipient", type: "address" },
|
|
209
|
+
{ indexed: true, name: "nullifier", type: "bytes32" },
|
|
210
|
+
{ indexed: false, name: "amount", type: "uint256" },
|
|
211
|
+
{ indexed: false, name: "isRagequit", type: "bool" }
|
|
212
|
+
]
|
|
213
|
+
};
|
|
214
|
+
var RateLimiter = class {
|
|
215
|
+
tokens;
|
|
216
|
+
maxTokens;
|
|
217
|
+
refillRate;
|
|
218
|
+
// tokens per ms
|
|
219
|
+
lastRefill;
|
|
220
|
+
activeRequests;
|
|
221
|
+
maxConcurrent;
|
|
222
|
+
queue = [];
|
|
223
|
+
constructor(requestsPerSecond, maxConcurrent) {
|
|
224
|
+
this.maxTokens = requestsPerSecond;
|
|
225
|
+
this.tokens = requestsPerSecond;
|
|
226
|
+
this.refillRate = requestsPerSecond / 1e3;
|
|
227
|
+
this.lastRefill = Date.now();
|
|
228
|
+
this.activeRequests = 0;
|
|
229
|
+
this.maxConcurrent = maxConcurrent;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Refill tokens based on time elapsed
|
|
233
|
+
*/
|
|
234
|
+
refill() {
|
|
235
|
+
const now = Date.now();
|
|
236
|
+
const elapsed = now - this.lastRefill;
|
|
237
|
+
this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRate);
|
|
238
|
+
this.lastRefill = now;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Try to process queued requests
|
|
242
|
+
*/
|
|
243
|
+
processQueue() {
|
|
244
|
+
while (this.queue.length > 0 && this.activeRequests < this.maxConcurrent) {
|
|
245
|
+
this.refill();
|
|
246
|
+
if (this.tokens >= 1) {
|
|
247
|
+
this.tokens -= 1;
|
|
248
|
+
this.activeRequests++;
|
|
249
|
+
const resolve = this.queue.shift();
|
|
250
|
+
resolve?.();
|
|
251
|
+
} else {
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Acquire a token (wait if necessary)
|
|
258
|
+
*/
|
|
259
|
+
async acquire() {
|
|
260
|
+
return new Promise((resolve) => {
|
|
261
|
+
this.refill();
|
|
262
|
+
if (this.tokens >= 1 && this.activeRequests < this.maxConcurrent) {
|
|
263
|
+
this.tokens -= 1;
|
|
264
|
+
this.activeRequests++;
|
|
265
|
+
resolve();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
this.queue.push(resolve);
|
|
269
|
+
const waitTime = this.tokens < 1 ? Math.ceil((1 - this.tokens) / this.refillRate) : 10;
|
|
270
|
+
setTimeout(() => this.processQueue(), waitTime);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Release a token (call after request completes)
|
|
275
|
+
*/
|
|
276
|
+
release() {
|
|
277
|
+
this.activeRequests = Math.max(0, this.activeRequests - 1);
|
|
278
|
+
this.processQueue();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Execute a function with rate limiting
|
|
282
|
+
*/
|
|
283
|
+
async execute(fn) {
|
|
284
|
+
await this.acquire();
|
|
285
|
+
try {
|
|
286
|
+
return await fn();
|
|
287
|
+
} finally {
|
|
288
|
+
this.release();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var sdkPromise = null;
|
|
293
|
+
async function loadSDK() {
|
|
294
|
+
if (!sdkPromise) {
|
|
295
|
+
sdkPromise = import('./index.js');
|
|
296
|
+
}
|
|
297
|
+
return sdkPromise;
|
|
298
|
+
}
|
|
299
|
+
var RpcIndexer = class {
|
|
300
|
+
client;
|
|
301
|
+
contractAddress;
|
|
302
|
+
chainId;
|
|
303
|
+
storage;
|
|
304
|
+
spendingSecret;
|
|
305
|
+
spendingPubkey;
|
|
306
|
+
masterViewingSecret;
|
|
307
|
+
masterViewingPubKey;
|
|
308
|
+
// Adaptive batching state
|
|
309
|
+
batchSize;
|
|
310
|
+
minBatchSize;
|
|
311
|
+
batchDelayMs;
|
|
312
|
+
// In-memory cache
|
|
313
|
+
notes = [];
|
|
314
|
+
nullifiers = /* @__PURE__ */ new Set();
|
|
315
|
+
lastBlock = 0;
|
|
316
|
+
initialized = false;
|
|
317
|
+
// Initial starting block (fallback if no saved state)
|
|
318
|
+
initialFromBlock;
|
|
319
|
+
// Live sync state
|
|
320
|
+
liveSyncInterval = null;
|
|
321
|
+
liveSyncConfig = null;
|
|
322
|
+
// Rate limiter for RPC requests
|
|
323
|
+
rateLimiter;
|
|
324
|
+
constructor(config) {
|
|
325
|
+
this.client = config.client;
|
|
326
|
+
this.contractAddress = config.contractAddress;
|
|
327
|
+
this.chainId = config.chainId;
|
|
328
|
+
this.spendingSecret = config.spendingSecret;
|
|
329
|
+
this.spendingPubkey = config.spendingPubkey;
|
|
330
|
+
this.masterViewingSecret = config.masterViewingSecret;
|
|
331
|
+
this.masterViewingPubKey = config.masterViewingPubKey;
|
|
332
|
+
this.batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
333
|
+
this.minBatchSize = config.minBatchSize ?? MIN_BATCH_SIZE;
|
|
334
|
+
this.batchDelayMs = config.batchDelayMs ?? BATCH_DELAY_MS;
|
|
335
|
+
this.initialFromBlock = config.fromBlock ?? 0;
|
|
336
|
+
const accountPrefix = `${config.chainId}_${config.contractAddress.slice(2, 10)}_${toHex(config.spendingPubkey.x).slice(2, 12)}`;
|
|
337
|
+
this.storage = config.storage ?? createAutoAdapter(accountPrefix);
|
|
338
|
+
this.rateLimiter = new RateLimiter(DEFAULT_REQUESTS_PER_SECOND, DEFAULT_MAX_CONCURRENT);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Initialize from storage
|
|
342
|
+
*/
|
|
343
|
+
async init() {
|
|
344
|
+
if (this.initialized) return;
|
|
345
|
+
const syncState = await this.storage.get(STORAGE_KEY_SYNC);
|
|
346
|
+
if (syncState && syncState.chainId === this.chainId && syncState.contractAddress === this.contractAddress) {
|
|
347
|
+
this.lastBlock = syncState.lastBlock;
|
|
348
|
+
} else {
|
|
349
|
+
this.lastBlock = this.initialFromBlock;
|
|
350
|
+
}
|
|
351
|
+
const serializedNotes = await this.storage.get(STORAGE_KEY_NOTES);
|
|
352
|
+
if (serializedNotes) {
|
|
353
|
+
this.notes = serializedNotes.map(deserializeNote);
|
|
354
|
+
}
|
|
355
|
+
const nullifiers = await this.storage.get(STORAGE_KEY_NULLIFIERS);
|
|
356
|
+
if (nullifiers) {
|
|
357
|
+
this.nullifiers = new Set(nullifiers.map((n) => n.toLowerCase()));
|
|
358
|
+
}
|
|
359
|
+
this.initialized = true;
|
|
360
|
+
if (this.notes.length > 0 && this.nullifiers.size > 0) {
|
|
361
|
+
await this.checkNotesAgainstNullifiers();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Check all notes against known nullifiers and mark spent ones
|
|
366
|
+
*/
|
|
367
|
+
async checkNotesAgainstNullifiers() {
|
|
368
|
+
const sdk = await loadSDK();
|
|
369
|
+
let spentCount = 0;
|
|
370
|
+
console.log(`[Indexer] checkNotesAgainstNullifiers: ${this.notes.length} notes, ${this.nullifiers.size} nullifiers in set`);
|
|
371
|
+
if (this.nullifiers.size > 0) {
|
|
372
|
+
console.log(`[Indexer] Known nullifiers:`);
|
|
373
|
+
for (const n of this.nullifiers) {
|
|
374
|
+
console.log(` ${n}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
for (const note of this.notes) {
|
|
378
|
+
if (note.status === "spent") continue;
|
|
379
|
+
try {
|
|
380
|
+
const nullifier = await sdk.poseidon([
|
|
381
|
+
BigInt(note.oneTimeSecret),
|
|
382
|
+
BigInt(note.leafIndex),
|
|
383
|
+
BigInt(note.commitment)
|
|
384
|
+
]);
|
|
385
|
+
const nullifierHex = toHex(nullifier, { size: 32 }).toLowerCase();
|
|
386
|
+
const isSpent = this.nullifiers.has(nullifierHex);
|
|
387
|
+
console.log(`[Indexer] Note ${note.commitment.slice(0, 18)}... leafIndex=${note.leafIndex} \u2192 nullifier=${nullifierHex.slice(0, 18)}... \u2192 ${isSpent ? "SPENT" : "unspent"}`);
|
|
388
|
+
if (isSpent) {
|
|
389
|
+
note.status = "spent";
|
|
390
|
+
spentCount++;
|
|
391
|
+
}
|
|
392
|
+
} catch (e) {
|
|
393
|
+
console.error(`[Indexer] Error computing nullifier for note:`, e);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (spentCount > 0) {
|
|
397
|
+
console.log(`[Indexer] Marked ${spentCount} notes as spent based on nullifiers`);
|
|
398
|
+
await this.persist();
|
|
399
|
+
}
|
|
400
|
+
return spentCount;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Persist current state to storage
|
|
404
|
+
*/
|
|
405
|
+
async persist() {
|
|
406
|
+
const syncState = {
|
|
407
|
+
lastBlock: this.lastBlock,
|
|
408
|
+
chainId: this.chainId,
|
|
409
|
+
contractAddress: this.contractAddress,
|
|
410
|
+
lastSyncTimestamp: Date.now()
|
|
411
|
+
};
|
|
412
|
+
await Promise.all([
|
|
413
|
+
this.storage.set(STORAGE_KEY_SYNC, syncState),
|
|
414
|
+
this.storage.set(STORAGE_KEY_NOTES, this.notes.map(serializeNote)),
|
|
415
|
+
this.storage.set(STORAGE_KEY_NULLIFIERS, Array.from(this.nullifiers))
|
|
416
|
+
]);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Sync notes from blockchain
|
|
420
|
+
*
|
|
421
|
+
* @param config - Optional sync configuration
|
|
422
|
+
*/
|
|
423
|
+
async sync(config) {
|
|
424
|
+
const startTime = Date.now();
|
|
425
|
+
await this.init();
|
|
426
|
+
const errors = [];
|
|
427
|
+
let newNotesCount = 0;
|
|
428
|
+
let spentNotesCount = 0;
|
|
429
|
+
const debug = config?.debug ?? false;
|
|
430
|
+
const useSearchTagFiltering = config?.useSearchTagFiltering ?? false;
|
|
431
|
+
try {
|
|
432
|
+
const currentBlock = Number(await this.client.getBlockNumber());
|
|
433
|
+
const fromBlock = this.lastBlock > 0 ? this.lastBlock + 1 : this.initialFromBlock;
|
|
434
|
+
if (fromBlock > currentBlock) {
|
|
435
|
+
return {
|
|
436
|
+
newNotesCount: 0,
|
|
437
|
+
spentNotesCount: 0,
|
|
438
|
+
lastBlock: this.lastBlock,
|
|
439
|
+
durationMs: Date.now() - startTime
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
const reportProgress = (currentBlock_, eventsProcessed2, notesFound, eventsSkipped2) => {
|
|
443
|
+
if (config?.onProgress) {
|
|
444
|
+
const totalBlocks = currentBlock - fromBlock + 1;
|
|
445
|
+
const processedBlocks = currentBlock_ - fromBlock + 1;
|
|
446
|
+
config.onProgress({
|
|
447
|
+
currentBlock: currentBlock_,
|
|
448
|
+
targetBlock: currentBlock,
|
|
449
|
+
eventsProcessed: eventsProcessed2,
|
|
450
|
+
notesFound,
|
|
451
|
+
percent: Math.round(processedBlocks / totalBlocks * 100),
|
|
452
|
+
eventsSkipped: eventsSkipped2
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
const commitmentMap = /* @__PURE__ */ new Map();
|
|
457
|
+
const stealthEvents = [];
|
|
458
|
+
const newNullifiers = [];
|
|
459
|
+
let currentBatchSize = this.batchSize;
|
|
460
|
+
let batchStart = fromBlock;
|
|
461
|
+
while (batchStart <= currentBlock) {
|
|
462
|
+
const batchEnd = Math.min(batchStart + currentBatchSize - 1, currentBlock);
|
|
463
|
+
try {
|
|
464
|
+
const [commitmentLogs, stealthLogs, shieldedLogs, transferredLogs, nullifiedLogs, withdrawnLogs] = await Promise.all([
|
|
465
|
+
this.rateLimiter.execute(
|
|
466
|
+
() => this.client.getLogs({
|
|
467
|
+
address: this.contractAddress,
|
|
468
|
+
event: COMMITMENT_INSERTED_EVENT,
|
|
469
|
+
fromBlock: BigInt(batchStart),
|
|
470
|
+
toBlock: BigInt(batchEnd)
|
|
471
|
+
})
|
|
472
|
+
),
|
|
473
|
+
this.rateLimiter.execute(
|
|
474
|
+
() => this.client.getLogs({
|
|
475
|
+
address: this.contractAddress,
|
|
476
|
+
event: STEALTH_TRANSACT_EVENT,
|
|
477
|
+
fromBlock: BigInt(batchStart),
|
|
478
|
+
toBlock: BigInt(batchEnd)
|
|
479
|
+
})
|
|
480
|
+
),
|
|
481
|
+
this.rateLimiter.execute(
|
|
482
|
+
() => this.client.getLogs({
|
|
483
|
+
address: this.contractAddress,
|
|
484
|
+
event: SHIELDED_EVENT,
|
|
485
|
+
fromBlock: BigInt(batchStart),
|
|
486
|
+
toBlock: BigInt(batchEnd)
|
|
487
|
+
})
|
|
488
|
+
),
|
|
489
|
+
this.rateLimiter.execute(
|
|
490
|
+
() => this.client.getLogs({
|
|
491
|
+
address: this.contractAddress,
|
|
492
|
+
event: TRANSFERRED_EVENT,
|
|
493
|
+
fromBlock: BigInt(batchStart),
|
|
494
|
+
toBlock: BigInt(batchEnd)
|
|
495
|
+
})
|
|
496
|
+
),
|
|
497
|
+
this.rateLimiter.execute(
|
|
498
|
+
() => this.client.getLogs({
|
|
499
|
+
address: this.contractAddress,
|
|
500
|
+
event: NULLIFIED_EVENT,
|
|
501
|
+
fromBlock: BigInt(batchStart),
|
|
502
|
+
toBlock: BigInt(batchEnd)
|
|
503
|
+
})
|
|
504
|
+
),
|
|
505
|
+
this.rateLimiter.execute(
|
|
506
|
+
() => this.client.getLogs({
|
|
507
|
+
address: this.contractAddress,
|
|
508
|
+
event: WITHDRAWN_EVENT,
|
|
509
|
+
fromBlock: BigInt(batchStart),
|
|
510
|
+
toBlock: BigInt(batchEnd)
|
|
511
|
+
})
|
|
512
|
+
)
|
|
513
|
+
]);
|
|
514
|
+
for (const log of commitmentLogs) {
|
|
515
|
+
const commitment = log.args.commitment.toLowerCase();
|
|
516
|
+
commitmentMap.set(commitment, {
|
|
517
|
+
leafIndex: Number(log.args.leafIndex),
|
|
518
|
+
timestamp: Number(log.args.timestamp),
|
|
519
|
+
blockNumber: Number(log.blockNumber)
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
for (const log of stealthLogs) {
|
|
523
|
+
const args = log.args;
|
|
524
|
+
stealthEvents.push({
|
|
525
|
+
commitment: args.commitment.toLowerCase(),
|
|
526
|
+
ephemeralPubkeyX: args.ephemeralPubkeyX,
|
|
527
|
+
ephemeralPubkeyY: args.ephemeralPubkeyY,
|
|
528
|
+
encryptedNote: args.encryptedNote,
|
|
529
|
+
txHash: log.transactionHash,
|
|
530
|
+
blockNumber: Number(log.blockNumber),
|
|
531
|
+
searchTag: args.searchTag
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
for (const log of shieldedLogs) {
|
|
535
|
+
const args = log.args;
|
|
536
|
+
const unpacked = unpackNoteData(args.encryptedNote);
|
|
537
|
+
if (unpacked) {
|
|
538
|
+
stealthEvents.push({
|
|
539
|
+
commitment: args.commitment.toLowerCase(),
|
|
540
|
+
ephemeralPubkeyX: unpacked.ephemeralPubkeyX,
|
|
541
|
+
ephemeralPubkeyY: unpacked.ephemeralPubkeyY,
|
|
542
|
+
encryptedNote: unpacked.encryptedNote,
|
|
543
|
+
txHash: log.transactionHash,
|
|
544
|
+
blockNumber: Number(log.blockNumber),
|
|
545
|
+
searchTag: unpacked.searchTag
|
|
546
|
+
});
|
|
547
|
+
commitmentMap.set(args.commitment.toLowerCase(), {
|
|
548
|
+
leafIndex: Number(args.leafIndex),
|
|
549
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
550
|
+
blockNumber: Number(log.blockNumber)
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
for (const log of transferredLogs) {
|
|
555
|
+
const args = log.args;
|
|
556
|
+
const unpacked1 = unpackNoteData(args.encryptedNote1);
|
|
557
|
+
if (unpacked1) {
|
|
558
|
+
stealthEvents.push({
|
|
559
|
+
commitment: args.outputCommitment1.toLowerCase(),
|
|
560
|
+
ephemeralPubkeyX: unpacked1.ephemeralPubkeyX,
|
|
561
|
+
ephemeralPubkeyY: unpacked1.ephemeralPubkeyY,
|
|
562
|
+
encryptedNote: unpacked1.encryptedNote,
|
|
563
|
+
txHash: log.transactionHash,
|
|
564
|
+
blockNumber: Number(log.blockNumber),
|
|
565
|
+
searchTag: unpacked1.searchTag
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
const unpacked2 = unpackNoteData(args.encryptedNote2);
|
|
569
|
+
if (unpacked2) {
|
|
570
|
+
stealthEvents.push({
|
|
571
|
+
commitment: args.outputCommitment2.toLowerCase(),
|
|
572
|
+
ephemeralPubkeyX: unpacked2.ephemeralPubkeyX,
|
|
573
|
+
ephemeralPubkeyY: unpacked2.ephemeralPubkeyY,
|
|
574
|
+
encryptedNote: unpacked2.encryptedNote,
|
|
575
|
+
txHash: log.transactionHash,
|
|
576
|
+
blockNumber: Number(log.blockNumber),
|
|
577
|
+
searchTag: unpacked2.searchTag
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
for (const log of nullifiedLogs) {
|
|
582
|
+
const nullifier = log.args.nullifier.toLowerCase();
|
|
583
|
+
newNullifiers.push(nullifier);
|
|
584
|
+
}
|
|
585
|
+
for (const log of transferredLogs) {
|
|
586
|
+
const args = log.args;
|
|
587
|
+
if (args.nullifier) {
|
|
588
|
+
newNullifiers.push(args.nullifier.toLowerCase());
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (withdrawnLogs.length > 0) {
|
|
592
|
+
console.log(`[Indexer] Found ${withdrawnLogs.length} Withdrawn events in batch`);
|
|
593
|
+
}
|
|
594
|
+
for (const log of withdrawnLogs) {
|
|
595
|
+
const args = log.args;
|
|
596
|
+
if (args.nullifier) {
|
|
597
|
+
const nullifierLower = args.nullifier.toLowerCase();
|
|
598
|
+
console.log(`[Indexer] Withdrawn event nullifier: ${nullifierLower}`);
|
|
599
|
+
newNullifiers.push(nullifierLower);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (currentBatchSize < MAX_BATCH_SIZE) {
|
|
603
|
+
currentBatchSize = Math.min(currentBatchSize * 2, MAX_BATCH_SIZE);
|
|
604
|
+
}
|
|
605
|
+
reportProgress(batchEnd, stealthEvents.length, 0, 0);
|
|
606
|
+
batchStart = batchEnd + 1;
|
|
607
|
+
if (batchStart <= currentBlock) {
|
|
608
|
+
await sleep(this.batchDelayMs);
|
|
609
|
+
}
|
|
610
|
+
} catch (e) {
|
|
611
|
+
if (isRateLimitError(e)) {
|
|
612
|
+
currentBatchSize = Math.max(Math.floor(currentBatchSize / 2), this.minBatchSize);
|
|
613
|
+
errors.push(`Rate limited at block ${batchStart}, reducing batch size to ${currentBatchSize}`);
|
|
614
|
+
await sleep(1e3);
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
throw e;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
this.batchSize = currentBatchSize;
|
|
621
|
+
console.log(`[Indexer] Processing ${newNullifiers.length} new nullifiers`);
|
|
622
|
+
for (const nullifier of newNullifiers) {
|
|
623
|
+
if (!this.nullifiers.has(nullifier.toLowerCase())) {
|
|
624
|
+
this.nullifiers.add(nullifier.toLowerCase());
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
console.log(`[Indexer] Total nullifiers in set: ${this.nullifiers.size}`);
|
|
628
|
+
const sdk = await loadSDK();
|
|
629
|
+
const existingCommitmentsMap = new Map(this.notes.map((n) => [n.commitment.toLowerCase(), n]));
|
|
630
|
+
console.log(`[Indexer] Sync: blocks ${fromBlock} to ${currentBlock} (${currentBlock - fromBlock + 1} blocks)`);
|
|
631
|
+
console.log(`[Indexer] Found: ${stealthEvents.length} stealth events, ${commitmentMap.size} commitment events`);
|
|
632
|
+
console.log(`[Indexer] Search tag filtering: ${useSearchTagFiltering ? "ENABLED" : "DISABLED"}`);
|
|
633
|
+
if (debug) {
|
|
634
|
+
console.log(`[Indexer] Existing notes in memory: ${this.notes.length}`);
|
|
635
|
+
console.log(`[Indexer] masterViewingPubKey: x=${this.masterViewingPubKey.x}, y=${this.masterViewingPubKey.y}`);
|
|
636
|
+
}
|
|
637
|
+
let eventsSkipped = 0;
|
|
638
|
+
let eventsProcessed = 0;
|
|
639
|
+
for (const event of stealthEvents) {
|
|
640
|
+
const commitmentLower = event.commitment.toLowerCase();
|
|
641
|
+
const existingNote = existingCommitmentsMap.get(commitmentLower);
|
|
642
|
+
if (existingNote && existingNote.status === "confirmed") {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
if (existingNote && existingNote.status === "pending") {
|
|
646
|
+
const info = commitmentMap.get(commitmentLower);
|
|
647
|
+
existingNote.status = "confirmed";
|
|
648
|
+
existingNote.leafIndex = info?.leafIndex ?? existingNote.leafIndex;
|
|
649
|
+
existingNote.blockNumber = info?.blockNumber ?? event.blockNumber;
|
|
650
|
+
existingNote.timestamp = info?.timestamp ?? existingNote.timestamp;
|
|
651
|
+
newNotesCount++;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (useSearchTagFiltering && event.searchTag !== void 0) {
|
|
655
|
+
const ephemeralPubkey = { x: event.ephemeralPubkeyX, y: event.ephemeralPubkeyY };
|
|
656
|
+
const shouldLogDetails = debug && eventsSkipped + eventsProcessed < 3;
|
|
657
|
+
if (shouldLogDetails) {
|
|
658
|
+
console.log(`[Indexer] Checking event searchTag=${event.searchTag}`);
|
|
659
|
+
console.log(`[Indexer] ephemeralPubkey: x=${ephemeralPubkey.x}, y=${ephemeralPubkey.y}`);
|
|
660
|
+
}
|
|
661
|
+
const matches = await matchesSearchTag(
|
|
662
|
+
event.searchTag,
|
|
663
|
+
sdk,
|
|
664
|
+
this.masterViewingSecret,
|
|
665
|
+
this.masterViewingPubKey,
|
|
666
|
+
ephemeralPubkey,
|
|
667
|
+
shouldLogDetails
|
|
668
|
+
// Pass debug flag
|
|
669
|
+
);
|
|
670
|
+
if (!matches) {
|
|
671
|
+
eventsSkipped++;
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (debug) {
|
|
675
|
+
console.log(`[Indexer] \u2713 Search tag MATCHED for commitment ${event.commitment.slice(0, 18)}...`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
eventsProcessed++;
|
|
679
|
+
try {
|
|
680
|
+
if (debug) {
|
|
681
|
+
console.log(`[Indexer] Trying to decrypt event commitment: ${event.commitment}`);
|
|
682
|
+
console.log(`[Indexer] Encrypted note length: ${(event.encryptedNote.length - 2) / 2} bytes`);
|
|
683
|
+
}
|
|
684
|
+
const decrypted = await tryDecryptNote2(
|
|
685
|
+
sdk,
|
|
686
|
+
this.spendingSecret,
|
|
687
|
+
this.spendingPubkey,
|
|
688
|
+
this.masterViewingSecret,
|
|
689
|
+
this.masterViewingPubKey,
|
|
690
|
+
{ x: event.ephemeralPubkeyX, y: event.ephemeralPubkeyY },
|
|
691
|
+
event.encryptedNote,
|
|
692
|
+
BigInt(event.commitment)
|
|
693
|
+
);
|
|
694
|
+
if (decrypted) {
|
|
695
|
+
if (debug) {
|
|
696
|
+
console.log(`[Indexer] \u2713 Decryption SUCCESS for commitment ${event.commitment.slice(0, 18)}...`);
|
|
697
|
+
console.log(`[Indexer] Amount: ${decrypted.amount}, Origin: 0x${decrypted.origin.toString(16)}, Token: 0x${decrypted.token.toString(16)}`);
|
|
698
|
+
}
|
|
699
|
+
const info = commitmentMap.get(event.commitment.toLowerCase());
|
|
700
|
+
const note = {
|
|
701
|
+
amount: decrypted.amount,
|
|
702
|
+
blinding: decrypted.blinding,
|
|
703
|
+
commitment: event.commitment.toLowerCase(),
|
|
704
|
+
oneTimeSecret: toHex(decrypted.oneTimeSecret, { size: 32 }),
|
|
705
|
+
oneTimePubkeyX: toHex(decrypted.oneTimePubkey.x, { size: 32 }),
|
|
706
|
+
oneTimePubkeyY: toHex(decrypted.oneTimePubkey.y, { size: 32 }),
|
|
707
|
+
leafIndex: info?.leafIndex ?? 0,
|
|
708
|
+
txHash: event.txHash,
|
|
709
|
+
status: "confirmed",
|
|
710
|
+
blockNumber: info?.blockNumber ?? event.blockNumber,
|
|
711
|
+
timestamp: info?.timestamp ?? Math.floor(Date.now() / 1e3),
|
|
712
|
+
origin: "0x" + decrypted.origin.toString(16).padStart(40, "0"),
|
|
713
|
+
token: "0x" + decrypted.token.toString(16).padStart(40, "0"),
|
|
714
|
+
ephemeralX: toHex(event.ephemeralPubkeyX, { size: 32 }),
|
|
715
|
+
ephemeralY: toHex(event.ephemeralPubkeyY, { size: 32 })
|
|
716
|
+
};
|
|
717
|
+
this.notes.push(note);
|
|
718
|
+
existingCommitmentsMap.set(note.commitment.toLowerCase(), note);
|
|
719
|
+
newNotesCount++;
|
|
720
|
+
}
|
|
721
|
+
} catch (decryptErr) {
|
|
722
|
+
if (debug) {
|
|
723
|
+
console.log(`[Indexer] \u2717 Decryption FAILED for commitment ${event.commitment.slice(0, 18)}...`, decryptErr);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (stealthEvents.length > 0) {
|
|
728
|
+
console.log(`[Indexer] Result: ${eventsSkipped} skipped by search tag, ${eventsProcessed} processed, ${newNotesCount} notes found`);
|
|
729
|
+
}
|
|
730
|
+
reportProgress(currentBlock, eventsProcessed, newNotesCount, eventsSkipped);
|
|
731
|
+
spentNotesCount = await this.checkNotesAgainstNullifiers();
|
|
732
|
+
this.lastBlock = currentBlock;
|
|
733
|
+
await this.persist();
|
|
734
|
+
if (newNotesCount > 0 && this.liveSyncConfig?.onNewNotes) {
|
|
735
|
+
const newNotes = this.notes.slice(-newNotesCount);
|
|
736
|
+
this.liveSyncConfig.onNewNotes(newNotes);
|
|
737
|
+
}
|
|
738
|
+
return {
|
|
739
|
+
newNotesCount,
|
|
740
|
+
spentNotesCount,
|
|
741
|
+
lastBlock: this.lastBlock,
|
|
742
|
+
durationMs: Date.now() - startTime,
|
|
743
|
+
errors: errors.length > 0 ? errors : void 0
|
|
744
|
+
};
|
|
745
|
+
} catch (e) {
|
|
746
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
747
|
+
if (this.liveSyncConfig?.onError) {
|
|
748
|
+
this.liveSyncConfig.onError(error);
|
|
749
|
+
}
|
|
750
|
+
throw error;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Get total balance of unspent notes
|
|
755
|
+
*/
|
|
756
|
+
getBalance(token) {
|
|
757
|
+
const unspent = this.getUnspentNotes();
|
|
758
|
+
const filtered = token ? unspent.filter((n) => n.token === token) : unspent;
|
|
759
|
+
return filtered.reduce((sum, n) => sum + n.amount, 0n);
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Get all notes matching filters
|
|
763
|
+
*/
|
|
764
|
+
getNotes(filters) {
|
|
765
|
+
let result = [...this.notes];
|
|
766
|
+
if (filters?.status) {
|
|
767
|
+
const statuses = Array.isArray(filters.status) ? filters.status : [filters.status];
|
|
768
|
+
result = result.filter((n) => statuses.includes(n.status));
|
|
769
|
+
}
|
|
770
|
+
if (filters?.token) {
|
|
771
|
+
result = result.filter((n) => n.token === filters.token);
|
|
772
|
+
}
|
|
773
|
+
if (filters?.minAmount !== void 0) {
|
|
774
|
+
result = result.filter((n) => n.amount >= filters.minAmount);
|
|
775
|
+
}
|
|
776
|
+
if (filters?.maxAmount !== void 0) {
|
|
777
|
+
result = result.filter((n) => n.amount <= filters.maxAmount);
|
|
778
|
+
}
|
|
779
|
+
return result;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Get unspent notes
|
|
783
|
+
*/
|
|
784
|
+
getUnspentNotes() {
|
|
785
|
+
return this.notes.filter((n) => n.status !== "spent");
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Get the last synced block number
|
|
789
|
+
*/
|
|
790
|
+
getLastBlock() {
|
|
791
|
+
return this.lastBlock;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Mark a note as spent
|
|
795
|
+
*/
|
|
796
|
+
markSpent(commitment) {
|
|
797
|
+
const note = this.notes.find((n) => n.commitment.toLowerCase() === commitment.toLowerCase());
|
|
798
|
+
if (note) {
|
|
799
|
+
note.status = "spent";
|
|
800
|
+
this.persist().catch(console.error);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Add a note directly
|
|
805
|
+
*/
|
|
806
|
+
addNote(note) {
|
|
807
|
+
if (this.notes.some((n) => n.commitment.toLowerCase() === note.commitment.toLowerCase())) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
this.notes.push(note);
|
|
811
|
+
this.persist().catch(console.error);
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Start live sync
|
|
815
|
+
*/
|
|
816
|
+
startLiveSync(config) {
|
|
817
|
+
if (this.liveSyncInterval) {
|
|
818
|
+
this.stopLiveSync();
|
|
819
|
+
}
|
|
820
|
+
this.liveSyncConfig = config ?? {};
|
|
821
|
+
const intervalMs = config?.intervalMs ?? DEFAULT_LIVE_SYNC_INTERVAL_MS;
|
|
822
|
+
this.liveSyncInterval = setInterval(() => {
|
|
823
|
+
this.sync().catch((e) => {
|
|
824
|
+
if (config?.onError) {
|
|
825
|
+
config.onError(e instanceof Error ? e : new Error(String(e)));
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
}, intervalMs);
|
|
829
|
+
this.sync().catch((e) => {
|
|
830
|
+
if (config?.onError) {
|
|
831
|
+
config.onError(e instanceof Error ? e : new Error(String(e)));
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Stop live sync
|
|
837
|
+
*/
|
|
838
|
+
stopLiveSync() {
|
|
839
|
+
if (this.liveSyncInterval) {
|
|
840
|
+
clearInterval(this.liveSyncInterval);
|
|
841
|
+
this.liveSyncInterval = null;
|
|
842
|
+
}
|
|
843
|
+
this.liveSyncConfig = null;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Check if live sync is running
|
|
847
|
+
*/
|
|
848
|
+
isLiveSyncing() {
|
|
849
|
+
return this.liveSyncInterval !== null;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Clear all indexed data
|
|
853
|
+
*/
|
|
854
|
+
async clear() {
|
|
855
|
+
this.notes = [];
|
|
856
|
+
this.nullifiers.clear();
|
|
857
|
+
this.lastBlock = 0;
|
|
858
|
+
await this.storage.clear();
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Export current state
|
|
862
|
+
*/
|
|
863
|
+
async exportState() {
|
|
864
|
+
await this.init();
|
|
865
|
+
return {
|
|
866
|
+
sync: {
|
|
867
|
+
lastBlock: this.lastBlock,
|
|
868
|
+
chainId: this.chainId,
|
|
869
|
+
contractAddress: this.contractAddress,
|
|
870
|
+
lastSyncTimestamp: Date.now()
|
|
871
|
+
},
|
|
872
|
+
notes: this.notes.map(serializeNote),
|
|
873
|
+
nullifiers: Array.from(this.nullifiers)
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Import state
|
|
878
|
+
*/
|
|
879
|
+
async importState(state) {
|
|
880
|
+
if (state.sync.chainId !== this.chainId || state.sync.contractAddress !== this.contractAddress) {
|
|
881
|
+
throw new Error("State does not match indexer configuration");
|
|
882
|
+
}
|
|
883
|
+
this.lastBlock = state.sync.lastBlock;
|
|
884
|
+
this.notes = state.notes.map(deserializeNote);
|
|
885
|
+
this.nullifiers = new Set(state.nullifiers.map((n) => n.toLowerCase()));
|
|
886
|
+
this.initialized = true;
|
|
887
|
+
await this.persist();
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
function makeRpcIndexer(config) {
|
|
891
|
+
return new RpcIndexer(config);
|
|
892
|
+
}
|
|
893
|
+
async function tryDecryptNote2(sdk, spendingSecret, spendingPubkey, masterViewingSecret, masterViewingPubKey, ephemeralPubkey, encryptedNote, expectedCommitment) {
|
|
894
|
+
try {
|
|
895
|
+
const dvk = await sdk.deriveDecryptionViewingKey(masterViewingSecret, masterViewingPubKey, ephemeralPubkey.x);
|
|
896
|
+
const viewingSharedSecret = await sdk.computeSharedSecret(dvk, ephemeralPubkey);
|
|
897
|
+
const keyMaterial = keccak256(toHex(viewingSharedSecret.x, { size: 32 }));
|
|
898
|
+
const keyBytes = hexToBytes(keyMaterial);
|
|
899
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
900
|
+
"raw",
|
|
901
|
+
keyBytes.buffer.slice(keyBytes.byteOffset, keyBytes.byteOffset + keyBytes.byteLength),
|
|
902
|
+
{ name: "AES-GCM", length: 256 },
|
|
903
|
+
false,
|
|
904
|
+
["decrypt"]
|
|
905
|
+
);
|
|
906
|
+
const encryptedBytes = hexToBytes(encryptedNote);
|
|
907
|
+
const nonce = encryptedBytes.slice(0, 12);
|
|
908
|
+
const ciphertext = encryptedBytes.slice(12);
|
|
909
|
+
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, ciphertext);
|
|
910
|
+
const plaintextBytes = new Uint8Array(plaintext);
|
|
911
|
+
console.log(`[tryDecryptNote] Decrypted plaintext length: ${plaintextBytes.length} bytes`);
|
|
912
|
+
const amount = bytesToBigint(plaintextBytes.slice(0, 32));
|
|
913
|
+
const blinding = bytesToBigint(plaintextBytes.slice(32, 64));
|
|
914
|
+
const origin = bytesToBigint(plaintextBytes.slice(64, 84));
|
|
915
|
+
const token = bytesToBigint(plaintextBytes.slice(84, 104));
|
|
916
|
+
const spendingSharedSecret = await sdk.computeSharedSecret(spendingSecret, ephemeralPubkey);
|
|
917
|
+
const stealthScalar = await sdk.poseidon([spendingSharedSecret.x, spendingSharedSecret.y]);
|
|
918
|
+
const stealthOffset = await sdk.privateToPublic(stealthScalar);
|
|
919
|
+
const oneTimePubkey = await sdk.addPoints(spendingPubkey, stealthOffset);
|
|
920
|
+
const subOrder = await sdk.getSubOrder();
|
|
921
|
+
const oneTimeSecret = (spendingSecret + stealthScalar) % subOrder;
|
|
922
|
+
const computedCommitment = await sdk.poseidon([
|
|
923
|
+
amount,
|
|
924
|
+
oneTimePubkey.x,
|
|
925
|
+
oneTimePubkey.y,
|
|
926
|
+
blinding,
|
|
927
|
+
origin,
|
|
928
|
+
token
|
|
929
|
+
]);
|
|
930
|
+
console.log(`[tryDecryptNote] Decrypted values:`);
|
|
931
|
+
console.log(` amount: ${amount}`);
|
|
932
|
+
console.log(` blinding: ${blinding}`);
|
|
933
|
+
console.log(` origin: 0x${origin.toString(16).padStart(40, "0")}`);
|
|
934
|
+
console.log(` token: 0x${token.toString(16).padStart(40, "0")}`);
|
|
935
|
+
console.log(` oneTimePubkey.x: ${oneTimePubkey.x}`);
|
|
936
|
+
console.log(` oneTimePubkey.y: ${oneTimePubkey.y}`);
|
|
937
|
+
console.log(` computedCommitment: ${computedCommitment}`);
|
|
938
|
+
console.log(` expectedCommitment: ${expectedCommitment}`);
|
|
939
|
+
console.log(` match: ${computedCommitment === expectedCommitment}`);
|
|
940
|
+
if (computedCommitment !== expectedCommitment) {
|
|
941
|
+
console.log(`[tryDecryptNote] Commitment mismatch - not our note`);
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
return { amount, blinding, oneTimeSecret, oneTimePubkey, origin, token };
|
|
945
|
+
} catch (e) {
|
|
946
|
+
console.log(`[tryDecryptNote] Decryption exception:`, e instanceof Error ? e.message : e);
|
|
947
|
+
console.log(`[tryDecryptNote] encryptedNote hex length: ${encryptedNote.length} chars = ${(encryptedNote.length - 2) / 2} bytes`);
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
function serializeNote(note) {
|
|
952
|
+
return {
|
|
953
|
+
amount: note.amount.toString(),
|
|
954
|
+
blinding: note.blinding.toString(),
|
|
955
|
+
commitment: note.commitment,
|
|
956
|
+
oneTimeSecret: note.oneTimeSecret,
|
|
957
|
+
oneTimePubkeyX: note.oneTimePubkeyX,
|
|
958
|
+
oneTimePubkeyY: note.oneTimePubkeyY,
|
|
959
|
+
leafIndex: note.leafIndex,
|
|
960
|
+
txHash: note.txHash,
|
|
961
|
+
status: note.status,
|
|
962
|
+
blockNumber: note.blockNumber,
|
|
963
|
+
timestamp: note.timestamp,
|
|
964
|
+
origin: note.origin,
|
|
965
|
+
token: note.token,
|
|
966
|
+
ephemeralX: note.ephemeralX,
|
|
967
|
+
ephemeralY: note.ephemeralY
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
function deserializeNote(note) {
|
|
971
|
+
return {
|
|
972
|
+
amount: BigInt(note.amount),
|
|
973
|
+
blinding: BigInt(note.blinding),
|
|
974
|
+
commitment: note.commitment,
|
|
975
|
+
oneTimeSecret: note.oneTimeSecret,
|
|
976
|
+
oneTimePubkeyX: note.oneTimePubkeyX,
|
|
977
|
+
oneTimePubkeyY: note.oneTimePubkeyY,
|
|
978
|
+
leafIndex: note.leafIndex,
|
|
979
|
+
txHash: note.txHash,
|
|
980
|
+
status: note.status,
|
|
981
|
+
blockNumber: note.blockNumber,
|
|
982
|
+
timestamp: note.timestamp,
|
|
983
|
+
origin: note.origin,
|
|
984
|
+
token: note.token,
|
|
985
|
+
ephemeralX: note.ephemeralX,
|
|
986
|
+
ephemeralY: note.ephemeralY
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
function isRateLimitError(e) {
|
|
990
|
+
if (e instanceof Error) {
|
|
991
|
+
const message = e.message.toLowerCase();
|
|
992
|
+
return message.includes("rate limit") || message.includes("too many requests") || message.includes("429") || message.includes("exceeded");
|
|
993
|
+
}
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
function sleep(ms) {
|
|
997
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
export { computeSearchTag, makeRpcIndexer, matchesSearchTag, tryDecryptNote, unpackNoteData };
|
|
1001
|
+
//# sourceMappingURL=chunk-OQDSHMXU.js.map
|
|
1002
|
+
//# sourceMappingURL=chunk-OQDSHMXU.js.map
|