shinjuku-shield 0.3.82 → 0.3.83

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,13 +6,13 @@ 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.82. The npm packages `shinjuku-shield` and
9
+ This is version 0.3.83. The npm packages `shinjuku-shield` and
10
10
  `shinjuku-shielded` hold the same files; `shinjuku` is the short command.
11
11
 
12
12
  ## Set up
13
13
 
14
14
  ```sh
15
- npx -y --no-audit --no-fund shinjuku-shield@0.3.82 setup
15
+ npx -y --no-audit --no-fund shinjuku-shield@0.3.83 setup
16
16
  ```
17
17
 
18
18
  Setup makes the wallet on this machine, a private passphrase file, and the
@@ -24,7 +24,7 @@ server to that host's config.
24
24
  ## The `shinjuku` command
25
25
 
26
26
  ```sh
27
- npm install -g shinjuku@0.3.82
27
+ npm install -g shinjuku@0.3.83
28
28
  shinjuku help
29
29
  ```
30
30
 
@@ -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-shielded -- npx -y --no-audit --no-fund shinjuku-shield@0.3.82 mcp
39
+ claude mcp add --scope user shinjuku-shielded -- npx -y --no-audit --no-fund shinjuku-shield@0.3.83 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-shielded": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.82", "mcp"] } } }
45
+ { "mcpServers": { "shinjuku-shielded": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.83", "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": "eaf7063ed1fe0dbcbf1c1f689e1ce60018401d669fe8bad7a7d4483cb875da0a",
5
- "bytes": 12477292,
6
- "sourceCommit": "9e701778bde2c7c186ff3ca9b631773c3abc02b2",
4
+ "sha256": "a59aa27b98cd1822c4651ed0567cd35b3f03e087818d8e5cfdb71e6320b7ffae",
5
+ "bytes": 12484127,
6
+ "sourceCommit": "ad531c40fe817d116a109d33472895bbbd854158",
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-23T17:26:04.670Z"
1285
+ "publishedAt": "2026-10-23T18:18:57.601Z"
1286
1286
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "shinjuku-shield",
3
- "version": "0.3.82",
3
+ "version": "0.3.83",
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": "eaf7063e",
7
- "shinjukuSha256": "eaf7063ed1fe0dbcbf1c1f689e1ce60018401d669fe8bad7a7d4483cb875da0a",
6
+ "shinjukuRelease": "a59aa27b",
7
+ "shinjukuSha256": "a59aa27b98cd1822c4651ed0567cd35b3f03e087818d8e5cfdb71e6320b7ffae",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-mcp#readme",
@@ -47211,8 +47211,9 @@ function reexec(args, mode, plainProxy) {
47211
47211
  ...proxyEnv(proxy),
47212
47212
  [MARK]: `${mode} ${proxy}`
47213
47213
  };
47214
- delete env[QUIET_CHAT_MARK];
47215
- if (quietChatRun(args)) env[QUIET_CHAT_MARK] = "1";
47214
+ delete env[QUIET_RUN_MARK];
47215
+ const quiet = quietRun(args);
47216
+ if (quiet) env[QUIET_RUN_MARK] = quiet;
47216
47217
  return new Promise((resolvePromise) => {
47217
47218
  const child = spawn(process.execPath, [...process.execArgv, process.argv[1], ...args], { env, stdio: "inherit" });
47218
47219
  const ignore = () => {
@@ -47232,8 +47233,9 @@ async function proxyEntry(argv2, run5, { err = (text3) => process.stderr.write(t
47232
47233
  const proxied = process.env[MARK];
47233
47234
  if (proxied) {
47234
47235
  const [mode, proxy] = proxied.split(" ");
47235
- const quiet = process.env[QUIET_CHAT_MARK] === "1";
47236
- delete process.env[QUIET_CHAT_MARK];
47236
+ const quietMark = process.env[QUIET_RUN_MARK];
47237
+ const quiet = Object.hasOwn(QUIET_NOTICE, quietMark ?? "") ? QUIET_NOTICE[quietMark] : void 0;
47238
+ delete process.env[QUIET_RUN_MARK];
47237
47239
  refuseDirectSockets(proxy);
47238
47240
  if (!canSwitchTorTag()) {
47239
47241
  err(`shinjuku: proxy_node_unsupported: Node ${process.versions.node} does not send its requests through a proxy (it ignores NODE_USE_ENV_PROXY), so this run cannot keep them private; nothing was sent. Install the current Node 22 or 24 release (Node 22.23 and 24.13 work), then rerun
@@ -47255,7 +47257,7 @@ async function proxyEntry(argv2, run5, { err = (text3) => process.stderr.write(t
47255
47257
  }
47256
47258
  return 1;
47257
47259
  }
47258
- err(`${quiet ? QUIET_NOTICE[mode] : NOTICE[mode]}
47260
+ err(`${quiet ? quiet[mode] : NOTICE[mode]}
47259
47261
  `);
47260
47262
  const ohttp = startOhttp(argv2, { tor: mode === "tor", err });
47261
47263
  if (ohttp.exitCode !== void 0) return ohttp.exitCode;
@@ -47289,7 +47291,7 @@ async function proxyEntry(argv2, run5, { err = (text3) => process.stderr.write(t
47289
47291
  }
47290
47292
  return reexec(options.args, options.mode, options.proxy);
47291
47293
  }
47292
- var TOR_HTTP_TUNNEL, TOR_CHECK, PROOF_TIMEOUT_MS, LOCAL_PROOF_TIMEOUT_MS, LOCAL_PROOF_MAX_BYTES, torProvenAt, proxyProvenAt, MARK, TOR_RUN_STARTED_ENV, ownProxyRefused, ONION_V3, RETRY_AFTER_CAP_MS, RETRY_AFTER_FLOOR_MS, RETRY_AFTER_DEFAULT_MS, RPC_NO_ANSWER_LIMIT_MS, RPC_PACING_CAP_MS, PACING_NOTICE_EVERY_MS, PACING_RATE_WINDOW_MS, TOR_MAX_ROTATIONS, runStartedAt, rpcNotBefore, pacingWaiters, pacingSince, pacedMs, rpcAnswered, lastAnswerAt, readSteps, pacingNoticeAt, pacingSamples, torMoves, torCircuitId, torMovesRefused, pendingMove, inTorRun, runNetworkMode, isOnionOrigin, ProxyUsageError, isOnionHost, TOR_EXPERT_BUNDLE, TUNNEL_LINE, TOR_INSTALL, OS_NAMES, osKey, torNotRunningMessage, NOTICE, QUIET_CHAT_MARK, QUIET_NOTICE, quietChatRun, TOR_TROUBLE_LINE, quietTorErr, withRunTag, proxyEnv, pacedSoFar, RESENDABLE_METHOD, torCircuit, GLOBAL_DISPATCHER2, laneDispatchers, paymentWarmStop;
47294
+ var TOR_HTTP_TUNNEL, TOR_CHECK, PROOF_TIMEOUT_MS, LOCAL_PROOF_TIMEOUT_MS, LOCAL_PROOF_MAX_BYTES, torProvenAt, proxyProvenAt, MARK, TOR_RUN_STARTED_ENV, ownProxyRefused, ONION_V3, RETRY_AFTER_CAP_MS, RETRY_AFTER_FLOOR_MS, RETRY_AFTER_DEFAULT_MS, RPC_NO_ANSWER_LIMIT_MS, RPC_PACING_CAP_MS, PACING_NOTICE_EVERY_MS, PACING_RATE_WINDOW_MS, TOR_MAX_ROTATIONS, runStartedAt, rpcNotBefore, pacingWaiters, pacingSince, pacedMs, rpcAnswered, lastAnswerAt, readSteps, pacingNoticeAt, pacingSamples, torMoves, torCircuitId, torMovesRefused, pendingMove, inTorRun, runNetworkMode, isOnionOrigin, ProxyUsageError, isOnionHost, TOR_EXPERT_BUNDLE, TUNNEL_LINE, TOR_INSTALL, OS_NAMES, osKey, torNotRunningMessage, NOTICE, QUIET_RUN_MARK, QUIET_NOTICE, quietRun, TOR_TROUBLE_LINE, quietTorErr, withRunTag, proxyEnv, pacedSoFar, RESENDABLE_METHOD, torCircuit, GLOBAL_DISPATCHER2, laneDispatchers, paymentWarmStop;
47293
47295
  var init_proxy = __esm({
47294
47296
  "wallet/src/net/proxy.mjs"() {
47295
47297
  "use strict";
@@ -47349,9 +47351,16 @@ var init_proxy = __esm({
47349
47351
  tor: "tor: every request of this run goes through Tor, so the facilitator, the RPC provider, and other servers see a Tor exit, not your IP. It does not hide what a request carries or when: a keyed RPC URL still names its account holder (use a key-free RPC), and timing still links your requests.",
47350
47352
  proxy: "proxy: every request of this run goes through the proxy, so servers see its address, not yours; the proxy sees every request. It does not hide what a request carries or when: a keyed RPC URL still names its account holder (use a key-free RPC), and timing still links your requests."
47351
47353
  };
47352
- QUIET_CHAT_MARK = "SHINJUKU_WALLET_QUIET_CHAT";
47353
- QUIET_NOTICE = { tor: "Private: this chat goes through Tor.", proxy: "Private: this chat goes through your proxy." };
47354
- quietChatRun = (args) => args[0] === "chat" && !args.includes("--verbose");
47354
+ QUIET_RUN_MARK = "SHINJUKU_WALLET_QUIET_RUN";
47355
+ QUIET_NOTICE = {
47356
+ chat: { tor: "Private: this chat goes through Tor.", proxy: "Private: this chat goes through your proxy." },
47357
+ funds: { tor: "Private: every request of this run goes through Tor.", proxy: "Private: every request of this run goes through your proxy." }
47358
+ };
47359
+ quietRun = (args) => {
47360
+ if (args.includes("--verbose")) return void 0;
47361
+ if (args[0] === "chat") return "chat";
47362
+ return ["add-funds", "funds-status"].includes(args[0]) && process.stdout.isTTY && !args.includes("--json") ? "funds" : void 0;
47363
+ };
47355
47364
  TOR_TROUBLE_LINE = /\bno (?:other )?(?:history-proxy )?onion\b|\bgave no answer\b|\bstalled\b|\bHTTP 429\b|\bcannot move\b/;
47356
47365
  quietTorErr = (err) => (text3) => {
47357
47366
  if (/^tor: /.test(text3) && !TOR_TROUBLE_LINE.test(text3) && !/\bWARNING\b/.test(text3)) return;
@@ -97243,7 +97252,11 @@ async function payViaFloat({
97243
97252
  });
97244
97253
  }
97245
97254
  const watched = watch.verdict();
97246
- const known = watched?.state === "final" || watched?.state === "dead" ? watched : lostAnswer ? await readFloatTransferState(record, fetchImpl) : void 0;
97255
+ let known = watched?.state === "final" || watched?.state === "dead" ? watched : lostAnswer || watched?.state === "landed" ? await readFloatTransferState(record, fetchImpl) : void 0;
97256
+ for (let read = 0; known?.state === "landed" && read < LANDED_READS; read += 1) {
97257
+ await (deps.floatStatusSleep ?? sleep6)(LANDED_POLL_MS);
97258
+ known = await readFloatTransferState(record, fetchImpl) ?? known;
97259
+ }
97247
97260
  if (known?.state === "final") {
97248
97261
  record = {
97249
97262
  ...record,
@@ -97269,7 +97282,7 @@ async function payViaFloat({
97269
97282
  state: "refund_pending",
97270
97283
  sellerStatus: response.status,
97271
97284
  refundPendingAtMs: record.refundPendingAtMs ?? Date.now(),
97272
- ...known?.state !== "dead" && (lostAnswer || earlierAmbiguous === true) ? { ambiguous: true } : {}
97285
+ ...known?.state !== "dead" && (lostAnswer || earlierAmbiguous === true || known?.state === "landed") ? { ambiguous: true } : {}
97273
97286
  };
97274
97287
  store.writeWal(walName2, record);
97275
97288
  fail18("p03_float_refund_pending", {
@@ -97553,7 +97566,7 @@ function paidResult(record, replayed) {
97553
97566
  float: { floatId: record.floatId }
97554
97567
  };
97555
97568
  }
97556
- var import_web3109, FLOAT_PAYMENT_SCHEMA, REGENERATE_CODES, FLOAT_PAYMENT_WAL_PREFIX, FLOAT_CONFIG_SCHEMA, FLOAT_PAY_PATH, FLOAT_MEMO_PATH, FLOAT_STATUS_PATH, MAX_BODY_BYTES, FLOAT_REFUND_ETA_SECONDS, REFUND_SETTLED_AFTER_MS, REFUND_OUTCOME_NEWS_MS, FINISHING_POLL_MS, FINISHING_LIMIT_MS, SELLER_TIMEOUT_MS, FLOAT_STATUS_START_MS, FLOAT_STATUS_POLL_MS, FLOAT_STATUS_TIMEOUT_MS, FLOAT_TRANSFER_DEAD, CONFIG_TIMEOUT_MS, MEMO_UPLOAD_TIMEOUT_MS, CONFIG_CACHE_MS, FLOAT_POOL_WARM_MS, DECIMAL3, POSITIVE, PENDING_CODES, FLOAT_FRONT_HEADER, hash8, fail18, FEE_PAYER_RESENDS, FEE_PAYER_RESEND_MS, GLOBAL_DISPATCHER4, sortedJson, MODEL_ID, floatPaymentWalName, floatRecordName, FLOAT_KEEP_BYTES_MS, memoryAnswers, MEMORY_ANSWERS_MAX, memoryAnswerKey, requestHashes, FLOAT_KEEP_OPEN_REQUEST_MS, configCache, configGeneration, configReadSequence, configReads, configRefreshes, SPECULATED_MAX_AGE_MS, speculatedQuotes, poolDomains, FLOAT_FACTS_SCHEMA, floatFacts, floatFactsKey, floatFactsMac, poolDomainReads, poolDomainWarmFailedAt, WARM_RETRY_MS, payerGenerator, etaEntry, loadPayerGenerator, plainText, BROADCAST_REFUSED, BROADCAST_REFUSED_STANDARD, BROADCAST_DEAD, TRANSFER_LANDING_WINDOW_MS, PROOF_DELIVERY_MS, SELF_BROADCAST_LEG1_LEFT_MS, BROADCAST_UNSIGNED, BROADCAST_NO_LOOKUP, BROADCAST_LANDED, BROADCAST_LIMIT_MS, BROADCAST_QUOTE_MARGIN_MS, SELF_BROADCAST_RESEND_MS, SELF_BROADCAST_UNANSWERED_RESEND_MS, SELLER_HEADERS_MAX_COUNT, SELLER_HEADERS_MAX_BYTES, SELLER_HEADERS_KEPT;
97569
+ var import_web3109, FLOAT_PAYMENT_SCHEMA, REGENERATE_CODES, FLOAT_PAYMENT_WAL_PREFIX, FLOAT_CONFIG_SCHEMA, FLOAT_PAY_PATH, FLOAT_MEMO_PATH, FLOAT_STATUS_PATH, MAX_BODY_BYTES, FLOAT_REFUND_ETA_SECONDS, REFUND_SETTLED_AFTER_MS, REFUND_OUTCOME_NEWS_MS, FINISHING_POLL_MS, FINISHING_LIMIT_MS, SELLER_TIMEOUT_MS, FLOAT_STATUS_START_MS, FLOAT_STATUS_POLL_MS, FLOAT_STATUS_TIMEOUT_MS, FLOAT_TRANSFER_DEAD, LANDED_POLL_MS, LANDED_READS, CONFIG_TIMEOUT_MS, MEMO_UPLOAD_TIMEOUT_MS, CONFIG_CACHE_MS, FLOAT_POOL_WARM_MS, DECIMAL3, POSITIVE, PENDING_CODES, FLOAT_FRONT_HEADER, hash8, fail18, FEE_PAYER_RESENDS, FEE_PAYER_RESEND_MS, GLOBAL_DISPATCHER4, sortedJson, MODEL_ID, floatPaymentWalName, floatRecordName, FLOAT_KEEP_BYTES_MS, memoryAnswers, MEMORY_ANSWERS_MAX, memoryAnswerKey, requestHashes, FLOAT_KEEP_OPEN_REQUEST_MS, configCache, configGeneration, configReadSequence, configReads, configRefreshes, SPECULATED_MAX_AGE_MS, speculatedQuotes, poolDomains, FLOAT_FACTS_SCHEMA, floatFacts, floatFactsKey, floatFactsMac, poolDomainReads, poolDomainWarmFailedAt, WARM_RETRY_MS, payerGenerator, etaEntry, loadPayerGenerator, plainText, BROADCAST_REFUSED, BROADCAST_REFUSED_STANDARD, BROADCAST_DEAD, TRANSFER_LANDING_WINDOW_MS, PROOF_DELIVERY_MS, SELF_BROADCAST_LEG1_LEFT_MS, BROADCAST_UNSIGNED, BROADCAST_NO_LOOKUP, BROADCAST_LANDED, BROADCAST_LIMIT_MS, BROADCAST_QUOTE_MARGIN_MS, SELF_BROADCAST_RESEND_MS, SELF_BROADCAST_UNANSWERED_RESEND_MS, SELLER_HEADERS_MAX_COUNT, SELLER_HEADERS_MAX_BYTES, SELLER_HEADERS_KEPT;
97557
97570
  var init_floatPay = __esm({
97558
97571
  "wallet/src/pay/floatPay.mjs"() {
97559
97572
  "use strict";
@@ -97593,6 +97606,8 @@ var init_floatPay = __esm({
97593
97606
  FLOAT_STATUS_POLL_MS = 5e3;
97594
97607
  FLOAT_STATUS_TIMEOUT_MS = 5e3;
97595
97608
  FLOAT_TRANSFER_DEAD = /* @__PURE__ */ new Set(["dead", "refund_queued", "refund_sent", "refunded"]);
97609
+ LANDED_POLL_MS = 2e3;
97610
+ LANDED_READS = 15;
97596
97611
  CONFIG_TIMEOUT_MS = 3e4;
97597
97612
  MEMO_UPLOAD_TIMEOUT_MS = 1e4;
97598
97613
  CONFIG_CACHE_MS = 6e4;
@@ -110009,15 +110024,16 @@ function refreshErrorWords(code2, lockedBy) {
110009
110024
  return void 0;
110010
110025
  }
110011
110026
  function balanceSay(a) {
110012
- const line = balanceLine(a);
110013
110027
  const settled = ATOMIC3.test(String(a.balanceAtomic ?? "")) && a.refreshing !== true && !a.refreshError && !positive(usdcText(a.landingAtomic));
110014
110028
  const card = settled && typeof a.card === "string" && a.card ? " The wallet card shows the rest." : "";
110015
110029
  const fits = (text3) => words(text3) <= SAY_MAX_WORDS;
110016
110030
  const count2 = Array.isArray(a.ownKeyTokens) ? a.ownKeyTokens.length + (Number.isSafeInteger(a.ownKeyTokensMore) ? a.ownKeyTokensMore : 0) : 0;
110017
- const withTokens = count2 < 1 ? line : `${line} The wallet's own key holds ${count2} other token${count2 === 1 ? "" : "s"} (ownKeyTokens).`;
110031
+ const tokens = count2 < 1 ? "" : ` The wallet's own key holds ${count2} other token${count2 === 1 ? "" : "s"} (ownKeyTokens).`;
110032
+ const line = balanceLine(a);
110033
+ const withTokens = `${balanceLine(a, SAY_MAX_WORDS - words(tokens))}${tokens}`;
110018
110034
  return [`${withTokens}${card}`, withTokens, `${line}${card}`].find(fits) ?? line;
110019
110035
  }
110020
- function balanceLine(a) {
110036
+ function balanceLine(a, room = SAY_MAX_WORDS) {
110021
110037
  const balance = usdcText(a.balanceAtomic);
110022
110038
  const pub = usdcText(a.unshieldedAtomic);
110023
110039
  const landing = usdcText(a.landingAtomic);
@@ -110072,6 +110088,13 @@ function balanceLine(a) {
110072
110088
  const held = heldByPaymentSay(a.detail);
110073
110089
  if (balance === "0" && held) return held;
110074
110090
  if (balance === "0" && !pubPart) return "Your private balance is empty. Use wallet_add_funds to get an address to send USDC to.";
110091
+ const parts = Number.isSafeInteger(a.balanceParts) && a.balanceParts > 1 && !a.sendWaiting ? a.balanceParts : 0;
110092
+ if (parts) {
110093
+ const minutes = Number.isSafeInteger(a.joinMinutes) && a.joinMinutes > 0 ? minutesText(a.joinMinutes) : "";
110094
+ const forms = minutes ? [`sending all of it first joins them, about ${minutes}`, `sending all of it takes about ${minutes}`] : ["sending all of it first joins them"];
110095
+ const split5 = forms.map((form) => have(` (in ${parts} parts: ${form}).${pubPart}`)).find((text3) => words(text3) <= room && (!positive(refund) || /\brefund/.test(text3)));
110096
+ if (split5) return split5;
110097
+ }
110075
110098
  return have(`.${pubPart}`);
110076
110099
  }
110077
110100
  function previewReason(a, npmVersion) {
@@ -110212,12 +110235,14 @@ function build(a, tool, npmVersion) {
110212
110235
  const amount = usdcText(a.done === true ? a.sentAtomic ?? a.amountAtomic : a.amountAtomic);
110213
110236
  const what = amount ? `${amount} USDC` : "the USDC";
110214
110237
  if (a.done !== true) return `Sending ${what} privately to ${a.to}.`;
110238
+ const took = Number.isSafeInteger(a.joinedParts) && a.joinedParts > 1 && Number.isSafeInteger(a.tookSeconds) && a.tookSeconds >= 0 ? ` It took about ${a.tookSeconds} s because the wallet first joined your balance's ${a.joinedParts} parts.` : "";
110215
110239
  if (typeof a.exitTransaction === "string" && a.privateSend === void 0) {
110216
110240
  const head = `Sent ${what} to ${a.to} (transaction ${a.exitTransaction}).`;
110241
+ if (took) return withBalance(`${head}${took}`, a);
110217
110242
  const known = withBalance(head, a, (balance) => ` Your private balance is now ${balance} USDC; nothing to do.`);
110218
110243
  return known !== head ? known : `${head} Your private balance shows it in a few seconds; nothing to do.`;
110219
110244
  }
110220
- return withBalance(`Sent ${what} to ${a.to}.`, a);
110245
+ return withBalance(`Sent ${what} to ${a.to}.${took}`, a);
110221
110246
  }
110222
110247
  if (typeof a.address === "string" && a.address && typeof a.stage === "string") return linkSay(a);
110223
110248
  if (a.stage === "waiting_for_funds" && typeof a.depositAddress === "string") {
@@ -120879,7 +120904,7 @@ function proofWalkReportOf(store, programId) {
120879
120904
  if (movedInAfterSnapshot(store, store.state, snapshot) > 0n) return null;
120880
120905
  const report = computeBalance({ store, snapshotRead: { snapshot }, source: "local" });
120881
120906
  const one = oneBalance(report, store, snapshot);
120882
- return one.balanceAtomic === null ? null : { report, one };
120907
+ return one.balanceAtomic === null ? null : { report, one, snapshot };
120883
120908
  } catch {
120884
120909
  return null;
120885
120910
  }
@@ -121326,6 +121351,18 @@ function createWalletTools({
121326
121351
  if (storeWriteCount() !== writesBefore) lastWriterEndedAt = Date.now();
121327
121352
  }
121328
121353
  });
121354
+ async function assertNotPromisedFresh(store, amountAtomic) {
121355
+ const shortfall = promisedShortfall(store, amountAtomic);
121356
+ if (shortfall?.error === "spend_private_send_unknown" && shortfall.pendingAtomic !== null) {
121357
+ try {
121358
+ await (ops.refresh ?? refreshWallet)({ session, store });
121359
+ log("MCP_PROMISED_REFRESH ok");
121360
+ } catch (error) {
121361
+ log(`MCP_PROMISED_REFRESH failed ${errorCode2(error, "mcp_refresh_failed")}`);
121362
+ }
121363
+ }
121364
+ assertUnshieldNotPromised(store, amountAtomic);
121365
+ }
121329
121366
  const autoPockets = createAutoPockets({
121330
121367
  session,
121331
121368
  money,
@@ -122712,7 +122749,7 @@ ${canonical5}`;
122712
122749
  const balance = {
122713
122750
  name: "wallet_balance",
122714
122751
  title: "Balance",
122715
- description: `Use this when the user asks "what is my balance" or "how much USDC do I have": it answers ONE balance (balanceAtomic, what x402_pay can spend) and the public (unshielded) USDC. Pays nothing and signs nothing. When the user asks to see the wallet ("show my wallet"), print \`card\` as it is: at most 8 lines with the private balance, the public USDC, Tor on or off, the last 3 payments (time UTC, seller host, USDC), and example requests. balanceAtomic is one number; breakdown names its parts (breakdown.shieldedAtomic is what wallet_unshield can send; privateSendPendingAtomic is the part of a running private send (wallet_unshield with parts or max_delay_minutes) not sent yet, already taken off shieldedAtomic; refundsInFlightAtomic is a Shinjuku refund on its way back; setAsideAtomic (only when not 0) is value an older version of this wallet set aside to pay sellers; during a pool move, pools lists each pool's part and movingAtomic is value on its way from the old pool to the new one; the move field shows the move, poolSwitches each switch). UNSHIELDED (unshieldedAtomic) is the plain public USDC on the wallet's own Solana key (ownAddress), the USDC wallet_shield makes private; unshieldedSolLamports is that key's SOL, information only: wallet_shield needs none (the facilitator's relayer pays every fee and rent). When the local balance is stale (a new deposit, a received payment), it reads the chain by itself first; when that takes longer than about 20 s it answers the last balance with refreshing: true and finishes in the background (call wallet_balance again in 30 s). A balance with refreshing: true and refreshReason is exact (payment_settling: a payment settles and the number counts it; account_join: the wallet joins its own balance parts and the number counts them; pool_move: value from the old pool arrives and the number counts it; first_check: a new wallet's first history check, the number is its own landed deposits). status reading (balanceAtomic null): the first balance read of a wallet that may hold money is not done; call wallet_balance again in 30 s. If the public read fails, unshieldedAtomic is null with unshieldedReason; the rest still answers. ownKeyTokens (only when there are any): the other tokens on ownAddress (not its USDC), each { mint, amountAtomic, decimals, amount }, at most 10 (ownKeyTokensMore counts the rest); a token is its mint, never a name or symbol; wallet_transfer_token with that mint as token_mint sells one and sends the USDC privately. earlierDeposits (only when there are any): USDC found at a one-time deposit address of an earlier pool, each with a notice line to tell the user; the wallet moves it into the current pool by itself. addFunds (only when there are any): each wallet_add_funds address still open, with its state and the USDC known to wait there (heldAtomic; null with heldNote when the address was not checked yet). startsAtNextRequest true (old name): its deposit is not sent yet; once the wallet has seen USDC there, it sends it by itself in about startsInMinutes + 1 minutes, in every host, with no ask. landingAtomic (only when not 0): USDC you added whose deposit landed on chain and that balanceAtomic does not count yet; this call finishes it and waits about 20 s for it, past that it is being counted: call wallet_balance again as next says (at once in the same turn when this call waited its 20 s, else in 30 s). session shows the spending limits (null or "none": no limit), what is left, where the limits come from, and how to change them (session.summary). verbose: true adds \`internal\` (accounts, notes, and other engineering numbers); the default answer leaves them out. To add money, use wallet_add_funds. Amounts are atomic units (USDC: 1000000 = 1 USDC); say and usdc give them in USDC.`,
122752
+ description: `Use this when the user asks "what is my balance" or "how much USDC do I have": it answers ONE balance (balanceAtomic, what x402_pay can spend) and the public (unshielded) USDC. Pays nothing and signs nothing. When the user asks to see the wallet ("show my wallet"), print \`card\` as it is: at most 8 lines with the private balance, the public USDC, Tor on or off, the last 3 payments (time UTC, seller host, USDC), and example requests. balanceParts (only above 1): the balance is in that many parts, and a send of all of it joins them first (next says the minutes). balanceAtomic is one number; breakdown names its parts (breakdown.shieldedAtomic is what wallet_unshield can send; privateSendPendingAtomic is the part of a running private send (wallet_unshield with parts or max_delay_minutes) not sent yet, already taken off shieldedAtomic; refundsInFlightAtomic is a Shinjuku refund on its way back; setAsideAtomic (only when not 0) is value an older version of this wallet set aside to pay sellers; during a pool move, pools lists each pool's part and movingAtomic is value on its way from the old pool to the new one; the move field shows the move, poolSwitches each switch). UNSHIELDED (unshieldedAtomic) is the plain public USDC on the wallet's own Solana key (ownAddress), the USDC wallet_shield makes private; unshieldedSolLamports is that key's SOL, information only: wallet_shield needs none (the facilitator's relayer pays every fee and rent). When the local balance is stale (a new deposit, a received payment), it reads the chain by itself first; when that takes longer than about 20 s it answers the last balance with refreshing: true and finishes in the background (call wallet_balance again in 30 s). A balance with refreshing: true and refreshReason is exact (payment_settling: a payment settles and the number counts it; account_join: the wallet joins its own balance parts and the number counts them; pool_move: value from the old pool arrives and the number counts it; first_check: a new wallet's first history check, the number is its own landed deposits). status reading (balanceAtomic null): the first balance read of a wallet that may hold money is not done; call wallet_balance again in 30 s. If the public read fails, unshieldedAtomic is null with unshieldedReason; the rest still answers. ownKeyTokens (only when there are any): the other tokens on ownAddress (not its USDC), each { mint, amountAtomic, decimals, amount }, at most 10 (ownKeyTokensMore counts the rest); a token is its mint, never a name or symbol; wallet_transfer_token with that mint as token_mint sells one and sends the USDC privately. earlierDeposits (only when there are any): USDC found at a one-time deposit address of an earlier pool, each with a notice line to tell the user; the wallet moves it into the current pool by itself. addFunds (only when there are any): each wallet_add_funds address still open, with its state and the USDC known to wait there (heldAtomic; null with heldNote when the address was not checked yet). startsAtNextRequest true (old name): its deposit is not sent yet; once the wallet has seen USDC there, it sends it by itself in about startsInMinutes + 1 minutes, in every host, with no ask. landingAtomic (only when not 0): USDC you added whose deposit landed on chain and that balanceAtomic does not count yet; this call finishes it and waits about 20 s for it, past that it is being counted: call wallet_balance again as next says (at once in the same turn when this call waited its 20 s, else in 30 s). session shows the spending limits (null or "none": no limit), what is left, where the limits come from, and how to change them (session.summary). verbose: true adds \`internal\` (accounts, notes, and other engineering numbers); the default answer leaves them out. To add money, use wallet_add_funds. Amounts are atomic units (USDC: 1000000 = 1 USDC); say and usdc give them in USDC.`,
122716
122753
  inputSchema: { type: "object", properties: {
122717
122754
  verbose: { type: "boolean", description: "Optional. true adds `internal`: the engineering numbers (funded accounts, notes, payments without waiting, a 0 set-aside value). Default false." }
122718
122755
  }, additionalProperties: false },
@@ -122963,8 +123000,7 @@ ${canonical5}`;
122963
123000
  else if (unshielded.unshieldedAtomic && unshielded.unshieldedAtomic !== "0" && shieldEnabled) next = 'to make the public USDC private, call wallet_shield with amount (like "0.37")';
122964
123001
  else if (one.balanceAtomic === "0" && shieldEnabled) {
122965
123002
  next = elsewhereEnabled ? `the shielded balance is 0, so x402_pay cannot pay yet. To add funds, call wallet_add_funds: it gives a one-time address; up to ${usdcText2(payMeRoundMax())} USDC sent there on Solana becomes private a few minutes after it arrives while this MCP server runs (or call wallet_add_funds with its request_id to finish it at once)` : `the shielded balance is 0, so x402_pay cannot pay yet. To add funds: send USDC on Solana to ownAddress (this wallet's own key), then call wallet_shield with amount (any amount from ${usdcText2(10000n)} to ${usdcText2(MAX_SHIELDED_BALANCE_ATOMIC)} USDC)`;
122966
- } else if (parts?.parts > 1) next = `Your balance is in ${parts.parts} parts. A send of the whole balance first joins them: about ${minutesText2(parts.minutes)}`;
122967
- else next = "nothing to do; x402_pay pays from balanceAtomic, wallet_unshield sends from breakdown.shieldedAtomic";
123003
+ } else next = "nothing to do; x402_pay pays from balanceAtomic, wallet_unshield sends from breakdown.shieldedAtomic";
122968
123004
  let landingAtomic = 0n;
122969
123005
  try {
122970
123006
  landingAtomic = landedNotCounted(store);
@@ -123073,6 +123109,10 @@ ${canonical5}`;
123073
123109
  if (sendWaiting) {
123074
123110
  next = `${next}. Tell the user: the send of ${usdcText2(sendWaiting.amountAtomic)} USDC to ${sendWaiting.to.slice(0, 4)}...${sendWaiting.to.slice(-4)} (request_id "${sendWaiting.requestId}") ${sendWaiting.state === "sending" ? "is being sent now" : sendWaiting.state === "retrying" ? retryWords(sendWaiting) : `waits for the join of the balance parts: ${joinAsksText(waitingPlan)}`}; it cannot be cancelled`;
123075
123111
  }
123112
+ const balanceParts = parts?.parts > 1 && ATOMIC_TEXT.test(String(one.balanceAtomic ?? "")) ? parts.parts : void 0;
123113
+ if (balanceParts && !sendWaiting) {
123114
+ next = `${next}. The balance is in ${balanceParts} parts: if the user asks to send all of it, tell them first that it takes about ${minutesText2(parts.minutes)}`;
123115
+ }
123076
123116
  if (earlierDeposits.length) next = `${next}. Tell the user each earlierDeposits notice`;
123077
123117
  const notices = takeRefundNotices();
123078
123118
  if (notices.refundNotices) next = `${next}. Tell the user each refundNotices line`;
@@ -123136,6 +123176,8 @@ ${canonical5}`;
123136
123176
  balanceAtomic: one.balanceAtomic,
123137
123177
  balance: one.balance,
123138
123178
  card: walletCard({ one, unshielded, landingAtomic, parts, store }),
123179
+ // joinMinutes: the card's minutes, so say.mjs names them too.
123180
+ ...balanceParts ? { balanceParts, ...Number.isSafeInteger(parts.minutes) ? { joinMinutes: parts.minutes } : {} } : {},
123139
123181
  ...report.detail ? { detail: detailPublic } : {},
123140
123182
  breakdown,
123141
123183
  ...refresh,
@@ -123206,12 +123248,13 @@ ${canonical5}`;
123206
123248
  continue;
123207
123249
  }
123208
123250
  if (record?.state === "submitted") {
123209
- const age = Number.isSafeInteger(record.submittedAtMs) ? Date.now() - record.submittedAtMs : NaN;
123210
- if (age > SUBMITTED_READ_AFTER_MS && age < 2 * STALE_REFUND_MS) names.push(name);
123251
+ const age2 = Number.isSafeInteger(record.submittedAtMs) ? Date.now() - record.submittedAtMs : NaN;
123252
+ if (age2 > SUBMITTED_READ_AFTER_MS && age2 < 2 * STALE_REFUND_MS) names.push(name);
123211
123253
  continue;
123212
123254
  }
123213
123255
  if (record?.state !== "refund_pending" || record.refundOutcome === "refund_arrived" || !Number.isSafeInteger(record.refundPendingAtMs)) continue;
123214
- if (Date.now() - record.refundPendingAtMs > STALE_REFUND_MS) names.push(name);
123256
+ const age = Date.now() - record.refundPendingAtMs;
123257
+ if (age > STALE_REFUND_MS || record.ambiguous === true && age > SUBMITTED_READ_AFTER_MS) names.push(name);
123215
123258
  }
123216
123259
  return names;
123217
123260
  }
@@ -123280,8 +123323,9 @@ ${canonical5}`;
123280
123323
  const { ambiguous: _settled, ...rest } = record;
123281
123324
  store.writeWal(name, { ...rest, state: "refund_pending", refundPendingAtMs: Date.now() });
123282
123325
  } else {
123326
+ const { ambiguous: _known, ...record_ } = record;
123283
123327
  store.writeWal(name, {
123284
- ...record,
123328
+ ...record_,
123285
123329
  state: "seller_kept",
123286
123330
  sellerKeptAtMs: record.sellerKeptAtMs ?? Date.now(),
123287
123331
  .../^[1-9A-HJ-NP-Za-km-z]{64,90}$/.test(String(transaction ?? "")) ? { landedTransaction: transaction } : {}
@@ -123291,6 +123335,12 @@ ${canonical5}`;
123291
123335
  }
123292
123336
  return written;
123293
123337
  }
123338
+ const refundStateWords = (floatState) => ({
123339
+ dead: "sure: the transfer to the seller expired, so Shinjuku sends the refund",
123340
+ refund_queued: "sure: Shinjuku is sending the refund",
123341
+ refund_sent: "sent by Shinjuku (refundTransaction)",
123342
+ refunded: "sent by Shinjuku (refundTransaction)"
123343
+ })[floatState] ?? floatState;
123294
123344
  function paymentRows(store, names, refundReads = /* @__PURE__ */ new Map()) {
123295
123345
  const floats = names.filter((name) => name.startsWith(FLOAT_PAYMENT_WAL_PREFIX)).map((name) => ({ name, record: store.readWal(name) }));
123296
123346
  const ENDED = /* @__PURE__ */ new Set(["receipt", "refund_pending", "seller_kept", "submitted"]);
@@ -123300,7 +123350,7 @@ ${canonical5}`;
123300
123350
  const refundHeld = (record) => {
123301
123351
  if (walk === void 0) {
123302
123352
  try {
123303
- walk = readBalanceSnapshot(store).snapshot ?? null;
123353
+ walk = proofWalkReportOf(store, session.programId)?.snapshot ?? readBalanceSnapshot(store).snapshot ?? null;
123304
123354
  } catch {
123305
123355
  walk = null;
123306
123356
  }
@@ -123359,7 +123409,7 @@ ${canonical5}`;
123359
123409
  refundAtomic: paid,
123360
123410
  refund: held ? "arrived" : "on its way",
123361
123411
  ...FLOAT_ID.test(String(record.floatId)) ? { floatId: record.floatId } : {},
123362
- refundState: shinjuku.floatState,
123412
+ refundState: refundStateWords(shinjuku.floatState),
123363
123413
  ...shinjuku.refundTransaction ? { refundTransaction: shinjuku.refundTransaction } : {},
123364
123414
  note: held ? "the seller did not take this payment; the whole amount came back to the private balance" : sent ? "the seller did not take this payment; Shinjuku sent the refund (refundTransaction), and the private balance counts it at its next read" : "the seller did not take this payment; the whole amount comes back to the private balance by itself"
123365
123415
  } : { note: landed ? `the transfer to the seller landed, but the seller has not served the request yet; ${ask} to get it` : `the seller has not served this request yet; ${ask}. If the seller never takes the payment, Shinjuku sends the whole amount back by itself in about ${Math.ceil(FLOAT_REFUND_ETA_SECONDS / 60)} minutes` }
@@ -123380,7 +123430,7 @@ ${canonical5}`;
123380
123430
  refundAtomic: paid,
123381
123431
  refund: arrived ? "arrived" : notSeen ? "not seen" : "on its way",
123382
123432
  ...FLOAT_ID.test(String(record.floatId)) ? { floatId: record.floatId } : {},
123383
- ...shinjuku?.floatState ? { refundState: shinjuku.floatState } : {},
123433
+ ...shinjuku?.floatState ? { refundState: refundStateWords(shinjuku.floatState) } : {},
123384
123434
  ...shinjuku?.refundTransaction ? { refundTransaction: shinjuku.refundTransaction } : {},
123385
123435
  note: arrived ? "the seller did not take this payment; the whole amount came back to the private balance" : notSeen ? "the seller did not take this payment; its refund is not seen in the balance (the seller may have kept it)" : sent ? "the seller did not take this payment; Shinjuku sent the refund (refundTransaction), and the private balance counts it at its next read" : "the seller did not take this payment; the whole amount comes back to the private balance by itself"
123386
123436
  };
@@ -123629,7 +123679,7 @@ ${canonical5}`;
123629
123679
  const receipts = {
123630
123680
  name: "wallet_receipts",
123631
123681
  title: "List recent receipts and wallet moves",
123632
- description: `List this wallet's most recent receipts, newest first. Payments (kind x402 or inference): seller (the URL paid), sellerHost, item (what was bought), priceAtomic, feeAtomic, amountAtomic (the total), sellerTransaction, privateTransaction (this wallet's private payment), facilitator, answeredAt, and transaction / host (the seller's when known). pay.shinjukustaition.com is Shinjuku, the private payment facilitator, never the seller. A payment the seller did not take shows state refund_pending (the refund is on its way; floatId, and refundState and refundTransaction from Shinjuku) until the balance holds it, then refunded. A payment the seller has not served yet shows waiting_for_seller (or seller_paid_not_served once the transfer to the seller landed): call x402_pay again with its requestId to get it. The wallet's own moves (kind shield, add-funds, unshield, private-send, token-transfer, or an older deposit kind): requestId, amountAtomic, state, transaction, at; a private-send row with parts still to go shows partsAt (each part's time and state) and next (it is not stuck); a token-transfer row (wallet_transfer_token) shows tokenMint, tokenAmountAtomic, usdcAtomic (what the sell gave) and sentAtomic; an add-funds row also shows heldAtomic, the USDC waiting at its address (it becomes private by itself after a random privacy wait: never close an address that holds USDC). An add-funds row with via "cli" was made by the shinjuku add-funds command in a terminal: fundsId, route, amountAtomic, state (done or not_finished), transaction; it has no requestId, and wallet_add_funds never continues it. requestId is shown for payments made by this MCP process; older ones show requestKey (its SHA-256). kind "payments" or "moves" lists one group only.`,
123682
+ description: `List this wallet's most recent receipts, newest first. Payments (kind x402 or inference): seller (the URL paid), sellerHost, item (what was bought), priceAtomic, feeAtomic, amountAtomic (the total), sellerTransaction, privateTransaction (this wallet's private payment), facilitator, answeredAt, and transaction / host (the seller's when known). pay.shinjukustaition.com is Shinjuku, the private payment facilitator, never the seller. A payment the seller did not take shows state refund_pending (the refund is on its way; floatId, refundState (Shinjuku's refund step in words) and refundTransaction from Shinjuku) until the balance holds it, then refunded. A payment the seller has not served yet shows waiting_for_seller (or seller_paid_not_served once the transfer to the seller landed): call x402_pay again with its requestId to get it. The wallet's own moves (kind shield, add-funds, unshield, private-send, token-transfer, or an older deposit kind): requestId, amountAtomic, state, transaction, at; a private-send row with parts still to go shows partsAt (each part's time and state) and next (it is not stuck); a token-transfer row (wallet_transfer_token) shows tokenMint, tokenAmountAtomic, usdcAtomic (what the sell gave) and sentAtomic; an add-funds row also shows heldAtomic, the USDC waiting at its address (it becomes private by itself after a random privacy wait: never close an address that holds USDC). An add-funds row with via "cli" was made by the shinjuku add-funds command in a terminal: fundsId, route, amountAtomic, state (done or not_finished), transaction; it has no requestId, and wallet_add_funds never continues it. requestId is shown for payments made by this MCP process; older ones show requestKey (its SHA-256). kind "payments" or "moves" lists one group only.`,
123633
123683
  inputSchema: {
123634
123684
  type: "object",
123635
123685
  properties: {
@@ -124259,7 +124309,9 @@ ${canonical5}`;
124259
124309
  else if (POOL_LAYOUT_CODE.test(code2)) next = `${POOL_LAYOUT_UPDATE}. Then ${again(kind, job)}: it continues where it stopped and never moves the money twice. If the latest release says the same, tell your owner: ${code2}`;
124260
124310
  else if (code2 === "mcp_unshield_to_not_allowed") next = unboundNext(job.requestId, job.amountAtomic);
124261
124311
  else if (code2 === "mcp_unshield_to_invalid") next = UNSHIELD_TO_INVALID_NEXT;
124262
- else if (kind === "unshield" && /^spend_private_send_(reserved|unknown)$/.test(code2)) next = PROMISED_NEXT;
124312
+ else if (kind === "unshield" && code2 === "spend_private_send_unknown") {
124313
+ next = `Nothing was sent. The wallet is finishing a part of its private send, so the value free beside it is not known yet. In about a minute, ${again(kind, job)}`;
124314
+ } else if (kind === "unshield" && code2 === "spend_private_send_reserved") next = PROMISED_NEXT;
124263
124315
  else if (kind === "unshield" && /send_part_id_taken/.test(code2)) {
124264
124316
  next = `Nothing was sent. A part id of this private send (mp<n>-${job.requestId}) already names another unshield of this wallet: call wallet_unshield with a NEW request_id`;
124265
124317
  } else if (kind === "unshield" && /accounts_split_merge_first/.test(line)) {
@@ -124622,8 +124674,21 @@ ${canonical5}`;
124622
124674
  }
124623
124675
  };
124624
124676
  let told;
124677
+ let joinedParts;
124678
+ let joinSeen = false;
124679
+ let startLogged = false;
124680
+ let waitIntent;
124681
+ try {
124682
+ [waitIntent] = openJoinIntents(session.openStore(), requestId);
124683
+ } catch {
124684
+ waitIntent = void 0;
124685
+ }
124686
+ if (waitIntent) {
124687
+ joinSeen = true;
124688
+ if (Number.isSafeInteger(waitIntent.parts) && waitIntent.parts > 1) joinedParts = waitIntent.parts;
124689
+ }
124625
124690
  joinWaitingCalls += 1;
124626
- line("wait");
124691
+ line("wait", Number.isSafeInteger(waitIntent?.joinAtMs) ? ` startInMs=${Math.max(0, waitIntent.joinAtMs - Date.now())}` : "");
124627
124692
  try {
124628
124693
  for (; ; ) {
124629
124694
  const job2 = jobs.get(key);
@@ -124640,9 +124705,9 @@ ${canonical5}`;
124640
124705
  line("final", Number.isSafeInteger(finalAtMs) ? ` finalMs=${finalAtMs - startedAt}` : "");
124641
124706
  }
124642
124707
  line("send");
124643
- await Promise.race([statusWait([job2.promise], Math.max(0, until - Date.now()), progress3, "sending privately").catch(() => {
124708
+ await Promise.race([statusWait([job2.answered ?? job2.promise], Math.max(0, until - Date.now()), progress3, "sending privately").catch(() => {
124644
124709
  }), stopped]);
124645
- line(job2.state === "done" ? "done" : job2.state === "running" ? signal?.aborted ? "cancel" : "cap" : "ended");
124710
+ line(job2.answeredEarly ? "answered" : job2.state === "done" ? "done" : job2.state === "running" ? signal?.aborted ? "cancel" : "cap" : "ended");
124646
124711
  break;
124647
124712
  }
124648
124713
  if (job2.state !== "failed" || job2.error?.code !== "unshield_joining_accounts") break;
@@ -124665,6 +124730,8 @@ ${canonical5}`;
124665
124730
  intent = void 0;
124666
124731
  }
124667
124732
  if (!intent) break;
124733
+ joinSeen = true;
124734
+ if (Number.isSafeInteger(intent.parts) && intent.parts > 1) joinedParts = intent.parts;
124668
124735
  const phase = Number.isSafeInteger(intent.joinFinalAtMs) ? "final" : Number.isSafeInteger(intent.joinSentAtMs) ? "join" : void 0;
124669
124736
  if (phase && phase !== told) {
124670
124737
  told = phase;
@@ -124675,6 +124742,10 @@ ${canonical5}`;
124675
124742
  const about = `about ${Math.max(1, left.minutes + (left.asks === 2 ? left.thenMinutes : 0))} min`;
124676
124743
  tell(intent.joinAtMs > Date.now() ? `joining your balance's ${parts} first (${about}): the join starts in about ${Math.ceil((intent.joinAtMs - Date.now()) / 1e3)} s (a random wait), then the send` : phase === "join" ? `joining your balance's ${parts} first (${about}): the join is sent; the send follows once it is final` : `joining your balance's ${parts} first (${about}), then the send`);
124677
124744
  if (intent.joinAtMs <= Date.now()) {
124745
+ if (!startLogged && phase === void 0) {
124746
+ startLogged = true;
124747
+ line("start");
124748
+ }
124678
124749
  const result = await joiner.tick();
124679
124750
  if (result?.state === "failed" || result?.skipped === "failed_recently") {
124680
124751
  const last = joiner.status().last ?? result;
@@ -124705,12 +124776,29 @@ ${canonical5}`;
124705
124776
  joinWaitingCalls -= 1;
124706
124777
  }
124707
124778
  const job = jobs.get(key);
124708
- return job ? jobAnswer("unshield", job) : refuse12(
124709
- "mcp_unshield_failed",
124710
- "the send was not found in this process; nothing new was sent by this call",
124711
- `call wallet_unshield again with request_id "${requestId}" for its state`,
124712
- { requestId }
124713
- );
124779
+ if (!job) {
124780
+ return refuse12(
124781
+ "mcp_unshield_failed",
124782
+ "the send was not found in this process; nothing new was sent by this call",
124783
+ `call wallet_unshield again with request_id "${requestId}" for its state`,
124784
+ { requestId }
124785
+ );
124786
+ }
124787
+ const answer = jobAnswer("unshield", job);
124788
+ if (job.state !== "done" || !joinSeen) return answer;
124789
+ let done;
124790
+ try {
124791
+ done = JSON.parse(answer.text);
124792
+ } catch {
124793
+ return answer;
124794
+ }
124795
+ const tookSeconds = Math.round((Date.now() - startedAt) / 1e3);
124796
+ return json3({
124797
+ ...done,
124798
+ ...joinedParts ? { joinedParts } : {},
124799
+ tookSeconds,
124800
+ next: `${done.next}. Tell the user it took about ${tookSeconds} s because the wallet first joined the balance's ${joinedParts ? `${joinedParts} ` : ""}parts (a private move inside the wallet, no fee)`
124801
+ });
124714
124802
  }
124715
124803
  function joinFailedAnswer(job, last) {
124716
124804
  const code2 = typeof last?.code === "string" && last.code ? last.code : "wallet_join_failed";
@@ -125151,9 +125239,9 @@ ${canonical5}`;
125151
125239
  try {
125152
125240
  shortfall = promisedShortfall(session.openStore(), request.amountAtomic);
125153
125241
  } catch {
125154
- shortfall = { error: "spend_private_send_unknown" };
125242
+ shortfall = { error: "spend_private_send_unknown", pendingAtomic: null };
125155
125243
  }
125156
- if (shortfall) {
125244
+ if (shortfall && !(shortfall.error === "spend_private_send_unknown" && shortfall.pendingAtomic !== null)) {
125157
125245
  return refuse12(
125158
125246
  shortfall.error,
125159
125247
  promisedShortfallText(shortfall, request.amountAtomic).replace(/^[a-z_]+: /, ""),
@@ -125538,8 +125626,8 @@ ${canonical5}`;
125538
125626
  }
125539
125627
  function runUnshieldJob(job, onStage) {
125540
125628
  if (isSplitSend(job)) return runPrivateSendJob(job, onStage);
125541
- return underLease((opened) => {
125542
- if (!opened.readWal(`self-pay-${moneyId(job.requestId)}`)) assertUnshieldNotPromised(opened, job.amountAtomic);
125629
+ return underLease(async (opened) => {
125630
+ if (!opened.readWal(`self-pay-${moneyId(job.requestId)}`)) await assertNotPromisedFresh(opened, job.amountAtomic);
125543
125631
  return unshieldPart(opened, job, moneyId(job.requestId), job.amountAtomic, onStage, (answer) => answerUnshieldEarly(job, answer));
125544
125632
  });
125545
125633
  }
@@ -125560,9 +125648,9 @@ ${canonical5}`;
125560
125648
  async function runPrivateSendJob(job, onStage) {
125561
125649
  const sendId = moneyId(job.requestId);
125562
125650
  let store;
125563
- const plan = await underLease((opened) => {
125651
+ const plan = await underLease(async (opened) => {
125564
125652
  store = opened;
125565
- if (!opened.readWal(privateSendPlanName(sendId))) assertUnshieldNotPromised(opened, job.amountAtomic);
125653
+ if (!opened.readWal(privateSendPlanName(sendId))) await assertNotPromisedFresh(opened, job.amountAtomic);
125566
125654
  return planPrivateSend({
125567
125655
  store,
125568
125656
  sendId,
@@ -126207,7 +126295,7 @@ ${canonical5}`;
126207
126295
  const unshield = {
126208
126296
  name: "wallet_unshield",
126209
126297
  title: "Send USDC privately from the shielded balance",
126210
- description: `Use this when the user says "send 2 USDC to <address>" or "withdraw to my wallet" (leave \`to\` out for the owner's own wallet when one is set): it sends amount (USDC, like "2"; or amount_atomic) from the SHIELDED balance as plain USDC to \`to\`, a public Solana address (the USDC goes to its USDC token account). Privately: the chain shows the pool paying the address, not which wallet paid. The wallet's own key (ownAddress in wallet_balance) is this wallet's public address: it holds the public USDC, wallet_shield deposits from it, and wallet_transfer_token sells from it. So it is refused as \`to\`: an exit there would show on chain that the private balance belongs to this wallet. This holds whether or not that key ever deposited: wallet_add_funds deposits come from one-time addresses, not from this key, so never tell the user the deposits came from it. The facilitator pays every network fee; no SOL is needed. The owner's --unshield-to addresses send at once. Any other address is sent only after the user confirms it in the MCP client (a confirmation dialog); a client that cannot ask is refused (mcp_unshield_to_not_allowed) and a decline sends nothing (mcp_unshield_declined). \`to\` must already hold USDC (have a USDC token account): an address without one is refused before any dialog (mcp_unshield_to_invalid). It takes minutes (one wait of up to 5 minutes is normal): the call answers within about 20 s with stage (confirming, queued, refreshing, self-pay = preparing, exit = sending, done; joining = the balance is in several parts and the wallet joins them first, then sends; the answer says how many more asks and when (joinAsks, joinMinutes), ok: true); call wallet_unshield again with the SAME request_id, amount, and to for the status (each call answers within about 20 s, at once when the send ends; a confirmation dialog the user answers may take longer). One exception: for a single send (no parts, no delay) whose balance is in several parts, when this MCP app sends a progress token with the call, that one call joins the parts and then sends, taking up to ${JOIN_CALL_MAX_MS / 6e4} minutes, with a progress line every ${JOIN_STEP_MS / 1e3} s ("joining your balance's N parts first (about M min)"); tell the user it waits for the join. Without a progress token it answers within about 20 s with stage joining and when to ask again. The same request_id never sends twice, also after a restart. request_id is optional: without it the answer names the id it made, and the same call again within 10 minutes after it ends reads that same send. When it lands, the wallet writes an encrypted backup (backupPath). x402_pay calls wait while it runs. An optional owner cap (--max-unshield) may bound each call and what this wallet sends in any 24 hours, from every MCP process (mcp_unshield_cap_exceeded: tell the owner). Other refusals: mcp_argument_invalid, mcp_request_id_reused, mcp_unshield_to_invalid, wallet_locked, mcp_tool_disabled, mcp_unshield_failed, and the unshield's own p03_* codes. PRIVATE SEND (optional): when the user says "send it in parts", "spread it out", or "do not send it all at once", pass parts (2-8) WITH max_delay_minutes (1-60; parts with no wait are refused: they would leave together): the amount is split into parts exits of unequal size (each at least 0.01 USDC), each sent after a random wait of up to max_delay_minutes (part 1 too), so the chain shows no single exit of this amount at this time. max_delay_minutes alone delays one exit. ` + SEND_RUNS_ON + ` Late parts never go together. While parts are unsent, x402_pay may spend only the rest of the balance. The plan is saved in the wallet before part 1, so the same request_id (with the same parts and max_delay_minutes) continues after a restart and never sends a part twice; the answer's privateSend lists each part (state, amount, when it may go, transactions), sentAtomic, and remainingAtomic. The user confirms the whole send once. Both left out: one exit, as before. Each part is a full unshield, so it takes as long as one, and the facilitator pays the network fee of each. Privacy limit: every part goes to the SAME \`to\`, so anyone who adds up that address's incoming transfers sees the total; the parts hide the time and the single amount of the exit, not the sum the address received. wallet_cancel with the same request_id stops the parts not sent yet (their value stays in the shielded balance); while a part is being sent, that call waits for the part to finish, up to about ${CALL_BUDGET_MS / 1e3} s.`,
126298
+ description: `Use this when the user says "send 2 USDC to <address>" or "withdraw to my wallet" (leave \`to\` out for the owner's own wallet when one is set): it sends amount (USDC, like "2"; or amount_atomic) from the SHIELDED balance as plain USDC to \`to\`, a public Solana address (the USDC goes to its USDC token account). Privately: the chain shows the pool paying the address, not which wallet paid. The wallet's own key (ownAddress in wallet_balance) is this wallet's public address: it holds the public USDC, wallet_shield deposits from it, and wallet_transfer_token sells from it. So it is refused as \`to\`: an exit there would show on chain that the private balance belongs to this wallet. This holds whether or not that key ever deposited: wallet_add_funds deposits come from one-time addresses, not from this key, so never tell the user the deposits came from it. The facilitator pays every network fee; no SOL is needed. The owner's --unshield-to addresses send at once. Any other address is sent only after the user confirms it in the MCP client (a confirmation dialog); a client that cannot ask is refused (mcp_unshield_to_not_allowed) and a decline sends nothing (mcp_unshield_declined). \`to\` must already hold USDC (have a USDC token account): an address without one is refused before any dialog (mcp_unshield_to_invalid). It takes minutes (one wait of up to 5 minutes is normal): the call answers within about 20 s with stage (confirming, queued, refreshing, self-pay = preparing, exit = sending, done; joining = the balance is in several parts and the wallet joins them first, then sends; the answer says how many more asks and when (joinAsks, joinMinutes), ok: true); call wallet_unshield again with the SAME request_id, amount, and to for the status (each call answers within about 20 s, at once when the send ends; a confirmation dialog the user answers may take longer). One exception: for a single send (no parts, no delay) whose balance is in several parts, when this MCP app sends a progress token with the call, that one call joins the parts and then sends, taking up to ${JOIN_CALL_MAX_MS / 6e4} minutes, with a progress line every ${JOIN_STEP_MS / 1e3} s ("joining your balance's N parts first (about M min)"). Some apps show nothing during this call: BEFORE it, when wallet_balance shows balanceParts above 1, tell the user that sending all of it first joins the parts and takes about the minutes wallet_balance names. Without a progress token it answers within about 20 s with stage joining and when to ask again. The same request_id never sends twice, also after a restart. request_id is optional: without it the answer names the id it made, and the same call again within 10 minutes after it ends reads that same send. When it lands, the wallet writes an encrypted backup (backupPath). x402_pay calls wait while it runs. An optional owner cap (--max-unshield) may bound each call and what this wallet sends in any 24 hours, from every MCP process (mcp_unshield_cap_exceeded: tell the owner). Other refusals: mcp_argument_invalid, mcp_request_id_reused, mcp_unshield_to_invalid, wallet_locked, mcp_tool_disabled, mcp_unshield_failed, and the unshield's own p03_* codes. PRIVATE SEND (optional): when the user says "send it in parts", "spread it out", or "do not send it all at once", pass parts (2-8) WITH max_delay_minutes (1-60; parts with no wait are refused: they would leave together): the amount is split into parts exits of unequal size (each at least 0.01 USDC), each sent after a random wait of up to max_delay_minutes (part 1 too), so the chain shows no single exit of this amount at this time. max_delay_minutes alone delays one exit. ` + SEND_RUNS_ON + ` Late parts never go together. While parts are unsent, x402_pay may spend only the rest of the balance. The plan is saved in the wallet before part 1, so the same request_id (with the same parts and max_delay_minutes) continues after a restart and never sends a part twice; the answer's privateSend lists each part (state, amount, when it may go, transactions), sentAtomic, and remainingAtomic. The user confirms the whole send once. Both left out: one exit, as before. Each part is a full unshield, so it takes as long as one, and the facilitator pays the network fee of each. Privacy limit: every part goes to the SAME \`to\`, so anyone who adds up that address's incoming transfers sees the total; the parts hide the time and the single amount of the exit, not the sum the address received. wallet_cancel with the same request_id stops the parts not sent yet (their value stays in the shielded balance); while a part is being sent, that call waits for the part to finish, up to about ${CALL_BUDGET_MS / 1e3} s.`,
126211
126299
  inputSchema: {
126212
126300
  type: "object",
126213
126301
  properties: {
@@ -126948,12 +127036,12 @@ ${canonical5}`;
126948
127036
  send: async (plan) => {
126949
127037
  const job = { requestId: plan.id, to: plan.to };
126950
127038
  let store;
126951
- const sendPlan = await withLockRetry(() => underLease((opened) => {
127039
+ const sendPlan = await withLockRetry(() => underLease(async (opened) => {
126952
127040
  store = opened;
126953
127041
  const name = privateSendPlanName(plan.sendId);
126954
127042
  if (!opened.readWal(name) && readTransferToken(opened, plan.id)?.state === "cancelled") return null;
126955
127043
  const amount = transferSendAtomic(plan, opened);
126956
- if (!opened.readWal(name)) assertUnshieldNotPromised(opened, amount);
127044
+ if (!opened.readWal(name)) await assertNotPromisedFresh(opened, amount);
126957
127045
  const written = planPrivateSend({
126958
127046
  store: opened,
126959
127047
  sendId: plan.sendId,
@@ -130209,69 +130297,6 @@ var init_stdioServer = __esm({
130209
130297
  }
130210
130298
  });
130211
130299
 
130212
- // wallet/src/net/torMissingMcp.mjs
130213
- var torMissingMcp_exports = {};
130214
- __export(torMissingMcp_exports, {
130215
- RESTART: () => RESTART,
130216
- serveTorMissingMcp: () => serveTorMissingMcp
130217
- });
130218
- async function serveTorMissingMcp({ message, recheck = async () => false, input = process.stdin, output: output2 = process.stdout, log = () => {
130219
- } }) {
130220
- const error = "mcp_tor_unavailable";
130221
- const match = /^([a-z][a-z0-9_]*): ([\s\S]*)$/.exec(String(message));
130222
- const cause = match ? match[1] : "tor_not_running";
130223
- const detail = match ? match[2] : String(message);
130224
- const answer = async () => {
130225
- let ready = false;
130226
- try {
130227
- ready = await recheck() === true;
130228
- } catch {
130229
- ready = false;
130230
- }
130231
- const body = ready ? {
130232
- say: `Tor runs on this machine now, but this wallet started before it did. ${RESTART}.`,
130233
- ok: false,
130234
- error: "mcp_restart_needed",
130235
- next: `Tell the user: ${RESTART}. Nothing was paid.`
130236
- } : {
130237
- say: `The wallet is set to run behind Tor, and Tor is not running on this machine, so nothing was sent and nothing was paid.`,
130238
- ok: false,
130239
- error,
130240
- cause,
130241
- detail,
130242
- next: `Tell the user what to install, with the command for their OS from detail; when Tor runs: ${RESTART}.`
130243
- };
130244
- log(`MCP_TOR_MISSING_ANSWER ${body.error}`);
130245
- return { text: JSON.stringify(body, null, 2), isError: true };
130246
- };
130247
- const hidden = new Set(MCP_HIDDEN_TOOL_NAMES);
130248
- const tools = [...MCP_TOOL_NAMES, "wallet_status"].map((name) => ({
130249
- name,
130250
- title: name,
130251
- description: `Not available yet: Tor is not running on this machine. Call it for the install command (it moves no money).`,
130252
- inputSchema: { type: "object", properties: {}, additionalProperties: true },
130253
- annotations: { readOnlyHint: true },
130254
- hidden: hidden.has(name),
130255
- run: answer
130256
- }));
130257
- const dispatch = createMcpDispatcher({
130258
- tools,
130259
- aliases: MCP_TOOL_ALIASES,
130260
- instructions: `The Shinjuku Shield wallet is set to run behind Tor, and Tor is not running on this machine. Call wallet_balance: its answer has the install command for the user's OS. ${RESTART} after Tor starts.`
130261
- });
130262
- await serveStdio({ input, output: output2, dispatch, log });
130263
- return 1;
130264
- }
130265
- var RESTART;
130266
- var init_torMissingMcp = __esm({
130267
- "wallet/src/net/torMissingMcp.mjs"() {
130268
- "use strict";
130269
- init_stdioServer();
130270
- init_tools();
130271
- RESTART = "Restart this MCP server in your app (or restart the app), then ask again";
130272
- }
130273
- });
130274
-
130275
130300
  // wallet/src/inference/chat.mjs
130276
130301
  var chat_exports = {};
130277
130302
  __export(chat_exports, {
@@ -130281,7 +130306,7 @@ __export(chat_exports, {
130281
130306
  isProgressLine: () => isProgressLine,
130282
130307
  runChat: () => runChat
130283
130308
  });
130284
- import { randomBytes as randomBytes28 } from "node:crypto";
130309
+ import { randomBytes as randomBytes27 } from "node:crypto";
130285
130310
  import { createInterface } from "node:readline";
130286
130311
  function chosenCap(value) {
130287
130312
  const text3 = value === void 0 || value === null ? "" : String(value);
@@ -130561,7 +130586,7 @@ async function runChat({ io, values, deps = {} }) {
130561
130586
  if (state.pending) say6("(The unfinished answer is dropped; this is a new question.)");
130562
130587
  state.pending = void 0;
130563
130588
  await runTurn({
130564
- requestId: `chat-${randomBytes28(8).toString("hex")}`,
130589
+ requestId: `chat-${randomBytes27(8).toString("hex")}`,
130565
130590
  model: state.model,
130566
130591
  messages: [...state.history, { role: "user", content: text3 }],
130567
130592
  capAtomic: void 0
@@ -130612,6 +130637,69 @@ var init_chat = __esm({
130612
130637
  }
130613
130638
  });
130614
130639
 
130640
+ // wallet/src/net/torMissingMcp.mjs
130641
+ var torMissingMcp_exports = {};
130642
+ __export(torMissingMcp_exports, {
130643
+ RESTART: () => RESTART,
130644
+ serveTorMissingMcp: () => serveTorMissingMcp
130645
+ });
130646
+ async function serveTorMissingMcp({ message, recheck = async () => false, input = process.stdin, output: output2 = process.stdout, log = () => {
130647
+ } }) {
130648
+ const error = "mcp_tor_unavailable";
130649
+ const match = /^([a-z][a-z0-9_]*): ([\s\S]*)$/.exec(String(message));
130650
+ const cause = match ? match[1] : "tor_not_running";
130651
+ const detail = match ? match[2] : String(message);
130652
+ const answer = async () => {
130653
+ let ready = false;
130654
+ try {
130655
+ ready = await recheck() === true;
130656
+ } catch {
130657
+ ready = false;
130658
+ }
130659
+ const body = ready ? {
130660
+ say: `Tor runs on this machine now, but this wallet started before it did. ${RESTART}.`,
130661
+ ok: false,
130662
+ error: "mcp_restart_needed",
130663
+ next: `Tell the user: ${RESTART}. Nothing was paid.`
130664
+ } : {
130665
+ say: `The wallet is set to run behind Tor, and Tor is not running on this machine, so nothing was sent and nothing was paid.`,
130666
+ ok: false,
130667
+ error,
130668
+ cause,
130669
+ detail,
130670
+ next: `Tell the user what to install, with the command for their OS from detail; when Tor runs: ${RESTART}.`
130671
+ };
130672
+ log(`MCP_TOR_MISSING_ANSWER ${body.error}`);
130673
+ return { text: JSON.stringify(body, null, 2), isError: true };
130674
+ };
130675
+ const hidden = new Set(MCP_HIDDEN_TOOL_NAMES);
130676
+ const tools = [...MCP_TOOL_NAMES, "wallet_status"].map((name) => ({
130677
+ name,
130678
+ title: name,
130679
+ description: `Not available yet: Tor is not running on this machine. Call it for the install command (it moves no money).`,
130680
+ inputSchema: { type: "object", properties: {}, additionalProperties: true },
130681
+ annotations: { readOnlyHint: true },
130682
+ hidden: hidden.has(name),
130683
+ run: answer
130684
+ }));
130685
+ const dispatch = createMcpDispatcher({
130686
+ tools,
130687
+ aliases: MCP_TOOL_ALIASES,
130688
+ instructions: `The Shinjuku Shield wallet is set to run behind Tor, and Tor is not running on this machine. Call wallet_balance: its answer has the install command for the user's OS. ${RESTART} after Tor starts.`
130689
+ });
130690
+ await serveStdio({ input, output: output2, dispatch, log });
130691
+ return 1;
130692
+ }
130693
+ var RESTART;
130694
+ var init_torMissingMcp = __esm({
130695
+ "wallet/src/net/torMissingMcp.mjs"() {
130696
+ "use strict";
130697
+ init_stdioServer();
130698
+ init_tools();
130699
+ RESTART = "Restart this MCP server in your app (or restart the app), then ask again";
130700
+ }
130701
+ });
130702
+
130615
130703
  // packages/ctvault-v0b/node_modules/@solana/errors/dist/index.node.mjs
130616
130704
  function encodeValue16(value) {
130617
130705
  if (Array.isArray(value)) {
@@ -154551,9 +154639,10 @@ init_fs();
154551
154639
  init_balance();
154552
154640
  init_deposit();
154553
154641
  import { spawn as spawn8 } from "node:child_process";
154554
- import { randomBytes as randomBytes27, randomInt as randomInt9 } from "node:crypto";
154642
+ import { randomBytes as randomBytes28, randomInt as randomInt9 } from "node:crypto";
154555
154643
  import { tmpdir as tmpdir4 } from "node:os";
154556
154644
  import { join as join36, resolve as resolve32 } from "node:path";
154645
+ init_chat();
154557
154646
  init_proxy();
154558
154647
  init_rpcDefault();
154559
154648
  init_usdcWords();
@@ -154812,8 +154901,12 @@ function fundsSummary(view2) {
154812
154901
  `Next: ${view2.next}.`
154813
154902
  ].join("\n");
154814
154903
  }
154815
- function printEnd(deps, view2) {
154816
- deps.io.out(deps.stdoutIsTTY?.() === true ? `${fundsSummary(view2)}
154904
+ function fundsOutput(values, deps) {
154905
+ const terminal = deps.stdoutIsTTY?.() === true && !values.has("--json");
154906
+ return { terminal, quiet: terminal && !values.has("--verbose") };
154907
+ }
154908
+ function printEnd(deps, view2, terminal) {
154909
+ deps.io.out(terminal ? `${fundsSummary(view2)}
154817
154910
  ` : `${JSON.stringify(view2, null, 2)}
154818
154911
  `);
154819
154912
  }
@@ -154829,14 +154922,14 @@ function fundsStatusSummary(view2) {
154829
154922
  `Privacy: ${view2.privacy}.`,
154830
154923
  ...view2.otherRuns?.length ? [`Other add-funds runs of this wallet: ${view2.otherRuns.join(", ")} (add --funds-id <id> to see one).`] : [],
154831
154924
  next,
154832
- "For the whole status as JSON, send this command's output to a file or a program."
154925
+ "For the whole status as JSON, add --json (or send this command's output to a file or a program)."
154833
154926
  ].join("\n");
154834
154927
  }
154835
- var HELD = /^(shinjuku(?:-wallet)?\b.*\bfailed: |hint: |Help: |next: |Examples?:|Every flag: | shinjuku(?:-wallet)? )/;
154928
+ var HELD = /^(shinjuku(?:-wallet)?\b.*\bfailed: |shinjuku(?:-wallet)?\b.*: waiting for funds, nothing failed \(|hint: |Help: |next: |Examples?:|Every flag: | shinjuku(?:-wallet)? )/;
154836
154929
  var STEP_TOR_LINE = /^(?:tor|proxy): /;
154837
154930
  async function runStep(ctx, argv2) {
154838
154931
  if (ctx.signal.aborted) return { exit: 1, held: [], codes: [], details: {}, aborted: true };
154839
- const file = join36(ctx.tempDir, `step-${randomBytes27(8).toString("hex")}.json`);
154932
+ const file = join36(ctx.tempDir, `step-${randomBytes28(8).toString("hex")}.json`);
154840
154933
  const held = [];
154841
154934
  let last;
154842
154935
  const proc = spawn8(process.execPath, [...process.execArgv, ctx.entry, ...ctx.proxyArgs, ...argv2], {
@@ -154853,6 +154946,7 @@ async function runStep(ctx, argv2) {
154853
154946
  }
154854
154947
  if (STEP_TOR_LINE.test(text3) && !TOR_TROUBLE_LINE.test(text3) && !/\bWARNING\b/.test(text3)) return;
154855
154948
  if (text3.trim()) last = text3;
154949
+ if (ctx.quiet && isProgressLine(text3)) return;
154856
154950
  if (!text3.trim() || ctx.shown.has(text3)) return;
154857
154951
  ctx.shown.add(text3);
154858
154952
  ctx.io.err(` ${text3}
@@ -155234,6 +155328,7 @@ var STAGE_START = Object.freeze({
155234
155328
  pocket: (plan) => `fills ${plan.pockets} ready pocket${plan.pockets === 1 ? "" : "s"} of ${usdc5(plan.pocketSizeAtomic)} USDC for sellers that take standard exact only (pocket fill); a payment from a pocket takes seconds`,
155235
155329
  balance: () => "reads the new balance from the chain (balance --refresh)"
155236
155330
  });
155331
+ var STEP_WORDS = / \((?:deposit --relay|balance --refresh|pocket fill|privacy-cash (?:deposit|withdraw))[^)]*\)/g;
155237
155332
  function readPrivateLine(path, flag3) {
155238
155333
  try {
155239
155334
  const text3 = readSecretText(path, flag3);
@@ -155438,7 +155533,7 @@ async function addFunds(values, deps) {
155438
155533
  }
155439
155534
  const tempDir = mkdtempSync(join36(tmpdir4(), "shinjuku-funds-"));
155440
155535
  const abort = new AbortController();
155441
- const ctx = { io, store, values: run5, tempDir, now: deps.now, signal: abort.signal, shown: /* @__PURE__ */ new Set(), transient: 0, entry: process.argv[1] };
155536
+ const ctx = { io, store, values: run5, tempDir, now: deps.now, signal: abort.signal, shown: /* @__PURE__ */ new Set(), transient: 0, entry: process.argv[1], ...fundsOutput(values, deps) };
155442
155537
  const onSignal = (signal) => {
155443
155538
  if (abort.signal.aborted) return;
155444
155539
  abort.abort();
@@ -155452,7 +155547,7 @@ async function addFunds(values, deps) {
155452
155547
  if (resolved.shown) {
155453
155548
  io.err(`add-funds ${plan.fundsId} is ready already; nothing new was started (a new top-up needs a new --funds-id)
155454
155549
  `);
155455
- printEnd(deps, fundsView(store, plan, { now: deps.now() }));
155550
+ printEnd(deps, fundsView(store, plan, { now: deps.now() }), ctx.terminal);
155456
155551
  return;
155457
155552
  }
155458
155553
  const save2 = () => store.writeWal(planName(plan.fundsId), plan);
@@ -155463,7 +155558,7 @@ async function addFunds(values, deps) {
155463
155558
  plan,
155464
155559
  proxyArgs: proxy.args,
155465
155560
  childEnv: { ...proxy.env, [PASSPHRASE_ENV]: passphrase2, [RPC_ENV2]: rpcUrl },
155466
- say: (stage, text3) => io.err(`add-funds ${plan.fundsId} [${stage}] ${text3}
155561
+ say: (stage, text3) => io.err(`add-funds ${plan.fundsId} [${stage}] ${ctx.quiet ? text3.replace(STEP_WORDS, "") : text3}
155467
155562
  `),
155468
155563
  mark: (stage) => {
155469
155564
  if (plan.done?.[stage]) return;
@@ -155495,8 +155590,8 @@ async function addFunds(values, deps) {
155495
155590
  plan.status = { stage: "ready", at: iso2(deps.now()) };
155496
155591
  save2();
155497
155592
  const view3 = fundsView(openStore(), plan, { now: deps.now() });
155498
- ctx.say("ready", view3.status);
155499
- printEnd(deps, view3);
155593
+ if (!ctx.terminal) ctx.say("ready", view3.status);
155594
+ printEnd(deps, view3, ctx.terminal);
155500
155595
  return;
155501
155596
  }
155502
155597
  if (stage !== lastStage) {
@@ -155532,7 +155627,7 @@ async function addFunds(values, deps) {
155532
155627
  if (values.has("--once")) {
155533
155628
  plan.status = { ...plan.status, ended: iso2(deps.now()) };
155534
155629
  save2();
155535
- printEnd(deps, fundsView(store, plan, { now: deps.now(), running: false }));
155630
+ printEnd(deps, fundsView(store, plan, { now: deps.now(), running: false }), ctx.terminal);
155536
155631
  return;
155537
155632
  }
155538
155633
  await sleep3(outcome.ms, abort.signal);
@@ -155542,7 +155637,7 @@ async function addFunds(values, deps) {
155542
155637
  const view2 = fundsView(store, plan, { now: deps.now(), running: false });
155543
155638
  io.err(`add-funds ${plan.fundsId} stopped at ${view2.stage}: nothing is lost. Rerun this same command to continue
155544
155639
  `);
155545
- printEnd(deps, view2);
155640
+ printEnd(deps, view2, ctx.terminal);
155546
155641
  } finally {
155547
155642
  process.off("SIGINT", onSignal);
155548
155643
  process.off("SIGTERM", onSignal);
@@ -155559,7 +155654,7 @@ async function fundsStatus(values, deps) {
155559
155654
  const plans = fundsPlans(store);
155560
155655
  const wanted = values.get("--funds-id");
155561
155656
  const plan = wanted === void 0 ? plans.at(-1) : plans.find((entry) => entry.fundsId === wanted);
155562
- const terminal = deps.stdoutIsTTY?.() === true;
155657
+ const { terminal } = fundsOutput(values, deps);
155563
155658
  if (!plan) {
155564
155659
  const none3 = {
155565
155660
  status: wanted === void 0 ? "this wallet has no add-funds run yet" : "this wallet has no add-funds run with that --funds-id",
@@ -157834,7 +157929,9 @@ var COMMANDS = [
157834
157929
  { ...TREE_RPC_FLAG, about: `--route privacy-cash: ${TREE_RPC_FLAG.about}` },
157835
157930
  flag2("--funds-id", "<id>", "the name of this funding run; default: the unfinished one, else a new funds-<n>. A new top-up after one is ready needs a new name"),
157836
157931
  flag2("--split", "<k|auto>", "default 1 (one deposit, one transfer). 1 to 8: k equal deposits of --amount/k (whole USDC each), each from its own one-time address, so k payments run in a row without waiting for a pool anchor. auto: up to 4 deposits, 1 USDC each and the rest in the last (5 USDC: 1, 1, 1 and 2 USDC). Exchange and changenow routes"),
157837
- bool2("--once", "run what can run now, print the status, and exit instead of waiting (for your own scheduler)")
157932
+ bool2("--once", "run what can run now, print the status, and exit instead of waiting (for your own scheduler)"),
157933
+ bool2("--json", "end with the status as JSON in a terminal too (a file or a program gets it without this flag)"),
157934
+ bool2("--verbose", "also show the wallet's own progress lines in a terminal (Tor, the RPC relay, the pool history, the memo reads) and the command each step runs")
157838
157935
  ],
157839
157936
  examples: [
157840
157937
  `${CLI2} add-funds --route exchange --amount 2 ${FUNDS_BASE}`,
@@ -157856,7 +157953,7 @@ var COMMANDS = [
157856
157953
  "It changes no rule: the crowd hold, the dwell, whole-USDC deposits, and every refusal are the single commands' own. A refusal that a later run clears (the hold, the crowd count, the dwell, a paused facilitator) is a wait; any other refusal stops it with that command's own line and hint (exit 1), and the rerun continues after your fix. It has no override flag: run the single command (for example privacy-cash withdraw --hold-override) when you choose to give up a wait.",
157857
157954
  "--tor (or --proxy): each step runs as its own Tor run, as if you typed it: a new circuit and its own Tor check. With --tor, --service may be the facilitator's http://<v3 onion>/ origin.",
157858
157955
  `--history-rpc: the balance --refresh and pocket fill steps (and the dwell check) read the pool's public history from it (${CLI2} help balance says what it sees); the deposit and Privacy Cash steps never get it. Without it, those two steps read from the history proxy this wallet pins for the pool (its onion with --tor, else https on the facilitator's pay host; --history-rpc own keeps them on your own RPC). add-funds does not keep it: give it again on each rerun. It never falls back to your own RPC: a failed source fails the step with history_rpc_failed, and add-funds handles that as it handles the step's other refusals.`,
157859
- "In a terminal it ends with a short summary (ready with your balance, or where it stopped and what to do); when its output goes to a file or a program, it ends with the status as JSON, as funds-status prints it. While it waits for the USDC it checks the one-time address every 20 s for the first 5 minutes, then every 2 minutes.",
157956
+ "In a terminal it shows one line per step and ends with a short summary (ready with your balance, or where it stopped and what to do); the wallet's own progress lines (Tor, the RPC relay, the pool history, the memo reads) show only with --verbose, and every warning or failure shows always. With --json, or when its output goes to a file or a program, it ends with the status as JSON, as funds-status prints it, and shows every progress line. While it waits for the USDC it checks the one-time address every 20 s for the first 5 minutes, then every 2 minutes.",
157860
157957
  '--once: run what can run now, print the status (the summary in a terminal; JSON for your scheduler, with "ready": false while it waits), and exit 0; run it again from your own scheduler.',
157861
157958
  "When it is ready, the same command again only shows its status; a new top-up needs a new --funds-id.",
157862
157959
  "One deposit by default: one transfer, one token-account rent for the sender. A payment spends one funded account, and its change (a pocket fill's too) can pay only after the next pool anchor (up to 5 minutes; about 30 s on some pools), so a second shielded spend before that anchor waits up to 5 minutes. The wallet sets money aside for payments in the background, where that wait costs you nothing, so one deposit is enough for most agents. --split auto (exchange and changenow) is for an agent that makes several shielded spends at once: up to 4 deposits, 1 USDC each and the rest in the last one (2 USDC: 1 and 1; 3 USDC: 1, 1 and 1; 5 USDC: 1, 1, 1 and 2; 20 USDC: 1, 1, 1 and 17). Each funded account pays one payment or one 1 USDC pocket fill per pool anchor without waiting, and a payment pays from the smallest account that covers it, so the large one stays for a payment above 1 USDC. With 5 USDC and --pockets 1: the pocket fill takes one 1 USDC account, and 3 more spends run before the next anchor without waiting (one deposit: the next spend after the fill waits up to 5 minutes for the anchor). What it costs: each deposit is its own one-time address, and the sender pays each address's USDC token account (about 0.0015 SOL each, so 4 deposits cost about 0.006 SOL), and you send 4 transfers instead of 1. --split <k> (2 to 8): k equal deposits of --amount/k, so the wallet holds k funded accounts. --amount must split into whole USDC (--amount 2 --split 2: two deposits of 1 USDC). add-funds records all k one-time addresses first, then prints them: send exactly --amount/k to EACH one, as k separate withdrawals (or k ChangeNOW exchanges); each address takes one deposit. Each deposit is its own record (deposit-<funds-id>, deposit-<funds-id>-part2, ...), so a rerun continues every part; it is ready when all k landed. What it shows: k public deposit amounts instead of one, read and landing close together, so they can show one depositor (about what one deposit of the total shows); the facilitator pays the fee and rent of each. --split (auto parts follow a new --amount) may change until any address is funded, never below the addresses already recorded. Not with --route privacy-cash. balance shows how many payments can run without waiting."
@@ -157864,6 +157961,8 @@ var COMMANDS = [
157864
157961
  next: `see where it is from any terminal: ${CLI2} funds-status --pool ${GUIDE.pool} --passphrase-file passphrase.txt`,
157865
157962
  nextInOutput: true,
157866
157963
  validate: (values) => validateAddFunds(values),
157964
+ // In a terminal without --json or --verbose: its own progress lines stay off the screen (funds/addFunds.mjs fundsOutput).
157965
+ quietProgress: (values, deps) => fundsOutput(values, deps).quiet,
157867
157966
  run: (args, values, deps, io) => deps.fundsMain("add-funds", values, { ...deps, io })
157868
157967
  },
157869
157968
  {
@@ -157873,12 +157972,13 @@ var COMMANDS = [
157873
157972
  flags: [
157874
157973
  flag2("--pool", "<address>", "the shielded pool", { required: true, check: "pubkey" }),
157875
157974
  flag2("--passphrase-file", "<file>", `the private file that holds the wallet passphrase (the one add-funds uses); or set ${PASSPHRASE_ENV2}`),
157876
- flag2("--funds-id", "<id>", "one add-funds run; default: the newest")
157975
+ flag2("--funds-id", "<id>", "one add-funds run; default: the newest"),
157976
+ bool2("--json", "print the status as JSON in a terminal too (a file or a program gets it without this flag)")
157877
157977
  ],
157878
157978
  examples: [`${CLI2} funds-status --pool ${GUIDE.pool} --passphrase-file passphrase.txt`],
157879
157979
  notes: [
157880
157980
  "It reads local files only (the wallet log and the add-funds lock) and makes no network call, so you can run it at any time: while add-funds runs, or after it stopped.",
157881
- "In a terminal it prints a short summary in plain words: the status, where to send the USDC while add-funds waits for it, the privacy cost, and what to do next. When its output goes to a file or a program, it prints the status as JSON, with the fields below.",
157981
+ "In a terminal it prints a short summary in plain words: the status, where to send the USDC while add-funds waits for it, the privacy cost, and what to do next. With --json, or when its output goes to a file or a program, it prints the status as JSON, with the fields below.",
157882
157982
  'In the JSON, status is one line: the stage and what it waits for (next try at), "not running" when no add-funds runs for this wallet, "stopped" with what needs you, or "ready" with your balance; it ends with what the route shows, and to whom (privacy). running is true while an add-funds run holds this wallet. stages lists every stage: done (with its time), waiting, next, not running, stopped, or later.',
157883
157983
  "oneTimeAddress and send show while the deposit waits for USDC: on the exchange and changenow routes, send to it once, and only the amount shown. leftoverAtOneTimeAddress shows USDC beyond the amount that stays at that address, and what taking it back shows. With --split (k or auto): split and parts show each deposit (its amount, state, one-time address while it waits for USDC, and signature once it landed); send goes to every address listed, each with its own amount.",
157884
157984
  "Without --funds-id it shows the newest run; otherRuns names the others."
@@ -158391,7 +158491,7 @@ var COMMANDS = [
158391
158491
  // The same for its RPC: only the Tor run resolves it, so the relay line shows once, not once per process.
158392
158492
  rpcWhen: (values, env) => inProxiedRun(env),
158393
158493
  // The Tor run, the one that pays: its progress lines show only with --verbose (inference/chat.mjs hideProgressLines).
158394
- quietProgress: (values, env) => inProxiedRun(env) && !values.has("--verbose"),
158494
+ quietProgress: (values, deps) => inProxiedRun(deps.env) && !values.has("--verbose"),
158395
158495
  run: (args, values, deps, io) => deps.chatMain(values, { ...deps, io })
158396
158496
  },
158397
158497
  {
@@ -160768,7 +160868,7 @@ This copy: ${walletVersionLine(deps.walletVersion())}
160768
160868
  const commandArgs = defaults.args;
160769
160869
  const runDeps = defaults.passphrase === void 0 ? deps : { ...deps, env: { ...deps.env, [PASSPHRASE_ENV2]: defaults.passphrase } };
160770
160870
  values = parseFlags(command2, commandArgs);
160771
- if (command2.quietProgress?.(values, runDeps.env) === true) showProgressAgain = hideProgressLines();
160871
+ if (command2.quietProgress?.(values, runDeps) === true) showProgressAgain = hideProgressLines();
160772
160872
  const skip = command2.beforeValidate?.(values, runDeps, typedArgs, usdc9.oldForm) === SKIP_VALIDATE;
160773
160873
  if (!skip) validate3(command2, values, runDeps.env);
160774
160874
  retiredPoolGuard(command2, values, io, deps.walletVersion().npm);