@shielded-x402/client 0.2.4 → 0.4.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/dist/agentPaymentFetch.d.ts +56 -0
- package/dist/agentPaymentFetch.d.ts.map +1 -0
- package/dist/agentPaymentFetch.js +208 -0
- package/dist/agentPaymentFetch.js.map +1 -0
- package/dist/client.d.ts +2 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +8 -25
- package/dist/client.js.map +1 -1
- package/dist/counterpartyPolicy.d.ts +9 -0
- package/dist/counterpartyPolicy.d.ts.map +1 -0
- package/dist/counterpartyPolicy.js +87 -0
- package/dist/counterpartyPolicy.js.map +1 -0
- package/dist/creditChannel.d.ts +28 -0
- package/dist/creditChannel.d.ts.map +1 -0
- package/dist/creditChannel.js +143 -0
- package/dist/creditChannel.js.map +1 -0
- package/dist/creditClose.d.ts +13 -0
- package/dist/creditClose.d.ts.map +1 -0
- package/dist/creditClose.js +21 -0
- package/dist/creditClose.js.map +1 -0
- package/dist/creditFetch.d.ts +18 -0
- package/dist/creditFetch.d.ts.map +1 -0
- package/dist/creditFetch.js +56 -0
- package/dist/creditFetch.js.map +1 -0
- package/dist/creditSignatures.d.ts +18 -0
- package/dist/creditSignatures.d.ts.map +1 -0
- package/dist/creditSignatures.js +18 -0
- package/dist/creditSignatures.js.map +1 -0
- package/dist/crypto.d.ts +1 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +4 -7
- package/dist/crypto.js.map +1 -1
- package/dist/http.d.ts +10 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +27 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/proofProvider.d.ts +1 -1
- package/dist/proofProvider.d.ts.map +1 -1
- package/dist/proofProvider.js +40 -44
- package/dist/proofProvider.js.map +1 -1
- package/dist/requirementAdapters.d.ts +16 -0
- package/dist/requirementAdapters.d.ts.map +1 -0
- package/dist/requirementAdapters.js +277 -0
- package/dist/requirementAdapters.js.map +1 -0
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/walletState.d.ts +20 -4
- package/dist/walletState.d.ts.map +1 -1
- package/dist/walletState.js +282 -191
- package/dist/walletState.js.map +1 -1
- package/package.json +4 -3
- package/dist/relayerFetch.d.ts +0 -43
- package/dist/relayerFetch.d.ts.map +0 -1
- package/dist/relayerFetch.js +0 -313
- package/dist/relayerFetch.js.map +0 -1
- package/dist/shieldedFetch.d.ts +0 -41
- package/dist/shieldedFetch.d.ts.map +0 -1
- package/dist/shieldedFetch.js +0 -91
- package/dist/shieldedFetch.js.map +0 -1
package/dist/walletState.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
+
import { normalizeHex } from '@shielded-x402/shared-types';
|
|
3
4
|
import { createPublicClient, http, parseAbiItem } from 'viem';
|
|
4
5
|
import { deriveWitness } from './merkle.js';
|
|
6
|
+
import { postJson } from './http.js';
|
|
5
7
|
const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
6
8
|
const depositedEvent = parseAbiItem('event Deposited(bytes32 indexed commitment, uint256 indexed leafIndex, bytes32 indexed root, uint256 amount)');
|
|
7
9
|
const spentEvent = parseAbiItem('event Spent(bytes32 indexed nullifier, bytes32 indexed merchantCommitment, bytes32 indexed changeCommitment, uint256 amount, bytes32 challengeHash, uint256 merchantLeafIndex, uint256 changeLeafIndex, bytes32 newRoot)');
|
|
8
|
-
|
|
9
|
-
return value.toLowerCase();
|
|
10
|
-
}
|
|
10
|
+
const INDEXER_PAGE_SIZE = 500;
|
|
11
11
|
function serialize(state, poolAddress) {
|
|
12
12
|
return {
|
|
13
|
-
version:
|
|
13
|
+
version: 3,
|
|
14
14
|
poolAddress,
|
|
15
15
|
lastSyncedBlock: state.lastSyncedBlock.toString(),
|
|
16
16
|
commitments: state.commitments,
|
|
@@ -18,14 +18,50 @@ function serialize(state, poolAddress) {
|
|
|
18
18
|
amount: note.amount.toString(),
|
|
19
19
|
rho: note.rho,
|
|
20
20
|
pkHash: note.pkHash,
|
|
21
|
+
nullifierSecret: note.nullifierSecret,
|
|
21
22
|
commitment: note.commitment,
|
|
22
23
|
leafIndex: note.leafIndex,
|
|
23
24
|
...(note.depositBlock !== undefined ? { depositBlock: note.depositBlock.toString() } : {}),
|
|
24
25
|
...(note.spent !== undefined ? { spent: note.spent } : {})
|
|
25
|
-
}))
|
|
26
|
+
})),
|
|
27
|
+
creditStates: Object.fromEntries(Object.entries(state.creditStates).map(([channelId, signed]) => [
|
|
28
|
+
channelId,
|
|
29
|
+
{
|
|
30
|
+
state: {
|
|
31
|
+
channelId: signed.state.channelId,
|
|
32
|
+
seq: signed.state.seq,
|
|
33
|
+
available: signed.state.available,
|
|
34
|
+
cumulativeSpent: signed.state.cumulativeSpent,
|
|
35
|
+
lastDebitDigest: signed.state.lastDebitDigest,
|
|
36
|
+
updatedAt: signed.state.updatedAt,
|
|
37
|
+
agentAddress: signed.state.agentAddress,
|
|
38
|
+
relayerAddress: signed.state.relayerAddress
|
|
39
|
+
},
|
|
40
|
+
agentSignature: signed.agentSignature,
|
|
41
|
+
relayerSignature: signed.relayerSignature
|
|
42
|
+
}
|
|
43
|
+
]))
|
|
26
44
|
};
|
|
27
45
|
}
|
|
28
46
|
function deserialize(payload) {
|
|
47
|
+
const persistedCreditStates = payload.creditStates ?? {};
|
|
48
|
+
const creditStates = {};
|
|
49
|
+
for (const [channelId, signed] of Object.entries(persistedCreditStates)) {
|
|
50
|
+
creditStates[channelId] = {
|
|
51
|
+
state: {
|
|
52
|
+
channelId: signed.state.channelId,
|
|
53
|
+
seq: signed.state.seq,
|
|
54
|
+
available: signed.state.available,
|
|
55
|
+
cumulativeSpent: signed.state.cumulativeSpent,
|
|
56
|
+
lastDebitDigest: signed.state.lastDebitDigest,
|
|
57
|
+
updatedAt: signed.state.updatedAt,
|
|
58
|
+
agentAddress: signed.state.agentAddress,
|
|
59
|
+
relayerAddress: signed.state.relayerAddress
|
|
60
|
+
},
|
|
61
|
+
agentSignature: signed.agentSignature,
|
|
62
|
+
relayerSignature: signed.relayerSignature
|
|
63
|
+
};
|
|
64
|
+
}
|
|
29
65
|
return {
|
|
30
66
|
lastSyncedBlock: BigInt(payload.lastSyncedBlock),
|
|
31
67
|
commitments: payload.commitments,
|
|
@@ -33,11 +69,13 @@ function deserialize(payload) {
|
|
|
33
69
|
amount: BigInt(note.amount),
|
|
34
70
|
rho: note.rho,
|
|
35
71
|
pkHash: note.pkHash,
|
|
72
|
+
nullifierSecret: note.nullifierSecret,
|
|
36
73
|
commitment: note.commitment,
|
|
37
74
|
leafIndex: note.leafIndex,
|
|
38
75
|
...(note.depositBlock !== undefined ? { depositBlock: BigInt(note.depositBlock) } : {}),
|
|
39
76
|
...(note.spent !== undefined ? { spent: note.spent } : {})
|
|
40
|
-
}))
|
|
77
|
+
})),
|
|
78
|
+
creditStates
|
|
41
79
|
};
|
|
42
80
|
}
|
|
43
81
|
export class FileBackedWalletState {
|
|
@@ -50,6 +88,8 @@ export class FileBackedWalletState {
|
|
|
50
88
|
startBlock;
|
|
51
89
|
indexerFieldNames;
|
|
52
90
|
state;
|
|
91
|
+
noteIndexesByCommitment;
|
|
92
|
+
leafIndexByCommitment;
|
|
53
93
|
constructor(config) {
|
|
54
94
|
this.filePath = config.filePath;
|
|
55
95
|
this.rpcUrl = config.rpcUrl;
|
|
@@ -61,8 +101,12 @@ export class FileBackedWalletState {
|
|
|
61
101
|
this.state = {
|
|
62
102
|
lastSyncedBlock: this.startBlock - 1n,
|
|
63
103
|
commitments: [],
|
|
64
|
-
notes: []
|
|
104
|
+
notes: [],
|
|
105
|
+
creditStates: {}
|
|
65
106
|
};
|
|
107
|
+
this.noteIndexesByCommitment = new Map();
|
|
108
|
+
this.leafIndexByCommitment = new Map();
|
|
109
|
+
this.rebuildCommitmentIndexes();
|
|
66
110
|
}
|
|
67
111
|
static async create(config) {
|
|
68
112
|
const instance = new FileBackedWalletState(config);
|
|
@@ -73,13 +117,14 @@ export class FileBackedWalletState {
|
|
|
73
117
|
try {
|
|
74
118
|
const raw = await readFile(this.filePath, 'utf8');
|
|
75
119
|
const parsed = JSON.parse(raw);
|
|
76
|
-
if (parsed.version !==
|
|
120
|
+
if (parsed.version !== 3) {
|
|
77
121
|
throw new Error(`unsupported wallet state version: ${String(parsed.version)}`);
|
|
78
122
|
}
|
|
79
123
|
if (normalizeHex(parsed.poolAddress) !== normalizeHex(this.shieldedPoolAddress)) {
|
|
80
124
|
throw new Error(`wallet state pool mismatch: expected ${this.shieldedPoolAddress}, found ${parsed.poolAddress}`);
|
|
81
125
|
}
|
|
82
126
|
this.state = deserialize(parsed);
|
|
127
|
+
this.rebuildCommitmentIndexes();
|
|
83
128
|
}
|
|
84
129
|
catch (error) {
|
|
85
130
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -106,12 +151,34 @@ export class FileBackedWalletState {
|
|
|
106
151
|
getNotes() {
|
|
107
152
|
return this.state.notes.map((note) => ({ ...note }));
|
|
108
153
|
}
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
154
|
+
getCreditState(channelId) {
|
|
155
|
+
const state = this.state.creditStates[channelId.toLowerCase()];
|
|
156
|
+
if (!state)
|
|
157
|
+
return undefined;
|
|
158
|
+
return {
|
|
159
|
+
state: { ...state.state },
|
|
160
|
+
agentSignature: state.agentSignature,
|
|
161
|
+
relayerSignature: state.relayerSignature
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async setCreditState(signedState) {
|
|
165
|
+
this.state.creditStates[signedState.state.channelId.toLowerCase()] = {
|
|
166
|
+
state: { ...signedState.state },
|
|
167
|
+
agentSignature: signedState.agentSignature,
|
|
168
|
+
relayerSignature: signedState.relayerSignature
|
|
169
|
+
};
|
|
170
|
+
await this.persist();
|
|
171
|
+
}
|
|
172
|
+
async clearCreditState(channelId) {
|
|
173
|
+
delete this.state.creditStates[channelId.toLowerCase()];
|
|
174
|
+
await this.persist();
|
|
175
|
+
}
|
|
176
|
+
async addOrUpdateNote(note, nullifierSecret, depositBlock) {
|
|
177
|
+
const existingIndex = this.findNoteIndexByCommitment(note.commitment);
|
|
112
178
|
const existing = existingIndex >= 0 ? this.state.notes[existingIndex] : undefined;
|
|
113
179
|
const record = {
|
|
114
180
|
...note,
|
|
181
|
+
nullifierSecret,
|
|
115
182
|
...(depositBlock !== undefined ? { depositBlock } : {}),
|
|
116
183
|
...(existing?.spent !== undefined ? { spent: existing.spent } : {})
|
|
117
184
|
};
|
|
@@ -124,11 +191,11 @@ export class FileBackedWalletState {
|
|
|
124
191
|
if (record.leafIndex >= 0) {
|
|
125
192
|
this.state.commitments[record.leafIndex] = record.commitment;
|
|
126
193
|
}
|
|
194
|
+
this.rebuildCommitmentIndexes();
|
|
127
195
|
await this.persist();
|
|
128
196
|
}
|
|
129
197
|
async markNoteSpent(commitment) {
|
|
130
|
-
const
|
|
131
|
-
const existingIndex = this.state.notes.findIndex((candidate) => normalizeHex(candidate.commitment) === normalized);
|
|
198
|
+
const existingIndex = this.findNoteIndexByCommitment(commitment);
|
|
132
199
|
if (existingIndex < 0) {
|
|
133
200
|
return false;
|
|
134
201
|
}
|
|
@@ -148,6 +215,7 @@ export class FileBackedWalletState {
|
|
|
148
215
|
this.state.commitments[params.merchantLeafIndex] = params.merchantCommitment;
|
|
149
216
|
this.state.commitments[params.changeLeafIndex] = params.changeCommitment;
|
|
150
217
|
}
|
|
218
|
+
this.rebuildCommitmentIndexes();
|
|
151
219
|
await this.persist();
|
|
152
220
|
}
|
|
153
221
|
async applyRelayerSettlement(params) {
|
|
@@ -165,11 +233,139 @@ export class FileBackedWalletState {
|
|
|
165
233
|
});
|
|
166
234
|
if (params.changeNote) {
|
|
167
235
|
const changeLeafIndex = delta.changeLeafIndex ?? -1;
|
|
236
|
+
if (!params.changeNullifierSecret) {
|
|
237
|
+
throw new Error('applyRelayerSettlement requires changeNullifierSecret when changeNote is provided');
|
|
238
|
+
}
|
|
168
239
|
await this.addOrUpdateNote({
|
|
169
240
|
...params.changeNote,
|
|
170
241
|
leafIndex: changeLeafIndex
|
|
171
|
-
}, this.state.lastSyncedBlock >= 0n ? this.state.lastSyncedBlock : undefined);
|
|
242
|
+
}, params.changeNullifierSecret, this.state.lastSyncedBlock >= 0n ? this.state.lastSyncedBlock : undefined);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
applyCommitmentWrites(writes) {
|
|
246
|
+
writes.sort((a, b) => {
|
|
247
|
+
if (a.blockNumber !== b.blockNumber)
|
|
248
|
+
return a.blockNumber < b.blockNumber ? -1 : 1;
|
|
249
|
+
if (a.logIndex !== b.logIndex)
|
|
250
|
+
return a.logIndex < b.logIndex ? -1 : 1;
|
|
251
|
+
return a.order - b.order;
|
|
252
|
+
});
|
|
253
|
+
for (const write of writes) {
|
|
254
|
+
if (write.leafIndex < 0)
|
|
255
|
+
continue;
|
|
256
|
+
this.state.commitments[write.leafIndex] = write.commitment;
|
|
257
|
+
const normalizedCommitment = normalizeHex(write.commitment);
|
|
258
|
+
this.leafIndexByCommitment.set(normalizedCommitment, write.leafIndex);
|
|
259
|
+
const noteIndexes = this.noteIndexesByCommitment.get(normalizedCommitment);
|
|
260
|
+
if (!noteIndexes)
|
|
261
|
+
continue;
|
|
262
|
+
for (const noteIndex of noteIndexes) {
|
|
263
|
+
const note = this.state.notes[noteIndex];
|
|
264
|
+
if (!note)
|
|
265
|
+
continue;
|
|
266
|
+
note.leafIndex = write.leafIndex;
|
|
267
|
+
if (note.depositBlock === undefined) {
|
|
268
|
+
note.depositBlock = write.blockNumber;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
rebuildCommitmentIndexes() {
|
|
274
|
+
this.noteIndexesByCommitment.clear();
|
|
275
|
+
for (let i = 0; i < this.state.notes.length; i += 1) {
|
|
276
|
+
const note = this.state.notes[i];
|
|
277
|
+
if (!note)
|
|
278
|
+
continue;
|
|
279
|
+
const normalizedCommitment = normalizeHex(note.commitment);
|
|
280
|
+
const indexes = this.noteIndexesByCommitment.get(normalizedCommitment) ?? [];
|
|
281
|
+
indexes.push(i);
|
|
282
|
+
this.noteIndexesByCommitment.set(normalizedCommitment, indexes);
|
|
283
|
+
}
|
|
284
|
+
this.leafIndexByCommitment.clear();
|
|
285
|
+
for (let i = 0; i < this.state.commitments.length; i += 1) {
|
|
286
|
+
const commitment = this.state.commitments[i];
|
|
287
|
+
if (!commitment)
|
|
288
|
+
continue;
|
|
289
|
+
this.leafIndexByCommitment.set(normalizeHex(commitment), i);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
mapRpcEventsToWrites(params) {
|
|
293
|
+
const writes = [];
|
|
294
|
+
let depositsApplied = 0;
|
|
295
|
+
let spendsApplied = 0;
|
|
296
|
+
for (const log of params.depositLogs) {
|
|
297
|
+
const args = log.args;
|
|
298
|
+
if (args.commitment === undefined || args.leafIndex === undefined)
|
|
299
|
+
continue;
|
|
300
|
+
writes.push({
|
|
301
|
+
blockNumber: log.blockNumber ?? 0n,
|
|
302
|
+
logIndex: log.logIndex ?? 0,
|
|
303
|
+
order: 0,
|
|
304
|
+
leafIndex: Number(args.leafIndex),
|
|
305
|
+
commitment: args.commitment
|
|
306
|
+
});
|
|
307
|
+
depositsApplied += 1;
|
|
172
308
|
}
|
|
309
|
+
for (const log of params.spendLogs) {
|
|
310
|
+
const args = log.args;
|
|
311
|
+
if (args.merchantCommitment === undefined ||
|
|
312
|
+
args.changeCommitment === undefined ||
|
|
313
|
+
args.merchantLeafIndex === undefined ||
|
|
314
|
+
args.changeLeafIndex === undefined) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
writes.push({
|
|
318
|
+
blockNumber: log.blockNumber ?? 0n,
|
|
319
|
+
logIndex: log.logIndex ?? 0,
|
|
320
|
+
order: 0,
|
|
321
|
+
leafIndex: Number(args.merchantLeafIndex),
|
|
322
|
+
commitment: args.merchantCommitment
|
|
323
|
+
});
|
|
324
|
+
writes.push({
|
|
325
|
+
blockNumber: log.blockNumber ?? 0n,
|
|
326
|
+
logIndex: log.logIndex ?? 0,
|
|
327
|
+
order: 1,
|
|
328
|
+
leafIndex: Number(args.changeLeafIndex),
|
|
329
|
+
commitment: args.changeCommitment
|
|
330
|
+
});
|
|
331
|
+
spendsApplied += 1;
|
|
332
|
+
}
|
|
333
|
+
return { writes, depositsApplied, spendsApplied };
|
|
334
|
+
}
|
|
335
|
+
mapIndexerEventsToWrites(deposits, spends) {
|
|
336
|
+
const writes = [];
|
|
337
|
+
let depositsApplied = 0;
|
|
338
|
+
let spendsApplied = 0;
|
|
339
|
+
for (const deposit of deposits) {
|
|
340
|
+
const { blockNumber, logIndex } = parseEventPositionFromId(deposit.id);
|
|
341
|
+
writes.push({
|
|
342
|
+
blockNumber,
|
|
343
|
+
logIndex,
|
|
344
|
+
order: 0,
|
|
345
|
+
leafIndex: Number(deposit.leafIndex),
|
|
346
|
+
commitment: deposit.commitment
|
|
347
|
+
});
|
|
348
|
+
depositsApplied += 1;
|
|
349
|
+
}
|
|
350
|
+
for (const spend of spends) {
|
|
351
|
+
const { blockNumber, logIndex } = parseEventPositionFromId(spend.id);
|
|
352
|
+
writes.push({
|
|
353
|
+
blockNumber,
|
|
354
|
+
logIndex,
|
|
355
|
+
order: 0,
|
|
356
|
+
leafIndex: Number(spend.merchantLeafIndex),
|
|
357
|
+
commitment: spend.merchantCommitment
|
|
358
|
+
});
|
|
359
|
+
writes.push({
|
|
360
|
+
blockNumber,
|
|
361
|
+
logIndex,
|
|
362
|
+
order: 1,
|
|
363
|
+
leafIndex: Number(spend.changeLeafIndex),
|
|
364
|
+
commitment: spend.changeCommitment
|
|
365
|
+
});
|
|
366
|
+
spendsApplied += 1;
|
|
367
|
+
}
|
|
368
|
+
return { writes, depositsApplied, spendsApplied };
|
|
173
369
|
}
|
|
174
370
|
async sync() {
|
|
175
371
|
if (this.indexerGraphqlUrl) {
|
|
@@ -212,68 +408,13 @@ export class FileBackedWalletState {
|
|
|
212
408
|
toBlock
|
|
213
409
|
})
|
|
214
410
|
]);
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (args.commitment === undefined || args.leafIndex === undefined)
|
|
219
|
-
continue;
|
|
220
|
-
writes.push({
|
|
221
|
-
blockNumber: log.blockNumber ?? 0n,
|
|
222
|
-
logIndex: log.logIndex ?? 0,
|
|
223
|
-
order: 0,
|
|
224
|
-
leafIndex: Number(args.leafIndex),
|
|
225
|
-
commitment: args.commitment
|
|
226
|
-
});
|
|
227
|
-
depositsApplied += 1;
|
|
228
|
-
}
|
|
229
|
-
for (const log of spendLogs) {
|
|
230
|
-
const args = log.args;
|
|
231
|
-
if (args.merchantCommitment === undefined ||
|
|
232
|
-
args.changeCommitment === undefined ||
|
|
233
|
-
args.merchantLeafIndex === undefined ||
|
|
234
|
-
args.changeLeafIndex === undefined) {
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
writes.push({
|
|
238
|
-
blockNumber: log.blockNumber ?? 0n,
|
|
239
|
-
logIndex: log.logIndex ?? 0,
|
|
240
|
-
order: 0,
|
|
241
|
-
leafIndex: Number(args.merchantLeafIndex),
|
|
242
|
-
commitment: args.merchantCommitment
|
|
243
|
-
});
|
|
244
|
-
writes.push({
|
|
245
|
-
blockNumber: log.blockNumber ?? 0n,
|
|
246
|
-
logIndex: log.logIndex ?? 0,
|
|
247
|
-
order: 1,
|
|
248
|
-
leafIndex: Number(args.changeLeafIndex),
|
|
249
|
-
commitment: args.changeCommitment
|
|
250
|
-
});
|
|
251
|
-
spendsApplied += 1;
|
|
252
|
-
}
|
|
253
|
-
writes.sort((a, b) => {
|
|
254
|
-
if (a.blockNumber !== b.blockNumber)
|
|
255
|
-
return a.blockNumber < b.blockNumber ? -1 : 1;
|
|
256
|
-
if (a.logIndex !== b.logIndex)
|
|
257
|
-
return a.logIndex < b.logIndex ? -1 : 1;
|
|
258
|
-
return a.order - b.order;
|
|
411
|
+
const mapped = this.mapRpcEventsToWrites({
|
|
412
|
+
depositLogs: depositLogs,
|
|
413
|
+
spendLogs: spendLogs
|
|
259
414
|
});
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this.state.commitments[write.leafIndex] = write.commitment;
|
|
264
|
-
const normalized = normalizeHex(write.commitment);
|
|
265
|
-
for (let i = 0; i < this.state.notes.length; i += 1) {
|
|
266
|
-
const note = this.state.notes[i];
|
|
267
|
-
if (!note)
|
|
268
|
-
continue;
|
|
269
|
-
if (normalizeHex(note.commitment) !== normalized)
|
|
270
|
-
continue;
|
|
271
|
-
note.leafIndex = write.leafIndex;
|
|
272
|
-
if (note.depositBlock === undefined) {
|
|
273
|
-
note.depositBlock = write.blockNumber;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
415
|
+
this.applyCommitmentWrites(mapped.writes);
|
|
416
|
+
depositsApplied += mapped.depositsApplied;
|
|
417
|
+
spendsApplied += mapped.spendsApplied;
|
|
277
418
|
this.state.lastSyncedBlock = toBlock;
|
|
278
419
|
await this.persist();
|
|
279
420
|
cursor = toBlock + 1n;
|
|
@@ -288,117 +429,62 @@ export class FileBackedWalletState {
|
|
|
288
429
|
async syncFromIndexer() {
|
|
289
430
|
const fromBlock = this.state.lastSyncedBlock + 1n;
|
|
290
431
|
const { deposits, spends, maxBlockNumber } = await this.fetchIndexerEvents();
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
let spendsApplied = 0;
|
|
294
|
-
for (const deposit of deposits) {
|
|
295
|
-
const { blockNumber, logIndex } = parseEventPositionFromId(deposit.id);
|
|
296
|
-
writes.push({
|
|
297
|
-
blockNumber,
|
|
298
|
-
logIndex,
|
|
299
|
-
order: 0,
|
|
300
|
-
leafIndex: Number(deposit.leafIndex),
|
|
301
|
-
commitment: deposit.commitment
|
|
302
|
-
});
|
|
303
|
-
depositsApplied += 1;
|
|
304
|
-
}
|
|
305
|
-
for (const spend of spends) {
|
|
306
|
-
const { blockNumber, logIndex } = parseEventPositionFromId(spend.id);
|
|
307
|
-
writes.push({
|
|
308
|
-
blockNumber,
|
|
309
|
-
logIndex,
|
|
310
|
-
order: 0,
|
|
311
|
-
leafIndex: Number(spend.merchantLeafIndex),
|
|
312
|
-
commitment: spend.merchantCommitment
|
|
313
|
-
});
|
|
314
|
-
writes.push({
|
|
315
|
-
blockNumber,
|
|
316
|
-
logIndex,
|
|
317
|
-
order: 1,
|
|
318
|
-
leafIndex: Number(spend.changeLeafIndex),
|
|
319
|
-
commitment: spend.changeCommitment
|
|
320
|
-
});
|
|
321
|
-
spendsApplied += 1;
|
|
322
|
-
}
|
|
323
|
-
writes.sort((a, b) => {
|
|
324
|
-
if (a.blockNumber !== b.blockNumber)
|
|
325
|
-
return a.blockNumber < b.blockNumber ? -1 : 1;
|
|
326
|
-
if (a.logIndex !== b.logIndex)
|
|
327
|
-
return a.logIndex < b.logIndex ? -1 : 1;
|
|
328
|
-
return a.order - b.order;
|
|
329
|
-
});
|
|
330
|
-
for (const write of writes) {
|
|
331
|
-
if (write.leafIndex < 0)
|
|
332
|
-
continue;
|
|
333
|
-
this.state.commitments[write.leafIndex] = write.commitment;
|
|
334
|
-
const normalized = normalizeHex(write.commitment);
|
|
335
|
-
for (let i = 0; i < this.state.notes.length; i += 1) {
|
|
336
|
-
const note = this.state.notes[i];
|
|
337
|
-
if (!note)
|
|
338
|
-
continue;
|
|
339
|
-
if (normalizeHex(note.commitment) !== normalized)
|
|
340
|
-
continue;
|
|
341
|
-
note.leafIndex = write.leafIndex;
|
|
342
|
-
if (note.depositBlock === undefined) {
|
|
343
|
-
note.depositBlock = write.blockNumber;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
432
|
+
const mapped = this.mapIndexerEventsToWrites(deposits, spends);
|
|
433
|
+
this.applyCommitmentWrites(mapped.writes);
|
|
347
434
|
const targetBlock = maxBlockNumber >= this.state.lastSyncedBlock ? maxBlockNumber : this.state.lastSyncedBlock;
|
|
348
435
|
this.state.lastSyncedBlock = targetBlock;
|
|
349
436
|
await this.persist();
|
|
350
437
|
return {
|
|
351
438
|
fromBlock,
|
|
352
439
|
toBlock: targetBlock,
|
|
353
|
-
depositsApplied,
|
|
354
|
-
spendsApplied
|
|
440
|
+
depositsApplied: mapped.depositsApplied,
|
|
441
|
+
spendsApplied: mapped.spendsApplied
|
|
355
442
|
};
|
|
356
443
|
}
|
|
357
444
|
async fetchIndexerEvents() {
|
|
445
|
+
const { deposits, spends } = await this.fetchIndexerRows();
|
|
446
|
+
return {
|
|
447
|
+
deposits,
|
|
448
|
+
spends,
|
|
449
|
+
maxBlockNumber: this.computeIndexerMaxBlock(deposits, spends)
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
async indexerPost(query, variables) {
|
|
358
453
|
if (!this.indexerGraphqlUrl) {
|
|
359
454
|
throw new Error('indexerGraphqlUrl is not configured');
|
|
360
455
|
}
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
})
|
|
372
|
-
});
|
|
373
|
-
if (!response.ok) {
|
|
374
|
-
const text = await response.text();
|
|
375
|
-
throw new Error(`indexer graphql request failed: ${response.status} ${text}`);
|
|
376
|
-
}
|
|
377
|
-
const payload = (await response.json());
|
|
378
|
-
if (payload.errors && payload.errors.length > 0) {
|
|
379
|
-
const message = payload.errors.map((error) => error.message ?? 'unknown error').join('; ');
|
|
380
|
-
throw new Error(`indexer graphql error: ${message}`);
|
|
381
|
-
}
|
|
382
|
-
if (!payload.data) {
|
|
383
|
-
throw new Error('indexer graphql response missing data');
|
|
384
|
-
}
|
|
385
|
-
return payload.data;
|
|
386
|
-
};
|
|
387
|
-
if (!this.indexerFieldNames) {
|
|
388
|
-
const introspection = await post(`query WalletIndexerFields { __schema { queryType { fields { name } } } }`);
|
|
389
|
-
const fieldNames = introspection.__schema.queryType.fields.map((field) => field.name);
|
|
390
|
-
const pickField = (needle) => fieldNames.find((name) => name.toLowerCase().includes(needle) &&
|
|
391
|
-
!name.toLowerCase().includes('aggregate') &&
|
|
392
|
-
!name.toLowerCase().includes('by_pk'));
|
|
393
|
-
const deposits = pickField('shieldedpool_deposited');
|
|
394
|
-
const spends = pickField('shieldedpool_spent');
|
|
395
|
-
if (!deposits || !spends) {
|
|
396
|
-
throw new Error(`unable to detect Envio fields for deposits/spends at ${endpoint}; available fields: ${fieldNames.join(', ')}`);
|
|
397
|
-
}
|
|
398
|
-
this.indexerFieldNames = { deposits, spends };
|
|
456
|
+
const payload = await postJson(fetch, this.indexerGraphqlUrl, {
|
|
457
|
+
query,
|
|
458
|
+
...(variables ? { variables } : {})
|
|
459
|
+
}, { errorPrefix: 'indexer graphql request failed' });
|
|
460
|
+
if (payload.errors && payload.errors.length > 0) {
|
|
461
|
+
const message = payload.errors.map((error) => error.message ?? 'unknown error').join('; ');
|
|
462
|
+
throw new Error(`indexer graphql error: ${message}`);
|
|
463
|
+
}
|
|
464
|
+
if (!payload.data) {
|
|
465
|
+
throw new Error('indexer graphql response missing data');
|
|
399
466
|
}
|
|
400
|
-
|
|
401
|
-
|
|
467
|
+
return payload.data;
|
|
468
|
+
}
|
|
469
|
+
async ensureIndexerFieldNames() {
|
|
470
|
+
if (this.indexerFieldNames) {
|
|
471
|
+
return this.indexerFieldNames;
|
|
472
|
+
}
|
|
473
|
+
const introspection = await this.indexerPost(`query WalletIndexerFields { __schema { queryType { fields { name } } } }`);
|
|
474
|
+
const fieldNames = introspection.__schema.queryType.fields.map((field) => field.name);
|
|
475
|
+
const pickField = (needle) => fieldNames.find((name) => name.toLowerCase().includes(needle) &&
|
|
476
|
+
!name.toLowerCase().includes('aggregate') &&
|
|
477
|
+
!name.toLowerCase().includes('by_pk'));
|
|
478
|
+
const deposits = pickField('shieldedpool_deposited');
|
|
479
|
+
const spends = pickField('shieldedpool_spent');
|
|
480
|
+
if (!deposits || !spends) {
|
|
481
|
+
throw new Error(`unable to detect Envio fields for deposits/spends at ${this.indexerGraphqlUrl}; available fields: ${fieldNames.join(', ')}`);
|
|
482
|
+
}
|
|
483
|
+
this.indexerFieldNames = { deposits, spends };
|
|
484
|
+
return this.indexerFieldNames;
|
|
485
|
+
}
|
|
486
|
+
async fetchIndexerRows() {
|
|
487
|
+
const { deposits: depositsField, spends: spendsField } = await this.ensureIndexerFieldNames();
|
|
402
488
|
const withPaginationQuery = `
|
|
403
489
|
query WalletIndexerData($limit: Int!, $offset: Int!) {
|
|
404
490
|
deposits: ${depositsField}(limit: $limit, offset: $offset) {
|
|
@@ -434,23 +520,28 @@ export class FileBackedWalletState {
|
|
|
434
520
|
let deposits = [];
|
|
435
521
|
let spends = [];
|
|
436
522
|
try {
|
|
437
|
-
const pageSize = 500;
|
|
438
523
|
let offset = 0;
|
|
439
524
|
while (true) {
|
|
440
|
-
const page = await
|
|
525
|
+
const page = await this.indexerPost(withPaginationQuery, {
|
|
526
|
+
limit: INDEXER_PAGE_SIZE,
|
|
527
|
+
offset
|
|
528
|
+
});
|
|
441
529
|
deposits = deposits.concat(page.deposits);
|
|
442
530
|
spends = spends.concat(page.spends);
|
|
443
|
-
if (page.deposits.length <
|
|
531
|
+
if (page.deposits.length < INDEXER_PAGE_SIZE && page.spends.length < INDEXER_PAGE_SIZE) {
|
|
444
532
|
break;
|
|
445
533
|
}
|
|
446
|
-
offset +=
|
|
534
|
+
offset += INDEXER_PAGE_SIZE;
|
|
447
535
|
}
|
|
448
536
|
}
|
|
449
537
|
catch {
|
|
450
|
-
const data = await
|
|
538
|
+
const data = await this.indexerPost(fullQuery);
|
|
451
539
|
deposits = data.deposits;
|
|
452
540
|
spends = data.spends;
|
|
453
541
|
}
|
|
542
|
+
return { deposits, spends };
|
|
543
|
+
}
|
|
544
|
+
computeIndexerMaxBlock(deposits, spends) {
|
|
454
545
|
let maxBlockNumber = this.state.lastSyncedBlock;
|
|
455
546
|
for (const deposit of deposits) {
|
|
456
547
|
const position = parseEventPositionFromId(deposit.id);
|
|
@@ -464,11 +555,11 @@ export class FileBackedWalletState {
|
|
|
464
555
|
maxBlockNumber = position.blockNumber;
|
|
465
556
|
}
|
|
466
557
|
}
|
|
467
|
-
return
|
|
558
|
+
return maxBlockNumber;
|
|
468
559
|
}
|
|
469
|
-
getSpendContextByCommitment(commitment
|
|
470
|
-
const
|
|
471
|
-
const note = this.state.notes
|
|
560
|
+
getSpendContextByCommitment(commitment) {
|
|
561
|
+
const noteIndex = this.findNoteIndexByCommitment(commitment);
|
|
562
|
+
const note = noteIndex >= 0 ? this.state.notes[noteIndex] : undefined;
|
|
472
563
|
if (!note) {
|
|
473
564
|
throw new Error(`note not found in wallet state for commitment ${commitment}; add note secrets first with addOrUpdateNote()`);
|
|
474
565
|
}
|
|
@@ -488,19 +579,19 @@ export class FileBackedWalletState {
|
|
|
488
579
|
return {
|
|
489
580
|
note: { ...note, leafIndex },
|
|
490
581
|
witness,
|
|
491
|
-
|
|
582
|
+
nullifierSecret: note.nullifierSecret
|
|
492
583
|
};
|
|
493
584
|
}
|
|
585
|
+
findNoteIndexByCommitment(commitment) {
|
|
586
|
+
const normalized = normalizeHex(commitment);
|
|
587
|
+
const noteIndexes = this.noteIndexesByCommitment.get(normalized);
|
|
588
|
+
if (!noteIndexes || noteIndexes.length === 0)
|
|
589
|
+
return -1;
|
|
590
|
+
return noteIndexes[0] ?? -1;
|
|
591
|
+
}
|
|
494
592
|
findLeafIndex(commitment) {
|
|
495
593
|
const normalized = normalizeHex(commitment);
|
|
496
|
-
|
|
497
|
-
const candidate = this.state.commitments[i];
|
|
498
|
-
if (!candidate)
|
|
499
|
-
continue;
|
|
500
|
-
if (normalizeHex(candidate) === normalized)
|
|
501
|
-
return i;
|
|
502
|
-
}
|
|
503
|
-
return -1;
|
|
594
|
+
return this.leafIndexByCommitment.get(normalized) ?? -1;
|
|
504
595
|
}
|
|
505
596
|
}
|
|
506
597
|
function parseEventPositionFromId(id) {
|