@candledottv/cli 0.13.9 → 0.13.10
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 +878 -94
- 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.10";
|
|
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",
|
|
@@ -16872,12 +16885,48 @@ function describeSwapEvidence(ctx, details, terminal) {
|
|
|
16872
16885
|
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
16886
|
`.trim());
|
|
16874
16887
|
}
|
|
16888
|
+
function tradeSignatures(result) {
|
|
16889
|
+
const nested = (name) => result[name] && typeof result[name] === "object" ? result[name] : {};
|
|
16890
|
+
const payload = nested("payload");
|
|
16891
|
+
const job = nested("job");
|
|
16892
|
+
const sources = [result, payload, job];
|
|
16893
|
+
const operation = job.operation && typeof job.operation === "object" ? job.operation : {};
|
|
16894
|
+
const legs = [...sources.map((source) => source.landedLegs), operation.legs].flatMap((value) => Array.isArray(value) ? value.filter((leg) => leg !== null && typeof leg === "object") : []);
|
|
16895
|
+
const primary = sources.map((source) => source.signature).find(isText) ?? legs.find((leg) => (leg.kind === "trade" || leg.kind === "bridgeDeposit") && isText(leg.hash))?.hash;
|
|
16896
|
+
return [
|
|
16897
|
+
...new Set([
|
|
16898
|
+
...isText(primary) ? [primary] : [],
|
|
16899
|
+
...sources.flatMap((source) => [
|
|
16900
|
+
...isText(source.signature) ? [source.signature] : [],
|
|
16901
|
+
...textsOf(source.signatures),
|
|
16902
|
+
...textsOf(source.hashes),
|
|
16903
|
+
...isText(source.feeSignature) ? [source.feeSignature] : []
|
|
16904
|
+
]),
|
|
16905
|
+
...legs.map((leg) => leg.hash).filter(isText)
|
|
16906
|
+
])
|
|
16907
|
+
];
|
|
16908
|
+
}
|
|
16909
|
+
function swapJsonResult(result) {
|
|
16910
|
+
const signatures = tradeSignatures(result);
|
|
16911
|
+
const inner = (name) => result[name] && typeof result[name] === "object" ? result[name].status : undefined;
|
|
16912
|
+
const status = [result.status, inner("job"), inner("payload")].find(isText) ?? null;
|
|
16913
|
+
return {
|
|
16914
|
+
...result,
|
|
16915
|
+
kind: result.kind,
|
|
16916
|
+
status,
|
|
16917
|
+
clientTradeId: result.clientTradeId ?? null,
|
|
16918
|
+
signature: signatures[0] ?? null,
|
|
16919
|
+
signatures,
|
|
16920
|
+
raw: result
|
|
16921
|
+
};
|
|
16922
|
+
}
|
|
16875
16923
|
function printTradingResult(ctx, result) {
|
|
16876
16924
|
if (result.kind === "swap" && result.job && typeof result.job === "object") {
|
|
16877
16925
|
const job = result.job;
|
|
16878
16926
|
describeSwapEvidence(ctx, job, job.status === "failed");
|
|
16879
16927
|
}
|
|
16880
|
-
ctx.
|
|
16928
|
+
const printed = ctx.json && (result.kind === "swap" || result.kind === "trade") ? swapJsonResult(result) : result;
|
|
16929
|
+
ctx.deps.stdout.write(ctx.json ? `${JSON.stringify(printed)}
|
|
16881
16930
|
` : `${JSON.stringify(result, null, 2)}
|
|
16882
16931
|
`);
|
|
16883
16932
|
return 0;
|
|
@@ -17684,7 +17733,7 @@ async function bridgeSwap(ctx, args) {
|
|
|
17684
17733
|
return printTradingResult(ctx, { ...receipt, waited: { final: false, error: reason } });
|
|
17685
17734
|
}
|
|
17686
17735
|
}
|
|
17687
|
-
var AGENT_MAX_SLIPPAGE_BPS = 5000, SLIPPAGE_RANGE_MESSAGE, settlementSchema, bridgeFactsSchema, HOOD_TRADE_LEGS, bridgeBuildSchema;
|
|
17736
|
+
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
17737
|
var init_swap = __esm(() => {
|
|
17689
17738
|
init_zod();
|
|
17690
17739
|
init_args();
|
|
@@ -28872,7 +28921,7 @@ __export(exports_vault_support, {
|
|
|
28872
28921
|
askForOwnPassphrase: () => askForOwnPassphrase,
|
|
28873
28922
|
PASSPHRASE_ONLY_PREFIX: () => PASSPHRASE_ONLY_PREFIX
|
|
28874
28923
|
});
|
|
28875
|
-
import { dirname as dirname6 } from "node:path";
|
|
28924
|
+
import { dirname as dirname6, resolve as resolvePath } from "node:path";
|
|
28876
28925
|
function refuseEnvPassphrase(ctx) {
|
|
28877
28926
|
if (ctx.deps.env.CANDLE_KEYSTORE_PASSPHRASE === undefined)
|
|
28878
28927
|
return true;
|
|
@@ -29002,6 +29051,9 @@ function assertVaultHelperIdentities(deps, envelopes) {
|
|
|
29002
29051
|
}
|
|
29003
29052
|
async function unlockInteractively(ctx, path, raw, opts = {}) {
|
|
29004
29053
|
await assertNotOlderCopy(ctx, path, raw, opts.acceptOlderCopy ?? false);
|
|
29054
|
+
const held = ctx.heldUnlock;
|
|
29055
|
+
if (held !== undefined && resolvePath(held.path) === resolvePath(path))
|
|
29056
|
+
return held.open(raw);
|
|
29005
29057
|
const { deps } = ctx;
|
|
29006
29058
|
const file = parseVaultFile(raw);
|
|
29007
29059
|
assertVaultHelperIdentities(deps, file.envelopes);
|
|
@@ -37251,9 +37303,11 @@ async function resolveTargetKey(ctx, deviceToken, raw, opts = {}) {
|
|
|
37251
37303
|
return { ok: false, code: 1 };
|
|
37252
37304
|
}
|
|
37253
37305
|
if (byPrefix)
|
|
37254
|
-
return { ok: true, keyPrefix: byPrefix.keyPrefix };
|
|
37255
|
-
if (byLabel.length === 1)
|
|
37256
|
-
|
|
37306
|
+
return { ok: true, keyPrefix: byPrefix.keyPrefix, embeddedWallet: byPrefix.embeddedWallet };
|
|
37307
|
+
if (byLabel.length === 1) {
|
|
37308
|
+
const only = byLabel[0];
|
|
37309
|
+
return { ok: true, keyPrefix: only.keyPrefix, embeddedWallet: only.embeddedWallet };
|
|
37310
|
+
}
|
|
37257
37311
|
if (byLabel.length > 1) {
|
|
37258
37312
|
writeLocalFailure(deps, {
|
|
37259
37313
|
code: `${codePrefix}_KEY_AMBIGUOUS`,
|
|
@@ -37265,8 +37319,10 @@ async function resolveTargetKey(ctx, deviceToken, raw, opts = {}) {
|
|
|
37265
37319
|
return { ok: true, keyPrefix: raw };
|
|
37266
37320
|
}
|
|
37267
37321
|
const matches = keys.filter((key) => key.label === raw);
|
|
37268
|
-
if (matches.length === 1)
|
|
37269
|
-
|
|
37322
|
+
if (matches.length === 1) {
|
|
37323
|
+
const only = matches[0];
|
|
37324
|
+
return { ok: true, keyPrefix: only.keyPrefix, embeddedWallet: only.embeddedWallet };
|
|
37325
|
+
}
|
|
37270
37326
|
if (matches.length === 0) {
|
|
37271
37327
|
const labelled = keys.filter((key) => key.label).map((key) => `${key.keyPrefix} ${labelCell(key.label)}`);
|
|
37272
37328
|
writeLocalFailure(deps, {
|
|
@@ -37581,6 +37637,11 @@ var init_tee_rebind = __esm(() => {
|
|
|
37581
37637
|
function effectiveEmbeddedWallet(value) {
|
|
37582
37638
|
return value === "denied" ? "denied" : "allowed";
|
|
37583
37639
|
}
|
|
37640
|
+
function withdrawNeedsEmbeddedWalletLine(keyPrefix) {
|
|
37641
|
+
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.
|
|
37642
|
+
` + `Allow it with: candle keys update ${keyPrefix} --embedded-wallet allow
|
|
37643
|
+
` + "Transfers out of the key's own TEE wallets do not need it.";
|
|
37644
|
+
}
|
|
37584
37645
|
function keyName(keyPrefix, label) {
|
|
37585
37646
|
const cleaned = labelCell(label ?? undefined);
|
|
37586
37647
|
return cleaned ? `${keyPrefix} (${cleaned})` : keyPrefix;
|
|
@@ -38094,6 +38155,10 @@ async function keysCreate(args, ctx) {
|
|
|
38094
38155
|
} catch (error) {
|
|
38095
38156
|
storeError = error instanceof Error ? error.message : String(error);
|
|
38096
38157
|
}
|
|
38158
|
+
if (presetForScopes(body.scopes) === "readwritetransfer" && body.embeddedWallet === "denied") {
|
|
38159
|
+
notice(deps, "stderr", "warning", `${withdrawNeedsEmbeddedWalletLine(body.keyPrefix)}
|
|
38160
|
+
Or mint it that way: candle keys create --access withdraw --embedded-wallet allow`);
|
|
38161
|
+
}
|
|
38097
38162
|
if (json) {
|
|
38098
38163
|
deps.stdout.write(`${JSON.stringify({ ...body, stored, ...storeError ? { storeError } : {} })}
|
|
38099
38164
|
`);
|
|
@@ -45295,7 +45360,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
45295
45360
|
}, async (args) => callAndRelay("candle_get_fees", args, getConfig(), doFetch));
|
|
45296
45361
|
register("candle_claim_fees", {
|
|
45297
45362
|
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.",
|
|
45363
|
+
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
45364
|
inputSchema: claimFeesShape
|
|
45300
45365
|
}, async (args) => callAndRelay("candle_claim_fees", args, getConfig(), doFetch));
|
|
45301
45366
|
register("candle_get_wallets", {
|
|
@@ -76159,7 +76224,7 @@ var LAUNCH_USAGE = `candle launch ${launchFlagFields.map((field) => {
|
|
|
76159
76224
|
if (field.key === "imageUrl")
|
|
76160
76225
|
return `--image <path>|${invocation}`;
|
|
76161
76226
|
return "required" in field && field.required ? invocation : `[${invocation}]`;
|
|
76162
|
-
}).join(" ")} [--check] [--yes] [--rpc-url <url>] [--json]`;
|
|
76227
|
+
}).join(" ")} [--check] [--yes] [--require-fee-wallet] [--rpc-url <url>] [--json]`;
|
|
76163
76228
|
function launchFieldProblem(flags) {
|
|
76164
76229
|
for (const field of launchFlagFields) {
|
|
76165
76230
|
const value = flags[field.cli.flag];
|
|
@@ -76344,7 +76409,7 @@ var HELP = {
|
|
|
76344
76409
|
group: "Trade",
|
|
76345
76410
|
intro: "Quote, swap and bridge on Solana or Hood; read an operation",
|
|
76346
76411
|
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.",
|
|
76412
|
+
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
76413
|
usage: ["candle swap <from> <to> [flags]", "candle swap status <id>"],
|
|
76349
76414
|
rows: [
|
|
76350
76415
|
{
|
|
@@ -76657,19 +76722,19 @@ var HELP = {
|
|
|
76657
76722
|
group: "Trade",
|
|
76658
76723
|
intro: "Move funds out of a TEE wallet to your wallets or its vault",
|
|
76659
76724
|
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.",
|
|
76725
|
+
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
76726
|
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]"
|
|
76727
|
+
"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
76728
|
],
|
|
76664
76729
|
rows: [],
|
|
76665
76730
|
flags: [
|
|
76666
76731
|
{
|
|
76667
|
-
invocation: "--to <address|wallet name|vault>",
|
|
76668
|
-
description: "Where the funds go: vault is the wallet's own pin"
|
|
76732
|
+
invocation: "--to <address|wallet name|allowlist label|vault>",
|
|
76733
|
+
description: "Where the funds go: vault is the wallet's own pin; a linked wallet's name wins over an allowlist label"
|
|
76669
76734
|
},
|
|
76670
76735
|
{
|
|
76671
76736
|
invocation: "--asset <SOL|USDC|CNDL|ETH|USDG>",
|
|
76672
|
-
description: "A base asset (required for a linked-wallet destination)"
|
|
76737
|
+
description: "A base asset (required for a linked-wallet or approved-address destination)"
|
|
76673
76738
|
},
|
|
76674
76739
|
{ invocation: "--mint <mint>", description: "Any Solana mint, instead of --asset (vault destinations only)" },
|
|
76675
76740
|
{ invocation: "--token <0x...>", description: "Any Hood ERC-20, instead of --asset (vault destinations only)" },
|
|
@@ -76691,9 +76756,14 @@ var HELP = {
|
|
|
76691
76756
|
launch: {
|
|
76692
76757
|
group: "Trade",
|
|
76693
76758
|
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, which the preview, the confirm and --check all say. --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.",
|
|
76695
|
-
usage: [LAUNCH_USAGE],
|
|
76696
|
-
rows: [
|
|
76759
|
+
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, which the preview, the confirm and --check all say; 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.",
|
|
76760
|
+
usage: [LAUNCH_USAGE, "candle launch status <mint-or-client-launch-id> [--wait]"],
|
|
76761
|
+
rows: [
|
|
76762
|
+
{
|
|
76763
|
+
invocation: "status <mint-or-client-launch-id> [--wait]",
|
|
76764
|
+
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."
|
|
76765
|
+
}
|
|
76766
|
+
],
|
|
76697
76767
|
flags: [
|
|
76698
76768
|
...launchFlagRows,
|
|
76699
76769
|
{
|
|
@@ -76705,6 +76775,10 @@ var HELP = {
|
|
|
76705
76775
|
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
76776
|
},
|
|
76707
76777
|
{ invocation: "--yes", description: "Confirm the launch without an interactive prompt." },
|
|
76778
|
+
{
|
|
76779
|
+
invocation: "--require-fee-wallet",
|
|
76780
|
+
description: "Refuse the launch when no --fee-wallet is given, rather than letting the fees earn to the account wallet."
|
|
76781
|
+
},
|
|
76708
76782
|
{
|
|
76709
76783
|
invocation: "--rpc-url <url>",
|
|
76710
76784
|
description: "The Solana RPC for a TEE launch with no first buy, or the Hood RPC for receipt reads."
|
|
@@ -76718,7 +76792,8 @@ var HELP = {
|
|
|
76718
76792
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4",
|
|
76719
76793
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1",
|
|
76720
76794
|
"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"
|
|
76795
|
+
"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",
|
|
76796
|
+
"candle launch status <mint-or-id> --wait"
|
|
76722
76797
|
],
|
|
76723
76798
|
env: ENV_API
|
|
76724
76799
|
},
|
|
@@ -77363,7 +77438,7 @@ var HELP = {
|
|
|
77363
77438
|
},
|
|
77364
77439
|
{
|
|
77365
77440
|
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"
|
|
77441
|
+
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
77442
|
},
|
|
77368
77443
|
{
|
|
77369
77444
|
invocation: "promote --from|--in-place <label> [--sweep-to <label>] [--rpc-url <url>] [--to-key <prefix|label>]",
|
|
@@ -77378,8 +77453,8 @@ var HELP = {
|
|
|
77378
77453
|
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
77454
|
},
|
|
77380
77455
|
{
|
|
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"
|
|
77456
|
+
invocation: "demote <tee-address>... | --all --empty [--rpc-url <url>] [--emergency] [--sweep-to <label>] [--yes] [--token <0x...>] [--from-block <n>]",
|
|
77457
|
+
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
77458
|
},
|
|
77384
77459
|
{
|
|
77385
77460
|
invocation: "export-key <label> --to <new-file>",
|
|
@@ -77398,7 +77473,15 @@ var HELP = {
|
|
|
77398
77473
|
},
|
|
77399
77474
|
{
|
|
77400
77475
|
invocation: "--all",
|
|
77401
|
-
description: "list: include retired TEE wallets (demoted and swept), which are hidden by default"
|
|
77476
|
+
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)"
|
|
77477
|
+
},
|
|
77478
|
+
{
|
|
77479
|
+
invocation: "--empty",
|
|
77480
|
+
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"
|
|
77481
|
+
},
|
|
77482
|
+
{
|
|
77483
|
+
invocation: "--yes",
|
|
77484
|
+
description: "demote with several wallets: skip the one typed confirmation for the listed set"
|
|
77402
77485
|
},
|
|
77403
77486
|
{
|
|
77404
77487
|
invocation: "--balances",
|
|
@@ -77436,6 +77519,7 @@ var HELP = {
|
|
|
77436
77519
|
"candle vault new-key --chain solana --labels-from ./replacement-names.txt",
|
|
77437
77520
|
"candle vault promote-batch --pairs-from ./promote-plan.csv",
|
|
77438
77521
|
"candle vault promote-batch --pairs-from ./promote-plan.csv --to-key tr-01",
|
|
77522
|
+
"candle vault demote --all --empty",
|
|
77439
77523
|
"candle vault enroll security-key --label yubikey-a",
|
|
77440
77524
|
"candle vault backup --to /Volumes/BACKUP/vault.enc",
|
|
77441
77525
|
"candle vault backup --to icloud",
|
|
@@ -77496,8 +77580,12 @@ var HELP = {
|
|
|
77496
77580
|
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
77581
|
},
|
|
77498
77582
|
{
|
|
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
|
|
77583
|
+
invocation: "signer new --key <prefix|label> [--out <pem>] [--force] [--no-wait]",
|
|
77584
|
+
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"
|
|
77585
|
+
},
|
|
77586
|
+
{
|
|
77587
|
+
invocation: " signer status --key <prefix|label>",
|
|
77588
|
+
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
77589
|
},
|
|
77502
77590
|
{
|
|
77503
77591
|
invocation: " signer remove --key <prefix|label>",
|
|
@@ -82610,14 +82698,69 @@ async function tokenAccountFrozen(rpc, address) {
|
|
|
82610
82698
|
return { exists: false, frozen: false };
|
|
82611
82699
|
return { exists: true, frozen: account.data[TOKEN_ACCOUNT_STATE_OFFSET2] === TOKEN_ACCOUNT_STATE_FROZEN2 };
|
|
82612
82700
|
}
|
|
82701
|
+
function rawToDecimal2(raw, decimals) {
|
|
82702
|
+
if (decimals === 0)
|
|
82703
|
+
return raw.toString();
|
|
82704
|
+
const digits = raw.toString().padStart(decimals + 1, "0");
|
|
82705
|
+
return `${digits.slice(0, -decimals)}.${digits.slice(-decimals)}`.replace(/\.?0+$/, "");
|
|
82706
|
+
}
|
|
82707
|
+
async function planSolMax(rpc, from, to) {
|
|
82708
|
+
if (from === to)
|
|
82709
|
+
throw new VaultError("VAULT_INDEX_INVALID", "SOL max needs a destination different from the source.");
|
|
82710
|
+
const account = await rpc.getAccountInfo(from);
|
|
82711
|
+
if (account && (account.owner !== SYSTEM_PROGRAM_ID || account.data.length !== 0)) {
|
|
82712
|
+
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.");
|
|
82713
|
+
}
|
|
82714
|
+
const balance = account?.lamports ?? 0n;
|
|
82715
|
+
const reserve = 0n;
|
|
82716
|
+
const blockhash = await rpc.getLatestBlockhash();
|
|
82717
|
+
let raw = balance;
|
|
82718
|
+
for (let attempt = 0;attempt < 3; attempt++) {
|
|
82719
|
+
const instructions = [systemTransfer(decodePubkey(from), decodePubkey(to), raw)];
|
|
82720
|
+
const message = compileLegacyMessage({ feePayer: decodePubkey(from), recentBlockhash: blockhash, instructions });
|
|
82721
|
+
const fee = await rpc.getFeeForMessage(toBase642(message));
|
|
82722
|
+
if (fee === null) {
|
|
82723
|
+
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.");
|
|
82724
|
+
}
|
|
82725
|
+
const available = balance - fee - reserve;
|
|
82726
|
+
if (available <= 0n) {
|
|
82727
|
+
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." });
|
|
82728
|
+
}
|
|
82729
|
+
if (raw === available) {
|
|
82730
|
+
const amount = rawToDecimal2(raw, 9);
|
|
82731
|
+
return {
|
|
82732
|
+
asset: "SOL",
|
|
82733
|
+
amount,
|
|
82734
|
+
amountRaw: raw,
|
|
82735
|
+
decimals: 9,
|
|
82736
|
+
from,
|
|
82737
|
+
to,
|
|
82738
|
+
instructions,
|
|
82739
|
+
solMax: { blockhash, fee, reserve },
|
|
82740
|
+
displayLines: [
|
|
82741
|
+
`fee payer ${from}`,
|
|
82742
|
+
`destination ${to}`,
|
|
82743
|
+
`amount ${amount} SOL = ${raw} lamports (max)`,
|
|
82744
|
+
"mint (native SOL)",
|
|
82745
|
+
`source reserve ${reserve} lamports (source emptied)`
|
|
82746
|
+
]
|
|
82747
|
+
};
|
|
82748
|
+
}
|
|
82749
|
+
raw = available;
|
|
82750
|
+
}
|
|
82751
|
+
throw new VaultError("VAULT_UNREADABLE", "The SOL max fee quote kept changing. Nothing was signed or sent; retry when the RPC is stable.");
|
|
82752
|
+
}
|
|
82613
82753
|
async function planTransfer(input) {
|
|
82614
82754
|
const asset = input.asset.toUpperCase();
|
|
82615
82755
|
const fromKey = decodePubkey(input.from);
|
|
82616
82756
|
const toKey = decodePubkey(input.to);
|
|
82757
|
+
const max = input.amount.toLowerCase() === "max";
|
|
82617
82758
|
if (asset === "SOL") {
|
|
82759
|
+
if (max)
|
|
82760
|
+
return planSolMax(input.rpc, input.from, input.to);
|
|
82618
82761
|
const raw2 = decimalToRaw4(input.amount, 9);
|
|
82619
82762
|
if (raw2 === null || raw2 === 0n) {
|
|
82620
|
-
throw new VaultError("VAULT_INDEX_INVALID", "--amount must be a positive SOL decimal with at most 9 places.");
|
|
82763
|
+
throw new VaultError("VAULT_INDEX_INVALID", "--amount must be max or a positive SOL decimal with at most 9 places.");
|
|
82621
82764
|
}
|
|
82622
82765
|
return {
|
|
82623
82766
|
asset: "SOL",
|
|
@@ -82648,13 +82791,16 @@ async function planTransfer(input) {
|
|
|
82648
82791
|
throw new VaultError("VAULT_UNREADABLE", `Could not read mint ${mintAddress}: ${asMessage(error)}`);
|
|
82649
82792
|
}
|
|
82650
82793
|
const decimals = profile.decimals;
|
|
82651
|
-
const
|
|
82794
|
+
const source = ataFor(profile, fromKey);
|
|
82795
|
+
const raw = max ? tokenAccountAmount((await rpc.getAccountInfo(encodePubkey(source)))?.data ?? new Uint8Array) : decimalToRaw4(input.amount, decimals);
|
|
82796
|
+
if (max && raw === 0n) {
|
|
82797
|
+
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.`);
|
|
82798
|
+
}
|
|
82652
82799
|
if (raw === null || raw === 0n) {
|
|
82653
|
-
throw new VaultError("VAULT_INDEX_INVALID", `--amount must be a positive decimal with at most ${decimals} decimal places.`);
|
|
82800
|
+
throw new VaultError("VAULT_INDEX_INVALID", `--amount must be max or a positive decimal with at most ${decimals} decimal places.`);
|
|
82654
82801
|
}
|
|
82655
82802
|
const mint = decodePubkey(mintAddress);
|
|
82656
82803
|
const tokenProgram = decodePubkey(profile.tokenProgram);
|
|
82657
|
-
const source = ataFor(profile, fromKey);
|
|
82658
82804
|
const destination = ataFor(profile, toKey);
|
|
82659
82805
|
const instructions = [];
|
|
82660
82806
|
const accountCreationLines = [];
|
|
@@ -82691,7 +82837,7 @@ async function planTransfer(input) {
|
|
|
82691
82837
|
}));
|
|
82692
82838
|
return {
|
|
82693
82839
|
asset: asset === "USDC" ? "USDC" : mintAddress,
|
|
82694
|
-
amount: input.amount,
|
|
82840
|
+
amount: max ? rawToDecimal2(raw, decimals) : input.amount,
|
|
82695
82841
|
amountRaw: raw,
|
|
82696
82842
|
decimals,
|
|
82697
82843
|
mint: mintAddress,
|
|
@@ -82708,7 +82854,7 @@ async function planTransfer(input) {
|
|
|
82708
82854
|
displayLines: [
|
|
82709
82855
|
`fee payer ${input.from}`,
|
|
82710
82856
|
`destination ${input.to}`,
|
|
82711
|
-
`amount ${input.amount} (${raw} raw, ${decimals} dp)`,
|
|
82857
|
+
`amount ${max ? rawToDecimal2(raw, decimals) : input.amount} (${raw} raw, ${decimals} dp)${max ? " (max)" : ""}`,
|
|
82712
82858
|
`mint ${mintAddress}`,
|
|
82713
82859
|
`program ${profile.tokenProgram}${profile.token2022 ? " (Token-2022)" : ""}`,
|
|
82714
82860
|
...accountCreationLines,
|
|
@@ -82788,7 +82934,7 @@ async function signAndBroadcastTransfer(input) {
|
|
|
82788
82934
|
if (encodePubkey(feePayer) !== input.plan.from) {
|
|
82789
82935
|
throw new VaultError("VAULT_VERIFY_FAILED", "The decrypted key does not match the planned fee payer.");
|
|
82790
82936
|
}
|
|
82791
|
-
const blockhash = await input.solana.read(() => rpc.getLatestBlockhash());
|
|
82937
|
+
const blockhash = input.plan.solMax?.blockhash ?? await input.solana.read(() => rpc.getLatestBlockhash());
|
|
82792
82938
|
const message = compileLegacyMessage({
|
|
82793
82939
|
feePayer,
|
|
82794
82940
|
recentBlockhash: blockhash,
|
|
@@ -84791,7 +84937,7 @@ function rebindJson(input, chain2) {
|
|
|
84791
84937
|
init_key_output();
|
|
84792
84938
|
init_keys();
|
|
84793
84939
|
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]";
|
|
84940
|
+
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
84941
|
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
84942
|
var SIGNER_POLL_MS = 5000;
|
|
84797
84943
|
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 +84971,8 @@ async function teeSigner(args, ctx) {
|
|
|
84825
84971
|
return teeSignerNew(rest, ctx);
|
|
84826
84972
|
if (sub === "remove")
|
|
84827
84973
|
return teeSignerRemove(rest, ctx);
|
|
84974
|
+
if (sub === "status")
|
|
84975
|
+
return teeSignerStatus(rest, ctx);
|
|
84828
84976
|
writeScreenUsageFailure(ctx.deps, sub === undefined ? USAGE_TEE_SIGNER : `Unknown subcommand: ${sub}. ${USAGE_TEE_SIGNER}`, ctx.json);
|
|
84829
84977
|
return 2;
|
|
84830
84978
|
}
|
|
@@ -84837,7 +84985,11 @@ function generatePair() {
|
|
|
84837
84985
|
}
|
|
84838
84986
|
async function teeSignerNew(args, ctx) {
|
|
84839
84987
|
const { deps, json } = ctx;
|
|
84840
|
-
const parsed = parseArgs(args, {
|
|
84988
|
+
const parsed = parseArgs(args, {
|
|
84989
|
+
valueFlags: ["--key", "--out"],
|
|
84990
|
+
booleanFlags: ["--force", "--no-wait"],
|
|
84991
|
+
pathFlags: ["--out"]
|
|
84992
|
+
});
|
|
84841
84993
|
if ("error" in parsed) {
|
|
84842
84994
|
writeScreenUsageFailure(deps, `${parsed.error}. ${USAGE_TEE_SIGNER}`, json);
|
|
84843
84995
|
return 2;
|
|
@@ -84853,6 +85005,7 @@ async function teeSignerNew(args, ctx) {
|
|
|
84853
85005
|
}
|
|
84854
85006
|
const out = parsed.values["--out"];
|
|
84855
85007
|
const force = parsed.booleans.has("--force");
|
|
85008
|
+
const noWait = parsed.booleans.has("--no-wait");
|
|
84856
85009
|
await printIdentity(ctx);
|
|
84857
85010
|
const apiKey = await resolveApiKey(deps, ctx.profile);
|
|
84858
85011
|
if (!apiKey) {
|
|
@@ -84891,7 +85044,7 @@ async function teeSignerNew(args, ctx) {
|
|
|
84891
85044
|
entry = undefined;
|
|
84892
85045
|
}
|
|
84893
85046
|
if (entry !== undefined && active !== null && active.spkiSha256 === entry.spkiSha256) {
|
|
84894
|
-
const recorded = { ...entry, signerQuorumId: active.signerQuorumId };
|
|
85047
|
+
const recorded = { ...entry, signerQuorumId: active.signerQuorumId, pendingRequest: undefined };
|
|
84895
85048
|
await saveKeySignerEntry(deps, recorded);
|
|
84896
85049
|
await writeOut(ctx, out, recorded);
|
|
84897
85050
|
return reportApproved(ctx, view, recorded, { resumed: true, requested: false });
|
|
@@ -84940,12 +85093,17 @@ async function teeSignerNew(args, ctx) {
|
|
|
84940
85093
|
resumed = false;
|
|
84941
85094
|
}
|
|
84942
85095
|
await writeOut(ctx, out, entry);
|
|
84943
|
-
const
|
|
85096
|
+
const cached = matchingPendingRequest(ctx, view, entry);
|
|
85097
|
+
const requested = cached ? {
|
|
85098
|
+
ok: true,
|
|
85099
|
+
status: 200,
|
|
85100
|
+
body: { ...cached, spkiSha256: entry.spkiSha256, fingerprint: entry.fingerprint }
|
|
85101
|
+
} : await requestSigner(ctx, apiKey, entry.publicKeyDer);
|
|
84944
85102
|
if (!requested.ok) {
|
|
84945
85103
|
if (requested.code === "KEY_SIGNER_ALREADY_ACTIVE") {
|
|
84946
85104
|
const quorum = errorDetails(requested).keySigner?.signerQuorumId;
|
|
84947
85105
|
if (typeof quorum === "string") {
|
|
84948
|
-
const recorded = { ...entry, signerQuorumId: quorum };
|
|
85106
|
+
const recorded = { ...entry, signerQuorumId: quorum, pendingRequest: undefined };
|
|
84949
85107
|
await saveKeySignerEntry(deps, recorded);
|
|
84950
85108
|
const reread = await readSigner(ctx, "self", { apiKey });
|
|
84951
85109
|
return reportApproved(ctx, reread.ok ? reread.body : view, recorded, {
|
|
@@ -84982,6 +85140,27 @@ async function teeSignerNew(args, ctx) {
|
|
|
84982
85140
|
}, json);
|
|
84983
85141
|
return 1;
|
|
84984
85142
|
}
|
|
85143
|
+
entry = {
|
|
85144
|
+
...entry,
|
|
85145
|
+
pendingRequest: {
|
|
85146
|
+
apiUrl: ctx.apiUrl,
|
|
85147
|
+
userCode: body.userCode,
|
|
85148
|
+
verificationUri: body.verificationUri,
|
|
85149
|
+
verificationUriComplete: body.verificationUriComplete,
|
|
85150
|
+
expiresAt: body.expiresAt
|
|
85151
|
+
}
|
|
85152
|
+
};
|
|
85153
|
+
await saveKeySignerEntry(deps, entry);
|
|
85154
|
+
if (json) {
|
|
85155
|
+
deps.stdout.write(`${JSON.stringify({
|
|
85156
|
+
state: "pending",
|
|
85157
|
+
userCode: body.userCode,
|
|
85158
|
+
verificationUriComplete: body.verificationUriComplete,
|
|
85159
|
+
expiresAt: body.expiresAt,
|
|
85160
|
+
fingerprint: body.fingerprint
|
|
85161
|
+
})}
|
|
85162
|
+
`);
|
|
85163
|
+
}
|
|
84985
85164
|
if (json) {
|
|
84986
85165
|
writeFields(deps, "stderr", [
|
|
84987
85166
|
["Key", keyPrefix],
|
|
@@ -84992,7 +85171,7 @@ async function teeSignerNew(args, ctx) {
|
|
|
84992
85171
|
["Approve on owner machine", `candle keys signer approve ${body.userCode} --key ${keyPrefix}`],
|
|
84993
85172
|
["Expires", formatTime(body.expiresAt)]
|
|
84994
85173
|
], "Key signer request");
|
|
84995
|
-
notice(deps, "stderr", "note",
|
|
85174
|
+
notice(deps, "stderr", "note", `The account owner approves it, typing this full fingerprint.${noWait ? "" : " Waiting for approval..."}`);
|
|
84996
85175
|
} else {
|
|
84997
85176
|
writeFields(deps, "stderr", [
|
|
84998
85177
|
["Key", keyPrefix],
|
|
@@ -85004,9 +85183,11 @@ async function teeSignerNew(args, ctx) {
|
|
|
85004
85183
|
], "Key signer request");
|
|
85005
85184
|
notice(deps, "stderr", "warning", "The account owner approves it, typing this full fingerprint.");
|
|
85006
85185
|
notice(deps, "stderr", "note", `Or, on the owner's machine:
|
|
85007
|
-
candle keys signer approve ${body.userCode} --key ${keyPrefix}
|
|
85008
|
-
Waiting for approval...`);
|
|
85186
|
+
candle keys signer approve ${body.userCode} --key ${keyPrefix}${noWait ? "" : `
|
|
85187
|
+
Waiting for approval...`}`);
|
|
85009
85188
|
}
|
|
85189
|
+
if (noWait)
|
|
85190
|
+
return 0;
|
|
85010
85191
|
while (deps.now() < body.expiresAt) {
|
|
85011
85192
|
await deps.sleep(SIGNER_POLL_MS);
|
|
85012
85193
|
const read = await readSigner(ctx, "self", { apiKey });
|
|
@@ -85015,9 +85196,9 @@ Waiting for approval...`);
|
|
|
85015
85196
|
view = read.body;
|
|
85016
85197
|
const now = activeSigner(view);
|
|
85017
85198
|
if (now !== null && now.spkiSha256 === entry.spkiSha256) {
|
|
85018
|
-
const recorded = { ...entry, signerQuorumId: now.signerQuorumId };
|
|
85199
|
+
const recorded = { ...entry, signerQuorumId: now.signerQuorumId, pendingRequest: undefined };
|
|
85019
85200
|
await saveKeySignerEntry(deps, recorded);
|
|
85020
|
-
return reportApproved(ctx, view, recorded, { resumed, requested:
|
|
85201
|
+
return reportApproved(ctx, view, recorded, { resumed, requested: cached === undefined, replaces });
|
|
85021
85202
|
}
|
|
85022
85203
|
if (view.pending === null || view.pending.spkiSha256 !== entry.spkiSha256) {
|
|
85023
85204
|
writeScreenLocalFailure(deps, {
|
|
@@ -85037,6 +85218,58 @@ Waiting for approval...`);
|
|
|
85037
85218
|
}, json);
|
|
85038
85219
|
return 1;
|
|
85039
85220
|
}
|
|
85221
|
+
function matchingPendingRequest(ctx, view, entry) {
|
|
85222
|
+
const pending = entry.pendingRequest;
|
|
85223
|
+
return pending?.apiUrl === ctx.apiUrl && pending.expiresAt > ctx.deps.now() && view.pending?.spkiSha256 === entry.spkiSha256 && view.pending.expiresAt === pending.expiresAt ? pending : undefined;
|
|
85224
|
+
}
|
|
85225
|
+
async function teeSignerStatus(args, ctx) {
|
|
85226
|
+
const { deps, json } = ctx;
|
|
85227
|
+
const parsed = parseArgs(args, { valueFlags: ["--key"] });
|
|
85228
|
+
if ("error" in parsed || !parsed.values["--key"] || parsed.positionals.length > 0) {
|
|
85229
|
+
writeScreenUsageFailure(deps, USAGE_TEE_SIGNER, json);
|
|
85230
|
+
return 2;
|
|
85231
|
+
}
|
|
85232
|
+
await printIdentity(ctx);
|
|
85233
|
+
const apiKey = await resolveApiKey(deps, ctx.profile);
|
|
85234
|
+
if (!apiKey) {
|
|
85235
|
+
writeScreenLocalFailure(deps, { code: "NO_API_KEY", message: "tee signer status needs this key's API key." }, json);
|
|
85236
|
+
return 1;
|
|
85237
|
+
}
|
|
85238
|
+
const read = await readSigner(ctx, "self", { apiKey });
|
|
85239
|
+
if (!read.ok)
|
|
85240
|
+
return writeSignerReadFailure(ctx, read);
|
|
85241
|
+
const view = read.body;
|
|
85242
|
+
const keyRaw = parsed.values["--key"];
|
|
85243
|
+
if (keyRaw !== view.keyPrefix && keyRaw !== view.keyLabel) {
|
|
85244
|
+
writeScreenLocalFailure(deps, { code: "KEY_SIGNER_KEY_MISMATCH", message: `This API key is ${view.keyPrefix}, not ${keyRaw}.` }, json);
|
|
85245
|
+
return 1;
|
|
85246
|
+
}
|
|
85247
|
+
const locals = await localKeySigners(deps, view.keyPrefix);
|
|
85248
|
+
const entry = locals.find((local) => local.spkiSha256 === view.pending?.spkiSha256);
|
|
85249
|
+
const pending = entry ? matchingPendingRequest(ctx, view, entry) : undefined;
|
|
85250
|
+
if (json) {
|
|
85251
|
+
deps.stdout.write(`${JSON.stringify({
|
|
85252
|
+
ok: true,
|
|
85253
|
+
command: "tee signer status",
|
|
85254
|
+
...view,
|
|
85255
|
+
...pending ? {
|
|
85256
|
+
userCode: pending.userCode,
|
|
85257
|
+
verificationUriComplete: pending.verificationUriComplete,
|
|
85258
|
+
expiresAt: pending.expiresAt
|
|
85259
|
+
} : {}
|
|
85260
|
+
})}
|
|
85261
|
+
`);
|
|
85262
|
+
} else {
|
|
85263
|
+
writeFields(deps, "stdout", [
|
|
85264
|
+
["Key", view.keyPrefix],
|
|
85265
|
+
["State", view.state],
|
|
85266
|
+
["Fingerprint", view.pending?.fingerprint ?? view.fingerprint],
|
|
85267
|
+
["Approve at", pending?.verificationUriComplete],
|
|
85268
|
+
["Expires", view.pending?.expiresAt ? formatTime(view.pending.expiresAt, { now: deps.now() }) : undefined]
|
|
85269
|
+
], "Key signer status");
|
|
85270
|
+
}
|
|
85271
|
+
return 0;
|
|
85272
|
+
}
|
|
85040
85273
|
async function writeOut(ctx, out, entry) {
|
|
85041
85274
|
if (out === undefined)
|
|
85042
85275
|
return;
|
|
@@ -89541,6 +89774,7 @@ var REASON_WORDS = {
|
|
|
89541
89774
|
delegation_off: "the embedded wallet is not delegated for agent access",
|
|
89542
89775
|
embedded_wallet_not_permitted: "this key may not use the embedded wallet",
|
|
89543
89776
|
not_recipient: "the fees go to someone else, or the token has no fee vault",
|
|
89777
|
+
fee_wallet_designated: "the fees go to this launch's designated fee wallet, which claims them itself",
|
|
89544
89778
|
insufficient_gas: "the recipient wallet cannot cover the network fee"
|
|
89545
89779
|
};
|
|
89546
89780
|
function reasonText(reason, missing) {
|
|
@@ -89612,7 +89846,53 @@ async function feesClaim(args, ctx) {
|
|
|
89612
89846
|
const dryRun = parsed.booleans.has("--dry-run");
|
|
89613
89847
|
try {
|
|
89614
89848
|
const key = await tradingKey(ctx);
|
|
89615
|
-
|
|
89849
|
+
let body = await request(ctx, key, `/api/v1/agent/fees/${encodeURIComponent(mint)}/claim`, {
|
|
89850
|
+
dryRun,
|
|
89851
|
+
teeSigning: true
|
|
89852
|
+
});
|
|
89853
|
+
if (!dryRun && Array.isArray(body.signing) && body.signing.length > 0) {
|
|
89854
|
+
if (body.signing.length > 2)
|
|
89855
|
+
throw new TradingError("INVALID_RESPONSE", "Too many fee claims to sign");
|
|
89856
|
+
const { rows, appId, keyPrefix } = await listTradingWallets(ctx, key, "launch:write");
|
|
89857
|
+
const signedClaims = [];
|
|
89858
|
+
for (const signing of body.signing) {
|
|
89859
|
+
const row = rows.find((row2) => row2.id === signing.walletId && row2.address === signing.wallet);
|
|
89860
|
+
if (!row || row.chain !== "solana")
|
|
89861
|
+
throw new TradingError("TEE_WALLET_REQUIRED", "The claim's wallet is not bound to this key");
|
|
89862
|
+
const wallet3 = await completeTradingWallet(ctx, row, appId, "account:read", "solana", {
|
|
89863
|
+
apiKey: key,
|
|
89864
|
+
keyPrefix
|
|
89865
|
+
});
|
|
89866
|
+
const rpc = signing.body;
|
|
89867
|
+
const transaction = rpc?.params?.transaction;
|
|
89868
|
+
if (typeof transaction !== "string" || !["streamer", "creator"].includes(String(signing.leg)))
|
|
89869
|
+
throw new TradingError("INVALID_RESPONSE", "The fee claim has no Solana transaction to sign");
|
|
89870
|
+
const approval = authorizationSignature(wallet3, transaction);
|
|
89871
|
+
if (JSON.stringify(approval.body) !== JSON.stringify(signing.body))
|
|
89872
|
+
throw new TradingError("INVALID_RESPONSE", "The fee claim's RPC body is not the exact Solana sign request");
|
|
89873
|
+
const signed = await request(ctx, key, `/api/v1/agent/wallets/${encodeURIComponent(wallet3.id)}/sign`, {
|
|
89874
|
+
...approval,
|
|
89875
|
+
feeClaim: true
|
|
89876
|
+
});
|
|
89877
|
+
if (typeof signed.signedTransaction !== "string" || signed.encoding !== "base64")
|
|
89878
|
+
throw new TradingError("INVALID_RESPONSE", "The relay returned no base64 signed fee claim");
|
|
89879
|
+
signedClaims.push({ walletId: wallet3.id, leg: signing.leg, signedTransaction: signed.signedTransaction });
|
|
89880
|
+
}
|
|
89881
|
+
const submitted = await request(ctx, key, `/api/v1/agent/fees/${encodeURIComponent(mint)}/claim/submit`, {
|
|
89882
|
+
signedClaims
|
|
89883
|
+
});
|
|
89884
|
+
body = {
|
|
89885
|
+
...submitted,
|
|
89886
|
+
claimed: [
|
|
89887
|
+
...Array.isArray(body.claimed) ? body.claimed : [],
|
|
89888
|
+
...Array.isArray(submitted.claimed) ? submitted.claimed : []
|
|
89889
|
+
],
|
|
89890
|
+
warnings: [
|
|
89891
|
+
...Array.isArray(body.warnings) ? body.warnings : [],
|
|
89892
|
+
...Array.isArray(submitted.warnings) ? submitted.warnings : []
|
|
89893
|
+
]
|
|
89894
|
+
};
|
|
89895
|
+
}
|
|
89616
89896
|
if (ctx.json)
|
|
89617
89897
|
return printTradingResult(ctx, body);
|
|
89618
89898
|
const style = terminalStyle(ctx.deps, "stdout");
|
|
@@ -89689,6 +89969,7 @@ init_render();
|
|
|
89689
89969
|
init_promote_support();
|
|
89690
89970
|
init_key_output();
|
|
89691
89971
|
init_keys();
|
|
89972
|
+
init_keys_embedded_wallet();
|
|
89692
89973
|
init_tee_rebind();
|
|
89693
89974
|
var KEYS_PATH4 = "/api/v1/agent/keys";
|
|
89694
89975
|
var USAGE_ACCESS = `Usage: candle keys access <prefix|label|self> --access <read|write|withdraw> [--yes] [--json]
|
|
@@ -89852,6 +90133,7 @@ async function keysAccess(args, ctx) {
|
|
|
89852
90133
|
let path;
|
|
89853
90134
|
let auth;
|
|
89854
90135
|
let credentials;
|
|
90136
|
+
let embeddedWallet;
|
|
89855
90137
|
if (self) {
|
|
89856
90138
|
const apiKey = await resolveApiKey(deps, ctx.profile);
|
|
89857
90139
|
if (!apiKey) {
|
|
@@ -89873,6 +90155,7 @@ async function keysAccess(args, ctx) {
|
|
|
89873
90155
|
});
|
|
89874
90156
|
if (!resolved.ok)
|
|
89875
90157
|
return resolved.code;
|
|
90158
|
+
embeddedWallet = resolved.embeddedWallet;
|
|
89876
90159
|
path = `${KEYS_PATH4}/${encodeURIComponent(resolved.keyPrefix)}/access`;
|
|
89877
90160
|
auth = "device";
|
|
89878
90161
|
credentials = { deviceToken };
|
|
@@ -89911,6 +90194,9 @@ async function keysAccess(args, ctx) {
|
|
|
89911
90194
|
const account = fields2.account ? shortAddress2(fields2.account) : json ? "unknown" : symbols(terminalStyle(deps, "stderr")).missing;
|
|
89912
90195
|
deps.stderr.write(`${accessScreen(shown, { account: fields2.username ? `${fields2.username} (${account})` : account, apiUrl }, json ? undefined : terminalStyle(deps, "stderr"))}
|
|
89913
90196
|
`);
|
|
90197
|
+
if (shown.to.access === "readwritetransfer" && embeddedWallet === "denied") {
|
|
90198
|
+
notice(deps, "stderr", "warning", withdrawNeedsEmbeddedWalletLine(shown.keyPrefix));
|
|
90199
|
+
}
|
|
89914
90200
|
if (widen) {
|
|
89915
90201
|
const typed = await deps.promptLine(`Type the key prefix ${shown.keyPrefix} to widen it: `);
|
|
89916
90202
|
if (typed.trim() !== shown.keyPrefix) {
|
|
@@ -90754,6 +91040,7 @@ function feeWalletLines(selection, echo) {
|
|
|
90754
91040
|
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)."
|
|
90755
91041
|
];
|
|
90756
91042
|
}
|
|
91043
|
+
var FEE_WALLET_DEFAULT_WARNING = "No --fee-wallet given: this launch's curve and pool fees earn to the account wallet. Pass --fee-wallet <address> to send them to another wallet.";
|
|
90757
91044
|
function feeWalletQuoteLine(selection) {
|
|
90758
91045
|
if (!selection)
|
|
90759
91046
|
return "defaults to the account wallet";
|
|
@@ -91109,7 +91396,8 @@ var LAUNCH_ARG_SPEC = {
|
|
|
91109
91396
|
booleanFlags: [
|
|
91110
91397
|
...launchFlagFields.filter((field) => field.type === "boolean").map((field) => field.cli.flag),
|
|
91111
91398
|
"--check",
|
|
91112
|
-
"--yes"
|
|
91399
|
+
"--yes",
|
|
91400
|
+
"--require-fee-wallet"
|
|
91113
91401
|
],
|
|
91114
91402
|
pathFlags: ["--image", "--banner"]
|
|
91115
91403
|
};
|
|
@@ -91251,6 +91539,10 @@ async function launch(args, ctx) {
|
|
|
91251
91539
|
writeUsageFailure(ctx.deps, `${error.message}. ${USAGE9}`, ctx.json);
|
|
91252
91540
|
return 2;
|
|
91253
91541
|
}
|
|
91542
|
+
if (parsed.booleans.has("--require-fee-wallet") && !feeWallet) {
|
|
91543
|
+
writeUsageFailure(ctx.deps, `--require-fee-wallet needs --fee-wallet <address>: pass the wallet this launch's fees earn to. ${USAGE9}`, ctx.json);
|
|
91544
|
+
return 2;
|
|
91545
|
+
}
|
|
91254
91546
|
try {
|
|
91255
91547
|
if (flags["--buy"]?.trim() && !/^0+(\.0+)?$/.test(flags["--buy"]))
|
|
91256
91548
|
launchBuyRaw(flags["--buy"], 18);
|
|
@@ -91318,6 +91610,8 @@ async function launch(args, ctx) {
|
|
|
91318
91610
|
const chain2 = wallet3.chain === "evm" ? "hood" : "solana";
|
|
91319
91611
|
if (chain2 === "hood" && flags["--dex-version"] === undefined)
|
|
91320
91612
|
throw new TradingUsage(`${wallet3.address} is a Hood TEE wallet, and a Hood launch needs --dex-version v3|v4.`);
|
|
91613
|
+
if (chain2 === "solana" && !dryRun && !feeWallet)
|
|
91614
|
+
notice(ctx.deps, "stderr", "warning", FEE_WALLET_DEFAULT_WARNING);
|
|
91321
91615
|
const buy = launchBuy(flags, chain2);
|
|
91322
91616
|
if (chain2 === "hood" && (Object.keys(poolRequestFields(flags)).length || feeWalletGiven(flags)))
|
|
91323
91617
|
throw new TradingUsage("Pool fee and pool shape choices are Solana only, and so is --fee-wallet; a Hood launch cannot take them.");
|
|
@@ -91767,6 +92061,8 @@ async function embeddedLaunch(ctx, key, args) {
|
|
|
91767
92061
|
firstBuyCeiling(ctx, flags, buy);
|
|
91768
92062
|
if (chain2 === "hood" && (Object.keys(poolRequestFields(flags)).length || feeWalletGiven(flags)))
|
|
91769
92063
|
throw new TradingUsage("Pool fee and pool shape choices are Solana only, and so is --fee-wallet; a Hood launch cannot take them.");
|
|
92064
|
+
if (chain2 === "solana" && !args.dryRun && !feeWalletGiven(flags))
|
|
92065
|
+
notice(ctx.deps, "stderr", "warning", FEE_WALLET_DEFAULT_WARNING);
|
|
91770
92066
|
const wallet3 = await embeddedLaunchWallet(ctx, key, chain2);
|
|
91771
92067
|
const uploaded = await uploadLaunchImages(ctx, key, args);
|
|
91772
92068
|
if (typeof uploaded === "number")
|
|
@@ -92023,6 +92319,92 @@ async function resumeHoodLaunch(ctx, key, id, operationId, prior) {
|
|
|
92023
92319
|
return printTradingResult(ctx, { ...result, clientTradeId: id, kind: "launch", chain: "hood", operationId });
|
|
92024
92320
|
}
|
|
92025
92321
|
|
|
92322
|
+
// src/commands/launch-status.ts
|
|
92323
|
+
init_args();
|
|
92324
|
+
init_presentation();
|
|
92325
|
+
init_render();
|
|
92326
|
+
init_trading();
|
|
92327
|
+
init_swap();
|
|
92328
|
+
var USAGE10 = "Usage: candle launch status <mint-or-client-launch-id> [--wait]";
|
|
92329
|
+
var LAUNCH_WAIT_MS = 10 * 60 * 1000;
|
|
92330
|
+
var LAUNCH_WAIT_POLL_MS = 1e4;
|
|
92331
|
+
var LIVE = new Set(["trading", "bonded", "migrated"]);
|
|
92332
|
+
function statusOf(body) {
|
|
92333
|
+
const status = body.status;
|
|
92334
|
+
return status !== null && typeof status === "object" && !Array.isArray(status) ? status : null;
|
|
92335
|
+
}
|
|
92336
|
+
function isFinal(status) {
|
|
92337
|
+
if (!status)
|
|
92338
|
+
return false;
|
|
92339
|
+
if (status.failed === true)
|
|
92340
|
+
return true;
|
|
92341
|
+
return typeof status.state === "string" && LIVE.has(status.state);
|
|
92342
|
+
}
|
|
92343
|
+
function humanLines(status) {
|
|
92344
|
+
const lines = [`state: ${safeText(String(status.state ?? ""))}`];
|
|
92345
|
+
if (status.failed === true)
|
|
92346
|
+
lines.push("failed: true");
|
|
92347
|
+
if (typeof status.errorCode === "string")
|
|
92348
|
+
lines.push(`error: ${safeText(status.errorCode)}`);
|
|
92349
|
+
if (typeof status.chain === "string")
|
|
92350
|
+
lines.push(`chain: ${safeText(status.chain)}`);
|
|
92351
|
+
if (typeof status.mint === "string")
|
|
92352
|
+
lines.push(`mint: ${safeText(status.mint)}`);
|
|
92353
|
+
if (typeof status.clientLaunchId === "string")
|
|
92354
|
+
lines.push(`clientLaunchId: ${safeText(status.clientLaunchId)}`);
|
|
92355
|
+
if (typeof status.lifecycle === "string")
|
|
92356
|
+
lines.push(`lifecycle: ${safeText(status.lifecycle)}`);
|
|
92357
|
+
const migration = status.migration;
|
|
92358
|
+
if (migration !== null && typeof migration === "object" && !Array.isArray(migration)) {
|
|
92359
|
+
const block2 = migration;
|
|
92360
|
+
const progress = typeof block2.progressBps === "number" ? ` ${String(block2.progressBps)} bps` : "";
|
|
92361
|
+
if (typeof block2.status === "string")
|
|
92362
|
+
lines.push(`migration: ${safeText(block2.status)}${progress}`);
|
|
92363
|
+
}
|
|
92364
|
+
if (typeof status.signature === "string")
|
|
92365
|
+
lines.push(`signature: ${safeText(status.signature)}`);
|
|
92366
|
+
return lines;
|
|
92367
|
+
}
|
|
92368
|
+
async function readStatus(ctx, key, id) {
|
|
92369
|
+
return request(ctx, key, `/api/v1/launch/status/${encodeURIComponent(id)}`);
|
|
92370
|
+
}
|
|
92371
|
+
async function launchStatus(args, ctx) {
|
|
92372
|
+
const parsed = parseArgs(args, { booleanFlags: ["--wait"] });
|
|
92373
|
+
if ("error" in parsed || parsed.positionals.length !== 1 || !validClientId(parsed.positionals[0] ?? "")) {
|
|
92374
|
+
writeUsageFailure(ctx.deps, USAGE10, ctx.json);
|
|
92375
|
+
return 2;
|
|
92376
|
+
}
|
|
92377
|
+
const id = parsed.positionals[0];
|
|
92378
|
+
const wait = parsed.booleans.has("--wait");
|
|
92379
|
+
try {
|
|
92380
|
+
const key = await tradingKey(ctx);
|
|
92381
|
+
let body = await readStatus(ctx, key, id);
|
|
92382
|
+
if (wait) {
|
|
92383
|
+
const deadline = ctx.deps.now() + LAUNCH_WAIT_MS;
|
|
92384
|
+
while (!isFinal(statusOf(body)) && ctx.deps.now() < deadline) {
|
|
92385
|
+
await ctx.deps.sleep(LAUNCH_WAIT_POLL_MS);
|
|
92386
|
+
body = await readStatus(ctx, key, id);
|
|
92387
|
+
}
|
|
92388
|
+
const final = isFinal(statusOf(body));
|
|
92389
|
+
if (!final) {
|
|
92390
|
+
notice(ctx.deps, "stderr", "note", `Still not trading after ten minutes. Re-check with candle launch status ${safeText(id)} --wait.`);
|
|
92391
|
+
}
|
|
92392
|
+
body = { ...body, waited: { final } };
|
|
92393
|
+
}
|
|
92394
|
+
if (ctx.json)
|
|
92395
|
+
return printTradingResult(ctx, body);
|
|
92396
|
+
const status = statusOf(body);
|
|
92397
|
+
if (!status)
|
|
92398
|
+
return printTradingResult(ctx, body);
|
|
92399
|
+
ctx.deps.stdout.write(`${humanLines(status).join(`
|
|
92400
|
+
`)}
|
|
92401
|
+
`);
|
|
92402
|
+
return 0;
|
|
92403
|
+
} catch (error) {
|
|
92404
|
+
return tradingFailure(ctx, error);
|
|
92405
|
+
}
|
|
92406
|
+
}
|
|
92407
|
+
|
|
92026
92408
|
// src/commands/lp.ts
|
|
92027
92409
|
init_args();
|
|
92028
92410
|
init_client();
|
|
@@ -97883,7 +98265,7 @@ init_swap();
|
|
|
97883
98265
|
init_vault_support();
|
|
97884
98266
|
var WITHDRAW_SCOPE = "perps:withdraw";
|
|
97885
98267
|
var ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
97886
|
-
var
|
|
98268
|
+
var USAGE11 = "Usage: candle perps withdraw <amount> --to solana|hood [--wallet <evm tee>] [--id <id>] [--keystore <path>] | candle perps withdraw status <id>";
|
|
97887
98269
|
var WITHDRAW_POLL_MS = 10 * 60 * 1000;
|
|
97888
98270
|
var WITHDRAW_POLL_INTERVAL_MS = 5000;
|
|
97889
98271
|
var TERMINAL = new Set(["settled", "refunded", "failed"]);
|
|
@@ -97934,7 +98316,7 @@ async function withdrawStatus(ctx, id) {
|
|
|
97934
98316
|
async function perpsWithdraw(args, ctx) {
|
|
97935
98317
|
if (args[0] === "status") {
|
|
97936
98318
|
if (args.length !== 2 || !ID_RE.test(args[1]))
|
|
97937
|
-
return usage10(ctx,
|
|
98319
|
+
return usage10(ctx, USAGE11);
|
|
97938
98320
|
try {
|
|
97939
98321
|
return await withdrawStatus(ctx, args[1]);
|
|
97940
98322
|
} catch (error) {
|
|
@@ -97953,7 +98335,7 @@ async function perpsWithdraw(args, ctx) {
|
|
|
97953
98335
|
const [amount] = parsed.positionals;
|
|
97954
98336
|
const to = parsed.values["--to"];
|
|
97955
98337
|
if (parsed.positionals.length !== 1 || !amount || to !== "solana" && to !== "hood")
|
|
97956
|
-
return usage10(ctx,
|
|
98338
|
+
return usage10(ctx, USAGE11);
|
|
97957
98339
|
const amountMicros = parseWithdrawAmount(amount);
|
|
97958
98340
|
if (amountMicros === null)
|
|
97959
98341
|
return usage10(ctx, "The amount must be a positive USDC decimal with at most 6 decimals.");
|
|
@@ -99841,12 +100223,12 @@ function planPromotionLine2(table) {
|
|
|
99841
100223
|
init_presentation();
|
|
99842
100224
|
init_render();
|
|
99843
100225
|
init_key_output();
|
|
99844
|
-
var
|
|
100226
|
+
var USAGE12 = "Usage: candle plans";
|
|
99845
100227
|
async function plans(args, ctx) {
|
|
99846
100228
|
const parsed = parseArgs(args, {});
|
|
99847
100229
|
if ("error" in parsed || parsed.positionals.length > 0) {
|
|
99848
100230
|
writeScreenUsageFailure(ctx.deps, "error" in parsed ? `${parsed.error}
|
|
99849
|
-
${
|
|
100231
|
+
${USAGE12}` : USAGE12, ctx.json);
|
|
99850
100232
|
return 2;
|
|
99851
100233
|
}
|
|
99852
100234
|
const result = await apiRequest("/api/v1/agent/plans", {
|
|
@@ -102120,7 +102502,7 @@ init_client();
|
|
|
102120
102502
|
init_human_output();
|
|
102121
102503
|
init_presentation();
|
|
102122
102504
|
init_render();
|
|
102123
|
-
var
|
|
102505
|
+
var USAGE13 = "Usage: candle scan <contract address> [--chain solana|hood] [--json]";
|
|
102124
102506
|
var OTHER_CHAIN = "Candle trades Solana and Hood today; that is not a Solana or Hood contract address.";
|
|
102125
102507
|
var HOOD_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
102126
102508
|
var SOLANA_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
@@ -102220,20 +102602,20 @@ async function scan(args, ctx) {
|
|
|
102220
102602
|
}
|
|
102221
102603
|
const named = parsed.values["--chain"];
|
|
102222
102604
|
if (parsed.positionals.length !== 1 || named !== undefined && named !== "solana" && named !== "hood") {
|
|
102223
|
-
writeUsageFailure(ctx.deps,
|
|
102605
|
+
writeUsageFailure(ctx.deps, USAGE13, ctx.json);
|
|
102224
102606
|
return 2;
|
|
102225
102607
|
}
|
|
102226
102608
|
const address = parsed.positionals[0].trim();
|
|
102227
102609
|
const found = chainOfAddress(address);
|
|
102228
102610
|
if (found === null) {
|
|
102229
102611
|
writeUsageFailure(ctx.deps, `${OTHER_CHAIN}
|
|
102230
|
-
${
|
|
102612
|
+
${USAGE13}`, ctx.json);
|
|
102231
102613
|
return 2;
|
|
102232
102614
|
}
|
|
102233
102615
|
if (named !== undefined && named !== found) {
|
|
102234
102616
|
const label = (c) => c === "hood" ? "Hood" : "Solana";
|
|
102235
102617
|
writeUsageFailure(ctx.deps, `That address is a ${label(found)} contract, not a ${label(named)} one. Candle trades Solana and Hood today.
|
|
102236
|
-
${
|
|
102618
|
+
${USAGE13}`, ctx.json);
|
|
102237
102619
|
return 2;
|
|
102238
102620
|
}
|
|
102239
102621
|
const chain2 = found;
|
|
@@ -104111,7 +104493,7 @@ init_deps();
|
|
|
104111
104493
|
init_human_output();
|
|
104112
104494
|
init_render();
|
|
104113
104495
|
var KINDS = ["trade", "signal", "callout"];
|
|
104114
|
-
var
|
|
104496
|
+
var USAGE14 = "Usage: candle token events <mint> [--chain solana|hood] [--kind trade|signal|callout] [--since <iso-8601>] [--limit <n>] [--before <cursor>] [--json]";
|
|
104115
104497
|
function readEventsBody(body) {
|
|
104116
104498
|
if (body === null || typeof body !== "object" || !Array.isArray(body.events))
|
|
104117
104499
|
return null;
|
|
@@ -104169,11 +104551,11 @@ async function tokenEvents(args, ctx) {
|
|
|
104169
104551
|
const since = parsed.values["--since"];
|
|
104170
104552
|
const before = parsed.values["--before"];
|
|
104171
104553
|
if (parsed.positionals.length !== 1 || chain2 !== "solana" && chain2 !== "hood") {
|
|
104172
|
-
writeUsageFailure(ctx.deps,
|
|
104554
|
+
writeUsageFailure(ctx.deps, USAGE14, ctx.json);
|
|
104173
104555
|
return 2;
|
|
104174
104556
|
}
|
|
104175
104557
|
if (kind !== undefined && !KINDS.includes(kind)) {
|
|
104176
|
-
writeUsageFailure(ctx.deps,
|
|
104558
|
+
writeUsageFailure(ctx.deps, USAGE14, ctx.json);
|
|
104177
104559
|
return 2;
|
|
104178
104560
|
}
|
|
104179
104561
|
if (limit !== undefined && !/^[1-9][0-9]{0,3}$/.test(limit) || Number(limit) > 1000) {
|
|
@@ -104270,7 +104652,7 @@ var TOKEN_PAGE_BODY_CAP_BYTES = 32 * 1024;
|
|
|
104270
104652
|
var TOKEN_PAGE_SECTIONS = ["thesis", "tokenomics", "lore"];
|
|
104271
104653
|
var FLAG_REASONS = ["spam", "scam", "abuse", "misleading", "unsafe_link", "other"];
|
|
104272
104654
|
var FLAG_DETAIL_LIMIT = 500;
|
|
104273
|
-
var
|
|
104655
|
+
var USAGE15 = {
|
|
104274
104656
|
about: "Usage: candle token about <address> [--chain solana|hood] [--capabilities]",
|
|
104275
104657
|
claim: "Usage: candle token claim <address> [--chain solana|hood] [--creator [--challenge [--message-out <path>] | --challenge-id <id> --signature <sig>]] [--idempotency-key <key>]",
|
|
104276
104658
|
section: `Usage: candle token section set <address> <thesis|tokenomics|lore> --file <path> --base-revision <n> [--clear] [--chain solana|hood] [--idempotency-key <key>]
|
|
@@ -104530,7 +104912,7 @@ function terminalTextKeepingLines(text4) {
|
|
|
104530
104912
|
`);
|
|
104531
104913
|
}
|
|
104532
104914
|
async function tokenAbout(args, ctx) {
|
|
104533
|
-
const usage12 = usageFor(ctx,
|
|
104915
|
+
const usage12 = usageFor(ctx, USAGE15.about);
|
|
104534
104916
|
const parsed = parseArgs(args, { valueFlags: ["--chain"], booleanFlags: ["--capabilities"] });
|
|
104535
104917
|
if ("error" in parsed)
|
|
104536
104918
|
return usage12(parsed.error);
|
|
@@ -104576,7 +104958,7 @@ async function tokenAbout(args, ctx) {
|
|
|
104576
104958
|
return 0;
|
|
104577
104959
|
}
|
|
104578
104960
|
async function tokenClaim(args, ctx) {
|
|
104579
|
-
const usage12 = usageFor(ctx,
|
|
104961
|
+
const usage12 = usageFor(ctx, USAGE15.claim);
|
|
104580
104962
|
const parsed = parseArgs(args, {
|
|
104581
104963
|
valueFlags: ["--chain", "--challenge-id", "--signature", "--message-out", "--idempotency-key"],
|
|
104582
104964
|
booleanFlags: ["--creator", "--challenge"],
|
|
@@ -104703,7 +105085,7 @@ var SECTION_FLAGS = {
|
|
|
104703
105085
|
restore: { valueFlags: ["--chain", "--version-id", "--base-revision", "--idempotency-key"], booleanFlags: [] }
|
|
104704
105086
|
};
|
|
104705
105087
|
async function tokenSection(args, ctx) {
|
|
104706
|
-
const usage12 = usageFor(ctx,
|
|
105088
|
+
const usage12 = usageFor(ctx, USAGE15.section);
|
|
104707
105089
|
const action2 = args[0];
|
|
104708
105090
|
if (action2 !== "set" && action2 !== "restore")
|
|
104709
105091
|
return usage12();
|
|
@@ -104796,7 +105178,7 @@ async function tokenSection(args, ctx) {
|
|
|
104796
105178
|
return 0;
|
|
104797
105179
|
}
|
|
104798
105180
|
async function tokenHistory(args, ctx) {
|
|
104799
|
-
const usage12 = usageFor(ctx,
|
|
105181
|
+
const usage12 = usageFor(ctx, USAGE15.history);
|
|
104800
105182
|
const parsed = parseArgs(args, { valueFlags: ["--chain", "--limit", "--cursor"] });
|
|
104801
105183
|
if ("error" in parsed)
|
|
104802
105184
|
return usage12(parsed.error);
|
|
@@ -104851,7 +105233,7 @@ More: --cursor ${data.nextCursor}
|
|
|
104851
105233
|
return 0;
|
|
104852
105234
|
}
|
|
104853
105235
|
async function tokenFlag(args, ctx) {
|
|
104854
|
-
const usage12 = usageFor(ctx,
|
|
105236
|
+
const usage12 = usageFor(ctx, USAGE15.flag);
|
|
104855
105237
|
const parsed = parseArgs(args, { valueFlags: ["--chain", "--content", "--reason", "--detail", "--idempotency-key"] });
|
|
104856
105238
|
if ("error" in parsed)
|
|
104857
105239
|
return usage12(parsed.error);
|
|
@@ -104946,7 +105328,7 @@ async function tokenUpdate(args, ctx) {
|
|
|
104946
105328
|
return 0;
|
|
104947
105329
|
}
|
|
104948
105330
|
async function tokenComment(args, ctx) {
|
|
104949
|
-
const usage12 = usageFor(ctx,
|
|
105331
|
+
const usage12 = usageFor(ctx, USAGE15.comment);
|
|
104950
105332
|
const parsed = parseArgs(args, { valueFlags: ["--chain", "--file", "--idempotency-key"], pathFlags: ["--file"] });
|
|
104951
105333
|
if ("error" in parsed)
|
|
104952
105334
|
return usage12(parsed.error);
|
|
@@ -104991,7 +105373,7 @@ async function tokenComment(args, ctx) {
|
|
|
104991
105373
|
return 0;
|
|
104992
105374
|
}
|
|
104993
105375
|
async function tokenComments(args, ctx) {
|
|
104994
|
-
const usage12 = usageFor(ctx,
|
|
105376
|
+
const usage12 = usageFor(ctx, USAGE15.comments);
|
|
104995
105377
|
const parsed = parseArgs(args, { valueFlags: ["--chain", "--limit", "--cursor"] });
|
|
104996
105378
|
if ("error" in parsed)
|
|
104997
105379
|
return usage12(parsed.error);
|
|
@@ -105337,7 +105719,7 @@ init_render();
|
|
|
105337
105719
|
init_solana_verify();
|
|
105338
105720
|
init_trading();
|
|
105339
105721
|
init_swap();
|
|
105340
|
-
var
|
|
105722
|
+
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
105723
|
var TRANSFER_ASSETS = [...Object.keys(BASES), ...Object.keys(HOOD_BASES)];
|
|
105342
105724
|
var BASE58_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
105343
105725
|
async function readLinkedWallets(ctx, key) {
|
|
@@ -105357,6 +105739,12 @@ async function readLinkedWallets(ctx, key) {
|
|
|
105357
105739
|
}
|
|
105358
105740
|
return rows;
|
|
105359
105741
|
}
|
|
105742
|
+
async function readWithdrawalAddresses(ctx, key) {
|
|
105743
|
+
const body = await request(ctx, key, "/api/v1/agent/withdrawal-addresses");
|
|
105744
|
+
if (!Array.isArray(body.addresses))
|
|
105745
|
+
throw new TradingError("INVALID_RESPONSE", "The withdrawal allowlist read did not return a list.");
|
|
105746
|
+
return body.addresses;
|
|
105747
|
+
}
|
|
105360
105748
|
async function resolveDestination(ctx, key, source, to, chain2 = "solana") {
|
|
105361
105749
|
const hood = chain2 === "hood";
|
|
105362
105750
|
const same = (a, b) => hood ? sameEvmAddress(a, b) : a === b;
|
|
@@ -105382,13 +105770,35 @@ async function resolveDestination(ctx, key, source, to, chain2 = "solana") {
|
|
|
105382
105770
|
return { kind: "linked", address: match.address, id: match._id, ...match.label ? { label: match.label } : {} };
|
|
105383
105771
|
if (same(to, source.address))
|
|
105384
105772
|
throw new TradingError("DESTINATION_IS_SOURCE", "--to names the wallet the funds would leave. Name a different destination.");
|
|
105773
|
+
const allowlist = (await readWithdrawalAddresses(ctx, key)).filter((row) => row.chain === chain2 && row.revokedAt === undefined && !same(row.address, source.address));
|
|
105774
|
+
const approved = allowlist.filter((row) => row.label === to || row.id === to || same(row.address, to));
|
|
105775
|
+
if (approved.length > 1)
|
|
105776
|
+
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.`);
|
|
105777
|
+
const listed = approved[0];
|
|
105778
|
+
if (listed) {
|
|
105779
|
+
const address = hood ? checkEvmAddress(listed.address) : undefined;
|
|
105780
|
+
if (address !== undefined && !address.ok)
|
|
105781
|
+
throw new TradingError("INVALID_RESPONSE", "Candle listed an approved Hood withdrawal address that is not an EVM address; nothing was built.");
|
|
105782
|
+
return {
|
|
105783
|
+
kind: "approved",
|
|
105784
|
+
address: address?.ok ? address.address : listed.address,
|
|
105785
|
+
id: listed.id,
|
|
105786
|
+
...listed.label ? { label: listed.label } : {}
|
|
105787
|
+
};
|
|
105788
|
+
}
|
|
105385
105789
|
if (!hood && BASE58_ADDRESS.test(to))
|
|
105386
105790
|
return { kind: "address", address: to };
|
|
105387
105791
|
const evm = hood ? checkEvmAddress(to) : undefined;
|
|
105388
105792
|
if (evm?.ok)
|
|
105389
105793
|
return { kind: "address", address: evm.address };
|
|
105390
105794
|
const name2 = hood ? "Hood" : "Solana";
|
|
105391
|
-
|
|
105795
|
+
const known = [
|
|
105796
|
+
...rows.length > 0 ? [`Linked wallets: ${rows.map((row) => `${row.label ?? ""} (${row._id}, ${row.address})`.trim()).join("; ")}.`] : [],
|
|
105797
|
+
...allowlist.length > 0 ? [
|
|
105798
|
+
`Approved withdrawal addresses: ${allowlist.map((row) => `${row.label ?? ""} (${row.id}, ${row.address})`.trim()).join("; ")}.`
|
|
105799
|
+
] : []
|
|
105800
|
+
];
|
|
105801
|
+
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
105802
|
}
|
|
105393
105803
|
function describeDestination(destination) {
|
|
105394
105804
|
switch (destination.kind) {
|
|
@@ -105396,10 +105806,31 @@ function describeDestination(destination) {
|
|
|
105396
105806
|
return `this wallet's pinned vault ${destination.address}`;
|
|
105397
105807
|
case "linked":
|
|
105398
105808
|
return `linked wallet ${destination.label ? `${destination.label} ` : ""}(${destination.id}, ${destination.address})`;
|
|
105809
|
+
case "approved":
|
|
105810
|
+
return `approved withdrawal address ${destination.label ? `${destination.label} ` : ""}(${destination.id}, ${destination.address})`;
|
|
105399
105811
|
case "address":
|
|
105400
|
-
return `address ${destination.address} (not one of this account's linked wallets; Candle decides whether it is allowed)`;
|
|
105812
|
+
return `address ${destination.address} (not one of this account's linked wallets or approved withdrawal addresses; Candle decides whether it is allowed)`;
|
|
105401
105813
|
}
|
|
105402
105814
|
}
|
|
105815
|
+
function destinationNote(destination, hood) {
|
|
105816
|
+
switch (destination.kind) {
|
|
105817
|
+
case "vault":
|
|
105818
|
+
return "Destination: this wallet's own vault, pinned when it was imported.";
|
|
105819
|
+
case "linked":
|
|
105820
|
+
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.`;
|
|
105821
|
+
case "approved":
|
|
105822
|
+
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.`;
|
|
105823
|
+
case "address":
|
|
105824
|
+
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.";
|
|
105825
|
+
}
|
|
105826
|
+
}
|
|
105827
|
+
function builtDestination(built) {
|
|
105828
|
+
if (built.destinationClass === "approved")
|
|
105829
|
+
return [["To", "an approved withdrawal address"]];
|
|
105830
|
+
if (typeof built.destinationKind === "string")
|
|
105831
|
+
return [["To", built.destinationKind === "vault" ? "the vault" : "a linked wallet"]];
|
|
105832
|
+
return [];
|
|
105833
|
+
}
|
|
105403
105834
|
function writePreview(ctx, title2, rows, note) {
|
|
105404
105835
|
const style = terminalStyle(ctx.deps, "stderr");
|
|
105405
105836
|
ctx.deps.stderr.write(`${heading(title2, style)}
|
|
@@ -105433,7 +105864,7 @@ async function transfer(args, ctx) {
|
|
|
105433
105864
|
const flags2 = parsed.values;
|
|
105434
105865
|
const asset = flags2["--asset"]?.toUpperCase();
|
|
105435
105866
|
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,
|
|
105867
|
+
writeUsageFailure(ctx.deps, USAGE16, ctx.json);
|
|
105437
105868
|
return 2;
|
|
105438
105869
|
}
|
|
105439
105870
|
const to = flags2["--to"];
|
|
@@ -105470,7 +105901,7 @@ async function transfer(args, ctx) {
|
|
|
105470
105901
|
const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags2["--amount"]} ${label}`;
|
|
105471
105902
|
const confirmed = await confirmTransfer(ctx, [
|
|
105472
105903
|
`Transfer ${amountText2} from ${wallet3.address} (${wallet3.id}) to ${describeDestination(destination)}`,
|
|
105473
|
-
destination
|
|
105904
|
+
destinationNote(destination, false)
|
|
105474
105905
|
], parsed.booleans.has("--yes"));
|
|
105475
105906
|
if (!confirmed)
|
|
105476
105907
|
return printTradingResult(ctx, { success: true, status: "cancelled", walletId: wallet3.id, destination });
|
|
@@ -105491,11 +105922,7 @@ async function transfer(args, ctx) {
|
|
|
105491
105922
|
if (builtAmount === null || !isMax && builtAmount !== amountRaw)
|
|
105492
105923
|
throw new TradingError("INVALID_RESPONSE", "The transfer build does not name the confirmed amount; nothing was signed.");
|
|
105493
105924
|
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
|
-
]);
|
|
105925
|
+
writePreview(ctx, "Built", [["Transfer", transferId], ["Amount", amountShown], ...builtDestination(built)]);
|
|
105499
105926
|
const reader = await solana();
|
|
105500
105927
|
const problem = await tradingRead(ctx, reader, () => solanaSpendProblem(unsigned[0], wallet3.address, reader.rpc, {
|
|
105501
105928
|
kind: "transfer",
|
|
@@ -105557,7 +105984,7 @@ async function hoodTransfer(ctx, args) {
|
|
|
105557
105984
|
const amountText2 = isMax ? `the full spendable balance of ${label}` : `${flags2["--amount"]} ${label}`;
|
|
105558
105985
|
const confirmed = await confirmTransfer(ctx, [
|
|
105559
105986
|
`Transfer ${amountText2} on Hood from ${wallet3.address} (${wallet3.id}) to ${describeDestination(destination)}`,
|
|
105560
|
-
destination
|
|
105987
|
+
destinationNote(destination, true),
|
|
105561
105988
|
...asset === "ETH" ? [
|
|
105562
105989
|
"ETH keeps this send's gas and the sweep-home gas reserve in the wallet; Candle refuses an amount that would spend them."
|
|
105563
105990
|
] : []
|
|
@@ -105592,7 +106019,7 @@ async function hoodTransfer(ctx, args) {
|
|
|
105592
106019
|
writePreview(ctx, "Built", [
|
|
105593
106020
|
["Transfer", safeText(transferId)],
|
|
105594
106021
|
["Amount", `${decimalAmount(amountRaw, decimals)} ${safeText(label)}`],
|
|
105595
|
-
...
|
|
106022
|
+
...builtDestination(built),
|
|
105596
106023
|
[
|
|
105597
106024
|
"Gas",
|
|
105598
106025
|
`up to ${formatUnits(BigInt(leg.gas) * maxFee, 18)} ETH (gas ${leg.gas} at ${formatUnits(maxFee, 9)} gwei)`
|
|
@@ -107037,8 +107464,332 @@ init_reconcile_grant();
|
|
|
107037
107464
|
init_tee_resolve();
|
|
107038
107465
|
init_tee();
|
|
107039
107466
|
init_tee_evm();
|
|
107467
|
+
|
|
107468
|
+
// src/commands/vault-demote-batch.ts
|
|
107469
|
+
init_args();
|
|
107470
|
+
init_human_output();
|
|
107471
|
+
init_presentation();
|
|
107472
|
+
init_solana_endpoint();
|
|
107473
|
+
init_errors2();
|
|
107474
|
+
init_promote_support();
|
|
107475
|
+
init_store();
|
|
107476
|
+
init_tee();
|
|
107477
|
+
init_vault_support();
|
|
107478
|
+
var BATCH_USAGE = "Usage: candle vault demote <tee-address> <tee-address>... | --all --empty [--rpc-url <url>] [--emergency] [--sweep-to <label>] [--yes]";
|
|
107479
|
+
var VALUE_FLAGS = new Set(["--rpc-url", "--keystore", "-k", "--sweep-to", "--from-block", "--token"]);
|
|
107480
|
+
var BATCH_FLAGS = new Set(["--all", "--empty", "--yes"]);
|
|
107481
|
+
var SINGLE_ONLY_FLAGS = ["--token", "--from-block", "--accept-unknown-exposure"];
|
|
107482
|
+
function isBatchDemote(args) {
|
|
107483
|
+
let addresses = 0;
|
|
107484
|
+
for (let i = 0;i < args.length; i++) {
|
|
107485
|
+
const arg = args[i];
|
|
107486
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
107487
|
+
i += 1;
|
|
107488
|
+
continue;
|
|
107489
|
+
}
|
|
107490
|
+
if (BATCH_FLAGS.has(arg))
|
|
107491
|
+
return true;
|
|
107492
|
+
if (!arg.startsWith("-"))
|
|
107493
|
+
addresses += 1;
|
|
107494
|
+
}
|
|
107495
|
+
return addresses > 1;
|
|
107496
|
+
}
|
|
107497
|
+
var isEvmAddress = (value) => /^0x[0-9a-fA-F]{40}$/.test(value);
|
|
107498
|
+
var sameAddress5 = (chain2, a, b) => chain2 === "evm" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
107499
|
+
async function vaultDemoteBatch(args, ctx) {
|
|
107500
|
+
const single = SINGLE_ONLY_FLAGS.find((flag) => args.includes(flag));
|
|
107501
|
+
if (single !== undefined) {
|
|
107502
|
+
return usage(ctx, `${single} names one Hood wallet's sweep; pass it to a single demote: candle vault demote <0x address> ${single} ...`);
|
|
107503
|
+
}
|
|
107504
|
+
const parsed = parseArgs(args, {
|
|
107505
|
+
valueFlags: ["--rpc-url", "--sweep-to", "--keystore"],
|
|
107506
|
+
booleanFlags: ["--emergency", "--accept-older-copy", "--all", "--empty", "--yes"],
|
|
107507
|
+
pathFlags: ["--keystore"]
|
|
107508
|
+
});
|
|
107509
|
+
if ("error" in parsed)
|
|
107510
|
+
return usage(ctx, parsed.error);
|
|
107511
|
+
const all = parsed.booleans.has("--all");
|
|
107512
|
+
const empty = parsed.booleans.has("--empty");
|
|
107513
|
+
const yes = parsed.booleans.has("--yes");
|
|
107514
|
+
if (all && parsed.positionals.length > 0)
|
|
107515
|
+
return usage(ctx, `--all takes no addresses. ${BATCH_USAGE}`);
|
|
107516
|
+
if (all && !empty) {
|
|
107517
|
+
return usage(ctx, `--all needs a filter: --empty (wallets with nothing left to sweep). ${BATCH_USAGE}`);
|
|
107518
|
+
}
|
|
107519
|
+
if (empty && !all)
|
|
107520
|
+
return usage(ctx, `--empty filters --all. ${BATCH_USAGE}`);
|
|
107521
|
+
if (!all && parsed.positionals.length === 0)
|
|
107522
|
+
return usage(ctx, BATCH_USAGE);
|
|
107523
|
+
const rpcUrl = parsed.values["--rpc-url"];
|
|
107524
|
+
const sweepTo = parsed.values["--sweep-to"];
|
|
107525
|
+
const named = parsed.positionals;
|
|
107526
|
+
if (rpcUrl !== undefined && named.some(isEvmAddress) && named.some((a) => !isEvmAddress(a))) {
|
|
107527
|
+
return usage(ctx, "--rpc-url names one chain's node; demote Solana and Hood wallets in separate batches.");
|
|
107528
|
+
}
|
|
107529
|
+
if (!refuseEnvPassphrase(ctx))
|
|
107530
|
+
return 1;
|
|
107531
|
+
if (!requireTty(ctx, "vault demote"))
|
|
107532
|
+
return 1;
|
|
107533
|
+
const resolvedVault = vaultPathFor(ctx, parsed);
|
|
107534
|
+
if ("error" in resolvedVault)
|
|
107535
|
+
return usage(ctx, resolvedVault.error);
|
|
107536
|
+
let solana;
|
|
107537
|
+
if (empty) {
|
|
107538
|
+
const opened = await openSolanaClient(ctx, rpcUrl);
|
|
107539
|
+
if ("error" in opened)
|
|
107540
|
+
return usage(ctx, opened.error);
|
|
107541
|
+
solana = opened;
|
|
107542
|
+
}
|
|
107543
|
+
return runVaultCommand(ctx, async ({ hold }) => {
|
|
107544
|
+
const raw = await requireVaultRaw(ctx, resolvedVault);
|
|
107545
|
+
const opened = await unlockInteractively(ctx, resolvedVault.path, raw, {
|
|
107546
|
+
acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
|
|
107547
|
+
});
|
|
107548
|
+
const held = hold(opened.vault);
|
|
107549
|
+
const outcomes = [];
|
|
107550
|
+
const planned = [];
|
|
107551
|
+
const plan2 = (entry) => {
|
|
107552
|
+
const chain2 = entry.chain === "evm" ? "evm" : "solana";
|
|
107553
|
+
const row = { address: entry.address, label: entry.label, chain: chain2, destination: null };
|
|
107554
|
+
const pinned = entry.tee?.vaultDestination;
|
|
107555
|
+
if (pinned !== undefined)
|
|
107556
|
+
row.destination = pinned;
|
|
107557
|
+
else if (sweepTo === undefined)
|
|
107558
|
+
row.destinationNote = "no pinned destination; pass --sweep-to";
|
|
107559
|
+
else {
|
|
107560
|
+
try {
|
|
107561
|
+
row.destination = assertColdVaultDestination(held.index, sweepTo, {
|
|
107562
|
+
chain: chain2,
|
|
107563
|
+
subjectAddress: entry.address
|
|
107564
|
+
}).address;
|
|
107565
|
+
} catch (error) {
|
|
107566
|
+
if (!isVaultError(error))
|
|
107567
|
+
throw error;
|
|
107568
|
+
row.destinationNote = `--sweep-to ${sweepTo}: ${error.message}`;
|
|
107569
|
+
}
|
|
107570
|
+
}
|
|
107571
|
+
planned.push(row);
|
|
107572
|
+
};
|
|
107573
|
+
const tees = held.index.entries.filter((entry) => entry.role === "tee-wallet");
|
|
107574
|
+
if (all) {
|
|
107575
|
+
for (const entry of tees) {
|
|
107576
|
+
if (entry.tee?.lifecycle === "retired")
|
|
107577
|
+
continue;
|
|
107578
|
+
const base2 = {
|
|
107579
|
+
address: entry.address,
|
|
107580
|
+
label: entry.label,
|
|
107581
|
+
chain: entry.chain === "evm" ? "evm" : "solana"
|
|
107582
|
+
};
|
|
107583
|
+
if (entry.chain === "evm") {
|
|
107584
|
+
outcomes.push({
|
|
107585
|
+
...base2,
|
|
107586
|
+
status: "skipped",
|
|
107587
|
+
reason: "--empty reads Solana balances only; demote a Hood wallet by its address"
|
|
107588
|
+
});
|
|
107589
|
+
continue;
|
|
107590
|
+
}
|
|
107591
|
+
if (!await nothingLeftToSweep(solana, entry)) {
|
|
107592
|
+
outcomes.push({
|
|
107593
|
+
...base2,
|
|
107594
|
+
status: "skipped",
|
|
107595
|
+
reason: "not empty: it holds SOL or token accounts, or an earlier sweep is unsettled"
|
|
107596
|
+
});
|
|
107597
|
+
continue;
|
|
107598
|
+
}
|
|
107599
|
+
plan2(entry);
|
|
107600
|
+
}
|
|
107601
|
+
} else {
|
|
107602
|
+
const seen = [];
|
|
107603
|
+
for (const address of named) {
|
|
107604
|
+
const chain2 = isEvmAddress(address) ? "evm" : "solana";
|
|
107605
|
+
if (seen.some((other) => sameAddress5(chain2, other, address))) {
|
|
107606
|
+
outcomes.push({ address, label: null, chain: chain2, status: "skipped", reason: "listed more than once" });
|
|
107607
|
+
continue;
|
|
107608
|
+
}
|
|
107609
|
+
seen.push(address);
|
|
107610
|
+
const entry = tees.find((candidate) => sameAddress5(chain2, candidate.address, address));
|
|
107611
|
+
if (entry === undefined) {
|
|
107612
|
+
outcomes.push({
|
|
107613
|
+
address,
|
|
107614
|
+
label: null,
|
|
107615
|
+
chain: chain2,
|
|
107616
|
+
status: "failed",
|
|
107617
|
+
code: "TEE_WALLET_UNKNOWN",
|
|
107618
|
+
reason: `${address} is not a TEE wallet in the vault`
|
|
107619
|
+
});
|
|
107620
|
+
continue;
|
|
107621
|
+
}
|
|
107622
|
+
if (entry.tee?.lifecycle === "retired") {
|
|
107623
|
+
outcomes.push({
|
|
107624
|
+
address,
|
|
107625
|
+
label: entry.label,
|
|
107626
|
+
chain: chain2,
|
|
107627
|
+
status: "skipped",
|
|
107628
|
+
reason: "already retired (demoted and swept)"
|
|
107629
|
+
});
|
|
107630
|
+
continue;
|
|
107631
|
+
}
|
|
107632
|
+
plan2(entry);
|
|
107633
|
+
}
|
|
107634
|
+
}
|
|
107635
|
+
if (rpcUrl !== undefined && new Set(planned.map((row) => row.chain)).size > 1) {
|
|
107636
|
+
return usage(ctx, "--rpc-url names one chain's node; demote Solana and Hood wallets in separate batches.");
|
|
107637
|
+
}
|
|
107638
|
+
if (planned.length > 0) {
|
|
107639
|
+
const display = ctx.json ? "stderr" : "stdout";
|
|
107640
|
+
ctx.deps[display].write(`Demote ${planned.length} TEE wallet${planned.length === 1 ? "" : "s"}, one at a time: disable, then sweep to the destination shown.
|
|
107641
|
+
`);
|
|
107642
|
+
writeTable(ctx.deps, display, ["#", "TEE wallet", "Label", "Sweeps to"], planned.map((row, i) => [
|
|
107643
|
+
String(i + 1),
|
|
107644
|
+
row.address,
|
|
107645
|
+
row.label,
|
|
107646
|
+
row.destination ?? `none (${row.destinationNote})`
|
|
107647
|
+
]), []);
|
|
107648
|
+
for (const skipped of outcomes) {
|
|
107649
|
+
ctx.deps[display].write(`Not demoted: ${skipped.address}: ${skipped.reason}
|
|
107650
|
+
`);
|
|
107651
|
+
}
|
|
107652
|
+
if (!yes) {
|
|
107653
|
+
const typed = await ctx.deps.promptLine(`Type confirm to demote these ${planned.length} wallets: `);
|
|
107654
|
+
if (typed.trim().toLowerCase() !== "confirm") {
|
|
107655
|
+
throw new VaultError("DESTINATION_NOT_CONFIRMED", "The batch was not confirmed; nothing was done.", {
|
|
107656
|
+
suggestion: "Nothing was disabled or swept. Run it again and type confirm at the prompt."
|
|
107657
|
+
});
|
|
107658
|
+
}
|
|
107659
|
+
}
|
|
107660
|
+
}
|
|
107661
|
+
const session = { path: held.path, open: (bytes) => openHeld(opened, held, bytes) };
|
|
107662
|
+
const width = String(planned.length).length;
|
|
107663
|
+
for (const [i, row] of planned.entries()) {
|
|
107664
|
+
if (!ctx.json) {
|
|
107665
|
+
notice(ctx.deps, "stdout", "note", `[${String(i + 1).padStart(width)}/${planned.length}] Demoting ${row.address} (${row.label})`);
|
|
107666
|
+
}
|
|
107667
|
+
const outcome = await demoteOne(ctx, session, row, parsed.values, parsed.booleans);
|
|
107668
|
+
outcomes.push(outcome);
|
|
107669
|
+
if (!ctx.json)
|
|
107670
|
+
writeResultLine(ctx.deps, outcome);
|
|
107671
|
+
}
|
|
107672
|
+
const count3 = (status) => outcomes.filter((o) => o.status === status).length;
|
|
107673
|
+
const summary = {
|
|
107674
|
+
total: outcomes.length,
|
|
107675
|
+
done: count3("done"),
|
|
107676
|
+
incomplete: count3("incomplete"),
|
|
107677
|
+
failed: count3("failed"),
|
|
107678
|
+
skipped: count3("skipped")
|
|
107679
|
+
};
|
|
107680
|
+
const exit = summary.failed > 0 ? 1 : summary.incomplete > 0 ? 3 : 0;
|
|
107681
|
+
if (ctx.json) {
|
|
107682
|
+
writeJson(ctx.deps, { ok: exit === 0, wallets: outcomes, summary });
|
|
107683
|
+
return exit;
|
|
107684
|
+
}
|
|
107685
|
+
ctx.deps.stdout.write(`
|
|
107686
|
+
Summary: ${summary.done} done, ${summary.incomplete} incomplete, ${summary.failed} failed, ${summary.skipped} skipped (of ${summary.total}).
|
|
107687
|
+
`);
|
|
107688
|
+
for (const outcome of outcomes) {
|
|
107689
|
+
if (outcome.status === "done")
|
|
107690
|
+
continue;
|
|
107691
|
+
ctx.deps.stdout.write(` ${outcome.status.padEnd(10)} ${outcome.address}${outcome.reason ? `: ${outcome.reason}` : ""}
|
|
107692
|
+
`);
|
|
107693
|
+
}
|
|
107694
|
+
return exit;
|
|
107695
|
+
});
|
|
107696
|
+
}
|
|
107697
|
+
async function openHeld(opened, held, raw) {
|
|
107698
|
+
const vault = await reopenWithHeldDek(held.path, raw, held);
|
|
107699
|
+
return {
|
|
107700
|
+
vault,
|
|
107701
|
+
factor: opened.factor,
|
|
107702
|
+
reopen: (path, bytes) => reopenWithHeldDek(path, bytes, held),
|
|
107703
|
+
confirm: opened.confirm
|
|
107704
|
+
};
|
|
107705
|
+
}
|
|
107706
|
+
async function demoteOne(ctx, session, row, values, booleans) {
|
|
107707
|
+
const base2 = { address: row.address, label: row.label, chain: row.chain };
|
|
107708
|
+
const args = [row.address];
|
|
107709
|
+
for (const flag of ["--rpc-url", "--sweep-to", "--keystore"]) {
|
|
107710
|
+
const value = values[flag];
|
|
107711
|
+
if (value !== undefined)
|
|
107712
|
+
args.push(flag, value);
|
|
107713
|
+
}
|
|
107714
|
+
for (const flag of ["--emergency", "--accept-older-copy"])
|
|
107715
|
+
if (booleans.has(flag))
|
|
107716
|
+
args.push(flag);
|
|
107717
|
+
const covered = row.destination === null ? null : `Type confirm to sweep everything to ${row.destination}: `;
|
|
107718
|
+
const promptLine = async (text4) => text4 === covered ? "confirm" : ctx.deps.promptLine(text4);
|
|
107719
|
+
const captured = [];
|
|
107720
|
+
const errLines = [];
|
|
107721
|
+
const stdout = ctx.json ? { write: (chunk) => void captured.push(chunk) } : ctx.deps.stdout;
|
|
107722
|
+
const stderr = {
|
|
107723
|
+
write: (chunk) => {
|
|
107724
|
+
errLines.push(chunk);
|
|
107725
|
+
ctx.deps.stderr.write(chunk);
|
|
107726
|
+
}
|
|
107727
|
+
};
|
|
107728
|
+
const deps = { ...ctx.deps, stdout, stderr, promptLine };
|
|
107729
|
+
const step = { ...ctx, deps, heldUnlock: session };
|
|
107730
|
+
let exitCode;
|
|
107731
|
+
let thrown;
|
|
107732
|
+
try {
|
|
107733
|
+
exitCode = await vaultDemote(args, step);
|
|
107734
|
+
} catch (error) {
|
|
107735
|
+
exitCode = 1;
|
|
107736
|
+
thrown = error instanceof Error ? error.message : String(error);
|
|
107737
|
+
}
|
|
107738
|
+
const outcome = {
|
|
107739
|
+
...base2,
|
|
107740
|
+
status: exitCode === 0 ? "done" : exitCode === 3 ? "incomplete" : "failed",
|
|
107741
|
+
exitCode
|
|
107742
|
+
};
|
|
107743
|
+
if (ctx.json) {
|
|
107744
|
+
const results = [];
|
|
107745
|
+
for (const line of captured.join("").split(`
|
|
107746
|
+
`)) {
|
|
107747
|
+
if (line.trim() === "")
|
|
107748
|
+
continue;
|
|
107749
|
+
try {
|
|
107750
|
+
results.push(JSON.parse(line));
|
|
107751
|
+
} catch {
|
|
107752
|
+
ctx.deps.stderr.write(`${line}
|
|
107753
|
+
`);
|
|
107754
|
+
}
|
|
107755
|
+
}
|
|
107756
|
+
outcome.results = results;
|
|
107757
|
+
const failure3 = [...results].reverse().find((doc) => typeof doc === "object" && doc !== null && doc.ok === false);
|
|
107758
|
+
if (failure3 !== undefined && exitCode !== 0) {
|
|
107759
|
+
if (typeof failure3.code === "string")
|
|
107760
|
+
outcome.code = failure3.code;
|
|
107761
|
+
if (typeof failure3.message === "string")
|
|
107762
|
+
outcome.reason = failure3.message;
|
|
107763
|
+
}
|
|
107764
|
+
}
|
|
107765
|
+
if (thrown !== undefined)
|
|
107766
|
+
outcome.reason = thrown;
|
|
107767
|
+
if (outcome.reason === undefined && exitCode === 3) {
|
|
107768
|
+
outcome.reason = "the disable is not verified or funds remain; remote authority may still be open";
|
|
107769
|
+
} else if (outcome.reason === undefined && exitCode !== 0) {
|
|
107770
|
+
const first = errLines.find((chunk) => chunk.trim() !== "" && !chunk.includes("vault.state.json"));
|
|
107771
|
+
outcome.reason = firstSentence(first ?? "") ?? `exit ${exitCode}`;
|
|
107772
|
+
}
|
|
107773
|
+
return outcome;
|
|
107774
|
+
}
|
|
107775
|
+
function firstSentence(chunk) {
|
|
107776
|
+
const text4 = chunk.replace(/\s+/g, " ").replace(/^[^\p{L}\p{N}]+/u, "").trim();
|
|
107777
|
+
if (text4 === "")
|
|
107778
|
+
return;
|
|
107779
|
+
const end = text4.search(/\.(\s|$)/);
|
|
107780
|
+
return end < 0 ? text4 : text4.slice(0, end + 1);
|
|
107781
|
+
}
|
|
107782
|
+
function writeResultLine(deps, outcome) {
|
|
107783
|
+
const word3 = { done: "done", incomplete: "incomplete", failed: "FAILED", skipped: "skipped" }[outcome.status];
|
|
107784
|
+
deps.stdout.write(`Result ${outcome.address}: ${word3} (exit ${outcome.exitCode})${outcome.reason && outcome.status !== "done" ? `: ${outcome.reason}` : ""}
|
|
107785
|
+
`);
|
|
107786
|
+
}
|
|
107787
|
+
|
|
107788
|
+
// src/commands/vault-demote.ts
|
|
107040
107789
|
init_vault_support();
|
|
107041
107790
|
async function vaultDemote(args, ctx) {
|
|
107791
|
+
if (ctx.heldUnlock === undefined && isBatchDemote(args))
|
|
107792
|
+
return vaultDemoteBatch(args, ctx);
|
|
107042
107793
|
if (namesEvmWallet(args))
|
|
107043
107794
|
return vaultDemoteEvm(args, ctx);
|
|
107044
107795
|
const parsed = parseArgs(args, {
|
|
@@ -108804,6 +109555,8 @@ async function vaultFund(args, ctx) {
|
|
|
108804
109555
|
const asset = (parsed.values["--asset"] ?? "SOL").toUpperCase();
|
|
108805
109556
|
if (!amount)
|
|
108806
109557
|
return usage(ctx, "--amount <n> is required.");
|
|
109558
|
+
if (amount.toLowerCase() === "max")
|
|
109559
|
+
return usage(ctx, "--amount must be a number; funding a wallet takes no max.");
|
|
108807
109560
|
if (asset !== "SOL" && asset !== "USDC")
|
|
108808
109561
|
return usage(ctx, "--asset must be SOL or USDC.");
|
|
108809
109562
|
const solana = await openSolanaClient(ctx, parsed.values["--rpc-url"]);
|
|
@@ -113027,6 +113780,7 @@ init_args();
|
|
|
113027
113780
|
init_deps();
|
|
113028
113781
|
init_evm_lite();
|
|
113029
113782
|
init_solana_endpoint();
|
|
113783
|
+
init_errors2();
|
|
113030
113784
|
init_evm_tee();
|
|
113031
113785
|
init_evm_transfer();
|
|
113032
113786
|
init_promote_support();
|
|
@@ -113049,7 +113803,7 @@ async function vaultTransfer(args, ctx) {
|
|
|
113049
113803
|
const fromLabel = parsed.values["--from"];
|
|
113050
113804
|
const rpcUrlFlag = parsed.values["--rpc-url"];
|
|
113051
113805
|
if (!amount)
|
|
113052
|
-
return usage(ctx, "--amount <n> is required.");
|
|
113806
|
+
return usage(ctx, "--amount <n|max> is required.");
|
|
113053
113807
|
if (!asset)
|
|
113054
113808
|
return usage(ctx, "--asset SOL|<mint>|ETH|USDG|<0x token> is required.");
|
|
113055
113809
|
if (!fromLabel)
|
|
@@ -113145,14 +113899,15 @@ async function vaultTransfer(args, ctx) {
|
|
|
113145
113899
|
const reconciled = await reconcileFundingReceipts(vault, [fromEntry.address, to], solana.rpc, ctx);
|
|
113146
113900
|
if (reconciled !== null)
|
|
113147
113901
|
return reconciled;
|
|
113148
|
-
const
|
|
113902
|
+
const buildPlan = () => solana.read(() => planTransfer({
|
|
113149
113903
|
from: fromEntry.address,
|
|
113150
113904
|
to,
|
|
113151
113905
|
amount,
|
|
113152
113906
|
asset,
|
|
113153
113907
|
rpc: solana.rpc
|
|
113154
113908
|
}));
|
|
113155
|
-
|
|
113909
|
+
let plan2 = await buildPlan();
|
|
113910
|
+
let feeQuote = plan2.solMax?.fee ?? await solana.read(() => quoteTransferFee(solana.rpc, plan2.from, plan2.instructions));
|
|
113156
113911
|
displayTransferPlan(ctx, plan2, feeQuote);
|
|
113157
113912
|
await assertTransferAffordable(solana, plan2, feeQuote);
|
|
113158
113913
|
if (promoted) {
|
|
@@ -113160,7 +113915,23 @@ async function vaultTransfer(args, ctx) {
|
|
|
113160
113915
|
if (notice2 !== undefined)
|
|
113161
113916
|
note(notice2);
|
|
113162
113917
|
}
|
|
113163
|
-
|
|
113918
|
+
for (let attempt = 0;attempt < 3; attempt++) {
|
|
113919
|
+
await confirmSend(ctx, { amount: plan2.amount, asset: plan2.asset, address: to });
|
|
113920
|
+
if (!plan2.solMax)
|
|
113921
|
+
break;
|
|
113922
|
+
const refreshed = await buildPlan();
|
|
113923
|
+
const unchanged = refreshed.amountRaw === plan2.amountRaw && refreshed.solMax?.fee === plan2.solMax.fee;
|
|
113924
|
+
plan2 = refreshed;
|
|
113925
|
+
feeQuote = refreshed.solMax?.fee ?? null;
|
|
113926
|
+
if (unchanged)
|
|
113927
|
+
break;
|
|
113928
|
+
if (attempt === 2) {
|
|
113929
|
+
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.");
|
|
113930
|
+
}
|
|
113931
|
+
note("SOL max balance or fee changed; review and confirm the refreshed amount.");
|
|
113932
|
+
displayTransferPlan(ctx, plan2, feeQuote);
|
|
113933
|
+
await assertTransferAffordable(solana, plan2, feeQuote);
|
|
113934
|
+
}
|
|
113164
113935
|
const secret = await decryptKey(vault, fromEntry.id);
|
|
113165
113936
|
try {
|
|
113166
113937
|
const result = await signAndBroadcastTransfer({ ctx, solana, secret64: secret, plan: plan2 });
|
|
@@ -113203,7 +113974,7 @@ init_human_output();
|
|
|
113203
113974
|
init_release();
|
|
113204
113975
|
import { dirname as dirname11, join as join15 } from "node:path";
|
|
113205
113976
|
init_key_output();
|
|
113206
|
-
var
|
|
113977
|
+
var USAGE17 = "Usage: candle verify <file> --bundle <path> [--identity <uri>] [--issuer <url>]";
|
|
113207
113978
|
async function resolveIdentity(deps, bundlePath, flag) {
|
|
113208
113979
|
if (flag)
|
|
113209
113980
|
return { kind: "ok", uri: flag, provenance: "identity from --identity" };
|
|
@@ -113235,19 +114006,19 @@ async function verify(args, ctx) {
|
|
|
113235
114006
|
});
|
|
113236
114007
|
if ("error" in parsed) {
|
|
113237
114008
|
writeScreenUsageFailure(deps, `${parsed.error}
|
|
113238
|
-
${
|
|
114009
|
+
${USAGE17}`, json);
|
|
113239
114010
|
return 2;
|
|
113240
114011
|
}
|
|
113241
114012
|
const file = parsed.positionals[0];
|
|
113242
114013
|
if (parsed.positionals.length !== 1 || file === undefined) {
|
|
113243
114014
|
writeScreenUsageFailure(deps, `verify takes exactly one file.
|
|
113244
|
-
${
|
|
114015
|
+
${USAGE17}`, json);
|
|
113245
114016
|
return 2;
|
|
113246
114017
|
}
|
|
113247
114018
|
const bundlePath = parsed.values["--bundle"];
|
|
113248
114019
|
if (!bundlePath) {
|
|
113249
114020
|
writeScreenUsageFailure(deps, `--bundle is required.
|
|
113250
|
-
${
|
|
114021
|
+
${USAGE17}`, json);
|
|
113251
114022
|
return 2;
|
|
113252
114023
|
}
|
|
113253
114024
|
const resolved = await resolveIdentity(deps, bundlePath, parsed.values["--identity"]);
|
|
@@ -113261,7 +114032,7 @@ ${USAGE16}`, json);
|
|
|
113261
114032
|
}
|
|
113262
114033
|
if (resolved.kind === "absent") {
|
|
113263
114034
|
writeScreenUsageFailure(deps, `--identity is required: there is no latest.json beside ${bundlePath} to take the release version from.
|
|
113264
|
-
${
|
|
114035
|
+
${USAGE17}`, json);
|
|
113265
114036
|
return 2;
|
|
113266
114037
|
}
|
|
113267
114038
|
const identity4 = resolved.uri;
|
|
@@ -113510,7 +114281,7 @@ init_solana_lite();
|
|
|
113510
114281
|
init_trading();
|
|
113511
114282
|
init_swap();
|
|
113512
114283
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
113513
|
-
var
|
|
114284
|
+
var USAGE18 = "Usage: candle wallet close-empty [--wallet embedded|<tee address|label>] [--keep <mint>]... [--client-trade-id <id>] [--yes] [--json]";
|
|
113514
114285
|
var BASE58_ADDRESS2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
113515
114286
|
var LISTED_ROWS = 50;
|
|
113516
114287
|
var TOKEN_IX_CLOSE_ACCOUNT3 = 9;
|
|
@@ -113773,7 +114544,7 @@ async function closeTee(ctx, key, wallet3, name2, run2) {
|
|
|
113773
114544
|
async function walletsCloseEmpty(args, ctx) {
|
|
113774
114545
|
const lifted = splitKeep(args);
|
|
113775
114546
|
if ("error" in lifted) {
|
|
113776
|
-
writeUsageFailure(ctx.deps, `${lifted.error}. ${
|
|
114547
|
+
writeUsageFailure(ctx.deps, `${lifted.error}. ${USAGE18}`, ctx.json);
|
|
113777
114548
|
return 2;
|
|
113778
114549
|
}
|
|
113779
114550
|
const parsed = parseArgs(lifted.rest, { valueFlags: ["--wallet", "--client-trade-id"], booleanFlags: ["--yes"] });
|
|
@@ -113784,7 +114555,7 @@ async function walletsCloseEmpty(args, ctx) {
|
|
|
113784
114555
|
const named = parsed.values["--client-trade-id"];
|
|
113785
114556
|
const id = named ?? `close-${randomUUID15()}`;
|
|
113786
114557
|
if (parsed.positionals.length !== 0 || !validClientId(id) || !lifted.keep.every((m) => BASE58_ADDRESS2.test(m))) {
|
|
113787
|
-
writeUsageFailure(ctx.deps,
|
|
114558
|
+
writeUsageFailure(ctx.deps, USAGE18, ctx.json);
|
|
113788
114559
|
return 2;
|
|
113789
114560
|
}
|
|
113790
114561
|
const run2 = { id, named: named !== undefined, keep: lifted.keep, yes: parsed.booleans.has("--yes") };
|
|
@@ -114101,14 +114872,27 @@ async function maybeWriteUpdateNotice(deps, opts = {}) {
|
|
|
114101
114872
|
return;
|
|
114102
114873
|
if (opts.command === "update" || opts.command === "doctor")
|
|
114103
114874
|
return;
|
|
114104
|
-
const
|
|
114105
|
-
if (!
|
|
114875
|
+
const seen = latestCliVersionFromApi();
|
|
114876
|
+
if (!seen || !isPlainVersion(seen) || compareVersions(seen, CLI_VERSION) <= 0)
|
|
114106
114877
|
return;
|
|
114878
|
+
const realExec = await deps.realpath(deps.execPath).catch(() => deps.execPath);
|
|
114879
|
+
const method = detectInstall(deps.execPath, realExec);
|
|
114880
|
+
let latest = seen;
|
|
114881
|
+
let command = "npm i -g @candledottv/cli@latest";
|
|
114882
|
+
if (method !== "script") {
|
|
114883
|
+
command = method === "homebrew" ? "brew upgrade candle" : "candle update";
|
|
114884
|
+
const feed = await fetchLatest(deps, releaseBaseUrl(deps.env, updateHomeFor(CLI_VERSION)));
|
|
114885
|
+
if (!feed.ok || !isPlainVersion(feed.manifest.version))
|
|
114886
|
+
return;
|
|
114887
|
+
latest = feed.manifest.version;
|
|
114888
|
+
if (compareVersions(latest, CLI_VERSION) <= 0)
|
|
114889
|
+
return;
|
|
114890
|
+
}
|
|
114107
114891
|
const config = await deps.readConfig();
|
|
114108
114892
|
const prior = config.updateNotice;
|
|
114109
114893
|
if (prior && prior.version === latest && deps.now() - prior.shownAt < DAY_MS2)
|
|
114110
114894
|
return;
|
|
114111
|
-
const line = `Update available: candle ${CLI_VERSION} -> ${latest}. Run:
|
|
114895
|
+
const line = `Update available: candle ${CLI_VERSION} -> ${latest}. Run: ${command}`;
|
|
114112
114896
|
const tty = terminalStyle(deps, "stderr", opts.json).color !== 0;
|
|
114113
114897
|
deps.stderr.write(tty ? `\x1B[2m${line}\x1B[0m
|
|
114114
114898
|
` : `${line}
|
|
@@ -114189,7 +114973,7 @@ var COMMANDS = {
|
|
|
114189
114973
|
stake: { bare: stake, subcommands: { add: stakeAdd, remove: stakeRemove, finish: stakeFinish, claim: stakeClaim } },
|
|
114190
114974
|
swap: { bare: swap, subcommands: { status: swapStatus } },
|
|
114191
114975
|
transfer: { bare: transfer },
|
|
114192
|
-
launch: { bare: launch },
|
|
114976
|
+
launch: { bare: launch, subcommands: { status: launchStatus } },
|
|
114193
114977
|
image: { subcommands: { upload: imageUpload } },
|
|
114194
114978
|
pnl: { bare: pnl },
|
|
114195
114979
|
portfolio: { bare: portfolio },
|