Skip to content

Commit d7c65e2

Browse files
committed
BREAKING CHANGE: multiple code changes
- refactor: improve internal code, documentation and tests; in types simplify documentation - refactor: migrate to npm deps - refactor(signing)!: change `createL1ActionHash` and `signMultiSigAction` arguments - refactor(transport)!: change `HttpTransport` and `WebSocketTransport` errors - refactor(transport)!: errors in `request`, `subscribe`, and `unsubscribe` calls are wrapped in a `TransportError` error class - feat(build): add `build/bundle.ts` - feat(exchange): add API `PerpDexTransfer`
1 parent 53e4312 commit d7c65e2

File tree

190 files changed

+6992
-11181
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

190 files changed

+6992
-11181
lines changed

.github/workflows/deno_tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ jobs:
2323
- name: deno lint
2424
run: deno lint
2525

26+
# Check the code/docs for type errors
27+
- name: deno check
28+
run: deno check --doc .
29+
2630
# Run all test files in the repository and collect code coverage.
2731
- name: deno test + coverage
2832
run: deno test -A --coverage --coverage-raw-data-only -- ${{ secrets.PRIVATE_KEY }}

.github/workflows/publish_npm.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ jobs:
2020
registry-url: "https://registry.npmjs.org" # Use npm registry
2121

2222
- name: Build package
23-
run: |
24-
VERSION=$(jq -r '.version' deno.json)
25-
deno run -A ./build/npm.ts $VERSION
23+
run: deno run -A ./build/npm.ts
2624

2725
- name: Publish package
2826
env:

README.md

Lines changed: 46 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Hyperliquid API TypeScript SDK
22

