@invisi-labs/sdk 1.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.
@@ -0,0 +1,4464 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/integrations/indexeddb.ts
21
+ var indexeddb_exports = {};
22
+ __export(indexeddb_exports, {
23
+ IndexedDBAtomicRecords: () => IndexedDBAtomicRecords,
24
+ IndexedDBWalletStore: () => IndexedDBWalletStore
25
+ });
26
+ module.exports = __toCommonJS(indexeddb_exports);
27
+
28
+ // src/integrations/ethers.ts
29
+ var import_ethers = require("ethers");
30
+ var import_contracts = require("@invisi-labs/protocol/contracts");
31
+ var import_address = require("@invisi-labs/core/address");
32
+ var getAddress = (value) => (0, import_ethers.getAddress)(value);
33
+ function check(value, message) {
34
+ if (!value) {
35
+ throw new Error(message);
36
+ }
37
+ }
38
+ async function chain(client, expected) {
39
+ check((await client.getNetwork()).chainId === expected, "EVM client chain mismatch");
40
+ }
41
+ function number(value) {
42
+ check(typeof value === "bigint" && value >= 0n, "invalid chain integer");
43
+ return value;
44
+ }
45
+ function boolean(value) {
46
+ check(typeof value === "boolean", "invalid chain boolean");
47
+ return value;
48
+ }
49
+ function word(value) {
50
+ check(typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value), "invalid chain word");
51
+ return value.toLowerCase();
52
+ }
53
+ function ownerWord(value) {
54
+ const identity = import_address.ReceivingAddress.fromIdentity(value).walletIdentity;
55
+ return (0, import_ethers.zeroPadValue)(identity, 32);
56
+ }
57
+ var EthersChainReader = class {
58
+ constructor(client) {
59
+ this.client = client;
60
+ }
61
+ async getChainId() {
62
+ return (await this.client.getNetwork()).chainId;
63
+ }
64
+ async getProtocolVersion(address2) {
65
+ return BigInt(
66
+ await new import_ethers.Contract(getAddress(address2), import_contracts.invisiProtocolAbi, this.client).getFunction(
67
+ "VERSION"
68
+ )()
69
+ );
70
+ }
71
+ async getCode(address2) {
72
+ return await this.client.getCode(getAddress(address2));
73
+ }
74
+ async getFactoryInstanceAddress(factory, salt) {
75
+ return getAddress(
76
+ await new import_ethers.Contract(factory, import_contracts.invisiFactoryAbi, this.client).getFunction(
77
+ "getInstanceAddress"
78
+ )(salt)
79
+ );
80
+ }
81
+ async isFactoryInstance(factory, instance) {
82
+ return boolean(
83
+ await new import_ethers.Contract(factory, import_contracts.invisiFactoryAbi, this.client).getFunction("isInstance")(
84
+ instance
85
+ )
86
+ );
87
+ }
88
+ async getInstanceFactory(instance) {
89
+ return getAddress(
90
+ await new import_ethers.Contract(instance, import_contracts.invisiProtocolAbi, this.client).getFunction("factory")()
91
+ );
92
+ }
93
+ async getLatestBlockNumber() {
94
+ return BigInt(await this.client.getBlockNumber());
95
+ }
96
+ async getBlock(blockNumber) {
97
+ const block = await this.client.getBlock(blockNumber);
98
+ check(
99
+ block && BigInt(block.number) === blockNumber && block.hash && block.parentHash,
100
+ "invalid canonical block"
101
+ );
102
+ return {
103
+ number: BigInt(block.number),
104
+ hash: word(block.hash),
105
+ parentHash: word(block.parentHash),
106
+ timestamp: number(BigInt(block.timestamp))
107
+ };
108
+ }
109
+ async read(context, address2, abi, functionName, args, block) {
110
+ await chain(this.client, context.chainId);
111
+ if (block) {
112
+ check(
113
+ (await this.getBlock(block.number)).hash === block.hash,
114
+ "canonical read block mismatch"
115
+ );
116
+ }
117
+ return new import_ethers.Contract(getAddress(address2), abi, this.client).getFunction(functionName)(
118
+ ...args,
119
+ ...block ? [{ blockTag: block.number }] : []
120
+ );
121
+ }
122
+ async getFundingNonce(context, payer, block) {
123
+ return number(
124
+ await this.read(
125
+ context,
126
+ context.protocolAddress,
127
+ import_contracts.invisiProtocolAbi,
128
+ "nonces",
129
+ [getAddress(payer)],
130
+ block
131
+ )
132
+ );
133
+ }
134
+ async getRegistryNonce(context, walletIdentity, block) {
135
+ return number(
136
+ await this.read(
137
+ context,
138
+ context.registryAddress,
139
+ import_contracts.invisiAddressRegistryAbi,
140
+ "nonceOf",
141
+ [ownerWord(walletIdentity)],
142
+ block
143
+ )
144
+ );
145
+ }
146
+ async isKnownRoot(context, treeId, root, block) {
147
+ return boolean(
148
+ await this.read(
149
+ context,
150
+ context.protocolAddress,
151
+ import_contracts.invisiProtocolAbi,
152
+ "rootHistory",
153
+ [number(treeId), word(root)],
154
+ block
155
+ )
156
+ );
157
+ }
158
+ async isSpent(context, nullifiers, block) {
159
+ const copied = nullifiers.map(word);
160
+ const result = [];
161
+ for (let offset = 0; offset < copied.length; offset += 32) {
162
+ result.push(
163
+ ...await Promise.all(
164
+ copied.slice(offset, offset + 32).map(
165
+ (nf) => this.read(
166
+ context,
167
+ context.protocolAddress,
168
+ import_contracts.invisiProtocolAbi,
169
+ "isNullifierSpent",
170
+ [nf],
171
+ block
172
+ ).then(boolean)
173
+ )
174
+ )
175
+ );
176
+ }
177
+ return result;
178
+ }
179
+ async getTransaction(hash2) {
180
+ try {
181
+ const tx = await this.client.getTransaction(word(hash2));
182
+ if (!tx?.to) {
183
+ return null;
184
+ }
185
+ return {
186
+ transactionHash: word(tx.hash),
187
+ to: getAddress(tx.to),
188
+ from: getAddress(tx.from),
189
+ nonce: number(BigInt(tx.nonce)),
190
+ data: tx.data,
191
+ value: number(tx.value)
192
+ };
193
+ } catch (error) {
194
+ if (error instanceof Error && error.name === "TransactionNotFoundError") {
195
+ return null;
196
+ }
197
+ throw error;
198
+ }
199
+ }
200
+ async getReceipt(hash2) {
201
+ try {
202
+ const receipt = await this.client.getTransactionReceipt(word(hash2));
203
+ if (!receipt) return null;
204
+ const block = await this.getBlock(BigInt(receipt.blockNumber));
205
+ check(block.hash === receipt.blockHash, "receipt block is no longer canonical");
206
+ return {
207
+ block,
208
+ transactionHash: word(receipt.hash),
209
+ status: receipt.status === 1 ? "success" : "reverted",
210
+ logs: receipt.logs.map((log) => {
211
+ check(
212
+ !log.removed && log.transactionHash === receipt.hash && log.index !== null,
213
+ "invalid receipt log"
214
+ );
215
+ return {
216
+ address: getAddress(log.address),
217
+ data: log.data,
218
+ topics: [...log.topics],
219
+ transactionHash: word(log.transactionHash),
220
+ logIndex: log.index
221
+ };
222
+ })
223
+ };
224
+ } catch (error) {
225
+ if (error instanceof Error && error.name === "TransactionReceiptNotFoundError") {
226
+ return null;
227
+ }
228
+ throw error;
229
+ }
230
+ }
231
+ };
232
+
233
+ // src/session/protocol-events.ts
234
+ var import_contracts2 = require("@invisi-labs/protocol/contracts");
235
+ var import_ethers2 = require("ethers");
236
+ var protocolInterface = new import_ethers2.Interface(import_contracts2.invisiProtocolAbi);
237
+ function decodeProtocolEvent(log) {
238
+ const decoded = protocolInterface.parseLog(log);
239
+ if (!decoded) throw new Error("Unknown protocol receipt event");
240
+ const encoded = protocolInterface.encodeEventLog(decoded.fragment, decoded.args);
241
+ if (encoded.data.toLowerCase() !== log.data.toLowerCase() || encoded.topics.length !== log.topics.length || encoded.topics.some(
242
+ (topic, index2) => topic.toLowerCase() !== log.topics[index2].toLowerCase()
243
+ ))
244
+ throw new Error("Noncanonical protocol receipt event");
245
+ const args = decoded.args.toObject(true);
246
+ for (const key of [
247
+ "tokens",
248
+ "commitments",
249
+ "nullifiers",
250
+ "outputCommitments",
251
+ "leafIndices",
252
+ "remainderLeafIndices"
253
+ ]) {
254
+ const values = decoded.args.getValue(key);
255
+ if (values) args[key] = values.toArray(true);
256
+ }
257
+ for (const key of ["sources", "deliveries"]) {
258
+ const values = decoded.args.getValue(key);
259
+ if (values) args[key] = values.map((value) => value.toObject(true));
260
+ }
261
+ const params = decoded.args.getValue("params");
262
+ if (params) {
263
+ const source = params.getValue("creation") ?? params;
264
+ const target = params.getValue("creation") ? args.params.creation : args.params;
265
+ for (const key of ["nullifiers", "remainderCommitments", "expiries", "recoveryAmounts"]) {
266
+ const values = source.getValue(key);
267
+ if (values) target[key] = values.toArray(true);
268
+ }
269
+ const sources = source.getValue("sources");
270
+ if (sources) target.sources = sources.map((value) => value.toObject(true));
271
+ }
272
+ return { eventName: decoded.name, args };
273
+ }
274
+
275
+ // src/session/receipt-logs.ts
276
+ function readProtocolLogs(receipt, protocolAddress) {
277
+ if (receipt.status !== "success" || !/^0x[0-9a-fA-F]{64}$/.test(receipt.transactionHash) || typeof receipt.block.number !== "bigint" || receipt.block.number < 0n || !/^0x[0-9a-fA-F]{64}$/.test(receipt.block.hash))
278
+ throw new Error("Invalid canonical protocol receipt");
279
+ const seen = /* @__PURE__ */ new Set();
280
+ return receipt.logs.filter((log) => log.address.toLowerCase() === protocolAddress.toLowerCase()).map((log) => {
281
+ if (log.transactionHash.toLowerCase() !== receipt.transactionHash.toLowerCase() || !Number.isSafeInteger(log.logIndex) || log.logIndex < 0 || seen.has(log.logIndex))
282
+ throw new Error("Invalid canonical receipt log identity");
283
+ seen.add(log.logIndex);
284
+ const decoded = decodeProtocolEvent(log);
285
+ return {
286
+ address: log.address,
287
+ eventName: decoded.eventName,
288
+ args: decoded.args,
289
+ blockNumber: receipt.block.number,
290
+ blockHash: receipt.block.hash,
291
+ transactionHash: receipt.transactionHash.toLowerCase(),
292
+ logIndex: log.logIndex,
293
+ removed: false
294
+ };
295
+ }).sort((a, b) => a.logIndex - b.logIndex);
296
+ }
297
+
298
+ // src/session/discovery.ts
299
+ var import_ethers3 = require("ethers");
300
+ var import_core = require("@invisi-labs/core");
301
+ var import_protocol = require("@invisi-labs/protocol");
302
+ var EVENTS = /* @__PURE__ */ new Set([
303
+ "CommitmentsInserted",
304
+ "BatchOutputs",
305
+ "Shield",
306
+ "Transact",
307
+ "CrossTreeTransact",
308
+ "CreditCreated",
309
+ "CreditDraw",
310
+ "CreditToppedUp",
311
+ "CreditCancelled"
312
+ ]);
313
+ function check2(ok, message) {
314
+ if (!ok) throw new Error(message);
315
+ }
316
+ function object(value) {
317
+ check2(value && typeof value === "object" && !Array.isArray(value), "invalid event object");
318
+ return value;
319
+ }
320
+ function array(value) {
321
+ check2(Array.isArray(value), "invalid event array");
322
+ check2(
323
+ Array.from({ length: value.length }, (_, i) => i in value).every(Boolean),
324
+ "sparse event array"
325
+ );
326
+ return value;
327
+ }
328
+ function integer(value) {
329
+ check2(
330
+ typeof value === "bigint" || typeof value === "number" && Number.isSafeInteger(value),
331
+ "invalid event integer"
332
+ );
333
+ const n = BigInt(value);
334
+ check2(n >= 0n, "negative event integer");
335
+ return n;
336
+ }
337
+ function index(value) {
338
+ const n = integer(value);
339
+ check2(n < 65536n, "invalid event leaf index");
340
+ return Number(n);
341
+ }
342
+ function hash(value) {
343
+ check2(typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value), "invalid event hash");
344
+ return value.toLowerCase();
345
+ }
346
+ function contextCheck(context) {
347
+ check2(context.chainId >= 0n && context.chainId < 1n << 64n, "invalid discovery chain");
348
+ check2(
349
+ (0, import_ethers3.isAddress)(context.protocolAddress) && BigInt(context.protocolAddress) !== 0n,
350
+ "invalid discovery instance/token"
351
+ );
352
+ }
353
+ var position = (tree, at) => `${tree}:${at}`;
354
+ function extractDeliveryCandidates(logs, context, confirmedThrough) {
355
+ contextCheck(context);
356
+ check2(confirmedThrough >= 0n, "invalid confirmed height");
357
+ const relevant = logs.filter(
358
+ (log) => log.address.toLowerCase() === context.protocolAddress.toLowerCase() && EVENTS.has(log.eventName)
359
+ );
360
+ const insertions = /* @__PURE__ */ new Map();
361
+ const metadata = (log) => {
362
+ check2(!log.removed, "discovery resync required after reorg");
363
+ check2(
364
+ typeof log.blockNumber === "bigint" && log.blockNumber >= 0n && log.blockNumber <= confirmedThrough,
365
+ "event is not confirmed"
366
+ );
367
+ check2(Number.isSafeInteger(log.logIndex) && log.logIndex >= 0, "invalid log position");
368
+ return {
369
+ blockNumber: log.blockNumber,
370
+ blockHash: hash(log.blockHash),
371
+ transactionHash: hash(log.transactionHash)
372
+ };
373
+ };
374
+ const insertionKey = (log, tree, at) => `${hash(log.transactionHash)}:${position(tree, at)}`;
375
+ for (const log of relevant) {
376
+ metadata(log);
377
+ if (log.eventName !== "CommitmentsInserted" && log.eventName !== "BatchOutputs") continue;
378
+ const a = object(log.args), tree = integer(a.treeId), start = index(a.startLeafIndex), leaves = array(a.commitments);
379
+ check2(tree > 0n && tree < 1n << 64n, "invalid insertion tree");
380
+ check2(start + leaves.length <= 65536, "insertion exceeds tree capacity");
381
+ for (const [i, leaf] of leaves.entries()) {
382
+ const key = insertionKey(log, tree, start + i), value = hash(leaf), previous = insertions.get(key);
383
+ check2(!previous || previous === value, "conflicting insertion events");
384
+ insertions.set(key, value);
385
+ }
386
+ }
387
+ const results = /* @__PURE__ */ new Map();
388
+ const add = (log, tree, at, commitment, role, wire, token = log.args?.token ?? log.args?.params?.token ?? log.args?.params?.creation?.token) => {
389
+ check2(tree > 0n && tree < 1n << 64n, "invalid output tree");
390
+ const inserted = insertions.get(insertionKey(log, tree, at));
391
+ check2(inserted, "delivery has no matching canonical insertion");
392
+ if (commitment !== void 0)
393
+ check2(hash(commitment) === inserted, "delivery commitment conflicts with insertion");
394
+ const delivery = (0, import_protocol.normalizeDelivery)(wire, { credit: role !== "funds" });
395
+ check2(
396
+ typeof token === "string" && (0, import_ethers3.isAddress)(token) && BigInt(token) !== 0n,
397
+ "invalid event token"
398
+ );
399
+ const value = {
400
+ tokenAddress: token,
401
+ treeId: tree,
402
+ leafIndex: at,
403
+ commitment: inserted,
404
+ role,
405
+ delivery,
406
+ ...metadata(log)
407
+ };
408
+ const key = `${insertionKey(log, tree, at)}:${role}`, previous = results.get(key);
409
+ check2(
410
+ !previous || previous.tokenAddress.toLowerCase() === token.toLowerCase() && previous.delivery.ciphertext === delivery.ciphertext && previous.delivery.clue === delivery.clue && previous.blockHash === value.blockHash,
411
+ "conflicting delivery events"
412
+ );
413
+ results.set(key, value);
414
+ };
415
+ const creation = (log, a, fields) => {
416
+ const tree = integer(a.outputTreeId), leaves = array(a.leafIndices), ds = array(a.deliveries);
417
+ const change = hash(fields.changeCommitment), hasChange = BigInt(change) !== 0n;
418
+ check2(
419
+ ds.length === 3 && leaves.length === (hasChange ? 2 : 1),
420
+ "invalid Credit creation delivery slots"
421
+ );
422
+ add(log, tree, index(leaves[0]), fields.creditCommitment, "owner", ds[0]);
423
+ add(log, tree, index(leaves[0]), fields.creditCommitment, "spender", ds[1]);
424
+ if (hasChange) add(log, tree, index(leaves[1]), change, "funds", ds[2]);
425
+ else (0, import_protocol.normalizeDelivery)(ds[2], { empty: true });
426
+ };
427
+ for (const log of relevant) {
428
+ const a = object(log.args);
429
+ if (log.eventName === "Shield") {
430
+ const ds = array(a.deliveries), tree = integer(a.treeId), start = index(a.startLeafIndex);
431
+ check2(start + ds.length <= 65536, "shield exceeds tree capacity");
432
+ ds.forEach((d, i) => add(log, tree, start + i, void 0, "funds", d));
433
+ } else if (log.eventName === "Transact" || log.eventName === "CrossTreeTransact") {
434
+ const cs = array(a.outputCommitments), ds = array(a.deliveries), leaves = array(a.leafIndices), tree = integer(a.outputTreeId);
435
+ check2(
436
+ cs.length === ds.length && cs.length === leaves.length,
437
+ "invalid transact delivery slots"
438
+ );
439
+ cs.forEach((c, i) => add(log, tree, index(leaves[i]), c, "funds", ds[i]));
440
+ } else if (log.eventName === "BatchOutputs") {
441
+ const tokens = array(a.tokens);
442
+ const cs = array(a.commitments), tree = integer(a.treeId), start = index(a.startLeafIndex), packed = a.packedDeliveries;
443
+ check2(
444
+ typeof packed === "string" && /^0x(?:[0-9a-fA-F]{2})*$/.test(packed) && packed.length === 2 + 452 * cs.length,
445
+ "invalid packed deliveries"
446
+ );
447
+ check2(tokens.length === cs.length, "batch token count mismatch");
448
+ cs.forEach((c, i) => {
449
+ const segment = packed.slice(2 + 452 * i, 2 + 452 * (i + 1));
450
+ add(
451
+ log,
452
+ tree,
453
+ start + i,
454
+ c,
455
+ "funds",
456
+ {
457
+ ciphertext: `0x${segment.slice(0, 316)}`,
458
+ clue: `0x${segment.slice(316)}`
459
+ },
460
+ tokens[i]
461
+ );
462
+ });
463
+ } else if (log.eventName === "CreditCreated") creation(log, a, a);
464
+ else if (log.eventName === "CreditToppedUp")
465
+ creation(log, a, object(object(a.params).creation));
466
+ else if (log.eventName === "CreditDraw") {
467
+ const p = object(a.params), cs = array(p.remainderCommitments), ds = array(a.deliveries), leaves = array(a.remainderLeafIndices), tree = integer(a.outputTreeId);
468
+ check2(
469
+ ds.length === 2 * cs.length && leaves.length === cs.length,
470
+ "invalid draw delivery slots"
471
+ );
472
+ cs.forEach((c, i) => {
473
+ if (BigInt(hash(c)) === 0n) {
474
+ check2(integer(leaves[i]) === 0n, "absent remainder has nonzero position");
475
+ (0, import_protocol.normalizeDelivery)(ds[2 * i], { empty: true });
476
+ (0, import_protocol.normalizeDelivery)(ds[2 * i + 1], { empty: true });
477
+ } else {
478
+ add(log, tree, index(leaves[i]), c, "owner", ds[2 * i]);
479
+ add(log, tree, index(leaves[i]), c, "spender", ds[2 * i + 1]);
480
+ }
481
+ });
482
+ }
483
+ }
484
+ return [...results.values()];
485
+ }
486
+ async function decryptCandidates(logs, options) {
487
+ contextCheck(options.context);
488
+ (0, import_core.decodeAddress)(import_core.ReceivingAddress.fromIdentity(options.viewing.walletIdentity).toString());
489
+ check2(
490
+ !logs.some(
491
+ (log) => log.address.toLowerCase() === options.context.protocolAddress.toLowerCase() && log.removed
492
+ ),
493
+ "discovery resync required after reorg"
494
+ );
495
+ const candidates = extractDeliveryCandidates(logs, options.context, options.confirmedThrough);
496
+ const positions = /* @__PURE__ */ new Map();
497
+ const unique = /* @__PURE__ */ new Map();
498
+ for (const candidate of candidates) {
499
+ const key = position(candidate.treeId, candidate.leafIndex);
500
+ const previous = positions.get(key);
501
+ check2(
502
+ !previous || previous.commitment === candidate.commitment && previous.blockHash === candidate.blockHash && previous.transactionHash === candidate.transactionHash && previous.tokenAddress.toLowerCase() === candidate.tokenAddress.toLowerCase(),
503
+ "conflicting canonical position"
504
+ );
505
+ positions.set(key, candidate);
506
+ unique.set(`${key}:${candidate.role}`, candidate);
507
+ }
508
+ const notes = [];
509
+ const credits = [];
510
+ for (const candidate of unique.values()) {
511
+ const { delivery, role, ...positionData } = candidate;
512
+ const context = {
513
+ chainId: options.context.chainId,
514
+ protocolAddress: BigInt(options.context.protocolAddress),
515
+ tokenAddress: BigInt(candidate.tokenAddress),
516
+ outputCommitment: candidate.commitment
517
+ };
518
+ try {
519
+ if (role === "funds") {
520
+ const note = await options.viewing.decryptNote(delivery.ciphertext, context);
521
+ notes.push({ ...positionData, note });
522
+ } else {
523
+ const state = await options.viewing.decryptCredit(delivery.ciphertext, {
524
+ ...context,
525
+ role
526
+ });
527
+ credits.push({ ...positionData, role, state });
528
+ }
529
+ } catch {
530
+ }
531
+ }
532
+ return { notes, credits };
533
+ }
534
+
535
+ // src/session/DisclosureVerifier.ts
536
+ function check3(value, message) {
537
+ if (!value) throw new Error(message);
538
+ }
539
+ function reference(value) {
540
+ check3(/^0x[0-9a-fA-F]{64}$/.test(value.transactionHash), "invalid disclosure transaction hash");
541
+ check3(
542
+ typeof value.treeId === "bigint" && value.treeId > 0n && value.treeId < 1n << 64n,
543
+ "invalid disclosure tree ID"
544
+ );
545
+ check3(
546
+ Number.isSafeInteger(value.leafIndex) && value.leafIndex >= 0 && value.leafIndex < 65536,
547
+ "invalid disclosure leaf index"
548
+ );
549
+ check3(["funds", "owner", "spender"].includes(value.role), "invalid disclosure reference role");
550
+ return {
551
+ transactionHash: value.transactionHash.toLowerCase(),
552
+ treeId: value.treeId,
553
+ leafIndex: value.leafIndex,
554
+ role: value.role
555
+ };
556
+ }
557
+ var DisclosureVerifier = class {
558
+ #protocol;
559
+ #reader;
560
+ #confirmations;
561
+ constructor(options) {
562
+ const confirmations = options.confirmations ?? 1;
563
+ check3(
564
+ Number.isSafeInteger(confirmations) && confirmations >= 1,
565
+ "invalid disclosure confirmations"
566
+ );
567
+ this.#protocol = options.protocol;
568
+ this.#reader = "provider" in options ? new EthersChainReader(options.provider) : options.reader;
569
+ this.#confirmations = BigInt(confirmations);
570
+ Object.freeze(this);
571
+ }
572
+ async barrier(block) {
573
+ await this.#protocol.validateDeployment(this.#reader);
574
+ const canonical2 = await this.#reader.getBlock(block.number);
575
+ check3(
576
+ canonical2.number === block.number && canonical2.hash.toLowerCase() === block.hash.toLowerCase(),
577
+ "disclosure canonical block changed; reorg verification required"
578
+ );
579
+ const head = await this.#reader.getLatestBlockNumber();
580
+ check3(
581
+ head >= block.number && head - block.number + 1n >= this.#confirmations,
582
+ "insufficient disclosure confirmations"
583
+ );
584
+ check3(
585
+ await this.#reader.getChainId() === this.#protocol.context.chainId,
586
+ "disclosure chain changed during verification"
587
+ );
588
+ return head - block.number + 1n;
589
+ }
590
+ /** Resolve the exact inserted output and its delivery from a successful canonical receipt. */
591
+ async readOutput(value) {
592
+ const selected = reference(value);
593
+ await this.#protocol.validateDeployment(this.#reader);
594
+ const received = await this.#reader.getReceipt(selected.transactionHash);
595
+ check3(received, "disclosure transaction receipt is unavailable");
596
+ const receipt = structuredClone(received);
597
+ check3(receipt.status === "success", "disclosure requires a successful transaction receipt");
598
+ check3(
599
+ receipt.transactionHash.toLowerCase() === selected.transactionHash,
600
+ "disclosure receipt transaction mismatch"
601
+ );
602
+ check3(
603
+ typeof receipt.block.number === "bigint" && receipt.block.number >= 0n && /^0x[0-9a-fA-F]{64}$/.test(receipt.block.hash),
604
+ "invalid disclosure receipt block"
605
+ );
606
+ const { context } = this.#protocol;
607
+ const logs = readProtocolLogs(receipt, context.protocolAddress);
608
+ const candidates = extractDeliveryCandidates(logs, context, receipt.block.number).filter(
609
+ (candidate2) => candidate2.treeId === selected.treeId && candidate2.leafIndex === selected.leafIndex && candidate2.role === selected.role && candidate2.tokenAddress.toLowerCase() === context.tokenAddress.toLowerCase()
610
+ );
611
+ check3(
612
+ candidates.length === 1,
613
+ "disclosure output has no unique canonical insertion and delivery"
614
+ );
615
+ const candidate = candidates[0];
616
+ const confirmations = await this.barrier(receipt.block);
617
+ return Object.freeze({
618
+ reference: Object.freeze(selected),
619
+ block: Object.freeze({ ...receipt.block }),
620
+ confirmations,
621
+ output: Object.freeze({
622
+ chainId: context.chainId,
623
+ protocolAddress: BigInt(context.protocolAddress),
624
+ tokenAddress: BigInt(context.tokenAddress),
625
+ outputCommitment: candidate.commitment,
626
+ role: selected.role,
627
+ ciphertext: candidate.delivery.ciphertext
628
+ })
629
+ });
630
+ }
631
+ async verify(disclosure, value) {
632
+ const evidence = await this.readOutput(value);
633
+ const result = await disclosure.verify(evidence.output);
634
+ const confirmations = await this.barrier(evidence.block);
635
+ return Object.freeze({
636
+ result: Object.freeze(result),
637
+ block: evidence.block,
638
+ confirmations,
639
+ output: Object.freeze({
640
+ ...evidence.reference,
641
+ commitment: evidence.output.outputCommitment
642
+ })
643
+ });
644
+ }
645
+ toJSON() {
646
+ return { disclosureVerifier: true, confirmations: this.#confirmations.toString() };
647
+ }
648
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
649
+ return this.toJSON();
650
+ }
651
+ };
652
+
653
+ // src/session/receipts.ts
654
+ function canonical(value) {
655
+ return JSON.stringify(value, (_, item) => {
656
+ if (typeof item === "bigint") {
657
+ return ["bigint", item.toString()];
658
+ }
659
+ if (item instanceof Uint8Array) {
660
+ return Array.from(item);
661
+ }
662
+ if (item && typeof item === "object" && !Array.isArray(item)) {
663
+ return Object.fromEntries(
664
+ Object.keys(item).sort().map((key) => [key, item[key]])
665
+ );
666
+ }
667
+ return item;
668
+ });
669
+ }
670
+ function validateInheritance(state, record) {
671
+ const source = record.receivingKeySource;
672
+ if (!source) return false;
673
+ const parent = state.credits[source.commitment + ":" + record.role];
674
+ if (!parent) {
675
+ throw new Error("Credit receiving inheritance has no authenticated parent");
676
+ }
677
+ const previous = parent.state;
678
+ const successor = record.state;
679
+ const immutableFields = [
680
+ "chainId",
681
+ "protocolAddress",
682
+ "tokenAddress",
683
+ "spenderWalletIdentity",
684
+ "ownerWalletIdentity",
685
+ "spenderPayout",
686
+ "ownerPayout",
687
+ "creditSharedKey"
688
+ ];
689
+ if (immutableFields.some((field) => previous.note[field] !== successor.note[field]) || canonical(previous.ownerReceivingKeys) !== canonical(successor.ownerReceivingKeys) || canonical(previous.spenderReceivingKeys) !== canonical(successor.spenderReceivingKeys)) {
690
+ throw new Error("Credit receiving inheritance changed immutable fields");
691
+ }
692
+ if (source.kind === "draw") {
693
+ if (successor.note.amount >= previous.note.amount || successor.note.expiry !== previous.note.expiry) {
694
+ throw new Error("invalid Credit receiving inheritance");
695
+ }
696
+ } else if (source.kind !== "top-up" || successor.note.amount <= previous.note.amount || successor.note.expiry < previous.note.expiry) {
697
+ throw new Error("invalid Credit receiving inheritance");
698
+ }
699
+ return true;
700
+ }
701
+ function mergeSpentObservation(previous, incoming) {
702
+ const next = incoming.checkedThrough;
703
+ const old = previous.checkedThrough;
704
+ if (!next || incoming.spent === "unknown") {
705
+ return;
706
+ }
707
+ if (old && next.number < old.number) {
708
+ return;
709
+ }
710
+ if (old && next.number === old.number && next.hash !== old.hash) {
711
+ throw new Error("canonical spent checkpoint conflict requires resync");
712
+ }
713
+ if (previous.spent === "spent" && incoming.spent !== "spent") {
714
+ throw new Error("canonical spent observation conflict requires resync");
715
+ }
716
+ previous.spent = incoming.spent;
717
+ previous.checkedThrough = { ...next };
718
+ }
719
+ function mergeReceiptRecords(state, batch) {
720
+ if (state.invalidated) {
721
+ throw new Error("wallet inventory is invalidated");
722
+ }
723
+ const checkpoint = { ...batch.checkpoint };
724
+ for (const record of [...batch.notes, ...batch.credits]) {
725
+ if (record.blockNumber > checkpoint.number) {
726
+ throw new Error("receipt exceeds confirmed checkpoint");
727
+ }
728
+ const atPosition = [...Object.values(state.notes), ...Object.values(state.credits)].filter(
729
+ (existing) => existing.treeId === record.treeId && existing.leafIndex === record.leafIndex
730
+ );
731
+ if (atPosition.some(
732
+ (existing) => existing.commitment !== record.commitment || existing.blockHash !== record.blockHash
733
+ )) {
734
+ throw new Error("canonical position conflict requires resync");
735
+ }
736
+ if ("note" in record) {
737
+ const previous = state.notes[record.commitment];
738
+ if (previous) {
739
+ if (canonical(previous.note) !== canonical(record.note)) {
740
+ throw new Error("canonical note preimage conflict");
741
+ }
742
+ mergeSpentObservation(previous, record);
743
+ continue;
744
+ }
745
+ state.notes[record.commitment] = record;
746
+ } else {
747
+ const key = record.commitment + ":" + record.role;
748
+ const previous = state.credits[key];
749
+ const inherited = validateInheritance(state, record);
750
+ if (previous) {
751
+ if (canonical(previous.state.note) !== canonical(record.state.note)) {
752
+ throw new Error("canonical Credit preimage conflict");
753
+ }
754
+ if (canonical(previous.state) !== canonical(record.state)) {
755
+ if (!inherited) {
756
+ if (previous.receivingKeySource) {
757
+ mergeSpentObservation(previous, record);
758
+ continue;
759
+ }
760
+ throw new Error("Credit receiving metadata conflict");
761
+ }
762
+ previous.state = record.state;
763
+ previous.receivingKeySource = record.receivingKeySource;
764
+ } else if (inherited) {
765
+ previous.receivingKeySource = record.receivingKeySource;
766
+ }
767
+ mergeSpentObservation(previous, record);
768
+ continue;
769
+ }
770
+ state.credits[key] = record;
771
+ }
772
+ }
773
+ }
774
+
775
+ // src/session/state.ts
776
+ function creditReservationKey(commitment) {
777
+ return "credit:" + commitment;
778
+ }
779
+ function walletBalance(state, tokenAddress) {
780
+ let confirmedKnown = 0n;
781
+ let reserved = 0n;
782
+ let unknown = !!state.invalidated;
783
+ const records = Object.values(state.notes).filter(
784
+ (r) => r.note.tokenAddress === BigInt(tokenAddress)
785
+ );
786
+ const points = records.map((record) => record.checkedThrough);
787
+ let checkedThrough = null;
788
+ if (records.length && points.every((point) => point !== void 0) && records.every((record) => record.spent !== "unknown")) {
789
+ checkedThrough = points.reduce(
790
+ (earliest, point) => point.number < earliest.number ? point : earliest
791
+ );
792
+ } else if (!records.length) {
793
+ checkedThrough = state.observedThrough ?? null;
794
+ }
795
+ if (!records.length && !checkedThrough) {
796
+ unknown = true;
797
+ }
798
+ for (const [id, record] of Object.entries(state.notes)) {
799
+ if (record.note.tokenAddress !== BigInt(tokenAddress)) continue;
800
+ if (record.spent !== "spent") {
801
+ confirmedKnown += record.note.amount;
802
+ if (record.spent === "unknown") {
803
+ unknown = true;
804
+ }
805
+ if (state.reservations[id]) {
806
+ reserved += record.note.amount;
807
+ }
808
+ }
809
+ }
810
+ return {
811
+ tokenAddress,
812
+ scope: { ...state.scope },
813
+ checkedThrough: state.invalidated ? null : checkedThrough ? { ...checkedThrough } : null,
814
+ confirmedKnown,
815
+ reserved,
816
+ availableKnown: unknown ? null : confirmedKnown - reserved,
817
+ invalidated: !!state.invalidated
818
+ };
819
+ }
820
+ function walletBalances(state) {
821
+ const tokens = new Set(
822
+ Object.values(state.notes).map(
823
+ (r) => `0x${r.note.tokenAddress.toString(16).padStart(40, "0")}`
824
+ )
825
+ );
826
+ return [...tokens].sort().map((token) => walletBalance(state, token));
827
+ }
828
+
829
+ // src/session/store.ts
830
+ var import_core2 = require("@invisi-labs/core");
831
+ var import_sha256 = require("@noble/hashes/sha256");
832
+ var import_utils = require("@noble/hashes/utils");
833
+ function scopeKey(scope) {
834
+ if (typeof scope.namespace !== "string" || !scope.namespace.length || scope.namespace.length > 256)
835
+ throw new Error("invalid wallet namespace");
836
+ if (typeof scope.chainId !== "bigint" || scope.chainId < 0n || scope.chainId >= 1n << 64n)
837
+ throw new Error("invalid scope chain");
838
+ for (const address2 of [scope.protocolAddress])
839
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address2) || BigInt(address2) === 0n)
840
+ throw new Error("invalid scope deployment");
841
+ if (!/^0x[0-9a-fA-F]{40}$/.test(scope.walletIdentity) || BigInt(scope.walletIdentity) <= 0n || BigInt(scope.walletIdentity) >= 1n << 160n)
842
+ throw new Error("invalid scope wallet");
843
+ return (0, import_utils.bytesToHex)(
844
+ (0, import_sha256.sha256)(
845
+ (0, import_utils.utf8ToBytes)(
846
+ "Invisi.WalletScope.v2:" + JSON.stringify([
847
+ scope.namespace,
848
+ scope.chainId.toString(),
849
+ scope.protocolAddress.toLowerCase(),
850
+ scope.walletIdentity.toLowerCase()
851
+ ])
852
+ )
853
+ )
854
+ );
855
+ }
856
+ function initialState(scope) {
857
+ scopeKey(scope);
858
+ return {
859
+ schemaVersion: 1,
860
+ scope: { ...scope },
861
+ revision: 0,
862
+ notes: {},
863
+ credits: {},
864
+ reservations: {},
865
+ operations: {}
866
+ };
867
+ }
868
+ function validateState(state, scope, revision) {
869
+ if (!state || state.schemaVersion !== 1 || scopeKey(state.scope) !== scopeKey(scope) || state.revision !== revision)
870
+ throw new Error("wallet state scope/schema/revision mismatch");
871
+ if (!Number.isSafeInteger(revision) || revision < 0)
872
+ throw new Error("invalid wallet state revision");
873
+ for (const field of [state.notes, state.credits, state.reservations, state.operations])
874
+ if (!field || typeof field !== "object" || Array.isArray(field))
875
+ throw new Error("invalid wallet state records");
876
+ for (const record of Object.values(state.notes)) {
877
+ if (typeof record.note.tokenAddress !== "bigint" || record.note.tokenAddress <= 0n || record.note.tokenAddress >= 1n << 160n || record.note.chainId !== scope.chainId || record.note.protocolAddress !== BigInt(scope.protocolAddress) || record.note.walletIdentity.toLowerCase() !== scope.walletIdentity.toLowerCase() || typeof record.note.amount !== "bigint" || record.note.amount <= 0n || record.note.amount >= 1n << 128n)
878
+ throw new Error("invalid stored note context or amount");
879
+ if (!["unspent", "unknown", "spent"].includes(record.spent))
880
+ throw new Error("invalid spent state");
881
+ }
882
+ for (const record of Object.values(state.credits)) {
883
+ const note = (0, import_core2.normalizeCreditDeliveryState)(record.state).note;
884
+ if (note.chainId !== scope.chainId || note.protocolAddress !== BigInt(scope.protocolAddress) || !["owner", "spender"].includes(record.role) || (record.role === "owner" ? note.ownerWalletIdentity : note.spenderWalletIdentity).toLowerCase() !== scope.walletIdentity.toLowerCase() || !["unspent", "unknown", "spent"].includes(record.spent))
885
+ throw new Error("invalid stored Credit context or role");
886
+ }
887
+ for (const record of [...Object.values(state.notes), ...Object.values(state.credits)]) {
888
+ if (!/^0x[0-9a-fA-F]{64}$/.test(record.commitment) || BigInt(record.commitment) <= 0n || BigInt(record.commitment) >= import_core2.FIELD_MODULUS || typeof record.treeId !== "bigint" || record.treeId < 1n || record.treeId >= 1n << 64n || !Number.isInteger(record.leafIndex) || record.leafIndex < 0 || record.leafIndex >= 65536 || typeof record.blockNumber !== "bigint" || record.blockNumber < 0n || !/^0x[0-9a-fA-F]{64}$/.test(record.blockHash))
889
+ throw new Error("invalid stored receipt position");
890
+ }
891
+ for (const [id, operation] of Object.entries(state.reservations))
892
+ if (!state.operations[operation] || !state.notes[id] && !Object.values(state.credits).some(
893
+ (record) => creditReservationKey(record.commitment) === id
894
+ ) && !["submitted", "uncertain", "reorged"].includes(
895
+ state.operations[operation].status
896
+ ))
897
+ throw new Error("orphan input reservation");
898
+ }
899
+ var TransactionalWalletStore = class {
900
+ persistent;
901
+ #records;
902
+ #codec;
903
+ constructor(records, codec, options) {
904
+ this.#records = records;
905
+ this.#codec = codec;
906
+ this.persistent = options.persistent;
907
+ }
908
+ async readSnapshot(scope) {
909
+ const key = scopeKey(scope), record = await this.#records.read(key);
910
+ if (!record) return initialState(scope);
911
+ const state = await this.#codec.decode(record.data, { key, revision: record.revision });
912
+ validateState(state, scope, record.revision);
913
+ return state;
914
+ }
915
+ async transaction(scope, action) {
916
+ if (action.constructor.name === "AsyncFunction")
917
+ throw new Error("wallet transaction action must be synchronous");
918
+ const copied = { ...scope }, key = scopeKey(copied);
919
+ for (let attempt = 0; attempt < 100; attempt++) {
920
+ const draft = await this.readSnapshot(copied), previous = draft.revision;
921
+ const result = action(draft);
922
+ if (result && typeof result === "object" && "then" in result) {
923
+ Promise.resolve(result).catch(() => {
924
+ });
925
+ throw new Error("wallet transaction action must be synchronous");
926
+ }
927
+ validateState(draft, copied, previous);
928
+ if (!Number.isSafeInteger(previous + 1))
929
+ throw new Error("wallet state revision exhausted");
930
+ draft.revision = previous + 1;
931
+ const data = await this.#codec.encode(draft, { key, revision: draft.revision });
932
+ if (await this.#records.compareAndSwap(key, previous, {
933
+ revision: draft.revision,
934
+ data
935
+ }))
936
+ return result;
937
+ }
938
+ throw new Error("wallet transaction contention limit exceeded");
939
+ }
940
+ toJSON() {
941
+ return { walletStore: true, persistent: this.persistent };
942
+ }
943
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
944
+ return this.toJSON();
945
+ }
946
+ };
947
+
948
+ // src/session/operation-protocol.ts
949
+ var import_protocol2 = require("@invisi-labs/protocol");
950
+ function requiresAuthorization(plan) {
951
+ if (plan.kind === "sweep") {
952
+ if (plan.credit || !plan.inputs.length || plan.outputs.length || !plan.params || plan.params.outputCommitments.length || (plan.funding.amount ?? 0n) !== 0n || BigInt(plan.params.withdrawalAddress) !== BigInt(plan.scope.walletIdentity) || plan.params.withdrawalAmount !== plan.inputs.reduce((sum, input) => sum + input.note.amount, 0n) || plan.inputs.some((input) => input.note.walletIdentity !== plan.scope.walletIdentity))
953
+ throw new Error("invalid owner-only sweep plan");
954
+ return false;
955
+ }
956
+ return !!plan.credit || plan.inputs.length > 0;
957
+ }
958
+ function inputFacts(plan) {
959
+ const credit = plan.credit;
960
+ const noteNullifiers = plan.params?.nullifiers ?? credit?.creation?.nullifiers ?? [];
961
+ let creditNullifiers = [];
962
+ if (credit?.draw) {
963
+ creditNullifiers = credit.draw.nullifiers;
964
+ } else if (credit?.topUp) {
965
+ creditNullifiers = [credit.topUp.creditNullifier];
966
+ } else if (credit?.cancel) {
967
+ creditNullifiers = [credit.cancel.nullifier];
968
+ }
969
+ if (noteNullifiers.length !== plan.inputs.length || creditNullifiers.length !== (credit?.inputs.length ?? 0)) {
970
+ throw new Error("operation input statement count mismatch");
971
+ }
972
+ return [
973
+ ...plan.inputs.map((input, index2) => ({
974
+ key: input.commitment,
975
+ reservationKey: input.commitment,
976
+ nullifier: noteNullifiers[index2],
977
+ kind: "notes"
978
+ })),
979
+ ...(credit?.inputs ?? []).map((input, index2) => ({
980
+ key: input.commitment + ":" + input.role,
981
+ reservationKey: creditReservationKey(input.commitment),
982
+ nullifier: creditNullifiers[index2],
983
+ kind: "credits"
984
+ }))
985
+ ];
986
+ }
987
+ function operationSources(plan) {
988
+ const noteSources = plan.params?.sources;
989
+ if (noteSources != null) return noteSources;
990
+ const credit = plan.credit;
991
+ if (credit?.topUp) {
992
+ return [...credit.topUp.creation.sources, credit.topUp.creditSource];
993
+ }
994
+ if (credit?.creation) return credit.creation.sources;
995
+ if (credit?.draw) return credit.draw.sources;
996
+ if (credit?.cancel) return [credit.cancel.source];
997
+ return [];
998
+ }
999
+ function statement(plan) {
1000
+ if (!plan.statement) throw new Error("operation has no proof statement");
1001
+ const credit = plan.credit;
1002
+ let words;
1003
+ if (credit?.topUp) {
1004
+ words = (0, import_protocol2.topUpCreditPublicInputs)(
1005
+ plan.context,
1006
+ credit.topUp,
1007
+ credit.deliveries,
1008
+ plan.funding
1009
+ );
1010
+ } else if (credit?.creation) {
1011
+ words = (0, import_protocol2.createCreditPublicInputs)(
1012
+ plan.context,
1013
+ credit.creation,
1014
+ credit.deliveries,
1015
+ plan.funding
1016
+ );
1017
+ } else if (credit?.draw) {
1018
+ words = (0, import_protocol2.creditDrawPublicInputs)(plan.context, credit.draw, credit.deliveries);
1019
+ } else if (credit?.cancel) {
1020
+ words = (0, import_protocol2.cancelCreditPublicInputs)(plan.context, credit.cancel);
1021
+ } else {
1022
+ words = (0, import_protocol2.transactUserPublicInputs)(
1023
+ plan.context,
1024
+ plan.params,
1025
+ plan.outputs.map((output) => output.delivery),
1026
+ plan.funding
1027
+ );
1028
+ }
1029
+ const result = import_protocol2.WitnessBuilder.statement(
1030
+ plan.statement.circuitName,
1031
+ words,
1032
+ plan.statement.target
1033
+ );
1034
+ if (JSON.stringify(result) !== JSON.stringify(plan.statement)) {
1035
+ throw new Error("frozen operation statement changed");
1036
+ }
1037
+ return result;
1038
+ }
1039
+ function authorizationRequest(plan) {
1040
+ let operation = "transact";
1041
+ if (plan.kind === "draw-credit") {
1042
+ operation = "credit-draw";
1043
+ } else if (plan.kind === "create-credit" || plan.kind === "top-up-credit" || plan.kind === "cancel-credit") {
1044
+ operation = plan.kind;
1045
+ }
1046
+ return {
1047
+ context: {
1048
+ chainId: plan.context.chainId,
1049
+ protocolAddress: BigInt(plan.context.protocolAddress)
1050
+ },
1051
+ operation,
1052
+ capacity: plan.capacity,
1053
+ words: statement(plan).expectedPublicInputs.slice(0, -2).map(BigInt)
1054
+ };
1055
+ }
1056
+ async function witnessFor(protocol, plan, authorization) {
1057
+ const builder = new import_protocol2.WitnessBuilder(protocol);
1058
+ const credit = plan.credit;
1059
+ if (credit) {
1060
+ if (!authorization) throw new Error("private Credit wallet authorization required");
1061
+ if (credit.topUp) {
1062
+ return builder.topUpCredit({
1063
+ params: credit.topUp,
1064
+ deliveries: credit.deliveries,
1065
+ funding: plan.funding,
1066
+ inputs: plan.inputs,
1067
+ state: credit.outputs[0].state,
1068
+ ...plan.outputs[0] ? { change: plan.outputs[0].note } : {},
1069
+ previous: credit.inputs[0],
1070
+ authorization
1071
+ });
1072
+ }
1073
+ if (credit.creation) {
1074
+ return builder.createCredit({
1075
+ params: credit.creation,
1076
+ deliveries: credit.deliveries,
1077
+ funding: plan.funding,
1078
+ inputs: plan.inputs,
1079
+ state: credit.outputs[0].state,
1080
+ ...plan.outputs[0] ? { change: plan.outputs[0].note } : {},
1081
+ authorization
1082
+ });
1083
+ }
1084
+ if (credit.draw) {
1085
+ return builder.drawCredit({
1086
+ params: credit.draw,
1087
+ deliveries: credit.deliveries,
1088
+ inputs: credit.inputs,
1089
+ amounts: credit.amounts,
1090
+ authorization
1091
+ });
1092
+ }
1093
+ if (credit.cancel) {
1094
+ return builder.cancelCredit({
1095
+ params: credit.cancel,
1096
+ input: credit.inputs[0],
1097
+ authorization
1098
+ });
1099
+ }
1100
+ throw new Error("unsupported Credit operation");
1101
+ }
1102
+ return builder.transact({
1103
+ params: plan.params,
1104
+ deliveries: plan.outputs.map((output) => output.delivery),
1105
+ funding: plan.funding,
1106
+ inputs: plan.inputs,
1107
+ outputs: plan.outputs.map((output) => output.note),
1108
+ ...authorization ? { authorization } : {},
1109
+ target: plan.statement.target
1110
+ });
1111
+ }
1112
+ async function encodeOperation(saved) {
1113
+ const plan = saved.plan;
1114
+ const credit = plan.credit;
1115
+ const caller = plan.fundingMode === "caller" ? plan.funding.payer : void 0;
1116
+ const common = { context: plan.context, proof: saved.proof?.proofHex ?? "0x" };
1117
+ const funded = { ...common, funding: plan.funding, ...caller ? { caller } : {} };
1118
+ let encoded;
1119
+ if (credit?.topUp) {
1120
+ encoded = (0, import_protocol2.buildTopUpCreditCalldata)({
1121
+ ...funded,
1122
+ params: credit.topUp,
1123
+ deliveries: credit.deliveries
1124
+ });
1125
+ } else if (credit?.creation) {
1126
+ encoded = (0, import_protocol2.buildCreateCreditCalldata)({
1127
+ ...funded,
1128
+ params: credit.creation,
1129
+ deliveries: credit.deliveries
1130
+ });
1131
+ } else if (credit?.draw) {
1132
+ encoded = (0, import_protocol2.buildCreditDrawCalldata)({
1133
+ ...common,
1134
+ params: credit.draw,
1135
+ deliveries: credit.deliveries
1136
+ });
1137
+ } else if (credit?.cancel) {
1138
+ encoded = (0, import_protocol2.buildCancelCreditCalldata)({ ...common, params: credit.cancel });
1139
+ } else if (plan.kind === "shield") {
1140
+ encoded = await (0, import_protocol2.buildShieldCalldata)({
1141
+ ...funded,
1142
+ params: plan.shieldParams,
1143
+ outputs: plan.shieldOutputs,
1144
+ deliveries: plan.outputs.map((output) => output.delivery)
1145
+ });
1146
+ } else {
1147
+ encoded = (0, import_protocol2.buildTransactCalldata)({
1148
+ ...funded,
1149
+ params: plan.params,
1150
+ deliveries: plan.outputs.map((output) => output.delivery)
1151
+ });
1152
+ }
1153
+ return { to: plan.context.protocolAddress, data: encoded.data, value: 0n };
1154
+ }
1155
+ async function fundingTypedData(saved) {
1156
+ const plan = saved.plan;
1157
+ const credit = plan.credit;
1158
+ if (credit?.topUp) {
1159
+ return (0, import_protocol2.encodeTopUpCreditFundingTypedData)(
1160
+ plan.context,
1161
+ credit.topUp,
1162
+ credit.deliveries,
1163
+ plan.funding
1164
+ );
1165
+ }
1166
+ if (credit?.creation) {
1167
+ return (0, import_protocol2.encodeCreateCreditFundingTypedData)(
1168
+ plan.context,
1169
+ credit.creation,
1170
+ credit.deliveries,
1171
+ plan.funding
1172
+ );
1173
+ }
1174
+ if (plan.kind === "shield") {
1175
+ return (0, import_protocol2.encodeShieldFundingTypedData)({
1176
+ context: plan.context,
1177
+ params: plan.shieldParams,
1178
+ outputs: plan.shieldOutputs,
1179
+ deliveries: plan.outputs.map((output) => output.delivery),
1180
+ funding: plan.funding
1181
+ });
1182
+ }
1183
+ if (plan.statement?.target === "noir-recursive") {
1184
+ return (0, import_protocol2.encodeAggregateFundingTypedData)(plan.context, {
1185
+ params: plan.params,
1186
+ deliveries: plan.outputs.map((output) => output.delivery),
1187
+ funding: plan.funding
1188
+ });
1189
+ }
1190
+ return (0, import_protocol2.encodeTransactFundingTypedData)(
1191
+ plan.context,
1192
+ saved.proof.proofHex,
1193
+ plan.params,
1194
+ plan.outputs.map((output) => output.delivery),
1195
+ plan.funding
1196
+ );
1197
+ }
1198
+ function publicProof(bundle) {
1199
+ return {
1200
+ formatVersion: bundle.formatVersion,
1201
+ circuitName: bundle.circuitName,
1202
+ target: bundle.target,
1203
+ bbVersion: bundle.bbVersion,
1204
+ artifactSha256: bundle.artifactSha256,
1205
+ vkSha256: bundle.vkSha256,
1206
+ proofHex: bundle.proofHex,
1207
+ proofSha256: bundle.proofSha256,
1208
+ publicInputs: [...bundle.publicInputs]
1209
+ };
1210
+ }
1211
+ function operationExpired(plan, now) {
1212
+ if (now > plan.deadline) return true;
1213
+ const credit = plan.credit;
1214
+ if (credit?.topUp && now >= credit.topUp.previousExpiry) return true;
1215
+ if (credit?.creation && now >= credit.creation.expiry) return true;
1216
+ if (credit?.draw && credit.draw.expiries.some((expiry) => now >= expiry)) return true;
1217
+ return false;
1218
+ }
1219
+
1220
+ // src/data/gas-benchmarks.json
1221
+ var gas_benchmarks_default = [
1222
+ {
1223
+ formatVersion: 1,
1224
+ id: "protocol-92ef274-osaka-real",
1225
+ deployment: {
1226
+ chainId: "31337",
1227
+ protocolAddress: "0x8870C63A18a9480C7de9B860b849fD293832856D",
1228
+ runtimeCodeHash: "0x604572c0da90ea139b774cd0d2962a2972fac46e0556c7cec6f6f9aac5deef14",
1229
+ tokenAddress: "0x4826533B4897376654Bb4d4AD88B7faFD0C98528"
1230
+ },
1231
+ source: {
1232
+ protocolCommit: "92ef274c6b36d783507699a33ee20e52192c83c7",
1233
+ measuredAt: "2026-10-27T08:42:15.991Z",
1234
+ chain: "Anvil 31337",
1235
+ hardfork: "osaka",
1236
+ token: "MockERC20",
1237
+ verifier: "real",
1238
+ assumptions: [
1239
+ "normal CREATE/Factory deployment; no code or storage injection",
1240
+ "Solidity 0.8.27 optimizer 200; real BB 5.2.1 verifiers",
1241
+ "one source tree; two existing leaves; no rollover or screener",
1242
+ "funding/approval setup excluded; complete transaction receipt gas included",
1243
+ "exact allowance for caller funding; configured benchmark payout balances",
1244
+ "observed local samples, not a confidence interval or gas limit"
1245
+ ]
1246
+ },
1247
+ measurements: [
1248
+ {
1249
+ id: "shield-1",
1250
+ shape: {
1251
+ kind: "shield",
1252
+ target: "evm",
1253
+ capacity: 0,
1254
+ noteInputs: 0,
1255
+ noteOutputs: 1,
1256
+ sourceTrees: 0,
1257
+ creditInputs: 0,
1258
+ creditOutputs: 0,
1259
+ creditRemainders: 0,
1260
+ funding: "caller",
1261
+ permit: false,
1262
+ deliveryBytes: 226
1263
+ },
1264
+ circuit: null,
1265
+ samples: [
1266
+ {
1267
+ gasUsed: "649286",
1268
+ transactionHash: "0xcc299d1780ada9a69b1d9f0f3d06a165db15fccbd2b7ff407834f02f5d563a18",
1269
+ blockNumber: "50",
1270
+ blockHash: "0xbcb868e781a20588d2fa099a4d7f87c1600044a2e5ec1e369e384d7a5f22f00a",
1271
+ calldataBytes: 1156
1272
+ },
1273
+ {
1274
+ gasUsed: "649322",
1275
+ transactionHash: "0x95139dff109725989f0769e3e6fb9bd3e7e1bf581fd475f14e822875eb90fda4",
1276
+ blockNumber: "50",
1277
+ blockHash: "0x7ace353fea4fc8c856c0fd45521d200b02619921977709ff4f910e0ff41c06fe",
1278
+ calldataBytes: 1156
1279
+ },
1280
+ {
1281
+ gasUsed: "649322",
1282
+ transactionHash: "0xef578bb38dc8ff8bfd47d2e76e3ab4273019ea9a385f5d2fd99add32ceaecb58",
1283
+ blockNumber: "50",
1284
+ blockHash: "0xded817f8ef89637e2443e3b626c0837f184ca134fe753be913d5570554c649a6",
1285
+ calldataBytes: 1156
1286
+ }
1287
+ ]
1288
+ },
1289
+ {
1290
+ id: "shield-2",
1291
+ shape: {
1292
+ kind: "shield",
1293
+ target: "evm",
1294
+ capacity: 0,
1295
+ noteInputs: 0,
1296
+ noteOutputs: 2,
1297
+ sourceTrees: 0,
1298
+ creditInputs: 0,
1299
+ creditOutputs: 0,
1300
+ creditRemainders: 0,
1301
+ funding: "caller",
1302
+ permit: false,
1303
+ deliveryBytes: 452
1304
+ },
1305
+ circuit: null,
1306
+ samples: [
1307
+ {
1308
+ gasUsed: "757063",
1309
+ transactionHash: "0x024e2dfbd6f50c368e2a4aa347e3029de668f4ebd36b80227aa003bb7ac21c74",
1310
+ blockNumber: "50",
1311
+ blockHash: "0x8ed63973c158d706ac54bb646626e4f0db5777b4bd56d179b94503af6c417c50",
1312
+ calldataBytes: 1668
1313
+ },
1314
+ {
1315
+ gasUsed: "757063",
1316
+ transactionHash: "0x898d852ab28f68adac4d30b160a3dbfee33fa7ca875f70df9eb7763b6b1cd1f0",
1317
+ blockNumber: "50",
1318
+ blockHash: "0x9bb6810de38193933a9f26fa03d3d9bad1ca0151ff5e04544c56520e09da3043",
1319
+ calldataBytes: 1668
1320
+ },
1321
+ {
1322
+ gasUsed: "757039",
1323
+ transactionHash: "0x8ff9720e3197aab2d741fbd8d0fa2969203ea8cd051d9649d1e5eb890bcc2be3",
1324
+ blockNumber: "50",
1325
+ blockHash: "0x8604c75eb6363aaae774276e638f5b749aae1f8f815eb99ab1183137eef5730e",
1326
+ calldataBytes: 1668
1327
+ }
1328
+ ]
1329
+ },
1330
+ {
1331
+ id: "shield-10",
1332
+ shape: {
1333
+ kind: "shield",
1334
+ target: "evm",
1335
+ capacity: 0,
1336
+ noteInputs: 0,
1337
+ noteOutputs: 10,
1338
+ sourceTrees: 0,
1339
+ creditInputs: 0,
1340
+ creditOutputs: 0,
1341
+ creditRemainders: 0,
1342
+ funding: "caller",
1343
+ permit: false,
1344
+ deliveryBytes: 2260
1345
+ },
1346
+ circuit: null,
1347
+ samples: [
1348
+ {
1349
+ gasUsed: "1692143",
1350
+ transactionHash: "0x6c13db8bd8a8599daf5034e56712abefbf406982fafe7647b6d22291ba4199ed",
1351
+ blockNumber: "50",
1352
+ blockHash: "0x1ab2ae514f408a79e0c40ca10cee74b2e23d7c8a5f24641b8eea3e1cf58ce700",
1353
+ calldataBytes: 5764
1354
+ },
1355
+ {
1356
+ gasUsed: "1692083",
1357
+ transactionHash: "0x5e476c9d3838b782a51332af9bf5c372d29fc411c2168a7af619c52bd5309c7b",
1358
+ blockNumber: "50",
1359
+ blockHash: "0x9c62bc72fc90f990c6711b48865ff922a1856ae05c4fef61dad5f4da7d948401",
1360
+ calldataBytes: 5764
1361
+ },
1362
+ {
1363
+ gasUsed: "1692203",
1364
+ transactionHash: "0x0092e2d9160fad95279cde586fc6c79c713244f55db8a2060a5abab9d98c176a",
1365
+ blockNumber: "50",
1366
+ blockHash: "0xac1bf780b4cc05b4dcc7ee5d741c049ba99ab4af9bdcd25104add730c2a14152",
1367
+ calldataBytes: 5764
1368
+ }
1369
+ ]
1370
+ },
1371
+ {
1372
+ id: "transfer-1x2",
1373
+ shape: {
1374
+ kind: "transfer",
1375
+ target: "evm",
1376
+ capacity: 14,
1377
+ noteInputs: 1,
1378
+ noteOutputs: 2,
1379
+ sourceTrees: 1,
1380
+ creditInputs: 0,
1381
+ creditOutputs: 0,
1382
+ creditRemainders: 0,
1383
+ funding: "none",
1384
+ permit: false,
1385
+ deliveryBytes: 452
1386
+ },
1387
+ circuit: {
1388
+ circuitName: "transact_user_slots14",
1389
+ target: "evm",
1390
+ artifactSha256: "2a6a20839e90b88b1ecd629f59a01821bad3ee11dbe04db1fd9cdce7a3de0e84",
1391
+ vkSha256: "ffe0e1a4e690792ecb4eb1fc1acc9ff60f13d3b5af03cfc6ae6d6bf26e99bdea"
1392
+ },
1393
+ samples: [
1394
+ {
1395
+ gasUsed: "1617516",
1396
+ transactionHash: "0xf49ea3d31cc1346472172e2dee84f9360f60a3fe948a3ef6e87f727af6d42f0d",
1397
+ blockNumber: "49",
1398
+ blockHash: "0xf59c57e63ec2f03abc36d28f273b0af5290184dc97197df740b23d79a851a887",
1399
+ calldataBytes: 11012
1400
+ },
1401
+ {
1402
+ gasUsed: "1617516",
1403
+ transactionHash: "0xf8d11286f1f2a4db006ff6b06900c47d0fbf685984499e1e5d4aea49478ba8e5",
1404
+ blockNumber: "49",
1405
+ blockHash: "0xeb5a800190be3534c9e2675c97ac56d0e0be8adef55f60bd32d375776a7d150c",
1406
+ calldataBytes: 11012
1407
+ },
1408
+ {
1409
+ gasUsed: "1617504",
1410
+ transactionHash: "0x8939fcea97079a3c5118b9e94325455e2ad1cdb47310ae499a277272654b3eb6",
1411
+ blockNumber: "49",
1412
+ blockHash: "0xecffefb593e2fec5d5827cfd39d0cc4d0efb67dbff6f8dbbd0d2db2439a80cf9",
1413
+ calldataBytes: 11012
1414
+ }
1415
+ ]
1416
+ },
1417
+ {
1418
+ id: "transfer-1x14",
1419
+ shape: {
1420
+ kind: "transfer",
1421
+ target: "evm",
1422
+ capacity: 56,
1423
+ noteInputs: 1,
1424
+ noteOutputs: 14,
1425
+ sourceTrees: 1,
1426
+ creditInputs: 0,
1427
+ creditOutputs: 0,
1428
+ creditRemainders: 0,
1429
+ funding: "none",
1430
+ permit: false,
1431
+ deliveryBytes: 3164
1432
+ },
1433
+ circuit: {
1434
+ circuitName: "transact_user_slots56",
1435
+ target: "evm",
1436
+ artifactSha256: "ba83ec2f177d16fa88ea04bc54f2e99920f1ac195cdb05b88b9b3bbc245fd953",
1437
+ vkSha256: "6ad5d3aff0c72a632fb19895658af093a6a92478a9994985e838a71e61237720"
1438
+ },
1439
+ samples: [
1440
+ {
1441
+ gasUsed: "2489010",
1442
+ transactionHash: "0x053557acea930a869d6469c91e9bc015d0893607a6ab18d2af684986f7074676",
1443
+ blockNumber: "49",
1444
+ blockHash: "0x734873097eb5e4cb66bfc2cdb61fac6753b53a4b9450fb22895281100af3aaf3",
1445
+ calldataBytes: 17156
1446
+ },
1447
+ {
1448
+ gasUsed: "2489106",
1449
+ transactionHash: "0xb3990edb994011775e275bc1eb38c527b7206a2bfadf510e8aa53e260f3594b8",
1450
+ blockNumber: "49",
1451
+ blockHash: "0xf8cb019ebb0ecd0ec5d6634198292883c63fe93a7c65c34f9f3120c6cc54f1cf",
1452
+ calldataBytes: 17156
1453
+ },
1454
+ {
1455
+ gasUsed: "2489094",
1456
+ transactionHash: "0xc97079019567ff8a79d285421351ee4bc48d14092a0e12ff634151719e991088",
1457
+ blockNumber: "49",
1458
+ blockHash: "0x5a6947839165c8961eeeb9190331415c40366a78b4a6a5771e7b8954dbb8b3c7",
1459
+ calldataBytes: 17156
1460
+ }
1461
+ ]
1462
+ },
1463
+ {
1464
+ id: "transfer-1x56",
1465
+ shape: {
1466
+ kind: "transfer",
1467
+ target: "evm",
1468
+ capacity: 112,
1469
+ noteInputs: 1,
1470
+ noteOutputs: 56,
1471
+ sourceTrees: 1,
1472
+ creditInputs: 0,
1473
+ creditOutputs: 0,
1474
+ creditRemainders: 0,
1475
+ funding: "none",
1476
+ permit: false,
1477
+ deliveryBytes: 12656
1478
+ },
1479
+ circuit: {
1480
+ circuitName: "transact_user_slots112",
1481
+ target: "evm",
1482
+ artifactSha256: "713b78469b6e97711122210f8497942120c1c9d66d3604bc184566c4eba59cc3",
1483
+ vkSha256: "d6a46e297949c28b9a62fd9aef1f202870fdca97b8bfb15beefd27b9e9f3d86e"
1484
+ },
1485
+ samples: [
1486
+ {
1487
+ gasUsed: "5389227",
1488
+ transactionHash: "0xb820d3c358724c1d64422017f232d074e4ff1ffaf1b1ce5ee719409427cf2208",
1489
+ blockNumber: "49",
1490
+ blockHash: "0x474bd1e52457801d1a596a9f23aeb6d365a17be028ee0c8cf8fd4758eaa1106b",
1491
+ calldataBytes: 36356
1492
+ },
1493
+ {
1494
+ gasUsed: "5389095",
1495
+ transactionHash: "0x16514d8cf1136d7a7f40591859e2ea2d256af66520498e1fc81eb28837db600d",
1496
+ blockNumber: "49",
1497
+ blockHash: "0x42314a0d6eb9e765869e1b66b279444c78f919d2c28aed544d9fa7ac3bd2acb4",
1498
+ calldataBytes: 36356
1499
+ },
1500
+ {
1501
+ gasUsed: "5389203",
1502
+ transactionHash: "0xee69d565a7eafb60ad7a47afe80a49d4fa40b50660669d9141e7dc6f4c3dbe0d",
1503
+ blockNumber: "49",
1504
+ blockHash: "0x0dcdada663ee04dd63e06fcdb79dacc5623201d29b687f63deede2b4762a994e",
1505
+ calldataBytes: 36356
1506
+ }
1507
+ ]
1508
+ },
1509
+ {
1510
+ id: "transfer-1x112",
1511
+ shape: {
1512
+ kind: "transfer",
1513
+ target: "evm",
1514
+ capacity: 168,
1515
+ noteInputs: 1,
1516
+ noteOutputs: 112,
1517
+ sourceTrees: 1,
1518
+ creditInputs: 0,
1519
+ creditOutputs: 0,
1520
+ creditRemainders: 0,
1521
+ funding: "none",
1522
+ permit: false,
1523
+ deliveryBytes: 25312
1524
+ },
1525
+ circuit: {
1526
+ circuitName: "transact_user_slots168",
1527
+ target: "evm",
1528
+ artifactSha256: "f7b3b1384e961367702f544299ca3521d8205fac31b7691c45ff84325d5a31c3",
1529
+ vkSha256: "e00181533d7e35d2307570528f9e7f8f88e2909b6ae338d93373b608dea96881"
1530
+ },
1531
+ samples: [
1532
+ {
1533
+ gasUsed: "9139842",
1534
+ transactionHash: "0x09951c4e00b868b6660429d187a482004646e4c84ef341d3b98352c249db6a06",
1535
+ blockNumber: "49",
1536
+ blockHash: "0xba9da3c08189335acff1eb33554f5f12475e4a6510324cf6ec9eadf340ce15d2",
1537
+ calldataBytes: 61444
1538
+ },
1539
+ {
1540
+ gasUsed: "9139650",
1541
+ transactionHash: "0xb06f69d1c6e5406758918d5d296ab889bab4c3814f616cc581ad312cc7ebb2f3",
1542
+ blockNumber: "49",
1543
+ blockHash: "0x5516a76b4b366f2e539a67bb848b7d232c3e6f328d34ca5bd3dba43a14c316bf",
1544
+ calldataBytes: 61444
1545
+ },
1546
+ {
1547
+ gasUsed: "9139530",
1548
+ transactionHash: "0x116c4875b7712b7cb51cdb0fcb6e66a95733dca272aa272bc0b1fd265edbe26c",
1549
+ blockNumber: "49",
1550
+ blockHash: "0xd36340fddd1eace21b73f785c6589bf73b07bef782f2e78a5e3d3d828b0b607c",
1551
+ calldataBytes: 61444
1552
+ }
1553
+ ]
1554
+ },
1555
+ {
1556
+ id: "withdraw-1x1",
1557
+ shape: {
1558
+ kind: "withdraw",
1559
+ target: "evm",
1560
+ capacity: 14,
1561
+ noteInputs: 1,
1562
+ noteOutputs: 1,
1563
+ sourceTrees: 1,
1564
+ creditInputs: 0,
1565
+ creditOutputs: 0,
1566
+ creditRemainders: 0,
1567
+ funding: "none",
1568
+ permit: false,
1569
+ deliveryBytes: 226
1570
+ },
1571
+ circuit: {
1572
+ circuitName: "transact_user_slots14",
1573
+ target: "evm",
1574
+ artifactSha256: "2a6a20839e90b88b1ecd629f59a01821bad3ee11dbe04db1fd9cdce7a3de0e84",
1575
+ vkSha256: "ffe0e1a4e690792ecb4eb1fc1acc9ff60f13d3b5af03cfc6ae6d6bf26e99bdea"
1576
+ },
1577
+ samples: [
1578
+ {
1579
+ gasUsed: "1597571",
1580
+ transactionHash: "0x326892d32146e0bc36b396fd3973151a629312df68ed25a4ffbb6a3da23ba274",
1581
+ blockNumber: "49",
1582
+ blockHash: "0x3e0f326cba3dd019b86431b4c22145758a7f10c121a696a81a094746b04495b5",
1583
+ calldataBytes: 10564
1584
+ },
1585
+ {
1586
+ gasUsed: "1597715",
1587
+ transactionHash: "0x2e3a15157ac9c07e4717bbd4641481ce3bc0068d26157752815b6ec6b30acdd0",
1588
+ blockNumber: "49",
1589
+ blockHash: "0xab7fceac97d55e8702d1202b98d3a05c4b1e0f71e51b6d88d7e80e91cc338596",
1590
+ calldataBytes: 10564
1591
+ },
1592
+ {
1593
+ gasUsed: "1597595",
1594
+ transactionHash: "0x5052e94a67520c43416cdcb60a1365a126560861173d0eef46bcbf0a566fe947",
1595
+ blockNumber: "49",
1596
+ blockHash: "0x9a64cf76b646b5a07b72fcc61266e87f17365fec891701c331982a93e5ba9b89",
1597
+ calldataBytes: 10564
1598
+ }
1599
+ ]
1600
+ },
1601
+ {
1602
+ id: "sweep-1x0",
1603
+ shape: {
1604
+ kind: "sweep",
1605
+ target: "evm",
1606
+ capacity: 14,
1607
+ noteInputs: 1,
1608
+ noteOutputs: 0,
1609
+ sourceTrees: 1,
1610
+ creditInputs: 0,
1611
+ creditOutputs: 0,
1612
+ creditRemainders: 0,
1613
+ funding: "none",
1614
+ permit: false,
1615
+ deliveryBytes: 0
1616
+ },
1617
+ circuit: {
1618
+ circuitName: "transact_user_slots14",
1619
+ target: "evm",
1620
+ artifactSha256: "2a6a20839e90b88b1ecd629f59a01821bad3ee11dbe04db1fd9cdce7a3de0e84",
1621
+ vkSha256: "ffe0e1a4e690792ecb4eb1fc1acc9ff60f13d3b5af03cfc6ae6d6bf26e99bdea"
1622
+ },
1623
+ samples: [
1624
+ {
1625
+ gasUsed: "1069308",
1626
+ transactionHash: "0xb1ab469d05ca5f49659f54a332cc172892dcce37e475342fbf52fa9cfb4d3927",
1627
+ blockNumber: "49",
1628
+ blockHash: "0xe5b04579149987198801f1ca19ab771d5262de2aaf9861a6c962d50d1c6baef0",
1629
+ calldataBytes: 10116
1630
+ },
1631
+ {
1632
+ gasUsed: "1069284",
1633
+ transactionHash: "0xd6adee997f7e1e19e3918f913f6ee48d94df69c09ed74e333325023089e7f6df",
1634
+ blockNumber: "49",
1635
+ blockHash: "0xcb225566f8f1ea6ae8da30158b8f1aef03d50fa4ea352a00aa42b7f41a05d63c",
1636
+ calldataBytes: 10116
1637
+ },
1638
+ {
1639
+ gasUsed: "1069308",
1640
+ transactionHash: "0xe06dd7f3a2718bc9a9412c2d6f505960ce225b2e5fe6c1c66bb5b0e8f427006f",
1641
+ blockNumber: "49",
1642
+ blockHash: "0x37a08c5918892e1c19df017c746ab0ede03c1bb195c3be593d70c1f9de91897e",
1643
+ calldataBytes: 10116
1644
+ }
1645
+ ]
1646
+ },
1647
+ {
1648
+ id: "create-credit-1x1",
1649
+ shape: {
1650
+ kind: "create-credit",
1651
+ target: "evm",
1652
+ capacity: 10,
1653
+ noteInputs: 1,
1654
+ noteOutputs: 1,
1655
+ sourceTrees: 1,
1656
+ creditInputs: 0,
1657
+ creditOutputs: 1,
1658
+ creditRemainders: 0,
1659
+ funding: "none",
1660
+ permit: false,
1661
+ deliveryBytes: 1162
1662
+ },
1663
+ circuit: {
1664
+ circuitName: "create_credit",
1665
+ target: "evm",
1666
+ artifactSha256: "ab97cd5a036851a9748a5cfd8e6f9834e23090c8e95db4531aabfc37d3abb353",
1667
+ vkSha256: "fac0e65b768a28750323cca851990598af936611125b6cfa3f5c16b04c7d084f"
1668
+ },
1669
+ samples: [
1670
+ {
1671
+ gasUsed: "1627325",
1672
+ transactionHash: "0x7763d93a5b1e8797b5f4af2e1e5b1bb029a8b0f9f7b90bcc04f581947d826803",
1673
+ blockNumber: "49",
1674
+ blockHash: "0x93e51ae5e99c68dc642693d478e444330d8e26255ff5bb9aafc4a3f4e70d3a45",
1675
+ calldataBytes: 11940
1676
+ },
1677
+ {
1678
+ gasUsed: "1627385",
1679
+ transactionHash: "0xe246d59cafa2a26dddf21c8385dd843be4d4596388ebe23e936df84e7f906635",
1680
+ blockNumber: "49",
1681
+ blockHash: "0x3c24b2d6ed2a998562d34b49e0517515742a9b39d59aec38a9ac6b22be1b694b",
1682
+ calldataBytes: 11940
1683
+ },
1684
+ {
1685
+ gasUsed: "1627157",
1686
+ transactionHash: "0x2f7ed0b75435cc6a820d0cd90fd82d362f74787057413722b6eb3c6e37db87d3",
1687
+ blockNumber: "49",
1688
+ blockHash: "0x0a0301670b46017da772800c68bb941245ac1941c79fde9c85717632154e15ee",
1689
+ calldataBytes: 11940
1690
+ }
1691
+ ]
1692
+ },
1693
+ {
1694
+ id: "draw-credit-1-partial",
1695
+ shape: {
1696
+ kind: "draw-credit",
1697
+ target: "evm",
1698
+ capacity: 1,
1699
+ noteInputs: 0,
1700
+ noteOutputs: 0,
1701
+ sourceTrees: 1,
1702
+ creditInputs: 1,
1703
+ creditOutputs: 1,
1704
+ creditRemainders: 1,
1705
+ funding: "none",
1706
+ permit: false,
1707
+ deliveryBytes: 936
1708
+ },
1709
+ circuit: {
1710
+ circuitName: "credit_draw_slots1",
1711
+ target: "evm",
1712
+ artifactSha256: "f5e458790948f37a61a833fad52cf2cc3b26a59515d271de32e0caaa4e229eb5",
1713
+ vkSha256: "613699a440f50a0937e4c57e5152b5fdea42c31b10157d6b93226fef313df553"
1714
+ },
1715
+ samples: [
1716
+ {
1717
+ gasUsed: "1581294",
1718
+ transactionHash: "0xd438e22b0fa07a657279f182537e4996f1936f0881d5b7d637845f8ca2808aab",
1719
+ blockNumber: "49",
1720
+ blockHash: "0x9e384a3054530b1ff237a639bca1a559afb2b73b30e21c262aac85ebf480fc9b",
1721
+ calldataBytes: 11396
1722
+ },
1723
+ {
1724
+ gasUsed: "1581402",
1725
+ transactionHash: "0xa70ac3e7284b6e98f8591a6ba88e4ddfb23ecbd05343641d0532f34920e10b56",
1726
+ blockNumber: "49",
1727
+ blockHash: "0x37c5eb34ce96a841e96c36103a7ab65c1a3e08603dc9843efb0c0752c069f1d4",
1728
+ calldataBytes: 11396
1729
+ },
1730
+ {
1731
+ gasUsed: "1581174",
1732
+ transactionHash: "0xd062494979d0bc0e284ccb070f87b48f3e8e5befb6001f15a006dd2a4a72ca24",
1733
+ blockNumber: "49",
1734
+ blockHash: "0x12840d920f14163b2d29491f28cee616e1bb9fd9514a4debe10ae35256345fc1",
1735
+ calldataBytes: 11396
1736
+ }
1737
+ ]
1738
+ },
1739
+ {
1740
+ id: "top-up-credit-1x1",
1741
+ shape: {
1742
+ kind: "top-up-credit",
1743
+ target: "evm",
1744
+ capacity: 10,
1745
+ noteInputs: 1,
1746
+ noteOutputs: 1,
1747
+ sourceTrees: 2,
1748
+ creditInputs: 1,
1749
+ creditOutputs: 1,
1750
+ creditRemainders: 0,
1751
+ funding: "none",
1752
+ permit: false,
1753
+ deliveryBytes: 1162
1754
+ },
1755
+ circuit: {
1756
+ circuitName: "credit_top_up",
1757
+ target: "evm",
1758
+ artifactSha256: "ee9dbcf8be6f6c09478190996e90b1d39d4a10a00245950f69fc1d2c329c6e9b",
1759
+ vkSha256: "0c369fec0469b60baebb3be650bad42f3ccae6f0ff139cce6ad9a3f451ed7513"
1760
+ },
1761
+ samples: [
1762
+ {
1763
+ gasUsed: "1666948",
1764
+ transactionHash: "0x1403da01167962a4349d2655ea217bfcfc1a2390138bea0279792c44b4381dd2",
1765
+ blockNumber: "49",
1766
+ blockHash: "0xcb554ffcdb4afe5f6458e5d23ffbdd1fdbef9c23f982343065772c0b3e3dd633",
1767
+ calldataBytes: 12100
1768
+ },
1769
+ {
1770
+ gasUsed: "1666900",
1771
+ transactionHash: "0x3b5e54f29e3f888a443526cf23ab9255b3f0c7adf1d0d127c16b85f4c6a324d9",
1772
+ blockNumber: "49",
1773
+ blockHash: "0xeb304df6bdd24b249cac3197b543b0eaa6381ed75ad812dff248f914864105ed",
1774
+ calldataBytes: 12100
1775
+ },
1776
+ {
1777
+ gasUsed: "1666780",
1778
+ transactionHash: "0x81c5ef25268a719275cb3b80618c2af291e1f7b3f91d7e82cc26bc894a8c332a",
1779
+ blockNumber: "49",
1780
+ blockHash: "0xfa42070d6349fc8d559b8bd4ad0ee6b79089107540c4472b9e492b8f5a6bd3e4",
1781
+ calldataBytes: 12100
1782
+ }
1783
+ ]
1784
+ },
1785
+ {
1786
+ id: "cancel-credit-1",
1787
+ shape: {
1788
+ kind: "cancel-credit",
1789
+ target: "evm",
1790
+ capacity: 1,
1791
+ noteInputs: 0,
1792
+ noteOutputs: 0,
1793
+ sourceTrees: 1,
1794
+ creditInputs: 1,
1795
+ creditOutputs: 0,
1796
+ creditRemainders: 0,
1797
+ funding: "none",
1798
+ permit: false,
1799
+ deliveryBytes: 0
1800
+ },
1801
+ circuit: {
1802
+ circuitName: "credit_cancel",
1803
+ target: "evm",
1804
+ artifactSha256: "d332453a2b8c2c544747ac8d5f01912c9757f48d986226fd6a9b0e24d8321474",
1805
+ vkSha256: "1f8a3c20cb081d6a90962556da3124919cc4e5675e59ad31562af39299da02fb"
1806
+ },
1807
+ samples: [
1808
+ {
1809
+ gasUsed: "998146",
1810
+ transactionHash: "0x97bc72692d241e2c344b912804f9939061db5ef59cf4bb23922d93812e382160",
1811
+ blockNumber: "49",
1812
+ blockHash: "0x2e09df4df84a1c25961386febe8404531bb5da274836d50cc15a26c91b433a98",
1813
+ calldataBytes: 9540
1814
+ },
1815
+ {
1816
+ gasUsed: "998134",
1817
+ transactionHash: "0x26c5067b12c2bc4b694070784e6b19d4855efa6e77c2422f0c6b1567f061570a",
1818
+ blockNumber: "49",
1819
+ blockHash: "0xdd3f20520c09e001374cab58387e52c82446db053539e30dad28b0de679a333c",
1820
+ calldataBytes: 9540
1821
+ },
1822
+ {
1823
+ gasUsed: "998230",
1824
+ transactionHash: "0x5dc09445a405150da1445c22f50d6dd10d8d428a6a3f41ee90db7d3574657e31",
1825
+ blockNumber: "49",
1826
+ blockHash: "0x5c8f8223a50d2c8d48188a2db28ffa33c1b150a2fb465e99db4430949db3664b",
1827
+ calldataBytes: 9540
1828
+ }
1829
+ ]
1830
+ }
1831
+ ]
1832
+ }
1833
+ ];
1834
+
1835
+ // src/gas-estimation.ts
1836
+ var word2 = /^0x[0-9a-fA-F]{64}$/;
1837
+ var address = /^0x[0-9a-fA-F]{40}$/;
1838
+ var decimal = /^(0|[1-9][0-9]*)$/;
1839
+ var shapeKeys = [
1840
+ "kind",
1841
+ "target",
1842
+ "capacity",
1843
+ "noteInputs",
1844
+ "noteOutputs",
1845
+ "sourceTrees",
1846
+ "creditInputs",
1847
+ "creditOutputs",
1848
+ "creditRemainders",
1849
+ "funding",
1850
+ "permit",
1851
+ "deliveryBytes"
1852
+ ];
1853
+ function copyGasBenchmarks(profiles) {
1854
+ const copy = structuredClone([...profiles]);
1855
+ for (const p of copy) {
1856
+ if (p.formatVersion !== 1 || !p.id || !decimal.test(p.deployment.chainId) || !address.test(p.deployment.protocolAddress) || !address.test(p.deployment.tokenAddress) || !p.deployment.runtimeCodeHash || !word2.test(p.deployment.runtimeCodeHash) || p.source.verifier !== "real" || !/^[0-9a-f]{40}$/i.test(p.source.protocolCommit) || !p.source.measuredAt || !Array.isArray(p.source.assumptions) || !p.source.assumptions.every((assumption) => typeof assumption === "string") || !p.measurements.length)
1857
+ throw new Error("invalid gas benchmark profile");
1858
+ for (const m of p.measurements) {
1859
+ if (!m.id || !m.samples.length || !["none", "caller", "signature"].includes(m.shape.funding) || typeof m.shape.permit !== "boolean")
1860
+ throw new Error("invalid gas benchmark measurement");
1861
+ for (const key of shapeKeys) {
1862
+ const value = m.shape[key];
1863
+ if (!["kind", "target", "funding", "permit"].includes(key) && (!Number.isSafeInteger(value) || Number(value) < 0))
1864
+ throw new Error("invalid gas benchmark shape");
1865
+ }
1866
+ if (m.circuit && (![m.circuit.artifactSha256, m.circuit.vkSha256].every(
1867
+ (hash2) => /^[a-f0-9]{64}$/i.test(hash2)
1868
+ ) || !m.circuit.circuitName || !m.circuit.target))
1869
+ throw new Error("invalid gas benchmark circuit identity");
1870
+ const hashes = /* @__PURE__ */ new Set();
1871
+ for (const sample of m.samples) {
1872
+ if (typeof sample.gasUsed !== "string" || !decimal.test(sample.gasUsed) || BigInt(sample.gasUsed) <= 0n || !word2.test(sample.transactionHash) || !word2.test(sample.blockHash) || !decimal.test(sample.blockNumber) || !Number.isSafeInteger(sample.calldataBytes) || sample.calldataBytes < 0 || hashes.has(sample.transactionHash.toLowerCase()))
1873
+ throw new Error("invalid gas benchmark receipt");
1874
+ hashes.add(sample.transactionHash.toLowerCase());
1875
+ }
1876
+ }
1877
+ }
1878
+ return copy;
1879
+ }
1880
+ function defineGasBenchmarks(input) {
1881
+ if (!Array.isArray(input)) throw new Error("invalid gas benchmark catalog");
1882
+ try {
1883
+ return copyGasBenchmarks(input);
1884
+ } catch {
1885
+ throw new Error("invalid gas benchmark catalog");
1886
+ }
1887
+ }
1888
+ var defaultProfiles = defineGasBenchmarks(gas_benchmarks_default);
1889
+ function sameDeployment(a, b) {
1890
+ return a.chainId === b.chainId && a.protocolAddress.toLowerCase() === b.protocolAddress.toLowerCase() && a.tokenAddress.toLowerCase() === b.tokenAddress.toLowerCase() && a.runtimeCodeHash?.toLowerCase() === b.runtimeCodeHash?.toLowerCase();
1891
+ }
1892
+ function sameCircuit(a, b) {
1893
+ return a === null ? b === null : b !== null && a.circuitName === b.circuitName && a.target === b.target && a.artifactSha256.toLowerCase() === b.artifactSha256.toLowerCase() && a.vkSha256.toLowerCase() === b.vkSha256.toLowerCase();
1894
+ }
1895
+ function estimateBenchmarkGas(plan, protocol, profiles) {
1896
+ const credit = plan.credit;
1897
+ const deliveries = credit?.deliveries ?? plan.outputs.map((out) => out.delivery);
1898
+ const request = {
1899
+ deployment: {
1900
+ chainId: plan.context.chainId.toString(),
1901
+ protocolAddress: plan.context.protocolAddress,
1902
+ runtimeCodeHash: protocol.expectedRuntimeCodeHash ?? null,
1903
+ tokenAddress: plan.context.tokenAddress
1904
+ },
1905
+ shape: {
1906
+ kind: plan.kind,
1907
+ target: plan.statement?.target ?? "evm",
1908
+ capacity: plan.capacity ?? 0,
1909
+ noteInputs: plan.inputs.length,
1910
+ noteOutputs: plan.outputs.length,
1911
+ sourceTrees: operationSources(plan).length,
1912
+ creditInputs: credit?.inputs.length ?? 0,
1913
+ creditOutputs: credit?.outputs.length ?? 0,
1914
+ creditRemainders: credit?.draw?.remainderCommitments.filter((hash2) => BigInt(hash2) !== 0n).length ?? 0,
1915
+ funding: plan.fundingMode,
1916
+ permit: (plan.funding.permit?.deadline ?? 0n) !== 0n,
1917
+ deliveryBytes: deliveries.reduce(
1918
+ (sum, d) => sum + (d.ciphertext.length - 2) / 2 + (d.clue.length - 2) / 2,
1919
+ 0
1920
+ )
1921
+ },
1922
+ circuit: plan.statement ? {
1923
+ circuitName: plan.statement.circuitName,
1924
+ target: plan.statement.target,
1925
+ artifactSha256: plan.statement.artifactSha256,
1926
+ vkSha256: plan.statement.vkSha256
1927
+ } : null
1928
+ };
1929
+ const base = { source: "benchmark", request };
1930
+ if (request.shape.target === "noir-recursive")
1931
+ return { ...base, available: false, reason: "recursive-member-only" };
1932
+ const candidates = (profiles ?? defaultProfiles).filter(
1933
+ (p) => sameDeployment(p.deployment, request.deployment)
1934
+ );
1935
+ if (!request.deployment.runtimeCodeHash || !candidates.length)
1936
+ return { ...base, available: false, reason: "deployment-not-covered" };
1937
+ for (const profile of candidates) {
1938
+ const measurement = profile.measurements.find(
1939
+ (m) => shapeKeys.every((key) => m.shape[key] === request.shape[key]) && sameCircuit(m.circuit, request.circuit)
1940
+ );
1941
+ if (!measurement) continue;
1942
+ const gas = measurement.samples.map((sample) => BigInt(sample.gasUsed));
1943
+ const count = BigInt(gas.length);
1944
+ return {
1945
+ ...base,
1946
+ available: true,
1947
+ gasUnits: (gas.reduce((sum, value) => sum + value, 0n) + count - 1n) / count,
1948
+ range: {
1949
+ min: gas.reduce((a, b) => a < b ? a : b),
1950
+ max: gas.reduce((a, b) => a > b ? a : b),
1951
+ kind: "observed"
1952
+ },
1953
+ samples: gas.length,
1954
+ profileId: profile.id,
1955
+ measurementId: measurement.id,
1956
+ evidence: structuredClone(profile.source)
1957
+ };
1958
+ }
1959
+ return { ...base, available: false, reason: "shape-not-covered" };
1960
+ }
1961
+ function estimateShapeGas(request, profiles = defaultProfiles) {
1962
+ if (request.shape.target === "noir-recursive") {
1963
+ throw new Error(
1964
+ "Estimate gas for the complete aggregate, not an individual recursive member"
1965
+ );
1966
+ }
1967
+ const measurements = profiles.flatMap((profile) => profile.measurements);
1968
+ const matching = measurements.filter(
1969
+ (measurement) => measurement.shape.kind === request.shape.kind && measurement.shape.target === request.shape.target && sameCircuit(measurement.circuit, request.circuit)
1970
+ );
1971
+ const exact = matching.find(
1972
+ (measurement) => shapeKeys.every((key) => measurement.shape[key] === request.shape[key])
1973
+ );
1974
+ if (exact) {
1975
+ const gas = exact.samples.map((sample) => BigInt(sample.gasUsed));
1976
+ const count = BigInt(gas.length);
1977
+ return { gasUnits: (gas.reduce((sum, value) => sum + value, 0n) + count - 1n) / count };
1978
+ }
1979
+ const candidates = matching.length ? matching : measurements.filter(
1980
+ (measurement) => measurement.shape.kind === request.shape.kind && measurement.shape.target === request.shape.target
1981
+ );
1982
+ if (!candidates.length) throw new Error("No gas reference for this operation kind and target");
1983
+ const dimensions = [
1984
+ "noteInputs",
1985
+ "noteOutputs",
1986
+ "sourceTrees",
1987
+ "creditInputs",
1988
+ "creditOutputs",
1989
+ "creditRemainders",
1990
+ "deliveryBytes",
1991
+ ...!matching.length ? ["capacity"] : []
1992
+ ];
1993
+ const estimates = candidates.map((measurement) => {
1994
+ let numerator = 1n;
1995
+ let denominator = 1n;
1996
+ for (const key of dimensions) {
1997
+ const current2 = BigInt(request.shape[key]) + 1n;
1998
+ const baseline = BigInt(measurement.shape[key]) + 1n;
1999
+ if (current2 * denominator > numerator * baseline) {
2000
+ numerator = current2;
2001
+ denominator = baseline;
2002
+ }
2003
+ }
2004
+ let gas = measurement.samples.reduce(
2005
+ (max, sample) => BigInt(sample.gasUsed) > max ? BigInt(sample.gasUsed) : max,
2006
+ 0n
2007
+ );
2008
+ gas = (gas * numerator + denominator - 1n) / denominator;
2009
+ if (request.shape.funding === "signature" && measurement.shape.funding !== "signature")
2010
+ gas *= 2n;
2011
+ if (request.shape.permit && !measurement.shape.permit) gas *= 2n;
2012
+ return gas;
2013
+ });
2014
+ return { gasUnits: estimates.reduce((min, gas) => gas < min ? gas : min) };
2015
+ }
2016
+ function estimateGasReference(plan, protocol, profiles) {
2017
+ const reference2 = estimateBenchmarkGas(plan, protocol, profiles);
2018
+ if (reference2.available) return { gasUnits: reference2.gasUnits };
2019
+ return estimateShapeGas(reference2.request);
2020
+ }
2021
+
2022
+ // src/session/operation-execution.ts
2023
+ var import_ethers6 = require("ethers");
2024
+ var import_core3 = require("@invisi-labs/core");
2025
+
2026
+ // src/session/proof-integrity.ts
2027
+ var import_ethers5 = require("ethers");
2028
+ var import_sha2562 = require("@noble/hashes/sha256");
2029
+ var import_utils2 = require("@noble/hashes/utils");
2030
+ function check4(ok, message) {
2031
+ if (!ok) throw new Error(message);
2032
+ }
2033
+ function sameStatement(proof, expected) {
2034
+ check4(
2035
+ proof.formatVersion === 1 && proof.bbVersion === "6.0.0" && proof.circuitName === expected.circuitName && proof.target === expected.target && proof.artifactSha256 === expected.artifactSha256 && proof.vkSha256 === expected.vkSha256,
2036
+ "proof release/target mismatch"
2037
+ );
2038
+ check4(
2039
+ proof.publicInputs.length === expected.expectedPublicInputs.length && proof.publicInputs.every((word4, i) => word4 === expected.expectedPublicInputs[i]),
2040
+ "proof public statement mismatch"
2041
+ );
2042
+ check4(
2043
+ /^0x(?:[a-fA-F0-9]{2})+$/.test(proof.proofHex) && (0, import_utils2.bytesToHex)((0, import_sha2562.sha256)((0, import_ethers5.getBytes)(proof.proofHex))) === proof.proofSha256,
2044
+ "proof integrity mismatch"
2045
+ );
2046
+ }
2047
+
2048
+ // src/session/operation-execution.ts
2049
+ async function authorizeOperation(plan, provider) {
2050
+ if (provider.walletIdentity !== plan.scope.walletIdentity)
2051
+ throw new Error("wallet authorization capability mismatch");
2052
+ const request = authorizationRequest(plan);
2053
+ const digest = (0, import_core3.computeWalletAuthorizationDigest)(request);
2054
+ const signed = await provider.authorize(structuredClone(request));
2055
+ if (signed.walletIdentity !== plan.scope.walletIdentity || signed.digest !== digest)
2056
+ throw new Error("wallet authorization statement mismatch");
2057
+ return signed;
2058
+ }
2059
+ async function proveOperation(protocol, plan, authorization, prover, options) {
2060
+ const request = await witnessFor(protocol, plan, authorization);
2061
+ if (JSON.stringify({ ...request, witness: void 0 }) !== JSON.stringify(plan.statement))
2062
+ throw new Error("prepared proof statement changed");
2063
+ try {
2064
+ const proof = publicProof(await prover.prove(request, options));
2065
+ sameStatement(proof, plan.statement);
2066
+ if (await prover.verify(
2067
+ structuredClone(proof),
2068
+ structuredClone(plan.statement),
2069
+ options
2070
+ ) !== true)
2071
+ throw new Error("proof verification failed");
2072
+ return proof;
2073
+ } catch {
2074
+ options.signal?.throwIfAborted();
2075
+ throw new Error("operation proof generation or verification failed");
2076
+ }
2077
+ }
2078
+ async function signOperationFunding(saved, signer, verifier) {
2079
+ const payer = saved.plan.funding.payer;
2080
+ if (signer.address.toLowerCase() !== payer.toLowerCase())
2081
+ throw new Error("funding signer is not the payer");
2082
+ const typed = await fundingTypedData(saved);
2083
+ const signature = await signer.signTypedData(structuredClone(typed));
2084
+ const valid = verifier ? await verifier.verifyTypedData({
2085
+ ...structuredClone(typed),
2086
+ address: payer,
2087
+ signature
2088
+ }) : (0, import_ethers6.verifyTypedData)(
2089
+ typed.domain,
2090
+ Object.fromEntries(
2091
+ Object.entries(typed.types).map(([name, fields]) => [name, [...fields]])
2092
+ ),
2093
+ typed.message,
2094
+ signature
2095
+ ).toLowerCase() === payer.toLowerCase();
2096
+ if (valid !== true) throw new Error("invalid EVM funding signature");
2097
+ return signature;
2098
+ }
2099
+
2100
+ // src/session/operation-preview.ts
2101
+ function operationPreview(plan, status) {
2102
+ const p = plan;
2103
+ return {
2104
+ id: p.id,
2105
+ kind: p.kind,
2106
+ status,
2107
+ context: { ...p.context },
2108
+ inputs: inputFacts(p).length,
2109
+ creditOutputs: p.credit?.outputs.map((output) => ({
2110
+ amount: output.state.note.amount,
2111
+ expiry: output.state.note.expiry,
2112
+ owner: output.state.note.ownerWalletIdentity,
2113
+ spender: output.state.note.spenderWalletIdentity,
2114
+ ownerPayout: output.state.note.ownerPayout,
2115
+ spenderPayout: output.state.note.spenderPayout
2116
+ })),
2117
+ outputs: p.outputs.map((output) => ({
2118
+ to: output.address,
2119
+ amount: output.note.amount,
2120
+ change: output.change
2121
+ })),
2122
+ capacity: p.capacity,
2123
+ deadline: p.deadline,
2124
+ fundingAmount: p.funding.amount ?? 0n,
2125
+ fundingPayer: p.funding.payer,
2126
+ withdrawalAmount: p.params?.withdrawalAmount ?? p.credit?.draw?.totalAmount ?? p.credit?.cancel?.amount ?? 0n,
2127
+ withdrawalAddress: p.params?.withdrawalAddress ?? p.credit?.draw?.spender ?? p.credit?.cancel?.owner,
2128
+ permissions: {
2129
+ walletAuthorization: requiresAuthorization(p),
2130
+ proof: p.kind !== "shield",
2131
+ fundingAuthorization: p.fundingMode === "signature"
2132
+ }
2133
+ };
2134
+ }
2135
+
2136
+ // src/session/Operation.ts
2137
+ var import_core7 = require("@invisi-labs/core");
2138
+ var import_protocol4 = require("@invisi-labs/protocol");
2139
+
2140
+ // src/session/operation-confirmation.ts
2141
+ var import_core6 = require("@invisi-labs/core");
2142
+
2143
+ // src/session/aggregate-receipt.ts
2144
+ var import_ethers7 = require("ethers");
2145
+ var import_contracts3 = require("@invisi-labs/protocol/contracts");
2146
+ var import_protocol3 = require("@invisi-labs/protocol");
2147
+ var aggregateAbi = import_contracts3.invisiProtocolAbi.filter(
2148
+ (item) => item.type === "function" && item.name === "transact" && item.inputs.length === 2 && item.inputs[1]?.type === "tuple[]"
2149
+ );
2150
+ function matchesAggregateTransaction(saved, transaction) {
2151
+ const plan = saved.plan;
2152
+ if (plan.statement?.target !== "noir-recursive" || !plan.params || !saved.externalized || transaction.to.toLowerCase() !== plan.context.protocolAddress.toLowerCase() || transaction.value !== 0n)
2153
+ return false;
2154
+ try {
2155
+ const decoded = new import_ethers7.Interface(aggregateAbi).parseTransaction({ data: transaction.data }), requests = decoded.args[1].map((request) => {
2156
+ const member = request.toObject(true);
2157
+ member.params.sources = request.params.sources.map(
2158
+ (source) => source.toObject(true)
2159
+ );
2160
+ member.params.nullifiers = request.params.nullifiers.toArray(true);
2161
+ member.params.outputCommitments = request.params.outputCommitments.toArray(true);
2162
+ member.deliveries = request.deliveries.map(
2163
+ (delivery) => delivery.toObject(true)
2164
+ );
2165
+ member.funding.permit.v = Number(member.funding.permit.v);
2166
+ return {
2167
+ params: member.params,
2168
+ deliveries: member.deliveries,
2169
+ funding: member.funding
2170
+ };
2171
+ });
2172
+ if (decoded.name !== "transact" || !Array.isArray(requests) || ![4, 8, 12].includes(requests.length))
2173
+ return false;
2174
+ const canonical2 = (request) => (0, import_protocol3.buildTransactCalldata)({
2175
+ context: { ...plan.context, tokenAddress: request.params.token },
2176
+ proof: "0x00",
2177
+ params: request.params,
2178
+ deliveries: request.deliveries,
2179
+ funding: request.funding,
2180
+ caller: transaction.from
2181
+ }).data.toLowerCase();
2182
+ const expected = (0, import_protocol3.buildTransactCalldata)({
2183
+ context: plan.context,
2184
+ proof: "0x00",
2185
+ params: plan.params,
2186
+ deliveries: plan.outputs.map((output) => output.delivery),
2187
+ funding: plan.funding,
2188
+ caller: transaction.from
2189
+ }).data.toLowerCase();
2190
+ return requests.filter((request) => canonical2(request) === expected).length === 1;
2191
+ } catch {
2192
+ return false;
2193
+ }
2194
+ }
2195
+
2196
+ // src/session/credit-values.ts
2197
+ var import_core4 = require("@invisi-labs/core");
2198
+ async function creditLeaf(state) {
2199
+ const n = state.note;
2200
+ return (0, import_core4.computeCreditCommitment)(
2201
+ n.chainId,
2202
+ n.spenderWalletIdentity,
2203
+ n.ownerWalletIdentity,
2204
+ n.spenderPayout,
2205
+ n.ownerPayout,
2206
+ n.tokenAddress,
2207
+ n.amount,
2208
+ n.nonce,
2209
+ n.expiry,
2210
+ await (0, import_core4.computeCreditSharedKeyHash)(n.creditSharedKey)
2211
+ );
2212
+ }
2213
+
2214
+ // src/session/membership.ts
2215
+ var import_core5 = require("@invisi-labs/core");
2216
+ async function verifyMembership(reader, context, block, input, proof) {
2217
+ if (proof.block.hash !== block.hash || proof.block.number !== block.number || proof.leaf !== input.commitment || proof.treeId !== input.treeId || proof.leafIndex !== input.leafIndex || proof.path.length !== 16 || !Number.isInteger(input.leafIndex) || input.leafIndex < 0 || input.leafIndex >= 65536 || !await reader.isKnownRoot(context, input.treeId, proof.root, block))
2218
+ throw new Error("membership context/root mismatch");
2219
+ let root = BigInt(input.commitment);
2220
+ for (let i = 0; i < 16; i++) {
2221
+ const sibling = proof.path[i];
2222
+ if (typeof sibling !== "bigint" || sibling < 0n || sibling >= import_core5.FIELD_MODULUS)
2223
+ throw new Error("noncanonical membership path");
2224
+ root = BigInt(
2225
+ await (0, import_core5.poseidonHash)(input.leafIndex >> i & 1 ? [sibling, root] : [root, sibling])
2226
+ );
2227
+ }
2228
+ if (root !== BigInt(proof.root))
2229
+ throw new Error("membership path does not produce canonical root");
2230
+ }
2231
+
2232
+ // src/session/operation-confirmation.ts
2233
+ function check5(ok, message) {
2234
+ if (!ok) {
2235
+ throw new Error(message);
2236
+ }
2237
+ }
2238
+ async function barrier(runtime, state, block) {
2239
+ const checkpoints = [
2240
+ state.observedThrough,
2241
+ ...block ? [{ number: block.number, hash: block.hash }] : []
2242
+ ];
2243
+ for (const point of checkpoints) {
2244
+ if (point && (await runtime.reader.getBlock(point.number)).hash !== point.hash) {
2245
+ await runtime.store.transaction(runtime.scope, (draft) => {
2246
+ draft.invalidated = "Canonical operation checkpoint changed; full resync required";
2247
+ for (const record of [
2248
+ ...Object.values(draft.notes),
2249
+ ...Object.values(draft.credits)
2250
+ ]) {
2251
+ record.spent = "unknown";
2252
+ }
2253
+ for (const operation of Object.values(draft.operations)) {
2254
+ if (["failed", "expired"].includes(operation.status) && !operation.payload?.externalized) {
2255
+ continue;
2256
+ }
2257
+ operation.status = "reorged";
2258
+ for (const id of operation.inputs) {
2259
+ if (!draft.reservations[id]) {
2260
+ draft.reservations[id] = operation.id;
2261
+ }
2262
+ }
2263
+ }
2264
+ });
2265
+ throw new Error("canonical operation checkpoint changed; full resync required");
2266
+ }
2267
+ }
2268
+ }
2269
+ function current(state, id, generation) {
2270
+ check5(!state.invalidated, "wallet requires canonical resync");
2271
+ const record = state.operations[id];
2272
+ const saved = record?.payload;
2273
+ check5(record && saved.generation === generation && !saved.claim, "stale operation recovery");
2274
+ return { record, saved };
2275
+ }
2276
+ function observe(state, block) {
2277
+ if (state.observedThrough?.number === block.number) {
2278
+ check5(state.observedThrough.hash === block.hash, "operation checkpoint conflict");
2279
+ }
2280
+ if (!state.observedThrough || state.observedThrough.number < block.number) {
2281
+ state.observedThrough = { number: block.number, hash: block.hash };
2282
+ }
2283
+ }
2284
+ async function confirmOperation(runtime, id, generation, hash2, signal) {
2285
+ const { reader, store, scope, options } = runtime;
2286
+ const state = await store.readSnapshot(scope);
2287
+ const { saved } = current(state, id, generation);
2288
+ const plan = saved.plan;
2289
+ await options.protocol.validateDeployment(reader);
2290
+ await barrier(runtime, state);
2291
+ const receipt = await reader.getReceipt(hash2);
2292
+ if (!receipt) {
2293
+ return void 0;
2294
+ }
2295
+ check5(receipt.transactionHash === hash2, "receipt transaction identity mismatch");
2296
+ await barrier(runtime, state, receipt.block);
2297
+ const transaction = await reader.getTransaction(hash2);
2298
+ check5(
2299
+ transaction && transaction.transactionHash === hash2,
2300
+ "confirmed transaction differs from the frozen operation"
2301
+ );
2302
+ let matchesRequest;
2303
+ if (plan.statement?.target === "noir-recursive") {
2304
+ matchesRequest = matchesAggregateTransaction(saved, transaction);
2305
+ } else {
2306
+ matchesRequest = !!saved.call && transaction.to.toLowerCase() === saved.call.to.toLowerCase() && transaction.value === saved.call.value && transaction.data.toLowerCase() === saved.call.data.toLowerCase();
2307
+ }
2308
+ check5(matchesRequest, "confirmed transaction differs from the frozen operation");
2309
+ check5(
2310
+ receipt.status === "success" || receipt.status === "reverted",
2311
+ "receipt execution status is missing"
2312
+ );
2313
+ if (receipt.status === "reverted") {
2314
+ return void 0;
2315
+ }
2316
+ const candidates = extractDeliveryCandidates(
2317
+ readProtocolLogs(receipt, plan.context.protocolAddress),
2318
+ plan.context,
2319
+ receipt.block.number
2320
+ );
2321
+ const latest = await reader.getLatestBlockNumber();
2322
+ const height = [latest, receipt.block.number, state.observedThrough?.number ?? 0n].reduce(
2323
+ (a, b) => a > b ? a : b
2324
+ );
2325
+ const spentBlock = await reader.getBlock(height);
2326
+ check5(spentBlock.number === height, "spent-state canonical block mismatch");
2327
+ const notes = [];
2328
+ for (const output of plan.outputs) {
2329
+ const candidate = candidates.find(
2330
+ (value) => value.role === "funds" && value.commitment === output.commitment
2331
+ );
2332
+ check5(candidate, "confirmed output is missing from canonical receipt");
2333
+ if (output.note.walletIdentity !== scope.walletIdentity) {
2334
+ continue;
2335
+ }
2336
+ const input = {
2337
+ treeId: candidate.treeId,
2338
+ leafIndex: candidate.leafIndex,
2339
+ commitment: candidate.commitment
2340
+ };
2341
+ check5(options.protocolRpc, "membership provider required for confirmed owned output");
2342
+ const proof = await options.protocolRpc.getMembership(plan.context, input, receipt.block);
2343
+ await verifyMembership(reader, plan.context, receipt.block, input, proof);
2344
+ notes.push({
2345
+ ...input,
2346
+ note: structuredClone(output.note),
2347
+ blockNumber: receipt.block.number,
2348
+ blockHash: receipt.block.hash,
2349
+ transactionHash: hash2,
2350
+ spent: "unknown"
2351
+ });
2352
+ }
2353
+ const credits = [];
2354
+ for (const output of plan.credit?.outputs ?? []) {
2355
+ const n = output.state.note;
2356
+ for (const role of ["owner", "spender"]) {
2357
+ if ((role === "owner" ? n.ownerWalletIdentity : n.spenderWalletIdentity) !== scope.walletIdentity) {
2358
+ continue;
2359
+ }
2360
+ const candidate = candidates.find(
2361
+ (value) => value.role === role && value.commitment === output.commitment
2362
+ );
2363
+ check5(candidate, "confirmed Credit output is missing from canonical receipt");
2364
+ const input = {
2365
+ treeId: candidate.treeId,
2366
+ leafIndex: candidate.leafIndex,
2367
+ commitment: candidate.commitment
2368
+ };
2369
+ check5(options.protocolRpc, "membership provider required for confirmed Credit");
2370
+ const proof = await options.protocolRpc.getMembership(
2371
+ plan.context,
2372
+ input,
2373
+ receipt.block
2374
+ );
2375
+ await verifyMembership(reader, plan.context, receipt.block, input, proof);
2376
+ const index2 = plan.credit?.draw?.remainderCommitments.indexOf(output.commitment);
2377
+ const parent = plan.credit?.topUp ? plan.credit.inputs[0] : index2 !== void 0 && index2 >= 0 ? plan.credit?.inputs[index2] : void 0;
2378
+ const receivingKeySource = parent && state.credits[parent.commitment + ":" + role] ? {
2379
+ kind: plan.credit?.topUp ? "top-up" : "draw",
2380
+ commitment: parent.commitment
2381
+ } : void 0;
2382
+ credits.push({
2383
+ ...input,
2384
+ state: structuredClone(output.state),
2385
+ role,
2386
+ blockNumber: receipt.block.number,
2387
+ blockHash: receipt.block.hash,
2388
+ transactionHash: hash2,
2389
+ spent: "unknown",
2390
+ ...receivingKeySource ? { receivingKeySource } : {}
2391
+ });
2392
+ }
2393
+ }
2394
+ if (credits.length) {
2395
+ const nullifiers = await Promise.all(
2396
+ credits.map(
2397
+ async (record) => (0, import_core6.computeCreditNullifier)(
2398
+ plan.context.chainId,
2399
+ record.state.note.creditSharedKey,
2400
+ await creditLeaf(record.state)
2401
+ )
2402
+ )
2403
+ );
2404
+ const values = await reader.isSpent(plan.context, nullifiers, spentBlock);
2405
+ check5(
2406
+ values.length === credits.length && values.every((value) => typeof value === "boolean"),
2407
+ "invalid Credit spent-state response"
2408
+ );
2409
+ credits.forEach((record, i) => {
2410
+ record.spent = values[i] ? "spent" : "unspent";
2411
+ record.checkedThrough = { number: spentBlock.number, hash: spentBlock.hash };
2412
+ });
2413
+ }
2414
+ const facts = inputFacts(plan);
2415
+ const nfs = facts.map((input) => input.nullifier);
2416
+ const spent = nfs.length ? await reader.isSpent(plan.context, nfs, spentBlock) : [];
2417
+ check5(
2418
+ spent.length === nfs.length && spent.every((value) => value === true),
2419
+ "successful operation inputs are not consumed"
2420
+ );
2421
+ const nullifier = options.wallet.exportNullifier();
2422
+ if (notes.length) {
2423
+ const values = await reader.isSpent(
2424
+ plan.context,
2425
+ await Promise.all(
2426
+ notes.map(
2427
+ (note) => nullifier.compute(plan.context.chainId, note.commitment, note.note.nonce)
2428
+ )
2429
+ ),
2430
+ spentBlock
2431
+ );
2432
+ check5(
2433
+ values.length === notes.length && values.every((value) => typeof value === "boolean"),
2434
+ "invalid output spent-state response"
2435
+ );
2436
+ notes.forEach((note, i) => {
2437
+ note.spent = values[i] ? "spent" : "unspent";
2438
+ note.checkedThrough = { number: spentBlock.number, hash: spentBlock.hash };
2439
+ });
2440
+ }
2441
+ signal?.throwIfAborted();
2442
+ await barrier(runtime, state, receipt.block);
2443
+ await barrier(runtime, state, spentBlock);
2444
+ return store.transaction(scope, (draft) => {
2445
+ signal?.throwIfAborted();
2446
+ const { record, saved: active } = current(draft, id, generation);
2447
+ for (const point of [draft.observedThrough]) {
2448
+ check5(
2449
+ !point || point.number < spentBlock.number || point.number === spentBlock.number && point.hash === spentBlock.hash,
2450
+ "wallet advanced or changed during confirmation; retry"
2451
+ );
2452
+ }
2453
+ mergeReceiptRecords(draft, {
2454
+ notes,
2455
+ credits,
2456
+ checkpoint: { number: receipt.block.number, hash: receipt.block.hash }
2457
+ });
2458
+ for (const fact of facts) {
2459
+ const input = draft[fact.kind][fact.key];
2460
+ if (input) {
2461
+ mergeSpentObservation(input, {
2462
+ spent: "spent",
2463
+ checkedThrough: { number: spentBlock.number, hash: spentBlock.hash }
2464
+ });
2465
+ }
2466
+ if (fact.kind === "credits" && input) {
2467
+ for (const other of Object.values(draft.credits)) {
2468
+ if (other.commitment === input.commitment) {
2469
+ mergeSpentObservation(other, {
2470
+ spent: "spent",
2471
+ checkedThrough: { number: spentBlock.number, hash: spentBlock.hash }
2472
+ });
2473
+ }
2474
+ }
2475
+ }
2476
+ if (draft.reservations[fact.reservationKey] === id) {
2477
+ delete draft.reservations[fact.reservationKey];
2478
+ }
2479
+ }
2480
+ observe(draft, spentBlock);
2481
+ record.status = "confirmed";
2482
+ record.transactionHash = hash2;
2483
+ active.generation++;
2484
+ return { status: record.status, saved: structuredClone(active) };
2485
+ });
2486
+ }
2487
+ async function reconcileOperation(runtime, id, generation, signal) {
2488
+ const { reader, store, scope, options } = runtime;
2489
+ const state = await store.readSnapshot(scope);
2490
+ const { record, saved } = current(state, id, generation);
2491
+ const plan = saved.plan;
2492
+ await options.protocol.validateDeployment(reader);
2493
+ await barrier(runtime, state);
2494
+ const block = await reader.getBlock(await reader.getLatestBlockNumber());
2495
+ const facts = inputFacts(plan);
2496
+ const nfs = facts.map((input) => input.nullifier);
2497
+ const spent = nfs.length ? await reader.isSpent(plan.context, nfs, block) : [];
2498
+ check5(
2499
+ spent.length === nfs.length && spent.every((value) => typeof value === "boolean"),
2500
+ "invalid reconciliation spent-state response"
2501
+ );
2502
+ const expired = operationExpired(plan, block.timestamp);
2503
+ const impossible = spent.some((value) => value);
2504
+ signal?.throwIfAborted();
2505
+ await barrier(runtime, state, block);
2506
+ return store.transaction(scope, (draft) => {
2507
+ signal?.throwIfAborted();
2508
+ const { record: active, saved: next } = current(draft, id, generation);
2509
+ for (const point of [
2510
+ draft.observedThrough,
2511
+ ...facts.map((fact) => draft[fact.kind][fact.key]?.checkedThrough)
2512
+ ]) {
2513
+ check5(
2514
+ !point || point.number < block.number || point.number === block.number && point.hash === block.hash,
2515
+ "wallet advanced or changed during reconciliation; retry"
2516
+ );
2517
+ }
2518
+ if (expired || impossible) {
2519
+ active.status = expired ? "expired" : "failed";
2520
+ facts.forEach((fact, i) => {
2521
+ const input = draft[fact.kind][fact.key];
2522
+ if (input) {
2523
+ mergeSpentObservation(input, {
2524
+ spent: spent[i] ? "spent" : "unspent",
2525
+ checkedThrough: { number: block.number, hash: block.hash }
2526
+ });
2527
+ if (fact.kind === "credits") {
2528
+ for (const other of Object.values(draft.credits)) {
2529
+ if (other.commitment === input.commitment) {
2530
+ mergeSpentObservation(other, {
2531
+ spent: spent[i] ? "spent" : "unspent",
2532
+ checkedThrough: { number: block.number, hash: block.hash }
2533
+ });
2534
+ }
2535
+ }
2536
+ }
2537
+ }
2538
+ if (draft.reservations[fact.reservationKey] === id) {
2539
+ delete draft.reservations[fact.reservationKey];
2540
+ }
2541
+ });
2542
+ observe(draft, block);
2543
+ } else if (next.externalized || active.status === "reorged") {
2544
+ active.status = "uncertain";
2545
+ }
2546
+ next.generation++;
2547
+ return { status: active.status, saved: structuredClone(next) };
2548
+ });
2549
+ }
2550
+
2551
+ // src/session/Operation.ts
2552
+ function check6(ok, message) {
2553
+ if (!ok) {
2554
+ throw new Error(message);
2555
+ }
2556
+ }
2557
+ var Operation = class _Operation {
2558
+ #runtime;
2559
+ #saved;
2560
+ #authorization;
2561
+ status;
2562
+ constructor(runtime, saved, status, authorization) {
2563
+ this.#runtime = runtime;
2564
+ this.#saved = structuredClone(saved);
2565
+ this.status = status;
2566
+ this.#authorization = authorization;
2567
+ Object.freeze(this);
2568
+ }
2569
+ static async prepare(runtime, plan) {
2570
+ const saved = { plan: structuredClone(plan), generation: 0 };
2571
+ await runtime.store.transaction(runtime.scope, (state) => {
2572
+ check6(!state.invalidated, "wallet state requires canonical resync");
2573
+ check6(!state.operations[plan.id], "operation already exists");
2574
+ const facts = inputFacts(plan);
2575
+ for (const input of facts) {
2576
+ const record = state[input.kind][input.key];
2577
+ check6(
2578
+ record && record.spent === "unspent" && !state.reservations[input.reservationKey],
2579
+ "input unavailable or already reserved"
2580
+ );
2581
+ }
2582
+ state.operations[plan.id] = {
2583
+ id: plan.id,
2584
+ status: "prepared",
2585
+ inputs: facts.map((input) => input.reservationKey),
2586
+ deadline: plan.deadline,
2587
+ payload: saved
2588
+ };
2589
+ for (const input of facts) {
2590
+ state.reservations[input.reservationKey] = plan.id;
2591
+ }
2592
+ });
2593
+ return new _Operation(runtime, saved, "prepared");
2594
+ }
2595
+ static async resume(runtime, id) {
2596
+ const result = await runtime.store.transaction(runtime.scope, (state) => {
2597
+ const record = state.operations[id];
2598
+ check6(record, "unknown operation");
2599
+ const saved = record.payload;
2600
+ check6(
2601
+ saved?.plan?.version === 1 && saved.plan.id === id && Number.isSafeInteger(saved.generation) && saved.generation >= 0 && saved.generation < Number.MAX_SAFE_INTEGER,
2602
+ "invalid operation payload"
2603
+ );
2604
+ const context = runtime.options.protocol.context;
2605
+ const actual = saved.plan.context;
2606
+ check6(
2607
+ scopeKey(saved.plan.scope) === scopeKey(runtime.scope) && actual.chainId === context.chainId && actual.protocolAddress.toLowerCase() === context.protocolAddress.toLowerCase() && actual.tokenAddress.toLowerCase() === context.tokenAddress.toLowerCase(),
2608
+ "recovered operation scope/deployment context mismatch"
2609
+ );
2610
+ if (saved.plan.kind !== "shield") {
2611
+ statement(saved.plan);
2612
+ }
2613
+ if (saved.proof) {
2614
+ sameStatement(saved.proof, saved.plan.statement);
2615
+ }
2616
+ saved.generation++;
2617
+ delete saved.claim;
2618
+ return new _Operation(runtime, saved, record.status);
2619
+ });
2620
+ if (result.#saved.call) {
2621
+ const expected = await result.encode(result.#saved);
2622
+ check6(
2623
+ expected.to === result.#saved.call.to && expected.value === result.#saved.call.value && expected.data === result.#saved.call.data,
2624
+ "recovered transaction differs from frozen operation"
2625
+ );
2626
+ }
2627
+ return result;
2628
+ }
2629
+ get id() {
2630
+ return this.#saved.plan.id;
2631
+ }
2632
+ get kind() {
2633
+ return this.#saved.plan.kind;
2634
+ }
2635
+ estimateGas() {
2636
+ return estimateGasReference(
2637
+ this.#saved.plan,
2638
+ this.#runtime.options.protocol,
2639
+ this.#runtime.options.gasBenchmarks
2640
+ );
2641
+ }
2642
+ preview() {
2643
+ return operationPreview(this.#saved.plan, this.status);
2644
+ }
2645
+ current(record) {
2646
+ check6(record, "unknown operation");
2647
+ const saved = record.payload;
2648
+ check6(
2649
+ saved.generation === this.#saved.generation && !saved.claim,
2650
+ "stale operation handle or active worker; recover the operation"
2651
+ );
2652
+ return saved;
2653
+ }
2654
+ async stage(stage, allowed, work, signal) {
2655
+ signal?.throwIfAborted();
2656
+ const { store, scope } = this.#runtime;
2657
+ const token = crypto.randomUUID();
2658
+ const saved = await store.transaction(scope, (state) => {
2659
+ check6(!state.invalidated, "wallet requires canonical resync");
2660
+ const record = state.operations[this.id];
2661
+ const current2 = this.current(record);
2662
+ check6(allowed.includes(record.status), `invalid operation stage for ${stage}`);
2663
+ current2.generation++;
2664
+ current2.claim = { stage, token };
2665
+ return structuredClone(current2);
2666
+ });
2667
+ try {
2668
+ const next = await work(saved);
2669
+ signal?.throwIfAborted();
2670
+ await store.transaction(scope, (state) => {
2671
+ check6(!state.invalidated, "wallet invalidated during operation");
2672
+ const record = state.operations[this.id];
2673
+ const current2 = record?.payload;
2674
+ check6(
2675
+ record && current2?.generation === saved.generation && current2.claim?.token === token && allowed.includes(record.status),
2676
+ "stale operation worker after recovery"
2677
+ );
2678
+ delete saved.claim;
2679
+ record.status = next.status;
2680
+ record.payload = structuredClone(saved);
2681
+ });
2682
+ return new _Operation(
2683
+ this.#runtime,
2684
+ saved,
2685
+ next.status,
2686
+ next.authorization ?? this.#authorization
2687
+ );
2688
+ } catch (error) {
2689
+ await store.transaction(scope, (state) => {
2690
+ const current2 = state.operations[this.id]?.payload;
2691
+ if (current2?.claim?.token === token) {
2692
+ delete current2.claim;
2693
+ }
2694
+ });
2695
+ throw error;
2696
+ }
2697
+ }
2698
+ async authorize(options = {}) {
2699
+ check6(
2700
+ requiresAuthorization(this.#saved.plan),
2701
+ "operation does not require wallet authorization"
2702
+ );
2703
+ const provider = this.#runtime.authorization();
2704
+ check6(
2705
+ provider.walletIdentity === this.#runtime.scope.walletIdentity,
2706
+ "wallet authorization capability mismatch"
2707
+ );
2708
+ return this.stage(
2709
+ "wallet authorization",
2710
+ ["prepared", "authorized"],
2711
+ async (saved) => {
2712
+ const authorization = await authorizeOperation(saved.plan, provider);
2713
+ return { status: "authorized", authorization };
2714
+ },
2715
+ options.signal
2716
+ );
2717
+ }
2718
+ async prove(options = {}) {
2719
+ const plan = this.#saved.plan;
2720
+ check6(plan.kind !== "shield", "shield requires no proof");
2721
+ if (requiresAuthorization(plan)) {
2722
+ check6(
2723
+ this.#authorization,
2724
+ "local wallet authorization required; authorize again after recovery"
2725
+ );
2726
+ }
2727
+ const prover = this.#runtime.options.prover;
2728
+ if (!prover) {
2729
+ throw new import_core7.MissingCapabilityError("prover");
2730
+ }
2731
+ return this.stage(
2732
+ "proof",
2733
+ requiresAuthorization(plan) ? ["authorized"] : ["prepared"],
2734
+ async (saved) => {
2735
+ const p = saved.plan;
2736
+ saved.proof = await proveOperation(
2737
+ this.#runtime.options.protocol,
2738
+ p,
2739
+ this.#authorization,
2740
+ prover,
2741
+ options
2742
+ );
2743
+ if (p.fundingMode === "signature") {
2744
+ return { status: "proved" };
2745
+ }
2746
+ if (p.statement?.target !== "noir-recursive") {
2747
+ saved.call = await this.encode(saved);
2748
+ }
2749
+ return { status: "ready" };
2750
+ },
2751
+ options.signal
2752
+ );
2753
+ }
2754
+ encode(saved) {
2755
+ return encodeOperation(saved);
2756
+ }
2757
+ async authorizeFunding(options = {}) {
2758
+ const p = this.#saved.plan;
2759
+ check6(p.fundingMode === "signature", "operation does not require a funding signature");
2760
+ check6(p.kind === "shield" || this.#saved.proof, "proof must precede funding authorization");
2761
+ const signer = this.#runtime.options.fundingSigner;
2762
+ if (!signer) {
2763
+ throw new import_core7.MissingCapabilityError("EVM funding signer");
2764
+ }
2765
+ check6(
2766
+ signer.address.toLowerCase() === p.funding.payer.toLowerCase(),
2767
+ "funding signer is not the payer"
2768
+ );
2769
+ return this.stage(
2770
+ "funding authorization",
2771
+ p.kind === "shield" ? ["prepared"] : ["proved"],
2772
+ async (saved) => {
2773
+ saved.plan.funding.signature = await signOperationFunding(
2774
+ saved,
2775
+ signer,
2776
+ this.#runtime.options.fundingVerifier
2777
+ );
2778
+ if (saved.plan.statement?.target !== "noir-recursive") {
2779
+ saved.call = await this.encode(saved);
2780
+ }
2781
+ return { status: "ready" };
2782
+ },
2783
+ options.signal
2784
+ );
2785
+ }
2786
+ /** Shield caller funding needs no wallet signing or proof; this step only encodes its frozen request. */
2787
+ async ready() {
2788
+ check6(
2789
+ this.kind === "shield" && this.#saved.plan.fundingMode === "caller",
2790
+ "operation needs authorization or proof first"
2791
+ );
2792
+ return this.stage("encoding", ["prepared"], async (saved) => {
2793
+ saved.call = await this.encode(saved);
2794
+ return { status: "ready" };
2795
+ });
2796
+ }
2797
+ async validateExport(signal) {
2798
+ signal?.throwIfAborted();
2799
+ const { reader, options: runtimeOptions } = this.#runtime;
2800
+ await runtimeOptions.protocol.validateDeployment(reader);
2801
+ const head = await reader.getBlock(await reader.getLatestBlockNumber());
2802
+ const plan = this.#saved.plan;
2803
+ check6(!operationExpired(plan, head.timestamp), "operation has expired");
2804
+ if (plan.fundingMode === "signature") {
2805
+ check6(head.timestamp <= (plan.funding.deadline ?? 0n), "funding authorization expired");
2806
+ check6(
2807
+ await reader.getFundingNonce(plan.context, plan.funding.payer, head) === plan.funding.sigNonce,
2808
+ "funding nonce changed; prepare again"
2809
+ );
2810
+ }
2811
+ for (const source of operationSources(plan)) {
2812
+ check6(
2813
+ await reader.isKnownRoot(plan.context, source.treeId, source.merkleRoot, head),
2814
+ "source root is no longer accepted; prepare again"
2815
+ );
2816
+ }
2817
+ const nullifiers = inputFacts(plan).map((input) => input.nullifier);
2818
+ if (nullifiers.length) {
2819
+ const spent = await reader.isSpent(plan.context, nullifiers, head);
2820
+ check6(
2821
+ spent.length === nullifiers.length && spent.every((value) => value === false),
2822
+ "input spent or invalid spent response"
2823
+ );
2824
+ }
2825
+ signal?.throwIfAborted();
2826
+ }
2827
+ async exportTransaction(options = {}) {
2828
+ check6(
2829
+ this.#saved.plan.statement?.target !== "noir-recursive",
2830
+ "recursive operations must be exported as aggregate members"
2831
+ );
2832
+ await this.validateExport(options.signal);
2833
+ const result = await this.#runtime.store.transaction(this.#runtime.scope, (state) => {
2834
+ options.signal?.throwIfAborted();
2835
+ const record = state.operations[this.id];
2836
+ const saved = this.current(record);
2837
+ check6(record.status === "ready" && saved.call, "operation is not ready");
2838
+ saved.externalized = true;
2839
+ return structuredClone(saved.call);
2840
+ });
2841
+ return result;
2842
+ }
2843
+ async exportAggregateMember() {
2844
+ return this.#runtime.store.transaction(this.#runtime.scope, (state) => {
2845
+ const record = state.operations[this.id];
2846
+ const saved = this.current(record);
2847
+ const plan = saved.plan;
2848
+ check6(
2849
+ ["ready", "uncertain"].includes(record.status) && plan.params && saved.proof && plan.statement?.target === "noir-recursive" && plan.statement.circuitName === "transact_user_slots14",
2850
+ "operation is not a ready recursive slots14 aggregate member"
2851
+ );
2852
+ sameStatement(saved.proof, statement(plan));
2853
+ check6(
2854
+ saved.proof.proofHex.length === 2 + 458 * 64,
2855
+ "recursive member proof must have 458 fields"
2856
+ );
2857
+ saved.externalized = true;
2858
+ return {
2859
+ bundle: publicProof(saved.proof),
2860
+ statement: structuredClone(plan.statement),
2861
+ context: { ...plan.context },
2862
+ request: {
2863
+ params: structuredClone(plan.params),
2864
+ deliveries: plan.outputs.map((output) => structuredClone(output.delivery)),
2865
+ funding: (0, import_protocol4.normalizeFunding)(plan.funding)
2866
+ }
2867
+ };
2868
+ });
2869
+ }
2870
+ async abandon() {
2871
+ const saved = await this.#runtime.store.transaction(this.#runtime.scope, (state) => {
2872
+ const record = state.operations[this.id];
2873
+ const current2 = this.current(record);
2874
+ check6(
2875
+ !current2.externalized,
2876
+ "externalized or broadcast operation requires chain reconciliation"
2877
+ );
2878
+ check6(
2879
+ !["confirmed", "reorged"].includes(record.status),
2880
+ "operation requires chain reconciliation"
2881
+ );
2882
+ for (const id of record.inputs) {
2883
+ if (state.reservations[id] === this.id) {
2884
+ delete state.reservations[id];
2885
+ }
2886
+ }
2887
+ record.status = "failed";
2888
+ current2.generation++;
2889
+ return structuredClone(current2);
2890
+ });
2891
+ return new _Operation(this.#runtime, saved, "failed");
2892
+ }
2893
+ /** Record an application-owned signed/broadcast transaction. This never sends or waits. */
2894
+ async trackTransaction(transactionHash) {
2895
+ check6(/^0x[a-fA-F0-9]{64}$/.test(transactionHash), "invalid transaction hash");
2896
+ const saved = await this.#runtime.store.transaction(this.#runtime.scope, (state) => {
2897
+ check6(!state.invalidated, "wallet requires canonical resync");
2898
+ const record = state.operations[this.id];
2899
+ const current2 = this.current(record);
2900
+ check6(
2901
+ ["ready", "uncertain", "submitted"].includes(record.status),
2902
+ "operation is not ready"
2903
+ );
2904
+ check6(
2905
+ current2.externalized,
2906
+ "export the transaction or aggregate member before tracking"
2907
+ );
2908
+ for (const input of record.inputs) {
2909
+ check6(state.reservations[input] === this.id, "operation input reservation lost");
2910
+ }
2911
+ current2.generation++;
2912
+ current2.externalized = true;
2913
+ record.transactionHash = transactionHash;
2914
+ record.status = "uncertain";
2915
+ return structuredClone(current2);
2916
+ });
2917
+ return new _Operation(this.#runtime, saved, "uncertain");
2918
+ }
2919
+ async reconcile(options = {}) {
2920
+ options.signal?.throwIfAborted();
2921
+ const state = await this.#runtime.store.readSnapshot(this.#runtime.scope);
2922
+ const record = state.operations[this.id];
2923
+ this.current(record);
2924
+ check6(!state.invalidated, "wallet requires canonical resync");
2925
+ const hash2 = options.transactionHash ?? record.transactionHash;
2926
+ let outcome;
2927
+ if (hash2) {
2928
+ outcome = await confirmOperation(
2929
+ this.#runtime,
2930
+ this.id,
2931
+ this.#saved.generation,
2932
+ hash2,
2933
+ options.signal
2934
+ );
2935
+ }
2936
+ outcome ??= await reconcileOperation(
2937
+ this.#runtime,
2938
+ this.id,
2939
+ this.#saved.generation,
2940
+ options.signal
2941
+ );
2942
+ return new _Operation(this.#runtime, outcome.saved, outcome.status);
2943
+ }
2944
+ toJSON() {
2945
+ return { id: this.id, kind: this.kind, status: this.status };
2946
+ }
2947
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
2948
+ return this.toJSON();
2949
+ }
2950
+ };
2951
+
2952
+ // src/session/WalletSession.ts
2953
+ var import_ethers11 = require("ethers");
2954
+ var import_core12 = require("@invisi-labs/core");
2955
+
2956
+ // src/session/transaction-preparation.ts
2957
+ var import_ethers10 = require("ethers");
2958
+ var import_protocol7 = require("@invisi-labs/protocol");
2959
+
2960
+ // src/session/prepare-common.ts
2961
+ var import_ethers8 = require("ethers");
2962
+ var import_core8 = require("@invisi-labs/core");
2963
+ var import_protocol5 = require("@invisi-labs/protocol");
2964
+
2965
+ // src/session/selection.ts
2966
+ function selectNotes(notes, target, reserved) {
2967
+ if (typeof target !== "bigint" || target < 0n)
2968
+ throw new Error("selection target must be a nonnegative amount");
2969
+ if (target === 0n) return [];
2970
+ const eligible = notes.filter(
2971
+ (note) => note.spent === "unspent" && !reserved.has(note.commitment)
2972
+ );
2973
+ for (const record of eligible)
2974
+ if (typeof record.note.amount !== "bigint" || record.note.amount <= 0n || record.note.amount >= 1n << 128n)
2975
+ throw new Error("invalid verified note amount");
2976
+ eligible.sort(
2977
+ (a, b) => a.note.amount > b.note.amount ? -1 : a.note.amount < b.note.amount ? 1 : a.treeId < b.treeId ? -1 : a.treeId > b.treeId ? 1 : a.leafIndex - b.leafIndex || a.commitment.localeCompare(b.commitment)
2978
+ );
2979
+ const selected = [];
2980
+ let total = 0n;
2981
+ for (const record of eligible) {
2982
+ selected.push(structuredClone(record));
2983
+ total += record.note.amount;
2984
+ if (total >= target) return selected;
2985
+ }
2986
+ throw new Error("insufficient verified available funds");
2987
+ }
2988
+
2989
+ // src/session/prepare-common.ts
2990
+ function positiveAmount(value, bits = 128) {
2991
+ if (typeof value !== "bigint" || value <= 0n || value >= 1n << BigInt(bits)) {
2992
+ throw new Error(`amount must be a positive uint${bits}`);
2993
+ }
2994
+ return value;
2995
+ }
2996
+ var Preparation = class {
2997
+ constructor(runtime) {
2998
+ this.runtime = runtime;
2999
+ }
3000
+ #roots = /* @__PURE__ */ new Map();
3001
+ async frame(requested) {
3002
+ const { reader, options } = this.runtime;
3003
+ await options.protocol.validateDeployment(reader);
3004
+ const block = await reader.getBlock(await reader.getLatestBlockNumber());
3005
+ const deadline = requested ?? block.timestamp + (options.transactionTTL ?? 300n);
3006
+ if (typeof deadline !== "bigint" || deadline < block.timestamp || deadline >= 1n << 64n) {
3007
+ throw new Error("invalid transaction deadline");
3008
+ }
3009
+ const state = await this.runtime.readInventory();
3010
+ if (state.invalidated) {
3011
+ throw new Error("wallet requires canonical resync");
3012
+ }
3013
+ for (const point of [state.observedThrough]) {
3014
+ if (point && (await reader.getBlock(point.number)).hash !== point.hash) {
3015
+ throw new Error("canonical wallet checkpoint changed; resync required");
3016
+ }
3017
+ }
3018
+ return { context: { ...options.protocol.context, now: block.timestamp }, block, deadline };
3019
+ }
3020
+ async assertBlock(frame) {
3021
+ if ((await this.runtime.reader.getBlock(frame.block.number)).hash !== frame.block.hash) {
3022
+ throw new Error("canonical block changed while preparing");
3023
+ }
3024
+ }
3025
+ async funding(source, frame) {
3026
+ if (!source) {
3027
+ return { funding: {}, fundingMode: "none" };
3028
+ }
3029
+ positiveAmount(source.amount);
3030
+ const payer = (0, import_ethers8.getAddress)(source.payer);
3031
+ const mode = source.authorization ?? "signature";
3032
+ if (BigInt(payer) === 0n) {
3033
+ throw new Error("funding payer must be nonzero");
3034
+ }
3035
+ if (mode !== "signature" && mode !== "caller") {
3036
+ throw new Error("invalid funding authorization mode");
3037
+ }
3038
+ if (mode === "caller" && (source.sigNonce || source.deadline)) {
3039
+ throw new Error("caller funding uses zero nonce/deadline");
3040
+ }
3041
+ const funding = (0, import_protocol5.normalizeFunding)({
3042
+ payer,
3043
+ amount: source.amount,
3044
+ sigNonce: mode === "caller" ? 0n : source.sigNonce ?? await this.runtime.reader.getFundingNonce(
3045
+ frame.context,
3046
+ payer,
3047
+ frame.block
3048
+ ),
3049
+ deadline: mode === "caller" ? 0n : source.deadline ?? frame.deadline,
3050
+ ...source.permit ? { permit: structuredClone(source.permit) } : {}
3051
+ });
3052
+ if (mode === "signature" && funding.deadline < frame.block.timestamp) {
3053
+ throw new Error("funding authorization deadline has expired");
3054
+ }
3055
+ return { funding, fundingMode: mode };
3056
+ }
3057
+ async output(payment, context, change = false) {
3058
+ positiveAmount(payment.amount);
3059
+ const recipient = await import_core8.ReceivingAddress.parse(payment.to).resolve(
3060
+ this.runtime.options.addressResolver
3061
+ );
3062
+ const nonce = (0, import_core8.generateNonce)();
3063
+ const identity = await (0, import_core8.computeNoteIdentity)(BigInt(recipient.walletIdentity), nonce);
3064
+ const keys = recipient.getReceivingKeys();
3065
+ const note = {
3066
+ chainId: context.chainId,
3067
+ protocolAddress: BigInt(context.protocolAddress),
3068
+ tokenAddress: BigInt(context.tokenAddress),
3069
+ walletIdentity: recipient.walletIdentity,
3070
+ amount: payment.amount,
3071
+ nonce
3072
+ };
3073
+ const commitment = await (0, import_core8.computeNoteCommitment)(
3074
+ context.chainId,
3075
+ identity,
3076
+ note.tokenAddress,
3077
+ note.amount
3078
+ );
3079
+ return {
3080
+ note,
3081
+ commitment,
3082
+ identity,
3083
+ address: payment.to,
3084
+ change,
3085
+ delivery: {
3086
+ ciphertext: (0, import_ethers8.hexlify)(
3087
+ await (0, import_core8.encryptPrivateTransferNote)(keys.viewingPublicKey, note, {
3088
+ chainId: context.chainId,
3089
+ protocolAddress: note.protocolAddress,
3090
+ outputCommitment: commitment
3091
+ })
3092
+ ),
3093
+ clue: (0, import_ethers8.hexlify)((0, import_core8.createClue)(keys.clueKey))
3094
+ }
3095
+ };
3096
+ }
3097
+ async membership(input, frame) {
3098
+ const { options, reader } = this.runtime;
3099
+ if (!options.protocolRpc)
3100
+ throw new import_core8.MissingCapabilityError("Protocol RPC membership provider");
3101
+ const root = this.#roots.get(input.treeId);
3102
+ const proof = await options.protocolRpc.getMembership(
3103
+ frame.context,
3104
+ {
3105
+ ...input,
3106
+ ...root ? { root } : {}
3107
+ },
3108
+ frame.block
3109
+ );
3110
+ if (root && proof.root !== root)
3111
+ throw new Error("membership provider changed the pinned source root");
3112
+ await verifyMembership(reader, frame.context, frame.block, input, proof);
3113
+ this.#roots.set(input.treeId, proof.root);
3114
+ return { ...proof, path: [...proof.path] };
3115
+ }
3116
+ async selectFunds(target, frame, maxInputs = 168) {
3117
+ const { scope, options, reader } = this.runtime;
3118
+ const state = await this.runtime.readInventory();
3119
+ if (state.invalidated) {
3120
+ throw new Error("wallet requires canonical resync");
3121
+ }
3122
+ const need = target > 0n ? target : 0n;
3123
+ const nullifier = options.wallet.exportNullifier();
3124
+ const reserved = new Set(Object.keys(state.reservations));
3125
+ const eligible = Object.values(state.notes).filter(
3126
+ (note) => note.note.tokenAddress === BigInt(frame.context.tokenAddress) && note.spent === "unspent" && !reserved.has(note.commitment)
3127
+ );
3128
+ const byId = new Map(eligible.map((note) => [note.commitment, note]));
3129
+ const choices = this.runtime.inputSelection ? [...this.runtime.inputSelection] : this.runtime.explicitInputs ? eligible.map((note) => note.commitment) : need > 0n && options.coinSelector ? await options.coinSelector.select(storedCandidates(eligible), need) : selectNotes(eligible, need, reserved).map((note) => note.commitment);
3130
+ if (!Array.isArray(choices) || new Set(choices).size !== choices.length || choices.some((id) => !byId.has(id))) {
3131
+ throw new Error("invalid or duplicate coin selection");
3132
+ }
3133
+ const selected = choices.map((id) => structuredClone(byId.get(id)));
3134
+ const amount = selected.reduce((sum, input) => sum + input.note.amount, 0n);
3135
+ if (amount < need) {
3136
+ throw new Error("coin selection has insufficient verified funds");
3137
+ }
3138
+ if (selected.length > maxInputs || amount >= 1n << 135n) {
3139
+ throw new Error("coin selection exceeds transaction capacity");
3140
+ }
3141
+ const inputs = [];
3142
+ const sources = /* @__PURE__ */ new Map();
3143
+ const nullifiers = [];
3144
+ for (const input of selected) {
3145
+ const note = input.note;
3146
+ const c = frame.context;
3147
+ if (note.walletIdentity !== scope.walletIdentity || note.chainId !== c.chainId || note.protocolAddress !== BigInt(c.protocolAddress) || note.tokenAddress !== BigInt(c.tokenAddress) || await (0, import_core8.computeNoteCommitment)(
3148
+ c.chainId,
3149
+ await (0, import_core8.computeNoteIdentity)(BigInt(note.walletIdentity), note.nonce),
3150
+ note.tokenAddress,
3151
+ note.amount
3152
+ ) !== input.commitment) {
3153
+ throw new Error("selected input preimage/context mismatch");
3154
+ }
3155
+ const proof = await this.membership(input, frame);
3156
+ const path = [...proof.path];
3157
+ const root = proof.root;
3158
+ if (sources.has(input.treeId) && sources.get(input.treeId) !== root) {
3159
+ throw new Error("source roots do not share a canonical snapshot");
3160
+ }
3161
+ sources.set(input.treeId, root);
3162
+ inputs.push({
3163
+ note,
3164
+ commitment: input.commitment,
3165
+ treeId: input.treeId,
3166
+ leafIndex: input.leafIndex,
3167
+ path
3168
+ });
3169
+ nullifiers.push(
3170
+ await nullifier.compute(c.chainId, input.commitment, input.note.nonce)
3171
+ );
3172
+ }
3173
+ await this.assertUnspent(nullifiers, frame);
3174
+ return {
3175
+ inputs,
3176
+ sources: [...sources].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([treeId, merkleRoot]) => ({ treeId, merkleRoot })),
3177
+ nullifiers,
3178
+ amount
3179
+ };
3180
+ }
3181
+ async assertUnspent(nullifiers, frame) {
3182
+ if (nullifiers.length) {
3183
+ const spent = await this.runtime.reader.isSpent(frame.context, nullifiers, frame.block);
3184
+ if (spent.length !== nullifiers.length || spent.some((value) => value !== false)) {
3185
+ throw new Error("selected input spent or invalid spent-state response");
3186
+ }
3187
+ }
3188
+ }
3189
+ };
3190
+ function storedCandidates(inputs) {
3191
+ const records = inputs.filter(
3192
+ (input) => "blockNumber" in input && typeof input.blockNumber === "bigint" && "blockHash" in input && typeof input.blockHash === "string"
3193
+ );
3194
+ if (records.length !== inputs.length)
3195
+ throw new Error("custom coin selection requires stored receipt metadata");
3196
+ return structuredClone(records);
3197
+ }
3198
+
3199
+ // src/session/prepare-credit.ts
3200
+ var import_ethers9 = require("ethers");
3201
+ var import_core9 = require("@invisi-labs/core");
3202
+ var import_protocol6 = require("@invisi-labs/protocol");
3203
+ var EMPTY = { ciphertext: "0x", clue: "0x" };
3204
+ var CreditPreparation = class {
3205
+ constructor(runtime) {
3206
+ this.runtime = runtime;
3207
+ this.#prepare = new Preparation(runtime);
3208
+ }
3209
+ #prepare;
3210
+ expiry(value, frame) {
3211
+ if (typeof value !== "bigint" || value <= frame.block.timestamp || value >= 1n << 64n) {
3212
+ throw new Error("Credit expiry must be a future uint64 timestamp");
3213
+ }
3214
+ return value;
3215
+ }
3216
+ async seal(input, frame) {
3217
+ const state = (0, import_core9.normalizeCreditDeliveryState)(input);
3218
+ const commitment = await creditLeaf(state);
3219
+ const context = {
3220
+ chainId: frame.context.chainId,
3221
+ protocolAddress: BigInt(frame.context.protocolAddress),
3222
+ outputCommitment: commitment
3223
+ };
3224
+ const deliveries = await Promise.all(
3225
+ ["owner", "spender"].map(async (role) => {
3226
+ const keys = role === "owner" ? state.ownerReceivingKeys : state.spenderReceivingKeys;
3227
+ return {
3228
+ ciphertext: (0, import_ethers9.hexlify)(
3229
+ await (0, import_core9.encryptCreditNote)(keys.viewingPublicKey, state, { ...context, role })
3230
+ ),
3231
+ clue: (0, import_ethers9.hexlify)((0, import_core9.createClue)(keys.clueKey))
3232
+ };
3233
+ })
3234
+ );
3235
+ return { state, commitment, deliveries };
3236
+ }
3237
+ async input(commitment, role, frame) {
3238
+ const stored = (await this.runtime.readInventory()).credits[commitment + ":" + role];
3239
+ if (!stored || stored.spent !== "unspent" || stored.role !== role) {
3240
+ throw new Error(`confirmed unspent ${role} Credit is unavailable`);
3241
+ }
3242
+ const state = (0, import_core9.normalizeCreditDeliveryState)(stored.state);
3243
+ const n = state.note;
3244
+ if ((role === "owner" ? n.ownerWalletIdentity : n.spenderWalletIdentity) !== this.runtime.scope.walletIdentity || n.chainId !== frame.context.chainId || n.protocolAddress !== BigInt(frame.context.protocolAddress) || n.tokenAddress !== BigInt(frame.context.tokenAddress) || await creditLeaf(state) !== commitment) {
3245
+ throw new Error("Credit role, commitment or deployment mismatch");
3246
+ }
3247
+ const proof = await this.#prepare.membership(stored, frame);
3248
+ const path = [...proof.path];
3249
+ const nullifier = await (0, import_core9.computeCreditNullifier)(n.chainId, n.creditSharedKey, commitment);
3250
+ await this.#prepare.assertUnspent([nullifier], frame);
3251
+ return {
3252
+ input: {
3253
+ state,
3254
+ commitment,
3255
+ role,
3256
+ treeId: stored.treeId,
3257
+ leafIndex: stored.leafIndex,
3258
+ path
3259
+ },
3260
+ nullifier,
3261
+ source: {
3262
+ treeId: stored.treeId,
3263
+ merkleRoot: proof.root
3264
+ }
3265
+ };
3266
+ }
3267
+ base(frame, kind) {
3268
+ return {
3269
+ version: 1,
3270
+ id: crypto.randomUUID(),
3271
+ kind,
3272
+ context: frame.context,
3273
+ scope: this.runtime.scope,
3274
+ preparedAt: frame.block,
3275
+ deadline: frame.deadline,
3276
+ outputs: [],
3277
+ inputs: [],
3278
+ funding: {},
3279
+ fundingMode: "none"
3280
+ };
3281
+ }
3282
+ async create(options) {
3283
+ positiveAmount(options.amount, 126);
3284
+ const frame = await this.#prepare.frame(options.deadline);
3285
+ const expiry = this.expiry(options.expiry, frame);
3286
+ const spender = await import_core9.ReceivingAddress.parse(options.spender).resolve(
3287
+ this.runtime.options.addressResolver
3288
+ );
3289
+ const owner = await this.runtime.options.wallet.getReceivingAddress().resolve(this.runtime.options.addressResolver);
3290
+ const ownerPayout = (0, import_ethers9.getAddress)(owner.walletIdentity);
3291
+ const spenderPayout = (0, import_ethers9.getAddress)(spender.walletIdentity);
3292
+ if (BigInt(ownerPayout) === 0n || BigInt(spenderPayout) === 0n) {
3293
+ throw new Error("Credit payouts must be nonzero");
3294
+ }
3295
+ const output = await this.seal(
3296
+ {
3297
+ note: {
3298
+ chainId: frame.context.chainId,
3299
+ protocolAddress: BigInt(frame.context.protocolAddress),
3300
+ tokenAddress: BigInt(frame.context.tokenAddress),
3301
+ ownerWalletIdentity: this.runtime.scope.walletIdentity,
3302
+ spenderWalletIdentity: spender.walletIdentity,
3303
+ ownerPayout: BigInt(ownerPayout),
3304
+ spenderPayout: BigInt(spenderPayout),
3305
+ amount: options.amount,
3306
+ expiry,
3307
+ nonce: (0, import_core9.generateNonce)(),
3308
+ creditSharedKey: (0, import_core9.generateCreditSharedKey)()
3309
+ },
3310
+ ownerReceivingKeys: owner.getReceivingKeys(),
3311
+ spenderReceivingKeys: spender.getReceivingKeys()
3312
+ },
3313
+ frame
3314
+ );
3315
+ return this.creation(
3316
+ this.base(frame, "create-credit"),
3317
+ output,
3318
+ options.amount,
3319
+ options.funding,
3320
+ frame
3321
+ );
3322
+ }
3323
+ async creation(plan, output, delta, fundingSource, frame, previous) {
3324
+ const funding = await this.#prepare.funding(fundingSource, frame);
3325
+ const selected = await this.#prepare.selectFunds(
3326
+ delta - (funding.funding.amount ?? 0n),
3327
+ frame,
3328
+ 10
3329
+ );
3330
+ const change = selected.amount + (funding.funding.amount ?? 0n) - delta;
3331
+ plan.inputs = selected.inputs;
3332
+ Object.assign(plan, funding);
3333
+ if (change > 0n) {
3334
+ plan.outputs.push(
3335
+ await this.#prepare.output(
3336
+ {
3337
+ to: this.runtime.changeAddress ?? this.runtime.options.wallet.getReceivingAddress().toString(),
3338
+ amount: change
3339
+ },
3340
+ frame.context,
3341
+ true
3342
+ )
3343
+ );
3344
+ }
3345
+ const creation = {
3346
+ token: frame.context.tokenAddress,
3347
+ sources: selected.sources,
3348
+ nullifiers: selected.nullifiers,
3349
+ creditCommitment: output.commitment,
3350
+ changeCommitment: plan.outputs[0]?.commitment ?? (0, import_protocol6.word)(0n),
3351
+ owner: (0, import_ethers9.getAddress)((0, import_ethers9.toBeHex)(output.state.note.ownerPayout, 20)),
3352
+ expiry: output.state.note.expiry,
3353
+ deadline: frame.deadline
3354
+ };
3355
+ const deliveries = [...output.deliveries, plan.outputs[0]?.delivery ?? { ...EMPTY }];
3356
+ plan.capacity = 10;
3357
+ plan.credit = {
3358
+ inputs: previous ? [previous.input] : [],
3359
+ outputs: [output],
3360
+ deliveries,
3361
+ creation
3362
+ };
3363
+ if (previous) {
3364
+ plan.credit.topUp = {
3365
+ creation,
3366
+ creditSource: previous.source,
3367
+ creditNullifier: previous.nullifier,
3368
+ previousExpiry: previous.input.state.note.expiry
3369
+ };
3370
+ plan.statement = import_protocol6.WitnessBuilder.statement(
3371
+ "credit_top_up",
3372
+ (0, import_protocol6.topUpCreditPublicInputs)(frame.context, plan.credit.topUp, deliveries, plan.funding)
3373
+ );
3374
+ } else {
3375
+ plan.statement = import_protocol6.WitnessBuilder.statement(
3376
+ "create_credit",
3377
+ (0, import_protocol6.createCreditPublicInputs)(frame.context, creation, deliveries, plan.funding)
3378
+ );
3379
+ }
3380
+ await this.#prepare.assertBlock(frame);
3381
+ return this.runtime.complete(plan);
3382
+ }
3383
+ async topUp(options) {
3384
+ positiveAmount(options.additionalAmount, 126);
3385
+ const frame = await this.#prepare.frame(options.deadline);
3386
+ const previous = await this.input(options.credit, "owner", frame);
3387
+ const old = previous.input.state;
3388
+ if (old.note.expiry <= frame.block.timestamp) {
3389
+ throw new Error("previous Credit has expired");
3390
+ }
3391
+ const expiry = this.expiry(options.expiry ?? old.note.expiry, frame);
3392
+ if (expiry < old.note.expiry) {
3393
+ throw new Error("Credit expiry must not decrease");
3394
+ }
3395
+ const amount = positiveAmount(old.note.amount + options.additionalAmount, 126);
3396
+ const output = await this.seal(
3397
+ { ...old, note: { ...old.note, amount, expiry, nonce: (0, import_core9.generateNonce)() } },
3398
+ frame
3399
+ );
3400
+ return this.creation(
3401
+ this.base(frame, "top-up-credit"),
3402
+ output,
3403
+ options.additionalAmount,
3404
+ options.funding,
3405
+ frame,
3406
+ previous
3407
+ );
3408
+ }
3409
+ async draw(options) {
3410
+ const frame = await this.#prepare.frame(options.deadline);
3411
+ const profile = (0, import_protocol6.selectCreditDrawCircuit)(options.credits.length);
3412
+ if (new Set(options.credits.map((value) => value.credit)).size !== options.credits.length) {
3413
+ throw new Error("duplicate Credit input");
3414
+ }
3415
+ const members = await Promise.all(
3416
+ options.credits.map((value) => this.input(value.credit, "spender", frame))
3417
+ );
3418
+ const payout = members[0].input.state.note.spenderPayout;
3419
+ const sources = /* @__PURE__ */ new Map();
3420
+ const outputs = [];
3421
+ const deliveries = [];
3422
+ const recoveryAmounts = [];
3423
+ const remainders = [];
3424
+ const amounts = [];
3425
+ for (const [i, member] of members.entries()) {
3426
+ const state = member.input.state;
3427
+ const n = state.note;
3428
+ const amount = positiveAmount(options.credits[i].amount, 126);
3429
+ if (n.spenderPayout !== payout) {
3430
+ throw new Error(
3431
+ "Credit draw requires separate groups for different public payouts; one call must be atomic"
3432
+ );
3433
+ }
3434
+ this.expiry(n.expiry, frame);
3435
+ if (amount > n.amount) {
3436
+ throw new Error("Credit draw exceeds remaining amount");
3437
+ }
3438
+ if (sources.has(member.source.treeId) && sources.get(member.source.treeId) !== member.source.merkleRoot) {
3439
+ throw new Error("Credit source root mismatch");
3440
+ }
3441
+ sources.set(member.source.treeId, member.source.merkleRoot);
3442
+ amounts.push(amount);
3443
+ const remaining = n.amount - amount;
3444
+ if (remaining === 0n) {
3445
+ remainders.push((0, import_protocol6.word)(0n));
3446
+ recoveryAmounts.push((0, import_protocol6.word)(0n));
3447
+ deliveries.push({ ...EMPTY }, { ...EMPTY });
3448
+ continue;
3449
+ }
3450
+ const args = [
3451
+ n.chainId,
3452
+ n.protocolAddress,
3453
+ n.tokenAddress,
3454
+ BigInt(member.nullifier),
3455
+ n.creditSharedKey
3456
+ ];
3457
+ const output = await this.seal(
3458
+ {
3459
+ ...state,
3460
+ note: {
3461
+ ...n,
3462
+ amount: remaining,
3463
+ nonce: await (0, import_core9.deriveRemainderNonce)(...args)
3464
+ }
3465
+ },
3466
+ frame
3467
+ );
3468
+ outputs.push(output);
3469
+ remainders.push(output.commitment);
3470
+ recoveryAmounts.push(
3471
+ (0, import_protocol6.word)((remaining + await (0, import_core9.computeCreditRecoveryPad)(...args)) % import_core9.FIELD_MODULUS)
3472
+ );
3473
+ deliveries.push(...output.deliveries);
3474
+ }
3475
+ const plan = this.base(frame, "draw-credit");
3476
+ const params = {
3477
+ token: frame.context.tokenAddress,
3478
+ sources: [...sources].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([treeId, merkleRoot]) => ({ treeId, merkleRoot })),
3479
+ nullifiers: members.map((member) => member.nullifier),
3480
+ remainderCommitments: remainders,
3481
+ expiries: members.map((member) => member.input.state.note.expiry),
3482
+ recoveryAmounts,
3483
+ spender: (0, import_ethers9.getAddress)((0, import_ethers9.toBeHex)(payout, 20)),
3484
+ totalAmount: amounts.reduce((sum, amount) => sum + amount, 0n),
3485
+ deadline: frame.deadline
3486
+ };
3487
+ plan.capacity = profile.maxSize;
3488
+ plan.credit = {
3489
+ inputs: members.map((member) => member.input),
3490
+ outputs,
3491
+ deliveries,
3492
+ draw: params,
3493
+ amounts
3494
+ };
3495
+ plan.statement = import_protocol6.WitnessBuilder.statement(
3496
+ profile.name,
3497
+ (0, import_protocol6.creditDrawPublicInputs)(frame.context, params, deliveries)
3498
+ );
3499
+ await this.#prepare.assertBlock(frame);
3500
+ return this.runtime.complete(plan);
3501
+ }
3502
+ async cancel(options) {
3503
+ const frame = await this.#prepare.frame(options.deadline);
3504
+ const previous = await this.input(options.credit, "owner", frame);
3505
+ const n = previous.input.state.note;
3506
+ const plan = this.base(frame, "cancel-credit");
3507
+ const params = {
3508
+ token: frame.context.tokenAddress,
3509
+ source: previous.source,
3510
+ nullifier: previous.nullifier,
3511
+ owner: (0, import_ethers9.getAddress)((0, import_ethers9.toBeHex)(n.ownerPayout, 20)),
3512
+ amount: n.amount,
3513
+ deadline: frame.deadline
3514
+ };
3515
+ plan.capacity = 1;
3516
+ plan.credit = { inputs: [previous.input], outputs: [], deliveries: [], cancel: params };
3517
+ plan.statement = import_protocol6.WitnessBuilder.statement(
3518
+ "credit_cancel",
3519
+ (0, import_protocol6.cancelCreditPublicInputs)(frame.context, params)
3520
+ );
3521
+ await this.#prepare.assertBlock(frame);
3522
+ return this.runtime.complete(plan);
3523
+ }
3524
+ };
3525
+
3526
+ // src/session/transaction-preparation.ts
3527
+ var TransactionPreparation = class {
3528
+ constructor(context) {
3529
+ this.context = context;
3530
+ }
3531
+ runtime(tokenAddress) {
3532
+ if ((0, import_ethers10.getAddress)(tokenAddress) !== (0, import_ethers10.getAddress)(this.context.options.protocol.context.tokenAddress))
3533
+ throw new Error("preparation token mismatch");
3534
+ return this.context;
3535
+ }
3536
+ get scope() {
3537
+ return this.context.scope;
3538
+ }
3539
+ async prepareTransfer(options) {
3540
+ const copied = structuredClone(options);
3541
+ const preparation = new Preparation(this.runtime(copied.tokenAddress));
3542
+ const frame = await preparation.frame(copied.deadline);
3543
+ if (copied.outputs.length < 1 || copied.outputs.length > 168) {
3544
+ throw new Error("transfer requires 1..168 outputs");
3545
+ }
3546
+ const total = copied.outputs.reduce(
3547
+ (sum, output) => sum + positiveAmount(output.amount),
3548
+ 0n
3549
+ );
3550
+ const funding = await preparation.funding(copied.funding, frame);
3551
+ return this.funds(
3552
+ "transfer",
3553
+ copied.outputs,
3554
+ total,
3555
+ import_protocol7.ZERO_ADDRESS,
3556
+ 0n,
3557
+ frame,
3558
+ funding,
3559
+ copied.target,
3560
+ preparation
3561
+ );
3562
+ }
3563
+ async prepareWithdraw(options) {
3564
+ const copied = structuredClone(options);
3565
+ const preparation = new Preparation(this.runtime(copied.tokenAddress));
3566
+ const frame = await preparation.frame(copied.deadline);
3567
+ const to = (0, import_ethers10.getAddress)(copied.to);
3568
+ positiveAmount(copied.amount);
3569
+ if (BigInt(to) === 0n) {
3570
+ throw new Error("withdrawal destination must be nonzero");
3571
+ }
3572
+ return this.funds(
3573
+ "withdraw",
3574
+ [],
3575
+ copied.amount,
3576
+ to,
3577
+ copied.amount,
3578
+ frame,
3579
+ { funding: {}, fundingMode: "none" },
3580
+ copied.target,
3581
+ preparation
3582
+ );
3583
+ }
3584
+ /** Full payout to the note owner; no arbitrary destination, change or spending signature. */
3585
+ async prepareSweep(options) {
3586
+ const copied = structuredClone(options);
3587
+ const runtime = this.runtime(copied.tokenAddress);
3588
+ const state = await runtime.readInventory();
3589
+ const available = Object.values(state.notes).filter(
3590
+ (record) => record.note.tokenAddress === BigInt(copied.tokenAddress) && record.spent === "unspent" && !state.reservations[record.commitment]
3591
+ );
3592
+ const selected = copied.commitments ?? available.map((record) => record.commitment);
3593
+ if (selected.length === 0) throw new Error("empty sweep input selection");
3594
+ if (new Set(selected).size !== selected.length) throw new Error("duplicate sweep inputs");
3595
+ const byId = new Map(available.map((record) => [record.commitment, record]));
3596
+ let amount = 0n;
3597
+ for (const id of selected) {
3598
+ const record = byId.get(id);
3599
+ if (!record) throw new Error("sweep input unavailable");
3600
+ amount += record.note.amount;
3601
+ }
3602
+ positiveAmount(amount);
3603
+ const preparation = new Preparation({
3604
+ ...runtime,
3605
+ inputSelection: selected
3606
+ });
3607
+ const frame = await preparation.frame(copied.deadline);
3608
+ return this.funds(
3609
+ "sweep",
3610
+ [],
3611
+ amount,
3612
+ this.scope.walletIdentity,
3613
+ amount,
3614
+ frame,
3615
+ { funding: {}, fundingMode: "none" },
3616
+ copied.target,
3617
+ preparation
3618
+ );
3619
+ }
3620
+ async funds(kind, payments, total, withdrawalAddress, withdrawalAmount, frame, funding, target, preparation) {
3621
+ if (total >= 1n << 135n) {
3622
+ throw new Error("transaction sum exceeds uint135");
3623
+ }
3624
+ const { context, block, deadline } = frame;
3625
+ const selection = await preparation.selectFunds(
3626
+ total - (funding.funding.amount ?? 0n),
3627
+ frame
3628
+ );
3629
+ const outputs = await Promise.all(
3630
+ payments.map((payment) => preparation.output(payment, context))
3631
+ );
3632
+ const change = selection.amount + (funding.funding.amount ?? 0n) - total;
3633
+ if (change > 0n) {
3634
+ outputs.push(
3635
+ await preparation.output(
3636
+ {
3637
+ to: this.context.changeAddress ?? this.context.options.wallet.getReceivingAddress().toString(),
3638
+ amount: change
3639
+ },
3640
+ context,
3641
+ true
3642
+ )
3643
+ );
3644
+ }
3645
+ const params = {
3646
+ token: context.tokenAddress,
3647
+ sources: selection.sources,
3648
+ nullifiers: selection.nullifiers,
3649
+ outputCommitments: outputs.map((output) => output.commitment),
3650
+ withdrawalAddress,
3651
+ withdrawalAmount,
3652
+ deadline
3653
+ };
3654
+ const deliveries = outputs.map((output) => output.delivery);
3655
+ const normalized = (0, import_protocol7.normalizeTransactRequest)(context, params, deliveries, funding.funding);
3656
+ const statement2 = import_protocol7.WitnessBuilder.statement(
3657
+ `transact_user_slots${normalized.capacity}`,
3658
+ (0, import_protocol7.transactUserPublicInputs)(context, params, deliveries, funding.funding),
3659
+ target ?? "evm"
3660
+ );
3661
+ await preparation.assertBlock(frame);
3662
+ return preparation.runtime.complete({
3663
+ version: 1,
3664
+ id: crypto.randomUUID(),
3665
+ kind,
3666
+ context,
3667
+ scope: this.scope,
3668
+ preparedAt: block,
3669
+ deadline,
3670
+ outputs,
3671
+ inputs: selection.inputs,
3672
+ ...funding,
3673
+ params,
3674
+ capacity: normalized.capacity,
3675
+ statement: statement2
3676
+ });
3677
+ }
3678
+ async prepareShield(options) {
3679
+ const copied = structuredClone(options);
3680
+ const preparation = new Preparation(this.runtime(copied.tokenAddress));
3681
+ const frame = await preparation.frame(copied.deadline);
3682
+ const funding = await preparation.funding(copied.funding, frame);
3683
+ if (copied.outputs.length < 1 || copied.outputs.length > 10) {
3684
+ throw new Error("shield requires 1..10 outputs");
3685
+ }
3686
+ const outputs = await Promise.all(
3687
+ copied.outputs.map((output) => preparation.output(output, frame.context))
3688
+ );
3689
+ if (outputs.reduce((sum, output) => sum + output.note.amount, 0n) !== (funding.funding.amount ?? 0n)) {
3690
+ throw new Error("shield funding must equal output total");
3691
+ }
3692
+ await preparation.assertBlock(frame);
3693
+ return preparation.runtime.complete({
3694
+ version: 1,
3695
+ id: crypto.randomUUID(),
3696
+ kind: "shield",
3697
+ context: frame.context,
3698
+ scope: this.scope,
3699
+ preparedAt: frame.block,
3700
+ deadline: frame.deadline,
3701
+ outputs,
3702
+ inputs: [],
3703
+ ...funding,
3704
+ shieldParams: {
3705
+ token: frame.context.tokenAddress,
3706
+ outputCommitments: outputs.map((output) => output.commitment),
3707
+ deadline: frame.deadline
3708
+ },
3709
+ shieldOutputs: outputs.map((output) => ({
3710
+ identity: output.identity,
3711
+ amount: output.note.amount
3712
+ }))
3713
+ });
3714
+ }
3715
+ prepareCreateCredit(options) {
3716
+ return new CreditPreparation(this.runtime(options.tokenAddress)).create(
3717
+ structuredClone(options)
3718
+ );
3719
+ }
3720
+ prepareDrawCredit(options) {
3721
+ return new CreditPreparation(this.runtime(options.tokenAddress)).draw(
3722
+ structuredClone(options)
3723
+ );
3724
+ }
3725
+ prepareTopUpCredit(options) {
3726
+ return new CreditPreparation(this.runtime(options.tokenAddress)).topUp(
3727
+ structuredClone(options)
3728
+ );
3729
+ }
3730
+ prepareCancelCredit(options) {
3731
+ return new CreditPreparation(this.runtime(options.tokenAddress)).cancel(
3732
+ structuredClone(options)
3733
+ );
3734
+ }
3735
+ };
3736
+
3737
+ // src/session/refresh.ts
3738
+ async function refreshWallet(runtime, options) {
3739
+ const { signal } = options;
3740
+ try {
3741
+ signal?.throwIfAborted();
3742
+ if (typeof options.fromBlock !== "bigint" || options.fromBlock < runtime.fromBlock)
3743
+ throw new Error("refresh range precedes deployment");
3744
+ const pageSize = options.pageSize ?? 100;
3745
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100)
3746
+ throw new Error("refresh page size must be 1..100");
3747
+ const status = await runtime.provider.getStatus(runtime.context, {
3748
+ ...signal ? { signal } : {}
3749
+ });
3750
+ if (!status.indexedHead) throw new Error("Protocol RPC has not indexed any blocks");
3751
+ const toBlock = options.toBlock ?? status.indexedHead.number;
3752
+ if (typeof toBlock !== "bigint" || toBlock < options.fromBlock || toBlock > status.indexedHead.number)
3753
+ throw new Error("refresh range is outside indexed history");
3754
+ const target = await runtime.reader.getBlock(toBlock);
3755
+ if (target.number !== toBlock)
3756
+ throw new Error("Chain RPC returned the wrong refresh anchor");
3757
+ let result;
3758
+ let notes = 0;
3759
+ let credits = 0;
3760
+ for (let from = options.fromBlock; from <= toBlock; from += BigInt(pageSize)) {
3761
+ signal?.throwIfAborted();
3762
+ const end = from + BigInt(pageSize) - 1n;
3763
+ const page = await runtime.provider.getUpdates(runtime.context, {
3764
+ fromBlock: from,
3765
+ toBlock: end < toBlock ? end : toBlock,
3766
+ ...options.detectionKey ? { detectionKey: options.detectionKey } : {},
3767
+ ...signal ? { signal } : {}
3768
+ });
3769
+ if (page.block.number !== (end < toBlock ? end : toBlock))
3770
+ throw new Error("Protocol RPC returned the wrong page anchor");
3771
+ result = await runtime.apply(
3772
+ {
3773
+ context: runtime.context,
3774
+ transactionHashes: page.transactionHashes,
3775
+ block: target
3776
+ },
3777
+ {
3778
+ ...signal ? { signal } : {},
3779
+ ...from === options.fromBlock && options.reset ? { reset: true } : {}
3780
+ }
3781
+ );
3782
+ notes += result.notes;
3783
+ credits += result.credits;
3784
+ }
3785
+ return { ...result, notes, credits };
3786
+ } finally {
3787
+ options.detectionKey?.fill(0);
3788
+ }
3789
+ }
3790
+
3791
+ // src/session/WalletUpdater.ts
3792
+ var import_core11 = require("@invisi-labs/core");
3793
+
3794
+ // src/session/inventory.ts
3795
+ var import_core10 = require("@invisi-labs/core");
3796
+ var CanonicalConflict = class extends Error {
3797
+ };
3798
+ var CreditMetadataMismatch = class extends Error {
3799
+ };
3800
+ function check7(value, message) {
3801
+ if (!value) {
3802
+ throw new Error(message);
3803
+ }
3804
+ }
3805
+ var clone = (value) => structuredClone(value);
3806
+ var InventoryProcessor = class {
3807
+ #options;
3808
+ constructor(options) {
3809
+ this.#options = { ...options, scope: Object.freeze({ ...options.scope }) };
3810
+ }
3811
+ reorgOperations(state) {
3812
+ for (const operation of Object.values(state.operations)) {
3813
+ if (["failed", "expired"].includes(operation.status) && !operation.payload?.externalized) {
3814
+ continue;
3815
+ }
3816
+ operation.status = "reorged";
3817
+ for (const id of operation.inputs) {
3818
+ if (!state.reservations[id]) {
3819
+ state.reservations[id] = operation.id;
3820
+ }
3821
+ }
3822
+ }
3823
+ }
3824
+ async invalidate() {
3825
+ const { store, scope } = this.#options;
3826
+ await store.transaction(scope, (state) => {
3827
+ state.invalidated = "Canonical history changed; rebuild inventory from canonical data";
3828
+ for (const record of [...Object.values(state.notes), ...Object.values(state.credits)]) {
3829
+ record.spent = "unknown";
3830
+ }
3831
+ this.reorgOperations(state);
3832
+ });
3833
+ }
3834
+ checkSnapshotKeys(record) {
3835
+ const expected = this.#options.receivingKeys;
3836
+ const keys = record.role === "owner" ? record.state.ownerReceivingKeys : record.state.spenderReceivingKeys;
3837
+ if (expected && !keys.viewingPublicKey.every((byte, i) => byte === expected.viewingPublicKey[i])) {
3838
+ throw new CreditMetadataMismatch("Credit receiving key snapshot mismatch");
3839
+ }
3840
+ const clue = expected?.clueKey;
3841
+ if (clue && !keys.clueKey.every((byte, i) => byte === clue[i])) {
3842
+ throw new CreditMetadataMismatch("Credit clue key snapshot mismatch");
3843
+ }
3844
+ }
3845
+ async creditNullifier(state) {
3846
+ const n = state.note;
3847
+ return (0, import_core10.computeCreditNullifier)(
3848
+ n.chainId,
3849
+ n.creditSharedKey,
3850
+ await (0, import_core10.computeCreditCommitment)(
3851
+ n.chainId,
3852
+ n.spenderWalletIdentity,
3853
+ n.ownerWalletIdentity,
3854
+ n.spenderPayout,
3855
+ n.ownerPayout,
3856
+ n.tokenAddress,
3857
+ n.amount,
3858
+ n.nonce,
3859
+ n.expiry,
3860
+ await (0, import_core10.computeCreditSharedKeyHash)(n.creditSharedKey)
3861
+ )
3862
+ );
3863
+ }
3864
+ async recoverCredits(base, found, logs, snapshot) {
3865
+ const known = /* @__PURE__ */ new Map();
3866
+ const byNullifier = /* @__PURE__ */ new Map();
3867
+ const cachedNullifiers = /* @__PURE__ */ new Map();
3868
+ const recovered = [];
3869
+ const foundByKey = new Map(
3870
+ found.map((record) => [record.commitment + ":" + record.role, record])
3871
+ );
3872
+ const remember = async (record) => {
3873
+ const key = record.commitment + ":" + record.role;
3874
+ let nullifier = cachedNullifiers.get(record.commitment);
3875
+ if (!nullifier) {
3876
+ nullifier = await this.creditNullifier(record.state);
3877
+ cachedNullifiers.set(record.commitment, nullifier);
3878
+ }
3879
+ known.set(key, record);
3880
+ let members = byNullifier.get(nullifier);
3881
+ if (!members) {
3882
+ members = /* @__PURE__ */ new Set();
3883
+ byNullifier.set(nullifier, members);
3884
+ }
3885
+ members.add(key);
3886
+ };
3887
+ const matching = (nullifier) => [...byNullifier.get(nullifier) ?? []].map((key) => known.get(key));
3888
+ for (const record of Object.values(base.credits)) {
3889
+ await remember(record);
3890
+ }
3891
+ for (const record of found) {
3892
+ try {
3893
+ this.checkSnapshotKeys(record);
3894
+ await remember({ ...record, spent: "unknown" });
3895
+ } catch (error) {
3896
+ if (!(error instanceof CreditMetadataMismatch)) {
3897
+ throw error;
3898
+ }
3899
+ }
3900
+ }
3901
+ const context = {
3902
+ chainId: this.#options.scope.chainId,
3903
+ protocolAddress: BigInt(this.#options.scope.protocolAddress)
3904
+ };
3905
+ for (const event of logs) {
3906
+ if (event.eventName === "CreditDraw") {
3907
+ const args = event.args;
3908
+ const params = args.params;
3909
+ const indices = args.remainderLeafIndices;
3910
+ check7(
3911
+ Array.isArray(params.nullifiers) && params.nullifiers.length === params.remainderCommitments.length && params.nullifiers.length === params.recoveryAmounts.length && params.nullifiers.length === params.expiries.length && params.nullifiers.length === indices.length,
3912
+ "invalid draw recovery slots"
3913
+ );
3914
+ for (const [index2, nullifier] of params.nullifiers.entries()) {
3915
+ for (const previous of matching(nullifier)) {
3916
+ check7(
3917
+ previous.state.note.expiry === params.expiries[index2],
3918
+ "draw recovery expiry mismatch"
3919
+ );
3920
+ const remainder = await (0, import_core10.recoverCreditRemainder)(
3921
+ context,
3922
+ previous.state.note,
3923
+ {
3924
+ creditNullifier: nullifier,
3925
+ remainderCommitment: params.remainderCommitments[index2],
3926
+ recoveryAmount: BigInt(params.recoveryAmounts[index2])
3927
+ }
3928
+ );
3929
+ if (!remainder) {
3930
+ continue;
3931
+ }
3932
+ const record = {
3933
+ commitment: params.remainderCommitments[index2],
3934
+ treeId: BigInt(args.outputTreeId),
3935
+ leafIndex: Number(indices[index2]),
3936
+ blockNumber: event.blockNumber,
3937
+ blockHash: event.blockHash,
3938
+ transactionHash: event.transactionHash,
3939
+ role: previous.role,
3940
+ state: { ...clone(previous.state), note: remainder },
3941
+ receivingKeySource: { kind: "draw", commitment: previous.commitment },
3942
+ spent: "unknown"
3943
+ };
3944
+ check7(
3945
+ await snapshot.getLeaf(record.treeId, record.leafIndex) === BigInt(record.commitment),
3946
+ "draw recovery canonical leaf mismatch"
3947
+ );
3948
+ this.checkSnapshotKeys(record);
3949
+ await remember(record);
3950
+ recovered.push(record);
3951
+ }
3952
+ }
3953
+ } else if (event.eventName === "CreditToppedUp") {
3954
+ const params = event.args.params;
3955
+ for (const previous of matching(params.creditNullifier)) {
3956
+ const child = foundByKey.get(
3957
+ params.creation.creditCommitment + ":" + previous.role
3958
+ );
3959
+ if (!child) {
3960
+ continue;
3961
+ }
3962
+ const old = previous.state.note;
3963
+ const next = child.state.note;
3964
+ if ([
3965
+ "chainId",
3966
+ "protocolAddress",
3967
+ "tokenAddress",
3968
+ "spenderWalletIdentity",
3969
+ "ownerWalletIdentity",
3970
+ "spenderPayout",
3971
+ "ownerPayout",
3972
+ "creditSharedKey"
3973
+ ].some(
3974
+ (field) => old[field] !== next[field]
3975
+ ) || next.amount <= old.amount || next.expiry < old.expiry) {
3976
+ continue;
3977
+ }
3978
+ const repaired = {
3979
+ ...clone(child),
3980
+ state: {
3981
+ note: clone(next),
3982
+ ownerReceivingKeys: clone(previous.state.ownerReceivingKeys),
3983
+ spenderReceivingKeys: clone(previous.state.spenderReceivingKeys)
3984
+ },
3985
+ receivingKeySource: { kind: "top-up", commitment: previous.commitment }
3986
+ };
3987
+ this.checkSnapshotKeys(repaired);
3988
+ await remember(repaired);
3989
+ recovered.push(repaired);
3990
+ }
3991
+ }
3992
+ }
3993
+ return recovered;
3994
+ }
3995
+ };
3996
+
3997
+ // src/session/WalletUpdater.ts
3998
+ var check8 = (value, message) => {
3999
+ if (!value) throw new Error(message);
4000
+ };
4001
+ var position2 = (treeId, leafIndex) => `${treeId}:${leafIndex}`;
4002
+ var WalletUpdater = class extends InventoryProcessor {
4003
+ #options;
4004
+ constructor(options) {
4005
+ super(options);
4006
+ this.#options = { ...options };
4007
+ check8(
4008
+ options.viewing.walletIdentity === options.scope.walletIdentity,
4009
+ "viewing capability wallet mismatch"
4010
+ );
4011
+ }
4012
+ applyUpdates(batch, options = {}) {
4013
+ return this.run(structuredClone(batch), { ...options });
4014
+ }
4015
+ async run(batch, options) {
4016
+ const { reader, protocol, scope, store, viewing } = this.#options;
4017
+ const nullifier = import_core11.NullifierCapability.fromIdentity(scope.walletIdentity);
4018
+ const { signal, reset } = options;
4019
+ signal?.throwIfAborted();
4020
+ const context = protocol.context;
4021
+ check8(
4022
+ batch.context.chainId === context.chainId && batch.context.protocolAddress.toLowerCase() === context.protocolAddress.toLowerCase(),
4023
+ "update deployment context mismatch"
4024
+ );
4025
+ check8(
4026
+ batch.block.number >= protocol.deployment.protocol.fromBlock,
4027
+ "update precedes deployment"
4028
+ );
4029
+ await protocol.validateDeployment(reader);
4030
+ const base = await store.readSnapshot(scope);
4031
+ check8(reset || !base.invalidated, "wallet state is invalidated; rebuild required");
4032
+ try {
4033
+ if (!reset) {
4034
+ for (const cursor of [base.observedThrough]) {
4035
+ if (!cursor) continue;
4036
+ if ((await reader.getBlock(cursor.number)).hash !== cursor.hash)
4037
+ throw new CanonicalConflict("Canonical history changed after a reorg");
4038
+ check8(
4039
+ cursor.number <= batch.block.number,
4040
+ "update target precedes wallet checkpoint"
4041
+ );
4042
+ }
4043
+ } else {
4044
+ base.notes = {};
4045
+ base.credits = {};
4046
+ }
4047
+ const block = await reader.getBlock(batch.block.number);
4048
+ check8(
4049
+ block.hash === batch.block.hash && block.number === batch.block.number,
4050
+ "update target block is not canonical"
4051
+ );
4052
+ const receipts = [];
4053
+ for (const hash2 of new Set(batch.transactionHashes)) {
4054
+ signal?.throwIfAborted();
4055
+ check8(/^0x[0-9a-fA-F]{64}$/.test(hash2), "invalid candidate transaction hash");
4056
+ const receipt = await reader.getReceipt(hash2);
4057
+ check8(
4058
+ receipt && receipt.status === "success" && receipt.transactionHash.toLowerCase() === hash2.toLowerCase(),
4059
+ "invalid or unsuccessful candidate receipt"
4060
+ );
4061
+ check8(
4062
+ receipt.block.number >= protocol.deployment.protocol.fromBlock && receipt.block.number <= block.number,
4063
+ "candidate receipt outside update range"
4064
+ );
4065
+ if ((await reader.getBlock(receipt.block.number)).hash !== receipt.block.hash)
4066
+ throw new CanonicalConflict("Candidate receipt disagrees with canonical chain");
4067
+ receipts.push(receipt);
4068
+ }
4069
+ const logs = receipts.flatMap(
4070
+ (receipt) => readProtocolLogs(receipt, context.protocolAddress)
4071
+ );
4072
+ logs.sort(
4073
+ (a, b) => a.blockNumber < b.blockNumber ? -1 : a.blockNumber > b.blockNumber ? 1 : a.logIndex - b.logIndex
4074
+ );
4075
+ const leaves = /* @__PURE__ */ new Map();
4076
+ for (const event of logs) {
4077
+ if (event.eventName !== "CommitmentsInserted" && event.eventName !== "BatchOutputs")
4078
+ continue;
4079
+ const { treeId, startLeafIndex, commitments } = event.args;
4080
+ for (const [i, commitment] of commitments.entries()) {
4081
+ const key = position2(treeId, Number(startLeafIndex) + i);
4082
+ check8(
4083
+ !leaves.has(key) || leaves.get(key) === BigInt(commitment),
4084
+ "conflicting canonical insertion"
4085
+ );
4086
+ leaves.set(key, BigInt(commitment));
4087
+ }
4088
+ }
4089
+ const getLeaf = (tree, index2) => {
4090
+ const leaf = leaves.get(position2(tree, index2));
4091
+ check8(leaf !== void 0, "missing canonical insertion");
4092
+ return leaf;
4093
+ };
4094
+ const decrypted = await decryptCandidates(logs, {
4095
+ context,
4096
+ viewing,
4097
+ confirmedThrough: block.number
4098
+ });
4099
+ const notes = decrypted.notes.map((record) => ({
4100
+ ...record,
4101
+ spent: "unknown"
4102
+ }));
4103
+ const incoming = decrypted.credits.map((record) => ({
4104
+ ...record,
4105
+ spent: "unknown"
4106
+ }));
4107
+ const recovered = await this.recoverCredits(base, incoming, logs, { getLeaf });
4108
+ const creditsByKey = new Map(
4109
+ incoming.map((record) => [record.commitment + ":" + record.role, record])
4110
+ );
4111
+ for (const record of recovered)
4112
+ creditsByKey.set(record.commitment + ":" + record.role, record);
4113
+ const credits = [...creditsByKey.values()].filter((record) => {
4114
+ try {
4115
+ this.checkSnapshotKeys(record);
4116
+ return true;
4117
+ } catch (error) {
4118
+ if (error instanceof CreditMetadataMismatch) return false;
4119
+ throw error;
4120
+ }
4121
+ }).sort(
4122
+ (a, b) => a.blockNumber < b.blockNumber ? -1 : a.blockNumber > b.blockNumber ? 1 : a.treeId < b.treeId ? -1 : a.treeId > b.treeId ? 1 : a.leafIndex - b.leafIndex
4123
+ );
4124
+ for (const record of [...notes, ...credits]) {
4125
+ signal?.throwIfAborted();
4126
+ const proof = await this.#options.protocolRpc.getMembership(context, record, block);
4127
+ const tokenAddress = "note" in record ? record.note.tokenAddress : record.state.note.tokenAddress;
4128
+ await verifyMembership(
4129
+ reader,
4130
+ {
4131
+ ...context,
4132
+ tokenAddress: `0x${tokenAddress.toString(16).padStart(40, "0")}`
4133
+ },
4134
+ block,
4135
+ record,
4136
+ proof
4137
+ );
4138
+ }
4139
+ const allNotes = {
4140
+ ...base.notes,
4141
+ ...Object.fromEntries(notes.map((record) => [record.commitment, record]))
4142
+ };
4143
+ const allCredits = {
4144
+ ...base.credits,
4145
+ ...Object.fromEntries(
4146
+ credits.map((record) => [record.commitment + ":" + record.role, record])
4147
+ )
4148
+ };
4149
+ const observations = [];
4150
+ for (const [id, record] of Object.entries(allNotes))
4151
+ observations.push({
4152
+ kind: "notes",
4153
+ id,
4154
+ nullifier: await nullifier.compute(
4155
+ scope.chainId,
4156
+ record.commitment,
4157
+ record.note.nonce
4158
+ )
4159
+ });
4160
+ for (const [id, record] of Object.entries(allCredits))
4161
+ observations.push({
4162
+ kind: "credits",
4163
+ id,
4164
+ nullifier: await this.creditNullifier(record.state)
4165
+ });
4166
+ const spent = observations.length ? await reader.isSpent(
4167
+ context,
4168
+ observations.map((item) => item.nullifier),
4169
+ block
4170
+ ) : [];
4171
+ check8(
4172
+ spent.length === observations.length && spent.every((value) => typeof value === "boolean"),
4173
+ "invalid spent-state response"
4174
+ );
4175
+ signal?.throwIfAborted();
4176
+ if ((await reader.getBlock(block.number)).hash !== block.hash)
4177
+ throw new CanonicalConflict("Canonical block changed during update");
4178
+ await store.transaction(scope, (draft) => {
4179
+ signal?.throwIfAborted();
4180
+ check8(draft.revision === base.revision, "wallet changed during update; retry");
4181
+ if (reset) {
4182
+ this.reorgOperations(draft);
4183
+ draft.notes = {};
4184
+ draft.credits = {};
4185
+ delete draft.observedThrough;
4186
+ delete draft.invalidated;
4187
+ }
4188
+ mergeReceiptRecords(draft, { notes, credits, checkpoint: block });
4189
+ observations.forEach((item, index2) => {
4190
+ const record = draft[item.kind][item.id];
4191
+ if (record) {
4192
+ mergeSpentObservation(record, {
4193
+ spent: spent[index2] ? "spent" : "unspent",
4194
+ checkedThrough: { number: block.number, hash: block.hash }
4195
+ });
4196
+ }
4197
+ });
4198
+ draft.observedThrough = { number: block.number, hash: block.hash };
4199
+ });
4200
+ return {
4201
+ block,
4202
+ notes: notes.length,
4203
+ credits: credits.length,
4204
+ balances: walletBalances(await store.readSnapshot(scope))
4205
+ };
4206
+ } catch (error) {
4207
+ if (error instanceof CanonicalConflict) await this.invalidate();
4208
+ throw error;
4209
+ }
4210
+ }
4211
+ };
4212
+
4213
+ // src/session/WalletSession.ts
4214
+ var WalletSession = class {
4215
+ #options;
4216
+ #updater;
4217
+ #sending = /* @__PURE__ */ new Set();
4218
+ #refreshQueue = Promise.resolve();
4219
+ scope;
4220
+ protocol;
4221
+ constructor(options) {
4222
+ this.#options = {
4223
+ ...options,
4224
+ ...options.gasBenchmarks ? { gasBenchmarks: copyGasBenchmarks(options.gasBenchmarks) } : {}
4225
+ };
4226
+ this.protocol = options.protocol;
4227
+ this.scope = Object.freeze({
4228
+ namespace: options.namespace ?? "default",
4229
+ ...options.protocol.context,
4230
+ walletIdentity: options.wallet.walletIdentity
4231
+ });
4232
+ scopeKey(this.scope);
4233
+ Object.freeze(this);
4234
+ }
4235
+ /** Delegate sending to ethers; managed operations additionally retain the resulting hash. */
4236
+ async sendTransaction(input, options = {}) {
4237
+ const signer = this.#options.signer;
4238
+ if (!signer) throw new import_core12.MissingCapabilityError("ethers signer");
4239
+ const operation = input instanceof Operation ? input : void 0;
4240
+ if (operation && this.#sending.has(operation.id))
4241
+ throw new Error("operation is already being sent by this session");
4242
+ if (operation) this.#sending.add(operation.id);
4243
+ const overrides = { ...options };
4244
+ const plain = operation ? void 0 : { ...input };
4245
+ try {
4246
+ if (!signer.provider) throw new import_core12.MissingCapabilityError("connected ethers signer");
4247
+ if ((await signer.provider.getNetwork()).chainId !== this.protocol.context.chainId) {
4248
+ throw new Error("signer-provider session chain mismatch");
4249
+ }
4250
+ if (operation) {
4251
+ if (!this.#options.store) throw new import_core12.MissingCapabilityError("wallet store");
4252
+ const state = await this.#options.store.readSnapshot(this.scope);
4253
+ if (!state.operations[operation.id])
4254
+ throw new Error("operation does not belong to this wallet session");
4255
+ }
4256
+ const call = operation ? await operation.exportTransaction() : plain;
4257
+ const transaction = await signer.sendTransaction({
4258
+ ...overrides,
4259
+ to: call.to,
4260
+ data: call.data,
4261
+ value: call.value,
4262
+ chainId: this.protocol.context.chainId
4263
+ });
4264
+ if (operation) {
4265
+ try {
4266
+ await operation.trackTransaction(transaction.hash);
4267
+ } catch (cause) {
4268
+ throw new Error(
4269
+ `Transaction ${transaction.hash} was sent but its operation association could not be saved; resume and reconcile this hash`,
4270
+ { cause }
4271
+ );
4272
+ }
4273
+ }
4274
+ return transaction;
4275
+ } finally {
4276
+ if (operation) this.#sending.delete(operation.id);
4277
+ }
4278
+ }
4279
+ runtime(tokenAddress) {
4280
+ const options = this.#options;
4281
+ if (!options.store) {
4282
+ throw new import_core12.MissingCapabilityError("wallet store");
4283
+ }
4284
+ if (!options.chainReader) {
4285
+ throw new import_core12.MissingCapabilityError("chain reader");
4286
+ }
4287
+ return {
4288
+ options: { ...options, protocol: options.protocol.forToken(tokenAddress) },
4289
+ scope: this.scope,
4290
+ store: options.store,
4291
+ reader: options.chainReader,
4292
+ authorization: () => options.authorization ?? options.wallet.exportAuthorization()
4293
+ };
4294
+ }
4295
+ updater() {
4296
+ if (this.#updater) return this.#updater;
4297
+ const options = this.#options;
4298
+ if (!options.store || !options.chainReader || !options.protocolRpc)
4299
+ throw new import_core12.MissingCapabilityError("wallet store, chain reader and Protocol RPC");
4300
+ const viewing = options.viewing ?? options.wallet.getViewer();
4301
+ const receivingAddress = options.wallet.getReceivingAddress();
4302
+ const receivingKeys = receivingAddress.kind === "long" ? receivingAddress.getReceivingKeys() : void 0;
4303
+ this.#updater = new WalletUpdater({
4304
+ protocol: options.protocol,
4305
+ scope: this.scope,
4306
+ store: options.store,
4307
+ reader: options.chainReader,
4308
+ viewing,
4309
+ protocolRpc: options.protocolRpc,
4310
+ ...receivingKeys ? { receivingKeys } : {}
4311
+ });
4312
+ return this.#updater;
4313
+ }
4314
+ async balance(tokenAddress) {
4315
+ const store = this.#options.store;
4316
+ if (!store) {
4317
+ throw new import_core12.MissingCapabilityError("wallet store");
4318
+ }
4319
+ return walletBalance(await store.readSnapshot(this.scope), (0, import_ethers11.getAddress)(tokenAddress));
4320
+ }
4321
+ /** Pull explicitly selected indexed pages; no chain scanning or public tree reconstruction. */
4322
+ async refresh(options) {
4323
+ const provider = this.#options.protocolRpc;
4324
+ const reader = this.#options.chainReader;
4325
+ if (!reader) throw new import_core12.MissingCapabilityError("chain reader");
4326
+ if (!provider) throw new import_core12.MissingCapabilityError("Protocol RPC");
4327
+ const updater = this.updater();
4328
+ const request = {
4329
+ ...options,
4330
+ ...options.detectionKey ? { detectionKey: options.detectionKey.slice() } : {}
4331
+ };
4332
+ const task = this.#refreshQueue.then(
4333
+ () => refreshWallet(
4334
+ {
4335
+ provider,
4336
+ reader,
4337
+ context: this.protocol.context,
4338
+ fromBlock: this.protocol.deployment.protocol.fromBlock,
4339
+ apply: (batch, settings) => updater.applyUpdates(batch, settings)
4340
+ },
4341
+ request
4342
+ )
4343
+ );
4344
+ this.#refreshQueue = task.catch(() => {
4345
+ });
4346
+ return task;
4347
+ }
4348
+ preparation(tokenAddress) {
4349
+ const runtime = this.runtime(tokenAddress);
4350
+ return new TransactionPreparation({
4351
+ options: runtime.options,
4352
+ scope: runtime.scope,
4353
+ reader: runtime.reader,
4354
+ authorization: runtime.authorization,
4355
+ readInventory: () => runtime.store.readSnapshot(runtime.scope),
4356
+ complete: (plan) => Operation.prepare(runtime, plan)
4357
+ });
4358
+ }
4359
+ async prepareTransfer(options) {
4360
+ return this.preparation(options.tokenAddress).prepareTransfer(options);
4361
+ }
4362
+ async prepareWithdraw(options) {
4363
+ return this.preparation(options.tokenAddress).prepareWithdraw(options);
4364
+ }
4365
+ async prepareSweep(options) {
4366
+ return this.preparation(options.tokenAddress).prepareSweep(options);
4367
+ }
4368
+ async prepareShield(options) {
4369
+ return this.preparation(options.tokenAddress).prepareShield(options);
4370
+ }
4371
+ async prepareCreateCredit(options) {
4372
+ return this.preparation(options.tokenAddress).prepareCreateCredit(options);
4373
+ }
4374
+ async prepareDrawCredit(options) {
4375
+ return this.preparation(options.tokenAddress).prepareDrawCredit(options);
4376
+ }
4377
+ async prepareTopUpCredit(options) {
4378
+ return this.preparation(options.tokenAddress).prepareTopUpCredit(options);
4379
+ }
4380
+ async prepareCancelCredit(options) {
4381
+ return this.preparation(options.tokenAddress).prepareCancelCredit(options);
4382
+ }
4383
+ async resume(operationId) {
4384
+ const store = this.#options.store;
4385
+ if (!store) throw new import_core12.MissingCapabilityError("wallet store");
4386
+ const state = await store.readSnapshot(this.scope);
4387
+ const saved = state.operations[operationId]?.payload;
4388
+ if (!saved?.plan) throw new Error("operation not found");
4389
+ return Operation.resume(this.runtime(saved.plan.context.tokenAddress), operationId);
4390
+ }
4391
+ toJSON() {
4392
+ return { walletSession: true, walletIdentity: this.scope.walletIdentity };
4393
+ }
4394
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
4395
+ return this.toJSON();
4396
+ }
4397
+ };
4398
+
4399
+ // src/integrations/indexeddb.ts
4400
+ var IndexedDBAtomicRecords = class {
4401
+ #database;
4402
+ constructor(name, factory = indexedDB) {
4403
+ if (!name) throw new Error("IndexedDB database name is required");
4404
+ this.#database = new Promise((resolve, reject) => {
4405
+ const request = factory.open(name, 1);
4406
+ request.onupgradeneeded = () => {
4407
+ request.result.createObjectStore("walletRecords");
4408
+ };
4409
+ request.onsuccess = () => resolve(request.result);
4410
+ request.onerror = () => reject(new Error("could not open wallet IndexedDB"));
4411
+ request.onblocked = () => reject(new Error("wallet IndexedDB upgrade is blocked"));
4412
+ });
4413
+ }
4414
+ async read(key) {
4415
+ const db = await this.#database;
4416
+ return new Promise((resolve, reject) => {
4417
+ const tx = db.transaction("walletRecords", "readonly"), request = tx.objectStore("walletRecords").get(key);
4418
+ let result;
4419
+ request.onsuccess = () => {
4420
+ const value = request.result;
4421
+ if (value) result = { revision: value.revision, data: Uint8Array.from(value.data) };
4422
+ };
4423
+ tx.oncomplete = () => resolve(result);
4424
+ tx.onerror = () => reject(new Error("wallet IndexedDB read failed"));
4425
+ tx.onabort = () => reject(new Error("wallet IndexedDB read aborted"));
4426
+ });
4427
+ }
4428
+ async compareAndSwap(key, previousRevision, next) {
4429
+ const db = await this.#database;
4430
+ return new Promise((resolve, reject) => {
4431
+ const tx = db.transaction("walletRecords", "readwrite"), store = tx.objectStore("walletRecords"), request = store.get(key);
4432
+ let swapped = false;
4433
+ request.onsuccess = () => {
4434
+ if ((request.result?.revision ?? 0) === previousRevision) {
4435
+ store.put({ revision: next.revision, data: Uint8Array.from(next.data) }, key);
4436
+ swapped = true;
4437
+ }
4438
+ };
4439
+ tx.oncomplete = () => resolve(swapped);
4440
+ tx.onerror = () => reject(new Error("wallet IndexedDB transaction failed"));
4441
+ tx.onabort = () => reject(new Error("wallet IndexedDB transaction aborted"));
4442
+ });
4443
+ }
4444
+ async close() {
4445
+ (await this.#database).close();
4446
+ }
4447
+ };
4448
+ var IndexedDBWalletStore = class extends TransactionalWalletStore {
4449
+ #records;
4450
+ constructor(name, codec, factory) {
4451
+ const records = new IndexedDBAtomicRecords(name, factory);
4452
+ super(records, codec, { persistent: true });
4453
+ this.#records = records;
4454
+ }
4455
+ close() {
4456
+ return this.#records.close();
4457
+ }
4458
+ };
4459
+ // Annotate the CommonJS export names for ESM import in node:
4460
+ 0 && (module.exports = {
4461
+ IndexedDBAtomicRecords,
4462
+ IndexedDBWalletStore
4463
+ });
4464
+ //# sourceMappingURL=indexeddb.cjs.map