@candledottv/agent-sdk 0.5.0 → 0.6.1

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
 
@@ -142,6 +186,42 @@ class TradeLandedFeeLegError extends Error {
142
186
  this.feeTxHash = args.feeTxHash;
143
187
  }
144
188
  }
189
+ function selfLaunchOutcomeMessage(args) {
190
+ const reason = args.cause instanceof Error ? args.cause.message : String(args.cause);
191
+ const feeTxHash = "feeTxHash" in args ? args.feeTxHash : undefined;
192
+ const head = `launch executed; ${args.step === "confirm" ? "confirmation" : "fee"} incomplete: the Hood curve is on chain ` + `(createCurveTxHash ${args.createCurveTxHash}, clientLaunchId ${args.clientLaunchId}), and a later step ` + `failed (${reason}). Do NOT call selfLaunch() or buildSelfLaunch() again for this attempt, under this id ` + `or a new one: a rebuild can create a second curve. Keep the id and every hash on this error.`;
193
+ const confirmWith = (fee) => `confirmSelfLaunch({ clientLaunchId, signature: createCurveTxHash${fee} }) for clientLaunchId ${args.clientLaunchId}`;
194
+ if (args.step === "confirm") {
195
+ const fee = args.feeOutcome === "landed" ? `The fee transfer landed (feeTxHash ${feeTxHash}).` : "This build had no fee transfer, so none is owed.";
196
+ return `${head} ${fee} The confirm call was refused or its answer was lost, so Candle may or may not have ` + `recorded the launch: read getLaunchJob(clientLaunchId). If the job is not confirmed and this error's ` + `cause allows it, call ${confirmWith(args.feeOutcome === "landed" ? ", feeTxHash" : "")} with these same ` + `hashes. Nothing needs signing or sending.`;
197
+ }
198
+ if (args.feeOutcome === "unknown") {
199
+ return `${head} The fee transfer MAY have paid: its outcome is unknown. Do NOT pay it again. ` + (feeTxHash ? `Check feeTxHash ${feeTxHash} and the payer wallet's history; a hash alone does not prove payment. ` : "Its hash is not known: reconcile the payer wallet's transactions before any payment decision. ") + `Once that exact fee is verified successful, call ${confirmWith(", feeTxHash")}.`;
200
+ }
201
+ const proof = args.feeOutcome === "reverted" ? `its receipt reverted${feeTxHash ? ` (feeTxHash ${feeTxHash})` : ""}` : `it was not broadcast${feeTxHash ? ` (feeTxHash ${feeTxHash} names the refused transaction, not a payment)` : ""}`;
202
+ return `${head} The fee transfer did NOT pay: ${proof}. The launch is not recorded without it. Pay only the ` + `missing fee, to the original build's treasury in its quote asset and raw amount, then call ` + `${confirmWith(", feeTxHash")} with the successful fee hash.`;
203
+ }
204
+
205
+ class SelfLaunchOutcomeError extends Error {
206
+ name = "SelfLaunchOutcomeError";
207
+ chain = "hood";
208
+ clientLaunchId;
209
+ createCurveTxHash;
210
+ stage = "executed";
211
+ step;
212
+ feeOutcome;
213
+ cause;
214
+ constructor(args) {
215
+ super(selfLaunchOutcomeMessage(args), { cause: args.cause });
216
+ this.clientLaunchId = args.clientLaunchId;
217
+ this.createCurveTxHash = args.createCurveTxHash;
218
+ this.step = args.step;
219
+ this.feeOutcome = args.feeOutcome;
220
+ if ("feeTxHash" in args && args.feeTxHash !== undefined)
221
+ this.feeTxHash = args.feeTxHash;
222
+ this.cause = args.cause;
223
+ }
224
+ }
145
225
  function isSolanaRpcErrorData(data) {
146
226
  if (typeof data !== "object" || data === null)
147
227
  return false;
@@ -191,7 +271,7 @@ function definedIds(ids) {
191
271
  };
192
272
  }
