@candledottv/cli 0.13.18 → 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 +11 -3
  2. package/dist/index.js +871 -237
  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.18";
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
  },
@@ -12448,6 +12449,7 @@ var init_errors2 = __esm(() => {
12448
12449
  "VAULT_FIELD_UNKNOWN",
12449
12450
  "VAULT_KDF_OUT_OF_BOUNDS",
12450
12451
  "VAULT_UNLOCK_FAILED",
12452
+ "VAULT_TERMINAL_REQUIRED",
12451
12453
  "VAULT_BLOB_TAMPERED",
12452
12454
  "VAULT_INDEX_INVALID",
12453
12455
  "VAULT_OLDER_COPY",
@@ -15328,24 +15330,44 @@ function teeWalletList(rows) {
15328
15330
  return rows.map(describeWallet).join("; ");
15329
15331
  }
15330
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 = [];
15331
15340
  if (rowChain(row) !== chain2)
15332
- 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 };
15333
15344
  if (!row.active)
15334
- 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 };
15335
15348
  if (scope === "launch:write" && row.allowLaunch !== true)
15336
- 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 };
15337
15352
  if (!appId || !row.privyWalletId)
15338
- 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 };
15339
15356
  const signer = await localSignerFor(ctx.deps, row.id, row.signerQuorumId);
15340
15357
  if (!signer)
15341
- 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 };
15342
15361
  return {
15343
- id: row.id,
15344
- address: row.address,
15345
- privyWalletId: row.privyWalletId,
15346
- appId,
15347
- signer: signer.pem,
15348
- 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
+ }
15349
15371
  };
15350
15372
  }
