Skip to content

Commit 213034c

Browse files
committed
add hl spot orderbook
1 parent 4914aea commit 213034c

File tree

1 file changed

+99
-0
lines changed
  • projects/hyperliquid-spot-orderbook

1 file changed

+99
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
const ADDRESSES = require('../helper/coreAssets.json')
2+
const axios = require('axios')
3+
const WebSocket = require('ws')
4+
5+
const USDC = ADDRESSES.ethereum.USDC
6+
const API_URL = 'https://api.hyperliquid.xyz/info'
7+
8+
const assetsInfos = async () => {
9+
const payload = { "type": "spotMetaAndAssetCtxs" }
10+
const { data } = await axios.post(API_URL, payload)
11+
12+
return data[0].tokens.map((token) => {
13+
const ctxToken = data[1].find((item) => item.coin.replace("@", "") == token.index);
14+
return { ...token, ...ctxToken };
15+
});
16+
}
17+
18+
const nSigFigs = 2
19+
const getOrderbooks = async () => {
20+
const assets = await assetsInfos()
21+
return new Promise((resolve, reject) => {
22+
const ws = new WebSocket('wss://api-ui.hyperliquid.xyz/ws');
23+
let coins = []
24+
let spotCoins = assets.map(asset => asset.coin).filter(Boolean)
25+
const receivedMessages = new Map(); // Track messages received per coin
26+
let allMids = null
27+
28+
ws.on('open', () => {
29+
ws.send(JSON.stringify({"method":"subscribe","subscription":{"type":"allMids"}}));
30+
});
31+
32+
ws.on('message', (data) => {
33+
const response = JSON.parse(data);
34+
// Check if message is for a specific coin
35+
if (response.channel === "l2Book" && response.data.coin) {
36+
if(!receivedMessages.has(response.data.coin)){
37+
receivedMessages.set(response.data.coin, response.data);
38+
ws.send(JSON.stringify({"method":"unsubscribe","subscription":{"type":"l2Book","coin":response.data.coin, nSigFigs}}));
39+
40+
if (coins.every(coin => receivedMessages.has(coin))) {
41+
ws.close();
42+
resolve({
43+
books: Array.from(receivedMessages.values()),
44+
allMids: allMids
45+
});
46+
}
47+
}
48+
} else if (response.channel === "allMids" && allMids === null) {
49+
allMids = response.data.mids
50+
ws.send(JSON.stringify({"method":"unsubscribe","subscription":{"type":"allMids"}}));
51+
coins = Object.keys(allMids).filter(coin => spotCoins.includes(coin))
52+
coins.forEach(coin => {
53+
const subscriptionMsg = {
54+
method: "subscribe",
55+
subscription: {
56+
type: "l2Book",
57+
coin: coin,
58+
nSigFigs
59+
}
60+
};
61+
ws.send(JSON.stringify(subscriptionMsg));
62+
});
63+
} else if(response.channel !== "subscriptionResponse"){
64+
console.log(response)
65+
}
66+
});
67+
68+
ws.on('error', (error) => {
69+
reject(error);
70+
});
71+
72+
// Add timeout to prevent hanging
73+
setTimeout(() => {
74+
ws.close();
75+
reject(new Error('WebSocket subscription timed out'));
76+
}, 10000);
77+
});
78+
}
79+
80+
81+
const tvl = async (api) => {
82+
const orderbooks = await getOrderbooks()
83+
let totalBalance = 0
84+
orderbooks.books.forEach(book => {
85+
const price = orderbooks.allMids[book.coin]
86+
const buySide = book.levels[0].reduce((sum, ask) => sum + Number(ask.sz)*Number(ask.px), 0)
87+
const sellSide = price * book.levels[1].reduce((sum, ask) => sum + Number(ask.sz), 0)
88+
const totalFromCoin = buySide + sellSide
89+
totalBalance += totalFromCoin
90+
})
91+
92+
return api.add(USDC, totalBalance * 1e6, { skipChain: true })
93+
}
94+
95+
module.exports = {
96+
methodology: 'TVL represents assets locked in limit order on the spot order book',
97+
misrepresentedTokens: true,
98+
hyperliquid: { tvl }
99+
}

0 commit comments

Comments
 (0)