@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.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/authorization-signature.d.ts +87 -0
- package/dist/authorization-signature.d.ts.map +1 -0
- package/dist/client.d.ts +1192 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/evm-tx.d.ts +100 -0
- package/dist/evm-tx.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1186 -0
- package/dist/internal/encoding.d.ts +19 -0
- package/dist/internal/encoding.d.ts.map +1 -0
- package/dist/keychain-secret-store.d.ts +51 -0
- package/dist/keychain-secret-store.d.ts.map +1 -0
- package/dist/secret-store.d.ts +82 -0
- package/dist/secret-store.d.ts.map +1 -0
- package/dist/wallet-import.d.ts +93 -0
- package/dist/wallet-import.d.ts.map +1 -0
- package/dist/webhooks.d.ts +20 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/package.json +60 -0
package/dist/client.d.ts
ADDED
|
@@ -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
|