@candledottv/agent-sdk 0.4.11 → 0.5.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/README.md +144 -8
- package/dist/client.d.ts +241 -19
- package/dist/client.d.ts.map +1 -1
- package/dist/errors.d.ts +98 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +137 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,11 +53,13 @@ refuse to fetch without one and throw a plain `Error` locally instead of a serve
|
|
|
53
53
|
|
|
54
54
|
`launch()` fills in a `clientLaunchId` (`"sdk-" + crypto.randomUUID()`) when you omit one, and
|
|
55
55
|
retries transient failures by re-sending the SAME id, which the server's idempotency ledger
|
|
56
|
-
resolves safely
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
resolves safely: no double mint inside one `launch()` call. That holds only inside the call. A
|
|
57
|
+
second `launch()` call with no id is a new launch under a new id, so to retry after `launch()`
|
|
58
|
+
throws, pass the id back: see "Retrying a write that failed or timed out" below. Retries cover
|
|
59
|
+
network errors, non-envelope 5xx responses, retryable 5xx envelopes, and the retryable in-flight
|
|
60
|
+
409; a non-retryable envelope (validation errors, an id reused with a different body,
|
|
61
|
+
`LAUNCH_DISABLED`) is thrown immediately. Backoff is 250ms doubling per attempt, jittered, capped
|
|
62
|
+
at 8s, bounded by `maxRetries` (default 3).
|
|
61
63
|
|
|
62
64
|
```ts
|
|
63
65
|
import { CandleApiError } from "@candledottv/agent-sdk"
|
|
@@ -70,11 +72,12 @@ try {
|
|
|
70
72
|
chain: "solana",
|
|
71
73
|
buyAmount: 100_000_000, // lamports
|
|
72
74
|
})
|
|
73
|
-
console.log("minted", result.mint, "explorer:", result.links.explorer)
|
|
75
|
+
console.log("minted", result.mint, "explorer:", result.links.explorer, "id:", result.clientLaunchId)
|
|
74
76
|
} catch (error) {
|
|
75
77
|
if (error instanceof CandleApiError) {
|
|
76
78
|
// Branch on code, never on message. error.status, error.retryable, error.field ride along.
|
|
77
|
-
|
|
79
|
+
// error.clientLaunchId is the id this call sent: keep it for the retry.
|
|
80
|
+
console.error("launch failed:", error.code, error.clientLaunchId)
|
|
78
81
|
} else {
|
|
79
82
|
throw error
|
|
80
83
|
}
|
|
@@ -115,6 +118,8 @@ const fill = await candle.trade({ mint: "9dXSV8...CNDL", side: "buy", amountRaw:
|
|
|
115
118
|
or from a linked wallet the caller signs for. Amounts are raw units. On Solana,
|
|
116
119
|
`amounts.actualOutRaw` is the delivered amount, decoded from the payer's balance change; book
|
|
117
120
|
positions from it rather than from `expectedOutRaw`. It is absent on Hood.
|
|
121
|
+
- `getTradeJob(clientTradeId)` reads one trade from the idempotency ledger without sending
|
|
122
|
+
anything: the read to make after a `trade()` that threw or timed out. Needs `swap:write`.
|
|
118
123
|
- `swap()` converts base assets (`SOL`, `USDC`, `CNDL`, `ETH`, `USDG`); a pair that spans Solana
|
|
119
124
|
and Hood is a bridge.
|
|
120
125
|
- `getPlans()` (no key) returns every plan's price, fees, limits and capabilities as the server
|
|
@@ -132,6 +137,135 @@ const fill = await candle.trade({ mint: "9dXSV8...CNDL", side: "buy", amountRaw:
|
|
|
132
137
|
Limit orders are a REST surface only (`/api/v1/trade/agent/orders`); the SDK has no method for
|
|
133
138
|
them.
|
|
134
139
|
|
|
140
|
+
## Retrying a write that failed or timed out
|
|
141
|
+
|
|
142
|
+
Six methods send an idempotency id, and generate one (`"sdk-" + crypto.randomUUID()`) when you
|
|
143
|
+
pass none: `trade()`, `launch()`, `launchAsync()`, `selfLaunch()`, `buildAtomicLaunch()` and
|
|
144
|
+
`launchAtomic()`. The server treats that id as the whole identity of the operation: the same id
|
|
145
|
+
is a replay, and a new id is a second, independent trade or launch. A call that does not come
|
|
146
|
+
back with a normal answer (a lost response, a proxy timeout, a 5xx) may still have executed, so
|
|
147
|
+
what you do next depends on having the id.
|
|
148
|
+
|
|
149
|
+
1. **Pass your own id for anything that moves funds, and store it before the call.** An error can
|
|
150
|
+
carry the id only if the process is still running to catch it. A crash or a kill mid-call
|
|
151
|
+
leaves nothing, and only an id you stored first survives that.
|
|
152
|
+
2. **If you did not, read the id from the result or the error**, and send it back on the retry
|
|
153
|
+
with the same request body. Every one of the six methods exposes the id it sent on every
|
|
154
|
+
outcome: on the result (`result.clientTradeId`, `result.clientLaunchId`) and on every error
|
|
155
|
+
thrown after its first request was sent (`operationIds(error)`). This is the whole procedure
|
|
156
|
+
for the flows Candle executes. For `selfLaunch()` and a linked `trade()` it is not enough: see
|
|
157
|
+
below.
|
|
158
|
+
3. **Never retry without the id.** A call with no id, or a new one, is a second trade or launch.
|
|
159
|
+
The SDK does not remember ids between calls and adds no retry of its own to `trade()`,
|
|
160
|
+
`selfLaunch()` or `launchAtomic()`; only `launch()` retries, inside its one call.
|
|
161
|
+
|
|
162
|
+
What the error is:
|
|
163
|
+
|
|
164
|
+
| The method threw | Class | The id |
|
|
165
|
+
| --- | --- | --- |
|
|
166
|
+
| a Candle error response | `CandleApiError`, unchanged | `error.clientTradeId` or `error.clientLaunchId`, and `error.bundleId` from `launchAtomic()` once its build answered |
|
|
167
|
+
| a JSON-RPC failure from a broadcast | `JsonRpcError`, unchanged | the same fields |
|
|
168
|
+
| a Hood trade whose fee leg failed after the trade landed | `TradeLandedFeeLegError`, unchanged | `error.clientTradeId` |
|
|
169
|
+
| anything else after the first request was sent: a rejected `fetch`, a 2xx body that is not JSON, a plain `Error` from one of the method's own steps | `CandleOperationError`, with the original as `error.cause` | the same fields |
|
|
170
|
+
| an error before anything was sent, such as no API key | the plain error | none: nothing reached the server, so there is nothing to replay |
|
|
171
|
+
|
|
172
|
+
`operationIds(error)` returns `{ clientTradeId?, clientLaunchId?, bundleId? }` from any of these and
|
|
173
|
+
`{}` from anything else, so a `catch (error: unknown)` block needs no cast. The ids are not secrets
|
|
174
|
+
and may be logged. A `CandleOperationError`'s message starts with the original message, names the
|
|
175
|
+
id, and says what to do with it.
|
|
176
|
+
|
|
177
|
+
**Changed in 0.5.0:** a transport or local failure from these six methods used to surface as the
|
|
178
|
+
raw error (a `TypeError` from `fetch`, for example). It is now a `CandleOperationError` with the
|
|
179
|
+
original as `error.cause`. Code that matched `error instanceof TypeError` or
|
|
180
|
+
`error.name === "AbortError"` on these calls should read `error.cause`. `launch()`, `selfLaunch()`,
|
|
181
|
+
`confirmSelfLaunch()` and `buildAtomicLaunch()` results gained `clientLaunchId`, and
|
|
182
|
+
`launchAtomic()` returns `LaunchAtomicResult`, its old result plus `clientLaunchId`.
|
|
183
|
+
|
|
184
|
+
A main-payer trade (`from: "main"`), where the retry is safe once you have the id:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { operationIds } from "@candledottv/agent-sdk"
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
return await candle.trade(req)
|
|
191
|
+
} catch (error) {
|
|
192
|
+
const { clientTradeId } = operationIds(error)
|
|
193
|
+
if (!clientTradeId) throw error // nothing was sent
|
|
194
|
+
return await candle.trade({ ...req, clientTradeId }) // from: "main" only: a replay, not a second trade
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
This example is not safe for a linked payer or for `selfLaunch()`: a same-id call there can
|
|
199
|
+
broadcast again.
|
|
200
|
+
|
|
201
|
+
### `trade()`
|
|
202
|
+
|
|
203
|
+
With the id: read `getTradeJob(id)`, or call `trade()` again with the same id and body. Both are
|
|
204
|
+
safe for a main payer.
|
|
205
|
+
|
|
206
|
+
| The job read says | Meaning | Do |
|
|
207
|
+
| --- | --- | --- |
|
|
208
|
+
| 404 `JOB_NOT_FOUND` (thrown as `CandleApiError`) | the write never began | call again with the same id |
|
|
209
|
+
| `confirmed` | the trade landed; `signature` is on the job | stop. A same-id `trade()` replays the result |
|
|
210
|
+
| `failed`, `errorCode: "TRADE_REVERTED"` | it landed and reverted | a new trade needs a new id |
|
|
211
|
+
| `failed`, other | not confirmed. A row with broadcast evidence may still be on chain, and a same-id call answers `stage: "unconfirmed"` for it. The same-id call is the check | call again with the same id |
|
|
212
|
+
| `built` | in flight or unresolved | a same-id `trade()` answers `IDEMPOTENCY_CONFLICT`. If it carries `stage: "unconfirmed"` and `retryable: false`, check its `signature` on chain and do not retry |
|
|
213
|
+
|
|
214
|
+
### `launch()`, `launchAsync()`
|
|
215
|
+
|
|
216
|
+
Retry with the same `clientLaunchId` and body, or read `getLaunchJob(id)` (`waitForLaunch(id)`
|
|
217
|
+
polls it). The rules are the ones under "Idempotency" in
|
|
218
|
+
[Headless launch](https://docs.candle.tv/developers/headless-launch#idempotency); what is new is
|
|
219
|
+
that you always have the id.
|
|
220
|
+
|
|
221
|
+
### `selfLaunch()` and a linked `trade()`
|
|
222
|
+
|
|
223
|
+
You sign and broadcast in these flows, so the id is necessary and **not** sufficient. An error
|
|
224
|
+
after the broadcast may sit on a transaction that landed, and calling the method again under the
|
|
225
|
+
same id builds and broadcasts a new one. So a `CandleOperationError` from these flows does not
|
|
226
|
+
tell you to retry with the id. Read the job first (`getLaunchJob(id)`, `getTradeJob(id)`); if it
|
|
227
|
+
is not `confirmed`, check the wallet's recent transactions before sending again.
|
|
228
|
+
|
|
229
|
+
### `launchAtomic()`
|
|
230
|
+
|
|
231
|
+
`launchAtomic()` returns `timeout` and `failed` as values, not errors, and each carries
|
|
232
|
+
`clientLaunchId`. `bundleId` is dead once `/submit` has been called and is never resubmitted; it
|
|
233
|
+
is on the result and the error for log correlation only. The launch's identity is
|
|
234
|
+
`clientLaunchId`. After a `timeout` result, a `failed` result, or an error that carries
|
|
235
|
+
`clientLaunchId`:
|
|
236
|
+
|
|
237
|
+
1. Read `getLaunchJob(clientLaunchId)`. This needs `launch:read` on the key. The Write and
|
|
238
|
+
Withdraw access levels hold it; a key whose scopes were named one by one holds it only if it
|
|
239
|
+
was named, so add `launch:read` to any key that runs atomic launches. A scope refusal
|
|
240
|
+
(`SCOPE_MISSING`) is not "not found".
|
|
241
|
+
2. `confirmed`: the bundle landed. The mint is on the job. Stop.
|
|
242
|
+
3. 404 `JOB_NOT_FOUND`: the build never began. Call `launchAtomic()` again with the same id.
|
|
243
|
+
4. `failed`: the bundle did not land. Call `launchAtomic()` again with the **same** id; the server
|
|
244
|
+
starts a clean attempt under it.
|
|
245
|
+
5. Any other status: the bundle may still land. Wait, then either read the job again or call
|
|
246
|
+
`launchAtomic()` again with the **same** id and the same body. The server resolves the first
|
|
247
|
+
attempt before doing anything new. It answers 409 `IDEMPOTENCY_CONFLICT` while the first
|
|
248
|
+
attempt is unresolved or has just been found landed, so on that 409 read the job again. It
|
|
249
|
+
builds a fresh bundle only when the first is proven reverted.
|
|
250
|
+
6. **Never send a new id while the job is not `failed`.** That is the one path the server cannot
|
|
251
|
+
refuse, and it is a second token. This includes the 409 whose message says "use a new
|
|
252
|
+
clientLaunchId to launch again": read the job first, because that message is also what you see
|
|
253
|
+
for a launch you do not yet know landed.
|
|
254
|
+
7. One state has no terminal answer today. For an attempt whose blockhash expired with no
|
|
255
|
+
signature on record, the same-id `launchAtomic()` keeps answering 409 `IDEMPOTENCY_CONFLICT`,
|
|
256
|
+
"This launch was sent; its original attempt must be resolved", and the job read is still
|
|
257
|
+
neither `confirmed` nor `failed`. Treat it as this state when that remains true well after the
|
|
258
|
+
bundle's blockhash must have expired: a Solana blockhash lives about 90 seconds and a same-id
|
|
259
|
+
rebuild can itself take about 95 seconds, so a few minutes after the submit. Then stop. Do not
|
|
260
|
+
send a new id on a timer; report the `clientLaunchId` and `bundleId` to whoever operates the
|
|
261
|
+
key.
|
|
262
|
+
8. Treat every `timeout` as unresolved. `retryable` is `false` on every `timeout` the server
|
|
263
|
+
returns today, and it is never permission to relaunch under a new id.
|
|
264
|
+
|
|
265
|
+
A same-id rebuild can take up to about 95 seconds to answer, because the server waits on the
|
|
266
|
+
chain, and a submit can take up to about 160 seconds. Set no client-side timeout shorter than
|
|
267
|
+
that on these calls.
|
|
268
|
+
|
|
135
269
|
## Importing a wallet
|
|
136
270
|
|
|
137
271
|
`importWallet()` drives Candle's ciphertext-only wallet import end to end: it fetches Privy's
|
|
@@ -206,7 +340,9 @@ Every non-2xx response throws `CandleApiError` with `code`, `status`, `retryable
|
|
|
206
340
|
`docsPath` when the envelope carries them. Structured envelopes map straight through; the few
|
|
207
341
|
legacy endpoints without envelopes (activity, users) surface as `code: "HTTP_<status>"`. A
|
|
208
342
|
Solana or EVM JSON-RPC failure throws `JsonRpcError`, with the RPC's numeric `code` and its
|
|
209
|
-
`data` (for a failed simulation, `{ err, logs }`). The
|
|
343
|
+
`data` (for a failed simulation, `{ err, logs }`). The one-call write methods also put the
|
|
344
|
+
idempotency id they sent on these errors, and wrap any other failure in `CandleOperationError`:
|
|
345
|
+
see "Retrying a write that failed or timed out" above. The full code table is on
|
|
210
346
|
[Headless launch](https://docs.candle.tv/developers/headless-launch).
|
|
211
347
|
|
|
212
348
|
## Development
|
package/dist/client.d.ts
CHANGED
|
@@ -26,6 +26,17 @@
|
|
|
26
26
|
* validation error), and it never retries a launch whose stage is `executed` or `unconfirmed`
|
|
27
27
|
* or whose error already names a mint. Backoff is 250ms * 2^n, jittered to 50-100% of that,
|
|
28
28
|
* capped at 8s, bounded by `maxRetries` (default 3 retries after the initial attempt).
|
|
29
|
+
* - The id on every outcome. The six one-call write methods (`trade()`, `launch()`,
|
|
30
|
+
* `launchAsync()`, `selfLaunch()`, `buildAtomicLaunch()`, `launchAtomic()`) send one idempotency
|
|
31
|
+
* id per call, the caller's or a generated one, and once the first request carrying it has been
|
|
32
|
+
* sent, every exit exposes that id: on the returned value, and on the thrown error
|
|
33
|
+
* (`operationIds(error)`). A `CandleApiError` or `JsonRpcError` keeps its class and gains the id;
|
|
34
|
+
* anything else (a rejected fetch, a 2xx body that is not JSON, a plain Error from one of the
|
|
35
|
+
* method's own steps) is wrapped once, at the outermost method, in a `CandleOperationError` with
|
|
36
|
+
* the original as `cause`. An error raised before anything was sent (no API key) carries no id
|
|
37
|
+
* and is not wrapped. The SDK never reuses an id between calls and adds no retry of its own: a
|
|
38
|
+
* call that passes no id is a new operation. The retry is the caller's, with the id passed back.
|
|
39
|
+
* The README's "Retrying a write that failed or timed out" is the caller-facing procedure.
|
|
29
40
|
*/
|
|
30
41
|
import { type HyperliquidNetwork, type HyperliquidTypedData } from "./hyperliquid";
|
|
31
42
|
import type { HyperliquidPnlSection } from "./hyperliquid-pnl";
|
|
@@ -278,9 +289,15 @@ export interface SelfLaunchDryRunResult extends Omit<DryRunResult, "launchWallet
|
|
|
278
289
|
launchWallet: string;
|
|
279
290
|
launchWalletId: string;
|
|
280
291
|
}
|
|
281
|
-
/**
|
|
292
|
+
/**
|
|
293
|
+
* POST /api/v1/launch/headless blocking (or replayed) success response, plus `clientLaunchId`.
|
|
294
|
+
* The server's body does not carry the id: `launch()`, `selfLaunch()` and `confirmSelfLaunch()`
|
|
295
|
+
* set it from the request they sent, so a caller who let the SDK generate it still has it.
|
|
296
|
+
*/
|
|
282
297
|
export interface LaunchResult {
|
|
283
298
|
success: true;
|
|
299
|
+
/** The idempotency id this launch was sent under, the caller's or the generated one. */
|
|
300
|
+
clientLaunchId: string;
|
|
284
301
|
chain: Chain;
|
|
285
302
|
mint: string;
|
|
286
303
|
pool: string | null;
|
|
@@ -1448,6 +1465,42 @@ export type ConfirmTradeRequest = {
|
|
|
1448
1465
|
};
|
|
1449
1466
|
/** POST /api/v1/trade/agent/confirm response. Always "executed": confirm only ever verifies and records a trade that already happened on-chain. */
|
|
1450
1467
|
export type ConfirmTradeResult = ExecutedTradeResult;
|
|
1468
|
+
/**
|
|
1469
|
+
* One trade from the idempotency ledger, from GET /api/v1/trade/agent/jobs/:clientTradeId
|
|
1470
|
+
* (`getTradeJob()`). `amountRaw` is raw units: the quote asset's on a buy, the token's on a sell.
|
|
1471
|
+
* `status` is `built` (in flight, or built and never confirmed), `confirmed` (landed; `signature`
|
|
1472
|
+
* is set) or `failed` (`errorCode` says why; `TRADE_REVERTED` means it landed and reverted).
|
|
1473
|
+
*/
|
|
1474
|
+
export interface TradeJob {
|
|
1475
|
+
clientTradeId: string;
|
|
1476
|
+
chain: Chain;
|
|
1477
|
+
side: TradeSide;
|
|
1478
|
+
mint: string;
|
|
1479
|
+
quoteAsset: string;
|
|
1480
|
+
amountRaw: string;
|
|
1481
|
+
status: "built" | "confirmed" | "failed";
|
|
1482
|
+
signature?: string;
|
|
1483
|
+
errorCode?: string;
|
|
1484
|
+
createdAt: number;
|
|
1485
|
+
updatedAt: number;
|
|
1486
|
+
/** A Hood TEE trade only: its sequenced operation, leg by leg. `kind` and `status` are open strings. */
|
|
1487
|
+
operation?: {
|
|
1488
|
+
operationId: string;
|
|
1489
|
+
kind: string;
|
|
1490
|
+
status: string;
|
|
1491
|
+
plannedLegCount: number;
|
|
1492
|
+
currentLegIndex: number;
|
|
1493
|
+
legs: Array<{
|
|
1494
|
+
kind: string;
|
|
1495
|
+
status: string;
|
|
1496
|
+
hash?: string;
|
|
1497
|
+
nonce?: number;
|
|
1498
|
+
}>;
|
|
1499
|
+
expiresAt: number;
|
|
1500
|
+
/** The fee a trade that landed after its lock expired still owes, once the read has recorded it. */
|
|
1501
|
+
feeOwed?: Record<string, unknown>;
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1451
1504
|
/**
|
|
1452
1505
|
* POST /api/v1/trade/agent/submit request body. `signedTransactions` is the ordered signed legs:
|
|
1453
1506
|
* one for Solana; one to four for Hood in the fixed approval, permit2Approval, trade, feeTransfer
|
|
@@ -2139,7 +2192,10 @@ export interface TradeRequest {
|
|
|
2139
2192
|
* The server defaults it when omitted, which is `sol` on Solana and ETH settlement on Hood.
|
|
2140
2193
|
*/
|
|
2141
2194
|
quoteAsset?: "sol" | "usdc" | "cndl" | "eth" | "usdg";
|
|
2142
|
-
/**
|
|
2195
|
+
/**
|
|
2196
|
+
* Idempotency key shared by the build and confirm calls; generated ("sdk-" + UUID) when omitted.
|
|
2197
|
+
* Pass your own for anything that moves funds and store it before the call: see `trade()`.
|
|
2198
|
+
*/
|
|
2143
2199
|
clientTradeId?: string;
|
|
2144
2200
|
}
|
|
2145
2201
|
/** selfLaunch()'s one-call request: BuildSelfLaunchRequest plus the linked wallet's Privy wallet id. */
|
|
@@ -2191,8 +2247,14 @@ export interface AtomicBundleResponseLeg {
|
|
|
2191
2247
|
amountOutRaw: string;
|
|
2192
2248
|
};
|
|
2193
2249
|
}
|
|
2194
|
-
/**
|
|
2250
|
+
/**
|
|
2251
|
+
* POST /api/v1/launch/atomic/build response, plus `clientLaunchId`. The server's body does not
|
|
2252
|
+
* carry the id: `buildAtomicLaunch()` sets it from the request it sent. It is the launch's
|
|
2253
|
+
* identity (`bundleId` is single-use), so a caller driving build and submit by hand keeps it.
|
|
2254
|
+
*/
|
|
2195
2255
|
export interface BuildAtomicLaunchResult {
|
|
2256
|
+
/** The idempotency id this build was sent under, the caller's or the generated one. */
|
|
2257
|
+
clientLaunchId: string;
|
|
2196
2258
|
bundleId: string;
|
|
2197
2259
|
legs: AtomicBundleResponseLeg[];
|
|
2198
2260
|
expiresAt: number;
|
|
@@ -2211,14 +2273,21 @@ export interface SubmitAtomicLaunchRequest {
|
|
|
2211
2273
|
* are the 502 body -- all three are NORMAL, expected outcomes of submitting a Jito bundle that
|
|
2212
2274
|
* this route documents as part of its own response surface, so `submitAtomicLaunch()`/
|
|
2213
2275
|
* `launchAtomic()` return them here rather than throwing `CandleApiError` (every OTHER non-2xx
|
|
2214
|
-
* status still throws, same as every other method).
|
|
2215
|
-
*
|
|
2216
|
-
*
|
|
2217
|
-
*
|
|
2218
|
-
*
|
|
2219
|
-
*
|
|
2220
|
-
*
|
|
2221
|
-
*
|
|
2276
|
+
* status still throws, same as every other method).
|
|
2277
|
+
*
|
|
2278
|
+
* `retryable` is `false` on every `"failed"` and on every `"timeout"` the server returns today.
|
|
2279
|
+
* It is not permission to relaunch, whatever its value: a `"timeout"` means the resolution window
|
|
2280
|
+
* ran out with no definitive answer and the bundle MIGHT STILL LAND. Treat every `"timeout"` as
|
|
2281
|
+
* unresolved and never rebuild under a new `clientLaunchId` because of one: that is a second
|
|
2282
|
+
* token. (This comment used to describe a `true` case meaning "the blockhash expired, relaunch".
|
|
2283
|
+
* No server path returns it.)
|
|
2284
|
+
*
|
|
2285
|
+
* `bundleId` itself is dead once this response arrives: it is consumed on the first `/submit`
|
|
2286
|
+
* call regardless of outcome and is never resubmitted. The launch's identity is the
|
|
2287
|
+
* `clientLaunchId` the build was sent under, and recovery goes through it: read
|
|
2288
|
+
* `getLaunchJob(clientLaunchId)`, then call `launchAtomic()` again with the SAME id when the job
|
|
2289
|
+
* says to (the steps are on `launchAtomic()`). See "Atomic launch with first buys" in
|
|
2290
|
+
* docs/headless-launch.md for the full model.
|
|
2222
2291
|
*/
|
|
2223
2292
|
export type SubmitAtomicLaunchResult = {
|
|
2224
2293
|
status: "landed";
|
|
@@ -2234,6 +2303,14 @@ export type SubmitAtomicLaunchResult = {
|
|
|
2234
2303
|
bundleId: string;
|
|
2235
2304
|
retryable: boolean;
|
|
2236
2305
|
};
|
|
2306
|
+
/**
|
|
2307
|
+
* What `launchAtomic()` returns: `submitAtomicLaunch()`'s outcome plus the `clientLaunchId` the
|
|
2308
|
+
* launch was sent under, on all three of `landed`, `failed` and `timeout`. `submitAtomicLaunch()`
|
|
2309
|
+
* itself is given only a `bundleId` and cannot know the launch id.
|
|
2310
|
+
*/
|
|
2311
|
+
export type LaunchAtomicResult = SubmitAtomicLaunchResult & {
|
|
2312
|
+
clientLaunchId: string;
|
|
2313
|
+
};
|
|
2237
2314
|
/** 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. */
|
|
2238
2315
|
export type LaunchAtomicPayer = {
|
|
2239
2316
|
type: "main";
|
|
@@ -2315,12 +2392,27 @@ export declare class CandleClient {
|
|
|
2315
2392
|
/**
|
|
2316
2393
|
* Blocking launch with idempotent retries. Generates `clientLaunchId` when absent and
|
|
2317
2394
|
* re-sends the SAME id on retryable failures; see the module doc for the exact policy.
|
|
2395
|
+
*
|
|
2396
|
+
* The id this call sent, yours or the generated one, is on the result
|
|
2397
|
+
* (`result.clientLaunchId`) and on every error thrown after the first request was sent
|
|
2398
|
+
* (`operationIds(error).clientLaunchId`), including the one thrown when the retries run out.
|
|
2399
|
+
* The built-in retries are safe only inside this one call. To retry after it throws, call
|
|
2400
|
+
* `launch()` again with that SAME id and the same body, or read `getLaunchJob(id)`. Never retry
|
|
2401
|
+
* without the id: a call with no id, or a new one, is a second launch. Pass your own id and
|
|
2402
|
+
* store it before the call when you can: an error can carry the id only if the process is
|
|
2403
|
+
* still running to catch it.
|
|
2318
2404
|
*/
|
|
2319
2405
|
launch(req: LaunchRequest): Promise<LaunchResult>;
|
|
2320
2406
|
/**
|
|
2321
2407
|
* Fire-and-poll launch: sends `async: true`, returns the 202 body. Single attempt (poll
|
|
2322
2408
|
* `waitForLaunch` instead of retrying the POST). Generates `clientLaunchId` like `launch()`
|
|
2323
2409
|
* so the returned body always carries the id to poll.
|
|
2410
|
+
*
|
|
2411
|
+
* An error thrown after the request was sent carries that id too
|
|
2412
|
+
* (`operationIds(error).clientLaunchId`): the launch may have been accepted. Poll
|
|
2413
|
+
* `getLaunchJob(id)` / `waitForLaunch(id)`, or call again with the SAME id and body. Never
|
|
2414
|
+
* retry without the id: a call with no id, or a new one, is a second launch. Pass your own id
|
|
2415
|
+
* and store it before the call when you can.
|
|
2324
2416
|
*/
|
|
2325
2417
|
launchAsync(req: LaunchRequest): Promise<AcceptedJob>;
|
|
2326
2418
|
getLaunchJob(clientLaunchId: string): Promise<LaunchJob>;
|
|
@@ -2521,11 +2613,14 @@ export declare class CandleClient {
|
|
|
2521
2613
|
* docs/headless-launch.md.
|
|
2522
2614
|
*/
|
|
2523
2615
|
buildSelfLaunch(req: BuildSelfLaunchRequest): Promise<BuildSelfLaunchResult>;
|
|
2616
|
+
/** buildSelfLaunch()'s request; selfLaunch() passes its `op` so its exits can expose the id. */
|
|
2617
|
+
private postSelfLaunchBuild;
|
|
2524
2618
|
/**
|
|
2525
2619
|
* Self-signed launch confirm: verifies the agent's own broadcast on-chain, then records the
|
|
2526
2620
|
* launch identically to the headless path. Signature is the transaction hash from the agent's
|
|
2527
2621
|
* own broadcast. devBuySignature (Hood only) is the optional follow-up dev-buy transaction. See
|
|
2528
|
-
* selfLaunch() below for the one-call version of this whole flow (Solana).
|
|
2622
|
+
* selfLaunch() below for the one-call version of this whole flow (Solana). The result carries
|
|
2623
|
+
* the `clientLaunchId` from the request (the server's body does not).
|
|
2529
2624
|
*/
|
|
2530
2625
|
confirmSelfLaunch(req: ConfirmSelfLaunchRequest): Promise<ConfirmSelfLaunchResult>;
|
|
2531
2626
|
/**
|
|
@@ -2577,6 +2672,30 @@ export declare class CandleClient {
|
|
|
2577
2672
|
* `built | confirmed | failed`.
|
|
2578
2673
|
*/
|
|
2579
2674
|
buildTrade(req: BuildTradeRequest): Promise<BuildTradeResult>;
|
|
2675
|
+
/** buildTrade()'s request; trade() passes its `op` so its exits can expose the id. */
|
|
2676
|
+
private postTradeBuild;
|
|
2677
|
+
/**
|
|
2678
|
+
* Reads one trade from the idempotency ledger by its `clientTradeId`, without sending
|
|
2679
|
+
* anything: the read to make after a `trade()` that threw or timed out, before deciding whether
|
|
2680
|
+
* to send again. Needs `swap:write` (the scope the trade itself needed) and answers during a
|
|
2681
|
+
* kill switch.
|
|
2682
|
+
*
|
|
2683
|
+
* - 404 `JOB_NOT_FOUND` (a thrown `CandleApiError`): the write never began. Call again with the
|
|
2684
|
+
* same id.
|
|
2685
|
+
* - `confirmed`: the trade landed; `signature` is on the job. A same-id `trade()` replays it.
|
|
2686
|
+
* - `failed` with `errorCode: "TRADE_REVERTED"`: it landed and reverted. A new trade needs a
|
|
2687
|
+
* new id.
|
|
2688
|
+
* - `failed`, other: not confirmed. A row with broadcast evidence may still be on chain; a
|
|
2689
|
+
* same-id `trade()` is the check, and answers `IDEMPOTENCY_CONFLICT` with
|
|
2690
|
+
* `stage: "unconfirmed"` for it.
|
|
2691
|
+
* - `built`: in flight or unresolved. A same-id `trade()` answers `IDEMPOTENCY_CONFLICT`; if it
|
|
2692
|
+
* carries `stage: "unconfirmed"` and `retryable: false`, check its `signature` on chain and
|
|
2693
|
+
* do not retry.
|
|
2694
|
+
*
|
|
2695
|
+
* For a linked payer, also check the wallet's recent transactions before sending again: see
|
|
2696
|
+
* `trade()`.
|
|
2697
|
+
*/
|
|
2698
|
+
getTradeJob(clientTradeId: string): Promise<TradeJob>;
|
|
2580
2699
|
/**
|
|
2581
2700
|
* Confirm a LINKED payer's own broadcast trade: Candle verifies it landed on-chain, that it was
|
|
2582
2701
|
* actually signed/sent by the declared linked wallet, that it moved this trade's own mint in
|
|
@@ -2676,8 +2795,30 @@ export declare class CandleClient {
|
|
|
2676
2795
|
* opt-in, but only for a caller that has already confirmed the build carries neither an
|
|
2677
2796
|
* `approval` nor a `permit2Approval` leg. Requires `evmRpcUrl`; throws a clear error
|
|
2678
2797
|
* naming it when unset, before any RPC read or signing.
|
|
2798
|
+
*
|
|
2799
|
+
* ## The id, and retrying
|
|
2800
|
+
*
|
|
2801
|
+
* The `clientTradeId` this call sent, yours or the generated one, is on the result and on every
|
|
2802
|
+
* error thrown after the build request was sent (`operationIds(error).clientTradeId`): a
|
|
2803
|
+
* `CandleApiError` or `JsonRpcError` gains it, and a lost response or any other failure is a
|
|
2804
|
+
* `CandleOperationError` carrying it, with the original as `cause`. Pass your own id for
|
|
2805
|
+
* anything that moves funds and store it before the call: an error can carry the id only if
|
|
2806
|
+
* the process is still running to catch it. Never retry without the id: a call with no id, or a
|
|
2807
|
+
* new one, is a second, independent trade. This method does not retry on its own.
|
|
2808
|
+
*
|
|
2809
|
+
* - **Main payer** (`from: "main"`): the trade executes inside the build call, so a lost
|
|
2810
|
+
* response can sit on a trade that already moved funds. With the id, read `getTradeJob(id)`
|
|
2811
|
+
* or call `trade()` again with the same id and body. Both are safe: the same-id call is a
|
|
2812
|
+
* replay, not a second trade. `getTradeJob()`'s jsdoc says what each job state means.
|
|
2813
|
+
* - **Linked payer**: the id is necessary and NOT sufficient. The caller's side signs and
|
|
2814
|
+
* broadcasts here, so an error after the broadcast may sit on a transaction that landed, and
|
|
2815
|
+
* calling again under the same id can build and broadcast a second one. Read
|
|
2816
|
+
* `getTradeJob(id)` first; if it is not `confirmed`, check the wallet's recent transactions
|
|
2817
|
+
* before sending again.
|
|
2679
2818
|
*/
|
|
2680
2819
|
trade(req: TradeRequest): Promise<ExecutedTradeResult>;
|
|
2820
|
+
/** trade()'s steps. Every error leaves through trade()'s runOperation(), which adds the id. */
|
|
2821
|
+
private tradeSteps;
|
|
2681
2822
|
/**
|
|
2682
2823
|
* One-call self-signed launch. Solana: `buildSelfLaunch` -> `signLinkedTransaction` ->
|
|
2683
2824
|
* `broadcastSignedTransaction` -> `confirmSelfLaunch` (`built.transaction` is a base64 unsigned
|
|
@@ -2690,8 +2831,23 @@ export declare class CandleClient {
|
|
|
2690
2831
|
* send a dev buy: a Hood self-launch's dev buy is a separate transaction the caller sends itself
|
|
2691
2832
|
* and reports to `confirmSelfLaunch` as `devBuySignature`. The build response never includes a
|
|
2692
2833
|
* dev-buy leg, and this method does not assemble one.
|
|
2834
|
+
*
|
|
2835
|
+
* ## The id, and retrying
|
|
2836
|
+
*
|
|
2837
|
+
* The `clientLaunchId` this call sent, yours or the generated one, is on the result and on every
|
|
2838
|
+
* error thrown after the build request was sent (`operationIds(error).clientLaunchId`). Pass
|
|
2839
|
+
* your own id and store it before the call when you can. Never retry without the id: a call
|
|
2840
|
+
* with no id, or a new one, is a second, independent launch.
|
|
2841
|
+
*
|
|
2842
|
+
* Here the id is necessary and NOT sufficient. This method signs and broadcasts on the caller's
|
|
2843
|
+
* side, so an error after the broadcast may sit on a launch transaction that landed, and
|
|
2844
|
+
* calling `selfLaunch()` again under the same id can build and broadcast a second one. Read
|
|
2845
|
+
* `getLaunchJob(id)` first; if it is not `confirmed`, check the wallet's recent transactions
|
|
2846
|
+
* before sending again.
|
|
2693
2847
|
*/
|
|
2694
2848
|
selfLaunch(req: SelfLaunchRequest): Promise<ConfirmSelfLaunchResult>;
|
|
2849
|
+
/** selfLaunch()'s steps. Every error leaves through selfLaunch()'s runOperation(), which adds the id. */
|
|
2850
|
+
private selfLaunchSteps;
|
|
2695
2851
|
/**
|
|
2696
2852
|
* Builds an atomic launch bundle: a Solana launch transaction plus 1-4 first-buy transactions,
|
|
2697
2853
|
* all sharing one recent blockhash so Jito lands them together or not at all. Returns the
|
|
@@ -2702,8 +2858,22 @@ export declare class CandleClient {
|
|
|
2702
2858
|
* the full model: Pro/Max tier, `launch:write` + `swap:write` scopes, Solana only, the 1-4 buy
|
|
2703
2859
|
* cap (Jito's 5-transaction bundle limit), and why every buy leg's own on-chain `minAmountOut` is
|
|
2704
2860
|
* "0" inside the bundle (`expectedFill` on the returned legs is the real consent surface).
|
|
2861
|
+
*
|
|
2862
|
+
* The result carries the `clientLaunchId` the build was sent under (the server's body does
|
|
2863
|
+
* not), and so does every error thrown after the request was sent
|
|
2864
|
+
* (`operationIds(error).clientLaunchId`). Keep it: `bundleId` is single-use, and the launch's
|
|
2865
|
+
* identity for any later read or retry is the `clientLaunchId`. Retry a failed build with the
|
|
2866
|
+
* SAME id and body, or read `getLaunchJob(id)`; a call with no id, or a new one, is a second
|
|
2867
|
+
* launch. After a submit, follow the steps on `launchAtomic()`. Pass your own id and store it
|
|
2868
|
+
* before the call when you can.
|
|
2705
2869
|
*/
|
|
2706
2870
|
buildAtomicLaunch(req: BuildAtomicLaunchRequest): Promise<BuildAtomicLaunchResult>;
|
|
2871
|
+
/**
|
|
2872
|
+
* The atomic build request itself. Sets `clientLaunchId` on the result from the request, since
|
|
2873
|
+
* the server's body omits it. Shared by buildAtomicLaunch() and launchAtomic(), each of which
|
|
2874
|
+
* runs it inside its OWN runOperation(), so an error is wrapped once, by the outermost method.
|
|
2875
|
+
*/
|
|
2876
|
+
private postAtomicBuild;
|
|
2707
2877
|
/**
|
|
2708
2878
|
* Submits an already-built bundle's client-signer legs (EXACTLY the ones
|
|
2709
2879
|
* `BuildAtomicLaunchResult.legs` named `signer: "client"`, in leg order -- omit every "server"
|
|
@@ -2723,7 +2893,15 @@ export declare class CandleClient {
|
|
|
2723
2893
|
* roughly 150-160s in the worst case (Jito's own ~60s poll budget plus a further ~90s resolution
|
|
2724
2894
|
* phase), since Candle checks the launch signature's own on-chain status before answering. Set a
|
|
2725
2895
|
* client-side timeout no shorter than ~160s (or none) for this call, or you risk abandoning a
|
|
2726
|
-
* request that was about to return a normal, if late, response.
|
|
2896
|
+
* request that was about to return a normal, if late, response. A same-id
|
|
2897
|
+
* `buildAtomicLaunch()`/`launchAtomic()` after a send can itself take up to about 95 seconds to
|
|
2898
|
+
* answer, because the server resolves the first attempt against the chain before doing anything
|
|
2899
|
+
* new: allow for that too.
|
|
2900
|
+
*
|
|
2901
|
+
* This method is given only a `bundleId`, so its result cannot carry the launch's
|
|
2902
|
+
* `clientLaunchId`. Keep the one `buildAtomicLaunch()` returned: after a `"failed"` or
|
|
2903
|
+
* `"timeout"`, recovery goes through it (the steps are on `launchAtomic()`), never through
|
|
2904
|
+
* this `bundleId` and never through a new id.
|
|
2727
2905
|
*/
|
|
2728
2906
|
submitAtomicLaunch(req: SubmitAtomicLaunchRequest): Promise<SubmitAtomicLaunchResult>;
|
|
2729
2907
|
/**
|
|
@@ -2737,11 +2915,44 @@ export declare class CandleClient {
|
|
|
2737
2915
|
* bundle with no linked payer at all (every leg "main") skips the signing round entirely and
|
|
2738
2916
|
* goes straight from build to submit.
|
|
2739
2917
|
*
|
|
2740
|
-
* Returns `submitAtomicLaunch()`'s
|
|
2741
|
-
* `"failed"
|
|
2742
|
-
*
|
|
2743
|
-
|
|
2744
|
-
|
|
2918
|
+
* Returns `submitAtomicLaunch()`'s outcome plus the `clientLaunchId` the launch was sent under
|
|
2919
|
+
* (`LaunchAtomicResult`), on `"landed"`, `"failed"` and `"timeout"` alike -- see that method's
|
|
2920
|
+
* jsdoc for why `"failed"`/`"timeout"` are returned here rather than thrown, and for the
|
|
2921
|
+
* recommended client-side timeout. A thrown error carries `clientLaunchId` too, and `bundleId`
|
|
2922
|
+
* once the build has answered (`operationIds(error)`). Pass your own id and store it before the
|
|
2923
|
+
* call when you can: an error can carry the id only if the process is still running to catch it.
|
|
2924
|
+
*
|
|
2925
|
+
* ## After a `timeout`, a `failed`, or an error that carries `clientLaunchId`
|
|
2926
|
+
*
|
|
2927
|
+
* `bundleId` is dead once `/submit` has been called and is never resubmitted; it is for log
|
|
2928
|
+
* correlation only. The launch's identity is `clientLaunchId`:
|
|
2929
|
+
*
|
|
2930
|
+
* 1. Read `getLaunchJob(clientLaunchId)`. It needs `launch:read` on the key; a key whose scopes
|
|
2931
|
+
* were named by hand holds it only if it was named. A scope refusal is not "not found".
|
|
2932
|
+
* 2. `confirmed`: the bundle landed. The mint is on the job. Stop.
|
|
2933
|
+
* 3. 404 `JOB_NOT_FOUND`: the build never began. Call `launchAtomic()` again with the same id.
|
|
2934
|
+
* 4. `failed`: the bundle did not land. Call `launchAtomic()` again with the SAME id; the server
|
|
2935
|
+
* starts a clean attempt under it.
|
|
2936
|
+
* 5. Any other status: the bundle may still land. Wait, then read the job again or call
|
|
2937
|
+
* `launchAtomic()` again with the SAME id and the same body. The server resolves the first
|
|
2938
|
+
* attempt before doing anything new. It answers 409 `IDEMPOTENCY_CONFLICT` while the first
|
|
2939
|
+
* attempt is unresolved or has just been found landed, so on that 409 read the job again. It
|
|
2940
|
+
* builds a fresh bundle only when the first is proven reverted. This same-id call can take
|
|
2941
|
+
* about 95 seconds to answer, because the server waits on the chain.
|
|
2942
|
+
* 6. Never send a new id while the job is not `failed`: that is a second token. This includes
|
|
2943
|
+
* the 409 whose message says "use a new clientLaunchId to launch again": read the job first,
|
|
2944
|
+
* because that message is also what a caller sees for a launch it does not yet know landed.
|
|
2945
|
+
* 7. One state has no terminal answer today. The same-id call keeps answering 409 "This launch
|
|
2946
|
+
* was sent; its original attempt must be resolved" and the job is neither `confirmed` nor
|
|
2947
|
+
* `failed`, still, a few minutes after the submit (a blockhash lives about 90 seconds and the
|
|
2948
|
+
* rebuild itself can take about 95). Stop: do not send a new id on a timer, and report the
|
|
2949
|
+
* `clientLaunchId` and `bundleId` to whoever operates the key.
|
|
2950
|
+
* 8. Treat every `timeout` as unresolved. `retryable` is `false` on every one the server returns
|
|
2951
|
+
* today, and it is never permission to relaunch under a new id.
|
|
2952
|
+
*/
|
|
2953
|
+
launchAtomic(req: LaunchAtomicRequest): Promise<LaunchAtomicResult>;
|
|
2954
|
+
/** launchAtomic()'s steps. Every error leaves through launchAtomic()'s runOperation(), which adds the ids. */
|
|
2955
|
+
private launchAtomicSteps;
|
|
2745
2956
|
/**
|
|
2746
2957
|
* The `EvmRpc` seam packages/sdk/src/evm-tx.ts's helpers are built against, built from this
|
|
2747
2958
|
* client's own `jsonRpcCall`/`jsonRpcCallRaw` closed over `evmRpcUrl`. Callers must have already
|
|
@@ -2825,6 +3036,17 @@ export declare class CandleClient {
|
|
|
2825
3036
|
private signLinkedTypedData;
|
|
2826
3037
|
private requireKey;
|
|
2827
3038
|
private headers;
|
|
3039
|
+
/**
|
|
3040
|
+
* Runs a one-call write method's steps so that every exit exposes the idempotency id it sent
|
|
3041
|
+
* (see the module doc, "The id on every outcome"). The steps pass `op` to the request that
|
|
3042
|
+
* carries the id. Until that request is handed to fetch, an error leaves unchanged: nothing
|
|
3043
|
+
* reached the server, so there is nothing to replay. After it, `withOperationIds()` puts the
|
|
3044
|
+
* ids on a `CandleApiError`/`JsonRpcError` and wraps anything else in a `CandleOperationError`.
|
|
3045
|
+
*
|
|
3046
|
+
* Called once per public method, never nested: an inner step that is itself a one-call method
|
|
3047
|
+
* (launchAtomic()'s build) runs its plain request, so the error is wrapped once, here.
|
|
3048
|
+
*/
|
|
3049
|
+
private runOperation;
|
|
2828
3050
|
private requestJson;
|
|
2829
3051
|
private parseResponse;
|
|
2830
3052
|
/**
|
|
@@ -2855,7 +3077,7 @@ export declare function __setEvmReceiptWaitForTest(opts: {
|
|
|
2855
3077
|
pollMs?: number;
|
|
2856
3078
|
} | null): void;
|
|
2857
3079
|
/** This build's own version. Kept in lockstep with package.json by the release-bump CI guard. */
|
|
2858
|
-
export declare const SDK_VERSION = "0.
|
|
3080
|
+
export declare const SDK_VERSION = "0.5.0";
|
|
2859
3081
|
/** Test seam: the once-per-process latch would otherwise weld the suite's first case to the rest. */
|
|
2860
3082
|
export declare function __resetSdkUpdateNoticeForTest(): void;
|
|
2861
3083
|
//# sourceMappingURL=client.d.ts.map
|