shinjuku-shield 0.3.83 → 0.3.84

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.83. The npm packages `shinjuku-shield` and
9
+ This is version 0.3.84. 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.83 setup
15
+ npx -y --no-audit --no-fund shinjuku-shield@0.3.84 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.83
27
+ npm install -g shinjuku@0.3.84
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.83 mcp
39
+ claude mcp add --scope user shinjuku-shielded -- npx -y --no-audit --no-fund shinjuku-shield@0.3.84 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.83", "mcp"] } } }
45
+ { "mcpServers": { "shinjuku-shielded": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.84", "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": "a59aa27b98cd1822c4651ed0567cd35b3f03e087818d8e5cfdb71e6320b7ffae",
5
- "bytes": 12484127,
6
- "sourceCommit": "ad531c40fe817d116a109d33472895bbbd854158",
4
+ "sha256": "b73f57e58ae0466a090f5b1728e7852bab48e7d28ad6bb0f0ba9657b6cc681b1",
5
+ "bytes": 12505075,
6
+ "sourceCommit": "52a8e17d03018f2cc026f9f9d306a785ef4da9f5",
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-23T18:18:57.601Z"
1285
+ "publishedAt": "2026-10-23T19:32:52.495Z"
1286
1286
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "shinjuku-shield",
3
- "version": "0.3.83",
3
+ "version": "0.3.84",
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": "a59aa27b",
7
- "shinjukuSha256": "a59aa27b98cd1822c4651ed0567cd35b3f03e087818d8e5cfdb71e6320b7ffae",
6
+ "shinjukuRelease": "b73f57e5",
7
+ "shinjukuSha256": "b73f57e58ae0466a090f5b1728e7852bab48e7d28ad6bb0f0ba9657b6cc681b1",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-mcp#readme",
@@ -30309,7 +30309,7 @@ Message: ${transactionMessage2}.
30309
30309
  await this._unsubscribeClientSubscription(clientSubscriptionId, "root change");
30310
30310
  }
30311
30311
  };