3-
[![NPM](https://img.shields.io/npm/v/@nktkas/hyperliquid?style=flat-square&color=blue)](https://www.npmjs.com/package/@nktkas/hyperliquid)
4-
[![JSR](https://img.shields.io/jsr/v/@nktkas/hyperliquid?style=flat-square&color=blue)](https://jsr.io/@nktkas/hyperliquid)
5-
[![Coveralls](https://img.shields.io/coverallsCoverage/github/nktkas/hyperliquid?style=flat-square)](https://coveralls.io/github/nktkas/hyperliquid)
6-
[![bundlejs](https://img.shields.io/bundlejs/size/@nktkas/hyperliquid?style=flat-square)](https://bundlejs.com/?q=@nktkas/hyperliquid)
3+
[![npm](https://img.shields.io/npm/v/@nktkas/hyperliquid?style=flat-square&color=blue)](https://www.npmjs.com/package/@nktkas/hyperliquid)
4+
[![jsr](https://img.shields.io/jsr/v/@nktkas/hyperliquid?style=flat-square&color=blue)](https://jsr.io/@nktkas/hyperliquid)
5+
[![coveralls](https://img.shields.io/coverallsCoverage/github/nktkas/hyperliquid?style=flat-square)](https://coveralls.io/github/nktkas/hyperliquid)
6+
[![bundlephobia](https://img.shields.io/bundlephobia/minzip/@nktkas/hyperliquid?style=flat-square)](https://bundlephobia.com/package/@nktkas/hyperliquid)
77

88
Unofficial [Hyperliquid API](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api) SDK for all major JS
99
runtimes, written in TypeScript and provided with tests.
@@ -44,19 +44,18 @@ deno add jsr:@nktkas/hyperliquid
4444
```html
4545
<script type="module">
4646
import * as hl from "https://esm.sh/jsr/@nktkas/hyperliquid";
47-
// Use hl.InfoClient, hl.ExchangeClient, etc.
4847
</script>
4948
```
5049

5150
### React Native
5251

5352
<details>
54-
<summary>For React Native, you need to import several polyfills before importing the SDK:</summary>
53+
<summary>For React Native, you need to import polyfills before importing the SDK:</summary>
5554

5655
```js
57-
// React Native 0.76.3
56+
// React Native v0.76.3 / Expo v52
5857
// Issues:
59-
// - signing: does not support private keys directly as an abstract wallet
58+
// - signing: does not support private keys directly, use viem or ethers
6059
import { Event, EventTarget } from "event-target-shim";
6160

6261
if (!globalThis.EventTarget || !globalThis.Event) {
@@ -176,16 +175,15 @@ const signers = [
176175
] as const;
177176

178177
const transport = new hl.HttpTransport();
179-
const multiSignClient = new hl.MultiSignClient({ transport, multiSignAddress, signers }); // extends ExchangeClient
178+
const multiSignClient = new hl.MultiSignClient({ transport, multiSignAddress, signers }); // extends `ExchangeClient`
180179

181-
const data = await multiSignClient.approveAgent({ // same API as ExchangeClient
180+
const data = await multiSignClient.approveAgent({ // same API as `ExchangeClient`
182181
agentAddress: "0x...",
183182
agentName: "agentName",
184183
});
185184
```
186185
187-
<details>
188-
<summary><h2>Usage</h2></summary>
186+
## Usage
189187
190188
### 1) Initialize Transport
191189
@@ -194,11 +192,11 @@ First, choose and configure your transport layer (more details in the [API Refer
194192
```ts
195193
import * as hl from "@nktkas/hyperliquid";
196194

197-
// HTTP Transport
198-
const httpTransport = new hl.HttpTransport(); // Accepts optional parameters
195+
// 1. HTTP Transport: suitable for one-time requests or serverless environments
196+
const httpTransport = new hl.HttpTransport(); // Accepts optional parameters (e.g. isTestnet, timeout, etc.)
199197

200-
// WebSocket Transport
201-
const wsTransport = new hl.WebSocketTransport(); // Accepts optional parameters
198+
// 2. WebSocket Transport: has better network latency than HTTP transport
199+
const wsTransport = new hl.WebSocketTransport(); // Accepts optional parameters (e.g. url, timeout, reconnect, etc.)
202200
```
203201
204202
### 2) Initialize Client
@@ -210,7 +208,7 @@ Next, initialize a client with the transport layer (more details in the [API Ref
210208
```ts
211209
import * as hl from "@nktkas/hyperliquid";
212210

213-
const transport = new hl.HttpTransport(); // or WebSocketTransport
211+
const transport = new hl.HttpTransport(); // or `WebSocketTransport`
214212
const infoClient = new hl.InfoClient({ transport });
215213
```
216214
@@ -222,17 +220,17 @@ import { createWalletClient, custom } from "viem";
222220
import { privateKeyToAccount } from "viem/accounts";
223221
import { ethers } from "ethers";
224222

225-
const transport = new hl.HttpTransport(); // or WebSocketTransport
223+
const transport = new hl.HttpTransport(); // or `WebSocketTransport`
226224

227-
// 1. Using private key
225+
// 1. Using private key directly
228226
const privateKey = "0x...";
229227
const exchClient_privateKey = new hl.ExchangeClient({ wallet: privateKey, transport });
230228

231-
// 2. Using Viem with private key
229+
// 2. Using Viem
232230
const viemAccount = privateKeyToAccount("0x...");
233231
const exchClient_viem = new hl.ExchangeClient({ wallet: viemAccount, transport });
234232

235-
// 3. Using Ethers (or Ethers V5) with private key
233+
// 3. Using Ethers (or Ethers V5)
236234
const ethersWallet = new ethers.Wallet("0x...");
237235
const exchClient_ethers = new hl.ExchangeClient({ wallet: ethersWallet, transport });
238236

@@ -241,7 +239,7 @@ const [account] = await window.ethereum.request({ method: "eth_requestAccounts"
241239
const externalWallet = createWalletClient({ account, transport: custom(window.ethereum) });
242240
const exchClient_viemMetamask = new hl.ExchangeClient({ wallet: externalWallet, transport });
243241

244-
// 5. Using external wallet (e.g. MetaMask) via `window.ethereum` (EIP-1193)
242+
// 5. Using external wallet (e.g. MetaMask) via `window.ethereum`
245243
const exchClient_windowMetamask = new hl.ExchangeClient({ wallet: window.ethereum, transport });
246244
```
247245
@@ -250,7 +248,7 @@ const exchClient_windowMetamask = new hl.ExchangeClient({ wallet: window.ethereu
250248
```ts
251249
import * as hl from "@nktkas/hyperliquid";
252250

253-
const transport = new hl.WebSocketTransport(); // only WebSocketTransport
251+
const transport = new hl.WebSocketTransport(); // only `WebSocketTransport`
254252
const subsClient = new hl.SubscriptionClient({ transport });
255253
```
256254
@@ -266,7 +264,7 @@ const signers = [
266264
privateKeyToAccount("0x..."), // first is leader for multi-sign transaction, must contain own address
267265
new ethers.Wallet("0x..."),
268266
{ // can be a custom async wallet
269-
signTypedData(params: {
267+
async signTypedData(params: {
270268
domain: {
271269
name: string;
272270
version: string;
@@ -283,14 +281,14 @@ const signers = [
283281
message: Record<string, unknown>;
284282
}): Promise<Hex> {
285283
// Custom signer logic
286-
return "0x..."; // return signature
284+
return "0x..."; // return hex signature
287285
},
288286
},
289287
"0x...", // private key directly
290288
];
291289

292290
const transport = new hl.HttpTransport();
293-
const multiSignClient = new hl.MultiSignClient({ transport, multiSignAddress, signers }); // extends ExchangeClient
291+
const multiSignClient = new hl.MultiSignClient({ transport, multiSignAddress, signers }); // extends `ExchangeClient`
294292
```
295293
296294
### 3) Use Client
@@ -392,7 +390,7 @@ const signers = [
392390
const transport = new hl.HttpTransport();
393391
const multiSignClient = new hl.MultiSignClient({ transport, multiSignAddress, signers });
394392

395-
// Interaction is the same as with ExchangeClient
393+
// Interaction is the same as with `ExchangeClient`
396394

397395
// Place an orders
398396
const result = await multiSignClient.order({
@@ -424,10 +422,7 @@ const result = await multiSignClient.withdraw3({
424422
});
425423
```
426424
427-
</details>
428-
429-
<details>
430-
<summary><h2>API Reference</h2></summary>
425+
## API Reference
431426
432427
### Clients
433428
@@ -515,6 +510,7 @@ class ExchangeClient {
515510
constructor(args: {
516511
transport: HttpTransport | WebSocketTransport;
517512
wallet:
513+
| Hex // Private key directly
518514
| AbstractViemWalletClient // viem
519515
| AbstractEthersSigner // ethers
520516
| AbstractEthersV5Signer // ethers v5
@@ -552,6 +548,7 @@ class ExchangeClient {
552548

553549
// Transfer
554550
perpDexClassTransfer(args: PerpDexClassTransferParameters): Promise<SuccessResponse>;
551+
perpDexTransfer(args: PerpDexTransferParameters): Promise<SuccessResponse>;
555552
spotSend(args: SpotSendParameters): Promise<SuccessResponse>;
556553
subAccountSpotTransfer(args: SubAccountSpotTransferParameters): Promise<SuccessResponse>;
557554
subAccountTransfer(args: SubAccountTransferParameters): Promise<SuccessResponse>;
@@ -637,16 +634,18 @@ class MultiSignClient extends ExchangeClient {
637634
},
638635
);
639636

640-
// Same methods as ExchangeClient
637+
// Same methods as `ExchangeClient`
641638
}
642639
```
643640
644641
### Transports
645642
646-
Transport acts as a layer between the class and Hyperliquid servers.
643+
Transport acts as a layer between class requests and Hyperliquid servers.
647644
648645
#### HTTP Transport
649646
647+
HTTP transport is suitable for one-off requests or serverless environments.
648+
650649
```ts
651650
class HttpTransport {
652651
constructor(options?: {
@@ -665,6 +664,8 @@ class HttpTransport {
665664
666665
#### WebSocket Transport
667666
667+
WebSocket transport has better network latency than HTTP transport.
668+
668669
```ts
669670
class WebSocketTransport {
670671
constructor(options?: {
@@ -688,10 +689,7 @@ class WebSocketTransport {
688689
}
689690
```
690691
691-
</details>
692-
693-
<details>
694-
<summary><h2>Additional Import Points</h2></summary>
692+
## Additional Import Points
695693
696694
### `/types`
697695
@@ -711,22 +709,22 @@ import { actionSorter, signL1Action } from "@nktkas/hyperliquid/signing";
711709

712710
const privateKey = "0x..."; // or `viem`, `ethers`
713711

712+
const nonce = Date.now();
714713
const action = {
715714
type: "cancel",
716715
cancels: [
717716
{ a: 0, o: 12345 },
718717
],
719-
};
720-
const nonce = Date.now();
718+
} as const;
721719

722720
const signature = await signL1Action({
723721
wallet: privateKey,
724-
action: actionSorter[action.type](action), // key order affects signature
722+
action: actionSorter[action.type](action),
725723
nonce,
726-
isTestnet: true, // change to `false` for mainnet
727724
});
728725

729-
const response = await fetch("https://api.hyperliquid-testnet.xyz/exchange", {
726+
// Send the signed action to the Hyperliquid API
727+
const response = await fetch("https://api.hyperliquid.xyz/exchange", {
730728
method: "POST",
731729
headers: { "Content-Type": "application/json" },
732730
body: JSON.stringify({ action, signature, nonce }),
@@ -743,30 +741,28 @@ const privateKey = "0x..."; // or `viem`, `ethers`
743741

744742
const action = {
745743
type: "approveAgent",
746-
signatureChainId: "0x66eee", // must match the current wallet network
747-
hyperliquidChain: "Testnet", // Mainnet | Testnet
744+
signatureChainId: "0x66eee",
745+
hyperliquidChain: "Mainnet",
748746
agentAddress: "0x...",
749747
agentName: "Agent",
750748
nonce: Date.now(),
751-
};
749+
} as const;
752750

753751
const signature = await signUserSignedAction({
754752
wallet: privateKey,
755753
action,
756-
types: userSignedActionEip712Types[action.type], // key order affects signature
757-
chainId: parseInt(action.signatureChainId, 16),
754+
types: userSignedActionEip712Types[action.type],
758755
});
759756

760-
const response = await fetch("https://api.hyperliquid-testnet.xyz/exchange", {
757+
// Send the signed action to the Hyperliquid API
758+
const response = await fetch("https://api.hyperliquid.xyz/exchange", {
761759
method: "POST",
762760
headers: { "Content-Type": "application/json" },
763761
body: JSON.stringify({ action, signature, nonce: action.nonce }),
764762
});
765763
const body = await response.json();
766764
```
767765
768-
</details>
769-
770766
## Contributing
771767
772768
We appreciate your help! To contribute, please read the [contributing instructions](CONTRIBUTING.md).

build/bundle.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Builds the Deno library into a bundle and calculates the bundle size
3+
* Command: deno run -A build/bundle.ts
4+
*/
5+
6+
import { build } from "npm:esbuild";
7+
import { denoPlugins } from "jsr:@luca/esbuild-deno-loader";
8+
9+
// Build bundle
10+
const nodeModulesExists = await Deno.stat("./node_modules").then(() => true).catch(() => false);
11+
12+
await build({
13+
plugins: [...denoPlugins({ nodeModulesDir: "auto" })],
14+
entryPoints: {
15+
mod: "./mod.ts",
16+
signing: "./src/signing/mod.ts",
17+
},
18+
format: "esm",
19+
bundle: true,
20+
target: "esnext",
21+
minify: true,
22+
outdir: "./build/bundle",
23+
sourcemap: true,
24+
});
25+
26+
if (!nodeModulesExists) {
27+
await Deno.remove("./node_modules", { recursive: true });
28+
}
29+
30+
// Calculate sizes
31+
async function gzip(data: Uint8Array): Promise<Uint8Array> {
32+
const gzipStream = new CompressionStream("gzip");
33+
const dataStream = ReadableStream.from([data]);
34+
const [compressed] = await Array.fromAsync(dataStream.pipeThrough(gzipStream));
35+
return compressed;
36+
}
37+
38+
const bundleFiles = await Array.fromAsync(Deno.readDir("./build/bundle"));
39+
const jsFiles = bundleFiles.filter((f) => f.name.endsWith(".js"));
40+
41+
for (const file of jsFiles) {
42+
const minifiedCode = await Deno.readFile(`./build/bundle/${file.name}`);
43+
const gzippedCode = await gzip(minifiedCode);
44+
45+
const minifiedSize = (minifiedCode.length / 1024).toFixed(1);
46+
const gzippedSize = (gzippedCode.length / 1024).toFixed(1);
47+
const compression = ((1 - gzippedCode.length / minifiedCode.length) * 100).toFixed(1);
48+
49+
console.log(`${file.name}: 📦 Minified: ${minifiedSize}KB → ⚡ Gzipped: ${gzippedSize}KB (-${compression}%)`);
50+
}

0 commit comments

Comments
 (0)