@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/README.md +87 -16
- package/dist/client.d.ts +432 -36
- package/dist/client.d.ts.map +1 -1
- package/dist/errors.d.ts +47 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/hyperliquid-pnl.d.ts +34 -0
- package/dist/hyperliquid-pnl.d.ts.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +355 -18
- package/dist/plans.d.ts +103 -0
- package/dist/plans.d.ts.map +1 -0
- package/package.json +1 -1
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
|
|
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
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
|
1436
|
-
|
|
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.
|
|
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,
|
package/dist/plans.d.ts
ADDED
|
@@ -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