@candledottv/cli 0.13.17 → 0.13.19

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.
Files changed (3) hide show
  1. package/README.md +18 -7
  2. package/dist/index.js +1148 -314
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -532,7 +532,7 @@ var init_terminal_theme = __esm(() => {
532
532
  });
533
533
 
534
534
  // src/version.ts
535
- var CLI_VERSION = "0.13.17";
535
+ var CLI_VERSION = "0.13.19";
536
536
 
537
537
  // src/presentation.ts
538
538
  function terminalStyle(deps, stream, machine = false) {
@@ -10551,8 +10551,8 @@ function createSolanaRpc(url, fetchFn, sleep) {
10551
10551
  const r = await read("simulateTransaction", [
10552
10552
  txBase64,
10553
10553
  withMinContextSlot({
10554
- sigVerify: false,
10555
- replaceRecentBlockhash: true,
10554
+ sigVerify: opts?.sigVerify ?? false,
10555
+ replaceRecentBlockhash: !opts?.sigVerify,
10556
10556
  commitment: "finalized",
10557
10557
  encoding: "base64",
10558
10558
  accounts: { encoding: "base64", addresses }
@@ -10686,6 +10686,7 @@ function withContextFloor(rpc, floor) {
10686
10686
  const finalized = (opts) => {
10687
10687
  const base = required(opts);
10688
10688
  return {
10689
+ ...opts?.sigVerify !== undefined ? { sigVerify: opts.sigVerify } : {},
10689
10690
  get minContextSlot() {
10690
10691
  return base.minContextSlot;
10691
10692
  },
@@ -11163,6 +11164,25 @@ var init_solana_alt = __esm(() => {
11163
11164
  ];
11164
11165
  });
11165
11166
 
11167
+ // src/account.ts
11168
+ async function fetchAccount(deps, apiUrl, apiKey) {
11169
+ const identity = await apiRequest("/api/v1/agent/wallets/embedded", {
11170
+ auth: "key",
11171
+ credentials: { apiKey },
11172
+ apiUrl,
11173
+ fetch: deps.fetch,
11174
+ env: deps.env
11175
+ });
11176
+ const body = identity.ok ? identity.body : undefined;
11177
+ const account = body?.account;
11178
+ if (account)
11179
+ return { account, ...body?.username ? { username: body.username } : {} };
11180
+ return { failure: identity.ok ? "no account in the response" : identity.message };
11181
+ }
11182
+ var init_account = __esm(() => {
11183
+ init_client();
11184
+ });
11185
+
11166
11186
  // src/profiles.ts
11167
11187
  function assertOrdinaryProfile(config, profile) {
11168
11188
  if (profile !== undefined && config.profiles?.[profile]?.apiKeyItem !== undefined) {
@@ -11297,7 +11317,7 @@ function credentialEnvOverrides(env) {
11297
11317
  }
11298
11318
  function identityLine(profile, account, apiUrl, overrides, username, options = {}) {
11299
11319
  const address = account === undefined ? undefined : shortId(account, options.full);
11300
- const shown = overrides?.length ? `unknown (${overrides.join(", ")} override)` : username && address ? `${username} (${address})` : address ?? "unknown";
11320
+ const shown = overrides?.length ? address ? `${address} (${overrides.join(", ")} override)` : `unknown (${overrides.join(", ")} override)` : username && address ? `${username} (${address})` : address ?? "unknown";
11301
11321
  const name = profile ?? "none";
11302
11322
  const host = apiUrl.replace(/^https:\/\//i, "").replace(/\/+$/, "");
11303
11323
  const sep = options.style ? symbols(options.style).note : "·";
@@ -11308,7 +11328,20 @@ async function printIdentity(ctx) {
11308
11328
  return;
11309
11329
  const config = await ctx.deps.readConfig();
11310
11330
  const fields2 = effectiveProfileFields(config, ctx.profile);
11311
- const line = identityLine(ctx.profile, fields2.account, ctx.apiUrl, credentialEnvOverrides(ctx.deps.env), fields2.username, identityStyle(ctx.deps, "stdout"));
11331
+ const overrides = credentialEnvOverrides(ctx.deps.env);
11332
+ let account = fields2.account;
11333
+ let username = fields2.username;
11334
+ if (overrides.length > 0) {
11335
+ account = undefined;
11336
+ username = undefined;
11337
+ const key = ctx.deps.env.CANDLE_API_KEY?.trim();
11338
+ if (key) {
11339
+ const lookup = await fetchAccount(ctx.deps, ctx.apiUrl, key);
11340
+ if (lookup.account)
11341
+ account = lookup.account;
11342
+ }
11343
+ }
11344
+ const line = identityLine(ctx.profile, account, ctx.apiUrl, overrides.length > 0 ? overrides : undefined, username, identityStyle(ctx.deps, "stdout"));
11312
11345
  ctx.deps.stdout.write(`${line}
11313
11346
 
11314
11347
  `);
@@ -11352,6 +11385,7 @@ function profileTable(config, now) {
11352
11385
  }
11353
11386
  var ProfileFleetOnlyError, PRE_PROFILE_FIELDS, API_KEY_PREFIXES, API_KEY_RANDOM_LENGTH = 43, INVALID_RPC_HOST = "invalid (fix with profile set)";
11354
11387
  var init_profiles = __esm(() => {
11388
+ init_account();
11355
11389
  init_presentation();
11356
11390
  init_render();
11357
11391
  ProfileFleetOnlyError = class ProfileFleetOnlyError extends Error {
@@ -12415,6 +12449,7 @@ var init_errors2 = __esm(() => {
12415
12449
  "VAULT_FIELD_UNKNOWN",
12416
12450
  "VAULT_KDF_OUT_OF_BOUNDS",
12417
12451
  "VAULT_UNLOCK_FAILED",
12452
+ "VAULT_TERMINAL_REQUIRED",
12418
12453
  "VAULT_BLOB_TAMPERED",
12419
12454
  "VAULT_INDEX_INVALID",
12420
12455
  "VAULT_OLDER_COPY",
@@ -15295,24 +15330,44 @@ function teeWalletList(rows) {
15295
15330
  return rows.map(describeWallet).join("; ");
15296
15331
  }
15297
15332
  async function completeTradingWallet(ctx, row, appId, scope, chain2 = "solana", key) {
15333
+ const inspected = await inspectTradingWallet(ctx, row, appId, scope, chain2, key, true);
15334
+ if (inspected.failures[0])
15335
+ throw inspected.failures[0];
15336
+ return inspected.row;
15337
+ }
15338
+ async function inspectTradingWallet(ctx, row, appId, scope, chain2 = "solana", key, firstOnly = false) {
15339
+ const failures = [];
15298
15340
  if (rowChain(row) !== chain2)
15299
- throw chainMismatch(`TEE wallet ${describeWallet(row)}`, rowChain(row), chain2);
15341
+ failures.push(chainMismatch(`TEE wallet ${describeWallet(row)}`, rowChain(row), chain2));
15342
+ if (firstOnly && failures.length)
15343
+ return { failures };
15300
15344
  if (!row.active)
15301
- throw new TradingError("TEE_WALLET_INACTIVE", `The payer must be a verified-active ${chainName(chain2)} TEE wallet.`);
15345
+ failures.push(new TradingError("TEE_WALLET_INACTIVE", `The payer must be a verified-active ${chainName(chain2)} TEE wallet.`));
15346
+ if (firstOnly && failures.length)
15347
+ return { failures };
15302
15348
  if (scope === "launch:write" && row.allowLaunch !== true)
15303
- throw new TradingError("LAUNCH_NOT_ALLOWED", `The account owner must turn on allowLaunch for wallet ${row.id}: run candle wallets allow-launch ${row.id} on a profile signed in with candle auth login. An agent key cannot.`);
15349
+ failures.push(new TradingError("LAUNCH_NOT_ALLOWED", `The account owner must turn on allowLaunch for wallet ${row.id}: run candle wallets allow-launch ${row.id} on a profile signed in with candle auth login. An agent key cannot.`));
15350
+ if (firstOnly && failures.length)
15351
+ return { failures };
15304
15352
  if (!appId || !row.privyWalletId)
15305
- throw new TradingError("SIGNER_UNAVAILABLE", "The server did not return its relay public identifiers.");
15353
+ failures.push(new TradingError("SIGNER_UNAVAILABLE", "The server did not return its relay public identifiers."));
15354
+ if (firstOnly && failures.length)
15355
+ return { failures };
15306
15356
  const signer = await localSignerFor(ctx.deps, row.id, row.signerQuorumId);
15307
15357
  if (!signer)
15308
- throw new TradingError("SIGNER_UNAVAILABLE", await signerUnavailableMessage(ctx, row, key));
15358
+ failures.push(new TradingError("SIGNER_UNAVAILABLE", await signerUnavailableMessage(ctx, row, key)));
15359
+ if (failures.length || !signer || !row.privyWalletId)
15360
+ return { failures };
15309
15361
  return {
15310
- id: row.id,
15311
- address: row.address,
15312
- privyWalletId: row.privyWalletId,
15313
- appId,
15314
- signer: signer.pem,
15315
- chain: row.chain === "evm" ? "evm" : "solana"
15362
+ failures,
15363
+ row: {
15364
+ id: row.id,
15365
+ address: row.address,
15366
+ privyWalletId: row.privyWalletId,
15367
+ appId,
15368
+ signer: signer.pem,
15369
+ chain: row.chain === "evm" ? "evm" : "solana"
15370
+ }
15316
15371
  };
15317
15372
  }
15318
15373
  async function signerUnavailableMessage(ctx, row, key) {
@@ -15345,7 +15400,18 @@ async function tradingWallet(ctx, key, name, scope, chain2 = "solana") {
15345
15400
  return { ...wallet, scopes };
15346
15401
  }
15347
15402
  async function tradingPayer(ctx, key, name, scope = "swap:write", chain2 = "solana") {
15348
- const { rows, appId, scopes, keyPrefix } = await listTradingWallets(ctx, key, scope);
15403
+ const listing = await listTradingWallets(ctx, key, scope);
15404
+ const selected = await selectTradingPayer(ctx, key, name, listing, chain2);
15405
+ if (selected.kind === "embedded")
15406
+ return selected;
15407
+ const wallet = await completeTradingWallet(ctx, selected.row, listing.appId, scope, chain2, {
15408
+ apiKey: key,
15409
+ keyPrefix: listing.keyPrefix
15410
+ });
15411
+ return { kind: "tee", wallet, scopes: listing.scopes };
15412
+ }
15413
+ async function selectTradingPayer(ctx, key, name, listing, chain2 = "solana") {
15414
+ const { rows, scopes, keyPrefix } = listing;
15349
15415
  const discovered = embeddedSchema.parse(await request(ctx, key, "/api/v1/agent/wallets/embedded"));
15350
15416
  const wallets = discovered.wallets;
15351
15417
  const embeddedOn = {
@@ -15364,14 +15430,7 @@ async function tradingPayer(ctx, key, name, scope = "swap:write", chain2 = "sola
15364
15430
  ].join("; ");
15365
15431
  if (name === undefined) {
15366
15432
  if (onChain.length === 1 && !embedded)
15367
- return {
15368
- kind: "tee",
15369
- wallet: await completeTradingWallet(ctx, onChain[0], appId, scope, chain2, {
15370
- apiKey: key,
15371
- keyPrefix
15372
- }),
15373
- scopes
15374
- };
15433
+ return { kind: "tee", row: onChain[0] };
15375
15434
  if (onChain.length === 0 && embedded)
15376
15435
  return asEmbedded();
15377
15436
  if (onChain.length === 0 && deniedAddress !== undefined)
@@ -15380,14 +15439,7 @@ async function tradingPayer(ctx, key, name, scope = "swap:write", chain2 = "sola
15380
15439
  }
15381
15440
  const matches = rows.filter((row) => matchesName(row, name));
15382
15441
  if (matches.length === 1)
15383
- return {
15384
- kind: "tee",
15385
- wallet: await completeTradingWallet(ctx, matches[0], appId, scope, chain2, {
15386
- apiKey: key,
15387
- keyPrefix
15388
- }),
15389
- scopes
15390
- };
15442
+ return { kind: "tee", row: matches[0] };
15391
15443
  if (matches.length === 0) {
15392
15444
  if (embedded !== undefined && sameAddress(embedded, name))
15393
15445
  return asEmbedded();
@@ -16916,8 +16968,431 @@ var init_solana_verify = __esm(() => {
16916
16968
  ]);
16917
16969
  });
16918
16970
 
16919
- // src/commands/swap.ts
16971
+ // src/trading-checks.ts
16920
16972
  import { randomUUID } from "node:crypto";
16973
+ function failure2(id, error) {
16974
+ if (error instanceof TradingUsage)
16975
+ throw error;
16976
+ return {
16977
+ id,
16978
+ status: "fail",
16979
+ code: error instanceof TradingError ? error.code : "INVALID_RESPONSE",
16980
+ message: error instanceof Error ? describeRpcFailure(error) : "The check could not complete.",
16981
+ ...error instanceof TradingError && error.suggestion ? { suggestion: error.suggestion } : {}
16982
+ };
16983
+ }
16984
+ function balanceProblem(value) {
16985
+ return /InsufficientFunds(?:ForFee)?|AccountNotFound|insufficient[_ ](?:funds|balance|lamports)/i.test(value instanceof Error ? `${value instanceof TradingError ? value.code : ""} ${value.message}` : String(value));
16986
+ }
16987
+ function needsBalance(id) {
16988
+ return {
16989
+ id,
16990
+ status: "skipped",
16991
+ needs: "balance",
16992
+ message: "This check needs balance; fund the wallet and check again."
16993
+ };
16994
+ }
16995
+ async function diagnosticListing(ctx, key) {
16996
+ try {
16997
+ return await listTradingWallets(ctx, key);
16998
+ } catch (error) {
16999
+ if (error instanceof exports_external.ZodError)
17000
+ throw new TradingError("INVALID_RESPONSE", "Trading wallet discovery returned a malformed page.");
17001
+ throw error;
17002
+ }
17003
+ }
17004
+ async function readWalletRecords(ctx, key) {
17005
+ const rows = [];
17006
+ const seen = new Set;
17007
+ let cursor;
17008
+ for (let page = 0;page < 25; page++) {
17009
+ const result = exports_external.object({
17010
+ page: exports_external.array(linkedRow),
17011
+ isDone: exports_external.boolean(),
17012
+ continueCursor: exports_external.string().nullable().optional()
17013
+ }).safeParse(await request(ctx, key, `/api/v1/agent/wallets?limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`));
17014
+ if (!result.success)
17015
+ throw new TradingError("INVALID_RESPONSE", "Wallet records did not return a valid page.");
17016
+ rows.push(...result.data.page);
17017
+ if (result.data.isDone)
17018
+ return rows;
17019
+ cursor = result.data.continueCursor ?? undefined;
17020
+ if (!cursor || seen.has(cursor))
17021
+ break;
17022
+ seen.add(cursor);
17023
+ }
17024
+ throw new TradingError("INVALID_RESPONSE", "Wallet records did not complete.");
17025
+ }
17026
+ function walletRecordCheck(row, trading, records) {
17027
+ const found = records.filter((record) => record.address === row.address);
17028
+ const differences = {};
17029
+ const other = found[0];
17030
+ const count = trading.filter((record) => record.address === row.address).length;
17031
+ if (count !== 1 || found.length !== 1)
17032
+ differences.addressCount = { trading: count, wallets: found.length };
17033
+ for (const [left, right] of [
17034
+ ["id", "_id"],
17035
+ ["signerQuorumId", "signerQuorumId"],
17036
+ ["chain", "chain"],
17037
+ ["label", "label"],
17038
+ ["address", "address"]
17039
+ ]) {
17040
+ if (!other || row[left] !== other[right])
17041
+ differences[left] = { trading: row[left] ?? null, wallets: other?.[right] ?? null };
17042
+ }
17043
+ if (!other || !["ember-tee", "ember-hot"].includes(other.profile ?? ""))
17044
+ differences.profile = { trading: "TEE", wallets: other?.profile ?? null };
17045
+ if (other?.revokedAt !== undefined)
17046
+ differences.revokedAt = { trading: null, wallets: other.revokedAt };
17047
+ return Object.keys(differences).length ? {
17048
+ id: "record",
17049
+ status: "fail",
17050
+ code: "WALLET_RECORD_MISMATCH",
17051
+ message: `Trading and wallet records differ: ${JSON.stringify(differences)}.`,
17052
+ differences
17053
+ } : pass("record");
17054
+ }
17055
+ async function inspectReport(ctx, key, row, listing, records, payable, name) {
17056
+ const checks = [pass("key"), payable ? failure2("payable", payable) : pass("payable")];
17057
+ checks.push(listing.scopes.includes("swap:write") ? pass("scope") : failure2("scope", new TradingError("SCOPE_MISSING", "The bound key needs swap:write.")));
17058
+ const report = {
17059
+ ...row ? { id: row.id, address: row.address, label: row.label } : { name },
17060
+ ok: false,
17061
+ checks
17062
+ };
17063
+ if (!row) {
17064
+ checks.push(...["record", "active", "signer"].map((id) => skipped(id, "payable")));
17065
+ return { report };
17066
+ }
17067
+ checks.push(walletRecordCheck(row, listing.rows, records));
17068
+ const inspected = await inspectTradingWallet(ctx, row, listing.appId, "swap:write", "solana", {
17069
+ apiKey: key,
17070
+ keyPrefix: listing.keyPrefix
17071
+ });
17072
+ const active = inspected.failures.filter((error) => error.code !== "SIGNER_UNAVAILABLE");
17073
+ checks.push(...active.length ? active.map((error) => failure2("active", error)) : [pass("active")]);
17074
+ const signer = inspected.failures.find((error) => error.code === "SIGNER_UNAVAILABLE");
17075
+ checks.push(signer ? failure2("signer", signer) : pass("signer"));
17076
+ return { report, wallet: inspected.row };
17077
+ }
17078
+ function printWalletReports(ctx, reports, swap = false) {
17079
+ for (const report of reports)
17080
+ report.ok = !report.checks.some((check) => check.status === "fail");
17081
+ const failed = reports.filter((report) => !report.ok).length;
17082
+ const summary = { total: reports.length, pass: reports.length - failed, fail: failed };
17083
+ if (ctx.json) {
17084
+ ctx.deps.stdout.write(`${JSON.stringify({
17085
+ success: failed === 0,
17086
+ dryRun: true,
17087
+ wallets: reports,
17088
+ summary,
17089
+ ...swap ? { checks: reports[0]?.checks, quote: reports[0]?.quote } : {}
17090
+ })}
17091
+ `);
17092
+ } else {
17093
+ for (const report of reports) {
17094
+ const first = report.checks.find((check) => check.status === "fail");
17095
+ ctx.deps.stdout.write(`${safeText(report.label ?? report.name ?? report.id ?? "wallet")} ${safeText(report.address ?? "")} ${report.ok ? "PASS" : "FAIL"}${first ? ` ${first.id}/${first.code}` : ""}
17096
+ `);
17097
+ if (reports.length === 1) {
17098
+ for (const check of report.checks)
17099
+ ctx.deps.stdout.write(` ${check.id} ${check.status}${check.code ? ` ${check.code}` : ""}${check.message ? ` ${safeText(check.message)}` : ""}${check.because ? ` (${check.because})` : ""}
17100
+ `);
17101
+ if (report.quote)
17102
+ ctx.deps.stdout.write(` quote ${safeText(JSON.stringify(report.quote))}
17103
+ `);
17104
+ }
17105
+ }
17106
+ if (reports.length > 1)
17107
+ ctx.deps.stdout.write(`${summary.pass} pass, ${summary.fail} fail
17108
+ `);
17109
+ }
17110
+ return failed ? 1 : 0;
17111
+ }
17112
+ async function buildCheck(ctx, key, wallet, report, from, to, amount, slippage, rpcUrl, sign3) {
17113
+ const checks = report.checks;
17114
+ const blocked = checks.find((check) => check.status === "fail");
17115
+ if (blocked) {
17116
+ checks.push(skipped("quote", blocked.id), skipped("spend", "quote"));
17117
+ if (sign3)
17118
+ checks.push(skipped("sign", "spend"));
17119
+ return;
17120
+ }
17121
+ const solana = lazySolanaClient(ctx, rpcUrl);
17122
+ let transaction;
17123
+ let quote;
17124
+ let amountRaw;
17125
+ let minimumRaw;
17126
+ let feeRaw;
17127
+ const fromBase = baseAsset(from);
17128
+ const toBase = baseAsset(to);
17129
+ const kind = fromBase && toBase ? "swap" : "trade";
17130
+ try {
17131
+ const decimals = await decimalsFor(ctx, from, solana);
17132
+ const outDecimals = await decimalsFor(ctx, to, solana);
17133
+ amountRaw = rawAmount(amount, decimals);
17134
+ if (BigInt(amountRaw) > BigInt(Number.MAX_SAFE_INTEGER))
17135
+ throw new TradingError("INVALID_AMOUNT", "Amount exceeds the venue's exact integer range.");
17136
+ const payer = { type: "linked", linkedWalletId: wallet.id };
17137
+ const clientTradeId = `swap-${randomUUID()}`;
17138
+ const built = kind === "swap" ? await request(ctx, key, "/api/v1/agent/swap/build", {
17139
+ clientTradeId,
17140
+ from,
17141
+ to,
17142
+ amountRaw,
17143
+ maxSlippageBps: slippage,
17144
+ payer
17145
+ }) : await request(ctx, key, "/api/v1/trade/agent/build", {
17146
+ clientTradeId,
17147
+ chain: "solana",
17148
+ mint: fromBase ? to : from,
17149
+ side: fromBase ? "buy" : "sell",
17150
+ quoteAsset: (fromBase ?? toBase)?.toLowerCase(),
17151
+ amountRaw,
17152
+ maxSlippageBps: slippage,
17153
+ payer
17154
+ });
17155
+ const parsed = swapBuildSchema.safeParse(kind === "swap" ? built.payload : built);
17156
+ if (!parsed.success || built.job || built.status === "executed")
17157
+ throw new TradingError("INVALID_RESPONSE", "Candle did not return a complete built quote.");
17158
+ const data = parsed.data;
17159
+ if (kind === "swap" && (data.venue !== "jupiter" || data.recipient !== wallet.address))
17160
+ throw new TradingError("INVALID_RESPONSE", "A base swap must use Jupiter and return to its payer.");
17161
+ if (kind === "trade" && (built.chain !== "solana" || built.walletAddress !== wallet.address))
17162
+ throw new TradingError("INVALID_RESPONSE", "The token build does not name the requested Solana payer.");
17163
+ const artifacts = kind === "swap" ? data : data.artifacts;
17164
+ if (!artifacts)
17165
+ throw new TradingError("INVALID_RESPONSE", "Missing quote artifacts.");
17166
+ if (kind === "trade" && data.artifacts?.quoteAsset !== (fromBase ?? toBase)?.toLowerCase())
17167
+ throw new TradingError("PAIR_UNSUPPORTED", "The token settles in a different quote asset.");
17168
+ if (data.expiresAt <= ctx.deps.now())
17169
+ throw new TradingError("QUOTE_EXPIRED", "The quote expired; build a fresh check.");
17170
+ minimumRaw = !fromBase && artifacts.venue === "curve" ? (BigInt(data.minOutRaw) > BigInt(data.fee.feeRaw) ? BigInt(data.minOutRaw) - BigInt(data.fee.feeRaw) : 0n).toString() : data.minOutRaw;
17171
+ quote = {
17172
+ venue: (kind === "trade" ? data.artifacts?.quoteSource : undefined) ?? artifacts.venue,
17173
+ priceImpactPct: artifacts.priceImpactPct ?? null,
17174
+ fee: data.fee,
17175
+ minimumReceived: `${decimalAmount(minimumRaw, outDecimals)} ${to}`,
17176
+ minOutRaw: data.minOutRaw,
17177
+ minimumReceivedRaw: minimumRaw,
17178
+ tokenRisks: artifacts.tokenRisks ?? []
17179
+ };
17180
+ const tx = kind === "swap" ? data.transactionsBase64?.[0] : data.artifacts?.transactionBase64;
17181
+ if (!tx || kind === "swap" && data.transactionsBase64?.length !== 1)
17182
+ throw new TradingError("INVALID_RESPONSE", "A TEE swap must contain exactly one same-chain transaction.");
17183
+ transaction = tx;
17184
+ feeRaw = BigInt(data.fee.feeRaw);
17185
+ report.quote = quote;
17186
+ checks.push(pass("quote"));
17187
+ } catch (error) {
17188
+ checks.push(balanceProblem(error) ? needsBalance("quote") : failure2("quote", error), skipped("spend", "quote"));
17189
+ if (sign3)
17190
+ checks.push(skipped("sign", "spend"));
17191
+ return;
17192
+ }
17193
+ const assetOf2 = (asset) => asset === "SOL" ? "SOL" : BASES[asset]?.mint ?? asset;
17194
+ let reader;
17195
+ try {
17196
+ reader = await solana();
17197
+ const problem = await tradingRead(ctx, reader, () => solanaSpendProblem(transaction, wallet.address, reader.rpc, {
17198
+ kind: "swap",
17199
+ spend: assetOf2(from),
17200
+ maxSpendRaw: BigInt(amountRaw) + (kind === "trade" && fromBase ? feeRaw : 0n),
17201
+ receive: { asset: assetOf2(to), minRaw: BigInt(minimumRaw) }
17202
+ }));
17203
+ if (problem) {
17204
+ checks.push(balanceProblem(problem) ? needsBalance("spend") : failure2("spend", new TradingError("INVALID_RESPONSE", `The transaction Candle built does not match the quote (${safeText(problem)}); nothing was signed.`)));
17205
+ if (sign3)
17206
+ checks.push(skipped("sign", "spend"));
17207
+ return;
17208
+ }
17209
+ checks.push(pass("spend"));
17210
+ } catch (error) {
17211
+ checks.push(balanceProblem(error) ? needsBalance("spend") : failure2("spend", error));
17212
+ if (sign3)
17213
+ checks.push(skipped("sign", "spend"));
17214
+ return;
17215
+ }
17216
+ if (!sign3)
17217
+ return;
17218
+ try {
17219
+ const signed = await relaySign(ctx, key, wallet, transaction);
17220
+ const decoded = decodeTransaction(decodeStrictBase64(signed));
17221
+ const original = decodeTransaction(decodeStrictBase64(transaction));
17222
+ if (decoded.message.staticKeys[0] !== wallet.address || !Buffer.from(decoded.message.bytes).equals(Buffer.from(original.message.bytes)) || !ed25519.verify(decoded.signatures[0], decoded.message.bytes, base58.decode(wallet.address)))
17223
+ throw new TradingError("SIGNATURE_INVALID", "The relay did not return the payer's signature over the built message.");
17224
+ checks.push(pass("sign"));
17225
+ try {
17226
+ const simulation = await tradingRead(ctx, reader, () => reader.rpc.simulateTransaction(signed, [], { sigVerify: true }));
17227
+ const problem = simulation.err == null ? undefined : JSON.stringify(simulation.err);
17228
+ if (!problem)
17229
+ checks.push(pass("simulation"));
17230
+ else if (balanceProblem(problem))
17231
+ checks.push(needsBalance("simulation"));
17232
+ else if (/BlockhashNotFound|blockhash not found|blockhash.*expir|block.?height.*exceed/i.test(problem))
17233
+ checks.push({ id: "simulation", status: "skipped", message: "The blockhash expired; run a fresh check." });
17234
+ else
17235
+ checks.push({
17236
+ ...failure2("simulation", new TradingError("SIGN_FAILED", "Signed simulation failed.")),
17237
+ cause: problem
17238
+ });
17239
+ } catch (error) {
17240
+ const problem = error instanceof Error ? error.message : String(error);
17241
+ if (balanceProblem(problem))
17242
+ checks.push(needsBalance("simulation"));
17243
+ else if (/BlockhashNotFound|blockhash not found|blockhash.*expir|block.?height.*exceed/i.test(problem))
17244
+ checks.push({ id: "simulation", status: "skipped", message: "The blockhash expired; run a fresh check." });
17245
+ else if (error instanceof TradingError && error.code === "RPC_RATE_LIMITED")
17246
+ checks.push(failure2("simulation", error));
17247
+ else
17248
+ checks.push({
17249
+ ...failure2("simulation", new TradingError("SIGN_FAILED", "Signed simulation could not complete.")),
17250
+ cause: error instanceof TradingError ? error.code : "RPC_FAILED"
17251
+ });
17252
+ }
17253
+ } catch (error) {
17254
+ checks.push({
17255
+ ...failure2("sign", new TradingError("SIGN_FAILED", "The signer did not produce a verified signature.")),
17256
+ cause: error instanceof TradingError ? error.code : "INVALID_RESPONSE"
17257
+ });
17258
+ }
17259
+ }
17260
+ async function teeCheck(args, ctx) {
17261
+ const parsed = parseArgs(args, { valueFlags: ["--label-prefix", "--max", "--rpc-url"], booleanFlags: ["--sign"] });
17262
+ if ("error" in parsed) {
17263
+ writeUsageFailure(ctx.deps, parsed.error, ctx.json);
17264
+ return 2;
17265
+ }
17266
+ const max = Number(parsed.values["--max"] ?? "10");
17267
+ if (!parsed.positionals.length && parsed.values["--label-prefix"] === undefined || !Number.isSafeInteger(max) || max < 1) {
17268
+ writeUsageFailure(ctx.deps, "Usage: candle tee check <address|id|label>... [--label-prefix <p>] [--sign] [--max <n>] [--rpc-url <url>]", ctx.json);
17269
+ return 2;
17270
+ }
17271
+ try {
17272
+ const key = await tradingKey(ctx);
17273
+ const listing = await diagnosticListing(ctx, key);
17274
+ const names = [...new Set(parsed.positionals)];
17275
+ const prefix = parsed.values["--label-prefix"];
17276
+ if (prefix !== undefined) {
17277
+ const selected2 = listing.rows.filter((row) => row.label?.startsWith(prefix));
17278
+ if (!selected2.length)
17279
+ throw new TradingError("TEE_WALLET_REQUIRED", `No TEE wallet matches this prefix. Bound to this key: ${listing.rows.map(describeWallet).join("; ") || "none"}.`);
17280
+ for (const row of selected2)
17281
+ names.push(row.id);
17282
+ }
17283
+ const selected = new Set;
17284
+ const targets = names.filter((name) => {
17285
+ const matches = listing.rows.filter((row) => matchesName(row, name));
17286
+ const id = matches.length === 1 ? matches[0].id : `name:${name}`;
17287
+ if (selected.has(id))
17288
+ return false;
17289
+ selected.add(id);
17290
+ return true;
17291
+ });
17292
+ const sign3 = parsed.booleans.has("--sign");
17293
+ if (sign3 && targets.length > max)
17294
+ throw new TradingError("VALIDATION_FAILED", `Signing ${targets.length} wallets exceeds the batch cap ${max}; raise it explicitly with --max.`);
17295
+ const records = await readWalletRecords(ctx, key);
17296
+ const reports = [];
17297
+ for (const name of targets) {
17298
+ const matches = listing.rows.filter((row) => matchesName(row, name));
17299
+ const payable = matches.length === 1 ? undefined : new TradingError("TEE_WALLET_REQUIRED", `"${safeText(name)}" matches ${matches.length} TEE wallets. Bound to this key: ${listing.rows.map(describeWallet).join("; ") || "no TEE wallets bound to this key"}.`);
17300
+ const { report, wallet } = await inspectReport(ctx, key, matches.length === 1 ? matches[0] : undefined, listing, records, payable, name);
17301
+ if (sign3 && wallet)
17302
+ await buildCheck(ctx, key, wallet, report, "SOL", "USDC", "0.000001", 50, parsed.values["--rpc-url"], true);
17303
+ else
17304
+ report.checks.push(skipped("sign", sign3 ? report.checks.find((check) => check.status === "fail")?.id ?? "signer" : "--sign not set"));
17305
+ reports.push(report);
17306
+ }
17307
+ return printWalletReports(ctx, reports);
17308
+ } catch (error) {
17309
+ return tradingFailure(ctx, error);
17310
+ }
17311
+ }
17312
+ async function swapCheck(args, ctx) {
17313
+ const parsed = parseArgs(args, {
17314
+ valueFlags: ["--amount", "--percent", "--wallet", "--to", "--client-trade-id", "--slippage-bps", "--rpc-url"],
17315
+ booleanFlags: ["--dry-run", "--yes", "--wait"]
17316
+ });
17317
+ if ("error" in parsed) {
17318
+ writeUsageFailure(ctx.deps, parsed.error, ctx.json);
17319
+ return 2;
17320
+ }
17321
+ try {
17322
+ if (parsed.booleans.has("--yes") || parsed.values["--client-trade-id"] !== undefined || parsed.values["--percent"] !== undefined)
17323
+ throw new TradingError("VALIDATION_FAILED", "--dry-run refuses --yes, --client-trade-id and --percent.");
17324
+ if (parsed.positionals.length !== 2 || !parsed.values["--amount"]) {
17325
+ writeUsageFailure(ctx.deps, "Usage: candle swap <from> <to> --amount <n> [--wallet <w>] --dry-run", ctx.json);
17326
+ return 2;
17327
+ }
17328
+ const slippage = Number(parsed.values["--slippage-bps"] ?? "50");
17329
+ if (!Number.isInteger(slippage) || slippage < 0 || slippage > 5000) {
17330
+ writeUsageFailure(ctx.deps, "Slippage must be an integer from 0 to 5000 bps.", ctx.json);
17331
+ return 2;
17332
+ }
17333
+ const from = classifyAsset(parsed.positionals[0]);
17334
+ const to = classifyAsset(parsed.positionals[1]);
17335
+ if (from.chain !== "solana" || to.chain !== "solana")
17336
+ throw new TradingError("VALIDATION_FAILED", "Dry run checks Solana TEE pairs; Hood and bridge pairs are unsupported.");
17337
+ if (parsed.values["--to"] !== undefined || parsed.booleans.has("--wait")) {
17338
+ writeUsageFailure(ctx.deps, "--to and --wait are for a bridge.", ctx.json);
17339
+ return 2;
17340
+ }
17341
+ if (from.asset === to.asset || !from.base && !to.base)
17342
+ throw new TradingError("PAIR_UNSUPPORTED", "Choose distinct assets with SOL, USDC or CNDL on one side.");
17343
+ rawAmount(parsed.values["--amount"], 18);
17344
+ const key = await tradingKey(ctx);
17345
+ const listing = await diagnosticListing(ctx, key);
17346
+ let row;
17347
+ let payable;
17348
+ try {
17349
+ const selected = await selectTradingPayer(ctx, key, parsed.values["--wallet"], listing);
17350
+ if (selected.kind === "embedded")
17351
+ throw new TradingError("VALIDATION_FAILED", "dry run checks TEE payers");
17352
+ row = selected.row;
17353
+ } catch (error) {
17354
+ if (error instanceof TradingError && error.code === "VALIDATION_FAILED")
17355
+ throw error;
17356
+ if (!(error instanceof TradingError))
17357
+ throw error;
17358
+ payable = error;
17359
+ }
17360
+ const records = await readWalletRecords(ctx, key);
17361
+ const { report, wallet } = await inspectReport(ctx, key, row, listing, records, payable, parsed.values["--wallet"]);
17362
+ if (wallet)
17363
+ await buildCheck(ctx, key, wallet, report, from.asset, to.asset, parsed.values["--amount"], slippage, parsed.values["--rpc-url"], false);
17364
+ else
17365
+ report.checks.push(skipped("quote", report.checks.find((check) => check.status === "fail")?.id ?? "signer"), skipped("spend", "quote"));
17366
+ return printWalletReports(ctx, [report], true);
17367
+ } catch (error) {
17368
+ return tradingFailure(ctx, error);
17369
+ }
17370
+ }
17371
+ var pass = (id) => ({ id, status: "pass" }), skipped = (id, because) => ({ id, status: "skipped", because }), linkedRow;
17372
+ var init_trading_checks = __esm(() => {
17373
+ init_ed25519();
17374
+ init_esm();
17375
+ init_zod();
17376
+ init_args();
17377
+ init_swap();
17378
+ init_render();
17379
+ init_solana_alt();
17380
+ init_solana_endpoint();
17381
+ init_solana_verify();
17382
+ init_trading();
17383
+ linkedRow = exports_external.object({
17384
+ _id: exports_external.string(),
17385
+ address: exports_external.string(),
17386
+ chain: exports_external.string(),
17387
+ label: exports_external.string().optional(),
17388
+ signerQuorumId: exports_external.string().optional(),
17389
+ profile: exports_external.string().optional(),
17390
+ revokedAt: exports_external.number().optional()
17391
+ });
17392
+ });
17393
+
17394
+ // src/commands/swap.ts
17395
+ import { randomUUID as randomUUID2 } from "node:crypto";
16921
17396
  function tradingFailure(ctx, error, id) {
16922
17397
  if (error instanceof TradingUsage) {
16923
17398
  writeUsageFailure(ctx.deps, error.message, ctx.json);
@@ -17151,6 +17626,8 @@ async function assertDeferredExecuteSupported(ctx, key, id) {
17151
17626
  throw new TradingError("INVALID_RESPONSE", "The execute route answered for a trade that does not exist.");
17152
17627
  }
17153
17628
  async function swap(args, ctx) {
17629
+ if (args.includes("--dry-run"))
17630
+ return swapCheck(args, ctx);
17154
17631
  const parsed = parseArgs(args, {
17155
17632
  valueFlags: ["--amount", "--percent", "--wallet", "--to", "--client-trade-id", "--slippage-bps", "--rpc-url"],
17156
17633
  booleanFlags: ["--yes", "--wait"]
@@ -17160,7 +17637,7 @@ async function swap(args, ctx) {
17160
17637
  return 2;
17161
17638
  }
17162
17639
  const flags = parsed.values;
17163
- const id = flags["--client-trade-id"] ?? `swap-${randomUUID()}`;
17640
+ const id = flags["--client-trade-id"] ?? `swap-${randomUUID2()}`;
17164
17641
  const slippage = Number(flags["--slippage-bps"] ?? "50");
17165
17642
  if (!Number.isInteger(slippage) || slippage < 0 || slippage > AGENT_MAX_SLIPPAGE_BPS) {
17166
17643
  writeUsageFailure(ctx.deps, SLIPPAGE_RANGE_MESSAGE, ctx.json);
@@ -17381,17 +17858,17 @@ async function hoodDecimals(asset, rpc) {
17381
17858
  return decimals;
17382
17859
  }
17383
17860
  async function recordTradedToken(ctx, wallet, token) {
17384
- const skipped = (reason) => `Notice: the sealed EVM record was not updated for ${token} (${reason}). The leg landed. A later sweep still finds this token with --token ${token}, or with --from-block.`;
17861
+ const skipped2 = (reason) => `Notice: the sealed EVM record was not updated for ${token} (${reason}). The leg landed. A later sweep still finds this token with --token ${token}, or with --from-block.`;
17385
17862
  const append = ctx.deps.appendEvmRecord;
17386
17863
  let notice2;
17387
17864
  if (!append)
17388
- notice2 = skipped("this CLI build has no sealed EVM record writer");
17865
+ notice2 = skipped2("this CLI build has no sealed EVM record writer");
17389
17866
  else {
17390
17867
  try {
17391
17868
  const outcome = await append(ctx, { kind: "token", wallet: toChecksumAddress(wallet), token });
17392
- notice2 = outcome.appended ? outcome.notice : skipped(outcome.notice);
17869
+ notice2 = outcome.appended ? outcome.notice : skipped2(outcome.notice);
17393
17870
  } catch (error) {
17394
- notice2 = skipped(error instanceof Error ? error.message : "the append failed");
17871
+ notice2 = skipped2(error instanceof Error ? error.message : "the append failed");
17395
17872
  }
17396
17873
  }
17397
17874
  if (notice2)
@@ -17839,6 +18316,7 @@ var init_swap = __esm(() => {
17839
18316
  init_solana_lite();
17840
18317
  init_solana_verify();
17841
18318
  init_trading();
18319
+ init_trading_checks();
17842
18320
  SLIPPAGE_RANGE_MESSAGE = `maxSlippageBps must be an integer in bps, between 0 and ${AGENT_MAX_SLIPPAGE_BPS}`;
17843
18321
  settlementSchema = exports_external.object({
17844
18322
  state: exports_external.enum(["settled", "pending", "failed", "uncertain"]),
@@ -17892,11 +18370,11 @@ function writeScreenFailure(deps, result, context, json) {
17892
18370
  `)}
17893
18371
  `);
17894
18372
  }
17895
- function writeScreenLocalFailure(deps, failure2, json) {
18373
+ function writeScreenLocalFailure(deps, failure3, json) {
17896
18374
  if (json)
17897
- writeLocalFailure(deps, failure2, json);
18375
+ writeLocalFailure(deps, failure3, json);
17898
18376
  else
17899
- writeScreenMessage(deps, failure2.message, failure2.suggestion);
18377
+ writeScreenMessage(deps, failure3.message, failure3.suggestion);
17900
18378
  }
17901
18379
  function writeScreenUsageFailure(deps, message, json, screen) {
17902
18380
  if (json)
@@ -18000,14 +18478,14 @@ async function readSignerRoles(rpc, addresses, opts) {
18000
18478
  let requests = 0;
18001
18479
  let rateLimited = 0;
18002
18480
  let settled = 0;
18003
- let skipped = 0;
18481
+ let skipped2 = 0;
18004
18482
  let next = 0;
18005
18483
  let active = 0;
18006
18484
  const progress = () => {
18007
18485
  opts.onProgress?.({
18008
- completed: settled + skipped,
18486
+ completed: settled + skipped2,
18009
18487
  settled,
18010
- skipped,
18488
+ skipped: skipped2,
18011
18489
  total: planned,
18012
18490
  refusedGroups: failed.size,
18013
18491
  elapsedMs: opts.now() - startedAt
@@ -18152,7 +18630,7 @@ async function readSignerRoles(rpc, addresses, opts) {
18152
18630
  while (active < cap && next < tasks.length) {
18153
18631
  const task = tasks[next++];
18154
18632
  if (failed.has(task.group.id)) {
18155
- skipped += 1;
18633
+ skipped2 += 1;
18156
18634
  skippedAny = true;
18157
18635
  continue;
18158
18636
  }
@@ -28989,13 +29467,13 @@ __export(exports_vault_support, {
28989
29467
  runVaultCommand: () => runVaultCommand,
28990
29468
  requireVaultRaw: () => requireVaultRaw,
28991
29469
  requireTty: () => requireTty,
28992
- requirePromptStreams: () => requirePromptStreams,
28993
29470
  refusePromptsWithoutTerminal: () => refusePromptsWithoutTerminal,
28994
29471
  refuseEnvPassphrase: () => refuseEnvPassphrase,
28995
29472
  openWithTypedPassphrase: () => openWithTypedPassphrase,
28996
29473
  nonDefaultVaultFooter: () => nonDefaultVaultFooter,
28997
29474
  missingVault: () => missingVault,
28998
29475
  menuSafeText: () => menuSafeText,
29476
+ isNoTerminalRefusal: () => isNoTerminalRefusal,
28999
29477
  groupAddress: () => groupAddress,
29000
29478
  findExternalEntry: () => findExternalEntry,
29001
29479
  factorMenu: () => factorMenu,
@@ -29034,27 +29512,28 @@ async function askForOwnPassphrase(ctx, promptText) {
29034
29512
  function requireTty(ctx, what) {
29035
29513
  if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stdout)
29036
29514
  return true;
29037
- writeVaultFailure(ctx, new VaultError("VAULT_UNLOCK_FAILED", `${what} needs a terminal: this CLI reads a vault passphrase or a security key PIN from a hidden prompt and from nowhere else.`, {
29515
+ writeVaultFailure(ctx, new VaultError("VAULT_TERMINAL_REQUIRED", `${what} needs a terminal: this CLI reads a vault passphrase or a security key PIN from a hidden prompt and from nowhere else.`, {
29038
29516
  suggestion: "There is no environment variable and no flag that supplies one."
29039
29517
  }));
29040
29518
  return false;
29041
29519
  }
29042
- function requirePromptStreams(ctx, what) {
29043
- if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stderr)
29044
- return true;
29045
- writeVaultFailure(ctx, new VaultError("VAULT_UNLOCK_FAILED", `${what} needs a terminal for the passphrase prompt: standard input and standard error must both be a terminal. Standard output may be redirected; that is where the listing goes.`));
29046
- return false;
29047
- }
29048
29520
  function refusePromptsWithoutTerminal(ctx, what) {
29049
29521
  if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stderr)
29050
29522
  return ctx;
29051
29523
  const refuse2 = async () => {
29052
- throw new VaultError("VAULT_UNLOCK_FAILED", `${what} needs a terminal here: this unlock reads a vault passphrase, a security key PIN or a choice from a prompt, and standard input and standard error must both be a terminal for that. Standard output may be redirected.`, {
29524
+ throw tagNoTerminal(new VaultError("VAULT_TERMINAL_REQUIRED", `${what} needs a terminal here: this unlock reads a vault passphrase, a security key PIN or a choice from a prompt, and standard input and standard error must both be a terminal for that. Standard output may be redirected.`, {
29053
29525
  suggestion: "Run it from a terminal, or pass --factor touch-id or --factor passkey to unlock with a factor that reads no prompt. No environment variable or flag supplies a passphrase or PIN."
29054
- });
29526
+ }));
29055
29527
  };
29056
29528
  return { ...ctx, deps: { ...ctx.deps, promptSecret: refuse2, promptLine: refuse2 } };
29057
29529
  }
29530
+ function tagNoTerminal(error) {
29531
+ noTerminalRefusals.add(error);
29532
+ return error;
29533
+ }
29534
+ function isNoTerminalRefusal(error) {
29535
+ return error instanceof VaultError && noTerminalRefusals.has(error);
29536
+ }
29058
29537
  function vaultPathFor(ctx, parsed) {
29059
29538
  const flag = parsed.values["--keystore"];
29060
29539
  if (flag !== undefined)
@@ -29674,7 +30153,7 @@ function takeRepeatedFlag(args, flag) {
29674
30153
  }
29675
30154
  return { values, rest };
29676
30155
  }
29677
- var PASSPHRASE_ONLY_PREFIX = "Passphrase only: ";
30156
+ var noTerminalRefusals, PASSPHRASE_ONLY_PREFIX = "Passphrase only: ";
29678
30157
  var init_vault_support = __esm(() => {
29679
30158
  init_args();
29680
30159
  init_presentation();
@@ -29690,6 +30169,7 @@ var init_vault_support = __esm(() => {
29690
30169
  init_platform();
29691
30170
  init_sidecar();
29692
30171
  init_store();
30172
+ noTerminalRefusals = new WeakSet;
29693
30173
  });
29694
30174
 
29695
30175
  // src/vault/ed25519.ts
@@ -29825,9 +30305,9 @@ async function reconcileGrant(ctx, entry, opts = {}) {
29825
30305
  if (!stranded.complete) {
29826
30306
  return { kind: "unreadable", reason: stranded.incompleteReason ?? "the stranded-import read was incomplete" };
29827
30307
  }
29828
- const failure2 = stranded.failures.find((candidate) => sameWalletAddress(candidate.address, entry.address));
29829
- if (failure2?.stage === "convex_link") {
29830
- return { kind: "strand-final", account: wallets.account, failure: failure2 };
30308
+ const failure3 = stranded.failures.find((candidate) => sameWalletAddress(candidate.address, entry.address));
30309
+ if (failure3?.stage === "convex_link") {
30310
+ return { kind: "strand-final", account: wallets.account, failure: failure3 };
29831
30311
  }
29832
30312
  return { kind: "unresolved", account: wallets.account };
29833
30313
  }
@@ -33959,23 +34439,23 @@ async function walletsImport(args, ctx) {
33959
34439
  deps
33960
34440
  });
33961
34441
  if (!flow.ok) {
33962
- const failure2 = flow.failure;
33963
- if (failure2.kind === "api") {
33964
- writeScreenFailure(deps, failure2.response, { apiUrl, authType: "key" }, json);
34442
+ const failure3 = flow.failure;
34443
+ if (failure3.kind === "api") {
34444
+ writeScreenFailure(deps, failure3.response, { apiUrl, authType: "key" }, json);
33965
34445
  return 1;
33966
34446
  }
33967
- if (failure2.kind === "signer-store") {
34447
+ if (failure3.kind === "signer-store") {
33968
34448
  writeScreenLocalFailure(deps, {
33969
34449
  code: "SIGNER_STORE_FAILED",
33970
- message: `Could not store the new signer in the ${deps.backend} store: ${failure2.error instanceof Error ? failure2.error.message : failure2.error}`,
34450
+ message: `Could not store the new signer in the ${deps.backend} store: ${failure3.error instanceof Error ? failure3.error.message : failure3.error}`,
33971
34451
  suggestion: "Nothing was imported. Unlock the store (or fix the error above) and run the command again."
33972
34452
  }, json);
33973
34453
  return 1;
33974
34454
  }
33975
34455
  writeScreenLocalFailure(deps, {
33976
34456
  code: "SIGNER_COMMIT_FAILED",
33977
- message: `Wallet ${failure2.walletId} was imported, but its signer could not be stored under the wallet's own ` + `ref: ${failure2.error instanceof Error ? failure2.error.message : failure2.error}`,
33978
- suggestion: `The signer is not lost: it is in the ${deps.backend} store under "${failure2.pendingRef}". Copy it to ` + `"${walletSignerRef(failure2.walletId)}", or revoke the wallet with: candle wallets revoke ${failure2.walletId}`
34457
+ message: `Wallet ${failure3.walletId} was imported, but its signer could not be stored under the wallet's own ` + `ref: ${failure3.error instanceof Error ? failure3.error.message : failure3.error}`,
34458
+ suggestion: `The signer is not lost: it is in the ${deps.backend} store under "${failure3.pendingRef}". Copy it to ` + `"${walletSignerRef(failure3.walletId)}", or revoke the wallet with: candle wallets revoke ${failure3.walletId}`
33979
34459
  }, json);
33980
34460
  return 1;
33981
34461
  }
@@ -35451,8 +35931,8 @@ function releaseActiveTee(active) {
35451
35931
  if (active.source === "vault")
35452
35932
  releaseResolvedTee(active.resolved);
35453
35933
  }
35454
- function writeCommitFailure(ctx, failure2, consequence) {
35455
- writeLocalFailure(ctx.deps, { code: failure2.code, message: failure2.message, suggestion: consequence }, ctx.json);
35934
+ function writeCommitFailure(ctx, failure3, consequence) {
35935
+ writeLocalFailure(ctx.deps, { code: failure3.code, message: failure3.message, suggestion: consequence }, ctx.json);
35456
35936
  }
35457
35937
  async function openExistingTeeStore(ctx, parsed) {
35458
35938
  const { deps, json } = ctx;
@@ -35753,13 +36233,13 @@ async function teeEnable(args, ctx) {
35753
36233
  vaultDestination: vault
35754
36234
  });
35755
36235
  if (!flow.ok) {
35756
- const failure2 = flow.failure;
35757
- if (failure2.kind === "api")
35758
- writeFailure(deps, failure2.response, { apiUrl, authType: "key" }, json);
36236
+ const failure3 = flow.failure;
36237
+ if (failure3.kind === "api")
36238
+ writeFailure(deps, failure3.response, { apiUrl, authType: "key" }, json);
35759
36239
  else {
35760
36240
  writeLocalFailure(deps, {
35761
- code: failure2.kind === "signer-store" ? "SIGNER_STORE_FAILED" : "SIGNER_COMMIT_FAILED",
35762
- message: `${address}: ${failure2.error instanceof Error ? failure2.error.message : failure2.error}`,
36241
+ code: failure3.kind === "signer-store" ? "SIGNER_STORE_FAILED" : "SIGNER_COMMIT_FAILED",
36242
+ message: `${address}: ${failure3.error instanceof Error ? failure3.error.message : failure3.error}`,
35763
36243
  suggestion: "The key is still sealed locally. Fix the error above and run enable again."
35764
36244
  }, json);
35765
36245
  }
@@ -38566,7 +39046,7 @@ var init_token_clusters = __esm(() => {
38566
39046
  });
38567
39047
 
38568
39048
  // ../mcp/src/version.ts
38569
- var SERVER_VERSION = "0.10.29";
39049
+ var SERVER_VERSION = "0.10.30";
38570
39050
 
38571
39051
  // ../mcp/src/update-notice.ts
38572
39052
  function newer(a, b) {
@@ -40298,7 +40778,7 @@ var init_market_disclosure = __esm(() => {
40298
40778
  });
40299
40779
 
40300
40780
  // ../mcp/src/orchestrate.ts
40301
- import { randomUUID as randomUUID3 } from "node:crypto";
40781
+ import { randomUUID as randomUUID4 } from "node:crypto";
40302
40782
  function requireApiKey(cfg) {
40303
40783
  if (!cfg.apiKey) {
40304
40784
  throw new Error("CANDLE_AGENT_API_KEY is required for this tool. Set it in the environment or MCP client config.");
@@ -40411,7 +40891,7 @@ async function readPaperInventory(cfg, doFetch, extra) {
40411
40891
  }
40412
40892
  async function executeTrade(args, cfg, doFetch) {
40413
40893
  const apiKey = requireApiKey(cfg);
40414
- const clientTradeId = args.clientTradeId ?? randomUUID3();
40894
+ const clientTradeId = args.clientTradeId ?? randomUUID4();
40415
40895
  if (args.amount !== undefined && args.percent !== undefined) {
40416
40896
  return errText("pass exactly one of amount or percent, not both", { clientTradeId });
40417
40897
  }
@@ -40424,6 +40904,12 @@ async function executeTrade(args, cfg, doFetch) {
40424
40904
  let amountRaw;
40425
40905
  let resolved = {};
40426
40906
  let paper = isPaperFlag(args.paper);
40907
+ const deferExecution = isPaperFlag(args.deferExecution);
40908
+ if (deferExecution && paper) {
40909
+ return errText("deferExecution cannot be combined with paper; a paper trade records a quote and never executes", {
40910
+ clientTradeId
40911
+ });
40912
+ }
40427
40913
  try {
40428
40914
  if (args.side === "buy") {
40429
40915
  const amount = args.amount;
@@ -40532,6 +41018,9 @@ async function executeTrade(args, cfg, doFetch) {
40532
41018
  } catch (err) {
40533
41019
  return errText(err instanceof Error ? err.message : String(err), { clientTradeId });
40534
41020
  }
41021
+ if (deferExecution && paper) {
41022
+ return errText("deferExecution needs a live holding, and this sell resolved to the paper book (the wallet holds none of the mint); a paper trade never executes", { clientTradeId });
41023
+ }
40535
41024
  const posted = await postJson(`${base(cfg)}/api/v1/trade/agent/build`, apiKey, {
40536
41025
  clientTradeId,
40537
41026
  mint: args.mint,
@@ -40541,7 +41030,8 @@ async function executeTrade(args, cfg, doFetch) {
40541
41030
  payer: { type: "main" },
40542
41031
  ...args.quoteAsset !== undefined ? { quoteAsset: args.quoteAsset } : {},
40543
41032
  ...args.maxSlippageBps !== undefined ? { maxSlippageBps: args.maxSlippageBps } : {},
40544
- ...paper ? { paper: true } : {}
41033
+ ...paper ? { paper: true } : {},
41034
+ ...deferExecution ? { deferExecution: true } : {}
40545
41035
  }, doFetch);
40546
41036
  if ("thrown" in posted)
40547
41037
  return transportError("clientTradeId", clientTradeId, posted.thrown);
@@ -40550,7 +41040,7 @@ async function executeTrade(args, cfg, doFetch) {
40550
41040
  }
40551
41041
  async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs, opts = {}) {
40552
41042
  const apiKey = requireApiKey(cfg);
40553
- const clientLaunchId = args.clientLaunchId ?? randomUUID3();
41043
+ const clientLaunchId = args.clientLaunchId ?? randomUUID4();
40554
41044
  const socialsProblem = socialsRefusal(args.socials);
40555
41045
  if (socialsProblem)
40556
41046
  return { text: imageRefusalBody(socialsProblem, { clientLaunchId }), isError: true };
@@ -40779,7 +41269,26 @@ async function resolveToken(args, cfg, doFetch) {
40779
41269
  return read.err;
40780
41270
  return { text: JSON.stringify({ success: true, chain: chain2, mint: args.mint, market: read.market }, null, 2) };
40781
41271
  }
40782
- async function executionStatus(cfg, doFetch) {
41272
+ function teePayers(body) {
41273
+ const page = body;
41274
+ if (!Array.isArray(page?.page))
41275
+ return;
41276
+ const paused = page.paused === true;
41277
+ const scopes = Array.isArray(page.scopes) ? page.scopes.filter((s) => typeof s === "string") : [];
41278
+ const spendAuthorized = TEE_SPEND_SCOPES.some((scope) => scopes.includes(scope));
41279
+ const wallets2 = page.page.map((row2) => ({
41280
+ id: String(row2.id),
41281
+ address: String(row2.address),
41282
+ chain: String(row2.chain),
41283
+ ...typeof row2.label === "string" ? { label: row2.label } : {},
41284
+ active: row2.active === true,
41285
+ allowLaunch: row2.allowLaunch === true,
41286
+ spendable: row2.active === true && !paused && spendAuthorized
41287
+ }));
41288
+ return { paused, scopes, wallets: wallets2, complete: page.isDone === true };
41289
+ }
41290
+ async function executionStatus(cfg, doFetch, options = {}) {
41291
+ const readTee = options.teeWallets !== false;
40783
41292
  const apiKey = requireApiKey(cfg);
40784
41293
  const get = async (path) => {
40785
41294
  const res = await doFetch(`${base(cfg)}${path}`, { method: "GET", headers: headers(apiKey) });
@@ -40793,14 +41302,17 @@ async function executionStatus(cfg, doFetch) {
40793
41302
  }
40794
41303
  return { ok: res.ok, status: res.status, body };
40795
41304
  };
40796
- const [wallets2, tier, limits, risk] = await Promise.all([
41305
+ const [wallets2, trading, tier, limits, risk] = await Promise.all([
40797
41306
  get("/api/v1/agent/wallets/embedded"),
41307
+ readTee ? get("/api/v1/agent/wallets/trading") : Promise.resolve(undefined),
40798
41308
  get("/api/v1/agent/tier"),
40799
41309
  get("/api/v1/agent/keys/self/limits"),
40800
41310
  get("/api/v1/agent/keys/self/risk")
40801
41311
  ]);
41312
+ const tee = trading?.ok ? teePayers(trading.body) : undefined;
40802
41313
  const unreadable2 = [
40803
41314
  ["wallets", wallets2],
41315
+ ...trading ? [["tee", tee ? trading : { ok: false }]] : [],
40804
41316
  ["tier", tier],
40805
41317
  ["limits", limits],
40806
41318
  ["risk", risk]
@@ -40817,6 +41329,11 @@ async function executionStatus(cfg, doFetch) {
40817
41329
  updateAvailable: updateAvailable() ?? undefined,
40818
41330
  unreadable: unreadable2.length > 0 ? unreadable2.map(([name]) => name) : undefined,
40819
41331
  wallets: wallets2.body,
41332
+ tee: tee?.wallets,
41333
+ teeScopes: tee ? tee.scopes : undefined,
41334
+ teePaused: tee ? tee.paused : undefined,
41335
+ teeComplete: tee ? tee.complete : undefined,
41336
+ teeError: trading && !tee ? trading.body : undefined,
40820
41337
  tier: tier.body,
40821
41338
  limits: limits.body,
40822
41339
  risk: risk.body
@@ -40824,7 +41341,7 @@ async function executionStatus(cfg, doFetch) {
40824
41341
  ...unreadable2.length > 0 ? { isError: true } : {}
40825
41342
  };
40826
41343
  }
40827
- var SOLANA_SEED_CEILINGS, MAIN_PAYER, SWEEP_BASE_ASSETS, STOP_CLEARED_BY = "An account owner reviews the profile and resumes it from the agent console; an API key cannot resume it.";
41344
+ var SOLANA_SEED_CEILINGS, MAIN_PAYER, SWEEP_BASE_ASSETS, TEE_SPEND_SCOPES, STOP_CLEARED_BY = "An account owner reviews the profile and resumes it from the agent console; an API key cannot resume it.";
40828
41345
  var init_orchestrate = __esm(() => {
40829
41346
  init_convert();
40830
41347
  init_image();
@@ -40840,6 +41357,7 @@ var init_orchestrate = __esm(() => {
40840
41357
  solana: ["USDC", "CNDL", "SOL"],
40841
41358
  hood: ["USDG", "ETH"]
40842
41359
  };
41360
+ TEE_SPEND_SCOPES = ["swap:write", "transfer:write", "launch:write"];
40843
41361
  });
40844
41362
 
40845
41363
  // ../mcp/src/hyperliquid.ts
@@ -44560,6 +45078,9 @@ function planTableRows(table) {
44560
45078
  row2("Uploads per minute", (e) => count(e.limits.uploadsPerMin)),
44561
45079
  row2("Linked wallets", (e) => count(e.limits.linkedWallets))
44562
45080
  ];
45081
+ if (plans.some((e) => typeof e.limits.labReplaysPerDay === "number")) {
45082
+ rows.push(row2("Strategy lab replays", (e) => typeof e.limits.labReplaysPerDay === "number" ? `${count(e.limits.labReplaysPerDay)} a day` : "-"));
45083
+ }
44563
45084
  const known = Object.keys(PLAN_CAPABILITY_LABELS);
44564
45085
  const served = new Set;
44565
45086
  for (const entry of plans)
@@ -44604,13 +45125,14 @@ var init_plans = __esm(() => {
44604
45125
  createLinkedWallets: "Create linked wallets",
44605
45126
  importLinkedWallets: "Import linked wallets",
44606
45127
  limitOrders: "Limit orders",
44607
- quant: "Quant deep analyses (Max)"
45128
+ quant: "Quant deep analyses (Max)",
45129
+ strategyLab: "Strategy lab (saved strategies stay Max)"
44608
45130
  };
44609
45131
  PLAN_LABELS = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
44610
45132
  });
44611
45133
 
44612
45134
  // ../mcp/src/token-metadata.ts
44613
- import { randomUUID as randomUUID4 } from "node:crypto";
45135
+ import { randomUUID as randomUUID5 } from "node:crypto";
44614
45136
  function editSocialsSchema() {
44615
45137
  const shape = {};
44616
45138
  for (const key of SOCIAL_KEYS) {
@@ -44661,7 +45183,7 @@ function tokenEditBody(args) {
44661
45183
  return { body };
44662
45184
  }
44663
45185
  function tokenEditRequest(base2, apiKey, args, body) {
44664
- const key = typeof args.idempotencyKey === "string" && args.idempotencyKey ? args.idempotencyKey : randomUUID4();
45186
+ const key = typeof args.idempotencyKey === "string" && args.idempotencyKey ? args.idempotencyKey : randomUUID5();
44665
45187
  return {
44666
45188
  url: `${base2}/api/v1/tokens/${encodeURIComponent(String(args.chain))}/${encodeURIComponent(String(args.mint))}/metadata`,
44667
45189
  init: {
@@ -44889,7 +45411,7 @@ __export(exports_tools, {
44889
45411
  OPERATION_NOT_FOUND_RULE: () => OPERATION_NOT_FOUND_RULE,
44890
45412
  LAB_TOOL_NAMES: () => LAB_TOOL_NAMES
44891
45413
  });
44892
- import { randomUUID as randomUUID5 } from "node:crypto";
45414
+ import { randomUUID as randomUUID6 } from "node:crypto";
44893
45415
  function resolveToolAllowlist(env) {
44894
45416
  const raw = env.CANDLE_MCP_TOOLS?.trim();
44895
45417
  if (!raw)
@@ -44990,6 +45512,26 @@ function buildRequest(name, args, cfg) {
44990
45512
  init: { method: "GET", headers: jsonHeaders() }
44991
45513
  };
44992
45514
  }
45515
+ case "candle_quote": {
45516
+ const apiKey = requireApiKey2(cfg);
45517
+ const { chain: chain2, mint, side, amountRaw, quoteAsset, poolKey, includeRoundTrip } = args;
45518
+ return {
45519
+ url: `${base2}/api/v1/trade/agent/quote`,
45520
+ init: {
45521
+ method: "POST",
45522
+ headers: jsonHeaders(apiKey),
45523
+ body: JSON.stringify({
45524
+ chain: chain2,
45525
+ mint,
45526
+ side,
45527
+ amountRaw,
45528
+ ...quoteAsset !== undefined && quoteAsset !== "" ? { quoteAsset } : {},
45529
+ ...poolKey !== undefined ? { poolKey } : {},
45530
+ ...includeRoundTrip !== undefined ? { includeRoundTrip } : {}
45531
+ })
45532
+ }
45533
+ };
45534
+ }
44993
45535
  case "candle_get_price_history": {
44994
45536
  const { chain: chain2, mint, interval, limit, to } = args;
44995
45537
  const query2 = new URLSearchParams;
@@ -45166,6 +45708,13 @@ function buildRequest(name, args, cfg) {
45166
45708
  const path = kind === "launch" ? `/api/v1/launch/headless/jobs/${id}` : kind === "swap" ? `/api/v1/agent/swap/by-client-id/${id}` : `/api/v1/trade/agent/jobs/${id}`;
45167
45709
  return { url: `${base2}${path}`, init: { method: "GET", headers: jsonHeaders(apiKey) } };
45168
45710
  }
45711
+ case "candle_trade_execute": {
45712
+ const { clientTradeId } = args;
45713
+ return {
45714
+ url: `${base2}/api/v1/trade/agent/execute`,
45715
+ init: { method: "POST", headers: jsonHeaders(requireApiKey2(cfg)), body: JSON.stringify({ clientTradeId }) }
45716
+ };
45717
+ }
45169
45718
  case "candle_get_staking": {
45170
45719
  return { url: `${base2}/api/v1/agent/staking`, init: { method: "GET", headers: jsonHeaders(requireApiKey2(cfg)) } };
45171
45720
  }
@@ -45220,6 +45769,28 @@ function buildRequest(name, args, cfg) {
45220
45769
  init: { method: "GET", headers: jsonHeaders(apiKey) }
45221
45770
  };
45222
45771
  }
45772
+ case "candle_get_trading_wallets": {
45773
+ const apiKey = requireApiKey2(cfg);
45774
+ const { cursor } = args;
45775
+ const query2 = cursor === undefined ? "" : `?cursor=${encodeURIComponent(cursor)}`;
45776
+ return {
45777
+ url: `${base2}/api/v1/agent/wallets/trading${query2}`,
45778
+ init: { method: "GET", headers: jsonHeaders(apiKey) }
45779
+ };
45780
+ }
45781
+ case "candle_get_spendable_balances": {
45782
+ const apiKey = requireApiKey2(cfg);
45783
+ const { cursor, mints } = args;
45784
+ const params = new URLSearchParams;
45785
+ if (cursor !== undefined)
45786
+ params.set("cursor", cursor);
45787
+ if (mints !== undefined && mints.length > 0)
45788
+ params.set("mints", mints.join(","));
45789
+ return {
45790
+ url: `${base2}/api/v1/agent/wallets/self/balances${params.size ? `?${params}` : ""}`,
45791
+ init: { method: "GET", headers: jsonHeaders(apiKey) }
45792
+ };
45793
+ }
45223
45794
  case "candle_get_profile_wallets": {
45224
45795
  const apiKey = requireApiKey2(cfg);
45225
45796
  const { keyPrefix } = args;
@@ -45350,7 +45921,7 @@ async function getOperation(args, cfg, doFetch) {
45350
45921
  };
45351
45922
  }
45352
45923
  async function swap2(args, cfg, doFetch) {
45353
- const clientSwapId = typeof args.clientSwapId === "string" && args.clientSwapId.length > 0 ? args.clientSwapId : randomUUID5();
45924
+ const clientSwapId = typeof args.clientSwapId === "string" && args.clientSwapId.length > 0 ? args.clientSwapId : randomUUID6();
45354
45925
  const { url, init } = buildRequest("candle_swap", { ...args, clientSwapId }, cfg);
45355
45926
  let ok;
45356
45927
  let body;
@@ -45525,6 +46096,12 @@ function registerToolSubset(server, options) {
45525
46096
  description: "Read Candle and indexed external markets, including indexed-but-not-routable tokens. No key needed. " + EXTERNAL_MARKET_DISCLOSURE + " " + "Read candleLaunched, launchpad, venue and trade.routable; jupiterOk and discovery flags are distinct. " + "Routability is stored eligibility, not a quote or permission. General quotes use POST /api/v1/trade/agent/quote. " + "Curve quotes and lifecycle describe Candle launches. MARKET_NOT_FOUND is a legacy code: read " + "error.routing.reason, error.discovery and sibling error.retryable. A curve-only 404 does not mean untradeable.",
45526
46097
  inputSchema: getMarketShape
45527
46098
  }, async (args) => callAndRelay("candle_get_market", args, getConfig(), doFetch));
46099
+ register("candle_quote", {
46100
+ title: "Quote a trade",
46101
+ description: "Price a buy or sell before making it. Moves nothing. Needs an API key with the swap:write scope: a read-only key (account:read) is refused, so use candle_get_market for state instead. " + "Returns amountOut (base units), venue, quoteAsset, quoteDecimals and quotedAt; Hood answers also carry fee and price impact. " + "route is present only on some answers, so do not assume it. A quote is a snapshot (Hood quotes are cached for 10 seconds), not a promise of the fill. " + "Limited to 600 quotes a minute per key, and counted against the key's own limit. Pass amountRaw in base units; candle_trade and candle_swap take decimals.",
46102
+ inputSchema: quoteShape,
46103
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
46104
+ }, async (args) => callAndRelay("candle_quote", args, getConfig(), doFetch));
45528
46105
  register("candle_token_forensics", {
45529
46106
  title: "Token forensics",
45530
46107
  description: "Gate a buy before making it: who launched it (resolved on-chain; pump.fun's shared updateAuthority is never the developer; when no developer is on chain, deployer.attribution names the launchpad or issuer instead, e.g. launched via stonk.fun or issued by xStocks), their went-to-zero rate and last coins, who bought in the deploy window (the creator's own wallets are marked disclosed; strangers in the same slot are the bundle signal), same-funder insider share, same-funder deployer cluster, and safety.summary with six sourced flags (mintAuthority, freezeAuthority, tokenExtensions, lpLock, sellability, liquidityDrain). Refuse an unprompted buy when flagged; incomplete or unknown is not clearance. deployer.priorLaunches is counted (pump.fun curves for a curve creator; record.count says the source); deployer.launcher is the wallet that sent the launch, often a launch tool's signer, never the developer; launch.deployerLaunches is that wallet's inclusive informational count, never a warning. Every measurement carries a coverage note -- 'unavailable' is not 'clean'. No key needed.\n\nMARKET_NOT_FOUND means Candle has no market for that token and this could not run. That is also not 'clean': report that you could not check it, rather than reporting the token as safe. That refusal now carries error.coverage -- covered:false, a reason ('external_launchpad' when the token launched somewhere else, 'unknown_mint' when nobody has indexed it), the launchpad when known, and every check that consequently did not run. Read it instead of guessing. Most of the feed, and any Solana mint Jupiter's index knows, now answers with a partial report instead. On Hood, a token Candle did not launch names its launch account (deployer.method cvc_launch_account) for pons.family and pools.trade launches, with no record of earlier coins; other Hood launchpads return no developer.",
@@ -45715,6 +46292,16 @@ function registerToolSubset(server, options) {
45715
46292
  description: "The account's EMBEDDED wallets, one per chain, with their delegation state. These are " + "the wallets candle_trade, candle_swap and candle_transfer spend from, so this is how an " + "agent finds its own funding addresses. Reads only; moves nothing. Not the same as the " + "account's LINKED wallets, which are the owner's own wallets and are not spent from here. " + "Balances are not included: read a specific one with the market and balance endpoints.",
45716
46293
  inputSchema: {}
45717
46294
  }, async () => callAndRelay("candle_get_wallets", {}, getConfig(), doFetch));
46295
+ register("candle_get_trading_wallets", {
46296
+ title: "List the TEE wallets bound to this key",
46297
+ description: "The TEE wallets bound to THIS key, on Solana (`chain: solana`) and Hood (`chain: evm`): the wallets " + "it may pay from beside the embedded ones in candle_get_wallets. Each row has its id, address, label, " + "`active` (true when the wallet's signing authority is verified active; a false row cannot pay) and " + "`allowLaunch`. The body also carries this key's `scopes` and `paused` (true while the owner has paused " + "the key, which then spends from nothing; spending also needs one of the spend scopes — " + "swap:write, transfer:write or launch:write — and `scopes` says which this key holds). A TEE wallet is signed on your own machine with the key " + "signer: the perps tools here and the candle CLI (`candle swap --wallet <id|address|label>`) pay from " + "it, while candle_trade and candle_swap spend the embedded wallet. Answers for any scope and on a " + "paused key. Paged: pass `continueCursor` back as `cursor` until `isDone`. Reads only; moves nothing.",
46298
+ inputSchema: tradingWalletsShape
46299
+ }, async (args) => callAndRelay("candle_get_trading_wallets", args, getConfig(), doFetch));
46300
+ register("candle_get_spendable_balances", {
46301
+ title: "Read the balances of every wallet this key may spend from",
46302
+ description: "Every wallet THIS key may spend from, and no other, with its balances: the embedded wallets when " + "the owner allows this key to spend them, the TEE wallets bound to it with an active signing " + "authority, and the linked wallets its wallet scope admits. Each row has `kind` (embedded, tee or " + "linked), `chain` (solana or evm), `address`, and `id` and `label` for a linked or TEE wallet. " + "`balances` always holds the base assets (SOL, USDC and CNDL on Solana; ETH and USDG on Hood) plus " + "the `mints` you name (at most 10), each in raw units with the slot or block it was read at. A read " + "that failed is in `unavailable`, never reported as zero. A key with no spend scope (swap:write, " + "transfer:write or launch:write) can spend from nothing and gets an empty page, not an error. " + "`complete: false` means the account's wallet list was cut off. 50 wallets a page: pass " + "`continueCursor` back as `cursor` until `isDone`. Needs no account:read; candle_get_portfolio is " + "the whole-account read. Reads only; moves nothing.",
46303
+ inputSchema: spendableBalancesShape
46304
+ }, async (args) => callAndRelay("candle_get_spendable_balances", args, getConfig(), doFetch));
45718
46305
  register("candle_get_profile_wallets", {
45719
46306
  title: "Read which wallets an agent profile can spend from",
45720
46307
  description: "An agent profile (API key) either spends from EVERY wallet on its account or only from " + "the ones assigned to it. Read walletScope before drawing any conclusion from the list: " + "an empty list means 'every wallet' under scope 'all' and 'none at all' under 'selected'. " + "Reads only; moves nothing.",
@@ -45732,7 +46319,7 @@ function registerToolSubset(server, options) {
45732
46319
  }, async (args) => callAndRelay("candle_get_profile_pnl", args, getConfig(), doFetch));
45733
46320
  register("candle_get_profile_trades", {
45734
46321
  title: "Read an agent profile's trade history",
45735
- description: "Orders, actual fills, fees, timestamps and transaction hashes for this profile. Page by time with " + "`since`, `until` and `cursor` (continue with the response's `nextCursor`). Includes FAILED " + "trades, with an errorCode saying why each did not go through, so this answers 'what happened to " + "my order' as well as 'what did I trade'. Reads only; moves nothing.",
46322
+ description: "Orders, actual fills, fees, timestamps and transaction hashes for this profile. Page by time with " + "`since`, `until` and `cursor` (continue with the response's `nextCursor`). Includes FAILED " + "trades, with an errorCode saying why each did not go through, so this answers 'what happened to " + "my order' as well as 'what did I trade'. The profile's swaps are listed among the trades by " + "time, each with kind 'swap', rail ('swap' for candle_swap, 'build' for the CLI's build and " + "submit), fromAsset, toAsset and amountRaw; a candle_swap made without a clientSwapId is not " + "recorded, so it is not listed. Reads only; moves nothing.",
45736
46323
  inputSchema: profileTradesShape
45737
46324
  }, async (args) => callAndRelay("candle_get_profile_trades", args, getConfig(), doFetch));
45738
46325
  register("candle_get_paper_inventory", {
@@ -45771,10 +46358,12 @@ function registerToolSubset(server, options) {
45771
46358
  });
45772
46359
  register("candle_execution_status", {
45773
46360
  title: "Can this key execute right now",
45774
- description: "One call before trading: the embedded wallets to spend from, the tier that decides what " + "may be traded, this key's own spend limits, and its loss limits and stop state (risk: " + "lossLimits, state, stopped, tripped). A stopped key reports ready: false and a top-level " + "stopped naming the bound that broke; every trade answers RISK_LIMIT_REACHED until an " + "account owner resumes it, so stop trading and say why rather than retrying. Any scope " + "can read its own. Reads only; moves nothing. Call it when " + "a run starts, or after an authorization error, rather than inferring readiness from a " + "failed trade. If a read could not be completed the tool says which one and does NOT " + "claim the account is unready: an unreachable endpoint and a missing tier are different " + "problems with different fixes.",
46361
+ description: "One call before trading: the embedded wallets to spend from, the TEE wallets bound to this " + "key, the tier that decides what may be traded, this key's own spend limits, and its loss " + "limits and stop state (risk: lossLimits, state, stopped, tripped). `wallets` is the embedded " + "read. `tee` lists the first page of TEE wallets with `chain` (solana or evm), `active` and " + "`spendable` (active, the key not paused — `teePaused` says whether it is — and the key " + "holding a spend scope: swap:write, transfer:write or launch:write; `teeScopes` lists the " + "key's scopes, so a read-only key shows its wallets with `spendable: false`); " + "`teeComplete: false` means more, which candle_get_trading_wallets lists. candle_trade and " + "candle_swap spend the embedded wallet; a TEE wallet is signed locally (the perps tools, the " + "candle CLI's --wallet). A stopped key reports ready: false and a top-level stopped naming " + "the bound that broke; every trade answers RISK_LIMIT_REACHED until an account owner resumes " + "it, so stop trading and say why rather than retrying. Any scope can read its own. Reads " + "only; moves nothing. Call it when " + "a run starts, or after an authorization error, rather than inferring readiness from a " + "failed trade. If a read could not be completed the tool says which one and does NOT " + "claim the account is unready: an unreachable endpoint and a missing tier are different " + "problems with different fixes.",
45775
46362
  inputSchema: {}
45776
46363
  }, async () => {
45777
- const result = await executionStatus(getConfig(), doFetch);
46364
+ const result = await executionStatus(getConfig(), doFetch, {
46365
+ teeWallets: options.embeddedExecutionStatus !== true
46366
+ });
45778
46367
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
45779
46368
  });
45780
46369
  register("candle_swap", {
@@ -45830,6 +46419,13 @@ function registerToolSubset(server, options) {
45830
46419
  const result = await executeTrade(args, getConfig(), doFetch);
45831
46420
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
45832
46421
  });
46422
+ register("candle_trade_execute", {
46423
+ title: "Execute a deferred trade",
46424
+ description: "Execute a trade you built with candle_trade and deferExecution: true. MOVES REAL FUNDS: Candle signs " + "the plan it kept back from the build with the account's own delegation and broadcasts it. Takes the " + "clientTradeId and nothing else, so nothing you send can change what gets signed; to change the trade, " + `build a new one.
46425
+
46426
+ ` + "Exactly one execution per build. Calling again never broadcasts a second time: it returns the stored " + "result once the first has confirmed, and a 409 IDEMPOTENCY_CONFLICT while the outcome is still " + "unknown (read candle_get_operation with the clientTradeId, kind trade, before doing anything else). " + "More than 30 minutes after the build it is 409 QUOTE_EXPIRED: build again for a fresh quote. A " + "trade built without deferExecution has already executed and is refused here.",
46427
+ inputSchema: tradeExecuteShape
46428
+ }, async (args) => callAndRelay("candle_trade_execute", args, getConfig(), doFetch));
45833
46429
  register("candle_atomic_launch_dry_run", {
45834
46430
  title: "Dry-run an atomic Solana launch",
45835
46431
  description: "Check a Solana launch bundled with first buys and price each buy, without building anything. Runs the " + "same checks as the atomic build, in the same order (payers, Believer check, fee and spend limits, the " + "key's transaction cap), then returns the launch preview (curveTerms) and one fill per first buy: the " + "tokens it would receive and the floor the build would sign it at. Reads only; moves nothing, reserves " + "nothing, and the clientLaunchId stays unused. It does not check balances, the daily cap, or the " + "transaction's size, so the real launch can still refuse. Needs launch:write, swap:write and Pro or Max.",
@@ -46038,7 +46634,7 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
46038
46634
  }
46039
46635
  return eligible;
46040
46636
  }
46041
- var VAMP_CANDIDATES_DESCRIPTION, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood staking reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. CNDL in a pending withdrawal counts toward nothing for Pro.", stakingWriteShape, TOOL_NAMES, LAB_TOOL_NAMES, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name) => PROFILE_EDIT_TOOL_NAMES.includes(name), OPERATION_NOT_FOUND_RULE, getMarketShape, priceHistoryShape, TOKEN_EVENTS_LIMIT_MESSAGE = "limit must be a whole number from 1 to 1000", tokenEventsLimit, TOKEN_EVENTS_KIND_NOTE = "kept back from the request and applied to the returned page by this server, so a filtered page can be shorter than limit while older events of other kinds remain: page with nextCursor as before until it is null", tokenCardShape, tokenEventsShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getAgentPositionsShape, getOperationShape, vampCandidatesShape, getFeesShape, atomicPayerShape, atomicLaunchDryRunShape, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, noArgsShape, setProfileWalletsShape, swapShape, tradeShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
46637
+ var VAMP_CANDIDATES_DESCRIPTION, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood staking reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. CNDL in a pending withdrawal counts toward nothing for Pro.", stakingWriteShape, TOOL_NAMES, LAB_TOOL_NAMES, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name) => PROFILE_EDIT_TOOL_NAMES.includes(name), OPERATION_NOT_FOUND_RULE, getMarketShape, quoteShape, priceHistoryShape, TOKEN_EVENTS_LIMIT_MESSAGE = "limit must be a whole number from 1 to 1000", tokenEventsLimit, TOKEN_EVENTS_KIND_NOTE = "kept back from the request and applied to the returned page by this server, so a filtered page can be shorter than limit while older events of other kinds remain: page with nextCursor as before until it is null", tokenCardShape, tokenEventsShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getAgentPositionsShape, getOperationShape, vampCandidatesShape, getFeesShape, atomicPayerShape, atomicLaunchDryRunShape, claimFeesShape, resolveTokenShape, tradingWalletsShape, spendableBalancesShape, profileWalletsShape, profilePnlShape, profileTradesShape, noArgsShape, setProfileWalletsShape, swapShape, tradeShape, tradeExecuteShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
46042
46638
  var init_tools = __esm(() => {
46043
46639
  init_zod();
46044
46640
  init_client2();
@@ -46067,6 +46663,7 @@ var init_tools = __esm(() => {
46067
46663
  TOOL_NAMES = [
46068
46664
  "candle_launch_token",
46069
46665
  "candle_get_market",
46666
+ "candle_quote",
46070
46667
  "candle_get_feed",
46071
46668
  "candle_vamp_candidates",
46072
46669
  "candle_token_events",
@@ -46103,12 +46700,15 @@ var init_tools = __esm(() => {
46103
46700
  "candle_get_agent_positions",
46104
46701
  "candle_get_plans",
46105
46702
  "candle_trade",
46703
+ "candle_trade_execute",
46106
46704
  "candle_launch_and_seed",
46107
46705
  "candle_atomic_launch_dry_run",
46108
46706
  "candle_swap",
46109
46707
  "candle_transfer",
46110
46708
  "candle_sweep",
46111
46709
  "candle_get_wallets",
46710
+ "candle_get_trading_wallets",
46711
+ "candle_get_spendable_balances",
46112
46712
  "candle_get_profile_wallets",
46113
46713
  "candle_set_profile_wallets",
46114
46714
  "candle_get_profile_pnl",
@@ -46172,6 +46772,15 @@ var init_tools = __esm(() => {
46172
46772
  chain: exports_external.string().describe('"solana" or "hood"'),
46173
46773
  mint: exports_external.string().describe("Token mint (solana) or contract address (hood)")
46174
46774
  };
46775
+ quoteShape = {
46776
+ chain: exports_external.enum(["solana", "hood"]),
46777
+ mint: exports_external.string().describe("Token mint (solana) or contract address (hood)"),
46778
+ side: exports_external.enum(["buy", "sell"]),
46779
+ amountRaw: exports_external.string().describe("Positive integer in base units: the quote asset's for a buy, the token's for a sell"),
46780
+ quoteAsset: exports_external.string().optional().describe('Asset to price in, e.g. "sol" or "usdc" on Solana, "eth" or "usdg" on Hood'),
46781
+ poolKey: exports_external.unknown().optional().describe("Hood only: a pool hint for a pool nobody has indexed yet"),
46782
+ includeRoundTrip: exports_external.boolean().optional().describe("Hood only: also return the buy-then-sell cost in basis points")
46783
+ };
46175
46784
  priceHistoryShape = {
46176
46785
  chain: exports_external.string().optional().describe('"solana" (the default). Hood is refused with the REST route that serves it'),
46177
46786
  mint: exports_external.string().describe("Token mint"),
@@ -46213,7 +46822,7 @@ var init_tools = __esm(() => {
46213
46822
  return /^[0-9]+$/.test(trimmed) ? Number(trimmed) : value;
46214
46823
  }, exports_external.number({ invalid_type_error: FEED_LIMIT_MESSAGE }).int(FEED_LIMIT_MESSAGE).min(1, FEED_LIMIT_MESSAGE).max(200, FEED_LIMIT_MESSAGE).optional().describe("Max rows to return, an integer from 1 to 200."));
46215
46824
  getFeedShape = {
46216
- bucket: exports_external.enum(["new", "graduated", "onfire", "bluechip", "finalstretch"]).describe("finalstretch: pump.fun tokens at 50% or more of their bonding curve, most complete first; no delta, read the whole list. Where it is not switched on it is refused as an unknown bucket: use another bucket. bluechip is deprecated and will be removed."),
46825
+ bucket: exports_external.enum(["new", "graduated", "onfire", "bluechip", "finalstretch"]).describe('finalstretch: pump.fun tokens at 50% or more of their bonding curve, most complete first, at most the 200 most complete (limit 200 reads all of them); no delta, read the whole list. Narrow it to tokens near graduation with where {"progress":{"gte":0.9}}. Where it is not switched on it is refused as an unknown bucket: use another bucket. bluechip is deprecated and will be removed.'),
46217
46826
  chain: exports_external.string().optional().describe("Optional chain filter"),
46218
46827
  where: exports_external.string().optional().describe('JSON filter, e.g. {"marketCap":{"lt":150000},"liquidityUsd":{"gte":25000},"mintAuthorityDisabled":{"eq":true}}. ' + "Comparators: eq, ne, lt, lte, gt, gte, present. An ABSENT field satisfies none of them except " + "present:false, so a filter for mintAuthorityDisabled eq true returns only tokens that actually say " + "true, never ones where the flag is simply missing. Use present:false to find the tokens with no data."),
46219
46828
  sort: exports_external.string().optional().describe('Sort as "field" or "field:asc" / "field:desc". A bare field means desc.'),
@@ -46289,6 +46898,13 @@ var init_tools = __esm(() => {
46289
46898
  resolveTokenShape = {
46290
46899
  mint: exports_external.string().describe("Token mint (Solana, base58) or contract address (Hood, 0x-prefixed)")
46291
46900
  };
46901
+ tradingWalletsShape = {
46902
+ cursor: exports_external.string().optional().describe("The continueCursor of the previous page. Omit for the first page")
46903
+ };
46904
+ spendableBalancesShape = {
46905
+ cursor: exports_external.string().optional().describe("The continueCursor of the previous page. Omit for the first page"),
46906
+ mints: exports_external.array(exports_external.string()).max(10).optional().describe("Up to 10 extra tokens to read beside the base assets: Solana mints or Hood (0x) token addresses. " + "Each is read on the wallets of its own chain")
46907
+ };
46292
46908
  profileWalletsShape = {
46293
46909
  keyPrefix: exports_external.string().describe("The profile's API key prefix, as listed by candle keys list or in the dashboard")
46294
46910
  };
@@ -46324,7 +46940,11 @@ var init_tools = __esm(() => {
46324
46940
  quoteAsset: exports_external.string().optional().describe('What the wallet spends on a buy or receives on a sell: "sol", "usdc" or "cndl" on Solana, ' + '"eth" or "usdg" on Hood. Echoing the `quoteAsset` a quote (POST /api/v1/trade/agent/quote) returned is safe. On Solana it applies only ' + "to an arbitrary mint Candle never launched (a buy or sell works on any plan) and is ignored for a Candle token, " + "whose quote comes from the token itself. On Hood it is the settlement asset of a DEX " + "trade; a USDG buy adds an approval transaction an ETH buy does not. It is not the route: " + "the cheapest path to the asset is chosen separately. Defaults to sol / ETH settlement."),
46325
46941
  maxSlippageBps: exports_external.number().int().min(0).max(5000).optional().describe("Slippage bound in bps, 0-5000 (50%). Server defaults to 100 (1%)"),
46326
46942
  clientTradeId: exports_external.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result. Retrying with the " + "SAME id is safe (idempotent replay); a new id is a SECOND trade."),
46327
- paper: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Rehearse instead of trading. The request passes every admission rule a live trade passes " + "-- the same planner, spend gate, key cap and loss limits -- and records the quote, but " + "nothing is ever broadcast and no funds move. Use it to check that a strategy is admitted " + "before risking anything on it. A paper fill is optimistic by construction: it books the " + "quoted price, so the gap between a paper arm and a live one IS the execution cost. " + "A sell of a mint this key already paper-bought also closes that paper book when the " + "live wallet is empty, even if this flag is omitted."))
46943
+ paper: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Rehearse instead of trading. The request passes every admission rule a live trade passes " + "-- the same planner, spend gate, key cap and loss limits -- and records the quote, but " + "nothing is ever broadcast and no funds move. Use it to check that a strategy is admitted " + "before risking anything on it. A paper fill is optimistic by construction: it books the " + "quoted price, so the gap between a paper arm and a live one IS the execution cost. " + "A sell of a mint this key already paper-bought also closes that paper book when the " + "live wallet is empty, even if this flag is omitted.")),
46944
+ deferExecution: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Quote first, spend later. The trade is built and the result carries the quote with " + 'status "built"; NOTHING is signed or spent until candle_trade_execute is called with the same ' + "clientTradeId. The build holds for 30 minutes; executing after that is QUOTE_EXPIRED and you " + "build again for a fresh quote. Rebuilding with the same clientTradeId re-quotes it, until it has " + "been sent to candle_trade_execute. There is no cancel: let an unwanted build expire. Cannot be " + "combined with `paper`."))
46945
+ };
46946
+ tradeExecuteShape = {
46947
+ clientTradeId: exports_external.string().min(1).describe("The clientTradeId of a candle_trade built with deferExecution: true. Nothing else is accepted.")
46328
46948
  };
46329
46949
  tokenImageUrl = launchSchemaField(launchTokenShape, "imageUrl");
46330
46950
  tokenBannerUrl = launchSchemaField(launchTokenShape, "bannerUrl");
@@ -74149,29 +74769,11 @@ ${USAGE3}` : USAGE3, ctx.json);
74149
74769
  }
74150
74770
 
74151
74771
  // src/commands/auth.ts
74152
- import { homedir as homedir3 } from "node:os";
74153
- import { join as join9 } from "node:path";
74154
-
74155
- // src/account.ts
74156
- init_client();
74157
- async function fetchAccount(deps, apiUrl, apiKey) {
74158
- const identity = await apiRequest("/api/v1/agent/wallets/embedded", {
74159
- auth: "key",
74160
- credentials: { apiKey },
74161
- apiUrl,
74162
- fetch: deps.fetch,
74163
- env: deps.env
74164
- });
74165
- const body = identity.ok ? identity.body : undefined;
74166
- const account = body?.account;
74167
- if (account)
74168
- return { account, ...body?.username ? { username: body.username } : {} };
74169
- return { failure: identity.ok ? "no account in the response" : identity.message };
74170
- }
74171
-
74172
- // src/commands/auth.ts
74772
+ init_account();
74173
74773
  init_agent_key_access();
74174
74774
  init_args();
74775
+ import { homedir as homedir3 } from "node:os";
74776
+ import { join as join9 } from "node:path";
74175
74777
 
74176
74778
  // src/checks.ts
74177
74779
  init_client();
@@ -76145,7 +76747,7 @@ var HELP = {
76145
76747
  group: "Trade",
76146
76748
  intro: "Quote, swap and bridge on Solana or Hood; read an operation",
76147
76749
  summary: "Quote, confirm and swap on Solana or Hood, bridge between them; read an operation by id",
76148
- description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent. The first buy after a launch is this command, unless the launch carried it with candle launch --buy. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana) and a named wallet must be on it. A Hood TEE wallet signs one leg at a time: approve, Permit2, trade, then the fee, each only after the one before it landed. SOL or USDC to ETH or USDG, or back, is a bridge through Relay from a TEE wallet into this key's own TEE wallet on the other chain (--to names it when there are several); Candle charges no fee, only configured raw caps and USD limits apply (unset means unlimited, zero still binds), and this machine checks Relay's deposit before it is signed. The bridge prints its deposit and returns while Relay fills; --wait follows it for up to ten minutes. While it is open, a sweep of either wallet refuses BRIDGE_IN_FLIGHT for two hours. Vault to vault: fund the Solana TEE wallet from the vault, bridge, then disable and sweep the Hood TEE wallet into its vault. Every plan can trade Candle-launched tokens and base assets, and buy or sell a token that was not launched on Candle. Each trade pays the plan's agent fee on top; a base-pair swap or a bridge between this key's own wallets pays none. candle plans shows the fees in force. With --json, stdout is one JSON value for every swap, trade, replay and status read: kind, status, clientTradeId, signature (the primary transaction, or null), signatures (a list) and raw (the original response); every human line is on stderr. The keys older versions printed stay beside them.",
76750
+ description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent. The first buy after a launch is this command, unless the launch carried it with candle launch --buy. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana) and a named wallet must be on it. A Hood TEE wallet signs one leg at a time: approve, Permit2, trade, then the fee, each only after the one before it landed. SOL or USDC to ETH or USDG, or back, is a bridge through Relay from a TEE wallet into this key's own TEE wallet on the other chain (--to names it when there are several); Candle charges no fee, only configured raw caps and USD limits apply (unset means unlimited, zero still binds), and this machine checks Relay's deposit before it is signed. The bridge prints its deposit and returns while Relay fills; --wait follows it for up to ten minutes. While it is open, a sweep of either wallet refuses BRIDGE_IN_FLIGHT for two hours. Vault to vault: fund the Solana TEE wallet from the vault, bridge, then disable and sweep the Hood TEE wallet into its vault. Every plan can trade Candle-launched tokens and base assets, and buy or sell a token that was not launched on Candle. Each trade pays the plan's agent fee on top; a base-pair swap or a bridge between this key's own wallets pays none. candle plans shows the fees in force. With --dry-run, stdout reports per-check results and the quote; nothing is signed or sent. Otherwise with --json, stdout is one JSON value for every swap, trade, replay and status read: kind, status, clientTradeId, signature (the primary transaction, or null), signatures (a list) and raw (the original response); every human line is on stderr. The keys older versions printed stay beside them.",
76149
76751
  usage: ["candle swap <from> <to> [flags]", "candle swap status <id>"],
76150
76752
  rows: [
76151
76753
  {
@@ -76157,7 +76759,14 @@ var HELP = {
76157
76759
  description: "Read an operation without resending it; --wait follows a bridge until it ends"
76158
76760
  }
76159
76761
  ],
76762
+ flags: [
76763
+ {
76764
+ invocation: "--dry-run",
76765
+ description: "Check a Solana TEE payer, build the quote and check spend, then stop. Never signs, sends, prompts or saves an operation. Refuses --yes, --percent and --client-trade-id; embedded, Hood and bridge payers are unsupported"
76766
+ }
76767
+ ],
76160
76768
  examples: [
76769
+ "candle swap SOL USDC --amount 0.001 --wallet AgentOne --dry-run --json",
76161
76770
  "candle swap SOL USDC --amount 0.5 --wallet AgentOne",
76162
76771
  "candle swap USDC SOL --percent 100 --wallet AgentOne",
76163
76772
  "candle swap ETH USDG --amount 0.05 --wallet HoodOne",
@@ -76458,9 +77067,10 @@ var HELP = {
76458
77067
  group: "Trade",
76459
77068
  intro: "Move funds out of a TEE wallet to your wallets or its vault",
76460
77069
  summary: "Move funds out of a TEE wallet to your own wallets or its vault, through its bound key",
76461
- description: "Moves one asset out of a TEE wallet this machine can sign for. The bound key must be a Withdraw key (transfer:bound); from that wallet, Candle allows only its own pinned vault or another of the account's wallets you linked while signed in or marked trusted, and, where the server has TEE_WITHDRAWALS_ENABLED on, an address on the account's withdrawal allowlist (named by its label, id or address). To a linked wallet or an approved address only configured raw caps and USD limits apply (unset means unlimited, zero still binds), and the asset must be a base asset; to the vault any token and max are allowed. Candle builds the transaction, this machine approves the relay, Privy signs, Candle broadcasts. The destination and its kind are shown before you confirm. The asset decides the chain: ETH, USDG or --token is Hood, from a Hood TEE wallet, where this machine checks the one leg moves exactly what you confirmed before it is signed, and ETH max leaves the gas reserve.",
77070
+ description: "Moves one asset out of a TEE wallet this machine can sign for. The bound key must be a Withdraw key (transfer:bound); from that wallet, Candle allows only its own pinned vault or another of the account's wallets you linked while signed in or marked trusted, and, where the server has TEE_WITHDRAWALS_ENABLED on, an address on the account's withdrawal allowlist (named by its label, id or address). To a linked wallet or an approved address only configured raw caps and USD limits apply (unset means unlimited, zero still binds), and the asset must be a base asset; to the vault any token and max are allowed. Candle builds the transaction, this machine approves the relay, Privy signs, Candle broadcasts. The destination and its kind are shown before you confirm. candle transfer status <id> reads a transfer by its id or your --client-id: built, expired, submitted with its signature, unconfirmed (check the chain or candle activity before using a new client id), or failed (kept for 24 hours; older transfers are in candle activity). The asset decides the chain: ETH, USDG or --token is Hood, from a Hood TEE wallet, where this machine checks the one leg moves exactly what you confirmed before it is signed, and ETH max leaves the gas reserve.",
76462
77071
  usage: [
76463
- "candle transfer --to <address|wallet name|allowlist label|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <tee>] [--yes] [--json]"
77072
+ "candle transfer --to <address|wallet name|allowlist label|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <tee>] [--client-id <id>] [--yes] [--json]",
77073
+ "candle transfer status <transfer id|client id> [--json]"
76464
77074
  ],
76465
77075
  rows: [],
76466
77076
  flags: [
@@ -76480,10 +77090,15 @@ var HELP = {
76480
77090
  invocation: "--rpc-url <url>",
76481
77091
  description: "Your own Solana RPC, or on Hood the EVM RPC a --token's decimals are read over (optional)"
76482
77092
  },
77093
+ {
77094
+ invocation: "--client-id <id>",
77095
+ description: "Your own id for this transfer (1 to 128 of letters, digits, . _ : -). An id already used for a built or sent transfer is refused with IDEMPOTENCY_CONFLICT naming that transfer, so a retry never sends twice; read it with candle transfer status <id>"
77096
+ },
76483
77097
  { invocation: "--yes", description: "Skip the confirmation prompt (the destination is still printed)" }
76484
77098
  ],
76485
77099
  examples: [
76486
- "candle transfer --to vault --asset SOL --amount max --wallet AgentOne",
77100
+ "candle transfer --to vault --asset SOL --amount max --wallet AgentOne --client-id sweep-2026-11-07",
77101
+ "candle transfer status sweep-2026-11-07",
76487
77102
  "candle transfer --to treasury --asset USDC --amount 250 --wallet AgentOne --yes --json",
76488
77103
  "candle transfer --to vault --asset ETH --amount max --wallet HoodAgent"
76489
77104
  ],
@@ -76567,9 +77182,9 @@ var HELP = {
76567
77182
  group: "Account",
76568
77183
  intro: "Holdings, prices and value of every wallet",
76569
77184
  summary: "Every wallet's holdings, prices and value on Solana and Hood (vault read over your own RPCs)",
76570
- description: "Vault, TEE and embedded wallets on Solana and Hood, each token with its chain, amount, price and value, then subtotals per group and per chain and one total. TEE and embedded balances, on both chains, come from Candle. Solana vault and external wallets are read over your own Solana RPC, and Candle is sent only the mints they hold, for prices. EVM vault keys are read over your own EVM RPC for ETH and USDG only (the CLI cannot list other tokens), which must be Hood's (chain id 4663); any other chain is refused. Their prices come from Candle by asset (ETH, USDG), never by address. Unpriced tokens are shown as unpriced with the reason (no-market-row, stale-mark, unusable-price, source-unavailable) and left out of the total. A wallet that could not be read in full is marked not read, the footer names its chain, and the exit is 3. Where Candle serves LP, each Solana TEE wallet's DAMM v2 positions follow the tokens (share of the pool plus unclaimed fees, valued by Candle at the same marks) and count in the total; a position whose pool could not be read is shown as not read and the total says it is partial. A bridge moves value between the chain subtotals; it is not a trade. With a filter, only the wallets whose label or address contains it are read and shown, the subtotals and total are for those wallets, and the first line says how many matched. When Candle listed only part of the account's wallets, the result stays partial even if every wallet shown was read, and no match says so. After the totals, a Hyperliquid section (live from Hyperliquid, not in the ledger, and never in the total) lists each EVM TEE wallet's Hyperliquid account value, unrealized P&L, positions and risk at the default thresholds, read from Hyperliquid's public API on this machine; a failed read there is not read and the exit is 3. --chain solana and --no-perps read nothing from Hyperliquid.",
77185
+ description: "Vault, TEE and embedded wallets on Solana and Hood, each token with its chain, amount, price and value, then subtotals per group and per chain and one total. TEE and embedded balances, on both chains, come from Candle. Solana vault and external wallets are read over your own Solana RPC, and Candle is sent only the mints they hold, for prices. EVM vault keys are read over your own EVM RPC for ETH and USDG only (the CLI cannot list other tokens), which must be Hood's (chain id 4663); any other chain is refused. Their prices come from Candle by asset (ETH, USDG), never by address. Unpriced tokens are shown as unpriced with the reason (no-market-row, stale-mark, unusable-price, source-unavailable) and left out of the total. A wallet that could not be read in full is marked not read, the footer names its chain, and the exit is 3. Where Candle serves LP, each Solana TEE wallet's DAMM v2 positions follow the tokens (share of the pool plus unclaimed fees, valued by Candle at the same marks) and count in the total; a position whose pool could not be read is shown as not read and the total says it is partial. A bridge moves value between the chain subtotals; it is not a trade. With a filter, only the wallets whose label or address contains it are read and shown, the subtotals and total are for those wallets, and the first line says how many matched. When Candle listed only part of the account's wallets, the result stays partial even if every wallet shown was read, and no match says so. After the totals, a Hyperliquid section (live from Hyperliquid, not in the ledger, and never in the total) lists each EVM TEE wallet's Hyperliquid account value, unrealized P&L, positions and risk at the default thresholds, read from Hyperliquid's public API on this machine; a failed read there is not read and the exit is 3. --chain solana and --no-perps read nothing from Hyperliquid. The vault is read only for its addresses, so the command needs no terminal: without one, a factor that asks nothing (--factor touch-id or passkey) still unlocks it, and otherwise the vault row says not read and the other groups are shown, but the result is partial: the total says so and the exit is 3. --no-vault leaves the vault out on purpose, and then the result can be complete (exit 0).",
76571
77186
  usage: [
76572
- "candle portfolio [<filter>] [--chain solana|hood] [--rpc-url <url>] [--evm-rpc-url <url>] [--no-perps] [--json]"
77187
+ "candle portfolio [<filter>] [--chain solana|hood] [--rpc-url <url>] [--evm-rpc-url <url>] [--no-perps] [--no-vault] [--json]"
76573
77188
  ],
76574
77189
  rows: [
76575
77190
  {
@@ -76591,6 +77206,10 @@ var HELP = {
76591
77206
  invocation: "--no-perps",
76592
77207
  description: "Skip the Hyperliquid section: send no EVM TEE address to Hyperliquid's public API"
76593
77208
  },
77209
+ {
77210
+ invocation: "--no-vault",
77211
+ description: "Leave the vault out: no unlock, and no vault address is sent to your RPC"
77212
+ },
76594
77213
  KEYSTORE_FLAG
76595
77214
  ],
76596
77215
  examples: [
@@ -77043,6 +77662,10 @@ var HELP = {
77043
77662
  description: "Give agents access to the account's embedded wallets: opens the Agents page's grant step in your browser (no key is created) and, with an API key, waits until it lands"
77044
77663
  },
77045
77664
  { invocation: "revoke <wallet-id>", description: "Revoke a linked wallet" },
77665
+ {
77666
+ invocation: "allowlist [--all]",
77667
+ description: "The account's withdrawal allowlist (the destinations candle transfer --to <label> resolves by). Any key reads it; approving an address is the owner's, in the agent console's Wallets tab (an API key cannot approve); an acting key can revoke one, a read-only key cannot. --all adds removed rows"
77668
+ },
77046
77669
  {
77047
77670
  invocation: "trust <label|address|id|prefix*>...",
77048
77671
  description: "Mark linked wallets yours, so agents can move funds into them (owner only; typed confirm)"
@@ -77076,6 +77699,7 @@ var HELP = {
77076
77699
  "candle wallet authorize",
77077
77700
  "candle wallet import --chain solana --key-file ./signer.json",
77078
77701
  "candle wallet revoke wal_123",
77702
+ "candle wallet allowlist",
77079
77703
  "candle wallet trust 'tr-*' 'dest-*'",
77080
77704
  "candle wallet allow-launch launcher-1",
77081
77705
  "candle wallet close-empty --keep EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
@@ -77273,6 +77897,10 @@ var HELP = {
77273
77897
  description: "A dedicated, capped Solana wallet for one agent. The key is generated and sealed on this machine; enable delegates it to this profile's API key with a sweep vault pinned, and sweep moves everything back to that vault, signed locally.",
77274
77898
  usage: ["candle tee <subcommand> [flags]"],
77275
77899
  rows: [
77900
+ {
77901
+ invocation: "check <address|id|label>... [--label-prefix <p>] [--sign] [--max <n>] [--rpc-url <url>]",
77902
+ description: "Check payable membership, record parity and this machine's signer without spending. --sign builds a tiny SOL/USDC quote, checks spend, signs and simulates; never broadcasts (default cap 10). Needs swap:write"
77903
+ },
77276
77904
  {
77277
77905
  invocation: "new [--label <name>]",
77278
77906
  description: "Add a Solana key to an existing tee-wallets.enc. With no store, refuses: candle vault new-key --chain solana, then candle vault promote --from <label> --to-key <prefix>. --factor does not apply"
@@ -77335,6 +77963,8 @@ var HELP = {
77335
77963
  }
77336
77964
  ],
77337
77965
  examples: [
77966
+ "candle tee check --label-prefix pool- --json",
77967
+ "candle tee check AgentOne --sign --rpc-url <url>",
77338
77968
  "candle tee new --label AgentOne",
77339
77969
  "candle tee status AgentOneAddress",
77340
77970
  "candle tee sweep AgentOneAddress",
@@ -77901,13 +78531,14 @@ class AgentProfiles {
77901
78531
  // src/core/cards.ts
77902
78532
  init_render();
77903
78533
  init_canonical_json();
77904
- import { createHash as createHash3, randomBytes as randomBytes5, randomUUID as randomUUID2 } from "node:crypto";
78534
+ import { createHash as createHash3, randomBytes as randomBytes5, randomUUID as randomUUID3 } from "node:crypto";
77905
78535
 
77906
78536
  // src/core/classify.ts
77907
78537
  var READ = { class: "read" };
77908
78538
  var card = (kind, idKey = "coreOpId") => ({ class: "card", kind, idKey });
77909
78539
  var TOOL_CLASSES = {
77910
78540
  candle_get_market: READ,
78541
+ candle_quote: READ,
77911
78542
  candle_get_feed: READ,
77912
78543
  candle_vamp_candidates: READ,
77913
78544
  candle_token_events: READ,
@@ -77931,6 +78562,8 @@ var TOOL_CLASSES = {
77931
78562
  candle_get_agent_positions: READ,
77932
78563
  candle_get_plans: READ,
77933
78564
  candle_get_wallets: READ,
78565
+ candle_get_trading_wallets: READ,
78566
+ candle_get_spendable_balances: READ,
77934
78567
  candle_get_profile_wallets: READ,
77935
78568
  candle_get_profile_pnl: READ,
77936
78569
  candle_get_profile_trades: READ,
@@ -77956,6 +78589,7 @@ var TOOL_CLASSES = {
77956
78589
  candle_perps_risk: READ,
77957
78590
  candle_perps_thresholds: READ,
77958
78591
  candle_trade: card("trade", "clientTradeId"),
78592
+ candle_trade_execute: card("trade"),
77959
78593
  candle_swap: card("swap", "clientSwapId"),
77960
78594
  candle_launch_token: card("launch", "clientLaunchId"),
77961
78595
  candle_launch_and_seed: card("launch", "clientLaunchId"),
@@ -78045,7 +78679,8 @@ async function readOnce(executor, target, writeKey) {
78045
78679
  if (!readsById(target))
78046
78680
  return { readable: false };
78047
78681
  const kind = target.kind === "launch" ? "launch" : "trade";
78048
- const outcome = await executor.execute("candle_get_operation", { clientId: target.opId, kind }, writeKey);
78682
+ const clientId = target.tool === "candle_trade_execute" ? target.params.clientTradeId : target.opId;
78683
+ const outcome = await executor.execute("candle_get_operation", { clientId, kind }, writeKey);
78049
78684
  if (outcome.state === "succeeded" || outcome.state === "pending") {
78050
78685
  const job = outcome.result?.job ?? outcome.result;
78051
78686
  const status = typeof job?.status === "string" ? job.status : "";
@@ -79763,8 +80398,8 @@ class CardBook {
79763
80398
  if (idKey === "clientId")
79764
80399
  return `0x${randomBytes5(16).toString("hex")}`;
79765
80400
  if (idKey === "clientDepositId")
79766
- return `deposit-${randomUUID2()}`;
79767
- return randomUUID2();
80401
+ return `deposit-${randomUUID3()}`;
80402
+ return randomUUID3();
79768
80403
  }
79769
80404
  view(card2) {
79770
80405
  return JSON.parse(JSON.stringify(card2));
@@ -80623,18 +81258,18 @@ async function readJournal(path) {
80623
81258
  const tornTail = text2.length > 0 && !text2.endsWith(`
80624
81259
  `);
80625
81260
  const rows = [];
80626
- let skipped = 0;
81261
+ let skipped2 = 0;
80627
81262
  for (const line of text2.split(`
80628
81263
  `)) {
80629
81264
  if (line.trim() === "")
80630
81265
  continue;
80631
81266
  const row2 = parseRow(line);
80632
81267
  if (row2 === undefined)
80633
- skipped++;
81268
+ skipped2++;
80634
81269
  else
80635
81270
  rows.push(row2);
80636
81271
  }
80637
- return { rows, skipped, tornTail };
81272
+ return { rows, skipped: skipped2, tornTail };
80638
81273
  }
80639
81274
  function parseRow(line) {
80640
81275
  let value;
@@ -80695,10 +81330,10 @@ function registerLaunchHandlers(server, deps) {
80695
81330
  server.handle("launch.dryRun", async (params, { client }) => deps.cards.dryRun({ cardId: params.cardId, factor: parseFactor(params.factor) }, client.clientId));
80696
81331
  }
80697
81332
  function coded(error, fallback) {
80698
- const failure2 = error;
80699
- if (failure2 !== null && typeof failure2 === "object" && "rpcCode" in failure2)
81333
+ const failure3 = error;
81334
+ if (failure3 !== null && typeof failure3 === "object" && "rpcCode" in failure3)
80700
81335
  return error;
80701
- return refused(typeof failure2?.code === "string" ? failure2.code : fallback, typeof failure2?.message === "string" ? failure2.message : String(error));
81336
+ return refused(typeof failure3?.code === "string" ? failure3.code : fallback, typeof failure3?.message === "string" ? failure3.message : String(error));
80702
81337
  }
80703
81338
 
80704
81339
  // src/commands/core.ts
@@ -81102,7 +81737,7 @@ ${text2}` : text2;
81102
81737
  const sleep = this.deps.sleep ?? defaultSleep;
81103
81738
  let tooMany = 0;
81104
81739
  let lost = 0;
81105
- let failure2;
81740
+ let failure3;
81106
81741
  while (!poll.stopped) {
81107
81742
  const answer2 = await client.events(poll.turnId, poll.next, { signal: poll.abort.signal });
81108
81743
  if (poll.stopped)
@@ -81137,7 +81772,7 @@ ${text2}` : text2;
81137
81772
  if (refusal2.code === "too_many_polls") {
81138
81773
  tooMany++;
81139
81774
  if (tooMany > TOO_MANY_POLLS_RETRIES) {
81140
- failure2 = "too_many_polls";
81775
+ failure3 = "too_many_polls";
81141
81776
  break;
81142
81777
  }
81143
81778
  await sleep(TOO_MANY_POLLS_WAIT_MS);
@@ -81146,13 +81781,13 @@ ${text2}` : text2;
81146
81781
  if (refusal2.shape === "transport") {
81147
81782
  lost++;
81148
81783
  if (lost > LOST_POLL_RETRIES) {
81149
- failure2 = "QUANT_UNREACHABLE";
81784
+ failure3 = "QUANT_UNREACHABLE";
81150
81785
  break;
81151
81786
  }
81152
81787
  await sleep(Math.min(1000 * lost, 5000));
81153
81788
  continue;
81154
81789
  }
81155
- failure2 = refusal2.code;
81790
+ failure3 = refusal2.code;
81156
81791
  break;
81157
81792
  }
81158
81793
  if (this.poll !== poll)
@@ -81163,11 +81798,11 @@ ${text2}` : text2;
81163
81798
  this.ended.delete(this.ended.values().next().value);
81164
81799
  if (this.turn?.turnId === poll.turnId)
81165
81800
  this.turn.running = false;
81166
- if (failure2 !== undefined) {
81801
+ if (failure3 !== undefined) {
81167
81802
  this.publish({
81168
81803
  turnId: poll.turnId,
81169
81804
  kind: "poll_failed",
81170
- data: { code: failure2 },
81805
+ data: { code: failure3 },
81171
81806
  seq: poll.next,
81172
81807
  origin: poll.origin
81173
81808
  });
@@ -83204,8 +83839,8 @@ async function prepare(params, call2, deps) {
83204
83839
  try {
83205
83840
  preview = await deps.reads.previewVault(parsed, wallet3);
83206
83841
  } catch (error) {
83207
- const failure2 = error;
83208
- throw refused(typeof failure2.code === "string" ? failure2.code : "TRANSFER_PREVIEW_FAILED", `${terminalText(typeof failure2.message === "string" ? failure2.message : String(error))} Nothing was prepared.`);
83842
+ const failure3 = error;
83843
+ throw refused(typeof failure3.code === "string" ? failure3.code : "TRANSFER_PREVIEW_FAILED", `${terminalText(typeof failure3.message === "string" ? failure3.message : String(error))} Nothing was prepared.`);
83209
83844
  }
83210
83845
  } else {
83211
83846
  const withdraw = deps.trading.withdrawState();
@@ -84579,8 +85214,8 @@ async function readTargetSigner(ctx, deviceToken, keyPrefix) {
84579
85214
  }
84580
85215
  async function preflightToKey(ctx, raw, opts) {
84581
85216
  const { deps, json } = ctx;
84582
- const refuse3 = (failure2) => {
84583
- writeLocalFailure(deps, failure2, json);
85217
+ const refuse3 = (failure3) => {
85218
+ writeLocalFailure(deps, failure3, json);
84584
85219
  return { ok: false, exit: 1 };
84585
85220
  };
84586
85221
  const deviceToken = await resolveDeviceToken(deps, ctx.profile);
@@ -84732,8 +85367,8 @@ async function rebindPromoted(ctx, deviceToken, toKeyPrefix, wallets2, pinnedSpk
84732
85367
  if (!committed.ok) {
84733
85368
  run2.ok = false;
84734
85369
  if ("missing" in committed) {
84735
- const failure2 = missingSignerFailure(committed.missing);
84736
- run2.failure = { ...failure2, status: 409, chunk: at2 + 1 };
85370
+ const failure3 = missingSignerFailure(committed.missing);
85371
+ run2.failure = { ...failure3, status: 409, chunk: at2 + 1 };
84737
85372
  } else if ("changed" in committed) {
84738
85373
  run2.failure = { ...committed.changed, status: 409, chunk: at2 + 1 };
84739
85374
  } else {
@@ -85517,8 +86152,8 @@ async function moveToLiveKey(ctx, deviceToken, source, toKeyRaw) {
85517
86152
  for (const chunk of chunkWallets(ids)) {
85518
86153
  const preview = await postRebind(ctx, deviceToken, { dryRun: true, toKeyPrefix: target.keyPrefix, wallets: chunk });
85519
86154
  if (!preview.ok) {
85520
- const failure2 = rebindFailureDetails(preview, {});
85521
- writeScreenLocalFailure(deps, failure2, json);
86155
+ const failure3 = rebindFailureDetails(preview, {});
86156
+ writeScreenLocalFailure(deps, failure3, json);
85522
86157
  return 1;
85523
86158
  }
85524
86159
  const shown = preview.body;
@@ -89536,8 +90171,7 @@ async function externalList(args, ctx) {
89536
90171
  return usage(ctx, `Unexpected argument: ${parsed.positionals[0]}`);
89537
90172
  if (!refuseEnvPassphrase(ctx))
89538
90173
  return 1;
89539
- if (!requireTty(ctx, "external list"))
89540
- return 1;
90174
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "external list");
89541
90175
  const { deps } = ctx;
89542
90176
  const resolvedVault = vaultPathFor(ctx, parsed);
89543
90177
  if ("error" in resolvedVault)
@@ -89545,7 +90179,7 @@ async function externalList(args, ctx) {
89545
90179
  const path = resolvedVault.path;
89546
90180
  return runVaultCommand(ctx, async ({ hold }) => {
89547
90181
  const raw = await requireVaultRaw(ctx, resolvedVault);
89548
- const vault = hold((await unlockInteractively(ctx, path, raw, { acceptOlderCopy: parsed.booleans.has("--accept-older-copy") })).vault);
90182
+ const vault = hold((await unlockInteractively(promptCtx, path, raw, { acceptOlderCopy: parsed.booleans.has("--accept-older-copy") })).vault);
89549
90183
  const externals = vault.index.entries.filter((entry) => entry.role === "external");
89550
90184
  const rows = externals.map((entry) => ({
89551
90185
  address: entry.address,
@@ -90433,7 +91067,7 @@ init_secret_store();
90433
91067
  init_key_output();
90434
91068
  init_keys();
90435
91069
  init_tee_rebind();
90436
- import { randomUUID as randomUUID7 } from "node:crypto";
91070
+ import { randomUUID as randomUUID8 } from "node:crypto";
90437
91071
 
90438
91072
  // src/commands/token-pages.ts
90439
91073
  init_esm();
@@ -90443,7 +91077,7 @@ init_deps();
90443
91077
  init_human_output();
90444
91078
  init_presentation();
90445
91079
  init_render();
90446
- import { createHash as createHash5, randomUUID as randomUUID6 } from "node:crypto";
91080
+ import { createHash as createHash5, randomUUID as randomUUID7 } from "node:crypto";
90447
91081
  var EXIT_OUTCOME_UNKNOWN = 3;
90448
91082
  var EXIT_REVISION_CONFLICT = 4;
90449
91083
  var TOKEN_PAGE_LIMITS = { thesis: 280, tokenomics: 5000, lore: 5000, update: 1000, comment: 500 };
@@ -90526,8 +91160,8 @@ function writeApiFailure(ctx, result, authType) {
90526
91160
  writeRefusal(ctx, refusal3);
90527
91161
  return 1;
90528
91162
  }
90529
- const failure2 = result;
90530
- writeFailure(ctx.deps, failure2.status === 404 ? { ...failure2, message: REFUSALS.not_found } : failure2, { apiUrl: ctx.apiUrl, authType }, ctx.json);
91163
+ const failure3 = result;
91164
+ writeFailure(ctx.deps, failure3.status === 404 ? { ...failure3, message: REFUSALS.not_found } : failure3, { apiUrl: ctx.apiUrl, authType }, ctx.json);
90531
91165
  return 1;
90532
91166
  }
90533
91167
  function dataOf(result) {
@@ -90590,7 +91224,7 @@ function writeUnknown(ctx, idempotencyKey) {
90590
91224
  function idempotencyKeyOf(values, usage4) {
90591
91225
  const given2 = values["--idempotency-key"];
90592
91226
  if (given2 === undefined)
90593
- return randomUUID6();
91227
+ return randomUUID7();
90594
91228
  if (given2.length < 1 || given2.length > 128)
90595
91229
  return usage4("--idempotency-key must be 1 to 128 characters.");
90596
91230
  return given2;
@@ -91306,7 +91940,7 @@ async function keysRotate(args, ctx) {
91306
91940
  writeScreenUsageFailure(deps, "--idempotency-key must be 1 to 128 characters.", json);
91307
91941
  return 2;
91308
91942
  }
91309
- const idempotencyKey = keyFlag ?? randomUUID7();
91943
+ const idempotencyKey = keyFlag ?? randomUUID8();
91310
91944
  let graceSeconds = now ? 0 : DEFAULT_GRACE_SECONDS;
91311
91945
  if (graceFlag !== undefined) {
91312
91946
  const grace = parseGrace(graceFlag);
@@ -91864,7 +92498,7 @@ async function client(ctx) {
91864
92498
  }, ctx.json);
91865
92499
  }
91866
92500
  }
91867
- function failure2(answer2, ctx, statusRoute = false) {
92501
+ function failure3(answer2, ctx, statusRoute = false) {
91868
92502
  const { refusal: refusal3 } = answer2;
91869
92503
  if (ctx.json && refusal3.body !== undefined)
91870
92504
  ctx.deps.stdout.write(`${JSON.stringify(refusal3.body)}
@@ -91932,7 +92566,7 @@ async function labStatus(args, ctx) {
91932
92566
  return 1;
91933
92567
  const answer2 = await connected.lab.status();
91934
92568
  if (!answer2.ok)
91935
- return failure2(answer2, ctx, true);
92569
+ return failure3(answer2, ctx, true);
91936
92570
  if (ctx.json)
91937
92571
  ctx.deps.stdout.write(`${JSON.stringify(answer2.body)}
91938
92572
  `);
@@ -91977,7 +92611,7 @@ async function labReplay(args, ctx) {
91977
92611
  const id = parsed2.positionals[0];
91978
92612
  const answer2 = action === "get" ? await connected2.lab.replayGet(id) : await connected2.lab.replayCancel(id);
91979
92613
  if (!answer2.ok)
91980
- return failure2(answer2, ctx);
92614
+ return failure3(answer2, ctx);
91981
92615
  if (action === "get")
91982
92616
  return replayOutput(answer2.body, ctx);
91983
92617
  if (ctx.json)
@@ -92043,7 +92677,7 @@ ${REPLAY_USAGE}`);
92043
92677
  };
92044
92678
  const started = await connected.lab.replay(body);
92045
92679
  if (!started.ok)
92046
- return failure2(started, ctx);
92680
+ return failure3(started, ctx);
92047
92681
  if (parsed.booleans.has("--no-wait")) {
92048
92682
  if (ctx.json)
92049
92683
  ctx.deps.stdout.write(`${JSON.stringify(started.body)}
@@ -92053,7 +92687,7 @@ ${REPLAY_USAGE}`);
92053
92687
  return 0;
92054
92688
  }
92055
92689
  const result = await connected.lab.waitForReplay(started.body.job_id);
92056
- return result.ok ? replayOutput(result.body, ctx) : failure2(result, ctx);
92690
+ return result.ok ? replayOutput(result.body, ctx) : failure3(result, ctx);
92057
92691
  }
92058
92692
  async function report(args, ctx, kind) {
92059
92693
  const flags = kind === "runners" ? ["--hours", "--min-wallets"] : ["--days", "--min-n"];
@@ -92078,7 +92712,7 @@ async function report(args, ctx, kind) {
92078
92712
  return 1;
92079
92713
  const answer2 = kind === "runners" ? await connected.lab.runners(q) : await connected.lab.edges(q);
92080
92714
  if (!answer2.ok)
92081
- return failure2(answer2, ctx);
92715
+ return failure3(answer2, ctx);
92082
92716
  if (ctx.json)
92083
92717
  ctx.deps.stdout.write(`${JSON.stringify(answer2.body)}
92084
92718
  `);
@@ -92116,7 +92750,7 @@ init_zod();
92116
92750
  init_args();
92117
92751
  init_client();
92118
92752
  init_evm_lite();
92119
- import { randomUUID as randomUUID8 } from "node:crypto";
92753
+ import { randomUUID as randomUUID9 } from "node:crypto";
92120
92754
 
92121
92755
  // src/fee-wallet.ts
92122
92756
  init_solana_lite();
@@ -92206,6 +92840,8 @@ init_solana_lite();
92206
92840
  var NATIVE_MINT2 = "So11111111111111111111111111111111111111112";
92207
92841
  var LAMPORTS_PER_SIGNATURE = 5000n;
92208
92842
  var TOKEN_ACCOUNT_RENT_LAMPORTS = 2039280n;
92843
+ var BUYER_TALLY_RENT_LAMPORTS = 1461600n;
92844
+ var REGISTER_BUYER = [144, 92, 137, 46, 209, 220, 27, 164];
92209
92845
  var DEFAULT_UNITS_PER_INSTRUCTION = 200000n;
92210
92846
  var MAX_COMPUTE_UNITS = 1400000n;
92211
92847
 
@@ -92289,7 +92925,7 @@ function readLeg(tx, index, wallet3) {
92289
92925
  if (program === MEMO_PROGRAM_ID)
92290
92926
  continue;
92291
92927
  if (program !== undefined)
92292
- others.push({ program, data: ix.data });
92928
+ others.push({ program, data: ix.data, accounts: accounts.map(key) });
92293
92929
  }
92294
92930
  if (unitPrice > 0n) {
92295
92931
  const units = unitLimit ?? (computed * DEFAULT_UNITS_PER_INSTRUCTION > MAX_COMPUTE_UNITS ? MAX_COMPUTE_UNITS : computed * DEFAULT_UNITS_PER_INSTRUCTION);
@@ -92297,12 +92933,36 @@ function readLeg(tx, index, wallet3) {
92297
92933
  }
92298
92934
  return { lamports, tokenOut, systemTransfers, others };
92299
92935
  }
92936
+ function isRegisterBuyer(data) {
92937
+ return data.length >= 8 && REGISTER_BUYER.every((byte, i) => data[i] === byte);
92938
+ }
92939
+ function readRegisterBuyer(register, swap3, wallet3) {
92940
+ if (register.program !== swap3.program)
92941
+ throw new BundleReadError("leg 1 registers the buyer with another program");
92942
+ if (register.data.length !== 12 || u323(register.data, 8) !== 0n)
92943
+ throw new BundleReadError("leg 1 registers the buyer with a proof");
92944
+ const [buyer, rentPayer, rules, tally] = register.accounts;
92945
+ if (buyer !== wallet3)
92946
+ throw new BundleReadError("leg 1 registers a buyer other than this wallet");
92947
+ if (rules === undefined || tally === undefined || !swap3.accounts.includes(rules) || !swap3.accounts.includes(tally))
92948
+ throw new BundleReadError("leg 1 registers the buyer for another pool than the one it buys");
92949
+ return rentPayer === wallet3 ? BUYER_TALLY_RENT_LAMPORTS : 0n;
92950
+ }
92300
92951
  function readLaunchBundle(args) {
92301
92952
  const launch = readLeg(decodeLeg(args.legs[0], 0), 0, args.wallet);
92302
92953
  const buy = readLeg(decodeLeg(args.legs[1], 1), 1, args.wallet);
92303
- if (buy.others.length !== 1)
92954
+ let registerLamports = 0n;
92955
+ const first = buy.others[0];
92956
+ if (first && isRegisterBuyer(first.data)) {
92957
+ if (!args.rules)
92958
+ throw new BundleReadError("leg 1 registers a buyer on a launch without rules");
92959
+ const swap4 = buy.others[1];
92960
+ if (buy.others.length !== 2 || !swap4 || isRegisterBuyer(swap4.data))
92961
+ throw new BundleReadError("leg 1 is not one curve swap");
92962
+ registerLamports = readRegisterBuyer(first, swap4, args.wallet);
92963
+ } else if (buy.others.length !== 1)
92304
92964
  throw new BundleReadError("leg 1 is not one curve swap");
92305
- const swap3 = buy.others[0];
92965
+ const swap3 = buy.others[buy.others.length - 1];
92306
92966
  if (u64(swap3.data, 8) !== args.buyRaw)
92307
92967
  throw new BundleReadError("leg 1 does not swap the confirmed --buy");
92308
92968
  if (args.minOutRaw !== undefined && u64(swap3.data, 16) !== args.minOutRaw)
@@ -92322,7 +92982,7 @@ function readLaunchBundle(args) {
92322
92982
  feeRaw = buy.tokenOut;
92323
92983
  quoteRaw = args.buyRaw + buy.tokenOut + launch.tokenOut;
92324
92984
  }
92325
- return { lamports: launch.lamports + buy.lamports, quoteRaw, feeRaw };
92985
+ return { lamports: launch.lamports + buy.lamports + registerLamports, quoteRaw, feeRaw };
92326
92986
  }
92327
92987
 
92328
92988
  // src/launch-check.ts
@@ -92645,7 +93305,7 @@ async function launch(args, ctx) {
92645
93305
  const flags = parsed.values;
92646
93306
  if (flags["--quote-asset"])
92647
93307
  flags["--quote-asset"] = flags["--quote-asset"].toLowerCase();
92648
- const id = flags["--client-trade-id"] ?? `launch-${randomUUID8()}`;
93308
+ const id = flags["--client-trade-id"] ?? `launch-${randomUUID9()}`;
92649
93309
  const embedded = EMBEDDED_WALLET_NAMES.has(flags["--wallet"] ?? "");
92650
93310
  const dryRun = parsed.booleans.has("--dry-run");
92651
93311
  const check = parsed.booleans.has("--check");
@@ -93131,7 +93791,8 @@ async function atomicLaunch(ctx, key, args) {
93131
93791
  wallet: wallet3.address,
93132
93792
  quote: quoteAsset,
93133
93793
  buyRaw: BigInt(buy.raw),
93134
- ...minOut !== undefined ? { minOutRaw: BigInt(minOut) } : {}
93794
+ ...minOut !== undefined ? { minOutRaw: BigInt(minOut) } : {},
93795
+ rules: args.rules !== undefined
93135
93796
  });
93136
93797
  } catch (error) {
93137
93798
  if (!(error instanceof BundleReadError))
@@ -93633,7 +94294,7 @@ init_render();
93633
94294
  init_solana_endpoint();
93634
94295
  init_solana_lite();
93635
94296
  init_trading();
93636
- import { randomUUID as randomUUID9 } from "node:crypto";
94297
+ import { randomUUID as randomUUID10 } from "node:crypto";
93637
94298
  init_swap();
93638
94299
  var LP_SCOPE = "lp:write";
93639
94300
  var CONFIRM_POLL_MS3 = 2000;
@@ -93936,7 +94597,7 @@ async function lpAdd(args, ctx) {
93936
94597
  if ("error" in parsed)
93937
94598
  return usage5(ctx, parsed.error);
93938
94599
  const flags = parsed.values;
93939
- const id = flags["--client-trade-id"] ?? `lp-${randomUUID9()}`;
94600
+ const id = flags["--client-trade-id"] ?? `lp-${randomUUID10()}`;
93940
94601
  if (parsed.positionals.length !== 2 || !flags["--amount"] || !flags["--wallet"] || !validClientId(id))
93941
94602
  return usage5(ctx, "Usage: candle lp add <pool> --amount <decimal> <token> --wallet <tee> [--position <nft-mint>] [--slippage-bps 100] [--client-trade-id <id>] [--rpc-url <url>] [--yes]");
93942
94603
  try {
@@ -93972,7 +94633,7 @@ async function lpRemove(args, ctx) {
93972
94633
  if ("error" in parsed)
93973
94634
  return usage5(ctx, parsed.error);
93974
94635
  const flags = parsed.values;
93975
- const id = flags["--client-trade-id"] ?? `lp-${randomUUID9()}`;
94636
+ const id = flags["--client-trade-id"] ?? `lp-${randomUUID10()}`;
93976
94637
  const percent2 = Number(flags["--percent"]);
93977
94638
  if (parsed.positionals.length !== 1 || !flags["--percent"] || !/^\d+(\.\d{1,2})?$/.test(flags["--percent"]) || !(percent2 > 0 && percent2 <= 100) || !validClientId(id))
93978
94639
  return usage5(ctx, "Usage: candle lp remove <position> --percent <0.01-100> [--wallet <tee>] [--slippage-bps 100] [--client-trade-id <id>] [--rpc-url <url>] [--yes]");
@@ -94004,7 +94665,7 @@ async function lpClaim(args, ctx) {
94004
94665
  if ("error" in parsed)
94005
94666
  return usage5(ctx, parsed.error);
94006
94667
  const flags = parsed.values;
94007
- const id = flags["--client-trade-id"] ?? `lp-${randomUUID9()}`;
94668
+ const id = flags["--client-trade-id"] ?? `lp-${randomUUID10()}`;
94008
94669
  if (parsed.positionals.length !== 1 || !validClientId(id))
94009
94670
  return usage5(ctx, "Usage: candle lp claim <position> [--wallet <tee>] [--client-trade-id <id>] [--rpc-url <url>] [--yes]");
94010
94671
  try {
@@ -94038,6 +94699,7 @@ var MCP_TOOL_NAMES = [
94038
94699
  "candle_launch_token",
94039
94700
  "candle_launch_and_seed",
94040
94701
  "candle_get_market",
94702
+ "candle_quote",
94041
94703
  "candle_get_feed",
94042
94704
  "candle_vamp_candidates",
94043
94705
  "candle_token_events",
@@ -94074,10 +94736,13 @@ var MCP_TOOL_NAMES = [
94074
94736
  "candle_get_plans",
94075
94737
  "candle_report_activity",
94076
94738
  "candle_trade",
94739
+ "candle_trade_execute",
94077
94740
  "candle_swap",
94078
94741
  "candle_transfer",
94079
94742
  "candle_sweep",
94080
94743
  "candle_get_wallets",
94744
+ "candle_get_trading_wallets",
94745
+ "candle_get_spendable_balances",
94081
94746
  "candle_get_profile_wallets",
94082
94747
  "candle_set_profile_wallets",
94083
94748
  "candle_get_profile_pnl",
@@ -94206,7 +94871,8 @@ async function mcp(args, ctx) {
94206
94871
  }
94207
94872
  const identityConfig = await deps.readConfig();
94208
94873
  const identityFields = effectiveProfileFields(identityConfig, ctx.profile);
94209
- deps.stderr.write(`${identityLine(ctx.profile, identityFields.account, apiUrl, credentialEnvOverrides(deps.env), identityFields.username, identityStyle(deps, "stderr"))}
94874
+ const identityOverrides = credentialEnvOverrides(deps.env);
94875
+ deps.stderr.write(`${identityLine(ctx.profile, identityOverrides.length > 0 ? undefined : identityFields.account, apiUrl, identityOverrides.length > 0 ? identityOverrides : undefined, identityFields.username, identityStyle(deps, "stderr"))}
94210
94876
 
94211
94877
  `);
94212
94878
  if (parsed.booleans.has("--print-config")) {
@@ -94253,7 +94919,7 @@ init_bridge();
94253
94919
  init_client();
94254
94920
  init_evm_lite();
94255
94921
  init_human_output();
94256
- import { randomUUID as randomUUID10, sign as sign4 } from "node:crypto";
94922
+ import { randomUUID as randomUUID11, sign as sign4 } from "node:crypto";
94257
94923
 
94258
94924
  // src/hyperliquid.ts
94259
94925
  init_sha3();
@@ -98166,7 +98832,7 @@ async function perpsDeposit(args, ctx) {
98166
98832
  if (parsed.positionals.length !== 2 || !amount || !DEC_RE3.test(amount) || !isDepositAsset(asset))
98167
98833
  return usage8(ctx, DEPOSIT_USAGE);
98168
98834
  const { values } = parsed;
98169
- const id = values["--id"] ?? `deposit-${randomUUID10()}`;
98835
+ const id = values["--id"] ?? `deposit-${randomUUID11()}`;
98170
98836
  if (!DEPOSIT_ID_RE.test(id))
98171
98837
  return usage8(ctx, "--id must be 1 to 128 letters, digits, '.', '_', ':' or '-'.");
98172
98838
  const slippageBps = Number(values["--slippage-bps"] ?? "100");
@@ -99305,8 +99971,8 @@ async function read2(ctx, argv, command) {
99305
99971
  writeUsageFailure(ctx.deps, error.message, ctx.json);
99306
99972
  return 2;
99307
99973
  }
99308
- const failure3 = { code: error.code, message: error.message, retryable: error.retryable };
99309
- writeLocalFailure(ctx.deps, failure3, ctx.json);
99974
+ const failure4 = { code: error.code, message: error.message, retryable: error.retryable };
99975
+ writeLocalFailure(ctx.deps, failure4, ctx.json);
99310
99976
  return 1;
99311
99977
  }
99312
99978
  return tradingFailure(ctx, error);
@@ -99320,7 +99986,7 @@ var perpsCandles = (argv, ctx) => read2(ctx, argv, "candles");
99320
99986
  // src/commands/perps-withdraw.ts
99321
99987
  init_args();
99322
99988
  init_human_output();
99323
- import { randomUUID as randomUUID11 } from "node:crypto";
99989
+ import { randomUUID as randomUUID12 } from "node:crypto";
99324
99990
 
99325
99991
  // src/hyperliquid-withdraw.ts
99326
99992
  var CANDLE_PERPS_WITHDRAW_PINS = {
@@ -99564,7 +100230,7 @@ async function perpsWithdraw(args, ctx) {
99564
100230
  const amountMicros = parseWithdrawAmount(amount);
99565
100231
  if (amountMicros === null)
99566
100232
  return usage10(ctx, "The amount must be a positive USDC decimal with at most 6 decimals.");
99567
- const id = parsed.values["--id"] ?? `withdraw-${randomUUID11()}`;
100233
+ const id = parsed.values["--id"] ?? `withdraw-${randomUUID12()}`;
99568
100234
  if (!ID_RE.test(id))
99569
100235
  return usage10(ctx, "--id must be 1 to 128 letters, digits, '.', '_', ':' or '-'.");
99570
100236
  if (!refuseEnvPassphrase(ctx))
@@ -99703,7 +100369,7 @@ init_render();
99703
100369
  init_secret_store();
99704
100370
  init_solana_lite();
99705
100371
  init_errors2();
99706
- import { generateKeyPairSync as generateKeyPairSync3, randomUUID as randomUUID13 } from "node:crypto";
100372
+ import { generateKeyPairSync as generateKeyPairSync3, randomUUID as randomUUID14 } from "node:crypto";
99707
100373
 
99708
100374
  // src/x402/codec.ts
99709
100375
  var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
@@ -100313,7 +100979,7 @@ init_deps();
100313
100979
  init_render();
100314
100980
  init_solana_endpoint();
100315
100981
  init_solana_lite();
100316
- import { randomUUID as randomUUID12 } from "node:crypto";
100982
+ import { randomUUID as randomUUID13 } from "node:crypto";
100317
100983
 
100318
100984
  // src/x402/pay.ts
100319
100985
  init_client();
@@ -100661,7 +101327,7 @@ async function runPaidRead(ctx, input) {
100661
101327
  blockhash: blockhashFor(ctx),
100662
101328
  env: ctx.deps.env,
100663
101329
  now: ctx.deps.now,
100664
- newId: randomUUID12
101330
+ newId: randomUUID13
100665
101331
  });
100666
101332
  const line = paymentLine(paid);
100667
101333
  if (line && !ctx.json)
@@ -101273,7 +101939,7 @@ async function runPlanPayment(ctx, run2) {
101273
101939
  const transaction = serializePartiallySigned(transfer, signer.sign(transfer.message));
101274
101940
  const at3 = new Date(deps.now()).toISOString();
101275
101941
  const pending = {
101276
- id: randomUUID13(),
101942
+ id: randomUUID14(),
101277
101943
  plan: run2.plan,
101278
101944
  renew,
101279
101945
  path,
@@ -101584,7 +102250,8 @@ var PLAN_CAPABILITY_LABELS2 = {
101584
102250
  createLinkedWallets: "Create linked wallets",
101585
102251
  importLinkedWallets: "Import linked wallets",
101586
102252
  limitOrders: "Limit orders",
101587
- quant: "Quant deep analyses (Max)"
102253
+ quant: "Quant deep analyses (Max)",
102254
+ strategyLab: "Strategy lab (saved strategies stay Max)"
101588
102255
  };
101589
102256
  var PLAN_LABELS2 = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
101590
102257
  function planLabel2(plan2) {
@@ -101613,6 +102280,9 @@ function planTableRows2(table) {
101613
102280
  row2("Uploads per minute", (e) => count2(e.limits.uploadsPerMin)),
101614
102281
  row2("Linked wallets", (e) => count2(e.limits.linkedWallets))
101615
102282
  ];
102283
+ if (plans.some((e) => typeof e.limits.labReplaysPerDay === "number")) {
102284
+ rows.push(row2("Strategy lab replays", (e) => typeof e.limits.labReplaysPerDay === "number" ? `${count2(e.limits.labReplaysPerDay)} a day` : "-"));
102285
+ }
101616
102286
  const known = Object.keys(PLAN_CAPABILITY_LABELS2);
101617
102287
  const served = new Set;
101618
102288
  for (const entry of plans)
@@ -102036,15 +102706,14 @@ async function runPlugin(name2, rawArgs, ctx) {
102036
102706
  if (split2.wallets.length > 0) {
102037
102707
  if (!refuseEnvPassphrase(ctx))
102038
102708
  return 1;
102039
- if (!requireTty(ctx, "candle <plugin> --wallet"))
102040
- return 1;
102709
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "candle <plugin> --wallet");
102041
102710
  const resolvedVault = vaultPathFor(ctx, { values: {}, booleans: new Set, positionals: [] });
102042
102711
  if ("error" in resolvedVault)
102043
102712
  return usage(ctx, resolvedVault.error);
102044
102713
  const vaultPath = resolvedVault.path;
102045
102714
  const resolved = await runVaultCommand(ctx, async ({ hold }) => {
102046
102715
  const raw = await requireVaultRaw(ctx, resolvedVault);
102047
- const vault = hold((await unlockInteractively(ctx, vaultPath, raw)).vault);
102716
+ const vault = hold((await unlockInteractively(promptCtx, vaultPath, raw)).vault);
102048
102717
  for (const requested of split2.wallets) {
102049
102718
  const entry = findExternalEntry(vault.index, requested);
102050
102719
  if (entry === undefined) {
@@ -102734,14 +103403,20 @@ var NO_API_KEY6 = {
102734
103403
  message: "No API key for this profile.",
102735
103404
  suggestion: "Set CANDLE_API_KEY, or run `candle auth login` to store one."
102736
103405
  };
103406
+ var NO_TERMINAL_REASON = "not read: no terminal for its unlock";
103407
+ var NO_TERMINAL_NOTE = "The vault was not read: its unlock needs a prompt, and standard input and standard error are not both a terminal. Run it at a terminal, pass --factor touch-id or --factor passkey to unlock without a prompt, or pass --no-vault to leave it out.";
102737
103408
  async function portfolio(args, ctx) {
102738
103409
  const parsed = parseArgs(args, {
102739
103410
  valueFlags: ["--rpc-url", "--evm-rpc-url", "--keystore", "--chain"],
102740
- booleanFlags: ["--no-perps"],
103411
+ booleanFlags: ["--no-perps", "--no-vault"],
102741
103412
  pathFlags: ["--keystore"]
102742
103413
  });
102743
103414
  if ("error" in parsed)
102744
103415
  return usage(ctx, parsed.error);
103416
+ const noVault = parsed.booleans.has("--no-vault");
103417
+ if (noVault && parsed.values["--keystore"] !== undefined) {
103418
+ return usage(ctx, "--keystore names the vault to read and has no effect with --no-vault.");
103419
+ }
102745
103420
  if (parsed.positionals.length > 1)
102746
103421
  return usage(ctx, `Unexpected argument: ${parsed.positionals[1]}`);
102747
103422
  const filter = parsed.positionals[0];
@@ -102788,16 +103463,30 @@ async function portfolio(args, ctx) {
102788
103463
  let vaultEntries;
102789
103464
  let evmEntries;
102790
103465
  let vaultReason;
103466
+ let vaultSkipped = false;
102791
103467
  let known = 0;
102792
- const raw = await readVaultRaw(resolvedVault.path);
102793
- if (raw === null) {
102794
- vaultReason = `no vault at ${resolvedVault.path}`;
102795
- } else {
103468
+ const raw = noVault ? null : await readVaultRaw(resolvedVault.path);
103469
+ let vault;
103470
+ if (raw !== null) {
102796
103471
  if (!refuseEnvPassphrase(ctx))
102797
103472
  return 1;
102798
- if (!requirePromptStreams(ctx, "portfolio"))
102799
- return 1;
102800
- const vault = hold((await unlockInteractively(ctx, resolvedVault.path, raw)).vault);
103473
+ try {
103474
+ const unlockCtx = refusePromptsWithoutTerminal(ctx, "portfolio");
103475
+ vault = hold((await unlockInteractively(unlockCtx, resolvedVault.path, raw)).vault);
103476
+ } catch (error) {
103477
+ if (!isNoTerminalRefusal(error))
103478
+ throw error;
103479
+ }
103480
+ }
103481
+ if (noVault) {
103482
+ vaultReason = "not read (--no-vault)";
103483
+ } else if (raw === null) {
103484
+ vaultReason = `no vault at ${resolvedVault.path}`;
103485
+ } else if (vault === undefined) {
103486
+ vaultReason = NO_TERMINAL_REASON;
103487
+ vaultSkipped = true;
103488
+ notice(deps, "stderr", "note", NO_TERMINAL_NOTE);
103489
+ } else {
102801
103490
  vaultEntries = showSolana ? vault.index.entries.filter((entry) => entry.chain === "solana" && (entry.role === "vault" || entry.role === "external")).map((entry) => ({ address: entry.address, label: entry.label, role: entry.role })) : [];
102802
103491
  evmEntries = showHood ? vault.index.entries.filter((entry) => entry.chain === "evm" && entry.role === "vault").map((entry) => ({ address: entry.address, label: entry.label })) : [];
102803
103492
  known += vaultEntries.length + evmEntries.length;
@@ -103012,7 +103701,7 @@ async function portfolio(args, ctx) {
103012
103701
  const listStatus = walletListStatus(answered);
103013
103702
  const shownUnread = unavailable6.length > 0 || lpUnread > 0;
103014
103703
  const listNote = listStatus === "cut-off" ? "cut-off" : listStatus === "unknown" && !shownUnread ? "unknown" : null;
103015
- const complete = listNote === null && !shownUnread && !perpsPartial;
103704
+ const complete = listNote === null && !shownUnread && !perpsPartial && !vaultSkipped;
103016
103705
  const totalUsd = groups.reduce((sum, g) => sum + g.valueUsd, 0);
103017
103706
  const unpriced = groups.reduce((sum, g) => sum + g.unpriced, 0);
103018
103707
  const lp = lpSection ? {
@@ -103089,6 +103778,7 @@ async function portfolio(args, ctx) {
103089
103778
  byChain,
103090
103779
  unavailableByChain,
103091
103780
  listNote,
103781
+ vaultSkipped,
103092
103782
  lp
103093
103783
  });
103094
103784
  if (perps)
@@ -103175,12 +103865,12 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
103175
103865
  try {
103176
103866
  chainId = await rpc.chainId();
103177
103867
  } catch (error) {
103178
- const failure3 = message(error);
103868
+ const failure4 = message(error);
103179
103869
  return {
103180
103870
  balances: async () => ({
103181
103871
  byAddress: new Map(unique.map((address) => [address, { wei: null, usdg: null }])),
103182
103872
  unavailable: unique,
103183
- failure: failure3
103873
+ failure: failure4
103184
103874
  })
103185
103875
  };
103186
103876
  }
@@ -103192,7 +103882,7 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
103192
103882
  return {
103193
103883
  balances: async () => {
103194
103884
  const byAddress = new Map;
103195
- let failure3;
103885
+ let failure4;
103196
103886
  let next = 0;
103197
103887
  await Promise.all(Array.from({ length: Math.min(EVM_READS_IN_FLIGHT, unique.length) }, async () => {
103198
103888
  while (next < unique.length) {
@@ -103201,7 +103891,7 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
103201
103891
  try {
103202
103892
  return await call2();
103203
103893
  } catch (error) {
103204
- failure3 ??= message(error);
103894
+ failure4 ??= message(error);
103205
103895
  return null;
103206
103896
  }
103207
103897
  };
@@ -103214,7 +103904,7 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
103214
103904
  const read3 = byAddress.get(address);
103215
103905
  return read3 === undefined || read3.wei === null || read3.usdg === null;
103216
103906
  });
103217
- return { byAddress, unavailable: unavailable6, ...failure3 === undefined ? {} : { failure: failure3 } };
103907
+ return { byAddress, unavailable: unavailable6, ...failure4 === undefined ? {} : { failure: failure4 } };
103218
103908
  }
103219
103909
  };
103220
103910
  }
@@ -103223,13 +103913,13 @@ async function readOwnRpc(addresses, solana, ctx) {
103223
103913
  const unique = [...new Set(addresses)];
103224
103914
  const lamports = new Map;
103225
103915
  let rateLimited = false;
103226
- let failure3;
103916
+ let failure4;
103227
103917
  const noteFailure = (error) => {
103228
103918
  if (isRateLimited(error)) {
103229
103919
  rateLimited = true;
103230
- failure3 ??= rateLimitedReadFailure(ctx, error);
103920
+ failure4 ??= rateLimitedReadFailure(ctx, error);
103231
103921
  } else
103232
- failure3 ??= describeRpcFailure(error);
103922
+ failure4 ??= describeRpcFailure(error);
103233
103923
  };
103234
103924
  for (let at3 = 0;at3 < unique.length; at3 += CHUNK) {
103235
103925
  const chunk = unique.slice(at3, at3 + CHUNK);
@@ -103291,7 +103981,7 @@ async function readOwnRpc(addresses, solana, ctx) {
103291
103981
  if (sol === null || held === null)
103292
103982
  unavailable6.push(address);
103293
103983
  }
103294
- return { byAddress, unavailable: unavailable6, ...failure3 === undefined ? {} : { failure: failure3 } };
103984
+ return { byAddress, unavailable: unavailable6, ...failure4 === undefined ? {} : { failure: failure4 } };
103295
103985
  }
103296
103986
  function solanaHoldings(read3, prices) {
103297
103987
  if (read3 === undefined || read3.lamports === null && read3.tokens === null)
@@ -103464,7 +104154,7 @@ ${tableBlock(ctx, "LP positions", ["GROUP", "WALLET", "POSITION", "POOL", "HOLDI
103464
104154
  notes.push([
103465
104155
  "total",
103466
104156
  formatUsd(totals.totalUsd),
103467
- totals.unpriced > 0 ? `${totals.unpriced} unpriced ${totals.unpriced === 1 ? "holding" : "holdings"} not counted` : "every holding priced"
104157
+ (totals.vaultSkipped ? "partial: the vault was not read (no terminal for its unlock). " : "") + (totals.unpriced > 0 ? `${totals.unpriced} unpriced ${totals.unpriced === 1 ? "holding" : "holdings"} not counted` : totals.vaultSkipped ? "other holdings priced" : "every holding priced")
103468
104158
  ]);
103469
104159
  const style = terminalStyle(deps, "stdout");
103470
104160
  const width = Math.max(...notes.map(([label]) => displayWidth(label)));
@@ -103486,6 +104176,8 @@ ${tableBlock(ctx, "LP positions", ["GROUP", "WALLET", "POSITION", "POOL", "HOLDI
103486
104176
  if (unavailable6 > 0) {
103487
104177
  warnings.push(`${unavailable6} ${unavailable6 === 1 ? "wallet" : "wallets"} could not be read in full (${unreadByChain.map(([chain2, n]) => `${n} on ${CHAIN_NAMES[chain2]}`).join(", ")}). What was not read is marked "not read" and is not in the total.`);
103488
104178
  }
104179
+ if (totals.vaultSkipped)
104180
+ warnings.push("The vault was not read: no terminal for its unlock. Its holdings are not in the total, so the total is partial (exit 3). Run it in a terminal, or pass --no-vault to leave the vault out on purpose.");
103489
104181
  writeNotes2(ctx, warnings);
103490
104182
  writeListNote(ctx, totals.listNote);
103491
104183
  const later = [];
@@ -103539,6 +104231,7 @@ ${financialHeading("Hyperliquid (live from Hyperliquid, not in the ledger; not i
103539
104231
  }
103540
104232
 
103541
104233
  // src/commands/profile.ts
104234
+ init_account();
103542
104235
  init_args();
103543
104236
  init_client();
103544
104237
  init_human_output();
@@ -103718,17 +104411,17 @@ async function profileUse(args, ctx) {
103718
104411
  let account = profile.account;
103719
104412
  let username = profile.username;
103720
104413
  if (apiKey) {
103721
- const { account: live, username: liveUsername, failure: failure3 } = await fetchAccount(deps, apiUrl, apiKey);
104414
+ const { account: live, username: liveUsername, failure: failure4 } = await fetchAccount(deps, apiUrl, apiKey);
103722
104415
  if (live) {
103723
104416
  account = live;
103724
104417
  username = liveUsername;
103725
104418
  await deps.updateProfile(name2, { account: live, username: liveUsername, accountCachedAt: deps.now() });
103726
104419
  } else {
103727
104420
  if (json)
103728
- notice(deps, "stderr", "warning", `Could not refresh the account for ${name2} (${failure3}); keeping the cached value.
104421
+ notice(deps, "stderr", "warning", `Could not refresh the account for ${name2} (${failure4}); keeping the cached value.
103729
104422
  `.trim());
103730
104423
  else
103731
- notice(deps, "stderr", "warning", `Could not refresh the account for ${name2} (${failure3}); keeping the cached value.`);
104424
+ notice(deps, "stderr", "warning", `Could not refresh the account for ${name2} (${failure4}); keeping the cached value.`);
103732
104425
  }
103733
104426
  } else {
103734
104427
  if (json)
@@ -104062,8 +104755,8 @@ async function quantAsk(args, ctx) {
104062
104755
  if (turnId === undefined) {
104063
104756
  if (reason === "interrupted")
104064
104757
  return await stopped(ctx, client2, undefined, reason, timeoutS);
104065
- const failure3 = refusal3?.status === 402 && offer ? offerFailure(offer.status, offer.headers, offer.body) : turnRefusal(refusal3 ?? { code: "BAD_RESPONSE", status: 0, shape: "transport" });
104066
- writeScreenLocalFailure(ctx.deps, failure3, ctx.json);
104758
+ const failure4 = refusal3?.status === 402 && offer ? offerFailure(offer.status, offer.headers, offer.body) : turnRefusal(refusal3 ?? { code: "BAD_RESPONSE", status: 0, shape: "transport" });
104759
+ writeScreenLocalFailure(ctx.deps, failure4, ctx.json);
104067
104760
  return 1;
104068
104761
  }
104069
104762
  if (stop.signal.aborted) {
@@ -104669,8 +105362,7 @@ async function sign5(args, ctx) {
104669
105362
  return usage(ctx, solana.error);
104670
105363
  if (!refuseEnvPassphrase(ctx))
104671
105364
  return 1;
104672
- if (!requireTty(ctx, "candle sign"))
104673
- return 1;
105365
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "candle sign");
104674
105366
  const { deps } = ctx;
104675
105367
  const resolvedVault = vaultPathFor(ctx, parsed);
104676
105368
  if ("error" in resolvedVault)
@@ -104700,7 +105392,7 @@ async function sign5(args, ctx) {
104700
105392
  throw error;
104701
105393
  }
104702
105394
  const raw = await requireVaultRaw(ctx, resolvedVault);
104703
- const opened = await unlockInteractively(ctx, path, raw, {
105395
+ const opened = await unlockInteractively(promptCtx, path, raw, {
104704
105396
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
104705
105397
  });
104706
105398
  const vault = hold(opened.vault);
@@ -104859,8 +105551,7 @@ async function signMessage2(args, ctx) {
104859
105551
  const requested = lifted.values[0];
104860
105552
  if (!refuseEnvPassphrase(ctx))
104861
105553
  return 1;
104862
- if (!requireTty(ctx, "candle sign message"))
104863
- return 1;
105554
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "candle sign message");
104864
105555
  const { deps } = ctx;
104865
105556
  const resolvedVault = vaultPathFor(ctx, parsed);
104866
105557
  if ("error" in resolvedVault)
@@ -104874,7 +105565,7 @@ async function signMessage2(args, ctx) {
104874
105565
  throw new VaultError("VAULT_UNREADABLE", `The message could not be read: ${error instanceof Error ? error.message : error}.`);
104875
105566
  }
104876
105567
  const raw = await requireVaultRaw(ctx, resolvedVault);
104877
- const opened = await unlockInteractively(ctx, path, raw, {
105568
+ const opened = await unlockInteractively(promptCtx, path, raw, {
104878
105569
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
104879
105570
  });
104880
105571
  const vault = hold(opened.vault);
@@ -105197,7 +105888,7 @@ init_presentation();
105197
105888
  init_render();
105198
105889
  init_trading();
105199
105890
  init_swap();
105200
- import { randomUUID as randomUUID14 } from "node:crypto";
105891
+ import { randomUUID as randomUUID15 } from "node:crypto";
105201
105892
  var withdrawal = exports_external.object({ withdrawal: exports_external.string(), amountRaw: exports_external.string(), endTime: exports_external.string(), claimable: exports_external.boolean() }).passthrough();
105202
105893
  var positionsSchema = exports_external.object({
105203
105894
  success: exports_external.literal(true),
@@ -105252,7 +105943,7 @@ async function runStake(action, args, ctx) {
105252
105943
  return 2;
105253
105944
  }
105254
105945
  const { values, positionals, booleans } = parsed;
105255
- const id = values["--client-stake-id"] ?? `stake-${randomUUID14()}`;
105946
+ const id = values["--client-stake-id"] ?? `stake-${randomUUID15()}`;
105256
105947
  const all = booleans.has("--all");
105257
105948
  if (!validClientId(id) || id.length > 128 || (action === "positions" ? positionals.length !== 0 : action === "finish" ? positionals.length > 1 : action === "claim" ? all ? positionals.length !== 0 : positionals.length !== 1 : positionals.length !== 1)) {
105258
105949
  writeUsageFailure(ctx.deps, usage12, ctx.json);
@@ -105642,8 +106333,7 @@ async function vaultRename(args, ctx) {
105642
106333
  const id = parsed.values["--id"];
105643
106334
  if (!refuseEnvPassphrase(ctx))
105644
106335
  return 1;
105645
- if (!requireTty(ctx, "vault rename"))
105646
- return 1;
106336
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "vault rename");
105647
106337
  const { deps } = ctx;
105648
106338
  const resolvedVault = vaultPathFor(ctx, parsed);
105649
106339
  if ("error" in resolvedVault)
@@ -105664,7 +106354,7 @@ async function vaultRename(args, ctx) {
105664
106354
  }
105665
106355
  return runVaultCommand(ctx, async ({ hold }) => {
105666
106356
  const raw = await requireVaultRaw(ctx, resolvedVault);
105667
- const opened = await unlockInteractively(ctx, path, raw, {
106357
+ const opened = await unlockInteractively(promptCtx, path, raw, {
105668
106358
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
105669
106359
  });
105670
106360
  const vault = hold(opened.vault);
@@ -105935,10 +106625,9 @@ async function teeRename(args, ctx) {
105935
106625
  if ("error" in vaultPath)
105936
106626
  return usage(ctx, vaultPath.error);
105937
106627
  const raw = rows.some((row2) => row2.tee) ? await readVaultRaw(vaultPath.path) : null;
105938
- if (raw !== null && !requireTty(ctx, "tee rename"))
105939
- return 1;
106628
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "tee rename");
105940
106629
  return runVaultCommand(ctx, async ({ hold }) => {
105941
- const vault = raw === null ? null : hold((await unlockInteractively(ctx, vaultPath.path, raw, {
106630
+ const vault = raw === null ? null : hold((await unlockInteractively(promptCtx, vaultPath.path, raw, {
105942
106631
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
105943
106632
  })).vault);
105944
106633
  const local = [];
@@ -106344,7 +107033,7 @@ init_args();
106344
107033
  init_client();
106345
107034
  init_deps();
106346
107035
  init_human_output();
106347
- import { randomUUID as randomUUID15 } from "node:crypto";
107036
+ import { randomUUID as randomUUID16 } from "node:crypto";
106348
107037
  init_render();
106349
107038
  var EXIT_OUTCOME_UNKNOWN2 = 3;
106350
107039
  var EDIT_FIELDS = launchFlagFields.filter((field) => field.key === "description" || field.key.startsWith("socials."));
@@ -106470,7 +107159,7 @@ async function tokenEdit(args, ctx) {
106470
107159
  if (given2 !== undefined && (given2.length < 1 || given2.length > 128)) {
106471
107160
  return usage12("--idempotency-key must be 1 to 128 characters.");
106472
107161
  }
106473
- const idempotencyKey = given2 ?? randomUUID15();
107162
+ const idempotencyKey = given2 ?? randomUUID16();
106474
107163
  const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
106475
107164
  if (!apiKey) {
106476
107165
  writeLocalFailure(ctx.deps, {
@@ -106495,17 +107184,17 @@ async function tokenEdit(args, ctx) {
106495
107184
  return EXIT_OUTCOME_UNKNOWN2;
106496
107185
  }
106497
107186
  if (!result.ok) {
106498
- const failure3 = { apiUrl: ctx.apiUrl, authType: "key" };
107187
+ const failure4 = { apiUrl: ctx.apiUrl, authType: "key" };
106499
107188
  if (!unsettledCommit && !mayHaveCommitted(result)) {
106500
- writeFailure(ctx.deps, result, failure3, ctx.json);
107189
+ writeFailure(ctx.deps, result, failure4, ctx.json);
106501
107190
  return 1;
106502
107191
  }
106503
107192
  const retry = `Rerun the same command with --idempotency-key ${idempotencyKey}: Candle returns the first result instead of editing twice.`;
106504
107193
  if (ctx.json) {
106505
- ctx.deps.stdout.write(`${JSON.stringify({ ...errorEnvelope(result, failure3), idempotencyKey, details: { idempotencyKey }, retryCommand: retry })}
107194
+ ctx.deps.stdout.write(`${JSON.stringify({ ...errorEnvelope(result, failure4), idempotencyKey, details: { idempotencyKey }, retryCommand: retry })}
106506
107195
  `);
106507
107196
  } else {
106508
- writeFailure(ctx.deps, result, failure3, false);
107197
+ writeFailure(ctx.deps, result, failure4, false);
106509
107198
  ctx.deps.stderr.write(`Idempotency key: ${idempotencyKey}
106510
107199
  ${retry}
106511
107200
  `);
@@ -106605,7 +107294,9 @@ init_render();
106605
107294
  init_solana_verify();
106606
107295
  init_trading();
106607
107296
  init_swap();
106608
- var USAGE18 = "Usage: candle transfer --to <address|wallet name|allowlist label|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <name>] [--rpc-url <url>] [--yes] [--json]";
107297
+ var USAGE18 = `Usage: candle transfer --to <address|wallet name|allowlist label|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <name>] [--client-id <id>] [--rpc-url <url>] [--yes] [--json]
107298
+ candle transfer status <transfer id|client id> [--json]`;
107299
+ var CLIENT_ID = /^[A-Za-z0-9._:-]{1,128}$/;
106609
107300
  var TRANSFER_ASSETS = [...Object.keys(BASES), ...Object.keys(HOOD_BASES)];
106610
107301
  var BASE58_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
106611
107302
  async function readLinkedWallets(ctx, key) {
@@ -106738,9 +107429,26 @@ async function confirmTransfer(ctx, lines, yes) {
106738
107429
  throw new TradingError("CONFIRMATION_REQUIRED", "Run interactively to confirm, or use --yes for an ordinary transfer prompt.");
106739
107430
  return (await ctx.deps.promptLine("Proceed? [y/N] ")).trim().toLowerCase() === "y";
106740
107431
  }
107432
+ async function transferStatus(args, ctx) {
107433
+ const parsed = parseArgs(args, { valueFlags: [], booleanFlags: [] });
107434
+ if ("error" in parsed || parsed.positionals.length !== 1 || !CLIENT_ID.test(parsed.positionals[0] ?? "")) {
107435
+ writeUsageFailure(ctx.deps, "Usage: candle transfer status <transfer id|client id> [--json]", ctx.json);
107436
+ return 2;
107437
+ }
107438
+ try {
107439
+ const key = await tradingKey(ctx);
107440
+ const id = parsed.positionals[0];
107441
+ const found = await request(ctx, key, `/api/v1/agent/transfer/${encodeURIComponent(id)}`);
107442
+ return printTradingResult(ctx, found);
107443
+ } catch (error) {
107444
+ return tradingFailure(ctx, error);
107445
+ }
107446
+ }
106741
107447
  async function transfer(args, ctx) {
107448
+ if (args[0] === "status")
107449
+ return await transferStatus(args.slice(1), ctx);
106742
107450
  const parsed = parseArgs(args, {
106743
- valueFlags: ["--wallet", "--to", "--asset", "--mint", "--token", "--amount", "--rpc-url"],
107451
+ valueFlags: ["--wallet", "--to", "--asset", "--mint", "--token", "--amount", "--rpc-url", "--client-id"],
106744
107452
  booleanFlags: ["--yes"]
106745
107453
  });
106746
107454
  if ("error" in parsed) {
@@ -106749,7 +107457,7 @@ async function transfer(args, ctx) {
106749
107457
  }
106750
107458
  const flags2 = parsed.values;
106751
107459
  const asset = flags2["--asset"]?.toUpperCase();
106752
- if (parsed.positionals.length !== 0 || !flags2["--to"] || !flags2["--amount"] || [flags2["--asset"], flags2["--mint"], flags2["--token"]].filter(Boolean).length !== 1 || asset !== undefined && !TRANSFER_ASSETS.includes(asset) || flags2["--mint"] !== undefined && !BASE58_ADDRESS.test(flags2["--mint"]) || flags2["--token"] !== undefined && !checkEvmAddress(flags2["--token"]).ok) {
107460
+ if (parsed.positionals.length !== 0 || !flags2["--to"] || !flags2["--amount"] || [flags2["--asset"], flags2["--mint"], flags2["--token"]].filter(Boolean).length !== 1 || asset !== undefined && !TRANSFER_ASSETS.includes(asset) || flags2["--mint"] !== undefined && !BASE58_ADDRESS.test(flags2["--mint"]) || flags2["--token"] !== undefined && !checkEvmAddress(flags2["--token"]).ok || flags2["--client-id"] !== undefined && !CLIENT_ID.test(flags2["--client-id"])) {
106753
107461
  writeUsageFailure(ctx.deps, USAGE18, ctx.json);
106754
107462
  return 2;
106755
107463
  }
@@ -106796,7 +107504,8 @@ async function transfer(args, ctx) {
106796
107504
  chain: "solana",
106797
107505
  ...asset ? { asset } : { mint: flags2["--mint"] },
106798
107506
  amountRaw,
106799
- to: destination.address
107507
+ to: destination.address,
107508
+ ...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {}
106800
107509
  });
106801
107510
  const transferId = built.transferId;
106802
107511
  const unsigned = built.unsignedTransactionsBase64;
@@ -106832,6 +107541,7 @@ async function transfer(args, ctx) {
106832
107541
  const receipt = {
106833
107542
  ...result,
106834
107543
  transferId,
107544
+ ...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {},
106835
107545
  walletId: wallet3.id,
106836
107546
  wallet: safeText(wallet3.address),
106837
107547
  destination,
@@ -106888,7 +107598,8 @@ async function hoodTransfer(ctx, args) {
106888
107598
  chain: "hood",
106889
107599
  ...asset ? { asset } : { mint: tokenAddress },
106890
107600
  amountRaw: requested,
106891
- to: destination.address
107601
+ to: destination.address,
107602
+ ...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {}
106892
107603
  });
106893
107604
  const parsed = sequencedSchema.safeParse(built);
106894
107605
  if (!parsed.success)
@@ -106925,6 +107636,7 @@ async function hoodTransfer(ctx, args) {
106925
107636
  const receipt = {
106926
107637
  ...run2.final,
106927
107638
  transferId,
107639
+ ...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {},
106928
107640
  chain: "hood",
106929
107641
  walletId: wallet3.id,
106930
107642
  wallet: safeText(wallet3.address),
@@ -107597,10 +108309,10 @@ candle update`);
107597
108309
  } catch (error) {
107598
108310
  steps.fail("installing");
107599
108311
  await discard(deps, helper.tmpPath);
107600
- const failure3 = notWritable(dir, error);
108312
+ const failure4 = notWritable(dir, error);
107601
108313
  writeScreenLocalFailure(deps, {
107602
- ...failure3,
107603
- message: `${failure3.message} candle ${target.version} was installed to ${realExec}, but its security key helper could not be renamed to ${helper.target}.`
108314
+ ...failure4,
108315
+ message: `${failure4.message} candle ${target.version} was installed to ${realExec}, but its security key helper could not be renamed to ${helper.target}.`
107604
108316
  }, json);
107605
108317
  return 1;
107606
108318
  }
@@ -108531,8 +109243,8 @@ async function vaultDemoteBatch(args, ctx) {
108531
109243
  row2.label,
108532
109244
  row2.destination ?? `none (${row2.destinationNote})`
108533
109245
  ]), []);
108534
- for (const skipped of outcomes) {
108535
- ctx.deps[display].write(`Not demoted: ${skipped.address}: ${skipped.reason}
109246
+ for (const skipped2 of outcomes) {
109247
+ ctx.deps[display].write(`Not demoted: ${skipped2.address}: ${skipped2.reason}
108536
109248
  `);
108537
109249
  }
108538
109250
  if (!yes) {
@@ -108640,12 +109352,12 @@ async function demoteOne(ctx, session, row2, values, booleans) {
108640
109352
  }
108641
109353
  }
108642
109354
  outcome.results = results;
108643
- const failure3 = [...results].reverse().find((doc) => typeof doc === "object" && doc !== null && doc.ok === false);
108644
- if (failure3 !== undefined && exitCode !== 0) {
108645
- if (typeof failure3.code === "string")
108646
- outcome.code = failure3.code;
108647
- if (typeof failure3.message === "string")
108648
- outcome.reason = failure3.message;
109355
+ const failure4 = [...results].reverse().find((doc) => typeof doc === "object" && doc !== null && doc.ok === false);
109356
+ if (failure4 !== undefined && exitCode !== 0) {
109357
+ if (typeof failure4.code === "string")
109358
+ outcome.code = failure4.code;
109359
+ if (typeof failure4.message === "string")
109360
+ outcome.reason = failure4.message;
108649
109361
  }
108650
109362
  }
108651
109363
  if (thrown !== undefined)
@@ -110303,6 +111015,7 @@ init_solana_endpoint();
110303
111015
  init_errors2();
110304
111016
  init_evm_tee();
110305
111017
  init_evm_transfer();
111018
+ init_format();
110306
111019
 
110307
111020
  // src/vault/funding-receipts.ts
110308
111021
  init_errors2();
@@ -110487,6 +111200,8 @@ async function vaultFund(args, ctx) {
110487
111200
  if (parsed.values["--from"] !== undefined) {
110488
111201
  return usage(ctx, "--from applies to an external wallet only; a TEE wallet is funded from its pinned vault key.");
110489
111202
  }
111203
+ if (isRetiredTee(teeEntry))
111204
+ return refuseRetiredFund(ctx, teeAddress);
110490
111205
  const reconciled = await reconcileFundingReceipts(vault, [teeAddress, ...teeEntry.tee?.vaultDestination ? [teeEntry.tee.vaultDestination] : []], solana.rpc, ctx);
110491
111206
  if (reconciled !== null)
110492
111207
  return reconciled;
@@ -110608,6 +111323,8 @@ async function vaultFundEvm(ctx, parsed, teeAddress) {
110608
111323
  }, ctx.json);
110609
111324
  return 1;
110610
111325
  }
111326
+ if (isRetiredTee(teeEntry))
111327
+ return refuseRetiredFund(ctx, teeEntry.address);
110611
111328
  if (teeEntry.tee?.remoteAuthority !== "verified-active" || teeEntry.tee.stopRequestedAt !== undefined) {
110612
111329
  writeLocalFailure(ctx.deps, {
110613
111330
  code: "TEE_WALLET_NOT_VERIFIED",
@@ -110642,6 +111359,14 @@ async function vaultFundEvm(ctx, parsed, teeAddress) {
110642
111359
  }, client2.rpc);
110643
111360
  });
110644
111361
  }
111362
+ function refuseRetiredFund(ctx, address) {
111363
+ writeLocalFailure(ctx.deps, {
111364
+ code: "TEE_WALLET_STOPPED",
111365
+ message: `${address} is a retired TEE wallet; a retired address is never refunded.`,
111366
+ suggestion: "Nothing was signed. Create a new wallet to fund: candle tee new"
111367
+ }, ctx.json);
111368
+ return 1;
111369
+ }
110645
111370
 
110646
111371
  // src/commands/vault-import-legacy.ts
110647
111372
  init_args();
@@ -110853,14 +111578,14 @@ async function vaultImportLegacy(args, ctx) {
110853
111578
  const digest = sourceDigest(vault.file.vaultId, new TextEncoder().encode(legacyRaw));
110854
111579
  const already = new Set(vault.index.entries.map((entry) => entry.address));
110855
111580
  const toMigrate = legacyEntries.filter((entry) => !already.has(entry.address));
110856
- const skipped = legacyEntries.length - toMigrate.length;
111581
+ const skipped2 = legacyEntries.length - toMigrate.length;
110857
111582
  if (toMigrate.length === 0) {
110858
111583
  await recordMigrationSidecar(vaultPath, vault, fromPath, digest, deps.now());
110859
111584
  return reportDone(ctx, {
110860
111585
  path: fromPath,
110861
111586
  vaultPath,
110862
111587
  migrated: 0,
110863
- skipped,
111588
+ skipped: skipped2,
110864
111589
  addresses: [],
110865
111590
  digest,
110866
111591
  idempotent: true
@@ -110900,7 +111625,7 @@ async function vaultImportLegacy(args, ctx) {
110900
111625
  path: fromPath,
110901
111626
  vaultPath,
110902
111627
  migrated: newEntries.length,
110903
- skipped,
111628
+ skipped: skipped2,
110904
111629
  addresses: newEntries.map((entry) => ({
110905
111630
  address: entry.address,
110906
111631
  lifecycle: entry.tee?.lifecycle ?? "local-candidate",
@@ -111032,7 +111757,7 @@ async function readLamports(addresses, solana, ctx) {
111032
111757
  const { rpc } = solana;
111033
111758
  const lamports = new Map;
111034
111759
  const unavailable6 = [];
111035
- let failure3;
111760
+ let failure4;
111036
111761
  let rateLimited = false;
111037
111762
  for (let at3 = 0;at3 < addresses.length; at3 += CHUNK2) {
111038
111763
  const chunk = addresses.slice(at3, at3 + CHUNK2);
@@ -111048,12 +111773,12 @@ async function readLamports(addresses, solana, ctx) {
111048
111773
  unavailable6.push(...chunk);
111049
111774
  if (isRateLimited(error)) {
111050
111775
  rateLimited = true;
111051
- failure3 ??= rateLimitedReadFailure(ctx, error);
111776
+ failure4 ??= rateLimitedReadFailure(ctx, error);
111052
111777
  } else
111053
- failure3 ??= describeRpcFailure(error);
111778
+ failure4 ??= describeRpcFailure(error);
111054
111779
  }
111055
111780
  }
111056
- return { lamports, unavailable: unavailable6, ...failure3 === undefined ? {} : { failure: failure3 } };
111781
+ return { lamports, unavailable: unavailable6, ...failure4 === undefined ? {} : { failure: failure4 } };
111057
111782
  }
111058
111783
  async function readEvmBalances(addresses, rpcUrl, fetchFn) {
111059
111784
  const rpc = createEvmRpc(rpcUrl, fetchFn);
@@ -111130,13 +111855,12 @@ async function vaultList(args, ctx) {
111130
111855
  if (balances && "error" in evmRpc)
111131
111856
  return usage(ctx, evmRpc.error);
111132
111857
  const evmRpcUrl = "url" in evmRpc ? evmRpc.url : DEFAULT_HOOD_RPC_URL;
111133
- if (!requirePromptStreams(ctx, "vault list"))
111134
- return 1;
111858
+ const promptCtx = refusePromptsWithoutTerminal(ctx, "vault list");
111135
111859
  return runVaultCommand(ctx, async ({ hold }) => {
111136
111860
  const raw = await readVaultRaw(path);
111137
111861
  if (raw === null)
111138
111862
  throw missingVault(ctx, resolvedVault);
111139
- const vault = hold((await unlockInteractively(ctx, path, raw, { acceptOlderCopy: parsed.booleans.has("--accept-older-copy") })).vault);
111863
+ const vault = hold((await unlockInteractively(promptCtx, path, raw, { acceptOlderCopy: parsed.booleans.has("--accept-older-copy") })).vault);
111140
111864
  const all = vault.index.entries;
111141
111865
  const filtered = filter === undefined ? all : all.filter((entry) => matches2(entry, filter));
111142
111866
  const showRetired = parsed.booleans.has("--all");
@@ -111153,17 +111877,17 @@ async function vaultList(args, ctx) {
111153
111877
  }
111154
111878
  let lamports = new Map;
111155
111879
  let unavailable6 = [];
111156
- let failure3;
111880
+ let failure4;
111157
111881
  if (balances && solana.length > 0 && solanaClient !== undefined) {
111158
111882
  const outcome = await readLamports(solana.map((entry) => entry.address), solanaClient, ctx);
111159
111883
  lamports = outcome.lamports;
111160
111884
  unavailable6 = outcome.unavailable;
111161
- failure3 = outcome.failure;
111885
+ failure4 = outcome.failure;
111162
111886
  }
111163
111887
  const solanaComplete = unavailable6.length === 0;
111164
111888
  const totalLamports = [...lamports.values()].reduce((sum, value) => sum + value, 0n);
111165
111889
  if (!solanaComplete) {
111166
- notice(deps, "stderr", "warning", `${unavailable6.length} ${unavailable6.length === 1 ? "address" : "addresses"} could not be read: ${failure3 ?? "the RPC did not answer"}. Narrow with a filter, or use your own endpoint with --rpc-url.
111890
+ notice(deps, "stderr", "warning", `${unavailable6.length} ${unavailable6.length === 1 ? "address" : "addresses"} could not be read: ${failure4 ?? "the RPC did not answer"}. Narrow with a filter, or use your own endpoint with --rpc-url.
111167
111891
  `.trim());
111168
111892
  }
111169
111893
  const evm = balances ? matched.filter((entry) => entry.chain === "evm") : [];
@@ -111395,8 +112119,8 @@ function unreadableRoomLine(reason) {
111395
112119
  `;
111396
112120
  }
111397
112121
  var RESTORED_SENTENCE = "The vault entry is back as it was: nothing left this machine.";
111398
- function restoresPreImportEntry(failure3) {
111399
- return failure3.stage === "init" && failure3.status !== undefined && failure3.status !== 0;
112122
+ function restoresPreImportEntry(failure4) {
112123
+ return failure4.stage === "init" && failure4.status !== undefined && failure4.status !== 0;
111400
112124
  }
111401
112125
  function restoreRefusedFailure(init, error) {
111402
112126
  if (error instanceof VaultError && error.code === "VAULT_CHANGED") {
@@ -111674,7 +112398,7 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
111674
112398
  });
111675
112399
  const code = imported.exit;
111676
112400
  if (imported.failure !== undefined) {
111677
- let failure3 = {
112401
+ let failure4 = {
111678
112402
  code: imported.failure.code,
111679
112403
  message: imported.failure.message,
111680
112404
  ...imported.failure.suggestion !== undefined ? { suggestion: imported.failure.suggestion } : {}
@@ -111682,12 +112406,12 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
111682
112406
  if (restoresPreImportEntry(imported.failure)) {
111683
112407
  try {
111684
112408
  vault = hold(await commitVault(vault, { index: preImportIndex }, ctx.deps));
111685
- failure3 = { ...failure3, message: `${failure3.message} ${RESTORED_SENTENCE}` };
112409
+ failure4 = { ...failure4, message: `${failure4.message} ${RESTORED_SENTENCE}` };
111686
112410
  } catch (error) {
111687
- failure3 = restoreRefusedFailure(failure3, error);
112411
+ failure4 = restoreRefusedFailure(failure4, error);
111688
112412
  }
111689
112413
  }
111690
- writeLocalFailure(ctx.deps, failure3, ctx.json);
112414
+ writeLocalFailure(ctx.deps, failure4, ctx.json);
111691
112415
  return code;
111692
112416
  }
111693
112417
  let rebound;
@@ -112133,7 +112857,7 @@ async function promoteInPlace(ctx, parsed, subjectLabel, toKey) {
112133
112857
  });
112134
112858
  const code = imported.exit;
112135
112859
  if (imported.failure !== undefined) {
112136
- let failure3 = {
112860
+ let failure4 = {
112137
112861
  code: imported.failure.code,
112138
112862
  message: imported.failure.message,
112139
112863
  ...imported.failure.suggestion !== undefined ? { suggestion: imported.failure.suggestion } : {}
@@ -112141,12 +112865,12 @@ async function promoteInPlace(ctx, parsed, subjectLabel, toKey) {
112141
112865
  if (restoresPreImportEntry(imported.failure)) {
112142
112866
  try {
112143
112867
  vault = hold(await commitVault(vault, { index: preImportIndex }, ctx.deps));
112144
- failure3 = { ...failure3, message: `${failure3.message} ${RESTORED_SENTENCE}` };
112868
+ failure4 = { ...failure4, message: `${failure4.message} ${RESTORED_SENTENCE}` };
112145
112869
  } catch (error) {
112146
- failure3 = restoreRefusedFailure(failure3, error);
112870
+ failure4 = restoreRefusedFailure(failure4, error);
112147
112871
  }
112148
112872
  }
112149
- writeLocalFailure(ctx.deps, failure3, ctx.json);
112873
+ writeLocalFailure(ctx.deps, failure4, ctx.json);
112150
112874
  return code;
112151
112875
  }
112152
112876
  let rebound;
@@ -112188,10 +112912,10 @@ async function promoteInPlace(ctx, parsed, subjectLabel, toKey) {
112188
112912
  async function resumePromote(ctx, vault, entry, reopen, path, hold, confirmations) {
112189
112913
  const announceGrant = confirmations.announceGrant ?? true;
112190
112914
  const report2 = confirmations.report ?? "write";
112191
- const fail4 = (failure3, exit) => {
112915
+ const fail4 = (failure4, exit) => {
112192
112916
  if (report2 === "write")
112193
- writeLocalFailure(ctx.deps, failure3, ctx.json);
112194
- return { exit, ...report2 === "return" ? { failure: failure3 } : {} };
112917
+ writeLocalFailure(ctx.deps, failure4, ctx.json);
112918
+ return { exit, ...report2 === "return" ? { failure: failure4 } : {} };
112195
112919
  };
112196
112920
  const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
112197
112921
  if (!apiKey) {
@@ -112337,10 +113061,10 @@ async function runTeeImport(ctx, opts) {
112337
113061
  const closeReopened = opts.closeReopened ?? true;
112338
113062
  const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
112339
113063
  if (!apiKey) {
112340
- const failure3 = { code: "NO_API_KEY", message: "No API key available.", suggestion: "Run: candle keys create" };
113064
+ const failure4 = { code: "NO_API_KEY", message: "No API key available.", suggestion: "Run: candle keys create" };
112341
113065
  if (report2 === "write")
112342
- writeLocalFailure(ctx.deps, failure3, ctx.json);
112343
- return { exit: 1, ...report2 === "return" ? { failure: failure3 } : {} };
113066
+ writeLocalFailure(ctx.deps, failure4, ctx.json);
113067
+ return { exit: 1, ...report2 === "return" ? { failure: failure4 } : {} };
112344
113068
  }
112345
113069
  opts.onImport?.();
112346
113070
  const flow = await runImportFlow({
@@ -112356,28 +113080,28 @@ async function runTeeImport(ctx, opts) {
112356
113080
  ...opts.keySigner !== undefined ? { keySigner: opts.keySigner } : {}
112357
113081
  });
112358
113082
  if (!flow.ok) {
112359
- const failure3 = flow.failure;
112360
- if (failure3.kind === "api") {
113083
+ const failure4 = flow.failure;
113084
+ if (failure4.kind === "api") {
112361
113085
  const render2 = { apiUrl: ctx.apiUrl, authType: "key" };
112362
113086
  if (report2 === "write") {
112363
- writeFailure(ctx.deps, failure3.response, render2, ctx.json);
113087
+ writeFailure(ctx.deps, failure4.response, render2, ctx.json);
112364
113088
  return { exit: 1 };
112365
113089
  }
112366
- const suggestion = suggestionFor(failure3.response, render2);
113090
+ const suggestion = suggestionFor(failure4.response, render2);
112367
113091
  return {
112368
113092
  exit: 1,
112369
113093
  failure: {
112370
- code: errorEnvelope(failure3.response, render2).code,
112371
- message: renderError(failure3.response, render2),
113094
+ code: errorEnvelope(failure4.response, render2).code,
113095
+ message: renderError(failure4.response, render2),
112372
113096
  ...suggestion ? { suggestion } : {},
112373
- stage: failure3.stage,
112374
- status: failure3.response.status
113097
+ stage: failure4.stage,
113098
+ status: failure4.response.status
112375
113099
  }
112376
113100
  };
112377
113101
  }
112378
113102
  const local = {
112379
- code: failure3.kind === "signer-store" ? "SIGNER_STORE_FAILED" : "SIGNER_COMMIT_FAILED",
112380
- message: `${opts.address}: ${failure3.error instanceof Error ? failure3.error.message : String(failure3.error)}`,
113103
+ code: failure4.kind === "signer-store" ? "SIGNER_STORE_FAILED" : "SIGNER_COMMIT_FAILED",
113104
+ message: `${opts.address}: ${failure4.error instanceof Error ? failure4.error.message : String(failure4.error)}`,
112381
113105
  suggestion: "The vault entry is import-pending. Fix the error and re-run promote to resume."
112382
113106
  };
112383
113107
  if (report2 === "write")
@@ -112641,15 +113365,15 @@ async function preflightBatch(index, rows, opts) {
112641
113365
  const failures = [];
112642
113366
  const pinnedBy = new Map;
112643
113367
  const promotedBy = new Map;
112644
- const refuse3 = (row2, failure3) => {
113368
+ const refuse3 = (row2, failure4) => {
112645
113369
  failures.push({
112646
113370
  line: row2.line,
112647
113371
  label: row2.label,
112648
113372
  destination: row2.destination,
112649
- code: failure3.code,
112650
- message: failure3.message,
112651
- ...failure3.suggestion !== undefined ? { suggestion: failure3.suggestion } : {},
112652
- ...failure3.why !== undefined ? { why: failure3.why } : {}
113373
+ code: failure4.code,
113374
+ message: failure4.message,
113375
+ ...failure4.suggestion !== undefined ? { suggestion: failure4.suggestion } : {},
113376
+ ...failure4.why !== undefined ? { why: failure4.why } : {}
112653
113377
  });
112654
113378
  };
112655
113379
  const refuseWith = (row2, error, why) => {
@@ -113092,8 +113816,8 @@ ${table}
113092
113816
  rateLimited: 0,
113093
113817
  elapsedMs: 0
113094
113818
  };
113095
- const skipped = planned.map((item) => skippedResult(item));
113096
- const rebindable = rebindPhase === undefined ? undefined : splitRebindable(skipped);
113819
+ const skipped2 = planned.map((item) => skippedResult(item));
113820
+ const rebindable = rebindPhase === undefined ? undefined : splitRebindable(skipped2);
113097
113821
  if (rebindPhase === undefined || rebindable === undefined || rebindable.wallets.length === 0) {
113098
113822
  deps.stderr.write(`
113099
113823
  ${table}
@@ -113103,7 +113827,7 @@ ${table}
113103
113827
  return finish(ctx, {
113104
113828
  file: pairsFile,
113105
113829
  rows: rows.length,
113106
- keys: skipped,
113830
+ keys: skipped2,
113107
113831
  destinations: destinations2,
113108
113832
  exit: 0,
113109
113833
  controlledBy,
@@ -113124,11 +113848,11 @@ ${renderControlledBy(controlledBy, n)}
113124
113848
  if (typed.trim().toLowerCase() !== CONFIRM_WORD) {
113125
113849
  throw new VaultError("PROMOTE_NOT_ACKNOWLEDGED", `The acknowledgement is the word ${CONFIRM_WORD}; nothing was moved, and nothing was written.`, { suggestion: `Run the command again and type ${CONFIRM_WORD} at the prompt.` });
113126
113850
  }
113127
- const rebound2 = await runRebindPhase(ctx, rebindPhase, skipped);
113851
+ const rebound2 = await runRebindPhase(ctx, rebindPhase, skipped2);
113128
113852
  return finish(ctx, {
113129
113853
  file: pairsFile,
113130
113854
  rows: rows.length,
113131
- keys: skipped,
113855
+ keys: skipped2,
113132
113856
  destinations: destinations2,
113133
113857
  exit: 0,
113134
113858
  controlledBy,
@@ -113201,24 +113925,24 @@ ${renderControlledBy(controlledBy, acting.length)}
113201
113925
  keySigner: keySignerImport(rebindPhase?.toKey)
113202
113926
  });
113203
113927
  if (imported.failure !== undefined || imported.submitted === undefined) {
113204
- let failure3 = imported.failure ?? {
113928
+ let failure4 = imported.failure ?? {
113205
113929
  code: "VAULT_WRITE_FAILED",
113206
113930
  message: "The import did not complete."
113207
113931
  };
113208
- if (restoresPreImportEntry(failure3)) {
113932
+ if (restoresPreImportEntry(failure4)) {
113209
113933
  try {
113210
113934
  current = hold(await commitVault(current, { index: preImportIndex }, deps));
113211
- failure3 = { ...failure3, message: `${failure3.message} ${RESTORED_SENTENCE}` };
113935
+ failure4 = { ...failure4, message: `${failure4.message} ${RESTORED_SENTENCE}` };
113212
113936
  } catch (error) {
113213
- failure3 = restoreRefusedFailure(failure3, error);
113937
+ failure4 = restoreRefusedFailure(failure4, error);
113214
113938
  }
113215
113939
  }
113216
113940
  stopped2 = {
113217
113941
  line: row3.line,
113218
113942
  exit: imported.exit,
113219
- code: failure3.code,
113220
- message: failure3.message,
113221
- ...failure3.suggestion !== undefined ? { suggestion: failure3.suggestion } : {}
113943
+ code: failure4.code,
113944
+ message: failure4.message,
113945
+ ...failure4.suggestion !== undefined ? { suggestion: failure4.suggestion } : {}
113222
113946
  };
113223
113947
  break;
113224
113948
  }
@@ -113387,7 +114111,7 @@ async function verifySubjectSecret(vault, subject) {
113387
114111
  }
113388
114112
  async function reconcileForPreflight(ctx, row2, subject, resolved) {
113389
114113
  const { onDisk } = resolved;
113390
- const refuse3 = (failure3) => ({ ok: false, failure: failure3 });
114114
+ const refuse3 = (failure4) => ({ ok: false, failure: failure4 });
113391
114115
  const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
113392
114116
  if (!apiKey) {
113393
114117
  return refuse3({
@@ -113513,11 +114237,11 @@ function renderBatchTable(planned, opts) {
113513
114237
  return renderHumanTable(headers2, rows, opts.style ?? terminalStyle({ env: {}, isTTY: { stdin: false, stdout: false, stderr: false } }, "stderr"), headers2.map((_, i) => i === 0 || i === 1 || i === 3 || i === 4 || i === 5 ? { id: true } : i === 2 ? { priority: 2 } : { align: "right", priority: 1 }));
113514
114238
  }
113515
114239
  function renderFooter(opts) {
113516
- const skipped = opts.planned.filter((item) => item.kind === "skip").length;
114240
+ const skipped2 = opts.planned.filter((item) => item.kind === "skip").length;
113517
114241
  const resumes = opts.planned.filter((item) => item.kind === "resume").length;
113518
114242
  const promotes = opts.planned.filter((item) => item.kind === "promote").length;
113519
114243
  const lines = [
113520
- `${opts.planned.length} rows from ${opts.file}: ${skipped} already promoted (skipped), ${resumes} to resume, ${promotes} to promote.`
114244
+ `${opts.planned.length} rows from ${opts.file}: ${skipped2} already promoted (skipped), ${resumes} to resume, ${promotes} to promote.`
113521
114245
  ];
113522
114246
  if (opts.destinations.length > 0) {
113523
114247
  lines.push(`${opts.destinations.length} destination${opts.destinations.length === 1 ? "" : "s"}, in this order: ${opts.destinations.map((d) => `${destinationCell(d.label, d.address)} ${d.keys} key${d.keys === 1 ? "" : "s"}`).join(" · ")}`);
@@ -113567,7 +114291,7 @@ ${opts.landed} of ${opts.acting} rows landed and ARE in the vault; the vault is
113567
114291
  function finish(ctx, opts) {
113568
114292
  const promoted = opts.keys.filter((r) => r.state === "promoted").length;
113569
114293
  const resumed = opts.keys.filter((r) => r.state === "resumed").length;
113570
- const skipped = opts.keys.filter((r) => r.state === "skipped").length;
114294
+ const skipped2 = opts.keys.filter((r) => r.state === "skipped").length;
113571
114295
  const unverified = opts.keys.filter((r) => r.state !== "skipped" && r.remoteAuthority !== "verified-active");
113572
114296
  const rebindFailed = opts.rebound?.run !== undefined && !opts.rebound.run.ok;
113573
114297
  const exit = rebindFailed ? Math.max(opts.exit, 1) : opts.exit;
@@ -113580,7 +114304,7 @@ function finish(ctx, opts) {
113580
114304
  rows: opts.rows,
113581
114305
  promoted,
113582
114306
  resumed,
113583
- skipped,
114307
+ skipped: skipped2,
113584
114308
  destinations: opts.destinations.map(({ label, address, keys }) => ({ label, address, keys })),
113585
114309
  keys: opts.rebound === undefined ? opts.keys : keysWithRebind(opts.keys, opts.rebound.phase, opts.rebound.run),
113586
114310
  controlledBy: controlledByJson(opts.controlledBy),
@@ -113592,7 +114316,7 @@ function finish(ctx, opts) {
113592
114316
  writeFields(ctx.deps, "stdout", [
113593
114317
  ["Promoted", promoted],
113594
114318
  ["Resumed", resumed],
113595
- ["Already promoted (skipped)", skipped],
114319
+ ["Already promoted (skipped)", skipped2],
113596
114320
  ["Commit", "one unlock, each committed on its own"]
113597
114321
  ]);
113598
114322
  if (unverified.length > 0) {
@@ -114672,10 +115396,12 @@ function describeEnvelope(envelope, facts) {
114672
115396
  init_args();
114673
115397
  init_deps();
114674
115398
  init_evm_lite();
115399
+ init_render();
114675
115400
  init_solana_endpoint();
114676
115401
  init_errors2();
114677
115402
  init_evm_tee();
114678
115403
  init_evm_transfer();
115404
+ init_format();
114679
115405
  init_promote_support();
114680
115406
  init_store();
114681
115407
  init_vault_support();
@@ -114731,6 +115457,15 @@ async function vaultTransfer(args, ctx) {
114731
115457
  return usage(ctx, `No vault key or promoted wallet matches --from ${fromLabel}.`);
114732
115458
  }
114733
115459
  assertTransferSigner(fromEntry);
115460
+ const retiredDestination = vault.index.entries.find((entry) => isRetiredTee(entry) && entry.address.toLowerCase() === to.toLowerCase());
115461
+ if (retiredDestination !== undefined) {
115462
+ writeLocalFailure(ctx.deps, {
115463
+ code: "TEE_WALLET_STOPPED",
115464
+ message: `${to} is a retired TEE wallet (${retiredDestination.label}); a retired address is never refunded.`,
115465
+ suggestion: "Nothing was signed. Send to a live wallet, or create one: candle tee new"
115466
+ }, ctx.json);
115467
+ return 1;
115468
+ }
114734
115469
  if (fromEntry.chain === "evm") {
114735
115470
  const evmRpc = resolveEvmRpcUrl(rpcUrlFlag, ctx.deps.env[EVM_RPC_URL_ENV], "--rpc-url");
114736
115471
  if ("error" in evmRpc)
@@ -115163,8 +115898,8 @@ async function setAllowLaunch(args, ctx, enabled) {
115163
115898
  for (const message of new Set(notEnforced.map((item) => item.message))) {
115164
115899
  notice(deps, "stderr", "danger", `${message} (ALLOW_LAUNCH_NOT_ENFORCED)`);
115165
115900
  }
115166
- for (const failure3 of failed) {
115167
- notice(deps, "stderr", "danger", `Not changed: ${failure3.id}: ${failure3.message}${failure3.code ? ` (${failure3.code})` : ""}.`);
115901
+ for (const failure4 of failed) {
115902
+ notice(deps, "stderr", "danger", `Not changed: ${failure4.id}: ${failure4.message}${failure4.code ? ` (${failure4.code})` : ""}.`);
115168
115903
  }
115169
115904
  return failed.length > 0 || notEnforced.length > 0 ? 1 : 0;
115170
115905
  }
@@ -115175,6 +115910,101 @@ function walletsDisallowLaunch(args, ctx) {
115175
115910
  return setAllowLaunch(args, ctx, false);
115176
115911
  }
115177
115912
 
115913
+ // src/commands/wallets-allowlist.ts
115914
+ init_args();
115915
+ init_client();
115916
+ init_deps();
115917
+ init_human_output();
115918
+ init_presentation();
115919
+ init_profiles();
115920
+ init_render();
115921
+ init_key_output();
115922
+ var EMPTY_NOTE = "No approved withdrawal addresses. An agent can move funds only to the account's own wallets and its vault.";
115923
+ var MANAGED_HINT = "Approving an address needs the owner signed in to the agent console's Wallets tab; an API key cannot approve. An acting key can revoke one; a read-only key cannot.";
115924
+ async function walletsAllowlist(args, ctx) {
115925
+ const { deps, apiUrl, json } = ctx;
115926
+ const parsed = parseArgs(args, { booleanFlags: ["--all"] });
115927
+ if ("error" in parsed) {
115928
+ writeScreenUsageFailure(deps, parsed.error, json);
115929
+ return 2;
115930
+ }
115931
+ if (parsed.positionals.length > 0) {
115932
+ writeScreenUsageFailure(deps, `Unexpected argument: ${parsed.positionals[0]}`, json);
115933
+ return 2;
115934
+ }
115935
+ await printIdentity(ctx);
115936
+ const apiKey = await resolveApiKey(deps, ctx.profile);
115937
+ if (!apiKey) {
115938
+ writeScreenLocalFailure(deps, { code: "NO_API_KEY", message: "No API key available.", suggestion: "Run: candle keys create" }, json);
115939
+ return 1;
115940
+ }
115941
+ const query3 = parsed.booleans.has("--all") ? "?includeRevoked=true" : "";
115942
+ const result = await apiRequest(`/api/v1/agent/withdrawal-addresses${query3}`, {
115943
+ method: "GET",
115944
+ auth: "key",
115945
+ credentials: { apiKey },
115946
+ apiUrl,
115947
+ fetch: deps.fetch,
115948
+ env: deps.env
115949
+ });
115950
+ if (!result.ok) {
115951
+ writeScreenFailure(deps, result, { apiUrl, authType: "key" }, json);
115952
+ return 1;
115953
+ }
115954
+ const body = result.body;
115955
+ if (!Array.isArray(body.addresses)) {
115956
+ writeScreenLocalFailure(deps, { code: "INVALID_RESPONSE", message: "The withdrawal allowlist read did not return a list." }, json);
115957
+ return 1;
115958
+ }
115959
+ const rows = body.addresses;
115960
+ if (json) {
115961
+ deps.stdout.write(`${JSON.stringify({ success: true, addresses: rows, count: rows.length })}
115962
+ `);
115963
+ return 0;
115964
+ }
115965
+ const style = terminalStyle(deps, "stdout");
115966
+ deps.stdout.write(`${heading(`Withdrawal allowlist (${rows.length})`, style)}
115967
+
115968
+ `);
115969
+ if (rows.length === 0) {
115970
+ notice(deps, "stdout", "note", EMPTY_NOTE);
115971
+ notice(deps, "stdout", "note", MANAGED_HINT);
115972
+ return 0;
115973
+ }
115974
+ if (style.width < 100) {
115975
+ for (const row2 of rows) {
115976
+ writeFields(deps, "stdout", [
115977
+ ["Id", row2.id],
115978
+ ["Address", row2.address],
115979
+ ["Label", row2.label],
115980
+ ["Chain", row2.chain],
115981
+ ["Revoked", row2.revokedAt ? "yes" : "no"]
115982
+ ]);
115983
+ deps.stdout.write(`
115984
+ `);
115985
+ }
115986
+ } else {
115987
+ deps.stdout.write(`${renderTable(["Id", "Chain", "Address", "Label", "Revoked"], rows.map((row2) => [
115988
+ row2.id,
115989
+ row2.chain,
115990
+ row2.address,
115991
+ row2.label ?? symbols(style).missing,
115992
+ row2.revokedAt ? "yes" : "no"
115993
+ ]), {
115994
+ style,
115995
+ width: style.width,
115996
+ indent: 2,
115997
+ rule: false,
115998
+ columns: [{ id: true }, { priority: 3 }, { id: true }, { priority: 2 }, { priority: 1 }]
115999
+ })}
116000
+ `);
116001
+ }
116002
+ deps.stdout.write(`
116003
+ `);
116004
+ notice(deps, "stdout", "note", MANAGED_HINT);
116005
+ return 0;
116006
+ }
116007
+
115178
116008
  // src/commands/wallets-authorize.ts
115179
116009
  init_args();
115180
116010
  init_client();
@@ -115327,7 +116157,7 @@ init_solana_alt();
115327
116157
  init_solana_lite();
115328
116158
  init_trading();
115329
116159
  init_swap();
115330
- import { randomUUID as randomUUID16 } from "node:crypto";
116160
+ import { randomUUID as randomUUID17 } from "node:crypto";
115331
116161
  var USAGE21 = "Usage: candle wallet close-empty [--wallet embedded|<tee address|label>] [--keep <mint>]... [--client-trade-id <id>] [--preview] [--yes] [--json]";
115332
116162
  var BASE58_ADDRESS2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
115333
116163
  var LISTED_ROWS = 50;
@@ -115615,7 +116445,7 @@ async function walletsCloseEmpty(args, ctx) {
115615
116445
  return 2;
115616
116446
  }
115617
116447
  const named = parsed.values["--client-trade-id"];
115618
- const id = named ?? `close-${randomUUID16()}`;
116448
+ const id = named ?? `close-${randomUUID17()}`;
115619
116449
  if (parsed.positionals.length !== 0 || !validClientId(id) || !lifted.keep.every((m) => BASE58_ADDRESS2.test(m))) {
115620
116450
  writeUsageFailure(ctx.deps, USAGE21, ctx.json);
115621
116451
  return 2;
@@ -115877,6 +116707,7 @@ init_ownership();
115877
116707
  init_file_lock();
115878
116708
 
115879
116709
  // src/guard.ts
116710
+ init_account();
115880
116711
  init_profiles();
115881
116712
  async function verifyProfileAccount(ctx, config) {
115882
116713
  const { deps, profile } = ctx;
@@ -115892,11 +116723,11 @@ async function verifyProfileAccount(ctx, config) {
115892
116723
  const apiKey = await deps.store.get(profileSecretRef(profile, "apiKey"));
115893
116724
  if (!apiKey)
115894
116725
  return { ok: true, skipped: "no stored key" };
115895
- const { account: live, failure: failure3 } = await fetchAccount(deps, ctx.apiUrl, apiKey);
116726
+ const { account: live, failure: failure4 } = await fetchAccount(deps, ctx.apiUrl, apiKey);
115896
116727
  if (!live) {
115897
116728
  return {
115898
116729
  ok: true,
115899
- warning: `Could not verify the account for ${profile} (${failure3}); proceeding on the cached value ${cached}.`
116730
+ warning: `Could not verify the account for ${profile} (${failure4}); proceeding on the cached value ${cached}.`
115900
116731
  };
115901
116732
  }
115902
116733
  if (live !== cached) {
@@ -115929,6 +116760,7 @@ init_profiles();
115929
116760
  init_release();
115930
116761
  init_render();
115931
116762
  init_secret_store();
116763
+ init_trading_checks();
115932
116764
 
115933
116765
  // src/update-notice.ts
115934
116766
  init_client();
@@ -116144,7 +116976,8 @@ var COMMANDS = {
116144
116976
  "allow-launch": walletsAllowLaunch,
116145
116977
  "disallow-launch": walletsDisallowLaunch,
116146
116978
  "close-empty": walletsCloseEmpty,
116147
- authorize: walletsAuthorize
116979
+ authorize: walletsAuthorize,
116980
+ allowlist: walletsAllowlist
116148
116981
  },
116149
116982
  bare: wallets
116150
116983
  },
@@ -116175,6 +117008,7 @@ var COMMANDS = {
116175
117008
  tee: {
116176
117009
  subcommands: {
116177
117010
  new: teeNew,
117011
+ check: teeCheck,
116178
117012
  enable: teeEnable,
116179
117013
  fund: teeFund,
116180
117014
  status: teeStatus,