opentool 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/hyperliquid/browser.d.ts +2 -100
- package/dist/adapters/hyperliquid/browser.js +847 -14
- package/dist/adapters/hyperliquid/browser.js.map +1 -1
- package/dist/adapters/hyperliquid/index.d.ts +4 -184
- package/dist/adapters/hyperliquid/index.js +591 -45
- package/dist/adapters/hyperliquid/index.js.map +1 -1
- package/dist/adapters/polymarket/index.d.ts +1 -1
- package/dist/browser-DSwtpkyr.d.ts +701 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +591 -45
- package/dist/index.js.map +1 -1
- package/dist/{types-DKohXZes.d.ts → types-BaTmu0gS.d.ts} +1 -1
- package/dist/wallet/browser.d.ts +1 -1
- package/dist/wallet/browser.js +3 -2
- package/dist/wallet/browser.js.map +1 -1
- package/dist/wallet/index.d.ts +2 -2
- package/package.json +1 -1
- package/templates/base/package.json +1 -1
- package/dist/exchange-XC9MHmxJ.d.ts +0 -360
|
@@ -156,11 +156,31 @@ function computeHyperliquidMarketIocLimitPrice(params) {
|
|
|
156
156
|
const slippage = bps / 1e4;
|
|
157
157
|
const multiplier = params.side === "buy" ? 1 + slippage : 1 - slippage;
|
|
158
158
|
const price = params.markPrice * multiplier;
|
|
159
|
-
|
|
159
|
+
const precision = Math.max(0, Math.min(12, Math.floor(decimals)));
|
|
160
|
+
const factor = 10 ** precision;
|
|
161
|
+
const scaled = price * factor;
|
|
162
|
+
const directionalRounded = params.side === "buy" ? Math.ceil(scaled) / factor : Math.floor(scaled) / factor;
|
|
163
|
+
return formatRoundedDecimal(directionalRounded, precision);
|
|
160
164
|
}
|
|
161
165
|
var HyperliquidApiError = class extends Error {
|
|
162
166
|
constructor(message, response) {
|
|
163
|
-
|
|
167
|
+
const responseRecord = response && typeof response === "object" ? response : null;
|
|
168
|
+
const explicitErrors = Array.isArray(responseRecord?.errors) ? responseRecord.errors.filter(
|
|
169
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
170
|
+
) : [];
|
|
171
|
+
const bodyStatuses = responseRecord?.body && typeof responseRecord.body === "object" && responseRecord.body !== null && "response" in responseRecord.body ? (responseRecord.body.response?.data?.statuses ?? []).map((status) => typeof status?.error === "string" ? status.error : null).filter((entry) => Boolean(entry && entry.trim().length > 0)) : [];
|
|
172
|
+
const singleStatusError = responseRecord?.body && typeof responseRecord.body === "object" && responseRecord.body !== null && "response" in responseRecord.body ? responseRecord.body.response?.data?.status?.error : null;
|
|
173
|
+
const details = Array.from(
|
|
174
|
+
new Set(
|
|
175
|
+
[
|
|
176
|
+
...explicitErrors,
|
|
177
|
+
...bodyStatuses,
|
|
178
|
+
typeof singleStatusError === "string" ? singleStatusError : null
|
|
179
|
+
].filter((entry) => Boolean(entry && entry.trim().length > 0))
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
const enrichedMessage = details.length > 0 ? `${message} ${details.join(" | ")}` : message;
|
|
183
|
+
super(enrichedMessage);
|
|
164
184
|
this.response = response;
|
|
165
185
|
this.name = "HyperliquidApiError";
|
|
166
186
|
}
|
|
@@ -388,7 +408,14 @@ async function resolveHyperliquidAssetIndex(args) {
|
|
|
388
408
|
}
|
|
389
409
|
function toApiDecimal(value) {
|
|
390
410
|
if (typeof value === "string") {
|
|
391
|
-
|
|
411
|
+
const trimmed = value.trim();
|
|
412
|
+
if (!trimmed.length) {
|
|
413
|
+
throw new Error("Decimal strings must be non-empty.");
|
|
414
|
+
}
|
|
415
|
+
if (!/^-?(?:\d+\.?\d*|\.\d+)$/.test(trimmed)) {
|
|
416
|
+
throw new Error("Decimal strings must be plain base-10 numbers.");
|
|
417
|
+
}
|
|
418
|
+
return trimmed.replace(/^(-?)0+(?=\d)/, "$1").replace(/\.0*$|(\.\d+?)0+$/, "$1").replace(/^(-?)\./, "$10.").replace(/^-?$/, "0").replace(/^-0$/, "0");
|
|
392
419
|
}
|
|
393
420
|
if (typeof value === "bigint") {
|
|
394
421
|
return value.toString();
|
|
@@ -1224,7 +1251,7 @@ async function placeHyperliquidTwapOrder(options) {
|
|
|
1224
1251
|
symbol: twap.symbol,
|
|
1225
1252
|
baseUrl: API_BASES[env],
|
|
1226
1253
|
environment: env,
|
|
1227
|
-
fetcher: fetch
|
|
1254
|
+
fetcher: (...args) => fetch(...args)
|
|
1228
1255
|
});
|
|
1229
1256
|
const action = {
|
|
1230
1257
|
type: "twapOrder",
|
|
@@ -1246,7 +1273,7 @@ async function cancelHyperliquidTwapOrder(options) {
|
|
|
1246
1273
|
symbol: options.cancel.symbol,
|
|
1247
1274
|
baseUrl: API_BASES[env],
|
|
1248
1275
|
environment: env,
|
|
1249
|
-
fetcher: fetch
|
|
1276
|
+
fetcher: (...args) => fetch(...args)
|
|
1250
1277
|
});
|
|
1251
1278
|
const action = {
|
|
1252
1279
|
type: "twapCancel",
|
|
@@ -1263,7 +1290,7 @@ async function updateHyperliquidLeverage(options) {
|
|
|
1263
1290
|
symbol: options.input.symbol,
|
|
1264
1291
|
baseUrl: API_BASES[env],
|
|
1265
1292
|
environment: env,
|
|
1266
|
-
fetcher: fetch
|
|
1293
|
+
fetcher: (...args) => fetch(...args)
|
|
1267
1294
|
});
|
|
1268
1295
|
const action = {
|
|
1269
1296
|
type: "updateLeverage",
|
|
@@ -1281,7 +1308,7 @@ async function updateHyperliquidIsolatedMargin(options) {
|
|
|
1281
1308
|
symbol: options.input.symbol,
|
|
1282
1309
|
baseUrl: API_BASES[env],
|
|
1283
1310
|
environment: env,
|
|
1284
|
-
fetcher: fetch
|
|
1311
|
+
fetcher: (...args) => fetch(...args)
|
|
1285
1312
|
});
|
|
1286
1313
|
const action = {
|
|
1287
1314
|
type: "updateIsolatedMargin",
|
|
@@ -1396,7 +1423,7 @@ async function withAssetIndexes(options, entries, mapper) {
|
|
|
1396
1423
|
symbol: entry.symbol,
|
|
1397
1424
|
baseUrl: API_BASES[env],
|
|
1398
1425
|
environment: env,
|
|
1399
|
-
fetcher: fetch
|
|
1426
|
+
fetcher: (...args) => fetch(...args)
|
|
1400
1427
|
});
|
|
1401
1428
|
return mapper(assetIndex, entry);
|
|
1402
1429
|
})
|
|
@@ -1411,7 +1438,7 @@ async function buildOrder(intent, options) {
|
|
|
1411
1438
|
symbol: intent.symbol,
|
|
1412
1439
|
baseUrl: API_BASES[env],
|
|
1413
1440
|
environment: env,
|
|
1414
|
-
fetcher: fetch
|
|
1441
|
+
fetcher: (...args) => fetch(...args)
|
|
1415
1442
|
});
|
|
1416
1443
|
const limitOrTrigger = intent.trigger ? mapTrigger(intent.trigger) : {
|
|
1417
1444
|
limit: {
|
|
@@ -1574,7 +1601,7 @@ function assertPositiveDecimalInput(value, label) {
|
|
|
1574
1601
|
if (!trimmed.length) {
|
|
1575
1602
|
throw new Error(`${label} must be a non-empty string.`);
|
|
1576
1603
|
}
|
|
1577
|
-
if (!/^(
|
|
1604
|
+
if (!/^(?:\d+\.?\d*|\.\d+)$/.test(trimmed)) {
|
|
1578
1605
|
throw new Error(`${label} must be a positive decimal string.`);
|
|
1579
1606
|
}
|
|
1580
1607
|
const numeric = Number(trimmed);
|
|
@@ -1587,10 +1614,10 @@ function normalizePositiveDecimalString(raw, label) {
|
|
|
1587
1614
|
if (!trimmed.length) {
|
|
1588
1615
|
throw new Error(`${label} must be a non-empty decimal string.`);
|
|
1589
1616
|
}
|
|
1590
|
-
if (!/^(
|
|
1617
|
+
if (!/^(?:\d+\.?\d*|\.\d+)$/.test(trimmed)) {
|
|
1591
1618
|
throw new Error(`${label} must be a positive decimal string.`);
|
|
1592
1619
|
}
|
|
1593
|
-
const normalized = trimmed.replace(/^0+(
|
|
1620
|
+
const normalized = trimmed.replace(/^0+(?=\d)/, "").replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
|
|
1594
1621
|
const numeric = Number(normalized);
|
|
1595
1622
|
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
1596
1623
|
throw new Error(`${label} must be positive.`);
|
|
@@ -1626,7 +1653,7 @@ async function placeHyperliquidOrder(options) {
|
|
|
1626
1653
|
symbol: intent.symbol,
|
|
1627
1654
|
baseUrl: resolvedBaseUrl,
|
|
1628
1655
|
environment: inferredEnvironment,
|
|
1629
|
-
fetcher: fetch
|
|
1656
|
+
fetcher: (...args) => fetch(...args)
|
|
1630
1657
|
});
|
|
1631
1658
|
const order = {
|
|
1632
1659
|
a: assetIndex,
|
|
@@ -1925,6 +1952,812 @@ function createHyperliquidActionHash(params) {
|
|
|
1925
1952
|
return createL1ActionHash(params);
|
|
1926
1953
|
}
|
|
1927
1954
|
|
|
1928
|
-
|
|
1955
|
+
// src/adapters/hyperliquid/symbols.ts
|
|
1956
|
+
var UNKNOWN_SYMBOL2 = "UNKNOWN";
|
|
1957
|
+
function extractHyperliquidDex(symbol) {
|
|
1958
|
+
const idx = symbol.indexOf(":");
|
|
1959
|
+
if (idx <= 0) return null;
|
|
1960
|
+
const dex = symbol.slice(0, idx).trim().toLowerCase();
|
|
1961
|
+
return dex || null;
|
|
1962
|
+
}
|
|
1963
|
+
function parseHyperliquidSymbol(value) {
|
|
1964
|
+
if (!value) return null;
|
|
1965
|
+
const trimmed = value.trim();
|
|
1966
|
+
if (!trimmed) return null;
|
|
1967
|
+
if (trimmed.startsWith("@")) {
|
|
1968
|
+
return {
|
|
1969
|
+
raw: trimmed,
|
|
1970
|
+
kind: "spotIndex",
|
|
1971
|
+
normalized: trimmed,
|
|
1972
|
+
routeTicker: trimmed,
|
|
1973
|
+
displaySymbol: trimmed,
|
|
1974
|
+
base: null,
|
|
1975
|
+
quote: null,
|
|
1976
|
+
pair: null,
|
|
1977
|
+
dex: null,
|
|
1978
|
+
leverageMode: "cross"
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
const dex = extractHyperliquidDex(trimmed);
|
|
1982
|
+
const pair = resolveHyperliquidPair(trimmed);
|
|
1983
|
+
const base = normalizeHyperliquidBaseSymbol(trimmed);
|
|
1984
|
+
if (dex) {
|
|
1985
|
+
if (!base) return null;
|
|
1986
|
+
return {
|
|
1987
|
+
raw: trimmed,
|
|
1988
|
+
kind: "perp",
|
|
1989
|
+
normalized: `${dex}:${base}`,
|
|
1990
|
+
routeTicker: `${dex}:${base}`,
|
|
1991
|
+
displaySymbol: `${dex.toUpperCase()}:${base}-USDC`,
|
|
1992
|
+
base,
|
|
1993
|
+
quote: null,
|
|
1994
|
+
pair: null,
|
|
1995
|
+
dex,
|
|
1996
|
+
leverageMode: "isolated"
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
if (pair) {
|
|
2000
|
+
const [pairBase, pairQuote] = pair.split("/");
|
|
2001
|
+
return {
|
|
2002
|
+
raw: trimmed,
|
|
2003
|
+
kind: "spot",
|
|
2004
|
+
normalized: pair,
|
|
2005
|
+
routeTicker: pair.replace("/", "-"),
|
|
2006
|
+
displaySymbol: pair.replace("/", "-"),
|
|
2007
|
+
base: pairBase ?? null,
|
|
2008
|
+
quote: pairQuote ?? null,
|
|
2009
|
+
pair,
|
|
2010
|
+
dex: null,
|
|
2011
|
+
leverageMode: "cross"
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
if (!base) return null;
|
|
2015
|
+
return {
|
|
2016
|
+
raw: trimmed,
|
|
2017
|
+
kind: "perp",
|
|
2018
|
+
normalized: base,
|
|
2019
|
+
routeTicker: base,
|
|
2020
|
+
displaySymbol: `${base}-USDC`,
|
|
2021
|
+
base,
|
|
2022
|
+
quote: null,
|
|
2023
|
+
pair: null,
|
|
2024
|
+
dex: null,
|
|
2025
|
+
leverageMode: "cross"
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
function normalizeSpotTokenName2(value) {
|
|
2029
|
+
const raw = (value ?? "").trim();
|
|
2030
|
+
if (!raw) return "";
|
|
2031
|
+
if (raw.endsWith("0") && raw.length > 1) {
|
|
2032
|
+
return raw.slice(0, -1);
|
|
2033
|
+
}
|
|
2034
|
+
return raw;
|
|
2035
|
+
}
|
|
2036
|
+
function normalizeHyperliquidBaseSymbol(value) {
|
|
2037
|
+
if (!value) return null;
|
|
2038
|
+
const trimmed = value.trim();
|
|
2039
|
+
if (!trimmed) return null;
|
|
2040
|
+
const withoutDex = trimmed.includes(":") ? trimmed.split(":").slice(1).join(":") : trimmed;
|
|
2041
|
+
const base = withoutDex.split("-")[0] ?? withoutDex;
|
|
2042
|
+
const baseNoPair = base.split("/")[0] ?? base;
|
|
2043
|
+
const normalized = baseNoPair.trim().toUpperCase();
|
|
2044
|
+
if (!normalized || normalized === UNKNOWN_SYMBOL2) return null;
|
|
2045
|
+
return normalized;
|
|
2046
|
+
}
|
|
2047
|
+
function normalizeHyperliquidMetaSymbol(symbol) {
|
|
2048
|
+
const trimmed = symbol.trim();
|
|
2049
|
+
const noDex = trimmed.includes(":") ? trimmed.split(":").slice(1).join(":") : trimmed;
|
|
2050
|
+
const noPair = noDex.split("-")[0] ?? noDex;
|
|
2051
|
+
return (noPair.split("/")[0] ?? noPair).trim();
|
|
2052
|
+
}
|
|
2053
|
+
function resolveHyperliquidPair(value) {
|
|
2054
|
+
if (!value) return null;
|
|
2055
|
+
const trimmed = value.trim();
|
|
2056
|
+
if (!trimmed) return null;
|
|
2057
|
+
const withoutDex = trimmed.includes(":") ? trimmed.split(":").slice(1).join(":") : trimmed;
|
|
2058
|
+
if (withoutDex.includes("/")) {
|
|
2059
|
+
return withoutDex.toUpperCase();
|
|
2060
|
+
}
|
|
2061
|
+
if (withoutDex.includes("-")) {
|
|
2062
|
+
const [base, ...rest] = withoutDex.split("-");
|
|
2063
|
+
const quote = rest.join("-").trim();
|
|
2064
|
+
if (!base || !quote) return null;
|
|
2065
|
+
return `${base.toUpperCase()}/${quote.toUpperCase()}`;
|
|
2066
|
+
}
|
|
2067
|
+
return null;
|
|
2068
|
+
}
|
|
2069
|
+
function resolveHyperliquidLeverageMode(symbol) {
|
|
2070
|
+
return symbol.includes(":") ? "isolated" : "cross";
|
|
2071
|
+
}
|
|
2072
|
+
function resolveHyperliquidProfileChain(environment) {
|
|
2073
|
+
return environment === "testnet" ? "hyperliquid-testnet" : "hyperliquid";
|
|
2074
|
+
}
|
|
2075
|
+
function buildHyperliquidProfileAssets(params) {
|
|
2076
|
+
const chain = resolveHyperliquidProfileChain(params.environment);
|
|
2077
|
+
return params.assets.map((asset) => {
|
|
2078
|
+
const symbols = asset.assetSymbols.map((symbol) => normalizeHyperliquidBaseSymbol(symbol)).filter((symbol) => Boolean(symbol));
|
|
2079
|
+
if (symbols.length === 0) return null;
|
|
2080
|
+
const explicitPair = typeof asset.pair === "string" ? resolveHyperliquidPair(asset.pair) : null;
|
|
2081
|
+
const derivedPair = symbols.length === 1 ? resolveHyperliquidPair(asset.assetSymbols[0] ?? symbols[0]) : null;
|
|
2082
|
+
const pair = explicitPair ?? derivedPair ?? void 0;
|
|
2083
|
+
const leverage = typeof asset.leverage === "number" && Number.isFinite(asset.leverage) && asset.leverage > 0 ? asset.leverage : void 0;
|
|
2084
|
+
const walletAddress = typeof asset.walletAddress === "string" && asset.walletAddress.trim().length > 0 ? asset.walletAddress.trim() : void 0;
|
|
2085
|
+
return {
|
|
2086
|
+
venue: "hyperliquid",
|
|
2087
|
+
chain,
|
|
2088
|
+
assetSymbols: symbols,
|
|
2089
|
+
...pair ? { pair } : {},
|
|
2090
|
+
...leverage ? { leverage } : {},
|
|
2091
|
+
...walletAddress ? { walletAddress } : {}
|
|
2092
|
+
};
|
|
2093
|
+
}).filter((asset) => asset !== null);
|
|
2094
|
+
}
|
|
2095
|
+
function parseSpotPairSymbol(symbol) {
|
|
2096
|
+
const trimmed = symbol.trim();
|
|
2097
|
+
if (!trimmed.includes("/")) return null;
|
|
2098
|
+
const [rawBase, rawQuote] = trimmed.split("/");
|
|
2099
|
+
const base = rawBase?.trim().toUpperCase() ?? "";
|
|
2100
|
+
const quote = rawQuote?.trim().toUpperCase() ?? "";
|
|
2101
|
+
if (!base || !quote) return null;
|
|
2102
|
+
return { base, quote };
|
|
2103
|
+
}
|
|
2104
|
+
function isHyperliquidSpotSymbol(symbol) {
|
|
2105
|
+
return symbol.startsWith("@") || symbol.includes("/");
|
|
2106
|
+
}
|
|
2107
|
+
function resolveSpotMidCandidates(baseSymbol) {
|
|
2108
|
+
const base = baseSymbol.trim().toUpperCase();
|
|
2109
|
+
if (!base) return [];
|
|
2110
|
+
const candidates = [base];
|
|
2111
|
+
if (base.startsWith("U") && base.length > 1) {
|
|
2112
|
+
candidates.push(base.slice(1));
|
|
2113
|
+
}
|
|
2114
|
+
return Array.from(new Set(candidates));
|
|
2115
|
+
}
|
|
2116
|
+
function resolveSpotTokenCandidates(value) {
|
|
2117
|
+
const normalized = normalizeSpotTokenName2(value).toUpperCase();
|
|
2118
|
+
if (!normalized) return [];
|
|
2119
|
+
const candidates = [normalized];
|
|
2120
|
+
if (normalized.startsWith("U") && normalized.length > 1) {
|
|
2121
|
+
candidates.push(normalized.slice(1));
|
|
2122
|
+
}
|
|
2123
|
+
return Array.from(new Set(candidates));
|
|
2124
|
+
}
|
|
2125
|
+
function resolveHyperliquidOrderSymbol(value) {
|
|
2126
|
+
if (!value) return null;
|
|
2127
|
+
const trimmed = value.trim();
|
|
2128
|
+
if (!trimmed) return null;
|
|
2129
|
+
if (trimmed.startsWith("@")) return trimmed;
|
|
2130
|
+
if (trimmed.includes(":")) {
|
|
2131
|
+
const [rawDex, ...restParts] = trimmed.split(":");
|
|
2132
|
+
const dex = rawDex.trim().toLowerCase();
|
|
2133
|
+
const rest = restParts.join(":");
|
|
2134
|
+
const base = rest.split("/")[0]?.split("-")[0] ?? rest;
|
|
2135
|
+
const normalizedBase = base.trim().toUpperCase();
|
|
2136
|
+
if (!dex || !normalizedBase || normalizedBase === UNKNOWN_SYMBOL2) {
|
|
2137
|
+
return null;
|
|
2138
|
+
}
|
|
2139
|
+
return `${dex}:${normalizedBase}`;
|
|
2140
|
+
}
|
|
2141
|
+
const pair = resolveHyperliquidPair(trimmed);
|
|
2142
|
+
if (pair) return pair;
|
|
2143
|
+
return normalizeHyperliquidBaseSymbol(trimmed);
|
|
2144
|
+
}
|
|
2145
|
+
function resolveHyperliquidSymbol(asset, override) {
|
|
2146
|
+
const raw = override && override.trim().length > 0 ? override.trim() : asset.trim();
|
|
2147
|
+
if (!raw) return raw;
|
|
2148
|
+
if (raw.startsWith("@")) return raw;
|
|
2149
|
+
if (raw.includes(":")) {
|
|
2150
|
+
const [dexRaw, ...restParts] = raw.split(":");
|
|
2151
|
+
const dex = dexRaw.trim().toLowerCase();
|
|
2152
|
+
const rest = restParts.join(":");
|
|
2153
|
+
const base2 = rest.split("/")[0]?.split("-")[0] ?? rest;
|
|
2154
|
+
const normalizedBase = base2.trim().toUpperCase();
|
|
2155
|
+
if (!dex) return normalizedBase;
|
|
2156
|
+
return `${dex}:${normalizedBase}`;
|
|
2157
|
+
}
|
|
2158
|
+
if (raw.includes("/")) {
|
|
2159
|
+
return raw.toUpperCase();
|
|
2160
|
+
}
|
|
2161
|
+
if (raw.includes("-")) {
|
|
2162
|
+
const [base2, ...rest] = raw.split("-");
|
|
2163
|
+
const quote = rest.join("-").trim();
|
|
2164
|
+
if (base2 && quote) {
|
|
2165
|
+
return `${base2.toUpperCase()}/${quote.toUpperCase()}`;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
const base = raw.split("-")[0] ?? raw;
|
|
2169
|
+
const baseNoPair = base.split("/")[0] ?? base;
|
|
2170
|
+
return baseNoPair.trim().toUpperCase();
|
|
2171
|
+
}
|
|
2172
|
+
function resolveHyperliquidPerpSymbol(asset) {
|
|
2173
|
+
const raw = asset.trim();
|
|
2174
|
+
if (!raw) return raw;
|
|
2175
|
+
const dex = extractHyperliquidDex(raw);
|
|
2176
|
+
const base = normalizeHyperliquidBaseSymbol(raw) ?? raw.toUpperCase();
|
|
2177
|
+
return dex ? `${dex}:${base}` : base;
|
|
2178
|
+
}
|
|
2179
|
+
function resolveHyperliquidSpotSymbol(asset, defaultQuote = "USDC") {
|
|
2180
|
+
const quote = defaultQuote.trim().toUpperCase() || "USDC";
|
|
2181
|
+
const raw = asset.trim().toUpperCase();
|
|
2182
|
+
if (!raw) {
|
|
2183
|
+
return { symbol: raw, base: raw, quote };
|
|
2184
|
+
}
|
|
2185
|
+
const pair = resolveHyperliquidPair(raw);
|
|
2186
|
+
if (pair) {
|
|
2187
|
+
const [base2, pairQuote] = pair.split("/");
|
|
2188
|
+
return {
|
|
2189
|
+
symbol: pair,
|
|
2190
|
+
base: base2?.trim() ?? raw,
|
|
2191
|
+
quote: pairQuote?.trim() ?? quote
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
const base = normalizeHyperliquidBaseSymbol(raw) ?? raw;
|
|
2195
|
+
return { symbol: `${base}/${quote}`, base, quote };
|
|
2196
|
+
}
|
|
2197
|
+
function gcd(a, b) {
|
|
2198
|
+
let left = a < 0n ? -a : a;
|
|
2199
|
+
let right = b < 0n ? -b : b;
|
|
2200
|
+
while (right !== 0n) {
|
|
2201
|
+
const next = left % right;
|
|
2202
|
+
left = right;
|
|
2203
|
+
right = next;
|
|
2204
|
+
}
|
|
2205
|
+
return left;
|
|
2206
|
+
}
|
|
2207
|
+
function maxDecimals(values) {
|
|
2208
|
+
let max = 0;
|
|
2209
|
+
for (const value of values) {
|
|
2210
|
+
const dot = value.indexOf(".");
|
|
2211
|
+
if (dot === -1) continue;
|
|
2212
|
+
const decimals = value.length - dot - 1;
|
|
2213
|
+
if (decimals > max) max = decimals;
|
|
2214
|
+
}
|
|
2215
|
+
return max;
|
|
2216
|
+
}
|
|
2217
|
+
function toScaledInt(value, decimals) {
|
|
2218
|
+
const trimmed = value.trim();
|
|
2219
|
+
const negative = trimmed.startsWith("-");
|
|
2220
|
+
const unsigned = negative ? trimmed.slice(1) : trimmed;
|
|
2221
|
+
const [intPart, fracPart = ""] = unsigned.split(".");
|
|
2222
|
+
const padded = fracPart.padEnd(decimals, "0").slice(0, decimals);
|
|
2223
|
+
const combined = `${intPart || "0"}${padded}`;
|
|
2224
|
+
const asInt = BigInt(combined || "0");
|
|
2225
|
+
return negative ? -asInt : asInt;
|
|
2226
|
+
}
|
|
2227
|
+
function resolveSpotSizeDecimals(meta, symbol) {
|
|
2228
|
+
const universe = meta.universe ?? [];
|
|
2229
|
+
const tokens = meta.tokens ?? [];
|
|
2230
|
+
if (!universe.length || !tokens.length) {
|
|
2231
|
+
throw new Error(`Spot metadata unavailable for ${symbol}.`);
|
|
2232
|
+
}
|
|
2233
|
+
const tokenMap = /* @__PURE__ */ new Map();
|
|
2234
|
+
for (const token of tokens) {
|
|
2235
|
+
const index = token?.index;
|
|
2236
|
+
const szDecimals = typeof token?.szDecimals === "number" ? token.szDecimals : null;
|
|
2237
|
+
if (typeof index !== "number" || szDecimals == null) continue;
|
|
2238
|
+
tokenMap.set(index, {
|
|
2239
|
+
name: normalizeSpotTokenName2(token?.name),
|
|
2240
|
+
szDecimals
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
if (symbol.startsWith("@")) {
|
|
2244
|
+
const targetIndex = Number.parseInt(symbol.slice(1), 10);
|
|
2245
|
+
if (!Number.isFinite(targetIndex)) {
|
|
2246
|
+
throw new Error(`Invalid spot pair id: ${symbol}`);
|
|
2247
|
+
}
|
|
2248
|
+
for (let idx = 0; idx < universe.length; idx += 1) {
|
|
2249
|
+
const market = universe[idx];
|
|
2250
|
+
const marketIndex = typeof market?.index === "number" ? market.index : idx;
|
|
2251
|
+
if (marketIndex !== targetIndex) continue;
|
|
2252
|
+
const [baseIndex] = Array.isArray(market?.tokens) ? market.tokens : [];
|
|
2253
|
+
const baseToken = tokenMap.get(baseIndex ?? -1);
|
|
2254
|
+
if (!baseToken) break;
|
|
2255
|
+
return baseToken.szDecimals;
|
|
2256
|
+
}
|
|
2257
|
+
throw new Error(`Unknown spot pair id: ${symbol}`);
|
|
2258
|
+
}
|
|
2259
|
+
const pair = parseSpotPairSymbol(symbol);
|
|
2260
|
+
if (!pair) {
|
|
2261
|
+
throw new Error(`Invalid spot symbol: ${symbol}`);
|
|
2262
|
+
}
|
|
2263
|
+
const normalizedBase = normalizeSpotTokenName2(pair.base).toUpperCase();
|
|
2264
|
+
const normalizedQuote = normalizeSpotTokenName2(pair.quote).toUpperCase();
|
|
2265
|
+
for (const market of universe) {
|
|
2266
|
+
const [baseIndex, quoteIndex] = Array.isArray(market?.tokens) ? market.tokens : [];
|
|
2267
|
+
const baseToken = tokenMap.get(baseIndex ?? -1);
|
|
2268
|
+
const quoteToken = tokenMap.get(quoteIndex ?? -1);
|
|
2269
|
+
if (!baseToken || !quoteToken) continue;
|
|
2270
|
+
if (baseToken.name.toUpperCase() === normalizedBase && quoteToken.name.toUpperCase() === normalizedQuote) {
|
|
2271
|
+
return baseToken.szDecimals;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
throw new Error(`No size decimals found for ${symbol}.`);
|
|
2275
|
+
}
|
|
2276
|
+
async function fetchHyperliquidTickSize(params) {
|
|
2277
|
+
return fetchHyperliquidTickSizeForCoin(params.environment, params.symbol);
|
|
2278
|
+
}
|
|
2279
|
+
async function fetchHyperliquidTickSizeForCoin(environment, coin) {
|
|
2280
|
+
const base = API_BASES[environment];
|
|
2281
|
+
const res = await fetch(`${base}/info`, {
|
|
2282
|
+
method: "POST",
|
|
2283
|
+
headers: { "content-type": "application/json" },
|
|
2284
|
+
body: JSON.stringify({ type: "l2Book", coin })
|
|
2285
|
+
});
|
|
2286
|
+
if (!res.ok) {
|
|
2287
|
+
throw new Error(`Hyperliquid l2Book failed for ${coin}`);
|
|
2288
|
+
}
|
|
2289
|
+
const data = await res.json().catch(() => null);
|
|
2290
|
+
const levels = Array.isArray(data?.levels) ? data?.levels ?? [] : [];
|
|
2291
|
+
const prices = levels.flatMap((side) => Array.isArray(side) ? side.map((entry) => String(entry?.px ?? "")) : []).filter((px) => px.length > 0);
|
|
2292
|
+
if (prices.length < 2) {
|
|
2293
|
+
throw new Error(`Hyperliquid l2Book missing price levels for ${coin}`);
|
|
2294
|
+
}
|
|
2295
|
+
const decimals = maxDecimals(prices);
|
|
2296
|
+
const scaled = prices.map((px) => toScaledInt(px, decimals));
|
|
2297
|
+
const unique = Array.from(new Set(scaled.map((v) => v.toString()))).map((v) => BigInt(v)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
2298
|
+
let tick = 0n;
|
|
2299
|
+
for (let i = 1; i < unique.length; i += 1) {
|
|
2300
|
+
const diff = unique[i] - unique[i - 1];
|
|
2301
|
+
if (diff <= 0n) continue;
|
|
2302
|
+
tick = tick === 0n ? diff : gcd(tick, diff);
|
|
2303
|
+
}
|
|
2304
|
+
if (tick === 0n) {
|
|
2305
|
+
tick = 1n;
|
|
2306
|
+
}
|
|
2307
|
+
return { tickSizeInt: tick, tickDecimals: decimals };
|
|
2308
|
+
}
|
|
2309
|
+
async function fetchHyperliquidSizeDecimals(params) {
|
|
2310
|
+
const { symbol, environment } = params;
|
|
2311
|
+
if (isHyperliquidSpotSymbol(symbol)) {
|
|
2312
|
+
const meta2 = await fetchHyperliquidSpotMeta(environment);
|
|
2313
|
+
return resolveSpotSizeDecimals(meta2, symbol);
|
|
2314
|
+
}
|
|
2315
|
+
const meta = await fetchHyperliquidMeta(environment);
|
|
2316
|
+
const universe = Array.isArray(meta?.universe) ? meta.universe : [];
|
|
2317
|
+
const normalized = normalizeHyperliquidMetaSymbol(symbol).toUpperCase();
|
|
2318
|
+
const match = universe.find(
|
|
2319
|
+
(entry) => normalizeHyperliquidMetaSymbol(entry?.name ?? "").toUpperCase() === normalized
|
|
2320
|
+
);
|
|
2321
|
+
if (!match || typeof match.szDecimals !== "number") {
|
|
2322
|
+
throw new Error(`No size decimals found for ${symbol}.`);
|
|
2323
|
+
}
|
|
2324
|
+
return match.szDecimals;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
// src/adapters/hyperliquid/order-utils.ts
|
|
2328
|
+
function countDecimalPlaces(value) {
|
|
2329
|
+
const [, dec = ""] = value.split(".");
|
|
2330
|
+
return dec.length;
|
|
2331
|
+
}
|
|
2332
|
+
function assertNumberString(value) {
|
|
2333
|
+
if (!/^-?(?:\d+\.?\d*|\.\d+)$/.test(value)) {
|
|
2334
|
+
throw new TypeError("Invalid decimal number string.");
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
function normalizeDecimalString(value) {
|
|
2338
|
+
return value.trim().replace(/^(-?)0+(?=\d)/, "$1").replace(/\.0*$|(\.\d+?)0+$/, "$1").replace(/^(-?)\./, "$10.").replace(/^-?$/, "0").replace(/^-0$/, "0");
|
|
2339
|
+
}
|
|
2340
|
+
var StringMath = {
|
|
2341
|
+
log10Floor(value) {
|
|
2342
|
+
const abs = value.startsWith("-") ? value.slice(1) : value;
|
|
2343
|
+
const num = Number(abs);
|
|
2344
|
+
if (!Number.isFinite(num) || num === 0) return -Infinity;
|
|
2345
|
+
const [intPart, fracPart = ""] = abs.split(".");
|
|
2346
|
+
if (Number(intPart) !== 0) {
|
|
2347
|
+
return intPart.replace(/^0+/, "").length - 1;
|
|
2348
|
+
}
|
|
2349
|
+
const leadingZeros = fracPart.match(/^0*/)?.[0]?.length ?? 0;
|
|
2350
|
+
return -(leadingZeros + 1);
|
|
2351
|
+
},
|
|
2352
|
+
multiplyByPow10(value, exp) {
|
|
2353
|
+
if (!Number.isInteger(exp)) {
|
|
2354
|
+
throw new RangeError("Exponent must be an integer.");
|
|
2355
|
+
}
|
|
2356
|
+
if (exp === 0) return normalizeDecimalString(value);
|
|
2357
|
+
const negative = value.startsWith("-");
|
|
2358
|
+
const abs = negative ? value.slice(1) : value;
|
|
2359
|
+
const [intRaw, fracRaw = ""] = abs.split(".");
|
|
2360
|
+
const intPart = intRaw || "0";
|
|
2361
|
+
let output;
|
|
2362
|
+
if (exp > 0) {
|
|
2363
|
+
if (exp >= fracRaw.length) {
|
|
2364
|
+
output = intPart + fracRaw + "0".repeat(exp - fracRaw.length);
|
|
2365
|
+
} else {
|
|
2366
|
+
output = `${intPart}${fracRaw.slice(0, exp)}.${fracRaw.slice(exp)}`;
|
|
2367
|
+
}
|
|
2368
|
+
} else {
|
|
2369
|
+
const absExp = -exp;
|
|
2370
|
+
if (absExp >= intPart.length) {
|
|
2371
|
+
output = `0.${"0".repeat(absExp - intPart.length)}${intPart}${fracRaw}`;
|
|
2372
|
+
} else {
|
|
2373
|
+
output = `${intPart.slice(0, -absExp)}.${intPart.slice(-absExp)}${fracRaw}`;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
return normalizeDecimalString((negative ? "-" : "") + output);
|
|
2377
|
+
},
|
|
2378
|
+
trunc(value) {
|
|
2379
|
+
const index = value.indexOf(".");
|
|
2380
|
+
return index === -1 ? value : value.slice(0, index) || "0";
|
|
2381
|
+
},
|
|
2382
|
+
roundInteger(value, mode) {
|
|
2383
|
+
const normalized = normalizeDecimalString(value);
|
|
2384
|
+
const negative = normalized.startsWith("-");
|
|
2385
|
+
if (negative) {
|
|
2386
|
+
throw new RangeError("Directional rounding only supports positive values.");
|
|
2387
|
+
}
|
|
2388
|
+
const [intPartRaw, fracPart = ""] = normalized.split(".");
|
|
2389
|
+
const intPart = intPartRaw.replace(/^0+(?=\d)/, "") || "0";
|
|
2390
|
+
const hasFraction = /[1-9]/.test(fracPart);
|
|
2391
|
+
if (!hasFraction) return intPart;
|
|
2392
|
+
if (mode === "down") return intPart;
|
|
2393
|
+
const digits = intPart.split("");
|
|
2394
|
+
let carry = 1;
|
|
2395
|
+
for (let idx = digits.length - 1; idx >= 0 && carry > 0; idx -= 1) {
|
|
2396
|
+
const next = Number(digits[idx] ?? "0") + carry;
|
|
2397
|
+
digits[idx] = String(next % 10);
|
|
2398
|
+
carry = next >= 10 ? 1 : 0;
|
|
2399
|
+
}
|
|
2400
|
+
if (carry > 0) {
|
|
2401
|
+
digits.unshift("1");
|
|
2402
|
+
}
|
|
2403
|
+
return digits.join("").replace(/^0+(?=\d)/, "") || "0";
|
|
2404
|
+
},
|
|
2405
|
+
toPrecisionTruncate(value, precision) {
|
|
2406
|
+
if (!Number.isInteger(precision) || precision < 1) {
|
|
2407
|
+
throw new RangeError("Precision must be a positive integer.");
|
|
2408
|
+
}
|
|
2409
|
+
if (/^-?0+(\.0*)?$/.test(value)) return "0";
|
|
2410
|
+
const negative = value.startsWith("-");
|
|
2411
|
+
const abs = negative ? value.slice(1) : value;
|
|
2412
|
+
const magnitude = StringMath.log10Floor(abs);
|
|
2413
|
+
const shiftAmount = precision - magnitude - 1;
|
|
2414
|
+
const shifted = StringMath.multiplyByPow10(abs, shiftAmount);
|
|
2415
|
+
const truncated = StringMath.trunc(shifted);
|
|
2416
|
+
const shiftedBack = StringMath.multiplyByPow10(truncated, -shiftAmount);
|
|
2417
|
+
return normalizeDecimalString(negative ? `-${shiftedBack}` : shiftedBack);
|
|
2418
|
+
},
|
|
2419
|
+
toFixedTruncate(value, decimals) {
|
|
2420
|
+
if (!Number.isInteger(decimals) || decimals < 0) {
|
|
2421
|
+
throw new RangeError("Decimals must be a non-negative integer.");
|
|
2422
|
+
}
|
|
2423
|
+
const matcher = new RegExp(`^-?(?:\\d+)?(?:\\.\\d{0,${decimals}})?`);
|
|
2424
|
+
const result = value.match(matcher)?.[0];
|
|
2425
|
+
if (!result) {
|
|
2426
|
+
throw new TypeError("Invalid number format.");
|
|
2427
|
+
}
|
|
2428
|
+
return normalizeDecimalString(result);
|
|
2429
|
+
}
|
|
2430
|
+
};
|
|
2431
|
+
function ceilDiv(numerator, denominator) {
|
|
2432
|
+
if (denominator <= 0n) {
|
|
2433
|
+
throw new RangeError("Denominator must be positive.");
|
|
2434
|
+
}
|
|
2435
|
+
return (numerator + denominator - 1n) / denominator;
|
|
2436
|
+
}
|
|
2437
|
+
function scaleDecimalToInt(value, decimals, mode) {
|
|
2438
|
+
if (!Number.isInteger(decimals) || decimals < 0) {
|
|
2439
|
+
throw new RangeError("Decimals must be a non-negative integer.");
|
|
2440
|
+
}
|
|
2441
|
+
const normalized = normalizeDecimalString(value);
|
|
2442
|
+
assertNumberString(normalized);
|
|
2443
|
+
const negative = normalized.startsWith("-");
|
|
2444
|
+
if (negative) {
|
|
2445
|
+
throw new RangeError("Only positive values are supported.");
|
|
2446
|
+
}
|
|
2447
|
+
const shifted = StringMath.multiplyByPow10(normalized, decimals);
|
|
2448
|
+
const rounded = StringMath.roundInteger(shifted, mode);
|
|
2449
|
+
return BigInt(rounded);
|
|
2450
|
+
}
|
|
2451
|
+
function formatScaledDecimal(value, decimals) {
|
|
2452
|
+
if (!Number.isInteger(decimals) || decimals < 0) {
|
|
2453
|
+
throw new RangeError("Decimals must be a non-negative integer.");
|
|
2454
|
+
}
|
|
2455
|
+
const negative = value < 0n;
|
|
2456
|
+
const abs = negative ? -value : value;
|
|
2457
|
+
const raw = abs.toString();
|
|
2458
|
+
if (decimals === 0) {
|
|
2459
|
+
return `${negative ? "-" : ""}${raw}`;
|
|
2460
|
+
}
|
|
2461
|
+
const padded = raw.padStart(decimals + 1, "0");
|
|
2462
|
+
const intPart = padded.slice(0, -decimals) || "0";
|
|
2463
|
+
const fracPart = padded.slice(-decimals);
|
|
2464
|
+
return normalizeDecimalString(`${negative ? "-" : ""}${intPart}.${fracPart}`);
|
|
2465
|
+
}
|
|
2466
|
+
function formatHyperliquidPrice(price, szDecimals, marketType = "perp") {
|
|
2467
|
+
const normalized = price.toString().trim();
|
|
2468
|
+
assertNumberString(normalized);
|
|
2469
|
+
if (/^-?\d+$/.test(normalized)) {
|
|
2470
|
+
return normalizeDecimalString(normalized);
|
|
2471
|
+
}
|
|
2472
|
+
const maxDecimals2 = Math.max((marketType === "perp" ? 6 : 8) - szDecimals, 0);
|
|
2473
|
+
const decimalsTrimmed = StringMath.toFixedTruncate(normalized, maxDecimals2);
|
|
2474
|
+
const sigFigTrimmed = StringMath.toPrecisionTruncate(decimalsTrimmed, 5);
|
|
2475
|
+
if (sigFigTrimmed === "0") {
|
|
2476
|
+
throw new RangeError("Price is too small and was truncated to 0.");
|
|
2477
|
+
}
|
|
2478
|
+
return sigFigTrimmed;
|
|
2479
|
+
}
|
|
2480
|
+
function formatHyperliquidSize(size, szDecimals) {
|
|
2481
|
+
const normalized = size.toString().trim();
|
|
2482
|
+
assertNumberString(normalized);
|
|
2483
|
+
const truncated = StringMath.toFixedTruncate(normalized, szDecimals);
|
|
2484
|
+
if (truncated === "0") {
|
|
2485
|
+
throw new RangeError("Size is too small and was truncated to 0.");
|
|
2486
|
+
}
|
|
2487
|
+
return truncated;
|
|
2488
|
+
}
|
|
2489
|
+
function roundHyperliquidPriceToTick(price, tick, side) {
|
|
2490
|
+
if (!Number.isFinite(tick.tickDecimals) || tick.tickDecimals < 0) {
|
|
2491
|
+
throw new Error("tick.tickDecimals must be a non-negative number.");
|
|
2492
|
+
}
|
|
2493
|
+
if (tick.tickSizeInt <= 0n) {
|
|
2494
|
+
throw new Error("tick.tickSizeInt must be positive.");
|
|
2495
|
+
}
|
|
2496
|
+
const normalized = normalizeDecimalString(price.toString());
|
|
2497
|
+
assertNumberString(normalized);
|
|
2498
|
+
if (Number.parseFloat(normalized) <= 0) {
|
|
2499
|
+
throw new Error("Price must be positive.");
|
|
2500
|
+
}
|
|
2501
|
+
const scaled = scaleDecimalToInt(
|
|
2502
|
+
normalized,
|
|
2503
|
+
tick.tickDecimals,
|
|
2504
|
+
side === "buy" ? "up" : "down"
|
|
2505
|
+
);
|
|
2506
|
+
const tickSize = tick.tickSizeInt;
|
|
2507
|
+
const rounded = side === "sell" ? scaled / tickSize * tickSize : (scaled + tickSize - 1n) / tickSize * tickSize;
|
|
2508
|
+
return formatScaledDecimal(rounded, tick.tickDecimals);
|
|
2509
|
+
}
|
|
2510
|
+
function formatHyperliquidMarketablePrice(params) {
|
|
2511
|
+
const { mid, side, slippageBps, tick } = params;
|
|
2512
|
+
if (!Number.isFinite(mid) || mid <= 0) {
|
|
2513
|
+
throw new Error("mid must be a positive number.");
|
|
2514
|
+
}
|
|
2515
|
+
if (!Number.isFinite(slippageBps) || slippageBps < 0) {
|
|
2516
|
+
throw new Error("slippageBps must be a non-negative number.");
|
|
2517
|
+
}
|
|
2518
|
+
const midString = normalizeDecimalString(mid.toString());
|
|
2519
|
+
const baseDecimals = countDecimalPlaces(midString);
|
|
2520
|
+
const workDecimals = Math.max(baseDecimals + 4, tick?.tickDecimals ?? 0, 8);
|
|
2521
|
+
const scaledMid = scaleDecimalToInt(midString, workDecimals, "down");
|
|
2522
|
+
const slippageNumerator = BigInt(
|
|
2523
|
+
side === "buy" ? 1e4 + slippageBps : 1e4 - slippageBps
|
|
2524
|
+
);
|
|
2525
|
+
const adjustedScaled = side === "buy" ? ceilDiv(scaledMid * slippageNumerator, 10000n) : scaledMid * slippageNumerator / 10000n;
|
|
2526
|
+
const adjusted = formatScaledDecimal(adjustedScaled, workDecimals);
|
|
2527
|
+
if (tick) {
|
|
2528
|
+
return roundHyperliquidPriceToTick(adjusted, tick, side);
|
|
2529
|
+
}
|
|
2530
|
+
const roundedScaled = scaleDecimalToInt(
|
|
2531
|
+
adjusted,
|
|
2532
|
+
baseDecimals,
|
|
2533
|
+
side === "buy" ? "up" : "down"
|
|
2534
|
+
);
|
|
2535
|
+
return formatScaledDecimal(roundedScaled, baseDecimals);
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// src/adapters/hyperliquid/tpsl.ts
|
|
2539
|
+
var DEFAULT_HYPERLIQUID_TPSL_MARKET_SLIPPAGE_BPS = 1e3;
|
|
2540
|
+
function toDecimalInput(value, label) {
|
|
2541
|
+
if (typeof value === "bigint") {
|
|
2542
|
+
if (value <= 0n) {
|
|
2543
|
+
throw new Error(`${label} must be positive.`);
|
|
2544
|
+
}
|
|
2545
|
+
return value.toString();
|
|
2546
|
+
}
|
|
2547
|
+
return value;
|
|
2548
|
+
}
|
|
2549
|
+
function toPositiveNumber(value, label) {
|
|
2550
|
+
if (typeof value === "bigint") {
|
|
2551
|
+
if (value <= 0n) {
|
|
2552
|
+
throw new Error(`${label} must be positive.`);
|
|
2553
|
+
}
|
|
2554
|
+
return Number(value);
|
|
2555
|
+
}
|
|
2556
|
+
const numeric = typeof value === "number" ? value : Number.parseFloat(value.toString().trim());
|
|
2557
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
2558
|
+
throw new Error(`${label} must be positive.`);
|
|
2559
|
+
}
|
|
2560
|
+
return numeric;
|
|
2561
|
+
}
|
|
2562
|
+
function normalizeExecutionType(value) {
|
|
2563
|
+
return value ?? "market";
|
|
2564
|
+
}
|
|
2565
|
+
function resolveTriggerDirection(params) {
|
|
2566
|
+
const isLong = params.parentSide === "buy";
|
|
2567
|
+
if (params.leg === "tp") {
|
|
2568
|
+
if (isLong && params.triggerPx <= params.referencePrice) {
|
|
2569
|
+
throw new Error("Take profit trigger must be above the current price for long positions.");
|
|
2570
|
+
}
|
|
2571
|
+
if (!isLong && params.triggerPx >= params.referencePrice) {
|
|
2572
|
+
throw new Error("Take profit trigger must be below the current price for short positions.");
|
|
2573
|
+
}
|
|
2574
|
+
return;
|
|
2575
|
+
}
|
|
2576
|
+
if (isLong && params.triggerPx >= params.referencePrice) {
|
|
2577
|
+
throw new Error("Stop loss trigger must be below the current price for long positions.");
|
|
2578
|
+
}
|
|
2579
|
+
if (!isLong && params.triggerPx <= params.referencePrice) {
|
|
2580
|
+
throw new Error("Stop loss trigger must be above the current price for short positions.");
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
async function buildTpSlChildOrder(params) {
|
|
2584
|
+
const marketType = isHyperliquidSpotSymbol(params.symbol) ? "spot" : "perp";
|
|
2585
|
+
const [szDecimals, tick] = await Promise.all([
|
|
2586
|
+
fetchHyperliquidSizeDecimals({
|
|
2587
|
+
environment: params.environment,
|
|
2588
|
+
symbol: params.symbol
|
|
2589
|
+
}),
|
|
2590
|
+
fetchHyperliquidTickSize({
|
|
2591
|
+
environment: params.environment,
|
|
2592
|
+
symbol: params.symbol
|
|
2593
|
+
}).catch(() => null)
|
|
2594
|
+
]);
|
|
2595
|
+
const childSide = params.parentSide === "buy" ? "sell" : "buy";
|
|
2596
|
+
const triggerPxNumeric = toPositiveNumber(params.leg.triggerPx, `${params.legType} triggerPx`);
|
|
2597
|
+
resolveTriggerDirection({
|
|
2598
|
+
leg: params.legType,
|
|
2599
|
+
parentSide: params.parentSide,
|
|
2600
|
+
referencePrice: params.referencePrice,
|
|
2601
|
+
triggerPx: triggerPxNumeric
|
|
2602
|
+
});
|
|
2603
|
+
const execution = normalizeExecutionType(params.leg.execution);
|
|
2604
|
+
const size = formatHyperliquidSize(toDecimalInput(params.size, "size"), szDecimals);
|
|
2605
|
+
const triggerPx = formatHyperliquidPrice(triggerPxNumeric, szDecimals, marketType);
|
|
2606
|
+
const explicitLimitPrice = params.leg.price != null ? toDecimalInput(params.leg.price, `${params.legType} price`) : null;
|
|
2607
|
+
const explicitLimitPriceNumeric = explicitLimitPrice != null ? toPositiveNumber(explicitLimitPrice, `${params.legType} price`) : null;
|
|
2608
|
+
if (execution === "limit" && explicitLimitPriceNumeric == null) {
|
|
2609
|
+
throw new Error(`${params.legType} limit price is required for limit execution.`);
|
|
2610
|
+
}
|
|
2611
|
+
if (execution === "limit" && explicitLimitPriceNumeric != null) {
|
|
2612
|
+
if (childSide === "sell" && explicitLimitPriceNumeric > triggerPxNumeric) {
|
|
2613
|
+
throw new Error(`${params.legType} sell limit price must be at or below the trigger price.`);
|
|
2614
|
+
}
|
|
2615
|
+
if (childSide === "buy" && explicitLimitPriceNumeric < triggerPxNumeric) {
|
|
2616
|
+
throw new Error(`${params.legType} buy limit price must be at or above the trigger price.`);
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
const price = execution === "limit" ? formatHyperliquidPrice(
|
|
2620
|
+
explicitLimitPrice,
|
|
2621
|
+
szDecimals,
|
|
2622
|
+
marketType
|
|
2623
|
+
) : formatHyperliquidMarketablePrice({
|
|
2624
|
+
mid: triggerPxNumeric,
|
|
2625
|
+
side: childSide,
|
|
2626
|
+
slippageBps: params.triggerMarketSlippageBps,
|
|
2627
|
+
tick
|
|
2628
|
+
});
|
|
2629
|
+
return {
|
|
2630
|
+
symbol: params.symbol,
|
|
2631
|
+
side: childSide,
|
|
2632
|
+
price,
|
|
2633
|
+
size,
|
|
2634
|
+
reduceOnly: true,
|
|
2635
|
+
trigger: {
|
|
2636
|
+
triggerPx,
|
|
2637
|
+
isMarket: execution === "market",
|
|
2638
|
+
tpsl: params.legType
|
|
2639
|
+
},
|
|
2640
|
+
...params.leg.clientId ? { clientId: params.leg.clientId } : {}
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
async function buildAttachedTpSlOrders(params) {
|
|
2644
|
+
const referencePrice = toPositiveNumber(params.referencePrice, "referencePrice");
|
|
2645
|
+
const legs = await Promise.all(
|
|
2646
|
+
[
|
|
2647
|
+
params.takeProfit ? buildTpSlChildOrder({
|
|
2648
|
+
symbol: params.symbol,
|
|
2649
|
+
parentSide: params.parentSide,
|
|
2650
|
+
size: params.size,
|
|
2651
|
+
referencePrice,
|
|
2652
|
+
legType: "tp",
|
|
2653
|
+
leg: params.takeProfit,
|
|
2654
|
+
environment: params.environment,
|
|
2655
|
+
triggerMarketSlippageBps: params.triggerMarketSlippageBps
|
|
2656
|
+
}) : null,
|
|
2657
|
+
params.stopLoss ? buildTpSlChildOrder({
|
|
2658
|
+
symbol: params.symbol,
|
|
2659
|
+
parentSide: params.parentSide,
|
|
2660
|
+
size: params.size,
|
|
2661
|
+
referencePrice,
|
|
2662
|
+
legType: "sl",
|
|
2663
|
+
leg: params.stopLoss,
|
|
2664
|
+
environment: params.environment,
|
|
2665
|
+
triggerMarketSlippageBps: params.triggerMarketSlippageBps
|
|
2666
|
+
}) : null
|
|
2667
|
+
]
|
|
2668
|
+
);
|
|
2669
|
+
return legs.filter((entry) => Boolean(entry));
|
|
2670
|
+
}
|
|
2671
|
+
async function placeHyperliquidOrderWithTpSl(options) {
|
|
2672
|
+
const env = options.environment ?? "mainnet";
|
|
2673
|
+
const childOrders = await buildAttachedTpSlOrders({
|
|
2674
|
+
symbol: options.parent.symbol,
|
|
2675
|
+
parentSide: options.parent.side,
|
|
2676
|
+
size: options.parent.size,
|
|
2677
|
+
referencePrice: options.referencePrice,
|
|
2678
|
+
takeProfit: options.takeProfit ?? null,
|
|
2679
|
+
stopLoss: options.stopLoss ?? null,
|
|
2680
|
+
environment: env,
|
|
2681
|
+
triggerMarketSlippageBps: options.triggerMarketSlippageBps ?? DEFAULT_HYPERLIQUID_TPSL_MARKET_SLIPPAGE_BPS
|
|
2682
|
+
});
|
|
2683
|
+
return placeHyperliquidOrder({
|
|
2684
|
+
wallet: options.wallet,
|
|
2685
|
+
orders: [options.parent, ...childOrders],
|
|
2686
|
+
grouping: options.grouping ?? "normalTpsl",
|
|
2687
|
+
environment: env,
|
|
2688
|
+
...options.vaultAddress ? { vaultAddress: options.vaultAddress } : {},
|
|
2689
|
+
...typeof options.expiresAfter === "number" ? { expiresAfter: options.expiresAfter } : {},
|
|
2690
|
+
...typeof options.nonce === "number" ? { nonce: options.nonce } : {},
|
|
2691
|
+
...options.nonceSource ? { nonceSource: options.nonceSource } : {}
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
async function placeHyperliquidPositionTpSl(options) {
|
|
2695
|
+
const env = options.environment ?? "mainnet";
|
|
2696
|
+
const parentSide = options.positionSide === "long" ? "buy" : "sell";
|
|
2697
|
+
const childOrders = await buildAttachedTpSlOrders({
|
|
2698
|
+
symbol: options.symbol,
|
|
2699
|
+
parentSide,
|
|
2700
|
+
size: options.size,
|
|
2701
|
+
referencePrice: options.referencePrice,
|
|
2702
|
+
takeProfit: options.takeProfit ?? null,
|
|
2703
|
+
stopLoss: options.stopLoss ?? null,
|
|
2704
|
+
environment: env,
|
|
2705
|
+
triggerMarketSlippageBps: options.triggerMarketSlippageBps ?? DEFAULT_HYPERLIQUID_TPSL_MARKET_SLIPPAGE_BPS
|
|
2706
|
+
});
|
|
2707
|
+
if (childOrders.length === 0) {
|
|
2708
|
+
throw new Error("At least one TP or SL order is required.");
|
|
2709
|
+
}
|
|
2710
|
+
return placeHyperliquidOrder({
|
|
2711
|
+
wallet: options.wallet,
|
|
2712
|
+
orders: childOrders,
|
|
2713
|
+
grouping: options.grouping ?? "positionTpsl",
|
|
2714
|
+
environment: env,
|
|
2715
|
+
...options.vaultAddress ? { vaultAddress: options.vaultAddress } : {},
|
|
2716
|
+
...typeof options.expiresAfter === "number" ? { expiresAfter: options.expiresAfter } : {},
|
|
2717
|
+
...typeof options.nonce === "number" ? { nonce: options.nonce } : {},
|
|
2718
|
+
...options.nonceSource ? { nonceSource: options.nonceSource } : {}
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
// src/adapters/hyperliquid/risk-utils.ts
|
|
2723
|
+
function toFinitePositive(value) {
|
|
2724
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
2725
|
+
}
|
|
2726
|
+
function estimateMaintenanceLeverage(maxLeverage) {
|
|
2727
|
+
const normalized = toFinitePositive(maxLeverage);
|
|
2728
|
+
if (!normalized) return null;
|
|
2729
|
+
return normalized * 2;
|
|
2730
|
+
}
|
|
2731
|
+
function estimateHyperliquidLiquidationPrice(params) {
|
|
2732
|
+
const entryPrice = toFinitePositive(params.entryPrice);
|
|
2733
|
+
const notionalUsd = toFinitePositive(params.notionalUsd);
|
|
2734
|
+
const leverage = toFinitePositive(params.leverage);
|
|
2735
|
+
const maintenanceLeverage = estimateMaintenanceLeverage(params.maxLeverage);
|
|
2736
|
+
if (!entryPrice || !notionalUsd || !leverage || !maintenanceLeverage) {
|
|
2737
|
+
return null;
|
|
2738
|
+
}
|
|
2739
|
+
const size = notionalUsd / entryPrice;
|
|
2740
|
+
if (!Number.isFinite(size) || size <= 0) {
|
|
2741
|
+
return null;
|
|
2742
|
+
}
|
|
2743
|
+
const isolatedMargin = notionalUsd / leverage;
|
|
2744
|
+
const marginAvailable = params.marginMode === "cross" ? Math.max(
|
|
2745
|
+
toFinitePositive(params.availableCollateralUsd ?? 0) ?? isolatedMargin,
|
|
2746
|
+
isolatedMargin
|
|
2747
|
+
) : isolatedMargin;
|
|
2748
|
+
const sideSign = params.side === "buy" ? 1 : -1;
|
|
2749
|
+
const maintenanceFactor = 1 / maintenanceLeverage;
|
|
2750
|
+
const denominator = 1 - maintenanceFactor * sideSign;
|
|
2751
|
+
if (!Number.isFinite(denominator) || denominator <= 0) {
|
|
2752
|
+
return null;
|
|
2753
|
+
}
|
|
2754
|
+
const liquidationPrice = entryPrice - sideSign * (marginAvailable / size) / denominator;
|
|
2755
|
+
if (!Number.isFinite(liquidationPrice) || liquidationPrice <= 0) {
|
|
2756
|
+
return null;
|
|
2757
|
+
}
|
|
2758
|
+
return liquidationPrice;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
export { DEFAULT_HYPERLIQUID_MARKET_SLIPPAGE_BPS, DEFAULT_HYPERLIQUID_TPSL_MARKET_SLIPPAGE_BPS, HyperliquidApiError, HyperliquidBuilderApprovalError, HyperliquidExchangeClient, HyperliquidGuardError, HyperliquidInfoClient, HyperliquidTermsError, approveHyperliquidBuilderFee, batchModifyHyperliquidOrders, buildHyperliquidMarketIdentity, buildHyperliquidProfileAssets, cancelAllHyperliquidOrders, cancelHyperliquidOrders, cancelHyperliquidOrdersByCloid, cancelHyperliquidTwapOrder, computeHyperliquidMarketIocLimitPrice, createHyperliquidActionHash, createHyperliquidSubAccount, createMonotonicNonceFactory, depositToHyperliquidBridge, estimateHyperliquidLiquidationPrice, extractHyperliquidDex, fetchHyperliquidAssetCtxs, fetchHyperliquidClearinghouseState, fetchHyperliquidFrontendOpenOrders, fetchHyperliquidHistoricalOrders, fetchHyperliquidMeta, fetchHyperliquidMetaAndAssetCtxs, fetchHyperliquidOpenOrders, fetchHyperliquidOrderStatus, fetchHyperliquidPreTransferCheck, fetchHyperliquidSizeDecimals, fetchHyperliquidSpotAssetCtxs, fetchHyperliquidSpotClearinghouseState, fetchHyperliquidSpotMeta, fetchHyperliquidSpotMetaAndAssetCtxs, fetchHyperliquidTickSize, fetchHyperliquidUserFills, fetchHyperliquidUserFillsByTime, fetchHyperliquidUserRateLimit, formatHyperliquidMarketablePrice, formatHyperliquidPrice, formatHyperliquidSize, getHyperliquidMaxBuilderFee, isHyperliquidSpotSymbol, modifyHyperliquidOrder, normalizeHyperliquidBaseSymbol, normalizeHyperliquidMetaSymbol, normalizeSpotTokenName2 as normalizeSpotTokenName, parseHyperliquidSymbol, parseSpotPairSymbol, placeHyperliquidOrder, placeHyperliquidOrderWithTpSl, placeHyperliquidPositionTpSl, placeHyperliquidTwapOrder, reserveHyperliquidRequestWeight, resolveHyperliquidAbstractionFromMode, resolveHyperliquidLeverageMode, resolveHyperliquidOrderSymbol, resolveHyperliquidPair, resolveHyperliquidPerpSymbol, resolveHyperliquidProfileChain, resolveHyperliquidSpotSymbol, resolveHyperliquidSymbol, resolveSpotMidCandidates, resolveSpotTokenCandidates, scheduleHyperliquidCancel, sendHyperliquidSpot, setHyperliquidAccountAbstractionMode, setHyperliquidDexAbstraction, setHyperliquidPortfolioMargin, transferHyperliquidSubAccount, updateHyperliquidIsolatedMargin, updateHyperliquidLeverage, withdrawFromHyperliquid };
|
|
1929
2762
|
//# sourceMappingURL=browser.js.map
|
|
1930
2763
|
//# sourceMappingURL=browser.js.map
|