@invisi-labs/sdk 1.1.3 → 1.1.5

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.
@@ -2,6 +2,53 @@ import {
2
2
  EthersChainReader
3
3
  } from "./chunk-4HMM7ZKY.js";
4
4
 
5
+ // src/errors.ts
6
+ import { MissingCapabilityError } from "@invisi-labs/core";
7
+ var InvisiError = class extends Error {
8
+ code;
9
+ operation;
10
+ address;
11
+ outputIndex;
12
+ field;
13
+ capability;
14
+ constructor(code, message, details = {}) {
15
+ super(message, { cause: details.cause });
16
+ this.name = "InvisiError";
17
+ this.code = code;
18
+ this.operation = details.operation;
19
+ this.address = details.address;
20
+ this.outputIndex = details.outputIndex;
21
+ this.field = details.field;
22
+ this.capability = details.capability;
23
+ }
24
+ };
25
+ async function prepareWithErrors(operation, prepare) {
26
+ try {
27
+ return await prepare();
28
+ } catch (cause) {
29
+ if (cause instanceof InvisiError) {
30
+ if (cause.operation) throw cause;
31
+ throw new InvisiError(cause.code, cause.message, {
32
+ operation,
33
+ ...cause.address === void 0 ? {} : { address: cause.address },
34
+ ...cause.outputIndex === void 0 ? {} : { outputIndex: cause.outputIndex },
35
+ ...cause.field === void 0 ? {} : { field: cause.field },
36
+ ...cause.capability === void 0 ? {} : { capability: cause.capability },
37
+ cause: cause.cause
38
+ });
39
+ }
40
+ throw new InvisiError(
41
+ cause instanceof MissingCapabilityError ? "MISSING_CAPABILITY" : "PREPARATION_FAILED",
42
+ cause instanceof Error ? cause.message : "Transaction preparation failed",
43
+ {
44
+ operation,
45
+ cause,
46
+ ...cause instanceof MissingCapabilityError ? { capability: cause.capability } : {}
47
+ }
48
+ );
49
+ }
50
+ }
51
+
5
52
  // src/session/state.ts
6
53
  function creditReservationKey(commitment) {
7
54
  return "credit:" + commitment;
@@ -78,11 +125,8 @@ import {
78
125
  transactUserPublicInputs
79
126
  } from "@invisi-labs/protocol";
80
127
  function requiresAuthorization(plan) {
81
- if (plan.kind === "sweep") {
82
- 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))
83
- throw new Error("invalid owner-only sweep plan");
84
- return false;
85
- }
128
+ 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);
129
+ if (ownerOnlyFullWithdrawal) return false;
86
130
  return !!plan.credit || plan.inputs.length > 0;
87
131
  }
