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 +5 -5
- package/manifest.json +4 -4
- package/package.json +3 -3
- package/shinjuku-wallet.mjs +324 -51
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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": "
|
|
5
|
-
"bytes":
|
|
6
|
-
"sourceCommit": "
|
|
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-
|
|
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.
|
|
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": "
|
|
7
|
-
"shinjukuSha256": "
|
|
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",
|
package/shinjuku-wallet.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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 = (
|
|
151919
|
-
const merges = plan.merge.map((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
155093
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
159759
|
-
|
|
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 ? {
|
|
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`;
|