@nktkas/hyperliquid 0.25.0-beta.1 → 0.25.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -16
- package/esm/bin/cli.d.ts +3 -0
- package/esm/bin/cli.d.ts.map +1 -0
- package/esm/bin/cli.js +452 -0
- package/esm/bin/cli.js.map +1 -0
- package/{script/src/errors.d.ts → esm/src/_errors.d.ts} +1 -1
- package/esm/src/_errors.d.ts.map +1 -0
- package/esm/src/{errors.js → _errors.js} +1 -0
- package/esm/src/_errors.js.map +1 -0
- package/esm/src/clients/exchange.d.ts +4 -11
- package/esm/src/clients/exchange.d.ts.map +1 -1
- package/esm/src/clients/exchange.js +8 -9
- package/esm/src/clients/exchange.js.map +1 -0
- package/esm/src/clients/info.d.ts +86 -2
- package/esm/src/clients/info.d.ts.map +1 -1
- package/esm/src/clients/info.js +103 -1
- package/esm/src/clients/info.js.map +1 -0
- package/esm/src/clients/multiSign.d.ts +1 -1
- package/esm/src/clients/multiSign.d.ts.map +1 -1
- package/esm/src/clients/multiSign.js +3 -2
- package/esm/src/clients/multiSign.js.map +1 -0
- package/esm/src/clients/subscription.js +1 -0
- package/esm/src/clients/subscription.js.map +1 -0
- package/esm/src/mod.d.ts +21 -0
- package/esm/src/mod.d.ts.map +1 -0
- package/esm/src/mod.js +14 -0
- package/esm/src/mod.js.map +1 -0
- package/esm/src/schemas/_base.d.ts +11 -3
- package/esm/src/schemas/_base.d.ts.map +1 -1
- package/esm/src/schemas/_base.js +30 -2
- package/esm/src/schemas/_base.js.map +1 -0
- package/esm/src/schemas/exchange/requests.d.ts +1872 -1872
- package/esm/src/schemas/exchange/requests.d.ts.map +1 -1
- package/esm/src/schemas/exchange/requests.js +277 -281
- package/esm/src/schemas/exchange/requests.js.map +1 -0
- package/esm/src/schemas/exchange/responses.d.ts +51 -51
- package/esm/src/schemas/exchange/responses.js +55 -54
- package/esm/src/schemas/exchange/responses.js.map +1 -0
- package/esm/src/schemas/explorer/requests.d.ts +5 -5
- package/esm/src/schemas/explorer/requests.js +6 -5
- package/esm/src/schemas/explorer/requests.js.map +1 -0
- package/esm/src/schemas/explorer/responses.d.ts +10 -10
- package/esm/src/schemas/explorer/responses.js +12 -11
- package/esm/src/schemas/explorer/responses.js.map +1 -0
- package/esm/src/schemas/info/accounts.d.ts +1042 -296
- package/esm/src/schemas/info/accounts.d.ts.map +1 -1
- package/esm/src/schemas/info/accounts.js +154 -109
- package/esm/src/schemas/info/accounts.js.map +1 -0
- package/esm/src/schemas/info/assets.d.ts +130 -101
- package/esm/src/schemas/info/assets.d.ts.map +1 -1
- package/esm/src/schemas/info/assets.js +69 -49
- package/esm/src/schemas/info/assets.js.map +1 -0
- package/esm/src/schemas/info/markets.d.ts +35 -18
- package/esm/src/schemas/info/markets.d.ts.map +1 -1
- package/esm/src/schemas/info/markets.js +27 -15
- package/esm/src/schemas/info/markets.js.map +1 -0
- package/esm/src/schemas/info/orders.d.ts +151 -151
- package/esm/src/schemas/info/orders.js +37 -36
- package/esm/src/schemas/info/orders.js.map +1 -0
- package/esm/src/schemas/info/requests.d.ts +145 -88
- package/esm/src/schemas/info/requests.d.ts.map +1 -1
- package/esm/src/schemas/info/requests.js +116 -73
- package/esm/src/schemas/info/requests.js.map +1 -0
- package/esm/src/schemas/info/validators.d.ts +60 -39
- package/esm/src/schemas/info/validators.d.ts.map +1 -1
- package/esm/src/schemas/info/validators.js +40 -26
- package/esm/src/schemas/info/validators.js.map +1 -0
- package/esm/src/schemas/info/vaults.d.ts +59 -59
- package/esm/src/schemas/info/vaults.js +20 -19
- package/esm/src/schemas/info/vaults.js.map +1 -0
- package/esm/src/schemas/mod.d.ts +4 -3
- package/esm/src/schemas/mod.d.ts.map +1 -1
- package/esm/src/schemas/mod.js +5 -3
- package/esm/src/schemas/mod.js.map +1 -0
- package/esm/src/schemas/subscriptions/requests.d.ts +23 -23
- package/esm/src/schemas/subscriptions/requests.d.ts.map +1 -1
- package/esm/src/schemas/subscriptions/requests.js +25 -24
- package/esm/src/schemas/subscriptions/requests.js.map +1 -0
- package/esm/src/schemas/subscriptions/responses.d.ts +547 -547
- package/esm/src/schemas/subscriptions/responses.js +41 -40
- package/esm/src/schemas/subscriptions/responses.js.map +1 -0
- package/esm/src/signing/mod.d.ts +1 -1
- package/esm/src/signing/mod.d.ts.map +1 -1
- package/esm/src/signing/mod.js +3 -2
- package/esm/src/signing/mod.js.map +1 -0
- package/esm/src/signing/signTypedData/ethers.d.ts.map +1 -0
- package/esm/src/signing/{_signTypedData → signTypedData}/ethers.js +1 -0
- package/esm/src/signing/signTypedData/ethers.js.map +1 -0
- package/esm/src/signing/signTypedData/mod.d.ts.map +1 -0
- package/esm/src/signing/{_signTypedData → signTypedData}/mod.js +1 -0
- package/esm/src/signing/signTypedData/mod.js.map +1 -0
- package/esm/src/signing/signTypedData/private_key.d.ts.map +1 -0
- package/esm/src/signing/{_signTypedData → signTypedData}/private_key.js +10 -8
- package/esm/src/signing/signTypedData/private_key.js.map +1 -0
- package/esm/src/signing/signTypedData/viem.d.ts.map +1 -0
- package/esm/src/signing/{_signTypedData → signTypedData}/viem.js +1 -0
- package/esm/src/signing/signTypedData/viem.js.map +1 -0
- package/esm/src/transports/base.d.ts +4 -4
- package/esm/src/transports/base.d.ts.map +1 -1
- package/esm/src/transports/base.js +3 -2
- package/esm/src/transports/base.js.map +1 -0
- package/esm/src/transports/http/http_transport.d.ts +1 -1
- package/esm/src/transports/http/http_transport.js +1 -0
- package/esm/src/transports/http/http_transport.js.map +1 -0
- package/esm/src/transports/websocket/_hyperliquid_event_target.js +1 -0
- package/esm/src/transports/websocket/_hyperliquid_event_target.js.map +1 -0
- package/esm/src/transports/websocket/_reconnecting_websocket.js +1 -0
- package/esm/src/transports/websocket/_reconnecting_websocket.js.map +1 -0
- package/esm/src/transports/websocket/_websocket_async_request.js +1 -0
- package/esm/src/transports/websocket/_websocket_async_request.js.map +1 -0
- package/esm/src/transports/websocket/websocket_transport.d.ts +7 -0
- package/esm/src/transports/websocket/websocket_transport.d.ts.map +1 -1
- package/esm/src/transports/websocket/websocket_transport.js +4 -0
- package/esm/src/transports/websocket/websocket_transport.js.map +1 -0
- package/package.json +15 -7
- package/script/bin/cli.d.ts +3 -0
- package/script/bin/cli.d.ts.map +1 -0
- package/script/bin/cli.js +490 -0
- package/script/bin/cli.js.map +1 -0
- package/{esm/src/errors.d.ts → script/src/_errors.d.ts} +1 -1
- package/script/src/_errors.d.ts.map +1 -0
- package/script/src/{errors.js → _errors.js} +1 -0
- package/script/src/_errors.js.map +1 -0
- package/script/src/clients/exchange.d.ts +4 -11
- package/script/src/clients/exchange.d.ts.map +1 -1
- package/script/src/clients/exchange.js +9 -10
- package/script/src/clients/exchange.js.map +1 -0
- package/script/src/clients/info.d.ts +86 -2
- package/script/src/clients/info.d.ts.map +1 -1
- package/script/src/clients/info.js +102 -0
- package/script/src/clients/info.js.map +1 -0
- package/script/src/clients/multiSign.d.ts +1 -1
- package/script/src/clients/multiSign.d.ts.map +1 -1
- package/script/src/clients/multiSign.js +3 -2
- package/script/src/clients/multiSign.js.map +1 -0
- package/script/src/clients/subscription.js +1 -0
- package/script/src/clients/subscription.js.map +1 -0
- package/script/src/mod.d.ts +21 -0
- package/script/src/mod.d.ts.map +1 -0
- package/script/{mod.js → src/mod.js} +13 -8
- package/script/src/mod.js.map +1 -0
- package/script/src/schemas/_base.d.ts +11 -3
- package/script/src/schemas/_base.d.ts.map +1 -1
- package/script/src/schemas/_base.js +31 -3
- package/script/src/schemas/_base.js.map +1 -0
- package/script/src/schemas/exchange/requests.d.ts +1872 -1872
- package/script/src/schemas/exchange/requests.d.ts.map +1 -1
- package/script/src/schemas/exchange/requests.js +276 -280
- package/script/src/schemas/exchange/requests.js.map +1 -0
- package/script/src/schemas/exchange/responses.d.ts +51 -51
- package/script/src/schemas/exchange/responses.js +54 -53
- package/script/src/schemas/exchange/responses.js.map +1 -0
- package/script/src/schemas/explorer/requests.d.ts +5 -5
- package/script/src/schemas/explorer/requests.js +5 -4
- package/script/src/schemas/explorer/requests.js.map +1 -0
- package/script/src/schemas/explorer/responses.d.ts +10 -10
- package/script/src/schemas/explorer/responses.js +11 -10
- package/script/src/schemas/explorer/responses.js.map +1 -0
- package/script/src/schemas/info/accounts.d.ts +1042 -296
- package/script/src/schemas/info/accounts.d.ts.map +1 -1
- package/script/src/schemas/info/accounts.js +154 -109
- package/script/src/schemas/info/accounts.js.map +1 -0
- package/script/src/schemas/info/assets.d.ts +130 -101
- package/script/src/schemas/info/assets.d.ts.map +1 -1
- package/script/src/schemas/info/assets.js +69 -49
- package/script/src/schemas/info/assets.js.map +1 -0
- package/script/src/schemas/info/markets.d.ts +35 -18
- package/script/src/schemas/info/markets.d.ts.map +1 -1
- package/script/src/schemas/info/markets.js +27 -15
- package/script/src/schemas/info/markets.js.map +1 -0
- package/script/src/schemas/info/orders.d.ts +151 -151
- package/script/src/schemas/info/orders.js +36 -35
- package/script/src/schemas/info/orders.js.map +1 -0
- package/script/src/schemas/info/requests.d.ts +145 -88
- package/script/src/schemas/info/requests.d.ts.map +1 -1
- package/script/src/schemas/info/requests.js +116 -73
- package/script/src/schemas/info/requests.js.map +1 -0
- package/script/src/schemas/info/validators.d.ts +60 -39
- package/script/src/schemas/info/validators.d.ts.map +1 -1
- package/script/src/schemas/info/validators.js +40 -26
- package/script/src/schemas/info/validators.js.map +1 -0
- package/script/src/schemas/info/vaults.d.ts +59 -59
- package/script/src/schemas/info/vaults.js +19 -18
- package/script/src/schemas/info/vaults.js.map +1 -0
- package/script/src/schemas/mod.d.ts +4 -3
- package/script/src/schemas/mod.d.ts.map +1 -1
- package/script/src/schemas/mod.js +8 -5
- package/script/src/schemas/mod.js.map +1 -0
- package/script/src/schemas/subscriptions/requests.d.ts +23 -23
- package/script/src/schemas/subscriptions/requests.d.ts.map +1 -1
- package/script/src/schemas/subscriptions/requests.js +24 -23
- package/script/src/schemas/subscriptions/requests.js.map +1 -0
- package/script/src/schemas/subscriptions/responses.d.ts +547 -547
- package/script/src/schemas/subscriptions/responses.js +40 -39
- package/script/src/schemas/subscriptions/responses.js.map +1 -0
- package/script/src/signing/mod.d.ts +1 -1
- package/script/src/signing/mod.d.ts.map +1 -1
- package/script/src/signing/mod.js +4 -3
- package/script/src/signing/mod.js.map +1 -0
- package/script/src/signing/signTypedData/ethers.d.ts.map +1 -0
- package/script/src/signing/{_signTypedData → signTypedData}/ethers.js +1 -0
- package/script/src/signing/signTypedData/ethers.js.map +1 -0
- package/script/src/signing/signTypedData/mod.d.ts.map +1 -0
- package/script/src/signing/{_signTypedData → signTypedData}/mod.js +1 -0
- package/script/src/signing/signTypedData/mod.js.map +1 -0
- package/script/src/signing/signTypedData/private_key.d.ts.map +1 -0
- package/script/src/signing/{_signTypedData → signTypedData}/private_key.js +17 -15
- package/script/src/signing/signTypedData/private_key.js.map +1 -0
- package/script/src/signing/signTypedData/viem.d.ts.map +1 -0
- package/script/src/signing/{_signTypedData → signTypedData}/viem.js +1 -0
- package/script/src/signing/signTypedData/viem.js.map +1 -0
- package/script/src/transports/base.d.ts +4 -4
- package/script/src/transports/base.d.ts.map +1 -1
- package/script/src/transports/base.js +4 -3
- package/script/src/transports/base.js.map +1 -0
- package/script/src/transports/http/http_transport.d.ts +1 -1
- package/script/src/transports/http/http_transport.js +1 -0
- package/script/src/transports/http/http_transport.js.map +1 -0
- package/script/src/transports/websocket/_hyperliquid_event_target.js +1 -0
- package/script/src/transports/websocket/_hyperliquid_event_target.js.map +1 -0
- package/script/src/transports/websocket/_reconnecting_websocket.js +1 -0
- package/script/src/transports/websocket/_reconnecting_websocket.js.map +1 -0
- package/script/src/transports/websocket/_websocket_async_request.js +1 -0
- package/script/src/transports/websocket/_websocket_async_request.js.map +1 -0
- package/script/src/transports/websocket/websocket_transport.d.ts +7 -0
- package/script/src/transports/websocket/websocket_transport.d.ts.map +1 -1
- package/script/src/transports/websocket/websocket_transport.js +4 -0
- package/script/src/transports/websocket/websocket_transport.js.map +1 -0
- package/src/bin/cli.ts +481 -0
- package/src/src/_errors.ts +7 -0
- package/src/src/clients/exchange.ts +2246 -0
- package/src/src/clients/info.ts +2110 -0
- package/src/src/clients/multiSign.ts +183 -0
- package/src/src/clients/subscription.ts +841 -0
- package/src/src/mod.ts +29 -0
- package/src/src/schemas/_base.ts +82 -0
- package/src/src/schemas/exchange/requests.ts +3052 -0
- package/src/src/schemas/exchange/responses.ts +540 -0
- package/src/src/schemas/explorer/requests.ts +65 -0
- package/src/src/schemas/explorer/responses.ts +138 -0
- package/src/src/schemas/info/accounts.ts +1598 -0
- package/src/src/schemas/info/assets.ts +693 -0
- package/src/src/schemas/info/markets.ts +171 -0
- package/src/src/schemas/info/orders.ts +597 -0
- package/src/src/schemas/info/requests.ts +1401 -0
- package/src/src/schemas/info/validators.ts +297 -0
- package/src/src/schemas/info/vaults.ts +262 -0
- package/src/src/schemas/mod.ts +121 -0
- package/src/src/schemas/subscriptions/requests.ts +514 -0
- package/src/src/schemas/subscriptions/responses.ts +576 -0
- package/src/src/signing/mod.ts +572 -0
- package/src/src/signing/signTypedData/ethers.ts +59 -0
- package/src/src/signing/signTypedData/mod.ts +121 -0
- package/src/src/signing/signTypedData/private_key.ts +234 -0
- package/src/src/signing/signTypedData/viem.ts +55 -0
- package/src/src/transports/base.ts +54 -0
- package/src/src/transports/http/http_transport.ts +208 -0
- package/src/src/transports/websocket/_hyperliquid_event_target.ts +118 -0
- package/src/src/transports/websocket/_reconnecting_websocket.ts +404 -0
- package/src/src/transports/websocket/_websocket_async_request.ts +229 -0
- package/src/src/transports/websocket/websocket_transport.ts +394 -0
- package/esm/mod.d.ts +0 -20
- package/esm/mod.d.ts.map +0 -1
- package/esm/mod.js +0 -11
- package/esm/src/errors.d.ts.map +0 -1
- package/esm/src/signing/_signTypedData/ethers.d.ts.map +0 -1
- package/esm/src/signing/_signTypedData/mod.d.ts.map +0 -1
- package/esm/src/signing/_signTypedData/private_key.d.ts.map +0 -1
- package/esm/src/signing/_signTypedData/viem.d.ts.map +0 -1
- package/script/mod.d.ts +0 -20
- package/script/mod.d.ts.map +0 -1
- package/script/src/errors.d.ts.map +0 -1
- package/script/src/signing/_signTypedData/ethers.d.ts.map +0 -1
- package/script/src/signing/_signTypedData/mod.d.ts.map +0 -1
- package/script/src/signing/_signTypedData/private_key.d.ts.map +0 -1
- package/script/src/signing/_signTypedData/viem.d.ts.map +0 -1
- /package/esm/src/signing/{_signTypedData → signTypedData}/ethers.d.ts +0 -0
- /package/esm/src/signing/{_signTypedData → signTypedData}/mod.d.ts +0 -0
- /package/esm/src/signing/{_signTypedData → signTypedData}/private_key.d.ts +0 -0
- /package/esm/src/signing/{_signTypedData → signTypedData}/viem.d.ts +0 -0
- /package/script/src/signing/{_signTypedData → signTypedData}/ethers.d.ts +0 -0
- /package/script/src/signing/{_signTypedData → signTypedData}/mod.d.ts +0 -0
- /package/script/src/signing/{_signTypedData → signTypedData}/private_key.d.ts +0 -0
- /package/script/src/signing/{_signTypedData → signTypedData}/viem.d.ts +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AbstractEthersV5Signer,
|
|
3
|
+
type AbstractEthersV6Signer,
|
|
4
|
+
isAbstractEthersV5Signer,
|
|
5
|
+
isAbstractEthersV6Signer,
|
|
6
|
+
} from "./ethers.js";
|
|
7
|
+
import { isValidPrivateKey, privateKeyToAddress, signTypedData as signTypedDataWithPrivateKey } from "./private_key.js";
|
|
8
|
+
import { type AbstractViemJsonRpcAccount, type AbstractViemLocalAccount, isAbstractViemWallet } from "./viem.js";
|
|
9
|
+
|
|
10
|
+
/** Abstract interface for a wallet that can sign typed data. */
|
|
11
|
+
export type AbstractWallet =
|
|
12
|
+
| `0x${string}` // Private key
|
|
13
|
+
| AbstractViemJsonRpcAccount
|
|
14
|
+
| AbstractViemLocalAccount
|
|
15
|
+
| AbstractEthersV6Signer
|
|
16
|
+
| AbstractEthersV5Signer;
|
|
17
|
+
|
|
18
|
+
/** ECDSA signature components for Ethereum transactions and typed data. */
|
|
19
|
+
export interface Signature {
|
|
20
|
+
/** First 32-byte component of ECDSA signature */
|
|
21
|
+
r: `0x${string}`;
|
|
22
|
+
/** Second 32-byte component of ECDSA signature */
|
|
23
|
+
s: `0x${string}`;
|
|
24
|
+
/** Recovery identifier */
|
|
25
|
+
v: 27 | 28;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function signTypedData(args: {
|
|
29
|
+
wallet: AbstractWallet;
|
|
30
|
+
domain: {
|
|
31
|
+
name: string;
|
|
32
|
+
version: string;
|
|
33
|
+
chainId: number;
|
|
34
|
+
verifyingContract: `0x${string}`;
|
|
35
|
+
};
|
|
36
|
+
types: {
|
|
37
|
+
[key: string]: {
|
|
38
|
+
name: string;
|
|
39
|
+
type: string;
|
|
40
|
+
}[];
|
|
41
|
+
};
|
|
42
|
+
primaryType: string;
|
|
43
|
+
message: Record<string, unknown>;
|
|
44
|
+
}): Promise<Signature> {
|
|
45
|
+
const { wallet, domain, types, primaryType, message } = args;
|
|
46
|
+
|
|
47
|
+
let signature: `0x${string}`;
|
|
48
|
+
if (isAbstractViemWallet(wallet)) {
|
|
49
|
+
signature = await wallet.signTypedData({
|
|
50
|
+
domain,
|
|
51
|
+
types: {
|
|
52
|
+
EIP712Domain: [
|
|
53
|
+
{ name: "name", type: "string" },
|
|
54
|
+
{ name: "version", type: "string" },
|
|
55
|
+
{ name: "chainId", type: "uint256" },
|
|
56
|
+
{ name: "verifyingContract", type: "address" },
|
|
57
|
+
],
|
|
58
|
+
...types,
|
|
59
|
+
},
|
|
60
|
+
primaryType,
|
|
61
|
+
message,
|
|
62
|
+
});
|
|
63
|
+
} else if (isAbstractEthersV6Signer(wallet)) {
|
|
64
|
+
signature = await wallet.signTypedData(domain, types, message) as `0x${string}`;
|
|
65
|
+
} else if (isAbstractEthersV5Signer(wallet)) {
|
|
66
|
+
signature = await wallet._signTypedData(domain, types, message) as `0x${string}`;
|
|
67
|
+
} else if (isValidPrivateKey(wallet)) {
|
|
68
|
+
signature = await signTypedDataWithPrivateKey({ privateKey: wallet, domain, types, primaryType, message });
|
|
69
|
+
} else {
|
|
70
|
+
throw new Error("Unsupported wallet for signing typed data");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return splitSignature(signature);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function splitSignature(signature: `0x${string}`): Signature {
|
|
77
|
+
const r = `0x${signature.slice(2, 66)}` as const;
|
|
78
|
+
const s = `0x${signature.slice(66, 130)}` as const;
|
|
79
|
+
const v = parseInt(signature.slice(130, 132), 16) as 27 | 28;
|
|
80
|
+
return { r, s, v };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Get the chain ID of the wallet. */
|
|
84
|
+
export async function getWalletChainId(wallet: AbstractWallet): Promise<`0x${string}`> {
|
|
85
|
+
if (isAbstractViemWallet(wallet)) {
|
|
86
|
+
if ("getChainId" in wallet && wallet.getChainId) {
|
|
87
|
+
const chainId = await wallet.getChainId();
|
|
88
|
+
return `0x${chainId.toString(16)}`;
|
|
89
|
+
} else {
|
|
90
|
+
return "0x1";
|
|
91
|
+
}
|
|
92
|
+
} else if (isAbstractEthersV6Signer(wallet) || isAbstractEthersV5Signer(wallet)) {
|
|
93
|
+
if ("provider" in wallet && wallet.provider) {
|
|
94
|
+
const network = await wallet.provider.getNetwork();
|
|
95
|
+
return `0x${network.chainId.toString(16)}`;
|
|
96
|
+
} else {
|
|
97
|
+
return "0x1";
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
return "0x1";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Get the wallet address from various wallet types. */
|
|
105
|
+
export async function getWalletAddress(wallet: AbstractWallet): Promise<`0x${string}`> {
|
|
106
|
+
if (isAbstractViemWallet(wallet)) {
|
|
107
|
+
if ("address" in wallet && wallet.address) {
|
|
108
|
+
return wallet.address;
|
|
109
|
+
} else if ("getAddresses" in wallet && wallet.getAddresses) {
|
|
110
|
+
const addresses = await wallet.getAddresses();
|
|
111
|
+
return addresses[0];
|
|
112
|
+
}
|
|
113
|
+
} else if (isAbstractEthersV6Signer(wallet) || isAbstractEthersV5Signer(wallet)) {
|
|
114
|
+
if ("getAddress" in wallet && wallet.getAddress) {
|
|
115
|
+
return await wallet.getAddress() as `0x${string}`;
|
|
116
|
+
}
|
|
117
|
+
} else if (isValidPrivateKey(wallet)) {
|
|
118
|
+
return privateKeyToAddress(wallet);
|
|
119
|
+
}
|
|
120
|
+
throw new Error("Unsupported wallet for getting address");
|
|
121
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
2
|
+
import { etc, getPublicKey, signAsync, utils } from "@noble/secp256k1";
|
|
3
|
+
|
|
4
|
+
interface Types {
|
|
5
|
+
[type: string]: {
|
|
6
|
+
name: string;
|
|
7
|
+
type: string;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Domain extends Record<string, unknown> {
|
|
12
|
+
name?: string;
|
|
13
|
+
version?: string;
|
|
14
|
+
chainId?: number | string | bigint | `0x${string}`;
|
|
15
|
+
verifyingContract?: `0x${string}`;
|
|
16
|
+
salt?: `0x${string}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Signs typed data with a private key. */
|
|
20
|
+
export async function signTypedData(args: {
|
|
21
|
+
privateKey: string;
|
|
22
|
+
domain?: Domain;
|
|
23
|
+
types: Types;
|
|
24
|
+
primaryType: string;
|
|
25
|
+
message: Record<string, unknown>;
|
|
26
|
+
}): Promise<`0x${string}`> {
|
|
27
|
+
const {
|
|
28
|
+
privateKey,
|
|
29
|
+
domain = {},
|
|
30
|
+
types,
|
|
31
|
+
primaryType,
|
|
32
|
+
message,
|
|
33
|
+
} = args;
|
|
34
|
+
|
|
35
|
+
const hash = hashTypedData({ domain, types, primaryType, message });
|
|
36
|
+
|
|
37
|
+
const signature = await signAsync(
|
|
38
|
+
hash,
|
|
39
|
+
etc.hexToBytes(cleanHex(privateKey)),
|
|
40
|
+
{ prehash: false, format: "recovered" },
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const r = etc.bytesToHex(signature.slice(1, 33));
|
|
44
|
+
const s = etc.bytesToHex(signature.slice(33, 65));
|
|
45
|
+
const v = (signature[0] + 27).toString(16).padStart(2, "0");
|
|
46
|
+
|
|
47
|
+
return `0x${r}${s}${v}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hashTypedData(args: {
|
|
51
|
+
domain: Domain;
|
|
52
|
+
types: Types;
|
|
53
|
+
primaryType: string;
|
|
54
|
+
message: Record<string, unknown>;
|
|
55
|
+
}): Uint8Array {
|
|
56
|
+
const { domain, types: types_, primaryType, message } = args;
|
|
57
|
+
|
|
58
|
+
const domainFields = [];
|
|
59
|
+
if (domain.name !== undefined) {
|
|
60
|
+
domainFields.push({ name: "name", type: "string" });
|
|
61
|
+
}
|
|
62
|
+
if (domain.version !== undefined) {
|
|
63
|
+
domainFields.push({ name: "version", type: "string" });
|
|
64
|
+
}
|
|
65
|
+
if (domain.chainId !== undefined) {
|
|
66
|
+
domainFields.push({ name: "chainId", type: "uint256" });
|
|
67
|
+
}
|
|
68
|
+
if (domain.verifyingContract !== undefined) {
|
|
69
|
+
domainFields.push({ name: "verifyingContract", type: "address" });
|
|
70
|
+
}
|
|
71
|
+
if (domain.salt !== undefined) {
|
|
72
|
+
domainFields.push({ name: "salt", type: "bytes32" });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const types = {
|
|
76
|
+
EIP712Domain: domainFields,
|
|
77
|
+
...types_,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const bytes: Uint8Array[] = [new Uint8Array([0x19, 0x01])];
|
|
81
|
+
bytes.push(hashStruct("EIP712Domain", domain, types));
|
|
82
|
+
if (primaryType !== "EIP712Domain") bytes.push(hashStruct(primaryType, message, types));
|
|
83
|
+
|
|
84
|
+
return keccak_256(etc.concatBytes(...bytes));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function hashStruct(primaryType: string, data: Record<string, unknown>, types: Types): Uint8Array {
|
|
88
|
+
const typeHash = keccak_256(new TextEncoder().encode(encodeType(primaryType, types)));
|
|
89
|
+
const encodedValues = types[primaryType].map((field) => encodeValue(field.type, data[field.name], types));
|
|
90
|
+
return keccak_256(etc.concatBytes(typeHash, ...encodedValues));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function encodeType(primaryType: string, types: Types): string {
|
|
94
|
+
const deps = findTypeDependencies(primaryType, types);
|
|
95
|
+
const sortedDeps = [primaryType, ...deps.filter((d) => d !== primaryType).sort()];
|
|
96
|
+
return sortedDeps
|
|
97
|
+
.map((type) =>
|
|
98
|
+
`${type}(${types[type].map((field) => `${resolveTypeAlias(field.type)} ${field.name}`).join(",")})`
|
|
99
|
+
)
|
|
100
|
+
.join("");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resolveTypeAlias(type: string): string {
|
|
104
|
+
if (type === "uint") return "uint256";
|
|
105
|
+
if (type === "int") return "int256";
|
|
106
|
+
return type;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function findTypeDependencies(primaryType: string, types: Types, _found = new Set<string>()): string[] {
|
|
110
|
+
if (_found.has(primaryType) || !types[primaryType]) return [];
|
|
111
|
+
_found.add(primaryType);
|
|
112
|
+
|
|
113
|
+
for (const field of types[primaryType]) {
|
|
114
|
+
const baseType = field.type.replace(/\[.*?\]/g, "");
|
|
115
|
+
if (types[baseType]) {
|
|
116
|
+
findTypeDependencies(baseType, types, _found);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return Array.from(_found);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function encodeValue(type: string, value: unknown, types: Types): Uint8Array {
|
|
123
|
+
const arrayMatch = type.match(/^(.*)\[(\d*)\]$/);
|
|
124
|
+
if (arrayMatch) {
|
|
125
|
+
// Extract type info: base type and optional length
|
|
126
|
+
const [, baseType, len] = arrayMatch;
|
|
127
|
+
if (!Array.isArray(value)) {
|
|
128
|
+
throw new Error(`Expected array for ${type}. Received: ${typeof value}`);
|
|
129
|
+
}
|
|
130
|
+
if (len && value.length !== +len) {
|
|
131
|
+
throw new Error(`Invalid length for ${type}: expected ${len}. Received: ${value.length}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Encode each element in the array and hash them together
|
|
135
|
+
const encodedElements = value.map((v) => encodeValue(baseType, v, types));
|
|
136
|
+
return keccak_256(etc.concatBytes(...encodedElements));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (types[type]) {
|
|
140
|
+
if (value === undefined) return new Uint8Array(32);
|
|
141
|
+
return hashStruct(type, value as Record<string, unknown>, types);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (type === "string") {
|
|
145
|
+
return keccak_256(new TextEncoder().encode(value as string));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (type === "address") {
|
|
149
|
+
const bytes = etc.hexToBytes(cleanHex(value as string));
|
|
150
|
+
if (bytes.length !== 20) {
|
|
151
|
+
throw new Error(`Address must be 20 bytes.`);
|
|
152
|
+
}
|
|
153
|
+
const padded = new Uint8Array(32);
|
|
154
|
+
padded.set(bytes, 12);
|
|
155
|
+
return padded;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (type.startsWith("uint") || type.startsWith("int")) {
|
|
159
|
+
// Extract type info: uint/int and bit size
|
|
160
|
+
const isUint = type.startsWith("uint");
|
|
161
|
+
const bitsStr = type.slice(isUint ? 4 : 3);
|
|
162
|
+
const bits = parseInt(bitsStr || "256");
|
|
163
|
+
if (bits > 256 || bits % 8 !== 0) {
|
|
164
|
+
throw new Error(`Invalid ${isUint ? "uint" : "int"} size: ${bitsStr}. Must be 8-256 in steps of 8`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Apply Two's complement for specified bit size
|
|
168
|
+
const bigIntValue = BigInt(value as number | string | bigint | `0x${string}`);
|
|
169
|
+
const resizedValue = isUint ? BigInt.asUintN(bits, bigIntValue) : BigInt.asIntN(bits, bigIntValue);
|
|
170
|
+
|
|
171
|
+
// Convert to 32-byte big-endian
|
|
172
|
+
const hex = BigInt.asUintN(256, resizedValue).toString(16).padStart(64, "0");
|
|
173
|
+
return etc.hexToBytes(hex);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (type === "bool") {
|
|
177
|
+
const result = new Uint8Array(32);
|
|
178
|
+
result[31] = value ? 1 : 0;
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (type === "bytes") {
|
|
183
|
+
const bytes = typeof value === "string" ? etc.hexToBytes(cleanHex(value)) : value as Uint8Array;
|
|
184
|
+
return keccak_256(bytes);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const bytesMatch = type.match(/^bytes(\d+)$/);
|
|
188
|
+
if (bytesMatch) {
|
|
189
|
+
// Extract type info: bytes size
|
|
190
|
+
const size = parseInt(bytesMatch[1]);
|
|
191
|
+
if (size === 0 || size > 32) {
|
|
192
|
+
throw new Error(`bytesN size must be 1-32. Received: ${size}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Convert hex to bytes
|
|
196
|
+
const bytes = etc.hexToBytes(cleanHex(value as string));
|
|
197
|
+
if (bytes.length !== size) {
|
|
198
|
+
throw new Error(`${type} requires exactly ${size} bytes. Received: ${bytes.length} from '${value}'`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Pad to 32 bytes
|
|
202
|
+
const padded = new Uint8Array(32);
|
|
203
|
+
padded.set(bytes, 0);
|
|
204
|
+
return padded;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
throw new Error(`Unsupported type: '${type}'.`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function cleanHex(hex: string): string {
|
|
211
|
+
return hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Validates if a string is a valid secp256k1 private key. */
|
|
215
|
+
export function isValidPrivateKey(privateKey: unknown): privateKey is string {
|
|
216
|
+
if (typeof privateKey !== "string") return false;
|
|
217
|
+
const privateKeyBytes = etc.hexToBytes(cleanHex(privateKey));
|
|
218
|
+
return utils.isValidSecretKey(privateKeyBytes);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Converts a private key to an Ethereum address. */
|
|
222
|
+
export function privateKeyToAddress(privateKey: string): `0x${string}` {
|
|
223
|
+
const privateKeyBytes = etc.hexToBytes(cleanHex(privateKey));
|
|
224
|
+
|
|
225
|
+
const publicKey = getPublicKey(privateKeyBytes, false);
|
|
226
|
+
const publicKeyWithoutPrefix = publicKey.slice(1);
|
|
227
|
+
|
|
228
|
+
const hash = keccak_256(publicKeyWithoutPrefix);
|
|
229
|
+
|
|
230
|
+
const addressBytes = hash.slice(-20);
|
|
231
|
+
const address = etc.bytesToHex(addressBytes);
|
|
232
|
+
|
|
233
|
+
return `0x${address}`;
|
|
234
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Abstract interface for a viem {@link https://viem.sh/docs/accounts/jsonRpc#json-rpc-account | JSON-RPC Account}. */
|
|
2
|
+
export interface AbstractViemJsonRpcAccount {
|
|
3
|
+
signTypedData(
|
|
4
|
+
params: {
|
|
5
|
+
domain: {
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
chainId: number;
|
|
9
|
+
verifyingContract: `0x${string}`;
|
|
10
|
+
};
|
|
11
|
+
types: {
|
|
12
|
+
[key: string]: {
|
|
13
|
+
name: string;
|
|
14
|
+
type: string;
|
|
15
|
+
}[];
|
|
16
|
+
};
|
|
17
|
+
primaryType: string;
|
|
18
|
+
message: Record<string, unknown>;
|
|
19
|
+
},
|
|
20
|
+
options?: unknown,
|
|
21
|
+
): Promise<`0x${string}`>;
|
|
22
|
+
getAddresses?(): Promise<`0x${string}`[]>;
|
|
23
|
+
getChainId?(): Promise<number>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Abstract interface for a viem {@link https://viem.sh/docs/accounts/local | Local Account}. */
|
|
27
|
+
export interface AbstractViemLocalAccount {
|
|
28
|
+
signTypedData(
|
|
29
|
+
params: {
|
|
30
|
+
domain: {
|
|
31
|
+
name: string;
|
|
32
|
+
version: string;
|
|
33
|
+
chainId: number;
|
|
34
|
+
verifyingContract: `0x${string}`;
|
|
35
|
+
};
|
|
36
|
+
types: {
|
|
37
|
+
[key: string]: {
|
|
38
|
+
name: string;
|
|
39
|
+
type: string;
|
|
40
|
+
}[];
|
|
41
|
+
};
|
|
42
|
+
primaryType: string;
|
|
43
|
+
message: Record<string, unknown>;
|
|
44
|
+
},
|
|
45
|
+
options?: unknown,
|
|
46
|
+
): Promise<`0x${string}`>;
|
|
47
|
+
address?: `0x${string}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Checks if the given value is an abstract viem wallet. */
|
|
51
|
+
export function isAbstractViemWallet(client: unknown): client is AbstractViemJsonRpcAccount | AbstractViemLocalAccount {
|
|
52
|
+
return typeof client === "object" && client !== null &&
|
|
53
|
+
"signTypedData" in client && typeof client.signTypedData === "function" &&
|
|
54
|
+
(client.signTypedData.length === 1 || client.signTypedData.length === 2);
|
|
55
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { HyperliquidError } from "../_errors.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface representing a REST transport.
|
|
5
|
+
* Handles communication with Hyperliquid API endpoints.
|
|
6
|
+
* @see {@link https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint | Info endpoint}
|
|
7
|
+
* @see {@link https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint | Exchange endpoint}
|
|
8
|
+
* @see {@link https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/post-requests | Websocket post requests}
|
|
9
|
+
*/
|
|
10
|
+
export interface IRequestTransport extends Partial<AsyncDisposable> {
|
|
11
|
+
/** Indicates this transport uses testnet endpoint(s). */
|
|
12
|
+
isTestnet: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Sends a request to the Hyperliquid API.
|
|
15
|
+
* @param endpoint - The API endpoint to send the request to.
|
|
16
|
+
* @param payload - The payload to send with the request.
|
|
17
|
+
* @param signal - An {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | AbortSignal}. If this option is set, the request can be canceled by calling {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort | abort()} on the corresponding {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/AbortController | AbortController}.
|
|
18
|
+
* @returns A promise that resolves with parsed JSON response body.
|
|
19
|
+
*/
|
|
20
|
+
request<T>(endpoint: "info" | "exchange", payload: unknown, signal?: AbortSignal): Promise<T>;
|
|
21
|
+
request<T>(endpoint: "info" | "exchange" | "explorer", payload: unknown, signal?: AbortSignal): Promise<T>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Interface representing an event subscription transport.
|
|
26
|
+
* Handles WebSocket subscriptions for real-time updates.
|
|
27
|
+
* @see {@link https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions | Websocket subscriptions}
|
|
28
|
+
*/
|
|
29
|
+
export interface ISubscriptionTransport extends Partial<AsyncDisposable> {
|
|
30
|
+
/**
|
|
31
|
+
* Subscribes to a Hyperliquid event channel.
|
|
32
|
+
* @param channel - The event channel to listen to.
|
|
33
|
+
* @param payload - The payload to send with the subscription request.
|
|
34
|
+
* @param listener - The function to call when the event is dispatched.
|
|
35
|
+
* @returns A promise that resolves with a {@link Subscription} object to manage the subscription lifecycle.
|
|
36
|
+
*/
|
|
37
|
+
subscribe<T>(channel: string, payload: unknown, listener: (data: CustomEvent<T>) => void): Promise<Subscription>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Controls event subscription lifecycle. */
|
|
41
|
+
export interface Subscription {
|
|
42
|
+
/** Unsubscribes from the event and sends an unsubscribe request to the server. */
|
|
43
|
+
unsubscribe(): Promise<void>;
|
|
44
|
+
/** Signal that aborts when resubscription fails during reconnection. */
|
|
45
|
+
resubscribeSignal?: AbortSignal;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Thrown when a transport layer error occurs. */
|
|
49
|
+
export class TransportError extends HyperliquidError {
|
|
50
|
+
constructor(message?: string, options?: ErrorOptions) {
|
|
51
|
+
super(message, options);
|
|
52
|
+
this.name = "TransportError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { TransportError } from "../base.js";
|
|
2
|
+
import type { IRequestTransport } from "../base.js";
|
|
3
|
+
|
|
4
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
5
|
+
|
|
6
|
+
/** Error thrown when an HTTP request fails. */
|
|
7
|
+
export class HttpRequestError extends TransportError {
|
|
8
|
+
response?: Response;
|
|
9
|
+
body?: string;
|
|
10
|
+
|
|
11
|
+
constructor(args?: { response?: Response; body?: string }, options?: ErrorOptions) {
|
|
12
|
+
const { response, body } = args ?? {};
|
|
13
|
+
|
|
14
|
+
let message: string;
|
|
15
|
+
if (response) {
|
|
16
|
+
message = `${response.status} ${response.statusText}`.trim();
|
|
17
|
+
if (body) message += ` - ${body}`;
|
|
18
|
+
} else {
|
|
19
|
+
message = `Unknown error while making an HTTP request: ${options?.cause}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
super(message, options);
|
|
23
|
+
this.name = "HttpRequestError";
|
|
24
|
+
this.response = response;
|
|
25
|
+
this.body = body;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Configuration options for the HTTP transport layer. */
|
|
30
|
+
export interface HttpTransportOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Specifies whether to use the testnet API endpoints from the {@linkcode server} property.
|
|
33
|
+
* @defaultValue `false`
|
|
34
|
+
*/
|
|
35
|
+
isTestnet?: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Request timeout in ms. Set to `null` to disable.
|
|
39
|
+
* @defaultValue `10_000`
|
|
40
|
+
*/
|
|
41
|
+
timeout?: number | null;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Custom server to use for API requests.
|
|
45
|
+
* @defaultValue `https://api.hyperliquid.xyz` for mainnet and `https://api.hyperliquid-testnet.xyz` for testnet.
|
|
46
|
+
*/
|
|
47
|
+
server?: {
|
|
48
|
+
mainnet?: { api?: string | URL; rpc?: string | URL };
|
|
49
|
+
testnet?: { api?: string | URL; rpc?: string | URL };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** A custom {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/RequestInit | RequestInit} that is merged with a fetch request. */
|
|
53
|
+
fetchOptions?: Omit<RequestInit, "body" | "method">;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A callback function that is called before the request is sent.
|
|
57
|
+
* @param request - An original request to send.
|
|
58
|
+
* @returns If returned a {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/Request/Request | Request}, it will replace the original request.
|
|
59
|
+
*/
|
|
60
|
+
onRequest?: (request: Request) => MaybePromise<Request | void | null | undefined>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A callback function that is called after the response is received.
|
|
64
|
+
* @param response - An original response to process.
|
|
65
|
+
* @returns If returned a {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/Response/Response | Response}, it will replace the original response.
|
|
66
|
+
*/
|
|
67
|
+
onResponse?: (response: Response) => MaybePromise<Response | void | null | undefined>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** HTTP implementation of the REST transport interface. */
|
|
71
|
+
export class HttpTransport implements IRequestTransport, HttpTransportOptions {
|
|
72
|
+
isTestnet: boolean;
|
|
73
|
+
timeout: number | null;
|
|
74
|
+
server: {
|
|
75
|
+
mainnet: { api: string | URL; rpc: string | URL };
|
|
76
|
+
testnet: { api: string | URL; rpc: string | URL };
|
|
77
|
+
};
|
|
78
|
+
fetchOptions: Omit<RequestInit, "body" | "method">;
|
|
79
|
+
onRequest?: (request: Request) => MaybePromise<Request | void | null | undefined>;
|
|
80
|
+
onResponse?: (response: Response) => MaybePromise<Response | void | null | undefined>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Creates a new HTTP transport instance.
|
|
84
|
+
* @param options - Configuration options for the HTTP transport layer.
|
|
85
|
+
*/
|
|
86
|
+
constructor(options?: HttpTransportOptions) {
|
|
87
|
+
this.isTestnet = options?.isTestnet ?? false;
|
|
88
|
+
this.timeout = options?.timeout === undefined ? 10_000 : options.timeout;
|
|
89
|
+
this.server = {
|
|
90
|
+
mainnet: {
|
|
91
|
+
api: options?.server?.mainnet?.api ?? "https://api.hyperliquid.xyz",
|
|
92
|
+
rpc: options?.server?.mainnet?.rpc ?? "https://rpc.hyperliquid.xyz",
|
|
93
|
+
},
|
|
94
|
+
testnet: {
|
|
95
|
+
api: options?.server?.testnet?.api ?? "https://api.hyperliquid-testnet.xyz",
|
|
96
|
+
rpc: options?.server?.testnet?.rpc ?? "https://rpc.hyperliquid-testnet.xyz",
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
this.fetchOptions = options?.fetchOptions ?? {};
|
|
100
|
+
this.onRequest = options?.onRequest;
|
|
101
|
+
this.onResponse = options?.onResponse;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sends a request to the Hyperliquid API via fetch.
|
|
106
|
+
* @param endpoint - The API endpoint to send the request to.
|
|
107
|
+
* @param payload - The payload to send with the request.
|
|
108
|
+
* @param signal - An {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | AbortSignal}. If this option is set, the request can be canceled by calling {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort | abort()} on the corresponding {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/AbortController | AbortController}.
|
|
109
|
+
* @returns A promise that resolves with parsed JSON response body.
|
|
110
|
+
*
|
|
111
|
+
* @throws {HttpRequestError} Thrown when the HTTP request fails.
|
|
112
|
+
*/
|
|
113
|
+
async request<T>(endpoint: "info" | "exchange" | "explorer", payload: unknown, signal?: AbortSignal): Promise<T> {
|
|
114
|
+
try {
|
|
115
|
+
// Construct a Request
|
|
116
|
+
const url = new URL(
|
|
117
|
+
endpoint,
|
|
118
|
+
this.server[this.isTestnet ? "testnet" : "mainnet"][endpoint === "explorer" ? "rpc" : "api"],
|
|
119
|
+
);
|
|
120
|
+
const init = mergeRequestInit(
|
|
121
|
+
{
|
|
122
|
+
body: JSON.stringify(payload),
|
|
123
|
+
headers: {
|
|
124
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
125
|
+
"Content-Type": "application/json",
|
|
126
|
+
},
|
|
127
|
+
keepalive: true,
|
|
128
|
+
method: "POST",
|
|
129
|
+
signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined,
|
|
130
|
+
},
|
|
131
|
+
this.fetchOptions,
|
|
132
|
+
{ signal },
|
|
133
|
+
);
|
|
134
|
+
let request = new Request(url, init);
|
|
135
|
+
|
|
136
|
+
// Call the onRequest callback, if provided
|
|
137
|
+
if (this.onRequest) {
|
|
138
|
+
const customRequest = await this.onRequest(request);
|
|
139
|
+
if (customRequest instanceof Request) request = customRequest;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Send the Request and wait for a Response
|
|
143
|
+
let response = await fetch(request);
|
|
144
|
+
|
|
145
|
+
// Call the onResponse callback, if provided
|
|
146
|
+
if (this.onResponse) {
|
|
147
|
+
const customResponse = await this.onResponse(response);
|
|
148
|
+
if (customResponse instanceof Response) response = customResponse;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Validate the Response
|
|
152
|
+
if (!response.ok || !response.headers.get("Content-Type")?.includes("application/json")) {
|
|
153
|
+
// Unload the response body to prevent memory leaks
|
|
154
|
+
const body = await response.text().catch(() => undefined);
|
|
155
|
+
throw new HttpRequestError({ response, body });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Parse the response body
|
|
159
|
+
const body = await response.json();
|
|
160
|
+
|
|
161
|
+
// Check if the response is an error
|
|
162
|
+
if (body?.type === "error") {
|
|
163
|
+
throw new HttpRequestError({ response, body: body?.message });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Return the response body
|
|
167
|
+
return body;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error instanceof TransportError) throw error; // Re-throw known errors
|
|
170
|
+
throw new HttpRequestError(undefined, { cause: error });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Merges multiple {@linkcode HeadersInit} into one {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/Headers/Headers | Headers}. */
|
|
176
|
+
function mergeHeadersInit(...inits: HeadersInit[]): Headers {
|
|
177
|
+
if (inits.length === 0 || inits.length === 1) {
|
|
178
|
+
return new Headers(inits[0] as HeadersInit | undefined);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const merged = new Headers();
|
|
182
|
+
for (const headers of inits) {
|
|
183
|
+
const iterator = Symbol.iterator in headers ? headers : Object.entries(headers);
|
|
184
|
+
for (const [key, value] of iterator) {
|
|
185
|
+
merged.set(key, value);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return merged;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Merges multiple {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/RequestInit | RequestInit} into one {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/RequestInit | RequestInit}. */
|
|
192
|
+
function mergeRequestInit(...inits: RequestInit[]): RequestInit {
|
|
193
|
+
const merged = inits.reduce((acc, init) => ({ ...acc, ...init }), {});
|
|
194
|
+
|
|
195
|
+
const headersList = inits.map((init) => init.headers)
|
|
196
|
+
.filter((headers) => typeof headers === "object");
|
|
197
|
+
if (headersList.length > 0) {
|
|
198
|
+
merged.headers = mergeHeadersInit(...headersList);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const signals = inits.map((init) => init.signal)
|
|
202
|
+
.filter((signal) => signal instanceof AbortSignal);
|
|
203
|
+
if (signals.length > 0) {
|
|
204
|
+
merged.signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return merged;
|
|
208
|
+
}
|