88
132
  function inputFacts(plan) {
@@ -1909,12 +1953,13 @@ function readProtocolLogs(receipt, protocolAddress) {
1909
1953
  }
1910
1954
 
1911
1955
  // src/session/discovery.ts
1956
+ import { MAX_CIPHERTEXT_BYTES } from "@invisi-labs/core";
1912
1957
  import { isAddress } from "ethers";
1913
1958
  import {
1914
1959
  decodeAddress,
1915
1960
  ReceivingAddress
1916
1961
  } from "@invisi-labs/core";
1917
- import { normalizeDelivery } from "@invisi-labs/protocol";
1962
+ import { normalizeOpaqueDelivery } from "@invisi-labs/protocol";
1918
1963
  var EVENTS = /* @__PURE__ */ new Set([
1919
1964
  "CommitmentsInserted",
1920
1965
  "BatchOutputs",
@@ -2007,7 +2052,7 @@ function extractDeliveryCandidates(logs, context, confirmedThrough) {
2007
2052
  check(inserted, "delivery has no matching canonical insertion");
2008
2053
  if (commitment !== void 0)
2009
2054
  check(hash(commitment) === inserted, "delivery commitment conflicts with insertion");
2010
- const delivery = normalizeDelivery(wire, { credit: role !== "funds" });
2055
+ const delivery = normalizeOpaqueDelivery(wire);
2011
2056
  check(
2012
2057
  typeof token === "string" && isAddress(token) && BigInt(token) !== 0n,
2013
2058
  "invalid event token"
@@ -2038,7 +2083,7 @@ function extractDeliveryCandidates(logs, context, confirmedThrough) {
2038
2083
  add(log, tree, index(leaves[0]), fields.creditCommitment, "owner", ds[0]);
2039
2084
  add(log, tree, index(leaves[0]), fields.creditCommitment, "spender", ds[1]);
2040
2085
  if (hasChange) add(log, tree, index(leaves[1]), change, "funds", ds[2]);
2041
- else normalizeDelivery(ds[2], { empty: true });
2086
+ else normalizeOpaqueDelivery(ds[2], { empty: true });
2042
2087
  };
2043
2088
  for (const log of relevant) {
2044
2089
  const a = object(log.args);
@@ -2068,7 +2113,7 @@ function extractDeliveryCandidates(logs, context, confirmedThrough) {
2068
2113
  const length = Number.parseInt(packed.slice(offset, offset + 4), 16);
2069
2114
  offset += 4;
2070
2115
  check(
2071
- length >= 160 && length <= 416 && offset + length * 2 + 136 <= packed.length,
2116
+ length >= 1 && length <= MAX_CIPHERTEXT_BYTES && offset + length * 2 + 136 <= packed.length,
2072
2117
  "invalid packed delivery length"
2073
2118
  );
2074
2119
  const ciphertext = `0x${packed.slice(offset, offset + length * 2)}`;
@@ -2090,8 +2135,8 @@ function extractDeliveryCandidates(logs, context, confirmedThrough) {
2090
2135
  cs.forEach((c, i) => {
2091
2136
  if (BigInt(hash(c)) === 0n) {
2092
2137
  check(integer(leaves[i]) === 0n, "absent remainder has nonzero position");
2093
- normalizeDelivery(ds[2 * i], { empty: true });
2094
- normalizeDelivery(ds[2 * i + 1], { empty: true });
2138
+ normalizeOpaqueDelivery(ds[2 * i], { empty: true });
2139
+ normalizeOpaqueDelivery(ds[2 * i + 1], { empty: true });
2095
2140
  } else {
2096
2141
  add(log, tree, index(leaves[i]), c, "owner", ds[2 * i]);
2097
2142
  add(log, tree, index(leaves[i]), c, "spender", ds[2 * i + 1]);
@@ -2285,7 +2330,7 @@ function scopeKey(scope) {
2285
2330
  return bytesToHex(
2286
2331
  sha256(
2287
2332
  utf8ToBytes(
2288
- "Invisi.WalletScope.v2:" + JSON.stringify([
2333
+ "Invisi.WalletScope.v1:" + JSON.stringify([
2289
2334
  scope.namespace,
2290
2335
  scope.chainId.toString(),
2291
2336
  scope.protocolAddress.toLowerCase(),
@@ -2400,10 +2445,15 @@ var MemoryAtomicRecords = class {
2400
2445
  };
2401
2446
 
2402
2447
  // src/session/operation-execution.ts
2403
- import { verifyTypedData } from "ethers";
2448
+ import { Signature, SigningKey, getBytes as getBytes2, concat, verifyTypedData } from "ethers";
2404
2449
  import {
2405
- computeWalletAuthorizationDigest
2450
+ computeWalletAuthorizationDigest,
2451
+ AuthorizedStatement,
2452
+ computeWalletIdentity,
2453
+ createSigningRequest,
2454
+ walletAuthorizationTypedData
2406
2455
  } from "@invisi-labs/core";
2456
+ import { WitnessBuilder as WitnessBuilder2 } from "@invisi-labs/protocol";
2407
2457
 
2408
2458
  // src/session/proof-integrity.ts
2409
2459
  import { getBytes } from "ethers";
@@ -2439,16 +2489,34 @@ async function authorizeOperation(plan, provider) {
2439
2489
  return signed;
2440
2490
  }
2441
2491
  async function proveOperation(protocol, plan, authorization, prover, options) {
2492
+ if (Object.hasOwn(options, "target")) {
2493
+ throw new Error("prove accepts execution, not a backend target");
2494
+ }
2495
+ const { execution = "single", ...proverOptions } = options;
2496
+ if (execution !== "single" && execution !== "aggregate") {
2497
+ throw new Error("execution must be single or aggregate");
2498
+ }
2499
+ const frozen = statement(plan);
2500
+ if (execution === "aggregate" && (plan.kind !== "transfer" || plan.capacity !== 14)) {
2501
+ throw new Error("aggregate execution requires a 14-slot transfer");
2502
+ }
2503
+ if (plan.kind === "transfer") {
2504
+ plan.statement = WitnessBuilder2.statement(
2505
+ frozen.circuitName,
2506
+ frozen.expectedPublicInputs,
2507
+ execution === "aggregate" ? "noir-recursive" : "evm"
2508
+ );
2509
+ }
2442
2510
  const request = await witnessFor(protocol, plan, authorization);
2443
2511
  if (JSON.stringify({ ...request, witness: void 0 }) !== JSON.stringify(plan.statement))
2444
2512
  throw new Error("prepared proof statement changed");
2445
2513
  try {
2446
- const proof = publicProof(await prover.prove(request, options));
2514
+ const proof = publicProof(await prover.prove(request, proverOptions));
2447
2515
  sameStatement(proof, plan.statement);
2448
2516
  if (await prover.verify(
2449
2517
  structuredClone(proof),
2450
2518
  structuredClone(plan.statement),
2451
- options
2519
+ proverOptions
2452
2520
  ) !== true)
2453
2521
  throw new Error("proof verification failed");
2454
2522
  return proof;
@@ -2463,19 +2531,88 @@ async function signOperationFunding(saved, signer, verifier) {
2463
2531
  throw new Error("funding signer is not the payer");
2464
2532
  const typed = await fundingTypedData(saved);
2465
2533
  const signature = await signer.signTypedData(structuredClone(typed));
2534
+ return verifyFundingSignature(typed, payer, signature, verifier);
2535
+ }
2536
+ function walletSigningRequest(plan) {
2537
+ return createSigningRequest(walletAuthorizationTypedData(authorizationRequest(plan)));
2538
+ }
2539
+ async function acceptWalletSignature(plan, signature) {
2540
+ const request = authorizationRequest(plan);
2541
+ const digest = computeWalletAuthorizationDigest(request);
2542
+ try {
2543
+ const signed = Signature.from(signature);
2544
+ const publicKey = getBytes2(SigningKey.recoverPublicKey(digest, signed));
2545
+ const owner = await computeWalletIdentity(publicKey);
2546
+ if (owner.toLowerCase() !== plan.scope.walletIdentity.toLowerCase()) {
2547
+ throw new Error("wallet signature identity mismatch");
2548
+ }
2549
+ return new AuthorizedStatement(plan.scope.walletIdentity, digest, {
2550
+ public_key_x: Array.from(publicKey.slice(1, 33)),
2551
+ public_key_y: Array.from(publicKey.slice(33)),
2552
+ signature: Array.from(getBytes2(concat([signed.r, signed.s])))
2553
+ });
2554
+ } catch {
2555
+ throw new Error("invalid wallet authorization signature");
2556
+ }
2557
+ }
2558
+ async function fundingSigningRequest(saved, runtime) {
2559
+ const plan = saved.plan;
2560
+ if (plan.fundingMode !== "signature") {
2561
+ throw new Error("operation does not require a funding signature");
2562
+ }
2563
+ if (plan.kind !== "shield" && !saved.proof) {
2564
+ throw new Error("proof must precede funding authorization");
2565
+ }
2566
+ await runtime.options.protocol.validateDeployment(runtime.reader);
2567
+ const block = await runtime.reader.getBlock(await runtime.reader.getLatestBlockNumber());
2568
+ if (block.timestamp > plan.deadline || block.timestamp > (plan.funding.deadline ?? 0n)) {
2569
+ throw new Error("funding authorization expired");
2570
+ }
2571
+ if (await runtime.reader.getFundingNonce(plan.context, plan.funding.payer, block) !== plan.funding.sigNonce) {
2572
+ throw new Error("funding nonce changed; prepare again");
2573
+ }
2574
+ const request = createSigningRequest(await fundingTypedData(saved));
2575
+ if ((await runtime.reader.getBlock(block.number)).hash !== block.hash) {
2576
+ throw new Error("canonical funding block changed");
2577
+ }
2578
+ return request;
2579
+ }
2580
+ async function acceptFundingSignature(saved, signature, runtime) {
2581
+ if (!/^0x(?:[a-fA-F0-9]{2})+$/.test(signature)) {
2582
+ throw new Error("invalid EVM funding signature");
2583
+ }
2584
+ const request = await fundingSigningRequest(saved, runtime);
2585
+ return verifyFundingSignature(
2586
+ request,
2587
+ saved.plan.funding.payer,
2588
+ signature,
2589
+ runtime.options.fundingVerifier
2590
+ );
2591
+ }
2592
+ async function verifyFundingSignature(request, payer, signature, verifier) {
2593
+ if (!/^0x(?:[a-fA-F0-9]{2})+$/.test(signature)) {
2594
+ throw new Error("invalid EVM funding signature");
2595
+ }
2596
+ const types = Object.fromEntries(
2597
+ Object.entries(request.types).map(([name, fields]) => [name, [...fields]])
2598
+ );
2599
+ let validEOA = false;
2600
+ try {
2601
+ validEOA = verifyTypedData(request.domain, types, request.message, signature).toLowerCase() === payer.toLowerCase();
2602
+ if (validEOA) {
2603
+ signature = Signature.from(signature).serialized;
2604
+ }
2605
+ } catch {
2606
+ validEOA = false;
2607
+ }
2466
2608
  const valid = verifier ? await verifier.verifyTypedData({
2467
- ...structuredClone(typed),
2609
+ ...structuredClone(request),
2468
2610
  address: payer,
2469
2611
  signature
2470
- }) : verifyTypedData(
2471
- typed.domain,
2472
- Object.fromEntries(
2473
- Object.entries(typed.types).map(([name, fields]) => [name, [...fields]])
2474
- ),
2475
- typed.message,
2476
- signature
2477
- ).toLowerCase() === payer.toLowerCase();
2478
- if (valid !== true) throw new Error("invalid EVM funding signature");
2612
+ }) : validEOA;
2613
+ if (valid !== true) {
2614
+ throw new Error("invalid EVM funding signature");
2615
+ }
2479
2616
  return signature;
2480
2617
  }
2481
2618
 
@@ -2516,7 +2653,7 @@ function operationPreview(plan, status) {
2516
2653
  }
2517
2654
 
2518
2655
  // src/session/Operation.ts
2519
- import { MissingCapabilityError } from "@invisi-labs/core";
2656
+ import { MissingCapabilityError as MissingCapabilityError2 } from "@invisi-labs/core";
2520
2657
  import { normalizeFunding } from "@invisi-labs/protocol";
2521
2658
 
2522
2659
  // src/session/operation-confirmation.ts
@@ -3203,6 +3340,81 @@ var Operation = class _Operation {
3203
3340
  throw error;
3204
3341
  }
3205
3342
  }
3343
+ async signingCurrent() {
3344
+ await this.#runtime.store.transaction(this.#runtime.scope, (state) => {
3345
+ check5(!state.invalidated, "wallet state requires canonical resync");
3346
+ this.current(state.operations[this.id]);
3347
+ });
3348
+ }
3349
+ /** Public EIP-712 request; signing transport and key custody belong to the caller. */
3350
+ async exportWalletAuthorization(options = {}) {
3351
+ options.signal?.throwIfAborted();
3352
+ await this.signingCurrent();
3353
+ check5(
3354
+ requiresAuthorization(this.#saved.plan),
3355
+ "operation does not require wallet authorization"
3356
+ );
3357
+ check5(
3358
+ ["prepared", "authorized"].includes(this.status),
3359
+ "operation is not awaiting wallet authorization"
3360
+ );
3361
+ return walletSigningRequest(this.#saved.plan);
3362
+ }
3363
+ /** Attach an external EOA signature, retaining its witness only at the private proving boundary. */
3364
+ async withWalletSignature(signature, options = {}) {
3365
+ options.signal?.throwIfAborted();
3366
+ await this.signingCurrent();
3367
+ check5(
3368
+ requiresAuthorization(this.#saved.plan),
3369
+ "operation does not require wallet authorization"
3370
+ );
3371
+ check5(
3372
+ ["prepared", "authorized"].includes(this.status),
3373
+ "operation is not awaiting wallet authorization"
3374
+ );
3375
+ const authorization = await acceptWalletSignature(this.#saved.plan, signature);
3376
+ return this.stage(
3377
+ "wallet authorization",
3378
+ ["prepared", "authorized"],
3379
+ async () => ({ status: "authorized", authorization }),
3380
+ options.signal
3381
+ );
3382
+ }
3383
+ /** Export only after proving when the funding authorization binds an actual proof hash. */
3384
+ async exportFundingAuthorization(options = {}) {
3385
+ options.signal?.throwIfAborted();
3386
+ await this.signingCurrent();
3387
+ check5(
3388
+ this.status === (this.kind === "shield" ? "prepared" : "proved"),
3389
+ "operation is not awaiting funding authorization or proof"
3390
+ );
3391
+ const request = await fundingSigningRequest(this.#saved, this.#runtime);
3392
+ options.signal?.throwIfAborted();
3393
+ await this.signingCurrent();
3394
+ return request;
3395
+ }
3396
+ /** Verify an external signature against this handle's own frozen operation. */
3397
+ async withFundingSignature(signature, options = {}) {
3398
+ options.signal?.throwIfAborted();
3399
+ await this.signingCurrent();
3400
+ check5(
3401
+ this.status === (this.kind === "shield" ? "prepared" : "proved"),
3402
+ "operation is not awaiting funding authorization or proof"
3403
+ );
3404
+ const verified = await acceptFundingSignature(this.#saved, signature, this.#runtime);
3405
+ return this.stage(
3406
+ "funding authorization",
3407
+ this.kind === "shield" ? ["prepared"] : ["proved"],
3408
+ async (saved) => {
3409
+ saved.plan.funding.signature = verified;
3410
+ if (saved.plan.statement?.target !== "noir-recursive") {
3411
+ saved.call = await this.encode(saved);
3412
+ }
3413
+ return { status: "ready" };
3414
+ },
3415
+ options.signal
3416
+ );
3417
+ }
3206
3418
  async authorize(options = {}) {
3207
3419
  check5(
3208
3420
  requiresAuthorization(this.#saved.plan),
@@ -3223,7 +3435,8 @@ var Operation = class _Operation {
3223
3435
  options.signal
3224
3436
  );
3225
3437
  }
3226
- async prove(options = {}) {
3438
+ async prove(suppliedOptions = {}) {
3439
+ const options = { ...suppliedOptions };
3227
3440
  const plan = this.#saved.plan;
3228
3441
  check5(plan.kind !== "shield", "shield requires no proof");
3229
3442
  if (requiresAuthorization(plan)) {
@@ -3234,7 +3447,7 @@ var Operation = class _Operation {
3234
3447
  }
3235
3448
  const prover = this.#runtime.options.prover;
3236
3449
  if (!prover) {
3237
- throw new MissingCapabilityError("prover");
3450
+ throw new MissingCapabilityError2("prover");
3238
3451
  }
3239
3452
  return this.stage(
3240
3453
  "proof",
@@ -3268,7 +3481,7 @@ var Operation = class _Operation {
3268
3481
  check5(p.kind === "shield" || this.#saved.proof, "proof must precede funding authorization");
3269
3482
  const signer = this.#runtime.options.fundingSigner;
3270
3483
  if (!signer) {
3271
- throw new MissingCapabilityError("EVM funding signer");
3484
+ throw new MissingCapabilityError2("EVM funding signer");
3272
3485
  }
3273
3486
  check5(
3274
3487
  signer.address.toLowerCase() === p.funding.payer.toLowerCase(),
@@ -3457,25 +3670,142 @@ var Operation = class _Operation {
3457
3670
  }
3458
3671
  };
3459
3672
 
3673
+ // src/session/WalletSession.ts
3674
+ import { Wallet } from "@invisi-labs/core";
3675
+
3676
+ // src/session/history.ts
3677
+ function walletHistory(state, options = {}) {
3678
+ const token = options.tokenAddress?.toLowerCase();
3679
+ const outgoing = [];
3680
+ for (const record of Object.values(state.operations)) {
3681
+ const saved = record.payload;
3682
+ if (!saved?.plan || token && saved.plan.context.tokenAddress.toLowerCase() !== token) {
3683
+ continue;
3684
+ }
3685
+ const plan = saved.plan;
3686
+ outgoing.push({
3687
+ ...operationPreview(plan, record.status),
3688
+ preparedAt: { ...plan.preparedAt },
3689
+ ...record.transactionHash ? { transactionHash: record.transactionHash } : {},
3690
+ outputs: plan.outputs.map((output) => ({
3691
+ to: output.address,
3692
+ amount: output.note.amount,
3693
+ change: output.change,
3694
+ commitment: output.commitment,
3695
+ payload: Uint8Array.from(output.note.payload ?? [])
3696
+ }))
3697
+ });
3698
+ }
3699
+ outgoing.sort(
3700
+ (a, b) => a.preparedAt.number === b.preparedAt.number ? a.id.localeCompare(b.id) : a.preparedAt.number > b.preparedAt.number ? -1 : 1
3701
+ );
3702
+ const incoming = [];
3703
+ for (const record of Object.values(state.notes)) {
3704
+ const tokenAddress = `0x${record.note.tokenAddress.toString(16).padStart(40, "0")}`;
3705
+ if (token && tokenAddress !== token) continue;
3706
+ incoming.push({
3707
+ commitment: record.commitment,
3708
+ treeId: record.treeId,
3709
+ leafIndex: record.leafIndex,
3710
+ blockNumber: record.blockNumber,
3711
+ blockHash: record.blockHash,
3712
+ ...record.transactionHash ? { transactionHash: record.transactionHash } : {},
3713
+ tokenAddress,
3714
+ amount: record.note.amount,
3715
+ payload: Uint8Array.from(record.note.payload ?? []),
3716
+ spent: record.spent
3717
+ });
3718
+ }
3719
+ incoming.sort(
3720
+ (a, b) => a.blockNumber === b.blockNumber ? a.commitment.localeCompare(b.commitment) : a.blockNumber > b.blockNumber ? -1 : 1
3721
+ );
3722
+ return structuredClone({
3723
+ revision: state.revision,
3724
+ checkedThrough: state.discoveryThrough ?? null,
3725
+ invalidated: !!state.invalidated,
3726
+ outgoing,
3727
+ incoming
3728
+ });
3729
+ }
3730
+
3460
3731
  // src/session/WalletSession.ts
3461
3732
  import { getAddress as getAddress4 } from "ethers";
3462
- import { MissingCapabilityError as MissingCapabilityError3 } from "@invisi-labs/core";
3733
+ import { MissingCapabilityError as MissingCapabilityError5 } from "@invisi-labs/core";
3463
3734
 
3464
3735
  // src/session/transaction-preparation.ts
3465
3736
  import { getAddress as getAddress3 } from "ethers";
3466
3737
  import {
3467
3738
  normalizeTransactRequest,
3468
3739
  transactUserPublicInputs as transactUserPublicInputs2,
3469
- WitnessBuilder as WitnessBuilder3,
3740
+ WitnessBuilder as WitnessBuilder4,
3470
3741
  ZERO_ADDRESS
3471
3742
  } from "@invisi-labs/protocol";
3472
3743
 
3744
+ // src/resolve-recipient.ts
3745
+ import {
3746
+ MissingCapabilityError as MissingCapabilityError3,
3747
+ ReceivingAddress as ReceivingAddress2
3748
+ } from "@invisi-labs/core";
3749
+ async function resolveRecipient(address2, lookup, details = {}) {
3750
+ const context = { ...details, address: address2 };
3751
+ let recipient;
3752
+ try {
3753
+ recipient = ReceivingAddress2.parse(address2);
3754
+ } catch (cause) {
3755
+ throw new InvisiError("INVALID_RECIPIENT", "Invalid recipient address", {
3756
+ ...context,
3757
+ cause
3758
+ });
3759
+ }
3760
+ if (recipient.kind === "long") return recipient;
3761
+ if (!lookup) {
3762
+ throw new InvisiError("MISSING_CAPABILITY", "Missing registry lookup capability", {
3763
+ ...context,
3764
+ capability: "registry lookup"
3765
+ });
3766
+ }
3767
+ let keys;
3768
+ try {
3769
+ keys = await lookup(recipient.walletIdentity);
3770
+ } catch (cause) {
3771
+ if (cause instanceof MissingCapabilityError3) {
3772
+ throw new InvisiError("MISSING_CAPABILITY", cause.message, {
3773
+ ...context,
3774
+ cause,
3775
+ capability: cause.capability
3776
+ });
3777
+ }
3778
+ throw new InvisiError("RECIPIENT_LOOKUP_FAILED", "Recipient registry lookup failed", {
3779
+ ...context,
3780
+ cause
3781
+ });
3782
+ }
3783
+ if (keys == null) {
3784
+ throw new InvisiError(
3785
+ "EVM_ADDRESS_NOT_REGISTERED",
3786
+ "EVM address has no registered receiving keys",
3787
+ context
3788
+ );
3789
+ }
3790
+ try {
3791
+ return ReceivingAddress2.from({
3792
+ walletIdentity: recipient.walletIdentity,
3793
+ viewingPublicKey: keys.viewingPublicKey,
3794
+ clueKey: keys.clueKey
3795
+ });
3796
+ } catch (cause) {
3797
+ throw new InvisiError(
3798
+ "RECIPIENT_LOOKUP_FAILED",
3799
+ "Registry returned invalid receiving keys",
3800
+ { ...context, cause }
3801
+ );
3802
+ }
3803
+ }
3804
+
3473
3805
  // src/session/prepare-common.ts
3474
3806
  import { getAddress, hexlify } from "ethers";
3475
3807
  import {
3476
- encodeMemo,
3477
- MissingCapabilityError as MissingCapabilityError2,
3478
- ReceivingAddress as ReceivingAddress2,
3808
+ MissingCapabilityError as MissingCapabilityError4,
3479
3809
  computeNoteCommitment,
3480
3810
  computeNoteIdentity,
3481
3811
  createClue,
@@ -3517,6 +3847,27 @@ function positiveAmount(value, bits = 128) {
3517
3847
  }
3518
3848
  return value;
3519
3849
  }
3850
+ function copyApplicationPayload(value) {
3851
+ if (value === void 0) return new Uint8Array();
3852
+ if (!(value instanceof Uint8Array)) throw new TypeError("payload must be Uint8Array");
3853
+ return Uint8Array.from(value);
3854
+ }
3855
+ function positiveOutputAmount(payment, outputIndex) {
3856
+ try {
3857
+ return positiveAmount(payment.amount);
3858
+ } catch (cause) {
3859
+ throw new InvisiError(
3860
+ "PREPARATION_FAILED",
3861
+ cause instanceof Error ? cause.message : "Invalid output amount",
3862
+ {
3863
+ address: payment.to,
3864
+ field: "amount",
3865
+ ...outputIndex === void 0 ? {} : { outputIndex },
3866
+ cause
3867
+ }
3868
+ );
3869
+ }
3870
+ }
3520
3871
  var Preparation = class {
3521
3872
  constructor(runtime) {
3522
3873
  this.runtime = runtime;
@@ -3586,52 +3937,70 @@ var Preparation = class {
3586
3937
  }
3587
3938
  return { funding, fundingMode: mode };
3588
3939
  }
3589
- async output(payment, context, change = false) {
3590
- positiveAmount(payment.amount);
3591
- const recipient = await ReceivingAddress2.parse(payment.to).resolve(
3592
- this.runtime.options.addressResolver
3593
- );
3594
- const memo = encodeMemo(payment.memo);
3595
- const nonce = generateNonce();
3596
- const identity = await computeNoteIdentity(BigInt(recipient.walletIdentity), nonce);
3597
- const keys = recipient.getReceivingKeys();
3598
- const note = {
3599
- chainId: context.chainId,
3600
- protocolAddress: BigInt(context.protocolAddress),
3601
- tokenAddress: BigInt(context.tokenAddress),
3602
- walletIdentity: recipient.walletIdentity,
3603
- amount: payment.amount,
3604
- ...memo.length === 0 ? {} : { memo },
3605
- nonce
3606
- };
3607
- const commitment = await computeNoteCommitment(
3608
- context.chainId,
3609
- identity,
3610
- note.tokenAddress,
3611
- note.amount
3612
- );
3613
- return {
3614
- note,
3615
- commitment,
3616
- identity,
3617
- address: payment.to,
3618
- change,
3619
- delivery: {
3620
- ciphertext: hexlify(
3621
- await encryptPrivateTransferNote(keys.viewingPublicKey, note, {
3622
- chainId: context.chainId,
3623
- protocolAddress: note.protocolAddress,
3624
- outputCommitment: commitment
3625
- })
3626
- ),
3627
- clue: hexlify(createClue(keys.clueKey))
3628
- }
3629
- };
3940
+ async output(payment, context, change = false, outputIndex) {
3941
+ try {
3942
+ positiveOutputAmount(payment, outputIndex);
3943
+ const recipient = await resolveRecipient(
3944
+ payment.to,
3945
+ this.runtime.options.addressResolver,
3946
+ {
3947
+ field: change ? "changeAddress" : "to",
3948
+ ...outputIndex === void 0 ? {} : { outputIndex }
3949
+ }
3950
+ );
3951
+ const nonce = generateNonce();
3952
+ const identity = await computeNoteIdentity(BigInt(recipient.walletIdentity), nonce);
3953
+ const keys = recipient.getReceivingKeys();
3954
+ const note = {
3955
+ chainId: context.chainId,
3956
+ protocolAddress: BigInt(context.protocolAddress),
3957
+ tokenAddress: BigInt(context.tokenAddress),
3958
+ walletIdentity: recipient.walletIdentity,
3959
+ amount: payment.amount,
3960
+ payload: copyApplicationPayload(payment.payload),
3961
+ nonce
3962
+ };
3963
+ const commitment = await computeNoteCommitment(
3964
+ context.chainId,
3965
+ identity,
3966
+ note.tokenAddress,
3967
+ note.amount
3968
+ );
3969
+ return {
3970
+ note,
3971
+ commitment,
3972
+ identity,
3973
+ address: payment.to,
3974
+ change,
3975
+ delivery: {
3976
+ ciphertext: hexlify(
3977
+ await encryptPrivateTransferNote(keys.viewingPublicKey, note, {
3978
+ chainId: context.chainId,
3979
+ protocolAddress: note.protocolAddress,
3980
+ outputCommitment: commitment
3981
+ })
3982
+ ),
3983
+ clue: hexlify(createClue(keys.clueKey))
3984
+ }
3985
+ };
3986
+ } catch (cause) {
3987
+ if (cause instanceof InvisiError) throw cause;
3988
+ throw new InvisiError(
3989
+ "PREPARATION_FAILED",
3990
+ cause instanceof Error ? cause.message : "Output preparation failed",
3991
+ {
3992
+ address: payment.to,
3993
+ field: change ? "changeAddress" : "to",
3994
+ ...outputIndex === void 0 ? {} : { outputIndex },
3995
+ cause
3996
+ }
3997
+ );
3998
+ }
3630
3999
  }
3631
4000
  async membership(input, frame) {
3632
4001
  const { options, reader } = this.runtime;
3633
4002
  if (!options.protocolRpc)
3634
- throw new MissingCapabilityError2("Protocol RPC membership provider");
4003
+ throw new MissingCapabilityError4("Protocol RPC membership provider");
3635
4004
  const root = this.#roots.get(input.treeId);
3636
4005
  const proof = await options.protocolRpc.getMembership(
3637
4006
  frame.context,
@@ -3736,7 +4105,6 @@ import {
3736
4105
  FIELD_MODULUS as FIELD_MODULUS3,
3737
4106
  normalizeCreditPolicy,
3738
4107
  applyCreditDrawUsage,
3739
- ReceivingAddress as ReceivingAddress3,
3740
4108
  computeCreditNullifier as computeCreditNullifier2,
3741
4109
  computeCreditRecoveryPad,
3742
4110
  createClue as createClue2,
@@ -3747,7 +4115,7 @@ import {
3747
4115
  normalizeCreditDeliveryState as normalizeCreditDeliveryState2
3748
4116
  } from "@invisi-labs/core";
3749
4117
  import {
3750
- WitnessBuilder as WitnessBuilder2,
4118
+ WitnessBuilder as WitnessBuilder3,
3751
4119
  cancelCreditPublicInputs as cancelCreditPublicInputs2,
3752
4120
  createCreditPublicInputs as createCreditPublicInputs2,
3753
4121
  creditDrawPublicInputs as creditDrawPublicInputs2,
@@ -3838,10 +4206,16 @@ var CreditPreparation = class {
3838
4206
  positiveAmount(options.amount, 126);
3839
4207
  const frame = await this.#prepare.frame(options.deadline);
3840
4208
  const expiry = this.expiry(options.expiry, frame);
3841
- const spender = await ReceivingAddress3.parse(options.spender).resolve(
3842
- this.runtime.options.addressResolver
4209
+ const spender = await resolveRecipient(
4210
+ options.spender,
4211
+ this.runtime.options.addressResolver,
4212
+ { field: "spender" }
4213
+ );
4214
+ const owner = await resolveRecipient(
4215
+ this.runtime.options.wallet.getReceivingAddress().toString(),
4216
+ this.runtime.options.addressResolver,
4217
+ { field: "wallet" }
3843
4218
  );
3844
- const owner = await this.runtime.options.wallet.getReceivingAddress().resolve(this.runtime.options.addressResolver);
3845
4219
  const ownerPayout = getAddress2(owner.walletIdentity);
3846
4220
  const spenderPayout = getAddress2(spender.walletIdentity);
3847
4221
  if (BigInt(ownerPayout) === 0n || BigInt(spenderPayout) === 0n) {
@@ -3863,7 +4237,7 @@ var CreditPreparation = class {
3863
4237
  monthlyLimit: options.monthlyLimit ?? 0n
3864
4238
  }),
3865
4239
  amount: options.amount,
3866
- ...options.memo === void 0 ? {} : { memo: options.memo },
4240
+ payload: copyApplicationPayload(options.payload),
3867
4241
  expiry,
3868
4242
  nonce: generateNonce2(),
3869
4243
  creditSharedKey: generateCreditSharedKey()
@@ -3928,12 +4302,12 @@ var CreditPreparation = class {
3928
4302
  creditNullifier: previous.nullifier,
3929
4303
  previousExpiry: previous.input.state.note.expiry
3930
4304
  };
3931
- plan.statement = WitnessBuilder2.statement(
4305
+ plan.statement = WitnessBuilder3.statement(
3932
4306
  "credit_top_up",
3933
4307
  topUpCreditPublicInputs2(frame.context, plan.credit.topUp, deliveries, plan.funding)
3934
4308
  );
3935
4309
  } else {
3936
- plan.statement = WitnessBuilder2.statement(
4310
+ plan.statement = WitnessBuilder3.statement(
3937
4311
  "create_credit",
3938
4312
  createCreditPublicInputs2(frame.context, creation, deliveries, plan.funding)
3939
4313
  );
@@ -3965,7 +4339,9 @@ var CreditPreparation = class {
3965
4339
  amount,
3966
4340
  expiry,
3967
4341
  nonce: generateNonce2(),
3968
- ...options.memo === void 0 ? {} : { memo: options.memo }
4342
+ payload: copyApplicationPayload(
4343
+ options.payload === void 0 ? old.note.payload : options.payload
4344
+ )
3969
4345
  }
3970
4346
  },
3971
4347
  frame
@@ -4035,7 +4411,9 @@ var CreditPreparation = class {
4035
4411
  ...n,
4036
4412
  ...usage,
4037
4413
  amount: remaining,
4038
- ...options.credits[i].memo === void 0 ? {} : { memo: options.credits[i].memo },
4414
+ payload: copyApplicationPayload(
4415
+ options.credits[i].payload === void 0 ? n.payload : options.credits[i].payload
4416
+ ),
4039
4417
  nonce: await deriveRemainderNonce(...args)
4040
4418
  }
4041
4419
  },
@@ -4068,7 +4446,7 @@ var CreditPreparation = class {
4068
4446
  draw: params,
4069
4447
  amounts
4070
4448
  };
4071
- plan.statement = WitnessBuilder2.statement(
4449
+ plan.statement = WitnessBuilder3.statement(
4072
4450
  profile.name,
4073
4451
  creditDrawPublicInputs2(frame.context, params, deliveries)
4074
4452
  );
@@ -4090,7 +4468,7 @@ var CreditPreparation = class {
4090
4468
  };
4091
4469
  plan.capacity = 1;
4092
4470
  plan.credit = { inputs: [previous.input], outputs: [], deliveries: [], cancel: params };
4093
- plan.statement = WitnessBuilder2.statement(
4471
+ plan.statement = WitnessBuilder3.statement(
4094
4472
  "credit_cancel",
4095
4473
  cancelCreditPublicInputs2(frame.context, params)
4096
4474
  );
@@ -4113,87 +4491,48 @@ var TransactionPreparation = class {
4113
4491
  return this.context.scope;
4114
4492
  }
4115
4493
  async prepareTransfer(options) {
4494
+ if (Object.hasOwn(options, "target") || Object.hasOwn(options, "execution")) {
4495
+ throw new Error(
4496
+ "prepareTransfer accepts no target or execution; choose execution in prove()"
4497
+ );
4498
+ }
4116
4499
  const copied = structuredClone(options);
4117
- const preparation = new Preparation(this.runtime(copied.tokenAddress));
4500
+ const runtime = this.runtime(copied.tokenAddress);
4501
+ if (copied.commitments !== void 0 && copied.commitments.length === 0)
4502
+ throw new Error("empty explicit input selection");
4503
+ const preparation = new Preparation({
4504
+ ...runtime,
4505
+ ...copied.commitments !== void 0 ? { inputSelection: copied.commitments } : {}
4506
+ });
4118
4507
  const frame = await preparation.frame(copied.deadline);
4119
- if (copied.outputs.length < 1 || copied.outputs.length > 168) {
4120
- throw new Error("transfer requires 1..168 outputs");
4508
+ if (copied.outputs.length > 168) throw new Error("transfer supports at most 168 outputs");
4509
+ let withdrawalAddress = ZERO_ADDRESS;
4510
+ let withdrawalAmount = 0n;
4511
+ if (copied.withdrawal !== void 0) {
4512
+ withdrawalAddress = getAddress3(copied.withdrawal.to);
4513
+ positiveAmount(copied.withdrawal.amount);
4514
+ if (BigInt(withdrawalAddress) === 0n)
4515
+ throw new Error("withdrawal destination must be nonzero");
4516
+ withdrawalAmount = copied.withdrawal.amount;
4121
4517
  }
4518
+ if (copied.outputs.length === 0 && withdrawalAmount === 0n)
4519
+ throw new Error("transfer requires a private output or public withdrawal");
4122
4520
  const total = copied.outputs.reduce(
4123
- (sum, output) => sum + positiveAmount(output.amount),
4124
- 0n
4521
+ (sum, output, index2) => sum + positiveOutputAmount(output, index2),
4522
+ withdrawalAmount
4125
4523
  );
4126
4524
  const funding = await preparation.funding(copied.funding, frame);
4127
4525
  return this.funds(
4128
- "transfer",
4129
4526
  copied.outputs,
4130
4527
  total,
4131
- ZERO_ADDRESS,
4132
- 0n,
4528
+ withdrawalAddress,
4529
+ withdrawalAmount,
4133
4530
  frame,
4134
4531
  funding,
4135
- copied.target,
4136
- preparation
4137
- );
4138
- }
4139
- async prepareWithdraw(options) {
4140
- const copied = structuredClone(options);
4141
- const preparation = new Preparation(this.runtime(copied.tokenAddress));
4142
- const frame = await preparation.frame(copied.deadline);
4143
- const to = getAddress3(copied.to);
4144
- positiveAmount(copied.amount);
4145
- if (BigInt(to) === 0n) {
4146
- throw new Error("withdrawal destination must be nonzero");
4147
- }
4148
- return this.funds(
4149
- "withdraw",
4150
- [],
4151
- copied.amount,
4152
- to,
4153
- copied.amount,
4154
- frame,
4155
- { funding: {}, fundingMode: "none" },
4156
- copied.target,
4157
4532
  preparation
4158
4533
  );
4159
4534
  }
4160
- /** Full payout to the note owner; no arbitrary destination, change or spending signature. */
4161
- async prepareSweep(options) {
4162
- const copied = structuredClone(options);
4163
- const runtime = this.runtime(copied.tokenAddress);
4164
- const state = await runtime.readInventory();
4165
- const available = Object.values(state.notes).filter(
4166
- (record) => record.note.tokenAddress === BigInt(copied.tokenAddress) && record.spent === "unspent" && !state.reservations[record.commitment]
4167
- );
4168
- const selected = copied.commitments ?? available.map((record) => record.commitment);
4169
- if (selected.length === 0) throw new Error("empty sweep input selection");
4170
- if (new Set(selected).size !== selected.length) throw new Error("duplicate sweep inputs");
4171
- const byId = new Map(available.map((record) => [record.commitment, record]));
4172
- let amount = 0n;
4173
- for (const id of selected) {
4174
- const record = byId.get(id);
4175
- if (!record) throw new Error("sweep input unavailable");
4176
- amount += record.note.amount;
4177
- }
4178
- positiveAmount(amount);
4179
- const preparation = new Preparation({
4180
- ...runtime,
4181
- inputSelection: selected
4182
- });
4183
- const frame = await preparation.frame(copied.deadline);
4184
- return this.funds(
4185
- "sweep",
4186
- [],
4187
- amount,
4188
- this.scope.walletIdentity,
4189
- amount,
4190
- frame,
4191
- { funding: {}, fundingMode: "none" },
4192
- copied.target,
4193
- preparation
4194
- );
4195
- }
4196
- async funds(kind, payments, total, withdrawalAddress, withdrawalAmount, frame, funding, target, preparation) {
4535
+ async funds(payments, total, withdrawalAddress, withdrawalAmount, frame, funding, preparation) {
4197
4536
  if (total >= 1n << 135n) {
4198
4537
  throw new Error("transaction sum exceeds uint135");
4199
4538
  }
@@ -4203,7 +4542,7 @@ var TransactionPreparation = class {
4203
4542
  frame
4204
4543
  );
4205
4544
  const outputs = await Promise.all(
4206
- payments.map((payment) => preparation.output(payment, context))
4545
+ payments.map((payment, index2) => preparation.output(payment, context, false, index2))
4207
4546
  );
4208
4547
  const change = selection.amount + (funding.funding.amount ?? 0n) - total;
4209
4548
  if (change > 0n) {
@@ -4229,16 +4568,16 @@ var TransactionPreparation = class {
4229
4568
  };
4230
4569
  const deliveries = outputs.map((output) => output.delivery);
4231
4570
  const normalized = normalizeTransactRequest(context, params, deliveries, funding.funding);
4232
- const statement2 = WitnessBuilder3.statement(
4571
+ const statement2 = WitnessBuilder4.statement(
4233
4572
  `transact_user_slots${normalized.capacity}`,
4234
4573
  transactUserPublicInputs2(context, params, deliveries, funding.funding),
4235
- target ?? "evm"
4574
+ "evm"
4236
4575
  );
4237
4576
  await preparation.assertBlock(frame);
4238
4577
  return preparation.runtime.complete({
4239
4578
  version: 1,
4240
4579
  id: crypto.randomUUID(),
4241
- kind,
4580
+ kind: "transfer",
4242
4581
  context,
4243
4582
  scope: this.scope,
4244
4583
  preparedAt: block,
@@ -4260,7 +4599,9 @@ var TransactionPreparation = class {
4260
4599
  throw new Error("shield requires 1..10 outputs");
4261
4600
  }
4262
4601
  const outputs = await Promise.all(
4263
- copied.outputs.map((output) => preparation.output(output, frame.context))
4602
+ copied.outputs.map(
4603
+ (output, index2) => preparation.output(output, frame.context, false, index2)
4604
+ )
4264
4605
  );
4265
4606
  if (outputs.reduce((sum, output) => sum + output.note.amount, 0n) !== (funding.funding.amount ?? 0n)) {
4266
4607
  throw new Error("shield funding must equal output total");
@@ -4369,7 +4710,6 @@ import { NullifierCapability } from "@invisi-labs/core";
4369
4710
 
4370
4711
  // src/session/inventory.ts
4371
4712
  import {
4372
- encodeMemo as encodeMemo2,
4373
4713
  computeCreditCommitment as computeCreditCommitment2,
4374
4714
  computeCreditNullifier as computeCreditNullifier3,
4375
4715
  computeCreditSharedKeyHash as computeCreditSharedKeyHash2,
@@ -4519,7 +4859,7 @@ var InventoryProcessor = class {
4519
4859
  const child = foundByKey.get(
4520
4860
  params.remainderCommitments[index2] + ":" + previous.role
4521
4861
  );
4522
- const memo = child?.state.note.memo;
4862
+ const payload = child?.state.note.payload;
4523
4863
  const record = {
4524
4864
  commitment: params.remainderCommitments[index2],
4525
4865
  treeId: BigInt(args.outputTreeId),
@@ -4532,7 +4872,7 @@ var InventoryProcessor = class {
4532
4872
  ...clone(previous.state),
4533
4873
  note: {
4534
4874
  ...remainder,
4535
- ...memo === void 0 ? {} : { memo: encodeMemo2(memo) }
4875
+ ...payload === void 0 ? {} : { payload: clone(payload) }
4536
4876
  }
4537
4877
  },
4538
4878
  receivingKeySource: { kind: "draw", commitment: previous.commitment },
@@ -4780,6 +5120,7 @@ var WalletUpdater = class extends InventoryProcessor {
4780
5120
  draft.notes = {};
4781
5121
  draft.credits = {};
4782
5122
  delete draft.observedThrough;
5123
+ delete draft.discoveryThrough;
4783
5124
  delete draft.invalidated;
4784
5125
  }
4785
5126
  mergeReceiptRecords(draft, { notes, credits, checkpoint: block });
@@ -4811,13 +5152,38 @@ var WalletUpdater = class extends InventoryProcessor {
4811
5152
  var WalletSession = class {
4812
5153
  #options;
4813
5154
  #updater;
5155
+ #listeners = /* @__PURE__ */ new Set();
4814
5156
  #sending = /* @__PURE__ */ new Set();
4815
5157
  #refreshQueue = Promise.resolve();
4816
5158
  scope;
4817
5159
  protocol;
4818
5160
  constructor(options) {
5161
+ if ("ciphertextCodecs" in options || "envelopeVersion" in options) {
5162
+ throw new Error(
5163
+ "SDK envelope versions are fixed; encode application payload bytes externally"
5164
+ );
5165
+ }
5166
+ const store = options.store;
4819
5167
  this.#options = {
4820
5168
  ...options,
5169
+ ...store ? {
5170
+ store: {
5171
+ persistent: store.persistent,
5172
+ readSnapshot: (scope) => store.readSnapshot(scope),
5173
+ transaction: async (scope, action) => {
5174
+ const result = await store.transaction(scope, action);
5175
+ if (scopeKey(scope) === scopeKey(this.scope)) {
5176
+ for (const listener of [...this.#listeners]) {
5177
+ try {
5178
+ listener();
5179
+ } catch {
5180
+ }
5181
+ }
5182
+ }
5183
+ return result;
5184
+ }
5185
+ }
5186
+ } : {},
4821
5187
  ...options.gasBenchmarks ? { gasBenchmarks: copyGasBenchmarks(options.gasBenchmarks) } : {}
4822
5188
  };
4823
5189
  this.protocol = options.protocol;
@@ -4832,7 +5198,7 @@ var WalletSession = class {
4832
5198
  /** Delegate sending to ethers; managed operations additionally retain the resulting hash. */
4833
5199
  async sendTransaction(input, options = {}) {
4834
5200
  const signer = this.#options.signer;
4835
- if (!signer) throw new MissingCapabilityError3("ethers signer");
5201
+ if (!signer) throw new MissingCapabilityError5("ethers signer");
4836
5202
  const operation = input instanceof Operation ? input : void 0;
4837
5203
  if (operation && this.#sending.has(operation.id))
4838
5204
  throw new Error("operation is already being sent by this session");
@@ -4840,12 +5206,12 @@ var WalletSession = class {
4840
5206
  const overrides = { ...options };
4841
5207
  const plain = operation ? void 0 : { ...input };
4842
5208
  try {
4843
- if (!signer.provider) throw new MissingCapabilityError3("connected ethers signer");
5209
+ if (!signer.provider) throw new MissingCapabilityError5("connected ethers signer");
4844
5210
  if ((await signer.provider.getNetwork()).chainId !== this.protocol.context.chainId) {
4845
5211
  throw new Error("signer-provider session chain mismatch");
4846
5212
  }
4847
5213
  if (operation) {
4848
- if (!this.#options.store) throw new MissingCapabilityError3("wallet store");
5214
+ if (!this.#options.store) throw new MissingCapabilityError5("wallet store");
4849
5215
  const state = await this.#options.store.readSnapshot(this.scope);
4850
5216
  if (!state.operations[operation.id])
4851
5217
  throw new Error("operation does not belong to this wallet session");
@@ -4876,25 +5242,30 @@ var WalletSession = class {
4876
5242
  runtime(tokenAddress) {
4877
5243
  const options = this.#options;
4878
5244
  if (!options.store) {
4879
- throw new MissingCapabilityError3("wallet store");
5245
+ throw new MissingCapabilityError5("wallet store");
4880
5246
  }
4881
5247
  if (!options.chainReader) {
4882
- throw new MissingCapabilityError3("chain reader");
5248
+ throw new MissingCapabilityError5("chain reader");
4883
5249
  }
4884
5250
  return {
4885
5251
  options: { ...options, protocol: options.protocol.forToken(tokenAddress) },
4886
5252
  scope: this.scope,
4887
5253
  store: options.store,
4888
5254
  reader: options.chainReader,
4889
- authorization: () => options.authorization ?? options.wallet.exportAuthorization()
5255
+ authorization: () => {
5256
+ if (options.authorization) return options.authorization;
5257
+ if (options.wallet instanceof Wallet) return options.wallet.exportAuthorization();
5258
+ throw new MissingCapabilityError5("wallet authorization");
5259
+ }
4890
5260
  };
4891
5261
  }
4892
5262
  updater() {
4893
5263
  if (this.#updater) return this.#updater;
4894
5264
  const options = this.#options;
4895
5265
  if (!options.store || !options.chainReader || !options.protocolRpc)
4896
- throw new MissingCapabilityError3("wallet store, chain reader and Protocol RPC");
4897
- const viewing = options.viewing ?? options.wallet.getViewer();
5266
+ throw new MissingCapabilityError5("wallet store, chain reader and Protocol RPC");
5267
+ const viewing = options.viewing ?? (options.wallet instanceof Wallet ? options.wallet.getViewer() : void 0);
5268
+ if (!viewing) throw new MissingCapabilityError5("viewing");
4898
5269
  const receivingAddress = options.wallet.getReceivingAddress();
4899
5270
  const receivingKeys = receivingAddress.kind === "long" ? receivingAddress.getReceivingKeys() : void 0;
4900
5271
  this.#updater = new WalletUpdater({
@@ -4908,10 +5279,27 @@ var WalletSession = class {
4908
5279
  });
4909
5280
  return this.#updater;
4910
5281
  }
5282
+ /** Local session commits only. Host applications own cross-tab/store subscriptions. */
5283
+ subscribe(listener) {
5284
+ this.#listeners.add(listener);
5285
+ return () => {
5286
+ this.#listeners.delete(listener);
5287
+ };
5288
+ }
5289
+ /** Detached history for wallet UI; construction records do not imply chain success. */
5290
+ async getHistory(options = {}) {
5291
+ const tokenAddress = options.tokenAddress ? getAddress4(options.tokenAddress) : void 0;
5292
+ const store = this.#options.store;
5293
+ if (!store) throw new MissingCapabilityError5("wallet store");
5294
+ return walletHistory(
5295
+ await store.readSnapshot(this.scope),
5296
+ tokenAddress ? { tokenAddress } : {}
5297
+ );
5298
+ }
4911
5299
  async balance(tokenAddress) {
4912
5300
  const store = this.#options.store;
4913
5301
  if (!store) {
4914
- throw new MissingCapabilityError3("wallet store");
5302
+ throw new MissingCapabilityError5("wallet store");
4915
5303
  }
4916
5304
  return walletBalance(await store.readSnapshot(this.scope), getAddress4(tokenAddress));
4917
5305
  }
@@ -4919,15 +5307,15 @@ var WalletSession = class {
4919
5307
  async refresh(options) {
4920
5308
  const provider = this.#options.protocolRpc;
4921
5309
  const reader = this.#options.chainReader;
4922
- if (!reader) throw new MissingCapabilityError3("chain reader");
4923
- if (!provider) throw new MissingCapabilityError3("Protocol RPC");
5310
+ if (!reader) throw new MissingCapabilityError5("chain reader");
5311
+ if (!provider) throw new MissingCapabilityError5("Protocol RPC");
4924
5312
  const updater = this.updater();
4925
5313
  const request = {
4926
5314
  ...options,
4927
5315
  ...options.detectionKey ? { detectionKey: options.detectionKey.slice() } : {}
4928
5316
  };
4929
- const task = this.#refreshQueue.then(
4930
- () => refreshWallet(
5317
+ const task = this.#refreshQueue.then(async () => {
5318
+ const result = await refreshWallet(
4931
5319
  {
4932
5320
  provider,
4933
5321
  reader,
@@ -4936,8 +5324,21 @@ var WalletSession = class {
4936
5324
  apply: (batch, settings) => updater.applyUpdates(batch, settings)
4937
5325
  },
4938
5326
  request
4939
- )
4940
- );
5327
+ );
5328
+ await this.#options.store.transaction(this.scope, (state) => {
5329
+ request.signal?.throwIfAborted();
5330
+ if (state.invalidated) throw new Error("wallet requires canonical resync");
5331
+ const previous = state.discoveryThrough;
5332
+ const start = previous ? previous.number + 1n : this.protocol.deployment.protocol.fromBlock;
5333
+ if (request.fromBlock <= start && (!previous || result.block.number >= previous.number)) {
5334
+ state.discoveryThrough = {
5335
+ number: result.block.number,
5336
+ hash: result.block.hash
5337
+ };
5338
+ }
5339
+ });
5340
+ return result;
5341
+ });
4941
5342
  this.#refreshQueue = task.catch(() => {
4942
5343
  });
4943
5344
  return task;
@@ -4948,38 +5349,43 @@ var WalletSession = class {
4948
5349
  options: runtime.options,
4949
5350
  scope: runtime.scope,
4950
5351
  reader: runtime.reader,
4951
- authorization: runtime.authorization,
4952
5352
  readInventory: () => runtime.store.readSnapshot(runtime.scope),
4953
5353
  complete: (plan) => Operation.prepare(runtime, plan)
4954
5354
  });
4955
5355
  }
4956
5356
  async prepareTransfer(options) {
4957
- return this.preparation(options.tokenAddress).prepareTransfer(options);
4958
- }
4959
- async prepareWithdraw(options) {
4960
- return this.preparation(options.tokenAddress).prepareWithdraw(options);
4961
- }
4962
- async prepareSweep(options) {
4963
- return this.preparation(options.tokenAddress).prepareSweep(options);
5357
+ return prepareWithErrors("prepareTransfer", async () => {
5358
+ return this.preparation(options.tokenAddress).prepareTransfer(options);
5359
+ });
4964
5360
  }
4965
5361
  async prepareShield(options) {
4966
- return this.preparation(options.tokenAddress).prepareShield(options);
5362
+ return prepareWithErrors("prepareShield", async () => {
5363
+ return this.preparation(options.tokenAddress).prepareShield(options);
5364
+ });
4967
5365
  }
4968
5366
  async prepareCreateCredit(options) {
4969
- return this.preparation(options.tokenAddress).prepareCreateCredit(options);
5367
+ return prepareWithErrors("prepareCreateCredit", async () => {
5368
+ return this.preparation(options.tokenAddress).prepareCreateCredit(options);
5369
+ });
4970
5370
  }
4971
5371
  async prepareDrawCredit(options) {
4972
- return this.preparation(options.tokenAddress).prepareDrawCredit(options);
5372
+ return prepareWithErrors("prepareDrawCredit", async () => {
5373
+ return this.preparation(options.tokenAddress).prepareDrawCredit(options);
5374
+ });
4973
5375
  }
4974
5376
  async prepareTopUpCredit(options) {
4975
- return this.preparation(options.tokenAddress).prepareTopUpCredit(options);
5377
+ return prepareWithErrors("prepareTopUpCredit", async () => {
5378
+ return this.preparation(options.tokenAddress).prepareTopUpCredit(options);
5379
+ });
4976
5380
  }
4977
5381
  async prepareCancelCredit(options) {
4978
- return this.preparation(options.tokenAddress).prepareCancelCredit(options);
5382
+ return prepareWithErrors("prepareCancelCredit", async () => {
5383
+ return this.preparation(options.tokenAddress).prepareCancelCredit(options);
5384
+ });
4979
5385
  }
4980
5386
  async resume(operationId) {
4981
5387
  const store = this.#options.store;
4982
- if (!store) throw new MissingCapabilityError3("wallet store");
5388
+ if (!store) throw new MissingCapabilityError5("wallet store");
4983
5389
  const state = await store.readSnapshot(this.scope);
4984
5390
  const saved = state.operations[operationId]?.payload;
4985
5391
  if (!saved?.plan) throw new Error("operation not found");
@@ -5051,6 +5457,8 @@ var MemoryWalletStore = class extends TransactionalWalletStore {
5051
5457
  };
5052
5458
 
5053
5459
  export {
5460
+ InvisiError,
5461
+ prepareWithErrors,
5054
5462
  walletBalance,
5055
5463
  requiresAuthorization,
5056
5464
  statement,
@@ -5060,14 +5468,18 @@ export {
5060
5468
  defineGasBenchmarks,
5061
5469
  estimateGasReference,
5062
5470
  sameStatement,
5063
- authorizeOperation,
5064
5471
  proveOperation,
5065
- signOperationFunding,
5472
+ walletSigningRequest,
5473
+ acceptWalletSignature,
5474
+ fundingSigningRequest,
5475
+ acceptFundingSignature,
5066
5476
  operationPreview,
5067
5477
  positiveAmount,
5068
5478
  TransactionPreparation,
5069
5479
  serializeState,
5070
5480
  deserializeState,
5481
+ readProtocolLogs,
5482
+ decryptCandidates,
5071
5483
  DisclosureVerifier,
5072
5484
  TransactionalWalletStore,
5073
5485
  MemoryAtomicRecords,
@@ -5075,4 +5487,4 @@ export {
5075
5487
  WalletSession,
5076
5488
  MemoryWalletStore
5077
5489
  };
5078
- //# sourceMappingURL=chunk-FSQYZO66.js.map
5490
+ //# sourceMappingURL=chunk-BE52B32Z.js.map