shinjuku-shield 0.3.113 → 0.3.114

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/README.md CHANGED
@@ -6,19 +6,19 @@ answers privately, in a terminal chat or for your agent app. Its `mcp`
6
6
  command runs a local MCP server (stdio) for your agent. Your keys never
7
7
  leave your machine.
8
8
 
9
- This is version 0.3.113. The npm packages `shinjuku-shield` and
9
+ This is version 0.3.114. The npm packages `shinjuku-shield` and
10
10
  `shinjuku-shielded` hold the same files. The command is `shinjuku`.
11
11
 
12
12
  ## Set up
13
13
 
14
14
  ```sh
15
- npm install -g shinjuku@0.3.113
15
+ npm install -g shinjuku@0.3.114
16
16
  shinjuku setup
17
17
  ```
18
18
 
19
19
  Type the two lines as they are. `npx install shinjuku` does not work: npx
20
20
  reads `install` as the name of another npm package. To set up without
21
- installing, type `npx shinjuku@0.3.113 setup`.
21
+ installing, type `npx shinjuku@0.3.114 setup`.
22
22
 
23
23
  Setup makes the wallet on this machine, a private passphrase file, and the
24
24
  checked proof tools (about 191 MB), and writes `config.json`. It also writes
@@ -36,13 +36,13 @@ Every wallet command works after `shinjuku`, for example `shinjuku balance`,
36
36
  Claude Code:
37
37
 
38
38
  ```sh
39
- claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.113 mcp
39
+ claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.114 mcp
40
40
  ```
41
41
 
42
42
  Claude Desktop (`claude_desktop_config.json`) or Cursor (`~/.cursor/mcp.json`):
43
43
 
44
44
  ```json
45
- { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.113", "mcp"] } } }
45
+ { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.114", "mcp"] } } }
46
46
  ```
47
47
 
48
48
  On native Windows, if the host cannot find `npx`, use `"command": "cmd"` and
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": "bfaedd12d120b4b5421cdf6c9c06b07b7e338fd6d4e74b7225f2a0d3aa150cf4",
5
- "bytes": 8339853,
6
- "sourceCommit": "0c37c0dea055d3c28bab9939cd5527fc290668f8",
4
+ "sha256": "dc0b6b99ced49b6c454c44c8b92ee46d77d38d5fdabcf44154b4200894425006",
5
+ "bytes": 8345095,
6
+ "sourceCommit": "dbf24f945933862ece16e3e283fea5273db59865",
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",
@@ -1037,5 +1037,5 @@
1037
1037
  "version": "1.8.0"
1038
1038
  }
1039
1039
  ],
1040
- "publishedAt": "2026-10-27T16:17:32.089Z"
1040
+ "publishedAt": "2026-10-27T17:51:29.501Z"
1041
1041
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "shinjuku-shield",
3
- "version": "0.3.113",
3
+ "version": "0.3.114",
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": "bfaedd12",
7
- "shinjukuSha256": "bfaedd12d120b4b5421cdf6c9c06b07b7e338fd6d4e74b7225f2a0d3aa150cf4",
6
+ "shinjukuRelease": "dc0b6b99",
7
+ "shinjukuSha256": "dc0b6b99ced49b6c454c44c8b92ee46d77d38d5fdabcf44154b4200894425006",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-shield#readme",
@@ -67216,7 +67216,7 @@ var init_errorCodes = __esm({
67216
67216
  "float_paid_finishing",
67217
67217
  "refund_pending",
67218
67218
  // Float record field values, not errors (floatPay.mjs): broadcastRefusal "answer_invalid" (leg 1's answer could not be
67219
- // used) and the refund outcomes returnArrivedFloatRefundBudgets writes and takeFloatRefundOutcomes returns.
67219
+ // used) and the refund outcomes returnArrivedFloatRefundBudgets writes and readFloatRefundOutcomes returns.
67220
67220
  "answer_invalid",
67221
67221
  "refund_arrived",
67222
67222
  "refund_not_seen",
@@ -102584,7 +102584,14 @@ function returnArrivedFloatRefundBudgets(store, snapshot) {
102584
102584
  }
102585
102585
  }
102586
102586
  }
102587
- function takeFloatRefundOutcomes(store) {
102587
+ function floatSellerHost(record) {
102588
+ try {
102589
+ return new URL(String(record?.url)).hostname || void 0;
102590
+ } catch {
102591
+ return void 0;
102592
+ }
102593
+ }
102594
+ function readFloatRefundOutcomes(store) {
102588
102595
  const outcomes = [];
102589
102596
  let names;
102590
102597
  try {
@@ -102598,13 +102605,32 @@ function takeFloatRefundOutcomes(store) {
102598
102605
  const record = store.readWal(name);
102599
102606
  if (record?.schema !== FLOAT_PAYMENT_SCHEMA2 || floatRecordState(record) !== "refund_pending") continue;
102600
102607
  if (record.refundOutcome === void 0 || record.refundOutcomeShown !== false) continue;
102601
- store.writeWal(name, { ...record, refundOutcomeShown: true });
102602
- outcomes.push({ floatId: record.floatId, paidAtomic: record.paidAtomic, outcome: record.refundOutcome, atMs: record.refundOutcomeAtMs });
102608
+ outcomes.push({ floatId: record.floatId, paidAtomic: record.paidAtomic, host: floatSellerHost(record), outcome: record.refundOutcome, atMs: record.refundOutcomeAtMs });
102603
102609
  } catch {
102604
102610
  }
102605
102611
  }
102606
102612
  return outcomes;
102607
102613
  }
102614
+ function markFloatRefundOutcomesShown(store, shown) {
102615
+ const wanted = new Map(shown.map(({ floatId, outcome }) => [floatId, outcome]));
102616
+ if (!wanted.size) return;
102617
+ let names;
102618
+ try {
102619
+ names = store.listWal();
102620
+ } catch {
102621
+ return;
102622
+ }
102623
+ for (const name of names) {
102624
+ if (!name.startsWith(FLOAT_PAYMENT_WAL_PREFIX)) continue;
102625
+ try {
102626
+ const record = store.readWal(name);
102627
+ if (record?.schema !== FLOAT_PAYMENT_SCHEMA2 || record.refundOutcomeShown !== false) continue;
102628
+ if (!wanted.has(record.floatId) || wanted.get(record.floatId) !== record.refundOutcome) continue;
102629
+ store.writeWal(name, { ...record, refundOutcomeShown: true });
102630
+ } catch {
102631
+ }
102632
+ }
102633
+ }
102608
102634
  function dropOldMemoryAnswers(now) {
102609
102635
  for (const [key, entry] of memoryAnswers) if (now >= entry.atMs + FLOAT_KEEP_BYTES_MS) memoryAnswers.delete(key);
102610
102636
  }
@@ -105922,8 +105948,10 @@ function fillsOnTheWayAtomic(store) {
105922
105948
  }
105923
105949
  function refundOf(record) {
105924
105950
  const inner = String(record.innerRequestId ?? "");
105951
+ const seller = floatSellerHost(record);
105925
105952
  return {
105926
105953
  ...inner.endsWith(":float") ? { requestId: inner.slice(0, -":float".length) } : { floatId: record.floatId ?? null },
105954
+ ...seller ? { seller } : {},
105927
105955
  refundAtomic: String(record.paidAtomic)
105928
105956
  };
105929
105957
  }
@@ -120326,8 +120354,7 @@ function withKeepIdNote(output2) {
120326
120354
  return output2;
120327
120355
  }
120328
120356
  if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2) || typeof parsed2.next !== "string" || parsed2.next.includes(KEEP_ID_NOTE)) return output2;
