@invisi-labs/sdk 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -21,33 +21,89 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  AddressRegistry: () => AddressRegistry,
24
- Detector: () => import_core16.Detector,
25
- Disclosure: () => import_core16.Disclosure,
24
+ Detector: () => import_core20.Detector,
25
+ Disclosure: () => import_core20.Disclosure,
26
26
  DisclosureVerifier: () => DisclosureVerifier,
27
27
  EncryptedWalletCodec: () => EncryptedWalletCodec,
28
28
  Invisi: () => Invisi,
29
- MAX_MEMO_BYTES: () => import_core16.MAX_MEMO_BYTES,
30
- MissingCapabilityError: () => import_core16.MissingCapabilityError,
31
- NullifierCapability: () => import_core16.NullifierCapability,
29
+ InvisiError: () => InvisiError,
30
+ MAX_MEMO_BYTES: () => import_core20.MAX_MEMO_BYTES,
31
+ MissingCapabilityError: () => import_core20.MissingCapabilityError,
32
+ NullifierCapability: () => import_core20.NullifierCapability,
32
33
  Operation: () => Operation,
33
34
  PreparedTransaction: () => PreparedTransaction,
34
- Protocol: () => import_protocol10.Protocol,
35
+ Protocol: () => import_protocol11.Protocol,
35
36
  ProtocolRpcClient: () => ProtocolRpcClient2,
36
37
  ProtocolRpcError: () => import_rpc3.ProtocolRpcError,
37
- RegistrationOperation: () => import_protocol10.RegistrationOperation,
38
- ViewingCapability: () => import_core16.ViewingCapability,
39
- Wallet: () => import_core16.Wallet,
40
- WalletAuthorization: () => import_core16.WalletAuthorization,
38
+ PublicWallet: () => import_core20.PublicWallet,
39
+ RegistrationOperation: () => import_protocol11.RegistrationOperation,
40
+ ViewingCapability: () => import_core20.ViewingCapability,
41
+ Wallet: () => import_core20.Wallet,
42
+ WalletAuthorization: () => import_core20.WalletAuthorization,
41
43
  WalletSession: () => WalletSession,
42
- decodeMemoText: () => import_core16.decodeMemoText,
44
+ computeCreditNullifier: () => import_core20.computeCreditNullifier,
45
+ creditPeriods: () => import_core20.creditPeriods,
46
+ creditV1: () => import_core20.creditV1,
47
+ decodeDeliveryHeader: () => import_core20.decodeDeliveryHeader,
48
+ decodeMemoText: () => import_core20.decodeMemoText,
49
+ decryptCandidates: () => decryptCandidates,
43
50
  defineGasBenchmarks: () => defineGasBenchmarks,
44
- encodeMemo: () => import_core16.encodeMemo,
51
+ encodeMemo: () => import_core20.encodeMemo,
52
+ fundsV1: () => import_core20.fundsV1,
53
+ readProtocolLogs: () => readProtocolLogs,
45
54
  walletBalance: () => walletBalance
46
55
  });
47
56
  module.exports = __toCommonJS(src_exports);
48
57
 
49
- // src/session/operation-protocol.ts
58
+ // src/errors.ts
50
59
  var import_core = require("@invisi-labs/core");
60
+ var InvisiError = class extends Error {
61
+ code;
62
+ operation;
63
+ address;
64
+ outputIndex;
65
+ field;
66
+ capability;
67
+ constructor(code, message, details = {}) {
68
+ super(message, { cause: details.cause });
69
+ this.name = "InvisiError";
70
+ this.code = code;
71
+ this.operation = details.operation;
72
+ this.address = details.address;
73
+ this.outputIndex = details.outputIndex;
74
+ this.field = details.field;
75
+ this.capability = details.capability;
76
+ }
77
+ };
78
+ async function prepareWithErrors(operation, prepare) {
79
+ try {
80
+ return await prepare();
81
+ } catch (cause) {
82
+ if (cause instanceof InvisiError) {
83
+ if (cause.operation) throw cause;
84
+ throw new InvisiError(cause.code, cause.message, {
85
+ operation,
86
+ ...cause.address === void 0 ? {} : { address: cause.address },
87
+ ...cause.outputIndex === void 0 ? {} : { outputIndex: cause.outputIndex },
88
+ ...cause.field === void 0 ? {} : { field: cause.field },
89
+ ...cause.capability === void 0 ? {} : { capability: cause.capability },
90
+ cause: cause.cause
91
+ });
92
+ }
93
+ throw new InvisiError(
94
+ cause instanceof import_core.MissingCapabilityError ? "MISSING_CAPABILITY" : "PREPARATION_FAILED",
95
+ cause instanceof Error ? cause.message : "Transaction preparation failed",
96
+ {
97
+ operation,
98
+ cause,
99
+ ...cause instanceof import_core.MissingCapabilityError ? { capability: cause.capability } : {}
100
+ }
101
+ );
102
+ }
103
+ }
104
+
105
+ // src/session/operation-protocol.ts
106
+ var import_core2 = require("@invisi-labs/core");
51
107
  var import_protocol = require("@invisi-labs/protocol");
52
108
 
53
109
  // src/session/state.ts
@@ -106,11 +162,8 @@ function walletBalances(state) {
106
162
 
107
163
  // src/session/operation-protocol.ts
108
164
  function requiresAuthorization(plan) {
109
- if (plan.kind === "sweep") {
110
- 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))
111
- throw new Error("invalid owner-only sweep plan");
112
- return false;
113
- }
165
+ const ownerOnlyFullWithdrawal = !plan.credit && plan.inputs.length > 0 && plan.outputs.length === 0 && plan.params !== void 0 && plan.params.outputCommitments.length === 0 && (plan.funding.amount ?? 0n) === 0n && plan.fundingMode === "none" && BigInt(plan.params.withdrawalAddress) === BigInt(plan.scope.walletIdentity) && plan.params.withdrawalAmount === plan.inputs.reduce((sum, input) => sum + input.note.amount, 0n) && plan.inputs.every((input) => input.note.walletIdentity === plan.scope.walletIdentity);
166
+ if (ownerOnlyFullWithdrawal) return false;
114
167
  return !!plan.credit || plan.inputs.length > 0;
115
168
  }