30312
- var Keypair15 = class _Keypair {
30312
+ var Keypair16 = class _Keypair {
30313
30313
  /**
30314
30314
  * Create a new keypair instance.
30315
30315
  * Generate random keypair if no {@link Ed25519Keypair} is provided.
@@ -30853,7 +30853,7 @@ Message: ${transactionMessage2}.
30853
30853
  } = params;
30854
30854
  assert(privateKey.length === PRIVATE_KEY_BYTES$1, `Private key must be ${PRIVATE_KEY_BYTES$1} bytes but received ${privateKey.length} bytes`);
30855
30855
  try {
30856
- const keypair = Keypair15.fromSecretKey(privateKey);
30856
+ const keypair = Keypair16.fromSecretKey(privateKey);
30857
30857
  const publicKey4 = keypair.publicKey.toBytes();
30858
30858
  const signature3 = sign3(message, keypair.secretKey);
30859
30859
  return this.createInstructionWithPublicKey({
@@ -32293,7 +32293,7 @@ Message: ${transactionMessage2}.
32293
32293
  exports.Enum = Enum;
32294
32294
  exports.EpochSchedule = EpochSchedule;
32295
32295
  exports.FeeCalculatorLayout = FeeCalculatorLayout;
32296
- exports.Keypair = Keypair15;
32296
+ exports.Keypair = Keypair16;
32297
32297
  exports.LAMPORTS_PER_SOL = LAMPORTS_PER_SOL;
32298
32298
  exports.LOOKUP_TABLE_INSTRUCTION_LAYOUTS = LOOKUP_TABLE_INSTRUCTION_LAYOUTS;
32299
32299
  exports.Loader = Loader;
@@ -42600,6 +42600,16 @@ var init_rpcDefault = __esm({
42600
42600
  });
42601
42601
 
42602
42602
  // wallet/src/net/facilitatorOnion.mjs
42603
+ function afterOnionRace(url, signal) {
42604
+ let origin;
42605
+ try {
42606
+ origin = new URL(url instanceof Request ? url.url : String(url)).origin;
42607
+ } catch {
42608
+ return void 0;
42609
+ }
42610
+ const group = routedGroupOf.get(origin);
42611
+ return group !== void 0 && !group.winner && !group.waitMs && group.pending ? untilAborted(group.pending, signal) : void 0;
42612
+ }
42603
42613
  function takeSiteOnionRaces() {
42604
42614
  if (siteRacesTaken) return Promise.resolve([]);
42605
42615
  siteRacesTaken = true;
@@ -42803,7 +42813,10 @@ async function routeFacilitatorsOverOnion(argv2, {
42803
42813
  }
42804
42814
  if (routes.size === 0) return { routes: /* @__PURE__ */ new Map(), stop: () => {
42805
42815
  } };
42806
- for (const [origin, group] of routes) routedOrigins.set(origin, group.onions);
42816
+ for (const [origin, group] of routes) {
42817
+ routedOrigins.set(origin, group.onions);
42818
+ routedGroupOf.set(origin, group);
42819
+ }
42807
42820
  const aborts = [];
42808
42821
  let stopped = false;
42809
42822
  const race = (group, onions, options) => {
@@ -42958,7 +42971,7 @@ async function routeFacilitatorsOverOnion(argv2, {
42958
42971
  }
42959
42972
  };
42960
42973
  }
42961
- var SITE_ONIONS, SITE_SPARE_ONIONS, PINNED_FACILITATOR_ONIONS, PINNED_SPARE_ONIONS, HISTORY_ONIONS, PINNED_HISTORY_ONIONS, HISTORY_PROBE_PATH, FACILITATOR_FLAGS, V3_ONION, DISCOVERY_TIMEOUT_MS, WARM_TIMEOUT_MS, RACE_TIMEOUT_MS, ROUTE_WAIT_MS, SPARE_AFTER_MS, SPARE_CAUSE, routedGroups, liveOnions, hostOf, noAnswer, routedOnions, routedOrigins, onionsOfOrigin, routesOverOnion, firstRaces, siteRaces, siteRacesTaken, firstOnionRace, GLOBAL_DISPATCHER, onionDispatchers;
42974
+ var SITE_ONIONS, SITE_SPARE_ONIONS, PINNED_FACILITATOR_ONIONS, PINNED_SPARE_ONIONS, HISTORY_ONIONS, PINNED_HISTORY_ONIONS, HISTORY_PROBE_PATH, FACILITATOR_FLAGS, V3_ONION, DISCOVERY_TIMEOUT_MS, WARM_TIMEOUT_MS, RACE_TIMEOUT_MS, ROUTE_WAIT_MS, SPARE_AFTER_MS, SPARE_CAUSE, routedGroups, liveOnions, hostOf, noAnswer, routedOnions, routedOrigins, onionsOfOrigin, routesOverOnion, routedGroupOf, firstRaces, siteRaces, siteRacesTaken, firstOnionRace, GLOBAL_DISPATCHER, onionDispatchers;
42962
42975
  var init_facilitatorOnion = __esm({
42963
42976
  "wallet/src/net/facilitatorOnion.mjs"() {
42964
42977
  "use strict";
@@ -43008,6 +43021,7 @@ var init_facilitatorOnion = __esm({
43008
43021
  routedOrigins = /* @__PURE__ */ new Map();
43009
43022
  onionsOfOrigin = (origin) => routedOrigins.get(origin) ?? PINNED_FACILITATOR_ONIONS[origin] ?? [];
43010
43023
  routesOverOnion = (origin) => routedOrigins.has(origin);
43024
+ routedGroupOf = /* @__PURE__ */ new Map();
43011
43025
  firstRaces = /* @__PURE__ */ new Map();
43012
43026
  siteRaces = [];
43013
43027
  siteRacesTaken = false;
@@ -47215,7 +47229,7 @@ function reexec(args, mode, plainProxy) {
47215
47229
  const quiet = quietRun(args);
47216
47230
  if (quiet) env[QUIET_RUN_MARK] = quiet;
47217
47231
  return new Promise((resolvePromise) => {
47218
- const child = spawn(process.execPath, [...process.execArgv, process.argv[1], ...args], { env, stdio: "inherit" });
47232
+ const child = spawn(process.execPath, [...proxiedExecArgv(process.execArgv), process.argv[1], ...args], { env, stdio: "inherit" });
47219
47233
  const ignore = () => {
47220
47234
  };
47221
47235
  const forward = () => child.kill("SIGTERM");
@@ -47245,7 +47259,7 @@ async function proxyEntry(argv2, run5, { err = (text3) => process.stderr.write(t
47245
47259
  try {
47246
47260
  await proveProxy(mode, proxy);
47247
47261
  } catch (error) {
47248
- err(`shinjuku: ${error.message}
47262
+ err(`shinjuku: ${error.message}${mode === "tor" && process.env[TOR_FROM_CONFIG_ENV] === "1" ? TOR_FROM_CONFIG_HINT : ""}
47249
47263
  `);
47250
47264
  if (mcpRefused2 && argv2[0] === "mcp" && !process.stdin.isTTY && !process.env[TOR_RUN_STARTED_ENV]) {
47251
47265
  return mcpRefused2({
@@ -47291,7 +47305,7 @@ async function proxyEntry(argv2, run5, { err = (text3) => process.stderr.write(t
47291
47305
  }
47292
47306
  return reexec(options.args, options.mode, options.proxy);
47293
47307
  }
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;
47308
+ 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, PROXY_WARNING_FLAG, proxiedExecArgv, TOR_FROM_CONFIG_ENV, TOR_FROM_CONFIG_HINT;
47295
47309
  var init_proxy = __esm({
47296
47310
  "wallet/src/net/proxy.mjs"() {
47297
47311
  "use strict";
@@ -47386,6 +47400,10 @@ var init_proxy = __esm({
47386
47400
  torCircuit = () => torCircuitId;
47387
47401
  GLOBAL_DISPATCHER2 = /* @__PURE__ */ Symbol.for("undici.globalDispatcher.1");
47388
47402
  laneDispatchers = /* @__PURE__ */ new Map();
47403
+ PROXY_WARNING_FLAG = "--disable-warning=UNDICI-EHPA";
47404
+ proxiedExecArgv = (execArgv = process.execArgv) => execArgv.includes(PROXY_WARNING_FLAG) ? [...execArgv] : [...execArgv, PROXY_WARNING_FLAG];
47405
+ TOR_FROM_CONFIG_ENV = "SHINJUKU_WALLET_TOR_FROM_CONFIG";
47406
+ TOR_FROM_CONFIG_HINT = '. Tor is on for every command because setup recorded it (config.json "tor": true); to run this one command without Tor, add --no-tor (the servers then see your IP address)';
47389
47407
  }
47390
47408
  });
47391
47409
 
@@ -50760,6 +50778,7 @@ function poolLayoutForLength(length) {
50760
50778
  function connect2(rpcUrl, commitment = "finalized", { timeoutMs, wrapFetch = (fetch2) => fetch2 } = {}) {
50761
50779
  const pooled = (input, init) => rpcFetch(input, rpcKeepAliveInit(input, init));
50762
50780
  const fetch2 = wrapFetch(timeoutMs === void 0 ? pooled : async (input, { onResponseHead, ...init } = {}) => {
50781
+ await afterOnionRace(input, init?.signal);
50763
50782
  const circuit = torCircuit();
50764
50783
  const timeout = AbortSignal.timeout(timeoutMs);
50765
50784
  try {
@@ -51744,6 +51763,7 @@ var init_chain2 = __esm({
51744
51763
  import_web395 = __toESM(require_index_cjs(), 1);
51745
51764
  init_esm2();
51746
51765
  init_proxy();
51766
+ init_facilitatorOnion();
51747
51767
  init_keepAlive();
51748
51768
  init_compressedReplay();
51749
51769
  init_reference2();
@@ -51841,6 +51861,7 @@ var init_chain2 = __esm({
51841
51861
  historyRequestsSent = 0;
51842
51862
  historySourceState = () => ({ silentSince: historySilentSince, sent: historyRequestsSent });
51843
51863
  trackHistoryAnswers = (fetch2) => async (input, init) => {
51864
+ await afterOnionRace(input, init?.signal);
51844
51865
  historyRequestsSent += 1;
51845
51866
  historySilentSince ??= Date.now();
51846
51867
  const response = await fetch2(input, init);
@@ -64207,6 +64228,8 @@ async function decryptKept(decrypt, items, eta, chainProgramId, pool2) {
64207
64228
  const program = chainProgramId.toBase58();
64208
64229
  const poolKey = pool2.toBase58();
64209
64230
  const keys3 = items.map(({ digest: digest2, context }) => decryptedKey(eta, program, poolKey, digest2, context));
64231
+ if (RECOVERY_TOOK.size >= DECRYPTED_MAX) RECOVERY_TOOK.clear();
64232
+ for (const key of keys3) RECOVERY_TOOK.add(key);
64210
64233
  const outputs = keys3.map((key) => {
64211
64234
  const kept = DECRYPTED.get(key);
64212
64235
  return kept === void 0 ? void 0 : copyOutputs(kept);
@@ -64435,6 +64458,7 @@ function prefetchRecoveryMemos({ store, finalizedHistory: finalizedHistory2, ser
64435
64458
  }
64436
64459
  async function decryptAhead({ store, contexts, reading, chainProgramId, pool: pool2 }) {
64437
64460
  const started = [];
64461
+ memoReadsAhead += 1;
64438
64462
  try {
64439
64463
  const answers = await reading;
64440
64464
  const eta = deriveWalletViewingAuthority2({ seedBytes: seedBytes3(store.state), generator: pinnedGenerator2() }).eta;
@@ -64483,9 +64507,33 @@ async function decryptAhead({ store, contexts, reading, chainProgramId, pool: po
64483
64507
  DECRYPTED.set(keys3[index], copyOutputs(outputs[at]));
64484
64508
  });
64485
64509
  markPhase("memos-decrypted-ahead");
64510
+ await new Promise((resolve40) => {
64511
+ setImmediate(resolve40);
64512
+ });
64513
+ const keep = open4.flatMap((index, at) => RECOVERY_TOOK.has(keys3[index]) ? [] : [[index, at]]);
64514
+ if (keep.length === 0) return;
64515
+ const cache = openRecoveryCache(store, { programId: program, pool: poolKey });
64516
+ if (!cache) return;
64517
+ let kept = 0;
64518
+ for (const [index, at] of keep) {
64519
+ const { context } = items[index];
64520
+ try {
64521
+ assertFinalizedRecoveryContext(context, context.memoDigest, chainProgramId, pool2);
64522
+ } catch {
64523
+ continue;
64524
+ }
64525
+ const digest2 = hexOf2(context.memoDigest);
64526
+ if (cache.lookup(digest2, context)) continue;
64527
+ cache.remember(digest2, context, copyOutputs(outputs[at]));
64528
+ kept += 1;
64529
+ }
64530
+ if (kept === 0) return;
64531
+ cache.save();
64532
+ markPhase("memos-kept-ahead", `n${kept}`);
64486
64533
  } catch {
64487
64534
  } finally {
64488
64535
  for (const [key, one] of started) if (DECRYPTING.get(key) === one) DECRYPTING.delete(key);
64536
+ memoReadsAhead -= 1;
64489
64537
  }
64490
64538
  }
64491
64539
  function successorHistoriesFor(finalizedHistory2, given, chainProgramId) {
@@ -64625,7 +64673,7 @@ async function runRecover({ store, connection, programId, digests, serviceUrl: s
64625
64673
  ...Array.isArray(digests) && digests.length > 0 ? { narrowedByDigests: true } : {}
64626
64674
  };
64627
64675
  }
64628
- var import_web3102, FIELD_MODULUS6, MODEL_DIR, seedBytes3, committedGenerator, REPLAY_VALUES, REPLAY_VALUES_MAX, DECRYPTED, DECRYPTED_MAX, DECRYPTING, copyOutputs, hexOf2, beforeMemoStart, decryptableContext;
64676
+ var import_web3102, FIELD_MODULUS6, MODEL_DIR, seedBytes3, committedGenerator, REPLAY_VALUES, REPLAY_VALUES_MAX, DECRYPTED, DECRYPTED_MAX, DECRYPTING, RECOVERY_TOOK, memoReadsAhead, memoReadsAheadRunning, copyOutputs, hexOf2, beforeMemoStart, decryptableContext;
64629
64677
  var init_recover = __esm({
64630
64678
  "wallet/src/recovery/recover.mjs"() {
64631
64679
  "use strict";
@@ -64650,6 +64698,9 @@ var init_recover = __esm({
64650
64698
  DECRYPTED = /* @__PURE__ */ new Map();
64651
64699
  DECRYPTED_MAX = 5e4;
64652
64700
  DECRYPTING = /* @__PURE__ */ new Map();
64701
+ RECOVERY_TOOK = /* @__PURE__ */ new Set();
64702
+ memoReadsAhead = 0;
64703
+ memoReadsAheadRunning = () => memoReadsAhead;
64653
64704
  copyOutputs = (outputs) => outputs.map((output2) => ({
64654
64705
  ...output2,
64655
64706
  ...output2.account ? { account: { ...output2.account } } : {},
@@ -107698,10 +107749,10 @@ function facilitatorExitRelay(origin, { fetchImpl = fetch, pause: pause3 = (ms)
107698
107749
  }
107699
107750
  };
107700
107751
  }
107701
- async function main3(args = process.argv.slice(2)) {
107752
+ async function main3(args = process.argv.slice(2), { output: output2 = (result) => console.log(JSON.stringify(result)) } = {}) {
107702
107753
  if (args.length === 1 && args[0] === "--help") {
107703
107754
  console.log(USAGE2);
107704
- return;
107755
+ return void 0;
107705
107756
  }
107706
107757
  const options = {};
107707
107758
  for (let index = 0; index < args.length; index += 2) {
@@ -107749,8 +107800,9 @@ async function main3(args = process.argv.slice(2)) {
107749
107800
  if (!store2.readWal(`self-pay-${options["unshield-id"]}`)) assertUnshieldNotPromised(store2, options.amount);
107750
107801
  return unshieldOnce({ ...context, store: store2, unshieldId: options["unshield-id"], amountAtomic: options.amount });
107751
107802
  });
107752
- console.log(JSON.stringify({ success: true, ...result }));
107753
- return;
107803
+ const answer2 = { success: true, ...result };
107804
+ output2(answer2);
107805
+ return answer2;
107754
107806
  }
107755
107807
  const partId = (index) => {
107756
107808
  const id = `${options["unshield-id"]}-p${index}`;
@@ -107798,7 +107850,9 @@ async function main3(args = process.argv.slice(2)) {
107798
107850
  return finishPrivateSend(opened, plan.sendId);
107799
107851
  })
107800
107852
  });
107801
- console.log(JSON.stringify({ success: true, privateSend: status }));
107853
+ const answer = { success: true, privateSend: status };
107854
+ output2(answer);
107855
+ return answer;
107802
107856
  }
107803
107857
  async function unshieldOnce({ options, origin, historyUrl, walletPath: walletPath2, recipientOwner, payerTools, exitTools, profile, connection, store, unshieldId, amountAtomic }) {
107804
107858
  const paid = await selfPay({
@@ -109828,6 +109882,7 @@ function refusalSay(a, tool, again, npmVersion) {
109828
109882
  if (PAY_CAP_CODES.has(code2)) {
109829
109883
  const total = usdcText(a.totalAtomic ?? a.neededAtomic ?? a.amountAtomic);
109830
109884
  const cap = usdcText(a.capAtomic ?? a.limitAtomic);
109885
+ if (a.capKind === "call" && total && cap) return `Nothing was paid: it costs ${total} USDC, over this request's ${cap} USDC cap. Allow ${total} USDC to pay it.`;
109831
109886
  return total && cap ? `Nothing was paid: ${total} USDC is over your ${cap} USDC limit. ${raiseLine("--max-payment", npmVersion)}` : `Nothing was paid: this is above your per-payment limit. ${raiseLine("--max-payment", npmVersion)}`;
109832
109887
  }
109833
109888
  if (MOVE_CAP_CODES.has(code2)) {
@@ -110055,7 +110110,9 @@ function balanceLine(a, room = SAY_MAX_WORDS) {
110055
110110
  if (moving) return MOVE_LINE;
110056
110111
  if (a.status === "reading") return "The first balance read of this wallet is not done yet. Ask again in 30 seconds.";
110057
110112
  if (a.refreshing === true) {
110058
- return Number(a.refreshingSeconds) >= 60 ? "The wallet is still reading the chain; this takes longer than usual. Ask again in a minute." : "The wallet is still reading the chain. Ask again in about 30 seconds.";
110113
+ const last = a.lastRead && usdcText(a.lastRead.privateAtomic) && Number.isSafeInteger(a.lastRead.ageMinutes) && a.lastRead.ageMinutes > 0 ? ` The last full read, ${ageWords(a.lastRead.ageMinutes)} ago, showed ${usdcText(a.lastRead.privateAtomic)} USDC private.` : "";
110114
+ const lines = Number(a.refreshingSeconds) >= 60 ? [`Still reading the chain; it takes longer than usual.${last} Ask again in a minute.`, "The wallet is still reading the chain; this takes longer than usual. Ask again in a minute."] : [`Still reading the chain.${last} Ask again in about 30 seconds.`, "The wallet is still reading the chain. Ask again in about 30 seconds."];
110115
+ return last ? lines.find((text3) => words(text3) <= SAY_MAX_WORDS) ?? lines[1] : lines[1];
110059
110116
  }
110060
110117
  const why = refreshErrorWords(str(a.refreshError), a.lockedBy);
110061
110118
  return why ? `Your private balance is not known yet: ${why}. Ask again in a minute.` : "Your private balance is not known yet. Ask again in a minute.";
@@ -110235,6 +110292,10 @@ function build(a, tool, npmVersion) {
110235
110292
  const amount = usdcText(a.done === true ? a.sentAtomic ?? a.amountAtomic : a.amountAtomic);
110236
110293
  const what = amount ? `${amount} USDC` : "the USDC";
110237
110294
  if (a.done !== true) return `Sending ${what} privately to ${a.to}.`;
110295
+ if (a.replayed === true) {
110296
+ const ago = Number.isSafeInteger(a.finishedMinutesAgo) ? a.finishedMinutesAgo > 0 ? ` about ${minutesText(a.finishedMinutesAgo)} ago` : " under a minute ago" : " earlier";
110297
+ return `Already sent${ago}: ${what} to ${a.to}. This request moved nothing new.`;
110298
+ }
110238
110299
  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.` : "";
110239
110300
  if (typeof a.exitTransaction === "string" && a.privateSend === void 0) {
110240
110301
  const head = `Sent ${what} to ${a.to} (transaction ${a.exitTransaction}).`;
@@ -110342,7 +110403,7 @@ function plainFields(parsed2, toolName) {
110342
110403
  function withSay(tool, options = {}) {
110343
110404
  return { ...tool, run: async (args, ctx) => addSay(await tool.run(args, ctx), tool.name, options) };
110344
110405
  }
110345
- var SAY_MAX_WORDS, DECIMALS, ATOMIC3, SAY_FIRST, SAY_BANNED, CODE_TOKEN, LONG_NUMBER, REFUND_MINUTES, MOVE_LINE, minutesText, MOVING_ROLES, CLOSE_LATER, PAY_CAP_CODES, raiseLine, MOVE_CAP_CODES, OWNER_LIMIT_CODES, INSUFFICIENT, BUSY, SHIELD_TOOLS, words, positive, str, OWN_WORK_WORDS, DISCOVER_LIST_NAMES, TRANSFER_TOOLS;
110406
+ var SAY_MAX_WORDS, DECIMALS, ATOMIC3, SAY_FIRST, SAY_BANNED, CODE_TOKEN, LONG_NUMBER, REFUND_MINUTES, MOVE_LINE, minutesText, ageWords, MOVING_ROLES, CLOSE_LATER, PAY_CAP_CODES, raiseLine, MOVE_CAP_CODES, OWNER_LIMIT_CODES, INSUFFICIENT, BUSY, SHIELD_TOOLS, words, positive, str, OWN_WORK_WORDS, DISCOVER_LIST_NAMES, TRANSFER_TOOLS;
110346
110407
  var init_say = __esm({
110347
110408
  "wallet/src/mcp/say.mjs"() {
110348
110409
  "use strict";
@@ -110358,6 +110419,7 @@ var init_say = __esm({
110358
110419
  REFUND_MINUTES = 7;
110359
110420
  MOVE_LINE = "Your balance is moving to an upgraded pool by itself. Nothing to do.";
110360
110421
  minutesText = (n) => `${n} minute${Number(n) === 1 ? "" : "s"}`;
110422
+ ageWords = (minutes) => minutes < 60 ? minutesText(minutes) : `${Math.round(minutes / 60)} hour${Math.round(minutes / 60) === 1 ? "" : "s"}`;
110361
110423
  MOVING_ROLES = /* @__PURE__ */ new Set(["draining", "old pool (switching)"]);
110362
110424
  CLOSE_LATER = "Cannot close yet: the seller could still take this payment. Ask again in about 2 minutes; if unpaid, it closes and the money comes back.";
110363
110425
  PAY_CAP_CODES = /* @__PURE__ */ new Set(["spend_payment_cap_exceeded", "mcp_cap_argument_above_launch_cap"]);
@@ -122379,7 +122441,8 @@ ${canonical5}`;
122379
122441
  const numbers = ASK_PAY_CODES.has(refusal3.error) || refusal3.error === "mcp_session_cap_exceeded" ? capNumbers(refusal3) : {};
122380
122442
  const launch = policy.limits.maxPaymentAtomic;
122381
122443
  if (refusal3.error === "spend_payment_cap_exceeded" && numbers.totalAtomic && numbers.capAtomic && BigInt(numbers.capAtomic) < BigInt(launch) && BigInt(numbers.totalAtomic) <= BigInt(launch)) {
122382
- return refuse12(refusal3.error, refusal3.detail, `Nothing was paid. This costs ${usdcText2(numbers.totalAtomic)} USDC, above this call's max_payment of ${usdcText2(numbers.capAtomic)} USDC. If the user agrees, call ${tool} again with request_id "${requestId}" and max_payment "${usdcText2(numbers.totalAtomic)}" (the limit is ${usdcText2(launch)} USDC)`, { requestId, ...numbers });
122444
+ const limitWords = String(launch) === NO_LIMIT_ATOMIC2 ? "this wallet has no per-payment limit" : `the wallet's per-payment limit is ${usdcText2(launch)} USDC`;
122445
+ return refuse12(refusal3.error, refusal3.detail, `Nothing was paid. This costs ${usdcText2(numbers.totalAtomic)} USDC, above this call's max_payment of ${usdcText2(numbers.capAtomic)} USDC. If the user agrees, call ${tool} again with request_id "${requestId}" and max_payment "${usdcText2(numbers.totalAtomic)}" (${limitWords})`, { requestId, ...numbers, capKind: "call" });
122383
122446
  }
122384
122447
  return refuse12(refusal3.error, refusal3.detail, mcpNext(refusal3.error, refusal3.detail, refusal3.next, refusal3), { requestId, ...numbers });
122385
122448
  }
@@ -122715,11 +122778,12 @@ ${canonical5}`;
122715
122778
  if (rows2.length === count2 && nextMs >= 0 && nextMs + 2e3 < rows2[count2 - 1].at) return rows2;
122716
122779
  }
122717
122780
  }
122718
- function walletCard({ one, unshielded, landingAtomic, parts, store }) {
122781
+ function walletCard({ one, unshielded, landingAtomic, parts, store, lastRead }) {
122719
122782
  const known = ATOMIC_TEXT.test(String(one.balanceAtomic ?? ""));
122720
122783
  const landing = landingAtomic > 0n ? `; ${usdcText2(landingAtomic)} USDC you added is being counted` : "";
122721
122784
  const split5 = parts?.parts > 1 ? ` (in ${parts.parts} parts: a send of all of it joins them first, about ${minutesText2(parts.minutes)})` : "";
122722
- const lines = [known ? `Private balance: ${usdcText2(one.balanceAtomic)} USDC${split5}${landing}` : `Private balance: not known yet (the wallet is still reading it)${landing}`];
122785
+ const reading = lastRead ? `the wallet is still reading it; the last full read, ${ageWords2(lastRead.ageMinutes)} ago, showed ${usdcText2(lastRead.privateAtomic)} USDC` : "the wallet is still reading it";
122786
+ const lines = [known ? `Private balance: ${usdcText2(one.balanceAtomic)} USDC${split5}${landing}` : `Private balance: not known yet (${reading})${landing}`];
122723
122787
  lines.push(ATOMIC_TEXT.test(String(unshielded.unshieldedAtomic ?? "")) ? `Public USDC on this wallet's own key: ${usdcText2(unshielded.unshieldedAtomic)} USDC` : "Public USDC on this wallet's own key: not read yet");
122724
122788
  let mode;
122725
122789
  try {
@@ -123001,6 +123065,10 @@ ${canonical5}`;
123001
123065
  else if (one.balanceAtomic === "0" && shieldEnabled) {
123002
123066
  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)`;
123003
123067
  } else next = "nothing to do; x402_pay pays from balanceAtomic, wallet_unshield sends from breakdown.shieldedAtomic";
123068
+ const lastRead = one.balanceAtomic === null && refresh.refreshing === true && !firstRead && ATOMIC_TEXT.test(String(report.detail?.unspentAtomic ?? "")) && Number.isFinite(report.detail?.lastRefresh?.ageSeconds) ? { privateAtomic: String(report.detail.unspentAtomic), ageMinutes: Math.max(1, Math.round(report.detail.lastRefresh.ageSeconds / 60)) } : void 0;
123069
+ if (lastRead) {
123070
+ next = `${next}. Meanwhile tell the user: the last full read, ${ageWords2(lastRead.ageMinutes)} ago, showed ${usdcText2(lastRead.privateAtomic)} USDC private (lastRead); a payment or a refund since then is not in it, so it is not the balance now`;
123071
+ }
123004
123072
  let landingAtomic = 0n;
123005
123073
  try {
123006
123074
  landingAtomic = landedNotCounted(store);
@@ -123111,7 +123179,7 @@ ${canonical5}`;
123111
123179
  }
123112
123180
  const balanceParts = parts?.parts > 1 && ATOMIC_TEXT.test(String(one.balanceAtomic ?? "")) ? parts.parts : void 0;
123113
123181
  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)}`;
123182
+ next = `${next}. The balance is in ${balanceParts} parts, and a send of all of it joins them first: it takes about ${minutesText2(parts.minutes)}. If the user asks to send all of it, write that time to the user BEFORE you call wallet_unshield (some apps show nothing until that call ends)`;
123115
123183
  }
123116
123184
  if (earlierDeposits.length) next = `${next}. Tell the user each earlierDeposits notice`;
123117
123185
  const notices = takeRefundNotices();
@@ -123175,7 +123243,8 @@ ${canonical5}`;
123175
123243
  ...firstRead ? { status: "reading" } : {},
123176
123244
  balanceAtomic: one.balanceAtomic,
123177
123245
  balance: one.balance,
123178
- card: walletCard({ one, unshielded, landingAtomic, parts, store }),
123246
+ card: walletCard({ one, unshielded, landingAtomic, parts, store, lastRead }),
123247
+ ...lastRead ? { lastRead } : {},
123179
123248
  // joinMinutes: the card's minutes, so say.mjs names them too.
123180
123249
  ...balanceParts ? { balanceParts, ...Number.isSafeInteger(parts.minutes) ? { joinMinutes: parts.minutes } : {} } : {},
123181
123250
  ...report.detail ? { detail: detailPublic } : {},
@@ -125047,10 +125116,11 @@ ${canonical5}`;
125047
125116
  }
125048
125117
  return answer;
125049
125118
  async function startOrRead() {
125119
+ const freshId = `${request.requestId.slice(0, 110)}-${Date.now().toString(36)}`;
125050
125120
  const reused = (detail) => refuse12(
125051
125121
  "mcp_request_id_reused",
125052
125122
  detail,
125053
- `read this one with the arguments it started with, or start a new ${spec.noun} with a NEW request_id`,
125123
+ `read this one with the arguments it started with, or start a new ${spec.noun} with a NEW request_id such as "${freshId}" (or leave request_id out and the wallet makes one)`,
125054
125124
  { requestId: request.requestId }
125055
125125
  );
125056
125126
  const known = jobs.get(key);
@@ -125143,6 +125213,9 @@ ${canonical5}`;
125143
125213
  const finished = kind === "unshield" ? split5 ? record.state === "done" || record.state === "cancelled" && privateSendStatus(store, record).done : record.state === "exited" : ["published", "foreign_funded"].includes(record.state);
125144
125214
  if (finished) {
125145
125215
  const send3 = split5 ? privateSendStatus(store, record) : void 0;
125216
+ const endedAtMs = Date.parse(String(record.doneAt ?? record.cancelledAt ?? "")) || record.exitedAtMs;
125217
+ const earlier = kind === "unshield" ? { replayed: true, ...Number.isSafeInteger(endedAtMs) ? { finishedMinutesAgo: Math.max(0, Math.round((Date.now() - endedAtMs) / 6e4)) } : {} } : {};
125218
+ const earlierWords = earlier.finishedMinutesAgo !== void 0 ? ` about ${minutesText2(earlier.finishedMinutesAgo)} ago` : " earlier";
125146
125219
  return json3({
125147
125220
  ok: true,
125148
125221
  requestId: request.requestId,
@@ -125152,10 +125225,11 @@ ${canonical5}`;
125152
125225
  done: true,
125153
125226
  stage: "done",
125154
125227
  state: record.state,
125228
+ ...earlier,
125155
125229
  ...kind !== "unshield" ? { transaction: typeof record.receipt?.signature === "string" ? record.receipt.signature : null } : split5 ? sendHeadline(send3) : { selfPayTransaction: record.transaction ?? null, exitTransaction: record.exitTransaction ?? null },
125156
125230
  ...kind === "elsewhere" ? elsewhereFacts(request.requestId, record) : {},
125157
125231
  ...kind !== "unshield" && record.state === "foreign_funded" ? { fundedByThisWallet: false } : {},
125158
- next: kind === "unshield" ? split5 ? sendDoneNext(record.mcpTo ?? request.to, send3) : "the unshield is done; nothing more to do" : record.state === "foreign_funded" ? kind === "elsewhere" ? `another key landed this deposit's note first, so the USDC sent to the one-time address did not move; it stays the user's there, and ${reclaimHow(request.requestId)}. wallet_balance shows the shielded value (it reads the chain by itself)` : "another key funded this deposit; this wallet's unshielded USDC did not move. wallet_balance shows the shielded value (it reads the chain by itself)" : "the deposit is done; wallet_balance shows it (it reads the chain by itself)"
125232
+ next: kind === "unshield" ? `This request_id finished${earlierWords}; this call sent nothing new. ${split5 ? sendDoneNext(record.mcpTo ?? request.to, send3).replace(/^the/, "The") : "The unshield is done; nothing more to do"}. To send again, call wallet_unshield with a NEW request_id, or leave request_id out and the wallet makes one` : record.state === "foreign_funded" ? kind === "elsewhere" ? `another key landed this deposit's note first, so the USDC sent to the one-time address did not move; it stays the user's there, and ${reclaimHow(request.requestId)}. wallet_balance shows the shielded value (it reads the chain by itself)` : "another key funded this deposit; this wallet's unshielded USDC did not move. wallet_balance shows the shielded value (it reads the chain by itself)" : "the deposit is done; wallet_balance shows it (it reads the chain by itself)"
125159
125233
  });
125160
125234
  }
125161
125235
  if (kind === "elsewhere" && record.state === "reclaimed" && record.relay.abandoned) {
@@ -126295,7 +126369,7 @@ ${canonical5}`;
126295
126369
  const unshield = {
126296
126370
  name: "wallet_unshield",
126297
126371
  title: "Send USDC privately from the shielded balance",
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.`,
126372
+ 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). BEFORE you call it to send all of the balance when wallet_balance showed balanceParts above 1, write to the user that it takes about joinMinutes minutes (the wallet first joins the parts; some apps show nothing until this call ends). 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 none of them, so the time is said before the call, as above). 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.`,
126299
126373
  inputSchema: {
126300
126374
  type: "object",
126301
126375
  properties: {
@@ -127287,7 +127361,8 @@ ${canonical5}`;
127287
127361
  const transferReused = (id) => refuse12(
127288
127362
  "mcp_request_id_reused",
127289
127363
  `request_id "${id}" already names a transfer with another token_mint, amount_atomic, to, parts, max_delay_minutes, or max_slippage_bps; nothing new was signed`,
127290
- `read it with wallet_transfer_token_status and request_id "${id}", or start a new transfer with a NEW request_id`,
127364
+ // K28 T7 (2026-10-23): the agent hit this twice before it found a free id; next names one that is new (TRANSFER_ID: at most 40).
127365
+ `read it with wallet_transfer_token_status and request_id "${id}", or start a new transfer with a NEW request_id such as "${id.slice(0, 31)}-${Date.now().toString(36)}"`,
127291
127366
  { requestId: id }
127292
127367
  );
127293
127368
  async function beginTransfer(args, call3 = {}) {
@@ -128003,7 +128078,7 @@ ${canonical5}`;
128003
128078
  return refuse12(
128004
128079
  "p03_float_over_maximum",
128005
128080
  floatRefusal(error)[1],
128006
- `Nothing was paid. Call inference_chat again with max_payment_atomic of at least ${error?.paidAtomic ?? "price + fee"} if the spending limit allows it, or lower max_tokens`,
128081
+ `Nothing was paid. Call inference_chat again with max_payment of at least ${total !== void 0 ? `"${usdcText2(total)}"` : "price + fee"} (USDC) if the spending limit allows it, or lower max_tokens`,
128007
128082
  { requestId, paid: false, ...facts }
128008
128083
  );
128009
128084
  }
@@ -128731,7 +128806,7 @@ function createSetupTools({
128731
128806
  };
128732
128807
  return { tools: [setupTool, statusTool, ...MCP_TOOL_NAMES.map((name) => name === "x402_discover" ? discoverTool(discoverCacheDir) : notSetUp(name))], doneTool };
128733
128808
  }
128734
- var import_web3133, shownCaps, RELEASE_RETRY_MS, STAGE_WORDS, stageWords, INTERNAL_BALANCE_DETAIL, shownBreakdown, sessionLeftShown, TOKEN_UNIT_TOOLS, RESOURCE_PAYMENT_WAL_PREFIX2, POOL_LAYOUT_CODE, POOL_LAYOUT_UPDATE, USEPOD_VIA_INFERENCE_CHAT, 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, 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, terminalCommand, capRaiseText, cheaperSellerText, limitNext, SAFE_TEXT, MCP_TOOL_ALIASES, POOL_SWITCH_WHY, POOL_SWITCH_NEXT, SWITCH_STATES, quietSwitch, switchKey, ATOMIC_TEXT, 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, refuse12, publicOffer, FLOAT_PAID_CODES, FLOAT_PRIVATE_LEG, floatStillSettling, MONEY_ID2, toRecordId, linkKindOf, moneyId, REFUSED_CODES, refusedCodeOf, PLAIN_REASONS, plainReason, KEPT_SEND_TRIES, shownId, isSplitSend, sendPartId, PROMISED_NEXT, PUBLIC_READ_TIMEOUT_MS, withTimeout, ownKey, readFailure, OWN_KEY_TOKENS_MAX, OWN_TOKENS_GRACE_MS, tokenAmountText, elsewhereId, AGENT_ID_SCHEMA, agentIdName, minutesText2, DEPOSIT_SENT_STATES, depositLanded, usdcText2, walkWalletHistory, REFRESH_LEASE_WALK_MS, HOME_MARKS_SETTLED_MS2, AUTO_BACKUPS_KEPT, AUTO_BACKUP_NAME, AUTO_BACKUP_VERIFIED, FRONTED_SETTLE_MARGIN_SLOTS, BALANCE_REFRESH_WAIT_MS, BALANCE_REFRESH_GRACE_MS, BALANCE_REFRESH_LONG_MS, INBOUND_REFRESH_AGE_MS, CAP_BALANCE_WAIT_MS, CAP_BALANCE_RETRY_MS, REFRESH_RETRY_MS, FINALITY_POLL_MS, FINALITY_WAIT_MS, FINALITY_REFRESH_LIMIT_MS, POOL_WATCH_MS, POOL_WATCH_POLL_MS, EARLY_BALANCE_DELAY_MS, FINALITY_SETTLE_WINDOW_SLOTS, PUBLIC_READ_ANSWER_MS, SWITCH_WAIT_MS, UNSHIELD_TO_NO_USDC_DETAIL, UNSHIELD_TO_INVALID_NEXT, INFERENCE_MIN_MAX_TOKENS, ELICIT_TIMEOUT_MS, ELICIT_UNSHOWN_MS, elicitUnshown, POLL_SECONDS3, 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_POLL_NEXT, PRIVATE_INFERENCE_WHY, PRIVATE_INFERENCE_NEXT;
128809
+ var import_web3133, shownCaps, RELEASE_RETRY_MS, STAGE_WORDS, stageWords, INTERNAL_BALANCE_DETAIL, shownBreakdown, sessionLeftShown, TOKEN_UNIT_TOOLS, RESOURCE_PAYMENT_WAL_PREFIX2, POOL_LAYOUT_CODE, POOL_LAYOUT_UPDATE, USEPOD_VIA_INFERENCE_CHAT, 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, 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, terminalCommand, capRaiseText, cheaperSellerText, limitNext, SAFE_TEXT, MCP_TOOL_ALIASES, POOL_SWITCH_WHY, POOL_SWITCH_NEXT, SWITCH_STATES, quietSwitch, switchKey, ATOMIC_TEXT, 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, refuse12, publicOffer, FLOAT_PAID_CODES, FLOAT_PRIVATE_LEG, floatStillSettling, MONEY_ID2, toRecordId, linkKindOf, moneyId, REFUSED_CODES, refusedCodeOf, PLAIN_REASONS, plainReason, KEPT_SEND_TRIES, shownId, isSplitSend, sendPartId, PROMISED_NEXT, PUBLIC_READ_TIMEOUT_MS, withTimeout, ownKey, readFailure, OWN_KEY_TOKENS_MAX, OWN_TOKENS_GRACE_MS, tokenAmountText, elsewhereId, AGENT_ID_SCHEMA, agentIdName, minutesText2, ageWords2, DEPOSIT_SENT_STATES, depositLanded, usdcText2, walkWalletHistory, REFRESH_LEASE_WALK_MS, HOME_MARKS_SETTLED_MS2, AUTO_BACKUPS_KEPT, AUTO_BACKUP_NAME, AUTO_BACKUP_VERIFIED, FRONTED_SETTLE_MARGIN_SLOTS, BALANCE_REFRESH_WAIT_MS, BALANCE_REFRESH_GRACE_MS, BALANCE_REFRESH_LONG_MS, INBOUND_REFRESH_AGE_MS, CAP_BALANCE_WAIT_MS, CAP_BALANCE_RETRY_MS, REFRESH_RETRY_MS, FINALITY_POLL_MS, FINALITY_WAIT_MS, FINALITY_REFRESH_LIMIT_MS, POOL_WATCH_MS, POOL_WATCH_POLL_MS, EARLY_BALANCE_DELAY_MS, FINALITY_SETTLE_WINDOW_SLOTS, PUBLIC_READ_ANSWER_MS, SWITCH_WAIT_MS, UNSHIELD_TO_NO_USDC_DETAIL, UNSHIELD_TO_INVALID_NEXT, INFERENCE_MIN_MAX_TOKENS, ELICIT_TIMEOUT_MS, ELICIT_UNSHOWN_MS, elicitUnshown, POLL_SECONDS3, 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_POLL_NEXT, PRIVATE_INFERENCE_WHY, PRIVATE_INFERENCE_NEXT;
128735
128810
  var init_tools = __esm({
128736
128811
  "wallet/src/mcp/tools.mjs"() {
128737
128812
  "use strict";
@@ -129074,6 +129149,7 @@ var init_tools = __esm({
129074
129149
  AGENT_ID_SCHEMA = "shinjuku-mcp-agent-id-v1";
129075
129150
  agentIdName = (kind, recordId2) => `mcp-agent-id-${kind}-${recordId2}`;
129076
129151
  minutesText2 = (n) => `${n} minute${Number(n) === 1 ? "" : "s"}`;
129152
+ ageWords2 = (minutes) => minutes < 60 ? minutesText2(minutes) : `${Math.round(minutes / 60)} hour${Math.round(minutes / 60) === 1 ? "" : "s"}`;
129077
129153
  DEPOSIT_SENT_STATES = /* @__PURE__ */ new Set(["signed", "finalized", "published"]);
129078
129154
  depositLanded = (deposit) => deposit?.schema === DEPOSIT_WAL_SCHEMA && (["finalized", "published"].includes(deposit.state) || deposit.state === "submitted" && typeof deposit.relay?.result?.signature === "string" && deposit.relay.result.signature.length > 0);
129079
129155
  usdcText2 = (atomic4, decimals = 6) => {
@@ -130249,6 +130325,7 @@ var init_stdioServer = __esm({
130249
130325
  init_session();
130250
130326
  init_payAndFetch();
130251
130327
  init_compact();
130328
+ init_recover();
130252
130329
  init_recoveredPayment();
130253
130330
  init_recoveryBundle();
130254
130331
  init_rpcDefault();
@@ -130292,7 +130369,7 @@ var init_stdioServer = __esm({
130292
130369
  MCP_CLOSE_HISTORY_BOUND_MS = 45e3;
130293
130370
  MCP_CLOSE_UNREADABLE_BOUND_MS = 6e4;
130294
130371
  MCP_CLOSE_POLL_MS = 500;
130295
- closeHistoryBusy = (server) => () => compactHistoryReadsRunning() > 0 || server.refreshOpen?.() === true;
130372
+ closeHistoryBusy = (server) => () => compactHistoryReadsRunning() > 0 || memoReadsAheadRunning() > 0 || server.refreshOpen?.() === true;
130296
130373
  SETUP_INSTRUCTIONS = `A self-custody Shinjuku Shield wallet server that is NOT SET UP on this machine yet. Every money tool answers wallet_not_set_up. When the user wants to pay, check a balance, or shield USDC, call shinjuku_setup: it asks the user to confirm in this client, then creates the wallet on this machine (the keys and a random passphrase stay in local files; no tool takes or returns them) and downloads and checks the proof tools. It takes minutes: call shinjuku_setup again as its answer's next says. When it answers stage done, the wallet tools are live in this same server (tools/list changes). ${INSTRUCTIONS}`;
130297
130374
  }
130298
130375
  });
@@ -151025,6 +151102,7 @@ import { resolve as resolve39 } from "node:path";
151025
151102
  // wallet/src/cli/shinjukuWallet.mjs
151026
151103
  init_fs();
151027
151104
  var import_web3138 = __toESM(require_index_cjs(), 1);
151105
+ init_esm2();
151028
151106
  init_shielded_wallet();
151029
151107
  init_walk();
151030
151108
  init_cli();
@@ -155684,6 +155762,7 @@ init_store();
155684
155762
  init_processLease();
155685
155763
  init_notes2();
155686
155764
  init_proxy();
155765
+ init_autoJoin();
155687
155766
  init_torMissingMcp();
155688
155767
  init_chain2();
155689
155768
  init_session();
@@ -156040,6 +156119,8 @@ var GUIDE = Object.freeze({
156040
156119
  facilitator: DEFAULT_FACILITATOR_ORIGIN
156041
156120
  });
156042
156121
  var WALLET_FILE = `"$SHIELDED_WALLET_HOME/${GUIDE.pool}/wallet.json"`;
156122
+ var FROM_SETUP = " (after setup, config.json gives it: no need to type it)";
156123
+ var UNSHIELD_EXAMPLE = `${CLI2} unshield --amount 0.5 --to <your wallet address>`;
156043
156124
  function hintForThisRun(text3, values) {
156044
156125
  let hint = String(text3);
156045
156126
  const get = (name) => {
@@ -157101,23 +157182,35 @@ function setupConfigOf(home) {
157101
157182
  }
157102
157183
  }
157103
157184
  function torArgv(argv2, env = process.env) {
157104
- if (!argv2.includes("--tor") || argv2.includes("--proxy")) return argv2;
157105
157185
  const rest = withoutConnectionFlags(argv2);
157106
157186
  const command2 = rest[0];
157107
- if (command2 === void 0 || command2.startsWith("-") || command2 === "setup") return argv2;
157108
- if (command2 === "mcp" && (argv2.includes("--pool") || argv2.includes("--wallet"))) return argv2;
157109
157187
  const walletAt = rest.indexOf("--wallet");
157110
157188
  const walletFile = walletAt >= 0 ? rest[walletAt + 1] : void 0;
157111
157189
  const walletHome2 = walletFile && !walletFile.startsWith("--") ? dirname32(dirname32(resolve38(walletFile))) : void 0;
157112
157190
  const home = walletHome2 && existsSync(join39(walletHome2, "config.json")) ? walletHome2 : setupHome(env);
157113
- let tunnel;
157191
+ let config;
157114
157192
  try {
157115
- tunnel = readSetupConfig(home)?.torTunnel;
157193
+ config = readSetupConfig(home);
157116
157194
  } catch {
157117
- tunnel = void 0;
157195
+ config = void 0;
157118
157196
  }
157119
- return tunnel ? [...argv2, "--proxy", tunnel] : argv2;
157120
- }
157197
+ const ownTor = command2 === void 0 || command2.startsWith("-") || OWN_TOR_COMMANDS.has(command2);
157198
+ let out = argv2;
157199
+ if (argv2.includes("--no-tor")) {
157200
+ env[NO_TOR_RUN_ENV] = "1";
157201
+ if (!ownTor) out = argv2.filter((word) => word !== "--no-tor");
157202
+ } else if (!ownTor && config?.tor === true && env[NO_TOR_RUN_ENV] !== "1" && !argv2.includes("--tor") && !argv2.includes("--proxy")) {
157203
+ env[TOR_FROM_CONFIG_ENV] = "1";
157204
+ out = [...argv2, "--tor"];
157205
+ }
157206
+ if (!out.includes("--tor") || out.includes("--proxy")) return out;
157207
+ if (command2 === void 0 || command2.startsWith("-") || command2 === "setup") return out;
157208
+ if (command2 === "mcp" && (out.includes("--pool") || out.includes("--wallet"))) return out;
157209
+ const tunnel = config?.torTunnel;
157210
+ return tunnel ? [...out, "--proxy", tunnel] : out;
157211
+ }
157212
+ var OWN_TOR_COMMANDS = /* @__PURE__ */ new Set(["mcp", "setup", "chat", "infer"]);
157213
+ var NO_TOR_RUN_ENV = "SHINJUKU_WALLET_NO_TOR";
157121
157214
  function fillFromConfig(values, home, config) {
157122
157215
  for (const [name, value] of mcpFlagsFromConfig(home, config)) if (!values.has(name)) values.set(name, value);
157123
157216
  }
@@ -157501,8 +157594,12 @@ function usdcArgs(command2, args) {
157501
157594
  if (value === void 0 || value.startsWith("--")) continue;
157502
157595
  index += 1;
157503
157596
  if (!spec.usdc || spec.usdc === "or-none" && /^none$/i.test(value)) continue;
157597
+ if (spec.usdc === "or-all" && /^all$/i.test(value)) {
157598
+ out[index] = "all";
157599
+ continue;
157600
+ }
157504
157601
  const read = usdcFlagAtomic(spec.name, value, { oldForm: command2.name === "mcp" ? "atomic" : "refuse" });
157505
- if (read.error) throw new UsageError(read.error, command2);
157602
+ if (read.error) throw new UsageError(spec.usdc === "or-all" ? `${read.error}; or ${spec.name} all sends the whole private balance` : read.error, command2);
157506
157603
  if (read.old) oldForm.push(read.old);
157507
157604
  out[index] = read.atomic;
157508
157605
  }
@@ -158223,7 +158320,8 @@ var COMMANDS = [
158223
158320
  flag2("--pool", "<address>", "the shielded pool", { required: true, check: "pubkey" }),
158224
158321
  bool2("--refresh", "run the same chain walk as recover first (network)"),
158225
158322
  // --digests narrows recover's printed output; a balance needs the whole history.
158226
- ...recoverFlags({ refreshOnly: true }).filter((entry) => entry.name !== "--digests")
158323
+ ...recoverFlags({ refreshOnly: true }).filter((entry) => entry.name !== "--digests"),
158324
+ bool2("--json", "print the whole report as JSON in a terminal too (a file or a program gets JSON without this flag)")
158227
158325
  ],
158228
158326
  examples: [
158229
158327
  `${CLI2} balance --pool ${GUIDE.pool}`,
@@ -158234,6 +158332,7 @@ var COMMANDS = [
158234
158332
  "shinjukuShielded is what you can spend: unspent accounts and notes minus the ones in-flight payments or unshields will use.",
158235
158333
  "Without --refresh, balance reads only local files: the last walk (recover or balance --refresh) and the wallet log. It makes no network call.",
158236
158334
  "shinjukuShielded is null when the last walk is missing, stale, or incomplete. reasons says why; next shows the command to run.",
158335
+ "In a terminal it prints one plain line: the private balance in USDC, and a short reason when it is not current (then the command to run). A program, or --json, gets the whole report as JSON (total, shinjukuShielded, status, reasons, next, detail).",
158237
158336
  "Payments that others send to this wallet appear only after a refresh.",
158238
158337
  "With --refresh it also reads the one-time deposit addresses of this home's earlier pools (one address per request). USDC found there goes into this pool by itself (the facilitator pays the fee), and earlierDeposits lists each one with a notice.",
158239
158338
  "paymentsWithoutWaiting (and detail.fundedAccounts): your funded accounts that no in-flight work holds. A payment spends one account, and its change pays only after the next pool anchor (up to 5 minutes; about 30 s on some pools), so this many payments can run before the next anchor without waiting (an account made since the last anchor, such as a change, waits for the next one). add-funds --split makes more of them."
@@ -158247,7 +158346,7 @@ var COMMANDS = [
158247
158346
  }
158248
158347
  return;
158249
158348
  }
158250
- const extra = [...values.keys()].find((name) => name !== "--pool");
158349
+ const extra = [...values.keys()].find((name) => name !== "--pool" && name !== "--json");
158251
158350
  if (extra) throw new UsageError(`${extra} is used only with --refresh`);
158252
158351
  },
158253
158352
  rpcWhen: (values) => values.has("--refresh"),
@@ -158679,45 +158778,42 @@ var COMMANDS = [
158679
158778
  },
158680
158779
  {
158681
158780
  name: "unshield",
158682
- summary: "Send part of your balance to a public USDC account, privately.",
158781
+ summary: "Send part or all of your private balance to a Solana wallet address, privately.",
158683
158782
  env: [],
158684
158783
  flags: [
158685
- flag2("--wallet", "<file>", "the encrypted wallet file", { required: true }),
158686
- flag2("--passphrase-file", "<file>", "a private file that holds the wallet passphrase", { required: true }),
158784
+ flag2("--amount", "<usdc|all>", "USDC to send (0.5 = 50 cents), or all: the whole private balance", { required: true, check: "atomic", usdc: "or-all" }),
158785
+ flag2("--to", "<address>", "the recipient's Solana wallet address (it gets the USDC in its USDC account). Not this wallet's own address. With --recipient-owner (the old form): the recipient's USDC token account itself", { required: true, check: "pubkey" }),
158786
+ flag2("--unshield-id", "<id>", "optional: your name for this unshield. Without it the wallet makes a new one and prints it; to finish an unshield that stopped, run it again with that --unshield-id (it never sends twice)"),
158787
+ flag2("--wallet", "<file>", `the encrypted wallet file${FROM_SETUP}`, { required: true }),
158788
+ flag2("--passphrase-file", "<file>", `a private file that holds the wallet passphrase${FROM_SETUP}`, { required: true }),
158687
158789
  flag2("--rpc-file", "<file>", RPC_FILE_ABOUT),
158688
- flag2("--payer-proof-tools", "<file>", "the public payer proof-tools manifest", { required: true }),
158689
- flag2("--exit-proof-tools", "<file>", "the public exit proof-tools manifest", { required: true }),
158690
- flag2("--profile", "<file>", "the facilitator's public seller profile (public-profile.json)", { required: true }),
158691
- flag2("--program", "<address>", "the shielded pool program", { required: true, check: "pubkey" }),
158692
- flag2("--facilitator", "<https origin>", "the facilitator's https origin", { required: true, check: "origin" }),
158693
- flag2("--unshield-id", "<id>", "your name for this unshield; reuse it for every retry", { required: true }),
158694
- flag2("--amount", "<usdc>", "USDC to send (0.5 = 50 cents)", { required: true, check: "atomic", usdc: true }),
158695
- flag2("--to", "<token account>", "the recipient's USDC token account", { required: true, check: "pubkey" }),
158696
- flag2("--recipient-owner", "<address>", "the owner of that token account (not your deposit key)", { required: true, check: "pubkey" }),
158790
+ flag2("--payer-proof-tools", "<file>", `the public payer proof-tools manifest${FROM_SETUP}`, { required: true }),
158791
+ flag2("--exit-proof-tools", "<file>", `the public exit proof-tools manifest${FROM_SETUP}`, { required: true }),
158792
+ flag2("--profile", "<file>", `the facilitator's public seller profile (public-profile.json)${FROM_SETUP}`, { required: true }),
158793
+ flag2("--program", "<address>", `the shielded pool program${FROM_SETUP}`, { required: true, check: "pubkey" }),
158794
+ flag2("--facilitator", "<https origin>", `the facilitator's https origin${FROM_SETUP}`, { required: true, check: "origin" }),
158795
+ flag2("--recipient-owner", "<address>", "optional, the old form: with it, --to is a USDC token account and this is its owner (not your deposit key)", { check: "pubkey" }),
158697
158796
  HISTORY_RPC_FLAG,
158698
158797
  flag2("--parts", "<1-8>", "optional, default 1: send the amount as this many separate exits of unequal size (a private send); each part carries at least 0.01 USDC; above 1 it needs --max-delay-minutes 1-60, and --unshield-id may then be at most 45 characters", { check: "count" }),
158699
- flag2("--max-delay-minutes", "<0-60>", "optional, default 0: each part, part 1 included, is sent after a random wait of up to this many minutes; a part that is late (the command was stopped) waits a new random time", { check: "count" })
158700
- ],
158701
- examples: [
158702
- `${CLI2} unshield --wallet ${WALLET_FILE} --passphrase-file passphrase.txt --payer-proof-tools proof-tools.json --exit-proof-tools exit-proof-tools.json --profile public-profile.json --program ${GUIDE.program} --facilitator ${GUIDE.facilitator}/ --unshield-id u1 --amount 0.003 --to <recipient USDC account> --recipient-owner <recipient owner>`,
158703
- `${CLI2} unshield --wallet ${WALLET_FILE} --passphrase-file passphrase.txt --payer-proof-tools proof-tools.json --exit-proof-tools exit-proof-tools.json --profile public-profile.json --program ${GUIDE.program} --facilitator ${GUIDE.facilitator}/ --unshield-id s1 --amount 3 --parts 3 --max-delay-minutes 20 --to <recipient USDC account> --recipient-owner <recipient owner>`
158798
+ flag2("--max-delay-minutes", "<0-60>", "optional, default 0: each part, part 1 included, is sent after a random wait of up to this many minutes; a part that is late (the command was stopped) waits a new random time", { check: "count" }),
158799
+ bool2("--json", "end with the result as JSON in a terminal too (a file or a program gets JSON without this flag)")
158704
158800
  ],
158801
+ // T9 (live run 2026-10-23 18:44 UTC, npm 0.3.83): the old two examples looked the same and neither had --amount or a
158802
+ // plain --to; after setup these two work as typed (config.json gives every file flag).
158803
+ examples: [UNSHIELD_EXAMPLE, `${CLI2} unshield --amount all --to <your wallet address>`],
158705
158804
  notes: [
158805
+ "After setup it needs only --amount and --to: config.json gives the wallet, the passphrase file, the proof tools, the profile, the program, and the facilitator. Without setup, give every flag marked required (the old form also takes --to <USDC token account> --recipient-owner <owner>).",
158706
158806
  "The wallet pays exactly --amount to a note it owns and keeps the rest private. The facilitator relays the note's exit to the recipient and pays every fee. No wallet key signs.",
158707
- "Use a recipient that is NOT your deposit address; the command refuses the deposit key.",
158708
- "Rerun the same command after any interruption; it replays.",
158709
- "--parts and --max-delay-minutes make a private send: the plan (part sizes, part ids <unshield-id>-p1.., earliest times) is saved in the wallet before part 1, each part is one unshield, and a stopped run continues at the first part not yet exited when the same command runs again. The wallet is free for other commands while a part waits. Keep the command running until it prints its result.",
158710
- `Stop the parts of a private send not sent yet: ${CLI2} unshield-cancel --wallet ${WALLET_FILE} --passphrase-file passphrase.txt --unshield-id <id>.`,
158807
+ "The recipient must already hold USDC (a Solana address with a USDC account, for example a Phantom address that received USDC); the command checks it first and sends nothing to an address without one. It refuses this wallet's own address: an exit there would show on chain that the money is yours. Best: an address that never sent USDC into this wallet.",
158808
+ `--amount all sends the whole private balance. When the balance is in more than one part, it says so and sends nothing: join the parts first with ${CLI2} merge (a private move inside this wallet; about 2 minutes per extra part), then run it again.`,
158809
+ "The command prints the unshield id first. If it stops, run the same command with --unshield-id <that id> added: it finishes that unshield and never sends twice.",
158810
+ "In a terminal it ends with one plain line (what was sent, to whom, the exit transaction) and the new private balance: after an exit the wallet reads the balance again itself (the same walk as balance --refresh, over the same connection; Tor when setup recorded it). A program, or --json, gets the result as JSON.",
158811
+ `--parts and --max-delay-minutes make a private send (${CLI2} unshield --amount 3 --parts 3 --max-delay-minutes 20 --to <your wallet address>): the plan (part sizes, part ids <unshield-id>-p1.., earliest times) is saved in the wallet before part 1, each part is one unshield, and a stopped run continues at the first part not yet exited when the same command with its --unshield-id runs again. The wallet is free for other commands while a part waits. Keep the command running until it prints its result.`,
158812
+ `Stop the parts of a private send not sent yet: ${CLI2} unshield-cancel --unshield-id <id>.`,
158711
158813
  "Privacy limit of a private send: every part goes to the same --to, so anyone who adds up that account's incoming transfers sees the total. The parts hide the time and the single amount of the exit, not the sum the account received."
158712
158814
  ],
158713
- next: `back up (${CLI2} help backup-export), then refresh the balance (${CLI2} help balance)`,
158714
- run: async (args, values, deps, io) => deps.unshieldMain(await spendGate(
158715
- args,
158716
- values,
158717
- deps,
158718
- io,
158719
- { amountAtomic: values.get("--amount"), again: " with the same --unshield-id" }
158720
- ))
158815
+ next: `back up the wallet (${CLI2} help backup-export); ${CLI2} balance shows the private balance`,
158816
+ run: (args, values, deps, io) => runUnshieldCommand(args, values, deps, io)
158721
158817
  },
158722
158818
  {
158723
158819
  name: "unshield-cancel",
@@ -159190,6 +159286,8 @@ function overviewText() {
159190
159286
  "",
159191
159287
  "Hide your IP, any command:",
159192
159288
  " --tor every request goes through Tor (torrc: HTTPTunnelPort 127.0.0.1:9080 IsolateDestAddr); the run starts only after the tunnel answers as Tor (else check.torproject.org must say IsTor)",
159289
+ ' after a setup that recorded Tor (config.json "tor": true) every command runs behind Tor by itself; a Tor that does not answer is refused, never a direct run',
159290
+ " --no-tor this one command runs without Tor although setup recorded Tor (the servers then see your IP address)",
159193
159291
  " --proxy http://host:port another HTTP proxy (never socks5://: Node would send https directly); the run starts only after the proxy answers",
159194
159292
  " with either, a facilitator may be an http://<v3 onion>/ origin; use a key-free RPC URL (a keyed one names you)",
159195
159293
  " --ohttp-relay <url> (or env SHINJUKU_OHTTP_RELAY) send POST /api/solana-rpc and /api/x402/* to the facilitator as Oblivious HTTP through this relay:",
@@ -159311,7 +159409,7 @@ var TRANSFER_START_FLAGS = /* @__PURE__ */ new Set([
159311
159409
  ]);
159312
159410
  var NOT_SHOWN = "; the value given is not one (it is not repeated here)";
159313
159411
  function checkValue(spec, value) {
159314
- if (spec.check === "atomic" && !/^[1-9][0-9]*$/.test(value)) {
159412
+ if (spec.check === "atomic" && !(spec.usdc === "or-all" && value === "all") && !/^[1-9][0-9]*$/.test(value)) {
159315
159413
  return `${spec.name} must be a whole number of atomic units above 0 (1 USDC = 1000000)${NOT_SHOWN}`;
159316
159414
  }
159317
159415
  if (spec.check === "count" && !/^(0|[1-9][0-9]*)$/.test(value)) {
@@ -159379,9 +159477,12 @@ function configDefaults(command2, args, deps) {
159379
159477
  ["--program", config.program],
159380
159478
  ["--proof-tools", config.proofTools],
159381
159479
  ["--facilitator", config.memoService],
159382
- ["--service", config.memoService]
159480
+ ["--service", config.memoService],
159481
+ // T9 (live run 2026-10-23 18:44 UTC, npm 0.3.83): unshield refused "missing 7 flags" although config.json holds its
159482
+ // files; merge (the step an unshield of the whole balance names) takes the same ones.
159483
+ ...["unshield", "merge"].includes(command2.name) ? [["--payer-proof-tools", config.proofTools], ["--exit-proof-tools", config.exitProofTools], ["--profile", config.profile]] : []
159383
159484
  ];
159384
- const needed = (spec) => spec.required || spec.refreshOnly && given.has("--refresh");
159485
+ const needed = (spec) => spec.required || spec.refreshOnly && given.has("--refresh") || spec.requiredUnless && !given.has(spec.requiredUnless);
159385
159486
  if (wantPool || wantWallet || given.has("--wallet") || given.get("--pool") === config.pool) {
159386
159487
  for (const [name, value] of fromConfig) {
159387
159488
  if (typeof value === "string" && command2.flags.some((spec) => spec.name === name && needed(spec)) && !given.has(name)) added.push(name, value);
@@ -159479,6 +159580,188 @@ function rpcFileArgs(command2, args, values, deps, io) {
159479
159580
  const { url } = resolveRpcUrl2({ env: {}, origin: facilitatorOriginOf(values.get("--service"), values.get("--facilitator"), values.get("--memo-service")), notice: io.err });
159480
159581
  return { args: [...args, "--rpc", url], url };
159481
159582
  }
159583
+ var UNSHIELD_MADE_ID = /* @__PURE__ */ Symbol("unshield id made here");
159584
+ var unshieldRefusal = (reason, text3) => new PublicFailure(`p03_unshield_${reason}: ${text3}`);
159585
+ function walletOwnAddress(store) {
159586
+ try {
159587
+ return import_web3138.Keypair.fromSecretKey(Uint8Array.from(store.state.solanaSecretKey), { skipValidation: true }).publicKey;
159588
+ } catch {
159589
+ return void 0;
159590
+ }
159591
+ }
159592
+ async function recipientHoldsUsdc({ connection, mint, owner }) {
159593
+ const account2 = getAssociatedTokenAddressSync(mint, owner, true, TOKEN_PROGRAM_ID);
159594
+ let info;
159595
+ try {
159596
+ info = await getAccount(connection, account2, "finalized", TOKEN_PROGRAM_ID);
159597
+ } catch (error) {
159598
+ if (error instanceof TokenAccountNotFoundError) return false;
159599
+ throw error;
159600
+ }
159601
+ return info.mint.equals(mint) && info.owner.equals(owner);
159602
+ }
159603
+ async function runUnshieldCommand(args, values, deps, io) {
159604
+ const terminal = deps.stdoutIsTTY?.() === true && !values.has("--json");
159605
+ let callArgs = args.filter((word) => word !== "--json");
159606
+ const setArg = (name, value) => {
159607
+ const at = callArgs.indexOf(name);
159608
+ callArgs = at >= 0 ? callArgs.map((word, index) => index === at + 1 ? value : word) : [...callArgs, name, value];
159609
+ values.set(name, value);
159610
+ };
159611
+ let store;
159612
+ const openStore = () => store ??= (deps.openWalletStore ?? openWalletStore)(resolve38(values.get("--wallet")), payerPassphrase(values, deps));
159613
+ const owner = values.has("--recipient-owner") ? new import_web3138.PublicKey(values.get("--recipient-owner")) : new import_web3138.PublicKey(values.get("--to"));
159614
+ if (!values.has("--recipient-owner")) {
159615
+ const wallet = openStore();
159616
+ if (walletOwnAddress(wallet)?.equals(owner)) {
159617
+ throw unshieldRefusal("recipient_owner_is_deposit_key", `--to is this wallet's own public address (its own key): an exit there would show on chain that the money is yours, so this private wallet does not send there; nothing was sent. Send to another address (best: one that never sent USDC into this wallet), like ${UNSHIELD_EXAMPLE}`);
159618
+ }
159619
+ const mint = new import_web3138.PublicKey(wallet.state.asset);
159620
+ const check = deps.unshieldRecipientHoldsUsdc ?? ((input) => recipientHoldsUsdc({ ...input, connection: connectRelay(spendRpcUrl(values, deps), "finalized") }));
159621
+ let holds;
159622
+ try {
159623
+ holds = await check({ mint, owner });
159624
+ } catch {
159625
+ holds = void 0;
159626
+ }
159627
+ if (holds === void 0) io.err("note: the recipient's USDC account could not be checked (the RPC did not answer); the unshield goes on\n");
159628
+ if (holds === false) {
159629
+ throw unshieldRefusal("exit_recipient_invalid", `this address has no USDC account yet (a new address, or a Shinjuku wallet's one-time add-funds address, which a Shinjuku wallet cannot pay yet); nothing was paid or sent. Use a Solana address that already holds USDC (for example a Phantom address), like ${UNSHIELD_EXAMPLE}`);
159630
+ }
159631
+ setArg("--to", getAssociatedTokenAddressSync(mint, owner, true, TOKEN_PROGRAM_ID).toBase58());
159632
+ setArg("--recipient-owner", owner.toBase58());
159633
+ }
159634
+ if (values.get("--amount") === "all") setArg("--amount", await sendAllAtomic(values, deps, io, openStore));
159635
+ let id = values.get("--unshield-id");
159636
+ if (id === void 0) {
159637
+ id = `unshield-${randomBytes35(6).toString("hex")}`;
159638
+ setArg("--unshield-id", id);
159639
+ values.set(UNSHIELD_MADE_ID, id);
159640
+ io.err(`unshield id: ${id} (if this command stops, run it again with --unshield-id ${id} added: it finishes this unshield and never sends twice)
159641
+ `);
159642
+ }
159643
+ const gated = await spendGate(callArgs, values, deps, io, { amountAtomic: values.get("--amount"), again: ` with --unshield-id ${id}` });
159644
+ let answer;
159645
+ const returned = await deps.unshieldMain(gated, { output: (result) => {
159646
+ answer = result;
159647
+ } });
159648
+ answer ??= returned;
159649
+ if (!answer || typeof answer !== "object") return;
159650
+ io.out(terminal ? unshieldDoneText(answer, owner.toBase58()) : `${JSON.stringify(answer)}
159651
+ `);
159652
+ const sent = answer.privateSend ? answer.privateSend.sentAtomic : answer.exitTransaction ? answer.amountAtomic : void 0;
159653
+ if (!/^[1-9][0-9]*$/.test(String(sent ?? ""))) return;
159654
+ const walletAt = gated.indexOf("--wallet");
159655
+ const report = await refreshAfterUnshield(walletAt >= 0 ? gated[walletAt + 1] : values.get("--wallet"), values, deps, io);
159656
+ if (terminal) io.out(`${privateBalanceLine(report, "Private balance now")}
159657
+ `);
159658
+ }
159659
+ function unshieldDoneText(answer, to) {
159660
+ const send3 = answer.privateSend;
159661
+ if (!send3) {
159662
+ const again = answer.replayed === true ? " (this unshield had finished before; nothing was sent twice)" : "";
159663
+ return `Sent ${usdcOr(answer.amountAtomic)} privately to ${to}${again}.
159664
+ Exit transaction: ${answer.exitTransaction}
159665
+ `;
159666
+ }
159667
+ const parts = Array.isArray(send3.parts) ? send3.parts : [];
159668
+ const lines = [`Sent ${usdcOr(send3.sentAtomic)} of ${usdcOr(send3.amountAtomic)} privately to ${to}, in ${parts.length} parts (unshield id ${send3.sendId}).`];
159669
+ const exits = parts.filter((part) => typeof part.exitTransaction === "string").map((part) => `part ${part.index} ${part.exitTransaction}`);
159670
+ if (exits.length) lines.push(`Exit transactions: ${exits.join(", ")}`);
159671
+ if (send3.done !== true && send3.cancelled !== true) {
159672
+ lines.push(`${usdcOr(send3.remainingAtomic)} is not sent yet: run the same command with --unshield-id ${send3.sendId} to send the rest.`);
159673
+ }
159674
+ return `${lines.join("\n")}
159675
+ `;
159676
+ }
159677
+ var STALE_WORDS = Object.freeze({
159678
+ first_check: "the first check of the pool history runs now",
159679
+ payment_settling: "a payment or send is still settling",
159680
+ account_join: "the wallet is joining your balance parts",
159681
+ pool_move: "money is moving to the facilitator's new pool"
159682
+ });
159683
+ var balanceNotCurrentWhy = (report) => STALE_WORDS[staleCauseOf(report)] ?? (report?.status === "unknown" ? "this wallet has no balance walk yet" : "a payment, deposit, or send finished after the last balance walk");
159684
+ function privateBalanceLine(report, label = "Private balance") {
159685
+ if (!report) return `${label}: not read again here; ${CLI2} balance --refresh shows it`;
159686
+ const atomic4 = report.totalAtomic ?? report.atomic;
159687
+ const pockets = report.split?.pocketsAtomic;
159688
+ const amount = atomic4 === null || atomic4 === void 0 ? void 0 : `${usdcOr(atomic4)}${/^[1-9][0-9]*$/.test(String(pockets ?? "")) && report.totalAtomic ? ` (${usdcOr(report.atomic)} shielded, ${usdcOr(pockets)} in pockets)` : ""}`;
159689
+ if (report.status === "current" || report.status === "empty") return `${label}: ${amount ?? "0 USDC"}`;
159690
+ const why = balanceNotCurrentWhy(report);
159691
+ return amount !== void 0 ? `${label}: ${amount} (not final yet: ${why}; ${CLI2} balance --refresh in a minute shows the final number)` : `${label}: not known yet (${why}); run ${CLI2} balance --refresh`;
159692
+ }
159693
+ async function refreshAfterUnshield(walletFile, values, deps, io) {
159694
+ const pool2 = /([1-9A-HJ-NP-Za-km-z]{32,44})[\\/]wallet\.json$/.exec(resolve38(walletFile))?.[1];
159695
+ if (!pool2 || resolve38(deps.walletPath(pool2)) !== resolve38(walletFile)) {
159696
+ io.err(`note: this wallet file is not in SHIELDED_WALLET_HOME, so its balance was not read again; ${CLI2} balance --refresh with its --pool shows it
159697
+ `);
159698
+ return void 0;
159699
+ }
159700
+ let restore = () => {
159701
+ };
159702
+ try {
159703
+ const passphrase2 = payerPassphrase(values, deps);
159704
+ const service = new URL(values.get("--facilitator")).origin;
159705
+ const history = values.get("--history-rpc");
159706
+ const refreshValues = new Map([
159707
+ ["--pool", pool2],
159708
+ ["--program", values.get("--program")],
159709
+ ["--proof-tools", values.get("--payer-proof-tools")],
159710
+ ["--service", service],
159711
+ ["--fresh", true],
159712
+ ...history ? [["--history-rpc", history]] : []
159713
+ ]);
159714
+ const args = [
159715
+ "--pool",
159716
+ pool2,
159717
+ "--program",
159718
+ values.get("--program"),
159719
+ "--proof-tools",
159720
+ values.get("--payer-proof-tools"),
159721
+ "--service",
159722
+ service,
159723
+ "--fresh",
159724
+ "--rpc",
159725
+ spendRpcUrl(values, deps),
159726
+ ...history ? ["--history-rpc", history] : []
159727
+ ];
159728
+ const runDeps = { ...deps, env: { ...deps.env, [PASSPHRASE_ENV2]: passphrase2 } };
159729
+ restore = lendPassphrase(passphrase2);
159730
+ io.err(`reading your private balance again (the same walk as ${CLI2} balance --refresh)
159731
+ `);
159732
+ const refreshed = await refreshSnapshot(args, refreshValues, runDeps);
159733
+ warnUnsavedSync(refreshed.result, io);
159734
+ refreshed.save();
159735
+ await refreshed.prune();
159736
+ const store = openPoolWallet(refreshValues, runDeps);
159737
+ return computeBalance({ store, snapshotRead: readBalanceSnapshot(store), source: "refresh", now: deps.now() });
159738
+ } catch (error) {
159739
+ io.err(`note: the private balance was not read again (${describeCliError(error, { fallback: "wallet_command_failed" })}); nothing about the send changes. ${CLI2} balance --refresh shows the new balance
159740
+ `);
159741
+ return void 0;
159742
+ } finally {
159743
+ restore();
159744
+ }
159745
+ }
159746
+ async function sendAllAtomic(values, deps, io, openStore) {
159747
+ const store = openStore();
159748
+ let report = computeBalance({ store, snapshotRead: readBalanceSnapshot(store), source: "local", now: deps.now() });
159749
+ if (report.status !== "current") report = await refreshAfterUnshield(values.get("--wallet"), values, deps, io) ?? report;
159750
+ const empty = report.status === "empty" || report.status === "current" && report.atomic === "0";
159751
+ if (!empty && (report.status !== "current" || !/^[1-9][0-9]*$/.test(String(report.atomic ?? "")))) {
159752
+ throw new PublicFailure(`balance_not_current: --amount all needs the current private balance, and it is not current (${balanceNotCurrentWhy(report)}); nothing was sent. Run ${CLI2} balance --refresh in a minute, then this command again, or give the amount, like ${UNSHIELD_EXAMPLE}`);
159753
+ }
159754
+ if (empty) {
159755
+ throw new PublicFailure(`no_spendable_account: the private balance is 0 USDC, so --amount all has nothing to send; nothing was sent. Add funds first (${CLI2} help add-funds)`);
159756
+ }
159757
+ const parts = joinParts(store);
159758
+ if (parts?.parts > 1) {
159759
+ throw new PublicFailure(`unshield_joining_accounts: the private balance (${usdcOr(report.atomic)}) is in ${parts.parts} parts, and one unshield sends from one part; nothing was sent. Join the parts first with ${CLI2} merge (a private move inside this wallet, no fee; about ${parts.minutes} minutes), then run ${CLI2} unshield --amount all --to <your wallet address> again`);
159760
+ }
159761
+ io.err(`--amount all: the whole private balance, ${usdcOr(report.atomic)}
159762
+ `);
159763
+ return report.atomic;
159764
+ }
159482
159765
  async function runUnshieldCancel(values, deps, io) {
159483
159766
  const walletFile = resolve38(values.get("--wallet"));
159484
159767
  const passphrase2 = payerPassphrase(values, deps);
@@ -159997,7 +160280,7 @@ async function runBalance(args, values, deps, io) {
159997
160280
  let store;
159998
160281
  let earlierDeposits = [];
159999
160282
  if (values.has("--refresh")) {
160000
- const refreshed = await refreshSnapshot(args, values, deps);
160283
+ const refreshed = await refreshSnapshot(args.filter((word) => word !== "--json"), values, deps);
160001
160284
  warnUnsavedSync(refreshed.result, io);
160002
160285
  refreshed.save();
160003
160286
  await refreshed.prune();
@@ -160027,6 +160310,16 @@ async function runBalance(args, values, deps, io) {
160027
160310
  }
160028
160311
  if (config?.pool === store.state.pool && config.program && config.proofTools && config.memoService) report.next = `${CLI2} balance --refresh --fresh`;
160029
160312
  }
160313
+ if (deps.stdoutIsTTY?.() === true && !values.has("--json")) {
160314
+ const notCurrent = report.status !== "current" && report.status !== "empty";
160315
+ io.out(`${[
160316
+ privateBalanceLine(report),
160317
+ ...shown.map((entry) => entry.notice).filter((notice) => typeof notice === "string" && notice),
160318
+ ...notCurrent && typeof report.next === "string" ? [`next: ${report.next}`] : []
160319
+ ].join("\n")}
160320
+ `);
160321
+ return;
160322
+ }
160030
160323
  io.out(`${JSON.stringify(shown.length ? { ...report, earlierDeposits: shown } : report, null, 2)}
160031
160324
  `);
160032
160325
  }
@@ -160749,7 +161042,7 @@ var VERSION_WORDS = /* @__PURE__ */ new Set(["version", "--version", "-v"]);
160749
161042
  function withoutConnectionFlags(argv2) {
160750
161043
  const out = [];
160751
161044
  for (let index = 0; index < argv2.length; index += 1) {
160752
- if (argv2[index] === "--tor" || argv2[index].startsWith("--ohttp-relay=")) continue;
161045
+ if (argv2[index] === "--tor" || argv2[index] === "--no-tor" || argv2[index].startsWith("--ohttp-relay=")) continue;
160753
161046
  if (argv2[index] === "--proxy" || argv2[index] === "--ohttp-relay") {
160754
161047
  index += 1;
160755
161048
  continue;
@@ -160946,10 +161239,13 @@ next: fund that address, then rerun the same command; it continues where it stop
160946
161239
  ` : /^p03_float_request_expired_nothing_paid/.test(reason) ? `why: ${floatExpiredWhy(floatError)}
160947
161240
  ` : "";
160948
161241
  const ambiguousLine = /^p03_float_refund_pending/.test(reason) && (error?.ambiguous ?? error?.cause?.ambiguous) === true && floatError.refundArrived !== true ? `note: ${FLOAT_AMBIGUOUS_NOTE}
161242
+ ` : "";
161243
+ const madeId = /use_new_unshield_id|new --unshield-id/.test(`${reason} ${hint ?? ""}`) ? void 0 : values?.get?.(UNSHIELD_MADE_ID);
161244
+ const idLine = madeId ? `this unshield's id is ${madeId}: to rerun or finish it, run the same command with --unshield-id ${madeId} added (it never sends twice)
160949
161245
  ` : "";
160950
161246
  io.err(`${CLI2} ${command2.name} failed: ${reason}
160951
161247
  ${sellerLine}${landedLine}${whyLine}${ambiguousLine}${blockingLine}${hint ? `hint: ${hint}
160952
- ` : ""}Help: ${CLI2} help ${command2.name}
161248
+ ` : ""}${idLine}Help: ${CLI2} help ${command2.name}
160953
161249
  `);
160954
161250
  return 1;
160955
161251
  } finally {
@@ -160961,10 +161257,10 @@ ${sellerLine}${landedLine}${whyLine}${ambiguousLine}${blockingLine}${hint ? `hin
160961
161257
  init_proxy();
160962
161258
  var argv = process.argv.slice(2);
160963
161259
  setCliEntry(process.argv[1] ? resolve39(process.argv[1]) : void 0);
160964
- var wrapper = runNetworkMode() === null && (argv.includes("--tor") || argv.includes("--proxy"));
161260
+ var entryArgv = runNetworkMode() === null ? torArgv(argv) : argv;
161261
+ var wrapper = runNetworkMode() === null && (entryArgv.includes("--tor") || entryArgv.includes("--proxy"));
160965
161262
  var stopSetupWatch = process.env[KEEP_WARM_ONCE_PARENT_ENV] === "1" && !wrapper ? watchSetupParentPipe() : void 0;
160966
161263
  var mcpRefused = async (options) => (await Promise.resolve().then(() => (init_torMissingMcp(), torMissingMcp_exports))).serveTorMissingMcp(options);
160967
- var entryArgv = runNetworkMode() === null ? torArgv(argv) : argv;
160968
161264
  var code = isHelpRequest(argv) ? await runShinjukuWallet(argv) : await proxyEntry(entryArgv, runShinjukuWallet, { mcpRefused });
160969
161265
  stopSetupWatch?.();
160970
161266
  process.exitCode = code;