candle-launch-helper 0.0.0-stage → 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog: candle-launch-helper
2
+
3
+ ## 0.1.0
4
+
5
+ First release, on candle-bot-core 0.4.0 (pinned exactly).
6
+
7
+ - One Candle launch per spec through the atomic route (payer main): build, the dev buy's expected fill checked,
8
+ submit within 45 s; the dev buy as the bundle's first buy.
9
+ - `clientLaunchId` saved before the first build, never replaced; every state saved before its call; restarts,
10
+ timeouts and errors reconcile from Candle's launch job (`building` saved before the build: a restart never builds).
11
+ - `RETRY_LAUNCH` builds once more under the same id only after a job read says `failed` or `JOB_NOT_FOUND`, and not
12
+ under `PAUSE_BUYS` or `STOP`; otherwise it is ignored with why.
13
+ - Before the first build: `minDevBuyOutRaw` required (live), the dev buy within `lifetimeSol` and `maxOpen`, `wallet`
14
+ the key's embedded wallet, and no earlier launch by the account with the same name and symbol (`launch.newLaunch`
15
+ overrides).
16
+ - The dev bag becomes a core position from its transaction on chain (core `openFromFill`); exits, `sell`, `STOP`,
17
+ `PAUSE_BUYS` as every bot. A missing or wrong dev-buy signature is found in the wallet's history for the mint;
18
+ `adopt <signature>` books it by hand. `run` waits for a launch call in flight before closing on STOP or SIGTERM.
19
+ - Dry mode calls nothing. `plan` (with the API key: Candle's `dryRunAtomicLaunch` preview of the dev buy's fill),
20
+ `run`, `status`, `sell`, `adopt`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 candle-launch-bot contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,147 @@
1
- # Temporary Holding Version
1
+ # candle-launch-helper
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Fires the [Candle](https://candle.tv) launch you describe, with your dev buy in the same bundle, then manages the dev
4
+ bag with exits and controls. One launch per name and symbol; a run never launches twice. Built on Candle's official SDK,
5
+ [`@candledottv/agent-sdk`](https://www.npmjs.com/package/@candledottv/agent-sdk) (pinned at 0.6.14), and
6
+ [candle-bot-core](https://www.npmjs.com/package/candle-bot-core).
7
+
8
+ Built by [Bruce Jeet](https://github.com/BruceJeet).
9
+
10
+ - **The launch:** name, symbol, image, description, socials and launch mode from the config, on Solana.
11
+ - **The dev buy:** the bundle's first buy, from the same wallet, landing in the launch's own block (Candle's atomic
12
+ route, a Jito bundle: all or nothing). Candle's expected fill for it is checked before anything is sent.
13
+ - **The bag:** once the launch lands, the dev buy is read from its transaction on chain (amount and cost) and becomes a
14
+ position the core manages: `tpsl`, `time`, `ladder`, `preGrad`, `hold` or your own exit, `STOP`, `PAUSE_BUYS`,
15
+ `sell`.
16
+ - **Dry mode** calls nothing: it prints the launch it would send. `plan` with the API key set asks Candle for a dry-run
17
+ preview of the dev buy's fill (`dryRunAtomicLaunch`: the build's checks, nothing recorded, reserved or sent).
18
+
19
+ ## Quick start
20
+
21
+ Node 22.18 or newer. A Candle API key with the `launch:write`, `swap:write` and `launch:read` scopes, on a plan that
22
+ allows atomic launches, and the account's embedded wallet funded.
23
+
24
+ ```sh
25
+ npm install -g candle-launch-helper
26
+ cp "$(npm root -g)/candle-launch-helper/examples/launch.json" launch.json # your coin's details
27
+ candle-launch-helper plan --config launch.json # the exact launch body; with CANDLE_API_KEY set, Candle's
28
+ # dry-run preview of the dev buy's fill (--offline: none)
29
+ candle-launch-helper run --config launch.json # dry: logs the plan, runs the loop
30
+ export CANDLE_API_KEY=cndl_live_...
31
+ # set "mode": "live", your embedded wallet and launch.minDevBuyOutRaw (from plan's preview) in launch.json, then:
32
+ candle-launch-helper run --config launch.json # launches once, then manages the dev bag
33
+ ```
34
+
35
+ ## The route, and why
36
+
37
+ Candle's atomic launch (`buildAtomicLaunch`, then `submitAtomicLaunch`) from the account's main wallet:
38
+
39
+ - the dev buy is a first-buy leg of the bundle, in the launch's block: nobody buys before it;
40
+ - Candle signs the main wallet's legs itself, so nothing is signed on this machine;
41
+ - the launch has one identity, its `clientLaunchId`, which Candle uses to keep a second launch from being built.
42
+
43
+ `payer` must be `"embedded"`: Candle refuses a TEE wallet on the atomic route.
44
+
45
+ ## A run never launches twice
46
+
47
+ - **Before the first build** a live run checks, and sends nothing unless all pass (an alert says which failed; a
48
+ read that failed is tried again every 30 s):
49
+ - `launch.minDevBuyOutRaw` is set;
50
+ - the dev buy fits `buy.lifetimeSol` and `buy.maxOpen`;
51
+ - `wallet` is the key's embedded (main) wallet, where the dev bag lands (Candle's portfolio);
52
+ - the account has not launched a token with the same name and symbol (Candle's list of the account's launched
53
+ tokens, case and spaces ignored). Another state dir with the same spec is refused. A listed token without a name
54
+ or symbol, or a list that ends without saying it is done, is refused too (it cannot be judged).
55
+ `launch.newLaunch: true` launches the same name and symbol again on purpose. The check relies on Candle's list
56
+ showing a launch from minutes ago; that was not verified live.
57
+ - The `clientLaunchId` is written to the ledger **before** the first build and is never replaced. A state dir holds
58
+ one launch; a config for another coin in the same state dir is refused (alert), nothing is sent.
59
+ - The build is checked before the submit: the dev buy's leg carries `minAmountOut: 0` inside the bundle, so Candle's
60
+ `expectedFill` is the check (present, above zero, and at least `launch.minDevBuyOutRaw`). The submit goes out within
61
+ 45 s of the build (the legs share one blockhash), and only from the process that built it.
62
+ - Every state is saved before the call it leads to (`building` before the build). A restart during a build, after a
63
+ build that was not submitted, or during a submit, a timeout, or any error after a request was sent reads Candle's
64
+ launch job (`getLaunchJob`) every 30 s: it resumes on `confirmed`, stops on `failed`, and waits otherwise. A restart
65
+ never builds. A launch unresolved for `launch.unresolvedAlertMin` minutes raises an alert with its `clientLaunchId`.
66
+ - **The bot never builds again on its own.** `RETRY_LAUNCH` in the state dir asks for one more build under the same
67
+ id. It acts only when a job read just then says `failed` or `JOB_NOT_FOUND` (the SDK's recovery steps), and not
68
+ while `PAUSE_BUYS` or `STOP` is set. Any other answer uses the file up (`RETRY_LAUNCH.ignored`) and logs and alerts
69
+ why. A job that says `confirmed` is taken as landed. A 409 on the rebuild (Candle still resolving the first attempt)
70
+ goes back to reading the job. A new `clientLaunchId` is never sent while a launch may land.
71
+ - `PAUSE_BUYS`: no launch fires (and none is submitted after a build). Dry mode makes no call at all.
72
+ - STOP, a signal and PAUSE_BUYS are read again right before every build.
73
+ - On `STOP` or SIGTERM the run waits for a launch call in flight (a submit can take about 160 s) before it closes the
74
+ ledger. Under systemd give the unit `TimeoutStopSec=200` or more; a kill in the middle is still safe (the state was
75
+ saved before the call, and the restart reads the job).
76
+
77
+ ## The dev bag
78
+
79
+ Once the launch lands, the dev buy's transaction (the submit's buy-leg signature, else Candle's job) is booked from
80
+ the chain by the core (`openFromFill`: this wallet's tokens and SOL spent, never an API's number). When Candle names no
81
+ dev-buy transaction, or one that gave the wallet no tokens, the wallet's token-account history for the mint is read
82
+ whole (paged), oldest first. The first transaction that gave the wallet this coin is the dev buy (the bundle bought
83
+ first and opened the account): it is booked if it passes the core's checks. The scan never walks past it to a later
84
+ buy. If it cannot be read yet, the scan tries again later; if it fails the checks (no SOL spent, already booked), or
85
+ the history runs past 10,000 signatures, an alert asks for a hand booking:
86
+
87
+ ```sh
88
+ candle-launch-helper adopt <signature> --config launch.json [--mint <mint>] [--force] # with the bot stopped
89
+ ```
90
+
91
+ `adopt` books the bag from that transaction through the same checks (this wallet, this mint, SOL spent, tokens
92
+ received, not booked already) and marks the launch open. On a launch whose bag is already open it refuses (a second
93
+ position would count twice in the caps and the exits); `--force` books it anyway.
94
+
95
+ ## Config
96
+
97
+ The core's sections as in every candle-bot-core bot (`mode`, `payer`, `apiKeyEnv`, `rpcUrlEnv`, `wallet`, `stateDir`,
98
+ `buy`, `sell`, `exit`, `retry`, `alerts`), plus:
99
+
100
+ ```json
101
+ {
102
+ "launch": {
103
+ "name": "My Coin", "symbol": "MINE", "imageUrl": "https://.../logo.png",
104
+ "description": "...", "website": "https://...", "twitter": "https://x.com/...", "telegram": "https://t.me/...",
105
+ "mode": "open",
106
+ "devBuySol": 0.05,
107
+ "minDevBuyOutRaw": "1000000000000",
108
+ "unresolvedAlertMin": 10
109
+ }
110
+ }
111
+ ```
112
+
113
+ | Key | Meaning |
114
+ |---|---|
115
+ | `launch.name`, `symbol`, `imageUrl` | required; the image is a hosted https URL |
116
+ | `launch.description`, `website`, `twitter`, `telegram` | optional |
117
+ | `launch.mode` | Candle launch mode (default `open`) |
118
+ | `launch.devBuySol` | the dev buy, SOL; at most `buy.sol`; counts against `buy.lifetimeSol` |
119
+ | `launch.minDevBuyOutRaw` | required in live mode: refuse to submit if Candle's expected fill is lower (raw token units, as a string). `plan` previews the fill |
120
+ | `launch.newLaunch` | `true` to launch even though the account already launched this name and symbol (default `false`: refused) |
121
+ | `launch.unresolvedAlertMin` | alert when a sent launch stays unresolved this long (default 10) |
122
+
123
+ ## State dir
124
+
125
+ | File | What |
126
+ |---|---|
127
+ | `ledger.sqlite` | the `launches` row (id, state, bundle, mint, signatures), the dev bag's position and its orders, alerts |
128
+ | `status.json` | the launch's state, mint and position; the bag's value and exits |
129
+ | `events.jsonl` | `launch_preflight_refused`, `launch_id_saved`, `launch_built`, `launch_landed`, `launch_unresolved`, `launch_retry`, `launch_retry_ignored`, `dev_buy_found_on_chain`, `dev_bag_open`, sells |
130
+ | `RETRY_LAUNCH`, `.done`, `.ignored` | the operator's retry request, and what became of it |
131
+
132
+ ## Known limits
133
+
134
+ - **Embedded payer only** (the atomic route). One dev-buy leg.
135
+ - **Image by URL.** The image must already be hosted.
136
+ - **The dev-buy transaction** is taken from the submit's signatures (the buy leg's index), Candle's launch job, or the
137
+ wallet's history for the mint; until one is booked, the bag is not managed (alert; `adopt` books it by hand).
138
+ - **The same-spec check** reads up to 20 pages (1,000 tokens) of the account's launches; an account with more is
139
+ refused unless `launch.newLaunch` is set.
140
+ - **A launch refused at the submit check** (expected fill under the floor) leaves a built bundle Candle never got; its
141
+ job may not answer `failed` or `JOB_NOT_FOUND`, so `RETRY_LAUNCH` may be ignored. Use a new state dir (with
142
+ `launch.newLaunch: true` only if the first launch is known not to exist).
143
+ - **Solana only.**
144
+
145
+ ## License
146
+
147
+ MIT
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ // The published package runs the compiled dist/; a source checkout runs src/ directly (Node strips types).
3
+ import { existsSync } from "node:fs";
4
+ const dist = new URL("../dist/cli.js", import.meta.url);
5
+ const entry = existsSync(dist) ? dist : new URL("../src/cli.ts", import.meta.url);
6
+ const { main } = await import(entry.href);
7
+ process.exit(await main(process.argv.slice(2)));
package/dist/cli.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { type Config } from "./config.ts";
2
+ import { type LaunchClient } from "./launcher.ts";
3
+ export declare const HELP = "candle-launch-helper: fires the Candle launch you describe, with its dev buy in the same bundle, then manages the\ndev bag with the core's exits and controls.\n\n candle-launch-helper plan --config launch.json [--offline] print the launch body and, with the API key set,\n Candle's dry-run preview of the dev buy's fill\n (dryRunAtomicLaunch: records and sends nothing)\n candle-launch-helper run --config launch.json [--mode dry|live] launch (live), then manage the bag\n candle-launch-helper status --config launch.json print status.json\n candle-launch-helper sell <mint> --config launch.json [--fraction 1]\n candle-launch-helper adopt <signature> --config launch.json [--mint <mint>] [--force]\n book the dev bag from its transaction (bot stopped)\n\nOne launch per spec: a live run refuses when the account already launched the same name and symbol (launch.newLaunch\noverrides), and a state dir never launches twice. Controls: STOP, PAUSE_BUYS (no launch fires), RETRY_LAUNCH (one more\nbuild under the same clientLaunchId, only when Candle's job says failed or not found). Docs: the README.\n";
4
+ /**
5
+ * `plan`: the launch body, and with a client Candle's dry-run preview of the dev buy's fill (dryRunAtomicLaunch:
6
+ * the build's checks; records, reserves and sends nothing). Its clientLaunchId is a throwaway, never built.
7
+ */
8
+ export declare function planOutput(cfg: Config, client: LaunchClient | null, skipped: string | null): Promise<Record<string, unknown>>;
9
+ /** Longest a launch call may take (the SDK: a submit up to ~160 s). */
10
+ export declare const LAUNCH_TIMEOUT_MS = 180000;
11
+ export declare function main(argv?: string[]): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,141 @@
1
+ /** The command line: plan, run, status, sell, adopt. */
2
+ import { randomUUID } from "node:crypto";
3
+ import { readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { CandleClient } from "@candledottv/agent-sdk";
6
+ import { timedFetch } from "candle-bot-core/exec/trader";
7
+ import { acquireLock } from "candle-bot-core/engine";
8
+ import { args, context as coreContext, die, guardMain, parts, run, sell, status } from "candle-bot-core/runtime";
9
+ import { loadConfig, ConfigError } from "./config.js";
10
+ import { Ledger } from "./ledger.js";
11
+ import { Engine, launchBody } from "./launcher.js";
12
+ export const HELP = `candle-launch-helper: fires the Candle launch you describe, with its dev buy in the same bundle, then manages the
13
+ dev bag with the core's exits and controls.
14
+
15
+ candle-launch-helper plan --config launch.json [--offline] print the launch body and, with the API key set,
16
+ Candle's dry-run preview of the dev buy's fill
17
+ (dryRunAtomicLaunch: records and sends nothing)
18
+ candle-launch-helper run --config launch.json [--mode dry|live] launch (live), then manage the bag
19
+ candle-launch-helper status --config launch.json print status.json
20
+ candle-launch-helper sell <mint> --config launch.json [--fraction 1]
21
+ candle-launch-helper adopt <signature> --config launch.json [--mint <mint>] [--force]
22
+ book the dev bag from its transaction (bot stopped)
23
+
24
+ One launch per spec: a live run refuses when the account already launched the same name and symbol (launch.newLaunch
25
+ overrides), and a state dir never launches twice. Controls: STOP, PAUSE_BUYS (no launch fires), RETRY_LAUNCH (one more
26
+ build under the same clientLaunchId, only when Candle's job says failed or not found). Docs: the README.
27
+ `;
28
+ function context(configPath, modeOverride) {
29
+ let cfg;
30
+ try {
31
+ cfg = loadConfig(configPath);
32
+ }
33
+ catch (e) {
34
+ if (e instanceof ConfigError)
35
+ die(e.message);
36
+ throw e;
37
+ }
38
+ return coreContext(cfg, modeOverride);
39
+ }
40
+ async function makeEngine(ctx, ledger) {
41
+ const { strategy, alerter, executor } = await parts(ctx, ledger, { maxBuyImpactBps: 10_000 });
42
+ // Live: Candle's client for the launch calls, with a longer timeout than trades (a submit can take ~160 s and a
43
+ // same-id rebuild ~95 s, per the SDK). Dry: none, so nothing can be built or sent.
44
+ const apiKey = process.env[ctx.cfg.apiKeyEnv];
45
+ if (ctx.cfg.mode === "live" && !apiKey)
46
+ die(`${ctx.cfg.apiKeyEnv} is not set (the Candle API key)`);
47
+ const client = ctx.cfg.mode === "live" ? new CandleClient({ apiUrl: ctx.cfg.apiUrl, apiKey, fetch: timedFetch(LAUNCH_TIMEOUT_MS) }) : null;
48
+ const engine = new Engine({ cfg: ctx.cfg, ledger, client, executor, strategy, rpc: ctx.rpc, log: ctx.log, alerter });
49
+ return { engine, trigger: engine.trigger };
50
+ }
51
+ /**
52
+ * `plan`: the launch body, and with a client Candle's dry-run preview of the dev buy's fill (dryRunAtomicLaunch:
53
+ * the build's checks; records, reserves and sends nothing). Its clientLaunchId is a throwaway, never built.
54
+ */
55
+ export async function planOutput(cfg, client, skipped) {
56
+ const body = launchBody(cfg.launch);
57
+ const out = { route: "atomic (build, check the dev buy's expected fill, submit)", body, minDevBuyOutRaw: cfg.launch.minDevBuyOutRaw?.toString() ?? null };
58
+ if (!client?.dryRunAtomicLaunch) {
59
+ out.preview = skipped ?? "skipped (no client)";
60
+ return out;
61
+ }
62
+ try {
63
+ const r = await client.dryRunAtomicLaunch({ ...body, clientLaunchId: `clh-plan-${randomUUID()}` });
64
+ const fill = r.fills?.[0]?.expectedFill ?? null;
65
+ out.preview = { devBuyExpectedOutRaw: fill?.amountOutRaw ?? null, devBuyMinOutRaw: fill?.minAmountOutRaw ?? null, graduatesInBundle: r.graduatesInBundle ?? null, note: "set launch.minDevBuyOutRaw at or below devBuyExpectedOutRaw (the submit is refused under it)" };
66
+ }
67
+ catch (e) {
68
+ out.preview = { error: String(e?.message ?? e).slice(0, 300) };
69
+ }
70
+ return out;
71
+ }
72
+ /** Longest a launch call may take (the SDK: a submit up to ~160 s). */
73
+ export const LAUNCH_TIMEOUT_MS = 180_000;
74
+ const openLedger = (ctx) => () => new Ledger(join(ctx.cfg.stateDir, "ledger.sqlite"));
75
+ export async function main(argv = process.argv.slice(2)) {
76
+ return guardMain(async () => {
77
+ const { pos, opt } = args(argv);
78
+ const cmd = pos[0];
79
+ if (!cmd || cmd === "help" || opt.help) {
80
+ process.stdout.write(HELP);
81
+ return cmd || opt.help ? 0 : 2;
82
+ }
83
+ if (cmd === "version" || opt.version) {
84
+ process.stdout.write(JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version + "\n");
85
+ return 0;
86
+ }
87
+ const config = opt.config ?? "launch.json";
88
+ switch (cmd) {
89
+ case "plan": {
90
+ let cfg;
91
+ try {
92
+ cfg = loadConfig(config);
93
+ }
94
+ catch (e) {
95
+ if (e instanceof ConfigError)
96
+ die(e.message);
97
+ throw e;
98
+ }
99
+ const apiKey = process.env[cfg.apiKeyEnv];
100
+ const skip = opt.offline ? "skipped (--offline)" : !apiKey ? `skipped (${cfg.apiKeyEnv} not set)` : null;
101
+ const client = skip ? null : new CandleClient({ apiUrl: cfg.apiUrl, apiKey, fetch: timedFetch(LAUNCH_TIMEOUT_MS) });
102
+ const out = await planOutput(cfg, client, skip);
103
+ process.stdout.write(JSON.stringify(out, null, 2) + "\n");
104
+ return 0;
105
+ }
106
+ case "run": {
107
+ const ctx = context(config, opt.mode);
108
+ return run(ctx, openLedger(ctx), (ledger) => makeEngine(ctx, ledger));
109
+ }
110
+ case "status":
111
+ return status(context(config));
112
+ case "sell": {
113
+ if (!pos[1])
114
+ die("sell <mint> [--fraction f]");
115
+ const ctx = context(config);
116
+ return sell(ctx, pos[1], opt.fraction ? Number(opt.fraction) : 1, openLedger(ctx), (ledger) => makeEngine(ctx, ledger));
117
+ }
118
+ case "adopt": {
119
+ if (!pos[1])
120
+ die("adopt <signature> [--mint <mint>]");
121
+ const ctx = context(config);
122
+ const lock = acquireLock(ctx.cfg.stateDir);
123
+ if (!lock)
124
+ die(`the bot is running (${ctx.cfg.stateDir}/lock): stop it, then adopt`);
125
+ const ledger = openLedger(ctx)();
126
+ try {
127
+ const { engine } = await makeEngine(ctx, ledger);
128
+ const r = await engine.launcher.adopt(pos[1], opt.mint, opt.force === "true");
129
+ process.stdout.write(r + "\n");
130
+ return /^position \d+/.test(r) ? 0 : 1;
131
+ }
132
+ finally {
133
+ ledger.close();
134
+ lock.release();
135
+ }
136
+ }
137
+ default:
138
+ die(`unknown command ${cmd}`);
139
+ }
140
+ });
141
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The one config file: the core's shared sections (candle-bot-core) plus `launch`, the launch spec. Unknown keys are
3
+ * refused at every level. The launch goes through Candle's atomic route from the account's main (embedded) wallet:
4
+ * `payer` must be "embedded".
5
+ */
6
+ import { type Config as CoreConfig, type SectionParser } from "candle-bot-core";
7
+ export { ConfigError } from "candle-bot-core/config";
8
+ export declare const LAUNCH_MODES: readonly ["open", "exclusive", "test-open", "test-exclusive", "exp-a", "exp-b", "exp-c", "exp-custom"];
9
+ export type LaunchMode = (typeof LAUNCH_MODES)[number];
10
+ export interface LaunchSpec {
11
+ name: string;
12
+ symbol: string;
13
+ imageUrl: string;
14
+ description?: string;
15
+ website?: string;
16
+ twitter?: string;
17
+ telegram?: string;
18
+ mode: LaunchMode;
19
+ /** The dev buy, SOL: the bundle's first buy, from the same wallet, in the launch's block. At most buy.sol. */
20
+ devBuySol: number;
21
+ /** Refuse to submit unless Candle's expected fill for the dev buy is at least this (raw token units). */
22
+ minDevBuyOutRaw?: bigint;
23
+ /** Alert when a sent launch is still unresolved after this many minutes. */
24
+ unresolvedAlertMin: number;
25
+ /** Launch even when the account already launched a token with this name and symbol (default false: refused). */
26
+ newLaunch: boolean;
27
+ }
28
+ export type Config = CoreConfig & {
29
+ launch: LaunchSpec;
30
+ };
31
+ export declare const SECTIONS: {
32
+ launch: SectionParser<LaunchSpec>;
33
+ };
34
+ export declare function parseConfig(raw: unknown, baseDir?: string): Config;
35
+ export declare function loadConfig(path: string): Config;
package/dist/config.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The one config file: the core's shared sections (candle-bot-core) plus `launch`, the launch spec. Unknown keys are
3
+ * refused at every level. The launch goes through Candle's atomic route from the account's main (embedded) wallet:
4
+ * `payer` must be "embedded".
5
+ */
6
+ import { parseConfig as parseCore, loadConfig as loadCore, ConfigError, isObj, only, num, str } from "candle-bot-core";
7
+ export { ConfigError } from "candle-bot-core/config";
8
+ export const LAUNCH_MODES = ["open", "exclusive", "test-open", "test-exclusive", "exp-a", "exp-b", "exp-c", "exp-custom"];
9
+ const parseLaunch = (raw, core) => {
10
+ if (!isObj(raw))
11
+ throw new ConfigError("config.launch: required object (name, symbol, imageUrl, devBuySol, ...)");
12
+ only(raw, ["name", "symbol", "imageUrl", "description", "website", "twitter", "telegram", "mode", "devBuySol", "minDevBuyOutRaw", "unresolvedAlertMin", "newLaunch"], "config.launch");
13
+ const name = str(raw, "name", "config.launch");
14
+ const symbol = str(raw, "symbol", "config.launch");
15
+ const imageUrl = str(raw, "imageUrl", "config.launch");
16
+ if (!/^https:\/\//.test(imageUrl))
17
+ throw new ConfigError("config.launch.imageUrl: must be an https:// URL");
18
+ const mode = str(raw, "mode", "config.launch", { def: "open" });
19
+ if (!LAUNCH_MODES.includes(mode))
20
+ throw new ConfigError(`config.launch.mode: "${mode}" is not a Candle launch mode (${LAUNCH_MODES.join(", ")})`);
21
+ const devBuySol = num(raw, "devBuySol", "config.launch", { min: 0.000001 });
22
+ if (devBuySol > core.buy.sol)
23
+ throw new ConfigError(`config.launch.devBuySol: ${devBuySol} is above buy.sol (${core.buy.sol})`);
24
+ let minDevBuyOutRaw;
25
+ if (raw.minDevBuyOutRaw !== undefined) {
26
+ const v = String(raw.minDevBuyOutRaw);
27
+ if (!/^\d+$/.test(v))
28
+ throw new ConfigError("config.launch.minDevBuyOutRaw: a whole number of raw token units (a string)");
29
+ minDevBuyOutRaw = BigInt(v);
30
+ }
31
+ return {
32
+ name, symbol, imageUrl, mode, devBuySol, minDevBuyOutRaw,
33
+ description: str(raw, "description", "config.launch", { optional: true }),
34
+ website: str(raw, "website", "config.launch", { optional: true }),
35
+ twitter: str(raw, "twitter", "config.launch", { optional: true }),
36
+ telegram: str(raw, "telegram", "config.launch", { optional: true }),
37
+ unresolvedAlertMin: num(raw, "unresolvedAlertMin", "config.launch", { min: 1, def: 10 }),
38
+ newLaunch: bool(raw, "newLaunch"),
39
+ };
40
+ };
41
+ function bool(o, k) {
42
+ const v = o[k];
43
+ if (v === undefined)
44
+ return false;
45
+ if (typeof v !== "boolean")
46
+ throw new ConfigError(`config.launch.${k}: true or false`);
47
+ return v;
48
+ }
49
+ export const SECTIONS = { launch: parseLaunch };
50
+ function check(cfg) {
51
+ if (cfg.payer !== "embedded")
52
+ throw new ConfigError('config.payer: the launch helper launches from the account\'s main wallet through Candle\'s atomic route; set "payer": "embedded" (Candle refuses a TEE wallet on that route)');
53
+ // The live run's pre-flight refuses too (a --mode live override is checked there).
54
+ if (cfg.mode === "live" && cfg.launch.minDevBuyOutRaw === undefined)
55
+ throw new ConfigError("config.launch.minDevBuyOutRaw: required in live mode (the dev buy's on-chain minimum is 0 inside the bundle; `plan` previews Candle's fill to set it from)");
56
+ return cfg;
57
+ }
58
+ export function parseConfig(raw, baseDir = process.cwd()) {
59
+ return check(parseCore(raw, baseDir, SECTIONS));
60
+ }
61
+ export function loadConfig(path) {
62
+ return check(loadCore(path, SECTIONS));
63
+ }
@@ -0,0 +1,8 @@
1
+ /** candle-launch-helper: the launcher, its trigger and engine, the config and the ledger, for use as a library. */
2
+ export { Launcher, LaunchTrigger, Engine, launchBody, specHash, BUILD_SUBMIT_MS, JOB_READ_MS, RETRY_FILE, LAUNCH_LIST_PAGES, DEV_BUY_PAGE, DEV_BUY_PAGES } from "./launcher.ts";
3
+ export type { LaunchClient, LauncherDeps, EngineDeps } from "./launcher.ts";
4
+ export { parseConfig, loadConfig, SECTIONS, ConfigError, LAUNCH_MODES } from "./config.ts";
5
+ export type { Config, LaunchSpec, LaunchMode } from "./config.ts";
6
+ export { Ledger } from "./ledger.ts";
7
+ export type { LaunchRow, LaunchState } from "./ledger.ts";
8
+ export { main, HELP, planOutput } from "./cli.ts";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ /** candle-launch-helper: the launcher, its trigger and engine, the config and the ledger, for use as a library. */
2
+ export { Launcher, LaunchTrigger, Engine, launchBody, specHash, BUILD_SUBMIT_MS, JOB_READ_MS, RETRY_FILE, LAUNCH_LIST_PAGES, DEV_BUY_PAGE, DEV_BUY_PAGES } from "./launcher.js";
3
+ export { parseConfig, loadConfig, SECTIONS, ConfigError, LAUNCH_MODES } from "./config.js";
4
+ export { Ledger } from "./ledger.js";
5
+ export { main, HELP, planOutput } from "./cli.js";