shinjuku-shielded 0.3.73 → 0.3.74

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/manifest.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schema": "carbon-shielded-wallet/bundle-manifest/v1",
3
3
  "file": "shinjuku-wallet.mjs",
4
- "sha256": "ff6f25bccfd2a50929a8dc82137b4fb42172790f9d71a6c07f166bda3e052d22",
5
- "bytes": 12210958,
6
- "sourceCommit": "0e33e750c294c38e85c041b105e87cf864edd5db",
4
+ "sha256": "bf5ad0c16095110ed0258b16525e55dc743dbaa36b6ba0e0738d4ab9c185ce90",
5
+ "bytes": 12226842,
6
+ "sourceCommit": "48cab6600f17f6d97cd27fcb657eaf344c1257ef",
7
7
  "sourceDirty": false,
8
8
  "installRecordMatchesLockfile": true,
9
9
  "releaseRule": "valid only when two fresh npm ci --ignore-scripts builds on different OSes give the same sha256; installRecordMatchesLockfile true means every entry npm installed equals its package-lock.json entry and every entry not marked optional, devOptional included, is installed: an optional entry (a package for another platform) may be absent",
@@ -1282,5 +1282,5 @@
1282
1282
  "version": "1.8.0"
1283
1283
  }
1284
1284
  ],
1285
- "publishedAt": "2026-10-22T18:10:20.059Z"
1285
+ "publishedAt": "2026-10-22T19:31:45.493Z"
1286
1286
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "shinjuku-shielded",
3
- "version": "0.3.73",
3
+ "version": "0.3.74",
4
4
  "mcpName": "io.github.ShinjukuStaition/shinjuku-mcp",
5
5
  "description": "Shinjuku Shielded, the private x402 facilitator on Solana: the self-custody wallet CLI and its MCP server. Shield, pay, and unshield USDC from any MCP agent, under caps you set.",
6
- "shinjukuRelease": "ff6f25bc",
7
- "shinjukuSha256": "ff6f25bccfd2a50929a8dc82137b4fb42172790f9d71a6c07f166bda3e052d22",
6
+ "shinjukuRelease": "bf5ad0c1",
7
+ "shinjukuSha256": "bf5ad0c16095110ed0258b16525e55dc743dbaa36b6ba0e0738d4ab9c185ce90",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-mcp#readme",
@@ -42600,6 +42600,11 @@ var init_rpcDefault = __esm({
42600
42600
  });
42601
42601
 
42602
42602
  // wallet/src/net/facilitatorOnion.mjs
