@invisi-labs/sdk 1.1.6 → 1.1.7

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.
Files changed (67) hide show
  1. package/dist/{NoteOutput-DRkVnwlx.d.cts → broadcaster-preparation-DMxxZ_1Q.d.cts} +309 -120
  2. package/dist/{NoteOutput-DpDC6T2O.d.ts → broadcaster-preparation-DMxxZ_1Q.d.ts} +309 -120
  3. package/dist/broadcaster-preparation-RMWQFYNL.js +11 -0
  4. package/dist/broadcaster-preparation-UGOTO4SX.cjs +11 -0
  5. package/dist/broadcaster-preparation-UGOTO4SX.cjs.map +1 -0
  6. package/dist/chunk-7F7ZZW3L.cjs +374 -0
  7. package/dist/chunk-7F7ZZW3L.cjs.map +1 -0
  8. package/dist/{chunk-4SD3MW6N.js → chunk-EHP5TBC6.js} +1347 -2493
  9. package/dist/chunk-EHP5TBC6.js.map +1 -0
  10. package/dist/{broadcaster-preparation-QVL62KM6.js → chunk-GTPUYPQL.js} +6 -6
  11. package/dist/chunk-GTPUYPQL.js.map +1 -0
  12. package/dist/{chunk-FL4TIGWW.js → chunk-ITTGBIG2.js} +617 -521
  13. package/dist/chunk-ITTGBIG2.js.map +1 -0
  14. package/dist/chunk-J2JH6HIG.cjs +1877 -0
  15. package/dist/chunk-J2JH6HIG.cjs.map +1 -0
  16. package/dist/chunk-LOU7AP2B.cjs +128 -0
  17. package/dist/chunk-LOU7AP2B.cjs.map +1 -0
  18. package/dist/chunk-RHNIHI4P.cjs +3565 -0
  19. package/dist/chunk-RHNIHI4P.cjs.map +1 -0
  20. package/dist/{chunk-QC6TQR3R.js → chunk-XHKQ2BO2.js} +12 -12
  21. package/dist/chunk-XHKQ2BO2.js.map +1 -0
  22. package/dist/ethers.cjs +11 -385
  23. package/dist/ethers.cjs.map +1 -1
  24. package/dist/ethers.d.cts +3 -4
  25. package/dist/ethers.d.ts +3 -4
  26. package/dist/ethers.js +1 -1
  27. package/dist/index.cjs +451 -7845
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +72 -702
  30. package/dist/index.d.ts +72 -702
  31. package/dist/index.js +374 -862
  32. package/dist/index.js.map +1 -1
  33. package/dist/receipt-logs-CvC951p7.d.cts +318 -0
  34. package/dist/receipt-logs-m0-bhaxW.d.ts +318 -0
  35. package/dist/session-support.cjs +168 -0
  36. package/dist/session-support.cjs.map +1 -0
  37. package/dist/session-support.d.cts +252 -0
  38. package/dist/session-support.d.ts +252 -0
  39. package/dist/session-support.js +168 -0
  40. package/dist/session-support.js.map +1 -0
  41. package/package.json +10 -20
  42. package/dist/broadcaster-preparation-QVL62KM6.js.map +0 -1
  43. package/dist/chunk-4SD3MW6N.js.map +0 -1
  44. package/dist/chunk-FL4TIGWW.js.map +0 -1
  45. package/dist/chunk-QC6TQR3R.js.map +0 -1
  46. package/dist/indexeddb.cjs +0 -6878
  47. package/dist/indexeddb.cjs.map +0 -1
  48. package/dist/indexeddb.d.cts +0 -20
  49. package/dist/indexeddb.d.ts +0 -20
  50. package/dist/indexeddb.js +0 -71
  51. package/dist/indexeddb.js.map +0 -1
  52. package/dist/memory.cjs +0 -6886
  53. package/dist/memory.cjs.map +0 -1
  54. package/dist/memory.d.cts +0 -12
  55. package/dist/memory.d.ts +0 -12
  56. package/dist/memory.js +0 -11
  57. package/dist/sqlite.cjs +0 -6862
  58. package/dist/sqlite.cjs.map +0 -1
  59. package/dist/sqlite.d.cts +0 -28
  60. package/dist/sqlite.d.ts +0 -28
  61. package/dist/sqlite.js +0 -55
  62. package/dist/sqlite.js.map +0 -1
  63. package/dist/state-C4HZFotm.d.cts +0 -78
  64. package/dist/state-C4HZFotm.d.ts +0 -78
  65. package/dist/store-8aBhnbGr.d.ts +0 -42
  66. package/dist/store-YrbXb6CY.d.cts +0 -42
  67. /package/dist/{memory.js.map → broadcaster-preparation-RMWQFYNL.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,807 +1,184 @@
1
1
  import {
2
- DisclosureVerifier,
3
- Operation,
4
- WalletSession,
5
- acceptFundingSignature,
6
- acceptWalletSignature,
7
- copyGasBenchmarks,
2
+ AddressRegistry,
3
+ BroadcasterClient,
4
+ BroadcasterRpcError,
5
+ Invisi,
6
+ PreparedTransaction,
7
+ TransactionOutput,
8
+ broadcasterQuoteDomain,
9
+ broadcasterQuoteTypes,
10
+ broadcasterRequestHash,
11
+ broadcasterTransactInterface,
12
+ decodeBroadcasterIntent,
8
13
  decryptCandidates,
9
14
  defineGasBenchmarks,
10
- deserializeState,
11
- estimateGasReference,
12
- fundingSigningRequest,
13
- operationPreview,
14
- proveOperation,
15
+ extractDeliveryCandidates,
15
16
  readProtocolLogs,
16
- sameStatement,
17
- serializeState,
18
- walletSigningRequest
19
- } from "./chunk-4SD3MW6N.js";
17
+ verifyBroadcasterQuote
18
+ } from "./chunk-EHP5TBC6.js";
20
19
  import {
21
20
  InvisiError,
22
21
  NoteOutput,
23
- TransactionPreparation,
24
- configureRecipientInfrastructure,
25
- encodeOperation,
26
- exportBroadcasterIntent,
27
- positiveAmount,
28
- prepareWithErrors,
29
- publicProof,
30
- requiresAuthorization,
31
- statement,
32
- walletBalance
33
- } from "./chunk-FL4TIGWW.js";
22
+ decodePaymentNote,
23
+ decodePaymentRequest,
24
+ encodePaymentNote,
25
+ encodePaymentRequest
26
+ } from "./chunk-ITTGBIG2.js";
34
27
  import {
35
- EthersChainReader,
36
- EthersFundingVerifier
37
- } from "./chunk-QC6TQR3R.js";
28
+ EthersChainReader
29
+ } from "./chunk-XHKQ2BO2.js";
38
30
 
39
- // src/explicit-preparation.ts
40
- import { getAddress } from "ethers";
31
+ // src/index.ts
32
+ import {
33
+ Detector,
34
+ Disclosure,
35
+ MAX_MEMO_BYTES,
36
+ MissingCapabilityError,
37
+ NullifierCapability,
38
+ PublicWallet,
39
+ ViewingCapability,
40
+ Wallet as Wallet2,
41
+ WalletAuthorization,
42
+ computeCreditNullifier,
43
+ creditPeriods,
44
+ creditV1,
45
+ decodeDeliveryHeader,
46
+ decodeMemoText,
47
+ encodeMemo,
48
+ fundsV1
49
+ } from "@invisi-labs/core";
50
+ import { RegistrationOperation } from "@invisi-labs/protocol";
41
51
 
