@invisi-labs/sdk 1.1.4 → 1.1.6

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 (45) hide show
  1. package/dist/{operation-types-DIazgS2d.d.cts → NoteOutput-DRkVnwlx.d.cts} +140 -17
  2. package/dist/{operation-types-D7I405FX.d.ts → NoteOutput-DpDC6T2O.d.ts} +140 -17
  3. package/dist/broadcaster-preparation-QVL62KM6.js +128 -0
  4. package/dist/broadcaster-preparation-QVL62KM6.js.map +1 -0
  5. package/dist/{chunk-BE52B32Z.js → chunk-4SD3MW6N.js} +846 -1625
  6. package/dist/chunk-4SD3MW6N.js.map +1 -0
  7. package/dist/chunk-FL4TIGWW.js +1781 -0
  8. package/dist/chunk-FL4TIGWW.js.map +1 -0
  9. package/dist/{chunk-4HMM7ZKY.js → chunk-QC6TQR3R.js} +8 -8
  10. package/dist/chunk-QC6TQR3R.js.map +1 -0
  11. package/dist/ethers.cjs +7 -7
  12. package/dist/ethers.cjs.map +1 -1
  13. package/dist/ethers.d.cts +3 -3
  14. package/dist/ethers.d.ts +3 -3
  15. package/dist/ethers.js +1 -1
  16. package/dist/index.cjs +4520 -2902
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +273 -25
  19. package/dist/index.d.ts +273 -25
  20. package/dist/index.js +459 -52
  21. package/dist/index.js.map +1 -1
  22. package/dist/indexeddb.cjs +5812 -4570
  23. package/dist/indexeddb.cjs.map +1 -1
  24. package/dist/indexeddb.d.cts +2 -2
  25. package/dist/indexeddb.d.ts +2 -2
  26. package/dist/indexeddb.js +3 -2
  27. package/dist/indexeddb.js.map +1 -1
  28. package/dist/memory.cjs +5868 -4626
  29. package/dist/memory.cjs.map +1 -1
  30. package/dist/memory.d.cts +3 -3
  31. package/dist/memory.d.ts +3 -3
  32. package/dist/memory.js +3 -2
  33. package/dist/sqlite.cjs +5812 -4570
  34. package/dist/sqlite.cjs.map +1 -1
  35. package/dist/sqlite.d.cts +2 -2
  36. package/dist/sqlite.d.ts +2 -2
  37. package/dist/sqlite.js +3 -2
  38. package/dist/sqlite.js.map +1 -1
  39. package/dist/{state-AUnz7TG7.d.cts → state-C4HZFotm.d.cts} +1 -1
  40. package/dist/{state-AUnz7TG7.d.ts → state-C4HZFotm.d.ts} +1 -1
  41. package/dist/{store-BMN8kd0c.d.ts → store-8aBhnbGr.d.ts} +1 -1
  42. package/dist/{store-Cz75STRl.d.cts → store-YrbXb6CY.d.cts} +1 -1
  43. package/package.json +5 -5
  44. package/dist/chunk-4HMM7ZKY.js.map +0 -1
  45. package/dist/chunk-BE52B32Z.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,8 +1,6 @@
1
1
  import {
2
2
  DisclosureVerifier,
3
- InvisiError,
4
3
  Operation,
5
- TransactionPreparation,
6
4
  WalletSession,
7
5
  acceptFundingSignature,
8
6
  acceptWalletSignature,
@@ -10,27 +8,33 @@ import {
10
8
  decryptCandidates,
11
9
  defineGasBenchmarks,
12
10
  deserializeState,
13
- encodeOperation,
14
11
  estimateGasReference,
15
12
  fundingSigningRequest,
16
13
  operationPreview,
17
- positiveAmount,
18
- prepareWithErrors,
19
14
  proveOperation,
20
- publicProof,
21
15
  readProtocolLogs,
22
- requiresAuthorization,
23
16
  sameStatement,
24
17
  serializeState,
25
- statement,
26
- walletBalance,
27
18
  walletSigningRequest
28
- } from "./chunk-BE52B32Z.js";
19
+ } from "./chunk-4SD3MW6N.js";
20
+ import {
21
+ InvisiError,
22
+ 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";
29
34
  import {
30
- EthersAddressResolver,
31
35
  EthersChainReader,
32
36
  EthersFundingVerifier
33
- } from "./chunk-4HMM7ZKY.js";
37
+ } from "./chunk-QC6TQR3R.js";
34
38
 
