@candledottv/cli 0.11.14 → 0.11.15
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 +75 -26
- package/dist/index.js +830 -75
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42842,20 +42842,20 @@ var require_resolve = __commonJS((exports) => {
|
|
|
42842
42842
|
return false;
|
|
42843
42843
|
}
|
|
42844
42844
|
function countKeys(schema) {
|
|
42845
|
-
let
|
|
42845
|
+
let count2 = 0;
|
|
42846
42846
|
for (const key in schema) {
|
|
42847
42847
|
if (key === "$ref")
|
|
42848
42848
|
return Infinity;
|
|
42849
|
-
|
|
42849
|
+
count2++;
|
|
42850
42850
|
if (SIMPLE_INLINED.has(key))
|
|
42851
42851
|
continue;
|
|
42852
42852
|
if (typeof schema[key] == "object") {
|
|
42853
|
-
(0, util_1.eachItem)(schema[key], (sch) =>
|
|
42853
|
+
(0, util_1.eachItem)(schema[key], (sch) => count2 += countKeys(sch));
|
|
42854
42854
|
}
|
|
42855
|
-
if (
|
|
42855
|
+
if (count2 === Infinity)
|
|
42856
42856
|
return Infinity;
|
|
42857
42857
|
}
|
|
42858
|
-
return
|
|
42858
|
+
return count2;
|
|
42859
42859
|
}
|
|
42860
42860
|
function getFullPath(resolver, id = "", normalize4) {
|
|
42861
42861
|
if (normalize4 !== false)
|
|
@@ -45910,8 +45910,8 @@ var require_contains = __commonJS((exports) => {
|
|
|
45910
45910
|
cxt.result(valid, () => cxt.reset());
|
|
45911
45911
|
function validateItemsWithCount() {
|
|
45912
45912
|
const schValid = gen2.name("_valid");
|
|
45913
|
-
const
|
|
45914
|
-
validateItems(schValid, () => gen2.if(schValid, () => checkLimits(
|
|
45913
|
+
const count2 = gen2.let("count", 0);
|
|
45914
|
+
validateItems(schValid, () => gen2.if(schValid, () => checkLimits(count2)));
|
|
45915
45915
|
}
|
|
45916
45916
|
function validateItems(_valid, block2) {
|
|
45917
45917
|
gen2.forRange("i", 0, len, (i) => {
|
|
@@ -45924,16 +45924,16 @@ var require_contains = __commonJS((exports) => {
|
|
|
45924
45924
|
block2();
|
|
45925
45925
|
});
|
|
45926
45926
|
}
|
|
45927
|
-
function checkLimits(
|
|
45928
|
-
gen2.code((0, codegen_1._)`${
|
|
45927
|
+
function checkLimits(count2) {
|
|
45928
|
+
gen2.code((0, codegen_1._)`${count2}++`);
|
|
45929
45929
|
if (max === undefined) {
|
|
45930
|
-
gen2.if((0, codegen_1._)`${
|
|
45930
|
+
gen2.if((0, codegen_1._)`${count2} >= ${min}`, () => gen2.assign(valid, true).break());
|
|
45931
45931
|
} else {
|
|
45932
|
-
gen2.if((0, codegen_1._)`${
|
|
45932
|
+
gen2.if((0, codegen_1._)`${count2} > ${max}`, () => gen2.assign(valid, false).break());
|
|
45933
45933
|
if (min === 1)
|
|
45934
45934
|
gen2.assign(valid, true);
|
|
45935
45935
|
else
|
|
45936
|
-
gen2.if((0, codegen_1._)`${
|
|
45936
|
+
gen2.if((0, codegen_1._)`${count2} >= ${min}`, () => gen2.assign(valid, true));
|
|
45937
45937
|
}
|
|
45938
45938
|
}
|
|
45939
45939
|
}
|
|
@@ -49845,7 +49845,7 @@ var init_convert = __esm(() => {
|
|
|
49845
49845
|
});
|
|
49846
49846
|
|
|
49847
49847
|
// ../mcp/src/version.ts
|
|
49848
|
-
var SERVER_VERSION = "0.10.
|
|
49848
|
+
var SERVER_VERSION = "0.10.6";
|
|
49849
49849
|
|
|
49850
49850
|
// ../mcp/src/update-notice.ts
|
|
49851
49851
|
function newer(a, b) {
|
|
@@ -49881,7 +49881,7 @@ var init_update_notice = __esm(() => {
|
|
|
49881
49881
|
});
|
|
49882
49882
|
|
|
49883
49883
|
// ../mcp/src/orchestrate.ts
|
|
49884
|
-
import { randomUUID as
|
|
49884
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
49885
49885
|
function requireApiKey(cfg) {
|
|
49886
49886
|
if (!cfg.apiKey) {
|
|
49887
49887
|
throw new Error("CANDLE_AGENT_API_KEY is required for this tool. Set it in the environment or MCP client config.");
|
|
@@ -49993,7 +49993,7 @@ async function readPaperInventory(cfg, doFetch, extra) {
|
|
|
49993
49993
|
}
|
|
49994
49994
|
async function executeTrade(args, cfg, doFetch) {
|
|
49995
49995
|
const apiKey = requireApiKey(cfg);
|
|
49996
|
-
const clientTradeId = args.clientTradeId ??
|
|
49996
|
+
const clientTradeId = args.clientTradeId ?? randomUUID6();
|
|
49997
49997
|
if (args.amount !== undefined && args.percent !== undefined) {
|
|
49998
49998
|
return errText("pass exactly one of amount or percent, not both", { clientTradeId });
|
|
49999
49999
|
}
|
|
@@ -50131,7 +50131,7 @@ async function executeTrade(args, cfg, doFetch) {
|
|
|
50131
50131
|
}
|
|
50132
50132
|
async function executeLaunchAndSeed(args, cfg, doFetch) {
|
|
50133
50133
|
const apiKey = requireApiKey(cfg);
|
|
50134
|
-
const clientLaunchId = args.clientLaunchId ??
|
|
50134
|
+
const clientLaunchId = args.clientLaunchId ?? randomUUID6();
|
|
50135
50135
|
const { devBuy, dryRun, buyAmount: _rawBuyAmount, ...launchFields } = args;
|
|
50136
50136
|
let buyAmount;
|
|
50137
50137
|
if (devBuy !== undefined) {
|
|
@@ -50860,7 +50860,164 @@ async function executePerps(tool, args, cfg, env, fetch2) {
|
|
|
50860
50860
|
return fail3("MCP_TRANSPORT", error instanceof Error ? error.message : "The perps call failed.");
|
|
50861
50861
|
}
|
|
50862
50862
|
}
|
|
50863
|
-
|
|
50863
|
+
function rawUnits(amount, decimals) {
|
|
50864
|
+
const [whole = "", fraction = ""] = amount.split(".");
|
|
50865
|
+
if (fraction.length > decimals)
|
|
50866
|
+
return null;
|
|
50867
|
+
const raw = BigInt(whole + fraction.padEnd(decimals, "0"));
|
|
50868
|
+
return raw > 0n ? raw.toString() : null;
|
|
50869
|
+
}
|
|
50870
|
+
function pickWallet(rows, chain2, name2, what) {
|
|
50871
|
+
const onChain = rows.filter((row) => row.chain === chain2);
|
|
50872
|
+
const matches2 = name2 === undefined ? onChain : onChain.filter((row) => row.id === name2 || row.label === name2 || row.address.toLowerCase() === name2.toLowerCase());
|
|
50873
|
+
if (matches2.length !== 1)
|
|
50874
|
+
throw new Refusal(fail3("TEE_WALLET_REQUIRED", onChain.length === 0 ? `This key has no ${what} bound to it.` : `Name exactly one ${what}: ${onChain.map((row) => row.label ?? row.id).join(", ")}`));
|
|
50875
|
+
return matches2[0];
|
|
50876
|
+
}
|
|
50877
|
+
function depositProblem(build, expect) {
|
|
50878
|
+
const destination = build.destination ?? {};
|
|
50879
|
+
const fee = build.fee ?? {};
|
|
50880
|
+
const same = (a, b) => typeof a === "string" && a.toLowerCase() === b.toLowerCase();
|
|
50881
|
+
if (build.walletId !== expect.walletId)
|
|
50882
|
+
return "the build names another paying wallet";
|
|
50883
|
+
if (build.asset !== expect.asset || build.amountRaw !== expect.amountRaw)
|
|
50884
|
+
return "the build is for another amount";
|
|
50885
|
+
if (destination.chainId !== HYPERLIQUID_RELAY_CHAIN_ID2)
|
|
50886
|
+
return "the build does not land on Hyperliquid";
|
|
50887
|
+
if (destination.currency !== HYPERLIQUID_RELAY_USDC2)
|
|
50888
|
+
return "the build does not deliver Hyperliquid USDC";
|
|
50889
|
+
if (!same(destination.address, expect.account))
|
|
50890
|
+
return "the build lands on another account";
|
|
50891
|
+
if (fee.bps !== 0 || fee.feeRaw !== "0")
|
|
50892
|
+
return "the build carries a Candle fee";
|
|
50893
|
+
if (expect.hood !== (build.chain === "hood"))
|
|
50894
|
+
return "the build pays from the wrong chain";
|
|
50895
|
+
if (!expect.hood && !(Array.isArray(build.transactionsBase64) && build.transactionsBase64.length === 1))
|
|
50896
|
+
return "the build is not one Solana deposit";
|
|
50897
|
+
return null;
|
|
50898
|
+
}
|
|
50899
|
+
async function executePerpsDeposit(args, cfg, env, fetch2) {
|
|
50900
|
+
if (!cfg.apiKey)
|
|
50901
|
+
return fail3("MCP_VALIDATION", "CANDLE_AGENT_API_KEY is required for this tool.");
|
|
50902
|
+
const asset = args.asset;
|
|
50903
|
+
const spec = DEPOSIT_ASSETS[asset];
|
|
50904
|
+
if (!spec)
|
|
50905
|
+
return fail3("MCP_VALIDATION", "asset must be SOL, USDC, ETH or USDG.");
|
|
50906
|
+
const amountRaw = typeof args.amount === "string" ? rawUnits(args.amount, spec.decimals) : null;
|
|
50907
|
+
if (!amountRaw)
|
|
50908
|
+
return fail3("MCP_VALIDATION", `amount must be a positive ${asset} amount with at most ${spec.decimals} decimals.`);
|
|
50909
|
+
const hood = spec.chain === "evm";
|
|
50910
|
+
const clientDepositId = typeof args.clientDepositId === "string" ? args.clientDepositId : `deposit-${crypto.randomUUID()}`;
|
|
50911
|
+
const willSubmit = args.submit !== false;
|
|
50912
|
+
let signerPem = null;
|
|
50913
|
+
if (willSubmit) {
|
|
50914
|
+
const pemFile = env.CANDLE_KEY_SIGNER_PEM_FILE?.trim();
|
|
50915
|
+
if (!pemFile)
|
|
50916
|
+
return fail3("SIGNER_UNAVAILABLE", "Set CANDLE_KEY_SIGNER_PEM_FILE to the key signer PEM (candle tee signer new --out <pem>) to sign a deposit.");
|
|
50917
|
+
try {
|
|
50918
|
+
signerPem = await readFile9(pemFile, "utf8");
|
|
50919
|
+
createPrivateKey2(signerPem);
|
|
50920
|
+
} catch {
|
|
50921
|
+
return fail3("SIGNER_UNAVAILABLE", "CANDLE_KEY_SIGNER_PEM_FILE does not hold a readable private key PEM.");
|
|
50922
|
+
}
|
|
50923
|
+
}
|
|
50924
|
+
try {
|
|
50925
|
+
const listed = await call(cfg, fetch2, "GET", "/api/v1/agent/wallets/trading");
|
|
50926
|
+
if (!(Array.isArray(listed.scopes) && listed.scopes.includes("swap:write")))
|
|
50927
|
+
throw new Refusal(fail3("SCOPE_MISSING", "A deposit needs swap:write on the key."));
|
|
50928
|
+
const rows = Array.isArray(listed.page) ? listed.page : [];
|
|
50929
|
+
const appId = typeof listed.privyAppId === "string" ? listed.privyAppId : "";
|
|
50930
|
+
const name2 = typeof args.wallet === "string" ? args.wallet : undefined;
|
|
50931
|
+
const source = pickWallet(rows, spec.chain, name2, `${hood ? "Hood" : "Solana"} TEE wallet`);
|
|
50932
|
+
if (hood && args.perpsWallet !== undefined)
|
|
50933
|
+
return fail3("MCP_VALIDATION", "From a Hood TEE wallet the deposit lands on that wallet's own account; omit perpsWallet.");
|
|
50934
|
+
const account = hood ? source : pickWallet(rows, "evm", typeof args.perpsWallet === "string" ? args.perpsWallet : undefined, "EVM TEE wallet");
|
|
50935
|
+
const build = await call(cfg, fetch2, "POST", "/api/v1/agent/perps/deposit", {
|
|
50936
|
+
clientDepositId,
|
|
50937
|
+
walletId: source.id,
|
|
50938
|
+
asset,
|
|
50939
|
+
amountRaw,
|
|
50940
|
+
...typeof args.maxSlippageBps === "number" ? { maxSlippageBps: args.maxSlippageBps } : {},
|
|
50941
|
+
...hood ? {} : { perpsWalletId: account.id }
|
|
50942
|
+
});
|
|
50943
|
+
if (build.job !== undefined)
|
|
50944
|
+
return { text: JSON.stringify(build) };
|
|
50945
|
+
const problem = depositProblem(build, { walletId: source.id, asset, amountRaw, account: account.address, hood });
|
|
50946
|
+
if (problem)
|
|
50947
|
+
return fail3("PERPS_BUILD_REFUSED", `Refused to sign this deposit: ${problem}. Nothing was signed.`);
|
|
50948
|
+
if (!willSubmit || signerPem === null)
|
|
50949
|
+
return { text: JSON.stringify({ ...build, signed: false, submitted: false }) };
|
|
50950
|
+
if (!appId || !source.privyWalletId)
|
|
50951
|
+
return fail3("SIGNER_UNAVAILABLE", "The server did not return its relay identifiers.");
|
|
50952
|
+
const pem = signerPem;
|
|
50953
|
+
const relaySign2 = async (body2) => {
|
|
50954
|
+
const payload = hyperliquidCanonicalJson2({
|
|
50955
|
+
body: body2,
|
|
50956
|
+
headers: { "privy-app-id": appId },
|
|
50957
|
+
method: "POST",
|
|
50958
|
+
url: `https://api.privy.io/v1/wallets/${source.privyWalletId}/rpc`,
|
|
50959
|
+
version: 1
|
|
50960
|
+
});
|
|
50961
|
+
const authorizationSignature2 = sign5("sha256", Buffer.from(payload), pem).toString("base64");
|
|
50962
|
+
const relay = await call(cfg, fetch2, "POST", `/api/v1/agent/wallets/${encodeURIComponent(source.id)}/sign`, {
|
|
50963
|
+
authorizationSignature: authorizationSignature2,
|
|
50964
|
+
body: body2
|
|
50965
|
+
});
|
|
50966
|
+
if (typeof relay.signedTransaction !== "string")
|
|
50967
|
+
throw new Refusal(fail3("INVALID_RESPONSE", "The relay did not return a signed transaction."));
|
|
50968
|
+
return relay.signedTransaction;
|
|
50969
|
+
};
|
|
50970
|
+
const ids = { clientDepositId, depositId: build.depositId };
|
|
50971
|
+
const submitPath = "/api/v1/agent/perps/deposit/submit";
|
|
50972
|
+
if (!hood) {
|
|
50973
|
+
const transaction = build.transactionsBase64[0];
|
|
50974
|
+
const signed = await relaySign2({ method: "signTransaction", params: { encoding: "base64", transaction } });
|
|
50975
|
+
return {
|
|
50976
|
+
text: JSON.stringify(await call(cfg, fetch2, "POST", submitPath, { ...ids, signedTransactionsBase64: [signed] }))
|
|
50977
|
+
};
|
|
50978
|
+
}
|
|
50979
|
+
const allowed = asset === "USDG" ? ["approval", "bridgeDeposit"] : ["bridgeDeposit"];
|
|
50980
|
+
const planned = build.plannedLegCount;
|
|
50981
|
+
const hex4 = (value) => `0x${BigInt(String(value)).toString(16)}`;
|
|
50982
|
+
const signedKinds = new Set;
|
|
50983
|
+
let body = build;
|
|
50984
|
+
while (body.mode === "sequenced") {
|
|
50985
|
+
const leg = body.nextLeg;
|
|
50986
|
+
const kind = String(body.legKind);
|
|
50987
|
+
if (!leg || body.operationId !== build.operationId || body.plannedLegCount !== planned || typeof planned !== "number" || planned > allowed.length || !allowed.includes(kind) || signedKinds.has(kind) || leg.chainId !== 4663)
|
|
50988
|
+
return fail3("PERPS_BUILD_REFUSED", `Refused to sign deposit leg ${kind}: it is not the plan this deposit was built with.`, { clientDepositId, operationId: build.operationId });
|
|
50989
|
+
signedKinds.add(kind);
|
|
50990
|
+
const signed = await relaySign2({
|
|
50991
|
+
method: "eth_signTransaction",
|
|
50992
|
+
params: {
|
|
50993
|
+
transaction: {
|
|
50994
|
+
chain_id: leg.chainId,
|
|
50995
|
+
data: leg.data,
|
|
50996
|
+
from: source.address,
|
|
50997
|
+
gas_limit: hex4(leg.gas),
|
|
50998
|
+
max_fee_per_gas: hex4(leg.maxFeePerGas),
|
|
50999
|
+
max_priority_fee_per_gas: hex4(leg.maxPriorityFeePerGas),
|
|
51000
|
+
nonce: leg.nonce,
|
|
51001
|
+
to: leg.to,
|
|
51002
|
+
type: 2,
|
|
51003
|
+
value: hex4(leg.value)
|
|
51004
|
+
}
|
|
51005
|
+
}
|
|
51006
|
+
});
|
|
51007
|
+
body = await call(cfg, fetch2, "POST", submitPath, {
|
|
51008
|
+
...ids,
|
|
51009
|
+
operationId: build.operationId,
|
|
51010
|
+
signedTransaction: signed
|
|
51011
|
+
});
|
|
51012
|
+
}
|
|
51013
|
+
return { text: JSON.stringify({ ...body, clientDepositId }) };
|
|
51014
|
+
} catch (error) {
|
|
51015
|
+
if (error instanceof Refusal)
|
|
51016
|
+
return error.tool;
|
|
51017
|
+
return fail3("MCP_TRANSPORT", error instanceof Error ? error.message : "The deposit call failed.");
|
|
51018
|
+
}
|
|
51019
|
+
}
|
|
51020
|
+
var decimal3, wallet, submit, perpsShapes, PATHS, Refusal, HYPERLIQUID_RELAY_CHAIN_ID2 = 1337, HYPERLIQUID_RELAY_USDC2 = "0x00000000000000000000000000000000", DEPOSIT_ASSETS, perpsDepositShape;
|
|
50864
51021
|
var init_perps = __esm(() => {
|
|
50865
51022
|
init_zod();
|
|
50866
51023
|
init_hyperliquid();
|
|
@@ -50919,6 +51076,97 @@ var init_perps = __esm(() => {
|
|
|
50919
51076
|
this.tool = tool;
|
|
50920
51077
|
}
|
|
50921
51078
|
};
|
|
51079
|
+
DEPOSIT_ASSETS = {
|
|
51080
|
+
SOL: { chain: "solana", decimals: 9 },
|
|
51081
|
+
USDC: { chain: "solana", decimals: 6 },
|
|
51082
|
+
ETH: { chain: "evm", decimals: 18 },
|
|
51083
|
+
USDG: { chain: "evm", decimals: 6 }
|
|
51084
|
+
};
|
|
51085
|
+
perpsDepositShape = {
|
|
51086
|
+
asset: exports_external.enum(["SOL", "USDC", "ETH", "USDG"]).describe("What to deposit. SOL or USDC pay from the key's Solana TEE wallet; ETH or USDG from its Hood one."),
|
|
51087
|
+
amount: decimal3.describe('Amount of the asset, decimal (e.g. "25")'),
|
|
51088
|
+
wallet: exports_external.string().optional().describe("The paying TEE wallet by id, address or label. Omit when the key has exactly one on that chain."),
|
|
51089
|
+
perpsWallet: exports_external.string().optional().describe("From Solana only: the EVM TEE wallet whose Hyperliquid account receives it. Omit when the key has one."),
|
|
51090
|
+
clientDepositId: exports_external.string().regex(/^[A-Za-z0-9._:-]{1,128}$/).optional().describe("Idempotency id; the same id answers the deposit it already built. Default: a new one."),
|
|
51091
|
+
maxSlippageBps: exports_external.number().int().min(1).max(1000).optional().describe("Relay slippage (default 100)"),
|
|
51092
|
+
submit
|
|
51093
|
+
};
|
|
51094
|
+
});
|
|
51095
|
+
|
|
51096
|
+
// ../mcp/src/plans.ts
|
|
51097
|
+
function planLabel2(plan) {
|
|
51098
|
+
return Object.hasOwn(PLAN_LABELS2, plan) ? PLAN_LABELS2[plan] : plan;
|
|
51099
|
+
}
|
|
51100
|
+
function formatPlanBps2(bps) {
|
|
51101
|
+
return bps === 0 ? "none" : `${Number((bps / 100).toFixed(4))}%`;
|
|
51102
|
+
}
|
|
51103
|
+
function count2(n) {
|
|
51104
|
+
return n.toLocaleString("en-US");
|
|
51105
|
+
}
|
|
51106
|
+
function priceCell2(entry) {
|
|
51107
|
+
if (entry.price)
|
|
51108
|
+
return `$${count2(entry.price.pricePerMonthUsd)} a month`;
|
|
51109
|
+
return entry.plan === "free" ? "free" : "not sold";
|
|
51110
|
+
}
|
|
51111
|
+
function planTableRows2(table) {
|
|
51112
|
+
const plans2 = table.plans;
|
|
51113
|
+
const row = (label, cell) => [label, ...plans2.map(cell)];
|
|
51114
|
+
const rows = [
|
|
51115
|
+
row("Price", priceCell2),
|
|
51116
|
+
row("Agent trade fee", (e) => formatPlanBps2(e.feeBps)),
|
|
51117
|
+
row("Perps builder fee", (e) => formatPlanBps2(e.perpFeeBps)),
|
|
51118
|
+
row("Requests per minute", (e) => count2(e.limits.rateLimitPerMin)),
|
|
51119
|
+
row("Launches per day", (e) => count2(e.limits.dailyLaunchCap)),
|
|
51120
|
+
row("Uploads per minute", (e) => count2(e.limits.uploadsPerMin)),
|
|
51121
|
+
row("Linked wallets", (e) => count2(e.limits.linkedWallets))
|
|
51122
|
+
];
|
|
51123
|
+
const known = Object.keys(PLAN_CAPABILITY_LABELS2);
|
|
51124
|
+
const served = new Set;
|
|
51125
|
+
for (const entry of plans2)
|
|
51126
|
+
for (const key of Object.keys(entry.capabilities ?? {}))
|
|
51127
|
+
served.add(key);
|
|
51128
|
+
const keys = [
|
|
51129
|
+
...known.filter((k) => served.has(k)),
|
|
51130
|
+
...[...served].filter((k) => !Object.hasOwn(PLAN_CAPABILITY_LABELS2, k))
|
|
51131
|
+
];
|
|
51132
|
+
for (const key of keys) {
|
|
51133
|
+
const label = Object.hasOwn(PLAN_CAPABILITY_LABELS2, key) ? PLAN_CAPABILITY_LABELS2[key] : key;
|
|
51134
|
+
rows.push(row(label, (e) => {
|
|
51135
|
+
const value = e.capabilities?.[key];
|
|
51136
|
+
return value === true ? "yes" : value === false ? "no" : "-";
|
|
51137
|
+
}));
|
|
51138
|
+
}
|
|
51139
|
+
return { headers: ["", ...plans2.map((e) => planLabel2(e.plan))], rows };
|
|
51140
|
+
}
|
|
51141
|
+
function planPromotionLine2(table) {
|
|
51142
|
+
if (!(table.promoMaxDays > 0))
|
|
51143
|
+
return null;
|
|
51144
|
+
const days = table.promoMaxDays === 1 ? "1 day" : `${table.promoMaxDays} days`;
|
|
51145
|
+
return `A first Pro purchase includes Max for its first ${days}, then continues on Pro.`;
|
|
51146
|
+
}
|
|
51147
|
+
function planTableMarkdown(table) {
|
|
51148
|
+
const { headers: headers2, rows } = planTableRows2(table);
|
|
51149
|
+
const line = (cells) => `| ${cells.map((c) => c.replace(/\|/g, "\\|")).join(" | ")} |`;
|
|
51150
|
+
return [line(headers2), line(headers2.map(() => "---")), ...rows.map(line)].join(`
|
|
51151
|
+
`);
|
|
51152
|
+
}
|
|
51153
|
+
var PLAN_CAPABILITY_NOTE2 = "A capability marked yes is what the plan allows. It is subject to the deployment's own switches (perps and own-wallet bridges each have one) and to the wallet, scopes and setup the feature needs.", PLAN_CAPABILITY_LABELS2, PLAN_LABELS2;
|
|
51154
|
+
var init_plans = __esm(() => {
|
|
51155
|
+
PLAN_CAPABILITY_LABELS2 = {
|
|
51156
|
+
tradeCandleTokens: "Trade Candle-launched tokens",
|
|
51157
|
+
tradeBaseAssets: "Trade base assets",
|
|
51158
|
+
freeBaseTransfers: "Base-pair swaps and own-wallet bridges (when enabled), no Candle fee",
|
|
51159
|
+
sellExternalTokens: "Sell tokens not launched on Candle",
|
|
51160
|
+
buyExternalTokens: "Buy tokens not launched on Candle",
|
|
51161
|
+
hyperliquidPerps: "Hyperliquid perps (when enabled)",
|
|
51162
|
+
selfLaunch: "Self-launch from a linked wallet",
|
|
51163
|
+
atomicLaunch: "Atomic launch with first buys",
|
|
51164
|
+
createLinkedWallets: "Create linked wallets",
|
|
51165
|
+
importLinkedWallets: "Import linked wallets",
|
|
51166
|
+
limitOrders: "Limit orders",
|
|
51167
|
+
quant: "Quant on Telegram"
|
|
51168
|
+
};
|
|
51169
|
+
PLAN_LABELS2 = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
|
|
50922
51170
|
});
|
|
50923
51171
|
|
|
50924
51172
|
// ../mcp/src/tools.ts
|
|
@@ -51019,6 +51267,9 @@ function buildRequest(name2, args, cfg) {
|
|
|
51019
51267
|
init: { method: "GET", headers: jsonHeaders() }
|
|
51020
51268
|
};
|
|
51021
51269
|
}
|
|
51270
|
+
case "candle_get_plans": {
|
|
51271
|
+
return { url: `${base2}/api/v1/agent/plans`, init: { method: "GET", headers: jsonHeaders() } };
|
|
51272
|
+
}
|
|
51022
51273
|
case "candle_swap": {
|
|
51023
51274
|
const apiKey = requireApiKey2(cfg);
|
|
51024
51275
|
return {
|
|
@@ -51088,6 +51339,22 @@ function buildRequest(name2, args, cfg) {
|
|
|
51088
51339
|
}
|
|
51089
51340
|
}
|
|
51090
51341
|
}
|
|
51342
|
+
function plansMarkdown(body) {
|
|
51343
|
+
try {
|
|
51344
|
+
const table = JSON.parse(body);
|
|
51345
|
+
if (!Array.isArray(table.plans))
|
|
51346
|
+
return "";
|
|
51347
|
+
const promotion = planPromotionLine2(table);
|
|
51348
|
+
const markdown = `${planTableMarkdown(table)}
|
|
51349
|
+
|
|
51350
|
+
${PLAN_CAPABILITY_NOTE2}`;
|
|
51351
|
+
return promotion ? `${markdown}
|
|
51352
|
+
|
|
51353
|
+
${promotion}` : markdown;
|
|
51354
|
+
} catch {
|
|
51355
|
+
return "";
|
|
51356
|
+
}
|
|
51357
|
+
}
|
|
51091
51358
|
async function callAndRelay(name2, args, cfg) {
|
|
51092
51359
|
const { url, init } = buildRequest(name2, args, cfg);
|
|
51093
51360
|
const res = await fetch(url, init);
|
|
@@ -51142,6 +51409,24 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
51142
51409
|
description: "Read a Candle user's public agent profile: whether agent features are enabled and launch counts.",
|
|
51143
51410
|
inputSchema: getAgentProfileShape
|
|
51144
51411
|
}, async (args) => callAndRelay("candle_get_agent_profile", args, cfg));
|
|
51412
|
+
register("candle_get_plans", {
|
|
51413
|
+
title: "Plans: prices, fees, limits and what each can do",
|
|
51414
|
+
description: "The plan table this Candle deployment serves (GET /api/v1/agent/plans). No key needed. Reads only. For " + "each plan: price (null when not sold), feeBps (the agent fee charged on top of every trade or dev buy the " + "API builds), perpFeeBps (the Hyperliquid builder fee), limits for a new key (requests per minute, launches " + "per day, uploads per minute, linked wallets) and capabilities: buyExternalTokens, sellExternalTokens, " + "tradeBaseAssets, tradeCandleTokens, selfLaunch, atomicLaunch, createLinkedWallets, importLinkedWallets, " + "hyperliquidPerps, limitOrders, quant, freeBaseTransfers. promoMaxDays is the days of Max a first Pro " + "purchase includes (0: no promotion). Quote prices and fees from here, never from memory: they differ by " + "deployment and change at the three-plan launch (Free, Pro, Max). The account's own plan and fee are in " + "candle_execution_status's tier. A TIER_REQUIRED refusal names a capability this table shows the account's " + "plan lacks. A capability true here is plan eligibility, not deployment availability: perps and own-wallet bridges " + "are each behind a deployment switch and need their wallet, scopes and setup. Returns the server's JSON, then the same table as Markdown.",
|
|
51415
|
+
inputSchema: {}
|
|
51416
|
+
}, async () => {
|
|
51417
|
+
const { url, init } = buildRequest("candle_get_plans", {}, cfg);
|
|
51418
|
+
const res = await fetch(url, init);
|
|
51419
|
+
noteVersionHeaders(res);
|
|
51420
|
+
const text = await res.text();
|
|
51421
|
+
if (!res.ok)
|
|
51422
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
51423
|
+
return {
|
|
51424
|
+
content: [
|
|
51425
|
+
{ type: "text", text },
|
|
51426
|
+
{ type: "text", text: plansMarkdown(text) }
|
|
51427
|
+
]
|
|
51428
|
+
};
|
|
51429
|
+
});
|
|
51145
51430
|
register("candle_get_operation", {
|
|
51146
51431
|
title: "What happened to a write",
|
|
51147
51432
|
description: "Look up a trade or launch by the id its write used, and find out whether it landed. " + `Reads only; moves nothing.
|
|
@@ -51172,7 +51457,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
51172
51457
|
}, async (args) => callAndRelay("candle_set_profile_wallets", args, cfg));
|
|
51173
51458
|
register("candle_get_profile_pnl", {
|
|
51174
51459
|
title: "Read an agent profile's P&L",
|
|
51175
|
-
description: "
|
|
51460
|
+
description: "This profile's share of the account's P&L: realized profit on the sales it made, the Candle fees " + "charged against it, and the positions that belong to it (the wallet's bound key, else the key " + "whose buy opened them) with their cost basis, each MARKED at Candle's current price where one " + "exists: `markPriceUsd`, `marketValueUsd` and `unrealizedUsd` per position, and `unrealizedUsd` " + "overall. A position with no price is counted in `unmarkedPositions` and left out of unrealized, " + "never valued at zero; `oldestMarkAt` says how old the marks are. Deposits and withdrawals are " + "excluded: funding a wallet is not profit. Check `unvalued` and `truncated` before quoting the " + "number; they mean the total is partial. The figures come from the account's one P&L read, the " + "same as the console's, to the cent: `pnl.totalUsd` (realized net plus unrealized) is the " + "console's P&L. Positions live in wallets: each wallet is its own average-cost pool, the total " + "is the sum of `pnl.byWallet` (one row per wallet, main or linked, with the linked wallet's " + "label; `unknown:solana` or `unknown:hood` is the row for fills no record places in a wallet), " + "and one token held in two wallets is two positions, each with its `wallet`. A recorded move " + "between the account's wallets carries its cost and realizes nothing. Each position carries " + "`agent` (the key it belongs to) and `dust` when it is worth under one cent; dust stays listed " + "and counted, and `openPositionsExDust` leaves it out. `closed` gives the closed positions as " + "`madeUsd` + `lostUsd` + `partialSellsUsd` = `realizedNetUsd`, with `wins` and `losses`. " + "`lookback` and `truncated` are the account's activity bound, and `tradesConsidered` counts this " + "key's ledger fills. Since 2026-10-02 the total is the sum of wallets rather than one pool across " + "them, so realized figures can differ from earlier reads; a server that predates it omits " + "`totalUsd`, `closed`, `openPositionsExDust`, `wallet`, `agent` and `dust`. " + "Every open position and every `byWallet` row (and its positions) carries `chain` ('solana' or " + "'hood'), and `pnl.byChain` has the total's figures over each chain's fills alone, both chains " + "always present, with `openPositions` there a count (the positions are in `pnl.openPositions`, by " + "`chain`). The two chains sum to the total. A " + "server that predates per-chain P&L omits `chain` and `byChain`. While Hyperliquid is enabled, " + "`hyperliquid` separately reports main-perp realized gross, signed funding, fees (inclusive of builder fees), " + "and net = gross + funding - fees, for currently bound EVM TEE wallets since the current TEE binding. " + "Check `read` and `truncated`; historical bindings and unrealized perps are excluded. Reads only.",
|
|
51176
51461
|
inputSchema: profilePnlShape
|
|
51177
51462
|
}, async (args) => callAndRelay("candle_get_profile_pnl", args, cfg));
|
|
51178
51463
|
register("candle_get_profile_trades", {
|
|
@@ -51242,6 +51527,8 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
51242
51527
|
|
|
51243
51528
|
` + "Arguments: `mint` and `side` are required. Amounts are DECIMAL, never raw base units " + '(amount: "0.5", not lamports). Omitting the amount on a sell sells the whole ' + `position.
|
|
51244
51529
|
|
|
51530
|
+
` + "Plans: every plan can buy and sell Candle-launched tokens and base assets, and SELL a token it holds that " + "Candle did not launch. BUYING such a token needs Pro or Max (TIER_REQUIRED otherwise). Each trade pays the " + `plan's agent fee on top (feeBps in candle_get_plans).
|
|
51531
|
+
|
|
51245
51532
|
` + "Pass `paper: true` to rehearse: every admission rule runs and the quote is recorded, but " + "nothing broadcasts and no funds move. A paper buy credits this key's paper inventory, " + "including for external Solana mints routed through Jupiter. A later sell by amount or " + "percent closes that book without reading the live wallet and without MARKET_NOT_FOUND " + "-- including when `paper` is omitted on the exit, as long as the paper position exists. " + "Do this before the first live trade of a new strategy, and whenever you are unsure a " + `trade would be admitted at all.
|
|
51246
51533
|
|
|
51247
51534
|
` + `After the call:
|
|
@@ -51272,7 +51559,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
51272
51559
|
}, perpsTool("candle_perps_setup"));
|
|
51273
51560
|
register("candle_perps_open", {
|
|
51274
51561
|
title: "Open a perps position",
|
|
51275
|
-
description: "Open or add to a perpetual position on Hyperliquid's main perp exchange: a market order (IOC within " + "slippageBps of the mid) without `price`, a limit order with it, and optional reduce-only takeProfit and " + "stopLoss triggers.
|
|
51562
|
+
description: "Open or add to a perpetual position on Hyperliquid's main perp exchange: a market order (IOC within " + "slippageBps of the mid) without `price`, a limit order with it, and optional reduce-only takeProfit and " + "stopLoss triggers. Every plan except Max pays Candle a builder fee on each order (0.1% today; perpFeeBps in " + "candle_get_plans); Max pays none." + perpsWrite,
|
|
51276
51563
|
inputSchema: perpsShapes.candle_perps_open
|
|
51277
51564
|
}, perpsTool("candle_perps_open"));
|
|
51278
51565
|
register("candle_perps_close", {
|
|
@@ -51300,6 +51587,14 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
51300
51587
|
description: "Set a market's leverage and margin mode (cross or isolated) on the account, within the key's maximum leverage." + perpsWrite,
|
|
51301
51588
|
inputSchema: perpsShapes.candle_perps_leverage
|
|
51302
51589
|
}, perpsTool("candle_perps_leverage"));
|
|
51590
|
+
register("candle_perps_deposit", {
|
|
51591
|
+
title: "Deposit to Hyperliquid",
|
|
51592
|
+
description: "MOVES REAL FUNDS unless submit is false. Deposit onto the key's Hyperliquid account through Relay: SOL or " + "USDC from its Solana TEE wallet, or ETH or USDG from its Hood TEE wallet, credited as Hyperliquid perps " + "USDC at the key's EVM TEE wallet's own address (Candle resolves it; no recipient is taken). Refused below a " + "floor that covers Hyperliquid's 1 USDC first-deposit charge. No Candle fee. Needs swap:write and a raw cap " + "on the asset, and CANDLE_KEY_SIGNER_PEM_FILE to sign. There is no withdrawal from Hyperliquid yet.",
|
|
51593
|
+
inputSchema: perpsDepositShape
|
|
51594
|
+
}, async (args) => {
|
|
51595
|
+
const result = await executePerpsDeposit(args, cfg, env, fetch);
|
|
51596
|
+
return { content: [{ type: "text", text: result.text }], ...result.isError ? { isError: true } : {} };
|
|
51597
|
+
});
|
|
51303
51598
|
}
|
|
51304
51599
|
var TOOL_NAMES, launchTokenShape, getMarketShape, tokenForensicsShape, getFeedShape, reportActivityShape, getAgentProfileShape, getOperationShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, setProfileWalletsShape, swapShape, tradeShape, _rawBuyAmount, seedableLaunchShape, launchAndSeedShape, transferShape, sweepShape;
|
|
51305
51600
|
var init_tools = __esm(() => {
|
|
@@ -51307,6 +51602,7 @@ var init_tools = __esm(() => {
|
|
|
51307
51602
|
init_convert();
|
|
51308
51603
|
init_orchestrate();
|
|
51309
51604
|
init_perps();
|
|
51605
|
+
init_plans();
|
|
51310
51606
|
init_update_notice();
|
|
51311
51607
|
TOOL_NAMES = [
|
|
51312
51608
|
"candle_launch_token",
|
|
@@ -51315,6 +51611,7 @@ var init_tools = __esm(() => {
|
|
|
51315
51611
|
"candle_token_forensics",
|
|
51316
51612
|
"candle_report_activity",
|
|
51317
51613
|
"candle_get_agent_profile",
|
|
51614
|
+
"candle_get_plans",
|
|
51318
51615
|
"candle_trade",
|
|
51319
51616
|
"candle_launch_and_seed",
|
|
51320
51617
|
"candle_swap",
|
|
@@ -51335,7 +51632,8 @@ var init_tools = __esm(() => {
|
|
|
51335
51632
|
"candle_perps_cancel",
|
|
51336
51633
|
"candle_perps_orders",
|
|
51337
51634
|
"candle_perps_positions",
|
|
51338
|
-
"candle_perps_leverage"
|
|
51635
|
+
"candle_perps_leverage",
|
|
51636
|
+
"candle_perps_deposit"
|
|
51339
51637
|
];
|
|
51340
51638
|
launchTokenShape = {
|
|
51341
51639
|
clientLaunchId: exports_external.string().describe("Caller-chosen idempotency key, unique per account"),
|
|
@@ -51411,7 +51709,7 @@ var init_tools = __esm(() => {
|
|
|
51411
51709
|
side: exports_external.enum(["buy", "sell"]),
|
|
51412
51710
|
amount: exports_external.string().optional().describe("Decimal amount. Buys: how much of THIS TOKEN'S OWN quote asset to spend (SOL for a " + 'SOL-launched token, USDC for a USDC-quoted one, and so on: e.g. "0.5"). Sells: how many ' + "TOKENS to sell. Pass exactly one of amount or percent."),
|
|
51413
51711
|
percent: exports_external.number().optional().describe("Sells only: sell this percent (integer 1-100) of the holding. Live trades size against the " + "embedded wallet. Paper trades (`paper: true`) size against this key's paper inventory -- " + "the position a previous paper buy credited -- because paper never moves the live wallet."),
|
|
51414
|
-
quoteAsset: exports_external.string().optional().describe('What the wallet spends on a buy or receives on a sell: "sol", "usdc" or "cndl" on Solana, ' + '"eth" or "usdg" on Hood.
|
|
51712
|
+
quoteAsset: exports_external.string().optional().describe('What the wallet spends on a buy or receives on a sell: "sol", "usdc" or "cndl" on Solana, ' + '"eth" or "usdg" on Hood. Echoing the `quoteAsset` a quote (POST /api/v1/trade/agent/quote) returned is safe. On Solana it applies only ' + "to an arbitrary mint Candle never launched (a buy needs Pro or Max; a sell works on any plan) and is ignored for a Candle token, " + "whose quote comes from the token itself. On Hood it is the settlement asset of a DEX " + "trade; a USDG buy adds an approval transaction an ETH buy does not. It is not the route: " + "the cheapest path to the asset is chosen separately. Defaults to sol / ETH settlement."),
|
|
51415
51713
|
maxSlippageBps: exports_external.number().optional().describe("Max slippage in basis points; API default applies when omitted"),
|
|
51416
51714
|
clientTradeId: exports_external.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result. Retrying with the " + "SAME id is safe (idempotent replay); a new id is a SECOND trade."),
|
|
51417
51715
|
paper: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Rehearse instead of trading. The request passes every admission rule a live trade passes " + "-- the same planner, spend gate, key cap and loss limits -- and records the quote, but " + "nothing is ever broadcast and no funds move. Use it to check that a strategy is admitted " + "before risking anything on it. A paper fill is optimistic by construction: it books the " + "quoted price, so the gap between a paper arm and a live one IS the execution cost. " + "A sell of a mint this key already paper-bought also closes that paper book when the " + "live wallet is empty, even if this flag is omitted."))
|
|
@@ -51461,12 +51759,13 @@ async function runStdioServer(env = process.env, transport = new StdioServerTran
|
|
|
51461
51759
|
}
|
|
51462
51760
|
var INSTRUCTIONS = `Candle is a trading and token-launch rail for agents. You hold a scoped API key, never a private key; signing and funding stay with the key owner's wallet.
|
|
51463
51761
|
|
|
51464
|
-
START HERE —
|
|
51762
|
+
START HERE — six tools need NO credential. Call these first to confirm the server is wired before asking anyone for anything:
|
|
51465
51763
|
candle_get_market price, market cap, volume, curve state for one token
|
|
51466
51764
|
candle_get_feed the roster: hot streak, new pairs, graduated, blue chip
|
|
51467
51765
|
candle_resolve_token a ticker or partial name -> mint address + chain
|
|
51468
51766
|
candle_token_forensics call this before quoting or buying. Returns the on-chain developer (never a launchpad shared authority; deployer.attribution names the launchpad or issuer when no developer is on chain), their went-to-zero rate and last coins, who bought in the deploy window (strangers in the same slot are the bundle signal), same-funder insider share, same-funder cluster, and safety.summary with six sourced flags. Refuse an unprompted buy when flagged; incomplete or unknown is not clearance. launch.deployerLaunches is an inclusive informational count, never a warning
|
|
51469
51767
|
candle_get_agent_profile your own tier, caps and verified activity
|
|
51768
|
+
candle_get_plans every plan's price, fees, limits and what it can do; quote these, never from memory
|
|
51470
51769
|
|
|
51471
51770
|
COVERAGE — read this before you treat an error as a broken server.
|
|
51472
51771
|
candle_get_feed indexes the wider market (pump.fun, pons.family and other external launchpads).
|
|
@@ -51558,7 +51857,7 @@ init_render();
|
|
|
51558
51857
|
init_secret_store();
|
|
51559
51858
|
|
|
51560
51859
|
// src/version.ts
|
|
51561
|
-
var CLI_VERSION = "0.11.
|
|
51860
|
+
var CLI_VERSION = "0.11.15";
|
|
51562
51861
|
|
|
51563
51862
|
// src/commands/auth.ts
|
|
51564
51863
|
init_keys_embedded_wallet();
|
|
@@ -52051,7 +52350,7 @@ var HELP = {
|
|
|
52051
52350
|
doctor: {
|
|
52052
52351
|
group: "Start here",
|
|
52053
52352
|
summary: "Diagnose CLI setup: credentials, storage backend, API reachability, security key helper",
|
|
52054
|
-
description: "One PASS/FAIL/SKIP table over the runtime, the storage backend, both credentials, API reachability, wallet delegation, the install method and whether the security key helper (candle-fido2) is beside the binary. Its output is meant to be pasted into a bug report. Exits nonzero on any FAIL.",
|
|
52353
|
+
description: "One PASS/FAIL/SKIP table over the runtime, the storage backend, both credentials, API reachability, wallet delegation, the install method, the account's plan and its trade fee, and whether the security key helper (candle-fido2) is beside the binary. Its output is meant to be pasted into a bug report. Exits nonzero on any FAIL.",
|
|
52055
52354
|
usage: ["candle doctor [--role owner|bot|auto]"],
|
|
52056
52355
|
rows: [],
|
|
52057
52356
|
examples: ["candle doctor", "candle doctor --json"],
|
|
@@ -52060,7 +52359,7 @@ var HELP = {
|
|
|
52060
52359
|
swap: {
|
|
52061
52360
|
group: "Trade",
|
|
52062
52361
|
summary: "Quote, confirm and swap on Solana or Hood, bridge between them; read an operation by id",
|
|
52063
|
-
description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent, and the first buy after a launch is this command rather than part of the launch. 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, the key needs a raw cap on the origin asset, 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.",
|
|
52362
|
+
description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent, and the first buy after a launch is this command rather than part of the launch. 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, the key needs a raw cap on the origin asset, 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.",
|
|
52064
52363
|
usage: ["candle swap <from> <to> [flags]", "candle swap status <id>"],
|
|
52065
52364
|
rows: [
|
|
52066
52365
|
{
|
|
@@ -52126,14 +52425,18 @@ var HELP = {
|
|
|
52126
52425
|
},
|
|
52127
52426
|
perps: {
|
|
52128
52427
|
group: "Trade",
|
|
52129
|
-
summary: "Hyperliquid perpetuals from your EVM TEE wallet: setup, open, close, cancel, orders, positions, leverage",
|
|
52130
|
-
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.
|
|
52428
|
+
summary: "Hyperliquid perpetuals from your EVM TEE wallet: setup, deposit, open, close, cancel, orders, positions, leverage",
|
|
52429
|
+
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 the account's P&L (candle pnl) carries a Hyperliquid section.",
|
|
52131
52430
|
usage: ["candle perps <subcommand> [flags]"],
|
|
52132
52431
|
rows: [
|
|
52133
52432
|
{
|
|
52134
52433
|
invocation: "setup [--wallet <tee>]",
|
|
52135
52434
|
description: "Approve Candle's builder fee once; shows the account's mode and balance"
|
|
52136
52435
|
},
|
|
52436
|
+
{
|
|
52437
|
+
invocation: "deposit <amount> <SOL|USDC|ETH|USDG> [--wallet <tee>] [--to <evm tee>] | deposit status <id>",
|
|
52438
|
+
description: "Move funds onto the Hyperliquid account through Relay, from the key's Solana or Hood TEE wallet (swap:write); status reads one"
|
|
52439
|
+
},
|
|
52137
52440
|
{
|
|
52138
52441
|
invocation: "open <coin> <long|short> <size> [--price <px>]",
|
|
52139
52442
|
description: "Market (IOC) without --price, limit with it; optional --tp and --sl"
|
|
@@ -52150,20 +52453,27 @@ var HELP = {
|
|
|
52150
52453
|
flags: [
|
|
52151
52454
|
{
|
|
52152
52455
|
invocation: "--wallet <tee>",
|
|
52153
|
-
description: "The EVM TEE wallet by id, address or unique label (default: the only one)"
|
|
52456
|
+
description: "The EVM TEE wallet by id, address or unique label (default: the only one); for deposit, the paying TEE wallet"
|
|
52154
52457
|
},
|
|
52155
52458
|
{ invocation: "--tif <Gtc|Alo|Ioc>", description: "open with --price: time in force (default Gtc)" },
|
|
52156
52459
|
{
|
|
52157
52460
|
invocation: "--slippage-bps <n>",
|
|
52158
|
-
description: "open at market and close: the bound against the mid (default 100)"
|
|
52461
|
+
description: "open at market and close: the bound against the mid; deposit: Relay's slippage (default 100)"
|
|
52159
52462
|
},
|
|
52160
52463
|
{ invocation: "--tp <px> / --sl <px>", description: "open: reduce-only take-profit and stop-loss triggers" },
|
|
52464
|
+
{
|
|
52465
|
+
invocation: "--to <evm tee>",
|
|
52466
|
+
description: "deposit from Solana: the EVM TEE wallet whose account receives it"
|
|
52467
|
+
},
|
|
52468
|
+
{ invocation: "--id <clientDepositId>", description: "deposit: the idempotency id (default: a new one)" },
|
|
52469
|
+
{ invocation: "--rpc-url <url>", description: "deposit from Solana: the RPC the deposit check reads" },
|
|
52161
52470
|
{ invocation: "--no-submit", description: "Build and check only; sign and submit nothing" },
|
|
52162
52471
|
{ invocation: "--sign-only", description: "Build, check and sign through the relay; submit nothing" },
|
|
52163
52472
|
{ invocation: "--yes", description: "Skip the confirmation prompt" }
|
|
52164
52473
|
],
|
|
52165
52474
|
examples: [
|
|
52166
52475
|
"candle perps setup",
|
|
52476
|
+
"candle perps deposit 25 USDC",
|
|
52167
52477
|
"candle perps open BTC long 0.01",
|
|
52168
52478
|
"candle perps open ETH short 0.5 --price 3200 --tp 2900 --sl 3400",
|
|
52169
52479
|
"candle perps close BTC",
|
|
@@ -52208,7 +52518,7 @@ var HELP = {
|
|
|
52208
52518
|
launch: {
|
|
52209
52519
|
group: "Trade",
|
|
52210
52520
|
summary: "Create a token on Solana or Hood (the first buy is a separate swap)",
|
|
52211
|
-
description: "Creates a token with no first buy, so the launch and the position are two decisions rather than one. Needs the launch:write scope and an operator-enabled allowLaunch. The wallet decides the chain: a Hood TEE wallet launches on Hood, needs --dex-version, and signs one leg at a time (the curve, then the fee), each only after the one before it landed.",
|
|
52521
|
+
description: "Creates a token with no first buy, so the launch and the position are two decisions rather than one. Needs the launch:write scope and an operator-enabled allowLaunch. The wallet decides the chain: a Hood TEE wallet launches on Hood, needs --dex-version, and signs one leg at a time (the curve, then the fee), each only after the one before it landed. Launching from a TEE wallet needs Pro or Max (TIER_REQUIRED otherwise; candle plans).",
|
|
52212
52522
|
usage: [
|
|
52213
52523
|
"candle launch --name <name> --symbol <symbol> --image-url <url> --wallet <tee> [--quote-asset <asset>] [--dex-version v3|v4]"
|
|
52214
52524
|
],
|
|
@@ -52257,13 +52567,22 @@ var HELP = {
|
|
|
52257
52567
|
},
|
|
52258
52568
|
pnl: {
|
|
52259
52569
|
group: "Account",
|
|
52260
|
-
summary: "P&L per chain and combined: realized, fees, unrealized and open positions (--profile for one key's
|
|
52261
|
-
description: "Without --profile, the account's books: every profile, the web app and the CLI, one ledger, the same figures the
|
|
52570
|
+
summary: "P&L per chain and combined: realized, fees, unrealized and open positions (--profile for one key's share)",
|
|
52571
|
+
description: "Without --profile, the account's books: every profile, the web app and the CLI, one ledger, the same figures the console shows, to the cent for the same moment. Needs a key with the Read scope (account:read). With --profile <name>, that profile's key reads its share of the same P&L: realized on the sales it made, and the open positions that belong to it (the wallet's bound key, else the key whose buy opened them), with a row per wallet that sums to the total. Positions live in wallets: each wallet is its own average-cost pool, the account is the sum of its wallets, and a recorded move between two of the account's wallets carries its cost and realizes nothing. Both show a Solana block, a Hood block, then the combined figure, the closed positions as money made, minus money lost, plus partial sells, and a CHAIN column (and WALLET, where the API names it) on positions. The account's books add a By agent table (each key, then Manual for web-app trades, then the account total). Unpriced positions are shown as unpriced and never valued at zero; a position worth under one cent is marked dust and still counted. A bridge between Solana and Hood is not a trade: it opens and closes nothing, and bridge and Relay costs are not netted from P&L. Where Candle serves LP, DAMM v2 positions are included: realized (withdrawals against the cost basis at the add, plus claimed fees), unrealized (open positions at their share of the pool plus unclaimed fees, against that basis), and vs holding (what the deposited tokens would be worth held); Total stays the console's token figure, and Total with LP covers tokens and LP.",
|
|
52262
52572
|
usage: ["candle pnl [--profile <name>] [--json]"],
|
|
52263
52573
|
rows: [],
|
|
52264
52574
|
examples: ["candle pnl", "candle pnl --profile scalper --json"],
|
|
52265
52575
|
env: ENV_API
|
|
52266
52576
|
},
|
|
52577
|
+
plans: {
|
|
52578
|
+
group: "Account",
|
|
52579
|
+
summary: "The plans this API serves: prices, fees, limits and what each can do",
|
|
52580
|
+
description: "The plan table in force on the API this profile points at (GET /api/v1/agent/plans): each plan's monthly price, the agent trade fee, the Hyperliquid perps builder fee, the limits a new key gets, and one row per capability (buying and selling tokens not launched on Candle, self-launch, atomic launch, linked wallets, perps, limit orders). Every number is the server's; the CLI bundles none. Needs no key and sends none. Which plan this account is on is candle doctor's Plan row.",
|
|
52581
|
+
usage: ["candle plans [--json]"],
|
|
52582
|
+
rows: [],
|
|
52583
|
+
examples: ["candle plans", "candle plans --json", "candle plans --api-url https://staging.api.candle.tv"],
|
|
52584
|
+
env: ["CANDLE_API_URL"]
|
|
52585
|
+
},
|
|
52267
52586
|
keys: {
|
|
52268
52587
|
group: "Account",
|
|
52269
52588
|
summary: "API keys, and the wallets each key may use",
|
|
@@ -52673,9 +52992,16 @@ var HELP = {
|
|
|
52673
52992
|
group: "Maintain",
|
|
52674
52993
|
summary: "Verify a release asset's Sigstore bundle",
|
|
52675
52994
|
description: "Verifies a release asset against the trusted root compiled into this binary. No network, no credentials, and nothing else installed: the bundle carries the certificate and the transparency-log entry.",
|
|
52676
|
-
usage: ["candle verify <file> --bundle <path>"],
|
|
52995
|
+
usage: ["candle verify <file> --bundle <path> [--identity <uri>] [--issuer <url>]"],
|
|
52677
52996
|
rows: [],
|
|
52678
|
-
flags: [
|
|
52997
|
+
flags: [
|
|
52998
|
+
{ invocation: "--bundle <path>", description: "The .sigstore.json bundle beside the asset" },
|
|
52999
|
+
{
|
|
53000
|
+
invocation: "--identity <uri>",
|
|
53001
|
+
description: "The signing identity to require; defaults to the release identity for the version in a latest.json beside the bundle"
|
|
53002
|
+
},
|
|
53003
|
+
{ invocation: "--issuer <url>", description: "The certificate issuer to require; defaults to GitHub Actions'" }
|
|
53004
|
+
],
|
|
52679
53005
|
examples: ["candle verify ./candle-darwin-arm64 --bundle ./candle-darwin-arm64.sigstore.json"]
|
|
52680
53006
|
},
|
|
52681
53007
|
completion: {
|
|
@@ -60047,6 +60373,7 @@ var MCP_TOOL_NAMES = [
|
|
|
60047
60373
|
"candle_get_feed",
|
|
60048
60374
|
"candle_token_forensics",
|
|
60049
60375
|
"candle_get_agent_profile",
|
|
60376
|
+
"candle_get_plans",
|
|
60050
60377
|
"candle_report_activity",
|
|
60051
60378
|
"candle_trade",
|
|
60052
60379
|
"candle_swap",
|
|
@@ -60067,14 +60394,16 @@ var MCP_TOOL_NAMES = [
|
|
|
60067
60394
|
"candle_perps_cancel",
|
|
60068
60395
|
"candle_perps_orders",
|
|
60069
60396
|
"candle_perps_positions",
|
|
60070
|
-
"candle_perps_leverage"
|
|
60397
|
+
"candle_perps_leverage",
|
|
60398
|
+
"candle_perps_deposit"
|
|
60071
60399
|
];
|
|
60072
60400
|
var READ_ONLY_TOOL_NAMES = [
|
|
60073
60401
|
"candle_get_market",
|
|
60074
60402
|
"candle_get_feed",
|
|
60075
60403
|
"candle_token_forensics",
|
|
60076
60404
|
"candle_get_agent_profile",
|
|
60077
|
-
"candle_resolve_token"
|
|
60405
|
+
"candle_resolve_token",
|
|
60406
|
+
"candle_get_plans"
|
|
60078
60407
|
];
|
|
60079
60408
|
var CREDENTIAL_ENV_NAMES = [
|
|
60080
60409
|
"CANDLE_API_KEY",
|
|
@@ -60177,7 +60506,9 @@ async function mcp(args, ctx) {
|
|
|
60177
60506
|
|
|
60178
60507
|
// src/commands/perps.ts
|
|
60179
60508
|
init_args();
|
|
60180
|
-
|
|
60509
|
+
init_bridge();
|
|
60510
|
+
init_evm_lite();
|
|
60511
|
+
import { randomUUID as randomUUID4, sign as sign3 } from "node:crypto";
|
|
60181
60512
|
|
|
60182
60513
|
// src/hyperliquid.ts
|
|
60183
60514
|
init_sha3();
|
|
@@ -60995,6 +61326,318 @@ async function perpsOrders(args, ctx) {
|
|
|
60995
61326
|
return tradingFailure(ctx, error);
|
|
60996
61327
|
}
|
|
60997
61328
|
}
|
|
61329
|
+
var HYPERLIQUID_RELAY_CHAIN_ID = 1337;
|
|
61330
|
+
var HYPERLIQUID_RELAY_USDC = "0x00000000000000000000000000000000";
|
|
61331
|
+
var DEPOSIT_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
61332
|
+
var DEPOSIT_USAGE = "Usage: candle perps deposit <amount> <SOL|USDC|ETH|USDG> [--wallet <tee>] [--to <evm tee>] [--id <clientDepositId>] [--slippage-bps <n>] [--rpc-url <url>] [--yes] [--no-submit] | candle perps deposit status <clientDepositId>";
|
|
61333
|
+
function isDepositAsset(value) {
|
|
61334
|
+
return value !== undefined && Object.hasOwn(BRIDGE_ASSETS, value);
|
|
61335
|
+
}
|
|
61336
|
+
function depositBuildProblem(build, expect) {
|
|
61337
|
+
const destination = build.destination ?? {};
|
|
61338
|
+
if (build.walletId !== expect.walletId)
|
|
61339
|
+
return "the build names another paying wallet";
|
|
61340
|
+
if (build.asset !== expect.asset || build.amountRaw !== expect.amountRaw)
|
|
61341
|
+
return "the build is for another amount";
|
|
61342
|
+
if (destination.chainId !== HYPERLIQUID_RELAY_CHAIN_ID)
|
|
61343
|
+
return `the build lands on chain ${safeText(destination.chainId)}`;
|
|
61344
|
+
if (destination.currency !== HYPERLIQUID_RELAY_USDC)
|
|
61345
|
+
return "the build does not deliver Hyperliquid USDC";
|
|
61346
|
+
if (typeof destination.address !== "string" || !sameEvmAddress(destination.address, expect.destination))
|
|
61347
|
+
return `the build lands on ${safeText(destination.address)}, not ${expect.destination}`;
|
|
61348
|
+
const fee = build.fee ?? {};
|
|
61349
|
+
if (fee.bps !== 0 || fee.feeRaw !== "0")
|
|
61350
|
+
return "the build carries a Candle fee";
|
|
61351
|
+
return null;
|
|
61352
|
+
}
|
|
61353
|
+
async function perpsDepositStatus(ctx, id) {
|
|
61354
|
+
const key = await tradingKey(ctx);
|
|
61355
|
+
const result = await perpsRequest(ctx, key, `/api/v1/agent/perps/deposit/${encodeURIComponent(id)}`);
|
|
61356
|
+
if (ctx.json)
|
|
61357
|
+
return printTradingResult(ctx, result);
|
|
61358
|
+
const job = result.job ?? {};
|
|
61359
|
+
const destination = job.destination ?? {};
|
|
61360
|
+
const relay = job.relay;
|
|
61361
|
+
ctx.deps.stdout.write(`Deposit ${safeText(id)}: ${safeText(job.status)}${job.signature ? ` (${safeText(job.signature)})` : ""}, ${safeText(job.amountRaw)} raw ${safeText(job.asset)} to ${safeText(destination.address)} on Hyperliquid. Relay: ${relay ? safeText(relay.status) : "not sent yet, or unknown"}.
|
|
61362
|
+
`);
|
|
61363
|
+
return 0;
|
|
61364
|
+
}
|
|
61365
|
+
async function perpsDeposit(args, ctx) {
|
|
61366
|
+
if (args[0] === "status") {
|
|
61367
|
+
if (args.length !== 2 || !DEPOSIT_ID_RE.test(args[1]))
|
|
61368
|
+
return usage4(ctx, DEPOSIT_USAGE);
|
|
61369
|
+
try {
|
|
61370
|
+
return await perpsDepositStatus(ctx, args[1]);
|
|
61371
|
+
} catch (error) {
|
|
61372
|
+
return tradingFailure(ctx, error);
|
|
61373
|
+
}
|
|
61374
|
+
}
|
|
61375
|
+
const parsed = parseArgs(args, {
|
|
61376
|
+
valueFlags: ["--wallet", "--to", "--id", "--slippage-bps", "--rpc-url"],
|
|
61377
|
+
booleanFlags: ["--yes", "--no-submit"]
|
|
61378
|
+
});
|
|
61379
|
+
if ("error" in parsed)
|
|
61380
|
+
return usage4(ctx, parsed.error);
|
|
61381
|
+
const [amount, asset] = parsed.positionals;
|
|
61382
|
+
if (parsed.positionals.length !== 2 || !amount || !DEC_RE.test(amount) || !isDepositAsset(asset))
|
|
61383
|
+
return usage4(ctx, DEPOSIT_USAGE);
|
|
61384
|
+
const { values } = parsed;
|
|
61385
|
+
const id = values["--id"] ?? `deposit-${randomUUID4()}`;
|
|
61386
|
+
if (!DEPOSIT_ID_RE.test(id))
|
|
61387
|
+
return usage4(ctx, "--id must be 1 to 128 letters, digits, '.', '_', ':' or '-'.");
|
|
61388
|
+
const slippageBps = Number(values["--slippage-bps"] ?? "100");
|
|
61389
|
+
if (!Number.isInteger(slippageBps) || slippageBps < 1 || slippageBps > 1000)
|
|
61390
|
+
return usage4(ctx, "--slippage-bps must be a whole number from 1 to 1000.");
|
|
61391
|
+
const origin = BRIDGE_ASSETS[asset].chain;
|
|
61392
|
+
try {
|
|
61393
|
+
const key = await tradingKey(ctx);
|
|
61394
|
+
const payer = await tradingPayer(ctx, key, values["--wallet"], "swap:write", origin);
|
|
61395
|
+
if (payer.kind !== "tee")
|
|
61396
|
+
throw new TradingError("TEE_WALLET_REQUIRED", `A Hyperliquid deposit pays only from this key's ${origin === "hood" ? "Hood" : "Solana"} TEE wallet, and ${safeText(payer.address)} is the embedded wallet. Name the TEE wallet with --wallet. Nothing was built.`);
|
|
61397
|
+
const wallet = payer.wallet;
|
|
61398
|
+
let destination;
|
|
61399
|
+
if (origin === "hood") {
|
|
61400
|
+
if (values["--to"] !== undefined)
|
|
61401
|
+
throw new TradingError("BRIDGE_DESTINATION_MISSING", "From a Hood TEE wallet the deposit lands on that wallet's own Hyperliquid account; drop --to. Nothing was built.");
|
|
61402
|
+
destination = { id: wallet.id, address: wallet.address };
|
|
61403
|
+
} else {
|
|
61404
|
+
destination = (await perpsWallet(ctx, key, values["--to"])).row;
|
|
61405
|
+
}
|
|
61406
|
+
const amountRaw = rawAmount(amount, BRIDGE_ASSETS[asset].decimals);
|
|
61407
|
+
ctx.deps.stderr.write(`Deposit: ${id}
|
|
61408
|
+
`);
|
|
61409
|
+
const build = await perpsRequest(ctx, key, "/api/v1/agent/perps/deposit", {
|
|
61410
|
+
clientDepositId: id,
|
|
61411
|
+
walletId: wallet.id,
|
|
61412
|
+
asset,
|
|
61413
|
+
amountRaw,
|
|
61414
|
+
maxSlippageBps: slippageBps,
|
|
61415
|
+
...origin === "solana" ? { perpsWalletId: destination.id } : {}
|
|
61416
|
+
});
|
|
61417
|
+
if (build.job)
|
|
61418
|
+
return printTradingResult(ctx, build);
|
|
61419
|
+
const refused = (problem2) => new TradingError("PERPS_BUILD_REFUSED", `Refused to sign this deposit: ${problem2}. Nothing was signed.`);
|
|
61420
|
+
const problem = depositBuildProblem(build, {
|
|
61421
|
+
walletId: wallet.id,
|
|
61422
|
+
asset,
|
|
61423
|
+
amountRaw,
|
|
61424
|
+
destination: destination.address
|
|
61425
|
+
});
|
|
61426
|
+
if (problem)
|
|
61427
|
+
throw refused(problem);
|
|
61428
|
+
let sequenced;
|
|
61429
|
+
let transaction;
|
|
61430
|
+
const hoodOrigin = asset === "ETH" || asset === "USDG" ? asset : undefined;
|
|
61431
|
+
if (hoodOrigin) {
|
|
61432
|
+
const parsedLeg = sequencedSchema.safeParse(build);
|
|
61433
|
+
if (!parsedLeg.success)
|
|
61434
|
+
throw refused("Candle did not answer with a sequenced Hood leg");
|
|
61435
|
+
sequenced = parsedLeg.data;
|
|
61436
|
+
const plan = plannedLegKinds(sequenced.legKind, sequenced.plannedLegCount, false, "bridgeDeposit");
|
|
61437
|
+
if (!bridgePlanAdmitted(hoodOrigin, plan))
|
|
61438
|
+
throw refused(`the plan is ${sequenced.plannedLegCount} leg(s) starting with ${sequenced.legKind}`);
|
|
61439
|
+
const legProblem = relayHoodLegProblem(sequenced.legKind, sequenced.nextLeg, {
|
|
61440
|
+
origin: hoodOrigin,
|
|
61441
|
+
payer: wallet.address,
|
|
61442
|
+
amountRaw
|
|
61443
|
+
});
|
|
61444
|
+
if (legProblem)
|
|
61445
|
+
throw refused(legProblem);
|
|
61446
|
+
} else {
|
|
61447
|
+
const txs = Array.isArray(build.transactionsBase64) ? build.transactionsBase64 : [];
|
|
61448
|
+
if (txs.length !== 1 || typeof txs[0] !== "string")
|
|
61449
|
+
throw refused(`Candle returned ${txs.length} transactions, not one deposit`);
|
|
61450
|
+
transaction = txs[0];
|
|
61451
|
+
const reader = await lazySolanaClient(ctx, values["--rpc-url"])();
|
|
61452
|
+
const solanaProblem = await tradingRead(ctx, reader, () => relaySolanaDepositProblem(transaction, wallet.address, reader.rpc));
|
|
61453
|
+
if (solanaProblem)
|
|
61454
|
+
throw refused(solanaProblem);
|
|
61455
|
+
}
|
|
61456
|
+
const decimals = Number(build.outDecimals);
|
|
61457
|
+
const minimum = decimalAmount(String(build.minimumOutRaw), decimals);
|
|
61458
|
+
const estimate2 = decimalAmount(String(build.expectedOutRaw), decimals);
|
|
61459
|
+
const output = ctx.json ? ctx.deps.stderr : ctx.deps.stdout;
|
|
61460
|
+
output.write(`Deposit ${amount} ${asset} from ${safeText(wallet.address)} (${origin === "hood" ? "Hood" : "Solana"} TEE wallet) to Hyperliquid account ${safeText(destination.address)} through Relay.
|
|
61461
|
+
`);
|
|
61462
|
+
output.write(`Relay delivers at least ${minimum} USDC (estimate ${estimate2})${build.firstDeposit ? "; Hyperliquid keeps 1 USDC to activate this new account" : ""}. Candle fee: none.
|
|
61463
|
+
`);
|
|
61464
|
+
output.write("There is no withdrawal from Hyperliquid in this release, and `candle tee disable` or `tee sweep` do not reach it.\n");
|
|
61465
|
+
if (sequenced)
|
|
61466
|
+
output.write(`Legs, signed one at a time: ${sequenced.plannedLegCount === 2 ? "approval, bridgeDeposit" : "bridgeDeposit"}
|
|
61467
|
+
`);
|
|
61468
|
+
if (parsed.booleans.has("--no-submit"))
|
|
61469
|
+
return printTradingResult(ctx, { ...build, signed: false, submitted: false });
|
|
61470
|
+
if (!parsed.booleans.has("--yes")) {
|
|
61471
|
+
if (!ctx.deps.isTTY.stdin)
|
|
61472
|
+
throw new TradingError("CONFIRMATION_REQUIRED", "Run interactively to confirm, or use --yes.");
|
|
61473
|
+
if ((await ctx.deps.promptLine("Sign and send? [y/N] ")).trim().toLowerCase() !== "y") {
|
|
61474
|
+
if (sequenced)
|
|
61475
|
+
ctx.deps.stderr.write(`Nothing was signed. Operation ${sequenced.operationId} holds this wallet until ${new Date(sequenced.expiresAt).toISOString()}.
|
|
61476
|
+
`);
|
|
61477
|
+
throw new TradingError("CANCELLED", "Nothing was signed.");
|
|
61478
|
+
}
|
|
61479
|
+
}
|
|
61480
|
+
if (typeof build.expiresAt !== "number" || build.expiresAt <= ctx.deps.now())
|
|
61481
|
+
throw new TradingError("QUOTE_EXPIRED", "The deposit build expired before signing. Start again with a new --id.");
|
|
61482
|
+
let result;
|
|
61483
|
+
if (sequenced && hoodOrigin) {
|
|
61484
|
+
const run2 = await runSequencedLegs(ctx, key, {
|
|
61485
|
+
wallet,
|
|
61486
|
+
first: sequenced,
|
|
61487
|
+
submitPath: "/api/v1/agent/perps/deposit/submit",
|
|
61488
|
+
submitFields: { clientDepositId: id, depositId: build.depositId },
|
|
61489
|
+
unwrap: (answer) => answer,
|
|
61490
|
+
primaryLeg: "bridgeDeposit",
|
|
61491
|
+
allowedLegs: bridgeLegKinds(hoodOrigin),
|
|
61492
|
+
checkLeg: (kind, leg) => relayHoodLegProblem(kind, leg, { origin: hoodOrigin, payer: wallet.address, amountRaw }) === null,
|
|
61493
|
+
onLanded: async () => {}
|
|
61494
|
+
});
|
|
61495
|
+
result = { ...run2.final, landedLegs: run2.landed };
|
|
61496
|
+
} else {
|
|
61497
|
+
const signed = await relaySign(ctx, key, wallet, transaction);
|
|
61498
|
+
result = await perpsRequest(ctx, key, "/api/v1/agent/perps/deposit/submit", {
|
|
61499
|
+
clientDepositId: id,
|
|
61500
|
+
depositId: build.depositId,
|
|
61501
|
+
signedTransactionsBase64: [signed]
|
|
61502
|
+
});
|
|
61503
|
+
}
|
|
61504
|
+
ctx.deps.stderr.write(`Deposit sent; Relay fills it on Hyperliquid. Check it with candle perps deposit status ${id}.
|
|
61505
|
+
`);
|
|
61506
|
+
return printTradingResult(ctx, {
|
|
61507
|
+
...result,
|
|
61508
|
+
clientDepositId: id,
|
|
61509
|
+
expectedOutRaw: build.expectedOutRaw,
|
|
61510
|
+
minimumOutRaw: build.minimumOutRaw,
|
|
61511
|
+
outDecimals: build.outDecimals
|
|
61512
|
+
});
|
|
61513
|
+
} catch (error) {
|
|
61514
|
+
return tradingFailure(ctx, error);
|
|
61515
|
+
}
|
|
61516
|
+
}
|
|
61517
|
+
|
|
61518
|
+
// src/commands/plans.ts
|
|
61519
|
+
init_args();
|
|
61520
|
+
|
|
61521
|
+
// src/plans.ts
|
|
61522
|
+
var PLAN_CAPABILITY_NOTE = "A capability marked yes is what the plan allows. It is subject to the deployment's own switches (perps and own-wallet bridges each have one) and to the wallet, scopes and setup the feature needs.";
|
|
61523
|
+
var PLAN_CAPABILITY_LABELS = {
|
|
61524
|
+
tradeCandleTokens: "Trade Candle-launched tokens",
|
|
61525
|
+
tradeBaseAssets: "Trade base assets",
|
|
61526
|
+
freeBaseTransfers: "Base-pair swaps and own-wallet bridges (when enabled), no Candle fee",
|
|
61527
|
+
sellExternalTokens: "Sell tokens not launched on Candle",
|
|
61528
|
+
buyExternalTokens: "Buy tokens not launched on Candle",
|
|
61529
|
+
hyperliquidPerps: "Hyperliquid perps (when enabled)",
|
|
61530
|
+
selfLaunch: "Self-launch from a linked wallet",
|
|
61531
|
+
atomicLaunch: "Atomic launch with first buys",
|
|
61532
|
+
createLinkedWallets: "Create linked wallets",
|
|
61533
|
+
importLinkedWallets: "Import linked wallets",
|
|
61534
|
+
limitOrders: "Limit orders",
|
|
61535
|
+
quant: "Quant on Telegram"
|
|
61536
|
+
};
|
|
61537
|
+
var PLAN_LABELS = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
|
|
61538
|
+
function planLabel(plan) {
|
|
61539
|
+
return Object.hasOwn(PLAN_LABELS, plan) ? PLAN_LABELS[plan] : plan;
|
|
61540
|
+
}
|
|
61541
|
+
function formatPlanBps(bps) {
|
|
61542
|
+
return bps === 0 ? "none" : `${Number((bps / 100).toFixed(4))}%`;
|
|
61543
|
+
}
|
|
61544
|
+
function count(n) {
|
|
61545
|
+
return n.toLocaleString("en-US");
|
|
61546
|
+
}
|
|
61547
|
+
function priceCell(entry) {
|
|
61548
|
+
if (entry.price)
|
|
61549
|
+
return `$${count(entry.price.pricePerMonthUsd)} a month`;
|
|
61550
|
+
return entry.plan === "free" ? "free" : "not sold";
|
|
61551
|
+
}
|
|
61552
|
+
function planTableRows(table) {
|
|
61553
|
+
const plans = table.plans;
|
|
61554
|
+
const row = (label, cell) => [label, ...plans.map(cell)];
|
|
61555
|
+
const rows = [
|
|
61556
|
+
row("Price", priceCell),
|
|
61557
|
+
row("Agent trade fee", (e) => formatPlanBps(e.feeBps)),
|
|
61558
|
+
row("Perps builder fee", (e) => formatPlanBps(e.perpFeeBps)),
|
|
61559
|
+
row("Requests per minute", (e) => count(e.limits.rateLimitPerMin)),
|
|
61560
|
+
row("Launches per day", (e) => count(e.limits.dailyLaunchCap)),
|
|
61561
|
+
row("Uploads per minute", (e) => count(e.limits.uploadsPerMin)),
|
|
61562
|
+
row("Linked wallets", (e) => count(e.limits.linkedWallets))
|
|
61563
|
+
];
|
|
61564
|
+
const known = Object.keys(PLAN_CAPABILITY_LABELS);
|
|
61565
|
+
const served = new Set;
|
|
61566
|
+
for (const entry of plans)
|
|
61567
|
+
for (const key of Object.keys(entry.capabilities ?? {}))
|
|
61568
|
+
served.add(key);
|
|
61569
|
+
const keys = [
|
|
61570
|
+
...known.filter((k) => served.has(k)),
|
|
61571
|
+
...[...served].filter((k) => !Object.hasOwn(PLAN_CAPABILITY_LABELS, k))
|
|
61572
|
+
];
|
|
61573
|
+
for (const key of keys) {
|
|
61574
|
+
const label = Object.hasOwn(PLAN_CAPABILITY_LABELS, key) ? PLAN_CAPABILITY_LABELS[key] : key;
|
|
61575
|
+
rows.push(row(label, (e) => {
|
|
61576
|
+
const value = e.capabilities?.[key];
|
|
61577
|
+
return value === true ? "yes" : value === false ? "no" : "-";
|
|
61578
|
+
}));
|
|
61579
|
+
}
|
|
61580
|
+
return { headers: ["", ...plans.map((e) => planLabel(e.plan))], rows };
|
|
61581
|
+
}
|
|
61582
|
+
function planPromotionLine(table) {
|
|
61583
|
+
if (!(table.promoMaxDays > 0))
|
|
61584
|
+
return null;
|
|
61585
|
+
const days = table.promoMaxDays === 1 ? "1 day" : `${table.promoMaxDays} days`;
|
|
61586
|
+
return `A first Pro purchase includes Max for its first ${days}, then continues on Pro.`;
|
|
61587
|
+
}
|
|
61588
|
+
|
|
61589
|
+
// src/commands/plans.ts
|
|
61590
|
+
init_render();
|
|
61591
|
+
var USAGE3 = "Usage: candle plans";
|
|
61592
|
+
async function plans(args, ctx) {
|
|
61593
|
+
const parsed = parseArgs(args, {});
|
|
61594
|
+
if ("error" in parsed || parsed.positionals.length > 0) {
|
|
61595
|
+
writeUsageFailure(ctx.deps, "error" in parsed ? `${parsed.error}
|
|
61596
|
+
${USAGE3}` : USAGE3, ctx.json);
|
|
61597
|
+
return 2;
|
|
61598
|
+
}
|
|
61599
|
+
const result = await apiRequest("/api/v1/agent/plans", {
|
|
61600
|
+
auth: "none",
|
|
61601
|
+
credentials: {},
|
|
61602
|
+
apiUrl: ctx.apiUrl,
|
|
61603
|
+
fetch: ctx.deps.fetch,
|
|
61604
|
+
env: ctx.deps.env
|
|
61605
|
+
});
|
|
61606
|
+
if (!result.ok) {
|
|
61607
|
+
if (result.status === 404 && !ctx.json) {
|
|
61608
|
+
ctx.deps.stderr.write(`${ctx.apiUrl} does not serve the plan table yet (GET /api/v1/agent/plans answered 404).
|
|
61609
|
+
`);
|
|
61610
|
+
return 1;
|
|
61611
|
+
}
|
|
61612
|
+
writeFailure(ctx.deps, result, { apiUrl: ctx.apiUrl, authType: "none" }, ctx.json);
|
|
61613
|
+
return 1;
|
|
61614
|
+
}
|
|
61615
|
+
const body = result.body;
|
|
61616
|
+
if (ctx.json) {
|
|
61617
|
+
ctx.deps.stdout.write(`${JSON.stringify(body)}
|
|
61618
|
+
`);
|
|
61619
|
+
return 0;
|
|
61620
|
+
}
|
|
61621
|
+
const { headers, rows } = planTableRows(body);
|
|
61622
|
+
const out = ctx.deps.stdout;
|
|
61623
|
+
out.write(`Plans served by ${ctx.apiUrl}
|
|
61624
|
+
|
|
61625
|
+
`);
|
|
61626
|
+
out.write(`${renderTable(headers.map(terminalText), rows.map((row) => row.map(terminalText)))}
|
|
61627
|
+
`);
|
|
61628
|
+
const promotion = planPromotionLine(body);
|
|
61629
|
+
if (promotion)
|
|
61630
|
+
out.write(`
|
|
61631
|
+
${promotion}
|
|
61632
|
+
`);
|
|
61633
|
+
out.write(`
|
|
61634
|
+
${PLAN_CAPABILITY_NOTE}
|
|
61635
|
+
`);
|
|
61636
|
+
out.write(`
|
|
61637
|
+
The agent trade fee is charged on top of each trade or dev buy the API builds. Your account's plan: candle doctor.
|
|
61638
|
+
`);
|
|
61639
|
+
return 0;
|
|
61640
|
+
}
|
|
60998
61641
|
|
|
60999
61642
|
// src/commands/plugins.ts
|
|
61000
61643
|
init_args();
|
|
@@ -61417,6 +62060,44 @@ function shortAddress2(address) {
|
|
|
61417
62060
|
return address.length > 12 ? `${address.slice(0, 4)}…${address.slice(-4)}` : address;
|
|
61418
62061
|
}
|
|
61419
62062
|
|
|
62063
|
+
// src/commands/pnl-hyperliquid.ts
|
|
62064
|
+
init_render();
|
|
62065
|
+
function writeHyperliquidPnl(ctx, section) {
|
|
62066
|
+
if (!section)
|
|
62067
|
+
return;
|
|
62068
|
+
const out = ctx.deps.stdout;
|
|
62069
|
+
out.write(`
|
|
62070
|
+
Hyperliquid
|
|
62071
|
+
`);
|
|
62072
|
+
if (!section.read) {
|
|
62073
|
+
out.write(`Unavailable: ${terminalText(section.reason)}
|
|
62074
|
+
`);
|
|
62075
|
+
return;
|
|
62076
|
+
}
|
|
62077
|
+
out.write(`Network: ${section.network}
|
|
62078
|
+
`);
|
|
62079
|
+
out.write(`Realized gross ${formatUsd(section.realizedGrossUsd)}
|
|
62080
|
+
`);
|
|
62081
|
+
out.write(`Funding ${formatUsd(section.fundingUsd)} (received positive, paid negative)
|
|
62082
|
+
`);
|
|
62083
|
+
out.write(`Fees ${formatUsd(section.feesUsd)} (includes builder fees)
|
|
62084
|
+
`);
|
|
62085
|
+
out.write(`Realized net ${formatUsd(section.realizedNetUsd)} (gross + funding - fees)
|
|
62086
|
+
`);
|
|
62087
|
+
out.write(`${section.fills} fills; ${section.fundingPayments} funding payments. Unrealized perps are excluded.
|
|
62088
|
+
`);
|
|
62089
|
+
out.write(`Coverage: currently bound EVM TEE wallets since the current TEE binding; previous bindings excluded.
|
|
62090
|
+
`);
|
|
62091
|
+
for (const wallet of section.byWallet) {
|
|
62092
|
+
out.write(`${terminalText(wallet.address)} since ${new Date(wallet.startTime).toISOString()}
|
|
62093
|
+
`);
|
|
62094
|
+
}
|
|
62095
|
+
if (section.truncated) {
|
|
62096
|
+
out.write(`History is truncated: at most ${section.fillsLimit} recent fills and ${section.fundingLimit} funding rows per wallet, ${section.walletsLimit} wallets. This is a partial figure.
|
|
62097
|
+
`);
|
|
62098
|
+
}
|
|
62099
|
+
}
|
|
62100
|
+
|
|
61420
62101
|
// src/commands/pnl.ts
|
|
61421
62102
|
var CHAINS = ["solana", "hood"];
|
|
61422
62103
|
var CHAIN_TITLES = { solana: "Solana", hood: "Hood" };
|
|
@@ -61483,8 +62164,11 @@ async function pnl(args, ctx) {
|
|
|
61483
62164
|
return 0;
|
|
61484
62165
|
}
|
|
61485
62166
|
if (perProfile) {
|
|
61486
|
-
const { pnl: p, lp } = body;
|
|
61487
|
-
|
|
62167
|
+
const { pnl: p, lp, hyperliquid } = body;
|
|
62168
|
+
const engine = typeof p.totalUsd === "number";
|
|
62169
|
+
deps.stdout.write(engine ? `P&L for profile ${ctx.profileFlag} (key ${keyPrefix}): this key's share of the account's P&L
|
|
62170
|
+
|
|
62171
|
+
` : `P&L for profile ${ctx.profileFlag} (key ${keyPrefix}): this key's own fills
|
|
61488
62172
|
|
|
61489
62173
|
`);
|
|
61490
62174
|
writeChainBlocks(ctx, p.byChain);
|
|
@@ -61496,16 +62180,20 @@ async function pnl(args, ctx) {
|
|
|
61496
62180
|
unmarked: p.unmarkedPositions,
|
|
61497
62181
|
unvalued: p.unvalued,
|
|
61498
62182
|
counted: p.counted,
|
|
62183
|
+
totalUsd: p.totalUsd,
|
|
62184
|
+
openPositionsExDust: p.openPositionsExDust,
|
|
62185
|
+
closed: p.closed,
|
|
61499
62186
|
positions: p.openPositions.length,
|
|
61500
62187
|
truncated: p.truncated,
|
|
61501
62188
|
lookback: p.lookback,
|
|
61502
|
-
lookbackUnit: "trades",
|
|
62189
|
+
lookbackUnit: engine ? "of the account's ledger rows" : "trades",
|
|
61503
62190
|
oldestMarkAt: p.oldestMarkAt,
|
|
61504
62191
|
lp
|
|
61505
62192
|
});
|
|
61506
62193
|
writePositions(ctx, p.openPositions, false);
|
|
61507
|
-
writeWallets(ctx, p.byWallet);
|
|
62194
|
+
writeWallets(ctx, p.byWallet, engine);
|
|
61508
62195
|
writeLpPositions(ctx, lp, false);
|
|
62196
|
+
writeHyperliquidPnl(ctx, hyperliquid);
|
|
61509
62197
|
return 0;
|
|
61510
62198
|
}
|
|
61511
62199
|
const books = body;
|
|
@@ -61522,6 +62210,8 @@ async function pnl(args, ctx) {
|
|
|
61522
62210
|
oldestMarkAt: books.oldestMarkAt,
|
|
61523
62211
|
lp: books.lp
|
|
61524
62212
|
});
|
|
62213
|
+
writeWalletEvents(ctx, books);
|
|
62214
|
+
writeAgents(ctx, books.byAgent, books.all);
|
|
61525
62215
|
writePositions(ctx, books.positions, true);
|
|
61526
62216
|
writeLpPositions(ctx, books.lp, true);
|
|
61527
62217
|
return 0;
|
|
@@ -61552,7 +62242,7 @@ function writeChainBlocks(ctx, byChain) {
|
|
|
61552
62242
|
formatUsd(s.unrealizedUsd),
|
|
61553
62243
|
`${marked} of ${s.openPositions} open ${s.openPositions === 1 ? "position" : "positions"} marked${unmarked > 0 ? `; ${unmarked} unpriced, not counted` : ""}`
|
|
61554
62244
|
],
|
|
61555
|
-
["Total", formatUsd(s
|
|
62245
|
+
["Total", formatUsd(totalOf(s)), "realized net plus unrealized"]
|
|
61556
62246
|
]);
|
|
61557
62247
|
if (s.unvalued > 0) {
|
|
61558
62248
|
ctx.deps.stdout.write(`${s.unvalued} ${s.unvalued === 1 ? "fill" : "fills"} could not be valued and are not in these figures.
|
|
@@ -61564,6 +62254,12 @@ function writeChainBlocks(ctx, byChain) {
|
|
|
61564
62254
|
ctx.deps.stdout.write(`All chains
|
|
61565
62255
|
`);
|
|
61566
62256
|
}
|
|
62257
|
+
function totalOf(s) {
|
|
62258
|
+
return typeof s.totalUsd === "number" ? s.totalUsd : s.realizedNetUsd + s.unrealizedUsd;
|
|
62259
|
+
}
|
|
62260
|
+
function isUnplacedWallet(wallet) {
|
|
62261
|
+
return wallet.startsWith("unknown:");
|
|
62262
|
+
}
|
|
61567
62263
|
function writeLines(ctx, lines) {
|
|
61568
62264
|
const width = Math.max(...lines.map(([label]) => label.length));
|
|
61569
62265
|
const valueWidth = Math.max(...lines.map(([, value]) => value.length));
|
|
@@ -61572,19 +62268,20 @@ function writeLines(ctx, lines) {
|
|
|
61572
62268
|
`);
|
|
61573
62269
|
}
|
|
61574
62270
|
}
|
|
61575
|
-
function writeWallets(ctx, wallets2) {
|
|
62271
|
+
function writeWallets(ctx, wallets2, engine) {
|
|
61576
62272
|
if (!wallets2 || wallets2.length === 0)
|
|
61577
62273
|
return;
|
|
61578
62274
|
const rows = wallets2.map((w) => [
|
|
61579
|
-
`${w.label?.trim() ? `${w.label.trim()} ` : w.payerType === "main" ? "main " : ""}(${shortAddress2(w.wallet)})`,
|
|
62275
|
+
isUnplacedWallet(w.wallet) ? "not placed in a wallet" : `${w.label?.trim() ? `${w.label.trim()} ` : w.payerType === "main" ? "main " : ""}(${shortAddress2(w.wallet)})`,
|
|
61580
62276
|
w.chain ?? "-",
|
|
61581
62277
|
String(w.tradesConsidered),
|
|
61582
62278
|
formatUsd(w.realizedNetUsd),
|
|
61583
62279
|
formatUsd(w.unrealizedUsd),
|
|
61584
62280
|
`${w.openPositions.length}${w.unmarkedPositions > 0 ? ` (${w.unmarkedPositions} unpriced)` : ""}`
|
|
61585
62281
|
]);
|
|
62282
|
+
const heading = engine ? "By wallet (each its own cost basis; the rows sum to the total)" : "By wallet (each its own cost basis, so the rows need not sum to the total)";
|
|
61586
62283
|
ctx.deps.stdout.write(`
|
|
61587
|
-
|
|
62284
|
+
${heading}
|
|
61588
62285
|
${renderTable(["WALLET", "CHAIN", "TRADES", "REALIZED NET", "UNREALIZED", "OPEN"], rows.map((row) => row.map(terminalText)))}
|
|
61589
62286
|
`);
|
|
61590
62287
|
}
|
|
@@ -61602,6 +62299,7 @@ function writeSummary(ctx, s) {
|
|
|
61602
62299
|
`${marked} of ${s.positions} open ${s.positions === 1 ? "position" : "positions"} marked${s.unmarked > 0 ? `; ${s.unmarked} unpriced, not counted` : ""}`
|
|
61603
62300
|
]
|
|
61604
62301
|
];
|
|
62302
|
+
const total = totalOf(s);
|
|
61605
62303
|
const lp = s.lp;
|
|
61606
62304
|
if (lp?.read) {
|
|
61607
62305
|
const plural2 = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
@@ -61617,17 +62315,24 @@ function writeSummary(ctx, s) {
|
|
|
61617
62315
|
"LP vs holding",
|
|
61618
62316
|
lp.vsHoldingPositions > 0 ? formatUsd(lp.vsHoldingUsd) : "-",
|
|
61619
62317
|
lp.vsHoldingPositions > 0 ? `${lp.vsHoldingPositions} of ${lp.valued} valued, against holding the deposited tokens (now ${formatUsd(lp.holdValueUsd)})` : "no valued LP position with every deposit priced"
|
|
61620
|
-
], [
|
|
61621
|
-
"Total",
|
|
61622
|
-
formatUsd(
|
|
61623
|
-
"
|
|
62318
|
+
], ["Total", formatUsd(total), "realized net plus unrealized, tokens only, as the console shows it"], [
|
|
62319
|
+
"Total with LP",
|
|
62320
|
+
formatUsd(total + lp.realizedUsd + lp.unrealizedUsd),
|
|
62321
|
+
"the total plus LP realized and unrealized"
|
|
61624
62322
|
]);
|
|
61625
62323
|
} else if (lp) {
|
|
61626
|
-
lines.push(["LP", "not read", `${terminalText(lp.reason)}; not in the total`], ["Total", formatUsd(
|
|
62324
|
+
lines.push(["LP", "not read", `${terminalText(lp.reason)}; not in the total`], ["Total", formatUsd(total), "realized net plus unrealized, tokens only"]);
|
|
61627
62325
|
} else {
|
|
61628
|
-
lines.push(["Total", formatUsd(
|
|
62326
|
+
lines.push(["Total", formatUsd(total), "realized net plus unrealized"]);
|
|
61629
62327
|
}
|
|
61630
62328
|
writeLines(ctx, lines);
|
|
62329
|
+
if (s.closed)
|
|
62330
|
+
writeClosed(ctx, s.closed);
|
|
62331
|
+
const dust = s.openPositionsExDust !== undefined ? s.positions - s.openPositionsExDust : 0;
|
|
62332
|
+
if (dust > 0) {
|
|
62333
|
+
ctx.deps.stdout.write(`${dust} open ${dust === 1 ? "position is" : "positions are"} dust (worth under one cent): listed and marked, and counted in the figures.
|
|
62334
|
+
`);
|
|
62335
|
+
}
|
|
61631
62336
|
if (s.oldestMarkAt !== undefined) {
|
|
61632
62337
|
ctx.deps.stdout.write(`Marks as old as ${new Date(s.oldestMarkAt).toISOString()}.
|
|
61633
62338
|
`);
|
|
@@ -61655,6 +62360,50 @@ function writeSummary(ctx, s) {
|
|
|
61655
62360
|
}
|
|
61656
62361
|
}
|
|
61657
62362
|
}
|
|
62363
|
+
function writeClosed(ctx, c) {
|
|
62364
|
+
const counts = [`${c.wins} won`, `${c.losses} lost`];
|
|
62365
|
+
if (c.costUnknown > 0)
|
|
62366
|
+
counts.push(`${c.costUnknown} with unknown cost`);
|
|
62367
|
+
ctx.deps.stdout.write(`Closed: ${c.closed} ${c.closed === 1 ? "position" : "positions"} (${counts.join(", ")}). Money made ${formatUsd(c.madeUsd)}, money lost ${formatUsd(c.lostUsd)} and partial sells ${formatUsd(c.partialSellsUsd)} add up to the realized net.
|
|
62368
|
+
`);
|
|
62369
|
+
}
|
|
62370
|
+
function writeWalletEvents(ctx, books) {
|
|
62371
|
+
const unknown = books.costUnknown?.length ?? 0;
|
|
62372
|
+
if (unknown > 0) {
|
|
62373
|
+
ctx.deps.stdout.write(`${unknown} ${unknown === 1 ? "arrival" : "arrivals"} of tokens came with no cost anyone can state: selling them realizes nothing (--json lists them).
|
|
62374
|
+
`);
|
|
62375
|
+
}
|
|
62376
|
+
const out = books.movedOutUntracked?.length ?? 0;
|
|
62377
|
+
if (out > 0) {
|
|
62378
|
+
ctx.deps.stdout.write(`${out} ${out === 1 ? "move" : "moves"} out of a wallet went where Candle does not follow: the tokens left at cost, not as a sale (--json lists them).
|
|
62379
|
+
`);
|
|
62380
|
+
}
|
|
62381
|
+
}
|
|
62382
|
+
function writeAgents(ctx, agents, all) {
|
|
62383
|
+
if (!agents || agents.length === 0)
|
|
62384
|
+
return;
|
|
62385
|
+
const open4 = (count2, unmarked) => `${count2}${unmarked > 0 ? ` (${unmarked} unpriced)` : ""}`;
|
|
62386
|
+
const rows = agents.map((a) => [
|
|
62387
|
+
a.keyPrefix === null ? "Manual (web app)" : a.label && a.label !== a.keyPrefix ? `${a.label} (${a.keyPrefix})` : a.keyPrefix,
|
|
62388
|
+
formatUsd(a.realizedNetUsd),
|
|
62389
|
+
formatUsd(a.unrealizedUsd),
|
|
62390
|
+
formatUsd(totalOf(a)),
|
|
62391
|
+
open4(a.openPositions, a.unmarked),
|
|
62392
|
+
String(a.closedRounds)
|
|
62393
|
+
]);
|
|
62394
|
+
rows.push([
|
|
62395
|
+
"Account total",
|
|
62396
|
+
formatUsd(all.realizedNetUsd),
|
|
62397
|
+
formatUsd(all.unrealizedUsd),
|
|
62398
|
+
formatUsd(totalOf(all)),
|
|
62399
|
+
open4(all.openPositions, all.unmarked),
|
|
62400
|
+
all.closedRounds !== undefined ? String(all.closedRounds) : "-"
|
|
62401
|
+
]);
|
|
62402
|
+
ctx.deps.stdout.write(`
|
|
62403
|
+
By agent (each key, then Manual; the rows sum to the account total)
|
|
62404
|
+
${renderTable(["AGENT", "REALIZED NET", "UNREALIZED", "TOTAL", "OPEN", "CLOSED"], rows.map((row) => row.map(terminalText)))}
|
|
62405
|
+
`);
|
|
62406
|
+
}
|
|
61658
62407
|
function writeLpPositions(ctx, lp, withBook) {
|
|
61659
62408
|
if (!lp?.read || lp.positions.length === 0)
|
|
61660
62409
|
return;
|
|
@@ -61688,13 +62437,16 @@ No open positions.
|
|
|
61688
62437
|
`);
|
|
61689
62438
|
return;
|
|
61690
62439
|
}
|
|
61691
|
-
const
|
|
62440
|
+
const withWallet = positions.some((p) => p.wallet !== undefined);
|
|
62441
|
+
const headers = ["TOKEN", "CHAIN", ...withWallet ? ["WALLET"] : [], "QUANTITY", "AVG ENTRY", "MARK", "UNREALIZED"];
|
|
61692
62442
|
if (withBook)
|
|
61693
62443
|
headers.push("BOOK");
|
|
62444
|
+
const wallet = (w) => w === undefined || isUnplacedWallet(w) ? "-" : shortAddress2(w);
|
|
61694
62445
|
const rows = positions.map((p) => {
|
|
61695
62446
|
const row = [
|
|
61696
|
-
p.symbol?.trim() || shortAddress2(p.mint)
|
|
62447
|
+
`${p.symbol?.trim() || shortAddress2(p.mint)}${p.dust === true ? " (dust)" : ""}`,
|
|
61697
62448
|
p.chain ?? "-",
|
|
62449
|
+
...withWallet ? [wallet(p.wallet)] : [],
|
|
61698
62450
|
formatQuantity(p.quantity),
|
|
61699
62451
|
formatPrice(p.avgEntryUsd),
|
|
61700
62452
|
p.markPriceUsd !== undefined ? formatPrice(p.markPriceUsd) : "unpriced",
|
|
@@ -62326,11 +63078,11 @@ function writeTable(ctx, groups, totals) {
|
|
|
62326
63078
|
rows.push([g.group, w.chain, name2, UNREAD_LABELS[part], "not read", "-", "-"]);
|
|
62327
63079
|
}
|
|
62328
63080
|
const empty = g.wallets.length - shown.length;
|
|
62329
|
-
const
|
|
63081
|
+
const count2 = `${g.wallets.length} ${g.wallets.length === 1 ? "wallet" : "wallets"}`;
|
|
62330
63082
|
notes.push([
|
|
62331
63083
|
g.group,
|
|
62332
63084
|
g.read ? formatUsd(g.valueUsd) : "-",
|
|
62333
|
-
g.read ? `${
|
|
63085
|
+
g.read ? `${count2}${empty > 0 ? `, ${empty} empty not shown` : ""}${lpCount > 0 ? `, ${lpCount} LP ${lpCount === 1 ? "position" : "positions"}` : ""}${lpUnread > 0 ? `, ${lpUnread} LP not read` : ""}${g.unpriced > 0 ? `, ${g.unpriced} unpriced` : ""}` : g.reason ?? "not read"
|
|
62334
63086
|
]);
|
|
62335
63087
|
}
|
|
62336
63088
|
if (rows.length > 0)
|
|
@@ -63210,7 +63962,7 @@ init_evm_lite();
|
|
|
63210
63962
|
init_profiles();
|
|
63211
63963
|
init_render();
|
|
63212
63964
|
init_trading();
|
|
63213
|
-
var
|
|
63965
|
+
var USAGE4 = "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]";
|
|
63214
63966
|
var TRANSFER_ASSETS = [...Object.keys(BASES), ...Object.keys(HOOD_BASES)];
|
|
63215
63967
|
var BASE58_ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
63216
63968
|
async function readLinkedWallets(ctx, key) {
|
|
@@ -63296,7 +64048,7 @@ async function transfer(args, ctx) {
|
|
|
63296
64048
|
const flags = parsed.values;
|
|
63297
64049
|
const asset = flags["--asset"]?.toUpperCase();
|
|
63298
64050
|
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) {
|
|
63299
|
-
writeUsageFailure(ctx.deps,
|
|
64051
|
+
writeUsageFailure(ctx.deps, USAGE4, ctx.json);
|
|
63300
64052
|
return 2;
|
|
63301
64053
|
}
|
|
63302
64054
|
const to = flags["--to"];
|
|
@@ -65938,11 +66690,11 @@ Anyone who reads them can move every derived key's funds.
|
|
|
65938
66690
|
`);
|
|
65939
66691
|
return 0;
|
|
65940
66692
|
}
|
|
65941
|
-
function randomPositions(
|
|
66693
|
+
function randomPositions(count2, of = PHRASE_WORDS) {
|
|
65942
66694
|
const chosen = new Set;
|
|
65943
66695
|
const scratch = new Uint32Array(1);
|
|
65944
66696
|
const limit = Math.floor(4294967296 / of) * of;
|
|
65945
|
-
while (chosen.size <
|
|
66697
|
+
while (chosen.size < count2) {
|
|
65946
66698
|
crypto.getRandomValues(scratch);
|
|
65947
66699
|
const draw = scratch[0] ?? 0;
|
|
65948
66700
|
if (draw >= limit)
|
|
@@ -67350,8 +68102,8 @@ async function readEvmBalances(addresses, rpcUrl, fetchFn) {
|
|
|
67350
68102
|
}
|
|
67351
68103
|
return read;
|
|
67352
68104
|
}
|
|
67353
|
-
function evmRequestsPlanned(
|
|
67354
|
-
return `${1 +
|
|
68105
|
+
function evmRequestsPlanned(count2) {
|
|
68106
|
+
return `${1 + count2} to ${1 + 2 * count2}`;
|
|
67355
68107
|
}
|
|
67356
68108
|
async function vaultList(args, ctx) {
|
|
67357
68109
|
const parsed = parseArgs(args, {
|
|
@@ -69978,9 +70730,9 @@ function resolveTarget(index, old, id) {
|
|
|
69978
70730
|
throw new VaultError("VAULT_LABEL_NOT_FOUND", id === undefined ? `No key in this vault is called ${old}, and no key has that address or id.` : `No key in this vault has the id ${id}.`, { suggestion: "List them with their labels: `candle vault status --unlock`" });
|
|
69979
70731
|
}
|
|
69980
70732
|
const candidates = match.candidates.map((entry) => `${entry.address} (${entry.id})`).join(", ");
|
|
69981
|
-
const
|
|
70733
|
+
const count2 = match.candidates.length;
|
|
69982
70734
|
const first = match.candidates[0];
|
|
69983
|
-
throw new VaultError("VAULT_LABEL_AMBIGUOUS", match.by === "label" ? `${
|
|
70735
|
+
throw new VaultError("VAULT_LABEL_AMBIGUOUS", match.by === "label" ? `${count2} keys in this vault are called ${old}, so this rename would not say which one it meant. Nothing was written.` : `${count2} keys in this vault have the address ${old}, so this rename would not say which one it meant. Nothing was written.`, {
|
|
69984
70736
|
suggestion: match.by === "label" ? `Name one by address or id. The candidates are ${candidates}.` : `Name one by id. The candidates are ${candidates}. Re-run: \`candle vault rename ${old} <new-label> --id ${first.id}\`.`
|
|
69985
70737
|
});
|
|
69986
70738
|
}
|
|
@@ -70144,7 +70896,7 @@ async function discardIncompleteRestore(ctx, path, sidecarExisted) {
|
|
|
70144
70896
|
}
|
|
70145
70897
|
}
|
|
70146
70898
|
var RESTORED_BRANCHES = ["solanaVault", "solanaTee", "solanaExternal"];
|
|
70147
|
-
function parseCounts(
|
|
70899
|
+
function parseCounts(count2, teeCount, externalCount, rpcUrl, evmCount, evmTeeCount) {
|
|
70148
70900
|
const parse = (raw, flag) => {
|
|
70149
70901
|
if (raw === undefined)
|
|
70150
70902
|
return;
|
|
@@ -70153,7 +70905,7 @@ function parseCounts(count, teeCount, externalCount, rpcUrl, evmCount, evmTeeCou
|
|
|
70153
70905
|
return { error: `${flag} must be a whole number, zero or greater.` };
|
|
70154
70906
|
return value;
|
|
70155
70907
|
};
|
|
70156
|
-
const vaultCount = parse(
|
|
70908
|
+
const vaultCount = parse(count2, "--count");
|
|
70157
70909
|
if (typeof vaultCount === "object" && vaultCount !== null)
|
|
70158
70910
|
return vaultCount;
|
|
70159
70911
|
const teeParsed = parse(teeCount, "--tee-count");
|
|
@@ -71283,7 +72035,7 @@ init_args();
|
|
|
71283
72035
|
init_release();
|
|
71284
72036
|
import { dirname as dirname10, join as join11 } from "node:path";
|
|
71285
72037
|
init_render();
|
|
71286
|
-
var
|
|
72038
|
+
var USAGE5 = "Usage: candle verify <file> --bundle <path> [--identity <uri>] [--issuer <url>]";
|
|
71287
72039
|
async function resolveIdentity(deps, bundlePath, flag) {
|
|
71288
72040
|
if (flag)
|
|
71289
72041
|
return { kind: "ok", uri: flag, provenance: "identity from --identity" };
|
|
@@ -71315,19 +72067,19 @@ async function verify(args, ctx) {
|
|
|
71315
72067
|
});
|
|
71316
72068
|
if ("error" in parsed) {
|
|
71317
72069
|
writeUsageFailure(deps, `${parsed.error}
|
|
71318
|
-
${
|
|
72070
|
+
${USAGE5}`, json);
|
|
71319
72071
|
return 2;
|
|
71320
72072
|
}
|
|
71321
72073
|
const file = parsed.positionals[0];
|
|
71322
72074
|
if (parsed.positionals.length !== 1 || file === undefined) {
|
|
71323
72075
|
writeUsageFailure(deps, `verify takes exactly one file.
|
|
71324
|
-
${
|
|
72076
|
+
${USAGE5}`, json);
|
|
71325
72077
|
return 2;
|
|
71326
72078
|
}
|
|
71327
72079
|
const bundlePath = parsed.values["--bundle"];
|
|
71328
72080
|
if (!bundlePath) {
|
|
71329
72081
|
writeUsageFailure(deps, `--bundle is required.
|
|
71330
|
-
${
|
|
72082
|
+
${USAGE5}`, json);
|
|
71331
72083
|
return 2;
|
|
71332
72084
|
}
|
|
71333
72085
|
const resolved = await resolveIdentity(deps, bundlePath, parsed.values["--identity"]);
|
|
@@ -71341,7 +72093,7 @@ ${USAGE4}`, json);
|
|
|
71341
72093
|
}
|
|
71342
72094
|
if (resolved.kind === "absent") {
|
|
71343
72095
|
writeUsageFailure(deps, `--identity is required: there is no latest.json beside ${bundlePath} to take the release version from.
|
|
71344
|
-
${
|
|
72096
|
+
${USAGE5}`, json);
|
|
71345
72097
|
return 2;
|
|
71346
72098
|
}
|
|
71347
72099
|
const identity = resolved.uri;
|
|
@@ -71389,8 +72141,8 @@ init_wallets();
|
|
|
71389
72141
|
init_args();
|
|
71390
72142
|
init_render();
|
|
71391
72143
|
init_trading();
|
|
71392
|
-
import { randomUUID as
|
|
71393
|
-
var
|
|
72144
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
72145
|
+
var USAGE6 = "Usage: candle wallet close-empty [--wallet embedded] [--keep <mint>]... [--client-trade-id <id>] [--yes] [--json]";
|
|
71394
72146
|
var BASE58_ADDRESS2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
71395
72147
|
var LISTED_ROWS = 50;
|
|
71396
72148
|
function splitKeep(args) {
|
|
@@ -71465,7 +72217,7 @@ async function embeddedSolana(ctx, key, wallet) {
|
|
|
71465
72217
|
async function walletsCloseEmpty(args, ctx) {
|
|
71466
72218
|
const lifted = splitKeep(args);
|
|
71467
72219
|
if ("error" in lifted) {
|
|
71468
|
-
writeUsageFailure(ctx.deps, `${lifted.error}. ${
|
|
72220
|
+
writeUsageFailure(ctx.deps, `${lifted.error}. ${USAGE6}`, ctx.json);
|
|
71469
72221
|
return 2;
|
|
71470
72222
|
}
|
|
71471
72223
|
const parsed = parseArgs(lifted.rest, { valueFlags: ["--wallet", "--client-trade-id"], booleanFlags: ["--yes"] });
|
|
@@ -71474,9 +72226,9 @@ async function walletsCloseEmpty(args, ctx) {
|
|
|
71474
72226
|
return 2;
|
|
71475
72227
|
}
|
|
71476
72228
|
const named = parsed.values["--client-trade-id"];
|
|
71477
|
-
const id = named ?? `close-${
|
|
72229
|
+
const id = named ?? `close-${randomUUID5()}`;
|
|
71478
72230
|
if (parsed.positionals.length !== 0 || !validClientId(id) || !lifted.keep.every((m) => BASE58_ADDRESS2.test(m))) {
|
|
71479
|
-
writeUsageFailure(ctx.deps,
|
|
72231
|
+
writeUsageFailure(ctx.deps, USAGE6, ctx.json);
|
|
71480
72232
|
return 2;
|
|
71481
72233
|
}
|
|
71482
72234
|
const keep = lifted.keep;
|
|
@@ -71883,10 +72635,12 @@ var COMMANDS = {
|
|
|
71883
72635
|
launch: { bare: launch },
|
|
71884
72636
|
pnl: { bare: pnl },
|
|
71885
72637
|
portfolio: { bare: portfolio },
|
|
72638
|
+
plans: { bare: plans },
|
|
71886
72639
|
lp: { subcommands: { pools: lpPools, add: lpAdd, positions: lpPositions, remove: lpRemove, claim: lpClaim } },
|
|
71887
72640
|
perps: {
|
|
71888
72641
|
subcommands: {
|
|
71889
72642
|
setup: perpsSetup,
|
|
72643
|
+
deposit: perpsDeposit,
|
|
71890
72644
|
open: perpsOpen,
|
|
71891
72645
|
close: perpsClose,
|
|
71892
72646
|
cancel: perpsCancel,
|
|
@@ -72008,6 +72762,7 @@ var NEVER_GUARDED = new Set([
|
|
|
72008
72762
|
"profile",
|
|
72009
72763
|
"doctor",
|
|
72010
72764
|
"verify",
|
|
72765
|
+
"plans",
|
|
72011
72766
|
"update",
|
|
72012
72767
|
"help",
|
|
72013
72768
|
"completion",
|