42
- // src/PreparedTransaction.ts
43
- import { MissingCapabilityError } from "@invisi-labs/core";
44
- import { normalizeFunding } from "@invisi-labs/protocol";
45
- function check(ok, message) {
46
- if (!ok) throw new Error(message);
52
+ // src/disclosure/DisclosureVerifier.ts
53
+ function check(value, message) {
54
+ if (!value) throw new Error(message);
47
55
  }
48
- var PreparedTransaction = class _PreparedTransaction {
49
- #runtime;
50
- #saved;
51
- #authorization;
52
- #lifecycle;
53
- status;
54
- constructor(runtime, saved, status, lifecycle, authorization) {
55
- this.#runtime = runtime;
56
- this.#saved = structuredClone(saved);
57
- this.#lifecycle = lifecycle;
58
- this.#authorization = authorization;
59
- this.status = status;
60
- Object.freeze(this);
61
- }
62
- static prepare(runtime, plan) {
63
- return new _PreparedTransaction(
64
- runtime,
65
- { plan: structuredClone(plan), generation: 0 },
66
- "prepared",
67
- { generation: 0, busy: false }
68
- );
69
- }
70
- broadcasterQuote() {
71
- return this.#saved.broadcasterQuote ? structuredClone(this.#saved.broadcasterQuote) : void 0;
72
- }
73
- async withBroadcaster(client, options) {
74
- this.current();
75
- check(
76
- this.status === "prepared" && this.#runtime.reprepare,
77
- "broadcaster must attach to an unsigned prepared transaction"
78
- );
79
- const { prepareBroadcasterPlan } = await import("./broadcaster-preparation-QVL62KM6.js");
80
- const selected = await prepareBroadcasterPlan(
81
- this.#saved.plan,
82
- this.#runtime.reprepare,
83
- client,
84
- options
85
- );
86
- return new _PreparedTransaction(
87
- this.#runtime,
88
- { plan: selected.plan, broadcasterQuote: selected.quote, generation: 0 },
89
- "prepared",
90
- { generation: 0, busy: false }
91
- );
92
- }
93
- get id() {
94
- return this.#saved.plan.id;
95
- }
96
- get kind() {
97
- return this.#saved.plan.kind;
98
- }
99
- estimateGas() {
100
- return estimateGasReference(
101
- this.#saved.plan,
102
- this.#runtime.options.protocol,
103
- this.#runtime.options.gasBenchmarks
104
- );
105
- }
106
- preview() {
107
- return operationPreview(this.#saved.plan, this.status);
108
- }
109
- /** Obtain a firm quote before signing. This intent contains no private inputs or witness. */
110
- async exportBroadcasterIntent() {
111
- this.current();
112
- check(
113
- ["prepared", "authorized"].includes(this.status),
114
- "quote intent requires a prepared transaction"
115
- );
116
- return exportBroadcasterIntent(this.#saved.plan);
117
- }
118
- current() {
119
- check(
120
- !this.#lifecycle.busy && this.#lifecycle.generation === this.#saved.generation,
121
- "stale transaction handle or active worker"
122
- );
123
- }
124
- async stage(stage, allowed, work, signal) {
125
- signal?.throwIfAborted();
126
- this.current();
127
- check(allowed.includes(this.status), `invalid transaction stage for ${stage}`);
128
- this.#lifecycle.busy = true;
129
- const saved = structuredClone(this.#saved);
130
- try {
131
- const next = await work(saved);
132
- signal?.throwIfAborted();
133
- saved.generation++;
134
- this.#lifecycle.generation = saved.generation;
135
- return new _PreparedTransaction(
136
- this.#runtime,
137
- saved,
138
- next.status,
139
- this.#lifecycle,
140
- next.authorization ?? this.#authorization
141
- );
142
- } finally {
143
- this.#lifecycle.busy = false;
144
- }
145
- }
146
- /** Public EIP-712 request; signing transport and key custody belong to the caller. */
147
- async exportWalletAuthorization(options = {}) {
148
- options.signal?.throwIfAborted();
149
- this.current();
150
- check(
151
- requiresAuthorization(this.#saved.plan),
152
- "operation does not require wallet authorization"
153
- );
154
- check(
155
- ["prepared", "authorized"].includes(this.status),
156
- "operation is not awaiting wallet authorization"
157
- );
158
- return walletSigningRequest(this.#saved.plan);
159
- }
160
- /** Attach an external EOA signature, retaining its witness only at the private proving boundary. */
161
- async withWalletSignature(signature, options = {}) {
162
- options.signal?.throwIfAborted();
163
- this.current();
164
- check(
165
- requiresAuthorization(this.#saved.plan),
166
- "operation does not require wallet authorization"
167
- );
56
+ function reference(value) {
57
+ check(/^0x[0-9a-fA-F]{64}$/.test(value.transactionHash), "invalid disclosure transaction hash");
58
+ check(
59
+ typeof value.treeId === "bigint" && value.treeId > 0n && value.treeId < 1n << 64n,
60
+ "invalid disclosure tree ID"
61
+ );
62
+ check(
63
+ Number.isSafeInteger(value.leafIndex) && value.leafIndex >= 0 && value.leafIndex < 65536,
64
+ "invalid disclosure leaf index"
65
+ );
66
+ check(["funds", "owner", "spender"].includes(value.role), "invalid disclosure reference role");
67
+ return {
68
+ transactionHash: value.transactionHash.toLowerCase(),
69
+ treeId: value.treeId,
70
+ leafIndex: value.leafIndex,
71
+ role: value.role
72
+ };
73
+ }
74
+ var DisclosureVerifier = class {
75
+ #protocol;
76
+ #reader;
77
+ #confirmations;
78
+ constructor(options) {
79
+ const confirmations = options.confirmations ?? 1;
168
80
  check(
169
- ["prepared", "authorized"].includes(this.status),
170
- "operation is not awaiting wallet authorization"
171
- );
172
- const authorization = await acceptWalletSignature(this.#saved.plan, signature);
173
- return this.stage(
174
- "wallet authorization",
175
- ["prepared", "authorized"],
176
- async () => ({ status: "authorized", authorization }),
177
- options.signal
81
+ Number.isSafeInteger(confirmations) && confirmations >= 1,
82
+ "invalid disclosure confirmations"
178
83
  );
84
+ this.#protocol = options.protocol;
85
+ this.#reader = "provider" in options ? new EthersChainReader(options.provider) : options.reader;
86
+ this.#confirmations = BigInt(confirmations);
87
+ Object.freeze(this);
179
88
  }
180
- /** Export only after proving when the funding authorization binds an actual proof hash. */
181
- async exportFundingAuthorization(options = {}) {
182
- options.signal?.throwIfAborted();
183
- this.current();
89
+ async barrier(block) {
90
+ await this.#protocol.validateDeployment(this.#reader);
91
+ const canonical = await this.#reader.getBlock(block.number);
184
92
  check(
185
- this.status === (this.kind === "shield" ? "prepared" : "proved"),
186
- "operation is not awaiting funding authorization or proof"
93
+ canonical.number === block.number && canonical.hash.toLowerCase() === block.hash.toLowerCase(),
94
+ "disclosure canonical block changed; reorg verification required"
187
95
  );
188
- const request = await fundingSigningRequest(this.#saved, this.#runtime);
189
- options.signal?.throwIfAborted();
190
- this.current();
191
- return request;
192
- }
193
- /** Verify an external signature against this handle's own frozen operation. */
194
- async withFundingSignature(signature, options = {}) {
195
- options.signal?.throwIfAborted();
196
- this.current();
96
+ const head = await this.#reader.getLatestBlockNumber();
197
97
  check(
198
- this.status === (this.kind === "shield" ? "prepared" : "proved"),
199
- "operation is not awaiting funding authorization or proof"
98
+ head >= block.number && head - block.number + 1n >= this.#confirmations,
99
+ "insufficient disclosure confirmations"
200
100
  );
201
- const verified = await acceptFundingSignature(this.#saved, signature, this.#runtime);
202
- return this.stage(
203
- "funding authorization",
204
- this.kind === "shield" ? ["prepared"] : ["proved"],
205
- async (saved) => {
206
- saved.plan.funding.signature = verified;
207
- if (saved.plan.statement?.target !== "noir-recursive") {
208
- saved.call = await this.encode(saved);
209
- }
210
- return { status: "ready" };
211
- },
212
- options.signal
213
- );
214
- }
215
- async prove(backendOrOptions = {}, suppliedOptions = {}) {
216
- const options = { ..."prove" in backendOrOptions ? suppliedOptions : backendOrOptions };
217
101
  check(
218
- !this.#saved.broadcasterQuote || options.execution !== "aggregate",
219
- "a broadcaster quote requires direct execution"
102
+ await this.#reader.getChainId() === this.#protocol.context.chainId,
103
+ "disclosure chain changed during verification"
220
104
  );
221
- const plan = this.#saved.plan;
222
- check(plan.kind !== "shield", "shield requires no proof");
223
- if (requiresAuthorization(plan)) {
224
- check(
225
- this.#authorization,
226
- "local wallet authorization required; authorize again after recovery"
227
- );
228
- }
229
- const prover = "prove" in backendOrOptions ? backendOrOptions : this.#runtime.options.prover;
230
- if (!prover) {
231
- throw new MissingCapabilityError("prover");
232
- }
233
- return this.stage(
234
- "proof",
235
- requiresAuthorization(plan) ? ["authorized"] : ["prepared"],
236
- async (saved) => {
237
- const p = saved.plan;
238
- saved.proof = await proveOperation(
239
- this.#runtime.options.protocol,
240
- p,
241
- this.#authorization,
242
- prover,
243
- options
244
- );
245
- if (p.fundingMode === "signature") {
246
- return { status: "proved" };
247
- }
248
- if (p.statement?.target !== "noir-recursive") {
249
- saved.call = await this.encode(saved);
250
- }
251
- return { status: "ready" };
252
- },
253
- options.signal
254
- );
255
- }
256
- encode(saved) {
257
- return encodeOperation(saved);
258
- }
259
- /** Shield caller funding needs no wallet signing or proof; this step only encodes its frozen request. */
260
- async ready() {
105
+ return head - block.number + 1n;
106
+ }
107
+ /** Resolve the exact inserted output and its delivery from a successful canonical receipt. */
108
+ async readOutput(value) {
109
+ const selected = reference(value);
110
+ await this.#protocol.validateDeployment(this.#reader);
111
+ const received = await this.#reader.getReceipt(selected.transactionHash);
112
+ check(received, "disclosure transaction receipt is unavailable");
113
+ const receipt = structuredClone(received);
114
+ check(receipt.status === "success", "disclosure requires a successful transaction receipt");
261
115
  check(
262
- this.kind === "shield" && this.#saved.plan.fundingMode === "caller",
263
- "operation needs authorization or proof first"
116
+ receipt.transactionHash.toLowerCase() === selected.transactionHash,
117
+ "disclosure receipt transaction mismatch"
264
118
  );
265
- return this.stage("encoding", ["prepared"], async (saved) => {
266
- saved.call = await this.encode(saved);
267
- return { status: "ready" };
268
- });
269
- }
270
- async exportTransaction() {
271
- this.current();
272
119
  check(
273
- this.#saved.plan.statement?.target !== "noir-recursive",
274
- "recursive operations must be exported as aggregate members"
120
+ typeof receipt.block.number === "bigint" && receipt.block.number >= 0n && /^0x[0-9a-fA-F]{64}$/.test(receipt.block.hash),
121
+ "invalid disclosure receipt block"
275
122
  );
276
- check(this.status === "ready" && this.#saved.call, "transaction is not ready");
277
- return structuredClone(this.#saved.call);
278
- }
279
- exportProof() {
280
- this.current();
281
- check(this.#saved.proof, "transaction has no proof");
282
- return publicProof(this.#saved.proof);
283
- }
284
- exportAggregateMember() {
285
- this.current();
286
- const saved = this.#saved, plan = saved.plan;
287
- check(
288
- this.status === "ready" && plan.params && saved.proof && plan.statement?.target === "noir-recursive" && plan.statement.circuitName === "transact_user_slots14",
289
- "ready recursive slots14 transaction required"
123
+ const { context } = this.#protocol;
124
+ const logs = readProtocolLogs(receipt, context.protocolAddress);
125
+ const candidates = extractDeliveryCandidates(logs, context, receipt.block.number).filter(
126
+ (candidate2) => candidate2.treeId === selected.treeId && candidate2.leafIndex === selected.leafIndex && candidate2.role === selected.role && candidate2.tokenAddress.toLowerCase() === context.tokenAddress.toLowerCase()
290
127
  );
291
- sameStatement(saved.proof, statement(plan));
292
128
  check(
293
- saved.proof.proofHex.length === 2 + 458 * 64,
294
- "recursive member proof must have 458 fields"
129
+ candidates.length === 1,
130
+ "disclosure output has no unique canonical insertion and delivery"
295
131
  );
296
- return {
297
- bundle: publicProof(saved.proof),
298
- statement: structuredClone(plan.statement),
299
- context: { ...plan.context },
300
- request: {
301
- params: structuredClone(plan.params),
302
- deliveries: plan.outputs.map((output) => structuredClone(output.delivery)),
303
- funding: normalizeFunding(plan.funding)
304
- }
305
- };
306
- }
307
- toJSON() {
308
- return { transaction: true, id: this.id, kind: this.kind, status: this.status };
309
- }
310
- [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
311
- return this.toJSON();
312
- }
313
- };
314
-
315
- // src/explicit-preparation.ts
316
- function explicitPreparation(options, data, tokenAddress) {
317
- if (!options.chainReader) throw new Error("chain reader required");
318
- const inputs = structuredClone(data.inputs ?? []);
319
- const credits = structuredClone(data.creditInputs ?? []);
320
- const inventory = { notes: {}, credits: {}, reservations: {} };
321
- const context = options.protocol.context;
322
- function position(input) {
323
- if (typeof input.treeId !== "bigint" || input.treeId < 0n || input.treeId >= 1n << 256n || !Number.isInteger(input.leafIndex) || input.leafIndex < 0 || input.leafIndex >= 65536)
324
- throw new Error("invalid explicit input position");
325
- }
326
- for (const input of inputs) {
327
- position(input);
328
- positiveAmount(input.note.amount);
329
- if (inventory.notes[input.commitment]) throw new Error("duplicate explicit note input");
330
- if (input.note.chainId !== context.chainId || input.note.protocolAddress !== BigInt(context.protocolAddress) || input.note.ownerAddress !== options.wallet.ownerAddress)
331
- throw new Error("explicit note identity or deployment mismatch");
332
- inventory.notes[input.commitment] = { ...input, spent: "unspent" };
333
- }
334
- for (const input of credits) {
335
- position(input);
336
- if (input.role !== "owner" && input.role !== "spender")
337
- throw new Error("invalid explicit Credit role");
338
- const key = input.commitment + ":" + input.role;
339
- if (inventory.credits[key]) throw new Error("duplicate explicit Credit input");
340
- inventory.credits[key] = { ...input, spent: "unspent" };
341
- }
342
- const scope = {
343
- ...context,
344
- namespace: "explicit",
345
- ownerAddress: options.wallet.ownerAddress
346
- };
347
- const {
348
- store: _store,
349
- coinSelector: _selector,
350
- authorization: _authorization,
351
- viewing: _viewing,
352
- signer: _signer,
353
- fundingSigner: _fundingSigner,
354
- ...capabilities
355
- } = options;
356
- if (inputs.some((input) => input.note.tokenAddress !== BigInt(getAddress(tokenAddress))))
357
- throw new Error("explicit note token mismatch");
358
- const runtime = {
359
- options: { ...capabilities, protocol: options.protocol.forToken(tokenAddress) },
360
- reader: options.chainReader,
361
- scope
362
- };
363
- const reprepare = (intent) => import("./broadcaster-preparation-QVL62KM6.js").then(
364
- ({ prepareIntent }) => prepareIntent(
365
- {
366
- ...runtime,
367
- explicitInputs: true,
368
- ...data.changeAddress ? { changeAddress: data.changeAddress } : {},
369
- readInventory: async () => structuredClone(inventory),
370
- complete: async (plan) => plan
371
- },
372
- intent
373
- )
374
- );
375
- return new TransactionPreparation({
376
- ...runtime,
377
- explicitInputs: true,
378
- ...data.changeAddress ? { changeAddress: data.changeAddress } : {},
379
- readInventory: async () => structuredClone(inventory),
380
- complete: async (plan) => PreparedTransaction.prepare({ ...runtime, reprepare }, plan)
381
- });
382
- }
383
-
384
- // src/Invisi.ts
385
- import { ProtocolRpcClient } from "@invisi-labs/rpc";
386
- import { MissingCapabilityError as MissingCapabilityError2, PublicWallet } from "@invisi-labs/core";
387
-
388
- // src/AddressRegistry.ts
389
- import { AddressRegistry as Registry } from "@invisi-labs/protocol";
390
- var AddressRegistry = class extends Registry {
391
- prepareRegistration(options) {
392
- return prepareWithErrors("prepareRegistration", async () => {
393
- if ("provider" in options) {
394
- const { provider, ...preparation } = options;
395
- return super.prepareRegistration({
396
- ...preparation,
397
- reader: new EthersChainReader(provider)
398
- });
399
- }
400
- return super.prepareRegistration(options);
132
+ const candidate = candidates[0];
133
+ const confirmations = await this.barrier(receipt.block);
134
+ return Object.freeze({
135
+ reference: Object.freeze(selected),
136
+ block: Object.freeze({ ...receipt.block }),
137
+ confirmations,
138
+ output: Object.freeze({
139
+ chainId: context.chainId,
140
+ protocolAddress: BigInt(context.protocolAddress),
141
+ tokenAddress: BigInt(context.tokenAddress),
142
+ outputCommitment: candidate.commitment,
143
+ role: selected.role,
144
+ ciphertext: candidate.delivery.ciphertext
145
+ })
401
146
  });
402
147
  }
403
- };
404
-
405
- // src/Invisi.ts
406
- var Invisi = class {
407
- #options;
408
- constructor(options = {}) {
409
- if ("addressLookup" in options || "addressResolver" in options)
410
- throw new TypeError(
411
- "Recipient lookup belongs to each EVM output; caller resolver injection is not supported"
412
- );
413
- if ("ciphertextCodecs" in options || "envelopeVersion" in options) {
414
- throw new Error(
415
- "SDK envelope versions are fixed; encode application payload bytes externally"
416
- );
417
- }
418
- if ("signer" in options || "fundingSigner" in options) {
419
- throw new Error(
420
- "Invisi does not accept signer defaults; export a signing request and attach the external signature"
421
- );
422
- }
423
- this.#options = {
424
- ...options,
425
- ...options.gasBenchmarks ? { gasBenchmarks: copyGasBenchmarks(options.gasBenchmarks) } : {}
426
- };
427
- Object.freeze(this);
428
- }
429
- async connect(options) {
430
- return new WalletSession(await this.resolve(options));
431
- }
432
- async resolve(options) {
433
- const allowed = [
434
- "wallet",
435
- "protocol",
436
- "namespace",
437
- "viewing",
438
- "authorization",
439
- "signer",
440
- "fundingSigner"
441
- ];
442
- for (const key of Object.keys(options)) {
443
- if (!allowed.includes(key))
444
- throw new Error(
445
- `Unknown connection option: ${key}; configure infrastructure on Invisi`
446
- );
447
- }
448
- const selected = { ...options };
449
- const combined = { ...this.#options, ...selected };
450
- const { provider, ...runtime } = combined;
451
- const reader = combined.chainReader ?? (provider ? new EthersChainReader(provider) : void 0);
452
- if (!reader) throw new MissingCapabilityError2("chain reader");
453
- if (provider && (await provider.getNetwork()).chainId !== selected.protocol.context.chainId)
454
- throw new Error("provider deployment chain mismatch");
455
- await selected.protocol.validateDeployment(reader);
456
- if (runtime.protocolRpc instanceof ProtocolRpcClient) {
457
- runtime.protocolRpc = runtime.protocolRpc.withReader(reader);
458
- }
459
- const fundingVerifier = combined.fundingVerifier ?? (provider ? new EthersFundingVerifier(provider, selected.protocol.context.chainId) : void 0);
460
- return configureRecipientInfrastructure({
461
- ...runtime,
462
- ...provider ? { provider } : {},
463
- chainReader: reader,
464
- ...fundingVerifier ? { fundingVerifier } : {},
465
- protocol: selected.protocol
466
- });
467
- }
468
- /** Discover the selected chain's registry and prepare an independent signing request. */
469
- async prepareRegistration(options) {
470
- return prepareWithErrors("prepareRegistration", async () => {
471
- for (const key of Object.keys(options)) {
472
- if (!["wallet", "update", "deadline"].includes(key)) {
473
- throw new Error(`Unknown registration option: ${key}`);
474
- }
475
- }
476
- this.assertPublicIdentity(options);
477
- const selected = { ...options };
478
- const { provider, protocolRpc } = this.#options;
479
- if (!provider) throw new MissingCapabilityError2("ethers provider");
480
- if (!(protocolRpc instanceof ProtocolRpcClient)) {
481
- throw new MissingCapabilityError2("Protocol RPC deployment discovery");
482
- }
483
- const chainId = (await provider.getNetwork()).chainId;
484
- const deployment = await protocolRpc.getDeployment(chainId);
485
- if (!deployment.registry) {
486
- throw new MissingCapabilityError2("receiving-key registry deployment");
487
- }
488
- const registry = new AddressRegistry(
489
- { chainId, registryAddress: deployment.registry.address },
490
- { expectedRuntimeCodeHash: deployment.registry.runtimeCodeHash }
491
- );
492
- return registry.prepareRegistration({ ...selected, provider });
493
- });
494
- }
495
- assertPublicIdentity(options) {
496
- const { wallet } = options;
497
- if (!(wallet instanceof PublicWallet) || ["getViewer", "getDetector", "exportAuthorization"].some((key) => key in wallet)) {
498
- throw new Error(
499
- "Independent preparation requires a PublicWallet without private capabilities"
500
- );
501
- }
502
- for (const key of ["authorization", "signer", "fundingSigner", "viewing"]) {
503
- if (key in options)
504
- throw new Error(`Independent preparation does not accept capability: ${key}`);
505
- }
506
- }
507
- resolveExplicit(options) {
508
- this.assertPublicIdentity(options);
509
- return this.resolve({ wallet: options.wallet, protocol: options.protocol });
510
- }
511
- async prepareTransfer(options) {
512
- return prepareWithErrors("prepareTransfer", async () => {
513
- const { wallet, protocol, ...input } = options;
514
- const request = structuredClone(input);
515
- const runtime = await this.resolveExplicit(options);
516
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
517
- return preparation.prepareTransfer(request);
518
- });
519
- }
520
- async prepareShield(options) {
521
- return prepareWithErrors("prepareShield", async () => {
522
- const { wallet, protocol, ...input } = options;
523
- const request = structuredClone(input);
524
- const runtime = await this.resolveExplicit(options);
525
- const preparation = explicitPreparation(runtime, {}, request.tokenAddress);
526
- return preparation.prepareShield(request);
527
- });
528
- }
529
- async prepareCreateCredit(options) {
530
- return prepareWithErrors("prepareCreateCredit", async () => {
531
- const { wallet, protocol, ...input } = options;
532
- const request = structuredClone(input);
533
- const runtime = await this.resolveExplicit(options);
534
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
535
- return preparation.prepareCreateCredit(request);
536
- });
537
- }
538
- async prepareDrawCredit(options) {
539
- return prepareWithErrors("prepareDrawCredit", async () => {
540
- const { wallet, protocol, ...input } = options;
541
- const request = structuredClone(input);
542
- const runtime = await this.resolveExplicit(options);
543
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
544
- return preparation.prepareDrawCredit(request);
545
- });
546
- }
547
- async prepareTopUpCredit(options) {
548
- return prepareWithErrors("prepareTopUpCredit", async () => {
549
- const { wallet, protocol, ...input } = options;
550
- const request = structuredClone(input);
551
- const runtime = await this.resolveExplicit(options);
552
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
553
- return preparation.prepareTopUpCredit(request);
554
- });
555
- }
556
- async prepareCancelCredit(options) {
557
- return prepareWithErrors("prepareCancelCredit", async () => {
558
- const { wallet, protocol, ...input } = options;
559
- const request = structuredClone(input);
560
- const runtime = await this.resolveExplicit(options);
561
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
562
- return preparation.prepareCancelCredit(request);
148
+ async verify(disclosure, value) {
149
+ const evidence = await this.readOutput(value);
150
+ const result = await disclosure.verify(evidence.output);
151
+ const confirmations = await this.barrier(evidence.block);
152
+ return Object.freeze({
153
+ result: Object.freeze(result),
154
+ block: evidence.block,
155
+ confirmations,
156
+ output: Object.freeze({
157
+ ...evidence.reference,
158
+ commitment: evidence.output.outputCommitment
159
+ })
563
160
  });
564
161
  }
565
162
  toJSON() {
566
- return { invisi: true };
163
+ return { disclosureVerifier: true, confirmations: this.#confirmations.toString() };
567
164
  }
568
165
  [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
569
166
  return this.toJSON();
570
167
  }
571
168
  };
572
169
 
573
- // src/index.ts
574
- import {
575
- fundsV1,
576
- creditV1,
577
- decodeDeliveryHeader,
578
- Wallet,
579
- PublicWallet as PublicWallet2,
580
- ViewingCapability,
581
- Detector,
582
- NullifierCapability,
583
- WalletAuthorization,
584
- MissingCapabilityError as MissingCapabilityError3,
585
- Disclosure,
586
- encodeMemo,
587
- decodeMemoText,
588
- MAX_MEMO_BYTES,
589
- computeCreditNullifier,
590
- creditPeriods
591
- } from "@invisi-labs/core";
592
- import { RegistrationOperation } from "@invisi-labs/protocol";
593
-
594
- // src/Protocol.ts
595
- import { Protocol as BaseProtocol } from "@invisi-labs/protocol";
596
- import { getAddress as getAddress3 } from "ethers";
597
-
598
- // src/broadcaster/index.ts
599
- import { AbiCoder, getAddress as getAddress2, Interface, keccak256, verifyTypedData } from "ethers";
600
- import { invisiProtocolAbi } from "@invisi-labs/protocol/contracts";
601
- var broadcasterQuoteTypes = {
602
- Quote: [
603
- { name: "token", type: "address" },
604
- { name: "feeRecipient", type: "address" },
605
- { name: "feeAmount", type: "uint256" },
606
- { name: "deadline", type: "uint256" },
607
- { name: "maxGas", type: "uint256" },
608
- { name: "requestHash", type: "bytes32" }
609
- ]
610
- };
611
- function broadcasterQuoteDomain(trust) {
612
- return {
613
- name: "InvisiBroadcaster",
614
- version: "1",
615
- chainId: trust.chainId,
616
- verifyingContract: getAddress2(trust.protocol)
617
- };
618
- }
619
- var supportedMethods = ["transact", "shield", "createCredit", "topUpCredit", "cancelCredit"];
620
- var intentAbi = invisiProtocolAbi.filter(
621
- (item) => item.type === "function" && supportedMethods.includes(item.name) && (item.name !== "transact" || item.inputs[1]?.type === "tuple")
622
- );
623
- var broadcasterTransactInterface = new Interface(intentAbi);
624
- function decodeBroadcasterIntent(data) {
625
- const parsed = broadcasterTransactInterface.parseTransaction({ data });
626
- if (!parsed || !supportedMethods.includes(parsed.name))
627
- throw new Error("unsupported broadcaster method");
628
- const fragment = parsed.fragment;
629
- const decoded = parsed.args;
630
- if (broadcasterTransactInterface.encodeFunctionData(fragment, decoded).toLowerCase() !== data.toLowerCase())
631
- throw new Error("noncanonical broadcaster calldata");
632
- const method = parsed.name;
633
- const raw = method === "transact" ? decoded[1] : void 0;
634
- const params = raw?.params ?? (method === "shield" ? decoded[0] : method === "topUpCredit" ? decoded[1].creation : decoded[1]);
635
- const funding = raw?.funding ?? (method === "shield" ? decoded[3] : ["createCredit", "topUpCredit"].includes(method) ? decoded[3] : void 0);
636
- const deliveries = raw?.deliveries ?? (method === "shield" ? decoded[2] : ["createCredit", "topUpCredit"].includes(method) ? decoded[2] : []);
637
- return {
638
- fragment,
639
- method,
640
- args: decoded,
641
- proof: method === "shield" ? "0x" : decoded[0],
642
- request: { params, funding, deliveries }
643
- };
644
- }
645
- function broadcasterRequestHash(data) {
646
- const { fragment, method, args } = decodeBroadcasterIntent(data);
647
- const copy = (value) => Array.isArray(value) ? value.map(copy) : value;
648
- const canonical = copy(args);
649
- if (method !== "shield") canonical[0] = "0x";
650
- if (method === "transact") canonical[1][2][4] = "0x";
651
- else if (method !== "cancelCredit") canonical[3][4] = "0x";
652
- const calldata = broadcasterTransactInterface.encodeFunctionData(fragment, canonical);
653
- return keccak256(
654
- AbiCoder.defaultAbiCoder().encode(
655
- ["string", "string", "bytes"],
656
- ["InvisiBroadcasterIntentV1", method, calldata]
657
- )
658
- );
170
+ // src/protocol/connections.ts
171
+ var connections = /* @__PURE__ */ new WeakMap();
172
+ function rememberProtocolConnection(protocol, provider, protocolRpc) {
173
+ connections.set(protocol, { provider, protocolRpc });
659
174
  }
660
- function verifyBroadcasterQuote(signed, trust, data, now = BigInt(Math.floor(Date.now() / 1e3))) {
661
- const { quote } = signed;
662
- const { request } = decodeBroadcasterIntent(data);
663
- const p = request.params;
664
- if (quote.requestHash !== broadcasterRequestHash(data) || getAddress2(quote.token) !== getAddress2(p.token) || getAddress2(quote.feeRecipient) !== getAddress2(p.feeRecipient) || BigInt(quote.feeAmount) !== p.feeAmount || BigInt(quote.deadline) > p.deadline || BigInt(quote.deadline) < now || BigInt(quote.maxGas) <= 0n || BigInt(quote.feeAmount) <= 0n || BigInt(quote.feeAmount) >= 1n << 128n)
665
- throw new Error("quote does not match final intent or bounds");
666
- if (getAddress2(
667
- verifyTypedData(
668
- broadcasterQuoteDomain(trust),
669
- broadcasterQuoteTypes,
670
- quote,
671
- signed.signature
672
- )
673
- ) !== getAddress2(trust.signer))
674
- throw new Error("untrusted quote signature");
675
- return quote;
175
+ function protocolConnection(protocol) {
176
+ return connections.get(protocol);
676
177
  }
677
- function checkedStatus(value, id) {
678
- const status = value;
679
- if (!status || status.id !== id || !["pending", "submitted", "confirmed", "reverted", "uncertain"].includes(status.state) || status.transactionHash !== void 0 && !/^0x[0-9a-fA-F]{64}$/.test(status.transactionHash))
680
- throw new Error("invalid broadcaster status");
681
- return status;
682
- }
683
- var BroadcasterClient = class {
684
- configuration;
685
- url;
686
- transport;
687
- requestId = 0;
688
- constructor(configuration, transport) {
689
- this.configuration = Object.freeze({
690
- endpoint: configuration.endpoint,
691
- quoteSigner: getAddress2(configuration.quoteSigner),
692
- feeRecipient: getAddress2(configuration.feeRecipient)
693
- });
694
- if (BigInt(this.configuration.quoteSigner) === 0n || BigInt(this.configuration.feeRecipient) === 0n)
695
- throw new Error("invalid broadcaster configuration");
696
- this.url = configuration.endpoint;
697
- const base = new URL(this.url);
698
- if (base.protocol !== "https:" || base.username || base.password)
699
- throw new Error("broadcaster requires HTTPS");
700
- this.transport = transport ?? (async (request) => {
701
- const response = await fetch(base, {
702
- redirect: "error",
703
- method: "POST",
704
- body: JSON.stringify(request),
705
- headers: { "content-type": "application/json" },
706
- signal: AbortSignal.timeout(3e4)
707
- });
708
- if (!response.ok) throw new Error(`Broadcaster HTTP ${response.status}`);
709
- if (!response.body) throw new Error("empty broadcaster response");
710
- const reader = response.body.getReader();
711
- const chunks = [];
712
- let size = 0;
713
- try {
714
- while (true) {
715
- const result = await reader.read();
716
- if (result.done) break;
717
- size += result.value.length;
718
- if (size > 65536) throw new Error("broadcaster response too large");
719
- chunks.push(result.value);
720
- }
721
- } finally {
722
- await reader.cancel();
723
- }
724
- const bytes = new Uint8Array(size);
725
- let offset = 0;
726
- for (const chunk of chunks) {
727
- bytes.set(chunk, offset);
728
- offset += chunk.length;
729
- }
730
- return JSON.parse(new TextDecoder().decode(bytes));
731
- });
732
- }
733
- async call(method, params) {
734
- const id = ++this.requestId;
735
- const response = await this.transport({
736
- jsonrpc: "2.0",
737
- id,
738
- method,
739
- params: [params]
740
- });
741
- if (!response || response.jsonrpc !== "2.0" || response.id !== id || Object.hasOwn(response, "result") === Object.hasOwn(response, "error"))
742
- throw new Error("invalid broadcaster JSON-RPC response");
743
- if (response.error) {
744
- if (!Number.isInteger(response.error.code) || typeof response.error.message !== "string")
745
- throw new Error("invalid broadcaster JSON-RPC error");
746
- throw new BroadcasterRpcError(response.error.code, response.error.message);
747
- }
748
- return response.result;
749
- }
750
- async fee(token, data) {
751
- const fee = await this.call("broadcaster_getFee", {
752
- token: getAddress2(token),
753
- ...data ? { data } : {}
754
- });
755
- if (!fee || getAddress2(fee.feeRecipient) !== this.configuration.feeRecipient || !/^\d+$/.test(fee.feeAmount) || BigInt(fee.feeAmount) <= 0n || BigInt(fee.feeAmount) >= 1n << 128n || !/^\d+$/.test(fee.maxGas) || BigInt(fee.maxGas) <= 0n)
756
- throw new Error("invalid broadcaster fee");
757
- return fee;
758
- }
759
- async quote(data, context) {
760
- const signed = await this.call("broadcaster_getQuote", {
761
- data
762
- });
763
- verifyBroadcasterQuote(
764
- signed,
765
- { ...context, signer: this.configuration.quoteSigner },
766
- data
767
- );
768
- if (getAddress2(signed.quote.feeRecipient) !== this.configuration.feeRecipient)
769
- throw new Error("unregistered fee recipient");
770
- return { quote: signed.quote, signature: signed.signature, context: { ...context } };
771
- }
772
- async submit(data, signed, idempotencyKey) {
773
- if (!signed.context) throw new Error("missing verified quote context");
774
- verifyBroadcasterQuote(
775
- signed,
776
- { ...signed.context, signer: this.configuration.quoteSigner },
777
- data,
778
- 0n
779
- );
780
- if (getAddress2(signed.quote.feeRecipient) !== this.configuration.feeRecipient)
781
- throw new Error("unregistered fee recipient");
782
- return checkedStatus(
783
- await this.call("broadcaster_submitTransaction", {
784
- data,
785
- quote: signed.quote,
786
- signature: signed.signature,
787
- idempotencyKey
788
- }),
789
- idempotencyKey
790
- );
791
- }
792
- async status(id) {
793
- return checkedStatus(await this.call("broadcaster_getTransactionStatus", { id }), id);
794
- }
795
- };
796
- var BroadcasterRpcError = class extends Error {
797
- constructor(code, message) {
798
- super(message);
799
- this.code = code;
800
- this.name = "BroadcasterRpcError";
801
- }
802
- };
803
178
 
804
- // src/Protocol.ts
179
+ // src/protocol/Protocol.ts
180
+ import { Protocol as BaseProtocol } from "@invisi-labs/protocol";
181
+ import { getAddress } from "ethers";
805
182
  var sources = /* @__PURE__ */ new WeakMap();
806
183
  var Protocol = class _Protocol extends BaseProtocol {
807
184
  static async fromRpc(rpc, provider, options = {}) {
@@ -809,11 +186,17 @@ var Protocol = class _Protocol extends BaseProtocol {
809
186
  const protocol = new _Protocol({ deployment: verified.deployment });
810
187
  if (rpc.getBroadcasters)
811
188
  sources.set(protocol, rpc);
189
+ if (rpc.withReader)
190
+ rememberProtocolConnection(
191
+ protocol,
192
+ provider,
193
+ rpc
194
+ );
812
195
  return protocol;
813
196
  }
814
197
  /** Returns enabled services that answer a token fee probe; final quotes are checked by prepared. */
815
198
  async getBroadcasters(options) {
816
- const token = getAddress3(options.tokenAddress);
199
+ const token = getAddress(options.tokenAddress);
817
200
  if (!this.deployment.broadcasterRegistry)
818
201
  throw new Error("broadcaster registry is not configured");
819
202
  const rpc = sources.get(this);
@@ -854,22 +237,14 @@ var Protocol = class _Protocol extends BaseProtocol {
854
237
  };
855
238
 
856
239
  // src/index.ts
857
- import { ProtocolRpcError, MAX_UPDATE_BLOCKS } from "@invisi-labs/rpc";
858
-
859
- // src/ProtocolRpcClient.ts
860
- import { ProtocolRpcClient as RpcClient } from "@invisi-labs/rpc";
861
- var ProtocolRpcClient2 = class extends RpcClient {
862
- withProvider(provider) {
863
- return this.withReader(new EthersChainReader(provider));
864
- }
865
- };
240
+ import { MAX_UPDATE_BLOCKS, ProtocolRpcError } from "@invisi-labs/rpc";
866
241
 
867
- // src/BroadcasterRegistry.ts
868
- import { getAddress as getAddress4, Interface as Interface2, keccak256 as keccak2562 } from "ethers";
242
+ // src/protocol/BroadcasterRegistry.ts
869
243
  import { broadcasterRegistryAbi } from "@invisi-labs/protocol/contracts";
870
- var abi = new Interface2(broadcasterRegistryAbi);
244
+ import { getAddress as getAddress2, Interface, keccak256 } from "ethers";
245
+ var abi = new Interface(broadcasterRegistryAbi);
871
246
  function address(value) {
872
- const normalized = getAddress4(value);
247
+ const normalized = getAddress2(value);
873
248
  if (BigInt(normalized) === 0n) throw new Error("zero broadcaster address");
874
249
  return normalized;
875
250
  }
@@ -912,7 +287,7 @@ var BroadcasterRegistry = class {
912
287
  if ((await provider.getNetwork()).chainId !== chainId) {
913
288
  throw new Error("broadcaster registry chain mismatch");
914
289
  }
915
- if (keccak2562(await provider.getCode(this.address)).toLowerCase() !== runtimeCodeHash.toLowerCase()) {
290
+ if (keccak256(await provider.getCode(this.address)).toLowerCase() !== runtimeCodeHash.toLowerCase()) {
916
291
  throw new Error("broadcaster registry runtime code mismatch");
917
292
  }
918
293
  }
@@ -952,7 +327,7 @@ var BroadcasterRegistry = class {
952
327
  toBlock: end
953
328
  });
954
329
  for (const log of logs) {
955
- if (getAddress4(log.address) !== this.address)
330
+ if (getAddress2(log.address) !== this.address)
956
331
  throw new Error("registry event address mismatch");
957
332
  const event = abi.parseLog(log);
958
333
  if (!event || event.name !== "BroadcasterSet")
@@ -970,87 +345,220 @@ var BroadcasterRegistry = class {
970
345
  }
971
346
  };
972
347
 
973
- // src/integrations/encrypted-codec.ts
974
- var EncryptedWalletCodec = class _EncryptedWalletCodec {
975
- #key;
976
- constructor(key) {
977
- this.#key = key;
348
+ // src/protocol/ProtocolRpcClient.ts
349
+ import { ProtocolRpcClient as RpcClient } from "@invisi-labs/rpc";
350
+ var ProtocolRpcClient = class extends RpcClient {
351
+ withProvider(provider) {
352
+ return this.withReader(new EthersChainReader(provider));
353
+ }
354
+ };
355
+
356
+ // src/payment-note/PaymentNote.ts
357
+ import { computeNoteIdentity, computeNoteCommitment, Wallet } from "@invisi-labs/core";
358
+
359
+ // src/payment-note/verification.ts
360
+ import {
361
+ poseidonHash,
362
+ TREE_DEPTH,
363
+ computeNoteNullifier
364
+ } from "@invisi-labs/core";
365
+ import { getAddress as getAddress3 } from "ethers";
366
+ function verificationOptions(protocol, options = {}) {
367
+ const connected = protocolConnection(protocol);
368
+ const resolved = { ...connected, ...options };
369
+ if (!("protocolRpc" in resolved) || !resolved.protocolRpc || !("chainReader" in resolved) && !("provider" in resolved))
370
+ throw new Error(
371
+ "Payment note verification requires Protocol.fromRpc or explicit chain and Protocol RPC connections"
372
+ );
373
+ return resolved;
374
+ }
375
+ function sameBlock(a, b) {
376
+ return a.number === b.number && a.hash === b.hash && a.parentHash === b.parentHash && a.timestamp === b.timestamp;
377
+ }
378
+ function asNote(data) {
379
+ return {
380
+ chainId: data.chainId,
381
+ protocolAddress: BigInt(data.protocolAddress),
382
+ tokenAddress: BigInt(data.tokenAddress),
383
+ amount: data.amount,
384
+ ownerAddress: data.ownerAddress,
385
+ nonce: data.nonce
386
+ };
387
+ }
388
+ async function verifyPaymentNote(data, protocol, options, wallet) {
389
+ if (data.chainId !== protocol.context.chainId || getAddress3(data.protocolAddress) !== getAddress3(protocol.context.protocolAddress))
390
+ throw new Error("Payment note protocol context mismatch");
391
+ const confirmations = options.confirmations ?? 1;
392
+ if (!Number.isSafeInteger(confirmations) || confirmations < 1)
393
+ throw new RangeError("confirmations must be positive");
394
+ const reader = options.chainReader ?? new EthersChainReader(options.provider);
395
+ await protocol.validateDeployment(reader);
396
+ if (wallet && getAddress3(wallet.ownerAddress) !== getAddress3(data.ownerAddress))
397
+ throw new Error("Payment note owner mismatch");
398
+ if (wallet) {
399
+ if (typeof wallet.exportAuthorization !== "function")
400
+ throw new Error("Wallet spending authorization capability required");
401
+ const authorization = wallet.exportAuthorization();
402
+ if (getAddress3(authorization.ownerAddress) !== getAddress3(data.ownerAddress))
403
+ throw new Error("Wallet authorization owner mismatch");
404
+ }
405
+ const latest = await reader.getLatestBlockNumber();
406
+ const number = latest - BigInt(confirmations - 1);
407
+ if (number < protocol.deployment.protocol.fromBlock)
408
+ throw new Error("Insufficient confirmed blocks");
409
+ const rpc = options.protocolRpc.withReader(reader);
410
+ const status = await rpc.getStatus(protocol.context);
411
+ if (!status.indexedHead) throw new Error("Protocol RPC has no indexed blocks");
412
+ const indexedNumber = status.indexedHead.number < number ? status.indexedHead.number : number;
413
+ if (indexedNumber < protocol.deployment.protocol.fromBlock)
414
+ throw new Error("Protocol RPC has not reached the deployment");
415
+ const checkedAt = await reader.getBlock(indexedNumber);
416
+ if (checkedAt.number !== indexedNumber)
417
+ throw new Error("Payment note confirmation block mismatch");
418
+ const result = await rpc.getNote(protocol.context, data.commitment, checkedAt);
419
+ if (!result) throw new Error("Payment note is not indexed at the requested confirmed block");
420
+ const { output, membership } = result;
421
+ if (output.commitment !== data.commitment || getAddress3(output.tokenAddress) !== getAddress3(data.tokenAddress) || membership.leaf !== data.commitment || membership.treeId !== output.treeId || membership.leafIndex !== output.leafIndex || membership.block.number > checkedAt.number || output.block.number > checkedAt.number || membership.path.length !== TREE_DEPTH || !Number.isSafeInteger(output.leafIndex) || output.leafIndex < 0 || output.leafIndex >= 2 ** TREE_DEPTH)
422
+ throw new Error("Payment note membership metadata mismatch");
423
+ let root = BigInt(data.commitment);
424
+ for (let level = 0; level < membership.path.length; level++) {
425
+ const sibling = membership.path[level];
426
+ root = BigInt(
427
+ await poseidonHash(output.leafIndex >> level & 1 ? [sibling, root] : [root, sibling])
428
+ );
978
429
  }
979
- static fromKey(key) {
980
- if (key.type !== "secret" || key.algorithm.name !== "AES-GCM" || key.algorithm.length !== 256 || !key.usages.includes("encrypt") || !key.usages.includes("decrypt"))
981
- throw new Error("wallet record key must support AES-256-GCM encryption and decryption");
982
- return new _EncryptedWalletCodec(key);
430
+ if (root !== BigInt(membership.root) || !await reader.isKnownRoot(protocol.context, membership.treeId, membership.root, checkedAt))
431
+ throw new Error("Payment note membership root is not accepted on chain");
432
+ if (!sameBlock(output.block, await reader.getBlock(output.block.number)) || !sameBlock(membership.block, await reader.getBlock(membership.block.number)))
433
+ throw new Error("Payment note block is no longer canonical");
434
+ let spendabilityCheckedAt;
435
+ if (wallet) {
436
+ const current = await reader.getBlock(await reader.getLatestBlockNumber());
437
+ const nullifier = await computeNoteNullifier(data.chainId, data.nonce, data.commitment);
438
+ const spent = await reader.isSpent(protocol.context, [nullifier], current);
439
+ if (spent.length !== 1 || spent[0] !== false)
440
+ throw new Error("Payment note is already spent");
441
+ if (!await reader.isKnownRoot(
442
+ protocol.context,
443
+ membership.treeId,
444
+ membership.root,
445
+ current
446
+ ))
447
+ throw new Error("Payment note membership root is no longer spendable");
448
+ if (!sameBlock(current, await reader.getBlock(current.number)))
449
+ throw new Error("Spendability block reorganized");
450
+ spendabilityCheckedAt = current;
451
+ }
452
+ if (!sameBlock(checkedAt, await reader.getBlock(checkedAt.number)))
453
+ throw new Error("Payment note verification block reorganized");
454
+ return {
455
+ input: {
456
+ note: asNote(data),
457
+ commitment: data.commitment,
458
+ treeId: output.treeId,
459
+ leafIndex: output.leafIndex
460
+ },
461
+ membership,
462
+ checkedAt,
463
+ ...spendabilityCheckedAt ? { spendabilityCheckedAt } : {}
464
+ };
465
+ }
466
+
467
+ // src/payment-note/PaymentNote.ts
468
+ var verification = /* @__PURE__ */ Symbol("PaymentNote verification");
469
+ var VerifiedPaymentNote = class {
470
+ #membership;
471
+ #checkedAt;
472
+ constructor(token, membership, checkedAt) {
473
+ if (token !== verification) throw new Error("Payment note verification required");
474
+ this.#membership = structuredClone(membership);
475
+ this.#checkedAt = structuredClone(checkedAt);
476
+ }
477
+ get membership() {
478
+ return structuredClone(this.#membership);
479
+ }
480
+ get checkedAt() {
481
+ return structuredClone(this.#checkedAt);
983
482
  }
984
- static async fromBytes(key) {
985
- if (key.length !== 32) throw new Error("wallet record encryption requires 32 key bytes");
986
- const copy = Uint8Array.from(key);
987
- try {
988
- return _EncryptedWalletCodec.fromKey(
989
- await crypto.subtle.importKey("raw", copy, { name: "AES-GCM" }, false, [
990
- "encrypt",
991
- "decrypt"
992
- ])
993
- );
994
- } finally {
995
- copy.fill(0);
996
- }
483
+ };
484
+ var SpendablePaymentNote = class extends VerifiedPaymentNote {
485
+ #input;
486
+ #spendabilityCheckedAt;
487
+ constructor(token, input, membership, checkedAt, spendabilityCheckedAt) {
488
+ super(token, membership, checkedAt);
489
+ this.#input = structuredClone(input);
490
+ this.#spendabilityCheckedAt = structuredClone(spendabilityCheckedAt);
491
+ Object.freeze(this);
997
492
  }
998
- aad(context) {
999
- if (!Number.isSafeInteger(context.revision) || context.revision < 1)
1000
- throw new Error("invalid encrypted record revision");
1001
- return new TextEncoder().encode(
1002
- `Invisi.WalletRecord.v1\0${context.revision}\0${context.key}`
1003
- );
493
+ get spendabilityCheckedAt() {
494
+ return structuredClone(this.#spendabilityCheckedAt);
1004
495
  }
1005
- async encode(state, context) {
1006
- const iv = crypto.getRandomValues(new Uint8Array(12)), plain = Uint8Array.from(serializeState(state));
1007
- try {
1008
- const encrypted = new Uint8Array(
1009
- await crypto.subtle.encrypt(
1010
- { name: "AES-GCM", iv, additionalData: this.aad(context) },
1011
- this.#key,
1012
- plain
1013
- )
1014
- );
1015
- const out = new Uint8Array(13 + encrypted.length);
1016
- out[0] = 1;
1017
- out.set(iv, 1);
1018
- out.set(encrypted, 13);
1019
- return out;
1020
- } finally {
1021
- plain.fill(0);
1022
- }
496
+ toInput() {
497
+ return structuredClone(this.#input);
1023
498
  }
1024
- async decode(data, context) {
1025
- if (data.length < 29 || data[0] !== 1)
1026
- throw new Error("wallet record authentication failed");
1027
- let plain;
1028
- try {
1029
- plain = new Uint8Array(
1030
- await crypto.subtle.decrypt(
1031
- {
1032
- name: "AES-GCM",
1033
- iv: Uint8Array.from(data.subarray(1, 13)),
1034
- additionalData: this.aad(context)
1035
- },
1036
- this.#key,
1037
- Uint8Array.from(data.subarray(13))
1038
- )
1039
- );
1040
- } catch {
1041
- throw new Error("wallet record authentication failed");
1042
- }
1043
- try {
1044
- return deserializeState(plain);
1045
- } finally {
1046
- plain.fill(0);
1047
- }
499
+ };
500
+ var PaymentNote = class _PaymentNote {
501
+ chainId;
502
+ protocolAddress;
503
+ tokenAddress;
504
+ amount;
505
+ ownerAddress;
506
+ nonce;
507
+ commitment;
508
+ ipn;
509
+ constructor(data, ipn) {
510
+ this.chainId = data.chainId;
511
+ this.protocolAddress = data.protocolAddress;
512
+ this.tokenAddress = data.tokenAddress;
513
+ this.amount = data.amount;
514
+ this.ownerAddress = data.ownerAddress;
515
+ this.nonce = data.nonce;
516
+ this.commitment = data.commitment;
517
+ this.ipn = ipn;
518
+ Object.freeze(this);
1048
519
  }
1049
- toJSON() {
1050
- return { walletRecordCodec: "AES-256-GCM" };
520
+ static async from(ipn) {
521
+ const data = decodePaymentNote(ipn);
522
+ const identity = await computeNoteIdentity(BigInt(data.ownerAddress), data.nonce);
523
+ const commitment = await computeNoteCommitment(
524
+ data.chainId,
525
+ identity,
526
+ BigInt(data.tokenAddress),
527
+ data.amount
528
+ );
529
+ if (commitment !== data.commitment) throw new Error("Payment note commitment mismatch");
530
+ return new _PaymentNote(data, encodePaymentNote(data));
531
+ }
532
+ async verify(protocol, options = {}) {
533
+ const result = await verifyPaymentNote(
534
+ this.data(),
535
+ protocol,
536
+ verificationOptions(protocol, options)
537
+ );
538
+ const verified = new VerifiedPaymentNote(verification, result.membership, result.checkedAt);
539
+ Object.freeze(verified);
540
+ return verified;
541
+ }
542
+ async verifySpendable(protocol, wallet, options = {}) {
543
+ if (!(wallet instanceof Wallet))
544
+ throw new Error("Wallet spending authorization capability required");
545
+ const result = await verifyPaymentNote(
546
+ this.data(),
547
+ protocol,
548
+ verificationOptions(protocol, options),
549
+ wallet
550
+ );
551
+ return new SpendablePaymentNote(
552
+ verification,
553
+ result.input,
554
+ result.membership,
555
+ result.checkedAt,
556
+ result.spendabilityCheckedAt
557
+ );
1051
558
  }
1052
- [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
1053
- return this.toJSON();
559
+ data() {
560
+ const { chainId, protocolAddress, tokenAddress, amount, ownerAddress, nonce, commitment } = this;
561
+ return { chainId, protocolAddress, tokenAddress, amount, ownerAddress, nonce, commitment };
1054
562
  }
1055
563
  };
1056
564
  export {
@@ -1061,25 +569,26 @@ export {
1061
569
  Detector,
1062
570
  Disclosure,
1063
571
  DisclosureVerifier,
1064
- EncryptedWalletCodec,
1065
572
  Invisi,
1066
573
  InvisiError,
1067
574
  MAX_MEMO_BYTES,
1068
575
  MAX_UPDATE_BLOCKS,
1069
- MissingCapabilityError3 as MissingCapabilityError,
576
+ MissingCapabilityError,
1070
577
  NoteOutput,
1071
578
  NullifierCapability,
1072
- Operation,
579
+ PaymentNote,
1073
580
  PreparedTransaction,
1074
581
  Protocol,
1075
- ProtocolRpcClient2 as ProtocolRpcClient,
582
+ ProtocolRpcClient,
1076
583
  ProtocolRpcError,
1077
- PublicWallet2 as PublicWallet,
584
+ PublicWallet,
1078
585
  RegistrationOperation,
586
+ SpendablePaymentNote,
587
+ TransactionOutput,
588
+ VerifiedPaymentNote,
1079
589
  ViewingCapability,
1080
- Wallet,
590
+ Wallet2 as Wallet,
1081
591
  WalletAuthorization,
1082
- WalletSession,
1083
592
  broadcasterQuoteDomain,
1084
593
  broadcasterQuoteTypes,
1085
594
  broadcasterRequestHash,
@@ -1090,12 +599,15 @@ export {
1090
599
  decodeBroadcasterIntent,
1091
600
  decodeDeliveryHeader,
1092
601
  decodeMemoText,
602
+ decodePaymentNote,
603
+ decodePaymentRequest,
1093
604
  decryptCandidates,
1094
605
  defineGasBenchmarks,
1095
606
  encodeMemo,
607
+ encodePaymentNote,
608
+ encodePaymentRequest,
1096
609
  fundsV1,
1097
610
  readProtocolLogs,
1098
- verifyBroadcasterQuote,
1099
- walletBalance
611
+ verifyBroadcasterQuote
1100
612
  };
1101
613
  //# sourceMappingURL=index.js.map