@candledottv/cli 0.11.31 → 0.11.32
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/dist/index.js +566 -76
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10972,6 +10972,7 @@ var init_errors2 = __esm(() => {
|
|
|
10972
10972
|
"EVM_RECIPIENT_IS_TOKEN",
|
|
10973
10973
|
"EVM_TRANSFER_REVERTED",
|
|
10974
10974
|
"TRANSFER_CHAIN_MISMATCH",
|
|
10975
|
+
"TRANSFER_INSUFFICIENT_FUNDS",
|
|
10975
10976
|
"SOLANA_COMMAND_EVM_KEY",
|
|
10976
10977
|
"RPC_RATE_LIMITED",
|
|
10977
10978
|
"EVM_RECORD_ENTRY_TOO_LONG",
|
|
@@ -17577,6 +17578,7 @@ function parseMintAccount(mint, owner, data) {
|
|
|
17577
17578
|
tokenProgram: owner,
|
|
17578
17579
|
token2022: owner === TOKEN_2022_PROGRAM_ID,
|
|
17579
17580
|
decimals,
|
|
17581
|
+
associatedTokenAccountSize: owner === TOKEN_2022_PROGRAM_ID ? TLV_START + 4 : ACCOUNT_TYPE_OFFSET2,
|
|
17580
17582
|
nonTransferable: false,
|
|
17581
17583
|
defaultFrozen: false,
|
|
17582
17584
|
paused: false,
|
|
@@ -17601,6 +17603,7 @@ function parseMintAccount(mint, owner, data) {
|
|
|
17601
17603
|
throw new MintReadError(`${mint} has an invalid mint extension length or a duplicate extension`);
|
|
17602
17604
|
}
|
|
17603
17605
|
seen.add(type);
|
|
17606
|
+
profile.associatedTokenAccountSize += REQUIRED_ACCOUNT_TLV_SIZE.get(type) ?? 0;
|
|
17604
17607
|
const requireLength = (expected) => {
|
|
17605
17608
|
if (length !== expected)
|
|
17606
17609
|
throw new MintReadError(`${mint} has an invalid extension ${type} length`);
|
|
@@ -17890,11 +17893,17 @@ function classifyTokenSendFailure(input) {
|
|
|
17890
17893
|
return "TOKEN_2022_HOOK_REFUSED";
|
|
17891
17894
|
return;
|
|
17892
17895
|
}
|
|
17893
|
-
var MintReadError, MINT_DECIMALS_OFFSET = 44, MINT_IS_INITIALIZED_OFFSET = 45, MINT_BASE_SIZE2 = 82, ACCOUNT_TYPE_OFFSET2 = 165, TLV_START = 166, MULTISIG_SIZE2 = 355, EXT_TRANSFER_FEE_CONFIG = 1, EXT_DEFAULT_ACCOUNT_STATE = 6, EXT_NON_TRANSFERABLE = 9, EXT_PERMANENT_DELEGATE = 12, EXT_TRANSFER_HOOK = 14, EXT_PAUSABLE = 26, EXECUTE_DISCRIMINATOR, EXTRA_ACCOUNT_METAS_SEED, EXTRA_ACCOUNT_META_SIZE = 35, HookResolutionError;
|
|
17896
|
+
var MintReadError, MINT_DECIMALS_OFFSET = 44, MINT_IS_INITIALIZED_OFFSET = 45, MINT_BASE_SIZE2 = 82, ACCOUNT_TYPE_OFFSET2 = 165, TLV_START = 166, MULTISIG_SIZE2 = 355, EXT_TRANSFER_FEE_CONFIG = 1, EXT_DEFAULT_ACCOUNT_STATE = 6, EXT_NON_TRANSFERABLE = 9, EXT_PERMANENT_DELEGATE = 12, EXT_TRANSFER_HOOK = 14, EXT_PAUSABLE = 26, REQUIRED_ACCOUNT_TLV_SIZE, EXECUTE_DISCRIMINATOR, EXTRA_ACCOUNT_METAS_SEED, EXTRA_ACCOUNT_META_SIZE = 35, HookResolutionError;
|
|
17894
17897
|
var init_token_2022 = __esm(() => {
|
|
17895
17898
|
init_solana_lite();
|
|
17896
17899
|
MintReadError = class MintReadError extends Error {
|
|
17897
17900
|
};
|
|
17901
|
+
REQUIRED_ACCOUNT_TLV_SIZE = new Map([
|
|
17902
|
+
[EXT_TRANSFER_FEE_CONFIG, 4 + 8],
|
|
17903
|
+
[EXT_NON_TRANSFERABLE, 4],
|
|
17904
|
+
[EXT_TRANSFER_HOOK, 4 + 1],
|
|
17905
|
+
[EXT_PAUSABLE, 4]
|
|
17906
|
+
]);
|
|
17898
17907
|
EXECUTE_DISCRIMINATOR = new Uint8Array([105, 37, 101, 197, 75, 251, 102, 26]);
|
|
17899
17908
|
EXTRA_ACCOUNT_METAS_SEED = new TextEncoder().encode("extra-account-metas");
|
|
17900
17909
|
HookResolutionError = class HookResolutionError extends Error {
|
|
@@ -19194,6 +19203,12 @@ function describeBiometry(report) {
|
|
|
19194
19203
|
suggestion: "Unlock the Mac with its password to reset Touch ID, then retry. Nothing was written and no other factor is substituted."
|
|
19195
19204
|
};
|
|
19196
19205
|
case "not-interactive":
|
|
19206
|
+
if (report.laError?.name === "systemCancel") {
|
|
19207
|
+
return {
|
|
19208
|
+
message: `Touch ID did not complete${la}: ${SYSTEM_CANCEL_WORDS}${reason}. This Mac ${report.biometryType && report.biometryType !== "none" ? `has ${biometryTypeWord(report.biometryType)}` : "may have Touch ID"}.`,
|
|
19209
|
+
suggestion: SYSTEM_CANCEL_SUGGESTION
|
|
19210
|
+
};
|
|
19211
|
+
}
|
|
19197
19212
|
return {
|
|
19198
19213
|
message: `Touch ID is not available from this session${la}: this Mac ${report.biometryType && report.biometryType !== "none" ? `has ${biometryTypeWord(report.biometryType)}, but` : "may have Touch ID, but"} no prompt can be shown to a process outside the interactive login session (SSH, a background agent, the lid closed with no display)${reason}.`,
|
|
19199
19214
|
suggestion: NOT_INTERACTIVE_SUGGESTION
|
|
@@ -19256,6 +19271,11 @@ function translateEnclaveFailure(code, message) {
|
|
|
19256
19271
|
DOMAIN_NOT_ASSOCIATED: `macOS did not associate the helper with cli.candle.tv: ${message}. ${AASA_REQUIREMENT}`,
|
|
19257
19272
|
NO_CREDENTIAL: `No synced passkey with this envelope's credential id is available to this Mac or this Apple account: ${message}.`
|
|
19258
19273
|
};
|
|
19274
|
+
if (code === "NOT_INTERACTIVE" && /\bsystemCancel\b/.test(message)) {
|
|
19275
|
+
return new VaultError("VAULT_FACTOR_UNAVAILABLE", `Touch ID did not complete: ${SYSTEM_CANCEL_WORDS}: ${message}.`, {
|
|
19276
|
+
suggestion: SYSTEM_CANCEL_SUGGESTION
|
|
19277
|
+
});
|
|
19278
|
+
}
|
|
19259
19279
|
const suggestion = {
|
|
19260
19280
|
BIOMETRY_UNAVAILABLE: "Open the lid, or use a keyboard with Touch ID, or unlock the Mac with its password first. Nothing was derived and no other factor was tried; the passphrase still opens the vault.",
|
|
19261
19281
|
NOT_INTERACTIVE: NOT_INTERACTIVE_SUGGESTION,
|
|
@@ -19470,7 +19490,7 @@ async function deleteEnclaveKey(deps, session, opts) {
|
|
|
19470
19490
|
return false;
|
|
19471
19491
|
}
|
|
19472
19492
|
}
|
|
19473
|
-
var ENCLAVE_HELPER_ENV = "CANDLE_ENCLAVE_HELPER", CODESIGN_PATH = "/usr/bin/codesign", ENCLAVE_HELPER_TIMEOUT_MS = 120000, CODESIGN_TIMEOUT_MS = 30000, AASA_URL = "https://cli.candle.tv/.well-known/apple-app-site-association", AASA_REQUIREMENT, ENCLAVE_INSTALL_SUGGESTION = "Install a release build of the CLI that ships the signed helper (the darwin tarball and Homebrew place candle-enclave.app beside candle), or set CANDLE_ENCLAVE_HELPER to the path of a signed candle-enclave.app. No other factor is substituted.", NOT_INTERACTIVE_SUGGESTION = "Run the command from a Terminal window inside the logged-in session on that Mac (not over SSH, not from a background agent, not with the lid closed and no display), then retry. Nothing was derived and no other factor is substituted; the passphrase still opens the vault.";
|
|
19493
|
+
var ENCLAVE_HELPER_ENV = "CANDLE_ENCLAVE_HELPER", CODESIGN_PATH = "/usr/bin/codesign", ENCLAVE_HELPER_TIMEOUT_MS = 120000, CODESIGN_TIMEOUT_MS = 30000, AASA_URL = "https://cli.candle.tv/.well-known/apple-app-site-association", AASA_REQUIREMENT, ENCLAVE_INSTALL_SUGGESTION = "Install a release build of the CLI that ships the signed helper (the darwin tarball and Homebrew place candle-enclave.app beside candle), or set CANDLE_ENCLAVE_HELPER to the path of a signed candle-enclave.app. No other factor is substituted.", NOT_INTERACTIVE_SUGGESTION = "Run the command from a Terminal window inside the logged-in session on that Mac (not over SSH, not from a background agent, not with the lid closed and no display), then retry. Nothing was derived and no other factor is substituted; the passphrase still opens the vault.", SYSTEM_CANCEL_SUGGESTION = "Retry with the Terminal window in front, and leave it in front until the prompt is answered. If it is cancelled again, run the command from a Terminal window inside the logged-in session on that Mac (not over SSH, not from a background agent, not with the lid closed and no display). Nothing was derived and no other factor is substituted; the passphrase still opens the vault.", SYSTEM_CANCEL_WORDS = "the system cancelled the prompt (for example another window took focus, or this is not the logged-in session: SSH, a background agent, the lid closed with no display)";
|
|
19474
19494
|
var init_enclave = __esm(() => {
|
|
19475
19495
|
init_sha256();
|
|
19476
19496
|
init_esm();
|
|
@@ -28207,6 +28227,7 @@ __export(exports_vault_support, {
|
|
|
28207
28227
|
requireVaultRaw: () => requireVaultRaw,
|
|
28208
28228
|
requireTty: () => requireTty,
|
|
28209
28229
|
requirePromptStreams: () => requirePromptStreams,
|
|
28230
|
+
refusePromptsWithoutTerminal: () => refusePromptsWithoutTerminal,
|
|
28210
28231
|
refuseEnvPassphrase: () => refuseEnvPassphrase,
|
|
28211
28232
|
openWithTypedPassphrase: () => openWithTypedPassphrase,
|
|
28212
28233
|
nonDefaultVaultFooter: () => nonDefaultVaultFooter,
|
|
@@ -28261,6 +28282,16 @@ function requirePromptStreams(ctx, what) {
|
|
|
28261
28282
|
writeVaultFailure(ctx, new VaultError("VAULT_UNLOCK_FAILED", `${what} needs a terminal for the passphrase prompt: standard input and standard error must both be a terminal. Standard output may be redirected; that is where the listing goes.`));
|
|
28262
28283
|
return false;
|
|
28263
28284
|
}
|
|
28285
|
+
function refusePromptsWithoutTerminal(ctx, what) {
|
|
28286
|
+
if (ctx.deps.isTTY.stdin && ctx.deps.isTTY.stderr)
|
|
28287
|
+
return ctx;
|
|
28288
|
+
const refuse2 = async () => {
|
|
28289
|
+
throw new VaultError("VAULT_UNLOCK_FAILED", `${what} needs a terminal here: this unlock reads a vault passphrase, a security key PIN or a choice from a prompt, and standard input and standard error must both be a terminal for that. Standard output may be redirected.`, {
|
|
28290
|
+
suggestion: "Run it from a terminal, or pass --factor touch-id or --factor passkey to unlock with a factor that reads no prompt. No environment variable or flag supplies a passphrase or PIN."
|
|
28291
|
+
});
|
|
28292
|
+
};
|
|
28293
|
+
return { ...ctx, deps: { ...ctx.deps, promptSecret: refuse2, promptLine: refuse2 } };
|
|
28294
|
+
}
|
|
28264
28295
|
function vaultPathFor(ctx, parsed) {
|
|
28265
28296
|
const flag = parsed.values["--keystore"];
|
|
28266
28297
|
if (flag !== undefined)
|
|
@@ -45684,7 +45715,7 @@ __export(exports_util, {
|
|
|
45684
45715
|
joinValues: () => joinValues,
|
|
45685
45716
|
issue: () => issue,
|
|
45686
45717
|
isPlainObject: () => isPlainObject,
|
|
45687
|
-
isObject: () =>
|
|
45718
|
+
isObject: () => isObject2,
|
|
45688
45719
|
hexToUint8Array: () => hexToUint8Array,
|
|
45689
45720
|
getSizableOrigin: () => getSizableOrigin,
|
|
45690
45721
|
getParsedType: () => getParsedType3,
|
|
@@ -45851,11 +45882,11 @@ function esc(str) {
|
|
|
45851
45882
|
function slugify(input) {
|
|
45852
45883
|
return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
45853
45884
|
}
|
|
45854
|
-
function
|
|
45885
|
+
function isObject2(data) {
|
|
45855
45886
|
return typeof data === "object" && data !== null && !Array.isArray(data);
|
|
45856
45887
|
}
|
|
45857
45888
|
function isPlainObject(o) {
|
|
45858
|
-
if (
|
|
45889
|
+
if (isObject2(o) === false)
|
|
45859
45890
|
return false;
|
|
45860
45891
|
const ctor = o.constructor;
|
|
45861
45892
|
if (ctor === undefined)
|
|
@@ -45863,7 +45894,7 @@ function isPlainObject(o) {
|
|
|
45863
45894
|
if (typeof ctor !== "function")
|
|
45864
45895
|
return true;
|
|
45865
45896
|
const prot = ctor.prototype;
|
|
45866
|
-
if (
|
|
45897
|
+
if (isObject2(prot) === false)
|
|
45867
45898
|
return false;
|
|
45868
45899
|
if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) {
|
|
45869
45900
|
return false;
|
|
@@ -47744,13 +47775,13 @@ var init_schemas = __esm(() => {
|
|
|
47744
47775
|
}
|
|
47745
47776
|
return propValues;
|
|
47746
47777
|
});
|
|
47747
|
-
const
|
|
47778
|
+
const isObject3 = isObject2;
|
|
47748
47779
|
const catchall = def.catchall;
|
|
47749
47780
|
let value;
|
|
47750
47781
|
inst._zod.parse = (payload, ctx) => {
|
|
47751
47782
|
value ?? (value = _normalized.value);
|
|
47752
47783
|
const input = payload.value;
|
|
47753
|
-
if (!
|
|
47784
|
+
if (!isObject3(input)) {
|
|
47754
47785
|
payload.issues.push({
|
|
47755
47786
|
expected: "object",
|
|
47756
47787
|
code: "invalid_type",
|
|
@@ -47848,7 +47879,7 @@ var init_schemas = __esm(() => {
|
|
|
47848
47879
|
return (payload, ctx) => fn(shape, payload, ctx);
|
|
47849
47880
|
};
|
|
47850
47881
|
let fastpass;
|
|
47851
|
-
const
|
|
47882
|
+
const isObject3 = isObject2;
|
|
47852
47883
|
const jit = !globalConfig.jitless;
|
|
47853
47884
|
const allowsEval2 = allowsEval;
|
|
47854
47885
|
const fastEnabled = jit && allowsEval2.value;
|
|
@@ -47857,7 +47888,7 @@ var init_schemas = __esm(() => {
|
|
|
47857
47888
|
inst._zod.parse = (payload, ctx) => {
|
|
47858
47889
|
value ?? (value = _normalized.value);
|
|
47859
47890
|
const input = payload.value;
|
|
47860
|
-
if (!
|
|
47891
|
+
if (!isObject3(input)) {
|
|
47861
47892
|
payload.issues.push({
|
|
47862
47893
|
expected: "object",
|
|
47863
47894
|
code: "invalid_type",
|
|
@@ -47961,7 +47992,7 @@ var init_schemas = __esm(() => {
|
|
|
47961
47992
|
});
|
|
47962
47993
|
inst._zod.parse = (payload, ctx) => {
|
|
47963
47994
|
const input = payload.value;
|
|
47964
|
-
if (!
|
|
47995
|
+
if (!isObject2(input)) {
|
|
47965
47996
|
payload.issues.push({
|
|
47966
47997
|
code: "invalid_type",
|
|
47967
47998
|
expected: "object",
|
|
@@ -62370,7 +62401,7 @@ var init_image = __esm(() => {
|
|
|
62370
62401
|
});
|
|
62371
62402
|
|
|
62372
62403
|
// ../mcp/src/orchestrate.ts
|
|
62373
|
-
import { randomUUID as
|
|
62404
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
62374
62405
|
function requireApiKey(cfg) {
|
|
62375
62406
|
if (!cfg.apiKey) {
|
|
62376
62407
|
throw new Error("CANDLE_AGENT_API_KEY is required for this tool. Set it in the environment or MCP client config.");
|
|
@@ -62483,7 +62514,7 @@ async function readPaperInventory(cfg, doFetch, extra) {
|
|
|
62483
62514
|
}
|
|
62484
62515
|
async function executeTrade(args, cfg, doFetch) {
|
|
62485
62516
|
const apiKey = requireApiKey(cfg);
|
|
62486
|
-
const clientTradeId = args.clientTradeId ??
|
|
62517
|
+
const clientTradeId = args.clientTradeId ?? randomUUID8();
|
|
62487
62518
|
if (args.amount !== undefined && args.percent !== undefined) {
|
|
62488
62519
|
return errText("pass exactly one of amount or percent, not both", { clientTradeId });
|
|
62489
62520
|
}
|
|
@@ -62621,7 +62652,7 @@ async function executeTrade(args, cfg, doFetch) {
|
|
|
62621
62652
|
}
|
|
62622
62653
|
async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs) {
|
|
62623
62654
|
const apiKey = requireApiKey(cfg);
|
|
62624
|
-
const clientLaunchId = args.clientLaunchId ??
|
|
62655
|
+
const clientLaunchId = args.clientLaunchId ?? randomUUID8();
|
|
62625
62656
|
const { devBuy, dryRun, buyAmount: _rawBuyAmount, ...imageFields } = args;
|
|
62626
62657
|
let buyAmount;
|
|
62627
62658
|
if (devBuy !== undefined) {
|
|
@@ -63169,7 +63200,7 @@ function checkOrderMarket2(action, intent, market) {
|
|
|
63169
63200
|
function verifyPerpsBuild2(build, opts) {
|
|
63170
63201
|
try {
|
|
63171
63202
|
const network2 = opts.network ?? "mainnet";
|
|
63172
|
-
if (!
|
|
63203
|
+
if (!EVM_ADDRESS_RE3.test(opts.builder))
|
|
63173
63204
|
return { ok: false, reason: "no valid Candle builder address to check against" };
|
|
63174
63205
|
if (build.network !== undefined && build.network !== network2) {
|
|
63175
63206
|
return { ok: false, reason: `the build is for ${build.network}, this client trades ${network2}` };
|
|
@@ -63257,7 +63288,7 @@ function hyperliquidExchangeBody2(action, nonce, signature) {
|
|
|
63257
63288
|
function hyperliquidRelayBody2(typedData) {
|
|
63258
63289
|
return { method: "eth_signTypedData_v4", params: { typed_data: typedData } };
|
|
63259
63290
|
}
|
|
63260
|
-
var HYPERLIQUID_ALLOWED_ACTION_TYPES2, HYPERLIQUID_EXCHANGE_URLS2, HYPERLIQUID_INFO_URLS2, CANDLE_HYPERLIQUID_BUILDER_ADDRESS2 = null, HYPERLIQUID_MAX_BUILDER_FEE_TENTHS_BPS2 = 100, HYPERLIQUID_MAX_BUILDER_FEE_RATE2 = "0.1%", HYPERLIQUID_SIGNATURE_CHAIN_ID2 = "0xa4b1", ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000", UINT64_MAX2, INT64_MIN2,
|
|
63291
|
+
var HYPERLIQUID_ALLOWED_ACTION_TYPES2, HYPERLIQUID_EXCHANGE_URLS2, HYPERLIQUID_INFO_URLS2, CANDLE_HYPERLIQUID_BUILDER_ADDRESS2 = null, HYPERLIQUID_MAX_BUILDER_FEE_TENTHS_BPS2 = 100, HYPERLIQUID_MAX_BUILDER_FEE_RATE2 = "0.1%", HYPERLIQUID_SIGNATURE_CHAIN_ID2 = "0xa4b1", ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000", UINT64_MAX2, INT64_MIN2, EVM_ADDRESS_RE3, HYPERLIQUID_DEFAULT_SLIPPAGE_BPS2 = 100, HYPERLIQUID_MID_DRIFT_BPS2 = 100, HYPERLIQUID_TPSL_SLIPPAGE_BPS2 = 500;
|
|
63261
63292
|
var init_hyperliquid = __esm(() => {
|
|
63262
63293
|
init_sha3();
|
|
63263
63294
|
HYPERLIQUID_ALLOWED_ACTION_TYPES2 = [
|
|
@@ -63277,7 +63308,7 @@ var init_hyperliquid = __esm(() => {
|
|
|
63277
63308
|
};
|
|
63278
63309
|
UINT64_MAX2 = (1n << 64n) - 1n;
|
|
63279
63310
|
INT64_MIN2 = -(1n << 63n);
|
|
63280
|
-
|
|
63311
|
+
EVM_ADDRESS_RE3 = /^0x[0-9a-fA-F]{40}$/;
|
|
63281
63312
|
});
|
|
63282
63313
|
|
|
63283
63314
|
// ../mcp/src/perps.ts
|
|
@@ -63764,7 +63795,7 @@ var init_plans = __esm(() => {
|
|
|
63764
63795
|
});
|
|
63765
63796
|
|
|
63766
63797
|
// ../mcp/src/tools.ts
|
|
63767
|
-
import { randomUUID as
|
|
63798
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
63768
63799
|
function resolveToolAllowlist(env) {
|
|
63769
63800
|
const raw = env.CANDLE_MCP_TOOLS?.trim();
|
|
63770
63801
|
if (!raw)
|
|
@@ -64096,7 +64127,7 @@ async function getOperation(args, cfg, doFetch) {
|
|
|
64096
64127
|
};
|
|
64097
64128
|
}
|
|
64098
64129
|
async function swap2(args, cfg, doFetch) {
|
|
64099
|
-
const clientSwapId = typeof args.clientSwapId === "string" && args.clientSwapId.length > 0 ? args.clientSwapId :
|
|
64130
|
+
const clientSwapId = typeof args.clientSwapId === "string" && args.clientSwapId.length > 0 ? args.clientSwapId : randomUUID9();
|
|
64100
64131
|
const { url, init } = buildRequest("candle_swap", { ...args, clientSwapId }, cfg);
|
|
64101
64132
|
let ok;
|
|
64102
64133
|
let body;
|
|
@@ -65007,7 +65038,7 @@ init_render();
|
|
|
65007
65038
|
init_secret_store();
|
|
65008
65039
|
|
|
65009
65040
|
// src/version.ts
|
|
65010
|
-
var CLI_VERSION = "0.11.
|
|
65041
|
+
var CLI_VERSION = "0.11.32";
|
|
65011
65042
|
|
|
65012
65043
|
// src/commands/auth.ts
|
|
65013
65044
|
init_keys_embedded_wallet();
|
|
@@ -65438,6 +65469,10 @@ var ENVIRONMENT = [
|
|
|
65438
65469
|
description: "candle perps: Candle's Hyperliquid builder address, pinned for the check every build passes before it is signed; without it the release's, else the one the server reports"
|
|
65439
65470
|
},
|
|
65440
65471
|
{ name: "CANDLE_HYPERLIQUID_NETWORK", description: "candle perps: set to testnet to trade Hyperliquid testnet" },
|
|
65472
|
+
{
|
|
65473
|
+
name: "CANDLE_PERPS_WITHDRAW_PINS",
|
|
65474
|
+
description: "candle perps withdraw: JSON {token, destinationDex, depositories, amountForm} pinning what every withdrawal is checked against before it is signed; this release ships none, so without it withdrawals are refused"
|
|
65475
|
+
},
|
|
65441
65476
|
{ name: "CANDLE_NO_UPDATE_NOTIFIER", description: "Set to 1 to silence the update notice" },
|
|
65442
65477
|
{
|
|
65443
65478
|
name: "CANDLE_KEYSTORE_PASSPHRASE",
|
|
@@ -65626,8 +65661,8 @@ var HELP = {
|
|
|
65626
65661
|
},
|
|
65627
65662
|
perps: {
|
|
65628
65663
|
group: "Trade",
|
|
65629
|
-
summary: "Hyperliquid perpetuals from your EVM TEE wallet: setup, deposit, open, close, cancel, orders, positions, leverage",
|
|
65630
|
-
description: "Trade perpetuals on Hyperliquid's main perp exchange from the EVM TEE wallet bound to this key (scope perps:write, opt-in). Candle builds each action within the key's limits; this machine recomputes its hash and checks the action type and Candle's builder before anything is signed, then Candle's relay signs and this machine submits to Hyperliquid. Every plan is eligible for perps, once the deployment has perps enabled. Every plan except Max pays Candle a builder fee on each order (0.1% today); Max pays none. candle plans shows the rate in force. Deposits move SOL, USDC, ETH or USDG onto the Hyperliquid account through Relay, and
|
|
65664
|
+
summary: "Hyperliquid perpetuals from your EVM TEE wallet: setup, deposit, withdraw, open, close, cancel, orders, positions, leverage",
|
|
65665
|
+
description: "Trade perpetuals on Hyperliquid's main perp exchange from the EVM TEE wallet bound to this key (scope perps:write, opt-in). Candle builds each action within the key's limits; this machine recomputes its hash and checks the action type and Candle's builder before anything is signed, then Candle's relay signs and this machine submits to Hyperliquid. Every plan is eligible for perps, once the deployment has perps enabled. Every plan except Max pays Candle a builder fee on each order (0.1% today); Max pays none. candle plans shows the rate in force. Deposits move SOL, USDC, ETH or USDG onto the Hyperliquid account through Relay; withdrawals move USDC back through Relay to the key's own Solana or Hood TEE wallet (scope perps:withdraw, opt-in, after one vault unlock and a typed confirm). The account's P&L (candle pnl) carries a Hyperliquid section.",
|
|
65631
65666
|
usage: ["candle perps <subcommand> [flags]"],
|
|
65632
65667
|
rows: [
|
|
65633
65668
|
{
|
|
@@ -65648,7 +65683,14 @@ var HELP = {
|
|
|
65648
65683
|
invocation: "orders [--wallet <tee>]",
|
|
65649
65684
|
description: "Open orders on Hyperliquid, and every action Candle built"
|
|
65650
65685
|
},
|
|
65651
|
-
{
|
|
65686
|
+
{
|
|
65687
|
+
invocation: "withdraw <amount> --to solana|hood [--wallet <evm tee>] | withdraw status <id>",
|
|
65688
|
+
description: "Move USDC from the Hyperliquid account to this key's own Solana or Hood TEE wallet through Relay (perps:withdraw); one vault unlock and a typed confirm, no --yes"
|
|
65689
|
+
},
|
|
65690
|
+
{
|
|
65691
|
+
invocation: "positions [--wallet <tee>]",
|
|
65692
|
+
description: "Positions, account value and withdrawable USDC, read live, and any withdrawal in flight"
|
|
65693
|
+
},
|
|
65652
65694
|
{ invocation: "leverage <coin> <x> [--isolated]", description: "Set a market's leverage and margin mode" }
|
|
65653
65695
|
],
|
|
65654
65696
|
flags: [
|
|
@@ -65666,7 +65708,8 @@ var HELP = {
|
|
|
65666
65708
|
invocation: "--to <evm tee>",
|
|
65667
65709
|
description: "deposit from Solana: the EVM TEE wallet whose account receives it"
|
|
65668
65710
|
},
|
|
65669
|
-
{ invocation: "--id <
|
|
65711
|
+
{ invocation: "--id <id>", description: "deposit and withdraw: the idempotency id (default: a new one)" },
|
|
65712
|
+
{ invocation: "--keystore <path>", description: "withdraw: the vault whose unlock confirms the withdrawal" },
|
|
65670
65713
|
{ invocation: "--rpc-url <url>", description: "deposit from Solana: the RPC the deposit check reads" },
|
|
65671
65714
|
{ invocation: "--no-submit", description: "Build and check only; sign and submit nothing" },
|
|
65672
65715
|
{ invocation: "--sign-only", description: "Build, check and sign through the relay; submit nothing" },
|
|
@@ -65678,9 +65721,10 @@ var HELP = {
|
|
|
65678
65721
|
"candle perps open BTC long 0.01",
|
|
65679
65722
|
"candle perps open ETH short 0.5 --price 3200 --tp 2900 --sl 3400",
|
|
65680
65723
|
"candle perps close BTC",
|
|
65681
|
-
"candle perps leverage BTC 5 --isolated"
|
|
65724
|
+
"candle perps leverage BTC 5 --isolated",
|
|
65725
|
+
"candle perps withdraw 25 --to solana"
|
|
65682
65726
|
],
|
|
65683
|
-
env: [...ENV_API, "CANDLE_HYPERLIQUID_BUILDER", "CANDLE_HYPERLIQUID_NETWORK"]
|
|
65727
|
+
env: [...ENV_API, "CANDLE_HYPERLIQUID_BUILDER", "CANDLE_HYPERLIQUID_NETWORK", "CANDLE_PERPS_WITHDRAW_PINS"]
|
|
65684
65728
|
},
|
|
65685
65729
|
transfer: {
|
|
65686
65730
|
group: "Trade",
|
|
@@ -74258,6 +74302,13 @@ async function perpsLeverage(args, ctx) {
|
|
|
74258
74302
|
return tradingFailure(ctx, error);
|
|
74259
74303
|
}
|
|
74260
74304
|
}
|
|
74305
|
+
function formatUsdcMicros(value) {
|
|
74306
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
74307
|
+
return "?";
|
|
74308
|
+
const micros = BigInt(value);
|
|
74309
|
+
const frac = (micros % 1000000n).toString().padStart(6, "0").replace(/0+$/, "");
|
|
74310
|
+
return frac ? `${micros / 1000000n}.${frac}` : `${micros / 1000000n}`;
|
|
74311
|
+
}
|
|
74261
74312
|
async function perpsPositions(args, ctx) {
|
|
74262
74313
|
const parsed = parseArgs(args, { valueFlags: ["--wallet"] });
|
|
74263
74314
|
if ("error" in parsed)
|
|
@@ -74281,6 +74332,13 @@ async function perpsPositions(args, ctx) {
|
|
|
74281
74332
|
for (const p of positions) {
|
|
74282
74333
|
const leverage = p.leverage ?? {};
|
|
74283
74334
|
out.write(` ${safeText(p.coin)} ${safeText(p.szi)} at ${safeText(p.entryPx)}, value ${safeText(p.positionValue)}, uPnL ${safeText(p.unrealizedPnl)}, ${safeText(leverage.value)}x ${safeText(leverage.type)}, liq ${safeText(p.liquidationPx ?? "none")}
|
|
74335
|
+
`);
|
|
74336
|
+
}
|
|
74337
|
+
const open5 = Array.isArray(result.openWithdrawals) ? result.openWithdrawals : null;
|
|
74338
|
+
out.write(`Withdrawable now: ${safeText(result.withdrawable)} USDC${open5 === null ? "" : open5.length === 0 ? "; no withdrawal in flight" : ""}.
|
|
74339
|
+
`);
|
|
74340
|
+
for (const w of open5 ?? []) {
|
|
74341
|
+
out.write(` Withdrawal ${safeText(w.id)}: ${safeText(w.status)}, ${formatUsdcMicros(w.amountUsdcMicros)} USDC to ${safeText(w.to)} (candle perps withdraw status ${safeText(w.id)})
|
|
74284
74342
|
`);
|
|
74285
74343
|
}
|
|
74286
74344
|
return 0;
|
|
@@ -74466,7 +74524,7 @@ async function perpsDeposit(args, ctx) {
|
|
|
74466
74524
|
`);
|
|
74467
74525
|
output.write(`Relay delivers at least ${minimum} USDC (estimate ${estimate2})${build.firstDeposit ? "; Hyperliquid keeps 1 USDC to activate this new account" : ""}. Candle fee: none.
|
|
74468
74526
|
`);
|
|
74469
|
-
output.write("
|
|
74527
|
+
output.write("To bring it back: candle perps withdraw <amount> --to solana|hood (perps:withdraw). `candle tee disable` and `tee sweep` do not reach it, so withdraw before disabling.\n");
|
|
74470
74528
|
if (sequenced)
|
|
74471
74529
|
output.write(`Legs, signed one at a time: ${sequenced.plannedLegCount === 2 ? "approval, bridgeDeposit" : "bridgeDeposit"}
|
|
74472
74530
|
`);
|
|
@@ -74520,6 +74578,382 @@ async function perpsDeposit(args, ctx) {
|
|
|
74520
74578
|
}
|
|
74521
74579
|
}
|
|
74522
74580
|
|
|
74581
|
+
// src/commands/perps-withdraw.ts
|
|
74582
|
+
init_args();
|
|
74583
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
74584
|
+
|
|
74585
|
+
// src/hyperliquid-withdraw.ts
|
|
74586
|
+
var CANDLE_PERPS_WITHDRAW_PINS = null;
|
|
74587
|
+
var RELAY_API_URL = "https://api.relay.link";
|
|
74588
|
+
var ZERO = "0x0000000000000000000000000000000000000000";
|
|
74589
|
+
var EVM_ADDRESS_RE2 = /^0x[0-9a-fA-F]{40}$/;
|
|
74590
|
+
var BYTES32_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
74591
|
+
var RELAY_NONCE_MAPPING_TYPES = {
|
|
74592
|
+
NonceMapping: [
|
|
74593
|
+
{ name: "chainId", type: "string" },
|
|
74594
|
+
{ name: "wallet", type: "address" },
|
|
74595
|
+
{ name: "depositor", type: "address" },
|
|
74596
|
+
{ name: "id", type: "bytes32" },
|
|
74597
|
+
{ name: "nonce", type: "uint256" }
|
|
74598
|
+
]
|
|
74599
|
+
};
|
|
74600
|
+
var HYPERLIQUID_SEND_ASSET_TYPES = {
|
|
74601
|
+
"HyperliquidTransaction:SendAsset": [
|
|
74602
|
+
{ name: "hyperliquidChain", type: "string" },
|
|
74603
|
+
{ name: "destination", type: "string" },
|
|
74604
|
+
{ name: "sourceDex", type: "string" },
|
|
74605
|
+
{ name: "destinationDex", type: "string" },
|
|
74606
|
+
{ name: "token", type: "string" },
|
|
74607
|
+
{ name: "amount", type: "string" },
|
|
74608
|
+
{ name: "fromSubAccount", type: "string" },
|
|
74609
|
+
{ name: "nonce", type: "uint64" }
|
|
74610
|
+
]
|
|
74611
|
+
};
|
|
74612
|
+
var SEND_ASSET_KEYS = HYPERLIQUID_SEND_ASSET_TYPES["HyperliquidTransaction:SendAsset"].map((field) => field.name);
|
|
74613
|
+
var REQUEST_AMOUNT_RE = /^(0|[1-9][0-9]{0,11})(\.[0-9]{1,6})?$/;
|
|
74614
|
+
var CANONICAL_RE = /^(0|[1-9][0-9]*)(\.[0-9]{0,5}[1-9])?$/;
|
|
74615
|
+
var FIXED6_RE = /^(0|[1-9][0-9]*)\.[0-9]{6}$/;
|
|
74616
|
+
function parseWithdrawAmount(value) {
|
|
74617
|
+
if (!REQUEST_AMOUNT_RE.test(value))
|
|
74618
|
+
return null;
|
|
74619
|
+
const [whole = "0", frac = ""] = value.split(".");
|
|
74620
|
+
const micros = BigInt(whole) * 1000000n + BigInt(frac.padEnd(6, "0"));
|
|
74621
|
+
return micros > 0n ? micros : null;
|
|
74622
|
+
}
|
|
74623
|
+
function formatWithdrawAmount(micros) {
|
|
74624
|
+
const frac = (micros % 1000000n).toString().padStart(6, "0").replace(/0+$/, "");
|
|
74625
|
+
return frac ? `${micros / 1000000n}.${frac}` : `${micros / 1000000n}`;
|
|
74626
|
+
}
|
|
74627
|
+
function parseSendAssetAmount(value, form) {
|
|
74628
|
+
if (typeof value !== "string" || !(form === "canonical" ? CANONICAL_RE : FIXED6_RE).test(value))
|
|
74629
|
+
return null;
|
|
74630
|
+
const [whole = "0", frac = ""] = value.split(".");
|
|
74631
|
+
if (frac.length > 6)
|
|
74632
|
+
return null;
|
|
74633
|
+
return BigInt(whole) * 1000000n + BigInt(frac.padEnd(6, "0"));
|
|
74634
|
+
}
|
|
74635
|
+
function isObject(value) {
|
|
74636
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
74637
|
+
}
|
|
74638
|
+
function exactKeys(value, keys) {
|
|
74639
|
+
const own = Object.keys(value);
|
|
74640
|
+
return own.length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
74641
|
+
}
|
|
74642
|
+
function sameEvm(a, b) {
|
|
74643
|
+
return typeof a === "string" && EVM_ADDRESS_RE2.test(a) && a.toLowerCase() === b.toLowerCase();
|
|
74644
|
+
}
|
|
74645
|
+
function typesMatch(types2, expected) {
|
|
74646
|
+
if (!isObject(types2) || !exactKeys(types2, Object.keys(expected)))
|
|
74647
|
+
return false;
|
|
74648
|
+
return Object.entries(expected).every(([key, want]) => {
|
|
74649
|
+
const fields = types2[key];
|
|
74650
|
+
return Array.isArray(fields) && fields.length === want.length && fields.every((field, i) => isObject(field) && exactKeys(field, ["name", "type"]) && field.name === want[i]?.name && field.type === want[i]?.type);
|
|
74651
|
+
});
|
|
74652
|
+
}
|
|
74653
|
+
function domainMatches(domain, expected) {
|
|
74654
|
+
return isObject(domain) && exactKeys(domain, ["name", "version", "chainId", "verifyingContract"]) && domain.name === expected.name && domain.version === expected.version && domain.chainId === expected.chainId && typeof domain.verifyingContract === "string" && domain.verifyingContract.toLowerCase() === ZERO;
|
|
74655
|
+
}
|
|
74656
|
+
function verifyWithdrawMapping(build, expect) {
|
|
74657
|
+
const refuse3 = (reason) => ({ ok: false, reason });
|
|
74658
|
+
if (expect.pins === null)
|
|
74659
|
+
return refuse3("the withdrawal pins are pending verification in this release");
|
|
74660
|
+
if (build.id !== expect.id)
|
|
74661
|
+
return refuse3("the build names another withdrawal id");
|
|
74662
|
+
if (!sameEvm(build.from?.address, expect.account))
|
|
74663
|
+
return refuse3("the build withdraws from another account");
|
|
74664
|
+
if (build.to?.chain !== expect.to)
|
|
74665
|
+
return refuse3(`the build lands on ${String(build.to?.chain)}, not ${expect.to}`);
|
|
74666
|
+
const destination = build.to.address;
|
|
74667
|
+
const own = expect.ownDestinations.some((address) => address.startsWith("0x") ? sameEvm(destination, address) : destination === address);
|
|
74668
|
+
if (!own)
|
|
74669
|
+
return refuse3("the build lands on a wallet that is not this key's own");
|
|
74670
|
+
if (parseWithdrawAmount(build.amountUsdc) !== expect.amountMicros)
|
|
74671
|
+
return refuse3("the build is for another amount");
|
|
74672
|
+
const typed = build.typedData;
|
|
74673
|
+
if (!isObject(typed) || typed.primary_type !== "NonceMapping")
|
|
74674
|
+
return refuse3("the typed data is not a NonceMapping");
|
|
74675
|
+
if (!domainMatches(typed.domain, { name: "RelayNonceMapping", version: "2", chainId: 1 }))
|
|
74676
|
+
return refuse3("the NonceMapping domain is not Relay's");
|
|
74677
|
+
if (!typesMatch(typed.types, RELAY_NONCE_MAPPING_TYPES))
|
|
74678
|
+
return refuse3("the NonceMapping type list differs");
|
|
74679
|
+
const message = typed.message;
|
|
74680
|
+
if (!isObject(message) || !exactKeys(message, ["chainId", "wallet", "depositor", "id", "nonce"]))
|
|
74681
|
+
return refuse3("the NonceMapping message has other fields");
|
|
74682
|
+
if (message.chainId !== "hyperliquid")
|
|
74683
|
+
return refuse3("the NonceMapping names another chain");
|
|
74684
|
+
if (!sameEvm(message.wallet, expect.account))
|
|
74685
|
+
return refuse3("the NonceMapping wallet is not this key's account");
|
|
74686
|
+
if (!sameEvm(message.depositor, expect.account))
|
|
74687
|
+
return refuse3("the NonceMapping depositor is not this key's account");
|
|
74688
|
+
if (typeof message.id !== "string" || !BYTES32_RE.test(message.id) || message.id !== build.relayRequestId)
|
|
74689
|
+
return refuse3("the NonceMapping names another Relay request");
|
|
74690
|
+
if (message.nonce !== build.nonce || !Number.isSafeInteger(build.nonce) || build.nonce <= 0)
|
|
74691
|
+
return refuse3("the NonceMapping nonce is not the build's");
|
|
74692
|
+
return { ok: true };
|
|
74693
|
+
}
|
|
74694
|
+
function verifyWithdrawSendAsset(authorization, expect) {
|
|
74695
|
+
const refuse3 = (reason) => ({ ok: false, reason });
|
|
74696
|
+
const pins = expect.pins;
|
|
74697
|
+
if (pins === null)
|
|
74698
|
+
return refuse3("the withdrawal pins are pending verification in this release");
|
|
74699
|
+
const typed = authorization.typedData;
|
|
74700
|
+
if (!isObject(typed) || typed.primary_type !== "HyperliquidTransaction:SendAsset")
|
|
74701
|
+
return refuse3("the typed data is not a SendAsset");
|
|
74702
|
+
if (!domainMatches(typed.domain, { name: "HyperliquidSignTransaction", version: "1", chainId: 42161 }))
|
|
74703
|
+
return refuse3("the SendAsset domain is not Hyperliquid's user-signed domain");
|
|
74704
|
+
if (!typesMatch(typed.types, HYPERLIQUID_SEND_ASSET_TYPES))
|
|
74705
|
+
return refuse3("the SendAsset type list differs");
|
|
74706
|
+
const m = typed.message;
|
|
74707
|
+
if (!isObject(m) || !exactKeys(m, SEND_ASSET_KEYS))
|
|
74708
|
+
return refuse3("the SendAsset message has other fields");
|
|
74709
|
+
if (m.hyperliquidChain !== "Mainnet")
|
|
74710
|
+
return refuse3("the SendAsset is not on Mainnet");
|
|
74711
|
+
if (m.nonce !== expect.mappingNonce || authorization.nonce !== expect.mappingNonce)
|
|
74712
|
+
return refuse3("the SendAsset nonce is not the NonceMapping's");
|
|
74713
|
+
if (parseSendAssetAmount(m.amount, pins.amountForm) !== expect.amountMicros)
|
|
74714
|
+
return refuse3("the SendAsset amount is not the amount asked for");
|
|
74715
|
+
if (m.fromSubAccount !== "")
|
|
74716
|
+
return refuse3("the SendAsset moves from a sub-account");
|
|
74717
|
+
if (m.sourceDex !== "")
|
|
74718
|
+
return refuse3("the SendAsset moves from another dex");
|
|
74719
|
+
if (m.token !== pins.token)
|
|
74720
|
+
return refuse3("the SendAsset moves another token");
|
|
74721
|
+
if (m.destinationDex !== pins.destinationDex)
|
|
74722
|
+
return refuse3("the SendAsset names another destination dex");
|
|
74723
|
+
if (typeof m.destination !== "string" || !pins.depositories.includes(m.destination.toLowerCase()))
|
|
74724
|
+
return refuse3("the SendAsset destination is not a Relay depository on the allow-list");
|
|
74725
|
+
const action = authorization.action;
|
|
74726
|
+
if (!isObject(action) || !exactKeys(action, ["type", "signatureChainId", ...SEND_ASSET_KEYS]))
|
|
74727
|
+
return refuse3("the action has other fields");
|
|
74728
|
+
if (action.type !== "sendAsset" || action.signatureChainId !== "0xa4b1")
|
|
74729
|
+
return refuse3("the action is not a sendAsset");
|
|
74730
|
+
for (const key of SEND_ASSET_KEYS) {
|
|
74731
|
+
if (action[key] !== m[key])
|
|
74732
|
+
return refuse3(`the action's ${key} differs from what is signed`);
|
|
74733
|
+
}
|
|
74734
|
+
return { ok: true };
|
|
74735
|
+
}
|
|
74736
|
+
async function relayRecipientProblem(fetchImpl, requestId, recipient, relayUrl = RELAY_API_URL) {
|
|
74737
|
+
let body;
|
|
74738
|
+
try {
|
|
74739
|
+
const res = await fetchImpl(`${relayUrl}/requests/v3?id=${encodeURIComponent(requestId)}`, { method: "GET" });
|
|
74740
|
+
if (!res.ok)
|
|
74741
|
+
return `Relay's record of the request could not be read (HTTP ${res.status})`;
|
|
74742
|
+
body = await res.json();
|
|
74743
|
+
} catch (error) {
|
|
74744
|
+
return `Relay's record of the request could not be read (${error instanceof Error ? error.message : "unknown"})`;
|
|
74745
|
+
}
|
|
74746
|
+
const requests = isObject(body) && Array.isArray(body.requests) ? body.requests : [];
|
|
74747
|
+
const match = requests.find((r) => isObject(r) && r.id === requestId);
|
|
74748
|
+
if (!isObject(match))
|
|
74749
|
+
return "Relay does not list this request";
|
|
74750
|
+
const named = match.recipient;
|
|
74751
|
+
const same = recipient.startsWith("0x") ? sameEvm(named, recipient) : named === recipient;
|
|
74752
|
+
return same ? null : `Relay's request pays ${String(named)}, not this key's wallet ${recipient}`;
|
|
74753
|
+
}
|
|
74754
|
+
|
|
74755
|
+
// src/commands/perps-withdraw.ts
|
|
74756
|
+
init_render();
|
|
74757
|
+
init_trading();
|
|
74758
|
+
init_errors2();
|
|
74759
|
+
init_store();
|
|
74760
|
+
init_swap();
|
|
74761
|
+
init_vault_support();
|
|
74762
|
+
var WITHDRAW_SCOPE = "perps:withdraw";
|
|
74763
|
+
var ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
74764
|
+
var USAGE5 = "Usage: candle perps withdraw <amount> --to solana|hood [--wallet <evm tee>] [--id <id>] [--keystore <path>] | candle perps withdraw status <id>";
|
|
74765
|
+
var WITHDRAW_POLL_MS = 10 * 60 * 1000;
|
|
74766
|
+
var WITHDRAW_POLL_INTERVAL_MS = 5000;
|
|
74767
|
+
var TERMINAL = new Set(["settled", "refunded", "failed"]);
|
|
74768
|
+
function usage5(ctx, line) {
|
|
74769
|
+
writeUsageFailure(ctx.deps, line, ctx.json);
|
|
74770
|
+
return 2;
|
|
74771
|
+
}
|
|
74772
|
+
function withdrawPins(env) {
|
|
74773
|
+
const raw = env.CANDLE_PERPS_WITHDRAW_PINS;
|
|
74774
|
+
if (raw === undefined || raw.trim() === "")
|
|
74775
|
+
return CANDLE_PERPS_WITHDRAW_PINS;
|
|
74776
|
+
let parsed;
|
|
74777
|
+
try {
|
|
74778
|
+
parsed = JSON.parse(raw);
|
|
74779
|
+
} catch {
|
|
74780
|
+
throw new TradingError("INVALID_PINS", "CANDLE_PERPS_WITHDRAW_PINS is not JSON.");
|
|
74781
|
+
}
|
|
74782
|
+
const p = parsed;
|
|
74783
|
+
if (typeof p !== "object" || p === null || typeof p.token !== "string" || typeof p.destinationDex !== "string" || !Array.isArray(p.depositories) || p.depositories.length === 0 || !p.depositories.every((d) => typeof d === "string" && /^0x[0-9a-fA-F]{40}$/.test(d)) || p.amountForm !== "canonical" && p.amountForm !== "fixed6") {
|
|
74784
|
+
throw new TradingError("INVALID_PINS", "CANDLE_PERPS_WITHDRAW_PINS must be {token, destinationDex, depositories: [0x...], amountForm: canonical|fixed6}.");
|
|
74785
|
+
}
|
|
74786
|
+
return {
|
|
74787
|
+
token: p.token,
|
|
74788
|
+
destinationDex: p.destinationDex,
|
|
74789
|
+
depositories: p.depositories.map((d) => d.toLowerCase()),
|
|
74790
|
+
amountForm: p.amountForm
|
|
74791
|
+
};
|
|
74792
|
+
}
|
|
74793
|
+
async function withdrawStatus(ctx, id) {
|
|
74794
|
+
const key = await tradingKey(ctx);
|
|
74795
|
+
const result = await perpsRequest(ctx, key, `/api/v1/agent/perps/withdraw/${encodeURIComponent(id)}`);
|
|
74796
|
+
if (ctx.json)
|
|
74797
|
+
return printTradingResult(ctx, result);
|
|
74798
|
+
const to = result.to ?? {};
|
|
74799
|
+
const relay = result.relay;
|
|
74800
|
+
ctx.deps.stdout.write(`Withdrawal ${safeText(id)}: ${safeText(result.status)}, ${safeText(result.amountUsdc)} USDC to ${safeText(to.address)} (${safeText(to.chain)}), fees ${safeText(result.feesUsdc)} USDC${result.failureReason ? `, ${safeText(result.failureReason)}` : ""}. Relay: ${relay ? safeText(relay.status) : "not read"}.
|
|
74801
|
+
`);
|
|
74802
|
+
return 0;
|
|
74803
|
+
}
|
|
74804
|
+
async function perpsWithdraw(args, ctx) {
|
|
74805
|
+
if (args[0] === "status") {
|
|
74806
|
+
if (args.length !== 2 || !ID_RE.test(args[1]))
|
|
74807
|
+
return usage5(ctx, USAGE5);
|
|
74808
|
+
try {
|
|
74809
|
+
return await withdrawStatus(ctx, args[1]);
|
|
74810
|
+
} catch (error) {
|
|
74811
|
+
return tradingFailure(ctx, error);
|
|
74812
|
+
}
|
|
74813
|
+
}
|
|
74814
|
+
if (args.includes("--yes") || args.includes("-y")) {
|
|
74815
|
+
return usage5(ctx, "candle perps withdraw has no --yes: every withdrawal is confirmed at the terminal.");
|
|
74816
|
+
}
|
|
74817
|
+
const parsed = parseArgs(args, {
|
|
74818
|
+
valueFlags: ["--to", "--wallet", "--id", "--keystore"],
|
|
74819
|
+
pathFlags: ["--keystore"]
|
|
74820
|
+
});
|
|
74821
|
+
if ("error" in parsed)
|
|
74822
|
+
return usage5(ctx, parsed.error);
|
|
74823
|
+
const [amount] = parsed.positionals;
|
|
74824
|
+
const to = parsed.values["--to"];
|
|
74825
|
+
if (parsed.positionals.length !== 1 || !amount || to !== "solana" && to !== "hood")
|
|
74826
|
+
return usage5(ctx, USAGE5);
|
|
74827
|
+
const amountMicros = parseWithdrawAmount(amount);
|
|
74828
|
+
if (amountMicros === null)
|
|
74829
|
+
return usage5(ctx, "The amount must be a positive USDC decimal with at most 6 decimals.");
|
|
74830
|
+
const id = parsed.values["--id"] ?? `withdraw-${randomUUID5()}`;
|
|
74831
|
+
if (!ID_RE.test(id))
|
|
74832
|
+
return usage5(ctx, "--id must be 1 to 128 letters, digits, '.', '_', ':' or '-'.");
|
|
74833
|
+
if (!refuseEnvPassphrase(ctx))
|
|
74834
|
+
return 1;
|
|
74835
|
+
if (!requireTty(ctx, "perps withdraw"))
|
|
74836
|
+
return 1;
|
|
74837
|
+
const resolvedVault = vaultPathFor(ctx, parsed);
|
|
74838
|
+
if ("error" in resolvedVault)
|
|
74839
|
+
return usage5(ctx, resolvedVault.error);
|
|
74840
|
+
try {
|
|
74841
|
+
const pins = withdrawPins(ctx.deps.env);
|
|
74842
|
+
if (pins === null) {
|
|
74843
|
+
throw new TradingError("PERPS_WITHDRAW_UNVERIFIED", "Hyperliquid withdrawals wait on a recorded verification of Relay's quote shape in this release. Nothing was built.");
|
|
74844
|
+
}
|
|
74845
|
+
const key = await tradingKey(ctx);
|
|
74846
|
+
const { wallet } = await perpsWallet(ctx, key, parsed.values["--wallet"], WITHDRAW_SCOPE);
|
|
74847
|
+
if (!wallet)
|
|
74848
|
+
throw new TradingError("TEE_WALLET_REQUIRED", "No EVM TEE wallet resolved.");
|
|
74849
|
+
const { rows } = await listTradingWallets(ctx, key);
|
|
74850
|
+
const ownDestinations = to === "hood" ? [wallet.address] : rows.filter((row) => row.chain === "solana").map((row) => row.address);
|
|
74851
|
+
if (ownDestinations.length === 0)
|
|
74852
|
+
throw new TradingError("TEE_WALLET_REQUIRED", "This key has no Solana TEE wallet, so --to solana has nowhere to land. Withdraw --to hood instead.");
|
|
74853
|
+
ctx.deps.stderr.write(`Withdrawal: ${id}
|
|
74854
|
+
`);
|
|
74855
|
+
const build = await perpsRequest(ctx, key, "/api/v1/agent/perps/withdraw", {
|
|
74856
|
+
clientWithdrawalId: id,
|
|
74857
|
+
amountUsdc: amount,
|
|
74858
|
+
to,
|
|
74859
|
+
wallet: wallet.id
|
|
74860
|
+
});
|
|
74861
|
+
if (build.status !== "quoted")
|
|
74862
|
+
return printTradingResult(ctx, build);
|
|
74863
|
+
const refused = (problem) => new TradingError("PERPS_BUILD_REFUSED", `Refused to sign this withdrawal: ${problem}. Nothing was signed.`);
|
|
74864
|
+
const mapping = verifyWithdrawMapping(build, {
|
|
74865
|
+
id,
|
|
74866
|
+
account: wallet.address,
|
|
74867
|
+
to,
|
|
74868
|
+
amountMicros,
|
|
74869
|
+
ownDestinations,
|
|
74870
|
+
pins
|
|
74871
|
+
});
|
|
74872
|
+
if (!mapping.ok)
|
|
74873
|
+
throw refused(mapping.reason);
|
|
74874
|
+
const recipient = await relayRecipientProblem(ctx.deps.fetch, build.relayRequestId, build.to.address);
|
|
74875
|
+
if (recipient)
|
|
74876
|
+
throw refused(recipient);
|
|
74877
|
+
const output = ctx.json ? ctx.deps.stderr : ctx.deps.stdout;
|
|
74878
|
+
output.write(`Withdraw ${formatWithdrawAmount(amountMicros)} USDC from Hyperliquid account ${safeText(wallet.address)} through Relay.
|
|
74879
|
+
`);
|
|
74880
|
+
output.write(`Relay fees ${safeText(build.feesUsdc)} USDC; you receive about ${safeText(build.netUsdc)} USDC, at least ${safeText(build.minimumOutUsdc)}.
|
|
74881
|
+
`);
|
|
74882
|
+
output.write(`To this key's ${to === "hood" ? "Hood" : "Solana"} TEE wallet:
|
|
74883
|
+
`);
|
|
74884
|
+
output.write(`to ${groupAddress(build.to.address)}
|
|
74885
|
+
`);
|
|
74886
|
+
const positions = await perpsRequest(ctx, key, `/api/v1/agent/perps/positions?walletId=${encodeURIComponent(wallet.id)}`).catch(() => null);
|
|
74887
|
+
if (Array.isArray(positions?.positions) && positions.positions.length > 0)
|
|
74888
|
+
output.write(`This account has open positions: withdrawing margin raises their liquidation risk.
|
|
74889
|
+
`);
|
|
74890
|
+
const raw = await requireVaultRaw(ctx, resolvedVault);
|
|
74891
|
+
const opened = await unlockInteractively(ctx, resolvedVault.path, raw, {
|
|
74892
|
+
promptText: "Vault passphrase (input hidden): ",
|
|
74893
|
+
reason: `withdraw ${formatWithdrawAmount(amountMicros)} USDC to ${build.to.address}`
|
|
74894
|
+
});
|
|
74895
|
+
closeVault(opened.vault);
|
|
74896
|
+
const typed = await ctx.deps.promptLine(`Type confirm to withdraw ${formatWithdrawAmount(amountMicros)} USDC to ${build.to.address}: `);
|
|
74897
|
+
if (typed.trim().toLowerCase() !== "confirm")
|
|
74898
|
+
throw new TradingError("CANCELLED", "The withdrawal was not confirmed; nothing was signed.");
|
|
74899
|
+
if (typeof build.expiresAt !== "number" || build.expiresAt <= ctx.deps.now())
|
|
74900
|
+
throw new TradingError("QUOTE_EXPIRED", "The quote expired before signing. Start again with a new --id.");
|
|
74901
|
+
const mappingSignature = await relaySignature(ctx, key, wallet, build.typedData);
|
|
74902
|
+
const authorization = await perpsRequest(ctx, key, "/api/v1/agent/perps/withdraw/authorize", {
|
|
74903
|
+
clientWithdrawalId: id,
|
|
74904
|
+
signature: mappingSignature
|
|
74905
|
+
});
|
|
74906
|
+
const send = verifyWithdrawSendAsset(authorization, { mappingNonce: build.nonce, amountMicros, pins });
|
|
74907
|
+
if (!send.ok)
|
|
74908
|
+
throw new TradingError("PERPS_BUILD_REFUSED", `Refused to sign the transfer: ${send.reason}. The nonce mapping was posted; nothing can move without this signature.`);
|
|
74909
|
+
const signature = await relaySignature(ctx, key, wallet, authorization.typedData);
|
|
74910
|
+
const exchangeBody = hyperliquidExchangeBody(authorization.action, authorization.nonce, signature);
|
|
74911
|
+
let exchange;
|
|
74912
|
+
try {
|
|
74913
|
+
const res = await ctx.deps.fetch(HYPERLIQUID_EXCHANGE_URLS.mainnet, {
|
|
74914
|
+
method: "POST",
|
|
74915
|
+
headers: { "content-type": "application/json" },
|
|
74916
|
+
body: JSON.stringify(exchangeBody)
|
|
74917
|
+
});
|
|
74918
|
+
const text = await res.text();
|
|
74919
|
+
if (!res.ok)
|
|
74920
|
+
throw new Error(`HTTP ${res.status}`);
|
|
74921
|
+
exchange = JSON.parse(text);
|
|
74922
|
+
} catch (error) {
|
|
74923
|
+
throw new TradingError("PERPS_SUBMIT_FAILED", `Signed, but the submit to Hyperliquid failed (${error instanceof Error ? error.message : "unknown"}). The signed transfer is in details.exchangeBody and stays valid for two days; check candle perps withdraw status ${id} before resubmitting.`, { exitCode: 3, details: { clientWithdrawalId: id, exchangeBody } });
|
|
74924
|
+
}
|
|
74925
|
+
await perpsRequest(ctx, key, "/api/v1/agent/perps/withdraw/submitted", {
|
|
74926
|
+
clientWithdrawalId: id,
|
|
74927
|
+
exchangeResponse: exchange
|
|
74928
|
+
}).catch(() => null);
|
|
74929
|
+
const refusal = venueRefusal(exchange);
|
|
74930
|
+
if (refusal)
|
|
74931
|
+
throw new TradingError("PERPS_VENUE_REFUSED", refusal, { details: { clientWithdrawalId: id, exchange } });
|
|
74932
|
+
ctx.deps.stderr.write(`Submitted to Hyperliquid; following Relay's fill.
|
|
74933
|
+
`);
|
|
74934
|
+
const deadline = ctx.deps.now() + WITHDRAW_POLL_MS;
|
|
74935
|
+
let status = await perpsRequest(ctx, key, `/api/v1/agent/perps/withdraw/${encodeURIComponent(id)}`);
|
|
74936
|
+
while (!TERMINAL.has(String(status.status)) && ctx.deps.now() < deadline) {
|
|
74937
|
+
await ctx.deps.sleep(WITHDRAW_POLL_INTERVAL_MS);
|
|
74938
|
+
status = await perpsRequest(ctx, key, `/api/v1/agent/perps/withdraw/${encodeURIComponent(id)}`);
|
|
74939
|
+
}
|
|
74940
|
+
if (!TERMINAL.has(String(status.status)))
|
|
74941
|
+
ctx.deps.stderr.write(`Still ${safeText(status.status)} after 10 minutes. Check it with candle perps withdraw status ${id}.
|
|
74942
|
+
`);
|
|
74943
|
+
return printTradingResult(ctx, { ...status, signature, exchange });
|
|
74944
|
+
} catch (error) {
|
|
74945
|
+
if (isVaultError(error))
|
|
74946
|
+
return writeVaultFailure(ctx, error);
|
|
74947
|
+
return tradingFailure(ctx, error);
|
|
74948
|
+
}
|
|
74949
|
+
}
|
|
74950
|
+
async function relaySignature(ctx, key, wallet, typedData) {
|
|
74951
|
+
const relay = await perpsRequest(ctx, key, `/api/v1/agent/wallets/${encodeURIComponent(wallet.id)}/sign`, typedDataAuthorization(wallet, typedData));
|
|
74952
|
+
if (typeof relay.signature !== "string" || !/^0x[0-9a-fA-F]{130}$/.test(relay.signature))
|
|
74953
|
+
throw new TradingError("INVALID_RESPONSE", "The relay did not return a signature.");
|
|
74954
|
+
return relay.signature;
|
|
74955
|
+
}
|
|
74956
|
+
|
|
74523
74957
|
// src/commands/plans.ts
|
|
74524
74958
|
init_args();
|
|
74525
74959
|
init_client();
|
|
@@ -74594,12 +75028,12 @@ function planPromotionLine(table) {
|
|
|
74594
75028
|
|
|
74595
75029
|
// src/commands/plans.ts
|
|
74596
75030
|
init_render();
|
|
74597
|
-
var
|
|
75031
|
+
var USAGE6 = "Usage: candle plans";
|
|
74598
75032
|
async function plans(args, ctx) {
|
|
74599
75033
|
const parsed = parseArgs(args, {});
|
|
74600
75034
|
if ("error" in parsed || parsed.positionals.length > 0) {
|
|
74601
75035
|
writeUsageFailure(ctx.deps, "error" in parsed ? `${parsed.error}
|
|
74602
|
-
${
|
|
75036
|
+
${USAGE6}` : USAGE6, ctx.json);
|
|
74603
75037
|
return 2;
|
|
74604
75038
|
}
|
|
74605
75039
|
const result = await apiRequest("/api/v1/agent/plans", {
|
|
@@ -74838,20 +75272,20 @@ async function writeSecretNames(deps, profile, names) {
|
|
|
74838
75272
|
else
|
|
74839
75273
|
await deps.updateProfile(profile, { secretNames: sorted });
|
|
74840
75274
|
}
|
|
74841
|
-
function
|
|
75275
|
+
function usage6(ctx, line) {
|
|
74842
75276
|
writeUsageFailure(ctx.deps, line, ctx.json);
|
|
74843
75277
|
return 2;
|
|
74844
75278
|
}
|
|
74845
75279
|
async function secretsSet(args, ctx) {
|
|
74846
75280
|
const parsed = parseArgs(args, {});
|
|
74847
75281
|
if ("error" in parsed)
|
|
74848
|
-
return
|
|
75282
|
+
return usage6(ctx, parsed.error);
|
|
74849
75283
|
const [raw, extra] = parsed.positionals;
|
|
74850
75284
|
if (!raw || extra !== undefined)
|
|
74851
|
-
return
|
|
75285
|
+
return usage6(ctx, "Usage: candle secrets set <name>");
|
|
74852
75286
|
const name2 = canonicalSecretName(raw);
|
|
74853
75287
|
if (name2 === undefined) {
|
|
74854
|
-
return
|
|
75288
|
+
return usage6(ctx, `A secret name is letters, digits and underscores, starting with a letter: ${raw}`);
|
|
74855
75289
|
}
|
|
74856
75290
|
if (!ctx.deps.isTTY.stdin || !ctx.deps.isTTY.stdout) {
|
|
74857
75291
|
writeLocalFailure(ctx.deps, {
|
|
@@ -74863,7 +75297,7 @@ async function secretsSet(args, ctx) {
|
|
|
74863
75297
|
}
|
|
74864
75298
|
const value = await ctx.deps.promptSecret(`Value for ${name2} (input hidden): `);
|
|
74865
75299
|
if (value.length === 0)
|
|
74866
|
-
return
|
|
75300
|
+
return usage6(ctx, "An empty value was typed; nothing was stored.");
|
|
74867
75301
|
try {
|
|
74868
75302
|
await ctx.deps.secretsStore.set(secretRef(ctx.profile, name2), value);
|
|
74869
75303
|
} catch (error) {
|
|
@@ -74883,9 +75317,9 @@ async function secretsSet(args, ctx) {
|
|
|
74883
75317
|
async function secretsList(args, ctx) {
|
|
74884
75318
|
const parsed = parseArgs(args, {});
|
|
74885
75319
|
if ("error" in parsed)
|
|
74886
|
-
return
|
|
75320
|
+
return usage6(ctx, parsed.error);
|
|
74887
75321
|
if (parsed.positionals.length > 0)
|
|
74888
|
-
return
|
|
75322
|
+
return usage6(ctx, `Unexpected argument: ${parsed.positionals[0]}`);
|
|
74889
75323
|
const names = await storedSecretNames(ctx.deps, ctx.profile);
|
|
74890
75324
|
if (ctx.json) {
|
|
74891
75325
|
ctx.deps.stdout.write(`${JSON.stringify({ ok: true, names, backend: ctx.deps.backend })}
|
|
@@ -74904,13 +75338,13 @@ async function secretsList(args, ctx) {
|
|
|
74904
75338
|
async function secretsRemove(args, ctx) {
|
|
74905
75339
|
const parsed = parseArgs(args, {});
|
|
74906
75340
|
if ("error" in parsed)
|
|
74907
|
-
return
|
|
75341
|
+
return usage6(ctx, parsed.error);
|
|
74908
75342
|
const [raw, extra] = parsed.positionals;
|
|
74909
75343
|
if (!raw || extra !== undefined)
|
|
74910
|
-
return
|
|
75344
|
+
return usage6(ctx, "Usage: candle secrets remove <name>");
|
|
74911
75345
|
const name2 = canonicalSecretName(raw);
|
|
74912
75346
|
if (name2 === undefined) {
|
|
74913
|
-
return
|
|
75347
|
+
return usage6(ctx, `A secret name is letters, digits and underscores, starting with a letter: ${raw}`);
|
|
74914
75348
|
}
|
|
74915
75349
|
await ctx.deps.secretsStore.delete(secretRef(ctx.profile, name2));
|
|
74916
75350
|
const names = await storedSecretNames(ctx.deps, ctx.profile);
|
|
@@ -77064,7 +77498,7 @@ init_args();
|
|
|
77064
77498
|
init_render();
|
|
77065
77499
|
init_trading();
|
|
77066
77500
|
init_swap();
|
|
77067
|
-
import { randomUUID as
|
|
77501
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
77068
77502
|
var withdrawal = exports_external.object({ withdrawal: exports_external.string(), amountRaw: exports_external.string(), endTime: exports_external.string(), claimable: exports_external.boolean() }).passthrough();
|
|
77069
77503
|
var positionsSchema = exports_external.object({
|
|
77070
77504
|
success: exports_external.literal(true),
|
|
@@ -77113,16 +77547,16 @@ async function runStake(action, args, ctx) {
|
|
|
77113
77547
|
valueFlags: ["--wallet", "--client-stake-id"],
|
|
77114
77548
|
booleanFlags: ["--yes", ...action === "claim" ? ["--all"] : []]
|
|
77115
77549
|
});
|
|
77116
|
-
const
|
|
77550
|
+
const usage7 = "Usage: candle stake [add <amount> | remove <amount> | finish [<withdrawal>] | claim <rewardId> | claim --all] [--wallet <main|id|address|label>] [--client-stake-id <id>] [--yes] [--json]";
|
|
77117
77551
|
if ("error" in parsed) {
|
|
77118
77552
|
writeUsageFailure(ctx.deps, parsed.error, ctx.json);
|
|
77119
77553
|
return 2;
|
|
77120
77554
|
}
|
|
77121
77555
|
const { values, positionals, booleans } = parsed;
|
|
77122
|
-
const id = values["--client-stake-id"] ?? `stake-${
|
|
77556
|
+
const id = values["--client-stake-id"] ?? `stake-${randomUUID6()}`;
|
|
77123
77557
|
const all = booleans.has("--all");
|
|
77124
77558
|
if (!validClientId(id) || id.length > 128 || (action === "positions" ? positionals.length !== 0 : action === "finish" ? positionals.length > 1 : action === "claim" ? all ? positionals.length !== 0 : positionals.length !== 1 : positionals.length !== 1)) {
|
|
77125
|
-
writeUsageFailure(ctx.deps,
|
|
77559
|
+
writeUsageFailure(ctx.deps, usage7, ctx.json);
|
|
77126
77560
|
return 2;
|
|
77127
77561
|
}
|
|
77128
77562
|
try {
|
|
@@ -77904,7 +78338,7 @@ init_render();
|
|
|
77904
78338
|
init_solana_verify();
|
|
77905
78339
|
init_trading();
|
|
77906
78340
|
init_swap();
|
|
77907
|
-
var
|
|
78341
|
+
var USAGE7 = "Usage: candle transfer --to <address|wallet name|vault> --asset <SOL|USDC|CNDL|ETH|USDG>|--mint <mint>|--token <0x...> --amount <decimal|max> [--wallet <name>] [--rpc-url <url>] [--yes] [--json]";
|
|
77908
78342
|
var TRANSFER_ASSETS = [...Object.keys(BASES), ...Object.keys(HOOD_BASES)];
|
|
77909
78343
|
var BASE58_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
77910
78344
|
async function readLinkedWallets(ctx, key) {
|
|
@@ -77990,7 +78424,7 @@ async function transfer(args, ctx) {
|
|
|
77990
78424
|
const flags = parsed.values;
|
|
77991
78425
|
const asset = flags["--asset"]?.toUpperCase();
|
|
77992
78426
|
if (parsed.positionals.length !== 0 || !flags["--to"] || !flags["--amount"] || [flags["--asset"], flags["--mint"], flags["--token"]].filter(Boolean).length !== 1 || asset !== undefined && !TRANSFER_ASSETS.includes(asset) || flags["--mint"] !== undefined && !BASE58_ADDRESS.test(flags["--mint"]) || flags["--token"] !== undefined && !checkEvmAddress(flags["--token"]).ok) {
|
|
77993
|
-
writeUsageFailure(ctx.deps,
|
|
78427
|
+
writeUsageFailure(ctx.deps, USAGE7, ctx.json);
|
|
77994
78428
|
return 2;
|
|
77995
78429
|
}
|
|
77996
78430
|
const to = flags["--to"];
|
|
@@ -81225,9 +81659,11 @@ async function planTransfer(input) {
|
|
|
81225
81659
|
const destination = ataFor(profile, toKey);
|
|
81226
81660
|
const instructions = [];
|
|
81227
81661
|
const accountCreationLines = [];
|
|
81662
|
+
let accountRent;
|
|
81228
81663
|
const destinationState = await tokenAccountFrozen(rpc, encodePubkey(destination));
|
|
81229
81664
|
if (!destinationState.exists) {
|
|
81230
|
-
const rent = await rpc.getMinimumBalanceForRentExemption(
|
|
81665
|
+
const rent = await rpc.getMinimumBalanceForRentExemption(profile.associatedTokenAccountSize);
|
|
81666
|
+
accountRent = rent;
|
|
81231
81667
|
instructions.push(createAssociatedTokenAccountIdempotent({ payer: fromKey, owner: toKey, mint, tokenProgram }));
|
|
81232
81668
|
accountCreationLines.push(`create associated token account ${encodePubkey(destination)} (idempotent)`, `account owner ${input.to}`, `account rent ${rent} lamports, paid by ${input.from} if created`);
|
|
81233
81669
|
}
|
|
@@ -81263,6 +81699,8 @@ async function planTransfer(input) {
|
|
|
81263
81699
|
from: input.from,
|
|
81264
81700
|
to: input.to,
|
|
81265
81701
|
instructions,
|
|
81702
|
+
sourceTokenAccount: encodePubkey(source),
|
|
81703
|
+
...accountRent !== undefined ? { accountRent } : {},
|
|
81266
81704
|
token: {
|
|
81267
81705
|
profile,
|
|
81268
81706
|
extraAccountsMissing: !hook.ok,
|
|
@@ -81293,6 +81731,45 @@ async function quoteTransferFee(rpc, from, instructions) {
|
|
|
81293
81731
|
});
|
|
81294
81732
|
return rpc.getFeeForMessage(toBase642(message));
|
|
81295
81733
|
}
|
|
81734
|
+
var FALLBACK_FEE_LAMPORTS = 5000n;
|
|
81735
|
+
var TOKEN_ACCOUNT_AMOUNT_OFFSET = 64;
|
|
81736
|
+
function tokenAccountAmount(data) {
|
|
81737
|
+
if (data.length < TOKEN_ACCOUNT_AMOUNT_OFFSET + 8)
|
|
81738
|
+
return 0n;
|
|
81739
|
+
return new DataView(data.buffer, data.byteOffset + TOKEN_ACCOUNT_AMOUNT_OFFSET, 8).getBigUint64(0, true);
|
|
81740
|
+
}
|
|
81741
|
+
async function assertTransferAffordable(solana, plan, feeQuote) {
|
|
81742
|
+
const fee = feeQuote ?? FALLBACK_FEE_LAMPORTS;
|
|
81743
|
+
const rent = plan.accountRent ?? 0n;
|
|
81744
|
+
const lamportsNeeded = (plan.asset === "SOL" ? plan.amountRaw : 0n) + fee + rent;
|
|
81745
|
+
const lamports = await solana.read(() => solana.rpc.getBalance(plan.from));
|
|
81746
|
+
if (plan.sourceTokenAccount !== undefined) {
|
|
81747
|
+
const account = await solana.read(() => solana.rpc.getAccountInfo(plan.sourceTokenAccount));
|
|
81748
|
+
const held = account === null ? 0n : tokenAccountAmount(account.data);
|
|
81749
|
+
if (held < plan.amountRaw) {
|
|
81750
|
+
throw new VaultError("TRANSFER_INSUFFICIENT_FUNDS", `${plan.from} holds ${held} raw of ${plan.asset} and this transfer moves ${plan.amountRaw} raw (${plan.amount}). Nothing was signed or sent.`, {
|
|
81751
|
+
suggestion: "Fund the key with this token first, or send a smaller --amount.",
|
|
81752
|
+
details: {
|
|
81753
|
+
from: plan.from,
|
|
81754
|
+
asset: plan.asset,
|
|
81755
|
+
heldRaw: held.toString(),
|
|
81756
|
+
neededRaw: plan.amountRaw.toString()
|
|
81757
|
+
}
|
|
81758
|
+
});
|
|
81759
|
+
}
|
|
81760
|
+
}
|
|
81761
|
+
if (lamports < lamportsNeeded) {
|
|
81762
|
+
const parts = [
|
|
81763
|
+
...plan.asset === "SOL" ? [`${plan.amountRaw} to send`] : [],
|
|
81764
|
+
`${fee} fee${feeQuote === null ? " (estimated)" : ""}`,
|
|
81765
|
+
...rent > 0n ? [`${rent} token account rent`] : []
|
|
81766
|
+
];
|
|
81767
|
+
throw new VaultError("TRANSFER_INSUFFICIENT_FUNDS", `${plan.from} holds ${lamports} lamports and this transfer needs ${lamportsNeeded} (${parts.join(" + ")}). Nothing was signed or sent.`, {
|
|
81768
|
+
suggestion: "Fund the key with SOL first, or send a smaller --amount. candle vault list --balances shows each key's SOL.",
|
|
81769
|
+
details: { from: plan.from, heldLamports: lamports.toString(), neededLamports: lamportsNeeded.toString() }
|
|
81770
|
+
});
|
|
81771
|
+
}
|
|
81772
|
+
}
|
|
81296
81773
|
function displayTransferPlan(ctx, plan, feeQuote) {
|
|
81297
81774
|
ctx.deps.stdout.write(`Decoded transfer (local signing only):
|
|
81298
81775
|
`);
|
|
@@ -81327,8 +81804,9 @@ async function signAndBroadcastTransfer(input) {
|
|
|
81327
81804
|
} catch (error) {
|
|
81328
81805
|
if (isRateLimited(error))
|
|
81329
81806
|
notePostSignatureRateLimit(input.ctx, sigB58);
|
|
81330
|
-
return { signature: sigB58, finalized: false };
|
|
81807
|
+
return { signature: sigB58, finalized: false, seen: true };
|
|
81331
81808
|
}
|
|
81809
|
+
let seen = false;
|
|
81332
81810
|
for (let i = 0;i < 30; i++) {
|
|
81333
81811
|
await input.ctx.deps.sleep(500);
|
|
81334
81812
|
let status;
|
|
@@ -81337,17 +81815,24 @@ async function signAndBroadcastTransfer(input) {
|
|
|
81337
81815
|
} catch (error) {
|
|
81338
81816
|
if (isRateLimited(error))
|
|
81339
81817
|
notePostSignatureRateLimit(input.ctx, sigB58);
|
|
81340
|
-
return { signature: sigB58, finalized: false };
|
|
81818
|
+
return { signature: sigB58, finalized: false, seen: true };
|
|
81341
81819
|
}
|
|
81820
|
+
if (status)
|
|
81821
|
+
seen = true;
|
|
81342
81822
|
if (status?.confirmationStatus === "finalized") {
|
|
81343
81823
|
if (status.err) {
|
|
81344
81824
|
const named = namedSendFailure(input.plan);
|
|
81345
81825
|
throw new VaultError("VAULT_WRITE_FAILED", `Transfer failed on chain${named ? ` (${named})` : ""}. Any pending funding receipt will be reconciled on a rerun.`);
|
|
81346
81826
|
}
|
|
81347
|
-
return { signature: sigB58, finalized: true };
|
|
81827
|
+
return { signature: sigB58, finalized: true, seen: true };
|
|
81348
81828
|
}
|
|
81349
81829
|
}
|
|
81350
|
-
return { signature: sigB58, finalized: false };
|
|
81830
|
+
return { signature: sigB58, finalized: false, seen };
|
|
81831
|
+
}
|
|
81832
|
+
function unfinalizedSendLine(result, suffix = "") {
|
|
81833
|
+
return result.seen ? `Submitted ${result.signature}; finality is not yet confirmed.${suffix}
|
|
81834
|
+
` : `Sent ${result.signature}, but no status read found it on the network: it was most likely dropped and nothing moved. Check the signature in an explorer before sending again.${suffix}
|
|
81835
|
+
`;
|
|
81351
81836
|
}
|
|
81352
81837
|
|
|
81353
81838
|
// src/commands/vault-fund.ts
|
|
@@ -81386,6 +81871,7 @@ async function fundExternal(ctx, vault, external2, input) {
|
|
|
81386
81871
|
}));
|
|
81387
81872
|
const feeQuote = await solana.read(() => quoteTransferFee(solana.rpc, plan.from, plan.instructions));
|
|
81388
81873
|
displayTransferPlan(ctx, plan, feeQuote);
|
|
81874
|
+
await assertTransferAffordable(solana, plan, feeQuote);
|
|
81389
81875
|
await confirmSend(ctx, { amount: plan.amount, asset: plan.asset, address: external2.address });
|
|
81390
81876
|
const secret = await decryptKey(vault, fromEntry.id);
|
|
81391
81877
|
try {
|
|
@@ -81399,12 +81885,12 @@ async function fundExternal(ctx, vault, external2, input) {
|
|
|
81399
81885
|
from: plan.from,
|
|
81400
81886
|
amount: plan.amount,
|
|
81401
81887
|
asset: plan.asset,
|
|
81402
|
-
finalized: result.finalized
|
|
81888
|
+
finalized: result.finalized,
|
|
81889
|
+
...result.finalized ? {} : { seen: result.seen }
|
|
81403
81890
|
});
|
|
81404
81891
|
} else {
|
|
81405
81892
|
ctx.deps.stdout.write(result.finalized ? `Funded ${external2.label} (${external2.address}) with ${plan.amount} ${plan.asset}: ${result.signature}
|
|
81406
|
-
` :
|
|
81407
|
-
`);
|
|
81893
|
+
` : unfinalizedSendLine(result));
|
|
81408
81894
|
}
|
|
81409
81895
|
return result.finalized ? 0 : 3;
|
|
81410
81896
|
} finally {
|
|
@@ -81501,6 +81987,7 @@ async function vaultFund(args, ctx) {
|
|
|
81501
81987
|
}));
|
|
81502
81988
|
const feeQuote = await solana.read(() => quoteTransferFee(solana.rpc, plan.from, plan.instructions));
|
|
81503
81989
|
displayTransferPlan(ctx, plan, feeQuote);
|
|
81990
|
+
await assertTransferAffordable(solana, plan, feeQuote);
|
|
81504
81991
|
await confirmSend(ctx, { amount: plan.amount, asset: plan.asset, address: teeAddress });
|
|
81505
81992
|
const secret = await decryptKey(vault, fromEntry.id);
|
|
81506
81993
|
try {
|
|
@@ -81535,12 +82022,12 @@ async function vaultFund(args, ctx) {
|
|
|
81535
82022
|
from: plan.from,
|
|
81536
82023
|
amount: plan.amount,
|
|
81537
82024
|
asset: plan.asset,
|
|
81538
|
-
finalized: result.finalized
|
|
82025
|
+
finalized: result.finalized,
|
|
82026
|
+
...result.finalized ? {} : { seen: result.seen }
|
|
81539
82027
|
});
|
|
81540
82028
|
} else {
|
|
81541
82029
|
ctx.deps.stdout.write(result.finalized ? `Funded ${teeAddress} with ${plan.amount} ${plan.asset}: ${result.signature}
|
|
81542
|
-
` :
|
|
81543
|
-
`);
|
|
82030
|
+
` : unfinalizedSendLine(result, " Receipt recorded."));
|
|
81544
82031
|
}
|
|
81545
82032
|
return result.finalized ? 0 : 3;
|
|
81546
82033
|
} finally {
|
|
@@ -85350,8 +85837,7 @@ async function vaultStatus(args, ctx) {
|
|
|
85350
85837
|
return usage(ctx, resolvedVault.error);
|
|
85351
85838
|
const path = resolvedVault.path;
|
|
85352
85839
|
const unlock = parsed.booleans.has("--unlock");
|
|
85353
|
-
|
|
85354
|
-
return 1;
|
|
85840
|
+
const unlockCtx = refusePromptsWithoutTerminal(ctx, "vault status --unlock");
|
|
85355
85841
|
return runVaultCommand(ctx, async ({ hold }) => {
|
|
85356
85842
|
const raw = await readVaultRaw(path);
|
|
85357
85843
|
if (raw === null)
|
|
@@ -85368,7 +85854,9 @@ async function vaultStatus(args, ctx) {
|
|
|
85368
85854
|
const backup = sidecar ? describeLastBackup(sidecar, file.envelopes) : undefined;
|
|
85369
85855
|
let unlocked;
|
|
85370
85856
|
if (unlock) {
|
|
85371
|
-
const vault = hold((await unlockInteractively(
|
|
85857
|
+
const vault = hold((await unlockInteractively(unlockCtx, path, raw, {
|
|
85858
|
+
acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
|
|
85859
|
+
})).vault);
|
|
85372
85860
|
unlocked = {
|
|
85373
85861
|
entries: vault.index.entries.map(describeEntry),
|
|
85374
85862
|
duplicateLabels: duplicateLabels(vault.index).map((duplicate) => ({
|
|
@@ -85794,6 +86282,7 @@ async function vaultTransfer(args, ctx) {
|
|
|
85794
86282
|
}));
|
|
85795
86283
|
const feeQuote = await solana.read(() => quoteTransferFee(solana.rpc, plan.from, plan.instructions));
|
|
85796
86284
|
displayTransferPlan(ctx, plan, feeQuote);
|
|
86285
|
+
await assertTransferAffordable(solana, plan, feeQuote);
|
|
85797
86286
|
if (promoted) {
|
|
85798
86287
|
const notice = serverStateNotice(await readTeeServerState(ctx, fromEntry, apiKey));
|
|
85799
86288
|
if (notice !== undefined)
|
|
@@ -85822,12 +86311,12 @@ async function vaultTransfer(args, ctx) {
|
|
|
85822
86311
|
asset: plan.asset,
|
|
85823
86312
|
amountRaw: plan.amountRaw.toString(),
|
|
85824
86313
|
finalized: result.finalized,
|
|
86314
|
+
...result.finalized ? {} : { seen: result.seen },
|
|
85825
86315
|
...activityReport !== undefined ? { activityReport } : {}
|
|
85826
86316
|
});
|
|
85827
86317
|
} else {
|
|
85828
86318
|
ctx.deps.stdout.write(result.finalized ? `Transferred ${plan.amount} ${plan.asset} to ${to}: ${result.signature}
|
|
85829
|
-
` :
|
|
85830
|
-
`);
|
|
86319
|
+
` : unfinalizedSendLine(result));
|
|
85831
86320
|
}
|
|
85832
86321
|
return result.finalized ? 0 : 3;
|
|
85833
86322
|
} finally {
|
|
@@ -85841,7 +86330,7 @@ init_args();
|
|
|
85841
86330
|
init_release();
|
|
85842
86331
|
import { dirname as dirname10, join as join11 } from "node:path";
|
|
85843
86332
|
init_render();
|
|
85844
|
-
var
|
|
86333
|
+
var USAGE8 = "Usage: candle verify <file> --bundle <path> [--identity <uri>] [--issuer <url>]";
|
|
85845
86334
|
async function resolveIdentity(deps, bundlePath, flag) {
|
|
85846
86335
|
if (flag)
|
|
85847
86336
|
return { kind: "ok", uri: flag, provenance: "identity from --identity" };
|
|
@@ -85873,19 +86362,19 @@ async function verify(args, ctx) {
|
|
|
85873
86362
|
});
|
|
85874
86363
|
if ("error" in parsed) {
|
|
85875
86364
|
writeUsageFailure(deps, `${parsed.error}
|
|
85876
|
-
${
|
|
86365
|
+
${USAGE8}`, json);
|
|
85877
86366
|
return 2;
|
|
85878
86367
|
}
|
|
85879
86368
|
const file = parsed.positionals[0];
|
|
85880
86369
|
if (parsed.positionals.length !== 1 || file === undefined) {
|
|
85881
86370
|
writeUsageFailure(deps, `verify takes exactly one file.
|
|
85882
|
-
${
|
|
86371
|
+
${USAGE8}`, json);
|
|
85883
86372
|
return 2;
|
|
85884
86373
|
}
|
|
85885
86374
|
const bundlePath = parsed.values["--bundle"];
|
|
85886
86375
|
if (!bundlePath) {
|
|
85887
86376
|
writeUsageFailure(deps, `--bundle is required.
|
|
85888
|
-
${
|
|
86377
|
+
${USAGE8}`, json);
|
|
85889
86378
|
return 2;
|
|
85890
86379
|
}
|
|
85891
86380
|
const resolved = await resolveIdentity(deps, bundlePath, parsed.values["--identity"]);
|
|
@@ -85899,7 +86388,7 @@ ${USAGE7}`, json);
|
|
|
85899
86388
|
}
|
|
85900
86389
|
if (resolved.kind === "absent") {
|
|
85901
86390
|
writeUsageFailure(deps, `--identity is required: there is no latest.json beside ${bundlePath} to take the release version from.
|
|
85902
|
-
${
|
|
86391
|
+
${USAGE8}`, json);
|
|
85903
86392
|
return 2;
|
|
85904
86393
|
}
|
|
85905
86394
|
const identity = resolved.uri;
|
|
@@ -86008,7 +86497,7 @@ function writeFailure2(ctx, result) {
|
|
|
86008
86497
|
}
|
|
86009
86498
|
async function setAllowLaunch(args, ctx, enabled) {
|
|
86010
86499
|
const { deps, apiUrl, json } = ctx;
|
|
86011
|
-
const
|
|
86500
|
+
const usage7 = enabled ? USAGE_ALLOW : USAGE_DISALLOW;
|
|
86012
86501
|
const command = enabled ? "wallets allow-launch" : "wallets disallow-launch";
|
|
86013
86502
|
const parsed = parseArgs(args, enabled ? {} : { booleanFlags: ["--yes"] });
|
|
86014
86503
|
if ("error" in parsed) {
|
|
@@ -86016,7 +86505,7 @@ async function setAllowLaunch(args, ctx, enabled) {
|
|
|
86016
86505
|
return 2;
|
|
86017
86506
|
}
|
|
86018
86507
|
if (parsed.positionals.length === 0) {
|
|
86019
|
-
writeUsageFailure(deps, `Name at least one wallet. ${
|
|
86508
|
+
writeUsageFailure(deps, `Name at least one wallet. ${usage7}`, json);
|
|
86020
86509
|
return 2;
|
|
86021
86510
|
}
|
|
86022
86511
|
const skipConfirm = !enabled && parsed.booleans.has("--yes");
|
|
@@ -86119,8 +86608,8 @@ init_args();
|
|
|
86119
86608
|
init_render();
|
|
86120
86609
|
init_trading();
|
|
86121
86610
|
init_swap();
|
|
86122
|
-
import { randomUUID as
|
|
86123
|
-
var
|
|
86611
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
86612
|
+
var USAGE9 = "Usage: candle wallet close-empty [--wallet embedded] [--keep <mint>]... [--client-trade-id <id>] [--yes] [--json]";
|
|
86124
86613
|
var BASE58_ADDRESS2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
86125
86614
|
var LISTED_ROWS = 50;
|
|
86126
86615
|
function splitKeep(args) {
|
|
@@ -86195,7 +86684,7 @@ async function embeddedSolana(ctx, key, wallet) {
|
|
|
86195
86684
|
async function walletsCloseEmpty(args, ctx) {
|
|
86196
86685
|
const lifted = splitKeep(args);
|
|
86197
86686
|
if ("error" in lifted) {
|
|
86198
|
-
writeUsageFailure(ctx.deps, `${lifted.error}. ${
|
|
86687
|
+
writeUsageFailure(ctx.deps, `${lifted.error}. ${USAGE9}`, ctx.json);
|
|
86199
86688
|
return 2;
|
|
86200
86689
|
}
|
|
86201
86690
|
const parsed = parseArgs(lifted.rest, { valueFlags: ["--wallet", "--client-trade-id"], booleanFlags: ["--yes"] });
|
|
@@ -86204,9 +86693,9 @@ async function walletsCloseEmpty(args, ctx) {
|
|
|
86204
86693
|
return 2;
|
|
86205
86694
|
}
|
|
86206
86695
|
const named = parsed.values["--client-trade-id"];
|
|
86207
|
-
const id = named ?? `close-${
|
|
86696
|
+
const id = named ?? `close-${randomUUID7()}`;
|
|
86208
86697
|
if (parsed.positionals.length !== 0 || !validClientId(id) || !lifted.keep.every((m) => BASE58_ADDRESS2.test(m))) {
|
|
86209
|
-
writeUsageFailure(ctx.deps,
|
|
86698
|
+
writeUsageFailure(ctx.deps, USAGE9, ctx.json);
|
|
86210
86699
|
return 2;
|
|
86211
86700
|
}
|
|
86212
86701
|
const keep = lifted.keep;
|
|
@@ -86335,7 +86824,7 @@ function trustTable(rows) {
|
|
|
86335
86824
|
}
|
|
86336
86825
|
async function setTrust(args, ctx, trusted) {
|
|
86337
86826
|
const { deps, apiUrl, json } = ctx;
|
|
86338
|
-
const
|
|
86827
|
+
const usage7 = trusted ? USAGE_TRUST : USAGE_UNTRUST;
|
|
86339
86828
|
const command = trusted ? "wallets trust" : "wallets untrust";
|
|
86340
86829
|
const parsed = parseArgs(args, trusted ? {} : { booleanFlags: ["--yes"] });
|
|
86341
86830
|
if ("error" in parsed) {
|
|
@@ -86343,7 +86832,7 @@ async function setTrust(args, ctx, trusted) {
|
|
|
86343
86832
|
return 2;
|
|
86344
86833
|
}
|
|
86345
86834
|
if (parsed.positionals.length === 0) {
|
|
86346
|
-
writeUsageFailure(deps, `Name at least one wallet. ${
|
|
86835
|
+
writeUsageFailure(deps, `Name at least one wallet. ${usage7}`, json);
|
|
86347
86836
|
return 2;
|
|
86348
86837
|
}
|
|
86349
86838
|
const skipConfirm = !trusted && parsed.booleans.has("--yes");
|
|
@@ -86630,7 +87119,8 @@ var COMMANDS = {
|
|
|
86630
87119
|
cancel: perpsCancel,
|
|
86631
87120
|
orders: perpsOrders,
|
|
86632
87121
|
positions: perpsPositions,
|
|
86633
|
-
leverage: perpsLeverage
|
|
87122
|
+
leverage: perpsLeverage,
|
|
87123
|
+
withdraw: perpsWithdraw
|
|
86634
87124
|
}
|
|
86635
87125
|
},
|
|
86636
87126
|
auth: { subcommands: { login: authLogin, status: authStatus, logout: authLogout } },
|
package/package.json
CHANGED