35
39
  // src/explicit-preparation.ts
36
40
  import { getAddress } from "ethers";
@@ -63,6 +67,29 @@ var PreparedTransaction = class _PreparedTransaction {
63
67
  { generation: 0, busy: false }
64
68
  );
65
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
+ }
66
93
  get id() {
67
94
  return this.#saved.plan.id;
68
95
  }
@@ -79,6 +106,15 @@ var PreparedTransaction = class _PreparedTransaction {
79
106
  preview() {
80
107
  return operationPreview(this.#saved.plan, this.status);
81
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
+ }
82
118
  current() {
83
119
  check(
84
120
  !this.#lifecycle.busy && this.#lifecycle.generation === this.#saved.generation,
@@ -178,6 +214,10 @@ var PreparedTransaction = class _PreparedTransaction {
178
214
  }
179
215
  async prove(backendOrOptions = {}, suppliedOptions = {}) {
180
216
  const options = { ..."prove" in backendOrOptions ? suppliedOptions : backendOrOptions };
217
+ check(
218
+ !this.#saved.broadcasterQuote || options.execution !== "aggregate",
219
+ "a broadcaster quote requires direct execution"
220
+ );
181
221
  const plan = this.#saved.plan;
182
222
  check(plan.kind !== "shield", "shield requires no proof");
183
223
  if (requiresAuthorization(plan)) {
@@ -287,7 +327,7 @@ function explicitPreparation(options, data, tokenAddress) {
287
327
  position(input);
288
328
  positiveAmount(input.note.amount);
289
329
  if (inventory.notes[input.commitment]) throw new Error("duplicate explicit note input");
290
- if (input.note.chainId !== context.chainId || input.note.protocolAddress !== BigInt(context.protocolAddress) || input.note.walletIdentity !== options.wallet.walletIdentity)
330
+ if (input.note.chainId !== context.chainId || input.note.protocolAddress !== BigInt(context.protocolAddress) || input.note.ownerAddress !== options.wallet.ownerAddress)
291
331
  throw new Error("explicit note identity or deployment mismatch");
292
332
  inventory.notes[input.commitment] = { ...input, spent: "unspent" };
293
333
  }
@@ -302,7 +342,7 @@ function explicitPreparation(options, data, tokenAddress) {
302
342
  const scope = {
303
343
  ...context,
304
344
  namespace: "explicit",
305
- walletIdentity: options.wallet.walletIdentity
345
+ ownerAddress: options.wallet.ownerAddress
306
346
  };
307
347
  const {
308
348
  store: _store,
@@ -320,18 +360,30 @@ function explicitPreparation(options, data, tokenAddress) {
320
360
  reader: options.chainReader,
321
361
  scope
322
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
+ );
323
375
  return new TransactionPreparation({
324
376
  ...runtime,
325
377
  explicitInputs: true,
326
378
  ...data.changeAddress ? { changeAddress: data.changeAddress } : {},
327
379
  readInventory: async () => structuredClone(inventory),
328
- complete: async (plan) => PreparedTransaction.prepare(runtime, plan)
380
+ complete: async (plan) => PreparedTransaction.prepare({ ...runtime, reprepare }, plan)
329
381
  });
330
382
  }
331
383
 
332
384
  // src/Invisi.ts
333
385
  import { ProtocolRpcClient } from "@invisi-labs/rpc";
334
- import { MissingCapabilityError as MissingCapabilityError2, PublicWallet, ReceivingAddress } from "@invisi-labs/core";
386
+ import { MissingCapabilityError as MissingCapabilityError2, PublicWallet } from "@invisi-labs/core";
335
387
 
336
388
  // src/AddressRegistry.ts
337
389
  import { AddressRegistry as Registry } from "@invisi-labs/protocol";
@@ -354,6 +406,10 @@ var AddressRegistry = class extends Registry {
354
406
  var Invisi = class {
355
407
  #options;
356
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
+ );
357
413
  if ("ciphertextCodecs" in options || "envelopeVersion" in options) {
358
414
  throw new Error(
359
415
  "SDK envelope versions are fixed; encode application payload bytes externally"
@@ -391,10 +447,7 @@ var Invisi = class {
391
447
  }
392
448
  const selected = { ...options };
393
449
  const combined = { ...this.#options, ...selected };
394
- const { provider, addressLookup = "protocol-rpc", ...runtime } = combined;
395
- if (addressLookup !== void 0 && addressLookup !== "chain" && addressLookup !== "protocol-rpc") {
396
- throw new Error("Invalid recipient address lookup source");
397
- }
450
+ const { provider, ...runtime } = combined;
398
451
  const reader = combined.chainReader ?? (provider ? new EthersChainReader(provider) : void 0);
399
452
  if (!reader) throw new MissingCapabilityError2("chain reader");
400
453
  if (provider && (await provider.getNetwork()).chainId !== selected.protocol.context.chainId)
@@ -403,41 +456,14 @@ var Invisi = class {
403
456
  if (runtime.protocolRpc instanceof ProtocolRpcClient) {
404
457
  runtime.protocolRpc = runtime.protocolRpc.withReader(reader);
405
458
  }
406
- const registry = selected.protocol.deployment.registry;
407
459
  const fundingVerifier = combined.fundingVerifier ?? (provider ? new EthersFundingVerifier(provider, selected.protocol.context.chainId) : void 0);
408
- let addressResolver = combined.addressResolver;
409
- if (!addressResolver && registry) {
410
- if (addressLookup === "protocol-rpc") {
411
- const rpc = runtime.protocolRpc;
412
- addressResolver = async (identity) => {
413
- if (!(rpc instanceof ProtocolRpcClient)) {
414
- throw new MissingCapabilityError2("Protocol RPC recipient discovery");
415
- }
416
- const discovered = await rpc.getDeployment(selected.protocol.context.chainId);
417
- const expected = selected.protocol.deployment;
418
- if (discovered.protocol.address !== expected.protocol.address || discovered.protocol.runtimeCodeHash !== expected.protocol.runtimeCodeHash || discovered.protocol.fromBlock !== expected.protocol.fromBlock || discovered.registry?.address !== registry.address || discovered.registry.runtimeCodeHash !== registry.runtimeCodeHash || discovered.registry.fromBlock !== registry.fromBlock)
419
- throw new Error("Recipient registry deployment pins mismatch");
420
- const registration = await rpc.getReceivingAddress(identity);
421
- return registration ? ReceivingAddress.parse(registration.receivingAddress).getReceivingKeys() : null;
422
- };
423
- } else if (provider) {
424
- addressResolver = new EthersAddressResolver(
425
- provider,
426
- {
427
- chainId: selected.protocol.context.chainId,
428
- registryAddress: registry.address
429
- },
430
- registry.runtimeCodeHash
431
- ).lookup;
432
- }
433
- }
434
- return {
460
+ return configureRecipientInfrastructure({
435
461
  ...runtime,
462
+ ...provider ? { provider } : {},
436
463
  chainReader: reader,
437
464
  ...fundingVerifier ? { fundingVerifier } : {},
438
- ...addressResolver ? { addressResolver } : {},
439
465
  protocol: selected.protocol
440
- };
466
+ });
441
467
  }
442
468
  /** Discover the selected chain's registry and prepare an independent signing request. */
443
469
  async prepareRegistration(options) {
@@ -563,8 +589,272 @@ import {
563
589
  computeCreditNullifier,
564
590
  creditPeriods
565
591
  } from "@invisi-labs/core";
566
- import { Protocol, RegistrationOperation } from "@invisi-labs/protocol";
567
- import { ProtocolRpcError } from "@invisi-labs/rpc";
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
+ );
659
+ }
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;
676
+ }
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
+
804
+ // src/Protocol.ts
805
+ var sources = /* @__PURE__ */ new WeakMap();
806
+ var Protocol = class _Protocol extends BaseProtocol {
807
+ static async fromRpc(rpc, provider, options = {}) {
808
+ const verified = await BaseProtocol.fromRpc(rpc, provider, options);
809
+ const protocol = new _Protocol({ deployment: verified.deployment });
810
+ if (rpc.getBroadcasters)
811
+ sources.set(protocol, rpc);
812
+ return protocol;
813
+ }
814
+ /** Returns enabled services that answer a token fee probe; final quotes are checked by prepared. */
815
+ async getBroadcasters(options) {
816
+ const token = getAddress3(options.tokenAddress);
817
+ if (!this.deployment.broadcasterRegistry)
818
+ throw new Error("broadcaster registry is not configured");
819
+ const rpc = sources.get(this);
820
+ if (!rpc) throw new Error("broadcaster discovery requires Protocol.fromRpc");
821
+ const clients = [];
822
+ let after;
823
+ let block;
824
+ const seen = /* @__PURE__ */ new Set();
825
+ do {
826
+ const page = await rpc.getBroadcasters({
827
+ enabled: true,
828
+ limit: 100,
829
+ ...after ? { after } : {},
830
+ ...block ? { block } : {}
831
+ });
832
+ block ??= page.block;
833
+ const compatible = await Promise.all(
834
+ page.broadcasters.map(async (entry) => {
835
+ if (!entry.enabled) return void 0;
836
+ try {
837
+ const client = new BroadcasterClient(entry);
838
+ await client.fee(token);
839
+ return client;
840
+ } catch {
841
+ return void 0;
842
+ }
843
+ })
844
+ );
845
+ clients.push(
846
+ ...compatible.filter((client) => client !== void 0)
847
+ );
848
+ after = page.next;
849
+ if (after && seen.has(after)) throw new Error("repeated broadcaster pagination cursor");
850
+ if (after) seen.add(after);
851
+ } while (after);
852
+ return clients;
853
+ }
854
+ };
855
+
856
+ // src/index.ts
857
+ import { ProtocolRpcError, MAX_UPDATE_BLOCKS } from "@invisi-labs/rpc";
568
858
 
569
859
  // src/ProtocolRpcClient.ts
570
860
  import { ProtocolRpcClient as RpcClient } from "@invisi-labs/rpc";
@@ -574,6 +864,112 @@ var ProtocolRpcClient2 = class extends RpcClient {
574
864
  }
575
865
  };
576
866
 
867
+ // src/BroadcasterRegistry.ts
868
+ import { getAddress as getAddress4, Interface as Interface2, keccak256 as keccak2562 } from "ethers";
869
+ import { broadcasterRegistryAbi } from "@invisi-labs/protocol/contracts";
870
+ var abi = new Interface2(broadcasterRegistryAbi);
871
+ function address(value) {
872
+ const normalized = getAddress4(value);
873
+ if (BigInt(normalized) === 0n) throw new Error("zero broadcaster address");
874
+ return normalized;
875
+ }
876
+ var BroadcasterRegistry = class {
877
+ address;
878
+ #options;
879
+ constructor(options) {
880
+ this.address = address(options.address);
881
+ const blockRange = options.blockRange ?? 2e3;
882
+ if (options.chainId <= 0n || !Number.isSafeInteger(options.fromBlock) || options.fromBlock < 0 || !Number.isSafeInteger(blockRange) || blockRange < 1 || blockRange > 1e5 || !/^0x[0-9a-fA-F]{64}$/.test(options.runtimeCodeHash))
883
+ throw new Error("invalid broadcaster registry configuration");
884
+ this.#options = Object.freeze({ ...options, blockRange });
885
+ }
886
+ prepareRegistration(registration) {
887
+ const { endpoint, metadataURI = "" } = registration;
888
+ if (!endpoint.startsWith("https://") || endpoint.length <= 8 || endpoint.length > 2048 || /[^\x21-\x7e]/.test(endpoint))
889
+ throw new Error("broadcaster endpoint must be a printable HTTPS URI");
890
+ const url = new URL(endpoint);
891
+ if (!url.hostname || url.username || url.password) {
892
+ throw new Error("broadcaster HTTPS endpoint must not include credentials");
893
+ }
894
+ if (metadataURI.length > 2048 || /[^\x21-\x7e]/.test(metadataURI)) {
895
+ throw new Error("invalid broadcaster metadata URI");
896
+ }
897
+ return this.call("setBroadcaster", [
898
+ endpoint,
899
+ metadataURI,
900
+ address(registration.quoteSigner),
901
+ address(registration.feeRecipient)
902
+ ]);
903
+ }
904
+ prepareAvailability(enabled) {
905
+ return this.call("setEnabled", [enabled]);
906
+ }
907
+ call(name, args) {
908
+ return { to: this.address, data: abi.encodeFunctionData(name, args), value: 0n };
909
+ }
910
+ async validate() {
911
+ const { provider, chainId, runtimeCodeHash } = this.#options;
912
+ if ((await provider.getNetwork()).chainId !== chainId) {
913
+ throw new Error("broadcaster registry chain mismatch");
914
+ }
915
+ if (keccak2562(await provider.getCode(this.address)).toLowerCase() !== runtimeCodeHash.toLowerCase()) {
916
+ throw new Error("broadcaster registry runtime code mismatch");
917
+ }
918
+ }
919
+ async get(operator) {
920
+ await this.validate();
921
+ return this.read(address(operator));
922
+ }
923
+ async read(operator) {
924
+ const result = await this.#options.provider.call(this.call("broadcasterOf", [operator]));
925
+ const [entry] = abi.decodeFunctionResult("broadcasterOf", result);
926
+ return {
927
+ operator,
928
+ endpoint: entry.endpoint,
929
+ metadataURI: entry.metadataURI,
930
+ quoteSigner: address(entry.quoteSigner),
931
+ feeRecipient: address(entry.feeRecipient),
932
+ enabled: entry.enabled
933
+ };
934
+ }
935
+ /** Discover event operators, then read their latest entry; disabled services remain explicit. */
936
+ async discover(options = {}) {
937
+ const { provider, fromBlock, blockRange } = this.#options;
938
+ options.signal?.throwIfAborted();
939
+ await this.validate();
940
+ const toBlock = options.toBlock ?? await provider.getBlockNumber();
941
+ if (!Number.isSafeInteger(toBlock) || toBlock < fromBlock) {
942
+ throw new Error("invalid broadcaster discovery block range");
943
+ }
944
+ const operators = /* @__PURE__ */ new Set();
945
+ for (let start = fromBlock; start <= toBlock; ) {
946
+ options.signal?.throwIfAborted();
947
+ const end = Math.min(start + blockRange - 1, toBlock);
948
+ const logs = await provider.getLogs({
949
+ address: this.address,
950
+ topics: [abi.getEvent("BroadcasterSet").topicHash],
951
+ fromBlock: start,
952
+ toBlock: end
953
+ });
954
+ for (const log of logs) {
955
+ if (getAddress4(log.address) !== this.address)
956
+ throw new Error("registry event address mismatch");
957
+ const event = abi.parseLog(log);
958
+ if (!event || event.name !== "BroadcasterSet")
959
+ throw new Error("invalid registry event");
960
+ operators.add(address(event.args.operator));
961
+ }
962
+ start = end + 1;
963
+ }
964
+ const entries = [];
965
+ for (const operator of operators) {
966
+ options.signal?.throwIfAborted();
967
+ entries.push(await this.read(operator));
968
+ }
969
+ return entries;
970
+ }
971
+ };
972
+
577
973
  // src/integrations/encrypted-codec.ts
578
974
  var EncryptedWalletCodec = class _EncryptedWalletCodec {
579
975
  #key;
@@ -659,6 +1055,9 @@ var EncryptedWalletCodec = class _EncryptedWalletCodec {
659
1055
  };
660
1056
  export {
661
1057
  AddressRegistry,
1058
+ BroadcasterClient,
1059
+ BroadcasterRegistry,
1060
+ BroadcasterRpcError,
662
1061
  Detector,
663
1062
  Disclosure,
664
1063
  DisclosureVerifier,
@@ -666,7 +1065,9 @@ export {
666
1065
  Invisi,
667
1066
  InvisiError,
668
1067
  MAX_MEMO_BYTES,
1068
+ MAX_UPDATE_BLOCKS,
669
1069
  MissingCapabilityError3 as MissingCapabilityError,
1070
+ NoteOutput,
670
1071
  NullifierCapability,
671
1072
  Operation,
672
1073
  PreparedTransaction,
@@ -679,9 +1080,14 @@ export {
679
1080
  Wallet,
680
1081
  WalletAuthorization,
681
1082
  WalletSession,
1083
+ broadcasterQuoteDomain,
1084
+ broadcasterQuoteTypes,
1085
+ broadcasterRequestHash,
1086
+ broadcasterTransactInterface,
682
1087
  computeCreditNullifier,
683
1088
  creditPeriods,
684
1089
  creditV1,
1090
+ decodeBroadcasterIntent,
685
1091
  decodeDeliveryHeader,
686
1092
  decodeMemoText,
687
1093
  decryptCandidates,
@@ -689,6 +1095,7 @@ export {
689
1095
  encodeMemo,
690
1096
  fundsV1,
691
1097
  readProtocolLogs,
1098
+ verifyBroadcasterQuote,
692
1099
  walletBalance
693
1100
  };
694
1101
  //# sourceMappingURL=index.js.map