opentool 0.12.0 → 0.13.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 +102 -0
- package/dist/adapters/hyperliquid/browser.js +1930 -0
- package/dist/adapters/hyperliquid/browser.js.map +1 -0
- package/dist/adapters/hyperliquid/index.d.ts +4 -359
- package/dist/adapters/polymarket/index.d.ts +1 -1
- package/dist/exchange-XC9MHmxJ.d.ts +360 -0
- package/dist/index.d.ts +3 -2
- package/dist/{types-BaTmu0gS.d.ts → types-DKohXZes.d.ts} +1 -1
- package/dist/wallet/browser.d.ts +1 -1
- package/dist/wallet/index.d.ts +2 -2
- package/package.json +5 -1
- package/templates/base/package.json +1 -1
|
@@ -0,0 +1,1930 @@
|
|
|
1
|
+
import { encode } from '@msgpack/msgpack';
|
|
2
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
3
|
+
import { hexToBytes, concatBytes, bytesToHex } from '@noble/hashes/utils';
|
|
4
|
+
import { parseUnits, encodeFunctionData, erc20Abi } from 'viem';
|
|
5
|
+
|
|
6
|
+
// src/adapters/hyperliquid/base.ts
|
|
7
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
8
|
+
var API_BASES = {
|
|
9
|
+
mainnet: "https://api.hyperliquid.xyz",
|
|
10
|
+
testnet: "https://api.hyperliquid-testnet.xyz"
|
|
11
|
+
};
|
|
12
|
+
var HL_ENDPOINT = {
|
|
13
|
+
mainnet: "https://api.hyperliquid.xyz",
|
|
14
|
+
testnet: "https://api.hyperliquid-testnet.xyz"
|
|
15
|
+
};
|
|
16
|
+
var HL_CHAIN_LABEL = {
|
|
17
|
+
mainnet: "Mainnet",
|
|
18
|
+
testnet: "Testnet"
|
|
19
|
+
};
|
|
20
|
+
var HL_BRIDGE_ADDRESSES = {
|
|
21
|
+
mainnet: "0x2df1c51e09aecf9cacb7bc98cb1742757f163df7",
|
|
22
|
+
testnet: "0x08cfc1b6b2dcf36a1480b99353a354aa8ac56f89"
|
|
23
|
+
};
|
|
24
|
+
var HL_USDC_ADDRESSES = {
|
|
25
|
+
mainnet: "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
|
|
26
|
+
testnet: "0x1baAbB04529D43a73232B713C0FE471f7c7334d5"
|
|
27
|
+
};
|
|
28
|
+
var HL_SIGNATURE_CHAIN_ID = {
|
|
29
|
+
mainnet: "0xa4b1",
|
|
30
|
+
testnet: "0x66eee"
|
|
31
|
+
};
|
|
32
|
+
var EXCHANGE_TYPED_DATA_DOMAIN = {
|
|
33
|
+
name: "Exchange",
|
|
34
|
+
version: "1",
|
|
35
|
+
chainId: 1337,
|
|
36
|
+
verifyingContract: "0x0000000000000000000000000000000000000000"
|
|
37
|
+
};
|
|
38
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
39
|
+
var MIN_DEPOSIT_USDC = 5;
|
|
40
|
+
var BUILDER_CODE = {
|
|
41
|
+
address: "0x4b2aec4F91612849d6e20C9c1881FabB1A48cd12",
|
|
42
|
+
fee: 100
|
|
43
|
+
};
|
|
44
|
+
var metaCache = /* @__PURE__ */ new Map();
|
|
45
|
+
var spotMetaCache = /* @__PURE__ */ new Map();
|
|
46
|
+
var perpDexsCache = /* @__PURE__ */ new Map();
|
|
47
|
+
var UNKNOWN_SYMBOL = "UNKNOWN";
|
|
48
|
+
var extractDexPrefix = (value) => {
|
|
49
|
+
if (!value) return null;
|
|
50
|
+
const trimmed = value.trim();
|
|
51
|
+
if (!trimmed) return null;
|
|
52
|
+
if (!trimmed.includes(":")) return null;
|
|
53
|
+
if (trimmed.startsWith("@")) return null;
|
|
54
|
+
const [prefix] = trimmed.split(":");
|
|
55
|
+
const dex = prefix?.trim().toLowerCase() ?? "";
|
|
56
|
+
return dex || null;
|
|
57
|
+
};
|
|
58
|
+
var normalizeHyperliquidBase = (value) => {
|
|
59
|
+
if (!value) return null;
|
|
60
|
+
const trimmed = value.trim();
|
|
61
|
+
if (!trimmed) return null;
|
|
62
|
+
const withoutDex = trimmed.includes(":") ? trimmed.split(":").slice(1).join(":") : trimmed;
|
|
63
|
+
const base = withoutDex.split("-")[0] ?? withoutDex;
|
|
64
|
+
const normalized = (base.split("/")[0] ?? base).trim().toUpperCase();
|
|
65
|
+
if (!normalized || normalized === UNKNOWN_SYMBOL) return null;
|
|
66
|
+
return normalized;
|
|
67
|
+
};
|
|
68
|
+
var normalizeSpotTokenName = (value) => {
|
|
69
|
+
const raw = (value ?? "").trim().toUpperCase();
|
|
70
|
+
if (!raw) return "";
|
|
71
|
+
if (raw.endsWith("0") && raw.length > 1) {
|
|
72
|
+
return raw.slice(0, -1);
|
|
73
|
+
}
|
|
74
|
+
return raw;
|
|
75
|
+
};
|
|
76
|
+
var parseHyperliquidPair = (value) => {
|
|
77
|
+
if (!value) return null;
|
|
78
|
+
const trimmed = value.trim();
|
|
79
|
+
if (!trimmed) return null;
|
|
80
|
+
const withoutDex = trimmed.includes(":") ? trimmed.split(":").slice(1).join(":") : trimmed;
|
|
81
|
+
const separator = withoutDex.includes("/") ? "/" : withoutDex.includes("-") ? "-" : null;
|
|
82
|
+
if (!separator) return null;
|
|
83
|
+
const [baseRaw, ...rest] = withoutDex.split(separator);
|
|
84
|
+
const quoteRaw = rest.join(separator);
|
|
85
|
+
if (!baseRaw || !quoteRaw) return null;
|
|
86
|
+
const base = baseRaw.trim().toUpperCase();
|
|
87
|
+
const quote = quoteRaw.trim().toUpperCase();
|
|
88
|
+
if (!base || !quote) return null;
|
|
89
|
+
return { base, quote };
|
|
90
|
+
};
|
|
91
|
+
function buildHyperliquidMarketIdentity(input) {
|
|
92
|
+
const rawSymbol = input.rawSymbol ?? input.symbol;
|
|
93
|
+
const dex = extractDexPrefix(rawSymbol);
|
|
94
|
+
const pair = parseHyperliquidPair(rawSymbol) ?? parseHyperliquidPair(input.symbol);
|
|
95
|
+
const isSpot = input.isSpot ?? (Boolean(pair) || rawSymbol.startsWith("@") || input.symbol.includes("/"));
|
|
96
|
+
const base = (input.base ? input.base.trim().toUpperCase() : null) ?? pair?.base ?? normalizeHyperliquidBase(input.symbol) ?? normalizeHyperliquidBase(rawSymbol);
|
|
97
|
+
if (!base) return null;
|
|
98
|
+
if (isSpot) {
|
|
99
|
+
const quote = (input.quote ? input.quote.trim().toUpperCase() : null) ?? pair?.quote ?? null;
|
|
100
|
+
if (!quote) return null;
|
|
101
|
+
return {
|
|
102
|
+
market_type: "spot",
|
|
103
|
+
venue: "hyperliquid",
|
|
104
|
+
environment: input.environment,
|
|
105
|
+
base,
|
|
106
|
+
quote,
|
|
107
|
+
dex,
|
|
108
|
+
raw_symbol: rawSymbol ?? null,
|
|
109
|
+
canonical_symbol: `spot:hyperliquid:${base}-${quote}`
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
market_type: "perp",
|
|
114
|
+
venue: "hyperliquid",
|
|
115
|
+
environment: input.environment,
|
|
116
|
+
base,
|
|
117
|
+
dex,
|
|
118
|
+
raw_symbol: rawSymbol ?? null,
|
|
119
|
+
canonical_symbol: `perp:hyperliquid:${base}`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function resolveHyperliquidAbstractionFromMode(mode) {
|
|
123
|
+
switch (mode) {
|
|
124
|
+
case "standard":
|
|
125
|
+
return "disabled";
|
|
126
|
+
case "unified":
|
|
127
|
+
return "unifiedAccount";
|
|
128
|
+
case "portfolio":
|
|
129
|
+
return "portfolioMargin";
|
|
130
|
+
default: {
|
|
131
|
+
const _exhaustive = mode;
|
|
132
|
+
return _exhaustive;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
var DEFAULT_HYPERLIQUID_MARKET_SLIPPAGE_BPS = 30;
|
|
137
|
+
function formatRoundedDecimal(value, decimals) {
|
|
138
|
+
const precision = Math.max(0, Math.min(12, Math.floor(decimals)));
|
|
139
|
+
const factor = 10 ** precision;
|
|
140
|
+
const rounded = Math.round(value * factor) / factor;
|
|
141
|
+
if (!Number.isFinite(rounded) || rounded <= 0) {
|
|
142
|
+
throw new Error("Price must be positive.");
|
|
143
|
+
}
|
|
144
|
+
const fixed = rounded.toFixed(precision);
|
|
145
|
+
return fixed.replace(/\.?0+$/, "");
|
|
146
|
+
}
|
|
147
|
+
function computeHyperliquidMarketIocLimitPrice(params) {
|
|
148
|
+
const bps = params.slippageBps ?? DEFAULT_HYPERLIQUID_MARKET_SLIPPAGE_BPS;
|
|
149
|
+
const decimals = params.decimals ?? 6;
|
|
150
|
+
if (!Number.isFinite(params.markPrice) || params.markPrice <= 0) {
|
|
151
|
+
throw new Error("markPrice must be a positive number.");
|
|
152
|
+
}
|
|
153
|
+
if (!Number.isFinite(bps) || bps < 0) {
|
|
154
|
+
throw new Error("slippageBps must be a non-negative number.");
|
|
155
|
+
}
|
|
156
|
+
const slippage = bps / 1e4;
|
|
157
|
+
const multiplier = params.side === "buy" ? 1 + slippage : 1 - slippage;
|
|
158
|
+
const price = params.markPrice * multiplier;
|
|
159
|
+
return formatRoundedDecimal(price, decimals);
|
|
160
|
+
}
|
|
161
|
+
var HyperliquidApiError = class extends Error {
|
|
162
|
+
constructor(message, response) {
|
|
163
|
+
super(message);
|
|
164
|
+
this.response = response;
|
|
165
|
+
this.name = "HyperliquidApiError";
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
var HyperliquidGuardError = class extends Error {
|
|
169
|
+
constructor(message) {
|
|
170
|
+
super(message);
|
|
171
|
+
this.name = "HyperliquidGuardError";
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
var HyperliquidTermsError = class extends HyperliquidGuardError {
|
|
175
|
+
constructor(message = "Hyperliquid terms must be accepted before proceeding.") {
|
|
176
|
+
super(message);
|
|
177
|
+
this.name = "HyperliquidTermsError";
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
var HyperliquidBuilderApprovalError = class extends HyperliquidGuardError {
|
|
181
|
+
constructor(message = "Hyperliquid builder approval is required before using builder codes.") {
|
|
182
|
+
super(message);
|
|
183
|
+
this.name = "HyperliquidBuilderApprovalError";
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
function createMonotonicNonceFactory(start = Date.now()) {
|
|
187
|
+
let last = start;
|
|
188
|
+
return () => {
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
if (now > last) {
|
|
191
|
+
last = now;
|
|
192
|
+
} else {
|
|
193
|
+
last += 1;
|
|
194
|
+
}
|
|
195
|
+
return last;
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
async function getUniverse(args) {
|
|
199
|
+
const dexKey = args.dex ? args.dex.trim().toLowerCase() : "";
|
|
200
|
+
const cacheKey = `${args.environment}:${args.baseUrl}:${dexKey}`;
|
|
201
|
+
const cached = metaCache.get(cacheKey);
|
|
202
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
203
|
+
return cached.universe;
|
|
204
|
+
}
|
|
205
|
+
const response = await args.fetcher(`${args.baseUrl}/info`, {
|
|
206
|
+
method: "POST",
|
|
207
|
+
headers: { "content-type": "application/json" },
|
|
208
|
+
body: JSON.stringify(dexKey ? { type: "meta", dex: dexKey } : { type: "meta" })
|
|
209
|
+
});
|
|
210
|
+
const json = await response.json().catch(() => null);
|
|
211
|
+
if (!response.ok || !json?.universe) {
|
|
212
|
+
throw new HyperliquidApiError(
|
|
213
|
+
"Unable to load Hyperliquid metadata.",
|
|
214
|
+
json ?? { status: response.status }
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
metaCache.set(cacheKey, { fetchedAt: Date.now(), universe: json.universe });
|
|
218
|
+
return json.universe;
|
|
219
|
+
}
|
|
220
|
+
async function getSpotMeta(args) {
|
|
221
|
+
const cacheKey = `${args.environment}:${args.baseUrl}`;
|
|
222
|
+
const cached = spotMetaCache.get(cacheKey);
|
|
223
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
224
|
+
return { universe: cached.universe, tokens: cached.tokens };
|
|
225
|
+
}
|
|
226
|
+
const response = await args.fetcher(`${args.baseUrl}/info`, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: { "content-type": "application/json" },
|
|
229
|
+
body: JSON.stringify({ type: "spotMeta" })
|
|
230
|
+
});
|
|
231
|
+
const json = await response.json().catch(() => null);
|
|
232
|
+
if (!response.ok || !json?.universe) {
|
|
233
|
+
throw new HyperliquidApiError(
|
|
234
|
+
"Unable to load Hyperliquid spot metadata.",
|
|
235
|
+
json ?? { status: response.status }
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
const universe = json.universe ?? [];
|
|
239
|
+
const tokens = json.tokens ?? [];
|
|
240
|
+
spotMetaCache.set(cacheKey, { fetchedAt: Date.now(), universe, tokens });
|
|
241
|
+
return { universe, tokens };
|
|
242
|
+
}
|
|
243
|
+
function resolveAssetIndex(symbol, universe) {
|
|
244
|
+
const [raw] = symbol.split("-");
|
|
245
|
+
const target = raw.trim();
|
|
246
|
+
const index = universe.findIndex((entry) => entry.name.toUpperCase() === target.toUpperCase());
|
|
247
|
+
if (index === -1) {
|
|
248
|
+
throw new Error(`Unknown Hyperliquid asset symbol: ${symbol}`);
|
|
249
|
+
}
|
|
250
|
+
return index;
|
|
251
|
+
}
|
|
252
|
+
async function getPerpDexs(args) {
|
|
253
|
+
const cacheKey = `${args.environment}:${args.baseUrl}`;
|
|
254
|
+
const cached = perpDexsCache.get(cacheKey);
|
|
255
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
256
|
+
return cached.dexs;
|
|
257
|
+
}
|
|
258
|
+
const response = await args.fetcher(`${args.baseUrl}/info`, {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: { "content-type": "application/json" },
|
|
261
|
+
body: JSON.stringify({ type: "perpDexs" })
|
|
262
|
+
});
|
|
263
|
+
const json = await response.json().catch(() => null);
|
|
264
|
+
if (!response.ok || !Array.isArray(json)) {
|
|
265
|
+
throw new HyperliquidApiError(
|
|
266
|
+
"Unable to load Hyperliquid perp dex metadata.",
|
|
267
|
+
json ?? { status: response.status }
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
perpDexsCache.set(cacheKey, { fetchedAt: Date.now(), dexs: json });
|
|
271
|
+
return json;
|
|
272
|
+
}
|
|
273
|
+
async function resolveDexIndex(args) {
|
|
274
|
+
const dexs = await getPerpDexs(args);
|
|
275
|
+
const target = args.dex.trim().toLowerCase();
|
|
276
|
+
const index = dexs.findIndex((entry) => entry?.name?.toLowerCase() === target);
|
|
277
|
+
if (index === -1) {
|
|
278
|
+
throw new Error(`Unknown Hyperliquid perp dex: ${args.dex}`);
|
|
279
|
+
}
|
|
280
|
+
return index;
|
|
281
|
+
}
|
|
282
|
+
function buildSpotTokenIndexMap(tokens) {
|
|
283
|
+
const map = /* @__PURE__ */ new Map();
|
|
284
|
+
for (const token of tokens) {
|
|
285
|
+
const name = normalizeSpotTokenName(token?.name);
|
|
286
|
+
const index = typeof token?.index === "number" && Number.isFinite(token.index) ? token.index : null;
|
|
287
|
+
if (!name || index == null) continue;
|
|
288
|
+
if (!map.has(name) || token?.isCanonical) {
|
|
289
|
+
map.set(name, index);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return map;
|
|
293
|
+
}
|
|
294
|
+
function resolveSpotTokenIndex(tokenMap, value) {
|
|
295
|
+
const normalized = normalizeSpotTokenName(value);
|
|
296
|
+
if (!normalized) return null;
|
|
297
|
+
const direct = tokenMap.get(normalized);
|
|
298
|
+
if (direct != null) return direct;
|
|
299
|
+
if (!normalized.startsWith("U")) {
|
|
300
|
+
const prefixed = tokenMap.get(`U${normalized}`);
|
|
301
|
+
if (prefixed != null) return prefixed;
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
function resolveSpotMarketIndex(args) {
|
|
306
|
+
for (let i = 0; i < args.universe.length; i += 1) {
|
|
307
|
+
const entry = args.universe[i];
|
|
308
|
+
const tokens = Array.isArray(entry?.tokens) ? entry.tokens : null;
|
|
309
|
+
const baseToken = tokens?.[0] ?? entry?.baseToken ?? null;
|
|
310
|
+
const quoteToken = tokens?.[1] ?? entry?.quoteToken ?? null;
|
|
311
|
+
if (baseToken === args.baseToken && quoteToken === args.quoteToken) {
|
|
312
|
+
if (typeof entry?.index === "number" && Number.isFinite(entry.index)) {
|
|
313
|
+
return entry.index;
|
|
314
|
+
}
|
|
315
|
+
return i;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
async function resolveHyperliquidAssetIndex(args) {
|
|
321
|
+
const trimmed = args.symbol.trim();
|
|
322
|
+
if (!trimmed) {
|
|
323
|
+
throw new Error("Hyperliquid symbol must be a non-empty string.");
|
|
324
|
+
}
|
|
325
|
+
if (trimmed.startsWith("@")) {
|
|
326
|
+
const rawIndex = trimmed.slice(1).trim();
|
|
327
|
+
const index = Number(rawIndex);
|
|
328
|
+
if (!Number.isFinite(index)) {
|
|
329
|
+
throw new Error(`Hyperliquid spot market index is invalid: ${trimmed}`);
|
|
330
|
+
}
|
|
331
|
+
return 1e4 + index;
|
|
332
|
+
}
|
|
333
|
+
const separator = trimmed.indexOf(":");
|
|
334
|
+
if (separator > 0) {
|
|
335
|
+
const dex = trimmed.slice(0, separator).trim();
|
|
336
|
+
if (!dex) {
|
|
337
|
+
throw new Error("Hyperliquid dex name is required.");
|
|
338
|
+
}
|
|
339
|
+
const dexIndex = await resolveDexIndex({
|
|
340
|
+
baseUrl: args.baseUrl,
|
|
341
|
+
environment: args.environment,
|
|
342
|
+
fetcher: args.fetcher,
|
|
343
|
+
dex
|
|
344
|
+
});
|
|
345
|
+
const universe2 = await getUniverse({
|
|
346
|
+
baseUrl: args.baseUrl,
|
|
347
|
+
environment: args.environment,
|
|
348
|
+
fetcher: args.fetcher,
|
|
349
|
+
dex
|
|
350
|
+
});
|
|
351
|
+
const assetIndex = universe2.findIndex(
|
|
352
|
+
(entry) => entry.name.toUpperCase() === trimmed.toUpperCase()
|
|
353
|
+
);
|
|
354
|
+
if (assetIndex === -1) {
|
|
355
|
+
throw new Error(`Unknown Hyperliquid asset symbol: ${trimmed}`);
|
|
356
|
+
}
|
|
357
|
+
return 1e5 + dexIndex * 1e4 + assetIndex;
|
|
358
|
+
}
|
|
359
|
+
const pair = parseHyperliquidPair(trimmed);
|
|
360
|
+
if (pair) {
|
|
361
|
+
const { universe: universe2, tokens } = await getSpotMeta({
|
|
362
|
+
baseUrl: args.baseUrl,
|
|
363
|
+
environment: args.environment,
|
|
364
|
+
fetcher: args.fetcher
|
|
365
|
+
});
|
|
366
|
+
const tokenMap = buildSpotTokenIndexMap(tokens);
|
|
367
|
+
const baseToken = resolveSpotTokenIndex(tokenMap, pair.base);
|
|
368
|
+
const quoteToken = resolveSpotTokenIndex(tokenMap, pair.quote);
|
|
369
|
+
if (baseToken == null || quoteToken == null) {
|
|
370
|
+
throw new Error(`Unknown Hyperliquid spot symbol: ${trimmed}`);
|
|
371
|
+
}
|
|
372
|
+
const marketIndex = resolveSpotMarketIndex({
|
|
373
|
+
universe: universe2,
|
|
374
|
+
baseToken,
|
|
375
|
+
quoteToken
|
|
376
|
+
});
|
|
377
|
+
if (marketIndex == null) {
|
|
378
|
+
throw new Error(`Unknown Hyperliquid spot symbol: ${trimmed}`);
|
|
379
|
+
}
|
|
380
|
+
return 1e4 + marketIndex;
|
|
381
|
+
}
|
|
382
|
+
const universe = await getUniverse({
|
|
383
|
+
baseUrl: args.baseUrl,
|
|
384
|
+
environment: args.environment,
|
|
385
|
+
fetcher: args.fetcher
|
|
386
|
+
});
|
|
387
|
+
return resolveAssetIndex(trimmed, universe);
|
|
388
|
+
}
|
|
389
|
+
function toApiDecimal(value) {
|
|
390
|
+
if (typeof value === "string") {
|
|
391
|
+
return value;
|
|
392
|
+
}
|
|
393
|
+
if (typeof value === "bigint") {
|
|
394
|
+
return value.toString();
|
|
395
|
+
}
|
|
396
|
+
if (!Number.isFinite(value)) {
|
|
397
|
+
throw new Error("Numeric values must be finite.");
|
|
398
|
+
}
|
|
399
|
+
const asString = value.toString();
|
|
400
|
+
if (/e/i.test(asString)) {
|
|
401
|
+
const [mantissa, exponentPart] = asString.split(/e/i);
|
|
402
|
+
const exponent = Number(exponentPart);
|
|
403
|
+
const [integerPart, fractionalPart = ""] = mantissa.split(".");
|
|
404
|
+
if (exponent >= 0) {
|
|
405
|
+
return integerPart + fractionalPart.padEnd(exponent + fractionalPart.length, "0");
|
|
406
|
+
}
|
|
407
|
+
const zeros = "0".repeat(Math.abs(exponent) - 1);
|
|
408
|
+
return `0.${zeros}${integerPart}${fractionalPart}`.replace(/\.0+$/, "");
|
|
409
|
+
}
|
|
410
|
+
return asString;
|
|
411
|
+
}
|
|
412
|
+
var NORMALIZED_HEX_PATTERN = /^0x[0-9a-f]+$/;
|
|
413
|
+
var ADDRESS_HEX_LENGTH = 42;
|
|
414
|
+
var CLOID_HEX_LENGTH = 34;
|
|
415
|
+
function normalizeHex(value) {
|
|
416
|
+
const lower = value.trim().toLowerCase();
|
|
417
|
+
if (!NORMALIZED_HEX_PATTERN.test(lower)) {
|
|
418
|
+
throw new Error(`Invalid hex value: ${value}`);
|
|
419
|
+
}
|
|
420
|
+
return lower;
|
|
421
|
+
}
|
|
422
|
+
function normalizeAddress(value) {
|
|
423
|
+
const normalized = normalizeHex(value);
|
|
424
|
+
if (normalized.length !== ADDRESS_HEX_LENGTH) {
|
|
425
|
+
throw new Error(`Invalid address length: ${normalized}`);
|
|
426
|
+
}
|
|
427
|
+
return normalized;
|
|
428
|
+
}
|
|
429
|
+
function normalizeCloid(value) {
|
|
430
|
+
const normalized = normalizeHex(value);
|
|
431
|
+
if (normalized.length !== CLOID_HEX_LENGTH) {
|
|
432
|
+
throw new Error(`Invalid cloid length: ${normalized}`);
|
|
433
|
+
}
|
|
434
|
+
return normalized;
|
|
435
|
+
}
|
|
436
|
+
async function signL1Action(args) {
|
|
437
|
+
const { wallet, action, nonce, vaultAddress, expiresAfter, isTestnet } = args;
|
|
438
|
+
const actionHash = createL1ActionHash({
|
|
439
|
+
action,
|
|
440
|
+
nonce,
|
|
441
|
+
vaultAddress,
|
|
442
|
+
expiresAfter
|
|
443
|
+
});
|
|
444
|
+
const message = {
|
|
445
|
+
source: isTestnet ? "b" : "a",
|
|
446
|
+
connectionId: actionHash
|
|
447
|
+
};
|
|
448
|
+
const signatureHex = await wallet.walletClient.signTypedData({
|
|
449
|
+
account: wallet.account,
|
|
450
|
+
domain: EXCHANGE_TYPED_DATA_DOMAIN,
|
|
451
|
+
types: {
|
|
452
|
+
Agent: [
|
|
453
|
+
{ name: "source", type: "string" },
|
|
454
|
+
{ name: "connectionId", type: "bytes32" }
|
|
455
|
+
]
|
|
456
|
+
},
|
|
457
|
+
primaryType: "Agent",
|
|
458
|
+
message
|
|
459
|
+
});
|
|
460
|
+
return splitSignature(signatureHex);
|
|
461
|
+
}
|
|
462
|
+
async function signSpotSend(args) {
|
|
463
|
+
const { wallet, hyperliquidChain, signatureChainId, destination, token, amount, time } = args;
|
|
464
|
+
const domain = {
|
|
465
|
+
name: "HyperliquidSignTransaction",
|
|
466
|
+
version: "1",
|
|
467
|
+
chainId: Number.parseInt(signatureChainId, 16),
|
|
468
|
+
verifyingContract: ZERO_ADDRESS
|
|
469
|
+
};
|
|
470
|
+
const message = {
|
|
471
|
+
hyperliquidChain,
|
|
472
|
+
destination,
|
|
473
|
+
token,
|
|
474
|
+
amount,
|
|
475
|
+
time
|
|
476
|
+
};
|
|
477
|
+
const types = {
|
|
478
|
+
"HyperliquidTransaction:SpotSend": [
|
|
479
|
+
{ name: "hyperliquidChain", type: "string" },
|
|
480
|
+
{ name: "destination", type: "string" },
|
|
481
|
+
{ name: "token", type: "string" },
|
|
482
|
+
{ name: "amount", type: "string" },
|
|
483
|
+
{ name: "time", type: "uint64" }
|
|
484
|
+
]
|
|
485
|
+
};
|
|
486
|
+
const signatureHex = await wallet.walletClient.signTypedData({
|
|
487
|
+
account: wallet.account,
|
|
488
|
+
domain,
|
|
489
|
+
types,
|
|
490
|
+
primaryType: "HyperliquidTransaction:SpotSend",
|
|
491
|
+
message
|
|
492
|
+
});
|
|
493
|
+
return splitSignature(signatureHex);
|
|
494
|
+
}
|
|
495
|
+
async function signApproveBuilderFee(args) {
|
|
496
|
+
const { wallet, maxFeeRate, nonce, signatureChainId, isTestnet } = args;
|
|
497
|
+
const hyperliquidChain = isTestnet ? "Testnet" : "Mainnet";
|
|
498
|
+
const domain = {
|
|
499
|
+
name: "HyperliquidSignTransaction",
|
|
500
|
+
version: "1",
|
|
501
|
+
chainId: Number.parseInt(signatureChainId, 16),
|
|
502
|
+
verifyingContract: ZERO_ADDRESS
|
|
503
|
+
};
|
|
504
|
+
const message = {
|
|
505
|
+
hyperliquidChain,
|
|
506
|
+
maxFeeRate,
|
|
507
|
+
builder: BUILDER_CODE.address,
|
|
508
|
+
nonce
|
|
509
|
+
};
|
|
510
|
+
const types = {
|
|
511
|
+
"HyperliquidTransaction:ApproveBuilderFee": [
|
|
512
|
+
{ name: "hyperliquidChain", type: "string" },
|
|
513
|
+
{ name: "maxFeeRate", type: "string" },
|
|
514
|
+
{ name: "builder", type: "address" },
|
|
515
|
+
{ name: "nonce", type: "uint64" }
|
|
516
|
+
]
|
|
517
|
+
};
|
|
518
|
+
const signatureHex = await wallet.walletClient.signTypedData({
|
|
519
|
+
account: wallet.account,
|
|
520
|
+
domain,
|
|
521
|
+
types,
|
|
522
|
+
primaryType: "HyperliquidTransaction:ApproveBuilderFee",
|
|
523
|
+
message
|
|
524
|
+
});
|
|
525
|
+
return splitSignature(signatureHex);
|
|
526
|
+
}
|
|
527
|
+
async function signUserPortfolioMargin(args) {
|
|
528
|
+
const { wallet, action } = args;
|
|
529
|
+
const domain = {
|
|
530
|
+
name: "HyperliquidSignTransaction",
|
|
531
|
+
version: "1",
|
|
532
|
+
chainId: Number.parseInt(action.signatureChainId, 16),
|
|
533
|
+
verifyingContract: ZERO_ADDRESS
|
|
534
|
+
};
|
|
535
|
+
const message = {
|
|
536
|
+
enabled: action.enabled,
|
|
537
|
+
hyperliquidChain: action.hyperliquidChain,
|
|
538
|
+
user: action.user,
|
|
539
|
+
nonce: BigInt(action.nonce)
|
|
540
|
+
};
|
|
541
|
+
const types = {
|
|
542
|
+
"HyperliquidTransaction:UserPortfolioMargin": [
|
|
543
|
+
{ name: "enabled", type: "bool" },
|
|
544
|
+
{ name: "hyperliquidChain", type: "string" },
|
|
545
|
+
{ name: "user", type: "address" },
|
|
546
|
+
{ name: "nonce", type: "uint64" }
|
|
547
|
+
]
|
|
548
|
+
};
|
|
549
|
+
const signatureHex = await wallet.walletClient.signTypedData({
|
|
550
|
+
account: wallet.account,
|
|
551
|
+
domain,
|
|
552
|
+
types,
|
|
553
|
+
primaryType: "HyperliquidTransaction:UserPortfolioMargin",
|
|
554
|
+
message
|
|
555
|
+
});
|
|
556
|
+
return splitSignature(signatureHex);
|
|
557
|
+
}
|
|
558
|
+
async function signUserDexAbstraction(args) {
|
|
559
|
+
const { wallet, action } = args;
|
|
560
|
+
const domain = {
|
|
561
|
+
name: "HyperliquidSignTransaction",
|
|
562
|
+
version: "1",
|
|
563
|
+
chainId: Number.parseInt(action.signatureChainId, 16),
|
|
564
|
+
verifyingContract: ZERO_ADDRESS
|
|
565
|
+
};
|
|
566
|
+
const message = {
|
|
567
|
+
hyperliquidChain: action.hyperliquidChain,
|
|
568
|
+
user: action.user,
|
|
569
|
+
enabled: action.enabled,
|
|
570
|
+
nonce: BigInt(action.nonce)
|
|
571
|
+
};
|
|
572
|
+
const types = {
|
|
573
|
+
"HyperliquidTransaction:UserDexAbstraction": [
|
|
574
|
+
{ name: "hyperliquidChain", type: "string" },
|
|
575
|
+
{ name: "user", type: "address" },
|
|
576
|
+
{ name: "enabled", type: "bool" },
|
|
577
|
+
{ name: "nonce", type: "uint64" }
|
|
578
|
+
]
|
|
579
|
+
};
|
|
580
|
+
const signatureHex = await wallet.walletClient.signTypedData({
|
|
581
|
+
account: wallet.account,
|
|
582
|
+
domain,
|
|
583
|
+
types,
|
|
584
|
+
primaryType: "HyperliquidTransaction:UserDexAbstraction",
|
|
585
|
+
message
|
|
586
|
+
});
|
|
587
|
+
return splitSignature(signatureHex);
|
|
588
|
+
}
|
|
589
|
+
async function signUserSetAbstraction(args) {
|
|
590
|
+
const { wallet, action } = args;
|
|
591
|
+
const domain = {
|
|
592
|
+
name: "HyperliquidSignTransaction",
|
|
593
|
+
version: "1",
|
|
594
|
+
chainId: Number.parseInt(action.signatureChainId, 16),
|
|
595
|
+
verifyingContract: ZERO_ADDRESS
|
|
596
|
+
};
|
|
597
|
+
const message = {
|
|
598
|
+
hyperliquidChain: action.hyperliquidChain,
|
|
599
|
+
user: action.user,
|
|
600
|
+
abstraction: action.abstraction,
|
|
601
|
+
nonce: BigInt(action.nonce)
|
|
602
|
+
};
|
|
603
|
+
const types = {
|
|
604
|
+
"HyperliquidTransaction:UserSetAbstraction": [
|
|
605
|
+
{ name: "hyperliquidChain", type: "string" },
|
|
606
|
+
{ name: "user", type: "address" },
|
|
607
|
+
{ name: "abstraction", type: "string" },
|
|
608
|
+
{ name: "nonce", type: "uint64" }
|
|
609
|
+
]
|
|
610
|
+
};
|
|
611
|
+
const signatureHex = await wallet.walletClient.signTypedData({
|
|
612
|
+
account: wallet.account,
|
|
613
|
+
domain,
|
|
614
|
+
types,
|
|
615
|
+
primaryType: "HyperliquidTransaction:UserSetAbstraction",
|
|
616
|
+
message
|
|
617
|
+
});
|
|
618
|
+
return splitSignature(signatureHex);
|
|
619
|
+
}
|
|
620
|
+
function splitSignature(signature) {
|
|
621
|
+
const cleaned = signature.slice(2);
|
|
622
|
+
const rHex = `0x${cleaned.slice(0, 64)}`;
|
|
623
|
+
const sHex = `0x${cleaned.slice(64, 128)}`;
|
|
624
|
+
let v = parseInt(cleaned.slice(128, 130), 16);
|
|
625
|
+
if (Number.isNaN(v)) {
|
|
626
|
+
throw new Error("Invalid signature returned by wallet client.");
|
|
627
|
+
}
|
|
628
|
+
if (v < 27) {
|
|
629
|
+
v += 27;
|
|
630
|
+
}
|
|
631
|
+
const normalizedV = v === 27 || v === 28 ? v : v % 2 ? 27 : 28;
|
|
632
|
+
return {
|
|
633
|
+
r: normalizeHex(rHex),
|
|
634
|
+
s: normalizeHex(sHex),
|
|
635
|
+
v: normalizedV
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function createL1ActionHash(args) {
|
|
639
|
+
const { action, nonce, vaultAddress, expiresAfter } = args;
|
|
640
|
+
const actionBytes = encode(action, { ignoreUndefined: true });
|
|
641
|
+
const nonceBytes = toUint64Bytes(nonce);
|
|
642
|
+
const vaultMarker = vaultAddress ? new Uint8Array([1]) : new Uint8Array([0]);
|
|
643
|
+
const vaultBytes = vaultAddress ? hexToBytes(vaultAddress.slice(2)) : new Uint8Array();
|
|
644
|
+
const hasExpiresAfter = typeof expiresAfter === "number";
|
|
645
|
+
const expiresMarker = hasExpiresAfter ? new Uint8Array([0]) : new Uint8Array();
|
|
646
|
+
const expiresBytes = hasExpiresAfter && expiresAfter !== void 0 ? toUint64Bytes(expiresAfter) : new Uint8Array();
|
|
647
|
+
const bytes = concatBytes(
|
|
648
|
+
actionBytes,
|
|
649
|
+
nonceBytes,
|
|
650
|
+
vaultMarker,
|
|
651
|
+
vaultBytes,
|
|
652
|
+
expiresMarker,
|
|
653
|
+
expiresBytes
|
|
654
|
+
);
|
|
655
|
+
const hash = keccak_256(bytes);
|
|
656
|
+
return `0x${bytesToHex(hash)}`;
|
|
657
|
+
}
|
|
658
|
+
function toUint64Bytes(value) {
|
|
659
|
+
const bytes = new Uint8Array(8);
|
|
660
|
+
new DataView(bytes.buffer).setBigUint64(0, BigInt(value));
|
|
661
|
+
return bytes;
|
|
662
|
+
}
|
|
663
|
+
function getBridgeAddress(env) {
|
|
664
|
+
const override = process.env.HYPERLIQUID_BRIDGE_ADDRESS;
|
|
665
|
+
if (override?.trim()) {
|
|
666
|
+
return normalizeAddress(override);
|
|
667
|
+
}
|
|
668
|
+
return HL_BRIDGE_ADDRESSES[env];
|
|
669
|
+
}
|
|
670
|
+
function getUsdcAddress(env) {
|
|
671
|
+
const override = process.env.HYPERLIQUID_USDC_ADDRESS;
|
|
672
|
+
if (override?.trim()) {
|
|
673
|
+
return normalizeAddress(override);
|
|
674
|
+
}
|
|
675
|
+
return HL_USDC_ADDRESSES[env];
|
|
676
|
+
}
|
|
677
|
+
function getSignatureChainId(env) {
|
|
678
|
+
const override = process.env.HYPERLIQUID_SIGNATURE_CHAIN_ID;
|
|
679
|
+
const selected = override?.trim() || HL_SIGNATURE_CHAIN_ID[env];
|
|
680
|
+
return normalizeHex(selected);
|
|
681
|
+
}
|
|
682
|
+
function assertPositiveNumber(value, label) {
|
|
683
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
684
|
+
throw new Error(`${label} must be a positive number.`);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// src/adapters/hyperliquid/info.ts
|
|
689
|
+
async function postInfo(environment, payload) {
|
|
690
|
+
const baseUrl = API_BASES[environment];
|
|
691
|
+
const response = await fetch(`${baseUrl}/info`, {
|
|
692
|
+
method: "POST",
|
|
693
|
+
headers: { "content-type": "application/json" },
|
|
694
|
+
body: JSON.stringify(payload)
|
|
695
|
+
});
|
|
696
|
+
const data = await response.json().catch(() => null);
|
|
697
|
+
if (!response.ok) {
|
|
698
|
+
throw new HyperliquidApiError(
|
|
699
|
+
"Hyperliquid info request failed.",
|
|
700
|
+
data ?? { status: response.status }
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
return data;
|
|
704
|
+
}
|
|
705
|
+
var HyperliquidInfoClient = class {
|
|
706
|
+
constructor(environment = "mainnet") {
|
|
707
|
+
this.environment = environment;
|
|
708
|
+
}
|
|
709
|
+
meta() {
|
|
710
|
+
return fetchHyperliquidMeta(this.environment);
|
|
711
|
+
}
|
|
712
|
+
metaAndAssetCtxs() {
|
|
713
|
+
return fetchHyperliquidMetaAndAssetCtxs(this.environment);
|
|
714
|
+
}
|
|
715
|
+
spotMeta() {
|
|
716
|
+
return fetchHyperliquidSpotMeta(this.environment);
|
|
717
|
+
}
|
|
718
|
+
spotMetaAndAssetCtxs() {
|
|
719
|
+
return fetchHyperliquidSpotMetaAndAssetCtxs(this.environment);
|
|
720
|
+
}
|
|
721
|
+
assetCtxs() {
|
|
722
|
+
return fetchHyperliquidAssetCtxs(this.environment);
|
|
723
|
+
}
|
|
724
|
+
spotAssetCtxs() {
|
|
725
|
+
return fetchHyperliquidSpotAssetCtxs(this.environment);
|
|
726
|
+
}
|
|
727
|
+
openOrders(user) {
|
|
728
|
+
return fetchHyperliquidOpenOrders({ user, environment: this.environment });
|
|
729
|
+
}
|
|
730
|
+
frontendOpenOrders(user) {
|
|
731
|
+
return fetchHyperliquidFrontendOpenOrders({
|
|
732
|
+
user,
|
|
733
|
+
environment: this.environment
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
orderStatus(user, oid) {
|
|
737
|
+
return fetchHyperliquidOrderStatus({
|
|
738
|
+
user,
|
|
739
|
+
oid,
|
|
740
|
+
environment: this.environment
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
historicalOrders(user) {
|
|
744
|
+
return fetchHyperliquidHistoricalOrders({
|
|
745
|
+
user,
|
|
746
|
+
environment: this.environment
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
userFills(user) {
|
|
750
|
+
return fetchHyperliquidUserFills({ user, environment: this.environment });
|
|
751
|
+
}
|
|
752
|
+
userFillsByTime(user, startTime, endTime) {
|
|
753
|
+
return fetchHyperliquidUserFillsByTime({
|
|
754
|
+
user,
|
|
755
|
+
startTime,
|
|
756
|
+
endTime,
|
|
757
|
+
environment: this.environment
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
userRateLimit(user) {
|
|
761
|
+
return fetchHyperliquidUserRateLimit({
|
|
762
|
+
user,
|
|
763
|
+
environment: this.environment
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
preTransferCheck(user, source) {
|
|
767
|
+
return fetchHyperliquidPreTransferCheck({
|
|
768
|
+
user,
|
|
769
|
+
source,
|
|
770
|
+
environment: this.environment
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
spotClearinghouseState(user) {
|
|
774
|
+
return fetchHyperliquidSpotClearinghouseState({
|
|
775
|
+
user,
|
|
776
|
+
environment: this.environment
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
async function fetchHyperliquidMeta(environment = "mainnet") {
|
|
781
|
+
return postInfo(environment, { type: "meta" });
|
|
782
|
+
}
|
|
783
|
+
async function fetchHyperliquidMetaAndAssetCtxs(environment = "mainnet") {
|
|
784
|
+
return postInfo(environment, { type: "metaAndAssetCtxs" });
|
|
785
|
+
}
|
|
786
|
+
async function fetchHyperliquidSpotMeta(environment = "mainnet") {
|
|
787
|
+
return postInfo(environment, { type: "spotMeta" });
|
|
788
|
+
}
|
|
789
|
+
async function fetchHyperliquidSpotMetaAndAssetCtxs(environment = "mainnet") {
|
|
790
|
+
return postInfo(environment, { type: "spotMetaAndAssetCtxs" });
|
|
791
|
+
}
|
|
792
|
+
async function fetchHyperliquidAssetCtxs(environment = "mainnet") {
|
|
793
|
+
return postInfo(environment, { type: "assetCtxs" });
|
|
794
|
+
}
|
|
795
|
+
async function fetchHyperliquidSpotAssetCtxs(environment = "mainnet") {
|
|
796
|
+
return postInfo(environment, { type: "spotAssetCtxs" });
|
|
797
|
+
}
|
|
798
|
+
async function fetchHyperliquidOpenOrders(params) {
|
|
799
|
+
const env = params.environment ?? "mainnet";
|
|
800
|
+
return postInfo(env, { type: "openOrders", user: normalizeAddress(params.user) });
|
|
801
|
+
}
|
|
802
|
+
async function fetchHyperliquidFrontendOpenOrders(params) {
|
|
803
|
+
const env = params.environment ?? "mainnet";
|
|
804
|
+
return postInfo(env, {
|
|
805
|
+
type: "frontendOpenOrders",
|
|
806
|
+
user: normalizeAddress(params.user)
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
async function fetchHyperliquidOrderStatus(params) {
|
|
810
|
+
const env = params.environment ?? "mainnet";
|
|
811
|
+
return postInfo(env, {
|
|
812
|
+
type: "orderStatus",
|
|
813
|
+
user: normalizeAddress(params.user),
|
|
814
|
+
oid: params.oid
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
async function fetchHyperliquidHistoricalOrders(params) {
|
|
818
|
+
const env = params.environment ?? "mainnet";
|
|
819
|
+
return postInfo(env, {
|
|
820
|
+
type: "historicalOrders",
|
|
821
|
+
user: normalizeAddress(params.user)
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
async function fetchHyperliquidUserFills(params) {
|
|
825
|
+
const env = params.environment ?? "mainnet";
|
|
826
|
+
return postInfo(env, {
|
|
827
|
+
type: "userFills",
|
|
828
|
+
user: normalizeAddress(params.user)
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
async function fetchHyperliquidUserFillsByTime(params) {
|
|
832
|
+
const env = params.environment ?? "mainnet";
|
|
833
|
+
return postInfo(env, {
|
|
834
|
+
type: "userFillsByTime",
|
|
835
|
+
user: normalizeAddress(params.user),
|
|
836
|
+
startTime: params.startTime,
|
|
837
|
+
endTime: params.endTime
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
async function fetchHyperliquidUserRateLimit(params) {
|
|
841
|
+
const env = params.environment ?? "mainnet";
|
|
842
|
+
return postInfo(env, {
|
|
843
|
+
type: "userRateLimit",
|
|
844
|
+
user: normalizeAddress(params.user)
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
async function fetchHyperliquidPreTransferCheck(params) {
|
|
848
|
+
const env = params.environment ?? "mainnet";
|
|
849
|
+
return postInfo(env, {
|
|
850
|
+
type: "preTransferCheck",
|
|
851
|
+
user: normalizeAddress(params.user),
|
|
852
|
+
source: normalizeAddress(params.source)
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
async function fetchHyperliquidSpotClearinghouseState(params) {
|
|
856
|
+
const env = params.environment ?? "mainnet";
|
|
857
|
+
return postInfo(env, {
|
|
858
|
+
type: "spotClearinghouseState",
|
|
859
|
+
user: normalizeAddress(params.user)
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/adapters/hyperliquid/exchange.ts
|
|
864
|
+
function resolveRequiredExchangeNonce(options) {
|
|
865
|
+
if (typeof options.nonce === "number") {
|
|
866
|
+
return options.nonce;
|
|
867
|
+
}
|
|
868
|
+
const resolved = options.walletNonceProvider?.() ?? options.wallet.nonceSource?.() ?? options.nonceSource?.();
|
|
869
|
+
if (resolved === void 0) {
|
|
870
|
+
throw new Error(`${options.action} requires an explicit nonce or wallet nonce source.`);
|
|
871
|
+
}
|
|
872
|
+
return resolved;
|
|
873
|
+
}
|
|
874
|
+
var HyperliquidExchangeClient = class {
|
|
875
|
+
constructor(args) {
|
|
876
|
+
this.wallet = args.wallet;
|
|
877
|
+
this.environment = args.environment ?? "mainnet";
|
|
878
|
+
this.vaultAddress = args.vaultAddress;
|
|
879
|
+
this.expiresAfter = args.expiresAfter;
|
|
880
|
+
const resolvedNonceSource = args.walletNonceProvider ?? args.wallet.nonceSource ?? args.nonceSource;
|
|
881
|
+
if (!resolvedNonceSource) {
|
|
882
|
+
throw new Error("Wallet nonce source is required for Hyperliquid exchange actions.");
|
|
883
|
+
}
|
|
884
|
+
this.nonceSource = resolvedNonceSource;
|
|
885
|
+
}
|
|
886
|
+
cancel(cancels) {
|
|
887
|
+
return cancelHyperliquidOrders({
|
|
888
|
+
wallet: this.wallet,
|
|
889
|
+
cancels,
|
|
890
|
+
environment: this.environment,
|
|
891
|
+
vaultAddress: this.vaultAddress,
|
|
892
|
+
expiresAfter: this.expiresAfter,
|
|
893
|
+
nonceSource: this.nonceSource
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
cancelByCloid(cancels) {
|
|
897
|
+
return cancelHyperliquidOrdersByCloid({
|
|
898
|
+
wallet: this.wallet,
|
|
899
|
+
cancels,
|
|
900
|
+
environment: this.environment,
|
|
901
|
+
vaultAddress: this.vaultAddress,
|
|
902
|
+
expiresAfter: this.expiresAfter,
|
|
903
|
+
nonceSource: this.nonceSource
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
cancelAll() {
|
|
907
|
+
return cancelAllHyperliquidOrders({
|
|
908
|
+
wallet: this.wallet,
|
|
909
|
+
environment: this.environment,
|
|
910
|
+
vaultAddress: this.vaultAddress,
|
|
911
|
+
expiresAfter: this.expiresAfter,
|
|
912
|
+
nonceSource: this.nonceSource
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
scheduleCancel(time) {
|
|
916
|
+
return scheduleHyperliquidCancel({
|
|
917
|
+
wallet: this.wallet,
|
|
918
|
+
time,
|
|
919
|
+
environment: this.environment,
|
|
920
|
+
vaultAddress: this.vaultAddress,
|
|
921
|
+
expiresAfter: this.expiresAfter,
|
|
922
|
+
nonceSource: this.nonceSource
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
modify(modification) {
|
|
926
|
+
return modifyHyperliquidOrder({
|
|
927
|
+
wallet: this.wallet,
|
|
928
|
+
modification,
|
|
929
|
+
environment: this.environment,
|
|
930
|
+
vaultAddress: this.vaultAddress,
|
|
931
|
+
expiresAfter: this.expiresAfter,
|
|
932
|
+
nonceSource: this.nonceSource
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
batchModify(modifications) {
|
|
936
|
+
return batchModifyHyperliquidOrders({
|
|
937
|
+
wallet: this.wallet,
|
|
938
|
+
modifications,
|
|
939
|
+
environment: this.environment,
|
|
940
|
+
vaultAddress: this.vaultAddress,
|
|
941
|
+
expiresAfter: this.expiresAfter,
|
|
942
|
+
nonceSource: this.nonceSource
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
twapOrder(twap) {
|
|
946
|
+
return placeHyperliquidTwapOrder({
|
|
947
|
+
wallet: this.wallet,
|
|
948
|
+
twap,
|
|
949
|
+
environment: this.environment,
|
|
950
|
+
vaultAddress: this.vaultAddress,
|
|
951
|
+
expiresAfter: this.expiresAfter,
|
|
952
|
+
nonceSource: this.nonceSource
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
twapCancel(cancel) {
|
|
956
|
+
return cancelHyperliquidTwapOrder({
|
|
957
|
+
wallet: this.wallet,
|
|
958
|
+
cancel,
|
|
959
|
+
environment: this.environment,
|
|
960
|
+
vaultAddress: this.vaultAddress,
|
|
961
|
+
expiresAfter: this.expiresAfter,
|
|
962
|
+
nonceSource: this.nonceSource
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
updateLeverage(input) {
|
|
966
|
+
return updateHyperliquidLeverage({
|
|
967
|
+
wallet: this.wallet,
|
|
968
|
+
input,
|
|
969
|
+
environment: this.environment,
|
|
970
|
+
vaultAddress: this.vaultAddress,
|
|
971
|
+
expiresAfter: this.expiresAfter,
|
|
972
|
+
nonceSource: this.nonceSource
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
updateIsolatedMargin(input) {
|
|
976
|
+
return updateHyperliquidIsolatedMargin({
|
|
977
|
+
wallet: this.wallet,
|
|
978
|
+
input,
|
|
979
|
+
environment: this.environment,
|
|
980
|
+
vaultAddress: this.vaultAddress,
|
|
981
|
+
expiresAfter: this.expiresAfter,
|
|
982
|
+
nonceSource: this.nonceSource
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
reserveRequestWeight(weight) {
|
|
986
|
+
return reserveHyperliquidRequestWeight({
|
|
987
|
+
wallet: this.wallet,
|
|
988
|
+
weight,
|
|
989
|
+
environment: this.environment,
|
|
990
|
+
vaultAddress: this.vaultAddress,
|
|
991
|
+
expiresAfter: this.expiresAfter,
|
|
992
|
+
nonceSource: this.nonceSource
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
spotSend(params) {
|
|
996
|
+
return sendHyperliquidSpot({
|
|
997
|
+
wallet: this.wallet,
|
|
998
|
+
environment: this.environment,
|
|
999
|
+
nonceSource: this.nonceSource,
|
|
1000
|
+
...params
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
setDexAbstraction(params) {
|
|
1004
|
+
const base = {
|
|
1005
|
+
wallet: this.wallet,
|
|
1006
|
+
enabled: params.enabled,
|
|
1007
|
+
environment: this.environment,
|
|
1008
|
+
vaultAddress: this.vaultAddress,
|
|
1009
|
+
expiresAfter: this.expiresAfter,
|
|
1010
|
+
nonceSource: this.nonceSource
|
|
1011
|
+
};
|
|
1012
|
+
return setHyperliquidDexAbstraction(params.user ? { ...base, user: params.user } : base);
|
|
1013
|
+
}
|
|
1014
|
+
setAccountAbstractionMode(params) {
|
|
1015
|
+
const base = {
|
|
1016
|
+
wallet: this.wallet,
|
|
1017
|
+
mode: params.mode,
|
|
1018
|
+
environment: this.environment,
|
|
1019
|
+
vaultAddress: this.vaultAddress,
|
|
1020
|
+
expiresAfter: this.expiresAfter,
|
|
1021
|
+
nonceSource: this.nonceSource
|
|
1022
|
+
};
|
|
1023
|
+
return setHyperliquidAccountAbstractionMode(
|
|
1024
|
+
params.user ? { ...base, user: params.user } : base
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
setPortfolioMargin(params) {
|
|
1028
|
+
const base = {
|
|
1029
|
+
wallet: this.wallet,
|
|
1030
|
+
enabled: params.enabled,
|
|
1031
|
+
environment: this.environment,
|
|
1032
|
+
vaultAddress: this.vaultAddress,
|
|
1033
|
+
expiresAfter: this.expiresAfter,
|
|
1034
|
+
nonceSource: this.nonceSource
|
|
1035
|
+
};
|
|
1036
|
+
return setHyperliquidPortfolioMargin(params.user ? { ...base, user: params.user } : base);
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
async function setHyperliquidPortfolioMargin(options) {
|
|
1040
|
+
const env = options.environment ?? "mainnet";
|
|
1041
|
+
if (!options.wallet?.account || !options.wallet.walletClient) {
|
|
1042
|
+
throw new Error("Wallet with signing capability is required for portfolio margin.");
|
|
1043
|
+
}
|
|
1044
|
+
const nonce = resolveRequiredExchangeNonce({
|
|
1045
|
+
nonce: options.nonce,
|
|
1046
|
+
nonceSource: options.nonceSource,
|
|
1047
|
+
walletNonceProvider: options.walletNonceProvider,
|
|
1048
|
+
wallet: options.wallet,
|
|
1049
|
+
action: "Hyperliquid portfolio margin"
|
|
1050
|
+
});
|
|
1051
|
+
const signatureChainId = getSignatureChainId(env);
|
|
1052
|
+
const hyperliquidChain = HL_CHAIN_LABEL[env];
|
|
1053
|
+
const user = normalizeAddress(options.user ?? options.wallet.address);
|
|
1054
|
+
const action = {
|
|
1055
|
+
type: "userPortfolioMargin",
|
|
1056
|
+
enabled: Boolean(options.enabled),
|
|
1057
|
+
hyperliquidChain,
|
|
1058
|
+
signatureChainId,
|
|
1059
|
+
user,
|
|
1060
|
+
nonce
|
|
1061
|
+
};
|
|
1062
|
+
const signature = await signUserPortfolioMargin({
|
|
1063
|
+
wallet: options.wallet,
|
|
1064
|
+
action
|
|
1065
|
+
});
|
|
1066
|
+
const body = {
|
|
1067
|
+
action,
|
|
1068
|
+
nonce,
|
|
1069
|
+
signature
|
|
1070
|
+
};
|
|
1071
|
+
if (options.vaultAddress) {
|
|
1072
|
+
body.vaultAddress = normalizeAddress(options.vaultAddress);
|
|
1073
|
+
}
|
|
1074
|
+
if (typeof options.expiresAfter === "number") {
|
|
1075
|
+
body.expiresAfter = options.expiresAfter;
|
|
1076
|
+
}
|
|
1077
|
+
return postExchange(env, body);
|
|
1078
|
+
}
|
|
1079
|
+
async function setHyperliquidDexAbstraction(options) {
|
|
1080
|
+
const env = options.environment ?? "mainnet";
|
|
1081
|
+
if (!options.wallet?.account || !options.wallet.walletClient) {
|
|
1082
|
+
throw new Error("Wallet with signing capability is required for dex abstraction.");
|
|
1083
|
+
}
|
|
1084
|
+
const nonce = resolveRequiredExchangeNonce({
|
|
1085
|
+
nonce: options.nonce,
|
|
1086
|
+
nonceSource: options.nonceSource,
|
|
1087
|
+
walletNonceProvider: options.walletNonceProvider,
|
|
1088
|
+
wallet: options.wallet,
|
|
1089
|
+
action: "Hyperliquid dex abstraction"
|
|
1090
|
+
});
|
|
1091
|
+
const signatureChainId = getSignatureChainId(env);
|
|
1092
|
+
const hyperliquidChain = HL_CHAIN_LABEL[env];
|
|
1093
|
+
const user = normalizeAddress(options.user ?? options.wallet.address);
|
|
1094
|
+
const action = {
|
|
1095
|
+
type: "userDexAbstraction",
|
|
1096
|
+
enabled: Boolean(options.enabled),
|
|
1097
|
+
hyperliquidChain,
|
|
1098
|
+
signatureChainId,
|
|
1099
|
+
user,
|
|
1100
|
+
nonce
|
|
1101
|
+
};
|
|
1102
|
+
const signature = await signUserDexAbstraction({
|
|
1103
|
+
wallet: options.wallet,
|
|
1104
|
+
action
|
|
1105
|
+
});
|
|
1106
|
+
const body = {
|
|
1107
|
+
action,
|
|
1108
|
+
nonce,
|
|
1109
|
+
signature
|
|
1110
|
+
};
|
|
1111
|
+
if (options.vaultAddress) {
|
|
1112
|
+
body.vaultAddress = normalizeAddress(options.vaultAddress);
|
|
1113
|
+
}
|
|
1114
|
+
if (typeof options.expiresAfter === "number") {
|
|
1115
|
+
body.expiresAfter = options.expiresAfter;
|
|
1116
|
+
}
|
|
1117
|
+
return postExchange(env, body);
|
|
1118
|
+
}
|
|
1119
|
+
async function setHyperliquidAccountAbstractionMode(options) {
|
|
1120
|
+
const env = options.environment ?? "mainnet";
|
|
1121
|
+
if (!options.wallet?.account || !options.wallet.walletClient) {
|
|
1122
|
+
throw new Error("Wallet with signing capability is required for account abstraction mode.");
|
|
1123
|
+
}
|
|
1124
|
+
const nonce = resolveRequiredExchangeNonce({
|
|
1125
|
+
nonce: options.nonce,
|
|
1126
|
+
nonceSource: options.nonceSource,
|
|
1127
|
+
walletNonceProvider: options.walletNonceProvider,
|
|
1128
|
+
wallet: options.wallet,
|
|
1129
|
+
action: "Hyperliquid account abstraction mode"
|
|
1130
|
+
});
|
|
1131
|
+
const signatureChainId = getSignatureChainId(env);
|
|
1132
|
+
const hyperliquidChain = HL_CHAIN_LABEL[env];
|
|
1133
|
+
const user = normalizeAddress(options.user ?? options.wallet.address);
|
|
1134
|
+
const abstraction = resolveHyperliquidAbstractionFromMode(options.mode);
|
|
1135
|
+
const action = {
|
|
1136
|
+
type: "userSetAbstraction",
|
|
1137
|
+
abstraction,
|
|
1138
|
+
hyperliquidChain,
|
|
1139
|
+
signatureChainId,
|
|
1140
|
+
user,
|
|
1141
|
+
nonce
|
|
1142
|
+
};
|
|
1143
|
+
const signature = await signUserSetAbstraction({
|
|
1144
|
+
wallet: options.wallet,
|
|
1145
|
+
action
|
|
1146
|
+
});
|
|
1147
|
+
const body = {
|
|
1148
|
+
action,
|
|
1149
|
+
nonce,
|
|
1150
|
+
signature
|
|
1151
|
+
};
|
|
1152
|
+
if (options.vaultAddress) {
|
|
1153
|
+
body.vaultAddress = normalizeAddress(options.vaultAddress);
|
|
1154
|
+
}
|
|
1155
|
+
if (typeof options.expiresAfter === "number") {
|
|
1156
|
+
body.expiresAfter = options.expiresAfter;
|
|
1157
|
+
}
|
|
1158
|
+
return postExchange(env, body);
|
|
1159
|
+
}
|
|
1160
|
+
async function cancelHyperliquidOrders(options) {
|
|
1161
|
+
options.cancels.forEach((c) => assertSymbol(c.symbol));
|
|
1162
|
+
const action = {
|
|
1163
|
+
type: "cancel",
|
|
1164
|
+
cancels: await withAssetIndexes(options, options.cancels, (idx, entry) => ({
|
|
1165
|
+
a: idx,
|
|
1166
|
+
o: entry.oid
|
|
1167
|
+
}))
|
|
1168
|
+
};
|
|
1169
|
+
return submitExchangeAction(options, action);
|
|
1170
|
+
}
|
|
1171
|
+
async function cancelHyperliquidOrdersByCloid(options) {
|
|
1172
|
+
options.cancels.forEach((c) => assertSymbol(c.symbol));
|
|
1173
|
+
const action = {
|
|
1174
|
+
type: "cancelByCloid",
|
|
1175
|
+
cancels: await withAssetIndexes(options, options.cancels, (idx, entry) => ({
|
|
1176
|
+
asset: idx,
|
|
1177
|
+
cloid: normalizeCloid(entry.cloid)
|
|
1178
|
+
}))
|
|
1179
|
+
};
|
|
1180
|
+
return submitExchangeAction(options, action);
|
|
1181
|
+
}
|
|
1182
|
+
async function cancelAllHyperliquidOrders(options) {
|
|
1183
|
+
const action = { type: "cancelAll" };
|
|
1184
|
+
return submitExchangeAction(options, action);
|
|
1185
|
+
}
|
|
1186
|
+
async function scheduleHyperliquidCancel(options) {
|
|
1187
|
+
if (options.time != null) {
|
|
1188
|
+
assertPositiveNumber(options.time, "time");
|
|
1189
|
+
}
|
|
1190
|
+
const action = options.time == null ? { type: "scheduleCancel" } : { type: "scheduleCancel", time: options.time };
|
|
1191
|
+
return submitExchangeAction(options, action);
|
|
1192
|
+
}
|
|
1193
|
+
async function modifyHyperliquidOrder(options) {
|
|
1194
|
+
const { modification } = options;
|
|
1195
|
+
const order = await buildOrder(modification.order, options);
|
|
1196
|
+
const action = {
|
|
1197
|
+
type: "modify",
|
|
1198
|
+
oid: modification.oid,
|
|
1199
|
+
order
|
|
1200
|
+
};
|
|
1201
|
+
return submitExchangeAction(options, action);
|
|
1202
|
+
}
|
|
1203
|
+
async function batchModifyHyperliquidOrders(options) {
|
|
1204
|
+
options.modifications.forEach((m) => assertSymbol(m.order.symbol));
|
|
1205
|
+
const modifies = await Promise.all(
|
|
1206
|
+
options.modifications.map(async (mod) => ({
|
|
1207
|
+
oid: mod.oid,
|
|
1208
|
+
order: await buildOrder(mod.order, options)
|
|
1209
|
+
}))
|
|
1210
|
+
);
|
|
1211
|
+
const action = {
|
|
1212
|
+
type: "batchModify",
|
|
1213
|
+
modifies
|
|
1214
|
+
};
|
|
1215
|
+
return submitExchangeAction(options, action);
|
|
1216
|
+
}
|
|
1217
|
+
async function placeHyperliquidTwapOrder(options) {
|
|
1218
|
+
const { twap } = options;
|
|
1219
|
+
assertSymbol(twap.symbol);
|
|
1220
|
+
assertPositiveDecimal(twap.size, "size");
|
|
1221
|
+
assertPositiveNumber(twap.minutes, "minutes");
|
|
1222
|
+
const env = options.environment ?? "mainnet";
|
|
1223
|
+
const asset = await resolveHyperliquidAssetIndex({
|
|
1224
|
+
symbol: twap.symbol,
|
|
1225
|
+
baseUrl: API_BASES[env],
|
|
1226
|
+
environment: env,
|
|
1227
|
+
fetcher: fetch
|
|
1228
|
+
});
|
|
1229
|
+
const action = {
|
|
1230
|
+
type: "twapOrder",
|
|
1231
|
+
twap: {
|
|
1232
|
+
a: asset,
|
|
1233
|
+
b: twap.side === "buy",
|
|
1234
|
+
s: toApiDecimal(twap.size),
|
|
1235
|
+
r: Boolean(twap.reduceOnly),
|
|
1236
|
+
m: twap.minutes,
|
|
1237
|
+
t: Boolean(twap.randomize)
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
return submitExchangeAction(options, action);
|
|
1241
|
+
}
|
|
1242
|
+
async function cancelHyperliquidTwapOrder(options) {
|
|
1243
|
+
assertSymbol(options.cancel.symbol);
|
|
1244
|
+
const env = options.environment ?? "mainnet";
|
|
1245
|
+
const asset = await resolveHyperliquidAssetIndex({
|
|
1246
|
+
symbol: options.cancel.symbol,
|
|
1247
|
+
baseUrl: API_BASES[env],
|
|
1248
|
+
environment: env,
|
|
1249
|
+
fetcher: fetch
|
|
1250
|
+
});
|
|
1251
|
+
const action = {
|
|
1252
|
+
type: "twapCancel",
|
|
1253
|
+
a: asset,
|
|
1254
|
+
t: options.cancel.twapId
|
|
1255
|
+
};
|
|
1256
|
+
return submitExchangeAction(options, action);
|
|
1257
|
+
}
|
|
1258
|
+
async function updateHyperliquidLeverage(options) {
|
|
1259
|
+
assertSymbol(options.input.symbol);
|
|
1260
|
+
assertPositiveNumber(options.input.leverage, "leverage");
|
|
1261
|
+
const env = options.environment ?? "mainnet";
|
|
1262
|
+
const asset = await resolveHyperliquidAssetIndex({
|
|
1263
|
+
symbol: options.input.symbol,
|
|
1264
|
+
baseUrl: API_BASES[env],
|
|
1265
|
+
environment: env,
|
|
1266
|
+
fetcher: fetch
|
|
1267
|
+
});
|
|
1268
|
+
const action = {
|
|
1269
|
+
type: "updateLeverage",
|
|
1270
|
+
asset,
|
|
1271
|
+
isCross: options.input.leverageMode === "cross",
|
|
1272
|
+
leverage: options.input.leverage
|
|
1273
|
+
};
|
|
1274
|
+
return submitExchangeAction(options, action);
|
|
1275
|
+
}
|
|
1276
|
+
async function updateHyperliquidIsolatedMargin(options) {
|
|
1277
|
+
assertSymbol(options.input.symbol);
|
|
1278
|
+
assertPositiveNumber(options.input.ntli, "ntli");
|
|
1279
|
+
const env = options.environment ?? "mainnet";
|
|
1280
|
+
const asset = await resolveHyperliquidAssetIndex({
|
|
1281
|
+
symbol: options.input.symbol,
|
|
1282
|
+
baseUrl: API_BASES[env],
|
|
1283
|
+
environment: env,
|
|
1284
|
+
fetcher: fetch
|
|
1285
|
+
});
|
|
1286
|
+
const action = {
|
|
1287
|
+
type: "updateIsolatedMargin",
|
|
1288
|
+
asset,
|
|
1289
|
+
isBuy: options.input.isBuy,
|
|
1290
|
+
ntli: options.input.ntli
|
|
1291
|
+
};
|
|
1292
|
+
return submitExchangeAction(options, action);
|
|
1293
|
+
}
|
|
1294
|
+
async function reserveHyperliquidRequestWeight(options) {
|
|
1295
|
+
assertPositiveNumber(options.weight, "weight");
|
|
1296
|
+
const action = {
|
|
1297
|
+
type: "reserveRequestWeight",
|
|
1298
|
+
weight: options.weight
|
|
1299
|
+
};
|
|
1300
|
+
return submitExchangeAction(options, action);
|
|
1301
|
+
}
|
|
1302
|
+
async function createHyperliquidSubAccount(options) {
|
|
1303
|
+
assertString(options.name, "name");
|
|
1304
|
+
const action = {
|
|
1305
|
+
type: "createSubAccount",
|
|
1306
|
+
name: options.name
|
|
1307
|
+
};
|
|
1308
|
+
return submitExchangeAction(options, action);
|
|
1309
|
+
}
|
|
1310
|
+
async function transferHyperliquidSubAccount(options) {
|
|
1311
|
+
assertString(options.subAccountUser, "subAccountUser");
|
|
1312
|
+
const usdScaled = normalizeUsdToInt(options.usd);
|
|
1313
|
+
const action = {
|
|
1314
|
+
type: "subAccountTransfer",
|
|
1315
|
+
subAccountUser: normalizeAddress(options.subAccountUser),
|
|
1316
|
+
isDeposit: Boolean(options.isDeposit),
|
|
1317
|
+
usd: usdScaled
|
|
1318
|
+
};
|
|
1319
|
+
return submitExchangeAction(options, action);
|
|
1320
|
+
}
|
|
1321
|
+
async function sendHyperliquidSpot(options) {
|
|
1322
|
+
const env = options.environment ?? "mainnet";
|
|
1323
|
+
if (!options.wallet.account || !options.wallet.walletClient) {
|
|
1324
|
+
throw new Error("Wallet with signing capability is required for spotSend.");
|
|
1325
|
+
}
|
|
1326
|
+
assertString(options.token, "token");
|
|
1327
|
+
assertPositiveDecimal(options.amount, "amount");
|
|
1328
|
+
const signatureChainId = getSignatureChainId(env);
|
|
1329
|
+
const hyperliquidChain = HL_CHAIN_LABEL[env];
|
|
1330
|
+
const nonce = resolveRequiredExchangeNonce({
|
|
1331
|
+
nonce: options.nonce,
|
|
1332
|
+
nonceSource: options.nonceSource,
|
|
1333
|
+
wallet: options.wallet,
|
|
1334
|
+
action: "Hyperliquid spot send"
|
|
1335
|
+
});
|
|
1336
|
+
const time = BigInt(nonce);
|
|
1337
|
+
const signature = await signSpotSend({
|
|
1338
|
+
wallet: options.wallet,
|
|
1339
|
+
hyperliquidChain,
|
|
1340
|
+
signatureChainId,
|
|
1341
|
+
destination: normalizeAddress(options.destination),
|
|
1342
|
+
token: options.token,
|
|
1343
|
+
amount: toApiDecimal(options.amount),
|
|
1344
|
+
time
|
|
1345
|
+
});
|
|
1346
|
+
const action = {
|
|
1347
|
+
type: "spotSend",
|
|
1348
|
+
hyperliquidChain,
|
|
1349
|
+
signatureChainId,
|
|
1350
|
+
destination: normalizeAddress(options.destination),
|
|
1351
|
+
token: options.token,
|
|
1352
|
+
amount: toApiDecimal(options.amount),
|
|
1353
|
+
time: nonce
|
|
1354
|
+
};
|
|
1355
|
+
return postExchange(env, { action, nonce, signature });
|
|
1356
|
+
}
|
|
1357
|
+
async function submitExchangeAction(options, action) {
|
|
1358
|
+
if (!options.wallet?.account || !options.wallet.walletClient) {
|
|
1359
|
+
throw new Error("Hyperliquid exchange actions require a signing wallet.");
|
|
1360
|
+
}
|
|
1361
|
+
const env = options.environment ?? "mainnet";
|
|
1362
|
+
const nonceSource = options.walletNonceProvider ?? options.wallet.nonceSource ?? options.nonceSource;
|
|
1363
|
+
if (!nonceSource && options.nonce === void 0) {
|
|
1364
|
+
throw new Error("Wallet nonce source is required for Hyperliquid exchange actions.");
|
|
1365
|
+
}
|
|
1366
|
+
const effectiveNonce = options.nonce ?? nonceSource?.();
|
|
1367
|
+
if (effectiveNonce === void 0) {
|
|
1368
|
+
throw new Error("Hyperliquid exchange actions require a nonce.");
|
|
1369
|
+
}
|
|
1370
|
+
const signature = await signL1Action({
|
|
1371
|
+
wallet: options.wallet,
|
|
1372
|
+
action,
|
|
1373
|
+
nonce: effectiveNonce,
|
|
1374
|
+
vaultAddress: options.vaultAddress ? normalizeAddress(options.vaultAddress) : void 0,
|
|
1375
|
+
expiresAfter: options.expiresAfter,
|
|
1376
|
+
isTestnet: env === "testnet"
|
|
1377
|
+
});
|
|
1378
|
+
const body = {
|
|
1379
|
+
action,
|
|
1380
|
+
nonce: effectiveNonce,
|
|
1381
|
+
signature
|
|
1382
|
+
};
|
|
1383
|
+
if (options.vaultAddress) {
|
|
1384
|
+
body.vaultAddress = normalizeAddress(options.vaultAddress);
|
|
1385
|
+
}
|
|
1386
|
+
if (typeof options.expiresAfter === "number") {
|
|
1387
|
+
body.expiresAfter = options.expiresAfter;
|
|
1388
|
+
}
|
|
1389
|
+
return postExchange(env, body);
|
|
1390
|
+
}
|
|
1391
|
+
async function withAssetIndexes(options, entries, mapper) {
|
|
1392
|
+
const env = options.environment ?? "mainnet";
|
|
1393
|
+
return Promise.all(
|
|
1394
|
+
entries.map(async (entry) => {
|
|
1395
|
+
const assetIndex = await resolveHyperliquidAssetIndex({
|
|
1396
|
+
symbol: entry.symbol,
|
|
1397
|
+
baseUrl: API_BASES[env],
|
|
1398
|
+
environment: env,
|
|
1399
|
+
fetcher: fetch
|
|
1400
|
+
});
|
|
1401
|
+
return mapper(assetIndex, entry);
|
|
1402
|
+
})
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
async function buildOrder(intent, options) {
|
|
1406
|
+
assertSymbol(intent.symbol);
|
|
1407
|
+
assertPositiveDecimal(intent.price, "price");
|
|
1408
|
+
assertPositiveDecimal(intent.size, "size");
|
|
1409
|
+
const env = options.environment ?? "mainnet";
|
|
1410
|
+
const assetIndex = await resolveHyperliquidAssetIndex({
|
|
1411
|
+
symbol: intent.symbol,
|
|
1412
|
+
baseUrl: API_BASES[env],
|
|
1413
|
+
environment: env,
|
|
1414
|
+
fetcher: fetch
|
|
1415
|
+
});
|
|
1416
|
+
const limitOrTrigger = intent.trigger ? mapTrigger(intent.trigger) : {
|
|
1417
|
+
limit: {
|
|
1418
|
+
tif: intent.tif ?? "Ioc"
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
return {
|
|
1422
|
+
a: assetIndex,
|
|
1423
|
+
b: intent.side === "buy",
|
|
1424
|
+
p: toApiDecimal(intent.price),
|
|
1425
|
+
s: toApiDecimal(intent.size),
|
|
1426
|
+
r: intent.reduceOnly ?? false,
|
|
1427
|
+
t: limitOrTrigger,
|
|
1428
|
+
...intent.clientId ? {
|
|
1429
|
+
c: normalizeCloid(intent.clientId)
|
|
1430
|
+
} : {}
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function mapTrigger(trigger) {
|
|
1434
|
+
assertPositiveDecimal(trigger.triggerPx, "triggerPx");
|
|
1435
|
+
return {
|
|
1436
|
+
trigger: {
|
|
1437
|
+
isMarket: Boolean(trigger.isMarket),
|
|
1438
|
+
triggerPx: toApiDecimal(trigger.triggerPx),
|
|
1439
|
+
tpsl: trigger.tpsl
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
function assertSymbol(value) {
|
|
1444
|
+
assertString(value, "symbol");
|
|
1445
|
+
}
|
|
1446
|
+
function normalizeUsdToInt(value) {
|
|
1447
|
+
if (typeof value === "bigint") {
|
|
1448
|
+
if (value < 0n) {
|
|
1449
|
+
throw new Error("usd must be non-negative.");
|
|
1450
|
+
}
|
|
1451
|
+
return Number(value);
|
|
1452
|
+
}
|
|
1453
|
+
const parsed = typeof value === "string" ? Number.parseFloat(value) : Number(value);
|
|
1454
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
1455
|
+
throw new Error("usd must be a non-negative number.");
|
|
1456
|
+
}
|
|
1457
|
+
return Math.round(parsed * 1e6);
|
|
1458
|
+
}
|
|
1459
|
+
function assertString(value, label) {
|
|
1460
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
1461
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
function assertPositiveDecimal(value, label) {
|
|
1465
|
+
if (typeof value === "number") {
|
|
1466
|
+
assertPositiveNumber(value, label);
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
if (typeof value === "bigint") {
|
|
1470
|
+
if (value <= 0n) {
|
|
1471
|
+
throw new Error(`${label} must be positive.`);
|
|
1472
|
+
}
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
assertString(value, label);
|
|
1476
|
+
if (!/^(?:\d+\.?\d*|\.\d+)$/.test(value.trim())) {
|
|
1477
|
+
throw new Error(`${label} must be a positive decimal string.`);
|
|
1478
|
+
}
|
|
1479
|
+
const numeric = Number(value);
|
|
1480
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
1481
|
+
throw new Error(`${label} must be positive.`);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
function collectExchangeErrorMessages(payload) {
|
|
1485
|
+
if (!payload || typeof payload !== "object") return [];
|
|
1486
|
+
const root = payload;
|
|
1487
|
+
const messages = [];
|
|
1488
|
+
const statuses = root.response?.data?.statuses;
|
|
1489
|
+
if (Array.isArray(statuses)) {
|
|
1490
|
+
statuses.forEach((status, index) => {
|
|
1491
|
+
if (status && typeof status === "object" && "error" in status && typeof status.error === "string") {
|
|
1492
|
+
const errorText = status.error;
|
|
1493
|
+
messages.push(`status[${index}]: ${errorText}`);
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
const singleStatus = root.response?.data?.status;
|
|
1498
|
+
if (singleStatus && typeof singleStatus === "object" && "error" in singleStatus && typeof singleStatus.error === "string") {
|
|
1499
|
+
messages.push(singleStatus.error);
|
|
1500
|
+
}
|
|
1501
|
+
return messages;
|
|
1502
|
+
}
|
|
1503
|
+
async function postExchange(env, body) {
|
|
1504
|
+
const response = await fetch(`${API_BASES[env]}/exchange`, {
|
|
1505
|
+
method: "POST",
|
|
1506
|
+
headers: { "content-type": "application/json" },
|
|
1507
|
+
body: JSON.stringify(body)
|
|
1508
|
+
});
|
|
1509
|
+
const text = await response.text().catch(() => "");
|
|
1510
|
+
const json = (() => {
|
|
1511
|
+
if (!text) return null;
|
|
1512
|
+
try {
|
|
1513
|
+
return JSON.parse(text);
|
|
1514
|
+
} catch {
|
|
1515
|
+
return null;
|
|
1516
|
+
}
|
|
1517
|
+
})();
|
|
1518
|
+
if (!response.ok) {
|
|
1519
|
+
throw new HyperliquidApiError("Hyperliquid exchange action failed.", {
|
|
1520
|
+
status: response.status,
|
|
1521
|
+
statusText: response.statusText,
|
|
1522
|
+
body: json ?? (text ? text : null)
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
if (!json) {
|
|
1526
|
+
throw new HyperliquidApiError("Hyperliquid exchange action failed.", {
|
|
1527
|
+
status: response.status,
|
|
1528
|
+
statusText: response.statusText,
|
|
1529
|
+
body: text ? text : null
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
if (json.status !== "ok") {
|
|
1533
|
+
throw new HyperliquidApiError("Hyperliquid exchange returned error.", {
|
|
1534
|
+
status: response.status,
|
|
1535
|
+
statusText: response.statusText,
|
|
1536
|
+
body: json
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
const nestedErrors = collectExchangeErrorMessages(json);
|
|
1540
|
+
if (nestedErrors.length > 0) {
|
|
1541
|
+
throw new HyperliquidApiError("Hyperliquid exchange returned action errors.", {
|
|
1542
|
+
status: response.status,
|
|
1543
|
+
statusText: response.statusText,
|
|
1544
|
+
body: json,
|
|
1545
|
+
errors: nestedErrors
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
return json;
|
|
1549
|
+
}
|
|
1550
|
+
function resolveRequiredNonce(params) {
|
|
1551
|
+
if (typeof params.nonce === "number") {
|
|
1552
|
+
return params.nonce;
|
|
1553
|
+
}
|
|
1554
|
+
const resolved = params.nonceSource?.() ?? params.wallet?.nonceSource?.();
|
|
1555
|
+
if (resolved === void 0) {
|
|
1556
|
+
throw new Error(`${params.action} requires an explicit nonce or wallet nonce source.`);
|
|
1557
|
+
}
|
|
1558
|
+
return resolved;
|
|
1559
|
+
}
|
|
1560
|
+
function assertPositiveDecimalInput(value, label) {
|
|
1561
|
+
if (typeof value === "number") {
|
|
1562
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
1563
|
+
throw new Error(`${label} must be a positive number.`);
|
|
1564
|
+
}
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (typeof value === "bigint") {
|
|
1568
|
+
if (value <= 0n) {
|
|
1569
|
+
throw new Error(`${label} must be positive.`);
|
|
1570
|
+
}
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
const trimmed = value.trim();
|
|
1574
|
+
if (!trimmed.length) {
|
|
1575
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
1576
|
+
}
|
|
1577
|
+
if (!/^(?:\\d+\\.?\\d*|\\.\\d+)$/.test(trimmed)) {
|
|
1578
|
+
throw new Error(`${label} must be a positive decimal string.`);
|
|
1579
|
+
}
|
|
1580
|
+
const numeric = Number(trimmed);
|
|
1581
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
1582
|
+
throw new Error(`${label} must be positive.`);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
function normalizePositiveDecimalString(raw, label) {
|
|
1586
|
+
const trimmed = raw.trim();
|
|
1587
|
+
if (!trimmed.length) {
|
|
1588
|
+
throw new Error(`${label} must be a non-empty decimal string.`);
|
|
1589
|
+
}
|
|
1590
|
+
if (!/^(?:\\d+\\.?\\d*|\\.\\d+)$/.test(trimmed)) {
|
|
1591
|
+
throw new Error(`${label} must be a positive decimal string.`);
|
|
1592
|
+
}
|
|
1593
|
+
const normalized = trimmed.replace(/^0+(?=\\d)/, "").replace(/(\\.\\d*?)0+$/, "$1").replace(/\\.$/, "");
|
|
1594
|
+
const numeric = Number(normalized);
|
|
1595
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
1596
|
+
throw new Error(`${label} must be positive.`);
|
|
1597
|
+
}
|
|
1598
|
+
return normalized;
|
|
1599
|
+
}
|
|
1600
|
+
async function placeHyperliquidOrder(options) {
|
|
1601
|
+
const {
|
|
1602
|
+
wallet,
|
|
1603
|
+
orders,
|
|
1604
|
+
grouping = "na",
|
|
1605
|
+
environment,
|
|
1606
|
+
vaultAddress,
|
|
1607
|
+
expiresAfter,
|
|
1608
|
+
nonce
|
|
1609
|
+
} = options;
|
|
1610
|
+
if (!wallet?.account || !wallet.walletClient) {
|
|
1611
|
+
throw new Error("Hyperliquid order signing requires a wallet with signing capabilities.");
|
|
1612
|
+
}
|
|
1613
|
+
if (!orders.length) {
|
|
1614
|
+
throw new Error("At least one order is required.");
|
|
1615
|
+
}
|
|
1616
|
+
const inferredEnvironment = environment ?? "mainnet";
|
|
1617
|
+
const resolvedBaseUrl = API_BASES[inferredEnvironment];
|
|
1618
|
+
const preparedOrders = await Promise.all(
|
|
1619
|
+
orders.map(async (intent) => {
|
|
1620
|
+
assertPositiveDecimalInput(intent.price, "price");
|
|
1621
|
+
assertPositiveDecimalInput(intent.size, "size");
|
|
1622
|
+
if (intent.trigger) {
|
|
1623
|
+
assertPositiveDecimalInput(intent.trigger.triggerPx, "triggerPx");
|
|
1624
|
+
}
|
|
1625
|
+
const assetIndex = await resolveHyperliquidAssetIndex({
|
|
1626
|
+
symbol: intent.symbol,
|
|
1627
|
+
baseUrl: resolvedBaseUrl,
|
|
1628
|
+
environment: inferredEnvironment,
|
|
1629
|
+
fetcher: fetch
|
|
1630
|
+
});
|
|
1631
|
+
const order = {
|
|
1632
|
+
a: assetIndex,
|
|
1633
|
+
b: intent.side === "buy",
|
|
1634
|
+
p: toApiDecimal(intent.price),
|
|
1635
|
+
s: toApiDecimal(intent.size),
|
|
1636
|
+
r: intent.reduceOnly ?? false,
|
|
1637
|
+
t: intent.trigger ? {
|
|
1638
|
+
trigger: {
|
|
1639
|
+
isMarket: Boolean(intent.trigger.isMarket),
|
|
1640
|
+
triggerPx: toApiDecimal(intent.trigger.triggerPx),
|
|
1641
|
+
tpsl: intent.trigger.tpsl
|
|
1642
|
+
}
|
|
1643
|
+
} : {
|
|
1644
|
+
limit: {
|
|
1645
|
+
tif: intent.tif ?? "Ioc"
|
|
1646
|
+
}
|
|
1647
|
+
},
|
|
1648
|
+
...intent.clientId ? { c: normalizeCloid(intent.clientId) } : {}
|
|
1649
|
+
};
|
|
1650
|
+
return order;
|
|
1651
|
+
})
|
|
1652
|
+
);
|
|
1653
|
+
const action = {
|
|
1654
|
+
type: "order",
|
|
1655
|
+
orders: preparedOrders,
|
|
1656
|
+
grouping,
|
|
1657
|
+
builder: {
|
|
1658
|
+
b: normalizeAddress(BUILDER_CODE.address),
|
|
1659
|
+
f: BUILDER_CODE.fee
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
const effectiveNonce = resolveRequiredNonce({
|
|
1663
|
+
nonce,
|
|
1664
|
+
nonceSource: options.nonceSource,
|
|
1665
|
+
wallet,
|
|
1666
|
+
action: "Hyperliquid order submission"
|
|
1667
|
+
});
|
|
1668
|
+
const signature = await signL1Action({
|
|
1669
|
+
wallet,
|
|
1670
|
+
action,
|
|
1671
|
+
nonce: effectiveNonce,
|
|
1672
|
+
...vaultAddress ? { vaultAddress } : {},
|
|
1673
|
+
...typeof expiresAfter === "number" ? { expiresAfter } : {},
|
|
1674
|
+
isTestnet: inferredEnvironment === "testnet"
|
|
1675
|
+
});
|
|
1676
|
+
const body = {
|
|
1677
|
+
action,
|
|
1678
|
+
nonce: effectiveNonce,
|
|
1679
|
+
signature
|
|
1680
|
+
};
|
|
1681
|
+
if (vaultAddress) {
|
|
1682
|
+
body.vaultAddress = normalizeAddress(vaultAddress);
|
|
1683
|
+
}
|
|
1684
|
+
if (typeof expiresAfter === "number") {
|
|
1685
|
+
body.expiresAfter = expiresAfter;
|
|
1686
|
+
}
|
|
1687
|
+
const response = await fetch(`${resolvedBaseUrl}/exchange`, {
|
|
1688
|
+
method: "POST",
|
|
1689
|
+
headers: { "content-type": "application/json" },
|
|
1690
|
+
body: JSON.stringify(body)
|
|
1691
|
+
});
|
|
1692
|
+
const rawText = await response.text().catch(() => null);
|
|
1693
|
+
let parsed = null;
|
|
1694
|
+
if (rawText && rawText.length) {
|
|
1695
|
+
try {
|
|
1696
|
+
parsed = JSON.parse(rawText);
|
|
1697
|
+
} catch {
|
|
1698
|
+
parsed = rawText;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
const json = parsed && typeof parsed === "object" && "status" in parsed ? parsed : null;
|
|
1702
|
+
if (!response.ok || !json) {
|
|
1703
|
+
const detail = parsed?.error ?? parsed?.message ?? (typeof parsed === "string" ? parsed : rawText);
|
|
1704
|
+
const suffix = detail ? ` Detail: ${detail}` : "";
|
|
1705
|
+
throw new HyperliquidApiError(
|
|
1706
|
+
`Failed to submit Hyperliquid order.${suffix}`,
|
|
1707
|
+
parsed ?? rawText ?? { status: response.status }
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
if (json.status !== "ok") {
|
|
1711
|
+
const detail = parsed?.error ?? rawText;
|
|
1712
|
+
throw new HyperliquidApiError(
|
|
1713
|
+
detail ? `Hyperliquid API returned an error status: ${detail}` : "Hyperliquid API returned an error status.",
|
|
1714
|
+
json
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
const statuses = json.response?.data?.statuses ?? [];
|
|
1718
|
+
const errorStatuses = statuses.filter(
|
|
1719
|
+
(entry) => Boolean(
|
|
1720
|
+
entry && typeof entry === "object" && "error" in entry && typeof entry.error === "string"
|
|
1721
|
+
)
|
|
1722
|
+
);
|
|
1723
|
+
if (errorStatuses.length) {
|
|
1724
|
+
const message = errorStatuses.map((entry) => entry.error).join(", ");
|
|
1725
|
+
throw new HyperliquidApiError(message || "Hyperliquid rejected the order.", json);
|
|
1726
|
+
}
|
|
1727
|
+
return json;
|
|
1728
|
+
}
|
|
1729
|
+
async function depositToHyperliquidBridge(options) {
|
|
1730
|
+
const { environment, amount, wallet } = options;
|
|
1731
|
+
const parsedAmount = Number(amount);
|
|
1732
|
+
if (!Number.isFinite(parsedAmount) || parsedAmount <= 0) {
|
|
1733
|
+
throw new Error("Deposit amount must be a positive number.");
|
|
1734
|
+
}
|
|
1735
|
+
if (parsedAmount < MIN_DEPOSIT_USDC) {
|
|
1736
|
+
throw new Error(`Minimum deposit is ${MIN_DEPOSIT_USDC} USDC.`);
|
|
1737
|
+
}
|
|
1738
|
+
if (!wallet.account || !wallet.walletClient || !wallet.publicClient) {
|
|
1739
|
+
throw new Error("Wallet client and public client are required for deposit.");
|
|
1740
|
+
}
|
|
1741
|
+
const bridgeAddress = getBridgeAddress(environment);
|
|
1742
|
+
const usdcAddress = getUsdcAddress(environment);
|
|
1743
|
+
const amountUnits = parseUnits(amount, 6);
|
|
1744
|
+
const data = encodeFunctionData({
|
|
1745
|
+
abi: erc20Abi,
|
|
1746
|
+
functionName: "transfer",
|
|
1747
|
+
args: [bridgeAddress, amountUnits]
|
|
1748
|
+
});
|
|
1749
|
+
const txHash = await wallet.walletClient.sendTransaction({
|
|
1750
|
+
account: wallet.account,
|
|
1751
|
+
to: usdcAddress,
|
|
1752
|
+
data
|
|
1753
|
+
});
|
|
1754
|
+
await wallet.publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
1755
|
+
return {
|
|
1756
|
+
txHash,
|
|
1757
|
+
amount: parsedAmount,
|
|
1758
|
+
amountUnits: amountUnits.toString(),
|
|
1759
|
+
environment,
|
|
1760
|
+
bridgeAddress
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
async function withdrawFromHyperliquid(options) {
|
|
1764
|
+
const { environment, amount, destination, wallet } = options;
|
|
1765
|
+
const normalizedAmount = normalizePositiveDecimalString(amount, "Withdraw amount");
|
|
1766
|
+
const parsedAmount = Number.parseFloat(normalizedAmount);
|
|
1767
|
+
if (!wallet.account || !wallet.walletClient || !wallet.publicClient) {
|
|
1768
|
+
throw new Error("Wallet client and public client are required for withdraw.");
|
|
1769
|
+
}
|
|
1770
|
+
const signatureChainId = getSignatureChainId(environment);
|
|
1771
|
+
const hyperliquidChain = HL_CHAIN_LABEL[environment];
|
|
1772
|
+
const nonce = resolveRequiredNonce({
|
|
1773
|
+
nonce: options.nonce,
|
|
1774
|
+
nonceSource: options.nonceSource,
|
|
1775
|
+
wallet,
|
|
1776
|
+
action: "Hyperliquid withdraw"
|
|
1777
|
+
});
|
|
1778
|
+
const time = BigInt(nonce);
|
|
1779
|
+
const normalizedDestination = normalizeAddress(destination);
|
|
1780
|
+
const signatureHex = await wallet.walletClient.signTypedData({
|
|
1781
|
+
account: wallet.account,
|
|
1782
|
+
domain: {
|
|
1783
|
+
name: "HyperliquidSignTransaction",
|
|
1784
|
+
version: "1",
|
|
1785
|
+
chainId: Number.parseInt(signatureChainId, 16),
|
|
1786
|
+
verifyingContract: ZERO_ADDRESS
|
|
1787
|
+
},
|
|
1788
|
+
types: {
|
|
1789
|
+
"HyperliquidTransaction:Withdraw": [
|
|
1790
|
+
{ name: "hyperliquidChain", type: "string" },
|
|
1791
|
+
{ name: "destination", type: "string" },
|
|
1792
|
+
{ name: "amount", type: "string" },
|
|
1793
|
+
{ name: "time", type: "uint64" }
|
|
1794
|
+
]
|
|
1795
|
+
},
|
|
1796
|
+
primaryType: "HyperliquidTransaction:Withdraw",
|
|
1797
|
+
message: {
|
|
1798
|
+
hyperliquidChain,
|
|
1799
|
+
destination: normalizedDestination,
|
|
1800
|
+
amount: normalizedAmount,
|
|
1801
|
+
time
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
const response = await fetch(`${HL_ENDPOINT[environment]}/exchange`, {
|
|
1805
|
+
method: "POST",
|
|
1806
|
+
headers: { "content-type": "application/json" },
|
|
1807
|
+
body: JSON.stringify({
|
|
1808
|
+
action: {
|
|
1809
|
+
type: "withdraw3",
|
|
1810
|
+
signatureChainId,
|
|
1811
|
+
hyperliquidChain,
|
|
1812
|
+
destination: normalizedDestination,
|
|
1813
|
+
amount: normalizedAmount,
|
|
1814
|
+
time: nonce
|
|
1815
|
+
},
|
|
1816
|
+
nonce,
|
|
1817
|
+
signature: splitSignature(signatureHex)
|
|
1818
|
+
})
|
|
1819
|
+
});
|
|
1820
|
+
const json = await response.json().catch(() => null);
|
|
1821
|
+
if (!response.ok || json?.status !== "ok") {
|
|
1822
|
+
throw new Error(
|
|
1823
|
+
`Hyperliquid withdraw failed: ${json?.response ?? json?.error ?? response.statusText}`
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
return {
|
|
1827
|
+
amount: parsedAmount,
|
|
1828
|
+
destination: normalizedDestination,
|
|
1829
|
+
environment,
|
|
1830
|
+
nonce,
|
|
1831
|
+
status: json.status ?? "ok"
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
async function fetchHyperliquidClearinghouseState(params) {
|
|
1835
|
+
const response = await fetch(`${HL_ENDPOINT[params.environment]}/info`, {
|
|
1836
|
+
method: "POST",
|
|
1837
|
+
headers: { "content-type": "application/json" },
|
|
1838
|
+
body: JSON.stringify({ type: "clearinghouseState", user: params.walletAddress })
|
|
1839
|
+
});
|
|
1840
|
+
const data = await response.json().catch(() => null);
|
|
1841
|
+
return { ok: response.ok, data };
|
|
1842
|
+
}
|
|
1843
|
+
async function approveHyperliquidBuilderFee(options) {
|
|
1844
|
+
const { environment, wallet, nonce, signatureChainId } = options;
|
|
1845
|
+
if (!wallet?.account || !wallet.walletClient) {
|
|
1846
|
+
throw new Error("Hyperliquid builder approval requires a wallet with signing capabilities.");
|
|
1847
|
+
}
|
|
1848
|
+
const inferredEnvironment = environment ?? "mainnet";
|
|
1849
|
+
const maxFeeRate = `${BUILDER_CODE.fee / 1e3}%`;
|
|
1850
|
+
const effectiveNonce = resolveRequiredNonce({
|
|
1851
|
+
nonce,
|
|
1852
|
+
nonceSource: options.nonceSource,
|
|
1853
|
+
wallet,
|
|
1854
|
+
action: "Hyperliquid builder approval"
|
|
1855
|
+
});
|
|
1856
|
+
const response = await fetch(`${API_BASES[inferredEnvironment]}/exchange`, {
|
|
1857
|
+
method: "POST",
|
|
1858
|
+
headers: { "content-type": "application/json" },
|
|
1859
|
+
body: JSON.stringify({
|
|
1860
|
+
action: {
|
|
1861
|
+
type: "approveBuilderFee",
|
|
1862
|
+
maxFeeRate,
|
|
1863
|
+
builder: normalizeAddress(BUILDER_CODE.address),
|
|
1864
|
+
hyperliquidChain: HL_CHAIN_LABEL[inferredEnvironment],
|
|
1865
|
+
signatureChainId: signatureChainId ?? getSignatureChainId(inferredEnvironment),
|
|
1866
|
+
nonce: effectiveNonce
|
|
1867
|
+
},
|
|
1868
|
+
nonce: effectiveNonce,
|
|
1869
|
+
signature: await signApproveBuilderFee({
|
|
1870
|
+
wallet,
|
|
1871
|
+
maxFeeRate,
|
|
1872
|
+
nonce: BigInt(effectiveNonce),
|
|
1873
|
+
signatureChainId: signatureChainId ?? getSignatureChainId(inferredEnvironment),
|
|
1874
|
+
isTestnet: inferredEnvironment === "testnet"
|
|
1875
|
+
})
|
|
1876
|
+
})
|
|
1877
|
+
});
|
|
1878
|
+
const rawText = await response.text().catch(() => null);
|
|
1879
|
+
let parsed = null;
|
|
1880
|
+
if (rawText && rawText.length) {
|
|
1881
|
+
try {
|
|
1882
|
+
parsed = JSON.parse(rawText);
|
|
1883
|
+
} catch {
|
|
1884
|
+
parsed = rawText;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
const json = parsed && typeof parsed === "object" && "status" in parsed ? parsed : null;
|
|
1888
|
+
if (!response.ok || !json) {
|
|
1889
|
+
const detail = parsed?.error ?? parsed?.message ?? (typeof parsed === "string" ? parsed : rawText);
|
|
1890
|
+
const suffix = detail ? ` Detail: ${detail}` : "";
|
|
1891
|
+
throw new HyperliquidApiError(
|
|
1892
|
+
`Failed to submit builder approval.${suffix}`,
|
|
1893
|
+
parsed ?? rawText ?? { status: response.status }
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
if (json.status !== "ok") {
|
|
1897
|
+
const detail = parsed?.error ?? rawText;
|
|
1898
|
+
throw new HyperliquidApiError(
|
|
1899
|
+
detail ? `Hyperliquid builder approval returned an error: ${detail}` : "Hyperliquid builder approval returned an error.",
|
|
1900
|
+
json
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
return json;
|
|
1904
|
+
}
|
|
1905
|
+
async function getHyperliquidMaxBuilderFee(params) {
|
|
1906
|
+
const response = await fetch(`${API_BASES[params.environment]}/info`, {
|
|
1907
|
+
method: "POST",
|
|
1908
|
+
headers: { "content-type": "application/json" },
|
|
1909
|
+
body: JSON.stringify({
|
|
1910
|
+
type: "maxBuilderFee",
|
|
1911
|
+
user: normalizeAddress(params.user),
|
|
1912
|
+
builder: BUILDER_CODE.address
|
|
1913
|
+
})
|
|
1914
|
+
});
|
|
1915
|
+
const data = await response.json().catch(() => null);
|
|
1916
|
+
if (!response.ok) {
|
|
1917
|
+
throw new HyperliquidApiError(
|
|
1918
|
+
"Failed to query max builder fee.",
|
|
1919
|
+
data ?? { status: response.status }
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
return data;
|
|
1923
|
+
}
|
|
1924
|
+
function createHyperliquidActionHash(params) {
|
|
1925
|
+
return createL1ActionHash(params);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
export { DEFAULT_HYPERLIQUID_MARKET_SLIPPAGE_BPS, HyperliquidApiError, HyperliquidBuilderApprovalError, HyperliquidExchangeClient, HyperliquidGuardError, HyperliquidInfoClient, HyperliquidTermsError, approveHyperliquidBuilderFee, batchModifyHyperliquidOrders, buildHyperliquidMarketIdentity, cancelAllHyperliquidOrders, cancelHyperliquidOrders, cancelHyperliquidOrdersByCloid, cancelHyperliquidTwapOrder, computeHyperliquidMarketIocLimitPrice, createHyperliquidActionHash, createHyperliquidSubAccount, createMonotonicNonceFactory, depositToHyperliquidBridge, fetchHyperliquidAssetCtxs, fetchHyperliquidClearinghouseState, fetchHyperliquidFrontendOpenOrders, fetchHyperliquidHistoricalOrders, fetchHyperliquidMeta, fetchHyperliquidMetaAndAssetCtxs, fetchHyperliquidOpenOrders, fetchHyperliquidOrderStatus, fetchHyperliquidPreTransferCheck, fetchHyperliquidSpotAssetCtxs, fetchHyperliquidSpotClearinghouseState, fetchHyperliquidSpotMeta, fetchHyperliquidSpotMetaAndAssetCtxs, fetchHyperliquidUserFills, fetchHyperliquidUserFillsByTime, fetchHyperliquidUserRateLimit, getHyperliquidMaxBuilderFee, modifyHyperliquidOrder, placeHyperliquidOrder, placeHyperliquidTwapOrder, reserveHyperliquidRequestWeight, resolveHyperliquidAbstractionFromMode, scheduleHyperliquidCancel, sendHyperliquidSpot, setHyperliquidAccountAbstractionMode, setHyperliquidDexAbstraction, setHyperliquidPortfolioMargin, transferHyperliquidSubAccount, updateHyperliquidIsolatedMargin, updateHyperliquidLeverage, withdrawFromHyperliquid };
|
|
1929
|
+
//# sourceMappingURL=browser.js.map
|
|
1930
|
+
//# sourceMappingURL=browser.js.map
|