@candledottv/agent-sdk 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.
@@ -0,0 +1,1192 @@
1
+ /**
2
+ * Typed client for the Candle agent rail REST surface (docs/headless-launch.md and
3
+ * docs/agent-trading.md are the authoritative endpoint references; apps/api/src/routes/{launch,
4
+ * launch-headless,markets,verify,activity,users,uploads,agent,trade-agent,trade-agent-shared,
5
+ * trade-agent-confirm}.ts are the implementations these types mirror).
6
+ *
7
+ * Design rules:
8
+ * - Near-zero runtime dependencies: global `fetch` (injectable for tests), global `crypto` for
9
+ * the generated idempotency id and (via `importWallet`) for HPKE. Works on Bun and Node 18+.
10
+ * The SDK's only three package dependencies, `@hpke/core`, `@hpke/chacha20poly1305`, and
11
+ * `@scure/base` (wallet-import.ts), exist solely to serve `importWallet` below.
12
+ * - The REST API is the authoritative validator (same stance as packages/mcp): request types
13
+ * describe the wire shape for autocomplete, they do not re-implement server validation.
14
+ * - Every non-2xx response throws `CandleApiError` (see errors.ts). Methods that unwrap a
15
+ * payload (`getQuotePairs`, `getPresets`, `getMarket`, `getLaunchJob`, `getAgentProfile`)
16
+ * return the useful inner object; methods whose top-level body IS the useful object
17
+ * (`launch`, `dryRunLaunch`, `getQuote`, `getFeed`, `verify`) return the parsed body.
18
+ * - `x-api-key` is attached to EVERY request when the client has a key. The write/keyed
19
+ * endpoints (launch, dry run, jobs, activity, uploads) refuse to fetch at all without one,
20
+ * throwing a plain `Error` that names the missing option, so a misconfigured agent fails
21
+ * fast and locally instead of with a server 401.
22
+ * - Idempotent launch retries: `launch()` fills in `clientLaunchId` ("sdk-" + UUID) when the
23
+ * caller omits one and re-sends the SAME id on network errors, non-envelope 5xx responses,
24
+ * retryable envelopes with 5xx status, and the retryable in-flight 409. It never retries a
25
+ * non-retryable envelope (IDEMPOTENCY_CONFLICT with a different body, LAUNCH_DISABLED, every
26
+ * validation error). Backoff is 250ms * 2^n, jittered to 50-100% of that, capped at 8s,
27
+ * bounded by `maxRetries` (default 3 retries after the initial attempt).
28
+ */
29
+ import type { SecretStore } from "./secret-store";
30
+ import { type WalletChain } from "./wallet-import";
31
+ export type Chain = "solana" | "hood";
32
+ /**
33
+ * The two shipped tiers plus their low-threshold TEST twins (~1/80 economics, same identity and
34
+ * NFT gate; see docs/superpowers/specs/2026-08-04-test-curve-configs-design.md). Test tiers are
35
+ * creatable only where the API's ENABLE_TEST_CURVES flag is on; the server refuses them
36
+ * otherwise, so client code needs no gate of its own.
37
+ */
38
+ export type LaunchTier = "open" | "exclusive" | "test-open" | "test-exclusive";
39
+ export type FeedBucket = "new" | "graduated" | "onfire" | "bluechip";
40
+ export interface CandleClientOptions {
41
+ /** Base URL of the Candle API, e.g. "https://api.alpha.candle.tv" (the alpha deployment). Trailing slashes are trimmed. */
42
+ apiUrl: string;
43
+ /** Agent API key (cndl_live_... / cndl_test_...). Required for launch, jobs, activity, uploads. */
44
+ apiKey?: string;
45
+ /** Injectable fetch for tests; defaults to the global. */
46
+ fetch?: typeof fetch;
47
+ /** Max launch() retries after the initial attempt. Default 3. */
48
+ maxRetries?: number;
49
+ /**
50
+ * Privy's app id: a PUBLIC identifier, the same value a frontend exposes as
51
+ * NEXT_PUBLIC_PRIVY_APP_ID, NOT a secret. Required by signLinkedTransaction() (and therefore by
52
+ * the linked-wallet paths of trade() and selfLaunch()): the sign relay authenticates to Privy
53
+ * under Candle's own server-side PRIVY_APP_ID, and the authorization signature this client
54
+ * computes locally covers that exact app id, so this option must be set to the SAME app id the
55
+ * relay uses or Privy rejects the forwarded signature as SIGNER_MISMATCH.
56
+ */
57
+ privyAppId?: string;
58
+ /**
59
+ * Where an agent's own P-256 signer private-key PEM lives, keyed by linkedWalletId (see
60
+ * secret-store.ts). Required by signLinkedTransaction() and the linked-wallet paths of trade()
61
+ * and selfLaunch(); Candle's servers never see this key, whichever SecretStore implementation
62
+ * holds it. That cuts both ways: if the caller loses this key, its linked wallet can no longer
63
+ * be signed for through this SDK, by design. There is no Candle-side recovery, since Candle
64
+ * never held a copy to recover; the only way back is revoking that linked wallet and
65
+ * re-importing it with a new signer key (see "Self-signed launches" in docs/headless-launch.md).
66
+ */
67
+ secretStore?: SecretStore;
68
+ /** Solana JSON-RPC endpoint used by broadcastSignedTransaction() and the Solana linked-wallet one-shots (trade()/selfLaunch()). */
69
+ solanaRpcUrl?: string;
70
+ /**
71
+ * EVM JSON-RPC endpoint used by broadcastSignedTransaction() and the Hood linked-wallet
72
+ * one-shots (trade()/selfLaunch()): fetching chain id, nonce, and fee data, and estimating gas
73
+ * for each leg (via packages/sdk/src/evm-tx.ts's helpers) all read from this endpoint. Required
74
+ * for a Hood linked payer; trade()/selfLaunch() throw a clear error naming this option when it
75
+ * is unset, before any signing.
76
+ */
77
+ evmRpcUrl?: string;
78
+ }
79
+ /** The bonding-curve terms of one (chain, quote asset, tier) cell. */
80
+ export interface CurveTerms {
81
+ symbol: string;
82
+ /** Migration threshold in the quote asset's smallest unit, as a decimal string. */
83
+ thresholdRaw: string;
84
+ raise: number;
85
+ startFdv: number;
86
+ bondingFdv: number;
87
+ supplySoldPct: number;
88
+ }
89
+ /** One quote asset a launch can be denominated in, with its per-tier terms. */
90
+ export interface QuotePair {
91
+ chain: Chain;
92
+ /** Stable lowercase id; what a launch request sends as `quoteAsset`. */
93
+ id: string;
94
+ symbol: string;
95
+ address: string;
96
+ decimals: number;
97
+ isNative: boolean;
98
+ /** Web-launcher dev-buy flag; agents should read `headlessDevBuy` instead once present. */
99
+ supportsDevBuy: boolean;
100
+ /** Whether a headless launch can bundle a dev buy in this asset (ships in Phase 2 wave 3). */
101
+ headlessDevBuy?: boolean;
102
+ tiers: Partial<Record<LaunchTier, CurveTerms>>;
103
+ }
104
+ /** GET /api/v1/launch/quote-pairs, unwrapped from its `payload` envelope. */
105
+ export interface QuotePairsPayload {
106
+ matrixVersion: number;
107
+ pairs: Partial<Record<Chain, QuotePair[]>>;
108
+ /** What each chain gets when a launch names no quote asset. */
109
+ defaults: Partial<Record<Chain, string>>;
110
+ }
111
+ /** One first-party preset, joined with the live tier terms. */
112
+ export interface LaunchPreset {
113
+ name: string;
114
+ description: string;
115
+ chain: Chain;
116
+ quoteAsset: string;
117
+ mode: LaunchTier;
118
+ dexVersion?: "v3" | "v4";
119
+ stakerAllocationBps: number;
120
+ terms: CurveTerms;
121
+ }
122
+ /** GET /api/v1/launch/presets, unwrapped from its `payload` envelope. */
123
+ export interface PresetsPayload {
124
+ matrixVersion: number;
125
+ presets: LaunchPreset[];
126
+ }
127
+ /** POST /api/v1/launch/headless request body. The server is the authoritative validator. */
128
+ export interface LaunchRequest {
129
+ /** Idempotency key, unique per account. launch() generates "sdk-" + UUID when absent. */
130
+ clientLaunchId?: string;
131
+ chain?: Chain;
132
+ quoteAsset?: string;
133
+ mode?: LaunchTier;
134
+ stakerAllocationBps?: number;
135
+ /** Hood only, required there: which Uniswap version the curve graduates through. */
136
+ dexVersion?: "v3" | "v4";
137
+ /** Initial dev buy. Solana: JSON number in the pair's base units. Hood: decimal string in wei. */
138
+ buyAmount?: number | string;
139
+ name: string;
140
+ symbol: string;
141
+ /** Roughly SQUARE (at most 1.5:1): this is the avatar. Wider is rejected as IMAGE_WRONG_SHAPE. */
142
+ imageUrl: string;
143
+ /**
144
+ * Optional WIDE artwork (wider than 1.5:1, e.g. 1200x630) for the token page's banner strip.
145
+ * Where a share card or OG image belongs; a square image here is rejected as
146
+ * BANNER_WRONG_SHAPE. Omitted, the strip falls back to `imageUrl`.
147
+ */
148
+ bannerUrl?: string;
149
+ description?: string;
150
+ socials?: {
151
+ twitter?: string;
152
+ telegram?: string;
153
+ website?: string;
154
+ discord?: string;
155
+ };
156
+ visibility?: "production" | "test" | "local" | "hidden";
157
+ }
158
+ /** POST /api/v1/launch/self/build request body. Extends LaunchRequest with linkedWalletId. */
159
+ export interface BuildSelfLaunchRequest extends LaunchRequest {
160
+ linkedWalletId: string;
161
+ }
162
+ /** POST /api/v1/launch/self/build response for Solana (unsigned transaction). */
163
+ export interface BuildSelfLaunchSolanaResult {
164
+ success: true;
165
+ transaction: string;
166
+ mint: string;
167
+ pool: string;
168
+ clientLaunchId: string;
169
+ expiresAt: number;
170
+ }
171
+ /** POST /api/v1/launch/self/build response for Hood (calldata). */
172
+ export interface BuildSelfLaunchHoodResult {
173
+ success: true;
174
+ transaction: {
175
+ to: string;
176
+ data: string;
177
+ };
178
+ curveAddress: string;
179
+ clientLaunchId: string;
180
+ expiresAt: number;
181
+ /** The linked payer's own checksummed EVM address: the transaction `from`, and the nonce query subject. */
182
+ walletAddress: string;
183
+ /** Present only when a platform fee applies; the companion transfer to send AFTER the createCurve tx. */
184
+ feeTransfer?: {
185
+ to: string;
186
+ data: string;
187
+ value: string;
188
+ };
189
+ /** Present only when a platform fee applies; itemizes what feeTransfer above actually moves. */
190
+ fee?: TradeFee;
191
+ }
192
+ /** POST /api/v1/launch/self/build response. */
193
+ export type BuildSelfLaunchResult = BuildSelfLaunchSolanaResult | BuildSelfLaunchHoodResult;
194
+ /** POST /api/v1/launch/self/confirm request body. */
195
+ export interface ConfirmSelfLaunchRequest {
196
+ clientLaunchId: string;
197
+ signature: string;
198
+ devBuySignature?: string;
199
+ /** Hood only: the fee-transfer leg's own transaction hash, required whenever the build carried a `feeTransfer` leg. */
200
+ feeTxHash?: string;
201
+ }
202
+ /** POST /api/v1/launch/self/confirm response. Mirrors LaunchResult. */
203
+ export interface ConfirmSelfLaunchResult extends LaunchResult {
204
+ }
205
+ /** POST /api/v1/launch/headless/dry-run response. */
206
+ export interface DryRunResult {
207
+ success: true;
208
+ dryRun: true;
209
+ resolved: {
210
+ chain: Chain;
211
+ quoteAsset: string;
212
+ mode: LaunchTier;
213
+ stakerAllocationBps: number;
214
+ dexVersion: "v3" | "v4" | null;
215
+ visibility: string;
216
+ buyAmount: string;
217
+ };
218
+ checks: {
219
+ image: string;
220
+ exclusiveEligible: boolean;
221
+ };
222
+ matrixVersion: number;
223
+ }
224
+ /** POST /api/v1/launch/headless blocking (or replayed) success response. */
225
+ export interface LaunchResult {
226
+ success: true;
227
+ chain: Chain;
228
+ mint: string;
229
+ pool: string | null;
230
+ signature: string;
231
+ quoteAsset: string;
232
+ mode: LaunchTier;
233
+ stakerAllocationBps: number;
234
+ matrixVersion: number;
235
+ links: {
236
+ candle: string;
237
+ explorer?: string;
238
+ };
239
+ nextBuy: {
240
+ market: string;
241
+ quoteAsset: string;
242
+ marketStateUrl: string;
243
+ };
244
+ devBuy?: {
245
+ signature: string;
246
+ };
247
+ }
248
+ /** The 202 body of an `async: true` launch. */
249
+ export interface AcceptedJob {
250
+ success: true;
251
+ accepted: true;
252
+ clientLaunchId: string;
253
+ status: "submitted";
254
+ jobUrl: string;
255
+ }
256
+ /** One idempotency-ledger attempt, from GET /api/v1/launch/headless/jobs/:clientLaunchId. */
257
+ export interface LaunchJob {
258
+ clientLaunchId: string;
259
+ chain: Chain;
260
+ status: "submitted" | "confirming" | "confirmed" | "failed";
261
+ mint?: string;
262
+ pool?: string;
263
+ signature?: string;
264
+ devBuy?: {
265
+ signature: string;
266
+ };
267
+ errorCode?: string;
268
+ createdAt: number;
269
+ updatedAt: number;
270
+ }
271
+ export interface MigrationStatus {
272
+ status: "not_started" | "in_progress" | "completed" | "delayed";
273
+ migratedAt?: number;
274
+ attempts?: number;
275
+ nextAttemptAt?: number;
276
+ gaveUpAt?: number;
277
+ }
278
+ /** GET /api/v1/markets/:chain/:mint, unwrapped from `{ success, market }`. */
279
+ export interface MarketState {
280
+ chain: Chain;
281
+ mint: string;
282
+ lifecycle: "trading" | "completed" | "migrated" | "recovery";
283
+ buysOpen: boolean;
284
+ sellsOpen: boolean;
285
+ curveAddress: string | null;
286
+ poolAddress: string | null;
287
+ quoteMint: string | null;
288
+ feeBps: number;
289
+ graduationVenue: string;
290
+ tier: string | null;
291
+ crossingModel: "full-fill-surplus" | "capped-refund";
292
+ migration: MigrationStatus;
293
+ }
294
+ /** All amounts are decimal strings in the relevant asset's smallest unit. */
295
+ export interface QuoteBreakdown {
296
+ amountOut: string;
297
+ fee: string;
298
+ minAmountOut: string;
299
+ /** Buys only: whether this buy crosses the graduation threshold. */
300
+ crossesGraduation?: boolean;
301
+ /** Hood crossing buys only: quote refunded past the capped fill. */
302
+ refund?: string;
303
+ /** Hood crossing buys only: quote actually consumed (amountIn minus refund). */
304
+ quoteConsumed?: string;
305
+ }
306
+ /** GET /api/v1/markets/:chain/:mint/quote response. */
307
+ export interface QuoteResult {
308
+ success: true;
309
+ chain: Chain;
310
+ mint: string;
311
+ side: "buy" | "sell";
312
+ amountIn: string;
313
+ crossingModel: "full-fill-surplus" | "capped-refund";
314
+ quote: QuoteBreakdown;
315
+ }
316
+ /** One feed row. The stats columns vary by bucket, hence the open index signature. */
317
+ export interface FeedToken {
318
+ chain: Chain;
319
+ address?: string;
320
+ name?: string;
321
+ symbol?: string;
322
+ image?: string;
323
+ /** True for a Candle-origin launch created via an agent key. */
324
+ isAgent?: boolean;
325
+ [key: string]: unknown;
326
+ }
327
+ /** GET /api/v1/markets/feed response. */
328
+ export interface FeedResult {
329
+ success: true;
330
+ bucket: FeedBucket;
331
+ tokens: FeedToken[];
332
+ }
333
+ /** GET /api/v1/verify/:chain/:mint. Branch on `candleLaunched`; unknown mints are not 404s. */
334
+ export type VerifyResult = {
335
+ success: true;
336
+ candleLaunched: false;
337
+ chain: Chain;
338
+ mint: string;
339
+ } | {
340
+ success: true;
341
+ candleLaunched: true;
342
+ chain: Chain;
343
+ mint: string;
344
+ tier: string | null;
345
+ quoteMint: string | null;
346
+ graduated: boolean;
347
+ pool: string | null;
348
+ createdAt: number;
349
+ creator: string | null;
350
+ viaAgentKey: boolean;
351
+ /** Hood only: curve/factory/configHash/dexVersion, re-verifiable against the registry. */
352
+ provenance?: Record<string, string>;
353
+ /** Solana only: the program and attribution signer indexers re-verify against. */
354
+ attribution?: {
355
+ program: string;
356
+ signer?: string;
357
+ };
358
+ };
359
+ /** GET /api/v1/users/:idOrWallet/agent, unwrapped from `{ success, agent }`. */
360
+ export interface AgentProfile {
361
+ enabled: boolean;
362
+ address: string;
363
+ username: string | null;
364
+ launches: number;
365
+ launchesViaApi: number;
366
+ }
367
+ /**
368
+ * GET /api/v1/agent/tier response, returned whole (same convention as `verify()`: the top-level
369
+ * body IS the useful object, no envelope to unwrap). Dual auth: an agent key works here, and so
370
+ * does a Privy session cookie, which is how the `/dev/agent` dashboard's tier strip fetches this
371
+ * same endpoint directly rather than through this SDK. `feeTotals[].feeRawSum` is a raw-unit
372
+ * BigInt string (lamports, wei, etc.); never coerce it with `Number()`.
373
+ */
374
+ export interface AgentTierInfo {
375
+ success: true;
376
+ /** Display tier: max > pro > believer > free. */
377
+ tier: "free" | "believer" | "pro" | "max";
378
+ /** Live-evaluated tier, independent of the Believer key-issuance label. */
379
+ liveTier: "free" | "pro" | "max";
380
+ stakedCndl: number;
381
+ heldCndl: number;
382
+ thresholds: {
383
+ minStakedCndl: number;
384
+ minHeldCndl: number;
385
+ graceMs: number;
386
+ };
387
+ /** `startedAt` is null unless `active` (see the endpoint's own doc for why). */
388
+ grace: {
389
+ active: boolean;
390
+ startedAt: number | null;
391
+ };
392
+ maxTierExpiresAt: number | null;
393
+ /** The account's resolved platform fee, in bps, on API-built value-moving transactions. */
394
+ feeBps: number;
395
+ feeTotals: Array<{
396
+ chain: Chain;
397
+ quoteAsset: string;
398
+ feeRawSum: string;
399
+ count: number;
400
+ }>;
401
+ }
402
+ /**
403
+ * One per-transaction spend cap, mirroring `SpendLimit` in `apps/api/src/lib/agent-policy.ts`
404
+ * and what `PUT /api/v1/agent/keys/{prefix}/limits` reads and writes (per-key only, 2026-08-23:
405
+ * the account-wide limits are retired). `asset` is `"sol" | "usdc" | "cndl"` on Solana or `"eth" | "usdg"` on Hood/EVM; `maxPerTxRaw` is
406
+ * a positive base-10 integer string of the asset's raw base-unit amount (same raw-string
407
+ * convention as `CurveTerms.thresholdRaw` above). Defined locally, not imported from
408
+ * `@candle/shared`, per this file's near-zero-dependency design rule.
409
+ */
410
+ export interface SpendLimit {
411
+ asset: string;
412
+ maxPerTxRaw: string;
413
+ }
414
+ /**
415
+ * `GET /api/v1/agent/keys/self/limits` response (roadmap C, Task 5): the CALLING key's own
416
+ * spend-limit configuration, exactly as the server-side gate resolves it
417
+ * (`apps/api/src/lib/spend-limit-gate.ts`'s `checkSpendAgainstLimits`). Per-key only
418
+ * (2026-08-23): `keyLimits` is the whole answer -- an asset the key does not mention is
419
+ * uncapped, with no account fallback. Resolve a cap as
420
+ * `keyLimits?.find((l) => l.asset === asset)?.maxPerTxRaw` (undefined means unlimited).
421
+ */
422
+ export interface SpendLimitsResult {
423
+ success: true;
424
+ keyLimits: SpendLimit[] | null;
425
+ }
426
+ /** POST /api/v1/agent/wallets/import/submit response: the linked-wallet row summary. */
427
+ export interface ImportWalletResult {
428
+ success: true;
429
+ id: string;
430
+ address: string;
431
+ chain: WalletChain;
432
+ privyWalletId: string;
433
+ }
434
+ /**
435
+ * One row of `GET /api/v1/agent/wallets`'s `page` array: the linkedWallets Convex row minus
436
+ * internal bookkeeping fields (userAddress/addressLower/privyWalletId/verifiedAt) a caller has
437
+ * no use for. `privyPolicyId` present is what makes a row spend-capable (the import flow); its
438
+ * absence means attribution-only (link-existing). `revokedAt` set means the row is a
439
+ * tombstone -- `listWallets()` excludes these by default; pass `includeRevoked: true` to see
440
+ * them.
441
+ */
442
+ export interface LinkedWalletRow {
443
+ _id: string;
444
+ chain: WalletChain;
445
+ address: string;
446
+ label?: string;
447
+ privyPolicyId?: string;
448
+ signerQuorumId?: string;
449
+ revokedAt?: number;
450
+ addedVia: "agent" | "session";
451
+ }
452
+ /** GET /api/v1/agent/wallets response: one page of the account's linked wallets. */
453
+ export interface ListWalletsResult {
454
+ success: true;
455
+ page: LinkedWalletRow[];
456
+ isDone: boolean;
457
+ continueCursor: string | null;
458
+ }
459
+ export type TradeSide = "buy" | "sell";
460
+ /** Who pays for a trade: the account's own delegated wallet ("main"), or an imported linked wallet. */
461
+ export type TradePayer = {
462
+ type: "main";
463
+ } | {
464
+ type: "linked";
465
+ linkedWalletId: string;
466
+ };
467
+ /** POST /api/v1/trade/agent/build request body. */
468
+ export interface BuildTradeRequest {
469
+ /** Idempotency key, unique per account; shared with the matching confirmTrade() call. */
470
+ clientTradeId: string;
471
+ mint: string;
472
+ side: TradeSide;
473
+ /** Buy: quote-asset raw units to spend. Sell: base-token raw units to sell. */
474
+ amountRaw: string;
475
+ payer: TradePayer;
476
+ /** Bps, 0-10000. Server defaults to 100 (1%) when omitted. */
477
+ maxSlippageBps?: number;
478
+ /**
479
+ * Which asset to quote this trade in. Applies only when trading a non-Candle-launched token
480
+ * (Pro/Max); ignored for a Candle token, whose curve/pool quote asset is fixed. The server
481
+ * defaults it when omitted.
482
+ */
483
+ quoteAsset?: "sol" | "usdc" | "cndl";
484
+ }
485
+ /** The platform fee actually itemized on this trade. `treasury` is null only when the fee is disabled server-side (unset AGENT_FEE_TREASURY_*). */
486
+ export interface TradeFee {
487
+ bps: number;
488
+ feeRaw: string;
489
+ treasury: string | null;
490
+ }
491
+ /** Solana "built" artifacts: one unsigned transaction, the fee (if any) already embedded inside it. */
492
+ export interface SolanaTradeArtifacts {
493
+ venue: "curve" | "jupiter";
494
+ transactionBase64: string;
495
+ quoteAsset: string;
496
+ quoteMint: string;
497
+ quoteDecimals: number;
498
+ }
499
+ /**
500
+ * Hood "built" artifacts: up to three calldata legs. Send order matters and is fixed: `approval`
501
+ * (present only when the payer's existing ERC-20 allowance is insufficient), then `trade`, then
502
+ * `feeTransfer` (present only when a fee applies). Hood cannot batch calls the way one Solana
503
+ * transaction can carry multiple instructions, so each leg is its own transaction.
504
+ */
505
+ export interface HoodTradeArtifacts {
506
+ venue: "curve";
507
+ trade: {
508
+ to: string;
509
+ data: string;
510
+ value: string;
511
+ };
512
+ approval?: {
513
+ to: string;
514
+ data: string;
515
+ };
516
+ feeTransfer?: {
517
+ to: string;
518
+ data: string;
519
+ value: string;
520
+ };
521
+ quoteAsset: string;
522
+ quoteDecimals: number;
523
+ }
524
+ /**
525
+ * POST /api/v1/trade/agent/build response for a LINKED payer: an unsigned artifact for the agent
526
+ * to sign and broadcast itself, then report to confirmTrade(). `chain` discriminates `artifacts`.
527
+ */
528
+ export type BuildTradeBuiltResult = {
529
+ success: true;
530
+ status: "built";
531
+ clientTradeId: string;
532
+ chain: "solana";
533
+ artifacts: SolanaTradeArtifacts;
534
+ fee: TradeFee;
535
+ expectedOutRaw: string;
536
+ minOutRaw: string;
537
+ expiresAt: number;
538
+ } | {
539
+ success: true;
540
+ status: "built";
541
+ clientTradeId: string;
542
+ chain: "hood";
543
+ artifacts: HoodTradeArtifacts;
544
+ fee: TradeFee;
545
+ expectedOutRaw: string;
546
+ minOutRaw: string;
547
+ expiresAt: number;
548
+ /** The linked payer's own checksummed EVM address: the transaction `from`, and the nonce query subject. */
549
+ walletAddress: string;
550
+ };
551
+ /**
552
+ * A trade that has already run: a MAIN payer's inline execution (buildTrade()'s own response, no
553
+ * confirmTrade() call needed at all) or a linked payer's verified confirmTrade() result (including
554
+ * an idempotent replay of a trade already confirmed). `signature` is a Solana transaction
555
+ * signature or a Hood transaction hash, matching `chain`.
556
+ */
557
+ export interface ExecutedTradeResult {
558
+ success: true;
559
+ status: "executed";
560
+ clientTradeId: string;
561
+ chain: Chain;
562
+ signature: string;
563
+ /**
564
+ * The fee-bearing signature, when tracked separately from `signature`. Solana: absent for a
565
+ * main payer (the fee rides inside the same transaction `signature` already covers); always
566
+ * equal to `signature` for a confirmed linked payer (confirmTrade() claims the broadcast
567
+ * signature itself as its anti-reuse guard, fee or not). Hood: the fee transfer's own tx hash,
568
+ * present only when a fee actually landed.
569
+ */
570
+ feeSignature?: string;
571
+ fee: TradeFee;
572
+ amounts: {
573
+ amountRaw: string;
574
+ expectedOutRaw: string;
575
+ minOutRaw: string;
576
+ quoteAsset: string;
577
+ };
578
+ }
579
+ /** POST /api/v1/trade/agent/build response: "built" for a linked payer, "executed" for a main payer (or an idempotent replay of an already-confirmed trade under the same clientTradeId). */
580
+ export type BuildTradeResult = BuildTradeBuiltResult | ExecutedTradeResult;
581
+ /**
582
+ * POST /api/v1/trade/agent/confirm request body. Solana reports its transaction signature; Hood
583
+ * reports its trade transaction hash and, ONLY when the matching build's `fee.feeRaw` was
584
+ * non-zero, the fee transfer's own transaction hash (omitting it there is refused
585
+ * `FEE_LEG_MISSING`).
586
+ */
587
+ export type ConfirmTradeRequest = {
588
+ clientTradeId: string;
589
+ signature: string;
590
+ } | {
591
+ clientTradeId: string;
592
+ tradeTxHash: string;
593
+ feeTxHash?: string;
594
+ };
595
+ /** POST /api/v1/trade/agent/confirm response. Always "executed": confirm only ever verifies and records a trade that already happened on-chain. */
596
+ export type ConfirmTradeResult = ExecutedTradeResult;
597
+ /**
598
+ * POST /api/v1/trade/agent/submit request body. `signedTransactions` is the ordered signed legs:
599
+ * one for Solana; one to three for Hood in the fixed approval, trade, feeTransfer order (omitting
600
+ * a leg that was not built). The server broadcasts them itself and confirms inline, so there is no
601
+ * separate confirmTrade() call after this one.
602
+ */
603
+ export interface SubmitTradeRequest {
604
+ clientTradeId: string;
605
+ signedTransactions: string[];
606
+ }
607
+ /**
608
+ * The exact fields Privy's `eth_signTransaction` RPC expects under `params.transaction`. The
609
+ * caller assembles these (nonce via eth_getTransactionCount, fee fields via
610
+ * eth_maxPriorityFeePerGas/the latest block's base fee, gas_limit via eth_estimateGas, chain_id
611
+ * via eth_chainId) -- signLinkedTransaction() only forwards them, it does not fetch or compute
612
+ * any of them itself.
613
+ */
614
+ export interface EvmSignTransactionParams {
615
+ from: string;
616
+ to: string;
617
+ nonce: number;
618
+ chain_id: number;
619
+ data: string;
620
+ value: string;
621
+ type: number;
622
+ gas_limit: string;
623
+ max_fee_per_gas: string;
624
+ max_priority_fee_per_gas: string;
625
+ }
626
+ /** `swapFromLinked()` request: SOL on the linked wallet into ETH/USDG on Hood. */
627
+ export interface LinkedSwapRequest {
628
+ from: "SOL";
629
+ to: "ETH" | "USDG";
630
+ /** Lamports, as a decimal string. */
631
+ amountRaw: string;
632
+ /** The linked Solana wallet funding the swap. */
633
+ payer: {
634
+ linkedWalletId: string;
635
+ privyWalletId: string;
636
+ };
637
+ /** The account's OWN linked EVM wallet to receive the output; omitted = the owner's embedded Hood wallet. */
638
+ toWalletId?: string;
639
+ maxSlippageBps?: number;
640
+ }
641
+ /** `swapFromLinked()` result. `hashes` is the Solana deposit; poll `statusChecks` for the fill. */
642
+ export interface LinkedSwapResult {
643
+ hashes: string[];
644
+ expectedOutRaw: string;
645
+ outDecimals: number;
646
+ statusChecks: string[];
647
+ recipient: string;
648
+ }
649
+ export interface SignLinkedTransactionParams {
650
+ /** The linked wallet's row id: keys the secretStore lookup AND is the relay's :id path segment. */
651
+ linkedWalletId: string;
652
+ /** The SAME wallet's Privy wallet id (from importWallet()'s result), the authorization signature's URL target. */
653
+ privyWalletId: string;
654
+ chain: WalletChain;
655
+ /** Solana only: the unsigned transaction to sign, base64-encoded. */
656
+ unsignedTransactionBase64?: string;
657
+ /** EVM only: the fully-assembled transaction to sign. */
658
+ evmTxParams?: EvmSignTransactionParams;
659
+ }
660
+ export interface SignLinkedTransactionResult {
661
+ /** Base64-encoded (Solana) or RLP-encoded (EVM), per `encoding`. */
662
+ signedTransaction: string;
663
+ encoding: string;
664
+ }
665
+ /**
666
+ * The base assets `swap()` converts between. Inlined rather than imported from `@candle/shared`'s
667
+ * `BaseAssetKey`, for the same reason `packages/mcp` inlines its curve constants: this SDK is
668
+ * published standalone and must not depend on a monorepo-internal package.
669
+ *
670
+ * SOL, USDC and CNDL are Solana-side; ETH and USDG are Hood-side. A pair that spans the two sides
671
+ * is a cross-chain swap and settles as more than one transaction.
672
+ */
673
+ export type BaseAssetKey = "SOL" | "USDC" | "CNDL" | "ETH" | "USDG";
674
+ /** swap()'s request body: `POST /api/v1/agent/swap`. */
675
+ export interface SwapRequest {
676
+ from: BaseAssetKey;
677
+ /** Must differ from `from`; the API rejects a same-asset pair. */
678
+ to: BaseAssetKey;
679
+ /** Raw base units of `from` to spend, as a positive integer string. */
680
+ amountRaw: string;
681
+ /** Bps, 0-10000. Server defaults to 100 (1%) when omitted. */
682
+ maxSlippageBps?: number;
683
+ /** Optional in-flight dedup key. See swap()'s jsdoc for what it does and does not guarantee. */
684
+ clientSwapId?: string;
685
+ }
686
+ /** swap()'s unwrapped `payload`. */
687
+ export interface SwapResult {
688
+ /** One hash per executed leg, in execution order. A cross-chain swap reports more than one. */
689
+ hashes: string[];
690
+ expectedOutRaw: string;
691
+ outDecimals: number;
692
+ venueCostUsd?: number;
693
+ /** URLs to poll a cross-chain fill's status, so a caller need not re-derive them from a quote. */
694
+ statusChecks: string[];
695
+ }
696
+ /** trade()'s one-call request: mirrors BuildTradeRequest minus clientTradeId/payer, plus who signs. */
697
+ export interface TradeRequest {
698
+ mint: string;
699
+ side: TradeSide;
700
+ /** Buy: quote-asset raw units to spend. Sell: base-token raw units to sell. */
701
+ amountRaw: string;
702
+ /** "main": the account's own delegated wallet, executed inline. A linked wallet: signed and broadcast by the caller via the sign relay. */
703
+ from: "main" | {
704
+ linkedWalletId: string;
705
+ privyWalletId: string;
706
+ };
707
+ /** Bps, 0-10000. Server defaults to 100 (1%) when omitted. */
708
+ maxSlippageBps?: number;
709
+ /**
710
+ * Which asset to quote this trade in. Applies only when trading a non-Candle-launched token
711
+ * (Pro/Max); ignored for a Candle token, whose curve/pool quote asset is fixed. The server
712
+ * defaults it when omitted.
713
+ */
714
+ quoteAsset?: "sol" | "usdc" | "cndl";
715
+ /** Idempotency key shared by the build and confirm calls; generated ("sdk-" + UUID) when omitted. */
716
+ clientTradeId?: string;
717
+ }
718
+ /** selfLaunch()'s one-call request: BuildSelfLaunchRequest plus the linked wallet's Privy wallet id. */
719
+ export type SelfLaunchRequest = BuildSelfLaunchRequest & {
720
+ /** The linked wallet's Privy wallet id (from importWallet()'s result). */
721
+ privyWalletId: string;
722
+ };
723
+ /** Who pays for one leg of an atomic bundle (the launch, or one first buy): the account's own delegated wallet ("main"), or an imported linked wallet. Mirrors TradePayer. */
724
+ export type AtomicLaunchPayer = {
725
+ type: "main";
726
+ } | {
727
+ type: "linked";
728
+ linkedWalletId: string;
729
+ };
730
+ /** One first-buy leg of an atomic launch request. */
731
+ export interface AtomicFirstBuyRequest {
732
+ payer: AtomicLaunchPayer;
733
+ /** Quote-asset raw units this leg spends. */
734
+ amountRaw: string;
735
+ }
736
+ /**
737
+ * POST /api/v1/launch/atomic/build request body: LaunchRequest without `buyAmount` (atomic
738
+ * launches never bundle a dev buy into the launch transaction itself -- give the creator a first
739
+ * buy via `firstBuys` instead, so it lands as bundle leg 1 against a still-virgin curve) plus the
740
+ * creator's own payer and 1-4 first-buy legs.
741
+ */
742
+ export interface BuildAtomicLaunchRequest extends Omit<LaunchRequest, "buyAmount"> {
743
+ payer: AtomicLaunchPayer;
744
+ /** 1 to 4 buy legs, sharing the launch transaction's own recent blockhash and landing atomically with it (Jito's 5-transaction bundle cap: 1 launch + up to 4 buys). */
745
+ firstBuys: AtomicFirstBuyRequest[];
746
+ }
747
+ export type AtomicBundleLegRole = "launch" | "buy";
748
+ export type AtomicBundleLegSigner = "server" | "client";
749
+ /**
750
+ * One leg of a built atomic bundle, in bundle order (index 0 is always the launch; 1..N are the
751
+ * first buys, in request order). `unsignedTxBase64` is present only for a "client" signer leg (a
752
+ * "server" leg -- a "main" payer -- is signed by Candle itself at submit time and never leaves the
753
+ * server). `expectedFill` is present only on a "buy" leg the pricing ladder could compute: it is
754
+ * an ADVISORY fill, not a slippage guarantee -- every buy leg's own on-chain `minAmountOut` is
755
+ * always "0" inside the bundle (see "Atomic launch with first buys" in docs/headless-launch.md for
756
+ * why that is safe here and what `expectedFill` is for instead).
757
+ */
758
+ export interface AtomicBundleResponseLeg {
759
+ index: number;
760
+ role: AtomicBundleLegRole;
761
+ signer: AtomicBundleLegSigner;
762
+ unsignedTxBase64?: string;
763
+ expectedFill?: {
764
+ amountOutRaw: string;
765
+ };
766
+ }
767
+ /** POST /api/v1/launch/atomic/build response. */
768
+ export interface BuildAtomicLaunchResult {
769
+ bundleId: string;
770
+ legs: AtomicBundleResponseLeg[];
771
+ expiresAt: number;
772
+ }
773
+ /**
774
+ * POST /api/v1/launch/atomic/submit request body: EXACTLY the client-signer legs
775
+ * `BuildAtomicLaunchResult.legs` named (`signer: "client"`), in leg order -- omit every "server"
776
+ * leg entirely, never pad the array.
777
+ */
778
+ export interface SubmitAtomicLaunchRequest {
779
+ bundleId: string;
780
+ signedTxsBase64: string[];
781
+ }
782
+ /**
783
+ * POST /api/v1/launch/atomic/submit response. `"landed"` is the 200 body; `"failed"`/`"timeout"`
784
+ * are the 502 body -- all three are NORMAL, expected outcomes of submitting a Jito bundle that
785
+ * this route documents as part of its own response surface, so `submitAtomicLaunch()`/
786
+ * `launchAtomic()` return them here rather than throwing `CandleApiError` (every OTHER non-2xx
787
+ * status still throws, same as every other method). `retryable` is always `false` for `"failed"`;
788
+ * for `"timeout"` it is `true` only when the bundle's shared blockhash was proven to have expired
789
+ * with no confirmation -- `false` means the resolution window simply ran out with no definitive
790
+ * answer and the bundle MIGHT STILL LAND, so the caller should wait rather than immediately
791
+ * rebuild under a fresh `clientLaunchId`. In every case, `bundleId` itself is dead once this
792
+ * response arrives: it is consumed on the first `/submit` call regardless of outcome, so recovery
793
+ * is always a fresh `buildAtomicLaunch()`/`launchAtomic()` call, never a retried `submit` with the
794
+ * same id. See "Atomic launch with first buys" in docs/headless-launch.md for the full model.
795
+ */
796
+ export type SubmitAtomicLaunchResult = {
797
+ status: "landed";
798
+ bundleId: string;
799
+ mint: string;
800
+ signatures: string[];
801
+ } | {
802
+ status: "failed";
803
+ bundleId: string;
804
+ retryable: false;
805
+ } | {
806
+ status: "timeout";
807
+ bundleId: string;
808
+ retryable: boolean;
809
+ };
810
+ /** Who pays for one leg of launchAtomic()'s one-call request. Mirrors AtomicLaunchPayer, but a "linked" payer also carries the privyWalletId signLinkedTransaction() needs to sign that leg -- mirrors TradeRequest's `from` field. */
811
+ export type LaunchAtomicPayer = {
812
+ type: "main";
813
+ } | {
814
+ type: "linked";
815
+ linkedWalletId: string;
816
+ privyWalletId: string;
817
+ };
818
+ export interface LaunchAtomicFirstBuyRequest {
819
+ payer: LaunchAtomicPayer;
820
+ amountRaw: string;
821
+ }
822
+ /** launchAtomic()'s one-call request: BuildAtomicLaunchRequest's fields, but `payer`/`firstBuys[].payer` use LaunchAtomicPayer (carrying privyWalletId for any linked leg). */
823
+ export type LaunchAtomicRequest = Omit<BuildAtomicLaunchRequest, "payer" | "firstBuys"> & {
824
+ payer: LaunchAtomicPayer;
825
+ firstBuys: LaunchAtomicFirstBuyRequest[];
826
+ };
827
+ export declare class CandleClient {
828
+ private readonly apiUrl;
829
+ private readonly apiKey?;
830
+ private readonly fetchImpl;
831
+ private readonly maxRetries;
832
+ private readonly privyAppId?;
833
+ private readonly secretStore?;
834
+ private readonly solanaRpcUrl?;
835
+ private readonly evmRpcUrl?;
836
+ constructor(opts: CandleClientOptions);
837
+ getQuotePairs(chain?: Chain): Promise<QuotePairsPayload>;
838
+ getPresets(): Promise<PresetsPayload>;
839
+ /**
840
+ * LOCAL preset expansion, no fetch: merges a preset from an already-fetched `getPresets()`
841
+ * payload with the caller's overrides into a launch body. Overrides win. Throws a plain
842
+ * `Error` on an unknown preset name. The result still needs `name`/`symbol`/`imageUrl`
843
+ * (typically supplied via `overrides`); the server remains the authoritative validator.
844
+ */
845
+ expandPreset(presets: PresetsPayload, name: string, overrides?: Partial<LaunchRequest>): LaunchRequest;
846
+ getMarket(chain: Chain, mint: string): Promise<MarketState>;
847
+ getQuote(chain: Chain, mint: string, q: {
848
+ side: "buy" | "sell";
849
+ amountIn: string;
850
+ slippageBps?: number;
851
+ }): Promise<QuoteResult>;
852
+ getFeed(bucket: FeedBucket, chain?: Chain): Promise<FeedResult>;
853
+ verify(chain: Chain, mint: string): Promise<VerifyResult>;
854
+ getAgentProfile(idOrWallet: string): Promise<AgentProfile>;
855
+ /**
856
+ * The calling account's tier snapshot: display/live tier, staked/held CNDL, qualification
857
+ * thresholds, grace window state, resolved fee bps, and lifetime fee totals by chain/asset.
858
+ * Not a "keyed endpoint" in the `requireKey()` sense below: the server accepts either an agent
859
+ * key or a Privy session, so this method sends whatever `x-api-key` the client was constructed
860
+ * with (possibly none) and lets the server's own auth middleware decide.
861
+ */
862
+ getAgentTier(): Promise<AgentTierInfo>;
863
+ dryRunLaunch(req: LaunchRequest): Promise<DryRunResult>;
864
+ /**
865
+ * Blocking launch with idempotent retries. Generates `clientLaunchId` when absent and
866
+ * re-sends the SAME id on retryable failures; see the module doc for the exact policy.
867
+ */
868
+ launch(req: LaunchRequest): Promise<LaunchResult>;
869
+ /**
870
+ * Fire-and-poll launch: sends `async: true`, returns the 202 body. Single attempt (poll
871
+ * `waitForLaunch` instead of retrying the POST). Generates `clientLaunchId` like `launch()`
872
+ * so the returned body always carries the id to poll.
873
+ */
874
+ launchAsync(req: LaunchRequest): Promise<AcceptedJob>;
875
+ getLaunchJob(clientLaunchId: string): Promise<LaunchJob>;
876
+ /**
877
+ * Polls the jobs endpoint until the attempt is terminal (`confirmed` or `failed`; the caller
878
+ * branches on `status`). Throws a plain `Error` once `timeoutMs` (default 3 minutes) passes
879
+ * without a terminal status; the launch itself keeps running server-side, so on timeout poll
880
+ * again or verify on-chain before starting over under a NEW clientLaunchId.
881
+ */
882
+ waitForLaunch(clientLaunchId: string, opts?: {
883
+ timeoutMs?: number;
884
+ pollMs?: number;
885
+ }): Promise<LaunchJob>;
886
+ reportActivity(chain: Chain, signature: string): Promise<unknown>;
887
+ /**
888
+ * Uploads raw image bytes to POST /api/v1/uploads/agent-image (ships in Phase 2 wave 3) and
889
+ * returns the hosted URL, immediately usable as a launch body's `imageUrl`.
890
+ */
891
+ uploadImage(bytes: Uint8Array, contentType: string): Promise<{
892
+ imageUrl: string;
893
+ }>;
894
+ /**
895
+ * Lists this account's linked wallets (GET /api/v1/agent/wallets), active rows first.
896
+ * Active-only by default (Agent Pilot Phase 1, Task 2): pass `includeRevoked: true` to also
897
+ * see revoked (tombstoned) rows. The query string stays clean when `includeRevoked` is
898
+ * omitted/false -- `?includeRevoked=true` is appended only when the caller asks for it.
899
+ */
900
+ listWallets(opts?: {
901
+ includeRevoked?: boolean;
902
+ }): Promise<ListWalletsResult>;
903
+ /**
904
+ * Reads this key's own effective spend limits (roadmap C, Task 5), so an agent can self-throttle
905
+ * before a trade or launch ever hits `SPEND_LIMIT_EXCEEDED`. Read-only: raising a cap always
906
+ * requires a Privy session (the portal), never this SDK -- see `SpendLimitsResult` for how to
907
+ * resolve `keyLimits` into the cap that applies to a given trade.
908
+ */
909
+ getSpendLimits(): Promise<SpendLimitsResult>;
910
+ /**
911
+ * One-shot base-asset conversion through the account's own embedded wallets: quote and execute
912
+ * in a single call. A pair that spans the Solana and Hood sides routes through the bridge, which
913
+ * makes this the only agent-facing way to move value between them, and therefore how a Hood
914
+ * wallet gets funded before a Hood launch or trade.
915
+ *
916
+ * MOVES REAL FUNDS on every call. Test-environment keys are refused outright with
917
+ * `TEST_ENVIRONMENT_FORBIDDEN`: no leg of this rail has a non-production equivalent, since every
918
+ * one settles on a live venue.
919
+ *
920
+ * `clientSwapId` is a coalescing key, NOT a durable idempotency ledger. A duplicate arriving
921
+ * while the first request is still in flight is handed that same result; one arriving after it
922
+ * settled executes a second swap. Omitting it never coalesces at all. This method therefore
923
+ * never retries on its own, unlike `launch()`: a retried funding call that already landed would
924
+ * silently move the funds twice.
925
+ */
926
+ swap(req: SwapRequest): Promise<SwapResult>;
927
+ /**
928
+ * Imports an existing wallet via Candle's ciphertext-only flow (PR3): calls
929
+ * `/agent/wallets/import/init` for Privy's HPKE receiver public key, encrypts `privateKey`
930
+ * locally with `encryptWalletKeyForImport` (wallet-import.ts) -- which decodes it to raw bytes
931
+ * per `chain` (hex for "evm", base58 for "solana") before sealing, matching Privy's own import
932
+ * reference -- then posts the ciphertext to `/agent/wallets/import/submit`. `privateKey` exists
933
+ * in memory only inside this function: it is read here, consumed by the local encrypt call, and
934
+ * never appears in a request body; only `ciphertext`/`encapsulatedKey` are sent over the wire.
935
+ *
936
+ * `signerPublicKey` is the base64 DER public half of a P-256 keypair (see
937
+ * `generateSignerKeypair()` in wallet-import.ts, or any equivalent key the caller manages) that
938
+ * Privy registers as the wallet's signer; its private half is never sent here or anywhere else
939
+ * in this call.
940
+ */
941
+ importWallet(params: {
942
+ chain: WalletChain;
943
+ address: string;
944
+ privateKey: string;
945
+ signerPublicKey: string;
946
+ label?: string;
947
+ }): Promise<ImportWalletResult>;
948
+ /**
949
+ * Self-signed launch build: returns an unsigned transaction for the agent to sign and
950
+ * broadcast itself. Candle never signs and never holds the signer key. Signing a linked wallet
951
+ * still needs an app-authenticated call to Privy that an external agent cannot make alone:
952
+ * Privy requires both the agent's own P-256 authorization signature over the exact transaction
953
+ * AND app-level auth that only Candle's server secret satisfies. That call goes through the
954
+ * Candle sign relay (POST /api/v1/agent/wallets/:id/sign) -- the agent authorizes locally with
955
+ * its own key, and Candle authenticates the app and forwards the already-authorized request,
956
+ * unable to substitute a different wallet or transaction. selfLaunch() below runs this whole
957
+ * round trip (build -> sign -> broadcast -> confirm) in one call for both Solana and Hood; call
958
+ * this method directly only to drive the steps yourself. See "Self-signed launches" in
959
+ * docs/headless-launch.md.
960
+ */
961
+ buildSelfLaunch(req: BuildSelfLaunchRequest): Promise<BuildSelfLaunchResult>;
962
+ /**
963
+ * Self-signed launch confirm: verifies the agent's own broadcast on-chain, then records the
964
+ * launch identically to the headless path. Signature is the transaction hash from the agent's
965
+ * own broadcast. devBuySignature (Hood only) is the optional follow-up dev-buy transaction. See
966
+ * selfLaunch() below for the one-call version of this whole flow (Solana).
967
+ */
968
+ confirmSelfLaunch(req: ConfirmSelfLaunchRequest): Promise<ConfirmSelfLaunchResult>;
969
+ /**
970
+ * Build (or, for a main payer, build-and-execute) one buy/sell trade against an existing
971
+ * market. See docs/agent-trading.md for the full fee model, spend gate, and error reference.
972
+ *
973
+ * - **Main payer**: the trade executes immediately, INLINE, through the account's own
974
+ * delegated wallet (the same server-signs-via-Privy model `launch()` uses) -- the response is
975
+ * `status: "executed"` outright, and there is nothing left to sign or confirm.
976
+ * - **Linked payer**: the response is `status: "built"`, an unsigned artifact for the agent to
977
+ * sign ITSELF -- Candle never signs a linked payer's trade and never holds its signer key.
978
+ * Signing goes through the Candle sign relay (the same one self-signed launches depend on; see
979
+ * buildSelfLaunch()'s jsdoc): Solana signs `artifacts.transactionBase64` via the linked
980
+ * wallet's own Privy signer quorum. Hood signs `artifacts.approval` (if present), then
981
+ * `artifacts.trade`, then `artifacts.feeTransfer` (if present) -- in that exact order. From
982
+ * there, trade() below's default differs by chain: Solana hands the already-signed bytes to
983
+ * `submit({ clientTradeId, signedTransactions })`, which broadcasts and confirms them
984
+ * server-side in one call; Hood stays on the client-broadcast sequence -- broadcast each signed
985
+ * leg with `broadcastSignedTransaction`, awaiting each leg's own receipt before assembling the
986
+ * next (its `trade` leg's gas estimate depends on the `approval` leg already being mined), then
987
+ * `confirmTrade({ clientTradeId, tradeTxHash, feeTxHash })`, where `feeTxHash` is REQUIRED
988
+ * whenever `artifacts.feeTransfer` was present (a confirm that omits it is refused
989
+ * `FEE_LEG_MISSING`). Solana's lower-level opt-in path
990
+ * (`broadcastSignedTransaction`/`confirmTrade({ clientTradeId, signature })` instead of
991
+ * `submit()`) and Hood's `submit()` opt-in (skipping the per-leg broadcast/confirm) both stay
992
+ * available too, for callers who want the other of the two.
993
+ *
994
+ * Requires an agent key with the `swap:write` scope -- opt-in, omitted by default when a key is
995
+ * issued; pass `scopes: [..., "swap:write"]` to `POST /api/v1/agent/keys` to grant it.
996
+ */
997
+ buildTrade(req: BuildTradeRequest): Promise<BuildTradeResult>;
998
+ /**
999
+ * Confirm a LINKED payer's own broadcast trade: Candle verifies it landed on-chain, that it was
1000
+ * actually signed/sent by the declared linked wallet, that it moved this trade's own mint in
1001
+ * the right direction, and -- when the build carried a fee -- that the fee transfer landed too,
1002
+ * BEFORE recording anything. A main payer's trade never reaches this call: it already completed
1003
+ * inline at `buildTrade()`, and confirming it is rejected `VALIDATION_FAILED`. Idempotent:
1004
+ * confirming an already-confirmed `clientTradeId` replays the stored result without
1005
+ * re-verifying anything on-chain.
1006
+ */
1007
+ confirmTrade(req: ConfirmTradeRequest): Promise<ConfirmTradeResult>;
1008
+ /**
1009
+ * Server-side alternative to the sign-then-confirmTrade() round trip above: hand the server the
1010
+ * already-signed legs (produced by signLinkedTransaction(), still unbroadcast) and it broadcasts
1011
+ * them itself, then confirms inline. One call instead of two; no client-side broadcast, no
1012
+ * separate confirmTrade() call.
1013
+ */
1014
+ submit(req: SubmitTradeRequest): Promise<ExecutedTradeResult>;
1015
+ /**
1016
+ * Signs an unsigned transaction (or, for EVM, an already-assembled transaction) with a linked
1017
+ * wallet's OWN Privy signer, without Candle ever holding the key: loads the agent's P-256
1018
+ * signer PEM from `secretStore` (keyed by `linkedWalletId`), computes the Privy authorization
1019
+ * signature over the exact RPC body locally (`buildPrivyAuthorizationSignature`), then calls
1020
+ * the Candle sign relay (`POST /api/v1/agent/wallets/:id/sign`), which forwards the
1021
+ * already-authorized request to Privy unchanged. Requires `privyAppId` in
1022
+ * `CandleClientOptions` -- the SAME Privy app id the relay's server authenticates under, since
1023
+ * the authorization signature covers that app id -- and an `apiKey` with the `swap:write`
1024
+ * scope. Throws a clear error naming the missing option/key when `privyAppId`, `secretStore`,
1025
+ * or a stored PEM for `linkedWalletId` is missing.
1026
+ */
1027
+ signLinkedTransaction(params: SignLinkedTransactionParams): Promise<SignLinkedTransactionResult>;
1028
+ /**
1029
+ * Broadcasts an already-signed transaction (from `signLinkedTransaction()`) via minimal
1030
+ * fetch-based JSON-RPC -- no web3.js/viem/ethers. Solana: `sendTransaction` against
1031
+ * `solanaRpcUrl`, returns the transaction signature. EVM: `eth_sendRawTransaction` against
1032
+ * `evmRpcUrl`, returns the transaction hash. Throws a clear error naming the missing option
1033
+ * when the relevant RPC URL is not configured. When the RPC itself rejects the broadcast (a
1034
+ * JSON-RPC `error` envelope), the underlying `JsonRpcError` propagates unchanged -- it is not
1035
+ * caught and flattened into a plain message -- so callers can inspect `.code` and `.data`
1036
+ * (`.err`/`.logs` on Solana) for the real on-chain cause, e.g. a Solana `-32002` whose
1037
+ * `data.err` is `"BlockhashNotFound"`.
1038
+ */
1039
+ broadcastSignedTransaction(chain: WalletChain, signedTransaction: string, encoding: string): Promise<string>;
1040
+ /**
1041
+ * Cross-chain base-asset swap FROM A LINKED WALLET: SOL on the linked Solana wallet into
1042
+ * ETH/USDG on Hood, without Candle ever holding a key. Three steps in one call, mirroring
1043
+ * `trade()`'s linked flow: `POST /api/v1/agent/swap/build` (the server quotes the bridge and
1044
+ * compiles the unsigned deposit transaction with the linked wallet as payer, stamping its
1045
+ * bytes for the sign relay), `signLinkedTransaction()` per returned transaction, and
1046
+ * `POST /api/v1/agent/swap/submit` (server-side broadcast -- no solanaRpcUrl needed).
1047
+ *
1048
+ * The output lands on the account's OWN wallets only: pass `toWalletId` (a linked EVM wallet
1049
+ * of the same account) or omit it for the owner's embedded Hood wallet. The bridge fill is
1050
+ * asynchronous -- poll the returned `statusChecks` URLs to observe it complete; `hashes` only
1051
+ * proves the Solana deposit landed. Sign promptly after building: the deposit transaction
1052
+ * carries a recent blockhash and expires in about a minute.
1053
+ *
1054
+ * v1 supports `from: "SOL"` only. Same-chain conversions (SOL/USDC/CNDL) are `trade()` with a
1055
+ * base-asset mint (free, every tier); USDC/CNDL origins convert to SOL that way first.
1056
+ */
1057
+ swapFromLinked(req: LinkedSwapRequest): Promise<LinkedSwapResult>;
1058
+ /**
1059
+ * One-call trade. A MAIN payer delegates unchanged to the existing inline
1060
+ * `buildTrade({ payer: { type: "main" } })` path -- it never touches the sign relay or a
1061
+ * secretStore, and returns that call's own `status: "executed"` result directly.
1062
+ *
1063
+ * A LINKED payer's default differs by chain -- Solana defaults to server-side submit, Hood
1064
+ * stays on client-side broadcast, because Hood's per-leg gas estimation genuinely needs an
1065
+ * earlier leg mined on-chain before the next is even assembled (see the Hood paragraph below).
1066
+ * Candle only ever handles already-signed bytes either way -- it never signs a linked payer's
1067
+ * trade and never holds its signer key.
1068
+ *
1069
+ * - **Solana**: `buildTrade` -> `signLinkedTransaction` -> `submit({ clientTradeId,
1070
+ * signedTransactions: [signed.signedTransaction] })`, returning the executed result the
1071
+ * server's own broadcast-and-confirm produced. The build response carries the FULL unsigned
1072
+ * transaction, blockhash included (`artifacts.transactionBase64`), so signing needs no RPC
1073
+ * read at all -- `solanaRpcUrl` is unused on this path, and there is no client-side
1074
+ * blockhash-rebuild loop; the server controls broadcast and its own blockhash freshness now.
1075
+ * The lower-level `buildTrade`/`signLinkedTransaction`/`broadcastSignedTransaction`/
1076
+ * `confirmTrade` sequence (with its own blockhash-rebuild loop) stays available, unchanged,
1077
+ * for callers who want to broadcast client-side instead -- see those methods' own jsdoc.
1078
+ * - **Hood/EVM**: unchanged from before -- `artifacts.approval` (when present), then
1079
+ * `artifacts.trade`, then `artifacts.feeTransfer` (when present) -- in that exact order, EACH
1080
+ * leg's receipt awaited (`waitForReceipt`) before the next leg is even assembled, then
1081
+ * `confirmTrade({ clientTradeId, tradeTxHash, feeTxHash })`. This ordering is load-bearing,
1082
+ * not a style choice: the `trade` leg's `eth_estimateGas` reverts if it runs before the
1083
+ * `approval` leg is mined, since the on-chain allowance is not yet set -- so Hood stays off
1084
+ * `submit()` by default (submit() is still available as an explicit opt-in for a caller that
1085
+ * has already confirmed no approval leg is needed). Requires `evmRpcUrl`; throws a clear error
1086
+ * naming it when unset, before any RPC read or signing.
1087
+ */
1088
+ trade(req: TradeRequest): Promise<ExecutedTradeResult>;
1089
+ /**
1090
+ * One-call self-signed launch. Solana: `buildSelfLaunch` -> `signLinkedTransaction` ->
1091
+ * `broadcastSignedTransaction` -> `confirmSelfLaunch` (`built.transaction` is a base64 unsigned
1092
+ * transaction). Fills in `clientLaunchId` (like `launch()`) when the caller omits one.
1093
+ *
1094
+ * Hood: `built.transaction` is `{ to, data }` calldata for the createCurve tx, no approval leg.
1095
+ * Signs, broadcasts, and waits for its receipt, then -- when the build carried a `feeTransfer`
1096
+ * leg (a platform fee applies) -- does that leg next at `nonce + 1` and captures `feeTxHash`.
1097
+ * Requires `evmRpcUrl`; throws a clear error naming it when unset, before any signing. Does NOT
1098
+ * send a dev buy: a Hood self-launch's dev buy runs out-of-band, server-side, after confirm --
1099
+ * the build response never includes a dev-buy leg, and this method does not assemble one.
1100
+ */
1101
+ selfLaunch(req: SelfLaunchRequest): Promise<ConfirmSelfLaunchResult>;
1102
+ /**
1103
+ * Builds an atomic launch bundle: a Solana launch transaction plus 1-4 first-buy transactions,
1104
+ * all sharing one recent blockhash so Jito lands them together or not at all. Returns the
1105
+ * UNSIGNED bytes for every "client" signer leg (a "main" payer leg is signed by Candle itself at
1106
+ * submit time and is never returned). Fills in `clientLaunchId` (like `launch()`) when the
1107
+ * caller omits one. Call `submitAtomicLaunch()` next -- or drive both calls plus signing in one
1108
+ * shot with `launchAtomic()`. See "Atomic launch with first buys" in docs/headless-launch.md for
1109
+ * the full model: Pro/Max tier, `launch:write` + `swap:write` scopes, Solana only, the 1-4 buy
1110
+ * cap (Jito's 5-transaction bundle limit), and why every buy leg's own on-chain `minAmountOut` is
1111
+ * "0" inside the bundle (`expectedFill` on the returned legs is the real consent surface).
1112
+ */
1113
+ buildAtomicLaunch(req: BuildAtomicLaunchRequest): Promise<BuildAtomicLaunchResult>;
1114
+ /**
1115
+ * Submits an already-built bundle's client-signer legs (EXACTLY the ones
1116
+ * `BuildAtomicLaunchResult.legs` named `signer: "client"`, in leg order -- omit every "server"
1117
+ * leg entirely, never pad the array) and relays the whole bundle to Jito as one atomic unit.
1118
+ * `bundleId` is single-use regardless of outcome: it is consumed on this call even before any
1119
+ * verification, so a rejected call (tampered bytes, wrong leg count) can only be corrected by
1120
+ * calling `buildAtomicLaunch()` again, never by retrying `submitAtomicLaunch()` with the same
1121
+ * `bundleId`.
1122
+ *
1123
+ * Unlike every other keyed method here, a `"failed"` or `"timeout"` outcome (HTTP 502) is
1124
+ * returned in this SAME result, not thrown as a `CandleApiError` -- both are normal, expected
1125
+ * outcomes of submitting a Jito bundle, not malformed requests or server malfunctions. Every
1126
+ * other non-2xx status (400/403/404/501/503 -- scope/tier/validation failures, an unknown or
1127
+ * expired `bundleId`, tampered signature bytes) still throws `CandleApiError` as usual.
1128
+ *
1129
+ * A `"timeout"` response can take meaningfully longer to arrive than a typical API call -- up to
1130
+ * roughly 150-160s in the worst case (Jito's own ~60s poll budget plus a further ~90s resolution
1131
+ * phase), since Candle checks the launch signature's own on-chain status before answering. Set a
1132
+ * client-side timeout no shorter than ~160s (or none) for this call, or you risk abandoning a
1133
+ * request that was about to return a normal, if late, response.
1134
+ */
1135
+ submitAtomicLaunch(req: SubmitAtomicLaunchRequest): Promise<SubmitAtomicLaunchResult>;
1136
+ /**
1137
+ * One-call atomic launch: `buildAtomicLaunch()` -> `signLinkedTransaction()` for every "client"
1138
+ * signer leg, IN LEG ORDER -> `submitAtomicLaunch()`. A "main" payer leg needs no client-side
1139
+ * signing at all (Candle signs it server-side at submit, the same delegated-wallet path
1140
+ * `launch()`/`trade()`'s main-payer branch uses); a "linked" payer leg is signed HERE, through
1141
+ * the same sign relay `trade()`'s linked branch uses -- requires `privyAppId` and a
1142
+ * `secretStore` holding that leg's own `linkedWalletId`'s signer key (see
1143
+ * `signLinkedTransaction()`'s own jsdoc for the exact errors thrown when either is missing). A
1144
+ * bundle with no linked payer at all (every leg "main") skips the signing round entirely and
1145
+ * goes straight from build to submit.
1146
+ *
1147
+ * Returns `submitAtomicLaunch()`'s own result untouched -- see that method's jsdoc for why
1148
+ * `"failed"`/`"timeout"` are returned here rather than thrown, and for the recommended
1149
+ * client-side timeout.
1150
+ */
1151
+ launchAtomic(req: LaunchAtomicRequest): Promise<SubmitAtomicLaunchResult>;
1152
+ /**
1153
+ * The `EvmRpc` seam packages/sdk/src/evm-tx.ts's helpers are built against, built from this
1154
+ * client's own `jsonRpcCall`/`jsonRpcCallRaw` closed over `evmRpcUrl`. Callers must have already
1155
+ * checked `evmRpcUrl` is set (trade()/selfLaunch() do, with a clear error naming it, before
1156
+ * calling this).
1157
+ */
1158
+ private evmRpc;
1159
+ /**
1160
+ * Assembles, signs, broadcasts, and waits for the mined receipt of ONE Hood leg, returning its
1161
+ * transaction hash. Shared by trade()'s and selfLaunch()'s Hood branches, both of which must run
1162
+ * their legs strictly sequentially -- see trade()'s jsdoc for why a later leg's `estimateGas`
1163
+ * depends on an earlier leg already being mined.
1164
+ */
1165
+ private signBroadcastAndWaitEvmLeg;
1166
+ private requireKey;
1167
+ private headers;
1168
+ private requestJson;
1169
+ private parseResponse;
1170
+ /**
1171
+ * Minimal fetch-based JSON-RPC 2.0 call, used only by broadcastSignedTransaction(). Both
1172
+ * current callers (Solana's sendTransaction, EVM's eth_sendRawTransaction) expect a string
1173
+ * result (a signature or tx hash), so this validates that shape here rather than letting a
1174
+ * malformed or missing `result` flow out as an unchecked cast at the call site. A JSON-RPC
1175
+ * `error` envelope throws a structured `JsonRpcError` (code + the full `data` field, e.g. a
1176
+ * Solana `-32002`'s `{ err, logs }`) rather than a plain `Error`, so callers -- notably
1177
+ * broadcastSignedTransaction()'s callers deciding whether a broadcast failure is a
1178
+ * blockhash-expiry worth rebuilding and retrying -- can inspect the real cause instead of only
1179
+ * a flattened message string.
1180
+ */
1181
+ private jsonRpcCall;
1182
+ /**
1183
+ * Same POST as jsonRpcCall() above, for RPC methods whose `result` is an OBJECT or `null`
1184
+ * rather than a string -- eth_getBlockByNumber (a block) and eth_getTransactionReceipt (a
1185
+ * receipt, or null before it is mined). Skips jsonRpcCall()'s string guard, since a non-string
1186
+ * (including null) result is the normal, valid shape here. This is the `rpc.callRaw` seam
1187
+ * packages/sdk/src/evm-tx.ts's helpers are built against, wired up via evmRpc() above. Throws
1188
+ * the same structured `JsonRpcError` as jsonRpcCall() on a JSON-RPC `error` envelope.
1189
+ */
1190
+ private jsonRpcCallRaw;
1191
+ }
1192
+ //# sourceMappingURL=client.d.ts.map