@candledottv/agent-sdk 0.4.11 → 0.6.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/dist/index.js CHANGED
@@ -3,6 +3,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // src/client.ts
5
5
  import { keccak_256 as keccak_2562 } from "@noble/hashes/sha3";
6
+ import { base58 as base582 } from "@scure/base";
6
7
 
7
8
  // src/authorization-signature.ts
8
9
  import canonicalize from "canonicalize";
@@ -78,6 +79,7 @@ async function buildPrivyAuthorizationSignature(params) {
78
79
  var BRIDGE_ERROR_CODES = ["BRIDGE_DESTINATION_MISSING", "RELAY_STEP_REFUSED", "BRIDGE_IN_FLIGHT"];
79
80
 
80
81
  class CandleApiError extends Error {
82
+ legs;
81
83
  code;
82
84
  status;
83
85
  retryable;
@@ -94,9 +96,12 @@ class CandleApiError extends Error {
94
96
  feeRaw;
95
97
  quoteAsset;
96
98
  treasury;
99
+ reason;
100
+ retryAfter;
97
101
  constructor(args) {
98
102
  super(args.message);
99
103
  this.name = "CandleApiError";
104
+ this.legs = args.legs;
100
105
  this.code = args.code;
101
106
  this.status = args.status;
102
107
  this.retryable = args.retryable;
@@ -121,6 +126,45 @@ class CandleApiError extends Error {
121
126
  this.quoteAsset = args.quoteAsset;
122
127
  if (args.treasury !== undefined)
123
128
  this.treasury = args.treasury;
129
+ if (args.reason !== undefined)
130
+ this.reason = args.reason;
131
+ if (args.retryAfter !== undefined)
132
+ this.retryAfter = args.retryAfter;
133
+ }
134
+ }
135
+
136
+ class TradeLegOutcomeError extends Error {
137
+ clientTradeId;
138
+ leg;
139
+ outcome;
140
+ txHash;
141
+ landedLegs;
142
+ cause;
143
+ constructor(args) {
144
+ super((args.cause instanceof Error ? args.cause.message + ". " : "") + "A Hood trade leg failed. Inspect txHash and landedLegs on chain; read getTradeJob(clientTradeId) or call trade with the same id and body for Candle's view. Never retry a retryable:false answer.", { cause: args.cause });
145
+ this.name = "TradeLegOutcomeError";
146
+ this.clientTradeId = args.clientTradeId;
147
+ this.leg = args.leg;
148
+ this.outcome = args.outcome;
149
+ this.txHash = args.txHash;
150
+ this.landedLegs = args.landedLegs;
151
+ this.cause = args.cause;
152
+ }
153
+ }
154
+
155
+ class TradeLandedUnconfirmedError extends Error {
156
+ clientTradeId;
157
+ tradeTxHash;
158
+ feeTxHash;
159
+ stage = "executed";
160
+ cause;
161
+ constructor(args) {
162
+ super("The trade is on chain and not booked. Read getTradeJob(clientTradeId) before calling confirmTrade again with these hashes. If the row is failed, this id cannot be recorded with /confirm; retain the hashes and do not retry or rebuild.", { cause: args.cause });
163
+ this.name = "TradeLandedUnconfirmedError";
164
+ this.clientTradeId = args.clientTradeId;
165
+ this.tradeTxHash = args.tradeTxHash;
166
+ this.feeTxHash = args.feeTxHash;
167
+ this.cause = args.cause;
124
168
  }
125
169
  }
126
170
 
@@ -161,6 +205,51 @@ class JsonRpcError extends Error {
161
205
  this.data = args.data;
162
206
  }
163
207
  }
208
+ function operationErrorMessage(cause, ids, flow) {
209
+ const original = cause instanceof Error ? cause.message : String(cause);
210
+ const isTrade = ids.clientTradeId !== undefined;
211
+ const idName = isTrade ? "clientTradeId" : "clientLaunchId";
212
+ const noun = isTrade ? "trade" : "launch";
213
+ const read = isTrade ? "getTradeJob()" : "getLaunchJob()";
214
+ const bundle = ids.bundleId !== undefined ? ` (bundleId ${ids.bundleId}, single-use: for logs only, never resubmitted)` : "";
215
+ const head = `${original} -- ${idName} ${ids.clientTradeId ?? ids.clientLaunchId}${bundle}: ` + "the request may or may not have reached Candle.";
216
+ const tail = `A call with a new id, or with none, is a second, independent ${noun}.`;
217
+ if (flow === "server-executed") {
218
+ return `${head} Read the job for this id (${read}), or retry the call with this same ${idName} and the same request. ${tail}`;
219
+ }
220
+ return `${head} This id identifies the job: read the job for this id (${read}), and check the wallet's recent ` + `transactions before sending again, because a call under the same id can sign and broadcast a second ` + `transaction. ${tail}`;
221
+ }
222
+
223
+ class CandleOperationError extends Error {
224
+ constructor(args) {
225
+ super(operationErrorMessage(args.cause, args.ids, args.flow), { cause: args.cause });
226
+ this.name = "CandleOperationError";
227
+ Object.assign(this, definedIds(args.ids));
228
+ }
229
+ }
230
+ function definedIds(ids) {
231
+ return {
232
+ ...typeof ids.clientTradeId === "string" ? { clientTradeId: ids.clientTradeId } : {},
233
+ ...typeof ids.clientLaunchId === "string" ? { clientLaunchId: ids.clientLaunchId } : {},
234
+ ...typeof ids.bundleId === "string" ? { bundleId: ids.bundleId } : {}
235
+ };
236
+ }
237
+ function operationIds(error) {
238
+ if (error instanceof CandleApiError || error instanceof JsonRpcError || error instanceof TradeLandedFeeLegError || error instanceof TradeLegOutcomeError || error instanceof TradeLandedUnconfirmedError || error instanceof CandleOperationError) {
239
+ return definedIds(error);
240
+ }
241
+ return {};
242
+ }
243
+ function withOperationIds(error, ids, flow) {
244
+ if (error instanceof CandleApiError || error instanceof JsonRpcError)
245
+ return Object.assign(error, definedIds(ids));
246
+ if (error instanceof TradeLandedFeeLegError || error instanceof TradeLegOutcomeError || error instanceof TradeLandedUnconfirmedError)
247
+ return error;
248
+ if (error instanceof CandleOperationError) {
249
+ return new CandleOperationError({ cause: error.cause, ids: { ...operationIds(error), ...definedIds(ids) }, flow });
250
+ }
251
+ return new CandleOperationError({ cause: error, ids, flow });
252
+ }
164
253
  function envelopeError(body) {
165
254
  if (typeof body !== "object" || body === null)
166
255
  return null;
@@ -182,13 +271,16 @@ function envelopeError(body) {
182
271
  message: error.message,
183
272
  ...typeof error.field === "string" ? { field: error.field } : {},
184
273
  ...typeof error.retryable === "boolean" ? { retryable: error.retryable } : {},
274
+ ...Array.isArray(error.legs) ? { legs: error.legs } : {},
185
275
  ...typeof error.stage === "string" ? { stage: error.stage } : {},
186
276
  ...typeof error.signature === "string" ? { signature: error.signature } : {},
187
277
  ...typeof error.mint === "string" ? { mint: error.mint } : {},
188
278
  ...typeof error.recorded === "boolean" ? { recorded: error.recorded } : {},
189
279
  ...typeof error.feeRaw === "string" ? { feeRaw: error.feeRaw } : {},
190
280
  ...typeof error.quoteAsset === "string" ? { quoteAsset: error.quoteAsset } : {},
191
- ...typeof error.treasury === "string" ? { treasury: error.treasury } : {}
281
+ ...typeof error.treasury === "string" ? { treasury: error.treasury } : {},
282
+ ...typeof error.reason === "string" ? { reason: error.reason } : {},
283
+ ...typeof error.retryAfter === "number" ? { retryAfter: error.retryAfter } : {}
192
284
  };
193
285
  }
194
286
  function candleApiErrorFromResponse(status, bodyText) {
@@ -1074,6 +1166,45 @@ class CandleClient {
1074
1166
  async verify(chain, mint) {
1075
1167
  return this.requestJson("GET", `/api/v1/verify/${chain}/${encodeURIComponent(mint)}`);
1076
1168
  }
1169
+ async getOwnProfile() {
1170
+ this.requireKey("getOwnProfile()");
1171
+ return this.requestJson("GET", "/api/v1/agent/keys/self/public-profile");
1172
+ }
1173
+ async updateOwnProfile(patch) {
1174
+ this.requireKey("updateOwnProfile()");
1175
+ return this.requestJson("PATCH", "/api/v1/agent/keys/self/public-profile", patch);
1176
+ }
1177
+ async uploadProfileImage(kind, bytes, contentType) {
1178
+ this.requireKey("uploadProfileImage()");
1179
+ const form = new FormData;
1180
+ form.set("kind", kind);
1181
+ form.set("image", new Blob([bytes], { type: contentType }), "profile.image");
1182
+ const headers = this.headers();
1183
+ delete headers["Content-Type"];
1184
+ const res = await this.fetchImpl(`${this.apiUrl}/api/v1/uploads/agent-profile-image`, {
1185
+ method: "POST",
1186
+ headers,
1187
+ body: form
1188
+ });
1189
+ return this.parseResponse(res);
1190
+ }
1191
+ async createPost(req) {
1192
+ this.requireKey("createPost()");
1193
+ return this.requestJson("POST", "/api/v1/agent/posts", req);
1194
+ }
1195
+ async deletePost(postId) {
1196
+ this.requireKey("deletePost()");
1197
+ return this.requestJson("DELETE", `/api/v1/agent/posts/${encodeURIComponent(postId)}`);
1198
+ }
1199
+ async listAgentPosts(profileId, options = {}) {
1200
+ const params = new URLSearchParams;
1201
+ if (options.limit !== undefined)
1202
+ params.set("limit", String(options.limit));
1203
+ if (options.cursor !== undefined)
1204
+ params.set("cursor", options.cursor);
1205
+ const body = await this.requestJson("GET", `/api/v1/agent/records/${encodeURIComponent(profileId)}/posts${params.size ? `?${params}` : ""}`);
1206
+ return { posts: body.posts, cursor: body.cursor ?? null };
1207
+ }
1077
1208
  async getAgentProfile(idOrWallet) {
1078
1209
  const body = await this.requestJson("GET", `/api/v1/users/${encodeURIComponent(idOrWallet)}/agent`);
1079
1210
  return body.agent;
@@ -1105,25 +1236,33 @@ class CandleClient {
1105
1236
  }
1106
1237
  async launch(req) {
1107
1238
  this.requireKey("launch()");
1108
- const body = { ...req, clientLaunchId: req.clientLaunchId ?? generateClientLaunchId() };
1109
- let lastError;
1110
- for (let attempt = 0;attempt <= this.maxRetries; attempt++) {
1111
- if (attempt > 0)
1112
- await sleep2(retryDelayMs(attempt - 1));
1113
- try {
1114
- return await this.requestJson("POST", "/api/v1/launch/headless", body);
1115
- } catch (error) {
1116
- if (!isRetryableLaunchFailure(error))
1117
- throw error;
1118
- lastError = error;
1239
+ const clientLaunchId = req.clientLaunchId ?? generateClientLaunchId();
1240
+ const body = { ...req, clientLaunchId };
1241
+ return this.runOperation({ clientLaunchId }, "server-executed", async (op) => {
1242
+ let lastError;
1243
+ for (let attempt = 0;attempt <= this.maxRetries; attempt++) {
1244
+ if (attempt > 0)
1245
+ await sleep2(retryDelayMs(attempt - 1));
1246
+ try {
1247
+ const result = await this.requestJson("POST", "/api/v1/launch/headless", body, op);
1248
+ return { ...result, clientLaunchId };
1249
+ } catch (error) {
1250
+ if (!isRetryableLaunchFailure(error))
1251
+ throw error;
1252
+ lastError = error;
1253
+ }
1119
1254
  }
1120
- }
1121
- throw lastError;
1255
+ throw lastError;
1256
+ });
1122
1257
  }
1123
1258
  async launchAsync(req) {
1124
1259
  this.requireKey("launchAsync()");
1125
- const body = { ...req, clientLaunchId: req.clientLaunchId ?? generateClientLaunchId(), async: true };
1126
- return this.requestJson("POST", "/api/v1/launch/headless", body);
1260
+ const clientLaunchId = req.clientLaunchId ?? generateClientLaunchId();
1261
+ const body = { ...req, clientLaunchId, async: true };
1262
+ return this.runOperation({ clientLaunchId }, "server-executed", async (op) => {
1263
+ const accepted = await this.requestJson("POST", "/api/v1/launch/headless", body, op);
1264
+ return { ...accepted, clientLaunchId: accepted.clientLaunchId ?? clientLaunchId };
1265
+ });
1127
1266
  }
1128
1267
  async getLaunchJob(clientLaunchId) {
1129
1268
  this.requireKey("getLaunchJob()");
@@ -1241,16 +1380,28 @@ class CandleClient {
1241
1380
  return this.requestJson("POST", "/api/v1/launch/self/dry-run", req);
1242
1381
  }
1243
1382
  async buildSelfLaunch(req) {
1383
+ return this.postSelfLaunchBuild(req);
1384
+ }
1385
+ async postSelfLaunchBuild(req, op) {
1244
1386
  this.requireKey("buildSelfLaunch()");
1245
- return this.requestJson("POST", "/api/v1/launch/self/build", req);
1387
+ return this.requestJson("POST", "/api/v1/launch/self/build", req, op);
1246
1388
  }
1247
1389
  async confirmSelfLaunch(req) {
1248
1390
  this.requireKey("confirmSelfLaunch()");
1249
- return this.requestJson("POST", "/api/v1/launch/self/confirm", req);
1391
+ const result = await this.requestJson("POST", "/api/v1/launch/self/confirm", req);
1392
+ return { ...result, clientLaunchId: req.clientLaunchId };
1250
1393
  }
1251
1394
  async buildTrade(req) {
1395
+ return this.postTradeBuild(req);
1396
+ }
1397
+ async postTradeBuild(req, op) {
1252
1398
  this.requireKey("buildTrade()");
1253
- return this.requestJson("POST", "/api/v1/trade/agent/build", req);
1399
+ return this.requestJson("POST", "/api/v1/trade/agent/build", req, op);
1400
+ }
1401
+ async getTradeJob(clientTradeId) {
1402
+ this.requireKey("getTradeJob()");
1403
+ const body = await this.requestJson("GET", `/api/v1/trade/agent/jobs/${encodeURIComponent(clientTradeId)}`);
1404
+ return body.job;
1254
1405
  }
1255
1406
  async confirmTrade(req) {
1256
1407
  this.requireKey("confirmTrade()");
@@ -1339,15 +1490,22 @@ class CandleClient {
1339
1490
  ...req.maxSlippageBps !== undefined ? { maxSlippageBps: req.maxSlippageBps } : {},
1340
1491
  ...req.quoteAsset !== undefined ? { quoteAsset: req.quoteAsset } : {}
1341
1492
  };
1493
+ const flow = req.from === "main" ? "server-executed" : "caller-broadcast";
1494
+ return this.runOperation({ clientTradeId }, flow, async (op) => {
1495
+ const result = await this.tradeSteps(req, buildReq, op);
1496
+ return typeof result.clientTradeId === "string" ? result : { ...result, clientTradeId };
1497
+ });
1498
+ }
1499
+ async tradeSteps(req, buildReq, op) {
1342
1500
  if (req.from === "main") {
1343
- const result = await this.buildTrade(buildReq);
1501
+ const result = await this.postTradeBuild(buildReq, op);
1344
1502
  if (result.status !== "executed") {
1345
1503
  throw new Error(`trade({ from: "main" }) expected an executed result but got status "${result.status}"`);
1346
1504
  }
1347
1505
  return result;
1348
1506
  }
1349
1507
  const { linkedWalletId, privyWalletId } = req.from;
1350
- const built = await this.buildTrade(buildReq);
1508
+ const built = await this.postTradeBuild(buildReq, op);
1351
1509
  if (built.status !== "built") {
1352
1510
  return built;
1353
1511
  }
@@ -1384,6 +1542,7 @@ class CandleClient {
1384
1542
  if (built.artifacts.feeTransfer) {
1385
1543
  legs.push({ kind: "feeTransfer", ...built.artifacts.feeTransfer });
1386
1544
  }
1545
+ const landedLegs = [];
1387
1546
  let tradeTxHash;
1388
1547
  let feeTxHash;
1389
1548
  for (let i = 0;i < legs.length; i++) {
@@ -1403,13 +1562,24 @@ class CandleClient {
1403
1562
  feeData,
1404
1563
  linkedWalletId,
1405
1564
  privyWalletId
1406
- }, { surfaceLegOutcome: leg.kind === "feeTransfer" });
1565
+ }, { surfaceLegOutcome: true });
1407
1566
  } catch (err) {
1408
1567
  if (leg.kind === "feeTransfer" && tradeTxHash) {
1409
1568
  throw tradeLandedFeeLegError({ clientTradeId: built.clientTradeId, tradeTxHash, err });
1410
1569
  }
1411
- throw err;
1570
+ if (err instanceof EvmLegOutcome && err.txHash !== undefined || landedLegs.length > 0) {
1571
+ throw new TradeLegOutcomeError({
1572
+ clientTradeId: built.clientTradeId,
1573
+ leg: leg.kind,
1574
+ outcome: err instanceof EvmLegOutcome ? err.kind : "not-broadcast",
1575
+ txHash: err instanceof EvmLegOutcome ? err.txHash : undefined,
1576
+ landedLegs: [...landedLegs],
1577
+ cause: err instanceof EvmLegOutcome ? err.cause : err
1578
+ });
1579
+ }
1580
+ throw err instanceof EvmLegOutcome ? err.cause : err;
1412
1581
  }
1582
+ landedLegs.push({ leg: leg.kind, txHash });
1413
1583
  if (leg.kind === "trade")
1414
1584
  tradeTxHash = txHash;
1415
1585
  if (leg.kind === "feeTransfer")
@@ -1418,29 +1588,38 @@ class CandleClient {
1418
1588
  if (!tradeTxHash) {
1419
1589
  throw new Error("trade(): Hood leg sequence completed without a trade leg");
1420
1590
  }
1421
- return this.confirmTrade({
1422
- clientTradeId: built.clientTradeId,
1423
- tradeTxHash,
1424
- ...feeTxHash ? { feeTxHash } : {}
1425
- });
1591
+ try {
1592
+ return await this.confirmTrade({
1593
+ clientTradeId: built.clientTradeId,
1594
+ tradeTxHash,
1595
+ ...feeTxHash ? { feeTxHash } : {}
1596
+ });
1597
+ } catch (cause) {
1598
+ throw new TradeLandedUnconfirmedError({ clientTradeId: built.clientTradeId, tradeTxHash, feeTxHash, cause });
1599
+ }
1426
1600
  }
1427
1601
  async selfLaunch(req) {
1428
1602
  const { privyWalletId, ...launchReq } = req;
1429
- const body = {
1430
- ...launchReq,
1431
- clientLaunchId: launchReq.clientLaunchId ?? generateClientLaunchId()
1432
- };
1433
- const built = await this.buildSelfLaunch(body);
1603
+ const clientLaunchId = launchReq.clientLaunchId ?? generateClientLaunchId();
1604
+ const body = { ...launchReq, clientLaunchId };
1605
+ return this.runOperation({ clientLaunchId }, "caller-broadcast", (op) => this.selfLaunchSteps(body, privyWalletId, op));
1606
+ }
1607
+ async selfLaunchSteps(body, privyWalletId, op) {
1608
+ const built = await this.postSelfLaunchBuild(body, op);
1434
1609
  if (typeof built.transaction === "string") {
1435
1610
  let unsignedTransactionBase64 = built.transaction;
1436
1611
  let clientLaunchId = built.clientLaunchId;
1612
+ let signed;
1437
1613
  for (let attempt = 0;attempt <= MAX_BLOCKHASH_REBUILDS; attempt++) {
1438
- const signed = await this.signLinkedTransaction({
1439
- linkedWalletId: body.linkedWalletId,
1440
- privyWalletId,
1441
- chain: "solana",
1442
- unsignedTransactionBase64
1443
- });
1614
+ if (signed?.forTransaction !== unsignedTransactionBase64) {
1615
+ const result = await this.signLinkedTransaction({
1616
+ linkedWalletId: body.linkedWalletId,
1617
+ privyWalletId,
1618
+ chain: "solana",
1619
+ unsignedTransactionBase64
1620
+ });
1621
+ signed = { forTransaction: unsignedTransactionBase64, ...result };
1622
+ }
1444
1623
  try {
1445
1624
  const signature = await this.broadcastSignedTransaction("solana", signed.signedTransaction, signed.encoding);
1446
1625
  return this.confirmSelfLaunch({ clientLaunchId, signature });
@@ -1449,7 +1628,20 @@ class CandleClient {
1449
1628
  throw error;
1450
1629
  if (attempt === MAX_BLOCKHASH_REBUILDS)
1451
1630
  throw withRpcLagHint(error);
1452
- const rebuilt = await this.buildSelfLaunch(body);
1631
+ let rebuilt;
1632
+ try {
1633
+ rebuilt = await this.postSelfLaunchBuild(body, op);
1634
+ } catch (buildError) {
1635
+ const reason = buildError instanceof CandleApiError ? buildError.reason : undefined;
1636
+ if (reason === "prior_attempt_in_flight")
1637
+ continue;
1638
+ if (reason === "prior_attempt_on_chain") {
1639
+ const signature = solanaSignedTxSignature(signed.signedTransaction, signed.encoding);
1640
+ if (signature)
1641
+ return this.confirmSelfLaunch({ clientLaunchId, signature });
1642
+ }
1643
+ throw buildError;
1644
+ }
1453
1645
  if (typeof rebuilt.transaction !== "string")
1454
1646
  throw error;
1455
1647
  unsignedTransactionBase64 = rebuilt.transaction;
@@ -1502,8 +1694,13 @@ class CandleClient {
1502
1694
  }
1503
1695
  async buildAtomicLaunch(req) {
1504
1696
  this.requireKey("buildAtomicLaunch()");
1505
- const body = { ...req, clientLaunchId: req.clientLaunchId ?? generateClientLaunchId() };
1506
- return this.requestJson("POST", "/api/v1/launch/atomic/build", body);
1697
+ const clientLaunchId = req.clientLaunchId ?? generateClientLaunchId();
1698
+ const body = { ...req, clientLaunchId };
1699
+ return this.runOperation({ clientLaunchId }, "server-executed", (op) => this.postAtomicBuild(body, clientLaunchId, op));
1700
+ }
1701
+ async postAtomicBuild(body, clientLaunchId, op) {
1702
+ const built = await this.requestJson("POST", "/api/v1/launch/atomic/build", body, op);
1703
+ return { ...built, clientLaunchId };
1507
1704
  }
1508
1705
  async submitAtomicLaunch(req) {
1509
1706
  this.requireKey("submitAtomicLaunch()");
@@ -1526,14 +1723,24 @@ class CandleClient {
1526
1723
  throw new Error(`submitAtomicLaunch(): unexpected 200 response shape: ${text}`);
1527
1724
  }
1528
1725
  async launchAtomic(req) {
1726
+ this.requireKey("buildAtomicLaunch()");
1529
1727
  const { payer, firstBuys, ...launchFields } = req;
1728
+ const clientLaunchId = launchFields.clientLaunchId ?? generateClientLaunchId();
1530
1729
  const buildReq = {
1531
1730
  ...launchFields,
1532
- clientLaunchId: launchFields.clientLaunchId ?? generateClientLaunchId(),
1731
+ clientLaunchId,
1533
1732
  payer: toAtomicWirePayer(payer),
1534
1733
  firstBuys: firstBuys.map((leg) => ({ payer: toAtomicWirePayer(leg.payer), amountRaw: leg.amountRaw }))
1535
1734
  };
1536
- const built = await this.buildAtomicLaunch(buildReq);
1735
+ return this.runOperation({ clientLaunchId }, "server-executed", async (op) => {
1736
+ const outcome = await this.launchAtomicSteps(req, buildReq, clientLaunchId, op);
1737
+ return { ...outcome, clientLaunchId };
1738
+ });
1739
+ }
1740
+ async launchAtomicSteps(req, buildReq, clientLaunchId, op) {
1741
+ const { payer, firstBuys } = req;
1742
+ const built = await this.postAtomicBuild(buildReq, clientLaunchId, op);
1743
+ op.ids.bundleId = built.bundleId;
1537
1744
  const signedTxsBase64 = [];
1538
1745
  for (const leg of built.legs) {
1539
1746
  if (leg.signer !== "client")
@@ -1605,7 +1812,7 @@ class CandleClient {
1605
1812
  if (!isDefiniteBroadcastRejection(err)) {
1606
1813
  throw new EvmLegOutcome({ kind: "unknown", txHash: predicted, cause: err });
1607
1814
  }
1608
- throw err;
1815
+ throw new EvmLegOutcome({ kind: "not-broadcast", txHash: predicted, cause: err });
1609
1816
  }
1610
1817
  try {
1611
1818
  await waitForReceipt(params.rpc, txHash, evmReceiptWaitForTest ?? {});
@@ -1869,12 +2076,25 @@ class CandleClient {
1869
2076
  headers["x-api-key"] = this.apiKey;
1870
2077
  return headers;
1871
2078
  }
1872
- async requestJson(method, path, body) {
1873
- const res = await this.fetchImpl(`${this.apiUrl}${path}`, {
2079
+ async runOperation(ids, flow, steps) {
2080
+ const op = { sent: false, ids: { ...ids } };
2081
+ try {
2082
+ return await steps(op);
2083
+ } catch (error) {
2084
+ if (!op.sent)
2085
+ throw error;
2086
+ throw withOperationIds(error, op.ids, flow);
2087
+ }
2088
+ }
2089
+ async requestJson(method, path, body, op) {
2090
+ const init = {
1874
2091
  method,
1875
2092
  headers: this.headers({ json: body !== undefined }),
1876
2093
  ...body !== undefined ? { body: JSON.stringify(body) } : {}
1877
- });
2094
+ };
2095
+ if (op)
2096
+ op.sent = true;
2097
+ const res = await this.fetchImpl(`${this.apiUrl}${path}`, init);
1878
2098
  return this.parseResponse(res);
1879
2099
  }
1880
2100
  async parseResponse(res) {
@@ -1949,7 +2169,7 @@ function tradeLandedFeeLegError(args) {
1949
2169
  tradeTxHash: args.tradeTxHash,
1950
2170
  cause: args.err.cause,
1951
2171
  feeOutcome: args.err.kind,
1952
- ...args.err.txHash !== undefined ? { feeTxHash: args.err.txHash } : {}
2172
+ ...args.err.kind !== "not-broadcast" && args.err.txHash !== undefined ? { feeTxHash: args.err.txHash } : {}
1953
2173
  });
1954
2174
  }
1955
2175
  return new TradeLandedFeeLegError({
@@ -1959,6 +2179,27 @@ function tradeLandedFeeLegError(args) {
1959
2179
  feeOutcome: "not-broadcast"
1960
2180
  });
1961
2181
  }
2182
+ function solanaSignedTxSignature(signedTransaction, encoding) {
2183
+ const enc = encoding.toLowerCase();
2184
+ let bytes;
2185
+ try {
2186
+ if (enc === "base64")
2187
+ bytes = fromBase64(signedTransaction);
2188
+ else if (enc === "base58")
2189
+ bytes = base582.decode(signedTransaction);
2190
+ else
2191
+ return;
2192
+ } catch {
2193
+ return;
2194
+ }
2195
+ const count = bytes[0];
2196
+ if (count === undefined || count === 0 || count >= 128 || bytes.length < 1 + count * 64)
2197
+ return;
2198
+ const first = bytes.subarray(1, 65);
2199
+ if (first.every((byte) => byte === 0))
2200
+ return;
2201
+ return base582.encode(first);
2202
+ }
1962
2203
  function evmSignedTxHash(signedTransaction, encoding) {
1963
2204
  const enc = encoding.toLowerCase();
1964
2205
  let bytes;
@@ -1997,7 +2238,7 @@ function generateClientLaunchId() {
1997
2238
  function generateClientTradeId() {
1998
2239
  return generateSdkId();
1999
2240
  }
2000
- var SDK_VERSION = "0.4.11";
2241
+ var SDK_VERSION = "0.6.0";
2001
2242
  var sdkUpdateWarned = false;
2002
2243
  function noteLatestSdkVersion(value) {
2003
2244
  if (sdkUpdateWarned || !value || !/^\d+\.\d+\.\d+$/.test(value))
@@ -2361,11 +2602,14 @@ export {
2361
2602
  planPromotionLine,
2362
2603
  planLabel,
2363
2604
  perpsDepositProblem,
2605
+ operationIds,
2364
2606
  isSolanaRpcErrorData,
2365
2607
  hyperliquidActionHash,
2366
2608
  generateSignerKeypair,
2367
2609
  formatPlanBps,
2368
2610
  encryptWalletKeyForImport,
2611
+ TradeLegOutcomeError,
2612
+ TradeLandedUnconfirmedError,
2369
2613
  TradeLandedFeeLegError,
2370
2614
  PLAN_CAPABILITY_NOTE,
2371
2615
  PLAN_CAPABILITY_LABELS,
@@ -2375,6 +2619,7 @@ export {
2375
2619
  HYPERLIQUID_EXCHANGE_URLS,
2376
2620
  HYPERLIQUID_ALLOWED_ACTION_TYPES,
2377
2621
  EncryptedFileSecretStore,
2622
+ CandleOperationError,
2378
2623
  CandleClient,
2379
2624
  CandleApiError,
2380
2625
  CANDLE_HYPERLIQUID_BUILDER_ADDRESS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@candledottv/agent-sdk",
3
- "version": "0.4.11",
3
+ "version": "0.6.0",
4
4
  "description": "TypeScript SDK for the Candle agent rail: typed headless launches, market reads, and webhook verification",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",