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