@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/dist/index.js CHANGED
@@ -161,6 +161,51 @@ class JsonRpcError extends Error {
161
161
  this.data = args.data;
162
162
  }
163
163
  }
164
+ function operationErrorMessage(cause, ids, flow) {
165
+ const original = cause instanceof Error ? cause.message : String(cause);
166
+ const isTrade = ids.clientTradeId !== undefined;
167
+ const idName = isTrade ? "clientTradeId" : "clientLaunchId";
168
+ const noun = isTrade ? "trade" : "launch";
169
+ const read = isTrade ? "getTradeJob()" : "getLaunchJob()";
170
+ const bundle = ids.bundleId !== undefined ? ` (bundleId ${ids.bundleId}, single-use: for logs only, never resubmitted)` : "";
171
+ const head = `${original} -- ${idName} ${ids.clientTradeId ?? ids.clientLaunchId}${bundle}: ` + "the request may or may not have reached Candle.";
172
+ const tail = `A call with a new id, or with none, is a second, independent ${noun}.`;
173
+ if (flow === "server-executed") {
174
+ return `${head} Read the job for this id (${read}), or retry the call with this same ${idName} and the same request. ${tail}`;
175
+ }
176
+ 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}`;
177
+ }
178
+
179
+ class CandleOperationError extends Error {
180
+ constructor(args) {
181
+ super(operationErrorMessage(args.cause, args.ids, args.flow), { cause: args.cause });
182
+ this.name = "CandleOperationError";
183
+ Object.assign(this, definedIds(args.ids));
184
+ }
185
+ }
186
+ function definedIds(ids) {
187
+ return {
188
+ ...typeof ids.clientTradeId === "string" ? { clientTradeId: ids.clientTradeId } : {},
189
+ ...typeof ids.clientLaunchId === "string" ? { clientLaunchId: ids.clientLaunchId } : {},
190
+ ...typeof ids.bundleId === "string" ? { bundleId: ids.bundleId } : {}
191
+ };
192
+ }
193
+ function operationIds(error) {
194
+ if (error instanceof CandleApiError || error instanceof JsonRpcError || error instanceof TradeLandedFeeLegError || error instanceof CandleOperationError) {
195
+ return definedIds(error);
196
+ }
197
+ return {};
198
+ }
199
+ function withOperationIds(error, ids, flow) {
200
+ if (error instanceof CandleApiError || error instanceof JsonRpcError)
201
+ return Object.assign(error, definedIds(ids));
202
+ if (error instanceof TradeLandedFeeLegError)
203
+ return error;
204
+ if (error instanceof CandleOperationError) {
205
+ return new CandleOperationError({ cause: error.cause, ids: { ...operationIds(error), ...definedIds(ids) }, flow });
206
+ }
207
+ return new CandleOperationError({ cause: error, ids, flow });
208
+ }
164
209
  function envelopeError(body) {
165
210
  if (typeof body !== "object" || body === null)
166
211
  return null;
@@ -1105,25 +1150,33 @@ class CandleClient {
1105
1150
  }
1106
1151
  async launch(req) {
1107
1152
  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;
1153
+ const clientLaunchId = req.clientLaunchId ?? generateClientLaunchId();
1154
+ const body = { ...req, clientLaunchId };
1155
+ return this.runOperation({ clientLaunchId }, "server-executed", async (op) => {
1156
+ let lastError;
1157
+ for (let attempt = 0;attempt <= this.maxRetries; attempt++) {
1158
+ if (attempt > 0)
1159
+ await sleep2(retryDelayMs(attempt - 1));
1160
+ try {
1161
+ const result = await this.requestJson("POST", "/api/v1/launch/headless", body, op);
1162
+ return { ...result, clientLaunchId };
1163
+ } catch (error) {
1164
+ if (!isRetryableLaunchFailure(error))
1165
+ throw error;
1166
+ lastError = error;
1167
+ }
1119
1168
  }
1120
- }
1121
- throw lastError;
1169
+ throw lastError;
1170
+ });
1122
1171
  }
1123
1172
  async launchAsync(req) {
1124
1173
  this.requireKey("launchAsync()");
1125
- const body = { ...req, clientLaunchId: req.clientLaunchId ?? generateClientLaunchId(), async: true };
1126
- return this.requestJson("POST", "/api/v1/launch/headless", body);
1174
+ const clientLaunchId = req.clientLaunchId ?? generateClientLaunchId();
1175
+ const body = { ...req, clientLaunchId, async: true };
1176
+ return this.runOperation({ clientLaunchId }, "server-executed", async (op) => {
1177
+ const accepted = await this.requestJson("POST", "/api/v1/launch/headless", body, op);
1178
+ return { ...accepted, clientLaunchId: accepted.clientLaunchId ?? clientLaunchId };
1179
+ });
1127
1180
  }
1128
1181
  async getLaunchJob(clientLaunchId) {
1129
1182
  this.requireKey("getLaunchJob()");
@@ -1241,16 +1294,28 @@ class CandleClient {
1241
1294
  return this.requestJson("POST", "/api/v1/launch/self/dry-run", req);
1242
1295
  }
1243
1296
  async buildSelfLaunch(req) {
1297
+ return this.postSelfLaunchBuild(req);
1298
+ }
1299
+ async postSelfLaunchBuild(req, op) {
1244
1300
  this.requireKey("buildSelfLaunch()");
1245
- return this.requestJson("POST", "/api/v1/launch/self/build", req);
1301
+ return this.requestJson("POST", "/api/v1/launch/self/build", req, op);
1246
1302
  }
1247
1303
  async confirmSelfLaunch(req) {
1248
1304
  this.requireKey("confirmSelfLaunch()");
1249
- return this.requestJson("POST", "/api/v1/launch/self/confirm", req);
1305
+ const result = await this.requestJson("POST", "/api/v1/launch/self/confirm", req);
1306
+ return { ...result, clientLaunchId: req.clientLaunchId };
1250
1307
  }
1251
1308
  async buildTrade(req) {
1309
+ return this.postTradeBuild(req);
1310
+ }
1311
+ async postTradeBuild(req, op) {
1252
1312
  this.requireKey("buildTrade()");
1253
- return this.requestJson("POST", "/api/v1/trade/agent/build", req);
1313
+ return this.requestJson("POST", "/api/v1/trade/agent/build", req, op);
1314
+ }
1315
+ async getTradeJob(clientTradeId) {
1316
+ this.requireKey("getTradeJob()");
1317
+ const body = await this.requestJson("GET", `/api/v1/trade/agent/jobs/${encodeURIComponent(clientTradeId)}`);
1318
+ return body.job;
1254
1319
  }
1255
1320
  async confirmTrade(req) {
1256
1321
  this.requireKey("confirmTrade()");
@@ -1339,15 +1404,22 @@ class CandleClient {
1339
1404
  ...req.maxSlippageBps !== undefined ? { maxSlippageBps: req.maxSlippageBps } : {},
1340
1405
  ...req.quoteAsset !== undefined ? { quoteAsset: req.quoteAsset } : {}
1341
1406
  };
1407
+ const flow = req.from === "main" ? "server-executed" : "caller-broadcast";
1408
+ return this.runOperation({ clientTradeId }, flow, async (op) => {
1409
+ const result = await this.tradeSteps(req, buildReq, op);
1410
+ return typeof result.clientTradeId === "string" ? result : { ...result, clientTradeId };
1411
+ });
1412
+ }
1413
+ async tradeSteps(req, buildReq, op) {
1342
1414
  if (req.from === "main") {
1343
- const result = await this.buildTrade(buildReq);
1415
+ const result = await this.postTradeBuild(buildReq, op);
1344
1416
  if (result.status !== "executed") {
1345
1417
  throw new Error(`trade({ from: "main" }) expected an executed result but got status "${result.status}"`);
1346
1418
  }
1347
1419
  return result;
1348
1420
  }
1349
1421
  const { linkedWalletId, privyWalletId } = req.from;
1350
- const built = await this.buildTrade(buildReq);
1422
+ const built = await this.postTradeBuild(buildReq, op);
1351
1423
  if (built.status !== "built") {
1352
1424
  return built;
1353
1425
  }
@@ -1426,11 +1498,12 @@ class CandleClient {
1426
1498
  }
1427
1499
  async selfLaunch(req) {
1428
1500
  const { privyWalletId, ...launchReq } = req;
1429
- const body = {
1430
- ...launchReq,
1431
- clientLaunchId: launchReq.clientLaunchId ?? generateClientLaunchId()
1432
- };
1433
- const built = await this.buildSelfLaunch(body);
1501
+ const clientLaunchId = launchReq.clientLaunchId ?? generateClientLaunchId();
1502
+ const body = { ...launchReq, clientLaunchId };
1503
+ return this.runOperation({ clientLaunchId }, "caller-broadcast", (op) => this.selfLaunchSteps(body, privyWalletId, op));
1504
+ }
1505
+ async selfLaunchSteps(body, privyWalletId, op) {
1506
+ const built = await this.postSelfLaunchBuild(body, op);
1434
1507
  if (typeof built.transaction === "string") {
1435
1508
  let unsignedTransactionBase64 = built.transaction;
1436
1509
  let clientLaunchId = built.clientLaunchId;
@@ -1449,7 +1522,7 @@ class CandleClient {
1449
1522
  throw error;
1450
1523
  if (attempt === MAX_BLOCKHASH_REBUILDS)
1451
1524
  throw withRpcLagHint(error);
1452
- const rebuilt = await this.buildSelfLaunch(body);
1525
+ const rebuilt = await this.postSelfLaunchBuild(body, op);
1453
1526
  if (typeof rebuilt.transaction !== "string")
1454
1527
  throw error;
1455
1528
  unsignedTransactionBase64 = rebuilt.transaction;
@@ -1502,8 +1575,13 @@ class CandleClient {
1502
1575
  }
1503
1576
  async buildAtomicLaunch(req) {
1504
1577
  this.requireKey("buildAtomicLaunch()");
1505
- const body = { ...req, clientLaunchId: req.clientLaunchId ?? generateClientLaunchId() };
1506
- return this.requestJson("POST", "/api/v1/launch/atomic/build", body);
1578
+ const clientLaunchId = req.clientLaunchId ?? generateClientLaunchId();
1579
+ const body = { ...req, clientLaunchId };
1580
+ return this.runOperation({ clientLaunchId }, "server-executed", (op) => this.postAtomicBuild(body, clientLaunchId, op));
1581
+ }
1582
+ async postAtomicBuild(body, clientLaunchId, op) {
1583
+ const built = await this.requestJson("POST", "/api/v1/launch/atomic/build", body, op);
1584
+ return { ...built, clientLaunchId };
1507
1585
  }
1508
1586
  async submitAtomicLaunch(req) {
1509
1587
  this.requireKey("submitAtomicLaunch()");
@@ -1526,14 +1604,24 @@ class CandleClient {
1526
1604
  throw new Error(`submitAtomicLaunch(): unexpected 200 response shape: ${text}`);
1527
1605
  }
1528
1606
  async launchAtomic(req) {
1607
+ this.requireKey("buildAtomicLaunch()");
1529
1608
  const { payer, firstBuys, ...launchFields } = req;
1609
+ const clientLaunchId = launchFields.clientLaunchId ?? generateClientLaunchId();
1530
1610
  const buildReq = {
1531
1611
  ...launchFields,
1532
- clientLaunchId: launchFields.clientLaunchId ?? generateClientLaunchId(),
1612
+ clientLaunchId,
1533
1613
  payer: toAtomicWirePayer(payer),
1534
1614
  firstBuys: firstBuys.map((leg) => ({ payer: toAtomicWirePayer(leg.payer), amountRaw: leg.amountRaw }))
1535
1615
  };
1536
- const built = await this.buildAtomicLaunch(buildReq);
1616
+ return this.runOperation({ clientLaunchId }, "server-executed", async (op) => {
1617
+ const outcome = await this.launchAtomicSteps(req, buildReq, clientLaunchId, op);
1618
+ return { ...outcome, clientLaunchId };
1619
+ });
1620
+ }
1621
+ async launchAtomicSteps(req, buildReq, clientLaunchId, op) {
1622
+ const { payer, firstBuys } = req;
1623
+ const built = await this.postAtomicBuild(buildReq, clientLaunchId, op);
1624
+ op.ids.bundleId = built.bundleId;
1537
1625
  const signedTxsBase64 = [];
1538
1626
  for (const leg of built.legs) {
1539
1627
  if (leg.signer !== "client")
@@ -1869,12 +1957,25 @@ class CandleClient {
1869
1957
  headers["x-api-key"] = this.apiKey;
1870
1958
  return headers;
1871
1959
  }
1872
- async requestJson(method, path, body) {
1873
- const res = await this.fetchImpl(`${this.apiUrl}${path}`, {
1960
+ async runOperation(ids, flow, steps) {
1961
+ const op = { sent: false, ids: { ...ids } };
1962
+ try {
1963
+ return await steps(op);
1964
+ } catch (error) {
1965
+ if (!op.sent)
1966
+ throw error;
1967
+ throw withOperationIds(error, op.ids, flow);
1968
+ }
1969
+ }
1970
+ async requestJson(method, path, body, op) {
1971
+ const init = {
1874
1972
  method,
1875
1973
  headers: this.headers({ json: body !== undefined }),
1876
1974
  ...body !== undefined ? { body: JSON.stringify(body) } : {}
1877
- });
1975
+ };
1976
+ if (op)
1977
+ op.sent = true;
1978
+ const res = await this.fetchImpl(`${this.apiUrl}${path}`, init);
1878
1979
  return this.parseResponse(res);
1879
1980
  }
1880
1981
  async parseResponse(res) {
@@ -1997,7 +2098,7 @@ function generateClientLaunchId() {
1997
2098
  function generateClientTradeId() {
1998
2099
  return generateSdkId();
1999
2100
  }
2000
- var SDK_VERSION = "0.4.11";
2101
+ var SDK_VERSION = "0.5.0";
2001
2102
  var sdkUpdateWarned = false;
2002
2103
  function noteLatestSdkVersion(value) {
2003
2104
  if (sdkUpdateWarned || !value || !/^\d+\.\d+\.\d+$/.test(value))
@@ -2361,6 +2462,7 @@ export {
2361
2462
  planPromotionLine,
2362
2463
  planLabel,
2363
2464
  perpsDepositProblem,
2465
+ operationIds,
2364
2466
  isSolanaRpcErrorData,
2365
2467
  hyperliquidActionHash,
2366
2468
  generateSignerKeypair,
@@ -2375,6 +2477,7 @@ export {
2375
2477
  HYPERLIQUID_EXCHANGE_URLS,
2376
2478
  HYPERLIQUID_ALLOWED_ACTION_TYPES,
2377
2479
  EncryptedFileSecretStore,
2480
+ CandleOperationError,
2378
2481
  CandleClient,
2379
2482
  CandleApiError,
2380
2483
  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.5.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",