116
169
  function inputFacts(plan) {
@@ -374,7 +427,7 @@ function operationExpired(plan, now) {
374
427
  if (credit?.creation && now >= credit.creation.expiry) return true;
375
428
  if (credit?.draw) {
376
429
  if (credit.draw.expiries.some((expiry) => now >= expiry)) return true;
377
- const prepared = (0, import_core.creditPeriods)(plan.preparedAt.timestamp), current2 = (0, import_core.creditPeriods)(now);
430
+ const prepared = (0, import_core2.creditPeriods)(plan.preparedAt.timestamp), current2 = (0, import_core2.creditPeriods)(now);
378
431
  if (prepared.currentDay !== current2.currentDay || prepared.currentMonth !== current2.currentMonth)
379
432
  return true;
380
433
  }
@@ -1876,7 +1929,8 @@ var import_ethers6 = require("ethers");
1876
1929
 
1877
1930
  // src/session/operation-execution.ts
1878
1931
  var import_ethers2 = require("ethers");
1879
- var import_core2 = require("@invisi-labs/core");
1932
+ var import_core3 = require("@invisi-labs/core");
1933
+ var import_protocol2 = require("@invisi-labs/protocol");
1880
1934
 
1881
1935
  // src/session/proof-integrity.ts
1882
1936
  var import_ethers = require("ethers");
@@ -1905,23 +1959,41 @@ async function authorizeOperation(plan, provider) {
1905
1959
  if (provider.walletIdentity !== plan.scope.walletIdentity)
1906
1960
  throw new Error("wallet authorization capability mismatch");
1907
1961
  const request = authorizationRequest(plan);
1908
- const digest = (0, import_core2.computeWalletAuthorizationDigest)(request);
1962
+ const digest = (0, import_core3.computeWalletAuthorizationDigest)(request);
1909
1963
  const signed = await provider.authorize(structuredClone(request));
1910
1964
  if (signed.walletIdentity !== plan.scope.walletIdentity || signed.digest !== digest)
1911
1965
  throw new Error("wallet authorization statement mismatch");
1912
1966
  return signed;
1913
1967
  }
1914
1968
  async function proveOperation(protocol, plan, authorization, prover, options) {
1969
+ if (Object.hasOwn(options, "target")) {
1970
+ throw new Error("prove accepts execution, not a backend target");
1971
+ }
1972
+ const { execution = "single", ...proverOptions } = options;
1973
+ if (execution !== "single" && execution !== "aggregate") {
1974
+ throw new Error("execution must be single or aggregate");
1975
+ }
1976
+ const frozen = statement(plan);
1977
+ if (execution === "aggregate" && (plan.kind !== "transfer" || plan.capacity !== 14)) {
1978
+ throw new Error("aggregate execution requires a 14-slot transfer");
1979
+ }
1980
+ if (plan.kind === "transfer") {
1981
+ plan.statement = import_protocol2.WitnessBuilder.statement(
1982
+ frozen.circuitName,
1983
+ frozen.expectedPublicInputs,
1984
+ execution === "aggregate" ? "noir-recursive" : "evm"
1985
+ );
1986
+ }
1915
1987
  const request = await witnessFor(protocol, plan, authorization);
1916
1988
  if (JSON.stringify({ ...request, witness: void 0 }) !== JSON.stringify(plan.statement))
1917
1989
  throw new Error("prepared proof statement changed");
1918
1990
  try {
1919
- const proof = publicProof(await prover.prove(request, options));
1991
+ const proof = publicProof(await prover.prove(request, proverOptions));
1920
1992
  sameStatement(proof, plan.statement);
1921
1993
  if (await prover.verify(
1922
1994
  structuredClone(proof),
1923
1995
  structuredClone(plan.statement),
1924
- options
1996
+ proverOptions
1925
1997
  ) !== true)
1926
1998
  throw new Error("proof verification failed");
1927
1999
  return proof;
@@ -1936,25 +2008,94 @@ async function signOperationFunding(saved, signer, verifier) {
1936
2008
  throw new Error("funding signer is not the payer");
1937
2009
  const typed = await fundingTypedData(saved);
1938
2010
  const signature = await signer.signTypedData(structuredClone(typed));
2011
+ return verifyFundingSignature(typed, payer, signature, verifier);
2012
+ }
2013
+ function walletSigningRequest(plan) {
2014
+ return (0, import_core3.createSigningRequest)((0, import_core3.walletAuthorizationTypedData)(authorizationRequest(plan)));
2015
+ }
2016
+ async function acceptWalletSignature(plan, signature) {
2017
+ const request = authorizationRequest(plan);
2018
+ const digest = (0, import_core3.computeWalletAuthorizationDigest)(request);
2019
+ try {
2020
+ const signed = import_ethers2.Signature.from(signature);
2021
+ const publicKey = (0, import_ethers2.getBytes)(import_ethers2.SigningKey.recoverPublicKey(digest, signed));
2022
+ const owner = await (0, import_core3.computeWalletIdentity)(publicKey);
2023
+ if (owner.toLowerCase() !== plan.scope.walletIdentity.toLowerCase()) {
2024
+ throw new Error("wallet signature identity mismatch");
2025
+ }
2026
+ return new import_core3.AuthorizedStatement(plan.scope.walletIdentity, digest, {
2027
+ public_key_x: Array.from(publicKey.slice(1, 33)),
2028
+ public_key_y: Array.from(publicKey.slice(33)),
2029
+ signature: Array.from((0, import_ethers2.getBytes)((0, import_ethers2.concat)([signed.r, signed.s])))
2030
+ });
2031
+ } catch {
2032
+ throw new Error("invalid wallet authorization signature");
2033
+ }
2034
+ }
2035
+ async function fundingSigningRequest(saved, runtime) {
2036
+ const plan = saved.plan;
2037
+ if (plan.fundingMode !== "signature") {
2038
+ throw new Error("operation does not require a funding signature");
2039
+ }
2040
+ if (plan.kind !== "shield" && !saved.proof) {
2041
+ throw new Error("proof must precede funding authorization");
2042
+ }
2043
+ await runtime.options.protocol.validateDeployment(runtime.reader);
2044
+ const block = await runtime.reader.getBlock(await runtime.reader.getLatestBlockNumber());
2045
+ if (block.timestamp > plan.deadline || block.timestamp > (plan.funding.deadline ?? 0n)) {
2046
+ throw new Error("funding authorization expired");
2047
+ }
2048
+ if (await runtime.reader.getFundingNonce(plan.context, plan.funding.payer, block) !== plan.funding.sigNonce) {
2049
+ throw new Error("funding nonce changed; prepare again");
2050
+ }
2051
+ const request = (0, import_core3.createSigningRequest)(await fundingTypedData(saved));
2052
+ if ((await runtime.reader.getBlock(block.number)).hash !== block.hash) {
2053
+ throw new Error("canonical funding block changed");
2054
+ }
2055
+ return request;
2056
+ }
2057
+ async function acceptFundingSignature(saved, signature, runtime) {
2058
+ if (!/^0x(?:[a-fA-F0-9]{2})+$/.test(signature)) {
2059
+ throw new Error("invalid EVM funding signature");
2060
+ }
2061
+ const request = await fundingSigningRequest(saved, runtime);
2062
+ return verifyFundingSignature(
2063
+ request,
2064
+ saved.plan.funding.payer,
2065
+ signature,
2066
+ runtime.options.fundingVerifier
2067
+ );
2068
+ }
2069
+ async function verifyFundingSignature(request, payer, signature, verifier) {
2070
+ if (!/^0x(?:[a-fA-F0-9]{2})+$/.test(signature)) {
2071
+ throw new Error("invalid EVM funding signature");
2072
+ }
2073
+ const types = Object.fromEntries(
2074
+ Object.entries(request.types).map(([name, fields]) => [name, [...fields]])
2075
+ );
2076
+ let validEOA = false;
2077
+ try {
2078
+ validEOA = (0, import_ethers2.verifyTypedData)(request.domain, types, request.message, signature).toLowerCase() === payer.toLowerCase();
2079
+ if (validEOA) {
2080
+ signature = import_ethers2.Signature.from(signature).serialized;
2081
+ }
2082
+ } catch {
2083
+ validEOA = false;
2084
+ }
1939
2085
  const valid = verifier ? await verifier.verifyTypedData({
1940
- ...structuredClone(typed),
2086
+ ...structuredClone(request),
1941
2087
  address: payer,
1942
2088
  signature
1943
- }) : (0, import_ethers2.verifyTypedData)(
1944
- typed.domain,
1945
- Object.fromEntries(
1946
- Object.entries(typed.types).map(([name, fields]) => [name, [...fields]])
1947
- ),
1948
- typed.message,
1949
- signature
1950
- ).toLowerCase() === payer.toLowerCase();
1951
- if (valid !== true) throw new Error("invalid EVM funding signature");
2089
+ }) : validEOA;
2090
+ if (valid !== true) {
2091
+ throw new Error("invalid EVM funding signature");
2092
+ }
1952
2093
  return signature;
1953
2094
  }
1954
2095
 
1955
2096
  // src/PreparedTransaction.ts
1956
- var import_core3 = require("@invisi-labs/core");
1957
- var import_protocol2 = require("@invisi-labs/protocol");
2097
+ var import_core4 = require("@invisi-labs/core");
2098
+ var import_protocol3 = require("@invisi-labs/protocol");
1958
2099
 
1959
2100
  // src/session/operation-preview.ts
1960
2101
  function operationPreview(plan, status) {
@@ -2062,30 +2203,77 @@ var PreparedTransaction = class _PreparedTransaction {
2062
2203
  this.#lifecycle.busy = false;
2063
2204
  }
2064
2205
  }
2065
- async authorize(providerOrOptions = {}, suppliedOptions = {}) {
2066
- const options = "authorize" in providerOrOptions ? suppliedOptions : providerOrOptions;
2067
- const provider = "authorize" in providerOrOptions ? providerOrOptions : this.#runtime.authorization();
2206
+ /** Public EIP-712 request; signing transport and key custody belong to the caller. */
2207
+ async exportWalletAuthorization(options = {}) {
2208
+ options.signal?.throwIfAborted();
2209
+ this.current();
2068
2210
  check2(
2069
2211
  requiresAuthorization(this.#saved.plan),
2070
2212
  "operation does not require wallet authorization"
2071
2213
  );
2072
- const selectedProvider = provider;
2073
2214
  check2(
2074
- provider.walletIdentity === this.#runtime.scope.walletIdentity,
2075
- "wallet authorization capability mismatch"
2215
+ ["prepared", "authorized"].includes(this.status),
2216
+ "operation is not awaiting wallet authorization"
2076
2217
  );
2218
+ return walletSigningRequest(this.#saved.plan);
2219
+ }
2220
+ /** Attach an external EOA signature, retaining its witness only at the private proving boundary. */
2221
+ async withWalletSignature(signature, options = {}) {
2222
+ options.signal?.throwIfAborted();
2223
+ this.current();
2224
+ check2(
2225
+ requiresAuthorization(this.#saved.plan),
2226
+ "operation does not require wallet authorization"
2227
+ );
2228
+ check2(
2229
+ ["prepared", "authorized"].includes(this.status),
2230
+ "operation is not awaiting wallet authorization"
2231
+ );
2232
+ const authorization = await acceptWalletSignature(this.#saved.plan, signature);
2077
2233
  return this.stage(
2078
2234
  "wallet authorization",
2079
2235
  ["prepared", "authorized"],
2236
+ async () => ({ status: "authorized", authorization }),
2237
+ options.signal
2238
+ );
2239
+ }
2240
+ /** Export only after proving when the funding authorization binds an actual proof hash. */
2241
+ async exportFundingAuthorization(options = {}) {
2242
+ options.signal?.throwIfAborted();
2243
+ this.current();
2244
+ check2(
2245
+ this.status === (this.kind === "shield" ? "prepared" : "proved"),
2246
+ "operation is not awaiting funding authorization or proof"
2247
+ );
2248
+ const request = await fundingSigningRequest(this.#saved, this.#runtime);
2249
+ options.signal?.throwIfAborted();
2250
+ this.current();
2251
+ return request;
2252
+ }
2253
+ /** Verify an external signature against this handle's own frozen operation. */
2254
+ async withFundingSignature(signature, options = {}) {
2255
+ options.signal?.throwIfAborted();
2256
+ this.current();
2257
+ check2(
2258
+ this.status === (this.kind === "shield" ? "prepared" : "proved"),
2259
+ "operation is not awaiting funding authorization or proof"
2260
+ );
2261
+ const verified = await acceptFundingSignature(this.#saved, signature, this.#runtime);
2262
+ return this.stage(
2263
+ "funding authorization",
2264
+ this.kind === "shield" ? ["prepared"] : ["proved"],
2080
2265
  async (saved) => {
2081
- const authorization = await authorizeOperation(saved.plan, selectedProvider);
2082
- return { status: "authorized", authorization };
2266
+ saved.plan.funding.signature = verified;
2267
+ if (saved.plan.statement?.target !== "noir-recursive") {
2268
+ saved.call = await this.encode(saved);
2269
+ }
2270
+ return { status: "ready" };
2083
2271
  },
2084
2272
  options.signal
2085
2273
  );
2086
2274
  }
2087
2275
  async prove(backendOrOptions = {}, suppliedOptions = {}) {
2088
- const options = "prove" in backendOrOptions ? suppliedOptions : backendOrOptions;
2276
+ const options = { ..."prove" in backendOrOptions ? suppliedOptions : backendOrOptions };
2089
2277
  const plan = this.#saved.plan;
2090
2278
  check2(plan.kind !== "shield", "shield requires no proof");
2091
2279
  if (requiresAuthorization(plan)) {
@@ -2096,7 +2284,7 @@ var PreparedTransaction = class _PreparedTransaction {
2096
2284
  }
2097
2285
  const prover = "prove" in backendOrOptions ? backendOrOptions : this.#runtime.options.prover;
2098
2286
  if (!prover) {
2099
- throw new import_core3.MissingCapabilityError("prover");
2287
+ throw new import_core4.MissingCapabilityError("prover");
2100
2288
  }
2101
2289
  return this.stage(
2102
2290
  "proof",
@@ -2124,37 +2312,6 @@ var PreparedTransaction = class _PreparedTransaction {
2124
2312
  encode(saved) {
2125
2313
  return encodeOperation(saved);
2126
2314
  }
2127
- async authorizeFunding(signerOrOptions = {}, suppliedOptions = {}) {
2128
- const options = "signTypedData" in signerOrOptions ? suppliedOptions : signerOrOptions;
2129
- const suppliedSigner = "signTypedData" in signerOrOptions ? signerOrOptions : void 0;
2130
- const p = this.#saved.plan;
2131
- check2(p.fundingMode === "signature", "operation does not require a funding signature");
2132
- check2(p.kind === "shield" || this.#saved.proof, "proof must precede funding authorization");
2133
- const signer = suppliedSigner ?? this.#runtime.options.fundingSigner;
2134
- if (!signer) {
2135
- throw new import_core3.MissingCapabilityError("EVM funding signer");
2136
- }
2137
- check2(
2138
- signer.address.toLowerCase() === p.funding.payer.toLowerCase(),
2139
- "funding signer is not the payer"
2140
- );
2141
- return this.stage(
2142
- "funding authorization",
2143
- p.kind === "shield" ? ["prepared"] : ["proved"],
2144
- async (saved) => {
2145
- saved.plan.funding.signature = await signOperationFunding(
2146
- saved,
2147
- signer,
2148
- this.#runtime.options.fundingVerifier
2149
- );
2150
- if (saved.plan.statement?.target !== "noir-recursive") {
2151
- saved.call = await this.encode(saved);
2152
- }
2153
- return { status: "ready" };
2154
- },
2155
- options.signal
2156
- );
2157
- }
2158
2315
  /** Shield caller funding needs no wallet signing or proof; this step only encodes its frozen request. */
2159
2316
  async ready() {
2160
2317
  check2(
@@ -2199,7 +2356,7 @@ var PreparedTransaction = class _PreparedTransaction {
2199
2356
  request: {
2200
2357
  params: structuredClone(plan.params),
2201
2358
  deliveries: plan.outputs.map((output) => structuredClone(output.delivery)),
2202
- funding: (0, import_protocol2.normalizeFunding)(plan.funding)
2359
+ funding: (0, import_protocol3.normalizeFunding)(plan.funding)
2203
2360
  }
2204
2361
  };
2205
2362
  }
@@ -2211,23 +2368,81 @@ var PreparedTransaction = class _PreparedTransaction {
2211
2368
  }
2212
2369
  };
2213
2370
 
2371
+ // src/resolve-recipient.ts
2372
+ var import_core5 = require("@invisi-labs/core");
2373
+ async function resolveRecipient(address2, lookup, details = {}) {
2374
+ const context = { ...details, address: address2 };
2375
+ let recipient;
2376
+ try {
2377
+ recipient = import_core5.ReceivingAddress.parse(address2);
2378
+ } catch (cause) {
2379
+ throw new InvisiError("INVALID_RECIPIENT", "Invalid recipient address", {
2380
+ ...context,
2381
+ cause
2382
+ });
2383
+ }
2384
+ if (recipient.kind === "long") return recipient;
2385
+ if (!lookup) {
2386
+ throw new InvisiError("MISSING_CAPABILITY", "Missing registry lookup capability", {
2387
+ ...context,
2388
+ capability: "registry lookup"
2389
+ });
2390
+ }
2391
+ let keys;
2392
+ try {
2393
+ keys = await lookup(recipient.walletIdentity);
2394
+ } catch (cause) {
2395
+ if (cause instanceof import_core5.MissingCapabilityError) {
2396
+ throw new InvisiError("MISSING_CAPABILITY", cause.message, {
2397
+ ...context,
2398
+ cause,
2399
+ capability: cause.capability
2400
+ });
2401
+ }
2402
+ throw new InvisiError("RECIPIENT_LOOKUP_FAILED", "Recipient registry lookup failed", {
2403
+ ...context,
2404
+ cause
2405
+ });
2406
+ }
2407
+ if (keys == null) {
2408
+ throw new InvisiError(
2409
+ "EVM_ADDRESS_NOT_REGISTERED",
2410
+ "EVM address has no registered receiving keys",
2411
+ context
2412
+ );
2413
+ }
2414
+ try {
2415
+ return import_core5.ReceivingAddress.from({
2416
+ walletIdentity: recipient.walletIdentity,
2417
+ viewingPublicKey: keys.viewingPublicKey,
2418
+ clueKey: keys.clueKey
2419
+ });
2420
+ } catch (cause) {
2421
+ throw new InvisiError(
2422
+ "RECIPIENT_LOOKUP_FAILED",
2423
+ "Registry returned invalid receiving keys",
2424
+ { ...context, cause }
2425
+ );
2426
+ }
2427
+ }
2428
+
2214
2429
  // src/session/prepare-common.ts
2215
2430
  var import_ethers3 = require("ethers");
2216
- var import_core5 = require("@invisi-labs/core");
2217
- var import_protocol3 = require("@invisi-labs/protocol");
2431
+ var import_core7 = require("@invisi-labs/core");
2432
+ var import_protocol4 = require("@invisi-labs/protocol");
2218
2433
 
2219
2434
  // src/session/membership.ts
2220
- var import_core4 = require("@invisi-labs/core");
2435
+ var import_core6 = require("@invisi-labs/core");
2221
2436
  async function verifyMembership(reader, context, block, input, proof) {
2222
2437
  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))
2223
2438
  throw new Error("membership context/root mismatch");
2224
2439
  let root = BigInt(input.commitment);
2225
2440
  for (let i = 0; i < 16; i++) {
2226
2441
  const sibling = proof.path[i];
2227
- if (typeof sibling !== "bigint" || sibling < 0n || sibling >= import_core4.FIELD_MODULUS)
2442
+ if (typeof sibling !== "bigint" || sibling < 0n || sibling >= import_core6.FIELD_MODULUS)
2228
2443
  throw new Error("noncanonical membership path");
2229
2444
  root = BigInt(
2230
- await (0, import_core4.poseidonHash)(input.leafIndex >> i & 1 ? [sibling, root] : [root, sibling])
2445
+ await (0, import_core6.poseidonHash)(input.leafIndex >> i & 1 ? [sibling, root] : [root, sibling])
2231
2446
  );
2232
2447
  }
2233
2448
  if (root !== BigInt(proof.root))
@@ -2265,6 +2480,27 @@ function positiveAmount(value, bits = 128) {
2265
2480
  }
2266
2481
  return value;
2267
2482
  }
2483
+ function copyApplicationPayload(value) {
2484
+ if (value === void 0) return new Uint8Array();
2485
+ if (!(value instanceof Uint8Array)) throw new TypeError("payload must be Uint8Array");
2486
+ return Uint8Array.from(value);
2487
+ }
2488
+ function positiveOutputAmount(payment, outputIndex) {
2489
+ try {
2490
+ return positiveAmount(payment.amount);
2491
+ } catch (cause) {
2492
+ throw new InvisiError(
2493
+ "PREPARATION_FAILED",
2494
+ cause instanceof Error ? cause.message : "Invalid output amount",
2495
+ {
2496
+ address: payment.to,
2497
+ field: "amount",
2498
+ ...outputIndex === void 0 ? {} : { outputIndex },
2499
+ cause
2500
+ }
2501
+ );
2502
+ }
2503
+ }
2268
2504
  var Preparation = class {
2269
2505
  constructor(runtime) {
2270
2506
  this.runtime = runtime;
@@ -2318,7 +2554,7 @@ var Preparation = class {
2318
2554
  if (mode === "caller" && (source.sigNonce || source.deadline)) {
2319
2555
  throw new Error("caller funding uses zero nonce/deadline");
2320
2556
  }
2321
- const funding = (0, import_protocol3.normalizeFunding)({
2557
+ const funding = (0, import_protocol4.normalizeFunding)({
2322
2558
  payer,
2323
2559
  amount: source.amount,
2324
2560
  sigNonce: mode === "caller" ? 0n : source.sigNonce ?? await this.runtime.reader.getFundingNonce(
@@ -2334,52 +2570,70 @@ var Preparation = class {
2334
2570
  }
2335
2571
  return { funding, fundingMode: mode };
2336
2572
  }
2337
- async output(payment, context, change = false) {
2338
- positiveAmount(payment.amount);
2339
- const recipient = await import_core5.ReceivingAddress.parse(payment.to).resolve(
2340
- this.runtime.options.addressResolver
2341
- );
2342
- const memo = (0, import_core5.encodeMemo)(payment.memo);
2343
- const nonce = (0, import_core5.generateNonce)();
2344
- const identity = await (0, import_core5.computeNoteIdentity)(BigInt(recipient.walletIdentity), nonce);
2345
- const keys = recipient.getReceivingKeys();
2346
- const note = {
2347
- chainId: context.chainId,
2348
- protocolAddress: BigInt(context.protocolAddress),
2349
- tokenAddress: BigInt(context.tokenAddress),
2350
- walletIdentity: recipient.walletIdentity,
2351
- amount: payment.amount,
2352
- ...memo.length === 0 ? {} : { memo },
2353
- nonce
2354
- };
2355
- const commitment = await (0, import_core5.computeNoteCommitment)(
2356
- context.chainId,
2357
- identity,
2358
- note.tokenAddress,
2359
- note.amount
2360
- );
2361
- return {
2362
- note,
2363
- commitment,
2364
- identity,
2365
- address: payment.to,
2366
- change,
2367
- delivery: {
2368
- ciphertext: (0, import_ethers3.hexlify)(
2369
- await (0, import_core5.encryptPrivateTransferNote)(keys.viewingPublicKey, note, {
2370
- chainId: context.chainId,
2371
- protocolAddress: note.protocolAddress,
2372
- outputCommitment: commitment
2373
- })
2374
- ),
2375
- clue: (0, import_ethers3.hexlify)((0, import_core5.createClue)(keys.clueKey))
2376
- }
2377
- };
2573
+ async output(payment, context, change = false, outputIndex) {
2574
+ try {
2575
+ positiveOutputAmount(payment, outputIndex);
2576
+ const recipient = await resolveRecipient(
2577
+ payment.to,
2578
+ this.runtime.options.addressResolver,
2579
+ {
2580
+ field: change ? "changeAddress" : "to",
2581
+ ...outputIndex === void 0 ? {} : { outputIndex }
2582
+ }
2583
+ );
2584
+ const nonce = (0, import_core7.generateNonce)();
2585
+ const identity = await (0, import_core7.computeNoteIdentity)(BigInt(recipient.walletIdentity), nonce);
2586
+ const keys = recipient.getReceivingKeys();
2587
+ const note = {
2588
+ chainId: context.chainId,
2589
+ protocolAddress: BigInt(context.protocolAddress),
2590
+ tokenAddress: BigInt(context.tokenAddress),
2591
+ walletIdentity: recipient.walletIdentity,
2592
+ amount: payment.amount,
2593
+ payload: copyApplicationPayload(payment.payload),
2594
+ nonce
2595
+ };
2596
+ const commitment = await (0, import_core7.computeNoteCommitment)(
2597
+ context.chainId,
2598
+ identity,
2599
+ note.tokenAddress,
2600
+ note.amount
2601
+ );
2602
+ return {
2603
+ note,
2604
+ commitment,
2605
+ identity,
2606
+ address: payment.to,
2607
+ change,
2608
+ delivery: {
2609
+ ciphertext: (0, import_ethers3.hexlify)(
2610
+ await (0, import_core7.encryptPrivateTransferNote)(keys.viewingPublicKey, note, {
2611
+ chainId: context.chainId,
2612
+ protocolAddress: note.protocolAddress,
2613
+ outputCommitment: commitment
2614
+ })
2615
+ ),
2616
+ clue: (0, import_ethers3.hexlify)((0, import_core7.createClue)(keys.clueKey))
2617
+ }
2618
+ };
2619
+ } catch (cause) {
2620
+ if (cause instanceof InvisiError) throw cause;
2621
+ throw new InvisiError(
2622
+ "PREPARATION_FAILED",
2623
+ cause instanceof Error ? cause.message : "Output preparation failed",
2624
+ {
2625
+ address: payment.to,
2626
+ field: change ? "changeAddress" : "to",
2627
+ ...outputIndex === void 0 ? {} : { outputIndex },
2628
+ cause
2629
+ }
2630
+ );
2631
+ }
2378
2632
  }
2379
2633
  async membership(input, frame) {
2380
2634
  const { options, reader } = this.runtime;
2381
2635
  if (!options.protocolRpc)
2382
- throw new import_core5.MissingCapabilityError("Protocol RPC membership provider");
2636
+ throw new import_core7.MissingCapabilityError("Protocol RPC membership provider");
2383
2637
  const root = this.#roots.get(input.treeId);
2384
2638
  const proof = await options.protocolRpc.getMembership(
2385
2639
  frame.context,
@@ -2426,9 +2680,9 @@ var Preparation = class {
2426
2680
  for (const input of selected) {
2427
2681
  const note = input.note;
2428
2682
  const c = frame.context;
2429
- if (note.walletIdentity !== scope.walletIdentity || note.chainId !== c.chainId || note.protocolAddress !== BigInt(c.protocolAddress) || note.tokenAddress !== BigInt(c.tokenAddress) || await (0, import_core5.computeNoteCommitment)(
2683
+ if (note.walletIdentity !== scope.walletIdentity || note.chainId !== c.chainId || note.protocolAddress !== BigInt(c.protocolAddress) || note.tokenAddress !== BigInt(c.tokenAddress) || await (0, import_core7.computeNoteCommitment)(
2430
2684
  c.chainId,
2431
- await (0, import_core5.computeNoteIdentity)(BigInt(note.walletIdentity), note.nonce),
2685
+ await (0, import_core7.computeNoteIdentity)(BigInt(note.walletIdentity), note.nonce),
2432
2686
  note.tokenAddress,
2433
2687
  note.amount
2434
2688
  ) !== input.commitment) {
@@ -2480,18 +2734,18 @@ function storedCandidates(inputs) {
2480
2734
 
2481
2735
  // src/session/transaction-preparation.ts
2482
2736
  var import_ethers5 = require("ethers");
2483
- var import_protocol5 = require("@invisi-labs/protocol");
2737
+ var import_protocol6 = require("@invisi-labs/protocol");
2484
2738
 
2485
2739
  // src/session/prepare-credit.ts
2486
2740
  var import_ethers4 = require("ethers");
2487
- var import_core7 = require("@invisi-labs/core");
2488
- var import_protocol4 = require("@invisi-labs/protocol");
2741
+ var import_core9 = require("@invisi-labs/core");
2742
+ var import_protocol5 = require("@invisi-labs/protocol");
2489
2743
 
2490
2744
  // src/session/credit-values.ts
2491
- var import_core6 = require("@invisi-labs/core");
2745
+ var import_core8 = require("@invisi-labs/core");
2492
2746
  async function creditLeaf(state) {
2493
2747
  const n = state.note;
2494
- return (0, import_core6.computeCreditCommitment)(
2748
+ return (0, import_core8.computeCreditCommitment)(
2495
2749
  n.chainId,
2496
2750
  n.spenderWalletIdentity,
2497
2751
  n.ownerWalletIdentity,
@@ -2501,7 +2755,7 @@ async function creditLeaf(state) {
2501
2755
  n.amount,
2502
2756
  n.nonce,
2503
2757
  n.expiry,
2504
- await (0, import_core6.computeCreditSharedKeyHash)(n.creditSharedKey),
2758
+ await (0, import_core8.computeCreditSharedKeyHash)(n.creditSharedKey),
2505
2759
  n
2506
2760
  );
2507
2761
  }
@@ -2521,7 +2775,7 @@ var CreditPreparation = class {
2521
2775
  return value;
2522
2776
  }
2523
2777
  async seal(input, frame) {
2524
- const state = (0, import_core7.normalizeCreditDeliveryState)(input);
2778
+ const state = (0, import_core9.normalizeCreditDeliveryState)(input);
2525
2779
  const commitment = await creditLeaf(state);
2526
2780
  const context = {
2527
2781
  chainId: frame.context.chainId,
@@ -2533,9 +2787,9 @@ var CreditPreparation = class {
2533
2787
  const keys = role === "owner" ? state.ownerReceivingKeys : state.spenderReceivingKeys;
2534
2788
  return {
2535
2789
  ciphertext: (0, import_ethers4.hexlify)(
2536
- await (0, import_core7.encryptCreditNote)(keys.viewingPublicKey, state, { ...context, role })
2790
+ await (0, import_core9.encryptCreditNote)(keys.viewingPublicKey, state, { ...context, role })
2537
2791
  ),
2538
- clue: (0, import_ethers4.hexlify)((0, import_core7.createClue)(keys.clueKey))
2792
+ clue: (0, import_ethers4.hexlify)((0, import_core9.createClue)(keys.clueKey))
2539
2793
  };
2540
2794
  })
2541
2795
  );
@@ -2546,14 +2800,14 @@ var CreditPreparation = class {
2546
2800
  if (!stored || stored.spent !== "unspent" || stored.role !== role) {
2547
2801
  throw new Error(`confirmed unspent ${role} Credit is unavailable`);
2548
2802
  }
2549
- const state = (0, import_core7.normalizeCreditDeliveryState)(stored.state);
2803
+ const state = (0, import_core9.normalizeCreditDeliveryState)(stored.state);
2550
2804
  const n = state.note;
2551
2805
  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) {
2552
2806
  throw new Error("Credit role, commitment or deployment mismatch");
2553
2807
  }
2554
2808
  const proof = await this.#prepare.membership(stored, frame);
2555
2809
  const path = [...proof.path];
2556
- const nullifier = await (0, import_core7.computeCreditNullifier)(n.chainId, n.creditSharedKey, commitment);
2810
+ const nullifier = await (0, import_core9.computeCreditNullifier)(n.chainId, n.creditSharedKey, commitment);
2557
2811
  await this.#prepare.assertUnspent([nullifier], frame);
2558
2812
  return {
2559
2813
  input: {
@@ -2590,10 +2844,16 @@ var CreditPreparation = class {
2590
2844
  positiveAmount(options.amount, 126);
2591
2845
  const frame = await this.#prepare.frame(options.deadline);
2592
2846
  const expiry = this.expiry(options.expiry, frame);
2593
- const spender = await import_core7.ReceivingAddress.parse(options.spender).resolve(
2594
- this.runtime.options.addressResolver
2847
+ const spender = await resolveRecipient(
2848
+ options.spender,
2849
+ this.runtime.options.addressResolver,
2850
+ { field: "spender" }
2851
+ );
2852
+ const owner = await resolveRecipient(
2853
+ this.runtime.options.wallet.getReceivingAddress().toString(),
2854
+ this.runtime.options.addressResolver,
2855
+ { field: "wallet" }
2595
2856
  );
2596
- const owner = await this.runtime.options.wallet.getReceivingAddress().resolve(this.runtime.options.addressResolver);
2597
2857
  const ownerPayout = (0, import_ethers4.getAddress)(owner.walletIdentity);
2598
2858
  const spenderPayout = (0, import_ethers4.getAddress)(spender.walletIdentity);
2599
2859
  if (BigInt(ownerPayout) === 0n || BigInt(spenderPayout) === 0n) {
@@ -2609,16 +2869,16 @@ var CreditPreparation = class {
2609
2869
  spenderWalletIdentity: spender.walletIdentity,
2610
2870
  ownerPayout: BigInt(ownerPayout),
2611
2871
  spenderPayout: BigInt(spenderPayout),
2612
- ...(0, import_core7.normalizeCreditPolicy)({
2872
+ ...(0, import_core9.normalizeCreditPolicy)({
2613
2873
  singleLimit: options.singleLimit ?? 0n,
2614
2874
  dailyLimit: options.dailyLimit ?? 0n,
2615
2875
  monthlyLimit: options.monthlyLimit ?? 0n
2616
2876
  }),
2617
2877
  amount: options.amount,
2618
- ...options.memo === void 0 ? {} : { memo: options.memo },
2878
+ payload: copyApplicationPayload(options.payload),
2619
2879
  expiry,
2620
- nonce: (0, import_core7.generateNonce)(),
2621
- creditSharedKey: (0, import_core7.generateCreditSharedKey)()
2880
+ nonce: (0, import_core9.generateNonce)(),
2881
+ creditSharedKey: (0, import_core9.generateCreditSharedKey)()
2622
2882
  },
2623
2883
  ownerReceivingKeys: owner.getReceivingKeys(),
2624
2884
  spenderReceivingKeys: spender.getReceivingKeys()
@@ -2660,7 +2920,7 @@ var CreditPreparation = class {
2660
2920
  sources: selected.sources,
2661
2921
  nullifiers: selected.nullifiers,
2662
2922
  creditCommitment: output.commitment,
2663
- changeCommitment: plan.outputs[0]?.commitment ?? (0, import_protocol4.word)(0n),
2923
+ changeCommitment: plan.outputs[0]?.commitment ?? (0, import_protocol5.word)(0n),
2664
2924
  owner: (0, import_ethers4.getAddress)((0, import_ethers4.toBeHex)(output.state.note.ownerPayout, 20)),
2665
2925
  expiry: output.state.note.expiry,
2666
2926
  deadline: frame.deadline
@@ -2680,14 +2940,14 @@ var CreditPreparation = class {
2680
2940
  creditNullifier: previous.nullifier,
2681
2941
  previousExpiry: previous.input.state.note.expiry
2682
2942
  };
2683
- plan.statement = import_protocol4.WitnessBuilder.statement(
2943
+ plan.statement = import_protocol5.WitnessBuilder.statement(
2684
2944
  "credit_top_up",
2685
- (0, import_protocol4.topUpCreditPublicInputs)(frame.context, plan.credit.topUp, deliveries, plan.funding)
2945
+ (0, import_protocol5.topUpCreditPublicInputs)(frame.context, plan.credit.topUp, deliveries, plan.funding)
2686
2946
  );
2687
2947
  } else {
2688
- plan.statement = import_protocol4.WitnessBuilder.statement(
2948
+ plan.statement = import_protocol5.WitnessBuilder.statement(
2689
2949
  "create_credit",
2690
- (0, import_protocol4.createCreditPublicInputs)(frame.context, creation, deliveries, plan.funding)
2950
+ (0, import_protocol5.createCreditPublicInputs)(frame.context, creation, deliveries, plan.funding)
2691
2951
  );
2692
2952
  }
2693
2953
  await this.#prepare.assertBlock(frame);
@@ -2716,8 +2976,10 @@ var CreditPreparation = class {
2716
2976
  monthlyLimit: options.monthlyLimit ?? old.note.monthlyLimit ?? 0n,
2717
2977
  amount,
2718
2978
  expiry,
2719
- nonce: (0, import_core7.generateNonce)(),
2720
- ...options.memo === void 0 ? {} : { memo: options.memo }
2979
+ nonce: (0, import_core9.generateNonce)(),
2980
+ payload: copyApplicationPayload(
2981
+ options.payload === void 0 ? old.note.payload : options.payload
2982
+ )
2721
2983
  }
2722
2984
  },
2723
2985
  frame
@@ -2733,7 +2995,7 @@ var CreditPreparation = class {
2733
2995
  }
2734
2996
  async draw(options) {
2735
2997
  const frame = await this.#prepare.frame(options.deadline);
2736
- const profile = (0, import_protocol4.selectCreditDrawCircuit)(options.credits.length);
2998
+ const profile = (0, import_protocol5.selectCreditDrawCircuit)(options.credits.length);
2737
2999
  if (new Set(options.credits.map((value) => value.credit)).size !== options.credits.length) {
2738
3000
  throw new Error("duplicate Credit input");
2739
3001
  }
@@ -2765,11 +3027,11 @@ var CreditPreparation = class {
2765
3027
  }
2766
3028
  sources.set(member.source.treeId, member.source.merkleRoot);
2767
3029
  amounts.push(amount);
2768
- const usage = (0, import_core7.applyCreditDrawUsage)(n, amount, frame.block.timestamp);
3030
+ const usage = (0, import_core9.applyCreditDrawUsage)(n, amount, frame.block.timestamp);
2769
3031
  const remaining = n.amount - amount;
2770
3032
  if (remaining === 0n) {
2771
- remainders.push((0, import_protocol4.word)(0n));
2772
- recoveryAmounts.push((0, import_protocol4.word)(0n));
3033
+ remainders.push((0, import_protocol5.word)(0n));
3034
+ recoveryAmounts.push((0, import_protocol5.word)(0n));
2773
3035
  deliveries.push({ ...EMPTY }, { ...EMPTY });
2774
3036
  continue;
2775
3037
  }
@@ -2787,8 +3049,10 @@ var CreditPreparation = class {
2787
3049
  ...n,
2788
3050
  ...usage,
2789
3051
  amount: remaining,
2790
- ...options.credits[i].memo === void 0 ? {} : { memo: options.credits[i].memo },
2791
- nonce: await (0, import_core7.deriveRemainderNonce)(...args)
3052
+ payload: copyApplicationPayload(
3053
+ options.credits[i].payload === void 0 ? n.payload : options.credits[i].payload
3054
+ ),
3055
+ nonce: await (0, import_core9.deriveRemainderNonce)(...args)
2792
3056
  }
2793
3057
  },
2794
3058
  frame
@@ -2796,7 +3060,7 @@ var CreditPreparation = class {
2796
3060
  outputs.push(output);
2797
3061
  remainders.push(output.commitment);
2798
3062
  recoveryAmounts.push(
2799
- (0, import_protocol4.word)((remaining + await (0, import_core7.computeCreditRecoveryPad)(...args)) % import_core7.FIELD_MODULUS)
3063
+ (0, import_protocol5.word)((remaining + await (0, import_core9.computeCreditRecoveryPad)(...args)) % import_core9.FIELD_MODULUS)
2800
3064
  );
2801
3065
  deliveries.push(...output.deliveries);
2802
3066
  }
@@ -2820,9 +3084,9 @@ var CreditPreparation = class {
2820
3084
  draw: params,
2821
3085
  amounts
2822
3086
  };
2823
- plan.statement = import_protocol4.WitnessBuilder.statement(
3087
+ plan.statement = import_protocol5.WitnessBuilder.statement(
2824
3088
  profile.name,
2825
- (0, import_protocol4.creditDrawPublicInputs)(frame.context, params, deliveries)
3089
+ (0, import_protocol5.creditDrawPublicInputs)(frame.context, params, deliveries)
2826
3090
  );
2827
3091
  await this.#prepare.assertBlock(frame);
2828
3092
  return this.runtime.complete(plan);
@@ -2842,9 +3106,9 @@ var CreditPreparation = class {
2842
3106
  };
2843
3107
  plan.capacity = 1;
2844
3108
  plan.credit = { inputs: [previous.input], outputs: [], deliveries: [], cancel: params };
2845
- plan.statement = import_protocol4.WitnessBuilder.statement(
3109
+ plan.statement = import_protocol5.WitnessBuilder.statement(
2846
3110
  "credit_cancel",
2847
- (0, import_protocol4.cancelCreditPublicInputs)(frame.context, params)
3111
+ (0, import_protocol5.cancelCreditPublicInputs)(frame.context, params)
2848
3112
  );
2849
3113
  await this.#prepare.assertBlock(frame);
2850
3114
  return this.runtime.complete(plan);
@@ -2865,87 +3129,48 @@ var TransactionPreparation = class {
2865
3129
  return this.context.scope;
2866
3130
  }
2867
3131
  async prepareTransfer(options) {
3132
+ if (Object.hasOwn(options, "target") || Object.hasOwn(options, "execution")) {
3133
+ throw new Error(
3134
+ "prepareTransfer accepts no target or execution; choose execution in prove()"
3135
+ );
3136
+ }
2868
3137
  const copied = structuredClone(options);
2869
- const preparation = new Preparation(this.runtime(copied.tokenAddress));
3138
+ const runtime = this.runtime(copied.tokenAddress);
3139
+ if (copied.commitments !== void 0 && copied.commitments.length === 0)
3140
+ throw new Error("empty explicit input selection");
3141
+ const preparation = new Preparation({
3142
+ ...runtime,
3143
+ ...copied.commitments !== void 0 ? { inputSelection: copied.commitments } : {}
3144
+ });
2870
3145
  const frame = await preparation.frame(copied.deadline);
2871
- if (copied.outputs.length < 1 || copied.outputs.length > 168) {
2872
- throw new Error("transfer requires 1..168 outputs");
3146
+ if (copied.outputs.length > 168) throw new Error("transfer supports at most 168 outputs");
3147
+ let withdrawalAddress = import_protocol6.ZERO_ADDRESS;
3148
+ let withdrawalAmount = 0n;
3149
+ if (copied.withdrawal !== void 0) {
3150
+ withdrawalAddress = (0, import_ethers5.getAddress)(copied.withdrawal.to);
3151
+ positiveAmount(copied.withdrawal.amount);
3152
+ if (BigInt(withdrawalAddress) === 0n)
3153
+ throw new Error("withdrawal destination must be nonzero");
3154
+ withdrawalAmount = copied.withdrawal.amount;
2873
3155
  }
3156
+ if (copied.outputs.length === 0 && withdrawalAmount === 0n)
3157
+ throw new Error("transfer requires a private output or public withdrawal");
2874
3158
  const total = copied.outputs.reduce(
2875
- (sum, output) => sum + positiveAmount(output.amount),
2876
- 0n
3159
+ (sum, output, index2) => sum + positiveOutputAmount(output, index2),
3160
+ withdrawalAmount
2877
3161
  );
2878
3162
  const funding = await preparation.funding(copied.funding, frame);
2879
3163
  return this.funds(
2880
- "transfer",
2881
3164
  copied.outputs,
2882
3165
  total,
2883
- import_protocol5.ZERO_ADDRESS,
2884
- 0n,
3166
+ withdrawalAddress,
3167
+ withdrawalAmount,
2885
3168
  frame,
2886
3169
  funding,
2887
- copied.target,
2888
- preparation
2889
- );
2890
- }
2891
- async prepareWithdraw(options) {
2892
- const copied = structuredClone(options);
2893
- const preparation = new Preparation(this.runtime(copied.tokenAddress));
2894
- const frame = await preparation.frame(copied.deadline);
2895
- const to = (0, import_ethers5.getAddress)(copied.to);
2896
- positiveAmount(copied.amount);
2897
- if (BigInt(to) === 0n) {
2898
- throw new Error("withdrawal destination must be nonzero");
2899
- }
2900
- return this.funds(
2901
- "withdraw",
2902
- [],
2903
- copied.amount,
2904
- to,
2905
- copied.amount,
2906
- frame,
2907
- { funding: {}, fundingMode: "none" },
2908
- copied.target,
2909
3170
  preparation
2910
3171
  );
2911
3172
  }
2912
- /** Full payout to the note owner; no arbitrary destination, change or spending signature. */
2913
- async prepareSweep(options) {
2914
- const copied = structuredClone(options);
2915
- const runtime = this.runtime(copied.tokenAddress);
2916
- const state = await runtime.readInventory();
2917
- const available = Object.values(state.notes).filter(
2918
- (record) => record.note.tokenAddress === BigInt(copied.tokenAddress) && record.spent === "unspent" && !state.reservations[record.commitment]
2919
- );
2920
- const selected = copied.commitments ?? available.map((record) => record.commitment);
2921
- if (selected.length === 0) throw new Error("empty sweep input selection");
2922
- if (new Set(selected).size !== selected.length) throw new Error("duplicate sweep inputs");
2923
- const byId = new Map(available.map((record) => [record.commitment, record]));
2924
- let amount = 0n;
2925
- for (const id of selected) {
2926
- const record = byId.get(id);
2927
- if (!record) throw new Error("sweep input unavailable");
2928
- amount += record.note.amount;
2929
- }
2930
- positiveAmount(amount);
2931
- const preparation = new Preparation({
2932
- ...runtime,
2933
- inputSelection: selected
2934
- });
2935
- const frame = await preparation.frame(copied.deadline);
2936
- return this.funds(
2937
- "sweep",
2938
- [],
2939
- amount,
2940
- this.scope.walletIdentity,
2941
- amount,
2942
- frame,
2943
- { funding: {}, fundingMode: "none" },
2944
- copied.target,
2945
- preparation
2946
- );
2947
- }
2948
- async funds(kind, payments, total, withdrawalAddress, withdrawalAmount, frame, funding, target, preparation) {
3173
+ async funds(payments, total, withdrawalAddress, withdrawalAmount, frame, funding, preparation) {
2949
3174
  if (total >= 1n << 135n) {
2950
3175
  throw new Error("transaction sum exceeds uint135");
2951
3176
  }
@@ -2955,7 +3180,7 @@ var TransactionPreparation = class {
2955
3180
  frame
2956
3181
  );
2957
3182
  const outputs = await Promise.all(
2958
- payments.map((payment) => preparation.output(payment, context))
3183
+ payments.map((payment, index2) => preparation.output(payment, context, false, index2))
2959
3184
  );
2960
3185
  const change = selection.amount + (funding.funding.amount ?? 0n) - total;
2961
3186
  if (change > 0n) {
@@ -2980,17 +3205,17 @@ var TransactionPreparation = class {
2980
3205
  deadline
2981
3206
  };
2982
3207
  const deliveries = outputs.map((output) => output.delivery);
2983
- const normalized = (0, import_protocol5.normalizeTransactRequest)(context, params, deliveries, funding.funding);
2984
- const statement2 = import_protocol5.WitnessBuilder.statement(
3208
+ const normalized = (0, import_protocol6.normalizeTransactRequest)(context, params, deliveries, funding.funding);
3209
+ const statement2 = import_protocol6.WitnessBuilder.statement(
2985
3210
  `transact_user_slots${normalized.capacity}`,
2986
- (0, import_protocol5.transactUserPublicInputs)(context, params, deliveries, funding.funding),
2987
- target ?? "evm"
3211
+ (0, import_protocol6.transactUserPublicInputs)(context, params, deliveries, funding.funding),
3212
+ "evm"
2988
3213
  );
2989
3214
  await preparation.assertBlock(frame);
2990
3215
  return preparation.runtime.complete({
2991
3216
  version: 1,
2992
3217
  id: crypto.randomUUID(),
2993
- kind,
3218
+ kind: "transfer",
2994
3219
  context,
2995
3220
  scope: this.scope,
2996
3221
  preparedAt: block,
@@ -3012,7 +3237,9 @@ var TransactionPreparation = class {
3012
3237
  throw new Error("shield requires 1..10 outputs");
3013
3238
  }
3014
3239
  const outputs = await Promise.all(
3015
- copied.outputs.map((output) => preparation.output(output, frame.context))
3240
+ copied.outputs.map(
3241
+ (output, index2) => preparation.output(output, frame.context, false, index2)
3242
+ )
3016
3243
  );
3017
3244
  if (outputs.reduce((sum, output) => sum + output.note.amount, 0n) !== (funding.funding.amount ?? 0n)) {
3018
3245
  throw new Error("shield funding must equal output total");
@@ -3094,14 +3321,21 @@ function explicitPreparation(options, data, tokenAddress) {
3094
3321
  namespace: "explicit",
3095
3322
  walletIdentity: options.wallet.walletIdentity
3096
3323
  };
3097
- const { store: _store, coinSelector: _selector, ...capabilities } = options;
3324
+ const {
3325
+ store: _store,
3326
+ coinSelector: _selector,
3327
+ authorization: _authorization,
3328
+ viewing: _viewing,
3329
+ signer: _signer,
3330
+ fundingSigner: _fundingSigner,
3331
+ ...capabilities
3332
+ } = options;
3098
3333
  if (inputs.some((input) => input.note.tokenAddress !== BigInt((0, import_ethers6.getAddress)(tokenAddress))))
3099
3334
  throw new Error("explicit note token mismatch");
3100
3335
  const runtime = {
3101
3336
  options: { ...capabilities, protocol: options.protocol.forToken(tokenAddress) },
3102
3337
  reader: options.chainReader,
3103
- scope,
3104
- authorization: () => options.authorization ?? options.wallet.exportAuthorization()
3338
+ scope
3105
3339
  };
3106
3340
  return new TransactionPreparation({
3107
3341
  ...runtime,
@@ -3399,34 +3633,6 @@ var EthersAddressResolver = class {
3399
3633
  }).getReceivingKeys();
3400
3634
  };
3401
3635
  };
3402
- var EthersFundingSigner = class _EthersFundingSigner {
3403
- constructor(signer, chainId, address2) {
3404
- this.signer = signer;
3405
- this.chainId = chainId;
3406
- this.address = getAddress5(address2);
3407
- }
3408
- address;
3409
- static async create(signer, chainId) {
3410
- return new _EthersFundingSigner(signer, chainId, getAddress5(await signer.getAddress()));
3411
- }
3412
- async signTypedData(data) {
3413
- const copied = structuredClone(data);
3414
- check3(
3415
- BigInt(copied.domain.chainId) === this.chainId,
3416
- "funding typed-data chain mismatch"
3417
- );
3418
- check3(
3419
- getAddress5(await this.signer.getAddress()) === this.address,
3420
- "funding signer address mismatch"
3421
- );
3422
- if (this.signer.provider) await chain(this.signer.provider, this.chainId);
3423
- return await this.signer.signTypedData(
3424
- copied.domain,
3425
- fundingTypes(copied),
3426
- copied.message
3427
- );
3428
- }
3429
- };
3430
3636
  var EthersFundingVerifier = class {
3431
3637
  constructor(client, chainId) {
3432
3638
  this.client = client;
@@ -3469,7 +3675,24 @@ var EthersFundingVerifier = class {
3469
3675
  };
3470
3676
 
3471
3677
  // src/Invisi.ts
3472
- var import_core15 = require("@invisi-labs/core");
3678
+ var import_core19 = require("@invisi-labs/core");
3679
+
3680
+ // src/AddressRegistry.ts
3681
+ var import_protocol7 = require("@invisi-labs/protocol");
3682
+ var AddressRegistry = class extends import_protocol7.AddressRegistry {
3683
+ prepareRegistration(options) {
3684
+ return prepareWithErrors("prepareRegistration", async () => {
3685
+ if ("provider" in options) {
3686
+ const { provider, ...preparation } = options;
3687
+ return super.prepareRegistration({
3688
+ ...preparation,
3689
+ reader: new EthersChainReader(provider)
3690
+ });
3691
+ }
3692
+ return super.prepareRegistration(options);
3693
+ });
3694
+ }
3695
+ };
3473
3696
 
3474
3697
  // src/session/codec.ts
3475
3698
  function encode(value) {
@@ -3515,8 +3738,8 @@ function deserializeState(data) {
3515
3738
 
3516
3739
  // src/session/protocol-events.ts
3517
3740
  var import_contracts2 = require("@invisi-labs/protocol/contracts");
3518
- var import_ethers8 = require("ethers");
3519
- var protocolInterface = new import_ethers8.Interface(import_contracts2.invisiProtocolAbi);
3741
+ var import_ethers9 = require("ethers");
3742
+ var protocolInterface = new import_ethers9.Interface(import_contracts2.invisiProtocolAbi);
3520
3743
  function decodeProtocolEvent(log) {
3521
3744
  const decoded = protocolInterface.parseLog(log);
3522
3745
  if (!decoded) throw new Error("Unknown protocol receipt event");
@@ -3579,9 +3802,10 @@ function readProtocolLogs(receipt, protocolAddress) {
3579
3802
  }
3580
3803
 
3581
3804
  // src/session/discovery.ts
3582
- var import_ethers9 = require("ethers");
3583
- var import_core8 = require("@invisi-labs/core");
3584
- var import_protocol6 = require("@invisi-labs/protocol");
3805
+ var import_core10 = require("@invisi-labs/core");
3806
+ var import_ethers10 = require("ethers");
3807
+ var import_core11 = require("@invisi-labs/core");
3808
+ var import_protocol8 = require("@invisi-labs/protocol");
3585
3809
  var EVENTS = /* @__PURE__ */ new Set([
3586
3810
  "CommitmentsInserted",
3587
3811
  "BatchOutputs",
@@ -3629,7 +3853,7 @@ function hash(value) {
3629
3853
  function contextCheck(context) {
3630
3854
  check4(context.chainId >= 0n && context.chainId < 1n << 64n, "invalid discovery chain");
3631
3855
  check4(
3632
- (0, import_ethers9.isAddress)(context.protocolAddress) && BigInt(context.protocolAddress) !== 0n,
3856
+ (0, import_ethers10.isAddress)(context.protocolAddress) && BigInt(context.protocolAddress) !== 0n,
3633
3857
  "invalid discovery instance/token"
3634
3858
  );
3635
3859
  }
@@ -3674,9 +3898,9 @@ function extractDeliveryCandidates(logs, context, confirmedThrough) {
3674
3898
  check4(inserted, "delivery has no matching canonical insertion");
3675
3899
  if (commitment !== void 0)
3676
3900
  check4(hash(commitment) === inserted, "delivery commitment conflicts with insertion");
3677
- const delivery = (0, import_protocol6.normalizeDelivery)(wire, { credit: role !== "funds" });
3901
+ const delivery = (0, import_protocol8.normalizeOpaqueDelivery)(wire);
3678
3902
  check4(
3679
- typeof token === "string" && (0, import_ethers9.isAddress)(token) && BigInt(token) !== 0n,
3903
+ typeof token === "string" && (0, import_ethers10.isAddress)(token) && BigInt(token) !== 0n,
3680
3904
  "invalid event token"
3681
3905
  );
3682
3906
  const value = {
@@ -3705,7 +3929,7 @@ function extractDeliveryCandidates(logs, context, confirmedThrough) {
3705
3929
  add(log, tree, index(leaves[0]), fields.creditCommitment, "owner", ds[0]);
3706
3930
  add(log, tree, index(leaves[0]), fields.creditCommitment, "spender", ds[1]);
3707
3931
  if (hasChange) add(log, tree, index(leaves[1]), change, "funds", ds[2]);
3708
- else (0, import_protocol6.normalizeDelivery)(ds[2], { empty: true });
3932
+ else (0, import_protocol8.normalizeOpaqueDelivery)(ds[2], { empty: true });
3709
3933
  };
3710
3934
  for (const log of relevant) {
3711
3935
  const a = object(log.args);
@@ -3735,7 +3959,7 @@ function extractDeliveryCandidates(logs, context, confirmedThrough) {
3735
3959
  const length = Number.parseInt(packed.slice(offset, offset + 4), 16);
3736
3960
  offset += 4;
3737
3961
  check4(
3738
- length >= 160 && length <= 416 && offset + length * 2 + 136 <= packed.length,
3962
+ length >= 1 && length <= import_core10.MAX_CIPHERTEXT_BYTES && offset + length * 2 + 136 <= packed.length,
3739
3963
  "invalid packed delivery length"
3740
3964
  );
3741
3965
  const ciphertext = `0x${packed.slice(offset, offset + length * 2)}`;
@@ -3757,8 +3981,8 @@ function extractDeliveryCandidates(logs, context, confirmedThrough) {
3757
3981
  cs.forEach((c, i) => {
3758
3982
  if (BigInt(hash(c)) === 0n) {
3759
3983
  check4(integer(leaves[i]) === 0n, "absent remainder has nonzero position");
3760
- (0, import_protocol6.normalizeDelivery)(ds[2 * i], { empty: true });
3761
- (0, import_protocol6.normalizeDelivery)(ds[2 * i + 1], { empty: true });
3984
+ (0, import_protocol8.normalizeOpaqueDelivery)(ds[2 * i], { empty: true });
3985
+ (0, import_protocol8.normalizeOpaqueDelivery)(ds[2 * i + 1], { empty: true });
3762
3986
  } else {
3763
3987
  add(log, tree, index(leaves[i]), c, "owner", ds[2 * i]);
3764
3988
  add(log, tree, index(leaves[i]), c, "spender", ds[2 * i + 1]);
@@ -3770,7 +3994,7 @@ function extractDeliveryCandidates(logs, context, confirmedThrough) {
3770
3994
  }
3771
3995
  async function decryptCandidates(logs, options) {
3772
3996
  contextCheck(options.context);
3773
- (0, import_core8.decodeAddress)(import_core8.ReceivingAddress.fromIdentity(options.viewing.walletIdentity).toString());
3997
+ (0, import_core11.decodeAddress)(import_core11.ReceivingAddress.fromIdentity(options.viewing.walletIdentity).toString());
3774
3998
  check4(
3775
3999
  !logs.some(
3776
4000
  (log) => log.address.toLowerCase() === options.context.protocolAddress.toLowerCase() && log.removed
@@ -4058,7 +4282,7 @@ function mergeReceiptRecords(state, batch) {
4058
4282
  }
4059
4283
 
4060
4284
  // src/session/store.ts
4061
- var import_core9 = require("@invisi-labs/core");
4285
+ var import_core12 = require("@invisi-labs/core");
4062
4286
  var import_sha2562 = require("@noble/hashes/sha256");
4063
4287
  var import_utils2 = require("@noble/hashes/utils");
4064
4288
  function scopeKey(scope) {
@@ -4074,7 +4298,7 @@ function scopeKey(scope) {
4074
4298
  return (0, import_utils2.bytesToHex)(
4075
4299
  (0, import_sha2562.sha256)(
4076
4300
  (0, import_utils2.utf8ToBytes)(
4077
- "Invisi.WalletScope.v2:" + JSON.stringify([
4301
+ "Invisi.WalletScope.v1:" + JSON.stringify([
4078
4302
  scope.namespace,
4079
4303
  scope.chainId.toString(),
4080
4304
  scope.protocolAddress.toLowerCase(),
@@ -4111,12 +4335,12 @@ function validateState(state, scope, revision) {
4111
4335
  throw new Error("invalid spent state");
4112
4336
  }
4113
4337
  for (const record of Object.values(state.credits)) {
4114
- const note = (0, import_core9.normalizeCreditDeliveryState)(record.state).note;
4338
+ const note = (0, import_core12.normalizeCreditDeliveryState)(record.state).note;
4115
4339
  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))
4116
4340
  throw new Error("invalid stored Credit context or role");
4117
4341
  }
4118
4342
  for (const record of [...Object.values(state.notes), ...Object.values(state.credits)]) {
4119
- if (!/^0x[0-9a-fA-F]{64}$/.test(record.commitment) || BigInt(record.commitment) <= 0n || BigInt(record.commitment) >= import_core9.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))
4343
+ if (!/^0x[0-9a-fA-F]{64}$/.test(record.commitment) || BigInt(record.commitment) <= 0n || BigInt(record.commitment) >= import_core12.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))
4120
4344
  throw new Error("invalid stored receipt position");
4121
4345
  }
4122
4346
  for (const [id, operation] of Object.entries(state.reservations))
@@ -4177,16 +4401,16 @@ var TransactionalWalletStore = class {
4177
4401
  };
4178
4402
 
4179
4403
  // src/session/Operation.ts
4180
- var import_core11 = require("@invisi-labs/core");
4181
- var import_protocol8 = require("@invisi-labs/protocol");
4404
+ var import_core14 = require("@invisi-labs/core");
4405
+ var import_protocol10 = require("@invisi-labs/protocol");
4182
4406
 
4183
4407
  // src/session/operation-confirmation.ts
4184
- var import_core10 = require("@invisi-labs/core");
4408
+ var import_core13 = require("@invisi-labs/core");
4185
4409
 
4186
4410
  // src/session/aggregate-receipt.ts
4187
- var import_ethers11 = require("ethers");
4411
+ var import_ethers12 = require("ethers");
4188
4412
  var import_contracts3 = require("@invisi-labs/protocol/contracts");
4189
- var import_protocol7 = require("@invisi-labs/protocol");
4413
+ var import_protocol9 = require("@invisi-labs/protocol");
4190
4414
  var aggregateAbi = import_contracts3.invisiProtocolAbi.filter(
4191
4415
  (item) => item.type === "function" && item.name === "transact" && item.inputs.length === 2 && item.inputs[1]?.type === "tuple[]"
4192
4416
  );
@@ -4195,7 +4419,7 @@ function matchesAggregateTransaction(saved, transaction) {
4195
4419
  if (plan.statement?.target !== "noir-recursive" || !plan.params || !saved.externalized || transaction.to.toLowerCase() !== plan.context.protocolAddress.toLowerCase() || transaction.value !== 0n)
4196
4420
  return false;
4197
4421
  try {
4198
- const decoded = new import_ethers11.Interface(aggregateAbi).parseTransaction({ data: transaction.data }), requests = decoded.args[1].map((request) => {
4422
+ const decoded = new import_ethers12.Interface(aggregateAbi).parseTransaction({ data: transaction.data }), requests = decoded.args[1].map((request) => {
4199
4423
  const member = request.toObject(true);
4200
4424
  member.params.sources = request.params.sources.map(
4201
4425
  (source) => source.toObject(true)
@@ -4214,7 +4438,7 @@ function matchesAggregateTransaction(saved, transaction) {
4214
4438
  });
4215
4439
  if (decoded.name !== "transact" || !Array.isArray(requests) || ![4, 8, 12].includes(requests.length))
4216
4440
  return false;
4217
- const canonical2 = (request) => (0, import_protocol7.buildTransactCalldata)({
4441
+ const canonical2 = (request) => (0, import_protocol9.buildTransactCalldata)({
4218
4442
  context: { ...plan.context, tokenAddress: request.params.token },
4219
4443
  proof: "0x00",
4220
4444
  params: request.params,
@@ -4222,7 +4446,7 @@ function matchesAggregateTransaction(saved, transaction) {
4222
4446
  funding: request.funding,
4223
4447
  caller: transaction.from
4224
4448
  }).data.toLowerCase();
4225
- const expected = (0, import_protocol7.buildTransactCalldata)({
4449
+ const expected = (0, import_protocol9.buildTransactCalldata)({
4226
4450
  context: plan.context,
4227
4451
  proof: "0x00",
4228
4452
  params: plan.params,
@@ -4401,7 +4625,7 @@ async function confirmOperation(runtime, id, generation, hash2, signal) {
4401
4625
  if (credits.length) {
4402
4626
  const nullifiers = await Promise.all(
4403
4627
  credits.map(
4404
- async (record) => (0, import_core10.computeCreditNullifier)(
4628
+ async (record) => (0, import_core13.computeCreditNullifier)(
4405
4629
  plan.context.chainId,
4406
4630
  record.state.note.creditSharedKey,
4407
4631
  await creditLeaf(record.state)
@@ -4702,6 +4926,81 @@ var Operation = class _Operation {
4702
4926
  throw error;
4703
4927
  }
4704
4928
  }
4929
+ async signingCurrent() {
4930
+ await this.#runtime.store.transaction(this.#runtime.scope, (state) => {
4931
+ check7(!state.invalidated, "wallet state requires canonical resync");
4932
+ this.current(state.operations[this.id]);
4933
+ });
4934
+ }
4935
+ /** Public EIP-712 request; signing transport and key custody belong to the caller. */
4936
+ async exportWalletAuthorization(options = {}) {
4937
+ options.signal?.throwIfAborted();
4938
+ await this.signingCurrent();
4939
+ check7(
4940
+ requiresAuthorization(this.#saved.plan),
4941
+ "operation does not require wallet authorization"
4942
+ );
4943
+ check7(
4944
+ ["prepared", "authorized"].includes(this.status),
4945
+ "operation is not awaiting wallet authorization"
4946
+ );
4947
+ return walletSigningRequest(this.#saved.plan);
4948
+ }
4949
+ /** Attach an external EOA signature, retaining its witness only at the private proving boundary. */
4950
+ async withWalletSignature(signature, options = {}) {
4951
+ options.signal?.throwIfAborted();
4952
+ await this.signingCurrent();
4953
+ check7(
4954
+ requiresAuthorization(this.#saved.plan),
4955
+ "operation does not require wallet authorization"
4956
+ );
4957
+ check7(
4958
+ ["prepared", "authorized"].includes(this.status),
4959
+ "operation is not awaiting wallet authorization"
4960
+ );
4961
+ const authorization = await acceptWalletSignature(this.#saved.plan, signature);
4962
+ return this.stage(
4963
+ "wallet authorization",
4964
+ ["prepared", "authorized"],
4965
+ async () => ({ status: "authorized", authorization }),
4966
+ options.signal
4967
+ );
4968
+ }
4969
+ /** Export only after proving when the funding authorization binds an actual proof hash. */
4970
+ async exportFundingAuthorization(options = {}) {
4971
+ options.signal?.throwIfAborted();
4972
+ await this.signingCurrent();
4973
+ check7(
4974
+ this.status === (this.kind === "shield" ? "prepared" : "proved"),
4975
+ "operation is not awaiting funding authorization or proof"
4976
+ );
4977
+ const request = await fundingSigningRequest(this.#saved, this.#runtime);
4978
+ options.signal?.throwIfAborted();
4979
+ await this.signingCurrent();
4980
+ return request;
4981
+ }
4982
+ /** Verify an external signature against this handle's own frozen operation. */
4983
+ async withFundingSignature(signature, options = {}) {
4984
+ options.signal?.throwIfAborted();
4985
+ await this.signingCurrent();
4986
+ check7(
4987
+ this.status === (this.kind === "shield" ? "prepared" : "proved"),
4988
+ "operation is not awaiting funding authorization or proof"
4989
+ );
4990
+ const verified = await acceptFundingSignature(this.#saved, signature, this.#runtime);
4991
+ return this.stage(
4992
+ "funding authorization",
4993
+ this.kind === "shield" ? ["prepared"] : ["proved"],
4994
+ async (saved) => {
4995
+ saved.plan.funding.signature = verified;
4996
+ if (saved.plan.statement?.target !== "noir-recursive") {
4997
+ saved.call = await this.encode(saved);
4998
+ }
4999
+ return { status: "ready" };
5000
+ },
5001
+ options.signal
5002
+ );
5003
+ }
4705
5004
  async authorize(options = {}) {
4706
5005
  check7(
4707
5006
  requiresAuthorization(this.#saved.plan),
@@ -4722,7 +5021,8 @@ var Operation = class _Operation {
4722
5021
  options.signal
4723
5022
  );
4724
5023
  }
4725
- async prove(options = {}) {
5024
+ async prove(suppliedOptions = {}) {
5025
+ const options = { ...suppliedOptions };
4726
5026
  const plan = this.#saved.plan;
4727
5027
  check7(plan.kind !== "shield", "shield requires no proof");
4728
5028
  if (requiresAuthorization(plan)) {
@@ -4733,7 +5033,7 @@ var Operation = class _Operation {
4733
5033
  }
4734
5034
  const prover = this.#runtime.options.prover;
4735
5035
  if (!prover) {
4736
- throw new import_core11.MissingCapabilityError("prover");
5036
+ throw new import_core14.MissingCapabilityError("prover");
4737
5037
  }
4738
5038
  return this.stage(
4739
5039
  "proof",
@@ -4767,7 +5067,7 @@ var Operation = class _Operation {
4767
5067
  check7(p.kind === "shield" || this.#saved.proof, "proof must precede funding authorization");
4768
5068
  const signer = this.#runtime.options.fundingSigner;
4769
5069
  if (!signer) {
4770
- throw new import_core11.MissingCapabilityError("EVM funding signer");
5070
+ throw new import_core14.MissingCapabilityError("EVM funding signer");
4771
5071
  }
4772
5072
  check7(
4773
5073
  signer.address.toLowerCase() === p.funding.payer.toLowerCase(),
@@ -4869,7 +5169,7 @@ var Operation = class _Operation {
4869
5169
  request: {
4870
5170
  params: structuredClone(plan.params),
4871
5171
  deliveries: plan.outputs.map((output) => structuredClone(output.delivery)),
4872
- funding: (0, import_protocol8.normalizeFunding)(plan.funding)
5172
+ funding: (0, import_protocol10.normalizeFunding)(plan.funding)
4873
5173
  }
4874
5174
  };
4875
5175
  });
@@ -4957,8 +5257,66 @@ var Operation = class _Operation {
4957
5257
  };
4958
5258
 
4959
5259
  // src/session/WalletSession.ts
4960
- var import_ethers12 = require("ethers");
4961
- var import_core14 = require("@invisi-labs/core");
5260
+ var import_core17 = require("@invisi-labs/core");
5261
+
5262
+ // src/session/history.ts
5263
+ function walletHistory(state, options = {}) {
5264
+ const token = options.tokenAddress?.toLowerCase();
5265
+ const outgoing = [];
5266
+ for (const record of Object.values(state.operations)) {
5267
+ const saved = record.payload;
5268
+ if (!saved?.plan || token && saved.plan.context.tokenAddress.toLowerCase() !== token) {
5269
+ continue;
5270
+ }
5271
+ const plan = saved.plan;
5272
+ outgoing.push({
5273
+ ...operationPreview(plan, record.status),
5274
+ preparedAt: { ...plan.preparedAt },
5275
+ ...record.transactionHash ? { transactionHash: record.transactionHash } : {},
5276
+ outputs: plan.outputs.map((output) => ({
5277
+ to: output.address,
5278
+ amount: output.note.amount,
5279
+ change: output.change,
5280
+ commitment: output.commitment,
5281
+ payload: Uint8Array.from(output.note.payload ?? [])
5282
+ }))
5283
+ });
5284
+ }
5285
+ outgoing.sort(
5286
+ (a, b) => a.preparedAt.number === b.preparedAt.number ? a.id.localeCompare(b.id) : a.preparedAt.number > b.preparedAt.number ? -1 : 1
5287
+ );
5288
+ const incoming = [];
5289
+ for (const record of Object.values(state.notes)) {
5290
+ const tokenAddress = `0x${record.note.tokenAddress.toString(16).padStart(40, "0")}`;
5291
+ if (token && tokenAddress !== token) continue;
5292
+ incoming.push({
5293
+ commitment: record.commitment,
5294
+ treeId: record.treeId,
5295
+ leafIndex: record.leafIndex,
5296
+ blockNumber: record.blockNumber,
5297
+ blockHash: record.blockHash,
5298
+ ...record.transactionHash ? { transactionHash: record.transactionHash } : {},
5299
+ tokenAddress,
5300
+ amount: record.note.amount,
5301
+ payload: Uint8Array.from(record.note.payload ?? []),
5302
+ spent: record.spent
5303
+ });
5304
+ }
5305
+ incoming.sort(
5306
+ (a, b) => a.blockNumber === b.blockNumber ? a.commitment.localeCompare(b.commitment) : a.blockNumber > b.blockNumber ? -1 : 1
5307
+ );
5308
+ return structuredClone({
5309
+ revision: state.revision,
5310
+ checkedThrough: state.discoveryThrough ?? null,
5311
+ invalidated: !!state.invalidated,
5312
+ outgoing,
5313
+ incoming
5314
+ });
5315
+ }
5316
+
5317
+ // src/session/WalletSession.ts
5318
+ var import_ethers13 = require("ethers");
5319
+ var import_core18 = require("@invisi-labs/core");
4962
5320
 
4963
5321
  // src/session/refresh.ts
4964
5322
  async function refreshWallet(runtime, options) {
@@ -5015,10 +5373,10 @@ async function refreshWallet(runtime, options) {
5015
5373
  }
5016
5374
 
5017
5375
  // src/session/WalletUpdater.ts
5018
- var import_core13 = require("@invisi-labs/core");
5376
+ var import_core16 = require("@invisi-labs/core");
5019
5377
 
5020
5378
  // src/session/inventory.ts
5021
- var import_core12 = require("@invisi-labs/core");
5379
+ var import_core15 = require("@invisi-labs/core");
5022
5380
  var CanonicalConflict = class extends Error {
5023
5381
  };
5024
5382
  var CreditMetadataMismatch = class extends Error {
@@ -5070,10 +5428,10 @@ var InventoryProcessor = class {
5070
5428
  }
5071
5429
  async creditNullifier(state) {
5072
5430
  const n = state.note;
5073
- return (0, import_core12.computeCreditNullifier)(
5431
+ return (0, import_core15.computeCreditNullifier)(
5074
5432
  n.chainId,
5075
5433
  n.creditSharedKey,
5076
- await (0, import_core12.computeCreditCommitment)(
5434
+ await (0, import_core15.computeCreditCommitment)(
5077
5435
  n.chainId,
5078
5436
  n.spenderWalletIdentity,
5079
5437
  n.ownerWalletIdentity,
@@ -5083,7 +5441,7 @@ var InventoryProcessor = class {
5083
5441
  n.amount,
5084
5442
  n.nonce,
5085
5443
  n.expiry,
5086
- await (0, import_core12.computeCreditSharedKeyHash)(n.creditSharedKey),
5444
+ await (0, import_core15.computeCreditSharedKeyHash)(n.creditSharedKey),
5087
5445
  n
5088
5446
  )
5089
5447
  );
@@ -5147,7 +5505,7 @@ var InventoryProcessor = class {
5147
5505
  previous.state.note.expiry === params.expiries[index2],
5148
5506
  "draw recovery expiry mismatch"
5149
5507
  );
5150
- const remainder = await (0, import_core12.recoverCreditRemainder)(
5508
+ const remainder = await (0, import_core15.recoverCreditRemainder)(
5151
5509
  context,
5152
5510
  previous.state.note,
5153
5511
  {
@@ -5163,7 +5521,7 @@ var InventoryProcessor = class {
5163
5521
  const child = foundByKey.get(
5164
5522
  params.remainderCommitments[index2] + ":" + previous.role
5165
5523
  );
5166
- const memo = child?.state.note.memo;
5524
+ const payload = child?.state.note.payload;
5167
5525
  const record = {
5168
5526
  commitment: params.remainderCommitments[index2],
5169
5527
  treeId: BigInt(args.outputTreeId),
@@ -5176,7 +5534,7 @@ var InventoryProcessor = class {
5176
5534
  ...clone(previous.state),
5177
5535
  note: {
5178
5536
  ...remainder,
5179
- ...memo === void 0 ? {} : { memo: (0, import_core12.encodeMemo)(memo) }
5537
+ ...payload === void 0 ? {} : { payload: clone(payload) }
5180
5538
  }
5181
5539
  },
5182
5540
  receivingKeySource: { kind: "draw", commitment: previous.commitment },
@@ -5255,7 +5613,7 @@ var WalletUpdater = class extends InventoryProcessor {
5255
5613
  }
5256
5614
  async run(batch, options) {
5257
5615
  const { reader, protocol, scope, store, viewing } = this.#options;
5258
- const nullifier = import_core13.NullifierCapability.fromIdentity(scope.walletIdentity);
5616
+ const nullifier = import_core16.NullifierCapability.fromIdentity(scope.walletIdentity);
5259
5617
  const { signal, reset } = options;
5260
5618
  signal?.throwIfAborted();
5261
5619
  const context = protocol.context;
@@ -5424,6 +5782,7 @@ var WalletUpdater = class extends InventoryProcessor {
5424
5782
  draft.notes = {};
5425
5783
  draft.credits = {};
5426
5784
  delete draft.observedThrough;
5785
+ delete draft.discoveryThrough;
5427
5786
  delete draft.invalidated;
5428
5787
  }
5429
5788
  mergeReceiptRecords(draft, { notes, credits, checkpoint: block });
@@ -5455,13 +5814,38 @@ var WalletUpdater = class extends InventoryProcessor {
5455
5814
  var WalletSession = class {
5456
5815
  #options;
5457
5816
  #updater;
5817
+ #listeners = /* @__PURE__ */ new Set();
5458
5818
  #sending = /* @__PURE__ */ new Set();
5459
5819
  #refreshQueue = Promise.resolve();
5460
5820
  scope;
5461
5821
  protocol;
5462
5822
  constructor(options) {
5823
+ if ("ciphertextCodecs" in options || "envelopeVersion" in options) {
5824
+ throw new Error(
5825
+ "SDK envelope versions are fixed; encode application payload bytes externally"
5826
+ );
5827
+ }
5828
+ const store = options.store;
5463
5829
  this.#options = {
5464
5830
  ...options,
5831
+ ...store ? {
5832
+ store: {
5833
+ persistent: store.persistent,
5834
+ readSnapshot: (scope) => store.readSnapshot(scope),
5835
+ transaction: async (scope, action) => {
5836
+ const result = await store.transaction(scope, action);
5837
+ if (scopeKey(scope) === scopeKey(this.scope)) {
5838
+ for (const listener of [...this.#listeners]) {
5839
+ try {
5840
+ listener();
5841
+ } catch {
5842
+ }
5843
+ }
5844
+ }
5845
+ return result;
5846
+ }
5847
+ }
5848
+ } : {},
5465
5849
  ...options.gasBenchmarks ? { gasBenchmarks: copyGasBenchmarks(options.gasBenchmarks) } : {}
5466
5850
  };
5467
5851
  this.protocol = options.protocol;
@@ -5476,7 +5860,7 @@ var WalletSession = class {
5476
5860
  /** Delegate sending to ethers; managed operations additionally retain the resulting hash. */
5477
5861
  async sendTransaction(input, options = {}) {
5478
5862
  const signer = this.#options.signer;
5479
- if (!signer) throw new import_core14.MissingCapabilityError("ethers signer");
5863
+ if (!signer) throw new import_core18.MissingCapabilityError("ethers signer");
5480
5864
  const operation = input instanceof Operation ? input : void 0;
5481
5865
  if (operation && this.#sending.has(operation.id))
5482
5866
  throw new Error("operation is already being sent by this session");
@@ -5484,12 +5868,12 @@ var WalletSession = class {
5484
5868
  const overrides = { ...options };
5485
5869
  const plain = operation ? void 0 : { ...input };
5486
5870
  try {
5487
- if (!signer.provider) throw new import_core14.MissingCapabilityError("connected ethers signer");
5871
+ if (!signer.provider) throw new import_core18.MissingCapabilityError("connected ethers signer");
5488
5872
  if ((await signer.provider.getNetwork()).chainId !== this.protocol.context.chainId) {
5489
5873
  throw new Error("signer-provider session chain mismatch");
5490
5874
  }
5491
5875
  if (operation) {
5492
- if (!this.#options.store) throw new import_core14.MissingCapabilityError("wallet store");
5876
+ if (!this.#options.store) throw new import_core18.MissingCapabilityError("wallet store");
5493
5877
  const state = await this.#options.store.readSnapshot(this.scope);
5494
5878
  if (!state.operations[operation.id])
5495
5879
  throw new Error("operation does not belong to this wallet session");
@@ -5520,25 +5904,30 @@ var WalletSession = class {
5520
5904
  runtime(tokenAddress) {
5521
5905
  const options = this.#options;
5522
5906
  if (!options.store) {
5523
- throw new import_core14.MissingCapabilityError("wallet store");
5907
+ throw new import_core18.MissingCapabilityError("wallet store");
5524
5908
  }
5525
5909
  if (!options.chainReader) {
5526
- throw new import_core14.MissingCapabilityError("chain reader");
5910
+ throw new import_core18.MissingCapabilityError("chain reader");
5527
5911
  }
5528
5912
  return {
5529
5913
  options: { ...options, protocol: options.protocol.forToken(tokenAddress) },
5530
5914
  scope: this.scope,
5531
5915
  store: options.store,
5532
5916
  reader: options.chainReader,
5533
- authorization: () => options.authorization ?? options.wallet.exportAuthorization()
5917
+ authorization: () => {
5918
+ if (options.authorization) return options.authorization;
5919
+ if (options.wallet instanceof import_core17.Wallet) return options.wallet.exportAuthorization();
5920
+ throw new import_core18.MissingCapabilityError("wallet authorization");
5921
+ }
5534
5922
  };
5535
5923
  }
5536
5924
  updater() {
5537
5925
  if (this.#updater) return this.#updater;
5538
5926
  const options = this.#options;
5539
5927
  if (!options.store || !options.chainReader || !options.protocolRpc)
5540
- throw new import_core14.MissingCapabilityError("wallet store, chain reader and Protocol RPC");
5541
- const viewing = options.viewing ?? options.wallet.getViewer();
5928
+ throw new import_core18.MissingCapabilityError("wallet store, chain reader and Protocol RPC");
5929
+ const viewing = options.viewing ?? (options.wallet instanceof import_core17.Wallet ? options.wallet.getViewer() : void 0);
5930
+ if (!viewing) throw new import_core18.MissingCapabilityError("viewing");
5542
5931
  const receivingAddress = options.wallet.getReceivingAddress();
5543
5932
  const receivingKeys = receivingAddress.kind === "long" ? receivingAddress.getReceivingKeys() : void 0;
5544
5933
  this.#updater = new WalletUpdater({
@@ -5552,26 +5941,43 @@ var WalletSession = class {
5552
5941
  });
5553
5942
  return this.#updater;
5554
5943
  }
5944
+ /** Local session commits only. Host applications own cross-tab/store subscriptions. */
5945
+ subscribe(listener) {
5946
+ this.#listeners.add(listener);
5947
+ return () => {
5948
+ this.#listeners.delete(listener);
5949
+ };
5950
+ }
5951
+ /** Detached history for wallet UI; construction records do not imply chain success. */
5952
+ async getHistory(options = {}) {
5953
+ const tokenAddress = options.tokenAddress ? (0, import_ethers13.getAddress)(options.tokenAddress) : void 0;
5954
+ const store = this.#options.store;
5955
+ if (!store) throw new import_core18.MissingCapabilityError("wallet store");
5956
+ return walletHistory(
5957
+ await store.readSnapshot(this.scope),
5958
+ tokenAddress ? { tokenAddress } : {}
5959
+ );
5960
+ }
5555
5961
  async balance(tokenAddress) {
5556
5962
  const store = this.#options.store;
5557
5963
  if (!store) {
5558
- throw new import_core14.MissingCapabilityError("wallet store");
5964
+ throw new import_core18.MissingCapabilityError("wallet store");
5559
5965
  }
5560
- return walletBalance(await store.readSnapshot(this.scope), (0, import_ethers12.getAddress)(tokenAddress));
5966
+ return walletBalance(await store.readSnapshot(this.scope), (0, import_ethers13.getAddress)(tokenAddress));
5561
5967
  }
5562
5968
  /** Pull explicitly selected indexed pages; no chain scanning or public tree reconstruction. */
5563
5969
  async refresh(options) {
5564
5970
  const provider = this.#options.protocolRpc;
5565
5971
  const reader = this.#options.chainReader;
5566
- if (!reader) throw new import_core14.MissingCapabilityError("chain reader");
5567
- if (!provider) throw new import_core14.MissingCapabilityError("Protocol RPC");
5972
+ if (!reader) throw new import_core18.MissingCapabilityError("chain reader");
5973
+ if (!provider) throw new import_core18.MissingCapabilityError("Protocol RPC");
5568
5974
  const updater = this.updater();
5569
5975
  const request = {
5570
5976
  ...options,
5571
5977
  ...options.detectionKey ? { detectionKey: options.detectionKey.slice() } : {}
5572
5978
  };
5573
- const task = this.#refreshQueue.then(
5574
- () => refreshWallet(
5979
+ const task = this.#refreshQueue.then(async () => {
5980
+ const result = await refreshWallet(
5575
5981
  {
5576
5982
  provider,
5577
5983
  reader,
@@ -5580,8 +5986,21 @@ var WalletSession = class {
5580
5986
  apply: (batch, settings) => updater.applyUpdates(batch, settings)
5581
5987
  },
5582
5988
  request
5583
- )
5584
- );
5989
+ );
5990
+ await this.#options.store.transaction(this.scope, (state) => {
5991
+ request.signal?.throwIfAborted();
5992
+ if (state.invalidated) throw new Error("wallet requires canonical resync");
5993
+ const previous = state.discoveryThrough;
5994
+ const start = previous ? previous.number + 1n : this.protocol.deployment.protocol.fromBlock;
5995
+ if (request.fromBlock <= start && (!previous || result.block.number >= previous.number)) {
5996
+ state.discoveryThrough = {
5997
+ number: result.block.number,
5998
+ hash: result.block.hash
5999
+ };
6000
+ }
6001
+ });
6002
+ return result;
6003
+ });
5585
6004
  this.#refreshQueue = task.catch(() => {
5586
6005
  });
5587
6006
  return task;
@@ -5592,38 +6011,43 @@ var WalletSession = class {
5592
6011
  options: runtime.options,
5593
6012
  scope: runtime.scope,
5594
6013
  reader: runtime.reader,
5595
- authorization: runtime.authorization,
5596
6014
  readInventory: () => runtime.store.readSnapshot(runtime.scope),
5597
6015
  complete: (plan) => Operation.prepare(runtime, plan)
5598
6016
  });
5599
6017
  }
5600
6018
  async prepareTransfer(options) {
5601
- return this.preparation(options.tokenAddress).prepareTransfer(options);
5602
- }
5603
- async prepareWithdraw(options) {
5604
- return this.preparation(options.tokenAddress).prepareWithdraw(options);
5605
- }
5606
- async prepareSweep(options) {
5607
- return this.preparation(options.tokenAddress).prepareSweep(options);
6019
+ return prepareWithErrors("prepareTransfer", async () => {
6020
+ return this.preparation(options.tokenAddress).prepareTransfer(options);
6021
+ });
5608
6022
  }
5609
6023
  async prepareShield(options) {
5610
- return this.preparation(options.tokenAddress).prepareShield(options);
6024
+ return prepareWithErrors("prepareShield", async () => {
6025
+ return this.preparation(options.tokenAddress).prepareShield(options);
6026
+ });
5611
6027
  }
5612
6028
  async prepareCreateCredit(options) {
5613
- return this.preparation(options.tokenAddress).prepareCreateCredit(options);
6029
+ return prepareWithErrors("prepareCreateCredit", async () => {
6030
+ return this.preparation(options.tokenAddress).prepareCreateCredit(options);
6031
+ });
5614
6032
  }
5615
6033
  async prepareDrawCredit(options) {
5616
- return this.preparation(options.tokenAddress).prepareDrawCredit(options);
6034
+ return prepareWithErrors("prepareDrawCredit", async () => {
6035
+ return this.preparation(options.tokenAddress).prepareDrawCredit(options);
6036
+ });
5617
6037
  }
5618
6038
  async prepareTopUpCredit(options) {
5619
- return this.preparation(options.tokenAddress).prepareTopUpCredit(options);
6039
+ return prepareWithErrors("prepareTopUpCredit", async () => {
6040
+ return this.preparation(options.tokenAddress).prepareTopUpCredit(options);
6041
+ });
5620
6042
  }
5621
6043
  async prepareCancelCredit(options) {
5622
- return this.preparation(options.tokenAddress).prepareCancelCredit(options);
6044
+ return prepareWithErrors("prepareCancelCredit", async () => {
6045
+ return this.preparation(options.tokenAddress).prepareCancelCredit(options);
6046
+ });
5623
6047
  }
5624
6048
  async resume(operationId) {
5625
6049
  const store = this.#options.store;
5626
- if (!store) throw new import_core14.MissingCapabilityError("wallet store");
6050
+ if (!store) throw new import_core18.MissingCapabilityError("wallet store");
5627
6051
  const state = await store.readSnapshot(this.scope);
5628
6052
  const saved = state.operations[operationId]?.payload;
5629
6053
  if (!saved?.plan) throw new Error("operation not found");
@@ -5641,6 +6065,16 @@ var WalletSession = class {
5641
6065
  var Invisi = class {
5642
6066
  #options;
5643
6067
  constructor(options = {}) {
6068
+ if ("ciphertextCodecs" in options || "envelopeVersion" in options) {
6069
+ throw new Error(
6070
+ "SDK envelope versions are fixed; encode application payload bytes externally"
6071
+ );
6072
+ }
6073
+ if ("signer" in options || "fundingSigner" in options) {
6074
+ throw new Error(
6075
+ "Invisi does not accept signer defaults; export a signing request and attach the external signature"
6076
+ );
6077
+ }
5644
6078
  this.#options = {
5645
6079
  ...options,
5646
6080
  ...options.gasBenchmarks ? { gasBenchmarks: copyGasBenchmarks(options.gasBenchmarks) } : {}
@@ -5651,7 +6085,15 @@ var Invisi = class {
5651
6085
  return new WalletSession(await this.resolve(options));
5652
6086
  }
5653
6087
  async resolve(options) {
5654
- const allowed = ["wallet", "protocol", "namespace", "viewing", "authorization"];
6088
+ const allowed = [
6089
+ "wallet",
6090
+ "protocol",
6091
+ "namespace",
6092
+ "viewing",
6093
+ "authorization",
6094
+ "signer",
6095
+ "fundingSigner"
6096
+ ];
5655
6097
  for (const key of Object.keys(options)) {
5656
6098
  if (!allowed.includes(key))
5657
6099
  throw new Error(
@@ -5660,97 +6102,150 @@ var Invisi = class {
5660
6102
  }
5661
6103
  const selected = { ...options };
5662
6104
  const combined = { ...this.#options, ...selected };
5663
- const { provider, signer, ...runtime } = combined;
6105
+ const { provider, addressLookup = "protocol-rpc", ...runtime } = combined;
6106
+ if (addressLookup !== void 0 && addressLookup !== "chain" && addressLookup !== "protocol-rpc") {
6107
+ throw new Error("Invalid recipient address lookup source");
6108
+ }
5664
6109
  const reader = combined.chainReader ?? (provider ? new EthersChainReader(provider) : void 0);
5665
- if (!reader) throw new import_core15.MissingCapabilityError("chain reader");
6110
+ if (!reader) throw new import_core19.MissingCapabilityError("chain reader");
5666
6111
  if (provider && (await provider.getNetwork()).chainId !== selected.protocol.context.chainId)
5667
6112
  throw new Error("provider deployment chain mismatch");
5668
6113
  await selected.protocol.validateDeployment(reader);
5669
6114
  if (runtime.protocolRpc instanceof import_rpc.ProtocolRpcClient) {
5670
6115
  runtime.protocolRpc = runtime.protocolRpc.withReader(reader);
5671
6116
  }
5672
- if (signer) {
5673
- if (!provider) throw new import_core15.MissingCapabilityError("provider for signer");
5674
- if (!signer.provider) throw new Error("signer must be connected to a provider");
5675
- if ((await signer.provider.getNetwork()).chainId !== selected.protocol.context.chainId)
5676
- throw new Error("signer-provider deployment chain mismatch");
5677
- }
5678
6117
  const registry = selected.protocol.deployment.registry;
5679
- const fundingSigner = combined.fundingSigner ?? (signer ? await EthersFundingSigner.create(signer, selected.protocol.context.chainId) : void 0);
5680
6118
  const fundingVerifier = combined.fundingVerifier ?? (provider ? new EthersFundingVerifier(provider, selected.protocol.context.chainId) : void 0);
5681
- const addressResolver = combined.addressResolver ?? (provider && registry ? new EthersAddressResolver(
5682
- provider,
5683
- {
5684
- chainId: selected.protocol.context.chainId,
5685
- registryAddress: registry.address
5686
- },
5687
- registry.runtimeCodeHash
5688
- ).lookup : void 0);
6119
+ let addressResolver = combined.addressResolver;
6120
+ if (!addressResolver && registry) {
6121
+ if (addressLookup === "protocol-rpc") {
6122
+ const rpc = runtime.protocolRpc;
6123
+ addressResolver = async (identity) => {
6124
+ if (!(rpc instanceof import_rpc.ProtocolRpcClient)) {
6125
+ throw new import_core19.MissingCapabilityError("Protocol RPC recipient discovery");
6126
+ }
6127
+ const discovered = await rpc.getDeployment(selected.protocol.context.chainId);
6128
+ const expected = selected.protocol.deployment;
6129
+ 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)
6130
+ throw new Error("Recipient registry deployment pins mismatch");
6131
+ const registration = await rpc.getReceivingAddress(identity);
6132
+ return registration ? import_core19.ReceivingAddress.parse(registration.receivingAddress).getReceivingKeys() : null;
6133
+ };
6134
+ } else if (provider) {
6135
+ addressResolver = new EthersAddressResolver(
6136
+ provider,
6137
+ {
6138
+ chainId: selected.protocol.context.chainId,
6139
+ registryAddress: registry.address
6140
+ },
6141
+ registry.runtimeCodeHash
6142
+ ).lookup;
6143
+ }
6144
+ }
5689
6145
  return {
5690
6146
  ...runtime,
5691
6147
  chainReader: reader,
5692
- ...signer ? { signer } : {},
5693
- ...fundingSigner ? { fundingSigner } : {},
5694
6148
  ...fundingVerifier ? { fundingVerifier } : {},
5695
6149
  ...addressResolver ? { addressResolver } : {},
5696
6150
  protocol: selected.protocol
5697
6151
  };
5698
6152
  }
6153
+ /** Discover the selected chain's registry and prepare an independent signing request. */
6154
+ async prepareRegistration(options) {
6155
+ return prepareWithErrors("prepareRegistration", async () => {
6156
+ for (const key of Object.keys(options)) {
6157
+ if (!["wallet", "update", "deadline"].includes(key)) {
6158
+ throw new Error(`Unknown registration option: ${key}`);
6159
+ }
6160
+ }
6161
+ this.assertPublicIdentity(options);
6162
+ const selected = { ...options };
6163
+ const { provider, protocolRpc } = this.#options;
6164
+ if (!provider) throw new import_core19.MissingCapabilityError("ethers provider");
6165
+ if (!(protocolRpc instanceof import_rpc.ProtocolRpcClient)) {
6166
+ throw new import_core19.MissingCapabilityError("Protocol RPC deployment discovery");
6167
+ }
6168
+ const chainId = (await provider.getNetwork()).chainId;
6169
+ const deployment = await protocolRpc.getDeployment(chainId);
6170
+ if (!deployment.registry) {
6171
+ throw new import_core19.MissingCapabilityError("receiving-key registry deployment");
6172
+ }
6173
+ const registry = new AddressRegistry(
6174
+ { chainId, registryAddress: deployment.registry.address },
6175
+ { expectedRuntimeCodeHash: deployment.registry.runtimeCodeHash }
6176
+ );
6177
+ return registry.prepareRegistration({ ...selected, provider });
6178
+ });
6179
+ }
6180
+ assertPublicIdentity(options) {
6181
+ const { wallet } = options;
6182
+ if (!(wallet instanceof import_core19.PublicWallet) || ["getViewer", "getDetector", "exportAuthorization"].some((key) => key in wallet)) {
6183
+ throw new Error(
6184
+ "Independent preparation requires a PublicWallet without private capabilities"
6185
+ );
6186
+ }
6187
+ for (const key of ["authorization", "signer", "fundingSigner", "viewing"]) {
6188
+ if (key in options)
6189
+ throw new Error(`Independent preparation does not accept capability: ${key}`);
6190
+ }
6191
+ }
6192
+ resolveExplicit(options) {
6193
+ this.assertPublicIdentity(options);
6194
+ return this.resolve({ wallet: options.wallet, protocol: options.protocol });
6195
+ }
5699
6196
  async prepareTransfer(options) {
5700
- const { wallet, protocol, ...input } = options;
5701
- const request = structuredClone(input);
5702
- const runtime = await this.resolve({ wallet, protocol });
5703
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
5704
- return preparation.prepareTransfer(request);
5705
- }
5706
- async prepareWithdraw(options) {
5707
- const { wallet, protocol, ...input } = options;
5708
- const request = structuredClone(input);
5709
- const runtime = await this.resolve({ wallet, protocol });
5710
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
5711
- return preparation.prepareWithdraw(request);
5712
- }
5713
- async prepareSweep(options) {
5714
- const { wallet, protocol, ...input } = options;
5715
- const request = structuredClone(input);
5716
- const runtime = await this.resolve({ wallet, protocol });
5717
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
5718
- return preparation.prepareSweep(request);
6197
+ return prepareWithErrors("prepareTransfer", async () => {
6198
+ const { wallet, protocol, ...input } = options;
6199
+ const request = structuredClone(input);
6200
+ const runtime = await this.resolveExplicit(options);
6201
+ const preparation = explicitPreparation(runtime, request, request.tokenAddress);
6202
+ return preparation.prepareTransfer(request);
6203
+ });
5719
6204
  }
5720
6205
  async prepareShield(options) {
5721
- const { wallet, protocol, ...input } = options;
5722
- const request = structuredClone(input);
5723
- const runtime = await this.resolve({ wallet, protocol });
5724
- const preparation = explicitPreparation(runtime, {}, request.tokenAddress);
5725
- return preparation.prepareShield(request);
6206
+ return prepareWithErrors("prepareShield", async () => {
6207
+ const { wallet, protocol, ...input } = options;
6208
+ const request = structuredClone(input);
6209
+ const runtime = await this.resolveExplicit(options);
6210
+ const preparation = explicitPreparation(runtime, {}, request.tokenAddress);
6211
+ return preparation.prepareShield(request);
6212
+ });
5726
6213
  }
5727
6214
  async prepareCreateCredit(options) {
5728
- const { wallet, protocol, ...input } = options;
5729
- const request = structuredClone(input);
5730
- const runtime = await this.resolve({ wallet, protocol });
5731
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
5732
- return preparation.prepareCreateCredit(request);
6215
+ return prepareWithErrors("prepareCreateCredit", async () => {
6216
+ const { wallet, protocol, ...input } = options;
6217
+ const request = structuredClone(input);
6218
+ const runtime = await this.resolveExplicit(options);
6219
+ const preparation = explicitPreparation(runtime, request, request.tokenAddress);
6220
+ return preparation.prepareCreateCredit(request);
6221
+ });
5733
6222
  }
5734
6223
  async prepareDrawCredit(options) {
5735
- const { wallet, protocol, ...input } = options;
5736
- const request = structuredClone(input);
5737
- const runtime = await this.resolve({ wallet, protocol });
5738
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
5739
- return preparation.prepareDrawCredit(request);
6224
+ return prepareWithErrors("prepareDrawCredit", async () => {
6225
+ const { wallet, protocol, ...input } = options;
6226
+ const request = structuredClone(input);
6227
+ const runtime = await this.resolveExplicit(options);
6228
+ const preparation = explicitPreparation(runtime, request, request.tokenAddress);
6229
+ return preparation.prepareDrawCredit(request);
6230
+ });
5740
6231
  }
5741
6232
  async prepareTopUpCredit(options) {
5742
- const { wallet, protocol, ...input } = options;
5743
- const request = structuredClone(input);
5744
- const runtime = await this.resolve({ wallet, protocol });
5745
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
5746
- return preparation.prepareTopUpCredit(request);
6233
+ return prepareWithErrors("prepareTopUpCredit", async () => {
6234
+ const { wallet, protocol, ...input } = options;
6235
+ const request = structuredClone(input);
6236
+ const runtime = await this.resolveExplicit(options);
6237
+ const preparation = explicitPreparation(runtime, request, request.tokenAddress);
6238
+ return preparation.prepareTopUpCredit(request);
6239
+ });
5747
6240
  }
5748
6241
  async prepareCancelCredit(options) {
5749
- const { wallet, protocol, ...input } = options;
5750
- const request = structuredClone(input);
5751
- const runtime = await this.resolve({ wallet, protocol });
5752
- const preparation = explicitPreparation(runtime, request, request.tokenAddress);
5753
- return preparation.prepareCancelCredit(request);
6242
+ return prepareWithErrors("prepareCancelCredit", async () => {
6243
+ const { wallet, protocol, ...input } = options;
6244
+ const request = structuredClone(input);
6245
+ const runtime = await this.resolveExplicit(options);
6246
+ const preparation = explicitPreparation(runtime, request, request.tokenAddress);
6247
+ return preparation.prepareCancelCredit(request);
6248
+ });
5754
6249
  }
5755
6250
  toJSON() {
5756
6251
  return { invisi: true };
@@ -5761,8 +6256,8 @@ var Invisi = class {
5761
6256
  };
5762
6257
 
5763
6258
  // src/index.ts
5764
- var import_core16 = require("@invisi-labs/core");
5765
- var import_protocol10 = require("@invisi-labs/protocol");
6259
+ var import_core20 = require("@invisi-labs/core");
6260
+ var import_protocol11 = require("@invisi-labs/protocol");
5766
6261
  var import_rpc3 = require("@invisi-labs/rpc");
5767
6262
 
5768
6263
  // src/ProtocolRpcClient.ts
@@ -5773,21 +6268,6 @@ var ProtocolRpcClient2 = class extends import_rpc2.ProtocolRpcClient {
5773
6268
  }
5774
6269
  };
5775
6270
 
5776
- // src/AddressRegistry.ts
5777
- var import_protocol9 = require("@invisi-labs/protocol");
5778
- var AddressRegistry = class extends import_protocol9.AddressRegistry {
5779
- prepareRegistration(options) {
5780
- if ("provider" in options) {
5781
- const { provider, ...preparation } = options;
5782
- return super.prepareRegistration({
5783
- ...preparation,
5784
- reader: new EthersChainReader(provider)
5785
- });
5786
- }
5787
- return super.prepareRegistration(options);
5788
- }
5789
- };
5790
-
5791
6271
  // src/integrations/encrypted-codec.ts
5792
6272
  var EncryptedWalletCodec = class _EncryptedWalletCodec {
5793
6273
  #key;
@@ -5879,6 +6359,7 @@ var EncryptedWalletCodec = class _EncryptedWalletCodec {
5879
6359
  DisclosureVerifier,
5880
6360
  EncryptedWalletCodec,
5881
6361
  Invisi,
6362
+ InvisiError,
5882
6363
  MAX_MEMO_BYTES,
5883
6364
  MissingCapabilityError,
5884
6365
  NullifierCapability,
@@ -5887,14 +6368,22 @@ var EncryptedWalletCodec = class _EncryptedWalletCodec {
5887
6368
  Protocol,
5888
6369
  ProtocolRpcClient,
5889
6370
  ProtocolRpcError,
6371
+ PublicWallet,
5890
6372
  RegistrationOperation,
5891
6373
  ViewingCapability,
5892
6374
  Wallet,
5893
6375
  WalletAuthorization,
5894
6376
  WalletSession,
6377
+ computeCreditNullifier,
6378
+ creditPeriods,
6379
+ creditV1,
6380
+ decodeDeliveryHeader,
5895
6381
  decodeMemoText,
6382
+ decryptCandidates,
5896
6383
  defineGasBenchmarks,
5897
6384
  encodeMemo,
6385
+ fundsV1,
6386
+ readProtocolLogs,
5898
6387
  walletBalance
5899
6388
  });
5900
6389
  //# sourceMappingURL=index.cjs.map