@candledottv/cli 0.11.16 → 0.11.17
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 +17 -10
- package/dist/index.js +348 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @candledottv/cli
|
|
2
2
|
|
|
3
|
-
The Candle CLI (current version 0.11.
|
|
3
|
+
The Candle CLI (current version 0.11.17): authorize a device from your browser, keep your own keys
|
|
4
4
|
in an encrypted vault on this machine, hand an agent a TEE wallet it can trade, and manage API
|
|
5
5
|
keys, wallets, and setup health from the terminal. Zero runtime dependencies; the whole thing is
|
|
6
6
|
one self-contained `dist/index.js` that runs under plain Node.
|
|
@@ -80,6 +80,7 @@ emergency procedure, rebinding, trusted wallets and API key access levels are al
|
|
|
80
80
|
| `candle wallets` (alias `candle wallet`) | Shows the account's embedded (launch) wallets and any linked wallets, using the API key, with a `Signer` column saying whether this machine holds each linked wallet's signing key and a `Trusted` column saying whether it is yours (linked while signed in, or marked with `trust`). It links and revokes; it never generates or prints a key, which the vault now derives. |
|
|
81
81
|
| `candle wallets import --chain solana\|evm`, `wallets revoke <wallet-id>` | Link a wallet you own (key from `--key-file` or a hidden prompt, sealed to Privy before it leaves the machine), or unlink one. |
|
|
82
82
|
| `candle wallets trust\|untrust <label\|address\|id\|prefix*>...` | Mark linked wallets as yours so an agent's Read:Write:Transfer key can move funds into them, or clear the mark. Owner only, over the device token, with a typed confirm; a trailing `*`, as in `'tr-*'`, selects by prefix. |
|
|
83
|
+
| `candle wallets allow-launch\|disallow-launch <label\|address\|id\|prefix*>...` | Let TEE wallets pay for a launch (`candle launch`), or stop them. Owner only, over the device token; the screen shows each wallet's label, address and bound key before a typed confirm. `disallow-launch --yes` skips the confirm. |
|
|
83
84
|
| `candle wallets close-empty [--wallet embedded] [--keep <mint>]... [--yes]` | Closes the embedded wallet's empty token accounts and returns their rent to it, after a preview (`transfer:write`). |
|
|
84
85
|
| `candle vault init\|status\|list\|new-key\|rename\|phrase show\|restore\|reconcile-exposure` | The encrypted local vault, `vault.enc` (Tier 1). `init` creates it with a passphrase factor and a 24-word recovery phrase (and no key), `new-key` derives a key inside it (`--count`, `--labels-from <file>` for many under one unlock), `list [filter] [--balances]` shows one line per key, `rename` changes a label only, `phrase show` displays the phrase on a terminal (never in `--json`, a log or a pipe), and `restore --phrase` rebuilds the derived keys on another machine with a new passphrase (`--count`, `--tee-count`, `--external-count`, `--evm-count` and `--evm-tee-count` for EVM keys and Hood TEE wallets, or a Solana gap scan with `--rpc-url`); a restored vault never allocates new keys. `reconcile-exposure` re-reads the account and adds exposure records, clearing nothing. No key ever leaves the vault to reach Candle. See [CLI custody](https://docs.candle.tv/developers/cli-custody). |
|
|
85
86
|
| `candle vault factor list\|add passphrase\|add security-key\|add touch-id\|add passkey\|remove` (or `vault enroll <kind>`), `vault backup --to <path>\|icloud`, `vault verify-backup <path>` | What can open the vault, and proving a copy of it works: `backup` verifies the copy in full before reporting (a copy is sealed, keeping only the passphrase and security-key envelopes, unless it goes to a recognised local disk or removable drive, so a cloud folder and any path the CLI cannot place are both sealed unless `--accept-shared-domain` is passed), and `verify-backup` re-checks an existing one against this vault's key and address set. `add security-key` enrolls a FIDO2 security key (CTAP2 `hmac-secret`, user-verified with the key's PIN or biometric) through the bundled `candle-fido2` helper (`--install-helper` fetches and verifies this release's helper first); one key is not a recoverable factor, two keys are a pair, an enrolled key can authorize adding another, and the passphrase stays the recovery floor. `add touch-id` and `add passkey` are refused with `VAULT_FACTOR_UNSUPPORTED_ON_PLATFORM` until the Apple-signed helper ships. Every vault command then takes `--factor <id\|passphrase\|security-key>` to say which envelope opens it and `--device <id>` to name the key when several are attached. |
|
|
@@ -93,10 +94,10 @@ emergency procedure, rebinding, trusted wallets and API key access levels are al
|
|
|
93
94
|
| `candle tee signer new --key <prefix\|label> [--out <pem>] [--force]` | On the trading machine, with that key's API key: generates the key's signer here and waits for the owner to approve it with `keys signer approve`. The key's wallets then trade from this machine. `--out` also writes a plaintext PEM for an SDK process. |
|
|
94
95
|
| `candle tee rebind <wallet...> --to-key <prefix\|label>`, `candle tee rebinds [wallet]` | Move TEE wallets to another key on the same account (owner only, preview then `confirm`; funds and the relay signer do not move), and list the rebind history. |
|
|
95
96
|
| `candle tee new\|enable` | The legacy path: `new` seals a fresh key in its own `tee-wallets.enc` and `enable <address> --vault <address>` delegates it with a pinned sweep vault. New setups use `vault promote`; `vault import-legacy --tee` migrates an existing store. |
|
|
96
|
-
| `candle swap <from> <to> --amount <n>\|--percent <n> --wallet <tee> [--to <tee>] [--wait]` | Quote, confirm and swap through the TEE wallet's bound key; first buy after a launch is this command. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana). SOL or USDC to ETH or USDG, or back, is a bridge through Relay into this key's own TEE wallet on the other chain (`--to` names it when there are several); `--wait` follows it for up to ten minutes. |
|
|
97
|
+
| `candle swap <from> <to> --amount <n>\|--percent <n> --wallet <tee> [--to <tee>] [--wait]` | Quote, confirm and swap through the TEE wallet's bound key; the first buy after a launch is this command, unless `candle launch --buy` made it. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana). SOL or USDC to ETH or USDG, or back, is a bridge through Relay into this key's own TEE wallet on the other chain (`--to` names it when there are several); `--wait` follows it for up to ten minutes. |
|
|
97
98
|
| `candle swap status <id> [--kind trade\|swap\|launch] [--wait]` | Read an operation without resending it; `--wait` follows a bridge until it ends. |
|
|
98
99
|
| `candle transfer --to <address\|wallet name\|vault> --asset <SOL\|USDC\|CNDL\|ETH\|USDG>\|--mint <mint>\|--token <0x...> --amount <decimal\|max> [--wallet <tee>] [--rpc-url <url>] [--yes]` | Move funds out of a TEE wallet through its bound Read:Write:Transfer key, to the wallet's own pinned vault (`--to vault`, any token, `max` allowed, one SOL transfer at most the server's backstop ceiling, 50 SOL by default) or to another of the account's wallets you linked while signed in or marked trusted (a base asset, counted against the key's caps, which must include a per-asset cap and a USD `--tx-limit`). The asset decides the chain: ETH, USDG or `--token` moves from a Hood TEE wallet. The destination and its kind are shown before you confirm; Candle builds, the relay signs, Candle broadcasts. |
|
|
99
|
-
| `candle launch --name <name> --symbol <symbol> --image-url <url> --wallet <tee> [--quote-asset <asset>] [--dex-version v3\|v4]` | Create a token
|
|
100
|
+
| `candle launch --name <name> --symbol <symbol> --image-url <url> --wallet <tee> [--buy <amount>] [--quote-asset <asset>] [--dex-version v3\|v4]` | Create a token from a TEE wallet. Without `--buy` it makes no first buy. `--buy <amount>` adds one from the same wallet, in whole units of the quote asset, reserved against the key's spend window before signing: in the launch transaction on Solana, as a last leg on Hood. It needs the server's `TEE_LAUNCH_BUYS_ENABLED`, which is off by default; while it is off Candle refuses the buy and nothing is built. Needs `launch:write`, `allowLaunch` turned on for the wallet by the account owner, and the Pro or Max plan. On Free, launch from the embedded wallet instead, with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood): the MCP tool `candle_launch_and_seed` or the SDK's `launch()`. The wallet decides the chain: a Hood TEE wallet launches on Hood and needs `--dex-version`. `--quote-asset` is sol, usdc or cndl on Solana, eth or usdg on Hood. |
|
|
100
101
|
| `candle pnl [--profile <name>]` | P&L: realized net, fees, unrealized and total, then open positions, the same figures the console shows. Without `--profile`, the account's books (every profile, the web app and the CLI, one ledger; needs the Read scope), with a By agent table (each key, then Manual, then the account total). With `--profile <name>`, that profile's key's share of the same P&L: realized on its sales and the open positions that belong to it, with a row per wallet that sums to the total. Each wallet is its own average-cost pool, and a recorded move between the account's wallets carries its cost. 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. Unpriced positions are never valued at zero; dust (worth under one cent) is marked and still counted. A bridge between Solana and Hood is not a trade, and bridge and Relay costs are not netted. DAMM v2 LP 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. |
|
|
101
102
|
| `candle portfolio [<filter>] [--chain solana\|hood] [--rpc-url <url>] [--evm-rpc-url <url>]` | Vault, TEE and embedded wallets on Solana and Hood in one table with chain, amount, price and value, subtotals per group and per chain, and a total. Solana vault and external balances are read over your own Solana RPC; Candle is sent only the mints, for prices. EVM vault keys are read over your own Hood RPC (`--evm-rpc-url`, else `CANDLE_EVM_RPC_URL`, else the built-in Hood RPC) for ETH and USDG only, priced by asset, never by address; an RPC on another chain is refused. An unpriced Hood token shows why (`no-market-row`, `stale-mark`, ...). `--chain` shows one chain. Each TEE wallet's DAMM v2 LP positions follow the tokens, valued by Candle at the same marks, and count in the total; one whose pool could not be read is shown as not read and the total says it is partial. |
|
|
102
103
|
| `candle plans` | The plans the API serves (`GET /api/v1/agent/plans`): each plan's monthly price, agent trade fee, 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. `--json` is the server's body. Your own plan is `candle doctor`'s Plan row. |
|
|
@@ -408,13 +409,19 @@ Before signing, the command displays venue, price impact, tier fee, minimum rece
|
|
|
408
409
|
all returned token warnings. Missing price impact is explicitly **unavailable**, never zero.
|
|
409
410
|
`--yes` skips the ordinary prompt but still prints warnings. With `--json`, stdout is one
|
|
410
411
|
JSON result containing the quote; the preview and operation id go to stderr. Launch creation
|
|
411
|
-
has no price impact or token receipt
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
`
|
|
417
|
-
`
|
|
412
|
+
has no price impact or token receipt and shows its maximum SOL debit. Without `--buy` it makes
|
|
413
|
+
no first buy; use a separate swap for that buy. With `--buy <amount>` (whole units of the quote
|
|
414
|
+
asset, as `--amount` is for a swap) the same wallet buys at launch: in the launch transaction on
|
|
415
|
+
Solana, as a last leg after the curve and the fee on Hood. Candle reserves the buy against the
|
|
416
|
+
key's spend window before anything is signed. `--buy` needs the server's
|
|
417
|
+
`TEE_LAUNCH_BUYS_ENABLED=1`, which is off by default; while it is off Candle refuses the buy
|
|
418
|
+
(`VALIDATION_FAILED` on `buyAmount`) and nothing is built or signed.
|
|
419
|
+
|
|
420
|
+
Swaps require `swap:write`. Launch requires `launch:write` **and** `allowLaunch` on the
|
|
421
|
+
wallet, which is off by default. The account owner (not Candle, and not the agent) sets it with
|
|
422
|
+
`candle wallets allow-launch <wallet>` and clears it with `candle wallets disallow-launch <wallet>`,
|
|
423
|
+
over the device token from `candle auth login` (the same endpoint, `PUT /api/v1/agent/wallets/<id>/capabilities`,
|
|
424
|
+
takes a device token or a web session). An agent key cannot grant itself this permission.
|
|
418
425
|
|
|
419
426
|
For scripts, always supply a stable `--client-trade-id` for each intention. Otherwise the CLI
|
|
420
427
|
generates and prints one. Reusing an existing id reads its status instead of rebuilding.
|
package/dist/index.js
CHANGED
|
@@ -14506,8 +14506,29 @@ async function request(ctx, key, path, body) {
|
|
|
14506
14506
|
fetch: ctx.deps.fetch,
|
|
14507
14507
|
env: ctx.deps.env
|
|
14508
14508
|
});
|
|
14509
|
-
if (!result.ok)
|
|
14510
|
-
|
|
14509
|
+
if (!result.ok) {
|
|
14510
|
+
const raw = result.raw;
|
|
14511
|
+
const error = raw?.error;
|
|
14512
|
+
const details = {};
|
|
14513
|
+
for (const field of [
|
|
14514
|
+
"field",
|
|
14515
|
+
"stage",
|
|
14516
|
+
"signature",
|
|
14517
|
+
"leg",
|
|
14518
|
+
"legs",
|
|
14519
|
+
"hashes",
|
|
14520
|
+
"retryable",
|
|
14521
|
+
"evidenceStored",
|
|
14522
|
+
"terminal"
|
|
14523
|
+
]) {
|
|
14524
|
+
if (error?.[field] !== undefined && (field === "field" || path === "/api/v1/agent/swap/submit"))
|
|
14525
|
+
details[field] = error[field];
|
|
14526
|
+
}
|
|
14527
|
+
throw new TradingError(result.code ?? "REQUEST_FAILED", result.message, {
|
|
14528
|
+
...Object.keys(details).length ? { details } : {},
|
|
14529
|
+
...path === "/api/v1/agent/swap/submit" && error?.stage === "unconfirmed" ? { exitCode: 3 } : {}
|
|
14530
|
+
});
|
|
14531
|
+
}
|
|
14511
14532
|
if (!result.body || typeof result.body !== "object")
|
|
14512
14533
|
throw new TradingError("INVALID_RESPONSE", "Candle returned an invalid response.");
|
|
14513
14534
|
return result.body;
|
|
@@ -14553,7 +14574,7 @@ async function completeTradingWallet(ctx, row, appId, scope, chain2 = "solana",
|
|
|
14553
14574
|
if (!row.active)
|
|
14554
14575
|
throw new TradingError("TEE_WALLET_INACTIVE", `The payer must be a verified-active ${chainName(chain2)} TEE wallet.`);
|
|
14555
14576
|
if (scope === "launch:write" && row.allowLaunch !== true)
|
|
14556
|
-
throw new TradingError("LAUNCH_NOT_ALLOWED", `The
|
|
14577
|
+
throw new TradingError("LAUNCH_NOT_ALLOWED", `The account owner must turn on allowLaunch for wallet ${row.id}: run candle wallets allow-launch ${row.id} on a profile signed in with candle auth login. An agent key cannot.`);
|
|
14557
14578
|
if (!appId || !row.privyWalletId)
|
|
14558
14579
|
throw new TradingError("SIGNER_UNAVAILABLE", "The server did not return its relay public identifiers.");
|
|
14559
14580
|
const signer = await localSignerFor(ctx.deps, row.id, row.signerQuorumId);
|
|
@@ -30311,6 +30332,8 @@ function tradingFailure(ctx, error, id) {
|
|
|
30311
30332
|
writeUsageFailure(ctx.deps, error.message, ctx.json);
|
|
30312
30333
|
return 2;
|
|
30313
30334
|
}
|
|
30335
|
+
if (error instanceof TradingError && error.details)
|
|
30336
|
+
describeSwapEvidence(ctx, error.details, error.details.terminal === true);
|
|
30314
30337
|
writeLocalFailure(ctx.deps, {
|
|
30315
30338
|
code: error instanceof TradingError ? error.code : "TRADING_FAILED",
|
|
30316
30339
|
message: `${error instanceof Error ? describeRpcFailure(error) : "Trading failed."}${id ? ` Operation ${id}; use candle swap status ${id} before another attempt.` : ""}`,
|
|
@@ -30335,7 +30358,31 @@ async function tradingRead(ctx, client, read) {
|
|
|
30335
30358
|
throw error;
|
|
30336
30359
|
}
|
|
30337
30360
|
}
|
|
30361
|
+
async function requestSwapBuild(ctx, key, body) {
|
|
30362
|
+
try {
|
|
30363
|
+
return await request(ctx, key, "/api/v1/agent/swap/build", body);
|
|
30364
|
+
} catch (error) {
|
|
30365
|
+
if (body.payer?.type === "main" && error instanceof TradingError && error.code === "VALIDATION_FAILED" && error.details?.field === "payer") {
|
|
30366
|
+
throw new TradingError("EMBEDDED_PAYER_UNSUPPORTED", "This Candle deployment cannot hold an embedded-wallet swap back for confirmation. Nothing was built. Name a TEE wallet with --wallet, or point at a deployment that supports embedded swap builds.");
|
|
30367
|
+
}
|
|
30368
|
+
throw error;
|
|
30369
|
+
}
|
|
30370
|
+
}
|
|
30371
|
+
function describeSwapEvidence(ctx, details, terminal) {
|
|
30372
|
+
if (details.stage === "not_broadcast" && terminal)
|
|
30373
|
+
ctx.deps.stderr.write(`Nothing was sent. This swap job is terminal; retry with a new id.
|
|
30374
|
+
`);
|
|
30375
|
+
if (details.stage === "unconfirmed") {
|
|
30376
|
+
const hashes = Array.isArray(details.hashes) ? details.hashes : typeof details.signature === "string" ? [details.signature] : [];
|
|
30377
|
+
ctx.deps.stderr.write(`Swap outcome needs verification${details.leg !== undefined ? ` (leg ${safeText(String(details.leg))})` : ""}. Known hashes: ${hashes.length ? hashes.map((hash) => safeText(String(hash))).join(", ") : "unknown"}. Do not automatically retry or use a new id.
|
|
30378
|
+
`);
|
|
30379
|
+
}
|
|
30380
|
+
}
|
|
30338
30381
|
function printTradingResult(ctx, result) {
|
|
30382
|
+
if (result.kind === "swap" && result.job && typeof result.job === "object") {
|
|
30383
|
+
const job = result.job;
|
|
30384
|
+
describeSwapEvidence(ctx, job, job.status === "failed");
|
|
30385
|
+
}
|
|
30339
30386
|
ctx.deps.stdout.write(ctx.json ? `${JSON.stringify(result)}
|
|
30340
30387
|
` : `${JSON.stringify(result, null, 2)}
|
|
30341
30388
|
`);
|
|
@@ -30533,8 +30580,6 @@ async function swap(args, ctx) {
|
|
|
30533
30580
|
if (prior)
|
|
30534
30581
|
return printTradingResult(ctx, prior);
|
|
30535
30582
|
const payerWallet = await tradingPayer(ctx, key, flags["--wallet"]);
|
|
30536
|
-
if (payerWallet.kind === "embedded" && kind === "swap")
|
|
30537
|
-
throw new TradingError("PAIR_UNSUPPORTED", "The embedded wallet cannot swap between base assets from this command yet: that rail executes in one call, so there would be nothing to confirm. Trade a token with it, or name a TEE wallet for a base pair.");
|
|
30538
30583
|
const wallet = payerWallet.kind === "tee" ? payerWallet.wallet : { address: payerWallet.address };
|
|
30539
30584
|
const solana = lazySolanaClient(ctx, flags["--rpc-url"]);
|
|
30540
30585
|
const decimals = await decimalsFor(ctx, from, solana);
|
|
@@ -30560,14 +30605,14 @@ async function swap(args, ctx) {
|
|
|
30560
30605
|
amountRaw = rawAmount(flags["--amount"], decimals);
|
|
30561
30606
|
if (BigInt(amountRaw) > BigInt(Number.MAX_SAFE_INTEGER))
|
|
30562
30607
|
throw new TradingError("INVALID_AMOUNT", "Amount exceeds the venue's exact integer range.");
|
|
30563
|
-
if (payerWallet.kind === "embedded")
|
|
30608
|
+
if (payerWallet.kind === "embedded" && kind === "trade")
|
|
30564
30609
|
await assertDeferredExecuteSupported(ctx, key, id);
|
|
30565
30610
|
if (!await claimOperation(ctx, key, id, kind))
|
|
30566
30611
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this id; no write was resent.");
|
|
30567
30612
|
ctx.deps.stderr.write(`Operation: ${id}
|
|
30568
30613
|
`);
|
|
30569
30614
|
const payer = payerWallet.kind === "tee" ? { type: "linked", linkedWalletId: payerWallet.wallet.id } : { type: "main" };
|
|
30570
|
-
const built = kind === "swap" ? await
|
|
30615
|
+
const built = kind === "swap" ? await requestSwapBuild(ctx, key, {
|
|
30571
30616
|
clientTradeId: id,
|
|
30572
30617
|
from,
|
|
30573
30618
|
to,
|
|
@@ -30589,7 +30634,7 @@ async function swap(args, ctx) {
|
|
|
30589
30634
|
return printTradingResult(ctx, { ...built, clientTradeId: id, kind });
|
|
30590
30635
|
const data = swapBuildSchema.parse(kind === "swap" ? built.payload : built);
|
|
30591
30636
|
if (kind === "swap" && (data.venue !== "jupiter" || data.recipient !== wallet.address))
|
|
30592
|
-
throw new TradingError("INVALID_RESPONSE", "A
|
|
30637
|
+
throw new TradingError("INVALID_RESPONSE", "A base swap must use Jupiter and return to its payer.");
|
|
30593
30638
|
if (kind === "trade" && (built.chain !== "solana" || built.walletAddress !== wallet.address))
|
|
30594
30639
|
throw new TradingError("INVALID_RESPONSE", "The token build does not name the requested Solana payer.");
|
|
30595
30640
|
const artifacts = kind === "swap" ? { ...data, transactionBase64: undefined, quoteSource: undefined, quoteAsset: undefined } : data.artifacts;
|
|
@@ -30616,9 +30661,11 @@ async function swap(args, ctx) {
|
|
|
30616
30661
|
if (!Number.isFinite(data.expiresAt) || data.expiresAt <= ctx.deps.now())
|
|
30617
30662
|
throw new TradingError("QUOTE_EXPIRED", "The quote expired before signing. Start a new intention with a new id.");
|
|
30618
30663
|
if (payerWallet.kind === "embedded") {
|
|
30619
|
-
const executed = await request(ctx, key, "/api/v1/trade/agent/execute", { clientTradeId: id });
|
|
30664
|
+
const executed = kind === "swap" ? await request(ctx, key, "/api/v1/agent/swap/submit", { clientTradeId: id, swapId: data.swapId }) : await request(ctx, key, "/api/v1/trade/agent/execute", { clientTradeId: id });
|
|
30665
|
+
const settled2 = kind === "swap" ? reportSettlement(ctx, executed.payload?.settlement, { asset: to, decimals: outDecimals }, id) : {};
|
|
30620
30666
|
return printTradingResult(ctx, {
|
|
30621
30667
|
...executed,
|
|
30668
|
+
...settled2,
|
|
30622
30669
|
clientTradeId: id,
|
|
30623
30670
|
kind,
|
|
30624
30671
|
quote,
|
|
@@ -30728,8 +30775,6 @@ async function hoodSwap(ctx, args) {
|
|
|
30728
30775
|
if (prior)
|
|
30729
30776
|
return printTradingResult(ctx, prior);
|
|
30730
30777
|
const payer = await tradingPayer(ctx, key, flags["--wallet"], "swap:write", "hood");
|
|
30731
|
-
if (payer.kind === "embedded" && kind === "swap")
|
|
30732
|
-
throw new TradingError("PAIR_UNSUPPORTED", "The embedded wallet cannot swap ETH and USDG from this command: that rail executes in one call, so there would be nothing to confirm. Trade a token with it, or name a Hood TEE wallet.");
|
|
30733
30778
|
const payerAddress = payer.kind === "tee" ? payer.wallet.address : payer.address;
|
|
30734
30779
|
const rpc = lazyEvmRpc(ctx, flags["--rpc-url"]);
|
|
30735
30780
|
const decimals = await hoodDecimals(from, rpc);
|
|
@@ -30742,7 +30787,7 @@ async function hoodSwap(ctx, args) {
|
|
|
30742
30787
|
throw new TradingError("INVALID_AMOUNT", "The selected percentage rounds to zero raw units.");
|
|
30743
30788
|
} else
|
|
30744
30789
|
amountRaw = rawAmount(flags["--amount"], decimals);
|
|
30745
|
-
if (payer.kind === "embedded")
|
|
30790
|
+
if (payer.kind === "embedded" && kind === "trade")
|
|
30746
30791
|
await assertDeferredExecuteSupported(ctx, key, id);
|
|
30747
30792
|
if (!await claimOperation(ctx, key, id, kind))
|
|
30748
30793
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this id; no write was resent.");
|
|
@@ -30751,7 +30796,7 @@ async function hoodSwap(ctx, args) {
|
|
|
30751
30796
|
const payerBody = payer.kind === "tee" ? { type: "linked", linkedWalletId: payer.wallet.id } : { type: "main" };
|
|
30752
30797
|
const base = from.base ?? to.base;
|
|
30753
30798
|
const token = from.base ? to.asset : from.asset;
|
|
30754
|
-
const built = kind === "swap" ? await
|
|
30799
|
+
const built = kind === "swap" ? await requestSwapBuild(ctx, key, {
|
|
30755
30800
|
clientTradeId: id,
|
|
30756
30801
|
from: from.asset,
|
|
30757
30802
|
to: to.asset,
|
|
@@ -30825,8 +30870,16 @@ async function hoodSwap(ctx, args) {
|
|
|
30825
30870
|
if (!Number.isFinite(data.expiresAt) || data.expiresAt <= ctx.deps.now())
|
|
30826
30871
|
throw new TradingError("QUOTE_EXPIRED", "The quote expired before signing. Start a new intention with a new id.");
|
|
30827
30872
|
if (payer.kind === "embedded") {
|
|
30828
|
-
const executed = await request(ctx, key, "/api/v1/trade/agent/execute", { clientTradeId: id });
|
|
30829
|
-
|
|
30873
|
+
const executed = kind === "swap" ? await request(ctx, key, "/api/v1/agent/swap/submit", { clientTradeId: id, swapId: data.swapId }) : await request(ctx, key, "/api/v1/trade/agent/execute", { clientTradeId: id });
|
|
30874
|
+
const settled2 = kind === "swap" ? reportSettlement(ctx, executed.payload?.settlement, { asset: to.asset, decimals: outDecimals }, id) : {};
|
|
30875
|
+
return printTradingResult(ctx, {
|
|
30876
|
+
...executed,
|
|
30877
|
+
...settled2,
|
|
30878
|
+
clientTradeId: id,
|
|
30879
|
+
kind,
|
|
30880
|
+
quote,
|
|
30881
|
+
wallet: safeText(payerAddress)
|
|
30882
|
+
});
|
|
30830
30883
|
}
|
|
30831
30884
|
const wallet = payer.wallet;
|
|
30832
30885
|
if (wallet.chain !== "evm" || !sequenced)
|
|
@@ -30935,7 +30988,7 @@ async function bridgeSwap(ctx, args) {
|
|
|
30935
30988
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this id; no write was resent.");
|
|
30936
30989
|
ctx.deps.stderr.write(`Operation: ${id}
|
|
30937
30990
|
`);
|
|
30938
|
-
const built = await
|
|
30991
|
+
const built = await requestSwapBuild(ctx, key, {
|
|
30939
30992
|
clientTradeId: id,
|
|
30940
30993
|
from,
|
|
30941
30994
|
to,
|
|
@@ -50758,7 +50811,7 @@ var init_convert = __esm(() => {
|
|
|
50758
50811
|
});
|
|
50759
50812
|
|
|
50760
50813
|
// ../mcp/src/version.ts
|
|
50761
|
-
var SERVER_VERSION = "0.10.
|
|
50814
|
+
var SERVER_VERSION = "0.10.7";
|
|
50762
50815
|
|
|
50763
50816
|
// ../mcp/src/update-notice.ts
|
|
50764
50817
|
function newer(a, b) {
|
|
@@ -52063,7 +52116,7 @@ function planTableMarkdown(table) {
|
|
|
52063
52116
|
return [line(headers2), line(headers2.map(() => "---")), ...rows.map(line)].join(`
|
|
52064
52117
|
`);
|
|
52065
52118
|
}
|
|
52066
|
-
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;
|
|
52119
|
+
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. Every plan can launch a token from its embedded wallet, with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the headless launch; the two launch rows are additional routes, not the only ones.", PLAN_CAPABILITY_LABELS2, PLAN_LABELS2;
|
|
52067
52120
|
var init_plans = __esm(() => {
|
|
52068
52121
|
PLAN_CAPABILITY_LABELS2 = {
|
|
52069
52122
|
tradeCandleTokens: "Trade Candle-launched tokens",
|
|
@@ -52072,8 +52125,8 @@ var init_plans = __esm(() => {
|
|
|
52072
52125
|
sellExternalTokens: "Sell tokens not launched on Candle",
|
|
52073
52126
|
buyExternalTokens: "Buy tokens not launched on Candle",
|
|
52074
52127
|
hyperliquidPerps: "Hyperliquid perps (when enabled)",
|
|
52075
|
-
selfLaunch: "
|
|
52076
|
-
atomicLaunch: "Atomic launch
|
|
52128
|
+
selfLaunch: "Launch from a linked or TEE wallet (self-signed)",
|
|
52129
|
+
atomicLaunch: "Atomic launch: launch + 1–4 first buys in one bundle",
|
|
52077
52130
|
createLinkedWallets: "Create linked wallets",
|
|
52078
52131
|
importLinkedWallets: "Import linked wallets",
|
|
52079
52132
|
limitOrders: "Limit orders",
|
|
@@ -52288,7 +52341,7 @@ function registerTools(server, env = process.env) {
|
|
|
52288
52341
|
};
|
|
52289
52342
|
register("candle_launch_token", {
|
|
52290
52343
|
title: "Launch a token on Candle",
|
|
52291
|
-
description: "Launch a new token via the Candle headless launch API. Set dryRun: true to validate without spending anything.",
|
|
52344
|
+
description: "Launch a new token via the Candle headless launch API, from the account's embedded wallet. Works on every plan, Free included. Set dryRun: true to validate without spending anything.",
|
|
52292
52345
|
inputSchema: launchTokenShape
|
|
52293
52346
|
}, async (args) => callAndRelay("candle_launch_token", args, cfg));
|
|
52294
52347
|
register("candle_get_market", {
|
|
@@ -52454,7 +52507,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
|
|
|
52454
52507
|
});
|
|
52455
52508
|
register("candle_launch_and_seed", {
|
|
52456
52509
|
title: "Launch a token and seed it",
|
|
52457
|
-
description: "Launch a new token with an optional dev-buy seed
|
|
52510
|
+
description: "Launch a new token from the account's embedded wallet, with an optional dev-buy seed (in the " + "launch transaction on solana, a best-effort follow-up on hood). Works on every plan, Free included (this is not the atomic launch). Then " + "return the fresh market state and token links in one result. MOVES REAL FUNDS unless " + "dryRun. Seeds above the platform dev-buy ceiling are rejected (DEV_BUY_TOO_HIGH); " + "launch, then top up with candle_trade.",
|
|
52458
52511
|
inputSchema: launchAndSeedShape
|
|
52459
52512
|
}, async (args) => {
|
|
52460
52513
|
const result = await executeLaunchAndSeed(args, cfg, fetch);
|
|
@@ -52631,7 +52684,7 @@ var init_tools = __esm(() => {
|
|
|
52631
52684
|
launchAndSeedShape = {
|
|
52632
52685
|
...seedableLaunchShape,
|
|
52633
52686
|
clientLaunchId: exports_external.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result."),
|
|
52634
|
-
devBuy: exports_external.string().optional().describe('Seed buy in DECIMAL units of the quote asset this launch selects (e.g. "0.25" SOL, or ' + "ETH on hood), bundled into the launch transaction itself. Follows quoteAsset, which " + "defaults to sol on solana and eth on hood. Capped by the platform dev-buy ceiling; for " + "a larger seed, launch then follow with candle_trade.")
|
|
52687
|
+
devBuy: exports_external.string().optional().describe('Seed buy in DECIMAL units of the quote asset this launch selects (e.g. "0.25" SOL, or ' + "ETH on hood), bundled into the launch transaction itself on solana and sent as a follow-up " + "transaction on hood. Paid from the account's embedded wallet. Follows quoteAsset, which " + "defaults to sol on solana and eth on hood. Capped by the platform dev-buy ceiling; for " + "a larger seed, launch then follow with candle_trade.")
|
|
52635
52688
|
};
|
|
52636
52689
|
transferShape = {
|
|
52637
52690
|
chain: exports_external.enum(["solana", "hood"]).describe("Which chain the transfer executes on"),
|
|
@@ -52770,7 +52823,7 @@ init_render();
|
|
|
52770
52823
|
init_secret_store();
|
|
52771
52824
|
|
|
52772
52825
|
// src/version.ts
|
|
52773
|
-
var CLI_VERSION = "0.11.
|
|
52826
|
+
var CLI_VERSION = "0.11.17";
|
|
52774
52827
|
|
|
52775
52828
|
// src/commands/auth.ts
|
|
52776
52829
|
init_keys_embedded_wallet();
|
|
@@ -53282,7 +53335,7 @@ var HELP = {
|
|
|
53282
53335
|
swap: {
|
|
53283
53336
|
group: "Trade",
|
|
53284
53337
|
summary: "Quote, confirm and swap on Solana or Hood, bridge between them; read an operation by id",
|
|
53285
|
-
description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent
|
|
53338
|
+
description: "Swaps run through a TEE wallet's bound key: the quote is shown and confirmed before anything is sent. The first buy after a launch is this command, unless the launch carried it with candle launch --buy. The assets decide the chain (ETH, USDG or a 0x token is Hood; SOL, USDC, CNDL or a mint is Solana) and a named wallet must be on it. A Hood TEE wallet signs one leg at a time: approve, Permit2, trade, then the fee, each only after the one before it landed. SOL or USDC to ETH or USDG, or back, is a bridge through Relay from a TEE wallet into this key's own TEE wallet on the other chain (--to names it when there are several); Candle charges no fee, 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.",
|
|
53286
53339
|
usage: ["candle swap <from> <to> [flags]", "candle swap status <id>"],
|
|
53287
53340
|
rows: [
|
|
53288
53341
|
{
|
|
@@ -53440,10 +53493,10 @@ var HELP = {
|
|
|
53440
53493
|
},
|
|
53441
53494
|
launch: {
|
|
53442
53495
|
group: "Trade",
|
|
53443
|
-
summary: "Create a token on Solana or Hood
|
|
53444
|
-
description: "Creates a token
|
|
53496
|
+
summary: "Create a token on Solana or Hood, with an optional first buy",
|
|
53497
|
+
description: "Creates a token from a TEE wallet. Without --buy it makes no first buy, so the launch and the position are two decisions, and the buy is a separate candle swap. --buy <amount> adds a first buy from the same wallet, in whole units of the quote asset (SOL, USDC or CNDL on Solana, ETH on Hood; not USDG), within the dev-buy ceilings: on Solana it rides in the launch transaction, and on Hood it is a last leg after the curve and the fee. Candle reserves the buy against the key's spend window before anything is signed. --buy needs a server with TEE_LAUNCH_BUYS_ENABLED on, which is off by default; while it is off Candle refuses the buy and nothing is built. Needs the launch:write scope and allowLaunch on the wallet, which only the account owner can turn on (candle wallets allow-launch <wallet>, over the device token; an agent key cannot). 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, then the buy with --buy), each only after the one before it landed. Launching from a TEE wallet needs Pro or Max (TIER_REQUIRED otherwise; candle plans). Every plan, Free included, can launch from its embedded wallet with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the MCP tool candle_launch_and_seed or the SDK's launch(); this command does not use that path.",
|
|
53445
53498
|
usage: [
|
|
53446
|
-
"candle launch --name <name> --symbol <symbol> --image-url <url> --wallet <tee> [--quote-asset <asset>] [--dex-version v3|v4]"
|
|
53499
|
+
"candle launch --name <name> --symbol <symbol> --image-url <url> --wallet <tee> [--buy <amount>] [--quote-asset <asset>] [--dex-version v3|v4]"
|
|
53447
53500
|
],
|
|
53448
53501
|
rows: [],
|
|
53449
53502
|
flags: [
|
|
@@ -53451,6 +53504,10 @@ var HELP = {
|
|
|
53451
53504
|
{ invocation: "--symbol <symbol>", description: "The token's ticker" },
|
|
53452
53505
|
{ invocation: "--image-url <url>", description: "The token image, already hosted" },
|
|
53453
53506
|
{ invocation: "--wallet <tee>", description: "The TEE wallet that creates it" },
|
|
53507
|
+
{
|
|
53508
|
+
invocation: "--buy <amount>",
|
|
53509
|
+
description: "A first buy from the same wallet, in whole units of the quote asset; needs TEE_LAUNCH_BUYS_ENABLED on the server"
|
|
53510
|
+
},
|
|
53454
53511
|
{ invocation: "--quote-asset <asset>", description: "sol, usdc or cndl on Solana; eth or usdg on Hood" },
|
|
53455
53512
|
{
|
|
53456
53513
|
invocation: "--dex-version v3|v4",
|
|
@@ -53459,7 +53516,8 @@ var HELP = {
|
|
|
53459
53516
|
],
|
|
53460
53517
|
examples: [
|
|
53461
53518
|
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne",
|
|
53462
|
-
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4"
|
|
53519
|
+
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4",
|
|
53520
|
+
"candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne --buy 0.1"
|
|
53463
53521
|
],
|
|
53464
53522
|
env: ENV_API
|
|
53465
53523
|
},
|
|
@@ -53596,6 +53654,14 @@ var HELP = {
|
|
|
53596
53654
|
invocation: "untrust <label|address|id|prefix*>... [--yes]",
|
|
53597
53655
|
description: "Clear the mark; moving funds in needs the withdrawal allowlist again (owner only)"
|
|
53598
53656
|
},
|
|
53657
|
+
{
|
|
53658
|
+
invocation: "allow-launch <label|address|id|prefix*>...",
|
|
53659
|
+
description: "Let TEE wallets pay for a launch (candle launch), after a screen of label, address and bound key (owner only; typed confirm)"
|
|
53660
|
+
},
|
|
53661
|
+
{
|
|
53662
|
+
invocation: "disallow-launch <label|address|id|prefix*>... [--yes]",
|
|
53663
|
+
description: "Stop TEE wallets paying for a launch; trading is unchanged (owner only)"
|
|
53664
|
+
},
|
|
53599
53665
|
{
|
|
53600
53666
|
invocation: "close-empty [--wallet embedded] [--keep <mint>]... [--client-trade-id <id>] [--yes]",
|
|
53601
53667
|
description: "Close the embedded wallet's empty token accounts and return their rent to it, after a preview (transfer:write)"
|
|
@@ -53606,6 +53672,7 @@ var HELP = {
|
|
|
53606
53672
|
"candle wallet import --chain solana --key-file ./signer.json",
|
|
53607
53673
|
"candle wallet revoke wal_123",
|
|
53608
53674
|
"candle wallet trust 'tr-*' 'dest-*'",
|
|
53675
|
+
"candle wallet allow-launch launcher-1",
|
|
53609
53676
|
"candle wallet close-empty --keep EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
|
53610
53677
|
],
|
|
53611
53678
|
env: ENV_API
|
|
@@ -59851,7 +59918,7 @@ init_solana_lite();
|
|
|
59851
59918
|
init_trading();
|
|
59852
59919
|
init_swap();
|
|
59853
59920
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
59854
|
-
var USAGE2 = "Usage: candle launch --name <name> --symbol <symbol> --image-url <https-url> --wallet <tee> [--client-trade-id <id>] [--quote-asset sol|usdc|cndl|eth|usdg] [--dex-version v3|v4] [--mode <mode>] [--rpc-url <url>] [--yes]. A Hood TEE wallet launches on Hood and needs --dex-version.";
|
|
59921
|
+
var USAGE2 = "Usage: candle launch --name <name> --symbol <symbol> --image-url <https-url> --wallet <tee> [--buy <amount>] [--client-trade-id <id>] [--quote-asset sol|usdc|cndl|eth|usdg] [--dex-version v3|v4] [--mode <mode>] [--rpc-url <url>] [--yes]. A Hood TEE wallet launches on Hood and needs --dex-version.";
|
|
59855
59922
|
var QUOTE_CHAIN = {
|
|
59856
59923
|
sol: "solana",
|
|
59857
59924
|
usdc: "solana",
|
|
@@ -59860,6 +59927,34 @@ var QUOTE_CHAIN = {
|
|
|
59860
59927
|
usdg: "hood"
|
|
59861
59928
|
};
|
|
59862
59929
|
var HOOD_LAUNCH_LEGS = ["createCurve", "feeTransfer"];
|
|
59930
|
+
var HOOD_LAUNCH_BUY_LEGS = ["createCurve", "feeTransfer", "trade"];
|
|
59931
|
+
function launchBuy(flags, chain2) {
|
|
59932
|
+
const amount = flags["--buy"];
|
|
59933
|
+
if (amount === undefined)
|
|
59934
|
+
return;
|
|
59935
|
+
const quote = (flags["--quote-asset"] ?? (chain2 === "hood" ? "eth" : "sol")).toUpperCase();
|
|
59936
|
+
const decimals = chain2 === "hood" ? HOOD_BASES[quote]?.decimals : BASES[quote]?.decimals;
|
|
59937
|
+
if (decimals === undefined)
|
|
59938
|
+
throw new TradingError("INVALID_AMOUNT", `--buy is in the launch's quote asset, and ${safeText(quote)} is not one on ${chainName(chain2)}. Nothing was built.`);
|
|
59939
|
+
const raw = rawAmount(amount, decimals);
|
|
59940
|
+
return { raw, display: `${decimalAmount(raw, decimals)} ${quote}` };
|
|
59941
|
+
}
|
|
59942
|
+
function buyRefusal(error) {
|
|
59943
|
+
if (!(error instanceof TradingError) || error.code !== "VALIDATION_FAILED" || error.details?.field !== "buyAmount")
|
|
59944
|
+
return error;
|
|
59945
|
+
return new TradingError("VALIDATION_FAILED", `Candle refused the first buy on this TEE launch: ${safeText(error.message)}. A TEE wallet's launch takes --buy only where the server has TEE_LAUNCH_BUYS_ENABLED on, and it is off on this deployment. Nothing was built or signed.`, {
|
|
59946
|
+
suggestion: "Launch without --buy under a new --client-trade-id, then make the first buy with candle swap once the token lands.",
|
|
59947
|
+
details: error.details
|
|
59948
|
+
});
|
|
59949
|
+
}
|
|
59950
|
+
function buyNotice(buy, chain2) {
|
|
59951
|
+
if (!buy)
|
|
59952
|
+
return `Launch creates the token only. Make the first buy with a separate candle swap. Price impact does not apply to creation.
|
|
59953
|
+
`;
|
|
59954
|
+
return chain2 === "hood" ? `Launch creates the token, then spends ${buy.display} on a first buy on the new curve, as the last leg. Candle reserves the buy against this key's spend window before any leg is signed. If the buy leg never lands, the token exists with no first buy. Price impact does not apply to creation.
|
|
59955
|
+
` : `Launch creates the token and spends ${buy.display} on a first buy in the same transaction. Candle reserves the whole debit against this key's spend window before anything is signed. Price impact does not apply to creation.
|
|
59956
|
+
`;
|
|
59957
|
+
}
|
|
59863
59958
|
function flagChain(flags) {
|
|
59864
59959
|
const said = [];
|
|
59865
59960
|
const named = walletNameChain(flags["--wallet"] ?? "");
|
|
@@ -59889,7 +59984,8 @@ async function launch(args, ctx) {
|
|
|
59889
59984
|
"--rpc-url",
|
|
59890
59985
|
"--quote-asset",
|
|
59891
59986
|
"--mode",
|
|
59892
|
-
"--dex-version"
|
|
59987
|
+
"--dex-version",
|
|
59988
|
+
"--buy"
|
|
59893
59989
|
],
|
|
59894
59990
|
booleanFlags: ["--yes"]
|
|
59895
59991
|
});
|
|
@@ -59904,6 +60000,8 @@ async function launch(args, ctx) {
|
|
|
59904
60000
|
return 2;
|
|
59905
60001
|
}
|
|
59906
60002
|
try {
|
|
60003
|
+
if (flags["--buy"] !== undefined)
|
|
60004
|
+
rawAmount(flags["--buy"], 18);
|
|
59907
60005
|
const hinted = flagChain(flags);
|
|
59908
60006
|
if (hinted === "hood" && flags["--dex-version"] === undefined) {
|
|
59909
60007
|
writeUsageFailure(ctx.deps, `A Hood launch needs --dex-version v3|v4. ${USAGE2}`, ctx.json);
|
|
@@ -59929,8 +60027,10 @@ async function launch(args, ctx) {
|
|
|
59929
60027
|
if (wallet.chain === "evm") {
|
|
59930
60028
|
if (flags["--dex-version"] === undefined)
|
|
59931
60029
|
throw new TradingUsage(`${wallet.address} is a Hood TEE wallet, and a Hood launch needs --dex-version v3|v4.`);
|
|
59932
|
-
|
|
60030
|
+
const buy2 = launchBuy(flags, "hood");
|
|
60031
|
+
return await hoodLaunch(ctx, key, { flags, id, wallet, buy: buy2, yes: parsed.booleans.has("--yes") });
|
|
59933
60032
|
}
|
|
60033
|
+
const buy = launchBuy(flags, "solana");
|
|
59934
60034
|
const solana = await tradingSolanaClient(ctx, flags["--rpc-url"]);
|
|
59935
60035
|
if (!await claimOperation(ctx, key, id, "launch"))
|
|
59936
60036
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
|
|
@@ -59939,7 +60039,7 @@ async function launch(args, ctx) {
|
|
|
59939
60039
|
const built = launchBuildSchema.parse(await request(ctx, key, "/api/v1/launch/self/build", {
|
|
59940
60040
|
clientLaunchId: id,
|
|
59941
60041
|
chain: "solana",
|
|
59942
|
-
buyAmount: 0,
|
|
60042
|
+
buyAmount: buy ? buy.raw : 0,
|
|
59943
60043
|
name: flags["--name"],
|
|
59944
60044
|
symbol: flags["--symbol"],
|
|
59945
60045
|
imageUrl: flags["--image-url"],
|
|
@@ -59947,21 +60047,22 @@ async function launch(args, ctx) {
|
|
|
59947
60047
|
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
59948
60048
|
...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"] } : {},
|
|
59949
60049
|
...flags["--mode"] ? { mode: flags["--mode"] } : {}
|
|
60050
|
+
}).catch((error) => {
|
|
60051
|
+
throw buyRefusal(error);
|
|
59950
60052
|
}));
|
|
59951
60053
|
if (!built.transaction || !/^\d+$/.test(built.maxDebitLamports ?? ""))
|
|
59952
60054
|
throw new TradingError("INVALID_RESPONSE", "Candle did not return a launch transaction and maximum debit; nothing was signed.");
|
|
59953
60055
|
const quote = {
|
|
59954
|
-
intent: `Launch ${flags["--name"]} (${flags["--symbol"]})`,
|
|
60056
|
+
intent: `Launch ${flags["--name"]} (${flags["--symbol"]})${buy ? ` with a first buy of ${buy.display}` : ""}`,
|
|
59955
60057
|
wallet: wallet.address,
|
|
59956
60058
|
venue: "curve launch",
|
|
59957
60059
|
priceImpactPct: null,
|
|
59958
60060
|
fee: built.fee ?? { bps: 0, feeRaw: "0" },
|
|
59959
|
-
minimumReceived: "0 tokens (no first buy)",
|
|
60061
|
+
minimumReceived: buy ? "not quoted (the first buy on the new curve)" : "0 tokens (no first buy)",
|
|
59960
60062
|
maxDebitLamports: built.maxDebitLamports,
|
|
59961
60063
|
tokenRisks: []
|
|
59962
60064
|
};
|
|
59963
|
-
ctx.deps.stderr.write(
|
|
59964
|
-
`);
|
|
60065
|
+
ctx.deps.stderr.write(buyNotice(buy, "solana"));
|
|
59965
60066
|
if (!await confirmQuote(ctx, quote, parsed.booleans.has("--yes")))
|
|
59966
60067
|
return printTradingResult(ctx, { success: true, status: "cancelled", clientTradeId: id, kind: "launch", quote });
|
|
59967
60068
|
if (!Number.isFinite(built.expiresAt) || built.expiresAt <= ctx.deps.now())
|
|
@@ -59987,7 +60088,8 @@ async function launch(args, ctx) {
|
|
|
59987
60088
|
}
|
|
59988
60089
|
}
|
|
59989
60090
|
async function hoodLaunch(ctx, key, args) {
|
|
59990
|
-
const { flags, id, wallet } = args;
|
|
60091
|
+
const { flags, id, wallet, buy } = args;
|
|
60092
|
+
const allowedLegs = buy ? HOOD_LAUNCH_BUY_LEGS : HOOD_LAUNCH_LEGS;
|
|
59991
60093
|
if (!await claimOperation(ctx, key, id, "launch"))
|
|
59992
60094
|
throw new TradingError("OPERATION_ALREADY_STARTED", "This machine already started this launch id; no write was resent.");
|
|
59993
60095
|
ctx.deps.stderr.write(`Operation: ${id}
|
|
@@ -59995,7 +60097,7 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
59995
60097
|
const built = await request(ctx, key, "/api/v1/launch/self/build", {
|
|
59996
60098
|
clientLaunchId: id,
|
|
59997
60099
|
chain: "hood",
|
|
59998
|
-
buyAmount: 0,
|
|
60100
|
+
buyAmount: buy ? buy.raw : 0,
|
|
59999
60101
|
name: flags["--name"],
|
|
60000
60102
|
symbol: flags["--symbol"],
|
|
60001
60103
|
imageUrl: flags["--image-url"],
|
|
@@ -60004,6 +60106,8 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
60004
60106
|
...flags["--description"] ? { description: flags["--description"] } : {},
|
|
60005
60107
|
...flags["--quote-asset"] ? { quoteAsset: flags["--quote-asset"].toLowerCase() } : {},
|
|
60006
60108
|
...flags["--mode"] ? { mode: flags["--mode"] } : {}
|
|
60109
|
+
}).catch((error) => {
|
|
60110
|
+
throw buyRefusal(error);
|
|
60007
60111
|
});
|
|
60008
60112
|
const parsed = sequencedSchema.safeParse(built);
|
|
60009
60113
|
if (!parsed.success)
|
|
@@ -60012,26 +60116,25 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
60012
60116
|
const curve = typeof built.curveAddress === "string" ? checkEvmAddress(built.curveAddress) : undefined;
|
|
60013
60117
|
if (built.chain !== "hood" || built.clientLaunchId !== id || typeof built.walletAddress !== "string" || !sameEvmAddress(built.walletAddress, wallet.address) || !curve?.ok)
|
|
60014
60118
|
throw new TradingError("INVALID_RESPONSE", "The Hood launch build does not name this launch and payer; nothing was signed.");
|
|
60015
|
-
if (first.legKind !== "createCurve" || first.plannedLegCount >
|
|
60119
|
+
if (first.legKind !== "createCurve" || first.plannedLegCount > allowedLegs.length || buy !== undefined && first.plannedLegCount < 2)
|
|
60016
60120
|
throw new TradingError("INVALID_RESPONSE", `A Hood launch starts with its createCurve leg, and Candle offered ${safeText(first.legKind)} of ${first.plannedLegCount}; nothing was signed.`);
|
|
60017
60121
|
const leg = first.nextLeg;
|
|
60018
60122
|
const maxFee = BigInt(leg.maxFeePerGas);
|
|
60019
60123
|
const reserve = sweepReserveFloor(maxFee);
|
|
60020
60124
|
const quote = {
|
|
60021
|
-
intent: `Launch ${flags["--name"]} (${flags["--symbol"]}) on Hood`,
|
|
60125
|
+
intent: `Launch ${flags["--name"]} (${flags["--symbol"]}) on Hood${buy ? ` with a first buy of ${buy.display}` : ""}`,
|
|
60022
60126
|
wallet: wallet.address,
|
|
60023
60127
|
venue: "curve launch",
|
|
60024
60128
|
priceImpactPct: null,
|
|
60025
|
-
minimumReceived: "0 tokens (no first buy)",
|
|
60129
|
+
minimumReceived: buy ? "not quoted (Candle's buy leg sets its floor on the new curve)" : "0 tokens (no first buy)",
|
|
60026
60130
|
tokenRisks: [],
|
|
60027
|
-
legs: first.plannedLegCount
|
|
60028
|
-
gas: `the createCurve leg up to ${formatUnits(BigInt(leg.gas) * maxFee, 18)} ETH (gas ${leg.gas} at ${formatUnits(maxFee, 9)} gwei)${first.plannedLegCount
|
|
60131
|
+
legs: hoodLaunchLegNames(first.plannedLegCount, buy !== undefined),
|
|
60132
|
+
gas: `the createCurve leg up to ${formatUnits(BigInt(leg.gas) * maxFee, 18)} ETH (gas ${leg.gas} at ${formatUnits(maxFee, 9)} gwei)${hoodLaterLegsGas(first.plannedLegCount, buy !== undefined)}`,
|
|
60029
60133
|
reserve: `at least ${formatUnits(reserve.wei, 18)} ETH stays in the wallet for a sweep home (${reserve.erc20Transfers} ERC-20 transfers and the final ETH transfer at twice the fee)`,
|
|
60030
60134
|
curveAddress: curve.address,
|
|
60031
60135
|
operationId: first.operationId
|
|
60032
60136
|
};
|
|
60033
|
-
ctx.deps.stderr.write(
|
|
60034
|
-
`);
|
|
60137
|
+
ctx.deps.stderr.write(buyNotice(buy, "hood"));
|
|
60035
60138
|
if (!await confirmQuote(ctx, quote, args.yes)) {
|
|
60036
60139
|
ctx.deps.stderr.write(`Nothing was signed. Operation ${first.operationId} holds this wallet until ${new Date(first.expiresAt).toISOString()}; a new Hood operation from it is refused as WALLET_BUSY until then.
|
|
60037
60140
|
`);
|
|
@@ -60046,6 +60149,8 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
60046
60149
|
walletHeldUntil: first.expiresAt
|
|
60047
60150
|
});
|
|
60048
60151
|
}
|
|
60152
|
+
const confirmedPlan = buy ? first.plannedLegCount === 3 ? ["createCurve", "feeTransfer", "trade"] : ["createCurve", "trade"] : undefined;
|
|
60153
|
+
let position = 0;
|
|
60049
60154
|
const run2 = await runSequencedLegs(ctx, key, {
|
|
60050
60155
|
wallet,
|
|
60051
60156
|
first,
|
|
@@ -60055,10 +60160,16 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
60055
60160
|
clientId: id,
|
|
60056
60161
|
kind: "launch",
|
|
60057
60162
|
primaryLeg: "createCurve",
|
|
60058
|
-
allowedLegs
|
|
60059
|
-
checkLeg: (kind, leg2) =>
|
|
60163
|
+
allowedLegs,
|
|
60164
|
+
checkLeg: (kind, leg2) => {
|
|
60165
|
+
if (confirmedPlan && confirmedPlan[position++] !== kind)
|
|
60166
|
+
return false;
|
|
60167
|
+
return kind === "createCurve" ? leg2.value === "0" : kind !== "trade" || buy !== undefined && leg2.value === buy.raw && sameEvmAddress(leg2.to, curve.address);
|
|
60168
|
+
},
|
|
60060
60169
|
onLanded: async () => {}
|
|
60061
60170
|
});
|
|
60171
|
+
if (confirmedPlan && run2.landed.map((done) => done.kind).join() !== confirmedPlan.join())
|
|
60172
|
+
throw new TradingError("INVALID_RESPONSE", `Candle ended the launch before every confirmed leg landed (${confirmedPlan.join(", ")}).${run2.landed.length === 0 ? " No leg landed." : ` Landed: ${run2.landed.map((done) => `${done.kind} ${safeText(done.hash)}`).join(", ")}.`}`, { details: { operationId: first.operationId, landedLegs: run2.landed } });
|
|
60062
60173
|
return printTradingResult(ctx, {
|
|
60063
60174
|
...run2.final,
|
|
60064
60175
|
...await recordLaunchedToken(ctx, wallet, run2.final),
|
|
@@ -60071,6 +60182,16 @@ async function hoodLaunch(ctx, key, args) {
|
|
|
60071
60182
|
landedLegs: run2.landed
|
|
60072
60183
|
});
|
|
60073
60184
|
}
|
|
60185
|
+
function hoodLaterLegsGas(count, buys) {
|
|
60186
|
+
if (count < 2)
|
|
60187
|
+
return "";
|
|
60188
|
+
return buys ? "; the later legs are priced by Candle when each becomes next" : "; the fee leg is priced by Candle when it becomes next";
|
|
60189
|
+
}
|
|
60190
|
+
function hoodLaunchLegNames(count, buys) {
|
|
60191
|
+
if (!buys)
|
|
60192
|
+
return count === 2 ? ["create curve", "fee"] : ["create curve"];
|
|
60193
|
+
return count === 3 ? ["create curve", "fee", "first buy"] : ["create curve", "first buy"];
|
|
60194
|
+
}
|
|
60074
60195
|
async function recordLaunchedToken(ctx, wallet, final) {
|
|
60075
60196
|
const mint = typeof final.mint === "string" ? checkEvmAddress(final.mint) : undefined;
|
|
60076
60197
|
if (!mint?.ok) {
|
|
@@ -61633,7 +61754,7 @@ async function perpsDeposit(args, ctx) {
|
|
|
61633
61754
|
init_args();
|
|
61634
61755
|
|
|
61635
61756
|
// src/plans.ts
|
|
61636
|
-
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.";
|
|
61757
|
+
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. Every plan can launch a token from its embedded wallet, with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the headless launch; the two launch rows are additional routes, not the only ones.";
|
|
61637
61758
|
var PLAN_CAPABILITY_LABELS = {
|
|
61638
61759
|
tradeCandleTokens: "Trade Candle-launched tokens",
|
|
61639
61760
|
tradeBaseAssets: "Trade base assets",
|
|
@@ -61641,8 +61762,8 @@ var PLAN_CAPABILITY_LABELS = {
|
|
|
61641
61762
|
sellExternalTokens: "Sell tokens not launched on Candle",
|
|
61642
61763
|
buyExternalTokens: "Buy tokens not launched on Candle",
|
|
61643
61764
|
hyperliquidPerps: "Hyperliquid perps (when enabled)",
|
|
61644
|
-
selfLaunch: "
|
|
61645
|
-
atomicLaunch: "Atomic launch
|
|
61765
|
+
selfLaunch: "Launch from a linked or TEE wallet (self-signed)",
|
|
61766
|
+
atomicLaunch: "Atomic launch: launch + 1–4 first buys in one bundle",
|
|
61646
61767
|
createLinkedWallets: "Create linked wallets",
|
|
61647
61768
|
importLinkedWallets: "Import linked wallets",
|
|
61648
61769
|
limitOrders: "Limit orders",
|
|
@@ -72327,6 +72448,176 @@ function messageOf3(error) {
|
|
|
72327
72448
|
// src/index.ts
|
|
72328
72449
|
init_wallets();
|
|
72329
72450
|
|
|
72451
|
+
// src/commands/wallets-allow-launch.ts
|
|
72452
|
+
init_args();
|
|
72453
|
+
init_deps();
|
|
72454
|
+
init_profiles();
|
|
72455
|
+
init_render();
|
|
72456
|
+
init_promote_support();
|
|
72457
|
+
var PREVIEW_PATH = "/api/v1/agent/tee-wallets/allow-launch/preview";
|
|
72458
|
+
var capabilityPath = (id) => `/api/v1/agent/wallets/${encodeURIComponent(id)}/capabilities`;
|
|
72459
|
+
var USAGE_ALLOW = "Usage: candle wallets allow-launch <label|address|id|prefix*>... [--json]";
|
|
72460
|
+
var USAGE_DISALLOW = "Usage: candle wallets disallow-launch <label|address|id|prefix*>... [--yes] [--json]";
|
|
72461
|
+
var DEVICE_TOKEN_REQUIRED2 = {
|
|
72462
|
+
code: "DEVICE_TOKEN_REQUIRED",
|
|
72463
|
+
message: "Changing whether a TEE wallet may launch needs the device token, the owner's credential; an API key cannot do it.",
|
|
72464
|
+
suggestion: "Run: candle auth login"
|
|
72465
|
+
};
|
|
72466
|
+
var nameOf = (row) => row.label ?? row.address;
|
|
72467
|
+
function allowLaunchTable(rows) {
|
|
72468
|
+
return renderTable(["line", "label", "wallet", "address", "bound key"], rows.map((row, i) => [String(i + 1), row.label ?? "-", row.chain, row.address, row.boundKeyPrefix ?? "-"]));
|
|
72469
|
+
}
|
|
72470
|
+
function nothingToChangeLine(shown, enabled) {
|
|
72471
|
+
const bits = [];
|
|
72472
|
+
if (shown.unchanged.length === 1)
|
|
72473
|
+
bits.push(`that wallet already has allowLaunch ${enabled ? "on" : "off"}`);
|
|
72474
|
+
else if (shown.unchanged.length > 1) {
|
|
72475
|
+
bits.push(`all ${shown.unchanged.length} wallets already have allowLaunch ${enabled ? "on" : "off"}`);
|
|
72476
|
+
}
|
|
72477
|
+
if (shown.notTee.length > 0) {
|
|
72478
|
+
bits.push(`${shown.notTee.map(nameOf).join(", ")} ${shown.notTee.length === 1 ? "is" : "are"} not a TEE wallet`);
|
|
72479
|
+
}
|
|
72480
|
+
if (bits.length === 0)
|
|
72481
|
+
return `Nothing to change.
|
|
72482
|
+
`;
|
|
72483
|
+
return `Nothing to change: ${bits.join("; ")}.
|
|
72484
|
+
`;
|
|
72485
|
+
}
|
|
72486
|
+
function writeFailure2(ctx, result) {
|
|
72487
|
+
const { deps, apiUrl, json } = ctx;
|
|
72488
|
+
let code = result.code;
|
|
72489
|
+
let message = result.message;
|
|
72490
|
+
let suggestion;
|
|
72491
|
+
if (result.status === 404) {
|
|
72492
|
+
code = "ALLOW_LAUNCH_UNSUPPORTED";
|
|
72493
|
+
message = "This Candle API does not offer allowLaunch on TEE wallets (an older API, or TEE launch is off); nothing changed.";
|
|
72494
|
+
} else {
|
|
72495
|
+
const error = result.raw?.error;
|
|
72496
|
+
if (error?.reason === "ambiguous" && Array.isArray(error.matches)) {
|
|
72497
|
+
const ids = error.matches.map((m) => String(m.id)).join(", ");
|
|
72498
|
+
suggestion = `Name one of them by id or address: ${ids}`;
|
|
72499
|
+
} else if (error?.reason === "not_found") {
|
|
72500
|
+
suggestion = "Run: candle wallets, to see this account's linked wallets and their labels.";
|
|
72501
|
+
}
|
|
72502
|
+
}
|
|
72503
|
+
const envelope = errorEnvelope({ ...result, code, message }, { apiUrl, authType: "device" });
|
|
72504
|
+
if (json) {
|
|
72505
|
+
deps.stdout.write(`${JSON.stringify({ ...envelope, ...suggestion ? { suggestion } : {} })}
|
|
72506
|
+
`);
|
|
72507
|
+
} else {
|
|
72508
|
+
deps.stderr.write(`${code ?? `HTTP ${result.status}`}: ${message}${suggestion ? ` ${suggestion}` : ""}
|
|
72509
|
+
`);
|
|
72510
|
+
}
|
|
72511
|
+
return 1;
|
|
72512
|
+
}
|
|
72513
|
+
async function setAllowLaunch(args, ctx, enabled) {
|
|
72514
|
+
const { deps, apiUrl, json } = ctx;
|
|
72515
|
+
const usage6 = enabled ? USAGE_ALLOW : USAGE_DISALLOW;
|
|
72516
|
+
const command = enabled ? "wallets allow-launch" : "wallets disallow-launch";
|
|
72517
|
+
const parsed = parseArgs(args, enabled ? {} : { booleanFlags: ["--yes"] });
|
|
72518
|
+
if ("error" in parsed) {
|
|
72519
|
+
writeUsageFailure(deps, parsed.error, json);
|
|
72520
|
+
return 2;
|
|
72521
|
+
}
|
|
72522
|
+
if (parsed.positionals.length === 0) {
|
|
72523
|
+
writeUsageFailure(deps, `Name at least one wallet. ${usage6}`, json);
|
|
72524
|
+
return 2;
|
|
72525
|
+
}
|
|
72526
|
+
const skipConfirm = !enabled && parsed.booleans.has("--yes");
|
|
72527
|
+
await printIdentity(ctx);
|
|
72528
|
+
const deviceToken = await resolveDeviceToken(deps, ctx.profile);
|
|
72529
|
+
if (!deviceToken) {
|
|
72530
|
+
writeLocalFailure(deps, DEVICE_TOKEN_REQUIRED2, json);
|
|
72531
|
+
return 1;
|
|
72532
|
+
}
|
|
72533
|
+
if (!skipConfirm && (!deps.isTTY.stdin || !deps.isTTY.stdout)) {
|
|
72534
|
+
writeLocalFailure(deps, {
|
|
72535
|
+
code: "ALLOW_LAUNCH_REQUIRES_TTY",
|
|
72536
|
+
message: `candle ${command} needs a terminal: the acknowledgement is typed, and nothing else supplies it.`,
|
|
72537
|
+
suggestion: enabled ? "Run it in an interactive shell; there is no flag and no environment variable for confirm." : "Run it in an interactive shell, or pass --yes to turn it off without the prompt."
|
|
72538
|
+
}, json);
|
|
72539
|
+
return 1;
|
|
72540
|
+
}
|
|
72541
|
+
const request2 = (path, method, body) => apiRequest(path, {
|
|
72542
|
+
method,
|
|
72543
|
+
body,
|
|
72544
|
+
auth: "device",
|
|
72545
|
+
credentials: { deviceToken },
|
|
72546
|
+
apiUrl,
|
|
72547
|
+
fetch: deps.fetch,
|
|
72548
|
+
env: deps.env
|
|
72549
|
+
});
|
|
72550
|
+
const preview = await request2(PREVIEW_PATH, "POST", { wallets: parsed.positionals, enabled });
|
|
72551
|
+
if (!preview.ok)
|
|
72552
|
+
return writeFailure2(ctx, preview);
|
|
72553
|
+
const shown = preview.body;
|
|
72554
|
+
const changing = shown.changed;
|
|
72555
|
+
if (changing.length === 0) {
|
|
72556
|
+
if (json)
|
|
72557
|
+
deps.stdout.write(`${JSON.stringify({ ...shown, command })}
|
|
72558
|
+
`);
|
|
72559
|
+
else
|
|
72560
|
+
deps.stdout.write(nothingToChangeLine(shown, enabled));
|
|
72561
|
+
return shown.unchanged.length === 0 && shown.notTee.length > 0 ? 1 : 0;
|
|
72562
|
+
}
|
|
72563
|
+
const n = changing.length;
|
|
72564
|
+
const screen = [
|
|
72565
|
+
allowLaunchTable(changing),
|
|
72566
|
+
...shown.unchanged.length > 0 ? [`${shown.unchanged.length} already ${enabled ? "on" : "off"}: ${shown.unchanged.map(nameOf).join(", ")}`] : [],
|
|
72567
|
+
...shown.notTee.length > 0 ? [`Not a TEE wallet, left alone: ${shown.notTee.map(nameOf).join(", ")}`] : [],
|
|
72568
|
+
"",
|
|
72569
|
+
enabled ? `${n === 1 ? "This wallet" : `These ${n} wallets`} will be allowed to pay for a launch: the bound key, with launch:write, can create tokens from ${n === 1 ? "it" : "them"} (candle launch).` : `${n === 1 ? "This wallet" : `These ${n} wallets`} will no longer be able to pay for a launch. Trading is unchanged.`,
|
|
72570
|
+
""
|
|
72571
|
+
];
|
|
72572
|
+
deps.stderr.write(`${screen.join(`
|
|
72573
|
+
`)}
|
|
72574
|
+
`);
|
|
72575
|
+
if (!skipConfirm) {
|
|
72576
|
+
const typed = await deps.promptLine(`Type ${CONFIRM_WORD} to ${enabled ? "allow" : "stop"} launches from ${n === 1 ? "this wallet" : `these ${n} wallets`}: `);
|
|
72577
|
+
if (typed.trim().toLowerCase() !== CONFIRM_WORD) {
|
|
72578
|
+
writeLocalFailure(deps, {
|
|
72579
|
+
code: "ALLOW_LAUNCH_NOT_ACKNOWLEDGED",
|
|
72580
|
+
message: `The acknowledgement is the word ${CONFIRM_WORD}; nothing changed.`,
|
|
72581
|
+
suggestion: `Run the command again and type ${CONFIRM_WORD} at the prompt.`
|
|
72582
|
+
}, json);
|
|
72583
|
+
return 1;
|
|
72584
|
+
}
|
|
72585
|
+
}
|
|
72586
|
+
const done = [];
|
|
72587
|
+
const failed = [];
|
|
72588
|
+
for (const row of changing) {
|
|
72589
|
+
const result = await request2(capabilityPath(row.id), "PUT", { capability: "allowLaunch", enabled });
|
|
72590
|
+
if (result.ok)
|
|
72591
|
+
done.push({ ...row, allowLaunch: enabled });
|
|
72592
|
+
else {
|
|
72593
|
+
failed.push({
|
|
72594
|
+
id: row.id,
|
|
72595
|
+
code: result.status === 404 ? "WALLET_NOT_FOUND" : result.code ?? null,
|
|
72596
|
+
message: result.status === 404 ? "no longer an active TEE wallet on this account" : result.message
|
|
72597
|
+
});
|
|
72598
|
+
}
|
|
72599
|
+
}
|
|
72600
|
+
if (json) {
|
|
72601
|
+
deps.stdout.write(`${JSON.stringify({ success: failed.length === 0, command, enabled, changed: done, unchanged: shown.unchanged, notTee: shown.notTee, failed })}
|
|
72602
|
+
`);
|
|
72603
|
+
return failed.length > 0 ? 1 : 0;
|
|
72604
|
+
}
|
|
72605
|
+
const verb = enabled ? "Allowed launches from" : "Stopped launches from";
|
|
72606
|
+
deps.stdout.write(`${verb} ${done.length} wallet${done.length === 1 ? "" : "s"}.
|
|
72607
|
+
`);
|
|
72608
|
+
for (const failure of failed) {
|
|
72609
|
+
deps.stderr.write(`Not changed: ${failure.id}: ${failure.message}${failure.code ? ` (${failure.code})` : ""}.
|
|
72610
|
+
`);
|
|
72611
|
+
}
|
|
72612
|
+
return failed.length > 0 ? 1 : 0;
|
|
72613
|
+
}
|
|
72614
|
+
function walletsAllowLaunch(args, ctx) {
|
|
72615
|
+
return setAllowLaunch(args, ctx, true);
|
|
72616
|
+
}
|
|
72617
|
+
function walletsDisallowLaunch(args, ctx) {
|
|
72618
|
+
return setAllowLaunch(args, ctx, false);
|
|
72619
|
+
}
|
|
72620
|
+
|
|
72330
72621
|
// src/commands/wallets-close-empty.ts
|
|
72331
72622
|
init_args();
|
|
72332
72623
|
init_render();
|
|
@@ -72484,7 +72775,7 @@ init_promote_support();
|
|
|
72484
72775
|
var TRUST_PATH = "/api/v1/agent/linked-wallets/trust";
|
|
72485
72776
|
var USAGE_TRUST = "Usage: candle wallets trust <label|address|id|prefix*>... [--json]";
|
|
72486
72777
|
var USAGE_UNTRUST = "Usage: candle wallets untrust <label|address|id|prefix*>... [--yes] [--json]";
|
|
72487
|
-
var
|
|
72778
|
+
var DEVICE_TOKEN_REQUIRED3 = {
|
|
72488
72779
|
code: "DEVICE_TOKEN_REQUIRED",
|
|
72489
72780
|
message: "Marking a wallet trusted needs the device token, the owner's credential; an API key cannot do it.",
|
|
72490
72781
|
suggestion: "Run: candle auth login"
|
|
@@ -72495,7 +72786,7 @@ function sessionLinkedLine(rows) {
|
|
|
72495
72786
|
const names = rows.map((row) => row.label ?? row.address).join(", ");
|
|
72496
72787
|
return `${n} linked while signed in (${names}): always yours while linked. Revoke ${it} to remove ${it}.`;
|
|
72497
72788
|
}
|
|
72498
|
-
function
|
|
72789
|
+
function nothingToChangeLine2(shown, trusted) {
|
|
72499
72790
|
const session = shown.sessionLinked ?? [];
|
|
72500
72791
|
const unchanged = shown.unchanged;
|
|
72501
72792
|
if (session.length > 0 && unchanged.length === 0) {
|
|
@@ -72562,7 +72853,7 @@ async function setTrust(args, ctx, trusted) {
|
|
|
72562
72853
|
await printIdentity(ctx);
|
|
72563
72854
|
const deviceToken = await resolveDeviceToken(deps, ctx.profile);
|
|
72564
72855
|
if (!deviceToken) {
|
|
72565
|
-
writeLocalFailure(deps,
|
|
72856
|
+
writeLocalFailure(deps, DEVICE_TOKEN_REQUIRED3, json);
|
|
72566
72857
|
return 1;
|
|
72567
72858
|
}
|
|
72568
72859
|
if (!skipConfirm && (!deps.isTTY.stdin || !deps.isTTY.stdout)) {
|
|
@@ -72592,7 +72883,7 @@ async function setTrust(args, ctx, trusted) {
|
|
|
72592
72883
|
deps.stdout.write(`${JSON.stringify({ ...shown, command })}
|
|
72593
72884
|
`);
|
|
72594
72885
|
} else {
|
|
72595
|
-
deps.stdout.write(
|
|
72886
|
+
deps.stdout.write(nothingToChangeLine2(shown, trusted));
|
|
72596
72887
|
}
|
|
72597
72888
|
return 0;
|
|
72598
72889
|
}
|
|
@@ -72859,6 +73150,8 @@ var COMMANDS = {
|
|
|
72859
73150
|
revoke: walletsRevoke,
|
|
72860
73151
|
trust: walletsTrust,
|
|
72861
73152
|
untrust: walletsUntrust,
|
|
73153
|
+
"allow-launch": walletsAllowLaunch,
|
|
73154
|
+
"disallow-launch": walletsDisallowLaunch,
|
|
72862
73155
|
"close-empty": walletsCloseEmpty
|
|
72863
73156
|
},
|
|
72864
73157
|
bare: wallets
|
package/package.json
CHANGED