shinjuku-shield 0.3.84 → 0.3.86

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.84. The npm packages `shinjuku-shield` and
9
+ This is version 0.3.86. 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.84 setup
15
+ npx -y --no-audit --no-fund shinjuku-shield@0.3.86 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.84
27
+ npm install -g shinjuku@0.3.86
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.84 mcp
39
+ claude mcp add --scope user shinjuku-shielded -- npx -y --no-audit --no-fund shinjuku-shield@0.3.86 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.84", "mcp"] } } }
45
+ { "mcpServers": { "shinjuku-shielded": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.86", "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": "b73f57e58ae0466a090f5b1728e7852bab48e7d28ad6bb0f0ba9657b6cc681b1",
5
- "bytes": 12505075,
6
- "sourceCommit": "52a8e17d03018f2cc026f9f9d306a785ef4da9f5",
4
+ "sha256": "b650bf0682830e05207f52bad2ba426ec7f8fa1bbbfdb7e028057865e3d83d24",
5
+ "bytes": 12522998,
6
+ "sourceCommit": "435a594c2af5c17d16a0f700bf2d9b7ff79af533",
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-23T19:32:52.495Z"
1285
+ "publishedAt": "2026-10-23T21:42:59.534Z"
1286
1286
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "shinjuku-shield",
3
- "version": "0.3.84",
3
+ "version": "0.3.86",
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": "b73f57e5",
7
- "shinjukuSha256": "b73f57e58ae0466a090f5b1728e7852bab48e7d28ad6bb0f0ba9657b6cc681b1",
6
+ "shinjukuRelease": "b650bf06",
7
+ "shinjukuSha256": "b650bf0682830e05207f52bad2ba426ec7f8fa1bbbfdb7e028057865e3d83d24",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-mcp#readme",
@@ -47222,6 +47222,7 @@ function reexec(args, mode, plainProxy) {
47222
47222
  const env = {
47223
47223
  ...process.env,
47224
47224
  NODE_USE_ENV_PROXY: "1",
47225
+ NODE_OPTIONS: proxiedNodeOptions(process.env.NODE_OPTIONS),
47225
47226
  ...proxyEnv(proxy),
47226
47227
  [MARK]: `${mode} ${proxy}`
47227
47228
  };
@@ -47305,7 +47306,7 @@ async function proxyEntry(argv2, run5, { err = (text3) => process.stderr.write(t
47305
47306
  }
47306
47307
  return reexec(options.args, options.mode, options.proxy);
47307
47308
  }
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;
47309
+ 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, proxiedNodeOptions, TOR_FROM_CONFIG_ENV, TOR_FROM_CONFIG_HINT;
47309
47310
  var init_proxy = __esm({
47310
47311
  "wallet/src/net/proxy.mjs"() {
47311
47312
  "use strict";
@@ -47402,6 +47403,10 @@ var init_proxy = __esm({
47402
47403
  laneDispatchers = /* @__PURE__ */ new Map();
47403
47404
  PROXY_WARNING_FLAG = "--disable-warning=UNDICI-EHPA";
47404
47405
  proxiedExecArgv = (execArgv = process.execArgv) => execArgv.includes(PROXY_WARNING_FLAG) ? [...execArgv] : [...execArgv, PROXY_WARNING_FLAG];
47406
+ proxiedNodeOptions = (nodeOptions = process.env.NODE_OPTIONS) => {
47407
+ const options = String(nodeOptions ?? "").trim();
47408
+ return options.split(/\s+/).includes(PROXY_WARNING_FLAG) ? options : `${options} ${PROXY_WARNING_FLAG}`.trim();
47409
+ };
47405
47410
  TOR_FROM_CONFIG_ENV = "SHINJUKU_WALLET_TOR_FROM_CONFIG";
47406
47411
  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)';
47407
47412
  }
@@ -62798,7 +62803,7 @@ async function readRelayConfig({ serviceUrl: serviceUrl2, fetchImpl, id }) {
62798
62803
  async function submitRelayedDeposit(input) {
62799
62804
  return withWalletLease(input.store, input.deps ?? {}, () => submitRelayedDepositLeased(input));
62800
62805
  }
62801
- async function submitRelayedDepositLeased({ store, connection, programId, proofTools, depositId, serviceUrl: serviceUrl2, deps = {} }) {
62806
+ async function submitRelayedDepositLeased({ store, connection, programId, proofTools, depositId, serviceUrl: serviceUrl2, historyUrl, deps = {} }) {
62802
62807
  assertDepositId(depositId);
62803
62808
  const name = walName(depositId);
62804
62809
  const record = store.readWal(name);
@@ -62812,7 +62817,8 @@ async function submitRelayedDepositLeased({ store, connection, programId, proofT
62812
62817
  const source = new import_web3101.PublicKey(record.relay.tokenAccount);
62813
62818
  const pause3 = deps.pause ?? delay;
62814
62819
  const fetchImpl = deps.fetchImpl ?? fetch;
62815
- const loadHistory = () => finalizedHistory({ store, connection, id, proofTools, deps });
62820
+ const historyConnection = connection.historyRpcChoice !== void 0 || connection.historyRpc ? connection : connectSplit(connection, historyUrl);
62821
+ const loadHistory = () => finalizedHistory({ store, connection: historyConnection, id, proofTools, deps });
62816
62822
  const refuse19 = async (reason) => {
62817
62823
  record.relay.approvedOnChain = (await relaySource(connection, id, record)).approved;
62818
62824
  record.relay.lastRefusal = { reason, at: (/* @__PURE__ */ new Date()).toISOString() };
@@ -62981,8 +62987,42 @@ function settleTransaction(connection, signature2) {
62981
62987
  maxSupportedTransactionVersion: 1
62982
62988
  }));
62983
62989
  }
62990
+ async function foundSettle(connection, id, record, intent) {
62991
+ let entries;
62992
+ try {
62993
+ if (typeof connection.getSignaturesForAddress !== "function") return void 0;
62994
+ entries = await retryRpc2(() => connection.getSignaturesForAddress(intent.address, { limit: SETTLE_SEARCH_LIMIT }, "finalized"));
62995
+ } catch {
62996
+ return void 0;
62997
+ }
62998
+ if (!Array.isArray(entries)) return void 0;
62999
+ for (const entry of [...entries].reverse()) {
63000
+ if (entry?.err !== null || typeof entry.signature !== "string") continue;
63001
+ const transaction = await settleTransaction(connection, entry.signature).catch(() => void 0);
63002
+ if (!transaction?.meta || transaction.meta.err) continue;
63003
+ const landing = returnedIntent(transaction, id, record);
63004
+ if (landing) return { signature: entry.signature, transaction, landing };
63005
+ }
63006
+ return void 0;
63007
+ }
62984
63008
  async function relayReceipt({ connection, id, record, intent, loadHistory, transaction: read, walkMs }) {
62985
63009
  const signature2 = record.relay.result?.signature;
63010
+ if (!signature2) {
63011
+ const found = await foundSettle(connection, id, record, intent);
63012
+ if (found) {
63013
+ const { transaction, landing } = found;
63014
+ record.relay.result = { signature: found.signature, slot: transaction.slot };
63015
+ return {
63016
+ intent: intent.address.toBase58(),
63017
+ indexAfter: landing.indexAfter,
63018
+ signature: found.signature,
63019
+ slot: transaction.slot,
63020
+ feeLamports: transaction.meta.fee,
63021
+ broadcaster: landing.broadcaster.toBase58(),
63022
+ source: landing.source.toBase58()
63023
+ };
63024
+ }
63025
+ }
62986
63026
  if (signature2) {
62987
63027
  const transaction = (read ? await read : void 0) ?? await settleTransaction(connection, signature2);
62988
63028
  const landing = transaction?.meta && !transaction.meta.err ? returnedIntent(transaction, id, record) ?? settleFunder(transaction, id, intent.address) : void 0;
@@ -63378,7 +63418,7 @@ async function publishDepositMemo({ serviceUrl: serviceUrl2, record, fetchImpl =
63378
63418
  const stored = await fetchMemoFromService(serviceUrl2, record.memoDigest, { fetchImpl });
63379
63419
  if (!stored || !Buffer.from(stored).equals(bytes2)) fail11("memo_publish_readback_mismatch");
63380
63420
  }
63381
- var import_web3101, DEPOSIT_WAL_SCHEMA, P03_BINDING, BUFFER_SIZE, DEPOSIT_INTENT_LEN, DEPOSIT_RECORD_LEN, CLOSE_BUFFER, COMPACT_DEPOSIT_INTENT, BUFFER_LIFETIME_SLOTS, BUFFER_MIN_REMAINING_SLOTS, FEE_MARGIN_LAMPORTS, RESOLVED_STATES, LANDED_STATES, hex, field32, walName, LANDED_WALK_MS, SLOW_WALK, delay, DEPOSIT_RELAY_SCHEMA, DEPOSIT_RECLAIM_SCHEMA, DEPOSIT_RELAY_CONFIG_SCHEMA, APPROVE_COMPUTE_UNITS, RECLAIM_COMPUTE_UNITS, RELAY_ROUNDS, RELAY_KEY_PROBES, MAX_SCAN_COUNT, RELAY_REQUEST_TIMEOUT_MS, RELAY_MAX_WAIT_SECONDS, RELAY_REFUSALS, RECLAIM_REFUSALS, relayHex, SPLIT_MAX, DEFAULT_SPLIT_PARTS, SPLIT_UNIT_ATOMIC, FUNDS_PLAN_SCHEMA, FUNDS_PLAN_PREFIX, relayKeyPool, __test;
63421
+ var import_web3101, DEPOSIT_WAL_SCHEMA, P03_BINDING, BUFFER_SIZE, DEPOSIT_INTENT_LEN, DEPOSIT_RECORD_LEN, CLOSE_BUFFER, COMPACT_DEPOSIT_INTENT, BUFFER_LIFETIME_SLOTS, BUFFER_MIN_REMAINING_SLOTS, FEE_MARGIN_LAMPORTS, RESOLVED_STATES, LANDED_STATES, hex, field32, walName, LANDED_WALK_MS, SLOW_WALK, delay, DEPOSIT_RELAY_SCHEMA, DEPOSIT_RECLAIM_SCHEMA, DEPOSIT_RELAY_CONFIG_SCHEMA, APPROVE_COMPUTE_UNITS, RECLAIM_COMPUTE_UNITS, RELAY_ROUNDS, RELAY_KEY_PROBES, MAX_SCAN_COUNT, RELAY_REQUEST_TIMEOUT_MS, RELAY_MAX_WAIT_SECONDS, RELAY_REFUSALS, RECLAIM_REFUSALS, relayHex, SPLIT_MAX, DEFAULT_SPLIT_PARTS, SPLIT_UNIT_ATOMIC, FUNDS_PLAN_SCHEMA, FUNDS_PLAN_PREFIX, relayKeyPool, SETTLE_SEARCH_LIMIT, __test;
63382
63422
  var init_deposit = __esm({
63383
63423
  "wallet/src/deposit/deposit.mjs"() {
63384
63424
  "use strict";
@@ -63486,6 +63526,7 @@ var init_deposit = __esm({
63486
63526
  FUNDS_PLAN_SCHEMA = "carbon-shielded-wallet/add-funds/v1";
63487
63527
  FUNDS_PLAN_PREFIX = "add-funds-";
63488
63528
  relayKeyPool = (store, record) => record.relay.keyPool ?? record.pool ?? store.state.pool;
63529
+ SETTLE_SEARCH_LIMIT = 10;
63489
63530
  __test = Object.freeze({ parseBuffer });
63490
63531
  }
63491
63532
  });
@@ -65833,6 +65874,27 @@ var init_errorCodes = __esm({
65833
65874
  "relay_reclaim_nothing_held",
65834
65875
  "relay_refused_deposit_body_invalid",
65835
65876
  "relay_refused_deposit_replay_conflict",
65877
+ // The exit relay's refusals (ShieldedSolanaP03ExitRelay.ts, createShieldedFacilitatorRoutes.ts), as
65878
+ // p03_unshield_relay_refused_<reason> (T16, 2026-10-23: an unknown one printed a bare p03_resource_cli_failed).
65879
+ "exit_amount_invalid",
65880
+ "exit_anchor_not_retained",
65881
+ "exit_body_invalid",
65882
+ "exit_buffer_unavailable",
65883
+ "exit_memo_digest_mismatch",
65884
+ "exit_operator_review_required",
65885
+ "exit_pending_retry_same_request",
65886
+ "exit_pool_busy_retry_same_request",
65887
+ "exit_pool_domain_mismatch",
65888
+ "exit_pool_invalid",
65889
+ "exit_proof_invalid",
65890
+ "exit_quote_expired",
65891
+ "exit_relay_busy",
65892
+ "exit_relay_unavailable",
65893
+ "exit_relayer_underfunded",
65894
+ "exit_replay_conflict",
65895
+ "exit_replay_consumed",
65896
+ "exit_settlement_not_landed_retry_same_request",
65897
+ "exit_vault_insufficient",
65836
65898
  "relay_relayer_underfunded_retry_later",
65837
65899
  "relay_service_required",
65838
65900
  "relay_transaction_invalid",
@@ -75305,6 +75367,7 @@ async function runSelfCustodyDeposit(args) {
75305
75367
  throw new Error("deposit requires --program, --deposit-id, --amount <atomic>, --proof-tools, and --rpc-file <file> (or --rpc, or UNDERMARKET_SOLANA_RPC_URL)");
75306
75368
  }
75307
75369
  const proofTools = loadP03PublicPaymentProofTools(flag(args, "--proof-tools"));
75370
+ const historyUrl = historyRpcUrl(flag(args, "--history-rpc"));
75308
75371
  const path = walletPath(pool2);
75309
75372
  const relay = args.includes("--relay");
75310
75373
  const indexFlag = flag(args, "--one-time-index");
@@ -75361,7 +75424,7 @@ async function runSelfCustodyDeposit(args) {
75361
75424
  }, null, 2));
75362
75425
  return;
75363
75426
  }
75364
- const result = await (relay ? submitRelayedDeposit : submitDeposit)({ ...common, serviceUrl: flag(args, "--service") });
75427
+ const result = await (relay ? submitRelayedDeposit : submitDeposit)({ ...common, serviceUrl: flag(args, "--service"), ...relay ? { historyUrl } : {} });
75365
75428
  console.log(JSON.stringify({
75366
75429
  ...result,
75367
75430
  next: result.state === "published" ? "run recover; export and verify two fresh backups" : "rerun with --submit --service <facilitator URL> to publish the memo"
@@ -107699,10 +107762,23 @@ function markedAnchorRequest(requestAnchor) {
107699
107762
  }
107700
107763
  async function exitWhenAnchored(run5, { now = Date.now, sleep: sleep6 = (ms) => new Promise((resolve40) => setTimeout(resolve40, ms)), ring } = {}) {
107701
107764
  const deadline = now() + EXIT_ANCHOR_WAIT_MS;
107765
+ let reproved = false;
107766
+ let clockWaits = 0;
107702
107767
  for (; ; ) {
107703
107768
  try {
107704
107769
  return await run5();
107705
107770
  } catch (error) {
107771
+ if (!reproved && /p03_unshield_relay_refused_exit_quote_expired/.test(String(error?.message))) {
107772
+ reproved = true;
107773
+ progress("the exit proof's time window ended before the facilitator's exit transaction landed; nothing was sent. The wallet makes a new exit proof now and sends it");
107774
+ continue;
107775
+ }
107776
+ if (reproved && clockWaits < EXPIRED_PROOF_CLOCK_WAITS && /p03_note_exit_proved_still_valid_rerun_later/.test(String(error?.message))) {
107777
+ clockWaits += 1;
107778
+ progress(`the finalized chain clock has not passed the old exit proof's end yet; waiting ${EXPIRED_PROOF_CLOCK_WAIT_MS / 1e3} s, then the wallet makes the new proof (wait ${clockWaits}/${EXPIRED_PROOF_CLOCK_WAITS}; nothing was sent)`);
107779
+ await sleep6(EXPIRED_PROOF_CLOCK_WAIT_MS);
107780
+ continue;
107781
+ }
107706
107782
  if (!/p03_note_exit_anchor_not_current_wait_next_epoch/.test(String(error?.message))) throw error;
107707
107783
  const start = now();
107708
107784
  const subAnchors = subAnchorPool(start) !== void 0;
@@ -107739,7 +107815,13 @@ function facilitatorExitRelay(origin, { fetchImpl = fetch, pause: pause3 = (ms)
107739
107815
  const answer = await response.json().catch(() => ({}));
107740
107816
  if (response.status === 200 && answer.success === true && isSolanaSignature(answer.transaction)) return answer.transaction;
107741
107817
  if (response.status === 200 && answer.success === true) fail29("relay_transaction_invalid");
107742
- if (response.status === 200) fail29(`relay_refused_${String(answer.errorReason ?? "unknown").replace(/[^a-z0-9_]/g, "")}`);
107818
+ const refused = String(answer.errorReason ?? "unknown").replace(/[^a-z0-9_]/g, "");
107819
+ if (response.status === 200 && RELAY_RETRY_SAME.test(refused) && attempt < attempts) {
107820
+ progress(`waiting: the facilitator's exit relay answered ${refused} (its exit transaction has not landed yet); sending the same exit again in ${RELAY_RETRY_SAME_SECONDS} s (retry ${attempt}/${attempts - 1}). Nothing is lost: the note is still yours, and a rerun of the same command continues`);
107821
+ await pause3(RELAY_RETRY_SAME_SECONDS * 1e3);
107822
+ continue;
107823
+ }
107824
+ if (response.status === 200) fail29(`relay_refused_${refused}`);
107743
107825
  if (![429, 503].includes(response.status) || attempt >= attempts) fail29(`relay_status_${response.status}_retry_same_unshield`);
107744
107826
  const seconds2 = Number(response.headers.get("retry-after"));
107745
107827
  const waitSeconds = Number.isFinite(seconds2) && seconds2 > 0 ? Math.min(seconds2, 120) : 15;
@@ -107897,7 +107979,7 @@ async function unshieldOnce({ options, origin, historyUrl, walletPath: walletPat
107897
107979
  store.writeWal(`self-pay-${unshieldId}`, { ...store.readWal(`self-pay-${unshieldId}`), state: "exited", exitTransaction: exit.signature });
107898
107980
  return { amountAtomic: String(exit.amountAtomic), selfPayTransaction: paid.transaction, exitTransaction: exit.signature, replayed: exit.replayed };
107899
107981
  }
107900
- var import_web3121, USAGE2, REQUIRED2, OPTIONAL, fail29, progress, EPOCH_MS2, EPOCH_MARGIN_MS, EXIT_ANCHOR_WAIT_MS;
107982
+ var import_web3121, USAGE2, REQUIRED2, OPTIONAL, fail29, RELAY_RETRY_SAME, RELAY_RETRY_SAME_SECONDS, progress, EPOCH_MS2, EPOCH_MARGIN_MS, EXIT_ANCHOR_WAIT_MS, EXPIRED_PROOF_CLOCK_WAITS, EXPIRED_PROOF_CLOCK_WAIT_MS;
107901
107983
  var init_cli2 = __esm({
107902
107984
  "wallet/src/unshield/cli.mjs"() {
107903
107985
  "use strict";
@@ -107942,6 +108024,8 @@ var init_cli2 = __esm({
107942
108024
  fail29 = (reason) => {
107943
108025
  throw new Error(`p03_unshield_${reason}`);
107944
108026
  };
108027
+ RELAY_RETRY_SAME = /_retry_same_request$|^p03_e1_value_pending$|^p03_e1_proof_unavailable$|^p03_validity_proof_unavailable$/;
108028
+ RELAY_RETRY_SAME_SECONDS = 10;
107945
108029
  progress = (text3) => {
107946
108030
  try {
107947
108031
  process.stderr.write(`${text3}
@@ -107952,6 +108036,8 @@ var init_cli2 = __esm({
107952
108036
  EPOCH_MS2 = 3e5;
107953
108037
  EPOCH_MARGIN_MS = 35e3;
107954
108038
  EXIT_ANCHOR_WAIT_MS = EPOCH_MS2 + 6e4;
108039
+ EXPIRED_PROOF_CLOCK_WAITS = 8;
108040
+ EXPIRED_PROOF_CLOCK_WAIT_MS = 15e3;
107955
108041
  if (process.argv[1] && resolve19(process.argv[1]) === fileURLToPath13(new URL("__bundled_source__/wallet/src/unshield/cli.mjs", import.meta.url).href)) {
107956
108042
  main3().catch((error) => {
107957
108043
  console.error(describeCliErrorWithHint(error));
@@ -109894,6 +109980,12 @@ function refusalSay(a, tool, again, npmVersion) {
109894
109980
  if (code2 === "mcp_move_count_unreadable") {
109895
109981
  return "Nothing was moved. This wallet's 24-hour limit cannot be checked just now. Try again in a minute; if it repeats, tell your owner.";
109896
109982
  }
109983
+ if (code2 === "inference_ceiling_over_cap" && a.capKind === "call" && usdcText(a.capAtomic) && usdcText(a.ceilingAtomic) && usdcText(a.capNeededAtomic)) {
109984
+ const needed = usdcText(a.capNeededAtomic);
109985
+ const pays = usdcText(a.paysUpToAtomic);
109986
+ const allow = pays && BigInt(a.paysUpToAtomic) < BigInt(a.capNeededAtomic) ? `Allow ${needed} USDC; it still pays only up to ${pays} USDC.` : `Allow ${needed} USDC to pay it.`;
109987
+ return `Nothing was paid: this request's ${usdcText(a.capAtomic)} USDC cap is below the ${usdcText(a.ceilingAtomic)} USDC ceiling. ${allow}`;
109988
+ }
109897
109989
  if ((code2 === "inference_ceiling_over_cap" || code2 === "p03_float_over_maximum") && usdcText(a.totalAtomic) && usdcText(a.capAtomic)) {
109898
109990
  return `Nothing was paid: up to ${usdcText(a.totalAtomic)} USDC is over your ${usdcText(a.capAtomic)} USDC limit. ${raiseLine("--max-payment", npmVersion)}`;
109899
109991
  }
@@ -127738,6 +127830,15 @@ ${canonical5}`;
127738
127830
  const proxied = () => typeof ops.proxied === "function" ? ops.proxied() : proxyProvenAt() !== void 0;
127739
127831
  const usd = (atomic4) => `$${formatAtomic(atomic4, 6)}`;
127740
127832
  const INFERENCE_COST_WORDS = "A short qwen3-8-flash answer pays about 0.0011 USDC: usePod's price (often 0.00001-0.0001 USDC) plus the Shinjuku fee (0.001 USDC). The call's cap (max_payment, else --max-payment) must still cover the call's ceiling: max_tokens at the model's output price plus the prompt, and at least 0.01 USDC";
127833
+ const paysUpToAtomic = (inference, asked) => {
127834
+ try {
127835
+ const extra = inference.listModels().find((model) => model.id === asked.model).priceOutPerMAtomic * 100n;
127836
+ const lifted = BigInt(inference.ceilingAtomic({ ...asked, maxTokens: asked.maxTokens + 1e8 }));
127837
+ return lifted > BigInt(inference.USEPOD_MIN_PAYMENT_ATOMIC ?? 10000n) ? lifted - extra + 1000n : void 0;
127838
+ } catch {
127839
+ return void 0;
127840
+ }
127841
+ };
127741
127842
  const moreTokens = (maxTokens) => Math.max(1024, 2 * maxTokens);
127742
127843
  const torRefusal = () => refuse12(
127743
127844
  "inference_tor_required",
@@ -127827,7 +127928,7 @@ ${canonical5}`;
127827
127928
  const inferenceChat2 = {
127828
127929
  name: "inference_chat",
127829
127930
  title: "Ask an AI model, paid privately",
127830
- description: `Use this when the user wants an AI model's answer paid from the SHIELDED balance: it calls usePod's x402 chat endpoint (usePod picks the provider) and pays it privately (the chain does not show which wallet paid); use this tool, not x402_pay, for usePod. Only prompt (or messages) is needed: model defaults to qwen3-8-flash and max_tokens to 512. Called with neither prompt nor messages, it lists the models and their prices and pays nothing (for example qwen3-8-flash, deepseek-v4-1-flash, claude-opus-5). COST: a short qwen3-8-flash answer pays about 0.0011 USDC (usePod's price, often 0.00001-0.0001 USDC, plus the Shinjuku fee 0.001 USDC). The call's cap (max_payment, in USDC like "0.05", or max_payment_atomic; else --max-payment) must still cover the call's ceiling: max_tokens of output plus the prompt at the model's prices, and at least 0.01 USDC (usePod's floor for its standard offer). The total paid is never above the cap. The unused part is NOT refunded when the answer is shorter. max_tokens must be at least ${INFERENCE_MIN_MAX_TOKENS} (a reasoning model spends its first tokens thinking). Example: { "prompt": "Name three prime numbers" }. PRIVACY: usePod and the model's provider see the prompt and the answer; never put keys, passphrases, or secrets in it. It runs only when this MCP server was started with --tor or --proxy (else inference_tor_required, nothing paid), so they never see this machine's IP. The same caps as x402_pay apply (max_payment can only lower --max-payment). When the call can cost more than --max-payment and the MCP client can ask the user, the wallet asks the user once to allow this one call; else the answer says the cost and the limit. On an error code that ends in _retry_same_request, call inference_chat again with the SAME request_id and arguments: it never pays twice. No streaming.`,
127931
+ description: `Use this when the user wants an AI model's answer paid from the SHIELDED balance: it calls usePod's x402 chat endpoint (usePod picks the provider) and pays it privately (the chain does not show which wallet paid); use this tool, not x402_pay, for usePod. Only prompt (or messages) is needed: model defaults to qwen3-8-flash and max_tokens to 512. Called with neither prompt nor messages, it lists the models and their prices and pays nothing (for example qwen3-8-flash, deepseek-v4-1-flash, claude-opus-5). COST: a short qwen3-8-flash answer pays about 0.0011 USDC (usePod's price, often 0.00001-0.0001 USDC, plus the Shinjuku fee 0.001 USDC). The call's cap (max_payment, in USDC like "0.05", or max_payment_atomic; else --max-payment) must still cover the call's ceiling: max_tokens of output plus the prompt at the model's prices, and at least 0.01 USDC (usePod's floor for its standard offer). The total paid is never above the cap, and the cap is only a limit: a larger cap never pays more. The unused part of max_tokens is NOT refunded when the answer is shorter. max_tokens must be at least ${INFERENCE_MIN_MAX_TOKENS} (a reasoning model spends its first tokens thinking). Example: { "prompt": "Name three prime numbers" }. PRIVACY: usePod and the model's provider see the prompt and the answer; never put keys, passphrases, or secrets in it. It runs only when this MCP server was started with --tor or --proxy (else inference_tor_required, nothing paid), so they never see this machine's IP. The same caps as x402_pay apply (max_payment can only lower --max-payment). When the call can cost more than --max-payment and the MCP client can ask the user, the wallet asks the user once to allow this one call; else the answer says the cost and the limit. On an error code that ends in _retry_same_request, call inference_chat again with the SAME request_id and arguments: it never pays twice. No streaming.`,
127831
127932
  inputSchema: {
127832
127933
  type: "object",
127833
127934
  properties: {
@@ -127930,15 +128031,24 @@ ${canonical5}`;
127930
128031
  let askedLimit = false;
127931
128032
  let narrowed = cap.maxPaymentAtomic;
127932
128033
  if (ceiling > BigInt(narrowed)) {
127933
- const detail = `up to ${args.max_tokens} output tokens on ${args.model} have a ceiling of ${usdcText2(ceiling)} USDC, above this call's cap of ${usdcText2(narrowed)} USDC; the cap must be at least the ceiling; nothing was paid`;
128034
+ const detail = `up to ${args.max_tokens} output tokens on ${args.model} have a ceiling of ${usdcText2(ceiling)} USDC, above this call's cap of ${usdcText2(narrowed)} USDC; the cap must be at least the ceiling; nothing was paid; the cap is only a limit, not the price`;
127934
128035
  const atFloor = ceiling <= BigInt(inference.USEPOD_MIN_PAYMENT_ATOMIC ?? 10000n);
127935
- const pays = atFloor ? `The cap must be at least the ceiling ${usdcText2(ceiling)} USDC; the call usually pays about 0.0011 USDC (usePod's price plus the 0.001 USDC Shinjuku fee).` : `The cap must be at least the ceiling ${usdcText2(ceiling)} USDC; the call pays usePod's price for these max_tokens, at most the ceiling, plus the 0.001 USDC Shinjuku fee.`;
128036
+ const paysUpTo = paysUpToAtomic(inference, { model: args.model, messages, maxTokens: args.max_tokens });
128037
+ const pays = `A cap (max_payment, or --max-payment) is only a limit, not the price: the call pays usePod's price for its max_tokens plus the 0.001 USDC Shinjuku fee${paysUpTo === void 0 ? "" : ` (up to ${usdcText2(paysUpTo)} USDC in all at this model's listed prices)`}, and a larger cap never makes it pay more. The cap must still be at least the ceiling ${usdcText2(ceiling)} USDC.`;
127936
128038
  if (BigInt(narrowed) !== BigInt(launch)) {
127937
128039
  return refuse12(
127938
128040
  "inference_ceiling_over_cap",
127939
128041
  detail,
127940
128042
  atFloor ? `${pays} So pass max_payment "${usdcText2(ceiling + 1000n)}" (the ceiling plus the fee) or more if the spending limit allows it, or leave max_payment out` : `${pays} So lower max_tokens (at least ${INFERENCE_MIN_MAX_TOKENS}), pick a cheaper model (inference_chat with no prompt lists them), or pass max_payment "${usdcText2(ceiling + 1000n)}" (the ceiling plus the fee) if the spending limit allows it`,
127941
- { requestId, paid: false, ...facts }
128043
+ {
128044
+ requestId,
128045
+ paid: false,
128046
+ ...facts,
128047
+ capKind: "call",
128048
+ capAtomic: String(narrowed),
128049
+ capNeededAtomic: (ceiling + 1000n).toString(),
128050
+ ...paysUpTo === void 0 ? {} : { paysUpToAtomic: paysUpTo.toString() }
128051
+ }
127942
128052
  );
127943
128053
  }
127944
128054
  askedLimit = true;
@@ -151811,7 +151921,7 @@ function parseArgs2(args) {
151811
151921
  if (!options["dry-run"] && FOR_RUN.some((key) => !options[key])) fail41("arguments_missing");
151812
151922
  return options;
151813
151923
  }
151814
- async function main5(args = process.argv.slice(2)) {
151924
+ async function main5(args = process.argv.slice(2), { output: output2 = (result) => console.log(JSON.stringify(result)) } = {}) {
151815
151925
  if (args.length === 1 && args[0] === "--help") {
151816
151926
  console.log(USAGE3);
151817
151927
  return;
@@ -151894,20 +152004,20 @@ async function main5(args = process.argv.slice(2)) {
151894
152004
  return { id, amountAtomic: record.amountAtomic, accountCommitment: record.mergeAccountCommitment, noteCommitments: record.mergeNoteCommitments ?? [] };
151895
152005
  });
151896
152006
  if (dryRun) {
151897
- console.log(JSON.stringify({
152007
+ output2({
151898
152008
  dryRun: true,
151899
152009
  resume: resume.map((entry) => ({ amountAtomic: entry.amountAtomic, amount: usdc9(entry.amountAtomic) })),
151900
152010
  next: "run the same command without --dry-run: it finishes these merges first (never pays twice)"
151901
- }));
152011
+ });
151902
152012
  return;
151903
152013
  }
151904
152014
  const results2 = [];
151905
152015
  for (const entry of resume) results2.push(await runOne(entry));
151906
- console.log(JSON.stringify({
152016
+ output2({
151907
152017
  success: true,
151908
152018
  resumed: results2,
151909
152019
  next: "after the next pool anchor (up to 5 minutes; about 30 s on a pool with sub-anchors): balance --refresh, then rerun merge (it plans the next round, or says nothing is left) or your payment"
151910
- }));
152020
+ });
151911
152021
  return;
151912
152022
  }
151913
152023
  const snapshotRead = readBalanceSnapshot(store);
@@ -151915,8 +152025,8 @@ async function main5(args = process.argv.slice(2)) {
151915
152025
  if (balance.status !== "current" || snapshotRead.snapshot?.programId !== String(options.program)) fail41("balance_walk_not_current_run_balance_refresh");
151916
152026
  if (balance.detail.inFlight.some((item) => item.kind !== "merge")) fail41("in_flight_work_finish_first");
151917
152027
  const plan = planMerge({ snapshot: snapshotRead.snapshot, exitReserved: exitReservedNoteCommitments(store) });
151918
- const shown = (output2) => ({ amountAtomic: output2.valueAtomic, amount: usdc9(output2.valueAtomic) });
151919
- const merges = plan.merge.map((output2) => ({ id: nextMergeId(store, output2.commitment), amountAtomic: output2.valueAtomic, accountCommitment: output2.commitment })).filter((entry) => entry.id !== void 0);
152028
+ const shown = (output3) => ({ amountAtomic: output3.valueAtomic, amount: usdc9(output3.valueAtomic) });
152029
+ const merges = plan.merge.map((output3) => ({ id: nextMergeId(store, output3.commitment), amountAtomic: output3.valueAtomic, accountCommitment: output3.commitment })).filter((entry) => entry.id !== void 0);
151920
152030
  const summary = {
151921
152031
  totalAtomic: plan.totalAtomic,
151922
152032
  total: usdc9(plan.totalAtomic),
@@ -151928,19 +152038,19 @@ async function main5(args = process.argv.slice(2)) {
151928
152038
  const nothing = merges.length === 0;
151929
152039
  const why = !plan.keep || plan.merge.length + plan.later.length === 0 ? "nothing to merge: one funded account (or none) holds the shielded balance, so one payment already draws it all (with up to 3 notes)" : plan.merge.length === 0 ? `nothing to merge this round: a payment already draws ${plan.notes.length} notes, the most one spend takes is ${MAX_RESPEND_NOTES}. Make one payment first (its change joins those notes into one account), then rerun merge` : void 0;
151930
152040
  if (dryRun || nothing) {
151931
- console.log(JSON.stringify({ dryRun, ...summary, ...why ? { next: why } : nothing ? { next: "nothing left to merge" } : {
152041
+ output2({ dryRun, ...summary, ...why ? { next: why } : nothing ? { next: "nothing left to merge" } : {
151932
152042
  next: "run the same command without --dry-run (with --proof-tools, --profile, --facilitator); nothing was signed or sent"
151933
- } }));
152043
+ } });
151934
152044
  return;
151935
152045
  }
151936
152046
  const results = [];
151937
152047
  for (const entry of merges) results.push(await runOne(entry));
151938
- console.log(JSON.stringify({
152048
+ output2({
151939
152049
  success: true,
151940
152050
  ...summary,
151941
152051
  merged: results,
151942
152052
  next: plan.later.length ? "after the next pool anchor (up to 5 minutes; about 30 s on a pool with sub-anchors): make your payment (it draws the kept account and the new notes), then balance --refresh and rerun merge for the accounts in later" : "after the next pool anchor (up to 5 minutes; about 30 s on a pool with sub-anchors), rerun your payment: it draws the kept account plus the new notes. balance --refresh shows the new state"
151943
- }));
152053
+ });
151944
152054
  } finally {
151945
152055
  await release();
151946
152056
  }
@@ -154914,7 +155024,8 @@ function fundsView(store, plan, { now = Date.now(), running } = {}) {
154914
155024
  state: last?.stopped ? "stopped" : idle ? "not running" : last?.until ? "waiting" : "next",
154915
155025
  ...last?.waitsFor ? { waitsFor: last.waitsFor } : {},
154916
155026
  ...last?.until && !idle ? { until: last.until } : {},
154917
- ...last?.stopped ? { code: last.stopped } : {},
155027
+ // A wait's code (K29 T13): the terminal says the wait in words only.
155028
+ ...last?.stopped ? { code: last.stopped } : last?.code ? { code: last.code } : {},
154918
155029
  ...last?.said?.length ? { said: last.said } : {}
154919
155030
  };
154920
155031
  });
@@ -155087,20 +155198,44 @@ var TRANSIENT = [
155087
155198
  /^E[A-Z0-9_]{1,40}$/
155088
155199
  ];
155089
155200
  var SOON_CODES = /(_retry_same_|_rerun$|wallet_process_locked|wallet_locked|_wait_next_epoch|_anchor_not_finalized)/;
155090
- function transient(ctx, run5, waitsFor) {
155201
+ var RETRY_WORDS = [
155202
+ [/^p03_deposit_landed_history_walk_slow_retry_later$/, {
155203
+ now: "the deposit is in the pool; the wallet could not read the pool history in time to confirm it, so it reads again",
155204
+ waitsFor: "a new read of the pool history to confirm the deposit (the deposit is in the pool)"
155205
+ }],
155206
+ [/^p03_deposit_(landed_history_pending|receipt_transaction_unavailable|receipt_evidence_missing)_retry_later$/, {
155207
+ now: "the deposit is in the pool; the chain does not show the wallet its receipt yet, so it reads again",
155208
+ waitsFor: "the chain to show the deposit's receipt (the deposit is in the pool)"
155209
+ }],
155210
+ [/^p03_deposit_relay_not_settled_rerun_same_id$/, {
155211
+ now: "the facilitator has not moved the deposit into the pool yet, so the wallet asks again",
155212
+ waitsFor: "the facilitator to move the deposit into the pool"
155213
+ }],
155214
+ [/^rpc_unreachable$/, { now: "the RPC did not answer, so the wallet asks again", waitsFor: "an answer from the RPC (it did not answer)" }],
155215
+ [/^rpc_rate_limited$/, { now: "the RPC asked the wallet to slow down, so it asks again", waitsFor: "the RPC to take requests again (it asked the wallet to slow down)" }],
155216
+ [/^(wallet_process_locked|p03_pocket_cli_wallet_locked)$/, {
155217
+ now: "another wallet command uses this wallet right now, so add-funds tries again",
155218
+ waitsFor: "the other wallet command to finish with this wallet"
155219
+ }]
155220
+ ];
155221
+ var RETRY_ANY = { now: "the step did not finish this time and nothing was lost, so it runs again", waitsFor: "a new try of the step (nothing was lost)" };
155222
+ var NO_CODE = { now: "the step ended without an error code (it was stopped, or a connection was refused), so it runs again", waitsFor: "a new try of the step (it ended without an error code)" };
155223
+ function transient(ctx, run5, words2) {
155091
155224
  ctx.transient += 1;
155092
- if (ctx.transient > MAX_TRANSIENT) return stop2(run5, `the step failed ${MAX_TRANSIENT} times in a row (${run5.code ?? "no error code"})`);
155093
- return wait(has(run5, SOON_CODES) ? SOON_MS : LATER_MS, waitsFor, { transient: true });
155225
+ if (ctx.transient > MAX_TRANSIENT) return { ...stop2(run5, words2.waitsFor), tries: true };
155226
+ const code2 = run5.code ?? run5.codes[0];
155227
+ return wait(has(run5, SOON_CODES) ? SOON_MS : LATER_MS, words2.waitsFor, { transient: true, said: words2.now, ...code2 ? { code: code2 } : {} });
155094
155228
  }
155095
155229
  function otherwise(ctx, run5) {
155096
155230
  if (run5.aborted) return stop2(run5, "stopped");
155097
155231
  if (run5.exit === 2) return stop2(run5, "the step's command line was refused");
155098
- if (!run5.code && !run5.codes.length) return transient(ctx, run5, "a new try of the step (it ended without an error code)");
155232
+ if (!run5.code && !run5.codes.length) return transient(ctx, run5, NO_CODE);
155099
155233
  if (has(run5, FACILITATOR_LATER)) {
155100
155234
  return wait(LATER_MS, has(run5, /_paused_retry_later$/) ? "the facilitator to take this step again (it is paused right now)" : "the facilitator's relayer to have its fee money again");
155101
155235
  }
155102
155236
  if (run5.codes.some((code2) => TRANSIENT.some((pattern) => pattern.test(code2)))) {
155103
- return transient(ctx, run5, `a new try (${run5.code ?? run5.codes[0]}: a later run clears it)`);
155237
+ const codes = [run5.code, ...run5.codes].filter(Boolean);
155238
+ return transient(ctx, run5, RETRY_WORDS.find(([pattern]) => codes.some((code2) => pattern.test(code2)))?.[1] ?? RETRY_ANY);
155104
155239
  }
155105
155240
  return stop2(run5, `the step refused (${run5.code ?? run5.codes[0]})`);
155106
155241
  }
@@ -155121,7 +155256,7 @@ function depositArgs(ctx, submit, part) {
155121
155256
  usdcFlag(part.amountAtomic),
155122
155257
  "--service",
155123
155258
  values.service,
155124
- ...submit ? ["--submit"] : []
155259
+ ...submit ? ["--submit", ...historyRpcArgs(ctx)] : []
155125
155260
  ];
155126
155261
  }
155127
155262
  function crowdFlags(ctx) {
@@ -155687,20 +155822,26 @@ async function addFunds(values, deps) {
155687
155822
  }
155688
155823
  const label = outcome.label ?? next ?? stage;
155689
155824
  if (outcome.kind === "stop") {
155690
- const said = (outcome.run?.held ?? []).filter((text3) => !/^(Help|next): /.test(text3));
155691
- if (!said.length && outcome.run?.last) said.push(outcome.run.last);
155825
+ const said = outcome.tries ? [] : (outcome.run?.held ?? []).filter((text3) => !/^(Help|next): /.test(text3));
155826
+ if (!said.length && outcome.run?.last && !outcome.tries) said.push(outcome.run.last);
155692
155827
  for (const text3 of said) io.err(` ${text3}
155693
155828
  `);
155694
155829
  plan.status = { stage: label, at: iso2(deps.now()), stopped: outcome.run?.code ?? outcome.run?.codes?.[0] ?? "refused", waitsFor: outcome.waitsFor, ...said.length ? { said } : {} };
155695
155830
  save2();
155696
- throw failure("stopped", `the ${label} step needs you: ${outcome.waitsFor}${said.length ? " (its own line and hint are above)" : ""}. Fix that, then rerun this same add-funds command: it continues at ${label}`);
155831
+ throw failure("stopped", outcome.tries ? `add-funds stopped after ${MAX_TRANSIENT} tries of the ${label} step: it still waits for ${outcome.waitsFor}. Nothing was lost. Run this same add-funds command again later: it continues at ${label}` : `the ${label} step needs you: ${outcome.waitsFor}${said.length ? " (its own line and hint are above)" : ""}. Fix that, then rerun this same add-funds command: it continues at ${label}`);
155697
155832
  }
155698
155833
  const until = iso2(deps.now() + outcome.ms);
155699
- plan.status = { stage: label, at: iso2(deps.now()), waitsFor: outcome.waitsFor, until };
155834
+ plan.status = { stage: label, at: iso2(deps.now()), waitsFor: outcome.waitsFor, until, ...outcome.code ? { code: outcome.code } : {} };
155700
155835
  save2();
155701
- if (outcome.waitsFor !== lastWait) {
155836
+ if (outcome.transient) {
155837
+ const at = `${until.slice(11, 19)} UTC`;
155838
+ ctx.say(label, values.has("--once") ? `${outcome.said} on the next add-funds run (--once ends this one here; the next try can start at ${at})` : ctx.transient === 1 ? `${outcome.said} at ${at} by itself (nothing to do)` : `try ${ctx.transient} of ${MAX_TRANSIENT}: still waits for ${outcome.waitsFor}; next try at ${at}`);
155839
+ lastWait = outcome.waitsFor;
155840
+ } else if (outcome.waitsFor !== lastWait) {
155702
155841
  ctx.say(label, `waits for ${outcome.waitsFor}; next try at ${until}${outcome.every ? ` (${outcome.every})` : ""}`);
155703
155842
  lastWait = outcome.waitsFor;
155843
+ } else if (!outcome.every) {
155844
+ ctx.say(label, `still waits for ${outcome.waitsFor}; next try at ${until}`);
155704
155845
  }
155705
155846
  if (values.has("--once")) {
155706
155847
  plan.status = { ...plan.status, ended: iso2(deps.now()) };
@@ -155708,7 +155849,7 @@ async function addFunds(values, deps) {
155708
155849
  printEnd(deps, fundsView(store, plan, { now: deps.now(), running: false }), ctx.terminal);
155709
155850
  return;
155710
155851
  }
155711
- await sleep3(outcome.ms, abort.signal);
155852
+ await (deps.sleep ?? sleep3)(outcome.ms, abort.signal);
155712
155853
  }
155713
155854
  plan.status = { ...plan.status ?? { stage: currentStage(store, plan) }, ended: iso2(deps.now()) };
155714
155855
  save2();
@@ -155771,6 +155912,7 @@ init_job();
155771
155912
  init_rpcDefault();
155772
155913
  init_adoptOneTime();
155773
155914
  init_privateSend();
155915
+ init_selfPay();
155774
155916
  init_payMe();
155775
155917
  init_transferToken();
155776
155918
  init_recoveredPayment();
@@ -156650,7 +156792,7 @@ function inferPreflight(inference, values, prompt) {
156650
156792
  const floor = BigInt(inference.USEPOD_MIN_PAYMENT_ATOMIC ?? 0n);
156651
156793
  const worst = ceiling > floor ? ceiling : floor;
156652
156794
  if (worst > BigInt(values.get("--max-payment"))) {
156653
- throw new PublicFailure(`inference_ceiling_over_cap: usePod's price for this request may be up to ${usdcOr(worst)} (ceiling ${usdcOr(ceiling)}, minimum ${usdcOr(floor)}), and Shinjuku's fee comes on top; that is above --max-payment ${usdcFlagText(values.get("--max-payment"))} (${usdcOr(values.get("--max-payment"))}); nothing was paid. Raise --max-payment to at least ${usdcFlagText(worst)} (${usdcOr(worst)}) plus Shinjuku's fee${ceiling > floor ? ", or lower --max-tokens" : ""}`);
156795
+ throw new PublicFailure(`inference_ceiling_over_cap: usePod's price for this request may be up to ${usdcOr(worst)} (ceiling ${usdcOr(ceiling)}, minimum ${usdcOr(floor)}), and Shinjuku's fee comes on top; that is above --max-payment ${usdcFlagText(values.get("--max-payment"))} (${usdcOr(values.get("--max-payment"))}); nothing was paid. Raise --max-payment to at least ${usdcFlagText(worst)} (${usdcOr(worst)}) plus Shinjuku's fee${ceiling > floor ? ", or lower --max-tokens" : ""}. The cap is only a limit, not the price: the call pays usePod's price for its tokens plus the 0.001 USDC fee, and a larger cap never pays more`);
156654
156796
  }
156655
156797
  return { model, maxTokens, ceiling, worst };
156656
156798
  }
@@ -158018,7 +158160,7 @@ var COMMANDS = [
158018
158160
  flag2("--profile", "<file>", "the facilitator's public seller profile, public-profile.json (pocket fill; needed only with --pockets 1 or more)"),
158019
158161
  flag2("--pockets", "<n>", "0 to 20; default 0 (no pocket and no dwell). 1 or more fills that many pockets after the deposit (pocket fill). A new payment never pays from a pocket: it pays through Shinjuku from the shielded balance, so most wallets leave this out", { check: "count" }),
158020
158162
  flag2("--pocket-size", "<usdc>", "the size of each pocket, in USDC; default 1 (1 USDC), the size our facilitator uses", { check: "atomic", usdc: true }),
158021
- { ...HISTORY_RPC_FLAG, about: `add-funds passes it only to the steps that read the pool's history (balance --refresh, pocket fill, and its dwell check; never to a deposit or Privacy Cash step): ${HISTORY_RPC_FLAG.about}` },
158163
+ { ...HISTORY_RPC_FLAG, about: `add-funds passes it only to the steps that read the pool's history (balance --refresh, pocket fill, its dwell check, and the deposit's walk that confirms a deposit that landed without the facilitator's answer; never to a Privacy Cash step): ${HISTORY_RPC_FLAG.about}` },
158022
158164
  flag2("--from-key-file", "<file>", "--route privacy-cash: a solana-keygen JSON key of your own wallet that pays into Privacy Cash and owns its notes"),
158023
158165
  flag2("--crowd-amount", "<usdc>", "--route privacy-cash: whole USDC to put into Privacy Cash first (13 = 13 USDC); leave it out to spend notes that key already holds there", { check: "atomic", usdc: true }),
158024
158166
  { ...PRIVACY_CASH_VIA_FLAG, about: `--route privacy-cash: ${PRIVACY_CASH_VIA_FLAG.about}` },
@@ -158103,6 +158245,7 @@ var COMMANDS = [
158103
158245
  flag2("--deposit-id", "<id>", "your name for this deposit; reuse it for every retry", { required: true }),
158104
158246
  flag2("--amount", "<usdc>", "USDC to deposit (0.03 = 3 cents)", { required: true, check: "atomic", usdc: true }),
158105
158247
  ...rpcFlags,
158248
+ { ...HISTORY_RPC_FLAG, about: `with --relay --submit, only for the pool-history walk that confirms a deposit that landed without the facilitator's answer: ${HISTORY_RPC_FLAG.about}` },
158106
158249
  bool2("--submit", "send the recorded deposit (second run)"),
158107
158250
  flag2("--service", "<url>", "facilitator that stores the deposit memo (use with --submit; with --relay, on both runs)"),
158108
158251
  bool2("--relay", "deposit from a one-time address of this wallet that holds only USDC; the facilitator pays every fee and rent, so it needs no SOL"),
@@ -158805,7 +158948,7 @@ var COMMANDS = [
158805
158948
  "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>).",
158806
158949
  "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.",
158807
158950
  "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.`,
158951
+ "--amount all sends the whole private balance. When the balance is in more than one part, the same command joins the parts first (a private move inside this wallet, no fee; nothing leaves it; about 2 minutes per extra part, after a random wait of up to 60 s), then sends all of it. Ctrl+C stops it; the same command again goes on and never sends twice.",
158809
158952
  "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
158953
  "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
158954
  `--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.`,
@@ -159631,15 +159774,34 @@ async function runUnshieldCommand(args, values, deps, io) {
159631
159774
  setArg("--to", getAssociatedTokenAddressSync(mint, owner, true, TOKEN_PROGRAM_ID).toBase58());
159632
159775
  setArg("--recipient-owner", owner.toBase58());
159633
159776
  }
159634
- if (values.get("--amount") === "all") setArg("--amount", await sendAllAtomic(values, deps, io, openStore));
159635
159777
  let id = values.get("--unshield-id");
159636
- if (id === void 0) {
159778
+ let recorded;
159779
+ if (id !== void 0) {
159780
+ let opened;
159781
+ try {
159782
+ opened = openStore();
159783
+ } catch {
159784
+ opened = void 0;
159785
+ }
159786
+ recorded = opened === void 0 ? void 0 : recordedUnshield(opened, id, values);
159787
+ }
159788
+ if (recorded && values.get("--amount") === "all") {
159789
+ setArg("--amount", recorded.amountAtomic);
159790
+ io.err(`--amount all: unshield ${id} started earlier with ${usdcOr(recorded.amountAtomic)}; this run finishes it (it never sends twice)
159791
+ `);
159792
+ } else if (recorded && values.get("--amount") !== recorded.amountAtomic) {
159793
+ throw unshieldRefusal("id_reused_with_different_amount", `unshield ${id} started earlier with ${usdcOr(recorded.amountAtomic)}, not ${usdcOr(values.get("--amount"))}, and one unshield id always sends the amount it started with; nothing was sent. Finish it: ${CLI2} unshield --amount all${recorded.flags} --to ${owner.toBase58()} --unshield-id ${id} (it never sends twice). To send another amount, leave out --unshield-id`);
159794
+ }
159795
+ const ensureId = () => {
159796
+ if (id !== void 0) return;
159637
159797
  id = `unshield-${randomBytes35(6).toString("hex")}`;
159638
159798
  setArg("--unshield-id", id);
159639
159799
  values.set(UNSHIELD_MADE_ID, id);
159640
159800
  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
159801
  `);
159642
- }
159802
+ };
159803
+ if (values.get("--amount") === "all") setArg("--amount", await sendAllAtomic(values, deps, io, openStore, ensureId));
159804
+ ensureId();
159643
159805
  const gated = await spendGate(callArgs, values, deps, io, { amountAtomic: values.get("--amount"), again: ` with --unshield-id ${id}` });
159644
159806
  let answer;
159645
159807
  const returned = await deps.unshieldMain(gated, { output: (result) => {
@@ -159653,9 +159815,36 @@ async function runUnshieldCommand(args, values, deps, io) {
159653
159815
  if (!/^[1-9][0-9]*$/.test(String(sent ?? ""))) return;
159654
159816
  const walletAt = gated.indexOf("--wallet");
159655
159817
  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")}
159818
+ if (!terminal) return;
159819
+ let unfinished2 = [];
159820
+ try {
159821
+ unfinished2 = unfinishedUnshields(openStore());
159822
+ } catch {
159823
+ }
159824
+ io.out(`${[privateBalanceLine(report, "Private balance now"), ...unfinished2.map(unfinishedUnshieldLine)].join("\n")}
159657
159825
  `);
159658
159826
  }
159827
+ function recordedUnshield(store, id, values) {
159828
+ if (!/^[0-9a-zA-Z_-]{1,48}$/.test(id)) return void 0;
159829
+ const whole = (atomic4) => /^[1-9][0-9]*$/.test(String(atomic4 ?? ""));
159830
+ if (isSingleSend(values.get("--parts") ?? 1, values.get("--max-delay-minutes") ?? 0)) {
159831
+ const record = store.readWal(`self-pay-${id}`);
159832
+ return record?.schema === SELF_PAY_WAL_SCHEMA && whole(record.amountAtomic) ? { amountAtomic: String(record.amountAtomic), flags: "" } : void 0;
159833
+ }
159834
+ const plan = store.readWal(privateSendPlanName(id));
159835
+ return plan?.schema === PRIVATE_SEND_WAL_SCHEMA && whole(plan.amountAtomic) && Array.isArray(plan.parts) ? { amountAtomic: String(plan.amountAtomic), flags: ` --parts ${plan.parts.length} --max-delay-minutes ${plan.maxDelayMinutes}` } : void 0;
159836
+ }
159837
+ function unfinishedUnshields(store) {
159838
+ const read = (name) => {
159839
+ try {
159840
+ return store.readWal(name);
159841
+ } catch {
159842
+ return void 0;
159843
+ }
159844
+ };
159845
+ return store.listWal().filter((name) => name.startsWith("self-pay-")).map((name) => name.slice("self-pay-".length)).filter((unshieldId) => !unshieldId.startsWith("merge-") && !/^pocket-\d+-f\d+$/.test(unshieldId)).map((unshieldId) => ({ unshieldId, record: read(`self-pay-${unshieldId}`) })).filter(({ record }) => record?.schema === SELF_PAY_WAL_SCHEMA && record.ownerUnshield === true && record.state === "paid" && record.mode !== DIRECT_EXIT_MODE && /^[1-9][0-9]*$/.test(String(record.amountAtomic ?? ""))).map(({ unshieldId, record }) => ({ unshieldId, amountAtomic: String(record.amountAtomic) }));
159846
+ }
159847
+ var unfinishedUnshieldLine = ({ unshieldId, amountAtomic }) => `${usdcOr(amountAtomic)} is in an unshield that has not finished (unshield id ${unshieldId}): run ${CLI2} unshield --amount all --to <the address you sent it to> --unshield-id ${unshieldId} to finish it; it never sends twice`;
159659
159848
  function unshieldDoneText(answer, to) {
159660
159849
  const send3 = answer.privateSend;
159661
159850
  if (!send3) {
@@ -159743,25 +159932,86 @@ async function refreshAfterUnshield(walletFile, values, deps, io) {
159743
159932
  restore();
159744
159933
  }
159745
159934
  }
159746
- async function sendAllAtomic(values, deps, io, openStore) {
159935
+ async function sendAllAtomic(values, deps, io, openStore, ensureId) {
159747
159936
  const store = openStore();
159748
159937
  let report = computeBalance({ store, snapshotRead: readBalanceSnapshot(store), source: "local", now: deps.now() });
159749
159938
  if (report.status !== "current") report = await refreshAfterUnshield(values.get("--wallet"), values, deps, io) ?? report;
159939
+ const notCurrent = (read) => new PublicFailure(`balance_not_current: --amount all needs the current private balance, and it is not current (${balanceNotCurrentWhy(read)}); nothing was sent. Run ${CLI2} balance --refresh in a minute, then this command again, or give the amount, like ${UNSHIELD_EXAMPLE}`);
159750
159940
  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
159941
  if (empty) {
159755
159942
  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
159943
  }
159757
159944
  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)}
159945
+ const join40 = parts?.joining > 0 || parts?.parts > 1 && report.status === "current";
159946
+ if (join40) report = await joinForSendAll(report, parts, store, values, deps, io, ensureId);
159947
+ if (report.status !== "current" || !/^[1-9][0-9]*$/.test(String(report.atomic ?? ""))) throw notCurrent(report);
159948
+ if (!join40) io.err(`--amount all: the whole private balance, ${usdcOr(report.atomic)}
159762
159949
  `);
159763
159950
  return report.atomic;
159764
159951
  }
159952
+ var SEND_ALL_JOIN_ROUNDS = 3;
159953
+ async function joinForSendAll(report, parts, store, values, deps, io, ensureId) {
159954
+ const count2 = parts.parts + parts.joining;
159955
+ const known = /^[0-9]+$/.test(String(report.atomic ?? ""));
159956
+ io.err(`--amount all: the private balance${known ? `, ${usdcOr(report.atomic)},` : ""} is in ${count2} parts, and one unshield sends from one part. The wallet joins the parts first (a private move inside this wallet, no fee; nothing leaves it), then sends all of it: about ${joinMinutes(count2)} min. Ctrl+C stops it; the same command again goes on and never sends twice
159957
+ `);
159958
+ ensureId();
159959
+ if (parts.joining === 0) {
159960
+ const waitMs = Math.floor((deps.random ?? Math.random)() * (JOIN_SOON_MAX_MS + 1));
159961
+ io.err(`the join starts in ${Math.round(waitMs / 1e3)} s: a random wait, so the chain does not show the join right after your request
159962
+ `);
159963
+ await (deps.sleep ?? ((ms) => new Promise((done) => {
159964
+ setTimeout(done, ms);
159965
+ })))(waitMs);
159966
+ }
159967
+ const history = values.get("--history-rpc");
159968
+ const mergeArgs = [
159969
+ "--wallet",
159970
+ values.get("--wallet"),
159971
+ "--passphrase-file",
159972
+ values.get("--passphrase-file"),
159973
+ "--program",
159974
+ values.get("--program"),
159975
+ "--proof-tools",
159976
+ values.get("--payer-proof-tools"),
159977
+ "--profile",
159978
+ values.get("--profile"),
159979
+ "--facilitator",
159980
+ new URL(values.get("--facilitator")).origin,
159981
+ ...values.has("--rpc-file") ? ["--rpc-file", values.get("--rpc-file")] : [],
159982
+ ...history ? ["--history-rpc", history] : []
159983
+ ];
159984
+ const startedAt = deps.now();
159985
+ let joined = 0;
159986
+ let left = parts;
159987
+ let keep;
159988
+ let read;
159989
+ for (let round = 1; round <= SEND_ALL_JOIN_ROUNDS; round += 1) {
159990
+ let answer;
159991
+ try {
159992
+ await deps.mergeMain(mergeArgs, { output: (result) => {
159993
+ answer = result;
159994
+ } });
159995
+ } catch (error) {
159996
+ io.err("note: the join stopped, so the unshield did not start; nothing was sent\n");
159997
+ throw error;
159998
+ }
159999
+ const merged = (answer?.merged ?? answer?.resumed ?? []).filter((entry) => entry?.state === "merged").length;
160000
+ joined += merged;
160001
+ keep = answer?.keep?.amountAtomic ?? keep;
160002
+ read = await refreshAfterUnshield(values.get("--wallet"), values, deps, io);
160003
+ left = joinParts(store);
160004
+ if (read?.status === "current" && left?.parts <= 1 && left.joining === 0) {
160005
+ const seconds2 = Math.max(0, Math.round((deps.now() - startedAt) / 1e3));
160006
+ io.err(`joined the ${count2} parts in ${Math.floor(seconds2 / 60)} min ${seconds2 % 60} s; sending all ${usdcOr(read.atomic)} now
160007
+ `);
160008
+ return read;
160009
+ }
160010
+ if (!read || read.status !== "current" || merged === 0) break;
160011
+ }
160012
+ const remaining = left ? left.parts + left.joining : void 0;
160013
+ throw new PublicFailure(`unshield_joining_accounts: the wallet joined ${joined} part${joined === 1 ? "" : "s"} of your private balance into the largest one` + (remaining > 1 ? `, and it is still in ${remaining} parts` : read ? ", but the balance walk after the join is not current yet" : ", but the balance could not be read again after the join") + `; nothing was sent. Send a fixed amount up to the largest part${keep ? ` (${usdcOr(keep)})` : ""} now, like ${UNSHIELD_EXAMPLE} (that send also joins the parts it draws), or run ${CLI2} unshield --amount all --to <your wallet address> again in a few minutes: it goes on with the next join`);
160014
+ }
159765
160015
  async function runUnshieldCancel(values, deps, io) {
159766
160016
  const walletFile = resolve38(values.get("--wallet"));
159767
160017
  const passphrase2 = payerPassphrase(values, deps);
@@ -160310,17 +160560,19 @@ async function runBalance(args, values, deps, io) {
160310
160560
  }
160311
160561
  if (config?.pool === store.state.pool && config.program && config.proofTools && config.memoService) report.next = `${CLI2} balance --refresh --fresh`;
160312
160562
  }
160563
+ const unfinished2 = unfinishedUnshields(store);
160313
160564
  if (deps.stdoutIsTTY?.() === true && !values.has("--json")) {
160314
160565
  const notCurrent = report.status !== "current" && report.status !== "empty";
160315
160566
  io.out(`${[
160316
160567
  privateBalanceLine(report),
160568
+ ...unfinished2.map(unfinishedUnshieldLine),
160317
160569
  ...shown.map((entry) => entry.notice).filter((notice) => typeof notice === "string" && notice),
160318
160570
  ...notCurrent && typeof report.next === "string" ? [`next: ${report.next}`] : []
160319
160571
  ].join("\n")}
160320
160572
  `);
160321
160573
  return;
160322
160574
  }
160323
- io.out(`${JSON.stringify(shown.length ? { ...report, earlierDeposits: shown } : report, null, 2)}
160575
+ io.out(`${JSON.stringify({ ...report, ...shown.length ? { earlierDeposits: shown } : {}, ...unfinished2.length ? { unfinishedUnshields: unfinished2 } : {} }, null, 2)}
160324
160576
  `);
160325
160577
  }
160326
160578
  async function adoptOneTimeForBalance({ store, values, deps, io }) {
@@ -160637,6 +160889,8 @@ function failureHint(command2, reason, deps, values, floatError) {
160637
160889
  if (relayHint) return relayHint;
160638
160890
  const privacyCashHint = privacyCashFailureHint(reason);
160639
160891
  if (privacyCashHint) return privacyCashHint;
160892
+ const exitRelayHint = exitRelayFailureHint(reason);
160893
+ if (exitRelayHint) return exitRelayHint;
160640
160894
  if (/_retry_same_request/.test(reason) && /the quote ended/.test(reason)) {
160641
160895
  return `the quote of this request ended, so the same payment can no longer settle. Run ${CLI2} cancel with the same request id (${CLI2} help cancel): it checks on chain that nothing was paid and frees the request. Then pay again with a new --request-id`;
160642
160896
  }
@@ -160655,6 +160909,25 @@ function failureHint(command2, reason, deps, values, floatError) {
160655
160909
  }
160656
160910
  return void 0;
160657
160911
  }
160912
+ function exitRelayFailureHint(reason) {
160913
+ if (!/p03_unshield_relay_refused_/.test(reason) || /_retry_same_request|exit_pocket_/.test(reason)) return void 0;
160914
+ if (/relay_refused_exit_quote_expired/.test(reason)) {
160915
+ return "the exit proof's time window ended before the facilitator's exit transaction landed, and the new proof met the same; nothing was sent and the note is still yours. Rerun the same command (same --unshield-id): it makes a new proof, and it never sends twice";
160916
+ }
160917
+ if (/relay_refused_exit_anchor_not_retained/.test(reason)) {
160918
+ return "the pool no longer keeps the anchor this exit proof names; nothing was sent and the note is still yours. Rerun the same command after the proof's time window ends (at most 10 minutes after it was made): the wallet then makes a new proof";
160919
+ }
160920
+ if (/relay_refused_exit_replay_consumed/.test(reason)) {
160921
+ return `the chain shows this exit's note is already spent (an earlier exit of it landed, or another payment of this wallet spent it); nothing new was sent. ${CLI2} balance --refresh shows where the value is now`;
160922
+ }
160923
+ if (/relay_refused_exit_operator_review_required/.test(reason)) {
160924
+ return "the facilitator cannot tell yet whether its exit transaction for this note landed, so it holds this exit for its operator and sends nothing more. Keep the wallet folder as it is and rerun the same command later (same --unshield-id); it never sends twice";
160925
+ }
160926
+ if (/relay_refused_exit_vault_insufficient/.test(reason)) {
160927
+ return "the pool's vault holds less than this exit right now; nothing was sent and the note is still yours. Rerun the same command later (same --unshield-id)";
160928
+ }
160929
+ return "the facilitator's exit relay refused this exit (the code names why); nothing was sent and the note is still yours. Rerun the same command once (same --unshield-id); if the same code comes back, keep the wallet folder as it is and ask for help";
160930
+ }
160658
160931
  function relayDepositFailureHint(reason) {
160659
160932
  if (/p03_deposit_relay_approval_standing/.test(reason)) {
160660
160933
  return `your USDC is still at the one-time address with an approval that names this deposit alone: it can only become your own note, so it is safe to leave. Rerun the same command later; it signs nothing new while the approval stands. To take the USDC back instead: ${CLI2} help deposit-reclaim`;