120329
- const ids = [parsed2.requestId, parsed2.newRequestId, ...Array.isArray(parsed2.privateSends) ? parsed2.privateSends.map((send3) => send3?.requestId) : []].filter((id) => typeof id === "string" && id.length >= 4);
120330
- if (!ids.some((id) => parsed2.next.includes(id)) && !/request_id "[^"]+"/.test(parsed2.next)) return output2;
120357
+ if (!/"(?:requestId|newRequestId)"\s*:\s*"[^"]{4,}"/.test(block.text) && !/request_id "[^"]+"/.test(parsed2.next)) return output2;
120331
120358
  return withFirstText(output2, block, { ...parsed2, next: `${parsed2.next.replace(/[.\s]+$/, "")}. ${KEEP_ID_NOTE}` });
120332
120359
  }
120333
120360
  function plainFields(parsed2, toolName) {
@@ -125398,8 +125425,9 @@ async function sellerTokenAccountExists(connection, asset, payTo) {
125398
125425
  }
125399
125426
  function refundOutcomeText(outcome) {
125400
125427
  const amount = ATOMIC_TEXT.test(String(outcome?.paidAtomic ?? "")) ? `${usdcText3(outcome.paidAtomic)} USDC` : "a Shinjuku payment";
125401
- if (outcome?.outcome === "refund_arrived") return `The refund of ${amount} arrived.`;
125402
- return `The refund of ${amount} could not be matched in your balance (this balance read cannot tell refunds apart); if it came back, balanceAtomic counts it already.`;
125428
+ const payment = typeof outcome?.host === "string" && /^[a-z0-9.-]{1,253}$/i.test(outcome.host) ? ` for the payment to ${outcome.host}` : "";
125429
+ if (outcome?.outcome === "refund_arrived") return `The refund of ${amount}${payment} arrived.`;
125430
+ return `The refund of ${amount}${payment} could not be matched in your balance (this balance read cannot tell refunds apart); if it came back, balanceAtomic counts it already.`;
125403
125431
  }
125404
125432
  function floatRefusal(error) {
125405
125433
  const code2 = String(error?.message ?? "").split(/[\s:;(]/, 1)[0];
@@ -125838,7 +125866,9 @@ async function checkUnshieldRecipient({ connection, mint, owner }) {
125838
125866
  }
125839
125867
  async function runUnshield({
125840
125868
  session,
125841
- store,
125869
+ lease,
125870
+ before = () => void 0,
125871
+ prepare = (store) => store,
125842
125872
  unshieldId,
125843
125873
  amountAtomic,
125844
125874
  recipientOwner,
@@ -125848,11 +125878,11 @@ async function runUnshield({
125848
125878
  onStage,
125849
125879
  selfPayStep = selfPay,
125850
125880
  onAnswered,
125851
- onCheckPending
125881
+ onCheckPending,
125882
+ onExited = () => {
125883
+ }
125852
125884
  }) {
125853
125885
  const owner = new import_web3132.PublicKey(recipientOwner);
125854
- const mint = new import_web3132.PublicKey(store.state.asset);
125855
- const recipient = getAssociatedTokenAddressSync(mint, owner, true, TOKEN_PROGRAM_ID);
125856
125886
  let timing;
125857
125887
  try {
125858
125888
  timing = typeof session.timing === "function" ? session.timing() : void 0;
@@ -125866,141 +125896,188 @@ async function runUnshield({
125866
125896
  }
125867
125897
  };
125868
125898
  const scoped = (prefix, fn) => phaseScopeActive() || timing === void 0 ? fn() : withPhaseScope(mark, prefix, fn);
125899
+ let current;
125900
+ const use = (store) => {
125901
+ current = store;
125902
+ };
125869
125903
  const walk = (deploymentIdentity) => () => scoped("unshield-walk", () => loadWalletP03History({
125870
- store,
125904
+ store: current,
125871
125905
  connection: connectSplit(session.connection, session.historyUrl),
125872
125906
  programId: session.programId,
125873
- pool: store.state.pool,
125907
+ pool: current.state.pool,
125874
125908
  deploymentIdentity
125875
125909
  }));
125876
125910
  const name = `self-pay-${unshieldId}`;
125877
- let record = store.readWal(name);
125878
- if (record && record.mode === void 0 && !selfPayMayHaveSigned(store, unshieldId)) {
125879
- const note = chooseDirectExitNote(store, amountAtomic, unshieldId);
125880
- if (note) {
125881
- record = {
125882
- ...record,
125883
- mode: DIRECT_EXIT_MODE,
125884
- noteCommitment: BigInt(note.commitment).toString(),
125885
- memoDigest: note.digest,
125886
- noteValueAtomic: String(note.valueAtomic),
125887
- exitAmountAtomic: String(amountAtomic),
125888
- exitRecipient: recipient.toBase58()
125889
- };
125890
- store.writeWal(name, record);
125911
+ const start = await lease(async (opened) => {
125912
+ mark("unshield-lease-self-pay");
125913
+ const stop3 = await before(opened);
125914
+ if (stop3 !== void 0) return { stop: stop3 };
125915
+ const store = await prepare(opened);
125916
+ use(store);
125917
+ const mint = new import_web3132.PublicKey(store.state.asset);
125918
+ const recipient = getAssociatedTokenAddressSync(mint, owner, true, TOKEN_PROGRAM_ID);
125919
+ let record = store.readWal(name);
125920
+ if (record && record.mode === void 0 && !selfPayMayHaveSigned(store, unshieldId)) {
125921
+ const note = chooseDirectExitNote(store, amountAtomic, unshieldId);
125922
+ if (note) {
125923
+ record = {
125924
+ ...record,
125925
+ mode: DIRECT_EXIT_MODE,
125926
+ noteCommitment: BigInt(note.commitment).toString(),
125927
+ memoDigest: note.digest,
125928
+ noteValueAtomic: String(note.valueAtomic),
125929
+ exitAmountAtomic: String(amountAtomic),
125930
+ exitRecipient: recipient.toBase58()
125931
+ };
125932
+ store.writeWal(name, record);
125933
+ }
125891
125934
  }
125892
- }
125893
- if (record?.mode === DIRECT_EXIT_MODE) {
125894
- return runDirectExit({
125895
- session,
125935
+ if (record?.mode === DIRECT_EXIT_MODE) {
125936
+ return runDirectExit({
125937
+ session,
125938
+ store,
125939
+ name,
125940
+ pool: store.state.pool,
125941
+ unshieldId,
125942
+ amountAtomic,
125943
+ record,
125944
+ owner,
125945
+ mint,
125946
+ recipient,
125947
+ exitProofTools,
125948
+ facilitatorOrigin: facilitatorOrigin2,
125949
+ onStage,
125950
+ walk,
125951
+ use,
125952
+ mark,
125953
+ scoped,
125954
+ onAnswered
125955
+ });
125956
+ }
125957
+ 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");
125958
+ if (!selfPayMayHaveSigned(store, unshieldId)) {
125959
+ await checkUnshieldRecipient({ connection: session.connection, mint, owner });
125960
+ }
125961
+ const payerTools = session.proofTools;
125962
+ onStage("self-pay");
125963
+ mark("unshield-self-pay");
125964
+ const paid = await selfPayStep({
125896
125965
  store,
125897
- unshieldId,
125966
+ connection: session.connection,
125967
+ programId: session.programId,
125968
+ proofTools: payerTools,
125969
+ profile,
125898
125970
  amountAtomic,
125899
- record,
125900
- owner,
125901
- mint,
125902
- recipient,
125903
- exitProofTools,
125904
- facilitatorOrigin: facilitatorOrigin2,
125905
- onStage,
125906
- walk,
125907
- mark,
125908
- scoped,
125909
- onAnswered,
125910
- onCheckPending
125971
+ unshieldId,
125972
+ facilitatorBaseUrl: serviceUrl(facilitatorOrigin2, "/api/x402").href,
125973
+ memoServiceUrl: serviceBase(facilitatorOrigin2),
125974
+ // An owner unshield: the self-payment pays this wallet itself, so it does not use the wallet-file payment budget.
125975
+ ownerUnshield: true,
125976
+ // The self-payment waits for the next anchor itself (bounded, like x402_pay), instead of refusing.
125977
+ deps: { payDeps: { timing: session.timing(), walkHistory: walk(payerTools.deploymentIdentity), waitForAnchor: true } }
125978
+ });
125979
+ const payment = store.readWal(`${RESOURCE_PAYMENT_WAL_PREFIX2}${sha256Hex2(Buffer.from(selfPayRequestId(unshieldId), "utf8"))}`);
125980
+ if (!payment?.memoDigest || !paid.noteCommitment) throw new Error("mcp_unshield_failed: the self-payment record is incomplete; call wallet_unshield again with the same request_id");
125981
+ mark("unshield-paid");
125982
+ return { paid, memoDigest: payment.memoDigest, recipient, pool: store.state.pool };
125983
+ });
125984
+ if (start.stop !== void 0) return start.stop;
125985
+ let { tried, exitOf } = start;
125986
+ if (tried === void 0) {
125987
+ const { paid, memoDigest, recipient } = start;
125988
+ exitOf = (store) => runRecoveredP03NoteExit({
125989
+ store,
125990
+ connection: session.connection,
125991
+ programId: session.programId,
125992
+ serviceUrl: facilitatorOrigin2,
125993
+ digests: [memoDigest],
125994
+ recipientTokenAccount: recipient.toBase58(),
125995
+ proofTools: exitProofTools,
125996
+ noteCommitment: paid.noteCommitment,
125997
+ recipientOwner: owner.toBase58(),
125998
+ allowForeignRecipient: true,
125999
+ relaySubmit: facilitatorExitRelay(facilitatorOrigin2),
126000
+ requestAnchor: markedAnchorRequest(facilitatorAnchor(facilitatorOrigin2)),
126001
+ answerAtRelay: true,
126002
+ deps: { walkHistory: walk(exitProofTools.deploymentIdentity) }
125911
126003
  });
126004
+ onStage("exit");
126005
+ mark("unshield-exit");
126006
+ tried = await exitTries({ session, lease, name, pool: start.pool, use, mark, scoped, exitOf, answer: { selfPayTransaction: paid.transaction ?? null }, onAnswered });
125912
126007
  }
125913
- 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");
125914
- if (!selfPayMayHaveSigned(store, unshieldId)) {
125915
- await checkUnshieldRecipient({ connection: session.connection, mint, owner });
125916
- }
125917
- const payerTools = session.proofTools;
125918
- onStage("self-pay");
125919
- mark("unshield-self-pay");
125920
- const paid = await selfPayStep({
125921
- store,
125922
- connection: session.connection,
125923
- programId: session.programId,
125924
- proofTools: payerTools,
125925
- profile,
125926
- amountAtomic,
125927
- unshieldId,
125928
- facilitatorBaseUrl: serviceUrl(facilitatorOrigin2, "/api/x402").href,
125929
- memoServiceUrl: serviceBase(facilitatorOrigin2),
125930
- // An owner unshield: the self-payment pays this wallet itself, so it does not use the wallet-file payment budget.
125931
- ownerUnshield: true,
125932
- // The self-payment waits for the next anchor itself (bounded, like x402_pay), instead of refusing.
125933
- deps: { payDeps: { timing: session.timing(), walkHistory: walk(payerTools.deploymentIdentity), waitForAnchor: true } }
125934
- });
125935
- const payment = store.readWal(`${RESOURCE_PAYMENT_WAL_PREFIX2}${sha256Hex2(Buffer.from(selfPayRequestId(unshieldId), "utf8"))}`);
125936
- if (!payment?.memoDigest || !paid.noteCommitment) throw new Error("mcp_unshield_failed: the self-payment record is incomplete; call wallet_unshield again with the same request_id");
125937
- mark("unshield-paid");
125938
- onStage("exit");
125939
- mark("unshield-exit");
125940
- const exit = await scoped("unshield-exit", () => exitWhenAnchored(() => runRecoveredP03NoteExit({
125941
- store,
125942
- connection: session.connection,
125943
- programId: session.programId,
125944
- serviceUrl: facilitatorOrigin2,
125945
- digests: [payment.memoDigest],
125946
- recipientTokenAccount: recipient.toBase58(),
125947
- proofTools: exitProofTools,
125948
- noteCommitment: paid.noteCommitment,
125949
- recipientOwner: owner.toBase58(),
125950
- allowForeignRecipient: true,
125951
- relaySubmit: facilitatorExitRelay(facilitatorOrigin2),
125952
- requestAnchor: markedAnchorRequest(facilitatorAnchor(facilitatorOrigin2)),
125953
- answerAtRelay: true,
125954
- deps: { walkHistory: walk(exitProofTools.deploymentIdentity) }
125955
- }), { ring: { connection: session.connection, pool: store.state.pool } }));
125956
- const answer = { selfPayTransaction: paid.transaction ?? null, exitTransaction: exit.signature ?? null };
125957
- if (exit.settled === false && !await finishAnsweredExit({ session, store, name, exit, answer, onAnswered, onCheckPending, mark, scoped })) return answer;
125958
- mark("unshield-exited");
125959
- const exited = store.readWal(`self-pay-${unshieldId}`);
125960
- store.writeWal(`self-pay-${unshieldId}`, { ...exited, state: "exited", exitTransaction: exit.signature, exitedAtMs: exited?.exitedAtMs ?? exited?.exitAnsweredAtMs ?? Date.now() });
125961
- return answer;
126008
+ return exitFinish({ lease, name, use, mark, scoped, exitOf, tried, onCheckPending, onExited });
125962
126009
  }
125963
126010
  function sentAtMs(record) {
125964
126011
  return Number.isSafeInteger(record?.exitAnsweredAtMs) ? record.exitAnsweredAtMs : record?.exitedAtMs;
125965
126012
  }
125966
- async function finishAnsweredExit({ session, store, name, exit, answer, changeAtomic, onAnswered, onCheckPending, mark, scoped }) {
125967
- if (typeof onAnswered !== "function") {
125968
- await scoped("unshield-exit", () => exit.finish());
125969
- return true;
125970
- }
125971
- const record = store.readWal(name);
125972
- store.writeWal(name, {
125973
- ...record,
125974
- exitTransaction: exit.signature,
125975
- exitAnsweredAtMs: record?.exitAnsweredAtMs ?? Date.now(),
125976
- ...changeAtomic !== void 0 ? { changeAtomic } : {}
125977
- });
125978
- try {
125979
- await scoped("unshield-exit", () => refreshWallet({ session, store, history: exit.preExitHistory }));
125980
- } catch {
125981
- mark("unshield-exit-snapshot-failed");
125982
- }
125983
- mark("unshield-exit-answered");
125984
- try {
125985
- onAnswered(answer);
125986
- } catch {
125987
- }
125988
- try {
125989
- await scoped("unshield-exit", () => exit.finish());
125990
- return true;
125991
- } catch (error) {
126013
+ async function exitTries({ session, lease, name, pool: pool2, use, mark, scoped, exitOf, answer: shown, changeAtomic, onAnswered }) {
126014
+ const answers = typeof onAnswered === "function";
126015
+ return scoped("unshield-exit", () => exitWhenAnchored(() => lease(async (store) => {
126016
+ mark("unshield-lease-exit");
126017
+ use(store);
126018
+ const exit = await exitOf(store);
126019
+ const answer = { ...shown, exitTransaction: exit.signature ?? null };
126020
+ const early = exit.settled === false && answers;
126021
+ if (early) {
126022
+ const record = store.readWal(name);
126023
+ store.writeWal(name, {
126024
+ ...record,
126025
+ exitTransaction: exit.signature,
126026
+ exitAnsweredAtMs: record?.exitAnsweredAtMs ?? Date.now(),
126027
+ ...changeAtomic !== void 0 ? { changeAtomic } : {}
126028
+ });
126029
+ try {
126030
+ await refreshWallet({ session, store, history: exit.preExitHistory });
126031
+ } catch {
126032
+ mark("unshield-exit-snapshot-failed");
126033
+ }
126034
+ mark("unshield-exit-answered");
126035
+ try {
126036
+ onAnswered(answer);
126037
+ } catch {
126038
+ }
126039
+ }
126040
+ return { answer, signature: exit.signature, settled: exit.settled !== false, early };
126041
+ }), { ring: { connection: session.connection, pool: pool2 } }));
126042
+ }
126043
+ async function exitFinish({ lease, name, use, mark, scoped, exitOf, tried, onCheckPending, onExited }) {
126044
+ const checkPending = (store, error) => {
125992
126045
  mark("unshield-exit-check-pending");
125993
- store.writeWal(name, { ...store.readWal(name), exitCheckPendingAtMs: Date.now() });
126046
+ if (store) store.writeWal(name, { ...store.readWal(name), exitCheckPendingAtMs: Date.now() });
125994
126047
  try {
125995
126048
  onCheckPending?.(error);
125996
126049
  } catch {
125997
126050
  }
125998
- return false;
126051
+ };
126052
+ try {
126053
+ await lease(async (store) => {
126054
+ mark("unshield-lease-finish");
126055
+ use(store);
126056
+ if (!tried.settled) {
126057
+ try {
126058
+ await scoped("unshield-exit", () => exitOf(store));
126059
+ } catch (error) {
126060
+ if (!tried.early) throw error;
126061
+ checkPending(store, error);
126062
+ return;
126063
+ }
126064
+ }
126065
+ mark("unshield-exited");
126066
+ const exited = store.readWal(name);
126067
+ store.writeWal(name, { ...exited, state: "exited", exitTransaction: tried.signature, exitedAtMs: exited?.exitedAtMs ?? exited?.exitAnsweredAtMs ?? Date.now() });
126068
+ onExited();
126069
+ });
126070
+ } catch (error) {
126071
+ if (!tried.early) throw error;
126072
+ checkPending(void 0, error);
125999
126073
  }
126074
+ return tried.answer;
126000
126075
  }
126001
126076
  async function runDirectExit({
126002
126077
  session,
126003
126078
  store,
126079
+ name,
126080
+ pool: pool2,
126004
126081
  unshieldId,
126005
126082
  amountAtomic,
126006
126083
  record,
@@ -126011,13 +126088,11 @@ async function runDirectExit({
126011
126088
  facilitatorOrigin: facilitatorOrigin2,
126012
126089
  onStage,
126013
126090
  walk,
126014
- mark = () => {
126015
- },
126016
- scoped = (_prefix, fn) => fn(),
126017
- onAnswered,
126018
- onCheckPending
126091
+ use,
126092
+ mark,
126093
+ scoped,
126094
+ onAnswered
126019
126095
  }) {
126020
- const name = `self-pay-${unshieldId}`;
126021
126096
  if (record.amountAtomic !== String(amountAtomic) || record.exitAmountAtomic !== String(amountAtomic)) {
126022
126097
  throw new Error("mcp_request_id_reused: this unshield's direct exit is bound to another amount; nothing new was signed");
126023
126098
  }
@@ -126030,45 +126105,54 @@ async function runDirectExit({
126030
126105
  if (!selfPayMayHaveSigned(store, unshieldId)) await checkUnshieldRecipient({ connection: session.connection, mint, owner });
126031
126106
  onStage("exit");
126032
126107
  mark("unshield-exit");
126033
- const proveExit = async (request) => {
126034
- const proved = await proveRecoveredP03NoteExit({ request, tools: exitProofTools });
126035
- const now = store.readWal(name);
126036
- if (now?.mode === DIRECT_EXIT_MODE && now.state === "started") store.writeWal(name, { ...now, state: "paid" });
126037
- return proved;
126038
- };
126039
126108
  const exitAmountAtomic = record.exitAmountAtomic === record.noteValueAtomic ? void 0 : record.exitAmountAtomic;
126040
- let exit;
126041
- try {
126042
- exit = await scoped("unshield-exit", () => exitWhenAnchored(() => runRecoveredP03NoteExit({
126043
- store,
126044
- connection: session.connection,
126045
- programId: session.programId,
126046
- serviceUrl: facilitatorOrigin2,
126047
- digests: [record.memoDigest],
126048
- recipientTokenAccount: recipient.toBase58(),
126049
- proofTools: exitProofTools,
126050
- noteCommitment: record.noteCommitment,
126051
- exitAmountAtomic,
126052
- recipientOwner: owner.toBase58(),
126053
- allowForeignRecipient: true,
126054
- relaySubmit: facilitatorExitRelay(facilitatorOrigin2),
126055
- requestAnchor: markedAnchorRequest(facilitatorAnchor(facilitatorOrigin2)),
126056
- answerAtRelay: true,
126057
- deps: { walkHistory: walk(exitProofTools.deploymentIdentity), proveExit }
126058
- }), { ring: { connection: session.connection, pool: store.state.pool } }));
126059
- } catch (error) {
126060
- if (/requested recovered note is not uniquely spendable/.test(String(error?.message)) && store.readWal(name)?.state === "started" && !store.readWal(directExitWalName(record)) && !selfPayMayHaveSigned(store, unshieldId)) {
126109
+ const changeAtomic = exitAmountAtomic === void 0 ? void 0 : (BigInt(record.noteValueAtomic) - BigInt(record.exitAmountAtomic)).toString();
126110
+ const exitOf = (opened) => runRecoveredP03NoteExit({
126111
+ store: opened,
126112
+ connection: session.connection,
126113
+ programId: session.programId,
126114
+ serviceUrl: facilitatorOrigin2,
126115
+ digests: [record.memoDigest],
126116
+ recipientTokenAccount: recipient.toBase58(),
126117
+ proofTools: exitProofTools,
126118
+ noteCommitment: record.noteCommitment,
126119
+ exitAmountAtomic,
126120
+ recipientOwner: owner.toBase58(),
126121
+ allowForeignRecipient: true,
126122
+ relaySubmit: facilitatorExitRelay(facilitatorOrigin2),
126123
+ requestAnchor: markedAnchorRequest(facilitatorAnchor(facilitatorOrigin2)),
126124
+ answerAtRelay: true,
126125
+ deps: {
126126
+ walkHistory: walk(exitProofTools.deploymentIdentity),
126127
+ // The record reads "paid" once the proof exists, before the library stores it and before any relay call: from then
126128
+ // on the part may have reached the relay (cancel and the private send's own records see that), and the note is held.
126129
+ proveExit: async (request) => {
126130
+ const proved = await proveRecoveredP03NoteExit({ request, tools: exitProofTools });
126131
+ const now = opened.readWal(name);
126132
+ if (now?.mode === DIRECT_EXIT_MODE && now.state === "started") opened.writeWal(name, { ...now, state: "paid" });
126133
+ return proved;
126134
+ }
126135
+ }
126136
+ }).catch((error) => {
126137
+ if (/requested recovered note is not uniquely spendable/.test(String(error?.message)) && opened.readWal(name)?.state === "started" && !opened.readWal(directExitWalName(record)) && !selfPayMayHaveSigned(opened, unshieldId)) {
126061
126138
  throw new Error("mcp_unshield_received_payment_gone: the received payment this unshield chose is no longer spendable (another spend took it, or the wallet's last chain read was old); nothing was signed, and this request_id cannot pass");
126062
126139
  }
126063
126140
  throw error;
126064
- }
126065
- const answer = { selfPayTransaction: null, exitTransaction: exit.signature ?? null };
126066
- const changeAtomic = exitAmountAtomic === void 0 ? void 0 : (BigInt(record.noteValueAtomic) - BigInt(record.exitAmountAtomic)).toString();
126067
- if (exit.settled === false && !await finishAnsweredExit({ session, store, name, exit, answer, changeAtomic, onAnswered, onCheckPending, mark, scoped })) return answer;
126068
- mark("unshield-exited");
126069
- const exited = store.readWal(name);
126070
- store.writeWal(name, { ...exited, state: "exited", exitTransaction: exit.signature, exitedAtMs: exited?.exitedAtMs ?? exited?.exitAnsweredAtMs ?? Date.now() });
126071
- return answer;
126141
+ });
126142
+ const tried = await exitTries({
126143
+ session,
126144
+ lease: (task) => task(store),
126145
+ name,
126146
+ pool: pool2,
126147
+ use,
126148
+ mark,
126149
+ scoped,
126150
+ exitOf,
126151
+ answer: { selfPayTransaction: null },
126152
+ changeAtomic,
126153
+ onAnswered
126154
+ });
126155
+ return { tried, exitOf };
126072
126156
  }
126073
126157
  function balanceFields(parts) {
126074
126158
  if (!parts) return null;
@@ -126389,8 +126473,37 @@ function createWalletTools({
126389
126473
  ...history ? { history } : {},
126390
126474
  ...share ? { share } : {}
126391
126475
  });
126392
- const refundNotices = [];
126393
- const takeRefundNotices = () => refundNotices.length ? { refundNotices: refundNotices.splice(0) } : {};
126476
+ const shownRefunds = /* @__PURE__ */ new Set();
126477
+ const takeRefundNotices = (store) => {
126478
+ let outcomes;
126479
+ try {
126480
+ outcomes = readFloatRefundOutcomes(store ?? session.openStore());
126481
+ } catch {
126482
+ return {};
126483
+ }
126484
+ const fresh = outcomes.filter(({ floatId, outcome }) => !shownRefunds.has(`${floatId}:${outcome}`));
126485
+ if (!fresh.length) return {};
126486
+ for (const { floatId, outcome } of fresh) shownRefunds.add(`${floatId}:${outcome}`);
126487
+ underLease((leased) => markFloatRefundOutcomesShown(leased, fresh)).catch(() => {
126488
+ });
126489
+ return { refundNotices: fresh.map(refundOutcomeText) };
126490
+ };
126491
+ const withRefundNotices = (answer, store) => {
126492
+ let value2;
126493
+ try {
126494
+ value2 = JSON.parse(answer.text);
126495
+ } catch {
126496
+ return answer;
126497
+ }
126498
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return answer;
126499
+ const notices = takeRefundNotices(store);
126500
+ if (!notices.refundNotices) return answer;
126501
+ return json3({
126502
+ ...value2,
126503
+ ...notices,
126504
+ ...typeof value2.next === "string" ? { next: `${value2.next.replace(/[.\s]+$/, "")}. Tell the user each refundNotices line` } : {}
126505
+ });
126506
+ };
126394
126507
  let lastWriterEndedAt = 0;
126395
126508
  let paysInFlight = 0;
126396
126509
  let payJoinWaiting = 0;
@@ -126829,7 +126942,6 @@ function createWalletTools({
126829
126942
  if (early !== void 0 && !same2) log("MCP_REFRESH_WALK_AGAIN");
126830
126943
  const share = early !== void 0 && !same2 ? { notBeforeMs: Date.now() } : void 0;
126831
126944
  let result2 = await refreshStep(store, same2 ? early.history : void 0, share);
126832
- refundNotices.push(...takeFloatRefundOutcomes(store).map(refundOutcomeText));
126833
126945
  let marksAfter = walletHomeMarks(session.walletPath);
126834
126946
  const settled = (marks) => marks !== void 0 && marks.readAtMs - marks.newestMs >= HOME_MARKS_SETTLED_MS2;
126835
126947
  if (background && settled(marksBefore) && marksAfter !== void 0 && marksAfter.marks !== marksBefore.marks && paysInFlight === 0) {
@@ -127311,8 +127423,12 @@ ${canonical5}`;
127311
127423
  };
127312
127424
  for (const [phase, at, outcome] of gateTimes) mark(phase, at, outcome);
127313
127425
  let earlyBalance;
127426
+ let earlyCorrection;
127314
127427
  const payTiming = (phase, outcome, at) => {
127315
- if (phase === "seller-sent") sellerSentAt.set(requestId, Date.now());
127428
+ if (phase === "seller-sent") {
127429
+ sellerSentAt.set(requestId, Date.now());
127430
+ earlyCorrection ??= startFloatCorrection();
127431
+ }
127316
127432
  if (timing === void 0) return;
127317
127433
  timing(phase, outcome, at);
127318
127434
  if (phase === "seller-sent" && earlyBalance === void 0) earlyBalance = earlyResultBalance(requestId);
@@ -127422,11 +127538,10 @@ ${canonical5}`;
127422
127538
  await waitWithProgress(retry.waitMs, progress3, "waiting to try the payment again; nothing was paid");
127423
127539
  continue;
127424
127540
  }
127425
- await correctFloatRecordsNow();
127541
+ await correctFloatRecordsNow(void 0, earlyCorrection);
127426
127542
  const failed = payFailure(error, requestId, "x402_pay", join39);
127427
127543
  const ready = readyInSecondsOf(error, code2);
127428
- const extra = { ...takeRefundNotices(), ...ready !== void 0 ? { readyInSeconds: ready } : {} };
127429
- return Object.keys(extra).length ? json3({ ...JSON.parse(failed.text), ...extra }) : failed;
127544
+ return withRefundNotices(ready !== void 0 ? json3({ ...JSON.parse(failed.text), readyInSeconds: ready }) : failed);
127430
127545
  }
127431
127546
  }
127432
127547
  const { bytes: bytes2, truncated } = await readBody(response);
@@ -127478,7 +127593,7 @@ ${canonical5}`;
127478
127593
  mark("mcp-result-float");
127479
127594
  const floatPaid = float?.state === "receipt";
127480
127595
  const amounts = floatPaid ? { priceAtomic: float.priceAtomic, feeAtomic: float.feeAtomic, amountAtomic: float.paidAtomic } : { priceAtomic: receipt.amountAtomic ?? null, feeAtomic: "0", amountAtomic: receipt.amountAtomic ?? null };
127481
- if (paidStore && await correctFloatRecordsNow(paidStore) > 0) {
127596
+ if (paidStore && await correctFloatRecordsNow(paidStore, earlyCorrection) > 0) {
127482
127597
  earlyBalance = void 0;
127483
127598
  try {
127484
127599
  paidStore = session.openStore();
@@ -127503,6 +127618,7 @@ ${canonical5}`;
127503
127618
  }
127504
127619
  mark("mcp-result");
127505
127620
  const shinjukuTransaction = floatPaid ? leg1Transaction(paidStore, float, requestId) : null;
127621
+ const notices = takeRefundNotices(paidStore);
127506
127622
  return answerResult(paidAnswerContent({
127507
127623
  status: response.status,
127508
127624
  headers: response.headers,
@@ -127521,8 +127637,8 @@ ${canonical5}`;
127521
127637
  // Leg 1 (this wallet's private payment to Shinjuku, a pool transaction): the seller may return no settle signature.
127522
127638
  ...floatPaid ? { via: "shinjuku", shinjukuTransaction } : {},
127523
127639
  ...balanceAnswer(one),
127524
- ...takeRefundNotices(),
127525
- next: receipt.replayed === true ? "this request id was already paid: the stored receipt came back and nothing new was paid" : floatPaid ? "paid through Shinjuku (privately: the chain shows Shinjuku paying the seller, not this wallet); tell the user the price (priceAtomic), the fee (feeAtomic), the total (amountAtomic), and the transaction (the seller's settle transaction; null when the seller returned none)" + (shinjukuTransaction ? ". shinjukuTransaction is this wallet's private payment to Shinjuku" : ". shinjukuTransaction (this wallet's private payment to Shinjuku) is not recorded yet; wallet_receipts shows it once it settles") + ". wallet_balance shows the new balance" : "paid; tell the user what was paid (amountAtomic, transaction). wallet_receipts lists this payment, wallet_balance shows the new balance"
127640
+ ...notices,
127641
+ next: (receipt.replayed === true ? "this request id was already paid: the stored receipt came back and nothing new was paid" : floatPaid ? "paid through Shinjuku (privately: the chain shows Shinjuku paying the seller, not this wallet); tell the user the price (priceAtomic), the fee (feeAtomic), the total (amountAtomic), and the transaction (the seller's settle transaction; null when the seller returned none)" + (shinjukuTransaction ? ". shinjukuTransaction is this wallet's private payment to Shinjuku" : ". shinjukuTransaction (this wallet's private payment to Shinjuku) is not recorded yet; wallet_receipts shows it once it settles") + ". wallet_balance shows the new balance" : "paid; tell the user what was paid (amountAtomic, transaction). wallet_receipts lists this payment, wallet_balance shows the new balance") + (notices.refundNotices ? ". Tell the user each refundNotices line" : "")
127526
127642
  }, "x402_pay")
127527
127643
  }));
127528
127644
  }
@@ -128669,7 +128785,21 @@ ${canonical5}`;
128669
128785
  }
128670
128786
  return names;
128671
128787
  }
128672
- async function correctFloatRecords(store, names, untilMs) {
128788
+ async function correctFloatRecords(store, names, untilMs, started = floatCorrectionReads(store, names)) {
128789
+ const { states, proven, settled } = started;
128790
+ if (settled === void 0) return { states, written: 0 };
128791
+ const until = (ms) => new Promise((resolve35) => {
128792
+ const timer = setTimeout(resolve35, Math.max(0, ms - Date.now()));
128793
+ timer.unref?.();
128794
+ });
128795
+ await Promise.race([settled, until(untilMs)]);
128796
+ if (!proven.length) return { states, written: 0 };
128797
+ const writing = underLease((leased) => Date.now() >= untilMs ? 0 : writeProven(leased, proven));
128798
+ const written = await Promise.race([writing.catch(() => 0), until(untilMs).then(() => 0)]);
128799
+ if (written > 0) log(`MCP_FLOAT_RECORDS_CORRECTED records=${written}`);
128800
+ return { states, written };
128801
+ }
128802
+ function floatCorrectionReads(store, names) {
128673
128803
  const now = Date.now();
128674
128804
  const due = [];
128675
128805
  const transfers = [];
@@ -128695,11 +128825,11 @@ ${canonical5}`;
128695
128825
  }
128696
128826
  const states = new Map(finalRefundStates);
128697
128827
  for (const [floatId, transaction] of finalTransfers) states.set(floatId, { floatState: "final", transaction });
128698
- if (!due.length && !transfers.length) return { states, written: 0 };
128828
+ const proven = [];
128829
+ if (!due.length && !transfers.length) return { states, proven, settled: void 0 };
128699
128830
  due.sort((left, right) => floatEndedAtMs(right.record) - floatEndedAtMs(left.record));
128700
128831
  transfers.sort((left, right) => floatAnsweredAtMs(right.record) - floatAnsweredAtMs(left.record));
128701
128832
  const fetchImpl = ops.fetch ?? fetch;
128702
- const proven = [];
128703
128833
  const reads = due.slice(0, REFUND_STATUS_READS).map(({ name, record }) => readFloatTransferState(record, fetchImpl).then((read) => {
128704
128834
  const state = floatRecordState(record);
128705
128835
  if (state === "submitted" && read?.state === "landed" || read?.state === "final") {
@@ -128727,26 +128857,24 @@ ${canonical5}`;
128727
128857
  proven.push({ name, from: record.state, transaction: read.transaction, fill: true });
128728
128858
  }));
128729
128859
  }
128730
- const until = (ms) => new Promise((resolve35) => {
128731
- const timer = setTimeout(resolve35, Math.max(0, ms - Date.now()));
128732
- timer.unref?.();
128733
- });
128734
- await Promise.race([Promise.all(reads).catch(() => {
128735
- }), until(untilMs)]);
128736
- if (!proven.length) return { states, written: 0 };
128737
- const writing = underLease((leased) => Date.now() >= untilMs ? 0 : writeProven(leased, proven));
128738
- const written = await Promise.race([writing.catch(() => 0), until(untilMs).then(() => 0)]);
128739
- if (written > 0) log(`MCP_FLOAT_RECORDS_CORRECTED records=${written}`);
128740
- return { states, written };
128860
+ return { states, proven, settled: Promise.all(reads).then(() => void 0, () => void 0) };
128741
128861
  }
128742
- async function correctFloatRecordsNow(store) {
128862
+ async function correctFloatRecordsNow(store, started) {
128743
128863
  try {
128744
128864
  const open4 = store ?? session.openStore();
128745
- return (await correctFloatRecords(open4, staleRefundNames(open4), Date.now() + FLOAT_CORRECT_WAIT_MS)).written;
128865
+ return (await correctFloatRecords(open4, void 0, Date.now() + FLOAT_CORRECT_WAIT_MS, started ?? floatCorrectionReads(open4, staleRefundNames(open4)))).written;
128746
128866
  } catch {
128747
128867
  return 0;
128748
128868
  }
128749
128869
  }
128870
+ function startFloatCorrection() {
128871
+ try {
128872
+ const open4 = session.openStore();
128873
+ return floatCorrectionReads(open4, staleRefundNames(open4));
128874
+ } catch {
128875
+ return void 0;
128876
+ }
128877
+ }
128750
128878
  function writeProven(store, proven) {
128751
128879
  let written = 0;
128752
128880
  for (const { name, from, verdict, transaction, refundDueAtMs, fill } of proven) {
@@ -131113,13 +131241,13 @@ ${canonical5}`;
131113
131241
  }
131114
131242
  return session.openStore();
131115
131243
  }
131116
- async function unshieldPart(opened, job, unshieldId, amountAtomic, onStage, onAnswered) {
131117
- const store = await refreshStaleBeforeUnshield(opened, onStage);
131118
- bindUnshieldRecipient(store, unshieldId, amountAtomic, job.to, unshieldId === moneyId(job.requestId) ? job.requestId : void 0);
131244
+ async function unshieldPart(job, unshieldId, amountAtomic, onStage, { before, onAnswered, firstLease = underLease, yieldMs = UNSHIELD_WINDOW_YIELD_MS } = {}) {
131245
+ const name = `self-pay-${unshieldId}`;
131246
+ let windows = 0;
131119
131247
  let backup;
131248
+ const finishing = { running: true, atMs: Date.now() };
131120
131249
  const result = await (ops.unshield ?? runUnshield)({
131121
131250
  session,
131122
- store,
131123
131251
  unshieldId,
131124
131252
  amountAtomic,
131125
131253
  recipientOwner: job.to,
@@ -131127,24 +131255,43 @@ ${canonical5}`;
131127
131255
  profile: money.profile,
131128
131256
  facilitatorOrigin: money.facilitatorOrigin,
131129
131257
  onStage,
131258
+ lease: (task) => windows++ === 0 ? firstLease(task) : new Promise((resolve35) => {
131259
+ setTimeout(resolve35, yieldMs);
131260
+ }).then(() => withLockRetry(() => underLease(task))),
131261
+ before,
131262
+ prepare: async (opened) => {
131263
+ const store = await refreshStaleBeforeUnshield(opened, onStage);
131264
+ bindUnshieldRecipient(store, unshieldId, amountAtomic, job.to, unshieldId === moneyId(job.requestId) ? job.requestId : void 0);
131265
+ return store;
131266
+ },
131130
131267
  ...ops.selfPay ? { selfPayStep: ops.selfPay } : {},
131131
131268
  ...typeof onAnswered === "function" ? { onAnswered: (answer) => {
131269
+ answeredExitTries.set(name, finishing);
131132
131270
  backup = backupNow(session.openStore());
131133
131271
  onAnswered({ ...answer, ...backup });
131134
131272
  } } : {},
131135
131273
  onCheckPending: (error) => {
131136
131274
  log(`MCP_UNSHIELD_CHECK_PENDING requestId=${job.requestId} cause=${JSON.stringify(String(error?.message ?? error ?? "").split("\n", 1)[0].slice(0, 240))}`);
131137
- answeredExitTries.set(`self-pay-${unshieldId}`, { running: false, atMs: Date.now() });
131275
+ answeredExitTries.set(name, { running: false, atMs: Date.now() });
131138
131276
  armAnsweredExitRetry();
131277
+ },
131278
+ onExited: () => {
131279
+ backup ??= backupNow(session.openStore());
131139
131280
  }
131281
+ }).finally(() => {
131282
+ if (answeredExitTries.get(name) === finishing) answeredExitTries.delete(name);
131140
131283
  });
131141
- return { ...result, ...backup ?? backupNow(session.openStore()) };
131284
+ return backup === void 0 ? result : { ...result, ...backup };
131142
131285
  }
131143
131286
  function runUnshieldJob(job, onStage) {
131144
131287
  if (isSplitSend(job)) return runPrivateSendJob(job, onStage);
131145
- return onOwnTorTag(() => underLease(async (opened) => {
131146
- if (!opened.readWal(`self-pay-${moneyId(job.requestId)}`)) await assertNotPromisedFresh(opened, job.amountAtomic);
131147
- return unshieldPart(opened, job, moneyId(job.requestId), job.amountAtomic, onStage, (answer) => answerUnshieldEarly(job, answer));
131288
+ return onOwnTorTag(() => unshieldPart(job, moneyId(job.requestId), job.amountAtomic, onStage, {
131289
+ // A new one (no self-payment yet) never takes value an open private send still owes; a started one continues.
131290
+ before: async (opened) => {
131291
+ if (!opened.readWal(`self-pay-${moneyId(job.requestId)}`)) await assertNotPromisedFresh(opened, job.amountAtomic);
131292
+ },
131293
+ onAnswered: (answer) => answerUnshieldEarly(job, answer),
131294
+ yieldMs: 0
131148
131295
  }));
131149
131296
  }
131150
131297
  function answerUnshieldEarly(job, answer) {
@@ -131201,17 +131348,21 @@ ${canonical5}`;
131201
131348
  // A wait never keeps this server alive by itself: at stdin end the send runner takes the send (handOffSends).
131202
131349
  sleep: sendSleep,
131203
131350
  stopped: () => sendsHandedOff,
131204
- // Each part on one tag of its own (runShieldJob).
131205
- runPart: ({ part, index, total }) => onOwnTorTag(() => underLease(async (opened) => {
131206
- store = opened;
131207
- if (partCancelled(opened, sendId, part)) return { cancelled: true };
131208
- if (partState(opened, part).state === "exited") return {};
131209
- const result = await unshieldPart(opened, job, part.unshieldId, part.amountAtomic, (stage) => onStage(`payment ${index}/${total}: ${stage}`));
131351
+ // Each part on one tag of its own (runShieldJob), in lease windows (unshieldPart).
131352
+ runPart: ({ part, index, total }) => onOwnTorTag(async () => {
131353
+ const result = await unshieldPart(job, part.unshieldId, part.amountAtomic, (stage) => onStage(`payment ${index}/${total}: ${stage}`), {
131354
+ before: (opened) => {
131355
+ store = opened;
131356
+ if (partCancelled(opened, sendId, part)) return { cancelled: true };
131357
+ if (partState(opened, part).state === "exited") return {};
131358
+ return void 0;
131359
+ }
131360
+ });
131210
131361
  if (result.backupPath !== void 0) backup = { backupPath: result.backupPath };
131211
131362
  if (result.backupError !== void 0) backup = { ...backup, backupError: result.backupError };
131212
131363
  store = session.openStore();
131213
131364
  return result;
131214
- })),
131365
+ }),
131215
131366
  // The plan's last write, under the lease like every other.
131216
131367
  finish: () => underLease((opened) => {
131217
131368
  store = opened;
@@ -131670,7 +131821,7 @@ ${canonical5}`;
131670
131821
  if (current?.state === "exited" || current?.exitUnconfirmed === true || typeof current?.exitTransaction !== "string") return void 0;
131671
131822
  return (ops.unshield ?? runUnshield)({
131672
131823
  session,
131673
- store: opened,
131824
+ lease: (task) => task(opened),
131674
131825
  unshieldId,
131675
131826
  amountAtomic: current.amountAtomic,
131676
131827
  recipientOwner: current.mcpTo,
@@ -132598,21 +132749,27 @@ ${canonical5}`;
132598
132749
  }),
132599
132750
  sleep: sendSleep,
132600
132751
  stopped: () => sendsHandedOff,
132601
- // Each part on one tag of its own (runShieldJob).
132602
- runPart: ({ part, index, total }) => onOwnTorTag(() => withLockRetry(() => underLease(async (opened) => {
132603
- store = opened;
132604
- if (partCancelled(opened, plan.sendId, part)) return { cancelled: true };
132605
- if (partState(opened, part).state === "exited") return {};
132752
+ // Each part on one tag of its own (runShieldJob), in lease windows (unshieldPart); window 1 waits for this server's
132753
+ // own lease holders, as before.
132754
+ runPart: ({ part, index, total }) => onOwnTorTag(async () => {
132606
132755
  const result = await unshieldPart(
132607
- opened,
132608
132756
  job,
132609
132757
  part.unshieldId,
132610
132758
  part.amountAtomic,
132611
- (stage) => log(`MCP_TRANSFER_SEND_STAGE requestId=${plan.id} part=${index}/${total} stage=${stage}`)
132759
+ (stage) => log(`MCP_TRANSFER_SEND_STAGE requestId=${plan.id} part=${index}/${total} stage=${stage}`),
132760
+ {
132761
+ firstLease: (task) => withLockRetry(() => underLease(task)),
132762
+ before: (opened) => {
132763
+ store = opened;
132764
+ if (partCancelled(opened, plan.sendId, part)) return { cancelled: true };
132765
+ if (partState(opened, part).state === "exited") return {};
132766
+ return void 0;
132767
+ }
132768
+ }
132612
132769
  );
132613
132770
  store = session.openStore();
132614
132771
  return result;
132615
- }))),
132772
+ }),
132616
132773
  finish: () => underLease((opened) => {
132617
132774
  store = opened;
132618
132775
  return finishPrivateSend(opened, plan.sendId);
@@ -133580,7 +133737,7 @@ ${canonical5}`;
133580
133737
  { requestId, paid: false, ...facts }
133581
133738
  );
133582
133739
  }
133583
- return inferenceRefusal(error, { requestId, ...facts }) ?? payFailure(error, requestId, "inference_chat");
133740
+ return withRefundNotices(inferenceRefusal(error, { requestId, ...facts }) ?? payFailure(error, requestId, "inference_chat"));
133584
133741
  }
133585
133742
  }
133586
133743
  const receipt = result.receipt ?? {};
@@ -133629,7 +133786,7 @@ ${canonical5}`;
133629
133786
  const larger = moreTokens(args.max_tokens);
133630
133787
  const paidText = usdcText3(paidAtomic ?? "");
133631
133788
  log(`MCP_INFERENCE_OUT_OF_TOKENS requestId=${requestId} maxTokens=${args.max_tokens}`);
133632
- return json3(sayFirst(
133789
+ return withRefundNotices(json3(sayFirst(
133633
133790
  {
133634
133791
  ok: false,
133635
133792
  error: "inference_out_of_tokens",
@@ -133639,13 +133796,13 @@ ${canonical5}`;
133639
133796
  next: receipt.replayed === true ? `this request id was already paid and its stored answer is empty; nothing new was paid. For an answer, call inference_chat with a NEW request_id and max_tokens ${larger}; it pays again` : `Tell the user: the model ran out of tokens before it answered, and this call paid ${paidText ? `${paidText} USDC` : "(see paidAtomic)"}. For an answer, call inference_chat again with a NEW request_id and max_tokens ${larger}; it pays again. The same request_id returns this same empty answer`
133640
133797
  },
133641
133798
  "inference_chat"
133642
- ));
133799
+ )), paidStore);
133643
133800
  }
133644
- return json3(sayFirst({
133801
+ return withRefundNotices(json3(sayFirst({
133645
133802
  ok: true,
133646
133803
  ...paidFields,
133647
133804
  next: receipt.replayed === true ? "this request id was already paid: the stored answer came back and nothing new was paid" : "answered; give the user content, and tell them what was paid: paidAtomic (the AI provider's price, at most ceilingAtomic, plus Shinjuku's fee feeAtomic when via is shinjuku; what is not used is not refunded). wallet_balance shows the new balance"
133648
- }, "inference_chat"));
133805
+ }, "inference_chat")), paidStore);
133649
133806
  }
133650
133807
  };
133651
133808
  const addFunds2 = createAddFundsTool({
@@ -134477,7 +134634,7 @@ function createSetupTools({
134477
134634
  doneTool: sayOnRefusal(doneTool)
134478
134635
  };
134479
134636
  }
134480
- var import_web3132, 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, SEND_RUNS_ON, DEPOSIT_RUNS_ON, CLI_FUNDS_PLAN_PREFIX, CLI_FUNDS_SCHEMA, 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, PAY_ANSWER_MARGIN_MS, MONEY_FIRST_ANSWER_MS, BALANCE_AFTER_LANDING_MS, JOIN_CALL_MAX_MS, JOIN_NUDGE_QUIET_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, runningNpmVersion, terminalCommand2, capPlaceWords, CAP_NAMES, capName, capRaiseText, runningLimits, payCapFlag, cheaperSellerText, limitNext, SAFE_TEXT, MCP_TOOL_ALIASES, POOL_SWITCH_WHY, POOL_SWITCH_NEXT, SWITCH_STATES, quietSwitch, switchKey, ATOMIC_TEXT, movedTo, FLOAT_ID, 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, MCP_CAP_FIX, capFix, ownerOnlyNext, mcpNext, CONFIDENTIAL_RETIRED, json3, answerResult, discoverTool, LAUNCH_CAP_CODES, dayCapKind, refuse12, newRequestIdFor, payCapName, publicOffer, FLOAT_PAID_CODES, FLOAT_PRIVATE_LEG, MONEY_ID2, toRecordId, linkKindOf, moneyId, REFUSED_CODES, refusedCodeOf, PLAIN_REASONS, INSUFFICIENT_CODE, insufficientCause, INSUFFICIENT_REASONS, reasonOf, plainReason, KEPT_SEND_TRIES, shownId, isSplitSend, sendPartId, PROMISED_NEXT, partFailReason, sendAnswerOf, PUBLIC_READ_TIMEOUT_MS, RECIPIENT_CHECK_TIMEOUT_MS, withTimeout, ownKey, readFailure, OWN_KEY_TOKENS_MAX, tokenAmountText, elsewhereId, AGENT_ID_SCHEMA, agentIdName, minutesText2, ageWords2, DEPOSIT_SENT_STATES, depositLanded, usdcText3, 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, changeFields, balanceAnswer, 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, LINK_READ_ANSWER_MS, OWN_READS_AHEAD_MS, FLOAT_CORRECT_WAIT_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_SECONDS2, TRANSFER_CHANGE_READ_MS, TEMP_CLEAN_DELAY_MS, KEPT_ON_DISK, MAX_SHIELDED_BALANCE_ATOMIC, receiveGenerator, MCP_TOOL_NAMES, MCP_HIDDEN_TOOL_NAMES, setupTerminalNext, SETUP_RUNNING_SAY, ACCEPTED_NEXT, SETUP_RUNNING_NEXT, SETUP_ANSWER_WAIT_MS, SETUP_CONFIRMING_SAY, SETUP_CONFIRMING_NEXT, PRIVATE_INFERENCE_WHY, RESTART_STEPS, TOR_BROWSER_PORT, SETUP_TOR_TITLE, SETUP_TOR_WHY;
134637
+ var import_web3132, 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, SEND_RUNS_ON, DEPOSIT_RUNS_ON, CLI_FUNDS_PLAN_PREFIX, CLI_FUNDS_SCHEMA, 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, PAY_ANSWER_MARGIN_MS, MONEY_FIRST_ANSWER_MS, BALANCE_AFTER_LANDING_MS, JOIN_CALL_MAX_MS, JOIN_NUDGE_QUIET_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, runningNpmVersion, terminalCommand2, capPlaceWords, CAP_NAMES, capName, capRaiseText, runningLimits, payCapFlag, cheaperSellerText, limitNext, SAFE_TEXT, MCP_TOOL_ALIASES, POOL_SWITCH_WHY, POOL_SWITCH_NEXT, SWITCH_STATES, quietSwitch, switchKey, ATOMIC_TEXT, movedTo, FLOAT_ID, 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, MCP_CAP_FIX, capFix, ownerOnlyNext, mcpNext, CONFIDENTIAL_RETIRED, json3, answerResult, discoverTool, LAUNCH_CAP_CODES, dayCapKind, refuse12, newRequestIdFor, payCapName, publicOffer, FLOAT_PAID_CODES, FLOAT_PRIVATE_LEG, MONEY_ID2, toRecordId, linkKindOf, moneyId, REFUSED_CODES, refusedCodeOf, PLAIN_REASONS, INSUFFICIENT_CODE, insufficientCause, INSUFFICIENT_REASONS, reasonOf, plainReason, KEPT_SEND_TRIES, shownId, isSplitSend, sendPartId, PROMISED_NEXT, partFailReason, sendAnswerOf, PUBLIC_READ_TIMEOUT_MS, RECIPIENT_CHECK_TIMEOUT_MS, withTimeout, ownKey, readFailure, OWN_KEY_TOKENS_MAX, tokenAmountText, elsewhereId, AGENT_ID_SCHEMA, agentIdName, minutesText2, ageWords2, DEPOSIT_SENT_STATES, depositLanded, usdcText3, walkWalletHistory, REFRESH_LEASE_WALK_MS, HOME_MARKS_SETTLED_MS2, AUTO_BACKUPS_KEPT, AUTO_BACKUP_NAME, AUTO_BACKUP_VERIFIED, FRONTED_SETTLE_MARGIN_SLOTS, UNSHIELD_WINDOW_YIELD_MS, BALANCE_REFRESH_WAIT_MS, BALANCE_REFRESH_GRACE_MS, BALANCE_REFRESH_LONG_MS, INBOUND_REFRESH_AGE_MS, changeFields, balanceAnswer, 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, LINK_READ_ANSWER_MS, OWN_READS_AHEAD_MS, FLOAT_CORRECT_WAIT_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_SECONDS2, TRANSFER_CHANGE_READ_MS, TEMP_CLEAN_DELAY_MS, KEPT_ON_DISK, MAX_SHIELDED_BALANCE_ATOMIC, receiveGenerator, MCP_TOOL_NAMES, MCP_HIDDEN_TOOL_NAMES, setupTerminalNext, SETUP_RUNNING_SAY, ACCEPTED_NEXT, SETUP_RUNNING_NEXT, SETUP_ANSWER_WAIT_MS, SETUP_CONFIRMING_SAY, SETUP_CONFIRMING_NEXT, PRIVATE_INFERENCE_WHY, RESTART_STEPS, TOR_BROWSER_PORT, SETUP_TOR_TITLE, SETUP_TOR_WHY;
134481
134638
  var init_tools = __esm({
134482
134639
  "wallet/src/mcp/tools.mjs"() {
134483
134640
  "use strict";
@@ -134904,6 +135061,7 @@ var init_tools = __esm({
134904
135061
  AUTO_BACKUP_NAME = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z(-\d+)?$/;
134905
135062
  AUTO_BACKUP_VERIFIED = ".verified";
134906
135063
  FRONTED_SETTLE_MARGIN_SLOTS = 20;
135064
+ UNSHIELD_WINDOW_YIELD_MS = 1500;
134907
135065
  BALANCE_REFRESH_WAIT_MS = 2e4;
134908
135066
  BALANCE_REFRESH_GRACE_MS = 2e3;
134909
135067
  BALANCE_REFRESH_LONG_MS = 6e4;