193
273
  function operationIds(error) {
194
- if (error instanceof CandleApiError || error instanceof JsonRpcError || error instanceof TradeLandedFeeLegError || error instanceof CandleOperationError) {
274
+ if (error instanceof CandleApiError || error instanceof JsonRpcError || error instanceof TradeLandedFeeLegError || error instanceof TradeLegOutcomeError || error instanceof TradeLandedUnconfirmedError || error instanceof SelfLaunchOutcomeError || error instanceof CandleOperationError) {
195
275
  return definedIds(error);
196
276
  }
197
277
  return {};
@@ -199,7 +279,7 @@ function operationIds(error) {
199
279
  function withOperationIds(error, ids, flow) {
200
280
  if (error instanceof CandleApiError || error instanceof JsonRpcError)
201
281
  return Object.assign(error, definedIds(ids));
202
- if (error instanceof TradeLandedFeeLegError)
282
+ if (error instanceof TradeLandedFeeLegError || error instanceof TradeLegOutcomeError || error instanceof TradeLandedUnconfirmedError || error instanceof SelfLaunchOutcomeError)
203
283
  return error;
204
284
  if (error instanceof CandleOperationError) {
205
285
  return new CandleOperationError({ cause: error.cause, ids: { ...operationIds(error), ...definedIds(ids) }, flow });
@@ -227,13 +307,16 @@ function envelopeError(body) {
227
307
  message: error.message,
228
308
  ...typeof error.field === "string" ? { field: error.field } : {},
229
309
  ...typeof error.retryable === "boolean" ? { retryable: error.retryable } : {},
310
+ ...Array.isArray(error.legs) ? { legs: error.legs } : {},
230
311
  ...typeof error.stage === "string" ? { stage: error.stage } : {},
231
312
  ...typeof error.signature === "string" ? { signature: error.signature } : {},
232
313
  ...typeof error.mint === "string" ? { mint: error.mint } : {},
233
314
  ...typeof error.recorded === "boolean" ? { recorded: error.recorded } : {},
234
315
  ...typeof error.feeRaw === "string" ? { feeRaw: error.feeRaw } : {},
235
316
  ...typeof error.quoteAsset === "string" ? { quoteAsset: error.quoteAsset } : {},
236
- ...typeof error.treasury === "string" ? { treasury: error.treasury } : {}
317
+ ...typeof error.treasury === "string" ? { treasury: error.treasury } : {},
318
+ ...typeof error.reason === "string" ? { reason: error.reason } : {},
319
+ ...typeof error.retryAfter === "number" ? { retryAfter: error.retryAfter } : {}
237
320
  };
238
321
  }
239
322
  function candleApiErrorFromResponse(status, bodyText) {
@@ -368,6 +451,10 @@ var HYPERLIQUID_EXCHANGE_URLS = {
368
451
  mainnet: "https://api.hyperliquid.xyz/exchange",
369
452
  testnet: "https://api.hyperliquid-testnet.xyz/exchange"
370
453
  };
454
+ var HYPERLIQUID_INFO_URLS = {
455
+ mainnet: "https://api.hyperliquid.xyz/info",
456
+ testnet: "https://api.hyperliquid-testnet.xyz/info"
457
+ };
371
458
  var CANDLE_HYPERLIQUID_BUILDER_ADDRESS = null;
372
459
  var HYPERLIQUID_MAX_BUILDER_FEE_TENTHS_BPS = 100;
373
460
  var HYPERLIQUID_MAX_BUILDER_FEE_RATE = "0.1%";
@@ -670,6 +757,67 @@ async function hyperliquidCloseOrder(fetcher, network, address, params) {
670
757
  const size = asked < held ? String(params.size) : String(szi).replace(/^-/, "");
671
758
  return { asset, isBuy: signed < 0n, size };
672
759
  }
760
+ var HYPERLIQUID_DEFAULT_SLIPPAGE_BPS = 100;
761
+ var HYPERLIQUID_MID_DRIFT_BPS = 100;
762
+ async function hyperliquidOrderMarket(fetcher, network, address, method, params) {
763
+ const read = async (body) => {
764
+ const res = await fetcher(HYPERLIQUID_INFO_URLS[network], {
765
+ method: "POST",
766
+ headers: { "content-type": "application/json" },
767
+ body: JSON.stringify(body)
768
+ });
769
+ if (!res.ok)
770
+ throw new Error("Cannot read the market from Hyperliquid");
771
+ return JSON.parse(await res.text());
772
+ };
773
+ let coin = String(params.coin ?? "").toUpperCase();
774
+ let order;
775
+ if (method === "modify") {
776
+ const status = await read({ type: "orderStatus", user: address, oid: params.cloid });
777
+ const resting = status?.order?.order ?? null;
778
+ if (!resting || typeof resting.coin !== "string" || decimal(resting.limitPx) === null)
779
+ throw new Error("No resting order with this cloid on Hyperliquid");
780
+ coin = resting.coin.toUpperCase();
781
+ order = { isBuy: resting.side === "B", limitPx: String(resting.limitPx) };
782
+ }
783
+ const [meta, contexts] = await read({ type: "metaAndAssetCtxs" });
784
+ const asset = meta.universe.findIndex((m) => m.name.toUpperCase() === coin);
785
+ if (asset < 0 || asset >= 1e4)
786
+ throw new Error("No main-universe market for this coin on Hyperliquid");
787
+ const context = contexts[asset];
788
+ const midPx = context?.midPx ?? context?.markPx;
789
+ if ((decimal(midPx) ?? 0n) <= 0n)
790
+ throw new Error("No Hyperliquid price for this coin");
791
+ return { asset, midPx: String(midPx), ...order ? { order } : {} };
792
+ }
793
+ function checkOrderMarket(action, intent, market) {
794
+ const list = action.type === "modify" ? [action.order] : action.orders;
795
+ if (!Array.isArray(list) || list.length === 0)
796
+ return "missing requested order";
797
+ if (list.some((o) => !o || o.a !== market.asset))
798
+ return "order asset is not the requested coin's on Hyperliquid";
799
+ const first = list[0];
800
+ const p = intent.params;
801
+ if (intent.method === "open" && p.price == null) {
802
+ const requested = p.slippageBps;
803
+ const slippage = typeof requested === "number" && Number.isInteger(requested) && requested > 0 ? requested : HYPERLIQUID_DEFAULT_SLIPPAGE_BPS;
804
+ const bound = BigInt(slippage + HYPERLIQUID_MID_DRIFT_BPS);
805
+ const px = decimal(first.p) ?? 0n;
806
+ const mid = decimal(market.midPx) ?? 0n;
807
+ const within = first.b === true ? px * 10000n <= mid * (10000n + bound) : px * 10000n >= mid * (10000n - bound);
808
+ if (mid <= 0n || !within)
809
+ return "market order price is outside its slippage of Hyperliquid's mid";
810
+ }
811
+ if (intent.method === "modify") {
812
+ if (!market.order)
813
+ return "no resting order on Hyperliquid to check the modify against";
814
+ if (first.b !== market.order.isBuy)
815
+ return "modify changes the resting order's side";
816
+ if (p.price == null && decimal(first.p) !== decimal(market.order.limitPx))
817
+ return "modify changes the resting order's price";
818
+ }
819
+ return null;
820
+ }
673
821
  function verifyPerpsBuild(build, opts) {
674
822
  try {
675
823
  const network = opts.network ?? "mainnet";
@@ -697,6 +845,13 @@ function verifyPerpsBuild(build, opts) {
697
845
  if (!order || order.a !== opts.closeOrder.asset || order.b !== opts.closeOrder.isBuy || decimal(order.s) !== decimal(opts.closeOrder.size) || order.r !== true)
698
846
  return { ok: false, reason: "close side, asset or size does not match the requested position" };
699
847
  }
848
+ if (opts.market) {
849
+ if (opts.intent?.method !== "open" && opts.intent?.method !== "modify")
850
+ return { ok: false, reason: "a market check needs an open or modify intent" };
851
+ const marketRefusal = checkOrderMarket(action, opts.intent, opts.market);
852
+ if (marketRefusal)
853
+ return { ok: false, reason: marketRefusal };
854
+ }
700
855
  if (typed.primary_type === "Agent") {
701
856
  const type = action.type;
702
857
  if (typeof type !== "string" || !HYPERLIQUID_ALLOWED_ACTION_TYPES.includes(type)) {
@@ -989,6 +1144,47 @@ function assertTransportSecurity(apiUrl, allowInsecureHttp) {
989
1144
  return;
990
1145
  throw new Error(`CandleClient: refusing to send credentials in the clear to ${parsed.origin}. Use https://.` + (isPrivateHost(parsed.hostname) ? " Pass allowInsecureHttp: true if this really is a trusted local endpoint." : " allowInsecureHttp does not apply here: it covers private networks only, and this is a" + " public address."));
991
1146
  }
1147
+ var REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
1148
+ var MAX_REDIRECTS = 5;
1149
+
1150
+ class CrossOriginRedirectError extends Error {
1151
+ from;
1152
+ to;
1153
+ constructor(from, to) {
1154
+ super(`The API at ${from} redirected to ${to}, another origin. Not followed, so the API key was not sent there.`);
1155
+ this.from = from;
1156
+ this.to = to;
1157
+ this.name = "CrossOriginRedirectError";
1158
+ }
1159
+ }
1160
+ function keyedRedirectGuard(doFetch) {
1161
+ const guarded = async (input, init) => {
1162
+ if (input instanceof Request || !init?.headers || !new Headers(init.headers).has("x-api-key")) {
1163
+ return doFetch(input, init);
1164
+ }
1165
+ const url = input.toString();
1166
+ let current = url;
1167
+ let next = { ...init, redirect: "manual" };
1168
+ for (let hops = 0;; hops++) {
1169
+ const response = await doFetch(current, next);
1170
+ const location = REDIRECT_STATUSES.has(response.status) ? response.headers?.get?.("location") : null;
1171
+ if (!location)
1172
+ return response;
1173
+ const from = new URL(current);
1174
+ const target = new URL(location, from);
1175
+ if (target.origin !== from.origin)
1176
+ throw new CrossOriginRedirectError(from.origin, target.origin);
1177
+ if (hops >= MAX_REDIRECTS)
1178
+ throw new Error(`More than ${MAX_REDIRECTS} redirects from ${url}`);
1179
+ const method = (next.method ?? "GET").toUpperCase();
1180
+ if (response.status === 303 && method !== "GET" && method !== "HEAD" || (response.status === 301 || response.status === 302) && method === "POST") {
1181
+ next = { ...next, method: "GET", body: undefined };
1182
+ }
1183
+ current = target.href;
1184
+ }
1185
+ };
1186
+ return guarded;
1187
+ }
992
1188
 
993
1189
  class CandleClient {
994
1190
  apiUrl;
@@ -1060,7 +1256,7 @@ class CandleClient {
1060
1256
  this.apiUrl = opts.apiUrl.replace(/\/+$/, "");
1061
1257
  if (opts.apiKey !== undefined)
1062
1258
  this.apiKey = opts.apiKey;
1063
- this.fetchImpl = opts.fetch ?? fetch;
1259
+ this.fetchImpl = keyedRedirectGuard(opts.fetch ?? fetch);
1064
1260
  if (opts.maxRetries !== undefined && (!Number.isInteger(opts.maxRetries) || opts.maxRetries < 0)) {
1065
1261
  throw new Error(`CandleClient: maxRetries must be a non-negative integer, got ${opts.maxRetries}`);
1066
1262
  }
@@ -1119,6 +1315,45 @@ class CandleClient {
1119
1315
  async verify(chain, mint) {
1120
1316
  return this.requestJson("GET", `/api/v1/verify/${chain}/${encodeURIComponent(mint)}`);
1121
1317
  }
1318
+ async getOwnProfile() {
1319
+ this.requireKey("getOwnProfile()");
1320
+ return this.requestJson("GET", "/api/v1/agent/keys/self/public-profile");
1321
+ }
1322
+ async updateOwnProfile(patch) {
1323
+ this.requireKey("updateOwnProfile()");
1324
+ return this.requestJson("PATCH", "/api/v1/agent/keys/self/public-profile", patch);
1325
+ }
1326
+ async uploadProfileImage(kind, bytes, contentType) {
1327
+ this.requireKey("uploadProfileImage()");
1328
+ const form = new FormData;
1329
+ form.set("kind", kind);
1330
+ form.set("image", new Blob([bytes], { type: contentType }), "profile.image");
1331
+ const headers = this.headers();
1332
+ delete headers["Content-Type"];
1333
+ const res = await this.fetchImpl(`${this.apiUrl}/api/v1/uploads/agent-profile-image`, {
1334
+ method: "POST",
1335
+ headers,
1336
+ body: form
1337
+ });
1338
+ return this.parseResponse(res);
1339
+ }
1340
+ async createPost(req) {
1341
+ this.requireKey("createPost()");
1342
+ return this.requestJson("POST", "/api/v1/agent/posts", req);
1343
+ }
1344
+ async deletePost(postId) {
1345
+ this.requireKey("deletePost()");
1346
+ return this.requestJson("DELETE", `/api/v1/agent/posts/${encodeURIComponent(postId)}`);
1347
+ }
1348
+ async listAgentPosts(profileId, options = {}) {
1349
+ const params = new URLSearchParams;
1350
+ if (options.limit !== undefined)
1351
+ params.set("limit", String(options.limit));
1352
+ if (options.cursor !== undefined)
1353
+ params.set("cursor", options.cursor);
1354
+ const body = await this.requestJson("GET", `/api/v1/agent/records/${encodeURIComponent(profileId)}/posts${params.size ? `?${params}` : ""}`);
1355
+ return { posts: body.posts, cursor: body.cursor ?? null };
1356
+ }
1122
1357
  async getAgentProfile(idOrWallet) {
1123
1358
  const body = await this.requestJson("GET", `/api/v1/users/${encodeURIComponent(idOrWallet)}/agent`);
1124
1359
  return body.agent;
@@ -1456,6 +1691,7 @@ class CandleClient {
1456
1691
  if (built.artifacts.feeTransfer) {
1457
1692
  legs.push({ kind: "feeTransfer", ...built.artifacts.feeTransfer });
1458
1693
  }
1694
+ const landedLegs = [];
1459
1695
  let tradeTxHash;
1460
1696
  let feeTxHash;
1461
1697
  for (let i = 0;i < legs.length; i++) {
@@ -1475,13 +1711,24 @@ class CandleClient {
1475
1711
  feeData,
1476
1712
  linkedWalletId,
1477
1713
  privyWalletId
1478
- }, { surfaceLegOutcome: leg.kind === "feeTransfer" });
1714
+ }, { surfaceLegOutcome: true });
1479
1715
  } catch (err) {
1480
1716
  if (leg.kind === "feeTransfer" && tradeTxHash) {
1481
1717
  throw tradeLandedFeeLegError({ clientTradeId: built.clientTradeId, tradeTxHash, err });
1482
1718
  }
1483
- throw err;
1719
+ if (err instanceof EvmLegOutcome && err.txHash !== undefined || landedLegs.length > 0) {
1720
+ throw new TradeLegOutcomeError({
1721
+ clientTradeId: built.clientTradeId,
1722
+ leg: leg.kind,
1723
+ outcome: err instanceof EvmLegOutcome ? err.kind : "not-broadcast",
1724
+ txHash: err instanceof EvmLegOutcome ? err.txHash : undefined,
1725
+ landedLegs: [...landedLegs],
1726
+ cause: err instanceof EvmLegOutcome ? err.cause : err
1727
+ });
1728
+ }
1729
+ throw err instanceof EvmLegOutcome ? err.cause : err;
1484
1730
  }
1731
+ landedLegs.push({ leg: leg.kind, txHash });
1485
1732
  if (leg.kind === "trade")
1486
1733
  tradeTxHash = txHash;
1487
1734
  if (leg.kind === "feeTransfer")
@@ -1490,11 +1737,15 @@ class CandleClient {
1490
1737
  if (!tradeTxHash) {
1491
1738
  throw new Error("trade(): Hood leg sequence completed without a trade leg");
1492
1739
  }
1493
- return this.confirmTrade({
1494
- clientTradeId: built.clientTradeId,
1495
- tradeTxHash,
1496
- ...feeTxHash ? { feeTxHash } : {}
1497
- });
1740
+ try {
1741
+ return await this.confirmTrade({
1742
+ clientTradeId: built.clientTradeId,
1743
+ tradeTxHash,
1744
+ ...feeTxHash ? { feeTxHash } : {}
1745
+ });
1746
+ } catch (cause) {
1747
+ throw new TradeLandedUnconfirmedError({ clientTradeId: built.clientTradeId, tradeTxHash, feeTxHash, cause });
1748
+ }
1498
1749
  }
1499
1750
  async selfLaunch(req) {
1500
1751
  const { privyWalletId, ...launchReq } = req;
@@ -1506,27 +1757,44 @@ class CandleClient {
1506
1757
  const built = await this.postSelfLaunchBuild(body, op);
1507
1758
  if (typeof built.transaction === "string") {
1508
1759
  let unsignedTransactionBase64 = built.transaction;
1509
- let clientLaunchId = built.clientLaunchId;
1760
+ let clientLaunchId2 = built.clientLaunchId;
1761
+ let signed;
1510
1762
  for (let attempt = 0;attempt <= MAX_BLOCKHASH_REBUILDS; attempt++) {
1511
- const signed = await this.signLinkedTransaction({
1512
- linkedWalletId: body.linkedWalletId,
1513
- privyWalletId,
1514
- chain: "solana",
1515
- unsignedTransactionBase64
1516
- });
1763
+ if (signed?.forTransaction !== unsignedTransactionBase64) {
1764
+ const result = await this.signLinkedTransaction({
1765
+ linkedWalletId: body.linkedWalletId,
1766
+ privyWalletId,
1767
+ chain: "solana",
1768
+ unsignedTransactionBase64
1769
+ });
1770
+ signed = { forTransaction: unsignedTransactionBase64, ...result };
1771
+ }
1517
1772
  try {
1518
1773
  const signature = await this.broadcastSignedTransaction("solana", signed.signedTransaction, signed.encoding);
1519
- return this.confirmSelfLaunch({ clientLaunchId, signature });
1774
+ return this.confirmSelfLaunch({ clientLaunchId: clientLaunchId2, signature });
1520
1775
  } catch (error) {
1521
1776
  if (!isBlockhashExpiry(error))
1522
1777
  throw error;
1523
1778
  if (attempt === MAX_BLOCKHASH_REBUILDS)
1524
1779
  throw withRpcLagHint(error);
1525
- const rebuilt = await this.postSelfLaunchBuild(body, op);
1780
+ let rebuilt;
1781
+ try {
1782
+ rebuilt = await this.postSelfLaunchBuild(body, op);
1783
+ } catch (buildError) {
1784
+ const reason = buildError instanceof CandleApiError ? buildError.reason : undefined;
1785
+ if (reason === "prior_attempt_in_flight")
1786
+ continue;
1787
+ if (reason === "prior_attempt_on_chain") {
1788
+ const signature = solanaSignedTxSignature(signed.signedTransaction, signed.encoding);
1789
+ if (signature)
1790
+ return this.confirmSelfLaunch({ clientLaunchId: clientLaunchId2, signature });
1791
+ }
1792
+ throw buildError;
1793
+ }
1526
1794
  if (typeof rebuilt.transaction !== "string")
1527
1795
  throw error;
1528
1796
  unsignedTransactionBase64 = rebuilt.transaction;
1529
- clientLaunchId = rebuilt.clientLaunchId;
1797
+ clientLaunchId2 = rebuilt.clientLaunchId;
1530
1798
  }
1531
1799
  }
1532
1800
  throw new Error("selfLaunch(): blockhash-rebuild loop exited without returning or throwing");
@@ -1552,26 +1820,41 @@ class CandleClient {
1552
1820
  linkedWalletId: body.linkedWalletId,
1553
1821
  privyWalletId
1554
1822
  });
1823
+ const clientLaunchId = built.clientLaunchId;
1555
1824
  let feeTxHash;
1556
1825
  if (hoodBuilt.feeTransfer) {
1557
- feeTxHash = await this.signBroadcastAndWaitEvmLeg({
1558
- rpc,
1559
- from,
1560
- to: hoodBuilt.feeTransfer.to,
1561
- data: hoodBuilt.feeTransfer.data,
1562
- valueDecimal: hoodBuilt.feeTransfer.value,
1563
- nonce: baseNonce + 1,
1564
- chainId,
1565
- feeData,
1566
- linkedWalletId: body.linkedWalletId,
1567
- privyWalletId
1826
+ try {
1827
+ feeTxHash = await this.signBroadcastAndWaitEvmLeg({
1828
+ rpc,
1829
+ from,
1830
+ to: hoodBuilt.feeTransfer.to,
1831
+ data: hoodBuilt.feeTransfer.data,
1832
+ valueDecimal: hoodBuilt.feeTransfer.value,
1833
+ nonce: baseNonce + 1,
1834
+ chainId,
1835
+ feeData,
1836
+ linkedWalletId: body.linkedWalletId,
1837
+ privyWalletId
1838
+ }, { surfaceLegOutcome: true });
1839
+ } catch (err) {
1840
+ throw selfLaunchFeeLegError({ clientLaunchId, createCurveTxHash, err });
1841
+ }
1842
+ }
1843
+ try {
1844
+ return await this.confirmSelfLaunch({
1845
+ clientLaunchId,
1846
+ signature: createCurveTxHash,
1847
+ ...feeTxHash ? { feeTxHash } : {}
1848
+ });
1849
+ } catch (cause) {
1850
+ throw new SelfLaunchOutcomeError({
1851
+ clientLaunchId,
1852
+ createCurveTxHash,
1853
+ step: "confirm",
1854
+ ...feeTxHash ? { feeOutcome: "landed", feeTxHash } : { feeOutcome: "not-required" },
1855
+ cause
1568
1856
  });
1569
1857
  }
1570
- return this.confirmSelfLaunch({
1571
- clientLaunchId: built.clientLaunchId,
1572
- signature: createCurveTxHash,
1573
- ...feeTxHash ? { feeTxHash } : {}
1574
- });
1575
1858
  }
1576
1859
  async buildAtomicLaunch(req) {
1577
1860
  this.requireKey("buildAtomicLaunch()");
@@ -1693,7 +1976,7 @@ class CandleClient {
1693
1976
  if (!isDefiniteBroadcastRejection(err)) {
1694
1977
  throw new EvmLegOutcome({ kind: "unknown", txHash: predicted, cause: err });
1695
1978
  }
1696
- throw err;
1979
+ throw new EvmLegOutcome({ kind: "not-broadcast", txHash: predicted, cause: err });
1697
1980
  }
1698
1981
  try {
1699
1982
  await waitForReceipt(params.rpc, txHash, evmReceiptWaitForTest ?? {});
@@ -1891,6 +2174,17 @@ class CandleClient {
1891
2174
  if (!closeCheck.ok)
1892
2175
  throw new Error(`perps: refused to sign this build: ${closeCheck.reason}`);
1893
2176
  }
2177
+ if (method === "open" || method === "modify") {
2178
+ const market = await hyperliquidOrderMarket(this.fetchImpl, this.hyperliquidNetwork, build.address, method, ref);
2179
+ const marketCheck = verifyPerpsBuild(build, {
2180
+ builder: await this.perpsBuilder(),
2181
+ network: this.hyperliquidNetwork,
2182
+ intent: { method, params: ref },
2183
+ market
2184
+ });
2185
+ if (!marketCheck.ok)
2186
+ throw new Error(`perps: refused to sign this build: ${marketCheck.reason}`);
2187
+ }
1894
2188
  if (ref.submit === false)
1895
2189
  return { build, signature: null, submitted: false, exchange: null };
1896
2190
  const signature = await this.signLinkedTypedData({
@@ -2050,7 +2344,7 @@ function tradeLandedFeeLegError(args) {
2050
2344
  tradeTxHash: args.tradeTxHash,
2051
2345
  cause: args.err.cause,
2052
2346
  feeOutcome: args.err.kind,
2053
- ...args.err.txHash !== undefined ? { feeTxHash: args.err.txHash } : {}
2347
+ ...args.err.kind !== "not-broadcast" && args.err.txHash !== undefined ? { feeTxHash: args.err.txHash } : {}
2054
2348
  });
2055
2349
  }
2056
2350
  return new TradeLandedFeeLegError({
@@ -2060,6 +2354,38 @@ function tradeLandedFeeLegError(args) {
2060
2354
  feeOutcome: "not-broadcast"
2061
2355
  });
2062
2356
  }
2357
+ function selfLaunchFeeLegError(args) {
2358
+ const outcome = args.err instanceof EvmLegOutcome ? args.err : undefined;
2359
+ return new SelfLaunchOutcomeError({
2360
+ clientLaunchId: args.clientLaunchId,
2361
+ createCurveTxHash: args.createCurveTxHash,
2362
+ step: "feeTransfer",
2363
+ feeOutcome: outcome?.kind ?? "not-broadcast",
2364
+ ...outcome?.txHash !== undefined ? { feeTxHash: outcome.txHash } : {},
2365
+ cause: outcome ? outcome.cause : args.err
2366
+ });
2367
+ }
2368
+ function solanaSignedTxSignature(signedTransaction, encoding) {
2369
+ const enc = encoding.toLowerCase();
2370
+ let bytes;
2371
+ try {
2372
+ if (enc === "base64")
2373
+ bytes = fromBase64(signedTransaction);
2374
+ else if (enc === "base58")
2375
+ bytes = base582.decode(signedTransaction);
2376
+ else
2377
+ return;
2378
+ } catch {
2379
+ return;
2380
+ }
2381
+ const count = bytes[0];
2382
+ if (count === undefined || count === 0 || count >= 128 || bytes.length < 1 + count * 64)
2383
+ return;
2384
+ const first = bytes.subarray(1, 65);
2385
+ if (first.every((byte) => byte === 0))
2386
+ return;
2387
+ return base582.encode(first);
2388
+ }
2063
2389
  function evmSignedTxHash(signedTransaction, encoding) {
2064
2390
  const enc = encoding.toLowerCase();
2065
2391
  let bytes;
@@ -2098,7 +2424,7 @@ function generateClientLaunchId() {
2098
2424
  function generateClientTradeId() {
2099
2425
  return generateSdkId();
2100
2426
  }
2101
- var SDK_VERSION = "0.5.0";
2427
+ var SDK_VERSION = "0.6.1";
2102
2428
  var sdkUpdateWarned = false;
2103
2429
  function noteLatestSdkVersion(value) {
2104
2430
  if (sdkUpdateWarned || !value || !/^\d+\.\d+\.\d+$/.test(value))
@@ -2237,7 +2563,7 @@ var PLAN_CAPABILITY_LABELS = {
2237
2563
  createLinkedWallets: "Create linked wallets",
2238
2564
  importLinkedWallets: "Import linked wallets",
2239
2565
  limitOrders: "Limit orders",
2240
- quant: "Quant on Telegram"
2566
+ quant: "Quant deep analyses (Max)"
2241
2567
  };
2242
2568
  var PLAN_LABELS = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
2243
2569
  function planLabel(plan) {
@@ -2464,11 +2790,15 @@ export {
2464
2790
  perpsDepositProblem,
2465
2791
  operationIds,
2466
2792
  isSolanaRpcErrorData,
2793
+ hyperliquidOrderMarket,
2467
2794
  hyperliquidActionHash,
2468
2795
  generateSignerKeypair,
2469
2796
  formatPlanBps,
2470
2797
  encryptWalletKeyForImport,
2798
+ TradeLegOutcomeError,
2799
+ TradeLandedUnconfirmedError,
2471
2800
  TradeLandedFeeLegError,
2801
+ SelfLaunchOutcomeError,
2472
2802
  PLAN_CAPABILITY_NOTE,
2473
2803
  PLAN_CAPABILITY_LABELS,
2474
2804
  KeychainSecretStore,
@@ -2477,6 +2807,7 @@ export {
2477
2807
  HYPERLIQUID_EXCHANGE_URLS,
2478
2808
  HYPERLIQUID_ALLOWED_ACTION_TYPES,
2479
2809
  EncryptedFileSecretStore,
2810
+ CrossOriginRedirectError,
2480
2811
  CandleOperationError,
2481
2812
  CandleClient,
2482
2813
  CandleApiError,
package/dist/plans.d.ts CHANGED
@@ -46,7 +46,7 @@ export interface PlanCapabilities {
46
46
  /** Plan eligibility only: whether perps are open on the deployment is its own switch, and trading needs the wallet and scopes. */
47
47
  hyperliquidPerps: boolean;
48
48
  limitOrders: boolean;
49
- /** The quant bot on Telegram. */
49
+ /** Max-level Quant: the deep `run_analysis` studies. Pro also has Quant, on Telegram and in Candle, but by its plan name, not this flag. */
50
50
  quant: boolean;
51
51
  /** Base-pair swaps and base-asset bridges between the key's own wallets carry no Candle fee. A bridge also needs its own deployment switch. */
52
52
  freeBaseTransfers: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,UAAU,CAAA;AAE1D,yFAAyF;AACzF,MAAM,WAAW,SAAS;IACxB,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,qGAAqG;AACrG,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,iBAAiB,EAAE,OAAO,CAAA;IAC1B,sFAAsF;IACtF,kBAAkB,EAAE,OAAO,CAAA;IAC3B,eAAe,EAAE,OAAO,CAAA;IACxB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,YAAY,EAAE,OAAO,CAAA;IACrB,mBAAmB,EAAE,OAAO,CAAA;IAC5B,mBAAmB,EAAE,OAAO,CAAA;IAC5B,kIAAkI;IAClI,gBAAgB,EAAE,OAAO,CAAA;IACzB,WAAW,EAAE,OAAO,CAAA;IACpB,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAA;IACd,+IAA+I;IAC/I,iBAAiB,EAAE,OAAO,CAAA;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,CAAA;IACd;;;OAGG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAA;IAC/B,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,UAAU,CAAA;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,iCAAiC;AACjC,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,OAAO,EAAE,IAAI,CAAA;CACd;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,ibAC+Y,CAAA;AAEhb,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,gBAAgB,EAAE,MAAM,CAazE,CAAA;AAID,mEAAmE;AACnE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAWD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;CAAE,CA+BvF;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAIjE;AAED,iGAAiG;AACjG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAI1D"}
1
+ {"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,UAAU,CAAA;AAE1D,yFAAyF;AACzF,MAAM,WAAW,SAAS;IACxB,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,qGAAqG;AACrG,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,iBAAiB,EAAE,OAAO,CAAA;IAC1B,sFAAsF;IACtF,kBAAkB,EAAE,OAAO,CAAA;IAC3B,eAAe,EAAE,OAAO,CAAA;IACxB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,YAAY,EAAE,OAAO,CAAA;IACrB,mBAAmB,EAAE,OAAO,CAAA;IAC5B,mBAAmB,EAAE,OAAO,CAAA;IAC5B,kIAAkI;IAClI,gBAAgB,EAAE,OAAO,CAAA;IACzB,WAAW,EAAE,OAAO,CAAA;IACpB,4IAA4I;IAC5I,KAAK,EAAE,OAAO,CAAA;IACd,+IAA+I;IAC/I,iBAAiB,EAAE,OAAO,CAAA;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,CAAA;IACd;;;OAGG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAA;IAC/B,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,UAAU,CAAA;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,iCAAiC;AACjC,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,OAAO,EAAE,IAAI,CAAA;CACd;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,ibAC+Y,CAAA;AAEhb,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,gBAAgB,EAAE,MAAM,CAazE,CAAA;AAID,mEAAmE;AACnE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAWD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;CAAE,CA+BvF;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAIjE;AAED,iGAAiG;AACjG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAI1D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@candledottv/agent-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
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",