@candledottv/cli 0.13.9 → 0.13.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/index.js +955 -114
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3476,7 +3476,7 @@ var init_terminal_theme = __esm(() => {
|
|
|
3476
3476
|
});
|
|
3477
3477
|
|
|
3478
3478
|
// src/version.ts
|
|
3479
|
-
var CLI_VERSION = "0.13.
|
|
3479
|
+
var CLI_VERSION = "0.13.11";
|
|
3480
3480
|
|
|
3481
3481
|
// src/presentation.ts
|
|
3482
3482
|
function terminalStyle(deps, stream, machine = false) {
|
|
@@ -14140,6 +14140,7 @@ __export(exports_store, {
|
|
|
14140
14140
|
serializeVault: () => serializeVault,
|
|
14141
14141
|
sealKeyBlob: () => sealKeyBlob,
|
|
14142
14142
|
sealIndex: () => sealIndex,
|
|
14143
|
+
reopenWithHeldDek: () => reopenWithHeldDek,
|
|
14143
14144
|
readVaultRaw: () => readVaultRaw,
|
|
14144
14145
|
ownSecret: () => ownSecret,
|
|
14145
14146
|
legacyWalletsPath: () => legacyWalletsPath,
|
|
@@ -14203,6 +14204,17 @@ async function unlockVault(path, raw, request, opts = {}) {
|
|
|
14203
14204
|
wipe(dek);
|
|
14204
14205
|
throw new VaultError("VAULT_UNLOCK_FAILED", "The unwrapped key is the wrong length; this file is corrupt.");
|
|
14205
14206
|
}
|
|
14207
|
+
return openWithDek(path, raw, file, envelope, dek);
|
|
14208
|
+
}
|
|
14209
|
+
async function reopenWithHeldDek(path, raw, held) {
|
|
14210
|
+
const file = parseVaultFile(raw);
|
|
14211
|
+
if (file.vaultId !== held.file.vaultId) {
|
|
14212
|
+
throw new VaultError("VAULT_UNLOCK_FAILED", `${path} is not the vault this command unlocked.`);
|
|
14213
|
+
}
|
|
14214
|
+
const envelope = file.envelopes.find((candidate) => candidate.id === held.envelope.id) ?? held.envelope;
|
|
14215
|
+
return openWithDek(path, raw, file, envelope, new Uint8Array(held.dek));
|
|
14216
|
+
}
|
|
14217
|
+
async function openWithDek(path, raw, file, envelope, dek) {
|
|
14206
14218
|
try {
|
|
14207
14219
|
const payloadKey = await derivePayloadKey(dek, unb64u(file.vaultId, "vaultId"));
|
|
14208
14220
|
const indexBytes = await open2(payloadKey, file.index, canonicalHeader(file), {
|
|
@@ -14548,6 +14560,7 @@ var init_ownership = __esm(() => {
|
|
|
14548
14560
|
"token clusters",
|
|
14549
14561
|
"wallets",
|
|
14550
14562
|
"swap status",
|
|
14563
|
+
"launch status",
|
|
14551
14564
|
"fees",
|
|
14552
14565
|
"lp pools",
|
|
14553
14566
|
"lp positions",
|
|
@@ -15758,7 +15771,9 @@ var init_trading = __esm(() => {
|
|
|
15758
15771
|
transaction: exports_external.string().min(1),
|
|
15759
15772
|
maxDebitLamports: exports_external.string().regex(/^\d+$/),
|
|
15760
15773
|
expiresAt: exports_external.number().finite(),
|
|
15761
|
-
fee: feeSchema.optional()
|
|
15774
|
+
fee: feeSchema.optional(),
|
|
15775
|
+
feeWallet: exports_external.string().optional(),
|
|
15776
|
+
feeWalletSource: exports_external.string().optional()
|
|
15762
15777
|
});
|
|
15763
15778
|
walletSchema = exports_external.object({
|
|
15764
15779
|
id: exports_external.string(),
|
|
@@ -16872,12 +16887,48 @@ function describeSwapEvidence(ctx, details, terminal) {
|
|
|
16872
16887
|
notice(ctx.deps, "stderr", "warning", `The trade landed${typeof details.signature === "string" ? ` (${safeText(details.signature)})` : ""} and a later step failed. Do not retry or use a new id.
|
|
16873
16888
|
`.trim());
|
|
16874
16889
|
}
|
|
16890
|
+
function tradeSignatures(result) {
|
|
16891
|
+
const nested = (name) => result[name] && typeof result[name] === "object" ? result[name] : {};
|
|
16892
|
+
const payload = nested("payload");
|
|
16893
|
+
const job = nested("job");
|
|
16894
|
+
const sources = [result, payload, job];
|
|
16895
|
+
const operation = job.operation && typeof job.operation === "object" ? job.operation : {};
|
|
16896
|
+
const legs = [...sources.map((source) => source.landedLegs), operation.legs].flatMap((value) => Array.isArray(value) ? value.filter((leg) => leg !== null && typeof leg === "object") : []);
|
|
16897
|
+
const primary = sources.map((source) => source.signature).find(isText) ?? legs.find((leg) => (leg.kind === "trade" || leg.kind === "bridgeDeposit") && isText(leg.hash))?.hash;
|
|
16898
|
+
return [
|
|
16899
|
+
...new Set([
|
|
16900
|
+
...isText(primary) ? [primary] : [],
|
|
16901
|
+
...sources.flatMap((source) => [
|
|
16902
|
+
...isText(source.signature) ? [source.signature] : [],
|
|
16903
|
+
...textsOf(source.signatures),
|
|
16904
|
+
...textsOf(source.hashes),
|
|
16905
|
+
...isText(source.feeSignature) ? [source.feeSignature] : []
|
|
16906
|
+
]),
|
|
16907
|
+
...legs.map((leg) => leg.hash).filter(isText)
|
|
16908
|
+
])
|
|
16909
|
+
];
|
|
16910
|
+
}
|
|
16911
|
+
function swapJsonResult(result) {
|
|
16912
|
+
const signatures = tradeSignatures(result);
|
|
16913
|
+
const inner = (name) => result[name] && typeof result[name] === "object" ? result[name].status : undefined;
|
|
16914
|
+
const status = [result.status, inner("job"), inner("payload")].find(isText) ?? null;
|
|
16915
|
+
return {
|
|
16916
|
+
...result,
|
|
16917
|
+
kind: result.kind,
|
|
16918
|
+
status,
|
|
16919
|
+
clientTradeId: result.clientTradeId ?? null,
|
|
16920
|
+
signature: signatures[0] ?? null,
|
|
16921
|
+
signatures,
|
|
16922
|
+
raw: result
|
|
16923
|
+
};
|
|
16924
|
+
}
|
|
16875
16925
|
function printTradingResult(ctx, result) {
|
|
16876
16926
|
if (result.kind === "swap" && result.job && typeof result.job === "object") {
|
|
16877
16927
|
const job = result.job;
|
|
16878
16928
|
describeSwapEvidence(ctx, job, job.status === "failed");
|
|
16879
16929
|
}
|
|
16880
|
-
ctx.
|
|
16930
|
+
const printed = ctx.json && (result.kind === "swap" || result.kind === "trade") ? swapJsonResult(result) : result;
|
|
16931
|
+
ctx.deps.stdout.write(ctx.json ? `${JSON.stringify(printed)}
|
|
16881
16932
|
` : `${JSON.stringify(result, null, 2)}
|
|
16882
16933
|
`);
|
|
16883
16934
|
return 0;
|
|
@@ -17684,7 +17735,7 @@ async function bridgeSwap(ctx, args) {
|
|
|
17684
17735
|
return printTradingResult(ctx, { ...receipt, waited: { final: false, error: reason } });
|
|
17685
17736
|
}
|
|
17686
17737
|
}
|
|
17687
|
-
var AGENT_MAX_SLIPPAGE_BPS = 5000, SLIPPAGE_RANGE_MESSAGE, settlementSchema, bridgeFactsSchema, HOOD_TRADE_LEGS, bridgeBuildSchema;
|
|
17738
|
+
var isText = (value) => typeof value === "string" && value.length > 0, textsOf = (value) => Array.isArray(value) ? value.filter(isText) : [], AGENT_MAX_SLIPPAGE_BPS = 5000, SLIPPAGE_RANGE_MESSAGE, settlementSchema, bridgeFactsSchema, HOOD_TRADE_LEGS, bridgeBuildSchema;
|
|
17688
17739
|
var init_swap = __esm(() => {
|
|
17689
17740
|
init_zod();
|
|
17690
17741
|
init_args();
|
|
@@ -28872,7 +28923,7 @@ __export(exports_vault_support, {
|
|
|
28872
28923
|
askForOwnPassphrase: () => askForOwnPassphrase,
|
|
28873
28924
|
PASSPHRASE_ONLY_PREFIX: () => PASSPHRASE_ONLY_PREFIX
|
|
28874
28925
|
});
|
|
28875
|
-
import { dirname as dirname6 } from "node:path";
|
|
28926
|
+
import { dirname as dirname6, resolve as resolvePath } from "node:path";
|
|
28876
28927
|
function refuseEnvPassphrase(ctx) {
|
|
28877
28928
|
if (ctx.deps.env.CANDLE_KEYSTORE_PASSPHRASE === undefined)
|
|
28878
28929
|
return true;
|
|
@@ -29002,6 +29053,9 @@ function assertVaultHelperIdentities(deps, envelopes) {
|
|
|
29002
29053
|
}
|
|
29003
29054
|
async function unlockInteractively(ctx, path, raw, opts = {}) {
|
|
29004
29055
|
await assertNotOlderCopy(ctx, path, raw, opts.acceptOlderCopy ?? false);
|
|
29056
|
+
const held = ctx.heldUnlock;
|
|
29057
|
+
if (held !== undefined && resolvePath(held.path) === resolvePath(path))
|
|
29058
|
+
return held.open(raw);
|
|
29005
29059
|
const { deps } = ctx;
|
|
29006
29060
|
const file = parseVaultFile(raw);
|
|
29007
29061
|
assertVaultHelperIdentities(deps, file.envelopes);
|
|
@@ -37251,9 +37305,11 @@ async function resolveTargetKey(ctx, deviceToken, raw, opts = {}) {
|
|
|
37251
37305
|
return { ok: false, code: 1 };
|
|
37252
37306
|
}
|
|
37253
37307
|
if (byPrefix)
|
|
37254
|
-
return { ok: true, keyPrefix: byPrefix.keyPrefix };
|
|
37255
|
-
if (byLabel.length === 1)
|
|
37256
|
-
|
|
37308
|
+
return { ok: true, keyPrefix: byPrefix.keyPrefix, embeddedWallet: byPrefix.embeddedWallet };
|
|
37309
|
+
if (byLabel.length === 1) {
|
|
37310
|
+
const only = byLabel[0];
|
|
37311
|
+
return { ok: true, keyPrefix: only.keyPrefix, embeddedWallet: only.embeddedWallet };
|
|
37312
|
+
}
|
|
37257
37313
|
if (byLabel.length > 1) {
|
|
37258
37314
|
writeLocalFailure(deps, {
|
|
37259
37315
|
code: `${codePrefix}_KEY_AMBIGUOUS`,
|
|
@@ -37265,8 +37321,10 @@ async function resolveTargetKey(ctx, deviceToken, raw, opts = {}) {
|
|
|
37265
37321
|
return { ok: true, keyPrefix: raw };
|
|
37266
37322
|
}
|
|
37267
37323
|
const matches = keys.filter((key) => key.label === raw);
|
|
37268
|
-
if (matches.length === 1)
|
|
37269
|
-
|
|
37324
|
+
if (matches.length === 1) {
|
|
37325
|
+
const only = matches[0];
|
|
37326
|
+
return { ok: true, keyPrefix: only.keyPrefix, embeddedWallet: only.embeddedWallet };
|
|
37327
|
+
}
|
|
37270
37328
|
if (matches.length === 0) {
|
|
37271
37329
|
const labelled = keys.filter((key) => key.label).map((key) => `${key.keyPrefix} ${labelCell(key.label)}`);
|
|
37272
37330
|
writeLocalFailure(deps, {
|
|
@@ -37581,6 +37639,11 @@ var init_tee_rebind = __esm(() => {
|
|
|
37581
37639
|
function effectiveEmbeddedWallet(value) {
|
|
37582
37640
|
return value === "denied" ? "denied" : "allowed";
|
|
37583
37641
|
}
|
|
37642
|
+
function withdrawNeedsEmbeddedWalletLine(keyPrefix) {
|
|
37643
|
+
return `Withdrawals to your approved addresses leave from the embedded wallet, so a Withdraw key needs the embedded wallet allowed; it is denied on this key, and those withdrawals will be refused.
|
|
37644
|
+
` + `Allow it with: candle keys update ${keyPrefix} --embedded-wallet allow
|
|
37645
|
+
` + "Transfers out of the key's own TEE wallets do not need it.";
|
|
37646
|
+
}
|
|
37584
37647
|
function keyName(keyPrefix, label) {
|
|
37585
37648
|
const cleaned = labelCell(label ?? undefined);
|
|
37586
37649
|
return cleaned ? `${keyPrefix} (${cleaned})` : keyPrefix;
|
|
@@ -38094,6 +38157,10 @@ async function keysCreate(args, ctx) {
|
|
|
38094
38157
|
} catch (error) {
|
|
38095
38158
|
storeError = error instanceof Error ? error.message : String(error);
|
|
38096
38159
|
}
|
|
38160
|
+
if (presetForScopes(body.scopes) === "readwritetransfer" && body.embeddedWallet === "denied") {
|
|
38161
|
+
notice(deps, "stderr", "warning", `${withdrawNeedsEmbeddedWalletLine(body.keyPrefix)}
|
|
38162
|
+
Or mint it that way: candle keys create --access withdraw --embedded-wallet allow`);
|
|
38163
|
+
}
|
|
38097
38164
|
if (json) {
|
|
38098
38165
|
deps.stdout.write(`${JSON.stringify({ ...body, stored, ...storeError ? { storeError } : {} })}
|
|
38099
38166
|
`);
|
|
@@ -38399,7 +38466,7 @@ var init_token_clusters = __esm(() => {
|
|
|
38399
38466
|
});
|
|
38400
38467
|
|
|
38401
38468
|
// ../mcp/src/version.ts
|
|
38402
|
-
var SERVER_VERSION = "0.10.
|
|
38469
|
+
var SERVER_VERSION = "0.10.29";
|
|
38403
38470
|
|
|
38404
38471
|
// ../mcp/src/update-notice.ts
|
|
38405
38472
|
function newer(a, b) {
|
|
@@ -39657,7 +39724,7 @@ var init_launch_definition = __esm(() => {
|
|
|
39657
39724
|
{
|
|
39658
39725
|
key: "feeWallet",
|
|
39659
39726
|
type: "string",
|
|
39660
|
-
help: "Solana only: the wallet this launch's curve and pool fees earn to, an on-curve wallet or a Squads v4 vault; defaults to the account wallet.",
|
|
39727
|
+
help: "Solana only: the wallet this launch's curve and pool fees earn to, an on-curve wallet or a Squads v4 vault; defaults to the wallet that launches the token (the account wallet on a server without LAUNCH_FEE_WALLET_FROM_LAUNCHER, except an embedded launch).",
|
|
39661
39728
|
chains: [
|
|
39662
39729
|
"solana"
|
|
39663
39730
|
],
|
|
@@ -44618,6 +44685,18 @@ function buildRequest(name, args, cfg) {
|
|
|
44618
44685
|
init: { method: "POST", headers: jsonHeaders(apiKey), body: JSON.stringify(body) }
|
|
44619
44686
|
};
|
|
44620
44687
|
}
|
|
44688
|
+
case "candle_atomic_launch_dry_run": {
|
|
44689
|
+
const apiKey = requireApiKey2(cfg);
|
|
44690
|
+
const { payer, chain: _chain, buyAmount: _buyAmount, ...rest } = args;
|
|
44691
|
+
return {
|
|
44692
|
+
url: `${base2}/api/v1/launch/atomic/dry-run`,
|
|
44693
|
+
init: {
|
|
44694
|
+
method: "POST",
|
|
44695
|
+
headers: jsonHeaders(apiKey),
|
|
44696
|
+
body: JSON.stringify({ ...rest, chain: "solana", payer: payer ?? { type: "main" } })
|
|
44697
|
+
}
|
|
44698
|
+
};
|
|
44699
|
+
}
|
|
44621
44700
|
case "candle_get_market": {
|
|
44622
44701
|
const { chain: chain2, mint } = args;
|
|
44623
44702
|
return {
|
|
@@ -45290,12 +45369,12 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
45290
45369
|
}, async (args) => callAndRelay("candle_claim_staking_reward", args, getConfig(), doFetch));
|
|
45291
45370
|
register("candle_get_fees", {
|
|
45292
45371
|
title: "Read the creator fees this account's tokens can pay",
|
|
45293
|
-
description: "The tokens launched by the account's own wallets, with what each could pay now in creator fees " + "(raw units from direct chain reads) and `
|
|
45372
|
+
description: "The tokens launched by the account's own wallets, with what each could pay now in creator fees " + "(raw units from direct chain reads) and a `reason` saying who can claim: `claimable_by_key` on a " + "claimable Solana row, and otherwise why this key cannot (including `claimable_via_web_session` " + "and `fee_wallet_designated`, which name whose authority collects). Reads only; " + "moves nothing. It returns no earned or paid-out totals. An unreadable token is marked, never " + "shown as zero. Paged: pass `continueCursor` back as `cursor`.",
|
|
45294
45373
|
inputSchema: getFeesShape
|
|
45295
45374
|
}, async (args) => callAndRelay("candle_get_fees", args, getConfig(), doFetch));
|
|
45296
45375
|
register("candle_claim_fees", {
|
|
45297
45376
|
title: "Claim a token's creator fees",
|
|
45298
|
-
description: "Collect one token's creator fees and pay them to the wallet fixed at launch. There is no destination " + "argument and the tool cannot send funds anywhere else. Candle signs with the embedded wallet, which " + "pays the network fee. Needs launch:write. " + "Call it with dryRun: true first; a real claim moves real funds into the recipient wallet, which may be " + "the wallet this agent trades from. Not idempotent by id: if the result is lost, read candle_get_fees " + "before claiming again.",
|
|
45377
|
+
description: "Collect one token's creator fees and pay them to the wallet fixed at launch. There is no destination " + "argument and the tool cannot send funds anywhere else. Candle signs with the embedded wallet, which " + "pays the network fee. A fee recipient that is a TEE wallet is refused here: claim it with the candle CLI " + "(`candle fees claim`). The dry run reports the same refusal (`claimable: false`, `reason: " + "wallet_not_signable`) and counts only what this tool could claim. Needs launch:write. " + "Call it with dryRun: true first; a real claim moves real funds into the recipient wallet, which may be " + "the wallet this agent trades from. Not idempotent by id: if the result is lost, read candle_get_fees " + "before claiming again.",
|
|
45299
45378
|
inputSchema: claimFeesShape
|
|
45300
45379
|
}, async (args) => callAndRelay("candle_claim_fees", args, getConfig(), doFetch));
|
|
45301
45380
|
register("candle_get_wallets", {
|
|
@@ -45403,6 +45482,12 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
45403
45482
|
const result = await executeTrade(args, getConfig(), doFetch);
|
|
45404
45483
|
return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
|
|
45405
45484
|
});
|
|
45485
|
+
register("candle_atomic_launch_dry_run", {
|
|
45486
|
+
title: "Dry-run an atomic Solana launch",
|
|
45487
|
+
description: "Check a Solana launch bundled with first buys and price each buy, without building anything. Runs the " + "same checks as the atomic build, in the same order (payers, Believer check, fee and spend limits, the " + "key's transaction cap), then returns the launch preview (curveTerms) and one fill per first buy: the " + "tokens it would receive and the floor the build would sign it at. Reads only; moves nothing, reserves " + "nothing, and the clientLaunchId stays unused. It does not check balances, the daily cap, or the " + "transaction's size, so the real launch can still refuse. Needs launch:write, swap:write and Pro or Max.",
|
|
45488
|
+
inputSchema: atomicLaunchDryRunShape,
|
|
45489
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
45490
|
+
}, async (args) => callAndRelay("candle_atomic_launch_dry_run", args, getConfig(), doFetch));
|
|
45406
45491
|
register("candle_launch_and_seed", {
|
|
45407
45492
|
title: "Launch a token and seed it",
|
|
45408
45493
|
description: "Launch a new token from the account's embedded wallet, with an optional dev-buy seed, then " + "return the fresh market state and token links in one result. On solana a seed makes this " + (options.headlessSeed === true ? "the seed is a separate first-buy transaction in the same Jito bundle as the launch, so " + "both land or neither does. That works on every plan, Free included, and needs no " + "swap:write. On hood the seed is " : "the atomic launch: the launch and the seed are two transactions Candle signs and sends as " + "one Jito bundle, so both land or neither does. That needs Pro or Max (TIER_REQUIRED " + "otherwise) and swap:write on the key, and the result carries bundleId. On hood the seed is ") + "a best-effort follow-up. A launch with no seed works on every plan, Free included. MOVES " + "REAL FUNDS unless dryRun; dry-run first, which spends nothing. Seeds above the platform " + "dev-buy ceiling are rejected (DEV_BUY_TOO_HIGH): at most 0.5 SOL / 500 USDC / 2,000,000 " + "CNDL, or on an exp- curve 10% of the threshold when that is lower; the server holds the " + "creator's own seed to the effective ceiling, so launch with the largest devBuy it allows " + "and top up with candle_trade. " + LAUNCH_SHARED_SENTENCES + (imageDir ? IMAGE_PATH_DISCLOSURE : ""),
|
|
@@ -45605,7 +45690,7 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
|
|
|
45605
45690
|
}
|
|
45606
45691
|
return eligible;
|
|
45607
45692
|
}
|
|
45608
|
-
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, 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, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, setProfileWalletsShape, swapShape, tradeShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
|
|
45693
|
+
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, 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, setProfileWalletsShape, swapShape, tradeShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
|
|
45609
45694
|
var init_tools = __esm(() => {
|
|
45610
45695
|
init_zod();
|
|
45611
45696
|
init_client2();
|
|
@@ -45670,6 +45755,7 @@ var init_tools = __esm(() => {
|
|
|
45670
45755
|
"candle_get_plans",
|
|
45671
45756
|
"candle_trade",
|
|
45672
45757
|
"candle_launch_and_seed",
|
|
45758
|
+
"candle_atomic_launch_dry_run",
|
|
45673
45759
|
"candle_swap",
|
|
45674
45760
|
"candle_transfer",
|
|
45675
45761
|
"candle_sweep",
|
|
@@ -45821,6 +45907,22 @@ var init_tools = __esm(() => {
|
|
|
45821
45907
|
cursor: exports_external.string().optional().describe("The previous page's continueCursor, verbatim"),
|
|
45822
45908
|
limit: exports_external.number().optional().describe("Tokens per page, 1 to 50 (default 20). Each one costs chain reads")
|
|
45823
45909
|
};
|
|
45910
|
+
atomicPayerShape = exports_external.union([
|
|
45911
|
+
exports_external.object({ type: exports_external.literal("main") }),
|
|
45912
|
+
exports_external.object({ type: exports_external.literal("linked"), linkedWalletId: exports_external.string().min(1) })
|
|
45913
|
+
]);
|
|
45914
|
+
atomicLaunchDryRunShape = (() => {
|
|
45915
|
+
const { buyAmount: _buyAmount, chain: _chain, ...launch } = launchTokenShape;
|
|
45916
|
+
return {
|
|
45917
|
+
...launch,
|
|
45918
|
+
clientLaunchId: exports_external.string().optional().describe("Not used by a dry run; send the id you will launch with"),
|
|
45919
|
+
payer: atomicPayerShape.optional().describe("Who pays for the launch: {type:'main'} (default) or {type:'linked', linkedWalletId}"),
|
|
45920
|
+
firstBuys: exports_external.array(exports_external.object({
|
|
45921
|
+
payer: atomicPayerShape,
|
|
45922
|
+
amountRaw: exports_external.string().regex(/^[1-9][0-9]*$/).describe("Quote-asset raw units")
|
|
45923
|
+
})).min(1).max(4).describe("One to four first buys, each priced on the curve this launch would deploy")
|
|
45924
|
+
};
|
|
45925
|
+
})();
|
|
45824
45926
|
claimFeesShape = {
|
|
45825
45927
|
mint: exports_external.string().describe("The launched token: a Solana mint or a Hood token address, as candle_get_fees lists it"),
|
|
45826
45928
|
dryRun: exports_external.boolean().optional().describe("Check and read only. Sends nothing and writes nothing. Use it first to see what would be claimed")
|
|
@@ -76035,7 +76137,7 @@ var LAUNCH_DEFINITION = {
|
|
|
76035
76137
|
{
|
|
76036
76138
|
key: "feeWallet",
|
|
76037
76139
|
type: "string",
|
|
76038
|
-
help: "Solana only: the wallet this launch's curve and pool fees earn to, an on-curve wallet or a Squads v4 vault; defaults to the account wallet.",
|
|
76140
|
+
help: "Solana only: the wallet this launch's curve and pool fees earn to, an on-curve wallet or a Squads v4 vault; defaults to the wallet that launches the token (the account wallet on a server without LAUNCH_FEE_WALLET_FROM_LAUNCHER, except an embedded launch).",
|
|
76039
76141
|
chains: [
|
|
76040
76142
|
"solana"
|
|
76041
76143
|
],
|
|
@@ -76159,7 +76261,7 @@ var LAUNCH_USAGE = `candle launch ${launchFlagFields.map((field) => {
|
|
|
76159
76261
|
if (field.key === "imageUrl")
|
|
76160
76262
|
return `--image <path>|${invocation}`;
|
|
76161
76263
|
return "required" in field && field.required ? invocation : `[${invocation}]`;
|
|
76162
|
-
}).join(" ")} [--check] [--yes] [--rpc-url <url>] [--json]`;
|
|
76264
|
+
}).join(" ")} [--check] [--yes] [--require-fee-wallet] [--rpc-url <url>] [--json]`;
|
|
76163
76265
|
function launchFieldProblem(flags) {
|
|
76164
76266
|
for (const field of launchFlagFields) {
|
|
76165
76267
|
const value = flags[field.cli.flag];
|
|
@@ -76344,7 +76446,7 @@ var HELP = {
|
|
|
76344
76446
|
group: "Trade",
|
|
76345
76447
|
intro: "Quote, swap and bridge on Solana or Hood; read an operation",
|
|
76346
76448
|
summary: "Quote, confirm and swap on Solana or Hood, bridge between them; read an operation by id",
|
|
76347
|
-
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 sell a token it holds that was not launched on Candle; buying such a token needs Pro or Max (TIER_REQUIRED otherwise). 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.",
|
|
76449
|
+
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 sell a token it holds that was not launched on Candle; buying such a token needs Pro or Max (TIER_REQUIRED otherwise). 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.",
|
|
76348
76450
|
usage: ["candle swap <from> <to> [flags]", "candle swap status <id>"],
|
|
76349
76451
|
rows: [
|
|
76350
76452
|
{
|
|
@@ -76657,19 +76759,19 @@ var HELP = {
|
|
|
76657
76759
|
group: "Trade",
|
|
76658
76760
|
intro: "Move funds out of a TEE wallet to your wallets or its vault",
|
|
76659
76761
|
summary: "Move funds out of a TEE wallet to your own wallets or its vault, through its bound key",
|
|
76660
|
-
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. To a linked wallet 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.",
|
|
76762
|
+
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.",
|
|
76661
76763
|
usage: [
|
|
76662
|
-
"candle transfer --to <address|wallet name|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <tee>] [--yes] [--json]"
|
|
76764
|
+
"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]"
|
|
76663
76765
|
],
|
|
76664
76766
|
rows: [],
|
|
76665
76767
|
flags: [
|
|
76666
76768
|
{
|
|
76667
|
-
invocation: "--to <address|wallet name|vault>",
|
|
76668
|
-
description: "Where the funds go: vault is the wallet's own pin"
|
|
76769
|
+
invocation: "--to <address|wallet name|allowlist label|vault>",
|
|
76770
|
+
description: "Where the funds go: vault is the wallet's own pin; a linked wallet's name wins over an allowlist label"
|
|
76669
76771
|
},
|
|
76670
76772
|
{
|
|
76671
76773
|
invocation: "--asset <SOL|USDC|CNDL|ETH|USDG>",
|
|
76672
|
-
description: "A base asset (required for a linked-wallet destination)"
|
|
76774
|
+
description: "A base asset (required for a linked-wallet or approved-address destination)"
|
|
76673
76775
|
},
|
|
76674
76776
|
{ invocation: "--mint <mint>", description: "Any Solana mint, instead of --asset (vault destinations only)" },
|
|
76675
76777
|
{ invocation: "--token <0x...>", description: "Any Hood ERC-20, instead of --asset (vault destinations only)" },
|
|
@@ -76691,9 +76793,14 @@ var HELP = {
|
|
|
76691
76793
|
launch: {
|
|
76692
76794
|
group: "Trade",
|
|
76693
76795
|
summary: "Create a token on Solana or Hood, with an optional first buy",
|
|
76694
|
-
description: "Creates a token from the wallet you name, with an optional first buy. Embedded launches use /launch/headless on every plan, with the key's embedded-wallet allowance and launch:write; no swap:write is needed. A Solana first buy is a creator bundle, signed by Candle. A Hood first buy is best effort after creation. TEE launches use /launch/self and require Pro or Max, allowLaunch, a bound key and verified authority. TEE buys need TEE_LAUNCH_BUYS_ENABLED on the server; Solana buys also need AGENT_TRADING_ENABLED and Jito. Existing server gates and ceilings apply. The embedded path previews the launch before confirmation; --dry-run stops at the preview. No buy creates only the token. Local image and banner uploads happen before confirmation and are public and permanent even if the launch is declined. Reuse --client-trade-id for recovery. On Solana, --fee-wallet <address> sets the wallet the launch's curve and pool fees earn to, an on-curve wallet by itself or a Squads v4 vault with --fee-wallet-multisig <address> and --fee-wallet-multisig-vault-index <n>; without it the fees earn to the account wallet,
|
|
76695
|
-
usage: [LAUNCH_USAGE],
|
|
76696
|
-
rows: [
|
|
76796
|
+
description: "Creates a token from the wallet you name, with an optional first buy. Embedded launches use /launch/headless on every plan, with the key's embedded-wallet allowance and launch:write; no swap:write is needed. A Solana first buy is a creator bundle, signed by Candle. A Hood first buy is best effort after creation. TEE launches use /launch/self and require Pro or Max, allowLaunch, a bound key and verified authority. TEE buys need TEE_LAUNCH_BUYS_ENABLED on the server; Solana buys also need AGENT_TRADING_ENABLED and Jito. Existing server gates and ceilings apply. The embedded path previews the launch before confirmation; --dry-run stops at the preview. No buy creates only the token. Local image and banner uploads happen before confirmation and are public and permanent even if the launch is declined. Reuse --client-trade-id for recovery. On Solana, --fee-wallet <address> sets the wallet the launch's curve and pool fees earn to, an on-curve wallet by itself or a Squads v4 vault with --fee-wallet-multisig <address> and --fee-wallet-multisig-vault-index <n>; without it the fees earn to the wallet that launches the token where the server has LAUNCH_FEE_WALLET_FROM_LAUNCHER on (an embedded launch already earns to its embedded wallet), otherwise to the account wallet, and the preview, the confirm and --check all say which; a live launch also warns on stderr when it defaults, and --require-fee-wallet refuses the launch without one. --check answers first, before any of it: which launch path works on this key and server (embedded allowance, tier, allowLaunch, TEE_LAUNCH_BUYS_ENABLED) in one call, with nothing uploaded, built or sent; name a wallet with --wallet to narrow it, and it exits 1 when no path works. status reads one launch by its mint or clientLaunchId: build, signed, confirmed, trading, bonded, then migrated, with the market's lifecycle and migration fields. --wait follows it until the token is trading, the launch has failed, or ten minutes pass.",
|
|
76797
|
+
usage: [LAUNCH_USAGE, "candle launch status <mint-or-client-launch-id> [--wait]"],
|
|
76798
|
+
rows: [
|
|
76799
|
+
{
|
|
76800
|
+
invocation: "status <mint-or-client-launch-id> [--wait]",
|
|
76801
|
+
description: "One state for a mint or a clientLaunchId: build, signed, confirmed, trading, bonded, migrated. Uses the market's lifecycle and migration fields. --wait follows until the token is trading, the launch has failed, or ten minutes pass. Reads only."
|
|
76802
|
+
}
|
|
76803
|
+
],
|
|
76697
76804
|
flags: [
|
|
76698
76805
|
...launchFlagRows,
|
|
76699
76806
|
{
|
|
@@ -76705,6 +76812,10 @@ var HELP = {
|
|
|
76705
76812
|
description: "Answer which launch path works (embedded allowance, tier, allowLaunch, TEE_LAUNCH_BUYS_ENABLED) in one call, before any image upload; nothing is sent. Exits 1 when no path works."
|
|
76706
76813
|
},
|
|
76707
76814
|
{ invocation: "--yes", description: "Confirm the launch without an interactive prompt." },
|
|
76815
|
+
{
|
|
76816
|
+
invocation: "--require-fee-wallet",
|
|
76817
|
+
description: "Refuse the launch when no --fee-wallet is given, rather than letting the fees earn to the account wallet."
|
|
76818
|
+
},
|
|
76708
76819
|
{
|
|
76709
76820
|
invocation: "--rpc-url <url>",
|
|
76710
76821
|
description: "The Solana RPC for a TEE launch with no first buy, or the Hood RPC for receipt reads."
|
|
@@ -76718,7 +76829,8 @@ var HELP = {
|
|
|
76718
76829
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4",
|
|
76719
76830
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1",
|
|
76720
76831
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet embedded --buy 0.1 --dry-run",
|
|
76721
|
-
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --mode exp-custom --curve-v 4 --curve-g 20 --creator-fee-bps 100 --dry-run"
|
|
76832
|
+
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --mode exp-custom --curve-v 4 --curve-g 20 --creator-fee-bps 100 --dry-run",
|
|
76833
|
+
"candle launch status <mint-or-id> --wait"
|
|
76722
76834
|
],
|
|
76723
76835
|
env: ENV_API
|
|
76724
76836
|
},
|
|
@@ -77363,7 +77475,7 @@ var HELP = {
|
|
|
77363
77475
|
},
|
|
77364
77476
|
{
|
|
77365
77477
|
invocation: "transfer <to> --amount <n|max> --asset SOL|<mint>|ETH|USDG|<0x token> --from <label> [--rpc-url <url>]",
|
|
77366
|
-
description: "Sign a transfer locally from a vault key or a promoted TEE wallet (Solana, or Hood: refused while a sequenced trade holds the wallet's nonce, and when that cannot be read). From an EVM key: ETH or an ERC-20, on Hood by default (--rpc-url for any EVM chain; the chain id is read from the RPC), exit 0 means depth-confirmed (1 block on Hood, 2 elsewhere), not finalized. A Solana key reads and sends over --rpc-url, else CANDLE_SOLANA_RPC_URL, else the profile's RPC, else the public endpoint. One verification: the unlock names the send; after the decoded display you type confirm. No --yes"
|
|
77478
|
+
description: "Sign a transfer locally from a vault key or a promoted TEE wallet (Solana, or Hood: refused while a sequenced trade holds the wallet's nonce, and when that cannot be read). From an EVM key: ETH or an ERC-20, on Hood by default (--rpc-url for any EVM chain; the chain id is read from the RPC), exit 0 means depth-confirmed (1 block on Hood, 2 elsewhere), not finalized. A Solana key reads and sends over --rpc-url, else CANDLE_SOLANA_RPC_URL, else the profile's RPC, else the public endpoint. Max sends SOL minus the quoted fee (zero source reserve when emptied), or the whole token balance; EVM native max deducts the fee cap. SOL max refreshes before signing; a changed amount or fee requires a fresh confirm. One verification: the unlock names the send; after the decoded display you type confirm. No --yes"
|
|
77367
77479
|
},
|
|
77368
77480
|
{
|
|
77369
77481
|
invocation: "promote --from|--in-place <label> [--sweep-to <label>] [--rpc-url <url>] [--to-key <prefix|label>]",
|
|
@@ -77378,8 +77490,8 @@ var HELP = {
|
|
|
77378
77490
|
description: "Fund a TEE or external wallet from a vault key. A Hood TEE wallet takes ETH or USDG from its pinned EVM vault key. One verification: the unlock names the send; after the decoded display you type confirm. No --yes"
|
|
77379
77491
|
},
|
|
77380
77492
|
{
|
|
77381
|
-
invocation: "demote <tee-address
|
|
77382
|
-
description: "Disable then sweep a TEE wallet back to its pin. A Hood wallet takes tee sweep's --token and --from-block"
|
|
77493
|
+
invocation: "demote <tee-address>... | --all --empty [--rpc-url <url>] [--emergency] [--sweep-to <label>] [--yes] [--token <0x...>] [--from-block <n>]",
|
|
77494
|
+
description: "Disable then sweep a TEE wallet back to its pin. A Hood wallet takes tee sweep's --token and --from-block (one wallet only). Several addresses, or --all --empty, demote one at a time under one unlock: every wallet and its sweep destination is listed, then you type confirm once (--yes skips it; it stands in for each sweep's confirm). A failure does not stop the rest; each wallet gets a result line and a summary counts done, incomplete, failed and skipped with reasons. --json writes one document: wallets[] and summary"
|
|
77383
77495
|
},
|
|
77384
77496
|
{
|
|
77385
77497
|
invocation: "export-key <label> --to <new-file>",
|
|
@@ -77398,7 +77510,15 @@ var HELP = {
|
|
|
77398
77510
|
},
|
|
77399
77511
|
{
|
|
77400
77512
|
invocation: "--all",
|
|
77401
|
-
description: "list: include retired TEE wallets (demoted and swept), which are hidden by default"
|
|
77513
|
+
description: "list: include retired TEE wallets (demoted and swept), which are hidden by default. demote: every TEE wallet in the vault that is not retired, with a filter (--empty)"
|
|
77514
|
+
},
|
|
77515
|
+
{
|
|
77516
|
+
invocation: "--empty",
|
|
77517
|
+
description: "demote --all: only Solana TEE wallets with nothing left to sweep (0 SOL, no token accounts, no unsettled sweep); the rest are listed as skipped"
|
|
77518
|
+
},
|
|
77519
|
+
{
|
|
77520
|
+
invocation: "--yes",
|
|
77521
|
+
description: "demote with several wallets: skip the one typed confirmation for the listed set"
|
|
77402
77522
|
},
|
|
77403
77523
|
{
|
|
77404
77524
|
invocation: "--balances",
|
|
@@ -77436,6 +77556,7 @@ var HELP = {
|
|
|
77436
77556
|
"candle vault new-key --chain solana --labels-from ./replacement-names.txt",
|
|
77437
77557
|
"candle vault promote-batch --pairs-from ./promote-plan.csv",
|
|
77438
77558
|
"candle vault promote-batch --pairs-from ./promote-plan.csv --to-key tr-01",
|
|
77559
|
+
"candle vault demote --all --empty",
|
|
77439
77560
|
"candle vault enroll security-key --label yubikey-a",
|
|
77440
77561
|
"candle vault backup --to /Volumes/BACKUP/vault.enc",
|
|
77441
77562
|
"candle vault backup --to icloud",
|
|
@@ -77496,8 +77617,12 @@ var HELP = {
|
|
|
77496
77617
|
description: "Rename 1–256 TEE wallets with one server call, all or nothing, then one vault commit. Every finding refuses the whole file. The vault is unlocked once, only when a named TEE wallet may be in it. --dry-run prints the plan and changes nothing; if the vault write fails after the server accepted, it prints address-keyed rows to re-run"
|
|
77497
77618
|
},
|
|
77498
77619
|
{
|
|
77499
|
-
invocation: "signer new --key <prefix|label> [--out <pem>] [--force]",
|
|
77500
|
-
description: "On the trading machine, with that key's API key: generate the key's signer here and wait for the owner to approve it. Its wallets then trade from this machine. --out also writes a plaintext
|
|
77620
|
+
invocation: "signer new --key <prefix|label> [--out <pem>] [--force] [--no-wait]",
|
|
77621
|
+
description: "On the trading machine, with that key's API key: generate the key's signer here and wait for the owner to approve it. Its wallets then trade from this machine. --out also writes a plaintext P-256 authorization key for Privy wallet RPC; it cannot sign Solana transactions (the TEE signs them). --no-wait exits 0 once pending. --json emits newline-delimited JSON: a pending event with userCode, verificationUriComplete, expiresAt and fingerprint immediately, then an active result or error if waiting. Retrying re-attaches to the same live request; --force adds a signer while an older signer on THIS machine still owns wallets; it moves, revokes and deletes nothing"
|
|
77622
|
+
},
|
|
77623
|
+
{
|
|
77624
|
+
invocation: " signer status --key <prefix|label>",
|
|
77625
|
+
description: "Read the server signer state once without generating a pair or requesting approval. --json prints one value; on the requesting machine it includes the cached code and URL while that request is live"
|
|
77501
77626
|
},
|
|
77502
77627
|
{
|
|
77503
77628
|
invocation: " signer remove --key <prefix|label>",
|
|
@@ -78119,6 +78244,7 @@ var TOOL_CLASSES = {
|
|
|
78119
78244
|
candle_execution_status: READ,
|
|
78120
78245
|
candle_get_operation: READ,
|
|
78121
78246
|
candle_get_fees: READ,
|
|
78247
|
+
candle_atomic_launch_dry_run: READ,
|
|
78122
78248
|
candle_get_staking: READ,
|
|
78123
78249
|
candle_perps_markets: READ,
|
|
78124
78250
|
candle_perps_market: READ,
|
|
@@ -82610,14 +82736,69 @@ async function tokenAccountFrozen(rpc, address) {
|
|
|
82610
82736
|
return { exists: false, frozen: false };
|
|
82611
82737
|
return { exists: true, frozen: account.data[TOKEN_ACCOUNT_STATE_OFFSET2] === TOKEN_ACCOUNT_STATE_FROZEN2 };
|
|
82612
82738
|
}
|
|
82739
|
+
function rawToDecimal2(raw, decimals) {
|
|
82740
|
+
if (decimals === 0)
|
|
82741
|
+
return raw.toString();
|
|
82742
|
+
const digits = raw.toString().padStart(decimals + 1, "0");
|
|
82743
|
+
return `${digits.slice(0, -decimals)}.${digits.slice(-decimals)}`.replace(/\.?0+$/, "");
|
|
82744
|
+
}
|
|
82745
|
+
async function planSolMax(rpc, from, to) {
|
|
82746
|
+
if (from === to)
|
|
82747
|
+
throw new VaultError("VAULT_INDEX_INVALID", "SOL max needs a destination different from the source.");
|
|
82748
|
+
const account = await rpc.getAccountInfo(from);
|
|
82749
|
+
if (account && (account.owner !== SYSTEM_PROGRAM_ID || account.data.length !== 0)) {
|
|
82750
|
+
throw new VaultError("VAULT_INDEX_INVALID", "SOL max requires a system wallet with no account data; nonce and program accounts cannot use this transfer shape. Nothing was signed or sent.");
|
|
82751
|
+
}
|
|
82752
|
+
const balance = account?.lamports ?? 0n;
|
|
82753
|
+
const reserve = 0n;
|
|
82754
|
+
const blockhash = await rpc.getLatestBlockhash();
|
|
82755
|
+
let raw = balance;
|
|
82756
|
+
for (let attempt = 0;attempt < 3; attempt++) {
|
|
82757
|
+
const instructions = [systemTransfer(decodePubkey(from), decodePubkey(to), raw)];
|
|
82758
|
+
const message = compileLegacyMessage({ feePayer: decodePubkey(from), recentBlockhash: blockhash, instructions });
|
|
82759
|
+
const fee = await rpc.getFeeForMessage(toBase642(message));
|
|
82760
|
+
if (fee === null) {
|
|
82761
|
+
throw new VaultError("VAULT_UNREADABLE", "The RPC could not quote the SOL max fee. Nothing was signed or sent; retry with a fresh RPC quote.");
|
|
82762
|
+
}
|
|
82763
|
+
const available = balance - fee - reserve;
|
|
82764
|
+
if (available <= 0n) {
|
|
82765
|
+
throw new VaultError("TRANSFER_INSUFFICIENT_FUNDS", `${from} holds ${balance} lamports, not above the ${fee} lamport fee; max leaves nothing to send. Nothing was signed or sent.`, { suggestion: "Fund the key with SOL first." });
|
|
82766
|
+
}
|
|
82767
|
+
if (raw === available) {
|
|
82768
|
+
const amount = rawToDecimal2(raw, 9);
|
|
82769
|
+
return {
|
|
82770
|
+
asset: "SOL",
|
|
82771
|
+
amount,
|
|
82772
|
+
amountRaw: raw,
|
|
82773
|
+
decimals: 9,
|
|
82774
|
+
from,
|
|
82775
|
+
to,
|
|
82776
|
+
instructions,
|
|
82777
|
+
solMax: { blockhash, fee, reserve },
|
|
82778
|
+
displayLines: [
|
|
82779
|
+
`fee payer ${from}`,
|
|
82780
|
+
`destination ${to}`,
|
|
82781
|
+
`amount ${amount} SOL = ${raw} lamports (max)`,
|
|
82782
|
+
"mint (native SOL)",
|
|
82783
|
+
`source reserve ${reserve} lamports (source emptied)`
|
|
82784
|
+
]
|
|
82785
|
+
};
|
|
82786
|
+
}
|
|
82787
|
+
raw = available;
|
|
82788
|
+
}
|
|
82789
|
+
throw new VaultError("VAULT_UNREADABLE", "The SOL max fee quote kept changing. Nothing was signed or sent; retry when the RPC is stable.");
|
|
82790
|
+
}
|
|
82613
82791
|
async function planTransfer(input) {
|
|
82614
82792
|
const asset = input.asset.toUpperCase();
|
|
82615
82793
|
const fromKey = decodePubkey(input.from);
|
|
82616
82794
|
const toKey = decodePubkey(input.to);
|
|
82795
|
+
const max = input.amount.toLowerCase() === "max";
|
|
82617
82796
|
if (asset === "SOL") {
|
|
82797
|
+
if (max)
|
|
82798
|
+
return planSolMax(input.rpc, input.from, input.to);
|
|
82618
82799
|
const raw2 = decimalToRaw4(input.amount, 9);
|
|
82619
82800
|
if (raw2 === null || raw2 === 0n) {
|
|
82620
|
-
throw new VaultError("VAULT_INDEX_INVALID", "--amount must be a positive SOL decimal with at most 9 places.");
|
|
82801
|
+
throw new VaultError("VAULT_INDEX_INVALID", "--amount must be max or a positive SOL decimal with at most 9 places.");
|
|
82621
82802
|
}
|
|
82622
82803
|
return {
|
|
82623
82804
|
asset: "SOL",
|
|
@@ -82648,13 +82829,16 @@ async function planTransfer(input) {
|
|
|
82648
82829
|
throw new VaultError("VAULT_UNREADABLE", `Could not read mint ${mintAddress}: ${asMessage(error)}`);
|
|
82649
82830
|
}
|
|
82650
82831
|
const decimals = profile.decimals;
|
|
82651
|
-
const
|
|
82832
|
+
const source = ataFor(profile, fromKey);
|
|
82833
|
+
const raw = max ? tokenAccountAmount((await rpc.getAccountInfo(encodePubkey(source)))?.data ?? new Uint8Array) : decimalToRaw4(input.amount, decimals);
|
|
82834
|
+
if (max && raw === 0n) {
|
|
82835
|
+
throw new VaultError("TRANSFER_INSUFFICIENT_FUNDS", `${input.from} holds no ${input.asset} in its source token account; max leaves nothing to send. Nothing was signed or sent.`);
|
|
82836
|
+
}
|
|
82652
82837
|
if (raw === null || raw === 0n) {
|
|
82653
|
-
throw new VaultError("VAULT_INDEX_INVALID", `--amount must be a positive decimal with at most ${decimals} decimal places.`);
|
|
82838
|
+
throw new VaultError("VAULT_INDEX_INVALID", `--amount must be max or a positive decimal with at most ${decimals} decimal places.`);
|
|
82654
82839
|
}
|
|
82655
82840
|
const mint = decodePubkey(mintAddress);
|
|
82656
82841
|
const tokenProgram = decodePubkey(profile.tokenProgram);
|
|
82657
|
-
const source = ataFor(profile, fromKey);
|
|
82658
82842
|
const destination = ataFor(profile, toKey);
|
|
82659
82843
|
const instructions = [];
|
|
82660
82844
|
const accountCreationLines = [];
|
|
@@ -82691,7 +82875,7 @@ async function planTransfer(input) {
|
|
|
82691
82875
|
}));
|
|
82692
82876
|
return {
|
|
82693
82877
|
asset: asset === "USDC" ? "USDC" : mintAddress,
|
|
82694
|
-
amount: input.amount,
|
|
82878
|
+
amount: max ? rawToDecimal2(raw, decimals) : input.amount,
|
|
82695
82879
|
amountRaw: raw,
|
|
82696
82880
|
decimals,
|
|
82697
82881
|
mint: mintAddress,
|
|
@@ -82708,7 +82892,7 @@ async function planTransfer(input) {
|
|
|
82708
82892
|
displayLines: [
|
|
82709
82893
|
`fee payer ${input.from}`,
|
|
82710
82894
|
`destination ${input.to}`,
|
|
82711
|
-
`amount ${input.amount} (${raw} raw, ${decimals} dp)`,
|
|
82895
|
+
`amount ${max ? rawToDecimal2(raw, decimals) : input.amount} (${raw} raw, ${decimals} dp)${max ? " (max)" : ""}`,
|
|
82712
82896
|
`mint ${mintAddress}`,
|
|
82713
82897
|
`program ${profile.tokenProgram}${profile.token2022 ? " (Token-2022)" : ""}`,
|
|
82714
82898
|
...accountCreationLines,
|
|
@@ -82788,7 +82972,7 @@ async function signAndBroadcastTransfer(input) {
|
|
|
82788
82972
|
if (encodePubkey(feePayer) !== input.plan.from) {
|
|
82789
82973
|
throw new VaultError("VAULT_VERIFY_FAILED", "The decrypted key does not match the planned fee payer.");
|
|
82790
82974
|
}
|
|
82791
|
-
const blockhash = await input.solana.read(() => rpc.getLatestBlockhash());
|
|
82975
|
+
const blockhash = input.plan.solMax?.blockhash ?? await input.solana.read(() => rpc.getLatestBlockhash());
|
|
82792
82976
|
const message = compileLegacyMessage({
|
|
82793
82977
|
feePayer,
|
|
82794
82978
|
recentBlockhash: blockhash,
|
|
@@ -84791,7 +84975,7 @@ function rebindJson(input, chain2) {
|
|
|
84791
84975
|
init_key_output();
|
|
84792
84976
|
init_keys();
|
|
84793
84977
|
init_tee_rebind();
|
|
84794
|
-
var USAGE_TEE_SIGNER = "Usage: candle tee signer new --key <prefix|label> [--out <pem>] [--force] [--json] | candle tee signer remove --key <prefix|label> [--json]";
|
|
84978
|
+
var USAGE_TEE_SIGNER = "Usage: candle tee signer new --key <prefix|label> [--out <pem>] [--force] [--no-wait] [--json] | candle tee signer status --key <prefix|label> [--json] | candle tee signer remove --key <prefix|label> [--json]";
|
|
84795
84979
|
var USAGE_KEYS_SIGNER = "Usage: candle keys signer approve <code> --key <prefix|label> [--reject] | candle keys signer move <prefix|label> [--to-key <prefix|label>] [--json]";
|
|
84796
84980
|
var SIGNER_POLL_MS = 5000;
|
|
84797
84981
|
var DEVICE_TOKEN_BESIDE_SIGNER_LINE = "Warning: this machine holds a device token as well as a key signer. A machine with both can approve its own signer request. Do not leave the trading machine logged in. If you signed in for a move, run candle auth logout --profile <that profile> here, and approve from the owner's machine.";
|
|
@@ -84825,6 +85009,8 @@ async function teeSigner(args, ctx) {
|
|
|
84825
85009
|
return teeSignerNew(rest, ctx);
|
|
84826
85010
|
if (sub === "remove")
|
|
84827
85011
|
return teeSignerRemove(rest, ctx);
|
|
85012
|
+
if (sub === "status")
|
|
85013
|
+
return teeSignerStatus(rest, ctx);
|
|
84828
85014
|
writeScreenUsageFailure(ctx.deps, sub === undefined ? USAGE_TEE_SIGNER : `Unknown subcommand: ${sub}. ${USAGE_TEE_SIGNER}`, ctx.json);
|
|
84829
85015
|
return 2;
|
|
84830
85016
|
}
|
|
@@ -84837,7 +85023,11 @@ function generatePair() {
|
|
|
84837
85023
|
}
|
|
84838
85024
|
async function teeSignerNew(args, ctx) {
|
|
84839
85025
|
const { deps, json } = ctx;
|
|
84840
|
-
const parsed = parseArgs(args, {
|
|
85026
|
+
const parsed = parseArgs(args, {
|
|
85027
|
+
valueFlags: ["--key", "--out"],
|
|
85028
|
+
booleanFlags: ["--force", "--no-wait"],
|
|
85029
|
+
pathFlags: ["--out"]
|
|
85030
|
+
});
|
|
84841
85031
|
if ("error" in parsed) {
|
|
84842
85032
|
writeScreenUsageFailure(deps, `${parsed.error}. ${USAGE_TEE_SIGNER}`, json);
|
|
84843
85033
|
return 2;
|
|
@@ -84853,6 +85043,7 @@ async function teeSignerNew(args, ctx) {
|
|
|
84853
85043
|
}
|
|
84854
85044
|
const out = parsed.values["--out"];
|
|
84855
85045
|
const force = parsed.booleans.has("--force");
|
|
85046
|
+
const noWait = parsed.booleans.has("--no-wait");
|
|
84856
85047
|
await printIdentity(ctx);
|
|
84857
85048
|
const apiKey = await resolveApiKey(deps, ctx.profile);
|
|
84858
85049
|
if (!apiKey) {
|
|
@@ -84891,7 +85082,7 @@ async function teeSignerNew(args, ctx) {
|
|
|
84891
85082
|
entry = undefined;
|
|
84892
85083
|
}
|
|
84893
85084
|
if (entry !== undefined && active !== null && active.spkiSha256 === entry.spkiSha256) {
|
|
84894
|
-
const recorded = { ...entry, signerQuorumId: active.signerQuorumId };
|
|
85085
|
+
const recorded = { ...entry, signerQuorumId: active.signerQuorumId, pendingRequest: undefined };
|
|
84895
85086
|
await saveKeySignerEntry(deps, recorded);
|
|
84896
85087
|
await writeOut(ctx, out, recorded);
|
|
84897
85088
|
return reportApproved(ctx, view, recorded, { resumed: true, requested: false });
|
|
@@ -84940,12 +85131,17 @@ async function teeSignerNew(args, ctx) {
|
|
|
84940
85131
|
resumed = false;
|
|
84941
85132
|
}
|
|
84942
85133
|
await writeOut(ctx, out, entry);
|
|
84943
|
-
const
|
|
85134
|
+
const cached = matchingPendingRequest(ctx, view, entry);
|
|
85135
|
+
const requested = cached ? {
|
|
85136
|
+
ok: true,
|
|
85137
|
+
status: 200,
|
|
85138
|
+
body: { ...cached, spkiSha256: entry.spkiSha256, fingerprint: entry.fingerprint }
|
|
85139
|
+
} : await requestSigner(ctx, apiKey, entry.publicKeyDer);
|
|
84944
85140
|
if (!requested.ok) {
|
|
84945
85141
|
if (requested.code === "KEY_SIGNER_ALREADY_ACTIVE") {
|
|
84946
85142
|
const quorum = errorDetails(requested).keySigner?.signerQuorumId;
|
|
84947
85143
|
if (typeof quorum === "string") {
|
|
84948
|
-
const recorded = { ...entry, signerQuorumId: quorum };
|
|
85144
|
+
const recorded = { ...entry, signerQuorumId: quorum, pendingRequest: undefined };
|
|
84949
85145
|
await saveKeySignerEntry(deps, recorded);
|
|
84950
85146
|
const reread = await readSigner(ctx, "self", { apiKey });
|
|
84951
85147
|
return reportApproved(ctx, reread.ok ? reread.body : view, recorded, {
|
|
@@ -84982,6 +85178,27 @@ async function teeSignerNew(args, ctx) {
|
|
|
84982
85178
|
}, json);
|
|
84983
85179
|
return 1;
|
|
84984
85180
|
}
|
|
85181
|
+
entry = {
|
|
85182
|
+
...entry,
|
|
85183
|
+
pendingRequest: {
|
|
85184
|
+
apiUrl: ctx.apiUrl,
|
|
85185
|
+
userCode: body.userCode,
|
|
85186
|
+
verificationUri: body.verificationUri,
|
|
85187
|
+
verificationUriComplete: body.verificationUriComplete,
|
|
85188
|
+
expiresAt: body.expiresAt
|
|
85189
|
+
}
|
|
85190
|
+
};
|
|
85191
|
+
await saveKeySignerEntry(deps, entry);
|
|
85192
|
+
if (json) {
|
|
85193
|
+
deps.stdout.write(`${JSON.stringify({
|
|
85194
|
+
state: "pending",
|
|
85195
|
+
userCode: body.userCode,
|
|
85196
|
+
verificationUriComplete: body.verificationUriComplete,
|
|
85197
|
+
expiresAt: body.expiresAt,
|
|
85198
|
+
fingerprint: body.fingerprint
|
|
85199
|
+
})}
|
|
85200
|
+
`);
|
|
85201
|
+
}
|
|
84985
85202
|
if (json) {
|
|
84986
85203
|
writeFields(deps, "stderr", [
|
|
84987
85204
|
["Key", keyPrefix],
|
|
@@ -84992,7 +85209,7 @@ async function teeSignerNew(args, ctx) {
|
|
|
84992
85209
|
["Approve on owner machine", `candle keys signer approve ${body.userCode} --key ${keyPrefix}`],
|
|
84993
85210
|
["Expires", formatTime(body.expiresAt)]
|
|
84994
85211
|
], "Key signer request");
|
|
84995
|
-
notice(deps, "stderr", "note",
|
|
85212
|
+
notice(deps, "stderr", "note", `The account owner approves it, typing this full fingerprint.${noWait ? "" : " Waiting for approval..."}`);
|
|
84996
85213
|
} else {
|
|
84997
85214
|
writeFields(deps, "stderr", [
|
|
84998
85215
|
["Key", keyPrefix],
|
|
@@ -85004,9 +85221,11 @@ async function teeSignerNew(args, ctx) {
|
|
|
85004
85221
|
], "Key signer request");
|
|
85005
85222
|
notice(deps, "stderr", "warning", "The account owner approves it, typing this full fingerprint.");
|
|
85006
85223
|
notice(deps, "stderr", "note", `Or, on the owner's machine:
|
|
85007
|
-
candle keys signer approve ${body.userCode} --key ${keyPrefix}
|
|
85008
|
-
Waiting for approval...`);
|
|
85224
|
+
candle keys signer approve ${body.userCode} --key ${keyPrefix}${noWait ? "" : `
|
|
85225
|
+
Waiting for approval...`}`);
|
|
85009
85226
|
}
|
|
85227
|
+
if (noWait)
|
|
85228
|
+
return 0;
|
|
85010
85229
|
while (deps.now() < body.expiresAt) {
|
|
85011
85230
|
await deps.sleep(SIGNER_POLL_MS);
|
|
85012
85231
|
const read = await readSigner(ctx, "self", { apiKey });
|
|
@@ -85015,9 +85234,9 @@ Waiting for approval...`);
|
|
|
85015
85234
|
view = read.body;
|
|
85016
85235
|
const now = activeSigner(view);
|
|
85017
85236
|
if (now !== null && now.spkiSha256 === entry.spkiSha256) {
|
|
85018
|
-
const recorded = { ...entry, signerQuorumId: now.signerQuorumId };
|
|
85237
|
+
const recorded = { ...entry, signerQuorumId: now.signerQuorumId, pendingRequest: undefined };
|
|
85019
85238
|
await saveKeySignerEntry(deps, recorded);
|
|
85020
|
-
return reportApproved(ctx, view, recorded, { resumed, requested:
|
|
85239
|
+
return reportApproved(ctx, view, recorded, { resumed, requested: cached === undefined, replaces });
|
|
85021
85240
|
}
|
|
85022
85241
|
if (view.pending === null || view.pending.spkiSha256 !== entry.spkiSha256) {
|
|
85023
85242
|
writeScreenLocalFailure(deps, {
|
|
@@ -85037,6 +85256,58 @@ Waiting for approval...`);
|
|
|
85037
85256
|
}, json);
|
|
85038
85257
|
return 1;
|
|
85039
85258
|
}
|
|
85259
|
+
function matchingPendingRequest(ctx, view, entry) {
|
|
85260
|
+
const pending = entry.pendingRequest;
|
|
85261
|
+
return pending?.apiUrl === ctx.apiUrl && pending.expiresAt > ctx.deps.now() && view.pending?.spkiSha256 === entry.spkiSha256 && view.pending.expiresAt === pending.expiresAt ? pending : undefined;
|
|
85262
|
+
}
|
|
85263
|
+
async function teeSignerStatus(args, ctx) {
|
|
85264
|
+
const { deps, json } = ctx;
|
|
85265
|
+
const parsed = parseArgs(args, { valueFlags: ["--key"] });
|
|
85266
|
+
if ("error" in parsed || !parsed.values["--key"] || parsed.positionals.length > 0) {
|
|
85267
|
+
writeScreenUsageFailure(deps, USAGE_TEE_SIGNER, json);
|
|
85268
|
+
return 2;
|
|
85269
|
+
}
|
|
85270
|
+
await printIdentity(ctx);
|
|
85271
|
+
const apiKey = await resolveApiKey(deps, ctx.profile);
|
|
85272
|
+
if (!apiKey) {
|
|
85273
|
+
writeScreenLocalFailure(deps, { code: "NO_API_KEY", message: "tee signer status needs this key's API key." }, json);
|
|
85274
|
+
return 1;
|
|
85275
|
+
}
|
|
85276
|
+
const read = await readSigner(ctx, "self", { apiKey });
|
|
85277
|
+
if (!read.ok)
|
|
85278
|
+
return writeSignerReadFailure(ctx, read);
|
|
85279
|
+
const view = read.body;
|
|
85280
|
+
const keyRaw = parsed.values["--key"];
|
|
85281
|
+
if (keyRaw !== view.keyPrefix && keyRaw !== view.keyLabel) {
|
|
85282
|
+
writeScreenLocalFailure(deps, { code: "KEY_SIGNER_KEY_MISMATCH", message: `This API key is ${view.keyPrefix}, not ${keyRaw}.` }, json);
|
|
85283
|
+
return 1;
|
|
85284
|
+
}
|
|
85285
|
+
const locals = await localKeySigners(deps, view.keyPrefix);
|
|
85286
|
+
const entry = locals.find((local) => local.spkiSha256 === view.pending?.spkiSha256);
|
|
85287
|
+
const pending = entry ? matchingPendingRequest(ctx, view, entry) : undefined;
|
|
85288
|
+
if (json) {
|
|
85289
|
+
deps.stdout.write(`${JSON.stringify({
|
|
85290
|
+
ok: true,
|
|
85291
|
+
command: "tee signer status",
|
|
85292
|
+
...view,
|
|
85293
|
+
...pending ? {
|
|
85294
|
+
userCode: pending.userCode,
|
|
85295
|
+
verificationUriComplete: pending.verificationUriComplete,
|
|
85296
|
+
expiresAt: pending.expiresAt
|
|
85297
|
+
} : {}
|
|
85298
|
+
})}
|
|
85299
|
+
`);
|
|
85300
|
+
} else {
|
|
85301
|
+
writeFields(deps, "stdout", [
|
|
85302
|
+
["Key", view.keyPrefix],
|
|
85303
|
+
["State", view.state],
|
|
85304
|
+
["Fingerprint", view.pending?.fingerprint ?? view.fingerprint],
|
|
85305
|
+
["Approve at", pending?.verificationUriComplete],
|
|
85306
|
+
["Expires", view.pending?.expiresAt ? formatTime(view.pending.expiresAt, { now: deps.now() }) : undefined]
|
|
85307
|
+
], "Key signer status");
|
|
85308
|
+
}
|
|
85309
|
+
return 0;
|
|
85310
|
+
}
|
|
85040
85311
|
async function writeOut(ctx, out, entry) {
|
|
85041
85312
|
if (out === undefined)
|
|
85042
85313
|
return;
|
|
@@ -89541,6 +89812,9 @@ var REASON_WORDS = {
|
|
|
89541
89812
|
delegation_off: "the embedded wallet is not delegated for agent access",
|
|
89542
89813
|
embedded_wallet_not_permitted: "this key may not use the embedded wallet",
|
|
89543
89814
|
not_recipient: "the fees go to someone else, or the token has no fee vault",
|
|
89815
|
+
fee_wallet_designated: "the fees go to this launch's designated fee wallet, which claims them itself",
|
|
89816
|
+
claimable_by_key: "claimable by this key",
|
|
89817
|
+
claimable_via_web_session: "the fees go to this account's web wallet; a signed-in session claims them",
|
|
89544
89818
|
insufficient_gas: "the recipient wallet cannot cover the network fee"
|
|
89545
89819
|
};
|
|
89546
89820
|
function reasonText(reason, missing) {
|
|
@@ -89575,7 +89849,7 @@ async function feesList(args, ctx) {
|
|
|
89575
89849
|
notice(ctx.deps, "stdout", "note", "No launched tokens on this account.");
|
|
89576
89850
|
else {
|
|
89577
89851
|
const rows = page.map((row) => {
|
|
89578
|
-
const status = row.unreadable ? "could not be read" : row.claimable ?
|
|
89852
|
+
const status = row.unreadable ? "could not be read" : row.claimable ? `${row.reason === "claimable_by_key" ? "claimable by this key" : "claimable"}: ${amountsText(row.claimableAmounts, missing)}` : `not claimable: ${reasonText(row.reason, missing)}`;
|
|
89579
89853
|
return [
|
|
89580
89854
|
safeText(String(row.symbol ?? row.name ?? row.mint)),
|
|
89581
89855
|
safeText(String(row.chain)),
|
|
@@ -89612,7 +89886,53 @@ async function feesClaim(args, ctx) {
|
|
|
89612
89886
|
const dryRun = parsed.booleans.has("--dry-run");
|
|
89613
89887
|
try {
|
|
89614
89888
|
const key = await tradingKey(ctx);
|
|
89615
|
-
|
|
89889
|
+
let body = await request(ctx, key, `/api/v1/agent/fees/${encodeURIComponent(mint)}/claim`, {
|
|
89890
|
+
dryRun,
|
|
89891
|
+
teeSigning: true
|
|
89892
|
+
});
|
|
89893
|
+
if (!dryRun && Array.isArray(body.signing) && body.signing.length > 0) {
|
|
89894
|
+
if (body.signing.length > 2)
|
|
89895
|
+
throw new TradingError("INVALID_RESPONSE", "Too many fee claims to sign");
|
|
89896
|
+
const { rows, appId, keyPrefix } = await listTradingWallets(ctx, key, "launch:write");
|
|
89897
|
+
const signedClaims = [];
|
|
89898
|
+
for (const signing of body.signing) {
|
|
89899
|
+
const row = rows.find((row2) => row2.id === signing.walletId && row2.address === signing.wallet);
|
|
89900
|
+
if (!row || row.chain !== "solana")
|
|
89901
|
+
throw new TradingError("TEE_WALLET_REQUIRED", "The claim's wallet is not bound to this key");
|
|
89902
|
+
const wallet3 = await completeTradingWallet(ctx, row, appId, "account:read", "solana", {
|
|
89903
|
+
apiKey: key,
|
|
89904
|
+
keyPrefix
|
|
89905
|
+
});
|
|
89906
|
+
const rpc = signing.body;
|
|
89907
|
+
const transaction = rpc?.params?.transaction;
|
|
89908
|
+
if (typeof transaction !== "string" || !["streamer", "creator"].includes(String(signing.leg)))
|
|
89909
|
+
throw new TradingError("INVALID_RESPONSE", "The fee claim has no Solana transaction to sign");
|
|
89910
|
+
const approval = authorizationSignature(wallet3, transaction);
|
|
89911
|
+
if (JSON.stringify(approval.body) !== JSON.stringify(signing.body))
|
|
89912
|
+
throw new TradingError("INVALID_RESPONSE", "The fee claim's RPC body is not the exact Solana sign request");
|
|
89913
|
+
const signed = await request(ctx, key, `/api/v1/agent/wallets/${encodeURIComponent(wallet3.id)}/sign`, {
|
|
89914
|
+
...approval,
|
|
89915
|
+
feeClaim: true
|
|
89916
|
+
});
|
|
89917
|
+
if (typeof signed.signedTransaction !== "string" || signed.encoding !== "base64")
|
|
89918
|
+
throw new TradingError("INVALID_RESPONSE", "The relay returned no base64 signed fee claim");
|
|
89919
|
+
signedClaims.push({ walletId: wallet3.id, leg: signing.leg, signedTransaction: signed.signedTransaction });
|
|
89920
|
+
}
|
|
89921
|
+
const submitted = await request(ctx, key, `/api/v1/agent/fees/${encodeURIComponent(mint)}/claim/submit`, {
|
|
89922
|
+
signedClaims
|
|
89923
|
+
});
|
|
89924
|
+
body = {
|
|
89925
|
+
...submitted,
|
|
89926
|
+
claimed: [
|
|
89927
|
+
...Array.isArray(body.claimed) ? body.claimed : [],
|
|
89928
|
+
...Array.isArray(submitted.claimed) ? submitted.claimed : []
|
|
89929
|
+
],
|
|
89930
|
+
warnings: [
|
|
89931
|
+
...Array.isArray(body.warnings) ? body.warnings : [],
|
|
89932
|
+
...Array.isArray(submitted.warnings) ? submitted.warnings : []
|
|
89933
|
+
]
|
|
89934
|
+
};
|
|
89935
|
+
}
|
|
89616
89936
|
if (ctx.json)
|
|
89617
89937
|
return printTradingResult(ctx, body);
|
|
89618
89938
|
const style = terminalStyle(ctx.deps, "stdout");
|
|
@@ -89689,6 +90009,7 @@ init_render();
|
|
|
89689
90009
|
init_promote_support();
|
|
89690
90010
|
init_key_output();
|
|
89691
90011
|
init_keys();
|
|
90012
|
+
init_keys_embedded_wallet();
|
|
89692
90013
|
init_tee_rebind();
|
|
89693
90014
|
var KEYS_PATH4 = "/api/v1/agent/keys";
|
|
89694
90015
|
var USAGE_ACCESS = `Usage: candle keys access <prefix|label|self> --access <read|write|withdraw> [--yes] [--json]
|
|
@@ -89852,6 +90173,7 @@ async function keysAccess(args, ctx) {
|
|
|
89852
90173
|
let path;
|
|
89853
90174
|
let auth;
|
|
89854
90175
|
let credentials;
|
|
90176
|
+
let embeddedWallet;
|
|
89855
90177
|
if (self) {
|
|
89856
90178
|
const apiKey = await resolveApiKey(deps, ctx.profile);
|
|
89857
90179
|
if (!apiKey) {
|
|
@@ -89873,6 +90195,7 @@ async function keysAccess(args, ctx) {
|
|
|
89873
90195
|
});
|
|
89874
90196
|
if (!resolved.ok)
|
|
89875
90197
|
return resolved.code;
|
|
90198
|
+
embeddedWallet = resolved.embeddedWallet;
|
|
89876
90199
|
path = `${KEYS_PATH4}/${encodeURIComponent(resolved.keyPrefix)}/access`;
|
|
89877
90200
|
auth = "device";
|
|
89878
90201
|
credentials = { deviceToken };
|
|
@@ -89911,6 +90234,9 @@ async function keysAccess(args, ctx) {
|
|
|
89911
90234
|
const account = fields2.account ? shortAddress2(fields2.account) : json ? "unknown" : symbols(terminalStyle(deps, "stderr")).missing;
|
|
89912
90235
|
deps.stderr.write(`${accessScreen(shown, { account: fields2.username ? `${fields2.username} (${account})` : account, apiUrl }, json ? undefined : terminalStyle(deps, "stderr"))}
|
|
89913
90236
|
`);
|
|
90237
|
+
if (shown.to.access === "readwritetransfer" && embeddedWallet === "denied") {
|
|
90238
|
+
notice(deps, "stderr", "warning", withdrawNeedsEmbeddedWalletLine(shown.keyPrefix));
|
|
90239
|
+
}
|
|
89914
90240
|
if (widen) {
|
|
89915
90241
|
const typed = await deps.promptLine(`Type the key prefix ${shown.keyPrefix} to widen it: `);
|
|
89916
90242
|
if (typed.trim() !== shown.keyPrefix) {
|
|
@@ -90742,7 +91068,8 @@ function decodeAddress(value, flag) {
|
|
|
90742
91068
|
throw new Error(`${flag} takes a Solana address.`);
|
|
90743
91069
|
}
|
|
90744
91070
|
}
|
|
90745
|
-
|
|
91071
|
+
var fromLauncher = (echo) => echo?.feeWalletSource === "launching_wallet" && typeof echo.feeWallet === "string";
|
|
91072
|
+
function feeWalletLines(selection, echo, launcherDefault = false) {
|
|
90746
91073
|
if (selection)
|
|
90747
91074
|
return [
|
|
90748
91075
|
`Fee wallet: ${selection.feeWallet} (earns this launch's fees; set by --fee-wallet)`,
|
|
@@ -90750,13 +91077,20 @@ function feeWalletLines(selection, echo) {
|
|
|
90750
91077
|
`Fee wallet multisig: ${selection.feeWalletMultisig.address}, vault ${selection.feeWalletMultisig.vaultIndex} (the Squads v4 multisig behind the fee wallet)`
|
|
90751
91078
|
] : []
|
|
90752
91079
|
];
|
|
91080
|
+
if (fromLauncher(echo))
|
|
91081
|
+
return [
|
|
91082
|
+
`Fee wallet: ${echo.feeWallet} (defaults to the launching wallet; pass --fee-wallet <address> to change it)`
|
|
91083
|
+
];
|
|
91084
|
+
if (launcherDefault)
|
|
91085
|
+
return ["Fee wallet: defaults to the launching wallet (pass --fee-wallet <address> to change it)."];
|
|
90753
91086
|
return [
|
|
90754
|
-
echo ? `Fee wallet: ${echo} (defaults to the account wallet; pass --fee-wallet <address> to change it)` : "Fee wallet: defaults to the account wallet (pass --fee-wallet <address> to change it)."
|
|
91087
|
+
echo?.feeWallet ? `Fee wallet: ${echo.feeWallet} (defaults to the account wallet; pass --fee-wallet <address> to change it)` : "Fee wallet: defaults to the account wallet (pass --fee-wallet <address> to change it)."
|
|
90755
91088
|
];
|
|
90756
91089
|
}
|
|
90757
|
-
|
|
91090
|
+
var FEE_WALLET_DEFAULT_WARNING = "No --fee-wallet given: this launch's curve and pool fees earn to the launching wallet, or to the account wallet on a server that does not record the launching wallet; the confirmation names which. Pass --fee-wallet <address> to send them to another wallet.";
|
|
91091
|
+
function feeWalletQuoteLine(selection, echo) {
|
|
90758
91092
|
if (!selection)
|
|
90759
|
-
return "defaults to the account wallet";
|
|
91093
|
+
return fromLauncher(echo) ? `${echo.feeWallet} (the launching wallet, by default)` : "defaults to the account wallet";
|
|
90760
91094
|
return `${selection.feeWallet}${selection.feeWalletMultisig ? ` (Squads vault of ${selection.feeWalletMultisig.address}, vault ${selection.feeWalletMultisig.vaultIndex})` : ""}`;
|
|
90761
91095
|
}
|
|
90762
91096
|
|
|
@@ -90903,7 +91237,8 @@ var checkSchema = exports_external.object({
|
|
|
90903
91237
|
switches: exports_external.object({
|
|
90904
91238
|
headlessLaunch: exports_external.boolean(),
|
|
90905
91239
|
agentTrading: exports_external.boolean(),
|
|
90906
|
-
teeLaunchBuys: exports_external.boolean()
|
|
91240
|
+
teeLaunchBuys: exports_external.boolean(),
|
|
91241
|
+
feeWalletFromLauncher: exports_external.boolean().optional()
|
|
90907
91242
|
}),
|
|
90908
91243
|
embedded: exports_external.object({
|
|
90909
91244
|
solana: exports_external.object({ address: exports_external.string(), delegated: exports_external.boolean().optional() }).nullable().optional(),
|
|
@@ -91015,8 +91350,9 @@ async function launchCheck(ctx, key, walletFlag, requestedChain, feeWallet = und
|
|
|
91015
91350
|
const lines = [`Launch paths for key ${answer2.key.keyPrefix} (tier ${answer2.key.tier}):`];
|
|
91016
91351
|
if (requestedChain)
|
|
91017
91352
|
lines.push(`Requested launch chain: ${chainName(requestedChain)}.`);
|
|
91353
|
+
const launcherDefault = answer2.switches.feeWalletFromLauncher === true;
|
|
91018
91354
|
if (!requestedChain || requestedChain === "solana")
|
|
91019
|
-
lines.push(...feeWalletLines(feeWallet));
|
|
91355
|
+
lines.push(...feeWalletLines(feeWallet, undefined, launcherDefault));
|
|
91020
91356
|
if (walletFlag === undefined || embeddedOnly)
|
|
91021
91357
|
lines.push(...embeddedLines(answer2, requestedChain));
|
|
91022
91358
|
for (const row of shownWallets)
|
|
@@ -91030,7 +91366,7 @@ async function launchCheck(ctx, key, walletFlag, requestedChain, feeWallet = und
|
|
|
91030
91366
|
...requestedChain ? { requestedChain } : {},
|
|
91031
91367
|
key: answer2.key,
|
|
91032
91368
|
switches: answer2.switches,
|
|
91033
|
-
...feeWallet ? { feeWallet: { ...feeWallet, set: true } } : { feeWallet: { default: "account wallet", set: false } },
|
|
91369
|
+
...feeWallet ? { feeWallet: { ...feeWallet, set: true } } : { feeWallet: { default: launcherDefault ? "launching wallet" : "account wallet", set: false } },
|
|
91034
91370
|
...answer2.embedded ? { embedded: answer2.embedded } : {},
|
|
91035
91371
|
wallets: shownWallets.map((row) => ({
|
|
91036
91372
|
...row,
|
|
@@ -91109,7 +91445,8 @@ var LAUNCH_ARG_SPEC = {
|
|
|
91109
91445
|
booleanFlags: [
|
|
91110
91446
|
...launchFlagFields.filter((field) => field.type === "boolean").map((field) => field.cli.flag),
|
|
91111
91447
|
"--check",
|
|
91112
|
-
"--yes"
|
|
91448
|
+
"--yes",
|
|
91449
|
+
"--require-fee-wallet"
|
|
91113
91450
|
],
|
|
91114
91451
|
pathFlags: ["--image", "--banner"]
|
|
91115
91452
|
};
|
|
@@ -91251,6 +91588,10 @@ async function launch(args, ctx) {
|
|
|
91251
91588
|
writeUsageFailure(ctx.deps, `${error.message}. ${USAGE9}`, ctx.json);
|
|
91252
91589
|
return 2;
|
|
91253
91590
|
}
|
|
91591
|
+
if (parsed.booleans.has("--require-fee-wallet") && !feeWallet) {
|
|
91592
|
+
writeUsageFailure(ctx.deps, `--require-fee-wallet needs --fee-wallet <address>: pass the wallet this launch's fees earn to. ${USAGE9}`, ctx.json);
|
|
91593
|
+
return 2;
|
|
91594
|
+
}
|
|
91254
91595
|
try {
|
|
91255
91596
|
if (flags["--buy"]?.trim() && !/^0+(\.0+)?$/.test(flags["--buy"]))
|
|
91256
91597
|
launchBuyRaw(flags["--buy"], 18);
|
|
@@ -91318,6 +91659,8 @@ async function launch(args, ctx) {
|
|
|
91318
91659
|
const chain2 = wallet3.chain === "evm" ? "hood" : "solana";
|
|
91319
91660
|
if (chain2 === "hood" && flags["--dex-version"] === undefined)
|
|
91320
91661
|
throw new TradingUsage(`${wallet3.address} is a Hood TEE wallet, and a Hood launch needs --dex-version v3|v4.`);
|
|
91662
|
+
if (chain2 === "solana" && !dryRun && !feeWallet)
|
|
91663
|
+
notice(ctx.deps, "stderr", "warning", FEE_WALLET_DEFAULT_WARNING);
|
|
91321
91664
|
const buy = launchBuy(flags, chain2);
|
|
91322
91665
|
if (chain2 === "hood" && (Object.keys(poolRequestFields(flags)).length || feeWalletGiven(flags)))
|
|
91323
91666
|
throw new TradingUsage("Pool fee and pool shape choices are Solana only, and so is --fee-wallet; a Hood launch cannot take them.");
|
|
@@ -91370,7 +91713,7 @@ async function launch(args, ctx) {
|
|
|
91370
91713
|
fee: built.fee ?? { bps: 0, feeRaw: "0" },
|
|
91371
91714
|
minimumReceived: buy ? "not quoted (the first buy on the new curve)" : "0 tokens (no first buy)",
|
|
91372
91715
|
maxDebitLamports: built.maxDebitLamports,
|
|
91373
|
-
feeWallet: feeWalletQuoteLine(feeWallet),
|
|
91716
|
+
feeWallet: feeWalletQuoteLine(feeWallet, built),
|
|
91374
91717
|
tokenRisks: []
|
|
91375
91718
|
};
|
|
91376
91719
|
notice(ctx.deps, "stderr", "note", buyNotice(buy, "solana").trim());
|
|
@@ -91500,14 +91843,19 @@ var previewSchema = exports_external.object({
|
|
|
91500
91843
|
}).passthrough().optional(),
|
|
91501
91844
|
devBuyMax: exports_external.string().nullable().optional(),
|
|
91502
91845
|
feeWallet: exports_external.string().optional(),
|
|
91846
|
+
feeWalletSource: exports_external.string().optional(),
|
|
91503
91847
|
fills: exports_external.array(exports_external.object({ index: exports_external.number(), amountRaw: exports_external.string(), supplyPct: exports_external.number() }).passthrough()).optional()
|
|
91504
91848
|
}).passthrough();
|
|
91505
91849
|
var percent = (value) => `${Number(value.toFixed(4))}%`;
|
|
91850
|
+
function feeWalletEchoOf(answer2) {
|
|
91851
|
+
const parsed = exports_external.object({ feeWallet: exports_external.string().optional(), feeWalletSource: exports_external.string().optional() }).safeParse(answer2);
|
|
91852
|
+
return parsed.success ? parsed.data : undefined;
|
|
91853
|
+
}
|
|
91506
91854
|
function previewLines(answer2, chain2, feeWallet = undefined) {
|
|
91507
91855
|
const parsed = previewSchema.safeParse(answer2);
|
|
91508
91856
|
if (!parsed.success)
|
|
91509
91857
|
return "";
|
|
91510
|
-
const { resolved, curveTerms: terms, devBuyMax, feeWallet: echo, fills } = parsed.data;
|
|
91858
|
+
const { resolved, curveTerms: terms, devBuyMax, feeWallet: echo, feeWalletSource, fills } = parsed.data;
|
|
91511
91859
|
const quote = (resolved?.quoteAsset ?? LAUNCH_DEFINITION.defaultQuote[chain2]).toUpperCase();
|
|
91512
91860
|
const decimals = (chain2 === "hood" ? HOOD_BASES[quote] : BASES[quote])?.decimals;
|
|
91513
91861
|
const whole = (raw) => decimals === undefined ? `${raw} raw` : `${shortAmount(raw, decimals)} ${quote}`;
|
|
@@ -91526,7 +91874,7 @@ function previewLines(answer2, chain2, feeWallet = undefined) {
|
|
|
91526
91874
|
if (devBuyMax)
|
|
91527
91875
|
lines.push(`Dev-buy ceiling: ${whole(devBuyMax)}.`);
|
|
91528
91876
|
if (chain2 === "solana")
|
|
91529
|
-
lines.push(...feeWalletLines(feeWallet, echo));
|
|
91877
|
+
lines.push(...feeWalletLines(feeWallet, { feeWallet: echo, feeWalletSource }));
|
|
91530
91878
|
return lines.length ? `${lines.join(`
|
|
91531
91879
|
`)}
|
|
91532
91880
|
` : "";
|
|
@@ -91592,7 +91940,9 @@ var atomicLegSchema = exports_external.object({
|
|
|
91592
91940
|
var atomicBuildSchema = exports_external.object({
|
|
91593
91941
|
bundleId: exports_external.string().min(1),
|
|
91594
91942
|
legs: exports_external.array(atomicLegSchema),
|
|
91595
|
-
expiresAt: exports_external.number().finite()
|
|
91943
|
+
expiresAt: exports_external.number().finite(),
|
|
91944
|
+
feeWallet: exports_external.string().optional(),
|
|
91945
|
+
feeWalletSource: exports_external.string().optional()
|
|
91596
91946
|
});
|
|
91597
91947
|
var atomicOutcomeSchema = exports_external.discriminatedUnion("status", [
|
|
91598
91948
|
exports_external.object({ status: exports_external.literal("landed"), bundleId: exports_external.string(), mint: exports_external.string(), signatures: exports_external.array(exports_external.string()) }).passthrough(),
|
|
@@ -91636,7 +91986,7 @@ async function atomicLaunch(ctx, key, args) {
|
|
|
91636
91986
|
candleFee: reading.feeRaw > 0n ? `${decimalAmount(reading.feeRaw.toString(), decimals)} ${quoteAsset} (the tier fee on the first buy, in its leg)` : "none on this first buy",
|
|
91637
91987
|
minimumReceived: minOut !== undefined ? `${minOut} raw units of the new token (the first buy's floor)` : "not quoted",
|
|
91638
91988
|
maxLoss: quoteAsset === "SOL" ? sol : `${sol} and ${decimalAmount(reading.quoteRaw.toString(), decimals)} ${quoteAsset}`,
|
|
91639
|
-
feeWallet: feeWalletQuoteLine(feeWalletSelection(flags)),
|
|
91989
|
+
feeWallet: feeWalletQuoteLine(feeWalletSelection(flags), built),
|
|
91640
91990
|
tokenRisks: [],
|
|
91641
91991
|
bundleId: built.bundleId
|
|
91642
91992
|
};
|
|
@@ -91767,6 +92117,8 @@ async function embeddedLaunch(ctx, key, args) {
|
|
|
91767
92117
|
firstBuyCeiling(ctx, flags, buy);
|
|
91768
92118
|
if (chain2 === "hood" && (Object.keys(poolRequestFields(flags)).length || feeWalletGiven(flags)))
|
|
91769
92119
|
throw new TradingUsage("Pool fee and pool shape choices are Solana only, and so is --fee-wallet; a Hood launch cannot take them.");
|
|
92120
|
+
if (chain2 === "solana" && !args.dryRun && !feeWalletGiven(flags))
|
|
92121
|
+
notice(ctx.deps, "stderr", "warning", FEE_WALLET_DEFAULT_WARNING);
|
|
91770
92122
|
const wallet3 = await embeddedLaunchWallet(ctx, key, chain2);
|
|
91771
92123
|
const uploaded = await uploadLaunchImages(ctx, key, args);
|
|
91772
92124
|
if (typeof uploaded === "number")
|
|
@@ -91796,7 +92148,7 @@ async function embeddedLaunch(ctx, key, args) {
|
|
|
91796
92148
|
venue: "curve launch (signed by Candle)",
|
|
91797
92149
|
priceImpactPct: null,
|
|
91798
92150
|
minimumReceived: "0 tokens (no first buy)",
|
|
91799
|
-
...chain2 === "solana" ? { feeWallet: feeWalletQuoteLine(feeWalletSelection(flags)) } : {},
|
|
92151
|
+
...chain2 === "solana" ? { feeWallet: feeWalletQuoteLine(feeWalletSelection(flags), feeWalletEchoOf(answer2)) } : {},
|
|
91800
92152
|
tokenRisks: []
|
|
91801
92153
|
};
|
|
91802
92154
|
if (buy && chain2 === "solana") {
|
|
@@ -92023,6 +92375,92 @@ async function resumeHoodLaunch(ctx, key, id, operationId, prior) {
|
|
|
92023
92375
|
return printTradingResult(ctx, { ...result, clientTradeId: id, kind: "launch", chain: "hood", operationId });
|
|
92024
92376
|
}
|
|
92025
92377
|
|
|
92378
|
+
// src/commands/launch-status.ts
|
|
92379
|
+
init_args();
|
|
92380
|
+
init_presentation();
|
|
92381
|
+
init_render();
|
|
92382
|
+
init_trading();
|
|
92383
|
+
init_swap();
|
|
92384
|
+
var USAGE10 = "Usage: candle launch status <mint-or-client-launch-id> [--wait]";
|
|
92385
|
+
var LAUNCH_WAIT_MS = 10 * 60 * 1000;
|
|
92386
|
+
var LAUNCH_WAIT_POLL_MS = 1e4;
|
|
92387
|
+
var LIVE = new Set(["trading", "bonded", "migrated"]);
|
|
92388
|
+
function statusOf(body) {
|
|
92389
|
+
const status = body.status;
|
|
92390
|
+
return status !== null && typeof status === "object" && !Array.isArray(status) ? status : null;
|
|
92391
|
+
}
|
|
92392
|
+
function isFinal(status) {
|
|
92393
|
+
if (!status)
|
|
92394
|
+
return false;
|
|
92395
|
+
if (status.failed === true)
|
|
92396
|
+
return true;
|
|
92397
|
+
return typeof status.state === "string" && LIVE.has(status.state);
|
|
92398
|
+
}
|
|
92399
|
+
function humanLines(status) {
|
|
92400
|
+
const lines = [`state: ${safeText(String(status.state ?? ""))}`];
|
|
92401
|
+
if (status.failed === true)
|
|
92402
|
+
lines.push("failed: true");
|
|
92403
|
+
if (typeof status.errorCode === "string")
|
|
92404
|
+
lines.push(`error: ${safeText(status.errorCode)}`);
|
|
92405
|
+
if (typeof status.chain === "string")
|
|
92406
|
+
lines.push(`chain: ${safeText(status.chain)}`);
|
|
92407
|
+
if (typeof status.mint === "string")
|
|
92408
|
+
lines.push(`mint: ${safeText(status.mint)}`);
|
|
92409
|
+
if (typeof status.clientLaunchId === "string")
|
|
92410
|
+
lines.push(`clientLaunchId: ${safeText(status.clientLaunchId)}`);
|
|
92411
|
+
if (typeof status.lifecycle === "string")
|
|
92412
|
+
lines.push(`lifecycle: ${safeText(status.lifecycle)}`);
|
|
92413
|
+
const migration = status.migration;
|
|
92414
|
+
if (migration !== null && typeof migration === "object" && !Array.isArray(migration)) {
|
|
92415
|
+
const block2 = migration;
|
|
92416
|
+
const progress = typeof block2.progressBps === "number" ? ` ${String(block2.progressBps)} bps` : "";
|
|
92417
|
+
if (typeof block2.status === "string")
|
|
92418
|
+
lines.push(`migration: ${safeText(block2.status)}${progress}`);
|
|
92419
|
+
}
|
|
92420
|
+
if (typeof status.signature === "string")
|
|
92421
|
+
lines.push(`signature: ${safeText(status.signature)}`);
|
|
92422
|
+
return lines;
|
|
92423
|
+
}
|
|
92424
|
+
async function readStatus(ctx, key, id) {
|
|
92425
|
+
return request(ctx, key, `/api/v1/launch/status/${encodeURIComponent(id)}`);
|
|
92426
|
+
}
|
|
92427
|
+
async function launchStatus(args, ctx) {
|
|
92428
|
+
const parsed = parseArgs(args, { booleanFlags: ["--wait"] });
|
|
92429
|
+
if ("error" in parsed || parsed.positionals.length !== 1 || !validClientId(parsed.positionals[0] ?? "")) {
|
|
92430
|
+
writeUsageFailure(ctx.deps, USAGE10, ctx.json);
|
|
92431
|
+
return 2;
|
|
92432
|
+
}
|
|
92433
|
+
const id = parsed.positionals[0];
|
|
92434
|
+
const wait = parsed.booleans.has("--wait");
|
|
92435
|
+
try {
|
|
92436
|
+
const key = await tradingKey(ctx);
|
|
92437
|
+
let body = await readStatus(ctx, key, id);
|
|
92438
|
+
if (wait) {
|
|
92439
|
+
const deadline = ctx.deps.now() + LAUNCH_WAIT_MS;
|
|
92440
|
+
while (!isFinal(statusOf(body)) && ctx.deps.now() < deadline) {
|
|
92441
|
+
await ctx.deps.sleep(LAUNCH_WAIT_POLL_MS);
|
|
92442
|
+
body = await readStatus(ctx, key, id);
|
|
92443
|
+
}
|
|
92444
|
+
const final = isFinal(statusOf(body));
|
|
92445
|
+
if (!final) {
|
|
92446
|
+
notice(ctx.deps, "stderr", "note", `Still not trading after ten minutes. Re-check with candle launch status ${safeText(id)} --wait.`);
|
|
92447
|
+
}
|
|
92448
|
+
body = { ...body, waited: { final } };
|
|
92449
|
+
}
|
|
92450
|
+
if (ctx.json)
|
|
92451
|
+
return printTradingResult(ctx, body);
|
|
92452
|
+
const status = statusOf(body);
|
|
92453
|
+
if (!status)
|
|
92454
|
+
return printTradingResult(ctx, body);
|
|
92455
|
+
ctx.deps.stdout.write(`${humanLines(status).join(`
|
|
92456
|
+
`)}
|
|
92457
|
+
`);
|
|
92458
|
+
return 0;
|
|
92459
|
+
} catch (error) {
|
|
92460
|
+
return tradingFailure(ctx, error);
|
|
92461
|
+
}
|
|
92462
|
+
}
|
|
92463
|
+
|
|
92026
92464
|
// src/commands/lp.ts
|
|
92027
92465
|
init_args();
|
|
92028
92466
|
init_client();
|
|
@@ -92485,6 +92923,7 @@ var MCP_TOOL_NAMES = [
|
|
|
92485
92923
|
"candle_execution_status",
|
|
92486
92924
|
"candle_get_operation",
|
|
92487
92925
|
"candle_get_fees",
|
|
92926
|
+
"candle_atomic_launch_dry_run",
|
|
92488
92927
|
"candle_claim_fees",
|
|
92489
92928
|
"candle_get_staking",
|
|
92490
92929
|
"candle_stake",
|
|
@@ -97883,7 +98322,7 @@ init_swap();
|
|
|
97883
98322
|
init_vault_support();
|
|
97884
98323
|
var WITHDRAW_SCOPE = "perps:withdraw";
|
|
97885
98324
|
var ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
97886
|
-
var
|
|
98325
|
+
var USAGE11 = "Usage: candle perps withdraw <amount> --to solana|hood [--wallet <evm tee>] [--id <id>] [--keystore <path>] | candle perps withdraw status <id>";
|
|
97887
98326
|
var WITHDRAW_POLL_MS = 10 * 60 * 1000;
|
|
97888
98327
|
var WITHDRAW_POLL_INTERVAL_MS = 5000;
|
|
97889
98328
|
var TERMINAL = new Set(["settled", "refunded", "failed"]);
|
|
@@ -97934,7 +98373,7 @@ async function withdrawStatus(ctx, id) {
|
|
|
97934
98373
|
async function perpsWithdraw(args, ctx) {
|
|
97935
98374
|
if (args[0] === "status") {
|
|
97936
98375
|
if (args.length !== 2 || !ID_RE.test(args[1]))
|
|
97937
|
-
return usage10(ctx,
|
|
98376
|
+
return usage10(ctx, USAGE11);
|
|
97938
98377
|
try {
|
|
97939
98378
|
return await withdrawStatus(ctx, args[1]);
|
|
97940
98379
|
} catch (error) {
|
|
@@ -97953,7 +98392,7 @@ async function perpsWithdraw(args, ctx) {
|
|
|
97953
98392
|
const [amount] = parsed.positionals;
|
|
97954
98393
|
const to = parsed.values["--to"];
|
|
97955
98394
|
if (parsed.positionals.length !== 1 || !amount || to !== "solana" && to !== "hood")
|
|
97956
|
-
return usage10(ctx,
|
|
98395
|
+
return usage10(ctx, USAGE11);
|
|
97957
98396
|
const amountMicros = parseWithdrawAmount(amount);
|
|
97958
98397
|
if (amountMicros === null)
|
|
97959
98398
|
return usage10(ctx, "The amount must be a positive USDC decimal with at most 6 decimals.");
|
|
@@ -99841,12 +100280,12 @@ function planPromotionLine2(table) {
|
|
|
99841
100280
|
init_presentation();
|
|
99842
100281
|
init_render();
|
|
99843
100282
|
init_key_output();
|
|
99844
|
-
var
|
|
100283
|
+
var USAGE12 = "Usage: candle plans";
|
|
99845
100284
|
async function plans(args, ctx) {
|
|
99846
100285
|
const parsed = parseArgs(args, {});
|
|
99847
100286
|
if ("error" in parsed || parsed.positionals.length > 0) {
|
|
99848
100287
|
writeScreenUsageFailure(ctx.deps, "error" in parsed ? `${parsed.error}
|
|
99849
|
-
${
|
|
100288
|
+
${USAGE12}` : USAGE12, ctx.json);
|
|
99850
100289
|
return 2;
|
|
99851
100290
|
}
|
|
99852
100291
|
const result = await apiRequest("/api/v1/agent/plans", {
|
|
@@ -102120,7 +102559,7 @@ init_client();
|
|
|
102120
102559
|
init_human_output();
|
|
102121
102560
|
init_presentation();
|
|
102122
102561
|
init_render();
|
|
102123
|
-
var
|
|
102562
|
+
var USAGE13 = "Usage: candle scan <contract address> [--chain solana|hood] [--json]";
|
|
102124
102563
|
var OTHER_CHAIN = "Candle trades Solana and Hood today; that is not a Solana or Hood contract address.";
|
|
102125
102564
|
var HOOD_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
102126
102565
|
var SOLANA_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
@@ -102220,20 +102659,20 @@ async function scan(args, ctx) {
|
|
|
102220
102659
|
}
|
|
102221
102660
|
const named = parsed.values["--chain"];
|
|
102222
102661
|
if (parsed.positionals.length !== 1 || named !== undefined && named !== "solana" && named !== "hood") {
|
|
102223
|
-
writeUsageFailure(ctx.deps,
|
|
102662
|
+
writeUsageFailure(ctx.deps, USAGE13, ctx.json);
|
|
102224
102663
|
return 2;
|
|
102225
102664
|
}
|
|
102226
102665
|
const address = parsed.positionals[0].trim();
|
|
102227
102666
|
const found = chainOfAddress(address);
|
|
102228
102667
|
if (found === null) {
|
|
102229
102668
|
writeUsageFailure(ctx.deps, `${OTHER_CHAIN}
|
|
102230
|
-
${
|
|
102669
|
+
${USAGE13}`, ctx.json);
|
|
102231
102670
|
return 2;
|
|
102232
102671
|
}
|
|
102233
102672
|
if (named !== undefined && named !== found) {
|
|
102234
102673
|
const label = (c) => c === "hood" ? "Hood" : "Solana";
|
|
102235
102674
|
writeUsageFailure(ctx.deps, `That address is a ${label(found)} contract, not a ${label(named)} one. Candle trades Solana and Hood today.
|
|
102236
|
-
${
|
|
102675
|
+
${USAGE13}`, ctx.json);
|
|
102237
102676
|
return 2;
|
|
102238
102677
|
}
|
|
102239
102678
|
const chain2 = found;
|
|
@@ -104111,7 +104550,7 @@ init_deps();
|
|
|
104111
104550
|
init_human_output();
|
|
104112
104551
|
init_render();
|
|
104113
104552
|
var KINDS = ["trade", "signal", "callout"];
|
|
104114
|
-
var
|
|
104553
|
+
var USAGE14 = "Usage: candle token events <mint> [--chain solana|hood] [--kind trade|signal|callout] [--since <iso-8601>] [--limit <n>] [--before <cursor>] [--json]";
|
|
104115
104554
|
function readEventsBody(body) {
|
|
104116
104555
|
if (body === null || typeof body !== "object" || !Array.isArray(body.events))
|
|
104117
104556
|
return null;
|
|
@@ -104169,11 +104608,11 @@ async function tokenEvents(args, ctx) {
|
|
|
104169
104608
|
const since = parsed.values["--since"];
|
|
104170
104609
|
const before = parsed.values["--before"];
|
|
104171
104610
|
if (parsed.positionals.length !== 1 || chain2 !== "solana" && chain2 !== "hood") {
|
|
104172
|
-
writeUsageFailure(ctx.deps,
|
|
104611
|
+
writeUsageFailure(ctx.deps, USAGE14, ctx.json);
|
|
104173
104612
|
return 2;
|
|
104174
104613
|
}
|
|
104175
104614
|
if (kind !== undefined && !KINDS.includes(kind)) {
|
|
104176
|
-
writeUsageFailure(ctx.deps,
|
|
104615
|
+
writeUsageFailure(ctx.deps, USAGE14, ctx.json);
|
|
104177
104616
|
return 2;
|
|
104178
104617
|
}
|
|
104179
104618
|
if (limit !== undefined && !/^[1-9][0-9]{0,3}$/.test(limit) || Number(limit) > 1000) {
|
|
@@ -104270,7 +104709,7 @@ var TOKEN_PAGE_BODY_CAP_BYTES = 32 * 1024;
|
|
|
104270
104709
|
var TOKEN_PAGE_SECTIONS = ["thesis", "tokenomics", "lore"];
|
|
104271
104710
|
var FLAG_REASONS = ["spam", "scam", "abuse", "misleading", "unsafe_link", "other"];
|
|
104272
104711
|
var FLAG_DETAIL_LIMIT = 500;
|
|
104273
|
-
var
|
|
104712
|
+
var USAGE15 = {
|
|
104274
104713
|
about: "Usage: candle token about <address> [--chain solana|hood] [--capabilities]",
|
|
104275
104714
|
claim: "Usage: candle token claim <address> [--chain solana|hood] [--creator [--challenge [--message-out <path>] | --challenge-id <id> --signature <sig>]] [--idempotency-key <key>]",
|
|
104276
104715
|
section: `Usage: candle token section set <address> <thesis|tokenomics|lore> --file <path> --base-revision <n> [--clear] [--chain solana|hood] [--idempotency-key <key>]
|
|
@@ -104530,7 +104969,7 @@ function terminalTextKeepingLines(text4) {
|
|
|
104530
104969
|
`);
|
|
104531
104970
|
}
|
|
104532
104971
|
async function tokenAbout(args, ctx) {
|
|
104533
|
-
const usage12 = usageFor(ctx,
|
|
104972
|
+
const usage12 = usageFor(ctx, USAGE15.about);
|
|
104534
104973
|
const parsed = parseArgs(args, { valueFlags: ["--chain"], booleanFlags: ["--capabilities"] });
|
|
104535
104974
|
if ("error" in parsed)
|
|
104536
104975
|
return usage12(parsed.error);
|
|
@@ -104576,7 +105015,7 @@ async function tokenAbout(args, ctx) {
|
|
|
104576
105015
|
return 0;
|
|
104577
105016
|
}
|
|
104578
105017
|
async function tokenClaim(args, ctx) {
|
|
104579
|
-
const usage12 = usageFor(ctx,
|
|
105018
|
+
const usage12 = usageFor(ctx, USAGE15.claim);
|
|
104580
105019
|
const parsed = parseArgs(args, {
|
|
104581
105020
|
valueFlags: ["--chain", "--challenge-id", "--signature", "--message-out", "--idempotency-key"],
|
|
104582
105021
|
booleanFlags: ["--creator", "--challenge"],
|
|
@@ -104703,7 +105142,7 @@ var SECTION_FLAGS = {
|
|
|
104703
105142
|
restore: { valueFlags: ["--chain", "--version-id", "--base-revision", "--idempotency-key"], booleanFlags: [] }
|
|
104704
105143
|
};
|
|
104705
105144
|
async function tokenSection(args, ctx) {
|
|
104706
|
-
const usage12 = usageFor(ctx,
|
|
105145
|
+
const usage12 = usageFor(ctx, USAGE15.section);
|
|
104707
105146
|
const action2 = args[0];
|
|
104708
105147
|
if (action2 !== "set" && action2 !== "restore")
|
|
104709
105148
|
return usage12();
|
|
@@ -104796,7 +105235,7 @@ async function tokenSection(args, ctx) {
|
|
|
104796
105235
|
return 0;
|
|
104797
105236
|
}
|
|
104798
105237
|
async function tokenHistory(args, ctx) {
|
|
104799
|
-
const usage12 = usageFor(ctx,
|
|
105238
|
+
const usage12 = usageFor(ctx, USAGE15.history);
|
|
104800
105239
|
const parsed = parseArgs(args, { valueFlags: ["--chain", "--limit", "--cursor"] });
|
|
104801
105240
|
if ("error" in parsed)
|
|
104802
105241
|
return usage12(parsed.error);
|
|
@@ -104851,7 +105290,7 @@ More: --cursor ${data.nextCursor}
|
|
|
104851
105290
|
return 0;
|
|
104852
105291
|
}
|
|
104853
105292
|
async function tokenFlag(args, ctx) {
|
|
104854
|
-
const usage12 = usageFor(ctx,
|
|
105293
|
+
const usage12 = usageFor(ctx, USAGE15.flag);
|
|
104855
105294
|
const parsed = parseArgs(args, { valueFlags: ["--chain", "--content", "--reason", "--detail", "--idempotency-key"] });
|
|
104856
105295
|
if ("error" in parsed)
|
|
104857
105296
|
return usage12(parsed.error);
|
|
@@ -104946,7 +105385,7 @@ async function tokenUpdate(args, ctx) {
|
|
|
104946
105385
|
return 0;
|
|
104947
105386
|
}
|
|
104948
105387
|
async function tokenComment(args, ctx) {
|
|
104949
|
-
const usage12 = usageFor(ctx,
|
|
105388
|
+
const usage12 = usageFor(ctx, USAGE15.comment);
|
|
104950
105389
|
const parsed = parseArgs(args, { valueFlags: ["--chain", "--file", "--idempotency-key"], pathFlags: ["--file"] });
|
|
104951
105390
|
if ("error" in parsed)
|
|
104952
105391
|
return usage12(parsed.error);
|
|
@@ -104991,7 +105430,7 @@ async function tokenComment(args, ctx) {
|
|
|
104991
105430
|
return 0;
|
|
104992
105431
|
}
|
|
104993
105432
|
async function tokenComments(args, ctx) {
|
|
104994
|
-
const usage12 = usageFor(ctx,
|
|
105433
|
+
const usage12 = usageFor(ctx, USAGE15.comments);
|
|
104995
105434
|
const parsed = parseArgs(args, { valueFlags: ["--chain", "--limit", "--cursor"] });
|
|
104996
105435
|
if ("error" in parsed)
|
|
104997
105436
|
return usage12(parsed.error);
|
|
@@ -105337,7 +105776,7 @@ init_render();
|
|
|
105337
105776
|
init_solana_verify();
|
|
105338
105777
|
init_trading();
|
|
105339
105778
|
init_swap();
|
|
105340
|
-
var
|
|
105779
|
+
var USAGE16 = "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]";
|
|
105341
105780
|
var TRANSFER_ASSETS = [...Object.keys(BASES), ...Object.keys(HOOD_BASES)];
|
|
105342
105781
|
var BASE58_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
105343
105782
|
async function readLinkedWallets(ctx, key) {
|
|
@@ -105357,6 +105796,12 @@ async function readLinkedWallets(ctx, key) {
|
|
|
105357
105796
|
}
|
|
105358
105797
|
return rows;
|
|
105359
105798
|
}
|
|
105799
|
+
async function readWithdrawalAddresses(ctx, key) {
|
|
105800
|
+
const body = await request(ctx, key, "/api/v1/agent/withdrawal-addresses");
|
|
105801
|
+
if (!Array.isArray(body.addresses))
|
|
105802
|
+
throw new TradingError("INVALID_RESPONSE", "The withdrawal allowlist read did not return a list.");
|
|
105803
|
+
return body.addresses;
|
|
105804
|
+
}
|
|
105360
105805
|
async function resolveDestination(ctx, key, source, to, chain2 = "solana") {
|
|
105361
105806
|
const hood = chain2 === "hood";
|
|
105362
105807
|
const same = (a, b) => hood ? sameEvmAddress(a, b) : a === b;
|
|
@@ -105382,13 +105827,35 @@ async function resolveDestination(ctx, key, source, to, chain2 = "solana") {
|
|
|
105382
105827
|
return { kind: "linked", address: match.address, id: match._id, ...match.label ? { label: match.label } : {} };
|
|
105383
105828
|
if (same(to, source.address))
|
|
105384
105829
|
throw new TradingError("DESTINATION_IS_SOURCE", "--to names the wallet the funds would leave. Name a different destination.");
|
|
105830
|
+
const allowlist = (await readWithdrawalAddresses(ctx, key)).filter((row) => row.chain === chain2 && row.revokedAt === undefined && !same(row.address, source.address));
|
|
105831
|
+
const approved = allowlist.filter((row) => row.label === to || row.id === to || same(row.address, to));
|
|
105832
|
+
if (approved.length > 1)
|
|
105833
|
+
throw new TradingError("DESTINATION_AMBIGUOUS", `"${to}" names ${approved.length} approved withdrawal addresses: ${approved.map((row) => `${row.label ?? ""} (${row.id}, ${row.address})`.trim()).join("; ")}. Name one by id or address.`);
|
|
105834
|
+
const listed = approved[0];
|
|
105835
|
+
if (listed) {
|
|
105836
|
+
const address = hood ? checkEvmAddress(listed.address) : undefined;
|
|
105837
|
+
if (address !== undefined && !address.ok)
|
|
105838
|
+
throw new TradingError("INVALID_RESPONSE", "Candle listed an approved Hood withdrawal address that is not an EVM address; nothing was built.");
|
|
105839
|
+
return {
|
|
105840
|
+
kind: "approved",
|
|
105841
|
+
address: address?.ok ? address.address : listed.address,
|
|
105842
|
+
id: listed.id,
|
|
105843
|
+
...listed.label ? { label: listed.label } : {}
|
|
105844
|
+
};
|
|
105845
|
+
}
|
|
105385
105846
|
if (!hood && BASE58_ADDRESS.test(to))
|
|
105386
105847
|
return { kind: "address", address: to };
|
|
105387
105848
|
const evm = hood ? checkEvmAddress(to) : undefined;
|
|
105388
105849
|
if (evm?.ok)
|
|
105389
105850
|
return { kind: "address", address: evm.address };
|
|
105390
105851
|
const name2 = hood ? "Hood" : "Solana";
|
|
105391
|
-
|
|
105852
|
+
const known = [
|
|
105853
|
+
...rows.length > 0 ? [`Linked wallets: ${rows.map((row) => `${row.label ?? ""} (${row._id}, ${row.address})`.trim()).join("; ")}.`] : [],
|
|
105854
|
+
...allowlist.length > 0 ? [
|
|
105855
|
+
`Approved withdrawal addresses: ${allowlist.map((row) => `${row.label ?? ""} (${row.id}, ${row.address})`.trim()).join("; ")}.`
|
|
105856
|
+
] : []
|
|
105857
|
+
];
|
|
105858
|
+
throw new TradingError("DESTINATION_UNKNOWN", known.length === 0 ? `"${to}" is not vault, a ${name2} address, a linked wallet or an approved withdrawal address on this account (it has no other active ${name2} linked wallets or approved addresses).` : `"${to}" is not vault, a ${name2} address, a linked wallet or an approved withdrawal address on this account. ${known.join(" ")}`);
|
|
105392
105859
|
}
|
|
105393
105860
|
function describeDestination(destination) {
|
|
105394
105861
|
switch (destination.kind) {
|
|
@@ -105396,10 +105863,31 @@ function describeDestination(destination) {
|
|
|
105396
105863
|
return `this wallet's pinned vault ${destination.address}`;
|
|
105397
105864
|
case "linked":
|
|
105398
105865
|
return `linked wallet ${destination.label ? `${destination.label} ` : ""}(${destination.id}, ${destination.address})`;
|
|
105866
|
+
case "approved":
|
|
105867
|
+
return `approved withdrawal address ${destination.label ? `${destination.label} ` : ""}(${destination.id}, ${destination.address})`;
|
|
105868
|
+
case "address":
|
|
105869
|
+
return `address ${destination.address} (not one of this account's linked wallets or approved withdrawal addresses; Candle decides whether it is allowed)`;
|
|
105870
|
+
}
|
|
105871
|
+
}
|
|
105872
|
+
function destinationNote(destination, hood) {
|
|
105873
|
+
switch (destination.kind) {
|
|
105874
|
+
case "vault":
|
|
105875
|
+
return "Destination: this wallet's own vault, pinned when it was imported.";
|
|
105876
|
+
case "linked":
|
|
105877
|
+
return `Destination: a linked wallet on this account. Candle allows it only if you linked it while signed in or marked it trusted,${hood ? " only for ETH or USDG," : ""} and the amount counts against this key's spend caps.`;
|
|
105878
|
+
case "approved":
|
|
105879
|
+
return `Destination: an address on this account's withdrawal allowlist, approved while signed in. Candle allows it from a TEE wallet only where TEE withdrawals are switched on,${hood ? " only for ETH or USDG," : " for base assets only,"} and the amount counts against this key's spend caps.`;
|
|
105399
105880
|
case "address":
|
|
105400
|
-
return
|
|
105881
|
+
return "Destination: an address Candle will classify at build; from a TEE wallet only its vault, a trusted linked wallet or an approved withdrawal address is allowed.";
|
|
105401
105882
|
}
|
|
105402
105883
|
}
|
|
105884
|
+
function builtDestination(built) {
|
|
105885
|
+
if (built.destinationClass === "approved")
|
|
105886
|
+
return [["To", "an approved withdrawal address"]];
|
|
105887
|
+
if (typeof built.destinationKind === "string")
|
|
105888
|
+
return [["To", built.destinationKind === "vault" ? "the vault" : "a linked wallet"]];
|
|
105889
|
+
return [];
|
|
105890
|
+
}
|
|
105403
105891
|
function writePreview(ctx, title2, rows, note) {
|
|
105404
105892
|
const style = terminalStyle(ctx.deps, "stderr");
|
|
105405
105893
|
ctx.deps.stderr.write(`${heading(title2, style)}
|
|
@@ -105433,7 +105921,7 @@ async function transfer(args, ctx) {
|
|
|
105433
105921
|
const flags2 = parsed.values;
|
|
105434
105922
|
const asset = flags2["--asset"]?.toUpperCase();
|
|
105435
105923
|
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) {
|
|
105436
|
-
writeUsageFailure(ctx.deps,
|
|
105924
|
+
writeUsageFailure(ctx.deps, USAGE16, ctx.json);
|
|
105437
105925
|
return 2;
|
|
105438
105926
|
}
|
|
105439
105927
|
const to = flags2["--to"];
|
|
@@ -105470,7 +105958,7 @@ async function transfer(args, ctx) {
|
|
|
105470
105958
|
const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags2["--amount"]} ${label}`;
|
|
105471
105959
|
const confirmed = await confirmTransfer(ctx, [
|
|
105472
105960
|
`Transfer ${amountText2} from ${wallet3.address} (${wallet3.id}) to ${describeDestination(destination)}`,
|
|
105473
|
-
destination
|
|
105961
|
+
destinationNote(destination, false)
|
|
105474
105962
|
], parsed.booleans.has("--yes"));
|
|
105475
105963
|
if (!confirmed)
|
|
105476
105964
|
return printTradingResult(ctx, { success: true, status: "cancelled", walletId: wallet3.id, destination });
|
|
@@ -105491,11 +105979,7 @@ async function transfer(args, ctx) {
|
|
|
105491
105979
|
if (builtAmount === null || !isMax && builtAmount !== amountRaw)
|
|
105492
105980
|
throw new TradingError("INVALID_RESPONSE", "The transfer build does not name the confirmed amount; nothing was signed.");
|
|
105493
105981
|
const amountShown = asset ? `${decimalAmount(builtAmount, BASES[asset]?.decimals ?? 0)} ${asset}` : `${builtAmount} raw units of ${label}`;
|
|
105494
|
-
writePreview(ctx, "Built", [
|
|
105495
|
-
["Transfer", transferId],
|
|
105496
|
-
["Amount", amountShown],
|
|
105497
|
-
...typeof built.destinationKind === "string" ? [["To", built.destinationKind === "vault" ? "the vault" : "a linked wallet"]] : []
|
|
105498
|
-
]);
|
|
105982
|
+
writePreview(ctx, "Built", [["Transfer", transferId], ["Amount", amountShown], ...builtDestination(built)]);
|
|
105499
105983
|
const reader = await solana();
|
|
105500
105984
|
const problem = await tradingRead(ctx, reader, () => solanaSpendProblem(unsigned[0], wallet3.address, reader.rpc, {
|
|
105501
105985
|
kind: "transfer",
|
|
@@ -105557,7 +106041,7 @@ async function hoodTransfer(ctx, args) {
|
|
|
105557
106041
|
const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags2["--amount"]} ${label}`;
|
|
105558
106042
|
const confirmed = await confirmTransfer(ctx, [
|
|
105559
106043
|
`Transfer ${amountText2} on Hood from ${wallet3.address} (${wallet3.id}) to ${describeDestination(destination)}`,
|
|
105560
|
-
destination
|
|
106044
|
+
destinationNote(destination, true),
|
|
105561
106045
|
...asset === "ETH" ? [
|
|
105562
106046
|
"ETH keeps this send's gas and the sweep-home gas reserve in the wallet; Candle refuses an amount that would spend them."
|
|
105563
106047
|
] : []
|
|
@@ -105592,7 +106076,7 @@ async function hoodTransfer(ctx, args) {
|
|
|
105592
106076
|
writePreview(ctx, "Built", [
|
|
105593
106077
|
["Transfer", safeText(transferId)],
|
|
105594
106078
|
["Amount", `${decimalAmount(amountRaw, decimals)} ${safeText(label)}`],
|
|
105595
|
-
...
|
|
106079
|
+
...builtDestination(built),
|
|
105596
106080
|
[
|
|
105597
106081
|
"Gas",
|
|
105598
106082
|
`up to ${formatUnits(BigInt(leg.gas) * maxFee, 18)} ETH (gas ${leg.gas} at ${formatUnits(maxFee, 9)} gwei)`
|
|
@@ -107037,8 +107521,332 @@ init_reconcile_grant();
|
|
|
107037
107521
|
init_tee_resolve();
|
|
107038
107522
|
init_tee();
|
|
107039
107523
|
init_tee_evm();
|
|
107524
|
+
|
|
107525
|
+
// src/commands/vault-demote-batch.ts
|
|
107526
|
+
init_args();
|
|
107527
|
+
init_human_output();
|
|
107528
|
+
init_presentation();
|
|
107529
|
+
init_solana_endpoint();
|
|
107530
|
+
init_errors2();
|
|
107531
|
+
init_promote_support();
|
|
107532
|
+
init_store();
|
|
107533
|
+
init_tee();
|
|
107534
|
+
init_vault_support();
|
|
107535
|
+
var BATCH_USAGE = "Usage: candle vault demote <tee-address> <tee-address>... | --all --empty [--rpc-url <url>] [--emergency] [--sweep-to <label>] [--yes]";
|
|
107536
|
+
var VALUE_FLAGS = new Set(["--rpc-url", "--keystore", "-k", "--sweep-to", "--from-block", "--token"]);
|
|
107537
|
+
var BATCH_FLAGS = new Set(["--all", "--empty", "--yes"]);
|
|
107538
|
+
var SINGLE_ONLY_FLAGS = ["--token", "--from-block", "--accept-unknown-exposure"];
|
|
107539
|
+
function isBatchDemote(args) {
|
|
107540
|
+
let addresses = 0;
|
|
107541
|
+
for (let i = 0;i < args.length; i++) {
|
|
107542
|
+
const arg = args[i];
|
|
107543
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
107544
|
+
i += 1;
|
|
107545
|
+
continue;
|
|
107546
|
+
}
|
|
107547
|
+
if (BATCH_FLAGS.has(arg))
|
|
107548
|
+
return true;
|
|
107549
|
+
if (!arg.startsWith("-"))
|
|
107550
|
+
addresses += 1;
|
|
107551
|
+
}
|
|
107552
|
+
return addresses > 1;
|
|
107553
|
+
}
|
|
107554
|
+
var isEvmAddress = (value) => /^0x[0-9a-fA-F]{40}$/.test(value);
|
|
107555
|
+
var sameAddress5 = (chain2, a, b) => chain2 === "evm" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
107556
|
+
async function vaultDemoteBatch(args, ctx) {
|
|
107557
|
+
const single = SINGLE_ONLY_FLAGS.find((flag) => args.includes(flag));
|
|
107558
|
+
if (single !== undefined) {
|
|
107559
|
+
return usage(ctx, `${single} names one Hood wallet's sweep; pass it to a single demote: candle vault demote <0x address> ${single} ...`);
|
|
107560
|
+
}
|
|
107561
|
+
const parsed = parseArgs(args, {
|
|
107562
|
+
valueFlags: ["--rpc-url", "--sweep-to", "--keystore"],
|
|
107563
|
+
booleanFlags: ["--emergency", "--accept-older-copy", "--all", "--empty", "--yes"],
|
|
107564
|
+
pathFlags: ["--keystore"]
|
|
107565
|
+
});
|
|
107566
|
+
if ("error" in parsed)
|
|
107567
|
+
return usage(ctx, parsed.error);
|
|
107568
|
+
const all = parsed.booleans.has("--all");
|
|
107569
|
+
const empty = parsed.booleans.has("--empty");
|
|
107570
|
+
const yes = parsed.booleans.has("--yes");
|
|
107571
|
+
if (all && parsed.positionals.length > 0)
|
|
107572
|
+
return usage(ctx, `--all takes no addresses. ${BATCH_USAGE}`);
|
|
107573
|
+
if (all && !empty) {
|
|
107574
|
+
return usage(ctx, `--all needs a filter: --empty (wallets with nothing left to sweep). ${BATCH_USAGE}`);
|
|
107575
|
+
}
|
|
107576
|
+
if (empty && !all)
|
|
107577
|
+
return usage(ctx, `--empty filters --all. ${BATCH_USAGE}`);
|
|
107578
|
+
if (!all && parsed.positionals.length === 0)
|
|
107579
|
+
return usage(ctx, BATCH_USAGE);
|
|
107580
|
+
const rpcUrl = parsed.values["--rpc-url"];
|
|
107581
|
+
const sweepTo = parsed.values["--sweep-to"];
|
|
107582
|
+
const named = parsed.positionals;
|
|
107583
|
+
if (rpcUrl !== undefined && named.some(isEvmAddress) && named.some((a) => !isEvmAddress(a))) {
|
|
107584
|
+
return usage(ctx, "--rpc-url names one chain's node; demote Solana and Hood wallets in separate batches.");
|
|
107585
|
+
}
|
|
107586
|
+
if (!refuseEnvPassphrase(ctx))
|
|
107587
|
+
return 1;
|
|
107588
|
+
if (!requireTty(ctx, "vault demote"))
|
|
107589
|
+
return 1;
|
|
107590
|
+
const resolvedVault = vaultPathFor(ctx, parsed);
|
|
107591
|
+
if ("error" in resolvedVault)
|
|
107592
|
+
return usage(ctx, resolvedVault.error);
|
|
107593
|
+
let solana;
|
|
107594
|
+
if (empty) {
|
|
107595
|
+
const opened = await openSolanaClient(ctx, rpcUrl);
|
|
107596
|
+
if ("error" in opened)
|
|
107597
|
+
return usage(ctx, opened.error);
|
|
107598
|
+
solana = opened;
|
|
107599
|
+
}
|
|
107600
|
+
return runVaultCommand(ctx, async ({ hold }) => {
|
|
107601
|
+
const raw = await requireVaultRaw(ctx, resolvedVault);
|
|
107602
|
+
const opened = await unlockInteractively(ctx, resolvedVault.path, raw, {
|
|
107603
|
+
acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
|
|
107604
|
+
});
|
|
107605
|
+
const held = hold(opened.vault);
|
|
107606
|
+
const outcomes = [];
|
|
107607
|
+
const planned = [];
|
|
107608
|
+
const plan2 = (entry) => {
|
|
107609
|
+
const chain2 = entry.chain === "evm" ? "evm" : "solana";
|
|
107610
|
+
const row = { address: entry.address, label: entry.label, chain: chain2, destination: null };
|
|
107611
|
+
const pinned = entry.tee?.vaultDestination;
|
|
107612
|
+
if (pinned !== undefined)
|
|
107613
|
+
row.destination = pinned;
|
|
107614
|
+
else if (sweepTo === undefined)
|
|
107615
|
+
row.destinationNote = "no pinned destination; pass --sweep-to";
|
|
107616
|
+
else {
|
|
107617
|
+
try {
|
|
107618
|
+
row.destination = assertColdVaultDestination(held.index, sweepTo, {
|
|
107619
|
+
chain: chain2,
|
|
107620
|
+
subjectAddress: entry.address
|
|
107621
|
+
}).address;
|
|
107622
|
+
} catch (error) {
|
|
107623
|
+
if (!isVaultError(error))
|
|
107624
|
+
throw error;
|
|
107625
|
+
row.destinationNote = `--sweep-to ${sweepTo}: ${error.message}`;
|
|
107626
|
+
}
|
|
107627
|
+
}
|
|
107628
|
+
planned.push(row);
|
|
107629
|
+
};
|
|
107630
|
+
const tees = held.index.entries.filter((entry) => entry.role === "tee-wallet");
|
|
107631
|
+
if (all) {
|
|
107632
|
+
for (const entry of tees) {
|
|
107633
|
+
if (entry.tee?.lifecycle === "retired")
|
|
107634
|
+
continue;
|
|
107635
|
+
const base2 = {
|
|
107636
|
+
address: entry.address,
|
|
107637
|
+
label: entry.label,
|
|
107638
|
+
chain: entry.chain === "evm" ? "evm" : "solana"
|
|
107639
|
+
};
|
|
107640
|
+
if (entry.chain === "evm") {
|
|
107641
|
+
outcomes.push({
|
|
107642
|
+
...base2,
|
|
107643
|
+
status: "skipped",
|
|
107644
|
+
reason: "--empty reads Solana balances only; demote a Hood wallet by its address"
|
|
107645
|
+
});
|
|
107646
|
+
continue;
|
|
107647
|
+
}
|
|
107648
|
+
if (!await nothingLeftToSweep(solana, entry)) {
|
|
107649
|
+
outcomes.push({
|
|
107650
|
+
...base2,
|
|
107651
|
+
status: "skipped",
|
|
107652
|
+
reason: "not empty: it holds SOL or token accounts, or an earlier sweep is unsettled"
|
|
107653
|
+
});
|
|
107654
|
+
continue;
|
|
107655
|
+
}
|
|
107656
|
+
plan2(entry);
|
|
107657
|
+
}
|
|
107658
|
+
} else {
|
|
107659
|
+
const seen = [];
|
|
107660
|
+
for (const address of named) {
|
|
107661
|
+
const chain2 = isEvmAddress(address) ? "evm" : "solana";
|
|
107662
|
+
if (seen.some((other) => sameAddress5(chain2, other, address))) {
|
|
107663
|
+
outcomes.push({ address, label: null, chain: chain2, status: "skipped", reason: "listed more than once" });
|
|
107664
|
+
continue;
|
|
107665
|
+
}
|
|
107666
|
+
seen.push(address);
|
|
107667
|
+
const entry = tees.find((candidate) => sameAddress5(chain2, candidate.address, address));
|
|
107668
|
+
if (entry === undefined) {
|
|
107669
|
+
outcomes.push({
|
|
107670
|
+
address,
|
|
107671
|
+
label: null,
|
|
107672
|
+
chain: chain2,
|
|
107673
|
+
status: "failed",
|
|
107674
|
+
code: "TEE_WALLET_UNKNOWN",
|
|
107675
|
+
reason: `${address} is not a TEE wallet in the vault`
|
|
107676
|
+
});
|
|
107677
|
+
continue;
|
|
107678
|
+
}
|
|
107679
|
+
if (entry.tee?.lifecycle === "retired") {
|
|
107680
|
+
outcomes.push({
|
|
107681
|
+
address,
|
|
107682
|
+
label: entry.label,
|
|
107683
|
+
chain: chain2,
|
|
107684
|
+
status: "skipped",
|
|
107685
|
+
reason: "already retired (demoted and swept)"
|
|
107686
|
+
});
|
|
107687
|
+
continue;
|
|
107688
|
+
}
|
|
107689
|
+
plan2(entry);
|
|
107690
|
+
}
|
|
107691
|
+
}
|
|
107692
|
+
if (rpcUrl !== undefined && new Set(planned.map((row) => row.chain)).size > 1) {
|
|
107693
|
+
return usage(ctx, "--rpc-url names one chain's node; demote Solana and Hood wallets in separate batches.");
|
|
107694
|
+
}
|
|
107695
|
+
if (planned.length > 0) {
|
|
107696
|
+
const display = ctx.json ? "stderr" : "stdout";
|
|
107697
|
+
ctx.deps[display].write(`Demote ${planned.length} TEE wallet${planned.length === 1 ? "" : "s"}, one at a time: disable, then sweep to the destination shown.
|
|
107698
|
+
`);
|
|
107699
|
+
writeTable(ctx.deps, display, ["#", "TEE wallet", "Label", "Sweeps to"], planned.map((row, i) => [
|
|
107700
|
+
String(i + 1),
|
|
107701
|
+
row.address,
|
|
107702
|
+
row.label,
|
|
107703
|
+
row.destination ?? `none (${row.destinationNote})`
|
|
107704
|
+
]), []);
|
|
107705
|
+
for (const skipped of outcomes) {
|
|
107706
|
+
ctx.deps[display].write(`Not demoted: ${skipped.address}: ${skipped.reason}
|
|
107707
|
+
`);
|
|
107708
|
+
}
|
|
107709
|
+
if (!yes) {
|
|
107710
|
+
const typed = await ctx.deps.promptLine(`Type confirm to demote these ${planned.length} wallets: `);
|
|
107711
|
+
if (typed.trim().toLowerCase() !== "confirm") {
|
|
107712
|
+
throw new VaultError("DESTINATION_NOT_CONFIRMED", "The batch was not confirmed; nothing was done.", {
|
|
107713
|
+
suggestion: "Nothing was disabled or swept. Run it again and type confirm at the prompt."
|
|
107714
|
+
});
|
|
107715
|
+
}
|
|
107716
|
+
}
|
|
107717
|
+
}
|
|
107718
|
+
const session = { path: held.path, open: (bytes) => openHeld(opened, held, bytes) };
|
|
107719
|
+
const width = String(planned.length).length;
|
|
107720
|
+
for (const [i, row] of planned.entries()) {
|
|
107721
|
+
if (!ctx.json) {
|
|
107722
|
+
notice(ctx.deps, "stdout", "note", `[${String(i + 1).padStart(width)}/${planned.length}] Demoting ${row.address} (${row.label})`);
|
|
107723
|
+
}
|
|
107724
|
+
const outcome = await demoteOne(ctx, session, row, parsed.values, parsed.booleans);
|
|
107725
|
+
outcomes.push(outcome);
|
|
107726
|
+
if (!ctx.json)
|
|
107727
|
+
writeResultLine(ctx.deps, outcome);
|
|
107728
|
+
}
|
|
107729
|
+
const count3 = (status) => outcomes.filter((o) => o.status === status).length;
|
|
107730
|
+
const summary = {
|
|
107731
|
+
total: outcomes.length,
|
|
107732
|
+
done: count3("done"),
|
|
107733
|
+
incomplete: count3("incomplete"),
|
|
107734
|
+
failed: count3("failed"),
|
|
107735
|
+
skipped: count3("skipped")
|
|
107736
|
+
};
|
|
107737
|
+
const exit = summary.failed > 0 ? 1 : summary.incomplete > 0 ? 3 : 0;
|
|
107738
|
+
if (ctx.json) {
|
|
107739
|
+
writeJson(ctx.deps, { ok: exit === 0, wallets: outcomes, summary });
|
|
107740
|
+
return exit;
|
|
107741
|
+
}
|
|
107742
|
+
ctx.deps.stdout.write(`
|
|
107743
|
+
Summary: ${summary.done} done, ${summary.incomplete} incomplete, ${summary.failed} failed, ${summary.skipped} skipped (of ${summary.total}).
|
|
107744
|
+
`);
|
|
107745
|
+
for (const outcome of outcomes) {
|
|
107746
|
+
if (outcome.status === "done")
|
|
107747
|
+
continue;
|
|
107748
|
+
ctx.deps.stdout.write(` ${outcome.status.padEnd(10)} ${outcome.address}${outcome.reason ? `: ${outcome.reason}` : ""}
|
|
107749
|
+
`);
|
|
107750
|
+
}
|
|
107751
|
+
return exit;
|
|
107752
|
+
});
|
|
107753
|
+
}
|
|
107754
|
+
async function openHeld(opened, held, raw) {
|
|
107755
|
+
const vault = await reopenWithHeldDek(held.path, raw, held);
|
|
107756
|
+
return {
|
|
107757
|
+
vault,
|
|
107758
|
+
factor: opened.factor,
|
|
107759
|
+
reopen: (path, bytes) => reopenWithHeldDek(path, bytes, held),
|
|
107760
|
+
confirm: opened.confirm
|
|
107761
|
+
};
|
|
107762
|
+
}
|
|
107763
|
+
async function demoteOne(ctx, session, row, values, booleans) {
|
|
107764
|
+
const base2 = { address: row.address, label: row.label, chain: row.chain };
|
|
107765
|
+
const args = [row.address];
|
|
107766
|
+
for (const flag of ["--rpc-url", "--sweep-to", "--keystore"]) {
|
|
107767
|
+
const value = values[flag];
|
|
107768
|
+
if (value !== undefined)
|
|
107769
|
+
args.push(flag, value);
|
|
107770
|
+
}
|
|
107771
|
+
for (const flag of ["--emergency", "--accept-older-copy"])
|
|
107772
|
+
if (booleans.has(flag))
|
|
107773
|
+
args.push(flag);
|
|
107774
|
+
const covered = row.destination === null ? null : `Type confirm to sweep everything to ${row.destination}: `;
|
|
107775
|
+
const promptLine = async (text4) => text4 === covered ? "confirm" : ctx.deps.promptLine(text4);
|
|
107776
|
+
const captured = [];
|
|
107777
|
+
const errLines = [];
|
|
107778
|
+
const stdout = ctx.json ? { write: (chunk) => void captured.push(chunk) } : ctx.deps.stdout;
|
|
107779
|
+
const stderr = {
|
|
107780
|
+
write: (chunk) => {
|
|
107781
|
+
errLines.push(chunk);
|
|
107782
|
+
ctx.deps.stderr.write(chunk);
|
|
107783
|
+
}
|
|
107784
|
+
};
|
|
107785
|
+
const deps = { ...ctx.deps, stdout, stderr, promptLine };
|
|
107786
|
+
const step = { ...ctx, deps, heldUnlock: session };
|
|
107787
|
+
let exitCode;
|
|
107788
|
+
let thrown;
|
|
107789
|
+
try {
|
|
107790
|
+
exitCode = await vaultDemote(args, step);
|
|
107791
|
+
} catch (error) {
|
|
107792
|
+
exitCode = 1;
|
|
107793
|
+
thrown = error instanceof Error ? error.message : String(error);
|
|
107794
|
+
}
|
|
107795
|
+
const outcome = {
|
|
107796
|
+
...base2,
|
|
107797
|
+
status: exitCode === 0 ? "done" : exitCode === 3 ? "incomplete" : "failed",
|
|
107798
|
+
exitCode
|
|
107799
|
+
};
|
|
107800
|
+
if (ctx.json) {
|
|
107801
|
+
const results = [];
|
|
107802
|
+
for (const line of captured.join("").split(`
|
|
107803
|
+
`)) {
|
|
107804
|
+
if (line.trim() === "")
|
|
107805
|
+
continue;
|
|
107806
|
+
try {
|
|
107807
|
+
results.push(JSON.parse(line));
|
|
107808
|
+
} catch {
|
|
107809
|
+
ctx.deps.stderr.write(`${line}
|
|
107810
|
+
`);
|
|
107811
|
+
}
|
|
107812
|
+
}
|
|
107813
|
+
outcome.results = results;
|
|
107814
|
+
const failure3 = [...results].reverse().find((doc) => typeof doc === "object" && doc !== null && doc.ok === false);
|
|
107815
|
+
if (failure3 !== undefined && exitCode !== 0) {
|
|
107816
|
+
if (typeof failure3.code === "string")
|
|
107817
|
+
outcome.code = failure3.code;
|
|
107818
|
+
if (typeof failure3.message === "string")
|
|
107819
|
+
outcome.reason = failure3.message;
|
|
107820
|
+
}
|
|
107821
|
+
}
|
|
107822
|
+
if (thrown !== undefined)
|
|
107823
|
+
outcome.reason = thrown;
|
|
107824
|
+
if (outcome.reason === undefined && exitCode === 3) {
|
|
107825
|
+
outcome.reason = "the disable is not verified or funds remain; remote authority may still be open";
|
|
107826
|
+
} else if (outcome.reason === undefined && exitCode !== 0) {
|
|
107827
|
+
const first = errLines.find((chunk) => chunk.trim() !== "" && !chunk.includes("vault.state.json"));
|
|
107828
|
+
outcome.reason = firstSentence(first ?? "") ?? `exit ${exitCode}`;
|
|
107829
|
+
}
|
|
107830
|
+
return outcome;
|
|
107831
|
+
}
|
|
107832
|
+
function firstSentence(chunk) {
|
|
107833
|
+
const text4 = chunk.replace(/\s+/g, " ").replace(/^[^\p{L}\p{N}]+/u, "").trim();
|
|
107834
|
+
if (text4 === "")
|
|
107835
|
+
return;
|
|
107836
|
+
const end = text4.search(/\.(\s|$)/);
|
|
107837
|
+
return end < 0 ? text4 : text4.slice(0, end + 1);
|
|
107838
|
+
}
|
|
107839
|
+
function writeResultLine(deps, outcome) {
|
|
107840
|
+
const word3 = { done: "done", incomplete: "incomplete", failed: "FAILED", skipped: "skipped" }[outcome.status];
|
|
107841
|
+
deps.stdout.write(`Result ${outcome.address}: ${word3} (exit ${outcome.exitCode})${outcome.reason && outcome.status !== "done" ? `: ${outcome.reason}` : ""}
|
|
107842
|
+
`);
|
|
107843
|
+
}
|
|
107844
|
+
|
|
107845
|
+
// src/commands/vault-demote.ts
|
|
107040
107846
|
init_vault_support();
|
|
107041
107847
|
async function vaultDemote(args, ctx) {
|
|
107848
|
+
if (ctx.heldUnlock === undefined && isBatchDemote(args))
|
|
107849
|
+
return vaultDemoteBatch(args, ctx);
|
|
107042
107850
|
if (namesEvmWallet(args))
|
|
107043
107851
|
return vaultDemoteEvm(args, ctx);
|
|
107044
107852
|
const parsed = parseArgs(args, {
|
|
@@ -108804,6 +109612,8 @@ async function vaultFund(args, ctx) {
|
|
|
108804
109612
|
const asset = (parsed.values["--asset"] ?? "SOL").toUpperCase();
|
|
108805
109613
|
if (!amount)
|
|
108806
109614
|
return usage(ctx, "--amount <n> is required.");
|
|
109615
|
+
if (amount.toLowerCase() === "max")
|
|
109616
|
+
return usage(ctx, "--amount must be a number; funding a wallet takes no max.");
|
|
108807
109617
|
if (asset !== "SOL" && asset !== "USDC")
|
|
108808
109618
|
return usage(ctx, "--asset must be SOL or USDC.");
|
|
108809
109619
|
const solana = await openSolanaClient(ctx, parsed.values["--rpc-url"]);
|
|
@@ -113027,6 +113837,7 @@ init_args();
|
|
|
113027
113837
|
init_deps();
|
|
113028
113838
|
init_evm_lite();
|
|
113029
113839
|
init_solana_endpoint();
|
|
113840
|
+
init_errors2();
|
|
113030
113841
|
init_evm_tee();
|
|
113031
113842
|
init_evm_transfer();
|
|
113032
113843
|
init_promote_support();
|
|
@@ -113049,7 +113860,7 @@ async function vaultTransfer(args, ctx) {
|
|
|
113049
113860
|
const fromLabel = parsed.values["--from"];
|
|
113050
113861
|
const rpcUrlFlag = parsed.values["--rpc-url"];
|
|
113051
113862
|
if (!amount)
|
|
113052
|
-
return usage(ctx, "--amount <n> is required.");
|
|
113863
|
+
return usage(ctx, "--amount <n|max> is required.");
|
|
113053
113864
|
if (!asset)
|
|
113054
113865
|
return usage(ctx, "--asset SOL|<mint>|ETH|USDG|<0x token> is required.");
|
|
113055
113866
|
if (!fromLabel)
|
|
@@ -113145,14 +113956,15 @@ async function vaultTransfer(args, ctx) {
|
|
|
113145
113956
|
const reconciled = await reconcileFundingReceipts(vault, [fromEntry.address, to], solana.rpc, ctx);
|
|
113146
113957
|
if (reconciled !== null)
|
|
113147
113958
|
return reconciled;
|
|
113148
|
-
const
|
|
113959
|
+
const buildPlan = () => solana.read(() => planTransfer({
|
|
113149
113960
|
from: fromEntry.address,
|
|
113150
113961
|
to,
|
|
113151
113962
|
amount,
|
|
113152
113963
|
asset,
|
|
113153
113964
|
rpc: solana.rpc
|
|
113154
113965
|
}));
|
|
113155
|
-
|
|
113966
|
+
let plan2 = await buildPlan();
|
|
113967
|
+
let feeQuote = plan2.solMax?.fee ?? await solana.read(() => quoteTransferFee(solana.rpc, plan2.from, plan2.instructions));
|
|
113156
113968
|
displayTransferPlan(ctx, plan2, feeQuote);
|
|
113157
113969
|
await assertTransferAffordable(solana, plan2, feeQuote);
|
|
113158
113970
|
if (promoted) {
|
|
@@ -113160,7 +113972,23 @@ async function vaultTransfer(args, ctx) {
|
|
|
113160
113972
|
if (notice2 !== undefined)
|
|
113161
113973
|
note(notice2);
|
|
113162
113974
|
}
|
|
113163
|
-
|
|
113975
|
+
for (let attempt = 0;attempt < 3; attempt++) {
|
|
113976
|
+
await confirmSend(ctx, { amount: plan2.amount, asset: plan2.asset, address: to });
|
|
113977
|
+
if (!plan2.solMax)
|
|
113978
|
+
break;
|
|
113979
|
+
const refreshed = await buildPlan();
|
|
113980
|
+
const unchanged = refreshed.amountRaw === plan2.amountRaw && refreshed.solMax?.fee === plan2.solMax.fee;
|
|
113981
|
+
plan2 = refreshed;
|
|
113982
|
+
feeQuote = refreshed.solMax?.fee ?? null;
|
|
113983
|
+
if (unchanged)
|
|
113984
|
+
break;
|
|
113985
|
+
if (attempt === 2) {
|
|
113986
|
+
throw new VaultError("VAULT_UNREADABLE", "The SOL max balance or fee kept changing during confirmation. Nothing was signed or sent; retry when the wallet and RPC are stable.");
|
|
113987
|
+
}
|
|
113988
|
+
note("SOL max balance or fee changed; review and confirm the refreshed amount.");
|
|
113989
|
+
displayTransferPlan(ctx, plan2, feeQuote);
|
|
113990
|
+
await assertTransferAffordable(solana, plan2, feeQuote);
|
|
113991
|
+
}
|
|
113164
113992
|
const secret = await decryptKey(vault, fromEntry.id);
|
|
113165
113993
|
try {
|
|
113166
113994
|
const result = await signAndBroadcastTransfer({ ctx, solana, secret64: secret, plan: plan2 });
|
|
@@ -113203,7 +114031,7 @@ init_human_output();
|
|
|
113203
114031
|
init_release();
|
|
113204
114032
|
import { dirname as dirname11, join as join15 } from "node:path";
|
|
113205
114033
|
init_key_output();
|
|
113206
|
-
var
|
|
114034
|
+
var USAGE17 = "Usage: candle verify <file> --bundle <path> [--identity <uri>] [--issuer <url>]";
|
|
113207
114035
|
async function resolveIdentity(deps, bundlePath, flag) {
|
|
113208
114036
|
if (flag)
|
|
113209
114037
|
return { kind: "ok", uri: flag, provenance: "identity from --identity" };
|
|
@@ -113235,19 +114063,19 @@ async function verify(args, ctx) {
|
|
|
113235
114063
|
});
|
|
113236
114064
|
if ("error" in parsed) {
|
|
113237
114065
|
writeScreenUsageFailure(deps, `${parsed.error}
|
|
113238
|
-
${
|
|
114066
|
+
${USAGE17}`, json);
|
|
113239
114067
|
return 2;
|
|
113240
114068
|
}
|
|
113241
114069
|
const file = parsed.positionals[0];
|
|
113242
114070
|
if (parsed.positionals.length !== 1 || file === undefined) {
|
|
113243
114071
|
writeScreenUsageFailure(deps, `verify takes exactly one file.
|
|
113244
|
-
${
|
|
114072
|
+
${USAGE17}`, json);
|
|
113245
114073
|
return 2;
|
|
113246
114074
|
}
|
|
113247
114075
|
const bundlePath = parsed.values["--bundle"];
|
|
113248
114076
|
if (!bundlePath) {
|
|
113249
114077
|
writeScreenUsageFailure(deps, `--bundle is required.
|
|
113250
|
-
${
|
|
114078
|
+
${USAGE17}`, json);
|
|
113251
114079
|
return 2;
|
|
113252
114080
|
}
|
|
113253
114081
|
const resolved = await resolveIdentity(deps, bundlePath, parsed.values["--identity"]);
|
|
@@ -113261,7 +114089,7 @@ ${USAGE16}`, json);
|
|
|
113261
114089
|
}
|
|
113262
114090
|
if (resolved.kind === "absent") {
|
|
113263
114091
|
writeScreenUsageFailure(deps, `--identity is required: there is no latest.json beside ${bundlePath} to take the release version from.
|
|
113264
|
-
${
|
|
114092
|
+
${USAGE17}`, json);
|
|
113265
114093
|
return 2;
|
|
113266
114094
|
}
|
|
113267
114095
|
const identity4 = resolved.uri;
|
|
@@ -113510,7 +114338,7 @@ init_solana_lite();
|
|
|
113510
114338
|
init_trading();
|
|
113511
114339
|
init_swap();
|
|
113512
114340
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
113513
|
-
var
|
|
114341
|
+
var USAGE18 = "Usage: candle wallet close-empty [--wallet embedded|<tee address|label>] [--keep <mint>]... [--client-trade-id <id>] [--yes] [--json]";
|
|
113514
114342
|
var BASE58_ADDRESS2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
113515
114343
|
var LISTED_ROWS = 50;
|
|
113516
114344
|
var TOKEN_IX_CLOSE_ACCOUNT3 = 9;
|
|
@@ -113773,7 +114601,7 @@ async function closeTee(ctx, key, wallet3, name2, run2) {
|
|
|
113773
114601
|
async function walletsCloseEmpty(args, ctx) {
|
|
113774
114602
|
const lifted = splitKeep(args);
|
|
113775
114603
|
if ("error" in lifted) {
|
|
113776
|
-
writeUsageFailure(ctx.deps, `${lifted.error}. ${
|
|
114604
|
+
writeUsageFailure(ctx.deps, `${lifted.error}. ${USAGE18}`, ctx.json);
|
|
113777
114605
|
return 2;
|
|
113778
114606
|
}
|
|
113779
114607
|
const parsed = parseArgs(lifted.rest, { valueFlags: ["--wallet", "--client-trade-id"], booleanFlags: ["--yes"] });
|
|
@@ -113784,7 +114612,7 @@ async function walletsCloseEmpty(args, ctx) {
|
|
|
113784
114612
|
const named = parsed.values["--client-trade-id"];
|
|
113785
114613
|
const id = named ?? `close-${randomUUID15()}`;
|
|
113786
114614
|
if (parsed.positionals.length !== 0 || !validClientId(id) || !lifted.keep.every((m) => BASE58_ADDRESS2.test(m))) {
|
|
113787
|
-
writeUsageFailure(ctx.deps,
|
|
114615
|
+
writeUsageFailure(ctx.deps, USAGE18, ctx.json);
|
|
113788
114616
|
return 2;
|
|
113789
114617
|
}
|
|
113790
114618
|
const run2 = { id, named: named !== undefined, keep: lifted.keep, yes: parsed.booleans.has("--yes") };
|
|
@@ -114101,14 +114929,27 @@ async function maybeWriteUpdateNotice(deps, opts = {}) {
|
|
|
114101
114929
|
return;
|
|
114102
114930
|
if (opts.command === "update" || opts.command === "doctor")
|
|
114103
114931
|
return;
|
|
114104
|
-
const
|
|
114105
|
-
if (!
|
|
114932
|
+
const seen = latestCliVersionFromApi();
|
|
114933
|
+
if (!seen || !isPlainVersion(seen) || compareVersions(seen, CLI_VERSION) <= 0)
|
|
114106
114934
|
return;
|
|
114935
|
+
const realExec = await deps.realpath(deps.execPath).catch(() => deps.execPath);
|
|
114936
|
+
const method = detectInstall(deps.execPath, realExec);
|
|
114937
|
+
let latest = seen;
|
|
114938
|
+
let command = "npm i -g @candledottv/cli@latest";
|
|
114939
|
+
if (method !== "script") {
|
|
114940
|
+
command = method === "homebrew" ? "brew upgrade candle" : "candle update";
|
|
114941
|
+
const feed = await fetchLatest(deps, releaseBaseUrl(deps.env, updateHomeFor(CLI_VERSION)));
|
|
114942
|
+
if (!feed.ok || !isPlainVersion(feed.manifest.version))
|
|
114943
|
+
return;
|
|
114944
|
+
latest = feed.manifest.version;
|
|
114945
|
+
if (compareVersions(latest, CLI_VERSION) <= 0)
|
|
114946
|
+
return;
|
|
114947
|
+
}
|
|
114107
114948
|
const config = await deps.readConfig();
|
|
114108
114949
|
const prior = config.updateNotice;
|
|
114109
114950
|
if (prior && prior.version === latest && deps.now() - prior.shownAt < DAY_MS2)
|
|
114110
114951
|
return;
|
|
114111
|
-
const line = `Update available: candle ${CLI_VERSION} -> ${latest}. Run:
|
|
114952
|
+
const line = `Update available: candle ${CLI_VERSION} -> ${latest}. Run: ${command}`;
|
|
114112
114953
|
const tty = terminalStyle(deps, "stderr", opts.json).color !== 0;
|
|
114113
114954
|
deps.stderr.write(tty ? `\x1B[2m${line}\x1B[0m
|
|
114114
114955
|
` : `${line}
|
|
@@ -114189,7 +115030,7 @@ var COMMANDS = {
|
|
|
114189
115030
|
stake: { bare: stake, subcommands: { add: stakeAdd, remove: stakeRemove, finish: stakeFinish, claim: stakeClaim } },
|
|
114190
115031
|
swap: { bare: swap, subcommands: { status: swapStatus } },
|
|
114191
115032
|
transfer: { bare: transfer },
|
|
114192
|
-
launch: { bare: launch },
|
|
115033
|
+
launch: { bare: launch, subcommands: { status: launchStatus } },
|
|
114193
115034
|
image: { subcommands: { upload: imageUpload } },
|
|
114194
115035
|
pnl: { bare: pnl },
|
|
114195
115036
|
portfolio: { bare: portfolio },
|