@shielded-x402/client 0.3.0 → 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 +1 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -22
- 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 +0 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/walletState.d.ts +15 -1
- package/dist/walletState.d.ts.map +1 -1
- package/dist/walletState.js +272 -187
- package/dist/walletState.js.map +1 -1
- package/package.json +3 -2
- 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,
|
|
@@ -23,10 +23,45 @@ function serialize(state, poolAddress) {
|
|
|
23
23
|
leafIndex: note.leafIndex,
|
|
24
24
|
...(note.depositBlock !== undefined ? { depositBlock: note.depositBlock.toString() } : {}),
|
|
25
25
|
...(note.spent !== undefined ? { spent: note.spent } : {})
|
|
26
|
-
}))
|
|
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
|
+
]))
|
|
27
44
|
};
|
|
28
45
|
}
|
|
29
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
|
+
}
|
|
30
65
|
return {
|
|
31
66
|
lastSyncedBlock: BigInt(payload.lastSyncedBlock),
|
|
32
67
|
commitments: payload.commitments,
|
|
@@ -39,7 +74,8 @@ function deserialize(payload) {
|
|
|
39
74
|
leafIndex: note.leafIndex,
|
|
40
75
|
...(note.depositBlock !== undefined ? { depositBlock: BigInt(note.depositBlock) } : {}),
|
|
41
76
|
...(note.spent !== undefined ? { spent: note.spent } : {})
|
|
42
|
-
}))
|
|
77
|
+
})),
|
|
78
|
+
creditStates
|
|
43
79
|
};
|
|
44
80
|
}
|
|
45
81
|
export class FileBackedWalletState {
|
|
@@ -52,6 +88,8 @@ export class FileBackedWalletState {
|
|
|
52
88
|
startBlock;
|
|
53
89
|
indexerFieldNames;
|
|
54
90
|
state;
|
|
91
|
+
noteIndexesByCommitment;
|
|
92
|
+
leafIndexByCommitment;
|
|
55
93
|
constructor(config) {
|
|
56
94
|
this.filePath = config.filePath;
|
|
57
95
|
this.rpcUrl = config.rpcUrl;
|
|
@@ -63,8 +101,12 @@ export class FileBackedWalletState {
|
|
|
63
101
|
this.state = {
|
|
64
102
|
lastSyncedBlock: this.startBlock - 1n,
|
|
65
103
|
commitments: [],
|
|
66
|
-
notes: []
|
|
104
|
+
notes: [],
|
|
105
|
+
creditStates: {}
|
|
67
106
|
};
|
|
107
|
+
this.noteIndexesByCommitment = new Map();
|
|
108
|
+
this.leafIndexByCommitment = new Map();
|
|
109
|
+
this.rebuildCommitmentIndexes();
|
|
68
110
|
}
|
|
69
111
|
static async create(config) {
|
|
70
112
|
const instance = new FileBackedWalletState(config);
|
|
@@ -75,13 +117,14 @@ export class FileBackedWalletState {
|
|
|
75
117
|
try {
|
|
76
118
|
const raw = await readFile(this.filePath, 'utf8');
|
|
77
119
|
const parsed = JSON.parse(raw);
|
|
78
|
-
if (parsed.version !==
|
|
120
|
+
if (parsed.version !== 3) {
|
|
79
121
|
throw new Error(`unsupported wallet state version: ${String(parsed.version)}`);
|
|
80
122
|
}
|
|
81
123
|
if (normalizeHex(parsed.poolAddress) !== normalizeHex(this.shieldedPoolAddress)) {
|
|
82
124
|
throw new Error(`wallet state pool mismatch: expected ${this.shieldedPoolAddress}, found ${parsed.poolAddress}`);
|
|
83
125
|
}
|
|
84
126
|
this.state = deserialize(parsed);
|
|
127
|
+
this.rebuildCommitmentIndexes();
|
|
85
128
|
}
|
|
86
129
|
catch (error) {
|
|
87
130
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -108,9 +151,30 @@ export class FileBackedWalletState {
|
|
|
108
151
|
getNotes() {
|
|
109
152
|
return this.state.notes.map((note) => ({ ...note }));
|
|
110
153
|
}
|
|
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
|
+
}
|
|
111
176
|
async addOrUpdateNote(note, nullifierSecret, depositBlock) {
|
|
112
|
-
const
|
|
113
|
-
const existingIndex = this.state.notes.findIndex((candidate) => normalizeHex(candidate.commitment) === normalizedCommitment);
|
|
177
|
+
const existingIndex = this.findNoteIndexByCommitment(note.commitment);
|
|
114
178
|
const existing = existingIndex >= 0 ? this.state.notes[existingIndex] : undefined;
|
|
115
179
|
const record = {
|
|
116
180
|
...note,
|
|
@@ -127,11 +191,11 @@ export class FileBackedWalletState {
|
|
|
127
191
|
if (record.leafIndex >= 0) {
|
|
128
192
|
this.state.commitments[record.leafIndex] = record.commitment;
|
|
129
193
|
}
|
|
194
|
+
this.rebuildCommitmentIndexes();
|
|
130
195
|
await this.persist();
|
|
131
196
|
}
|
|
132
197
|
async markNoteSpent(commitment) {
|
|
133
|
-
const
|
|
134
|
-
const existingIndex = this.state.notes.findIndex((candidate) => normalizeHex(candidate.commitment) === normalized);
|
|
198
|
+
const existingIndex = this.findNoteIndexByCommitment(commitment);
|
|
135
199
|
if (existingIndex < 0) {
|
|
136
200
|
return false;
|
|
137
201
|
}
|
|
@@ -151,6 +215,7 @@ export class FileBackedWalletState {
|
|
|
151
215
|
this.state.commitments[params.merchantLeafIndex] = params.merchantCommitment;
|
|
152
216
|
this.state.commitments[params.changeLeafIndex] = params.changeCommitment;
|
|
153
217
|
}
|
|
218
|
+
this.rebuildCommitmentIndexes();
|
|
154
219
|
await this.persist();
|
|
155
220
|
}
|
|
156
221
|
async applyRelayerSettlement(params) {
|
|
@@ -177,6 +242,131 @@ export class FileBackedWalletState {
|
|
|
177
242
|
}, params.changeNullifierSecret, this.state.lastSyncedBlock >= 0n ? this.state.lastSyncedBlock : undefined);
|
|
178
243
|
}
|
|
179
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;
|
|
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 };
|
|
369
|
+
}
|
|
180
370
|
async sync() {
|
|
181
371
|
if (this.indexerGraphqlUrl) {
|
|
182
372
|
return this.syncFromIndexer();
|
|
@@ -218,68 +408,13 @@ export class FileBackedWalletState {
|
|
|
218
408
|
toBlock
|
|
219
409
|
})
|
|
220
410
|
]);
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (args.commitment === undefined || args.leafIndex === undefined)
|
|
225
|
-
continue;
|
|
226
|
-
writes.push({
|
|
227
|
-
blockNumber: log.blockNumber ?? 0n,
|
|
228
|
-
logIndex: log.logIndex ?? 0,
|
|
229
|
-
order: 0,
|
|
230
|
-
leafIndex: Number(args.leafIndex),
|
|
231
|
-
commitment: args.commitment
|
|
232
|
-
});
|
|
233
|
-
depositsApplied += 1;
|
|
234
|
-
}
|
|
235
|
-
for (const log of spendLogs) {
|
|
236
|
-
const args = log.args;
|
|
237
|
-
if (args.merchantCommitment === undefined ||
|
|
238
|
-
args.changeCommitment === undefined ||
|
|
239
|
-
args.merchantLeafIndex === undefined ||
|
|
240
|
-
args.changeLeafIndex === undefined) {
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
writes.push({
|
|
244
|
-
blockNumber: log.blockNumber ?? 0n,
|
|
245
|
-
logIndex: log.logIndex ?? 0,
|
|
246
|
-
order: 0,
|
|
247
|
-
leafIndex: Number(args.merchantLeafIndex),
|
|
248
|
-
commitment: args.merchantCommitment
|
|
249
|
-
});
|
|
250
|
-
writes.push({
|
|
251
|
-
blockNumber: log.blockNumber ?? 0n,
|
|
252
|
-
logIndex: log.logIndex ?? 0,
|
|
253
|
-
order: 1,
|
|
254
|
-
leafIndex: Number(args.changeLeafIndex),
|
|
255
|
-
commitment: args.changeCommitment
|
|
256
|
-
});
|
|
257
|
-
spendsApplied += 1;
|
|
258
|
-
}
|
|
259
|
-
writes.sort((a, b) => {
|
|
260
|
-
if (a.blockNumber !== b.blockNumber)
|
|
261
|
-
return a.blockNumber < b.blockNumber ? -1 : 1;
|
|
262
|
-
if (a.logIndex !== b.logIndex)
|
|
263
|
-
return a.logIndex < b.logIndex ? -1 : 1;
|
|
264
|
-
return a.order - b.order;
|
|
411
|
+
const mapped = this.mapRpcEventsToWrites({
|
|
412
|
+
depositLogs: depositLogs,
|
|
413
|
+
spendLogs: spendLogs
|
|
265
414
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.state.commitments[write.leafIndex] = write.commitment;
|
|
270
|
-
const normalized = normalizeHex(write.commitment);
|
|
271
|
-
for (let i = 0; i < this.state.notes.length; i += 1) {
|
|
272
|
-
const note = this.state.notes[i];
|
|
273
|
-
if (!note)
|
|
274
|
-
continue;
|
|
275
|
-
if (normalizeHex(note.commitment) !== normalized)
|
|
276
|
-
continue;
|
|
277
|
-
note.leafIndex = write.leafIndex;
|
|
278
|
-
if (note.depositBlock === undefined) {
|
|
279
|
-
note.depositBlock = write.blockNumber;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
415
|
+
this.applyCommitmentWrites(mapped.writes);
|
|
416
|
+
depositsApplied += mapped.depositsApplied;
|
|
417
|
+
spendsApplied += mapped.spendsApplied;
|
|
283
418
|
this.state.lastSyncedBlock = toBlock;
|
|
284
419
|
await this.persist();
|
|
285
420
|
cursor = toBlock + 1n;
|
|
@@ -294,117 +429,62 @@ export class FileBackedWalletState {
|
|
|
294
429
|
async syncFromIndexer() {
|
|
295
430
|
const fromBlock = this.state.lastSyncedBlock + 1n;
|
|
296
431
|
const { deposits, spends, maxBlockNumber } = await this.fetchIndexerEvents();
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
let spendsApplied = 0;
|
|
300
|
-
for (const deposit of deposits) {
|
|
301
|
-
const { blockNumber, logIndex } = parseEventPositionFromId(deposit.id);
|
|
302
|
-
writes.push({
|
|
303
|
-
blockNumber,
|
|
304
|
-
logIndex,
|
|
305
|
-
order: 0,
|
|
306
|
-
leafIndex: Number(deposit.leafIndex),
|
|
307
|
-
commitment: deposit.commitment
|
|
308
|
-
});
|
|
309
|
-
depositsApplied += 1;
|
|
310
|
-
}
|
|
311
|
-
for (const spend of spends) {
|
|
312
|
-
const { blockNumber, logIndex } = parseEventPositionFromId(spend.id);
|
|
313
|
-
writes.push({
|
|
314
|
-
blockNumber,
|
|
315
|
-
logIndex,
|
|
316
|
-
order: 0,
|
|
317
|
-
leafIndex: Number(spend.merchantLeafIndex),
|
|
318
|
-
commitment: spend.merchantCommitment
|
|
319
|
-
});
|
|
320
|
-
writes.push({
|
|
321
|
-
blockNumber,
|
|
322
|
-
logIndex,
|
|
323
|
-
order: 1,
|
|
324
|
-
leafIndex: Number(spend.changeLeafIndex),
|
|
325
|
-
commitment: spend.changeCommitment
|
|
326
|
-
});
|
|
327
|
-
spendsApplied += 1;
|
|
328
|
-
}
|
|
329
|
-
writes.sort((a, b) => {
|
|
330
|
-
if (a.blockNumber !== b.blockNumber)
|
|
331
|
-
return a.blockNumber < b.blockNumber ? -1 : 1;
|
|
332
|
-
if (a.logIndex !== b.logIndex)
|
|
333
|
-
return a.logIndex < b.logIndex ? -1 : 1;
|
|
334
|
-
return a.order - b.order;
|
|
335
|
-
});
|
|
336
|
-
for (const write of writes) {
|
|
337
|
-
if (write.leafIndex < 0)
|
|
338
|
-
continue;
|
|
339
|
-
this.state.commitments[write.leafIndex] = write.commitment;
|
|
340
|
-
const normalized = normalizeHex(write.commitment);
|
|
341
|
-
for (let i = 0; i < this.state.notes.length; i += 1) {
|
|
342
|
-
const note = this.state.notes[i];
|
|
343
|
-
if (!note)
|
|
344
|
-
continue;
|
|
345
|
-
if (normalizeHex(note.commitment) !== normalized)
|
|
346
|
-
continue;
|
|
347
|
-
note.leafIndex = write.leafIndex;
|
|
348
|
-
if (note.depositBlock === undefined) {
|
|
349
|
-
note.depositBlock = write.blockNumber;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
432
|
+
const mapped = this.mapIndexerEventsToWrites(deposits, spends);
|
|
433
|
+
this.applyCommitmentWrites(mapped.writes);
|
|
353
434
|
const targetBlock = maxBlockNumber >= this.state.lastSyncedBlock ? maxBlockNumber : this.state.lastSyncedBlock;
|
|
354
435
|
this.state.lastSyncedBlock = targetBlock;
|
|
355
436
|
await this.persist();
|
|
356
437
|
return {
|
|
357
438
|
fromBlock,
|
|
358
439
|
toBlock: targetBlock,
|
|
359
|
-
depositsApplied,
|
|
360
|
-
spendsApplied
|
|
440
|
+
depositsApplied: mapped.depositsApplied,
|
|
441
|
+
spendsApplied: mapped.spendsApplied
|
|
361
442
|
};
|
|
362
443
|
}
|
|
363
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) {
|
|
364
453
|
if (!this.indexerGraphqlUrl) {
|
|
365
454
|
throw new Error('indexerGraphqlUrl is not configured');
|
|
366
455
|
}
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
})
|
|
378
|
-
});
|
|
379
|
-
if (!response.ok) {
|
|
380
|
-
const text = await response.text();
|
|
381
|
-
throw new Error(`indexer graphql request failed: ${response.status} ${text}`);
|
|
382
|
-
}
|
|
383
|
-
const payload = (await response.json());
|
|
384
|
-
if (payload.errors && payload.errors.length > 0) {
|
|
385
|
-
const message = payload.errors.map((error) => error.message ?? 'unknown error').join('; ');
|
|
386
|
-
throw new Error(`indexer graphql error: ${message}`);
|
|
387
|
-
}
|
|
388
|
-
if (!payload.data) {
|
|
389
|
-
throw new Error('indexer graphql response missing data');
|
|
390
|
-
}
|
|
391
|
-
return payload.data;
|
|
392
|
-
};
|
|
393
|
-
if (!this.indexerFieldNames) {
|
|
394
|
-
const introspection = await post(`query WalletIndexerFields { __schema { queryType { fields { name } } } }`);
|
|
395
|
-
const fieldNames = introspection.__schema.queryType.fields.map((field) => field.name);
|
|
396
|
-
const pickField = (needle) => fieldNames.find((name) => name.toLowerCase().includes(needle) &&
|
|
397
|
-
!name.toLowerCase().includes('aggregate') &&
|
|
398
|
-
!name.toLowerCase().includes('by_pk'));
|
|
399
|
-
const deposits = pickField('shieldedpool_deposited');
|
|
400
|
-
const spends = pickField('shieldedpool_spent');
|
|
401
|
-
if (!deposits || !spends) {
|
|
402
|
-
throw new Error(`unable to detect Envio fields for deposits/spends at ${endpoint}; available fields: ${fieldNames.join(', ')}`);
|
|
403
|
-
}
|
|
404
|
-
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');
|
|
405
466
|
}
|
|
406
|
-
|
|
407
|
-
|
|
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();
|
|
408
488
|
const withPaginationQuery = `
|
|
409
489
|
query WalletIndexerData($limit: Int!, $offset: Int!) {
|
|
410
490
|
deposits: ${depositsField}(limit: $limit, offset: $offset) {
|
|
@@ -440,23 +520,28 @@ export class FileBackedWalletState {
|
|
|
440
520
|
let deposits = [];
|
|
441
521
|
let spends = [];
|
|
442
522
|
try {
|
|
443
|
-
const pageSize = 500;
|
|
444
523
|
let offset = 0;
|
|
445
524
|
while (true) {
|
|
446
|
-
const page = await
|
|
525
|
+
const page = await this.indexerPost(withPaginationQuery, {
|
|
526
|
+
limit: INDEXER_PAGE_SIZE,
|
|
527
|
+
offset
|
|
528
|
+
});
|
|
447
529
|
deposits = deposits.concat(page.deposits);
|
|
448
530
|
spends = spends.concat(page.spends);
|
|
449
|
-
if (page.deposits.length <
|
|
531
|
+
if (page.deposits.length < INDEXER_PAGE_SIZE && page.spends.length < INDEXER_PAGE_SIZE) {
|
|
450
532
|
break;
|
|
451
533
|
}
|
|
452
|
-
offset +=
|
|
534
|
+
offset += INDEXER_PAGE_SIZE;
|
|
453
535
|
}
|
|
454
536
|
}
|
|
455
537
|
catch {
|
|
456
|
-
const data = await
|
|
538
|
+
const data = await this.indexerPost(fullQuery);
|
|
457
539
|
deposits = data.deposits;
|
|
458
540
|
spends = data.spends;
|
|
459
541
|
}
|
|
542
|
+
return { deposits, spends };
|
|
543
|
+
}
|
|
544
|
+
computeIndexerMaxBlock(deposits, spends) {
|
|
460
545
|
let maxBlockNumber = this.state.lastSyncedBlock;
|
|
461
546
|
for (const deposit of deposits) {
|
|
462
547
|
const position = parseEventPositionFromId(deposit.id);
|
|
@@ -470,11 +555,11 @@ export class FileBackedWalletState {
|
|
|
470
555
|
maxBlockNumber = position.blockNumber;
|
|
471
556
|
}
|
|
472
557
|
}
|
|
473
|
-
return
|
|
558
|
+
return maxBlockNumber;
|
|
474
559
|
}
|
|
475
560
|
getSpendContextByCommitment(commitment) {
|
|
476
|
-
const
|
|
477
|
-
const note = this.state.notes
|
|
561
|
+
const noteIndex = this.findNoteIndexByCommitment(commitment);
|
|
562
|
+
const note = noteIndex >= 0 ? this.state.notes[noteIndex] : undefined;
|
|
478
563
|
if (!note) {
|
|
479
564
|
throw new Error(`note not found in wallet state for commitment ${commitment}; add note secrets first with addOrUpdateNote()`);
|
|
480
565
|
}
|
|
@@ -497,16 +582,16 @@ export class FileBackedWalletState {
|
|
|
497
582
|
nullifierSecret: note.nullifierSecret
|
|
498
583
|
};
|
|
499
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
|
+
}
|
|
500
592
|
findLeafIndex(commitment) {
|
|
501
593
|
const normalized = normalizeHex(commitment);
|
|
502
|
-
|
|
503
|
-
const candidate = this.state.commitments[i];
|
|
504
|
-
if (!candidate)
|
|
505
|
-
continue;
|
|
506
|
-
if (normalizeHex(candidate) === normalized)
|
|
507
|
-
return i;
|
|
508
|
-
}
|
|
509
|
-
return -1;
|
|
594
|
+
return this.leafIndexByCommitment.get(normalized) ?? -1;
|
|
510
595
|
}
|
|
511
596
|
}
|
|
512
597
|
function parseEventPositionFromId(id) {
|