@candledottv/agent-sdk 0.4.6 → 0.4.8

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
@@ -1,6 +1,9 @@
1
1
  import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
+ // src/client.ts
5
+ import { keccak_256 as keccak_2562 } from "@noble/hashes/sha3";
6
+
4
7
  // src/authorization-signature.ts
5
8
  import canonicalize from "canonicalize";
6
9
 
@@ -84,6 +87,12 @@ class CandleApiError extends Error {
84
87
  coverage;
85
88
  uiHint;
86
89
  docsPath;
90
+ stage;
91
+ signature;
92
+ recorded;
93
+ feeRaw;
94
+ quoteAsset;
95
+ treasury;
87
96
  constructor(args) {
88
97
  super(args.message);
89
98
  this.name = "CandleApiError";
@@ -97,6 +106,37 @@ class CandleApiError extends Error {
97
106
  this.coverage = args.coverage;
98
107
  this.uiHint = args.uiHint;
99
108
  this.docsPath = args.docsPath;
109
+ if (args.stage !== undefined)
110
+ this.stage = args.stage;
111
+ if (args.signature !== undefined)
112
+ this.signature = args.signature;
113
+ if (args.recorded !== undefined)
114
+ this.recorded = args.recorded;
115
+ if (args.feeRaw !== undefined)
116
+ this.feeRaw = args.feeRaw;
117
+ if (args.quoteAsset !== undefined)
118
+ this.quoteAsset = args.quoteAsset;
119
+ if (args.treasury !== undefined)
120
+ this.treasury = args.treasury;
121
+ }
122
+ }
123
+
124
+ class TradeLandedFeeLegError extends Error {
125
+ clientTradeId;
126
+ tradeTxHash;
127
+ feeTxHash;
128
+ feeOutcome;
129
+ stage = "executed";
130
+ constructor(args) {
131
+ const reason = args.cause instanceof Error ? args.cause.message : String(args.cause);
132
+ const recover = args.feeOutcome === "unknown" ? args.feeTxHash ? `Do NOT repeat the trade. Do NOT pay again: the fee may already be pending or mined. Check feeTxHash ` + `${args.feeTxHash} and re-confirm clientTradeId ${args.clientTradeId} with tradeTxHash ` + `${args.tradeTxHash} and that same feeTxHash.` : `Do NOT repeat the trade. Do NOT pay again: the fee broadcast outcome is unknown and it may already ` + `be pending or mined. Check the payer wallet before any new payment, then re-confirm clientTradeId ` + `${args.clientTradeId} with tradeTxHash ${args.tradeTxHash} and the fee hash you find.` : `Do NOT repeat the trade: pay the fee, then call confirmTrade for clientTradeId ${args.clientTradeId} ` + `with tradeTxHash and the fee's feeTxHash.`;
133
+ super(`trade executed; fee/booking incomplete: the trade landed (tradeTxHash ${args.tradeTxHash}) but its fee ` + `transfer ${args.feeOutcome === "unknown" ? "outcome is unknown" : "failed"} (${reason}). The trade is ` + `NOT confirmed or booked. ${recover}`, { cause: args.cause });
134
+ this.name = "TradeLandedFeeLegError";
135
+ this.clientTradeId = args.clientTradeId;
136
+ this.tradeTxHash = args.tradeTxHash;
137
+ this.feeOutcome = args.feeOutcome;
138
+ if (args.feeTxHash !== undefined)
139
+ this.feeTxHash = args.feeTxHash;
100
140
  }
101
141
  }
102
142
  function isSolanaRpcErrorData(data) {
@@ -138,7 +178,13 @@ function envelopeError(body) {
138
178
  code: error.code,
139
179
  message: error.message,
140
180
  ...typeof error.field === "string" ? { field: error.field } : {},
141
- ...typeof error.retryable === "boolean" ? { retryable: error.retryable } : {}
181
+ ...typeof error.retryable === "boolean" ? { retryable: error.retryable } : {},
182
+ ...typeof error.stage === "string" ? { stage: error.stage } : {},
183
+ ...typeof error.signature === "string" ? { signature: error.signature } : {},
184
+ ...typeof error.recorded === "boolean" ? { recorded: error.recorded } : {},
185
+ ...typeof error.feeRaw === "string" ? { feeRaw: error.feeRaw } : {},
186
+ ...typeof error.quoteAsset === "string" ? { quoteAsset: error.quoteAsset } : {},
187
+ ...typeof error.treasury === "string" ? { treasury: error.treasury } : {}
142
188
  };
143
189
  }
144
190
  function candleApiErrorFromResponse(status, bodyText) {
@@ -750,6 +796,34 @@ ${lines.join(`
750
796
  }
751
797
 
752
798
  // src/client.ts
799
+ function historyTime(value) {
800
+ return value instanceof Date ? String(value.getTime()) : String(value);
801
+ }
802
+ var HYPERLIQUID_RELAY_CHAIN_ID = 1337;
803
+ var HYPERLIQUID_RELAY_USDC = "0x00000000000000000000000000000000";
804
+ function perpsDepositProblem(build, params) {
805
+ const same = (a, b) => typeof a === "string" && a.toLowerCase() === b.toLowerCase();
806
+ if (build.walletId !== params.walletId)
807
+ return "the build names another paying wallet";
808
+ if (build.asset !== params.asset || build.amountRaw !== params.amountRaw)
809
+ return "the build is for another amount";
810
+ if (build.destination?.chainId !== HYPERLIQUID_RELAY_CHAIN_ID)
811
+ return "the build does not land on Hyperliquid";
812
+ if (build.destination.currency !== HYPERLIQUID_RELAY_USDC)
813
+ return "the build does not deliver Hyperliquid USDC";
814
+ if (!same(build.destination.address, params.account))
815
+ return "the build lands on another account";
816
+ if (build.fee?.bps !== 0 || build.fee.feeRaw !== "0")
817
+ return "the build carries a Candle fee";
818
+ const hood = params.asset === "ETH" || params.asset === "USDG";
819
+ if (hood !== (build.chain === "hood"))
820
+ return "the build pays from the wrong chain";
821
+ if (hood && !same(build.walletAddress, params.account))
822
+ return "a Hood deposit lands only on the paying wallet's own account";
823
+ if (!hood && build.transactionsBase64?.length !== 1)
824
+ return "the build is not one Solana deposit";
825
+ return null;
826
+ }
753
827
  var BACKOFF_BASE_MS = 250;
754
828
  var BACKOFF_CAP_MS = 8000;
755
829
  function retryDelayMs(retry) {
@@ -964,6 +1038,9 @@ class CandleClient {
964
1038
  async getAgentTier() {
965
1039
  return this.requestJson("GET", "/api/v1/agent/tier");
966
1040
  }
1041
+ async getPlans() {
1042
+ return this.requestJson("GET", "/api/v1/agent/plans");
1043
+ }
967
1044
  async dryRunLaunch(req) {
968
1045
  this.requireKey("dryRunLaunch()");
969
1046
  return this.requestJson("POST", "/api/v1/launch/headless/dry-run", req);
@@ -1034,7 +1111,16 @@ class CandleClient {
1034
1111
  }
1035
1112
  async getProfileTrades(keyPrefix, opts = {}) {
1036
1113
  this.requireKey("getProfileTrades()");
1037
- const query = opts.limit !== undefined ? `?limit=${encodeURIComponent(String(opts.limit))}` : "";
1114
+ const params = new URLSearchParams;
1115
+ if (opts.limit !== undefined)
1116
+ params.set("limit", String(opts.limit));
1117
+ if (opts.since !== undefined)
1118
+ params.set("since", historyTime(opts.since));
1119
+ if (opts.until !== undefined)
1120
+ params.set("until", historyTime(opts.until));
1121
+ if (opts.cursor !== undefined)
1122
+ params.set("cursor", opts.cursor);
1123
+ const query = params.toString() === "" ? "" : `?${params.toString()}`;
1038
1124
  return this.requestJson("GET", `/api/v1/agent/keys/${encodeURIComponent(keyPrefix)}/trades${query}`);
1039
1125
  }
1040
1126
  async getProfilePnl(keyPrefix) {
@@ -1092,6 +1178,10 @@ class CandleClient {
1092
1178
  ...params.label !== undefined ? { label: params.label } : {}
1093
1179
  });
1094
1180
  }
1181
+ async dryRunSelfLaunch(req) {
1182
+ this.requireKey("dryRunSelfLaunch()");
1183
+ return this.requestJson("POST", "/api/v1/launch/self/dry-run", req);
1184
+ }
1095
1185
  async buildSelfLaunch(req) {
1096
1186
  this.requireKey("buildSelfLaunch()");
1097
1187
  return this.requestJson("POST", "/api/v1/launch/self/build", req);
@@ -1242,18 +1332,26 @@ class CandleClient {
1242
1332
  const leg = legs[i];
1243
1333
  if (!leg)
1244
1334
  continue;
1245
- const txHash = await this.signBroadcastAndWaitEvmLeg({
1246
- rpc,
1247
- from,
1248
- to: leg.to,
1249
- data: leg.data,
1250
- valueDecimal: leg.value,
1251
- nonce: baseNonce + i,
1252
- chainId,
1253
- feeData,
1254
- linkedWalletId,
1255
- privyWalletId
1256
- });
1335
+ let txHash;
1336
+ try {
1337
+ txHash = await this.signBroadcastAndWaitEvmLeg({
1338
+ rpc,
1339
+ from,
1340
+ to: leg.to,
1341
+ data: leg.data,
1342
+ valueDecimal: leg.value,
1343
+ nonce: baseNonce + i,
1344
+ chainId,
1345
+ feeData,
1346
+ linkedWalletId,
1347
+ privyWalletId
1348
+ }, { surfaceLegOutcome: leg.kind === "feeTransfer" });
1349
+ } catch (err) {
1350
+ if (leg.kind === "feeTransfer" && tradeTxHash) {
1351
+ throw tradeLandedFeeLegError({ clientTradeId: built.clientTradeId, tradeTxHash, err });
1352
+ }
1353
+ throw err;
1354
+ }
1257
1355
  if (leg.kind === "trade")
1258
1356
  tradeTxHash = txHash;
1259
1357
  if (leg.kind === "feeTransfer")
@@ -1409,7 +1507,16 @@ class CandleClient {
1409
1507
  callRaw: (method, params) => this.jsonRpcCallRaw(url, method, params)
1410
1508
  };
1411
1509
  }
1412
- async signBroadcastAndWaitEvmLeg(params) {
1510
+ async signBroadcastAndWaitEvmLeg(params, opts = {}) {
1511
+ try {
1512
+ return await this.broadcastAndWaitEvmLeg(params);
1513
+ } catch (err) {
1514
+ if (opts.surfaceLegOutcome || !(err instanceof EvmLegOutcome))
1515
+ throw err;
1516
+ throw err.cause;
1517
+ }
1518
+ }
1519
+ async broadcastAndWaitEvmLeg(params) {
1413
1520
  const gasLimitHex = await estimateGas(params.rpc, {
1414
1521
  from: params.from,
1415
1522
  to: params.to,
@@ -1432,8 +1539,23 @@ class CandleClient {
1432
1539
  chain: "evm",
1433
1540
  evmTxParams
1434
1541
  });
1435
- const txHash = await this.broadcastSignedTransaction("evm", signed.signedTransaction, signed.encoding);
1436
- await waitForReceipt(params.rpc, txHash);
1542
+ const predicted = evmSignedTxHash(signed.signedTransaction, signed.encoding);
1543
+ let txHash;
1544
+ try {
1545
+ txHash = await this.broadcastSignedTransaction("evm", signed.signedTransaction, signed.encoding);
1546
+ } catch (err) {
1547
+ if (!isDefiniteBroadcastRejection(err)) {
1548
+ throw new EvmLegOutcome({ kind: "unknown", txHash: predicted, cause: err });
1549
+ }
1550
+ throw err;
1551
+ }
1552
+ try {
1553
+ await waitForReceipt(params.rpc, txHash, evmReceiptWaitForTest ?? {});
1554
+ } catch (err) {
1555
+ if (isRevertedReceipt(err))
1556
+ throw new EvmLegOutcome({ kind: "reverted", txHash, cause: err });
1557
+ throw new EvmLegOutcome({ kind: "unknown", txHash, cause: err });
1558
+ }
1437
1559
  return txHash;
1438
1560
  }
1439
1561
  async perpsConfig() {
@@ -1522,6 +1644,79 @@ class CandleClient {
1522
1644
  params.set("startTime", String(startTime));
1523
1645
  return this.requestJson("GET", `/api/v1/agent/perps/funding?${params}`);
1524
1646
  }
1647
+ async perpsDeposit(params) {
1648
+ this.requireKey("perpsDeposit()");
1649
+ const build = await this.requestJson("POST", "/api/v1/agent/perps/deposit", {
1650
+ clientDepositId: params.clientDepositId,
1651
+ walletId: params.walletId,
1652
+ asset: params.asset,
1653
+ amountRaw: params.amountRaw,
1654
+ ...params.perpsWalletId !== undefined ? { perpsWalletId: params.perpsWalletId } : {},
1655
+ ...params.maxSlippageBps !== undefined ? { maxSlippageBps: params.maxSlippageBps } : {}
1656
+ });
1657
+ if (build.job !== undefined)
1658
+ throw new Error(`perpsDeposit(): ${params.clientDepositId} already names a deposit; read it with perpsDepositStatus()`);
1659
+ const problem = perpsDepositProblem(build, params);
1660
+ if (problem)
1661
+ throw new Error(`perps: refused to sign this deposit: ${problem}`);
1662
+ if (params.submit === false)
1663
+ return { build, submitted: false, result: null };
1664
+ const submitPath = "/api/v1/agent/perps/deposit/submit";
1665
+ const ids = { clientDepositId: params.clientDepositId, depositId: build.depositId };
1666
+ if (build.chain === "solana") {
1667
+ const signed = await this.signLinkedTransaction({
1668
+ chain: "solana",
1669
+ linkedWalletId: params.walletId,
1670
+ privyWalletId: params.privyWalletId,
1671
+ unsignedTransactionBase64: build.transactionsBase64[0]
1672
+ });
1673
+ const result = await this.requestJson("POST", submitPath, {
1674
+ ...ids,
1675
+ signedTransactionsBase64: [signed.signedTransaction]
1676
+ });
1677
+ return { build, submitted: true, result };
1678
+ }
1679
+ const allowed = params.asset === "USDG" ? ["approval", "bridgeDeposit"] : ["bridgeDeposit"];
1680
+ const planned = build.plannedLegCount ?? 0;
1681
+ const hex = (value) => `0x${BigInt(value).toString(16)}`;
1682
+ let body = build;
1683
+ const signedKinds = new Set;
1684
+ while (body.mode === "sequenced") {
1685
+ const leg = body.nextLeg;
1686
+ const kind = String(body.legKind);
1687
+ if (!leg || body.operationId !== build.operationId || body.plannedLegCount !== planned || planned > allowed.length || !allowed.includes(kind) || signedKinds.has(kind) || leg.chainId !== 4663) {
1688
+ throw new Error(`perps: refused to sign deposit leg ${kind}: it is not the plan this deposit was built with`);
1689
+ }
1690
+ signedKinds.add(kind);
1691
+ const signed = await this.signLinkedTransaction({
1692
+ chain: "evm",
1693
+ linkedWalletId: params.walletId,
1694
+ privyWalletId: params.privyWalletId,
1695
+ evmTxParams: {
1696
+ chain_id: leg.chainId,
1697
+ data: leg.data,
1698
+ from: build.walletAddress,
1699
+ gas_limit: hex(leg.gas),
1700
+ max_fee_per_gas: hex(leg.maxFeePerGas),
1701
+ max_priority_fee_per_gas: hex(leg.maxPriorityFeePerGas),
1702
+ nonce: leg.nonce,
1703
+ to: leg.to,
1704
+ type: 2,
1705
+ value: hex(leg.value)
1706
+ }
1707
+ });
1708
+ body = await this.requestJson("POST", submitPath, {
1709
+ ...ids,
1710
+ operationId: build.operationId,
1711
+ signedTransaction: signed.signedTransaction
1712
+ });
1713
+ }
1714
+ return { build, submitted: true, result: body };
1715
+ }
1716
+ async perpsDepositStatus(clientDepositId) {
1717
+ this.requireKey("perpsDepositStatus()");
1718
+ return this.requestJson("GET", `/api/v1/agent/perps/deposit/${encodeURIComponent(clientDepositId)}`);
1719
+ }
1525
1720
  async perpsBuilder() {
1526
1721
  if (this.hyperliquidBuilder)
1527
1722
  return this.hyperliquidBuilder;
@@ -1675,6 +1870,66 @@ class CandleClient {
1675
1870
  return parsed.result;
1676
1871
  }
1677
1872
  }
1873
+
1874
+ class EvmLegOutcome extends Error {
1875
+ kind;
1876
+ txHash;
1877
+ cause;
1878
+ constructor(args) {
1879
+ super(args.cause instanceof Error ? args.cause.message : String(args.cause), { cause: args.cause });
1880
+ this.name = "EvmLegOutcome";
1881
+ this.kind = args.kind;
1882
+ this.txHash = args.txHash;
1883
+ this.cause = args.cause;
1884
+ }
1885
+ }
1886
+ var evmReceiptWaitForTest = null;
1887
+ function tradeLandedFeeLegError(args) {
1888
+ if (args.err instanceof EvmLegOutcome) {
1889
+ return new TradeLandedFeeLegError({
1890
+ clientTradeId: args.clientTradeId,
1891
+ tradeTxHash: args.tradeTxHash,
1892
+ cause: args.err.cause,
1893
+ feeOutcome: args.err.kind,
1894
+ ...args.err.txHash !== undefined ? { feeTxHash: args.err.txHash } : {}
1895
+ });
1896
+ }
1897
+ return new TradeLandedFeeLegError({
1898
+ clientTradeId: args.clientTradeId,
1899
+ tradeTxHash: args.tradeTxHash,
1900
+ cause: args.err,
1901
+ feeOutcome: "not-broadcast"
1902
+ });
1903
+ }
1904
+ function evmSignedTxHash(signedTransaction, encoding) {
1905
+ const enc = encoding.toLowerCase();
1906
+ let bytes;
1907
+ if (enc === "base64") {
1908
+ bytes = fromBase64(signedTransaction);
1909
+ } else if (enc === "hex" || enc === "rlp" || signedTransaction.startsWith("0x")) {
1910
+ const hex2 = signedTransaction.startsWith("0x") ? signedTransaction.slice(2) : signedTransaction;
1911
+ if (!/^[0-9a-fA-F]+$/.test(hex2) || hex2.length % 2 !== 0)
1912
+ return;
1913
+ bytes = new Uint8Array(hex2.length / 2);
1914
+ for (let i = 0;i < bytes.length; i++)
1915
+ bytes[i] = Number.parseInt(hex2.slice(i * 2, i * 2 + 2), 16);
1916
+ }
1917
+ if (!bytes || bytes.length === 0)
1918
+ return;
1919
+ let hex = "";
1920
+ for (const b of keccak_2562(bytes))
1921
+ hex += b.toString(16).padStart(2, "0");
1922
+ return `0x${hex}`;
1923
+ }
1924
+ function isDefiniteBroadcastRejection(err) {
1925
+ if (!(err instanceof JsonRpcError))
1926
+ return false;
1927
+ const rpcMessage = err.message.match(/\(code -?\d+\): (.*)$/)?.[1]?.toLowerCase();
1928
+ return rpcMessage === "insufficient funds for gas * price + value" || rpcMessage === "intrinsic gas too low" || rpcMessage === "invalid sender";
1929
+ }
1930
+ function isRevertedReceipt(err) {
1931
+ return err instanceof Error && err.message.includes("transaction reverted (receipt status 0x0)");
1932
+ }
1678
1933
  function generateSdkId() {
1679
1934
  return `sdk-${crypto.randomUUID()}`;
1680
1935
  }
@@ -1684,7 +1939,7 @@ function generateClientLaunchId() {
1684
1939
  function generateClientTradeId() {
1685
1940
  return generateSdkId();
1686
1941
  }
1687
- var SDK_VERSION = "0.4.6";
1942
+ var SDK_VERSION = "0.4.8";
1688
1943
  var sdkUpdateWarned = false;
1689
1944
  function noteLatestSdkVersion(value) {
1690
1945
  if (sdkUpdateWarned || !value || !/^\d+\.\d+\.\d+$/.test(value))
@@ -1809,6 +2064,79 @@ class KeychainSecretStore {
1809
2064
  await this.exec("secret-tool", ["clear", "service", SERVICE, "account", ref]);
1810
2065
  }
1811
2066
  }
2067
+ // src/plans.ts
2068
+ var PLAN_CAPABILITY_NOTE = "A capability marked yes is what the plan allows. It is subject to the deployment's own switches (perps and own-wallet bridges each have one) and to the wallet, scopes and setup the feature needs. Every plan can launch a token from its embedded wallet, with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the headless launch; the two launch rows are additional routes, not the only ones.";
2069
+ var PLAN_CAPABILITY_LABELS = {
2070
+ tradeCandleTokens: "Trade Candle-launched tokens",
2071
+ tradeBaseAssets: "Trade base assets",
2072
+ freeBaseTransfers: "Base-pair swaps and own-wallet bridges (when enabled), no Candle fee",
2073
+ sellExternalTokens: "Sell tokens not launched on Candle",
2074
+ buyExternalTokens: "Buy tokens not launched on Candle",
2075
+ hyperliquidPerps: "Hyperliquid perps (when enabled)",
2076
+ selfLaunch: "Launch from a linked or TEE wallet (self-signed)",
2077
+ atomicLaunch: "Atomic launch: launch + 1–4 first buys in one bundle",
2078
+ createLinkedWallets: "Create linked wallets",
2079
+ importLinkedWallets: "Import linked wallets",
2080
+ limitOrders: "Limit orders",
2081
+ quant: "Quant on Telegram"
2082
+ };
2083
+ var PLAN_LABELS = { free: "Free", believer: "Believer", pro: "Pro", max: "Max" };
2084
+ function planLabel(plan) {
2085
+ return Object.hasOwn(PLAN_LABELS, plan) ? PLAN_LABELS[plan] : plan;
2086
+ }
2087
+ function formatPlanBps(bps) {
2088
+ return bps === 0 ? "none" : `${Number((bps / 100).toFixed(4))}%`;
2089
+ }
2090
+ function count(n) {
2091
+ return n.toLocaleString("en-US");
2092
+ }
2093
+ function priceCell(entry) {
2094
+ if (entry.price)
2095
+ return `$${count(entry.price.pricePerMonthUsd)} a month`;
2096
+ return entry.plan === "free" ? "free" : "not sold";
2097
+ }
2098
+ function planTableRows(table) {
2099
+ const plans = table.plans;
2100
+ const row = (label, cell) => [label, ...plans.map(cell)];
2101
+ const rows = [
2102
+ row("Price", priceCell),
2103
+ row("Agent trade fee", (e) => formatPlanBps(e.feeBps)),
2104
+ row("Perps builder fee", (e) => formatPlanBps(e.perpFeeBps)),
2105
+ row("Requests per minute", (e) => count(e.limits.rateLimitPerMin)),
2106
+ row("Launches per day", (e) => count(e.limits.dailyLaunchCap)),
2107
+ row("Uploads per minute", (e) => count(e.limits.uploadsPerMin)),
2108
+ row("Linked wallets", (e) => count(e.limits.linkedWallets))
2109
+ ];
2110
+ const known = Object.keys(PLAN_CAPABILITY_LABELS);
2111
+ const served = new Set;
2112
+ for (const entry of plans)
2113
+ for (const key of Object.keys(entry.capabilities ?? {}))
2114
+ served.add(key);
2115
+ const keys = [
2116
+ ...known.filter((k) => served.has(k)),
2117
+ ...[...served].filter((k) => !Object.hasOwn(PLAN_CAPABILITY_LABELS, k))
2118
+ ];
2119
+ for (const key of keys) {
2120
+ const label = Object.hasOwn(PLAN_CAPABILITY_LABELS, key) ? PLAN_CAPABILITY_LABELS[key] : key;
2121
+ rows.push(row(label, (e) => {
2122
+ const value = e.capabilities?.[key];
2123
+ return value === true ? "yes" : value === false ? "no" : "-";
2124
+ }));
2125
+ }
2126
+ return { headers: ["", ...plans.map((e) => planLabel(e.plan))], rows };
2127
+ }
2128
+ function planPromotionLine(table) {
2129
+ if (!(table.promoMaxDays > 0))
2130
+ return null;
2131
+ const days = table.promoMaxDays === 1 ? "1 day" : `${table.promoMaxDays} days`;
2132
+ return `A first Pro purchase includes Max for its first ${days}, then continues on Pro.`;
2133
+ }
2134
+ function planTableMarkdown(table) {
2135
+ const { headers, rows } = planTableRows(table);
2136
+ const line = (cells) => `| ${cells.map((c) => c.replace(/\|/g, "\\|")).join(" | ")} |`;
2137
+ return [line(headers), line(headers.map(() => "---")), ...rows.map(line)].join(`
2138
+ `);
2139
+ }
1812
2140
  // src/file-lock.ts
1813
2141
  var STALE_MS = 30000;
1814
2142
  var RETRY_MS = 25;
@@ -1970,10 +2298,19 @@ function verifyWebhookSignature(secret, header, body, nowSec, toleranceSec = 300
1970
2298
  export {
1971
2299
  verifyWebhookSignature,
1972
2300
  verifyPerpsBuild,
2301
+ planTableRows,
2302
+ planTableMarkdown,
2303
+ planPromotionLine,
2304
+ planLabel,
2305
+ perpsDepositProblem,
1973
2306
  isSolanaRpcErrorData,
1974
2307
  hyperliquidActionHash,
1975
2308
  generateSignerKeypair,
2309
+ formatPlanBps,
1976
2310
  encryptWalletKeyForImport,
2311
+ TradeLandedFeeLegError,
2312
+ PLAN_CAPABILITY_NOTE,
2313
+ PLAN_CAPABILITY_LABELS,
1977
2314
  KeychainSecretStore,
1978
2315
  JsonRpcError,
1979
2316
  InMemorySecretStore,
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The served plan table (Plans v2, PL-ED-1 and PL-ED-14): its types and one way to render it.
3
+ *
4
+ * `GET /api/v1/agent/plans` (no credential) and the `planTable` field of `GET /api/v1/agent/tier`
5
+ * return the table in force on the deployment that answered: each plan's price, agent fee, perps
6
+ * builder fee, the limits a new key gets, and what the plan can do. A client quotes these numbers
7
+ * rather than numbers it bundled, so it never shows a price or a fee the server is not charging.
8
+ *
9
+ * Dependency-free on purpose. The CLI and the MCP server carry byte-identical copies
10
+ * (`plans.drift.test.ts` in each), and the repo's docs freshness gate renders the docs plan table
11
+ * through this file, so the CLI, the MCP text and the docs say the same thing in the same words.
12
+ * Copy any change verbatim: `cp packages/sdk/src/plans.ts packages/cli/src/plans.ts` and the same
13
+ * for `packages/mcp/src/plans.ts`.
14
+ */
15
+ /**
16
+ * A plan name. `believer` is served only by a deployment that has not switched to the three plans
17
+ * (Free, Pro, Max); it is accepted here for one release after that launch, then removed.
18
+ */
19
+ export type PlanName = "free" | "pro" | "max" | "believer";
20
+ /** A sold plan's price, in the shape the tier response's `maxPricing` has always had. */
21
+ export interface PlanPrice {
22
+ pricePerMonthUsd: number;
23
+ currency: string;
24
+ approval: string;
25
+ }
26
+ /** What a freshly issued key on the plan gets. Enforcement never lowers a key set higher by hand. */
27
+ export interface PlanLimits {
28
+ rateLimitPerMin: number;
29
+ dailyLaunchCap: number;
30
+ uploadsPerMin: number;
31
+ /** Linked wallets the account may have active at once. */
32
+ linkedWallets: number;
33
+ }
34
+ /** What a plan can do, one flag per gate the API enforces (PL-ED-14). */
35
+ export interface PlanCapabilities {
36
+ /** Buy a token that was not launched on Candle and is not a base asset. */
37
+ buyExternalTokens: boolean;
38
+ /** Sell such a token. True on every plan, so losing a plan never traps a position. */
39
+ sellExternalTokens: boolean;
40
+ tradeBaseAssets: boolean;
41
+ tradeCandleTokens: boolean;
42
+ selfLaunch: boolean;
43
+ atomicLaunch: boolean;
44
+ createLinkedWallets: boolean;
45
+ importLinkedWallets: boolean;
46
+ /** Plan eligibility only: whether perps are open on the deployment is its own switch, and trading needs the wallet and scopes. */
47
+ hyperliquidPerps: boolean;
48
+ limitOrders: boolean;
49
+ /** The quant bot on Telegram. */
50
+ quant: boolean;
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
+ freeBaseTransfers: boolean;
53
+ }
54
+ export interface PlanTableEntry {
55
+ plan: PlanName;
56
+ /**
57
+ * Absent from a deployment that predates the capability table. A newer deployment may add a
58
+ * capability this release does not name; `planTableRows` shows it under its own key.
59
+ */
60
+ capabilities?: PlanCapabilities;
61
+ /** The agent platform fee this deployment charges, in basis points, overrides included. */
62
+ feeBps: number;
63
+ /** The Hyperliquid perps builder fee, in basis points. */
64
+ perpFeeBps: number;
65
+ /** Null when the plan is not sold: Free, Believer, and Pro before the three-plan launch. */
66
+ price: PlanPrice | null;
67
+ limits: PlanLimits;
68
+ }
69
+ export interface PlanTable {
70
+ plans: PlanTableEntry[];
71
+ /** Days of Max a first Pro purchase grants. 0 when there is no promotion. */
72
+ promoMaxDays: number;
73
+ }
74
+ /** `GET /api/v1/agent/plans`. */
75
+ export interface AgentPlansResult extends PlanTable {
76
+ success: true;
77
+ }
78
+ /**
79
+ * What a capability "yes" means. It is the plan's eligibility, not a statement that the deployment has
80
+ * the feature switched on, so perps and own-wallet bridges say "when enabled" in their labels.
81
+ */
82
+ export declare const PLAN_CAPABILITY_NOTE = "A capability marked yes is what the plan allows. It is subject to the deployment's own switches (perps and own-wallet bridges each have one) and to the wallet, scopes and setup the feature needs. Every plan can launch a token from its embedded wallet, with an optional dev buy (same transaction on Solana; best-effort follow-up on Hood), through the headless launch; the two launch rows are additional routes, not the only ones.";
83
+ /** The row label for each capability, in the order the rows are shown. */
84
+ export declare const PLAN_CAPABILITY_LABELS: Record<keyof PlanCapabilities, string>;
85
+ /** "Free", "Pro", "Max", "Believer"; an unknown name as served. */
86
+ export declare function planLabel(plan: string): string;
87
+ /** Basis points as a percentage: 100 is "1%", 25 is "0.25%", 0 is "none". */
88
+ export declare function formatPlanBps(bps: number): string;
89
+ /**
90
+ * The table as rows of text cells: a header row of plan names, then price, fees, limits and one
91
+ * row per capability. The CLI prints it as a terminal table and the docs as a Markdown table.
92
+ * Capabilities this release does not name are appended under their own key, so a newer server's
93
+ * additions are shown rather than dropped.
94
+ */
95
+ export declare function planTableRows(table: PlanTable): {
96
+ headers: string[];
97
+ rows: string[][];
98
+ };
99
+ /** The promotion as a sentence, or null when there is none. */
100
+ export declare function planPromotionLine(table: PlanTable): string | null;
101
+ /** The table as Markdown: what the MCP tool returns beside the JSON, and what the docs embed. */
102
+ export declare function planTableMarkdown(table: PlanTable): string;
103
+ //# sourceMappingURL=plans.d.ts.map
@@ -0,0 +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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@candledottv/agent-sdk",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
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",