15351
15373
  async function signerUnavailableMessage(ctx, row, key) {
@@ -15378,7 +15400,18 @@ async function tradingWallet(ctx, key, name, scope, chain2 = "solana") {
15378
15400
  return { ...wallet, scopes };
15379
15401
  }
15380
15402
  async function tradingPayer(ctx, key, name, scope = "swap:write", chain2 = "solana") {
15381
- 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;
15382
15415
  const discovered = embeddedSchema.parse(await request(ctx, key, "/api/v1/agent/wallets/embedded"));
15383
15416
  const wallets = discovered.wallets;
15384
15417
  const embeddedOn = {
@@ -15397,14 +15430,7 @@ async function tradingPayer(ctx, key, name, scope = "swap:write", chain2 = "sola
15397
15430
  ].join("; ");
15398
15431
  if (name === undefined) {
15399
15432
  if (onChain.length === 1 && !embedded)
15400
- return {
15401
- kind: "tee",
15402
- wallet: await completeTradingWallet(ctx, onChain[0], appId, scope, chain2, {
15403
- apiKey: key,
15404
- keyPrefix
15405
- }),
15406
- scopes
15407
- };
15433
+ return { kind: "tee", row: onChain[0] };
15408
15434
  if (onChain.length === 0 && embedded)
15409
15435
  return asEmbedded();
15410
15436
  if (onChain.length === 0 && deniedAddress !== undefined)
@@ -15413,14 +15439,7 @@ async function tradingPayer(ctx, key, name, scope = "swap:write", chain2 = "sola
15413
15439
  }
15414
15440
  const matches = rows.filter((row) => matchesName(row, name));
15415
15441
  if (matches.length === 1)
15416
- return {
15417
- kind: "tee",
15418
- wallet: await completeTradingWallet(ctx, matches[0], appId, scope, chain2, {
15419
- apiKey: key,
15420
- keyPrefix
15421
- }),
15422
- scopes
15423
- };
15442
+ return { kind: "tee", row: matches[0] };
15424
15443
  if (matches.length === 0) {
15425
15444
  if (embedded !== undefined && sameAddress(embedded, name))
15426
15445
  return asEmbedded();
@@ -16949,8 +16968,431 @@ var init_solana_verify = __esm(() => {
16949
16968
  ]);
16950
16969
  });
16951
16970
 
16952
- // src/commands/swap.ts
16971
+ // src/trading-checks.ts
16953
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";
16954
17396
  function tradingFailure(ctx, error, id) {
16955
17397
  if (error instanceof TradingUsage) {
16956
17398
  writeUsageFailure(ctx.deps, error.message, ctx.json);
@@ -17184,6 +17626,8 @@ async function assertDeferredExecuteSupported(ctx, key, id) {
17184
17626
  throw new TradingError("INVALID_RESPONSE", "The execute route answered for a trade that does not exist.");
17185
17627
  }
17186
17628
  async function swap(args, ctx) {
17629
+ if (args.includes("--dry-run"))
17630
+ return swapCheck(args, ctx);
17187
17631
  const parsed = parseArgs(args, {
17188
17632
  valueFlags: ["--amount", "--percent", "--wallet", "--to", "--client-trade-id", "--slippage-bps", "--rpc-url"],
17189
17633
  booleanFlags: ["--yes", "--wait"]
@@ -17193,7 +17637,7 @@ async function swap(args, ctx) {
17193
17637
  return 2;
17194
17638
  }
17195
17639
  const flags = parsed.values;
17196
- const id = flags["--client-trade-id"] ?? `swap-${randomUUID()}`;
17640
+ const id = flags["--client-trade-id"] ?? `swap-${randomUUID2()}`;
17197
17641
  const slippage = Number(flags["--slippage-bps"] ?? "50");
17198
17642
  if (!Number.isInteger(slippage) || slippage < 0 || slippage > AGENT_MAX_SLIPPAGE_BPS) {
17199
17643
  writeUsageFailure(ctx.deps, SLIPPAGE_RANGE_MESSAGE, ctx.json);
@@ -17414,17 +17858,17 @@ async function hoodDecimals(asset, rpc) {
17414
17858
  return decimals;
17415
17859
  }
17416
17860
  async function recordTradedToken(ctx, wallet, token) {
17417
- 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.`;
17418
17862
  const append = ctx.deps.appendEvmRecord;
17419
17863
  let notice2;
17420
17864
  if (!append)
17421
- notice2 = skipped("this CLI build has no sealed EVM record writer");
17865
+ notice2 = skipped2("this CLI build has no sealed EVM record writer");
17422
17866
  else {
17423
17867
  try {
17424
17868
  const outcome = await append(ctx, { kind: "token", wallet: toChecksumAddress(wallet), token });
17425
- notice2 = outcome.appended ? outcome.notice : skipped(outcome.notice);
17869
+ notice2 = outcome.appended ? outcome.notice : skipped2(outcome.notice);
17426
17870
  } catch (error) {
17427
- notice2 = skipped(error instanceof Error ? error.message : "the append failed");
17871
+ notice2 = skipped2(error instanceof Error ? error.message : "the append failed");
17428
17872
  }
17429
17873
  }
17430
17874
  if (notice2)
@@ -17872,6 +18316,7 @@ var init_swap = __esm(() => {
17872
18316
  init_solana_lite();
17873
18317
  init_solana_verify();
17874
18318
  init_trading();
18319
+ init_trading_checks();
17875
18320
  SLIPPAGE_RANGE_MESSAGE = `maxSlippageBps must be an integer in bps, between 0 and ${AGENT_MAX_SLIPPAGE_BPS}`;
17876
18321
  settlementSchema = exports_external.object({
17877
18322
  state: exports_external.enum(["settled", "pending", "failed", "uncertain"]),
@@ -17925,11 +18370,11 @@ function writeScreenFailure(deps, result, context, json) {
17925
18370
  `)}
17926
18371
  `);
17927
18372
  }
17928
- function writeScreenLocalFailure(deps, failure2, json) {
18373
+ function writeScreenLocalFailure(deps, failure3, json) {
17929
18374
  if (json)
17930
- writeLocalFailure(deps, failure2, json);
18375
+ writeLocalFailure(deps, failure3, json);
17931
18376
  else
17932
- writeScreenMessage(deps, failure2.message, failure2.suggestion);
18377
+ writeScreenMessage(deps, failure3.message, failure3.suggestion);
17933
18378
  }
17934
18379
  function writeScreenUsageFailure(deps, message, json, screen) {
17935
18380
  if (json)
@@ -18033,14 +18478,14 @@ async function readSignerRoles(rpc, addresses, opts) {
18033
18478
  let requests = 0;
18034
18479
  let rateLimited = 0;
18035
18480
  let settled = 0;
18036
- let skipped = 0;
18481
+ let skipped2 = 0;
18037
18482
  let next = 0;
18038
18483
  let active = 0;
18039
18484
  const progress = () => {
18040
18485
  opts.onProgress?.({
18041
- completed: settled + skipped,
18486
+ completed: settled + skipped2,
18042
18487
  settled,
18043
- skipped,
18488
+ skipped: skipped2,
18044
18489
  total: planned,
18045
18490
  refusedGroups: failed.size,
18046
18491
  elapsedMs: opts.now() - startedAt
@@ -18185,7 +18630,7 @@ async function readSignerRoles(rpc, addresses, opts) {
18185
18630
  while (active < cap && next < tasks.length) {
18186
18631
  const task = tasks[next++];
18187
18632
  if (failed.has(task.group.id)) {
18188
- skipped += 1;
18633
+ skipped2 += 1;
18189
18634
  skippedAny = true;
18190
18635
  continue;
18191
18636
  }
@@ -29067,7 +29512,7 @@ async function askForOwnPassphrase(ctx, promptText) {
29067
29512
  function requireTty(ctx, what) {
29068
29513
  if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stdout)
29069
29514
  return true;
29070
- 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.`, {
29071
29516
  suggestion: "There is no environment variable and no flag that supplies one."
29072
29517
  }));
29073
29518
  return false;
@@ -29076,7 +29521,7 @@ function refusePromptsWithoutTerminal(ctx, what) {
29076
29521
  if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stderr)
29077
29522
  return ctx;
29078
29523
  const refuse2 = async () => {
29079
- throw tagNoTerminal(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.`, {
29080
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."
29081
29526
  }));
29082
29527
  };
@@ -29860,9 +30305,9 @@ async function reconcileGrant(ctx, entry, opts = {}) {
29860
30305
  if (!stranded.complete) {
29861
30306
  return { kind: "unreadable", reason: stranded.incompleteReason ?? "the stranded-import read was incomplete" };
29862
30307
  }
29863
- const failure2 = stranded.failures.find((candidate) => sameWalletAddress(candidate.address, entry.address));
29864
- if (failure2?.stage === "convex_link") {
29865
- 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 };
29866
30311
  }
29867
30312
  return { kind: "unresolved", account: wallets.account };
29868
30313
  }
@@ -33994,23 +34439,23 @@ async function walletsImport(args, ctx) {
33994
34439
  deps
33995
34440
  });
33996
34441
  if (!flow.ok) {
33997
- const failure2 = flow.failure;
33998
- if (failure2.kind === "api") {
33999
- 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);
34000
34445
  return 1;
34001
34446
  }
34002
- if (failure2.kind === "signer-store") {
34447
+ if (failure3.kind === "signer-store") {
34003
34448
  writeScreenLocalFailure(deps, {
34004
34449
  code: "SIGNER_STORE_FAILED",
34005
- 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}`,
34006
34451
  suggestion: "Nothing was imported. Unlock the store (or fix the error above) and run the command again."
34007
34452
  }, json);
34008
34453
  return 1;
34009
34454
  }
34010
34455
  writeScreenLocalFailure(deps, {
34011
34456
  code: "SIGNER_COMMIT_FAILED",
34012
- 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}`,
34013
- 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}`
34014
34459
  }, json);
34015
34460
  return 1;
34016
34461
  }
@@ -35486,8 +35931,8 @@ function releaseActiveTee(active) {
35486
35931
  if (active.source === "vault")
35487
35932
  releaseResolvedTee(active.resolved);
35488
35933
  }
35489
- function writeCommitFailure(ctx, failure2, consequence) {
35490
- 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);
35491
35936
  }
35492
35937
  async function openExistingTeeStore(ctx, parsed) {
35493
35938
  const { deps, json } = ctx;
@@ -35788,13 +36233,13 @@ async function teeEnable(args, ctx) {
35788
36233
  vaultDestination: vault
35789
36234
  });
35790
36235
  if (!flow.ok) {
35791
- const failure2 = flow.failure;
35792
- if (failure2.kind === "api")
35793
- 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);
35794
36239
  else {
35795
36240
  writeLocalFailure(deps, {
35796
- code: failure2.kind === "signer-store" ? "SIGNER_STORE_FAILED" : "SIGNER_COMMIT_FAILED",
35797
- 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}`,
35798
36243
  suggestion: "The key is still sealed locally. Fix the error above and run enable again."
35799
36244
  }, json);
35800
36245
  }
@@ -40333,7 +40778,7 @@ var init_market_disclosure = __esm(() => {
40333
40778
  });
40334
40779
 
40335
40780
  // ../mcp/src/orchestrate.ts
40336
- import { randomUUID as randomUUID3 } from "node:crypto";
40781
+ import { randomUUID as randomUUID4 } from "node:crypto";
40337
40782
  function requireApiKey(cfg) {
40338
40783
  if (!cfg.apiKey) {
40339
40784
  throw new Error("CANDLE_AGENT_API_KEY is required for this tool. Set it in the environment or MCP client config.");
@@ -40446,7 +40891,7 @@ async function readPaperInventory(cfg, doFetch, extra) {
40446
40891
  }
40447
40892
  async function executeTrade(args, cfg, doFetch) {
40448
40893
  const apiKey = requireApiKey(cfg);
40449
- const clientTradeId = args.clientTradeId ?? randomUUID3();
40894
+ const clientTradeId = args.clientTradeId ?? randomUUID4();
40450
40895
  if (args.amount !== undefined && args.percent !== undefined) {
40451
40896
  return errText("pass exactly one of amount or percent, not both", { clientTradeId });
40452
40897
  }
@@ -40595,7 +41040,7 @@ async function executeTrade(args, cfg, doFetch) {
40595
41040
  }
40596
41041
  async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs, opts = {}) {
40597
41042
  const apiKey = requireApiKey(cfg);
40598
- const clientLaunchId = args.clientLaunchId ?? randomUUID3();
41043
+ const clientLaunchId = args.clientLaunchId ?? randomUUID4();
40599
41044
  const socialsProblem = socialsRefusal(args.socials);
40600
41045
  if (socialsProblem)
40601
41046
  return { text: imageRefusalBody(socialsProblem, { clientLaunchId }), isError: true };
@@ -40824,7 +41269,26 @@ async function resolveToken(args, cfg, doFetch) {
40824
41269
  return read.err;
40825
41270
  return { text: JSON.stringify({ success: true, chain: chain2, mint: args.mint, market: read.market }, null, 2) };
40826
41271
  }
40827
- 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;
40828
41292
  const apiKey = requireApiKey(cfg);
40829
41293
  const get = async (path) => {
40830
41294
  const res = await doFetch(`${base(cfg)}${path}`, { method: "GET", headers: headers(apiKey) });
@@ -40838,14 +41302,17 @@ async function executionStatus(cfg, doFetch) {
40838
41302
  }
40839
41303
  return { ok: res.ok, status: res.status, body };
40840
41304
  };
40841
- const [wallets2, tier, limits, risk] = await Promise.all([
41305
+ const [wallets2, trading, tier, limits, risk] = await Promise.all([
40842
41306
  get("/api/v1/agent/wallets/embedded"),
41307
+ readTee ? get("/api/v1/agent/wallets/trading") : Promise.resolve(undefined),
40843
41308
  get("/api/v1/agent/tier"),
40844
41309
  get("/api/v1/agent/keys/self/limits"),
40845
41310
  get("/api/v1/agent/keys/self/risk")
40846
41311
  ]);
41312
+ const tee = trading?.ok ? teePayers(trading.body) : undefined;
40847
41313
  const unreadable2 = [
40848
41314
  ["wallets", wallets2],
41315
+ ...trading ? [["tee", tee ? trading : { ok: false }]] : [],
40849
41316
  ["tier", tier],
40850
41317
  ["limits", limits],
40851
41318
  ["risk", risk]
@@ -40862,6 +41329,11 @@ async function executionStatus(cfg, doFetch) {
40862
41329
  updateAvailable: updateAvailable() ?? undefined,
40863
41330
  unreadable: unreadable2.length > 0 ? unreadable2.map(([name]) => name) : undefined,
40864
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,
40865
41337
  tier: tier.body,
40866
41338
  limits: limits.body,
40867
41339
  risk: risk.body
@@ -40869,7 +41341,7 @@ async function executionStatus(cfg, doFetch) {
40869
41341
  ...unreadable2.length > 0 ? { isError: true } : {}
40870
41342
  };
40871
41343
  }
40872
- 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.";
40873
41345
  var init_orchestrate = __esm(() => {
40874
41346
  init_convert();
40875
41347
  init_image();
@@ -40885,6 +41357,7 @@ var init_orchestrate = __esm(() => {
40885
41357
  solana: ["USDC", "CNDL", "SOL"],
40886
41358
  hood: ["USDG", "ETH"]
40887
41359
  };
41360
+ TEE_SPEND_SCOPES = ["swap:write", "transfer:write", "launch:write"];
40888
41361
  });
40889
41362
 
40890
41363
  // ../mcp/src/hyperliquid.ts
@@ -44605,6 +45078,9 @@ function planTableRows(table) {
44605
45078
  row2("Uploads per minute", (e) => count(e.limits.uploadsPerMin)),
44606
45079
  row2("Linked wallets", (e) => count(e.limits.linkedWallets))
44607
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
+ }
44608
45084
  const known = Object.keys(PLAN_CAPABILITY_LABELS);
44609
45085
  const served = new Set;
44610
45086
  for (const entry of plans)
@@ -44649,13 +45125,14 @@ var init_plans = __esm(() => {
44649
45125
  createLinkedWallets: "Create linked wallets",
44650
45126
  importLinkedWallets: "Import linked wallets",
44651
45127
  limitOrders: "Limit orders",
44652
- quant: "Quant deep analyses (Max)"
45128
+ quant: "Quant deep analyses (Max)",
45129
+ strategyLab: "Strategy lab (saved strategies stay Max)"
44653
45130
  };
44654
45131
  PLAN_LABELS = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
44655
45132
  });
44656
45133
 
44657
45134
  // ../mcp/src/token-metadata.ts
44658
- import { randomUUID as randomUUID4 } from "node:crypto";
45135
+ import { randomUUID as randomUUID5 } from "node:crypto";
44659
45136
  function editSocialsSchema() {
44660
45137
  const shape = {};
44661
45138
  for (const key of SOCIAL_KEYS) {
@@ -44706,7 +45183,7 @@ function tokenEditBody(args) {
44706
45183
  return { body };
44707
45184
  }
44708
45185
  function tokenEditRequest(base2, apiKey, args, body) {
44709
- const key = typeof args.idempotencyKey === "string" && args.idempotencyKey ? args.idempotencyKey : randomUUID4();
45186
+ const key = typeof args.idempotencyKey === "string" && args.idempotencyKey ? args.idempotencyKey : randomUUID5();
44710
45187
  return {
44711
45188
  url: `${base2}/api/v1/tokens/${encodeURIComponent(String(args.chain))}/${encodeURIComponent(String(args.mint))}/metadata`,
44712
45189
  init: {
@@ -44934,7 +45411,7 @@ __export(exports_tools, {
44934
45411
  OPERATION_NOT_FOUND_RULE: () => OPERATION_NOT_FOUND_RULE,
44935
45412
  LAB_TOOL_NAMES: () => LAB_TOOL_NAMES
44936
45413
  });
44937
- import { randomUUID as randomUUID5 } from "node:crypto";
45414
+ import { randomUUID as randomUUID6 } from "node:crypto";
44938
45415
  function resolveToolAllowlist(env) {
44939
45416
  const raw = env.CANDLE_MCP_TOOLS?.trim();
44940
45417
  if (!raw)
@@ -45035,6 +45512,26 @@ function buildRequest(name, args, cfg) {
45035
45512
  init: { method: "GET", headers: jsonHeaders() }
45036
45513
  };
45037
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
+ }
45038
45535
  case "candle_get_price_history": {
45039
45536
  const { chain: chain2, mint, interval, limit, to } = args;
45040
45537
  const query2 = new URLSearchParams;
@@ -45272,6 +45769,28 @@ function buildRequest(name, args, cfg) {
45272
45769
  init: { method: "GET", headers: jsonHeaders(apiKey) }
45273
45770
  };
45274
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
+ }
45275
45794
  case "candle_get_profile_wallets": {
45276
45795
  const apiKey = requireApiKey2(cfg);
45277
45796
  const { keyPrefix } = args;
@@ -45402,7 +45921,7 @@ async function getOperation(args, cfg, doFetch) {
45402
45921
  };
45403
45922
  }
45404
45923
  async function swap2(args, cfg, doFetch) {
45405
- 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();
45406
45925
  const { url, init } = buildRequest("candle_swap", { ...args, clientSwapId }, cfg);
45407
45926
  let ok;
45408
45927
  let body;
@@ -45577,6 +46096,12 @@ function registerToolSubset(server, options) {
45577
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.",
45578
46097
  inputSchema: getMarketShape
45579
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));
45580
46105
  register("candle_token_forensics", {
45581
46106
  title: "Token forensics",
45582
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.",
@@ -45767,6 +46292,16 @@ function registerToolSubset(server, options) {
45767
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.",
45768
46293
  inputSchema: {}
45769
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));
45770
46305
  register("candle_get_profile_wallets", {
45771
46306
  title: "Read which wallets an agent profile can spend from",
45772
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.",
@@ -45823,10 +46358,12 @@ function registerToolSubset(server, options) {
45823
46358
  });
45824
46359
  register("candle_execution_status", {
45825
46360
  title: "Can this key execute right now",
45826
- 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.",
45827
46362
  inputSchema: {}
45828
46363
  }, async () => {
45829
- const result = await executionStatus(getConfig(), doFetch);
46364
+ const result = await executionStatus(getConfig(), doFetch, {
46365
+ teeWallets: options.embeddedExecutionStatus !== true
46366
+ });
45830
46367
  return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
45831
46368
  });
45832
46369
  register("candle_swap", {
@@ -46097,7 +46634,7 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
46097
46634
  }
46098
46635
  return eligible;
46099
46636
  }
46100
- 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, tradeExecuteShape, 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;
46101
46638
  var init_tools = __esm(() => {
46102
46639
  init_zod();
46103
46640
  init_client2();
@@ -46126,6 +46663,7 @@ var init_tools = __esm(() => {
46126
46663
  TOOL_NAMES = [
46127
46664
  "candle_launch_token",
46128
46665
  "candle_get_market",
46666
+ "candle_quote",
46129
46667
  "candle_get_feed",
46130
46668
  "candle_vamp_candidates",
46131
46669
  "candle_token_events",
@@ -46169,6 +46707,8 @@ var init_tools = __esm(() => {
46169
46707
  "candle_transfer",
46170
46708
  "candle_sweep",
46171
46709
  "candle_get_wallets",
46710
+ "candle_get_trading_wallets",
46711
+ "candle_get_spendable_balances",
46172
46712
  "candle_get_profile_wallets",
46173
46713
  "candle_set_profile_wallets",
46174
46714
  "candle_get_profile_pnl",
@@ -46232,6 +46772,15 @@ var init_tools = __esm(() => {
46232
46772
  chain: exports_external.string().describe('"solana" or "hood"'),
46233
46773
  mint: exports_external.string().describe("Token mint (solana) or contract address (hood)")
46234
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
+ };
46235
46784
  priceHistoryShape = {
46236
46785
  chain: exports_external.string().optional().describe('"solana" (the default). Hood is refused with the REST route that serves it'),
46237
46786
  mint: exports_external.string().describe("Token mint"),
@@ -46273,7 +46822,7 @@ var init_tools = __esm(() => {
46273
46822
  return /^[0-9]+$/.test(trimmed) ? Number(trimmed) : value;
46274
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."));
46275
46824
  getFeedShape = {
46276
- 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.'),
46277
46826
  chain: exports_external.string().optional().describe("Optional chain filter"),
46278
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."),
46279
46828
  sort: exports_external.string().optional().describe('Sort as "field" or "field:asc" / "field:desc". A bare field means desc.'),
@@ -46349,6 +46898,13 @@ var init_tools = __esm(() => {
46349
46898
  resolveTokenShape = {
46350
46899
  mint: exports_external.string().describe("Token mint (Solana, base58) or contract address (Hood, 0x-prefixed)")
46351
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
+ };
46352
46908
  profileWalletsShape = {
46353
46909
  keyPrefix: exports_external.string().describe("The profile's API key prefix, as listed by candle keys list or in the dashboard")
46354
46910
  };
@@ -76191,7 +76747,7 @@ var HELP = {
76191
76747
  group: "Trade",
76192
76748
  intro: "Quote, swap and bridge on Solana or Hood; read an operation",
76193
76749
  summary: "Quote, confirm and swap on Solana or Hood, bridge between them; read an operation by id",
76194
- 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.",
76195
76751
  usage: ["candle swap <from> <to> [flags]", "candle swap status <id>"],
76196
76752
  rows: [
76197
76753
  {
@@ -76203,7 +76759,14 @@ var HELP = {
76203
76759
  description: "Read an operation without resending it; --wait follows a bridge until it ends"
76204
76760
  }
76205
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
+ ],
76206
76768
  examples: [
76769
+ "candle swap SOL USDC --amount 0.001 --wallet AgentOne --dry-run --json",
76207
76770
  "candle swap SOL USDC --amount 0.5 --wallet AgentOne",
76208
76771
  "candle swap USDC SOL --percent 100 --wallet AgentOne",
76209
76772
  "candle swap ETH USDG --amount 0.05 --wallet HoodOne",
@@ -76504,9 +77067,10 @@ var HELP = {
76504
77067
  group: "Trade",
76505
77068
  intro: "Move funds out of a TEE wallet to your wallets or its vault",
76506
77069
  summary: "Move funds out of a TEE wallet to your own wallets or its vault, through its bound key",
76507
- 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.",
76508
77071
  usage: [
76509
- "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]"
76510
77074
  ],
76511
77075
  rows: [],
76512
77076
  flags: [
@@ -76526,10 +77090,15 @@ var HELP = {
76526
77090
  invocation: "--rpc-url <url>",
76527
77091
  description: "Your own Solana RPC, or on Hood the EVM RPC a --token's decimals are read over (optional)"
76528
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
+ },
76529
77097
  { invocation: "--yes", description: "Skip the confirmation prompt (the destination is still printed)" }
76530
77098
  ],
76531
77099
  examples: [
76532
- "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",
76533
77102
  "candle transfer --to treasury --asset USDC --amount 250 --wallet AgentOne --yes --json",
76534
77103
  "candle transfer --to vault --asset ETH --amount max --wallet HoodAgent"
76535
77104
  ],
@@ -77328,6 +77897,10 @@ var HELP = {
77328
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.",
77329
77898
  usage: ["candle tee <subcommand> [flags]"],
77330
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
+ },
77331
77904
  {
77332
77905
  invocation: "new [--label <name>]",
77333
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"
@@ -77390,6 +77963,8 @@ var HELP = {
77390
77963
  }
77391
77964
  ],
77392
77965
  examples: [
77966
+ "candle tee check --label-prefix pool- --json",
77967
+ "candle tee check AgentOne --sign --rpc-url <url>",
77393
77968
  "candle tee new --label AgentOne",
77394
77969
  "candle tee status AgentOneAddress",
77395
77970
  "candle tee sweep AgentOneAddress",
@@ -77956,13 +78531,14 @@ class AgentProfiles {
77956
78531
  // src/core/cards.ts
77957
78532
  init_render();
77958
78533
  init_canonical_json();
77959
- 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";
77960
78535
 
77961
78536
  // src/core/classify.ts
77962
78537
  var READ = { class: "read" };
77963
78538
  var card = (kind, idKey = "coreOpId") => ({ class: "card", kind, idKey });
77964
78539
  var TOOL_CLASSES = {
77965
78540
  candle_get_market: READ,
78541
+ candle_quote: READ,
77966
78542
  candle_get_feed: READ,
77967
78543
  candle_vamp_candidates: READ,
77968
78544
  candle_token_events: READ,
@@ -77986,6 +78562,8 @@ var TOOL_CLASSES = {
77986
78562
  candle_get_agent_positions: READ,
77987
78563
  candle_get_plans: READ,
77988
78564
  candle_get_wallets: READ,
78565
+ candle_get_trading_wallets: READ,
78566
+ candle_get_spendable_balances: READ,
77989
78567
  candle_get_profile_wallets: READ,
77990
78568
  candle_get_profile_pnl: READ,
77991
78569
  candle_get_profile_trades: READ,
@@ -79820,8 +80398,8 @@ class CardBook {
79820
80398
  if (idKey === "clientId")
79821
80399
  return `0x${randomBytes5(16).toString("hex")}`;
79822
80400
  if (idKey === "clientDepositId")
79823
- return `deposit-${randomUUID2()}`;
79824
- return randomUUID2();
80401
+ return `deposit-${randomUUID3()}`;
80402
+ return randomUUID3();
79825
80403
  }
79826
80404
  view(card2) {
79827
80405
  return JSON.parse(JSON.stringify(card2));
@@ -80680,18 +81258,18 @@ async function readJournal(path) {
80680
81258
  const tornTail = text2.length > 0 && !text2.endsWith(`
80681
81259
  `);
80682
81260
  const rows = [];
80683
- let skipped = 0;
81261
+ let skipped2 = 0;
80684
81262
  for (const line of text2.split(`
80685
81263
  `)) {
80686
81264
  if (line.trim() === "")
80687
81265
  continue;
80688
81266
  const row2 = parseRow(line);
80689
81267
  if (row2 === undefined)
80690
- skipped++;
81268
+ skipped2++;
80691
81269
  else
80692
81270
  rows.push(row2);
80693
81271
  }
80694
- return { rows, skipped, tornTail };
81272
+ return { rows, skipped: skipped2, tornTail };
80695
81273
  }
80696
81274
  function parseRow(line) {
80697
81275
  let value;
@@ -80752,10 +81330,10 @@ function registerLaunchHandlers(server, deps) {
80752
81330
  server.handle("launch.dryRun", async (params, { client }) => deps.cards.dryRun({ cardId: params.cardId, factor: parseFactor(params.factor) }, client.clientId));
80753
81331
  }
80754
81332
  function coded(error, fallback) {
80755
- const failure2 = error;
80756
- if (failure2 !== null && typeof failure2 === "object" && "rpcCode" in failure2)
81333
+ const failure3 = error;
81334
+ if (failure3 !== null && typeof failure3 === "object" && "rpcCode" in failure3)
80757
81335
  return error;
80758
- 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));
80759
81337
  }
80760
81338
 
80761
81339
  // src/commands/core.ts
@@ -81159,7 +81737,7 @@ ${text2}` : text2;
81159
81737
  const sleep = this.deps.sleep ?? defaultSleep;
81160
81738
  let tooMany = 0;
81161
81739
  let lost = 0;
81162
- let failure2;
81740
+ let failure3;
81163
81741
  while (!poll.stopped) {
81164
81742
  const answer2 = await client.events(poll.turnId, poll.next, { signal: poll.abort.signal });
81165
81743
  if (poll.stopped)
@@ -81194,7 +81772,7 @@ ${text2}` : text2;
81194
81772
  if (refusal2.code === "too_many_polls") {
81195
81773
  tooMany++;
81196
81774
  if (tooMany > TOO_MANY_POLLS_RETRIES) {
81197
- failure2 = "too_many_polls";
81775
+ failure3 = "too_many_polls";
81198
81776
  break;
81199
81777
  }
81200
81778
  await sleep(TOO_MANY_POLLS_WAIT_MS);
@@ -81203,13 +81781,13 @@ ${text2}` : text2;
81203
81781
  if (refusal2.shape === "transport") {
81204
81782
  lost++;
81205
81783
  if (lost > LOST_POLL_RETRIES) {
81206
- failure2 = "QUANT_UNREACHABLE";
81784
+ failure3 = "QUANT_UNREACHABLE";
81207
81785
  break;
81208
81786
  }
81209
81787
  await sleep(Math.min(1000 * lost, 5000));
81210
81788
  continue;
81211
81789
  }
81212
- failure2 = refusal2.code;
81790
+ failure3 = refusal2.code;
81213
81791
  break;
81214
81792
  }
81215
81793
  if (this.poll !== poll)
@@ -81220,11 +81798,11 @@ ${text2}` : text2;
81220
81798
  this.ended.delete(this.ended.values().next().value);
81221
81799
  if (this.turn?.turnId === poll.turnId)
81222
81800
  this.turn.running = false;
81223
- if (failure2 !== undefined) {
81801
+ if (failure3 !== undefined) {
81224
81802
  this.publish({
81225
81803
  turnId: poll.turnId,
81226
81804
  kind: "poll_failed",
81227
- data: { code: failure2 },
81805
+ data: { code: failure3 },
81228
81806
  seq: poll.next,
81229
81807
  origin: poll.origin
81230
81808
  });
@@ -83261,8 +83839,8 @@ async function prepare(params, call2, deps) {
83261
83839
  try {
83262
83840
  preview = await deps.reads.previewVault(parsed, wallet3);
83263
83841
  } catch (error) {
83264
- const failure2 = error;
83265
- 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.`);
83266
83844
  }
83267
83845
  } else {
83268
83846
  const withdraw = deps.trading.withdrawState();
@@ -84636,8 +85214,8 @@ async function readTargetSigner(ctx, deviceToken, keyPrefix) {
84636
85214
  }
84637
85215
  async function preflightToKey(ctx, raw, opts) {
84638
85216
  const { deps, json } = ctx;
84639
- const refuse3 = (failure2) => {
84640
- writeLocalFailure(deps, failure2, json);
85217
+ const refuse3 = (failure3) => {
85218
+ writeLocalFailure(deps, failure3, json);
84641
85219
  return { ok: false, exit: 1 };
84642
85220
  };
84643
85221
  const deviceToken = await resolveDeviceToken(deps, ctx.profile);
@@ -84789,8 +85367,8 @@ async function rebindPromoted(ctx, deviceToken, toKeyPrefix, wallets2, pinnedSpk
84789
85367
  if (!committed.ok) {
84790
85368
  run2.ok = false;
84791
85369
  if ("missing" in committed) {
84792
- const failure2 = missingSignerFailure(committed.missing);
84793
- run2.failure = { ...failure2, status: 409, chunk: at2 + 1 };
85370
+ const failure3 = missingSignerFailure(committed.missing);
85371
+ run2.failure = { ...failure3, status: 409, chunk: at2 + 1 };
84794
85372
  } else if ("changed" in committed) {
84795
85373
  run2.failure = { ...committed.changed, status: 409, chunk: at2 + 1 };
84796
85374
  } else {
@@ -85574,8 +86152,8 @@ async function moveToLiveKey(ctx, deviceToken, source, toKeyRaw) {
85574
86152
  for (const chunk of chunkWallets(ids)) {
85575
86153
  const preview = await postRebind(ctx, deviceToken, { dryRun: true, toKeyPrefix: target.keyPrefix, wallets: chunk });
85576
86154
  if (!preview.ok) {
85577
- const failure2 = rebindFailureDetails(preview, {});
85578
- writeScreenLocalFailure(deps, failure2, json);
86155
+ const failure3 = rebindFailureDetails(preview, {});
86156
+ writeScreenLocalFailure(deps, failure3, json);
85579
86157
  return 1;
85580
86158
  }
85581
86159
  const shown = preview.body;
@@ -90489,7 +91067,7 @@ init_secret_store();
90489
91067
  init_key_output();
90490
91068
  init_keys();
90491
91069
  init_tee_rebind();
90492
- import { randomUUID as randomUUID7 } from "node:crypto";
91070
+ import { randomUUID as randomUUID8 } from "node:crypto";
90493
91071
 
90494
91072
  // src/commands/token-pages.ts
90495
91073
  init_esm();
@@ -90499,7 +91077,7 @@ init_deps();
90499
91077
  init_human_output();
90500
91078
  init_presentation();
90501
91079
  init_render();
90502
- import { createHash as createHash5, randomUUID as randomUUID6 } from "node:crypto";
91080
+ import { createHash as createHash5, randomUUID as randomUUID7 } from "node:crypto";
90503
91081
  var EXIT_OUTCOME_UNKNOWN = 3;
90504
91082
  var EXIT_REVISION_CONFLICT = 4;
90505
91083
  var TOKEN_PAGE_LIMITS = { thesis: 280, tokenomics: 5000, lore: 5000, update: 1000, comment: 500 };
@@ -90582,8 +91160,8 @@ function writeApiFailure(ctx, result, authType) {
90582
91160
  writeRefusal(ctx, refusal3);
90583
91161
  return 1;
90584
91162
  }
90585
- const failure2 = result;
90586
- 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);
90587
91165
  return 1;
90588
91166
  }
90589
91167
  function dataOf(result) {
@@ -90646,7 +91224,7 @@ function writeUnknown(ctx, idempotencyKey) {
90646
91224
  function idempotencyKeyOf(values, usage4) {
90647
91225
  const given2 = values["--idempotency-key"];
90648
91226
  if (given2 === undefined)
90649
- return randomUUID6();
91227
+ return randomUUID7();
90650
91228
  if (given2.length < 1 || given2.length > 128)
90651
91229
  return usage4("--idempotency-key must be 1 to 128 characters.");
90652
91230
  return given2;
@@ -91362,7 +91940,7 @@ async function keysRotate(args, ctx) {
91362
91940
  writeScreenUsageFailure(deps, "--idempotency-key must be 1 to 128 characters.", json);
91363
91941
  return 2;
91364
91942
  }
91365
- const idempotencyKey = keyFlag ?? randomUUID7();
91943
+ const idempotencyKey = keyFlag ?? randomUUID8();
91366
91944
  let graceSeconds = now ? 0 : DEFAULT_GRACE_SECONDS;
91367
91945
  if (graceFlag !== undefined) {
91368
91946
  const grace = parseGrace(graceFlag);
@@ -91920,7 +92498,7 @@ async function client(ctx) {
91920
92498
  }, ctx.json);
91921
92499
  }
91922
92500
  }
91923
- function failure2(answer2, ctx, statusRoute = false) {
92501
+ function failure3(answer2, ctx, statusRoute = false) {
91924
92502
  const { refusal: refusal3 } = answer2;
91925
92503
  if (ctx.json && refusal3.body !== undefined)
91926
92504
  ctx.deps.stdout.write(`${JSON.stringify(refusal3.body)}
@@ -91988,7 +92566,7 @@ async function labStatus(args, ctx) {
91988
92566
  return 1;
91989
92567
  const answer2 = await connected.lab.status();
91990
92568
  if (!answer2.ok)
91991
- return failure2(answer2, ctx, true);
92569
+ return failure3(answer2, ctx, true);
91992
92570
  if (ctx.json)
91993
92571
  ctx.deps.stdout.write(`${JSON.stringify(answer2.body)}
91994
92572
  `);
@@ -92033,7 +92611,7 @@ async function labReplay(args, ctx) {
92033
92611
  const id = parsed2.positionals[0];
92034
92612
  const answer2 = action === "get" ? await connected2.lab.replayGet(id) : await connected2.lab.replayCancel(id);
92035
92613
  if (!answer2.ok)
92036
- return failure2(answer2, ctx);
92614
+ return failure3(answer2, ctx);
92037
92615
  if (action === "get")
92038
92616
  return replayOutput(answer2.body, ctx);
92039
92617
  if (ctx.json)
@@ -92099,7 +92677,7 @@ ${REPLAY_USAGE}`);
92099
92677
  };
92100
92678
  const started = await connected.lab.replay(body);
92101
92679
  if (!started.ok)
92102
- return failure2(started, ctx);
92680
+ return failure3(started, ctx);
92103
92681
  if (parsed.booleans.has("--no-wait")) {
92104
92682
  if (ctx.json)
92105
92683
  ctx.deps.stdout.write(`${JSON.stringify(started.body)}
@@ -92109,7 +92687,7 @@ ${REPLAY_USAGE}`);
92109
92687
  return 0;
92110
92688
  }
92111
92689
  const result = await connected.lab.waitForReplay(started.body.job_id);
92112
- return result.ok ? replayOutput(result.body, ctx) : failure2(result, ctx);
92690
+ return result.ok ? replayOutput(result.body, ctx) : failure3(result, ctx);
92113
92691
  }
92114
92692
  async function report(args, ctx, kind) {
92115
92693
  const flags = kind === "runners" ? ["--hours", "--min-wallets"] : ["--days", "--min-n"];
@@ -92134,7 +92712,7 @@ async function report(args, ctx, kind) {
92134
92712
  return 1;
92135
92713
  const answer2 = kind === "runners" ? await connected.lab.runners(q) : await connected.lab.edges(q);
92136
92714
  if (!answer2.ok)
92137
- return failure2(answer2, ctx);
92715
+ return failure3(answer2, ctx);
92138
92716
  if (ctx.json)
92139
92717
  ctx.deps.stdout.write(`${JSON.stringify(answer2.body)}
92140
92718
  `);
@@ -92172,7 +92750,7 @@ init_zod();
92172
92750
  init_args();
92173
92751
  init_client();
92174
92752
  init_evm_lite();
92175
- import { randomUUID as randomUUID8 } from "node:crypto";
92753
+ import { randomUUID as randomUUID9 } from "node:crypto";
92176
92754
 
92177
92755
  // src/fee-wallet.ts
92178
92756
  init_solana_lite();
@@ -92727,7 +93305,7 @@ async function launch(args, ctx) {
92727
93305
  const flags = parsed.values;
92728
93306
  if (flags["--quote-asset"])
92729
93307
  flags["--quote-asset"] = flags["--quote-asset"].toLowerCase();
92730
- const id = flags["--client-trade-id"] ?? `launch-${randomUUID8()}`;
93308
+ const id = flags["--client-trade-id"] ?? `launch-${randomUUID9()}`;
92731
93309
  const embedded = EMBEDDED_WALLET_NAMES.has(flags["--wallet"] ?? "");
92732
93310
  const dryRun = parsed.booleans.has("--dry-run");
92733
93311
  const check = parsed.booleans.has("--check");
@@ -93716,7 +94294,7 @@ init_render();
93716
94294
  init_solana_endpoint();
93717
94295
  init_solana_lite();
93718
94296
  init_trading();
93719
- import { randomUUID as randomUUID9 } from "node:crypto";
94297
+ import { randomUUID as randomUUID10 } from "node:crypto";
93720
94298
  init_swap();
93721
94299
  var LP_SCOPE = "lp:write";
93722
94300
  var CONFIRM_POLL_MS3 = 2000;
@@ -94019,7 +94597,7 @@ async function lpAdd(args, ctx) {
94019
94597
  if ("error" in parsed)
94020
94598
  return usage5(ctx, parsed.error);
94021
94599
  const flags = parsed.values;
94022
- const id = flags["--client-trade-id"] ?? `lp-${randomUUID9()}`;
94600
+ const id = flags["--client-trade-id"] ?? `lp-${randomUUID10()}`;
94023
94601
  if (parsed.positionals.length !== 2 || !flags["--amount"] || !flags["--wallet"] || !validClientId(id))
94024
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]");
94025
94603
  try {
@@ -94055,7 +94633,7 @@ async function lpRemove(args, ctx) {
94055
94633
  if ("error" in parsed)
94056
94634
  return usage5(ctx, parsed.error);
94057
94635
  const flags = parsed.values;
94058
- const id = flags["--client-trade-id"] ?? `lp-${randomUUID9()}`;
94636
+ const id = flags["--client-trade-id"] ?? `lp-${randomUUID10()}`;
94059
94637
  const percent2 = Number(flags["--percent"]);
94060
94638
  if (parsed.positionals.length !== 1 || !flags["--percent"] || !/^\d+(\.\d{1,2})?$/.test(flags["--percent"]) || !(percent2 > 0 && percent2 <= 100) || !validClientId(id))
94061
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]");
@@ -94087,7 +94665,7 @@ async function lpClaim(args, ctx) {
94087
94665
  if ("error" in parsed)
94088
94666
  return usage5(ctx, parsed.error);
94089
94667
  const flags = parsed.values;
94090
- const id = flags["--client-trade-id"] ?? `lp-${randomUUID9()}`;
94668
+ const id = flags["--client-trade-id"] ?? `lp-${randomUUID10()}`;
94091
94669
  if (parsed.positionals.length !== 1 || !validClientId(id))
94092
94670
  return usage5(ctx, "Usage: candle lp claim <position> [--wallet <tee>] [--client-trade-id <id>] [--rpc-url <url>] [--yes]");
94093
94671
  try {
@@ -94121,6 +94699,7 @@ var MCP_TOOL_NAMES = [
94121
94699
  "candle_launch_token",
94122
94700
  "candle_launch_and_seed",
94123
94701
  "candle_get_market",
94702
+ "candle_quote",
94124
94703
  "candle_get_feed",
94125
94704
  "candle_vamp_candidates",
94126
94705
  "candle_token_events",
@@ -94162,6 +94741,8 @@ var MCP_TOOL_NAMES = [
94162
94741
  "candle_transfer",
94163
94742
  "candle_sweep",
94164
94743
  "candle_get_wallets",
94744
+ "candle_get_trading_wallets",
94745
+ "candle_get_spendable_balances",
94165
94746
  "candle_get_profile_wallets",
94166
94747
  "candle_set_profile_wallets",
94167
94748
  "candle_get_profile_pnl",
@@ -94338,7 +94919,7 @@ init_bridge();
94338
94919
  init_client();
94339
94920
  init_evm_lite();
94340
94921
  init_human_output();
94341
- import { randomUUID as randomUUID10, sign as sign4 } from "node:crypto";
94922
+ import { randomUUID as randomUUID11, sign as sign4 } from "node:crypto";
94342
94923
 
94343
94924
  // src/hyperliquid.ts
94344
94925
  init_sha3();
@@ -98251,7 +98832,7 @@ async function perpsDeposit(args, ctx) {
98251
98832
  if (parsed.positionals.length !== 2 || !amount || !DEC_RE3.test(amount) || !isDepositAsset(asset))
98252
98833
  return usage8(ctx, DEPOSIT_USAGE);
98253
98834
  const { values } = parsed;
98254
- const id = values["--id"] ?? `deposit-${randomUUID10()}`;
98835
+ const id = values["--id"] ?? `deposit-${randomUUID11()}`;
98255
98836
  if (!DEPOSIT_ID_RE.test(id))
98256
98837
  return usage8(ctx, "--id must be 1 to 128 letters, digits, '.', '_', ':' or '-'.");
98257
98838
  const slippageBps = Number(values["--slippage-bps"] ?? "100");
@@ -99390,8 +99971,8 @@ async function read2(ctx, argv, command) {
99390
99971
  writeUsageFailure(ctx.deps, error.message, ctx.json);
99391
99972
  return 2;
99392
99973
  }
99393
- const failure3 = { code: error.code, message: error.message, retryable: error.retryable };
99394
- 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);
99395
99976
  return 1;
99396
99977
  }
99397
99978
  return tradingFailure(ctx, error);
@@ -99405,7 +99986,7 @@ var perpsCandles = (argv, ctx) => read2(ctx, argv, "candles");
99405
99986
  // src/commands/perps-withdraw.ts
99406
99987
  init_args();
99407
99988
  init_human_output();
99408
- import { randomUUID as randomUUID11 } from "node:crypto";
99989
+ import { randomUUID as randomUUID12 } from "node:crypto";
99409
99990
 
99410
99991
  // src/hyperliquid-withdraw.ts
99411
99992
  var CANDLE_PERPS_WITHDRAW_PINS = {
@@ -99649,7 +100230,7 @@ async function perpsWithdraw(args, ctx) {
99649
100230
  const amountMicros = parseWithdrawAmount(amount);
99650
100231
  if (amountMicros === null)
99651
100232
  return usage10(ctx, "The amount must be a positive USDC decimal with at most 6 decimals.");
99652
- const id = parsed.values["--id"] ?? `withdraw-${randomUUID11()}`;
100233
+ const id = parsed.values["--id"] ?? `withdraw-${randomUUID12()}`;
99653
100234
  if (!ID_RE.test(id))
99654
100235
  return usage10(ctx, "--id must be 1 to 128 letters, digits, '.', '_', ':' or '-'.");
99655
100236
  if (!refuseEnvPassphrase(ctx))
@@ -99788,7 +100369,7 @@ init_render();
99788
100369
  init_secret_store();
99789
100370
  init_solana_lite();
99790
100371
  init_errors2();
99791
- import { generateKeyPairSync as generateKeyPairSync3, randomUUID as randomUUID13 } from "node:crypto";
100372
+ import { generateKeyPairSync as generateKeyPairSync3, randomUUID as randomUUID14 } from "node:crypto";
99792
100373
 
99793
100374
  // src/x402/codec.ts
99794
100375
  var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
@@ -100398,7 +100979,7 @@ init_deps();
100398
100979
  init_render();
100399
100980
  init_solana_endpoint();
100400
100981
  init_solana_lite();
100401
- import { randomUUID as randomUUID12 } from "node:crypto";
100982
+ import { randomUUID as randomUUID13 } from "node:crypto";
100402
100983
 
100403
100984
  // src/x402/pay.ts
100404
100985
  init_client();
@@ -100746,7 +101327,7 @@ async function runPaidRead(ctx, input) {
100746
101327
  blockhash: blockhashFor(ctx),
100747
101328
  env: ctx.deps.env,
100748
101329
  now: ctx.deps.now,
100749
- newId: randomUUID12
101330
+ newId: randomUUID13
100750
101331
  });
100751
101332
  const line = paymentLine(paid);
100752
101333
  if (line && !ctx.json)
@@ -101358,7 +101939,7 @@ async function runPlanPayment(ctx, run2) {
101358
101939
  const transaction = serializePartiallySigned(transfer, signer.sign(transfer.message));
101359
101940
  const at3 = new Date(deps.now()).toISOString();
101360
101941
  const pending = {
101361
- id: randomUUID13(),
101942
+ id: randomUUID14(),
101362
101943
  plan: run2.plan,
101363
101944
  renew,
101364
101945
  path,
@@ -101669,7 +102250,8 @@ var PLAN_CAPABILITY_LABELS2 = {
101669
102250
  createLinkedWallets: "Create linked wallets",
101670
102251
  importLinkedWallets: "Import linked wallets",
101671
102252
  limitOrders: "Limit orders",
101672
- quant: "Quant deep analyses (Max)"
102253
+ quant: "Quant deep analyses (Max)",
102254
+ strategyLab: "Strategy lab (saved strategies stay Max)"
101673
102255
  };
101674
102256
  var PLAN_LABELS2 = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
101675
102257
  function planLabel2(plan2) {
@@ -101698,6 +102280,9 @@ function planTableRows2(table) {
101698
102280
  row2("Uploads per minute", (e) => count2(e.limits.uploadsPerMin)),
101699
102281
  row2("Linked wallets", (e) => count2(e.limits.linkedWallets))
101700
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
+ }
101701
102286
  const known = Object.keys(PLAN_CAPABILITY_LABELS2);
101702
102287
  const served = new Set;
101703
102288
  for (const entry of plans)
@@ -103280,12 +103865,12 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
103280
103865
  try {
103281
103866
  chainId = await rpc.chainId();
103282
103867
  } catch (error) {
103283
- const failure3 = message(error);
103868
+ const failure4 = message(error);
103284
103869
  return {
103285
103870
  balances: async () => ({
103286
103871
  byAddress: new Map(unique.map((address) => [address, { wei: null, usdg: null }])),
103287
103872
  unavailable: unique,
103288
- failure: failure3
103873
+ failure: failure4
103289
103874
  })
103290
103875
  };
103291
103876
  }
@@ -103297,7 +103882,7 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
103297
103882
  return {
103298
103883
  balances: async () => {
103299
103884
  const byAddress = new Map;
103300
- let failure3;
103885
+ let failure4;
103301
103886
  let next = 0;
103302
103887
  await Promise.all(Array.from({ length: Math.min(EVM_READS_IN_FLIGHT, unique.length) }, async () => {
103303
103888
  while (next < unique.length) {
@@ -103306,7 +103891,7 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
103306
103891
  try {
103307
103892
  return await call2();
103308
103893
  } catch (error) {
103309
- failure3 ??= message(error);
103894
+ failure4 ??= message(error);
103310
103895
  return null;
103311
103896
  }
103312
103897
  };
@@ -103319,7 +103904,7 @@ async function startEvmRead(addresses, rpcUrl, fetchFn) {
103319
103904
  const read3 = byAddress.get(address);
103320
103905
  return read3 === undefined || read3.wei === null || read3.usdg === null;
103321
103906
  });
103322
- return { byAddress, unavailable: unavailable6, ...failure3 === undefined ? {} : { failure: failure3 } };
103907
+ return { byAddress, unavailable: unavailable6, ...failure4 === undefined ? {} : { failure: failure4 } };
103323
103908
  }
103324
103909
  };
103325
103910
  }
@@ -103328,13 +103913,13 @@ async function readOwnRpc(addresses, solana, ctx) {
103328
103913
  const unique = [...new Set(addresses)];
103329
103914
  const lamports = new Map;
103330
103915
  let rateLimited = false;
103331
- let failure3;
103916
+ let failure4;
103332
103917
  const noteFailure = (error) => {
103333
103918
  if (isRateLimited(error)) {
103334
103919
  rateLimited = true;
103335
- failure3 ??= rateLimitedReadFailure(ctx, error);
103920
+ failure4 ??= rateLimitedReadFailure(ctx, error);
103336
103921
  } else
103337
- failure3 ??= describeRpcFailure(error);
103922
+ failure4 ??= describeRpcFailure(error);
103338
103923
  };
103339
103924
  for (let at3 = 0;at3 < unique.length; at3 += CHUNK) {
103340
103925
  const chunk = unique.slice(at3, at3 + CHUNK);
@@ -103396,7 +103981,7 @@ async function readOwnRpc(addresses, solana, ctx) {
103396
103981
  if (sol === null || held === null)
103397
103982
  unavailable6.push(address);
103398
103983
  }
103399
- return { byAddress, unavailable: unavailable6, ...failure3 === undefined ? {} : { failure: failure3 } };
103984
+ return { byAddress, unavailable: unavailable6, ...failure4 === undefined ? {} : { failure: failure4 } };
103400
103985
  }
103401
103986
  function solanaHoldings(read3, prices) {
103402
103987
  if (read3 === undefined || read3.lamports === null && read3.tokens === null)
@@ -103826,17 +104411,17 @@ async function profileUse(args, ctx) {
103826
104411
  let account = profile.account;
103827
104412
  let username = profile.username;
103828
104413
  if (apiKey) {
103829
- 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);
103830
104415
  if (live) {
103831
104416
  account = live;
103832
104417
  username = liveUsername;
103833
104418
  await deps.updateProfile(name2, { account: live, username: liveUsername, accountCachedAt: deps.now() });
103834
104419
  } else {
103835
104420
  if (json)
103836
- 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.
103837
104422
  `.trim());
103838
104423
  else
103839
- 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.`);
103840
104425
  }
103841
104426
  } else {
103842
104427
  if (json)
@@ -104170,8 +104755,8 @@ async function quantAsk(args, ctx) {
104170
104755
  if (turnId === undefined) {
104171
104756
  if (reason === "interrupted")
104172
104757
  return await stopped(ctx, client2, undefined, reason, timeoutS);
104173
- const failure3 = refusal3?.status === 402 && offer ? offerFailure(offer.status, offer.headers, offer.body) : turnRefusal(refusal3 ?? { code: "BAD_RESPONSE", status: 0, shape: "transport" });
104174
- 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);
104175
104760
  return 1;
104176
104761
  }
104177
104762
  if (stop.signal.aborted) {
@@ -105303,7 +105888,7 @@ init_presentation();
105303
105888
  init_render();
105304
105889
  init_trading();
105305
105890
  init_swap();
105306
- import { randomUUID as randomUUID14 } from "node:crypto";
105891
+ import { randomUUID as randomUUID15 } from "node:crypto";
105307
105892
  var withdrawal = exports_external.object({ withdrawal: exports_external.string(), amountRaw: exports_external.string(), endTime: exports_external.string(), claimable: exports_external.boolean() }).passthrough();
105308
105893
  var positionsSchema = exports_external.object({
105309
105894
  success: exports_external.literal(true),
@@ -105358,7 +105943,7 @@ async function runStake(action, args, ctx) {
105358
105943
  return 2;
105359
105944
  }
105360
105945
  const { values, positionals, booleans } = parsed;
105361
- const id = values["--client-stake-id"] ?? `stake-${randomUUID14()}`;
105946
+ const id = values["--client-stake-id"] ?? `stake-${randomUUID15()}`;
105362
105947
  const all = booleans.has("--all");
105363
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)) {
105364
105949
  writeUsageFailure(ctx.deps, usage12, ctx.json);
@@ -106448,7 +107033,7 @@ init_args();
106448
107033
  init_client();
106449
107034
  init_deps();
106450
107035
  init_human_output();
106451
- import { randomUUID as randomUUID15 } from "node:crypto";
107036
+ import { randomUUID as randomUUID16 } from "node:crypto";
106452
107037
  init_render();
106453
107038
  var EXIT_OUTCOME_UNKNOWN2 = 3;
106454
107039
  var EDIT_FIELDS = launchFlagFields.filter((field) => field.key === "description" || field.key.startsWith("socials."));
@@ -106574,7 +107159,7 @@ async function tokenEdit(args, ctx) {
106574
107159
  if (given2 !== undefined && (given2.length < 1 || given2.length > 128)) {
106575
107160
  return usage12("--idempotency-key must be 1 to 128 characters.");
106576
107161
  }
106577
- const idempotencyKey = given2 ?? randomUUID15();
107162
+ const idempotencyKey = given2 ?? randomUUID16();
106578
107163
  const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
106579
107164
  if (!apiKey) {
106580
107165
  writeLocalFailure(ctx.deps, {
@@ -106599,17 +107184,17 @@ async function tokenEdit(args, ctx) {
106599
107184
  return EXIT_OUTCOME_UNKNOWN2;
106600
107185
  }
106601
107186
  if (!result.ok) {
106602
- const failure3 = { apiUrl: ctx.apiUrl, authType: "key" };
107187
+ const failure4 = { apiUrl: ctx.apiUrl, authType: "key" };
106603
107188
  if (!unsettledCommit && !mayHaveCommitted(result)) {
106604
- writeFailure(ctx.deps, result, failure3, ctx.json);
107189
+ writeFailure(ctx.deps, result, failure4, ctx.json);
106605
107190
  return 1;
106606
107191
  }
106607
107192
  const retry = `Rerun the same command with --idempotency-key ${idempotencyKey}: Candle returns the first result instead of editing twice.`;
106608
107193
  if (ctx.json) {
106609
- 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 })}
106610
107195
  `);
106611
107196
  } else {
106612
- writeFailure(ctx.deps, result, failure3, false);
107197
+ writeFailure(ctx.deps, result, failure4, false);
106613
107198
  ctx.deps.stderr.write(`Idempotency key: ${idempotencyKey}
106614
107199
  ${retry}
106615
107200
  `);
@@ -106709,7 +107294,9 @@ init_render();
106709
107294
  init_solana_verify();
106710
107295
  init_trading();
106711
107296
  init_swap();
106712
- 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}$/;
106713
107300
  var TRANSFER_ASSETS = [...Object.keys(BASES), ...Object.keys(HOOD_BASES)];
106714
107301
  var BASE58_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
106715
107302
  async function readLinkedWallets(ctx, key) {
@@ -106842,9 +107429,26 @@ async function confirmTransfer(ctx, lines, yes) {
106842
107429
  throw new TradingError("CONFIRMATION_REQUIRED", "Run interactively to confirm, or use --yes for an ordinary transfer prompt.");
106843
107430
  return (await ctx.deps.promptLine("Proceed? [y/N] ")).trim().toLowerCase() === "y";
106844
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
+ }
106845
107447
  async function transfer(args, ctx) {
107448
+ if (args[0] === "status")
107449
+ return await transferStatus(args.slice(1), ctx);
106846
107450
  const parsed = parseArgs(args, {
106847
- valueFlags: ["--wallet", "--to", "--asset", "--mint", "--token", "--amount", "--rpc-url"],
107451
+ valueFlags: ["--wallet", "--to", "--asset", "--mint", "--token", "--amount", "--rpc-url", "--client-id"],
106848
107452
  booleanFlags: ["--yes"]
106849
107453
  });
106850
107454
  if ("error" in parsed) {
@@ -106853,7 +107457,7 @@ async function transfer(args, ctx) {
106853
107457
  }
106854
107458
  const flags2 = parsed.values;
106855
107459
  const asset = flags2["--asset"]?.toUpperCase();
106856
- 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"])) {
106857
107461
  writeUsageFailure(ctx.deps, USAGE18, ctx.json);
106858
107462
  return 2;
106859
107463
  }
@@ -106900,7 +107504,8 @@ async function transfer(args, ctx) {
106900
107504
  chain: "solana",
106901
107505
  ...asset ? { asset } : { mint: flags2["--mint"] },
106902
107506
  amountRaw,
106903
- to: destination.address
107507
+ to: destination.address,
107508
+ ...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {}
106904
107509
  });
106905
107510
  const transferId = built.transferId;
106906
107511
  const unsigned = built.unsignedTransactionsBase64;
@@ -106936,6 +107541,7 @@ async function transfer(args, ctx) {
106936
107541
  const receipt = {
106937
107542
  ...result,
106938
107543
  transferId,
107544
+ ...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {},
106939
107545
  walletId: wallet3.id,
106940
107546
  wallet: safeText(wallet3.address),
106941
107547
  destination,
@@ -106992,7 +107598,8 @@ async function hoodTransfer(ctx, args) {
106992
107598
  chain: "hood",
106993
107599
  ...asset ? { asset } : { mint: tokenAddress },
106994
107600
  amountRaw: requested,
106995
- to: destination.address
107601
+ to: destination.address,
107602
+ ...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {}
106996
107603
  });
106997
107604
  const parsed = sequencedSchema.safeParse(built);
106998
107605
  if (!parsed.success)
@@ -107029,6 +107636,7 @@ async function hoodTransfer(ctx, args) {
107029
107636
  const receipt = {
107030
107637
  ...run2.final,
107031
107638
  transferId,
107639
+ ...flags2["--client-id"] !== undefined ? { clientTransferId: flags2["--client-id"] } : {},
107032
107640
  chain: "hood",
107033
107641
  walletId: wallet3.id,
107034
107642
  wallet: safeText(wallet3.address),
@@ -107701,10 +108309,10 @@ candle update`);
107701
108309
  } catch (error) {
107702
108310
  steps.fail("installing");
107703
108311
  await discard(deps, helper.tmpPath);
107704
- const failure3 = notWritable(dir, error);
108312
+ const failure4 = notWritable(dir, error);
107705
108313
  writeScreenLocalFailure(deps, {
107706
- ...failure3,
107707
- 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}.`
107708
108316
  }, json);
107709
108317
  return 1;
107710
108318
  }
@@ -108635,8 +109243,8 @@ async function vaultDemoteBatch(args, ctx) {
108635
109243
  row2.label,
108636
109244
  row2.destination ?? `none (${row2.destinationNote})`
108637
109245
  ]), []);
108638
- for (const skipped of outcomes) {
108639
- 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}
108640
109248
  `);
108641
109249
  }
108642
109250
  if (!yes) {
@@ -108744,12 +109352,12 @@ async function demoteOne(ctx, session, row2, values, booleans) {
108744
109352
  }
108745
109353
  }
108746
109354
  outcome.results = results;
108747
- const failure3 = [...results].reverse().find((doc) => typeof doc === "object" && doc !== null && doc.ok === false);
108748
- if (failure3 !== undefined && exitCode !== 0) {
108749
- if (typeof failure3.code === "string")
108750
- outcome.code = failure3.code;
108751
- if (typeof failure3.message === "string")
108752
- 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;
108753
109361
  }
108754
109362
  }
108755
109363
  if (thrown !== undefined)
@@ -110407,6 +111015,7 @@ init_solana_endpoint();
110407
111015
  init_errors2();
110408
111016
  init_evm_tee();
110409
111017
  init_evm_transfer();
111018
+ init_format();
110410
111019
 
110411
111020
  // src/vault/funding-receipts.ts
110412
111021
  init_errors2();
@@ -110591,6 +111200,8 @@ async function vaultFund(args, ctx) {
110591
111200
  if (parsed.values["--from"] !== undefined) {
110592
111201
  return usage(ctx, "--from applies to an external wallet only; a TEE wallet is funded from its pinned vault key.");
110593
111202
  }
111203
+ if (isRetiredTee(teeEntry))
111204
+ return refuseRetiredFund(ctx, teeAddress);
110594
111205
  const reconciled = await reconcileFundingReceipts(vault, [teeAddress, ...teeEntry.tee?.vaultDestination ? [teeEntry.tee.vaultDestination] : []], solana.rpc, ctx);
110595
111206
  if (reconciled !== null)
110596
111207
  return reconciled;
@@ -110712,6 +111323,8 @@ async function vaultFundEvm(ctx, parsed, teeAddress) {
110712
111323
  }, ctx.json);
110713
111324
  return 1;
110714
111325
  }
111326
+ if (isRetiredTee(teeEntry))
111327
+ return refuseRetiredFund(ctx, teeEntry.address);
110715
111328
  if (teeEntry.tee?.remoteAuthority !== "verified-active" || teeEntry.tee.stopRequestedAt !== undefined) {
110716
111329
  writeLocalFailure(ctx.deps, {
110717
111330
  code: "TEE_WALLET_NOT_VERIFIED",
@@ -110746,6 +111359,14 @@ async function vaultFundEvm(ctx, parsed, teeAddress) {
110746
111359
  }, client2.rpc);
110747
111360
  });
110748
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
+ }
110749
111370
 
110750
111371
  // src/commands/vault-import-legacy.ts
110751
111372
  init_args();
@@ -110957,14 +111578,14 @@ async function vaultImportLegacy(args, ctx) {
110957
111578
  const digest = sourceDigest(vault.file.vaultId, new TextEncoder().encode(legacyRaw));
110958
111579
  const already = new Set(vault.index.entries.map((entry) => entry.address));
110959
111580
  const toMigrate = legacyEntries.filter((entry) => !already.has(entry.address));
110960
- const skipped = legacyEntries.length - toMigrate.length;
111581
+ const skipped2 = legacyEntries.length - toMigrate.length;
110961
111582
  if (toMigrate.length === 0) {
110962
111583
  await recordMigrationSidecar(vaultPath, vault, fromPath, digest, deps.now());
110963
111584
  return reportDone(ctx, {
110964
111585
  path: fromPath,
110965
111586
  vaultPath,
110966
111587
  migrated: 0,
110967
- skipped,
111588
+ skipped: skipped2,
110968
111589
  addresses: [],
110969
111590
  digest,
110970
111591
  idempotent: true
@@ -111004,7 +111625,7 @@ async function vaultImportLegacy(args, ctx) {
111004
111625
  path: fromPath,
111005
111626
  vaultPath,
111006
111627
  migrated: newEntries.length,
111007
- skipped,
111628
+ skipped: skipped2,
111008
111629
  addresses: newEntries.map((entry) => ({
111009
111630
  address: entry.address,
111010
111631
  lifecycle: entry.tee?.lifecycle ?? "local-candidate",
@@ -111136,7 +111757,7 @@ async function readLamports(addresses, solana, ctx) {
111136
111757
  const { rpc } = solana;
111137
111758
  const lamports = new Map;
111138
111759
  const unavailable6 = [];
111139
- let failure3;
111760
+ let failure4;
111140
111761
  let rateLimited = false;
111141
111762
  for (let at3 = 0;at3 < addresses.length; at3 += CHUNK2) {
111142
111763
  const chunk = addresses.slice(at3, at3 + CHUNK2);
@@ -111152,12 +111773,12 @@ async function readLamports(addresses, solana, ctx) {
111152
111773
  unavailable6.push(...chunk);
111153
111774
  if (isRateLimited(error)) {
111154
111775
  rateLimited = true;
111155
- failure3 ??= rateLimitedReadFailure(ctx, error);
111776
+ failure4 ??= rateLimitedReadFailure(ctx, error);
111156
111777
  } else
111157
- failure3 ??= describeRpcFailure(error);
111778
+ failure4 ??= describeRpcFailure(error);
111158
111779
  }
111159
111780
  }
111160
- return { lamports, unavailable: unavailable6, ...failure3 === undefined ? {} : { failure: failure3 } };
111781
+ return { lamports, unavailable: unavailable6, ...failure4 === undefined ? {} : { failure: failure4 } };
111161
111782
  }
111162
111783
  async function readEvmBalances(addresses, rpcUrl, fetchFn) {
111163
111784
  const rpc = createEvmRpc(rpcUrl, fetchFn);
@@ -111256,17 +111877,17 @@ async function vaultList(args, ctx) {
111256
111877
  }
111257
111878
  let lamports = new Map;
111258
111879
  let unavailable6 = [];
111259
- let failure3;
111880
+ let failure4;
111260
111881
  if (balances && solana.length > 0 && solanaClient !== undefined) {
111261
111882
  const outcome = await readLamports(solana.map((entry) => entry.address), solanaClient, ctx);
111262
111883
  lamports = outcome.lamports;
111263
111884
  unavailable6 = outcome.unavailable;
111264
- failure3 = outcome.failure;
111885
+ failure4 = outcome.failure;
111265
111886
  }
111266
111887
  const solanaComplete = unavailable6.length === 0;
111267
111888
  const totalLamports = [...lamports.values()].reduce((sum, value) => sum + value, 0n);
111268
111889
  if (!solanaComplete) {
111269
- 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.
111270
111891
  `.trim());
111271
111892
  }
111272
111893
  const evm = balances ? matched.filter((entry) => entry.chain === "evm") : [];
@@ -111498,8 +112119,8 @@ function unreadableRoomLine(reason) {
111498
112119
  `;
111499
112120
  }
111500
112121
  var RESTORED_SENTENCE = "The vault entry is back as it was: nothing left this machine.";
111501
- function restoresPreImportEntry(failure3) {
111502
- 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;
111503
112124
  }
111504
112125
  function restoreRefusedFailure(init, error) {
111505
112126
  if (error instanceof VaultError && error.code === "VAULT_CHANGED") {
@@ -111777,7 +112398,7 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
111777
112398
  });
111778
112399
  const code = imported.exit;
111779
112400
  if (imported.failure !== undefined) {
111780
- let failure3 = {
112401
+ let failure4 = {
111781
112402
  code: imported.failure.code,
111782
112403
  message: imported.failure.message,
111783
112404
  ...imported.failure.suggestion !== undefined ? { suggestion: imported.failure.suggestion } : {}
@@ -111785,12 +112406,12 @@ async function promoteEvmInPlace(input, subjectLabel, sweepTo) {
111785
112406
  if (restoresPreImportEntry(imported.failure)) {
111786
112407
  try {
111787
112408
  vault = hold(await commitVault(vault, { index: preImportIndex }, ctx.deps));
111788
- failure3 = { ...failure3, message: `${failure3.message} ${RESTORED_SENTENCE}` };
112409
+ failure4 = { ...failure4, message: `${failure4.message} ${RESTORED_SENTENCE}` };
111789
112410
  } catch (error) {
111790
- failure3 = restoreRefusedFailure(failure3, error);
112411
+ failure4 = restoreRefusedFailure(failure4, error);
111791
112412
  }
111792
112413
  }
111793
- writeLocalFailure(ctx.deps, failure3, ctx.json);
112414
+ writeLocalFailure(ctx.deps, failure4, ctx.json);
111794
112415
  return code;
111795
112416
  }
111796
112417
  let rebound;
@@ -112236,7 +112857,7 @@ async function promoteInPlace(ctx, parsed, subjectLabel, toKey) {
112236
112857
  });
112237
112858
  const code = imported.exit;
112238
112859
  if (imported.failure !== undefined) {
112239
- let failure3 = {
112860
+ let failure4 = {
112240
112861
  code: imported.failure.code,
112241
112862
  message: imported.failure.message,
112242
112863
  ...imported.failure.suggestion !== undefined ? { suggestion: imported.failure.suggestion } : {}
@@ -112244,12 +112865,12 @@ async function promoteInPlace(ctx, parsed, subjectLabel, toKey) {
112244
112865
  if (restoresPreImportEntry(imported.failure)) {
112245
112866
  try {
112246
112867
  vault = hold(await commitVault(vault, { index: preImportIndex }, ctx.deps));
112247
- failure3 = { ...failure3, message: `${failure3.message} ${RESTORED_SENTENCE}` };
112868
+ failure4 = { ...failure4, message: `${failure4.message} ${RESTORED_SENTENCE}` };
112248
112869
  } catch (error) {
112249
- failure3 = restoreRefusedFailure(failure3, error);
112870
+ failure4 = restoreRefusedFailure(failure4, error);
112250
112871
  }
112251
112872
  }
112252
- writeLocalFailure(ctx.deps, failure3, ctx.json);
112873
+ writeLocalFailure(ctx.deps, failure4, ctx.json);
112253
112874
  return code;
112254
112875
  }
112255
112876
  let rebound;
@@ -112291,10 +112912,10 @@ async function promoteInPlace(ctx, parsed, subjectLabel, toKey) {
112291
112912
  async function resumePromote(ctx, vault, entry, reopen, path, hold, confirmations) {
112292
112913
  const announceGrant = confirmations.announceGrant ?? true;
112293
112914
  const report2 = confirmations.report ?? "write";
112294
- const fail4 = (failure3, exit) => {
112915
+ const fail4 = (failure4, exit) => {
112295
112916
  if (report2 === "write")
112296
- writeLocalFailure(ctx.deps, failure3, ctx.json);
112297
- return { exit, ...report2 === "return" ? { failure: failure3 } : {} };
112917
+ writeLocalFailure(ctx.deps, failure4, ctx.json);
112918
+ return { exit, ...report2 === "return" ? { failure: failure4 } : {} };
112298
112919
  };
112299
112920
  const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
112300
112921
  if (!apiKey) {
@@ -112440,10 +113061,10 @@ async function runTeeImport(ctx, opts) {
112440
113061
  const closeReopened = opts.closeReopened ?? true;
112441
113062
  const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
112442
113063
  if (!apiKey) {
112443
- 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" };
112444
113065
  if (report2 === "write")
112445
- writeLocalFailure(ctx.deps, failure3, ctx.json);
112446
- return { exit: 1, ...report2 === "return" ? { failure: failure3 } : {} };
113066
+ writeLocalFailure(ctx.deps, failure4, ctx.json);
113067
+ return { exit: 1, ...report2 === "return" ? { failure: failure4 } : {} };
112447
113068
  }
112448
113069
  opts.onImport?.();
112449
113070
  const flow = await runImportFlow({
@@ -112459,28 +113080,28 @@ async function runTeeImport(ctx, opts) {
112459
113080
  ...opts.keySigner !== undefined ? { keySigner: opts.keySigner } : {}
112460
113081
  });
112461
113082
  if (!flow.ok) {
112462
- const failure3 = flow.failure;
112463
- if (failure3.kind === "api") {
113083
+ const failure4 = flow.failure;
113084
+ if (failure4.kind === "api") {
112464
113085
  const render2 = { apiUrl: ctx.apiUrl, authType: "key" };
112465
113086
  if (report2 === "write") {
112466
- writeFailure(ctx.deps, failure3.response, render2, ctx.json);
113087
+ writeFailure(ctx.deps, failure4.response, render2, ctx.json);
112467
113088
  return { exit: 1 };
112468
113089
  }
112469
- const suggestion = suggestionFor(failure3.response, render2);
113090
+ const suggestion = suggestionFor(failure4.response, render2);
112470
113091
  return {
112471
113092
  exit: 1,
112472
113093
  failure: {
112473
- code: errorEnvelope(failure3.response, render2).code,
112474
- message: renderError(failure3.response, render2),
113094
+ code: errorEnvelope(failure4.response, render2).code,
113095
+ message: renderError(failure4.response, render2),
112475
113096
  ...suggestion ? { suggestion } : {},
112476
- stage: failure3.stage,
112477
- status: failure3.response.status
113097
+ stage: failure4.stage,
113098
+ status: failure4.response.status
112478
113099
  }
112479
113100
  };
112480
113101
  }
112481
113102
  const local = {
112482
- code: failure3.kind === "signer-store" ? "SIGNER_STORE_FAILED" : "SIGNER_COMMIT_FAILED",
112483
- 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)}`,
112484
113105
  suggestion: "The vault entry is import-pending. Fix the error and re-run promote to resume."
112485
113106
  };
112486
113107
  if (report2 === "write")
@@ -112744,15 +113365,15 @@ async function preflightBatch(index, rows, opts) {
112744
113365
  const failures = [];
112745
113366
  const pinnedBy = new Map;
112746
113367
  const promotedBy = new Map;
112747
- const refuse3 = (row2, failure3) => {
113368
+ const refuse3 = (row2, failure4) => {
112748
113369
  failures.push({
112749
113370
  line: row2.line,
112750
113371
  label: row2.label,
112751
113372
  destination: row2.destination,
112752
- code: failure3.code,
112753
- message: failure3.message,
112754
- ...failure3.suggestion !== undefined ? { suggestion: failure3.suggestion } : {},
112755
- ...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 } : {}
112756
113377
  });
112757
113378
  };
112758
113379
  const refuseWith = (row2, error, why) => {
@@ -113195,8 +113816,8 @@ ${table}
113195
113816
  rateLimited: 0,
113196
113817
  elapsedMs: 0
113197
113818
  };
113198
- const skipped = planned.map((item) => skippedResult(item));
113199
- const rebindable = rebindPhase === undefined ? undefined : splitRebindable(skipped);
113819
+ const skipped2 = planned.map((item) => skippedResult(item));
113820
+ const rebindable = rebindPhase === undefined ? undefined : splitRebindable(skipped2);
113200
113821
  if (rebindPhase === undefined || rebindable === undefined || rebindable.wallets.length === 0) {
113201
113822
  deps.stderr.write(`
113202
113823
  ${table}
@@ -113206,7 +113827,7 @@ ${table}
113206
113827
  return finish(ctx, {
113207
113828
  file: pairsFile,
113208
113829
  rows: rows.length,
113209
- keys: skipped,
113830
+ keys: skipped2,
113210
113831
  destinations: destinations2,
113211
113832
  exit: 0,
113212
113833
  controlledBy,
@@ -113227,11 +113848,11 @@ ${renderControlledBy(controlledBy, n)}
113227
113848
  if (typed.trim().toLowerCase() !== CONFIRM_WORD) {
113228
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.` });
113229
113850
  }
113230
- const rebound2 = await runRebindPhase(ctx, rebindPhase, skipped);
113851
+ const rebound2 = await runRebindPhase(ctx, rebindPhase, skipped2);
113231
113852
  return finish(ctx, {
113232
113853
  file: pairsFile,
113233
113854
  rows: rows.length,
113234
- keys: skipped,
113855
+ keys: skipped2,
113235
113856
  destinations: destinations2,
113236
113857
  exit: 0,
113237
113858
  controlledBy,
@@ -113304,24 +113925,24 @@ ${renderControlledBy(controlledBy, acting.length)}
113304
113925
  keySigner: keySignerImport(rebindPhase?.toKey)
113305
113926
  });
113306
113927
  if (imported.failure !== undefined || imported.submitted === undefined) {
113307
- let failure3 = imported.failure ?? {
113928
+ let failure4 = imported.failure ?? {
113308
113929
  code: "VAULT_WRITE_FAILED",
113309
113930
  message: "The import did not complete."
113310
113931
  };
113311
- if (restoresPreImportEntry(failure3)) {
113932
+ if (restoresPreImportEntry(failure4)) {
113312
113933
  try {
113313
113934
  current = hold(await commitVault(current, { index: preImportIndex }, deps));
113314
- failure3 = { ...failure3, message: `${failure3.message} ${RESTORED_SENTENCE}` };
113935
+ failure4 = { ...failure4, message: `${failure4.message} ${RESTORED_SENTENCE}` };
113315
113936
  } catch (error) {
113316
- failure3 = restoreRefusedFailure(failure3, error);
113937
+ failure4 = restoreRefusedFailure(failure4, error);
113317
113938
  }
113318
113939
  }
113319
113940
  stopped2 = {
113320
113941
  line: row3.line,
113321
113942
  exit: imported.exit,
113322
- code: failure3.code,
113323
- message: failure3.message,
113324
- ...failure3.suggestion !== undefined ? { suggestion: failure3.suggestion } : {}
113943
+ code: failure4.code,
113944
+ message: failure4.message,
113945
+ ...failure4.suggestion !== undefined ? { suggestion: failure4.suggestion } : {}
113325
113946
  };
113326
113947
  break;
113327
113948
  }
@@ -113490,7 +114111,7 @@ async function verifySubjectSecret(vault, subject) {
113490
114111
  }
113491
114112
  async function reconcileForPreflight(ctx, row2, subject, resolved) {
113492
114113
  const { onDisk } = resolved;
113493
- const refuse3 = (failure3) => ({ ok: false, failure: failure3 });
114114
+ const refuse3 = (failure4) => ({ ok: false, failure: failure4 });
113494
114115
  const apiKey = await resolveApiKey(ctx.deps, ctx.profile);
113495
114116
  if (!apiKey) {
113496
114117
  return refuse3({
@@ -113616,11 +114237,11 @@ function renderBatchTable(planned, opts) {
113616
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 }));
113617
114238
  }
113618
114239
  function renderFooter(opts) {
113619
- const skipped = opts.planned.filter((item) => item.kind === "skip").length;
114240
+ const skipped2 = opts.planned.filter((item) => item.kind === "skip").length;
113620
114241
  const resumes = opts.planned.filter((item) => item.kind === "resume").length;
113621
114242
  const promotes = opts.planned.filter((item) => item.kind === "promote").length;
113622
114243
  const lines = [
113623
- `${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.`
113624
114245
  ];
113625
114246
  if (opts.destinations.length > 0) {
113626
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(" · ")}`);
@@ -113670,7 +114291,7 @@ ${opts.landed} of ${opts.acting} rows landed and ARE in the vault; the vault is
113670
114291
  function finish(ctx, opts) {
113671
114292
  const promoted = opts.keys.filter((r) => r.state === "promoted").length;
113672
114293
  const resumed = opts.keys.filter((r) => r.state === "resumed").length;
113673
- const skipped = opts.keys.filter((r) => r.state === "skipped").length;
114294
+ const skipped2 = opts.keys.filter((r) => r.state === "skipped").length;
113674
114295
  const unverified = opts.keys.filter((r) => r.state !== "skipped" && r.remoteAuthority !== "verified-active");
113675
114296
  const rebindFailed = opts.rebound?.run !== undefined && !opts.rebound.run.ok;
113676
114297
  const exit = rebindFailed ? Math.max(opts.exit, 1) : opts.exit;
@@ -113683,7 +114304,7 @@ function finish(ctx, opts) {
113683
114304
  rows: opts.rows,
113684
114305
  promoted,
113685
114306
  resumed,
113686
- skipped,
114307
+ skipped: skipped2,
113687
114308
  destinations: opts.destinations.map(({ label, address, keys }) => ({ label, address, keys })),
113688
114309
  keys: opts.rebound === undefined ? opts.keys : keysWithRebind(opts.keys, opts.rebound.phase, opts.rebound.run),
113689
114310
  controlledBy: controlledByJson(opts.controlledBy),
@@ -113695,7 +114316,7 @@ function finish(ctx, opts) {
113695
114316
  writeFields(ctx.deps, "stdout", [
113696
114317
  ["Promoted", promoted],
113697
114318
  ["Resumed", resumed],
113698
- ["Already promoted (skipped)", skipped],
114319
+ ["Already promoted (skipped)", skipped2],
113699
114320
  ["Commit", "one unlock, each committed on its own"]
113700
114321
  ]);
113701
114322
  if (unverified.length > 0) {
@@ -114775,10 +115396,12 @@ function describeEnvelope(envelope, facts) {
114775
115396
  init_args();
114776
115397
  init_deps();
114777
115398
  init_evm_lite();
115399
+ init_render();
114778
115400
  init_solana_endpoint();
114779
115401
  init_errors2();
114780
115402
  init_evm_tee();
114781
115403
  init_evm_transfer();
115404
+ init_format();
114782
115405
  init_promote_support();
114783
115406
  init_store();
114784
115407
  init_vault_support();
@@ -114834,6 +115457,15 @@ async function vaultTransfer(args, ctx) {
114834
115457
  return usage(ctx, `No vault key or promoted wallet matches --from ${fromLabel}.`);
114835
115458
  }
114836
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
+ }
114837
115469
  if (fromEntry.chain === "evm") {
114838
115470
  const evmRpc = resolveEvmRpcUrl(rpcUrlFlag, ctx.deps.env[EVM_RPC_URL_ENV], "--rpc-url");
114839
115471
  if ("error" in evmRpc)
@@ -115266,8 +115898,8 @@ async function setAllowLaunch(args, ctx, enabled) {
115266
115898
  for (const message of new Set(notEnforced.map((item) => item.message))) {
115267
115899
  notice(deps, "stderr", "danger", `${message} (ALLOW_LAUNCH_NOT_ENFORCED)`);
115268
115900
  }
115269
- for (const failure3 of failed) {
115270
- 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})` : ""}.`);
115271
115903
  }
115272
115904
  return failed.length > 0 || notEnforced.length > 0 ? 1 : 0;
115273
115905
  }
@@ -115525,7 +116157,7 @@ init_solana_alt();
115525
116157
  init_solana_lite();
115526
116158
  init_trading();
115527
116159
  init_swap();
115528
- import { randomUUID as randomUUID16 } from "node:crypto";
116160
+ import { randomUUID as randomUUID17 } from "node:crypto";
115529
116161
  var USAGE21 = "Usage: candle wallet close-empty [--wallet embedded|<tee address|label>] [--keep <mint>]... [--client-trade-id <id>] [--preview] [--yes] [--json]";
115530
116162
  var BASE58_ADDRESS2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
115531
116163
  var LISTED_ROWS = 50;
@@ -115813,7 +116445,7 @@ async function walletsCloseEmpty(args, ctx) {
115813
116445
  return 2;
115814
116446
  }
115815
116447
  const named = parsed.values["--client-trade-id"];
115816
- const id = named ?? `close-${randomUUID16()}`;
116448
+ const id = named ?? `close-${randomUUID17()}`;
115817
116449
  if (parsed.positionals.length !== 0 || !validClientId(id) || !lifted.keep.every((m) => BASE58_ADDRESS2.test(m))) {
115818
116450
  writeUsageFailure(ctx.deps, USAGE21, ctx.json);
115819
116451
  return 2;
@@ -116091,11 +116723,11 @@ async function verifyProfileAccount(ctx, config) {
116091
116723
  const apiKey = await deps.store.get(profileSecretRef(profile, "apiKey"));
116092
116724
  if (!apiKey)
116093
116725
  return { ok: true, skipped: "no stored key" };
116094
- const { account: live, failure: failure3 } = await fetchAccount(deps, ctx.apiUrl, apiKey);
116726
+ const { account: live, failure: failure4 } = await fetchAccount(deps, ctx.apiUrl, apiKey);
116095
116727
  if (!live) {
116096
116728
  return {
116097
116729
  ok: true,
116098
- 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}.`
116099
116731
  };
116100
116732
  }
116101
116733
  if (live !== cached) {
@@ -116128,6 +116760,7 @@ init_profiles();
116128
116760
  init_release();
116129
116761
  init_render();
116130
116762
  init_secret_store();
116763
+ init_trading_checks();
116131
116764
 
116132
116765
  // src/update-notice.ts
116133
116766
  init_client();
@@ -116375,6 +117008,7 @@ var COMMANDS = {
116375
117008
  tee: {
116376
117009
  subcommands: {
116377
117010
  new: teeNew,
117011
+ check: teeCheck,
116378
117012
  enable: teeEnable,
116379
117013
  fund: teeFund,
116380
117014
  status: teeStatus,