shinjuku-shield 0.3.84 → 0.3.85

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.85. 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.85 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.85
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.85 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.85", "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": "654041bce4139bfb928180cc1a3c1ff05e570d9fc51ddfa718e9d0ef7fbf9095",
5
+ "bytes": 12513236,
6
+ "sourceCommit": "1e2a8c09784450c8bc0a5d391c428d08f699c44a",
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-23T20:33:40.235Z"
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.85",
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": "654041bc",
7
+ "shinjukuSha256": "654041bce4139bfb928180cc1a3c1ff05e570d9fc51ddfa718e9d0ef7fbf9095",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-mcp#readme",
@@ -62798,7 +62798,7 @@ async function readRelayConfig({ serviceUrl: serviceUrl2, fetchImpl, id }) {
62798
62798
  async function submitRelayedDeposit(input) {
62799
62799
  return withWalletLease(input.store, input.deps ?? {}, () => submitRelayedDepositLeased(input));
62800
62800
  }
62801
- async function submitRelayedDepositLeased({ store, connection, programId, proofTools, depositId, serviceUrl: serviceUrl2, deps = {} }) {
62801
+ async function submitRelayedDepositLeased({ store, connection, programId, proofTools, depositId, serviceUrl: serviceUrl2, historyUrl, deps = {} }) {
62802
62802
  assertDepositId(depositId);
62803
62803
  const name = walName(depositId);
62804
62804
  const record = store.readWal(name);
@@ -62812,7 +62812,8 @@ async function submitRelayedDepositLeased({ store, connection, programId, proofT
62812
62812
  const source = new import_web3101.PublicKey(record.relay.tokenAccount);
62813
62813
  const pause3 = deps.pause ?? delay;
62814
62814
  const fetchImpl = deps.fetchImpl ?? fetch;
62815
- const loadHistory = () => finalizedHistory({ store, connection, id, proofTools, deps });
62815
+ const historyConnection = connection.historyRpcChoice !== void 0 || connection.historyRpc ? connection : connectSplit(connection, historyUrl);
62816
+ const loadHistory = () => finalizedHistory({ store, connection: historyConnection, id, proofTools, deps });
62816
62817
  const refuse19 = async (reason) => {
62817
62818
  record.relay.approvedOnChain = (await relaySource(connection, id, record)).approved;
62818
62819
  record.relay.lastRefusal = { reason, at: (/* @__PURE__ */ new Date()).toISOString() };
@@ -62981,8 +62982,42 @@ function settleTransaction(connection, signature2) {
62981
62982
  maxSupportedTransactionVersion: 1
62982
62983
  }));
62983
62984
  }
62985
+ async function foundSettle(connection, id, record, intent) {
62986
+ let entries;
62987
+ try {
62988
+ if (typeof connection.getSignaturesForAddress !== "function") return void 0;
62989
+ entries = await retryRpc2(() => connection.getSignaturesForAddress(intent.address, { limit: SETTLE_SEARCH_LIMIT }, "finalized"));
62990
+ } catch {
62991
+ return void 0;
62992
+ }
62993
+ if (!Array.isArray(entries)) return void 0;
62994
+ for (const entry of [...entries].reverse()) {
62995
+ if (entry?.err !== null || typeof entry.signature !== "string") continue;
62996
+ const transaction = await settleTransaction(connection, entry.signature).catch(() => void 0);
62997
+ if (!transaction?.meta || transaction.meta.err) continue;
62998
+ const landing = returnedIntent(transaction, id, record);
62999
+ if (landing) return { signature: entry.signature, transaction, landing };
63000
+ }
63001
+ return void 0;
63002
+ }
62984
63003
  async function relayReceipt({ connection, id, record, intent, loadHistory, transaction: read, walkMs }) {
62985
63004
  const signature2 = record.relay.result?.signature;
63005
+ if (!signature2) {
63006
+ const found = await foundSettle(connection, id, record, intent);
63007
+ if (found) {
63008
+ const { transaction, landing } = found;
63009
+ record.relay.result = { signature: found.signature, slot: transaction.slot };
63010
+ return {
63011
+ intent: intent.address.toBase58(),
63012
+ indexAfter: landing.indexAfter,
63013
+ signature: found.signature,
63014
+ slot: transaction.slot,
63015
+ feeLamports: transaction.meta.fee,
63016
+ broadcaster: landing.broadcaster.toBase58(),
63017
+ source: landing.source.toBase58()
63018
+ };
63019
+ }
63020
+ }
62986
63021
  if (signature2) {
62987
63022
  const transaction = (read ? await read : void 0) ?? await settleTransaction(connection, signature2);
62988
63023
  const landing = transaction?.meta && !transaction.meta.err ? returnedIntent(transaction, id, record) ?? settleFunder(transaction, id, intent.address) : void 0;
@@ -63378,7 +63413,7 @@ async function publishDepositMemo({ serviceUrl: serviceUrl2, record, fetchImpl =
63378
63413
  const stored = await fetchMemoFromService(serviceUrl2, record.memoDigest, { fetchImpl });
63379
63414
  if (!stored || !Buffer.from(stored).equals(bytes2)) fail11("memo_publish_readback_mismatch");
63380
63415
  }
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;
63416
+ 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
63417
  var init_deposit = __esm({
63383
63418
  "wallet/src/deposit/deposit.mjs"() {
63384
63419
  "use strict";
@@ -63486,6 +63521,7 @@ var init_deposit = __esm({
63486
63521
  FUNDS_PLAN_SCHEMA = "carbon-shielded-wallet/add-funds/v1";
63487
63522
  FUNDS_PLAN_PREFIX = "add-funds-";
63488
63523
  relayKeyPool = (store, record) => record.relay.keyPool ?? record.pool ?? store.state.pool;
63524
+ SETTLE_SEARCH_LIMIT = 10;
63489
63525
  __test = Object.freeze({ parseBuffer });
63490
63526
  }
63491
63527
  });
@@ -75305,6 +75341,7 @@ async function runSelfCustodyDeposit(args) {
75305
75341
  throw new Error("deposit requires --program, --deposit-id, --amount <atomic>, --proof-tools, and --rpc-file <file> (or --rpc, or UNDERMARKET_SOLANA_RPC_URL)");
75306
75342
  }
75307
75343
  const proofTools = loadP03PublicPaymentProofTools(flag(args, "--proof-tools"));
75344
+ const historyUrl = historyRpcUrl(flag(args, "--history-rpc"));
75308
75345
  const path = walletPath(pool2);
75309
75346
  const relay = args.includes("--relay");
75310
75347
  const indexFlag = flag(args, "--one-time-index");
@@ -75361,7 +75398,7 @@ async function runSelfCustodyDeposit(args) {
75361
75398
  }, null, 2));
75362
75399
  return;
75363
75400
  }
75364
- const result = await (relay ? submitRelayedDeposit : submitDeposit)({ ...common, serviceUrl: flag(args, "--service") });
75401
+ const result = await (relay ? submitRelayedDeposit : submitDeposit)({ ...common, serviceUrl: flag(args, "--service"), ...relay ? { historyUrl } : {} });
75365
75402
  console.log(JSON.stringify({
75366
75403
  ...result,
75367
75404
  next: result.state === "published" ? "run recover; export and verify two fresh backups" : "rerun with --submit --service <facilitator URL> to publish the memo"
@@ -151811,7 +151848,7 @@ function parseArgs2(args) {
151811
151848
  if (!options["dry-run"] && FOR_RUN.some((key) => !options[key])) fail41("arguments_missing");
151812
151849
  return options;
151813
151850
  }
151814
- async function main5(args = process.argv.slice(2)) {
151851
+ async function main5(args = process.argv.slice(2), { output: output2 = (result) => console.log(JSON.stringify(result)) } = {}) {
151815
151852
  if (args.length === 1 && args[0] === "--help") {
151816
151853
  console.log(USAGE3);
151817
151854
  return;
@@ -151894,20 +151931,20 @@ async function main5(args = process.argv.slice(2)) {
151894
151931
  return { id, amountAtomic: record.amountAtomic, accountCommitment: record.mergeAccountCommitment, noteCommitments: record.mergeNoteCommitments ?? [] };
151895
151932
  });
151896
151933
  if (dryRun) {
151897
- console.log(JSON.stringify({
151934
+ output2({
151898
151935
  dryRun: true,
151899
151936
  resume: resume.map((entry) => ({ amountAtomic: entry.amountAtomic, amount: usdc9(entry.amountAtomic) })),
151900
151937
  next: "run the same command without --dry-run: it finishes these merges first (never pays twice)"
151901
- }));
151938
+ });
151902
151939
  return;
151903
151940
  }
151904
151941
  const results2 = [];
151905
151942
  for (const entry of resume) results2.push(await runOne(entry));
151906
- console.log(JSON.stringify({
151943
+ output2({
151907
151944
  success: true,
151908
151945
  resumed: results2,
151909
151946
  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
- }));
151947
+ });
151911
151948
  return;
151912
151949
  }
151913
151950
  const snapshotRead = readBalanceSnapshot(store);
@@ -151915,8 +151952,8 @@ async function main5(args = process.argv.slice(2)) {
151915
151952
  if (balance.status !== "current" || snapshotRead.snapshot?.programId !== String(options.program)) fail41("balance_walk_not_current_run_balance_refresh");
151916
151953
  if (balance.detail.inFlight.some((item) => item.kind !== "merge")) fail41("in_flight_work_finish_first");
151917
151954
  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);
151955
+ const shown = (output3) => ({ amountAtomic: output3.valueAtomic, amount: usdc9(output3.valueAtomic) });
151956
+ const merges = plan.merge.map((output3) => ({ id: nextMergeId(store, output3.commitment), amountAtomic: output3.valueAtomic, accountCommitment: output3.commitment })).filter((entry) => entry.id !== void 0);
151920
151957
  const summary = {
151921
151958
  totalAtomic: plan.totalAtomic,
151922
151959
  total: usdc9(plan.totalAtomic),
@@ -151928,19 +151965,19 @@ async function main5(args = process.argv.slice(2)) {
151928
151965
  const nothing = merges.length === 0;
151929
151966
  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
151967
  if (dryRun || nothing) {
151931
- console.log(JSON.stringify({ dryRun, ...summary, ...why ? { next: why } : nothing ? { next: "nothing left to merge" } : {
151968
+ output2({ dryRun, ...summary, ...why ? { next: why } : nothing ? { next: "nothing left to merge" } : {
151932
151969
  next: "run the same command without --dry-run (with --proof-tools, --profile, --facilitator); nothing was signed or sent"
151933
- } }));
151970
+ } });
151934
151971
  return;
151935
151972
  }
151936
151973
  const results = [];
151937
151974
  for (const entry of merges) results.push(await runOne(entry));
151938
- console.log(JSON.stringify({
151975
+ output2({
151939
151976
  success: true,
151940
151977
  ...summary,
151941
151978
  merged: results,
151942
151979
  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
- }));
151980
+ });
151944
151981
  } finally {
151945
151982
  await release();
151946
151983
  }
@@ -154914,7 +154951,8 @@ function fundsView(store, plan, { now = Date.now(), running } = {}) {
154914
154951
  state: last?.stopped ? "stopped" : idle ? "not running" : last?.until ? "waiting" : "next",
154915
154952
  ...last?.waitsFor ? { waitsFor: last.waitsFor } : {},
154916
154953
  ...last?.until && !idle ? { until: last.until } : {},
154917
- ...last?.stopped ? { code: last.stopped } : {},
154954
+ // A wait's code (K29 T13): the terminal says the wait in words only.
154955
+ ...last?.stopped ? { code: last.stopped } : last?.code ? { code: last.code } : {},
154918
154956
  ...last?.said?.length ? { said: last.said } : {}
154919
154957
  };
154920
154958
  });
@@ -155087,20 +155125,44 @@ var TRANSIENT = [
155087
155125
  /^E[A-Z0-9_]{1,40}$/
155088
155126
  ];
155089
155127
  var SOON_CODES = /(_retry_same_|_rerun$|wallet_process_locked|wallet_locked|_wait_next_epoch|_anchor_not_finalized)/;
155090
- function transient(ctx, run5, waitsFor) {
155128
+ var RETRY_WORDS = [
155129
+ [/^p03_deposit_landed_history_walk_slow_retry_later$/, {
155130
+ now: "the deposit is in the pool; the wallet could not read the pool history in time to confirm it, so it reads again",
155131
+ waitsFor: "a new read of the pool history to confirm the deposit (the deposit is in the pool)"
155132
+ }],
155133
+ [/^p03_deposit_(landed_history_pending|receipt_transaction_unavailable|receipt_evidence_missing)_retry_later$/, {
155134
+ now: "the deposit is in the pool; the chain does not show the wallet its receipt yet, so it reads again",
155135
+ waitsFor: "the chain to show the deposit's receipt (the deposit is in the pool)"
155136
+ }],
155137
+ [/^p03_deposit_relay_not_settled_rerun_same_id$/, {
155138
+ now: "the facilitator has not moved the deposit into the pool yet, so the wallet asks again",
155139
+ waitsFor: "the facilitator to move the deposit into the pool"
155140
+ }],
155141
+ [/^rpc_unreachable$/, { now: "the RPC did not answer, so the wallet asks again", waitsFor: "an answer from the RPC (it did not answer)" }],
155142
+ [/^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)" }],
155143
+ [/^(wallet_process_locked|p03_pocket_cli_wallet_locked)$/, {
155144
+ now: "another wallet command uses this wallet right now, so add-funds tries again",
155145
+ waitsFor: "the other wallet command to finish with this wallet"
155146
+ }]
155147
+ ];
155148
+ 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)" };
155149
+ 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)" };
155150
+ function transient(ctx, run5, words2) {
155091
155151
  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 });
155152
+ if (ctx.transient > MAX_TRANSIENT) return { ...stop2(run5, words2.waitsFor), tries: true };
155153
+ const code2 = run5.code ?? run5.codes[0];
155154
+ return wait(has(run5, SOON_CODES) ? SOON_MS : LATER_MS, words2.waitsFor, { transient: true, said: words2.now, ...code2 ? { code: code2 } : {} });
155094
155155
  }
155095
155156
  function otherwise(ctx, run5) {
155096
155157
  if (run5.aborted) return stop2(run5, "stopped");
155097
155158
  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)");
155159
+ if (!run5.code && !run5.codes.length) return transient(ctx, run5, NO_CODE);
155099
155160
  if (has(run5, FACILITATOR_LATER)) {
155100
155161
  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
155162
  }
155102
155163
  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)`);
155164
+ const codes = [run5.code, ...run5.codes].filter(Boolean);
155165
+ return transient(ctx, run5, RETRY_WORDS.find(([pattern]) => codes.some((code2) => pattern.test(code2)))?.[1] ?? RETRY_ANY);
155104
155166
  }
155105
155167
  return stop2(run5, `the step refused (${run5.code ?? run5.codes[0]})`);
155106
155168
  }
@@ -155121,7 +155183,7 @@ function depositArgs(ctx, submit, part) {
155121
155183
  usdcFlag(part.amountAtomic),
155122
155184
  "--service",
155123
155185
  values.service,
155124
- ...submit ? ["--submit"] : []
155186
+ ...submit ? ["--submit", ...historyRpcArgs(ctx)] : []
155125
155187
  ];
155126
155188
  }
155127
155189
  function crowdFlags(ctx) {
@@ -155687,20 +155749,26 @@ async function addFunds(values, deps) {
155687
155749
  }
155688
155750
  const label = outcome.label ?? next ?? stage;
155689
155751
  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);
155752
+ const said = outcome.tries ? [] : (outcome.run?.held ?? []).filter((text3) => !/^(Help|next): /.test(text3));
155753
+ if (!said.length && outcome.run?.last && !outcome.tries) said.push(outcome.run.last);
155692
155754
  for (const text3 of said) io.err(` ${text3}
155693
155755
  `);
155694
155756
  plan.status = { stage: label, at: iso2(deps.now()), stopped: outcome.run?.code ?? outcome.run?.codes?.[0] ?? "refused", waitsFor: outcome.waitsFor, ...said.length ? { said } : {} };
155695
155757
  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}`);
155758
+ 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
155759
  }
155698
155760
  const until = iso2(deps.now() + outcome.ms);
155699
- plan.status = { stage: label, at: iso2(deps.now()), waitsFor: outcome.waitsFor, until };
155761
+ plan.status = { stage: label, at: iso2(deps.now()), waitsFor: outcome.waitsFor, until, ...outcome.code ? { code: outcome.code } : {} };
155700
155762
  save2();
155701
- if (outcome.waitsFor !== lastWait) {
155763
+ if (outcome.transient) {
155764
+ const at = `${until.slice(11, 19)} UTC`;
155765
+ 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}`);
155766
+ lastWait = outcome.waitsFor;
155767
+ } else if (outcome.waitsFor !== lastWait) {
155702
155768
  ctx.say(label, `waits for ${outcome.waitsFor}; next try at ${until}${outcome.every ? ` (${outcome.every})` : ""}`);
155703
155769
  lastWait = outcome.waitsFor;
155770
+ } else if (!outcome.every) {
155771
+ ctx.say(label, `still waits for ${outcome.waitsFor}; next try at ${until}`);
155704
155772
  }
155705
155773
  if (values.has("--once")) {
155706
155774
  plan.status = { ...plan.status, ended: iso2(deps.now()) };
@@ -155708,7 +155776,7 @@ async function addFunds(values, deps) {
155708
155776
  printEnd(deps, fundsView(store, plan, { now: deps.now(), running: false }), ctx.terminal);
155709
155777
  return;
155710
155778
  }
155711
- await sleep3(outcome.ms, abort.signal);
155779
+ await (deps.sleep ?? sleep3)(outcome.ms, abort.signal);
155712
155780
  }
155713
155781
  plan.status = { ...plan.status ?? { stage: currentStage(store, plan) }, ended: iso2(deps.now()) };
155714
155782
  save2();
@@ -158018,7 +158086,7 @@ var COMMANDS = [
158018
158086
  flag2("--profile", "<file>", "the facilitator's public seller profile, public-profile.json (pocket fill; needed only with --pockets 1 or more)"),
158019
158087
  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
158088
  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}` },
158089
+ { ...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
158090
  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
158091
  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
158092
  { ...PRIVACY_CASH_VIA_FLAG, about: `--route privacy-cash: ${PRIVACY_CASH_VIA_FLAG.about}` },
@@ -158103,6 +158171,7 @@ var COMMANDS = [
158103
158171
  flag2("--deposit-id", "<id>", "your name for this deposit; reuse it for every retry", { required: true }),
158104
158172
  flag2("--amount", "<usdc>", "USDC to deposit (0.03 = 3 cents)", { required: true, check: "atomic", usdc: true }),
158105
158173
  ...rpcFlags,
158174
+ { ...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
158175
  bool2("--submit", "send the recorded deposit (second run)"),
158107
158176
  flag2("--service", "<url>", "facilitator that stores the deposit memo (use with --submit; with --relay, on both runs)"),
158108
158177
  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 +158874,7 @@ var COMMANDS = [
158805
158874
  "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
158875
  "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
158876
  "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.`,
158877
+ "--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
158878
  "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
158879
  "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
158880
  `--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 +159700,17 @@ async function runUnshieldCommand(args, values, deps, io) {
159631
159700
  setArg("--to", getAssociatedTokenAddressSync(mint, owner, true, TOKEN_PROGRAM_ID).toBase58());
159632
159701
  setArg("--recipient-owner", owner.toBase58());
159633
159702
  }
159634
- if (values.get("--amount") === "all") setArg("--amount", await sendAllAtomic(values, deps, io, openStore));
159635
159703
  let id = values.get("--unshield-id");
159636
- if (id === void 0) {
159704
+ const ensureId = () => {
159705
+ if (id !== void 0) return;
159637
159706
  id = `unshield-${randomBytes35(6).toString("hex")}`;
159638
159707
  setArg("--unshield-id", id);
159639
159708
  values.set(UNSHIELD_MADE_ID, id);
159640
159709
  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
159710
  `);
159642
- }
159711
+ };
159712
+ if (values.get("--amount") === "all") setArg("--amount", await sendAllAtomic(values, deps, io, openStore, ensureId));
159713
+ ensureId();
159643
159714
  const gated = await spendGate(callArgs, values, deps, io, { amountAtomic: values.get("--amount"), again: ` with --unshield-id ${id}` });
159644
159715
  let answer;
159645
159716
  const returned = await deps.unshieldMain(gated, { output: (result) => {
@@ -159743,25 +159814,86 @@ async function refreshAfterUnshield(walletFile, values, deps, io) {
159743
159814
  restore();
159744
159815
  }
159745
159816
  }
159746
- async function sendAllAtomic(values, deps, io, openStore) {
159817
+ async function sendAllAtomic(values, deps, io, openStore, ensureId) {
159747
159818
  const store = openStore();
159748
159819
  let report = computeBalance({ store, snapshotRead: readBalanceSnapshot(store), source: "local", now: deps.now() });
159749
159820
  if (report.status !== "current") report = await refreshAfterUnshield(values.get("--wallet"), values, deps, io) ?? report;
159821
+ 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
159822
  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
159823
  if (empty) {
159755
159824
  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
159825
  }
159757
159826
  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)}
159827
+ const join40 = parts?.joining > 0 || parts?.parts > 1 && report.status === "current";
159828
+ if (join40) report = await joinForSendAll(report, parts, store, values, deps, io, ensureId);
159829
+ if (report.status !== "current" || !/^[1-9][0-9]*$/.test(String(report.atomic ?? ""))) throw notCurrent(report);
159830
+ if (!join40) io.err(`--amount all: the whole private balance, ${usdcOr(report.atomic)}
159762
159831
  `);
159763
159832
  return report.atomic;
159764
159833
  }
159834
+ var SEND_ALL_JOIN_ROUNDS = 3;
159835
+ async function joinForSendAll(report, parts, store, values, deps, io, ensureId) {
159836
+ const count2 = parts.parts + parts.joining;
159837
+ const known = /^[0-9]+$/.test(String(report.atomic ?? ""));
159838
+ 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
159839
+ `);
159840
+ ensureId();
159841
+ if (parts.joining === 0) {
159842
+ const waitMs = Math.floor((deps.random ?? Math.random)() * (JOIN_SOON_MAX_MS + 1));
159843
+ 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
159844
+ `);
159845
+ await (deps.sleep ?? ((ms) => new Promise((done) => {
159846
+ setTimeout(done, ms);
159847
+ })))(waitMs);
159848
+ }
159849
+ const history = values.get("--history-rpc");
159850
+ const mergeArgs = [
159851
+ "--wallet",
159852
+ values.get("--wallet"),
159853
+ "--passphrase-file",
159854
+ values.get("--passphrase-file"),
159855
+ "--program",
159856
+ values.get("--program"),
159857
+ "--proof-tools",
159858
+ values.get("--payer-proof-tools"),
159859
+ "--profile",
159860
+ values.get("--profile"),
159861
+ "--facilitator",
159862
+ new URL(values.get("--facilitator")).origin,
159863
+ ...values.has("--rpc-file") ? ["--rpc-file", values.get("--rpc-file")] : [],
159864
+ ...history ? ["--history-rpc", history] : []
159865
+ ];
159866
+ const startedAt = deps.now();
159867
+ let joined = 0;
159868
+ let left = parts;
159869
+ let keep;
159870
+ let read;
159871
+ for (let round = 1; round <= SEND_ALL_JOIN_ROUNDS; round += 1) {
159872
+ let answer;
159873
+ try {
159874
+ await deps.mergeMain(mergeArgs, { output: (result) => {
159875
+ answer = result;
159876
+ } });
159877
+ } catch (error) {
159878
+ io.err("note: the join stopped, so the unshield did not start; nothing was sent\n");
159879
+ throw error;
159880
+ }
159881
+ const merged = (answer?.merged ?? answer?.resumed ?? []).filter((entry) => entry?.state === "merged").length;
159882
+ joined += merged;
159883
+ keep = answer?.keep?.amountAtomic ?? keep;
159884
+ read = await refreshAfterUnshield(values.get("--wallet"), values, deps, io);
159885
+ left = joinParts(store);
159886
+ if (read?.status === "current" && left?.parts <= 1 && left.joining === 0) {
159887
+ const seconds2 = Math.max(0, Math.round((deps.now() - startedAt) / 1e3));
159888
+ io.err(`joined the ${count2} parts in ${Math.floor(seconds2 / 60)} min ${seconds2 % 60} s; sending all ${usdcOr(read.atomic)} now
159889
+ `);
159890
+ return read;
159891
+ }
159892
+ if (!read || read.status !== "current" || merged === 0) break;
159893
+ }
159894
+ const remaining = left ? left.parts + left.joining : void 0;
159895
+ 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`);
159896
+ }
159765
159897
  async function runUnshieldCancel(values, deps, io) {
159766
159898
  const walletFile = resolve38(values.get("--wallet"));
159767
159899
  const passphrase2 = payerPassphrase(values, deps);