42603
+ function takeSiteOnionRaces() {
42604
+ if (siteRacesTaken) return Promise.resolve([]);
42605
+ siteRacesTaken = true;
42606
+ return Promise.all(siteRaces);
42607
+ }
42603
42608
  function onionInit(init = {}) {
42604
42609
  if (init.dispatcher) return init;
42605
42610
  try {
@@ -42637,7 +42642,8 @@ function raceOnions(onions, {
42637
42642
  onNone,
42638
42643
  spares = [],
42639
42644
  spareAfterMs = SPARE_AFTER_MS,
42640
- onSpares
42645
+ onSpares,
42646
+ onProbe
42641
42647
  } = {}) {
42642
42648
  const controller = new AbortController();
42643
42649
  const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(timeoutMs)]);
@@ -42667,9 +42673,22 @@ function raceOnions(onions, {
42667
42673
  if (!controller.signal.aborted) onNone?.();
42668
42674
  settle(onions[0]);
42669
42675
  };
42676
+ const told = (onion, event) => {
42677
+ try {
42678
+ onProbe?.(onion, event);
42679
+ } catch {
42680
+ }
42681
+ };
42670
42682
  const start = (onion) => {
42671
42683
  open3 += 1;
42672
- probe(onion).then(settle, failed);
42684
+ told(onion, "start");
42685
+ probe(onion).then((won) => {
42686
+ told(onion, "answered");
42687
+ settle(won);
42688
+ }, () => {
42689
+ told(onion, controller.signal.aborted ? "stopped" : signal.aborted ? "timeout" : "failed");
42690
+ failed();
42691
+ });
42673
42692
  };
42674
42693
  startSpares = (cause) => {
42675
42694
  if (sparesStarted || done || signal.aborted) return;
@@ -42790,6 +42809,33 @@ async function routeFacilitatorsOverOnion(argv2, {
42790
42809
  const race = (group, onions, options) => {
42791
42810
  let unanswered2 = false;
42792
42811
  const onNone = options.onNone;
42812
+ const first = firstRaces.has(group.label) ? void 0 : { startedAt: Date.now() };
42813
+ if (first) firstRaces.set(group.label, first);
42814
+ let site;
42815
+ let ended;
42816
+ if (first && !group.path) {
42817
+ site = { startedAt: first.startedAt, probes: [] };
42818
+ siteRaces.push(new Promise((resolve38) => {
42819
+ ended = resolve38;
42820
+ }));
42821
+ const pinned = group.onions.length - (group.spares?.length ?? 0);
42822
+ const role = (onion) => {
42823
+ const index = group.onions.indexOf(onion);
42824
+ return index < pinned ? `pinned${index + 1}` : `spare${index - pinned + 1}`;
42825
+ };
42826
+ const note = (entry) => {
42827
+ if (site.endedAt === void 0) site.probes.push({ ...entry, at: Date.now() });
42828
+ };
42829
+ const onSpares = options.onSpares;
42830
+ options = {
42831
+ ...options,
42832
+ onProbe: (onion, event) => note({ role: role(onion), event }),
42833
+ ...onSpares ? { onSpares: (info) => {
42834
+ note({ role: "spares", event: "join" });
42835
+ onSpares(info);
42836
+ } } : {}
42837
+ };
42838
+ }
42793
42839
  const raced = raceOnions(onions, {
42794
42840
  fetchImpl,
42795
42841
  timeoutMs: raceTimeoutMs,
@@ -42800,10 +42846,18 @@ async function routeFacilitatorsOverOnion(argv2, {
42800
42846
  onNone?.();
42801
42847
  }
42802
42848
  });
42803
- const first = firstRaces.has(group.label) ? void 0 : { startedAt: Date.now() };
42804
- if (first) firstRaces.set(group.label, first);
42805
42849
  group.pending = raced.winner.then((onion) => {
42806
42850
  if (first) Object.assign(first, { endedAt: Date.now(), answered: !unanswered2 });
42851
+ if (site) {
42852
+ const open3 = /* @__PURE__ */ new Set();
42853
+ for (const probe of site.probes) {
42854
+ if (probe.event === "start") open3.add(probe.role);
42855
+ else open3.delete(probe.role);
42856
+ }
42857
+ for (const role of open3) site.probes.push({ role, event: "stopped", at: first.endedAt });
42858
+ Object.assign(site, { endedAt: first.endedAt, answered: !unanswered2 });
42859
+ ended(site);
42860
+ }
42807
42861
  group.winner = onion;
42808
42862
  group.pending = void 0;
42809
42863
  if (!unanswered2 && !raced.stopped) err(`tor: the onion ${hostOf(onion)} of ${group.label} answered first; it serves this run's requests to it
@@ -42904,18 +42958,18 @@ async function routeFacilitatorsOverOnion(argv2, {
42904
42958
  }
42905
42959
  };
42906
42960
  }
42907
- var SITE_ONIONS, SITE_SPARE_ONIONS, PINNED_FACILITATOR_ONIONS, PINNED_SPARE_ONIONS, HISTORY_ONIONS, PINNED_HISTORY_ONIONS, HISTORY_PROBE_PATH, FACILITATOR_FLAGS, V3_ONION, DISCOVERY_TIMEOUT_MS, WARM_TIMEOUT_MS, RACE_TIMEOUT_MS, ROUTE_WAIT_MS, SPARE_AFTER_MS, SPARE_CAUSE, routedGroups, liveOnions, hostOf, noAnswer, routedOnions, routedOrigins, onionsOfOrigin, routesOverOnion, firstRaces, firstOnionRace, GLOBAL_DISPATCHER, onionDispatchers;
42961
+ var SITE_ONIONS, SITE_SPARE_ONIONS, PINNED_FACILITATOR_ONIONS, PINNED_SPARE_ONIONS, HISTORY_ONIONS, PINNED_HISTORY_ONIONS, HISTORY_PROBE_PATH, FACILITATOR_FLAGS, V3_ONION, DISCOVERY_TIMEOUT_MS, WARM_TIMEOUT_MS, RACE_TIMEOUT_MS, ROUTE_WAIT_MS, SPARE_AFTER_MS, SPARE_CAUSE, routedGroups, liveOnions, hostOf, noAnswer, routedOnions, routedOrigins, onionsOfOrigin, routesOverOnion, firstRaces, siteRaces, siteRacesTaken, firstOnionRace, GLOBAL_DISPATCHER, onionDispatchers;
42908
42962
  var init_facilitatorOnion = __esm({
42909
42963
  "wallet/src/net/facilitatorOnion.mjs"() {
42910
42964
  "use strict";
42911
42965
  init_rpcDefault();
42912
42966
  SITE_ONIONS = Object.freeze([
42913
42967
  "http://2kfhlfuyuwvhmibjrpxqsg4nrbcxasjgjq7kmnfzgfwzptkhhznz3dad.onion",
42914
- "http://chhgw7nfggqhhjawz54bpsiihdow66s2yvkh6ccnugpcdmhhww6xkkqd.onion"
42968
+ "http://vofhhl336r4bnwn7hid2uvqaa72wx66xkdu2yt4n54xljsxwn2yds6qd.onion"
42915
42969
  ]);
42916
42970
  SITE_SPARE_ONIONS = Object.freeze([
42917
42971
  "http://ntr5qkdfgff4gspwderjnqtkzekyhb5a4j76ov4dqncxmcb53gdsmmid.onion",
42918
- "http://vofhhl336r4bnwn7hid2uvqaa72wx66xkdu2yt4n54xljsxwn2yds6qd.onion"
42972
+ "http://chhgw7nfggqhhjawz54bpsiihdow66s2yvkh6ccnugpcdmhhww6xkkqd.onion"
42919
42973
  ]);
42920
42974
  PINNED_FACILITATOR_ONIONS = Object.freeze({
42921
42975
  "https://shinjukustaition.com": SITE_ONIONS,
@@ -42955,6 +43009,8 @@ var init_facilitatorOnion = __esm({
42955
43009
  onionsOfOrigin = (origin) => routedOrigins.get(origin) ?? PINNED_FACILITATOR_ONIONS[origin] ?? [];
42956
43010
  routesOverOnion = (origin) => routedOrigins.has(origin);
42957
43011
  firstRaces = /* @__PURE__ */ new Map();
43012
+ siteRaces = [];
43013
+ siteRacesTaken = false;
42958
43014
  firstOnionRace = (origin) => {
42959
43015
  const race = firstRaces.get(origin);
42960
43016
  return race === void 0 ? void 0 : { ...race };
@@ -46739,6 +46795,16 @@ function markPhase(phase, outcome) {
46739
46795
  } catch {
46740
46796
  }
46741
46797
  }
46798
+ function poolReadOutcome(slot3, moved) {
46799
+ let text3;
46800
+ try {
46801
+ text3 = BigInt(slot3).toString();
46802
+ } catch {
46803
+ return void 0;
46804
+ }
46805
+ if (!/^\d{1,20}$/.test(text3)) return void 0;
46806
+ return `slot${text3}${Number.isSafeInteger(moved) && moved >= 0 ? `_moved${moved}` : ""}`;
46807
+ }
46742
46808
  var scope, phaseScopeActive;
46743
46809
  var init_phaseTiming = __esm({
46744
46810
  "wallet/src/pay/phaseTiming.mjs"() {
@@ -53881,7 +53947,7 @@ async function refreshedFromSnapshot({ connection, program, poolKey, authenticat
53881
53947
  if (snapshot.terminal !== authenticated.finalizedPoolSnapshot.terminal || snapshot.currentIndex !== authenticated.finalizedPoolSnapshot.currentIndex || snapshot.currentRoot !== authenticated.finalizedPoolSnapshot.currentRoot || !sameP03PoolPolicy(snapshot, authenticated.finalizedPoolSnapshot.policy) || !sameE1TreeHeader(snapshot, authenticated.finalizedPoolSnapshot.e1Tree, program, poolKey) || paymentSettler !== void 0 && snapshot.paymentSettler?.toBase58() !== paymentSettler) {
53882
53948
  throw Object.assign(
53883
53949
  new Error("p03_history_refresh_transition_detected"),
53884
- { currentIndex: snapshot.currentIndex, snapshotBinding: { programId: b58(program), pool: poolKey.toBase58() } }
53950
+ { currentIndex: snapshot.currentIndex, snapshotSlot: snapshot.slot, snapshotBinding: { programId: b58(program), pool: poolKey.toBase58() } }
53885
53951
  );
53886
53952
  }
53887
53953
  const priorCoverage = authenticated.deploymentCoverage;
@@ -54131,7 +54197,8 @@ async function readFinalizedPoolSliced(connection, program, pool2) {
54131
54197
  } catch {
54132
54198
  snapshot = void 0;
54133
54199
  }
54134
- markPhase(snapshot === void 0 ? "pool-sliced-fallback-answer" : "pool-sliced");
54200
+ if (snapshot === void 0) markPhase("pool-sliced-fallback-answer");
54201
+ else markPhase("pool-sliced", poolReadOutcome(snapshot.slot));
54135
54202
  return snapshot;
54136
54203
  }
54137
54204
  async function readFinalizedPoolForRefresh(connection, program, pool2) {
@@ -57993,7 +58060,7 @@ async function readCompactP03HistoryNow({
57993
58060
  }
57994
58061
  }
57995
58062
  snapshot ??= await readFinalizedPool(own, program, poolKey, { treeSlices: true });
57996
- markPhase(knownSnapshot !== void 0 ? "compact-pool-reused" : "compact-pool");
58063
+ markPhase(knownSnapshot !== void 0 ? "compact-pool-reused" : "compact-pool", poolReadOutcome(snapshot.slot));
57997
58064
  if (snapshot.terminal) throw refuse3("p03_compact_history_pool_terminal");
57998
58065
  if (snapshot.currentIndex % SUBTREE_LEAVES3 !== 0n) throw refuse3("p03_compact_history_snapshot_invalid");
57999
58066
  const count2 = Number(snapshot.currentIndex / SUBTREE_LEAVES3);
@@ -59463,7 +59530,7 @@ async function walkWalletP03HistoryNow({ store, connection: given, programId, po
59463
59530
  })();
59464
59531
  try {
59465
59532
  const refreshed = await refreshing;
59466
- markPhase("walk-refreshed");
59533
+ markPhase("walk-refreshed", poolReadOutcome(refreshed?.finalizedPoolSnapshot?.slot));
59467
59534
  if (page0Asked) {
59468
59535
  markPhase(page0Answered ? "compact-page0-answered-before-hit" : "compact-page0-read-on-after-hit");
59469
59536
  const currentIndex = refreshed?.finalizedPoolSnapshot?.currentIndex;
@@ -59476,10 +59543,10 @@ async function walkWalletP03HistoryNow({ store, connection: given, programId, po
59476
59543
  writeStderr("history: the pool made no transition since this run's last walk; that history was moved to the current pool account " + (fromSliced ? "(your own RPC read the pool account and the program's deployment account in one request)\n" : "(your own RPC read the pool account and the program's deployment account; the history source answered one signature read)\n"));
59477
59544
  return refreshed;
59478
59545
  } catch (error) {
59479
- markPhase("refresh-missed");
59480
- if (error?.message === "p03_history_refresh_transition_detected" && typeof error.currentIndex === "bigint" && error.snapshotBinding?.programId === String(programId) && error.snapshotBinding?.pool === String(pool2)) {
59481
- movedIndex = error.currentIndex;
59482
- }
59546
+ const moved = error?.message === "p03_history_refresh_transition_detected" && typeof error.currentIndex === "bigint" && error.snapshotBinding?.programId === String(programId) && error.snapshotBinding?.pool === String(pool2);
59547
+ const earlierIndex = earlier.finalizedPoolSnapshot?.currentIndex;
59548
+ markPhase("refresh-missed", moved ? poolReadOutcome(error.snapshotSlot, typeof earlierIndex === "bigint" ? Number((error.currentIndex - earlierIndex) / SUBTREE_LEAVES4) : void 0) : void 0);
59549
+ if (moved) movedIndex = error.currentIndex;
59483
59550
  floor?.reset();
59484
59551
  if (strict) floor?.strict();
59485
59552
  }
@@ -74055,6 +74122,10 @@ async function runRecoveredP03NoteExit({
74055
74122
  photonUrl,
74056
74123
  // Parsed --recovered-note-file (recoveredNotesFromFile): notes the walk cannot find.
74057
74124
  recoveredNoteFile,
74125
+ // Relay path only (realSpeed3 2026-10-22, mcp55 Part 1): return once the relay answered (it answers only after the
74126
+ // exit is finalized) and the "confirmed" record holds the signature, with `settled: false` and `finish`, the post-exit
74127
+ // walk and postcondition below. The caller runs `finish` after its own answer; a rerun of this exit resumes the same way.
74128
+ answerAtRelay = false,
74058
74129
  deps = {}
74059
74130
  }) {
74060
74131
  const state = store.state;
@@ -74484,39 +74555,46 @@ async function runRecoveredP03NoteExit({
74484
74555
  }
74485
74556
  wal = { ...wal, state: "confirmed", signature: signature2 };
74486
74557
  store.writeWal(walName2, wal);
74487
- for (let attempt = 1; ; attempt += 1) {
74488
- history = await walkHistory();
74489
- markPhase("exit-post-walk");
74490
- poolState2 = await readPool2(connection, pool2, "finalized");
74491
- if (!poolState2 || !poolState2.owner.equals(chainProgramId) || poolState2.version !== 3) {
74492
- throw new Error("finalized note-exit pool postcondition invalid");
74558
+ const preExitHistory = history;
74559
+ const finish = async () => {
74560
+ for (let attempt = 1; ; attempt += 1) {
74561
+ history = await walkHistory();
74562
+ markPhase("exit-post-walk");
74563
+ poolState2 = await readPool2(connection, pool2, "finalized");
74564
+ if (!poolState2 || !poolState2.owner.equals(chainProgramId) || poolState2.version !== 3) {
74565
+ throw new Error("finalized note-exit pool postcondition invalid");
74566
+ }
74567
+ if (attempt >= 4 || history.finalizedPoolSnapshot.currentIndex === poolState2.currentIndex && history.finalizedPoolSnapshot.currentRoot === poolState2.currentRoot) break;
74493
74568
  }
74494
- if (attempt >= 4 || history.finalizedPoolSnapshot.currentIndex === poolState2.currentIndex && history.finalizedPoolSnapshot.currentRoot === poolState2.currentRoot) break;
74495
- }
74496
- await prepareFold(history);
74497
- const spentPath = buildRecoveryPath({
74498
- history,
74499
- recoveredOutput: recoveredForPath,
74500
- circuitSource: p03CircuitSource,
74501
- anchor: recoveryPath.anchor
74502
- });
74503
- if (spentPath.spendStatus !== "spent" || poolState2.currentIndex < frontier.nextIndex + 128n || history.finalizedPoolSnapshot.currentIndex !== poolState2.currentIndex || history.finalizedPoolSnapshot.currentRoot !== poolState2.currentRoot) {
74504
- throw new Error("finalized note-exit postcondition failed");
74569
+ await prepareFold(history);
74570
+ const spentPath = buildRecoveryPath({
74571
+ history,
74572
+ recoveredOutput: recoveredForPath,
74573
+ circuitSource: p03CircuitSource,
74574
+ anchor: recoveryPath.anchor
74575
+ });
74576
+ if (spentPath.spendStatus !== "spent" || poolState2.currentIndex < frontier.nextIndex + 128n || history.finalizedPoolSnapshot.currentIndex !== poolState2.currentIndex || history.finalizedPoolSnapshot.currentRoot !== poolState2.currentRoot) {
74577
+ throw new Error("finalized note-exit postcondition failed");
74578
+ }
74579
+ store.writeWal(walName2, { ...wal, state: "settled" });
74580
+ return Object.freeze({
74581
+ signature: signature2,
74582
+ replayed: false,
74583
+ noteCommitment: selected.commitment,
74584
+ amountAtomic: amount,
74585
+ poolIndex: poolState2.currentIndex,
74586
+ manifestSha256: proved.manifestSha256,
74587
+ ...denomination.record ? { denominationPolicy: denomination.record } : {},
74588
+ ...denominationWarnings.length > 0 ? { denominationWarnings } : {},
74589
+ ...foreignRecipient ? { recipientOwner: expectedRecipientOwner.toBase58() } : {},
74590
+ ...relayed ? { broadcaster: broadcasterKey } : {},
74591
+ ...relaySubmit ? { facilitatorRelay: true } : {}
74592
+ });
74593
+ };
74594
+ if (answerAtRelay === true && relaySubmit) {
74595
+ return Object.freeze({ signature: signature2, settled: false, noteCommitment: selected.commitment, amountAtomic: amount, preExitHistory, finish });
74505
74596
  }
74506
- store.writeWal(walName2, { ...wal, state: "settled" });
74507
- return Object.freeze({
74508
- signature: signature2,
74509
- replayed: false,
74510
- noteCommitment: selected.commitment,
74511
- amountAtomic: amount,
74512
- poolIndex: poolState2.currentIndex,
74513
- manifestSha256: proved.manifestSha256,
74514
- ...denomination.record ? { denominationPolicy: denomination.record } : {},
74515
- ...denominationWarnings.length > 0 ? { denominationWarnings } : {},
74516
- ...foreignRecipient ? { recipientOwner: expectedRecipientOwner.toBase58() } : {},
74517
- ...relayed ? { broadcaster: broadcasterKey } : {},
74518
- ...relaySubmit ? { facilitatorRelay: true } : {}
74519
- });
74597
+ return finish();
74520
74598
  }
74521
74599
  function loadBroadcasterKeypairFile(path) {
74522
74600
  let text3;
@@ -95998,11 +96076,11 @@ function dropFloatConfigs() {
95998
96076
  configCache.clear();
95999
96077
  configReads.clear();
96000
96078
  }
96001
- function warmFloatConfig({ origin, fetchImpl = fetch } = {}) {
96079
+ function warmFloatConfig({ origin, fetchImpl = fetch, timing } = {}) {
96002
96080
  try {
96003
96081
  const kept = configCache.get(origin);
96004
96082
  if (kept && Date.now() - kept.readAtMs < CONFIG_CACHE_MS && floatPoolRecentlyUsed(FLOAT_POOL_WARM_MS) || configReads.has(origin)) return;
96005
- const read = readFloatConfig({ origin, fetchImpl });
96083
+ const read = timedConfigRead({ origin, fetchImpl, timing, route: "warm" });
96006
96084
  configReads.set(origin, read);
96007
96085
  read.then(
96008
96086
  () => {
@@ -96015,6 +96093,18 @@ function warmFloatConfig({ origin, fetchImpl = fetch } = {}) {
96015
96093
  } catch {
96016
96094
  }
96017
96095
  }
96096
+ function timedConfigRead({ origin, fetchImpl, timing, route }) {
96097
+ const mark = (phase) => {
96098
+ try {
96099
+ timing?.(phase);
96100
+ } catch {
96101
+ }
96102
+ };
96103
+ mark("float-config-start");
96104
+ const read = readFloatConfig({ origin, fetchImpl });
96105
+ read.then(() => mark(`float-config-answered-${route}`), () => mark(`float-config-failed-${route}`));
96106
+ return read;
96107
+ }
96018
96108
  function refreshFloatConfig(origin, fetchImpl) {
96019
96109
  try {
96020
96110
  if (configRefreshes.has(origin) || configReads.has(origin)) return;
@@ -96031,19 +96121,32 @@ function refreshKeptFloatConfig({ origin, fetchImpl = fetch } = {}) {
96031
96121
  } catch {
96032
96122
  }
96033
96123
  }
96034
- async function readFloatOffer({ origin, paymentRequired, store, resourceUrl, fetchImpl = fetch }) {
96124
+ async function readFloatOffer({ origin, paymentRequired, store, resourceUrl, fetchImpl = fetch, timing }) {
96125
+ const mark = (phase) => {
96126
+ try {
96127
+ timing?.(phase);
96128
+ } catch {
96129
+ }
96130
+ };
96035
96131
  const inFlight = configReads.get(origin);
96036
96132
  if (inFlight) await inFlight.catch(() => void 0);
96037
96133
  const kept = configCache.get(origin);
96038
96134
  if (kept && Date.now() - kept.readAtMs < CONFIG_CACHE_MS) {
96039
96135
  try {
96040
- return { offer: floatOffer({ paymentRequired, store, config: kept.config, resourceUrl }), config: kept.config };
96136
+ const offer = floatOffer({ paymentRequired, store, config: kept.config, resourceUrl });
96137
+ mark(inFlight ? "float-config-joined-warm" : "float-config-joined-kept");
96138
+ return { offer, config: kept.config };
96041
96139
  } catch {
96042
96140
  configCache.delete(origin);
96043
96141
  }
96044
96142
  }
96045
96143
  const warm = configReads.get(origin);
96046
- const config = (warm ? await warm.catch(() => void 0) : void 0) ?? await readFloatConfig({ origin, fetchImpl });
96144
+ let config = warm ? await warm.catch(() => void 0) : void 0;
96145
+ if (config !== void 0) mark("float-config-joined-warm");
96146
+ else {
96147
+ config = await timedConfigRead({ origin, fetchImpl, timing, route: "read" });
96148
+ mark("float-config-joined-read");
96149
+ }
96047
96150
  return { offer: floatOffer({ paymentRequired, store, config, resourceUrl }), config };
96048
96151
  }
96049
96152
  function keepSellerTerms(origin, url, paymentRequired) {
@@ -98761,6 +98864,15 @@ async function openPayerSession({
98761
98864
  notePayHost(memoService);
98762
98865
  const connection = connectRelay(rpc2, "finalized");
98763
98866
  const timingLog = createTimingLog(`${path}.timing.log`, { component: "payer" });
98867
+ takeSiteOnionRaces().then((races) => {
98868
+ for (const race of races) {
98869
+ const mark = timingLog.start({ startedAt: race.startedAt });
98870
+ mark("onion:race-start", void 0, race.startedAt);
98871
+ for (const probe of race.probes) mark(`onion:${probe.role}-${probe.event}`, void 0, probe.at);
98872
+ mark("onion:race-end", race.answered ? "answered" : "none", race.endedAt);
98873
+ }
98874
+ }).catch(() => {
98875
+ });
98764
98876
  let closed = false;
98765
98877
  let queue = Promise.resolve();
98766
98878
  const session = {
@@ -98820,6 +98932,7 @@ var init_session = __esm({
98820
98932
  init_processLease();
98821
98933
  init_proxy();
98822
98934
  init_keepAlive();
98935
+ init_facilitatorOnion();
98823
98936
  RELAY_READ_TIMEOUT_MS = 3e4;
98824
98937
  READ_ONLY_METHOD = /^(get[A-Za-z]+|simulateTransaction|isBlockhashValid|minimumLedgerSlot)$/;
98825
98938
  connectRelay = (rpcUrl, commitment = "finalized") => connect2(rpcUrl, commitment, { wrapFetch: (fetch2) => relayRpcFetch(fetch2) });
@@ -104588,6 +104701,20 @@ function computeBalance({ store, snapshotRead, source = "local", now = Date.now(
104588
104701
  const inputs = provenInputs(byName.get(`resource-proof-${entry.name.slice("resource-payment-".length)}`)?.record);
104589
104702
  return Boolean(digest2) && snapshotDigests.has(digest2) && inputs !== void 0 && inputs.length > 0 && inputs.every((commitment) => spentInSnapshot.has(commitment));
104590
104703
  };
104704
+ const answeredExits = /* @__PURE__ */ new Map();
104705
+ for (const entry of records) {
104706
+ if (entry.type !== "unshield" || entry.name.startsWith(POCKET_FILL_RECORD) || entry.name.startsWith(MERGE_RECORD) || !Number.isSafeInteger(entry.record.exitAnsweredAtMs) || typeof entry.record.exitTransaction !== "string" || !canonicalAtomic(String(entry.record.noteCommitment ?? ""))) continue;
104707
+ answeredExits.set(String(entry.record.noteCommitment), {
104708
+ changeAtomic: canonicalAtomic(entry.record.changeAtomic) ? BigInt(entry.record.changeAtomic) : 0n,
104709
+ unconfirmed: entry.record.exitUnconfirmed === true
104710
+ });
104711
+ }
104712
+ const answeredSettling = (note, answered) => {
104713
+ const output2 = unspent.get(note);
104714
+ if (output2 === void 0) return void 0;
104715
+ const exited = BigInt(output2.valueAtomic) - answered.changeAtomic;
104716
+ return exited > 0n ? { inputs: [note], amountAtomic: exited, join: false } : void 0;
104717
+ };
104591
104718
  for (const entry of records) {
104592
104719
  const before = Object.hasOwn(snapshot.walStates, entry.name) ? snapshot.walStates[entry.name] : void 0;
104593
104720
  const beforePhase = before === void 0 ? "absent" : entry.type === "payment-proof" ? entry.phase : classify(entry.name, { ...entry.record, state: before }).phase;
@@ -104595,6 +104722,18 @@ function computeBalance({ store, snapshotRead, source = "local", now = Date.now(
104595
104722
  const join37 = entry.type === "payment" && joinedAccountOf(entry, byName) !== void 0;
104596
104723
  if (entry.phase === "done" && beforePhase !== "done") {
104597
104724
  if (join37 && snapshotDigests.has(digestKey(entry.record.memoDigest))) continue;
104725
+ const answeredExit = ["unshield", "note-exit"].includes(entry.type) ? answeredExits.get(String(entry.record.noteCommitment)) : void 0;
104726
+ if (answeredExit !== void 0) {
104727
+ const note = String(entry.record.noteCommitment);
104728
+ if (spentInSnapshot.has(note) || entry.type === "note-exit" && unspent.has(note)) continue;
104729
+ const exiting = answeredSettling(note, answeredExit);
104730
+ if (exiting) {
104731
+ settling.push(exiting);
104732
+ noteSettling(entry.name);
104733
+ settlingReasons.push(`unshield record ${entry.name} was answered after the last refresh: its exit is final on chain and the balance counts it as sent; refresh again in a minute`);
104734
+ continue;
104735
+ }
104736
+ }
104598
104737
  const paying = settlingPayment(entry);
104599
104738
  if (paying) {
104600
104739
  settling.push(paying);
@@ -104672,6 +104811,24 @@ function computeBalance({ store, snapshotRead, source = "local", now = Date.now(
104672
104811
  listedNotes.add(note);
104673
104812
  const fill = entry.name.startsWith(POCKET_FILL_RECORD);
104674
104813
  const merge = entry.name.startsWith(MERGE_RECORD);
104814
+ const answered = answeredExits.get(note);
104815
+ if (answered !== void 0) {
104816
+ const exiting = answered.changeAtomic > 0n && !spentInWalk.has(note) ? answeredSettling(note, answered) : void 0;
104817
+ if (exiting) {
104818
+ settling.push(exiting);
104819
+ noteSettling(entry.name);
104820
+ settlingReasons.push(`unshield record ${entry.name} was answered: its exit is final on chain and the balance counts it as sent; its change becomes spendable at the next refresh`);
104821
+ }
104822
+ detail.inFlight.push({
104823
+ kind: "unshield-exit",
104824
+ state: entry.state,
104825
+ amountAtomic: entry.record.amountAtomic ?? null,
104826
+ answered: true,
104827
+ inputs: exiting ? "settling" : reserve([note], `unshield ${entry.name}`),
104828
+ next: answered.unconfirmed ? "rerun the same unshield command; it resumes the exit (the pool history has not shown this exit as spent for 10 minutes)" : "nothing to do: the exit is final on chain and this wallet checks it against the pool history by itself"
104829
+ });
104830
+ continue;
104831
+ }
104675
104832
  const inputs = reserve(
104676
104833
  [note],
104677
104834
  `unshield ${entry.name}`,
@@ -104715,7 +104872,8 @@ function computeBalance({ store, snapshotRead, source = "local", now = Date.now(
104715
104872
  }
104716
104873
  const anchorIndex = canonicalAtomic(snapshot.anchorIndex) ? BigInt(snapshot.anchorIndex) : void 0;
104717
104874
  detail.fundedAccounts = unknownInputs ? 0 : [...unspent.values()].filter((output2) => output2.kind === "account" && BigInt(output2.valueAtomic) > 0n && !reserved.has(output2.commitment) && !joining.has(output2.commitment) && !settlingInputs.has(output2.commitment) && (anchorIndex === void 0 || BigInt(output2.leafIndex) < anchorIndex)).length;
104718
- const next = detail.inFlight.length ? "finish or cancel the work in detail.inFlight; the wallet runs one payment at a time" : pendingDeposits.length ? `deposit ${pendingDeposits[0].depositId} is not finished: rerun the same deposit command with --submit --service <facilitator URL>` : pocketsUnscanned ? scanPockets2 : unspentAtomic === 0n && successionMovableAtomic > 0n ? "this pool was succeeded by a new pool: its value moves there (a move per account, the facilitator pays the fee) and counts in this balance until then" : unspentAtomic === 0n ? "nothing to spend: deposit (shinjuku-wallet help deposit), or run balance --refresh if a payment to this wallet settled" : void 0;
104875
+ const answeredOnly = detail.inFlight.length > 0 && detail.inFlight.every((item) => item.answered === true);
104876
+ const next = answeredOnly ? detail.inFlight.find((item) => item.next.startsWith("rerun"))?.next ?? "nothing to do: an answered unshield finishes its check by itself" : detail.inFlight.length ? "finish or cancel the work in detail.inFlight; the wallet runs one payment at a time" : pendingDeposits.length ? `deposit ${pendingDeposits[0].depositId} is not finished: rerun the same deposit command with --submit --service <facilitator URL>` : pocketsUnscanned ? scanPockets2 : unspentAtomic === 0n && successionMovableAtomic > 0n ? "this pool was succeeded by a new pool: its value moves there (a move per account, the facilitator pays the fee) and counts in this balance until then" : unspentAtomic === 0n ? "nothing to spend: deposit (shinjuku-wallet help deposit), or run balance --refresh if a payment to this wallet settled" : void 0;
104719
104877
  if (movedInAtomic > 0n) {
104720
104878
  settlingReasons.push(`${formatAtomic(movedInAtomic, decimals)} moved into this pool from the old pool after the last refresh: the balance counts it; refresh again in a minute`);
104721
104879
  }
@@ -104850,7 +105008,7 @@ function requestBody(method, body) {
104850
105008
  if (bytes2.length > 1024 * 1024) fail24("fetch_options_invalid", "the body is over 1 MiB");
104851
105009
  return bytes2;
104852
105010
  }
104853
- async function offeredPrice(paymentRequired, store, serviceUrl2, target) {
105011
+ async function offeredPrice(paymentRequired, store, serviceUrl2, target, timing) {
104854
105012
  let rail = paymentRail(paymentRequired);
104855
105013
  if (rail === "confidential" && paymentRequired.accepts.some((offer) => offer?.scheme === "exact")) rail = "exact";
104856
105014
  if (rail === "none") {
@@ -104861,7 +105019,7 @@ async function offeredPrice(paymentRequired, store, serviceUrl2, target) {
104861
105019
  }
104862
105020
  if (rail === "exact" && store) {
104863
105021
  const origin = new URL(serviceUrl2).origin;
104864
- const { offer, config } = await readFloatOffer({ origin, paymentRequired, store, resourceUrl: target });
105022
+ const { offer, config } = await readFloatOffer({ origin, paymentRequired, store, resourceUrl: target, timing });
104865
105023
  return { rail, amountAtomic: offer.paidAtomic, priceAtomic: offer.priceAtomic, floatConfig: { origin, config } };
104866
105024
  }
104867
105025
  let highest;
@@ -104971,7 +105129,7 @@ async function payAndFetch(session, url, init = {}, opts = {}) {
104971
105129
  if (ahead) {
104972
105130
  mark("leg1-ahead");
104973
105131
  } else {
104974
- warmFloatConfig({ origin: new URL(session.memoServiceUrl).origin });
105132
+ warmFloatConfig({ origin: new URL(session.memoServiceUrl).origin, timing: mark });
104975
105133
  }
104976
105134
  startRecoveredPaymentTools(lazyProofTools(session));
104977
105135
  }
@@ -104995,7 +105153,7 @@ async function payAndFetch(session, url, init = {}, opts = {}) {
104995
105153
  }
104996
105154
  challenge3 = { encoded, atMs: Date.now() };
104997
105155
  mark("fetch-challenge");
104998
- price = await offeredPrice(paymentRequired, store ?? session.openStore(), session.memoServiceUrl, target);
105156
+ price = await offeredPrice(paymentRequired, store ?? session.openStore(), session.memoServiceUrl, target, mark);
104999
105157
  mark("fetch-price");
105000
105158
  if (maxPrice !== void 0 && BigInt(price.priceAtomic) > BigInt(maxPrice)) {
105001
105159
  fail24(
@@ -109097,7 +109255,8 @@ function build(a, tool) {
109097
109255
  if (a.stage === "confirming") return "Waiting for you to confirm the send in your app.";
109098
109256
  const amount = usdcText(a.done === true ? a.sentAtomic ?? a.amountAtomic : a.amountAtomic);
109099
109257
  const what = amount ? `${amount} USDC` : "the USDC";
109100
- return a.done === true ? `Sent ${what} to ${a.to}.` : `Sending ${what} privately to ${a.to}.`;
109258
+ if (a.done !== true) return `Sending ${what} privately to ${a.to}.`;
109259
+ return typeof a.exitTransaction === "string" && a.privateSend === void 0 ? `Sent ${what} to ${a.to} (transaction ${a.exitTransaction}). Your private balance shows it in a few seconds; nothing to do.` : `Sent ${what} to ${a.to}.`;
109101
109260
  }
109102
109261
  if (typeof a.address === "string" && a.address && typeof a.stage === "string") return linkSay(a);
109103
109262
  if (a.stage === "waiting_for_funds" && typeof a.depositAddress === "string") {
@@ -117592,7 +117751,9 @@ async function runUnshield({
117592
117751
  profile,
117593
117752
  facilitatorOrigin: facilitatorOrigin2,
117594
117753
  onStage,
117595
- selfPayStep = selfPay
117754
+ selfPayStep = selfPay,
117755
+ onAnswered,
117756
+ onCheckPending
117596
117757
  }) {
117597
117758
  const owner = new import_web3133.PublicKey(recipientOwner);
117598
117759
  const mint = new import_web3133.PublicKey(store.state.asset);
@@ -117649,7 +117810,9 @@ async function runUnshield({
117649
117810
  onStage,
117650
117811
  walk,
117651
117812
  mark,
117652
- scoped
117813
+ scoped,
117814
+ onAnswered,
117815
+ onCheckPending
117653
117816
  });
117654
117817
  }
117655
117818
  if (record?.mode !== void 0) throw new Error("mcp_unshield_failed: this unshield's record names an exit mode this version does not know; nothing new was signed. Update the wallet");
@@ -117692,12 +117855,50 @@ async function runUnshield({
117692
117855
  allowForeignRecipient: true,
117693
117856
  relaySubmit: facilitatorExitRelay(facilitatorOrigin2),
117694
117857
  requestAnchor: markedAnchorRequest(facilitatorAnchor(facilitatorOrigin2)),
117858
+ answerAtRelay: true,
117695
117859
  deps: { walkHistory: walk(exitProofTools.deploymentIdentity) }
117696
117860
  }), { ring: { connection: session.connection, pool: store.state.pool } }));
117861
+ const answer = { selfPayTransaction: paid.transaction ?? null, exitTransaction: exit.signature ?? null };
117862
+ if (exit.settled === false && !await finishAnsweredExit({ session, store, name, exit, answer, onAnswered, onCheckPending, mark, scoped })) return answer;
117697
117863
  mark("unshield-exited");
117698
117864
  const exited = store.readWal(`self-pay-${unshieldId}`);
117699
117865
  store.writeWal(`self-pay-${unshieldId}`, { ...exited, state: "exited", exitTransaction: exit.signature, exitedAtMs: exited?.exitedAtMs ?? Date.now() });
117700
- return { selfPayTransaction: paid.transaction ?? null, exitTransaction: exit.signature ?? null };
117866
+ return answer;
117867
+ }
117868
+ async function finishAnsweredExit({ session, store, name, exit, answer, changeAtomic, onAnswered, onCheckPending, mark, scoped }) {
117869
+ if (typeof onAnswered !== "function") {
117870
+ await scoped("unshield-exit", () => exit.finish());
117871
+ return true;
117872
+ }
117873
+ const record = store.readWal(name);
117874
+ store.writeWal(name, {
117875
+ ...record,
117876
+ exitTransaction: exit.signature,
117877
+ exitAnsweredAtMs: record?.exitAnsweredAtMs ?? Date.now(),
117878
+ ...changeAtomic !== void 0 ? { changeAtomic } : {}
117879
+ });
117880
+ try {
117881
+ await scoped("unshield-exit", () => refreshWallet({ session, store, history: exit.preExitHistory }));
117882
+ } catch {
117883
+ mark("unshield-exit-snapshot-failed");
117884
+ }
117885
+ mark("unshield-exit-answered");
117886
+ try {
117887
+ onAnswered(answer);
117888
+ } catch {
117889
+ }
117890
+ try {
117891
+ await scoped("unshield-exit", () => exit.finish());
117892
+ return true;
117893
+ } catch (error) {
117894
+ mark("unshield-exit-check-pending");
117895
+ store.writeWal(name, { ...store.readWal(name), exitCheckPendingAtMs: Date.now() });
117896
+ try {
117897
+ onCheckPending?.(error);
117898
+ } catch {
117899
+ }
117900
+ return false;
117901
+ }
117701
117902
  }
117702
117903
  async function runDirectExit({
117703
117904
  session,
@@ -117714,7 +117915,9 @@ async function runDirectExit({
117714
117915
  walk,
117715
117916
  mark = () => {
117716
117917
  },
117717
- scoped = (_prefix, fn) => fn()
117918
+ scoped = (_prefix, fn) => fn(),
117919
+ onAnswered,
117920
+ onCheckPending
117718
117921
  }) {
117719
117922
  const name = `self-pay-${unshieldId}`;
117720
117923
  if (record.amountAtomic !== String(amountAtomic) || record.exitAmountAtomic !== String(amountAtomic)) {
@@ -117752,6 +117955,7 @@ async function runDirectExit({
117752
117955
  allowForeignRecipient: true,
117753
117956
  relaySubmit: facilitatorExitRelay(facilitatorOrigin2),
117754
117957
  requestAnchor: markedAnchorRequest(facilitatorAnchor(facilitatorOrigin2)),
117958
+ answerAtRelay: true,
117755
117959
  deps: { walkHistory: walk(exitProofTools.deploymentIdentity), proveExit }
117756
117960
  }), { ring: { connection: session.connection, pool: store.state.pool } }));
117757
117961
  } catch (error) {
@@ -117760,10 +117964,13 @@ async function runDirectExit({
117760
117964
  }
117761
117965
  throw error;
117762
117966
  }
117967
+ const answer = { selfPayTransaction: null, exitTransaction: exit.signature ?? null };
117968
+ const changeAtomic = exitAmountAtomic === void 0 ? void 0 : (BigInt(record.noteValueAtomic) - BigInt(record.exitAmountAtomic)).toString();
117969
+ if (exit.settled === false && !await finishAnsweredExit({ session, store, name, exit, answer, changeAtomic, onAnswered, onCheckPending, mark, scoped })) return answer;
117763
117970
  mark("unshield-exited");
117764
117971
  const exited = store.readWal(name);
117765
117972
  store.writeWal(name, { ...exited, state: "exited", exitTransaction: exit.signature, exitedAtMs: exited?.exitedAtMs ?? Date.now() });
117766
- return { selfPayTransaction: null, exitTransaction: exit.signature ?? null };
117973
+ return answer;
117767
117974
  }
117768
117975
  function movingAtomic(store) {
117769
117976
  let moving = 0n;
@@ -119583,6 +119790,12 @@ ${canonical5}`;
119583
119790
  watcher?.kick?.();
119584
119791
  } catch {
119585
119792
  }
119793
+ if (resumeSends) {
119794
+ try {
119795
+ finishAnsweredExits();
119796
+ } catch {
119797
+ }
119798
+ }
119586
119799
  let store;
119587
119800
  try {
119588
119801
  store = session.openStore();
@@ -120096,7 +120309,7 @@ ${canonical5}`;
120096
120309
  amountAtomic: typeof record.amountAtomic === "string" ? record.amountAtomic : null,
120097
120310
  to: record.mcpTo,
120098
120311
  // One word per state (real-user pass F3): "sent" as wallet_cancel and openSendState say, never the record's "exited".
120099
- state: retry ? "retrying" : joining ? "joining" : record.state === "exited" ? "sent" : live === "sending" ? "sending" : idle ?? record.state,
120312
+ state: retry ? "retrying" : joining ? "joining" : record.state === "exited" || live === "sent" ? "sent" : live === "sending" ? "sending" : idle ?? record.state,
120100
120313
  transaction: sigOrNull(record.exitTransaction),
120101
120314
  at,
120102
120315
  ...record.state === "exited" && started !== void 0 ? { startedAt: isoOf(started) } : {},
@@ -120542,7 +120755,8 @@ ${canonical5}`;
120542
120755
  return sendDoneNext(job.to, job.result.privateSend) + `${job.result?.backupPath ? `. An encrypted backup of the wallet was saved at ${job.result.backupPath}` : ""}`;
120543
120756
  }
120544
120757
  if (kind === "unshield") {
120545
- return `the USDC was sent to ${job.to}; tell the user (exitTransaction). wallet_balance shows the new shielded balance${job.result?.backupPath ? `. An encrypted backup of the wallet was saved at ${job.result.backupPath}` : ""}`;
120758
+ const tx = job.result?.exitTransaction;
120759
+ return `Sent ${usdcText2(job.amountAtomic)} USDC to ${job.to}${typeof tx === "string" ? ` (transaction ${tx})` : ""}. Tell the user; nothing to do: the private balance shows it in a few seconds (wallet_balance)${job.result?.backupPath ? `. An encrypted backup of the wallet was saved at ${job.result.backupPath}` : ""}`;
120546
120760
  }
120547
120761
  const post = job.post;
120548
120762
  if (!post || post.state === "running") {
@@ -120995,7 +121209,9 @@ ${canonical5}`;
120995
121209
  }
120996
121210
  function singleSendState(store, requestId, intent) {
120997
121211
  const unshieldId = moneyId(requestId);
120998
- if (store.readWal(`self-pay-${unshieldId}`)?.state === "exited") return "sent";
121212
+ const record = store.readWal(`self-pay-${unshieldId}`);
121213
+ if (record?.state === "exited") return "sent";
121214
+ if (Number.isSafeInteger(record?.exitAnsweredAtMs) && typeof record.exitTransaction === "string" && record.exitUnconfirmed !== true) return "sent";
120999
121215
  if (jobs.get(`unshield:${toRecordId(requestId)}`)?.state === "running" || selfPayMayHaveSigned(store, unshieldId)) return "sending";
121000
121216
  return intent ? "joining" : void 0;
121001
121217
  }
@@ -121478,7 +121694,7 @@ ${canonical5}`;
121478
121694
  if (known && known.state !== "failed" && known.state !== "waiting" && sameRequest(known, request)) {
121479
121695
  const wait3 = waitFor();
121480
121696
  if (known.state === "running" && wait3 > 0) {
121481
- const ends = (kind === "shield" || kind === "unshield") && known.stage !== "confirming" ? [known.promise] : [known.promise, stageChanged(known)];
121697
+ const ends = (kind === "shield" || kind === "unshield") && known.stage !== "confirming" ? [known.answered ?? known.promise] : [known.promise, stageChanged(known)];
121482
121698
  await statusWait(
121483
121699
  ends,
121484
121700
  wait3,
@@ -121853,6 +122069,10 @@ ${canonical5}`;
121853
122069
  if (kind !== "unshield") afterShield(job);
121854
122070
  else refreshAtFinality();
121855
122071
  }, async (error) => {
122072
+ if (job.answeredEarly) {
122073
+ log(`MCP_UNSHIELD_AFTER_ANSWER_FAILED requestId=${job.requestId} cause=${JSON.stringify(String(error?.message ?? error ?? "").split("\n", 1)[0].slice(0, 240))}`);
122074
+ return;
122075
+ }
121856
122076
  const linkHeld = /* @__PURE__ */ new Map();
121857
122077
  if (kind === "unshield" && /p03_merchant_respend_no_spendable_account|insufficient_recovered_value|insufficient_value/.test(String(error?.message ?? error))) {
121858
122078
  try {
@@ -121896,7 +122116,14 @@ ${canonical5}`;
121896
122116
  recorded = true;
121897
122117
  }
121898
122118
  if (!recorded) uncount(kind, key);
121899
- }).finally(() => signalStage(job));
122119
+ }).finally(() => {
122120
+ job.finishing = false;
122121
+ signalStage(job);
122122
+ });
122123
+ job.answered = new Promise((resolve38) => {
122124
+ job.answerNow = resolve38;
122125
+ job.promise.then(resolve38, resolve38);
122126
+ });
121900
122127
  if (needsConfirm) {
121901
122128
  await statusWait([confirmation], fullWait ? Infinity : budgetLeft(startedAt), progress3, "waiting for the user to confirm the send");
121902
122129
  if (job.stage === "confirming" || job.state === "failed") return jobAnswer(kind, job);
@@ -121905,7 +122132,7 @@ ${canonical5}`;
121905
122132
  if (wait2 === Infinity) await job.promise.catch(() => {
121906
122133
  });
121907
122134
  else if (wait2 > 0) {
121908
- await statusWait([job.promise], wait2, progress3, kind === "unshield" ? "sending privately; it takes a few minutes" : kind === "elsewhere" ? "checking the one-time address; a deposit takes a few minutes" : "making the USDC private; it takes a few minutes");
122135
+ await statusWait([job.answered], wait2, progress3, kind === "unshield" ? "sending privately; it takes a few minutes" : kind === "elsewhere" ? "checking the one-time address; a deposit takes a few minutes" : "making the USDC private; it takes a few minutes");
121909
122136
  }
121910
122137
  return jobAnswer(kind, job);
121911
122138
  }
@@ -121963,9 +122190,10 @@ ${canonical5}`;
121963
122190
  }
121964
122191
  return session.openStore();
121965
122192
  }
121966
- async function unshieldPart(opened, job, unshieldId, amountAtomic, onStage) {
122193
+ async function unshieldPart(opened, job, unshieldId, amountAtomic, onStage, onAnswered) {
121967
122194
  const store = await refreshStaleBeforeUnshield(opened, onStage);
121968
122195
  bindUnshieldRecipient(store, unshieldId, amountAtomic, job.to, unshieldId === moneyId(job.requestId) ? job.requestId : void 0);
122196
+ let backup;
121969
122197
  const result = await (ops.unshield ?? runUnshield)({
121970
122198
  session,
121971
122199
  store,
@@ -121976,17 +122204,40 @@ ${canonical5}`;
121976
122204
  profile: money.profile,
121977
122205
  facilitatorOrigin: money.facilitatorOrigin,
121978
122206
  onStage,
121979
- ...ops.selfPay ? { selfPayStep: ops.selfPay } : {}
122207
+ ...ops.selfPay ? { selfPayStep: ops.selfPay } : {},
122208
+ ...typeof onAnswered === "function" ? { onAnswered: (answer) => {
122209
+ backup = backupNow(session.openStore());
122210
+ onAnswered({ ...answer, ...backup });
122211
+ } } : {},
122212
+ onCheckPending: (error) => {
122213
+ log(`MCP_UNSHIELD_CHECK_PENDING requestId=${job.requestId} cause=${JSON.stringify(String(error?.message ?? error ?? "").split("\n", 1)[0].slice(0, 240))}`);
122214
+ answeredExitTries.set(`self-pay-${unshieldId}`, { running: false, atMs: Date.now() });
122215
+ armAnsweredExitRetry();
122216
+ }
121980
122217
  });
121981
- return { ...result, ...backupNow(session.openStore()) };
122218
+ return { ...result, ...backup ?? backupNow(session.openStore()) };
121982
122219
  }
121983
122220
  function runUnshieldJob(job, onStage) {
121984
122221
  if (isSplitSend(job)) return runPrivateSendJob(job, onStage);
121985
122222
  return underLease((opened) => {
121986
122223
  if (!opened.readWal(`self-pay-${moneyId(job.requestId)}`)) assertUnshieldNotPromised(opened, job.amountAtomic);
121987
- return unshieldPart(opened, job, moneyId(job.requestId), job.amountAtomic, onStage);
122224
+ return unshieldPart(opened, job, moneyId(job.requestId), job.amountAtomic, onStage, (answer) => answerUnshieldEarly(job, answer));
121988
122225
  });
121989
122226
  }
122227
+ function answerUnshieldEarly(job, answer) {
122228
+ job.state = "done";
122229
+ job.doneAtMs = Date.now();
122230
+ job.result = answer;
122231
+ job.answeredEarly = true;
122232
+ job.finishing = true;
122233
+ try {
122234
+ closeJoinIntent(session.openStore(), job.requestId, "done");
122235
+ } catch {
122236
+ }
122237
+ log(`MCP_UNSHIELD_ANSWERED requestId=${job.requestId}`);
122238
+ signalStage(job);
122239
+ job.answerNow?.();
122240
+ }
121990
122241
  async function runPrivateSendJob(job, onStage) {
121991
122242
  const sendId = moneyId(job.requestId);
121992
122243
  let store;
@@ -122198,6 +122449,89 @@ ${canonical5}`;
122198
122449
  }
122199
122450
  }
122200
122451
  const resumeRefusals = /* @__PURE__ */ new Map();
122452
+ const answeredExitTries = /* @__PURE__ */ new Map();
122453
+ let answeredExitRetryTimer;
122454
+ function finishAnsweredExits() {
122455
+ if (!unshieldEnabled || finalityClosed || session.closed === true) return;
122456
+ let store;
122457
+ try {
122458
+ store = session.openStore();
122459
+ } catch {
122460
+ return;
122461
+ }
122462
+ let names = [];
122463
+ try {
122464
+ names = store.listWal().filter((name) => name.startsWith("self-pay-") && !name.startsWith("self-pay-merge-") && !name.startsWith("self-pay-pocket-"));
122465
+ } catch {
122466
+ return;
122467
+ }
122468
+ for (const name of names) {
122469
+ let record;
122470
+ try {
122471
+ record = store.readWal(name);
122472
+ } catch {
122473
+ continue;
122474
+ }
122475
+ if (record?.schema !== SELF_PAY_WAL_SCHEMA || record.state === "exited" || record.exitUnconfirmed === true || typeof record.exitTransaction !== "string" || typeof record.mcpTo !== "string" || !ATOMIC_TEXT.test(String(record.amountAtomic))) continue;
122476
+ const tried = answeredExitTries.get(name);
122477
+ if (tried !== void 0 && (tried.running || Date.now() - tried.atMs < ANSWERED_EXIT_RETRY_MS)) continue;
122478
+ const attempt = { running: true, atMs: Date.now() };
122479
+ answeredExitTries.set(name, attempt);
122480
+ const unshieldId = name.slice("self-pay-".length);
122481
+ log(`MCP_UNSHIELD_CHECK_RESUME unshieldId=${unshieldId}`);
122482
+ underLease((opened) => {
122483
+ const current = opened.readWal(name);
122484
+ if (current?.state === "exited" || current?.exitUnconfirmed === true || typeof current?.exitTransaction !== "string") return void 0;
122485
+ return (ops.unshield ?? runUnshield)({
122486
+ session,
122487
+ store: opened,
122488
+ unshieldId,
122489
+ amountAtomic: current.amountAtomic,
122490
+ recipientOwner: current.mcpTo,
122491
+ exitProofTools: money.exitProofTools,
122492
+ profile: money.profile,
122493
+ facilitatorOrigin: money.facilitatorOrigin,
122494
+ onStage: () => {
122495
+ },
122496
+ ...ops.selfPay ? { selfPayStep: ops.selfPay } : {}
122497
+ });
122498
+ }, { orClosed: ANSWERED_EXIT_CLOSED }).then((result) => {
122499
+ if (result === ANSWERED_EXIT_CLOSED) return;
122500
+ const now = session.openStore().readWal(name);
122501
+ log(now?.state === "exited" ? `MCP_UNSHIELD_CHECK_DONE unshieldId=${unshieldId}` : `MCP_UNSHIELD_CHECK_PENDING unshieldId=${unshieldId}`);
122502
+ if (now?.state === "exited") refreshAtFinality();
122503
+ }, (error) => {
122504
+ const cause = String(error?.message ?? error ?? "").split("\n", 1)[0];
122505
+ attempt.provenUnspent = /confirmed note-exit is not finalized spent/.test(cause);
122506
+ log(`MCP_UNSHIELD_CHECK_PENDING unshieldId=${unshieldId} cause=${JSON.stringify(cause.slice(0, 240))}`);
122507
+ }).then(() => underLease((opened) => {
122508
+ const now = opened.readWal(name);
122509
+ if (attempt.provenUnspent === true && now?.state !== "exited" && now?.exitUnconfirmed !== true && Number.isSafeInteger(now?.exitAnsweredAtMs) && Date.now() - now.exitAnsweredAtMs > ANSWERED_EXIT_UNCONFIRMED_MS) {
122510
+ opened.writeWal(name, { ...now, exitUnconfirmed: true });
122511
+ log(`MCP_UNSHIELD_UNCONFIRMED unshieldId=${unshieldId}`);
122512
+ }
122513
+ }, { orClosed: ANSWERED_EXIT_CLOSED })).catch(() => {
122514
+ }).finally(() => {
122515
+ attempt.running = false;
122516
+ let now;
122517
+ try {
122518
+ now = session.openStore().readWal(name);
122519
+ } catch {
122520
+ now = void 0;
122521
+ }
122522
+ if (now?.state === "exited" || now?.exitUnconfirmed === true || !Number.isSafeInteger(now?.exitAnsweredAtMs) || Date.now() - now.exitAnsweredAtMs > ANSWERED_EXIT_RETRY_WINDOW_MS) return;
122523
+ armAnsweredExitRetry();
122524
+ });
122525
+ }
122526
+ }
122527
+ function armAnsweredExitRetry() {
122528
+ if (finalityClosed || session.closed === true || answeredExitRetryTimer !== void 0) return;
122529
+ answeredExitRetryTimer = setTimeout(() => {
122530
+ answeredExitRetryTimer = void 0;
122531
+ finishAnsweredExits();
122532
+ }, ANSWERED_EXIT_RETRY_MS + 100);
122533
+ answeredExitRetryTimer.unref?.();
122534
+ }
122201
122535
  async function cancelElsewhere(args) {
122202
122536
  const spec = KINDS.elsewhere;
122203
122537
  if (!spec.enabled()) return refuse11("mcp_tool_disabled", `${spec.tool} is off: the owner did not enable it when starting this MCP server`, spec.disabledNext);
@@ -124221,6 +124555,8 @@ ${JSON.stringify(args ?? {})}`;
124221
124555
  ));
124222
124556
  const resumeTimer = resumeSends ? setTimeout(resumePrivateSends, 0) : void 0;
124223
124557
  resumeTimer?.unref?.();
124558
+ const answeredExitTimer = resumeSends ? setTimeout(finishAnsweredExits, 0) : void 0;
124559
+ answeredExitTimer?.unref?.();
124224
124560
  const tempCleanTimer = typeof session.walletPath === "string" ? setTimeout(() => {
124225
124561
  const pool2 = dirname24(session.walletPath);
124226
124562
  removeStaleTemporaryFiles([pool2, join28(pool2, "wal"), join28(pool2, "move-counts")]).then((removed) => {
@@ -124246,6 +124582,8 @@ ${JSON.stringify(args ?? {})}`;
124246
124582
  for (const timer of joinTimers) clearTimeout(timer);
124247
124583
  joinTimers.clear();
124248
124584
  clearTimeout(resumeTimer);
124585
+ clearTimeout(answeredExitTimer);
124586
+ clearTimeout(answeredExitRetryTimer);
124249
124587
  clearTimeout(tempCleanTimer);
124250
124588
  clearTimeout(intentStartTimer);
124251
124589
  clearTimeout(intentTimer);
@@ -124269,6 +124607,7 @@ ${JSON.stringify(args ?? {})}`;
124269
124607
  */
124270
124608
  moving: { value: () => {
124271
124609
  if (moneyCallsInFlight - joinWaitingCalls > 0 || [...jobs.values()].some((job) => job.state === "running")) return true;
124610
+ if ([...jobs.values()].some((job) => job.finishing === true) || [...answeredExitTries.values()].some((attempt) => attempt.running)) return true;
124272
124611
  try {
124273
124612
  return mover.running?.() === true || successionRunning() || transferRuns.size > 0 || Boolean(joiner.status().running) || adopter.busy?.() === true || autoPockets.busy?.() === true || watcher?.depositing?.() === true;
124274
124613
  } catch {
@@ -124577,7 +124916,7 @@ function createSetupTools({
124577
124916
  };
124578
124917
  return { tools: [setupTool, statusTool, ...MCP_TOOL_NAMES.map((name) => name === "x402_discover" ? discoverTool(discoverCacheDir) : notSetUp(name))], doneTool };
124579
124918
  }
124580
- var import_web3133, shownCaps, RELEASE_RETRY_MS, STAGE_WORDS, stageWords, INTERNAL_BALANCE_DETAIL, shownBreakdown, sessionLeftShown, TOKEN_UNIT_TOOLS, RESOURCE_PAYMENT_WAL_PREFIX2, POOL_LAYOUT_CODE, POOL_LAYOUT_UPDATE, USEPOD_VIA_INFERENCE_CHAT, SELF_PAY_ORIGIN3, REQUEST_ID4, POSITIVE_ATOMIC6, PRIVATE_HOST3, FORBIDDEN_HEADERS3, MAX_BODY_BYTES2, PREVIEW_TIMEOUT_MS, PREVIEW_SELLER_READ_MS, CALL_BUDGET_MS, budgetLeft, MONEY_FIRST_ANSWER_MS, JOIN_CALL_MAX_MS, JOIN_STEP_MS, sleep2, PROGRESS_EVERY_MS2, READ_BUDGET_MS, UNPAID_TOR_TRY_MS, UNPAID_CLEAR_MS, READ_BUDGET_TOOLS, FLOAT_BUSY_CODES, RETRY_MARGIN_MS, ASK_PAY_CODES, limitNext, SAFE_TEXT, MCP_TOOL_ALIASES, POOL_SWITCH_WHY, POOL_SWITCH_NEXT, SWITCH_STATES, quietSwitch, switchKey, ATOMIC_TEXT, switchAmount, switchEta, switchRows, OWNER_CAP_CODES, JOIN_END_MCP, JOIN_END_TERMINAL_LIBRARY, JOIN_RETRY_BACKOFF_MS, WALLET_CAP_FIX, capFix, ownerOnlyNext, mcpNext, CONFIDENTIAL_RETIRED, json2, answerResult, discoverTool, refuse11, publicOffer, FLOAT_PAID_CODES, floatStillSettling, MONEY_ID2, toRecordId, linkKindOf, moneyId, REFUSED_CODES, refusedCodeOf, PLAIN_REASONS, plainReason, KEPT_SEND_TRIES, shownId, isSplitSend, sendPartId, PROMISED_NEXT, PUBLIC_READ_TIMEOUT_MS, withTimeout, ownKey, readFailure, OWN_KEY_TOKENS_MAX, OWN_TOKENS_GRACE_MS, tokenAmountText, elsewhereId, AGENT_ID_SCHEMA, agentIdName, minutesText2, DEPOSIT_SENT_STATES, depositLanded, usdcText2, walkWalletHistory, REFRESH_LEASE_WALK_MS, HOME_MARKS_SETTLED_MS2, AUTO_BACKUPS_KEPT, AUTO_BACKUP_NAME, AUTO_BACKUP_VERIFIED, FRONTED_SETTLE_MARGIN_SLOTS, BALANCE_REFRESH_WAIT_MS, BALANCE_REFRESH_GRACE_MS, BALANCE_REFRESH_LONG_MS, INBOUND_REFRESH_AGE_MS, CAP_BALANCE_WAIT_MS, CAP_BALANCE_RETRY_MS, REFRESH_RETRY_MS, FINALITY_POLL_MS, FINALITY_WAIT_MS, FINALITY_REFRESH_LIMIT_MS, POOL_WATCH_MS, POOL_WATCH_POLL_MS, EARLY_BALANCE_DELAY_MS, FINALITY_SETTLE_WINDOW_SLOTS, PUBLIC_READ_ANSWER_MS, SWITCH_WAIT_MS, UNSHIELD_TO_NO_USDC_DETAIL, UNSHIELD_TO_INVALID_NEXT, INFERENCE_MIN_MAX_TOKENS, ELICIT_TIMEOUT_MS, ELICIT_UNSHOWN_MS, elicitUnshown, POLL_SECONDS3, TEMP_CLEAN_DELAY_MS, KEPT_ON_DISK, MAX_SHIELDED_BALANCE_ATOMIC, receiveGenerator, MCP_TOOL_NAMES, MCP_HIDDEN_TOOL_NAMES, SETUP_TERMINAL_NEXT, SETUP_POLL_NEXT, PRIVATE_INFERENCE_WHY, PRIVATE_INFERENCE_NEXT;
124919
+ var import_web3133, shownCaps, RELEASE_RETRY_MS, STAGE_WORDS, stageWords, INTERNAL_BALANCE_DETAIL, shownBreakdown, sessionLeftShown, TOKEN_UNIT_TOOLS, RESOURCE_PAYMENT_WAL_PREFIX2, POOL_LAYOUT_CODE, POOL_LAYOUT_UPDATE, USEPOD_VIA_INFERENCE_CHAT, SELF_PAY_ORIGIN3, REQUEST_ID4, POSITIVE_ATOMIC6, PRIVATE_HOST3, FORBIDDEN_HEADERS3, MAX_BODY_BYTES2, PREVIEW_TIMEOUT_MS, PREVIEW_SELLER_READ_MS, CALL_BUDGET_MS, budgetLeft, MONEY_FIRST_ANSWER_MS, JOIN_CALL_MAX_MS, JOIN_STEP_MS, sleep2, PROGRESS_EVERY_MS2, READ_BUDGET_MS, UNPAID_TOR_TRY_MS, UNPAID_CLEAR_MS, READ_BUDGET_TOOLS, FLOAT_BUSY_CODES, RETRY_MARGIN_MS, ASK_PAY_CODES, limitNext, SAFE_TEXT, MCP_TOOL_ALIASES, POOL_SWITCH_WHY, POOL_SWITCH_NEXT, SWITCH_STATES, quietSwitch, switchKey, ATOMIC_TEXT, ANSWERED_EXIT_RETRY_MS, ANSWERED_EXIT_UNCONFIRMED_MS, ANSWERED_EXIT_RETRY_WINDOW_MS, ANSWERED_EXIT_CLOSED, switchAmount, switchEta, switchRows, OWNER_CAP_CODES, JOIN_END_MCP, JOIN_END_TERMINAL_LIBRARY, JOIN_RETRY_BACKOFF_MS, WALLET_CAP_FIX, capFix, ownerOnlyNext, mcpNext, CONFIDENTIAL_RETIRED, json2, answerResult, discoverTool, refuse11, publicOffer, FLOAT_PAID_CODES, floatStillSettling, MONEY_ID2, toRecordId, linkKindOf, moneyId, REFUSED_CODES, refusedCodeOf, PLAIN_REASONS, plainReason, KEPT_SEND_TRIES, shownId, isSplitSend, sendPartId, PROMISED_NEXT, PUBLIC_READ_TIMEOUT_MS, withTimeout, ownKey, readFailure, OWN_KEY_TOKENS_MAX, OWN_TOKENS_GRACE_MS, tokenAmountText, elsewhereId, AGENT_ID_SCHEMA, agentIdName, minutesText2, DEPOSIT_SENT_STATES, depositLanded, usdcText2, walkWalletHistory, REFRESH_LEASE_WALK_MS, HOME_MARKS_SETTLED_MS2, AUTO_BACKUPS_KEPT, AUTO_BACKUP_NAME, AUTO_BACKUP_VERIFIED, FRONTED_SETTLE_MARGIN_SLOTS, BALANCE_REFRESH_WAIT_MS, BALANCE_REFRESH_GRACE_MS, BALANCE_REFRESH_LONG_MS, INBOUND_REFRESH_AGE_MS, CAP_BALANCE_WAIT_MS, CAP_BALANCE_RETRY_MS, REFRESH_RETRY_MS, FINALITY_POLL_MS, FINALITY_WAIT_MS, FINALITY_REFRESH_LIMIT_MS, POOL_WATCH_MS, POOL_WATCH_POLL_MS, EARLY_BALANCE_DELAY_MS, FINALITY_SETTLE_WINDOW_SLOTS, PUBLIC_READ_ANSWER_MS, SWITCH_WAIT_MS, UNSHIELD_TO_NO_USDC_DETAIL, UNSHIELD_TO_INVALID_NEXT, INFERENCE_MIN_MAX_TOKENS, ELICIT_TIMEOUT_MS, ELICIT_UNSHOWN_MS, elicitUnshown, POLL_SECONDS3, TEMP_CLEAN_DELAY_MS, KEPT_ON_DISK, MAX_SHIELDED_BALANCE_ATOMIC, receiveGenerator, MCP_TOOL_NAMES, MCP_HIDDEN_TOOL_NAMES, SETUP_TERMINAL_NEXT, SETUP_POLL_NEXT, PRIVATE_INFERENCE_WHY, PRIVATE_INFERENCE_NEXT;
124581
124920
  var init_tools = __esm({
124582
124921
  "wallet/src/mcp/tools.mjs"() {
124583
124922
  "use strict";
@@ -124712,6 +125051,10 @@ var init_tools = __esm({
124712
125051
  quietSwitch = (item) => item.state === "done" && item.tellUser !== true;
124713
125052
  switchKey = (item) => `${item.from}:${item.state}`;
124714
125053
  ATOMIC_TEXT = /^[0-9]{1,31}$/;
125054
+ ANSWERED_EXIT_RETRY_MS = 3e4;
125055
+ ANSWERED_EXIT_UNCONFIRMED_MS = 10 * 6e4;
125056
+ ANSWERED_EXIT_RETRY_WINDOW_MS = 30 * 6e4;
125057
+ ANSWERED_EXIT_CLOSED = /* @__PURE__ */ Symbol("answered-exit-closed");
124715
125058
  switchAmount = (atomic4, decimals) => {
124716
125059
  try {
124717
125060
  return ATOMIC_TEXT.test(String(atomic4)) ? formatAtomic(BigInt(atomic4), decimals) : null;