@lifi/perps-sdk-provider-hyperliquid 26.0.0 → 27.0.1
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/cjs/services/getAccount.d.ts.map +1 -1
- package/dist/cjs/services/getAccount.js +7 -6
- package/dist/cjs/services/getAccount.js.map +1 -1
- package/dist/cjs/services/getOrders.d.ts.map +1 -1
- package/dist/cjs/services/getOrders.js +5 -27
- package/dist/cjs/services/getOrders.js.map +1 -1
- package/dist/cjs/services/getPortfolioHistory.d.ts.map +1 -1
- package/dist/cjs/services/getPortfolioHistory.js +3 -1
- package/dist/cjs/services/getPortfolioHistory.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +1 -1
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +3 -1
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/liquidation.d.ts +4 -1
- package/dist/cjs/utils/liquidation.d.ts.map +1 -1
- package/dist/cjs/utils/liquidation.js +9 -8
- package/dist/cjs/utils/liquidation.js.map +1 -1
- package/dist/cjs/utils/mapActivity.d.ts.map +1 -1
- package/dist/cjs/utils/mapActivity.js +9 -3
- package/dist/cjs/utils/mapActivity.js.map +1 -1
- package/dist/cjs/utils/mapFill.d.ts.map +1 -1
- package/dist/cjs/utils/mapFill.js +4 -1
- package/dist/cjs/utils/mapFill.js.map +1 -1
- package/dist/cjs/utils/mapMarket.d.ts.map +1 -1
- package/dist/cjs/utils/mapMarket.js +1 -1
- package/dist/cjs/utils/mapMarket.js.map +1 -1
- package/dist/cjs/utils/mapOrder.d.ts +2 -0
- package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
- package/dist/cjs/utils/mapOrder.js +31 -19
- package/dist/cjs/utils/mapOrder.js.map +1 -1
- package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
- package/dist/cjs/utils/mapPosition.js +4 -4
- package/dist/cjs/utils/mapPosition.js.map +1 -1
- package/dist/cjs/utils/spotBalance.d.ts +1 -1
- package/dist/cjs/utils/spotBalance.d.ts.map +1 -1
- package/dist/cjs/utils/spotBalance.js +4 -1
- package/dist/cjs/utils/spotBalance.js.map +1 -1
- package/dist/cjs/utils/transferMargin.js +1 -1
- package/dist/cjs/utils/transferMargin.js.map +1 -1
- package/dist/cjs/utils/venueTotals.d.ts +0 -1
- package/dist/cjs/utils/venueTotals.d.ts.map +1 -1
- package/dist/cjs/utils/venueTotals.js +13 -4
- package/dist/cjs/utils/venueTotals.js.map +1 -1
- package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +2 -0
- package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/HyperliquidWsProvider.js +63 -51
- package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
- package/dist/esm/services/getAccount.d.ts.map +1 -1
- package/dist/esm/services/getAccount.js +8 -7
- package/dist/esm/services/getAccount.js.map +1 -1
- package/dist/esm/services/getOrders.d.ts.map +1 -1
- package/dist/esm/services/getOrders.js +7 -35
- package/dist/esm/services/getOrders.js.map +1 -1
- package/dist/esm/services/getPortfolioHistory.d.ts.map +1 -1
- package/dist/esm/services/getPortfolioHistory.js +3 -1
- package/dist/esm/services/getPortfolioHistory.js.map +1 -1
- package/dist/esm/utils/index.d.ts +1 -1
- package/dist/esm/utils/index.d.ts.map +1 -1
- package/dist/esm/utils/index.js +1 -1
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/liquidation.d.ts +24 -23
- package/dist/esm/utils/liquidation.d.ts.map +1 -1
- package/dist/esm/utils/liquidation.js +29 -31
- package/dist/esm/utils/liquidation.js.map +1 -1
- package/dist/esm/utils/mapActivity.d.ts.map +1 -1
- package/dist/esm/utils/mapActivity.js +9 -3
- package/dist/esm/utils/mapActivity.js.map +1 -1
- package/dist/esm/utils/mapFill.d.ts.map +1 -1
- package/dist/esm/utils/mapFill.js +4 -1
- package/dist/esm/utils/mapFill.js.map +1 -1
- package/dist/esm/utils/mapMarket.d.ts.map +1 -1
- package/dist/esm/utils/mapMarket.js +2 -2
- package/dist/esm/utils/mapMarket.js.map +1 -1
- package/dist/esm/utils/mapOrder.d.ts +7 -0
- package/dist/esm/utils/mapOrder.d.ts.map +1 -1
- package/dist/esm/utils/mapOrder.js +35 -19
- package/dist/esm/utils/mapOrder.js.map +1 -1
- package/dist/esm/utils/mapPosition.d.ts.map +1 -1
- package/dist/esm/utils/mapPosition.js +4 -4
- package/dist/esm/utils/mapPosition.js.map +1 -1
- package/dist/esm/utils/spotBalance.d.ts +2 -1
- package/dist/esm/utils/spotBalance.d.ts.map +1 -1
- package/dist/esm/utils/spotBalance.js +5 -1
- package/dist/esm/utils/spotBalance.js.map +1 -1
- package/dist/esm/utils/transferMargin.js +1 -1
- package/dist/esm/utils/transferMargin.js.map +1 -1
- package/dist/esm/utils/venueTotals.d.ts +2 -6
- package/dist/esm/utils/venueTotals.d.ts.map +1 -1
- package/dist/esm/utils/venueTotals.js +15 -8
- package/dist/esm/utils/venueTotals.js.map +1 -1
- package/dist/esm/utils/withdrawableBalances.d.ts +2 -2
- package/dist/esm/utils/withdrawableBalances.js +2 -2
- package/dist/esm/websocket/HyperliquidWsProvider.d.ts +3 -1
- package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/HyperliquidWsProvider.js +66 -54
- package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
- package/dist/types/services/getAccount.d.ts.map +1 -1
- package/dist/types/services/getOrders.d.ts.map +1 -1
- package/dist/types/services/getPortfolioHistory.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +1 -1
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/liquidation.d.ts +24 -23
- package/dist/types/utils/liquidation.d.ts.map +1 -1
- package/dist/types/utils/mapActivity.d.ts.map +1 -1
- package/dist/types/utils/mapFill.d.ts.map +1 -1
- package/dist/types/utils/mapMarket.d.ts.map +1 -1
- package/dist/types/utils/mapOrder.d.ts +7 -0
- package/dist/types/utils/mapOrder.d.ts.map +1 -1
- package/dist/types/utils/mapPosition.d.ts.map +1 -1
- package/dist/types/utils/spotBalance.d.ts +2 -1
- package/dist/types/utils/spotBalance.d.ts.map +1 -1
- package/dist/types/utils/venueTotals.d.ts +2 -6
- package/dist/types/utils/venueTotals.d.ts.map +1 -1
- package/dist/types/utils/withdrawableBalances.d.ts +2 -2
- package/dist/types/websocket/HyperliquidWsProvider.d.ts +3 -1
- package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/services/getAccount.ts +16 -6
- package/src/services/getOrders.ts +9 -44
- package/src/services/getPortfolioHistory.ts +3 -7
- package/src/utils/index.ts +2 -0
- package/src/utils/liquidation.ts +42 -34
- package/src/utils/mapActivity.ts +9 -3
- package/src/utils/mapFill.ts +4 -1
- package/src/utils/mapMarket.ts +4 -2
- package/src/utils/mapOrder.ts +46 -20
- package/src/utils/mapPosition.ts +8 -4
- package/src/utils/spotBalance.ts +8 -1
- package/src/utils/transferMargin.ts +1 -1
- package/src/utils/venueTotals.ts +21 -28
- package/src/utils/withdrawableBalances.ts +2 -2
- package/src/websocket/HyperliquidWsProvider.ts +105 -69
package/src/utils/mapOrder.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createWarnOnce,
|
|
3
|
+
isDecimalString,
|
|
2
4
|
isDecimalStringGreaterThan,
|
|
5
|
+
type MarketRegistry,
|
|
3
6
|
PerpsError,
|
|
4
7
|
safeDivideDecimalString,
|
|
5
8
|
safeIsDecimalStringGreaterThan,
|
|
6
9
|
safeIsDecimalStringZero,
|
|
7
10
|
safeSubtractDecimalString,
|
|
8
|
-
subtractDecimalString,
|
|
9
11
|
triggerConditionFor,
|
|
10
|
-
unknownToDecimalString,
|
|
11
12
|
warnSkippedVenueRow,
|
|
12
13
|
} from '@lifi/perps-sdk'
|
|
13
14
|
import type {
|
|
@@ -29,6 +30,7 @@ import type {
|
|
|
29
30
|
HlOrderDetail,
|
|
30
31
|
HlTwapHistoryEntry,
|
|
31
32
|
} from '../types/index.js'
|
|
33
|
+
import { assetIsOutcome } from './assetId.js'
|
|
32
34
|
import { rowTimestampToIsoStringOrUndefined } from './rowTimestamp.js'
|
|
33
35
|
|
|
34
36
|
/** Order payload shared by frontend, historical, and single-order reads. */
|
|
@@ -98,15 +100,42 @@ export const mapOrderStatus = (status: string): OrderStatus => {
|
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
const skipOrder = (
|
|
102
|
-
|
|
103
|
+
const skipOrder = (
|
|
104
|
+
marketId: string,
|
|
105
|
+
field: string,
|
|
106
|
+
value: unknown
|
|
107
|
+
): undefined => {
|
|
108
|
+
warnSkippedVenueRow(PROVIDER_KEY, 'order', field, value, { marketId })
|
|
103
109
|
return undefined
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
const
|
|
112
|
+
const warnDroppedRowOnce = createWarnOnce()
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Map one venue row, or drop it. An outcome market identity, a coin the backend
|
|
116
|
+
* market list does not hold, and a row the mapper rejects each drop only their
|
|
117
|
+
* own row; each distinct mapper message warns once.
|
|
118
|
+
*/
|
|
119
|
+
export const mapOrderRow = (
|
|
120
|
+
coin: string,
|
|
121
|
+
registry: MarketRegistry | undefined,
|
|
122
|
+
map: (market: MarketDisplay) => Order | undefined
|
|
123
|
+
): Order | undefined => {
|
|
124
|
+
if (assetIsOutcome(coin)) {
|
|
125
|
+
return undefined
|
|
126
|
+
}
|
|
127
|
+
const market = registry?.get(coin)
|
|
128
|
+
if (market === undefined) {
|
|
129
|
+
return undefined
|
|
130
|
+
}
|
|
107
131
|
try {
|
|
108
|
-
return
|
|
109
|
-
} catch {
|
|
132
|
+
return map(market)
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (!(error instanceof PerpsError)) {
|
|
135
|
+
throw error
|
|
136
|
+
}
|
|
137
|
+
const message = `[${PROVIDER_KEY}] dropped order row: ${error.message}`
|
|
138
|
+
warnDroppedRowOnce(message, message)
|
|
110
139
|
return undefined
|
|
111
140
|
}
|
|
112
141
|
}
|
|
@@ -133,15 +162,15 @@ export const mapOrder = (
|
|
|
133
162
|
const { state } = raw
|
|
134
163
|
const remainingSize = safeSubtractDecimalString(state.sz, state.executedSz)
|
|
135
164
|
if (remainingSize === undefined) {
|
|
136
|
-
return skipOrder('executedSz', state.executedSz)
|
|
165
|
+
return skipOrder(market.id, 'executedSz', state.executedSz)
|
|
137
166
|
}
|
|
138
167
|
const createdAt = rowTimestampToIsoStringOrUndefined(state.timestamp)
|
|
139
168
|
if (createdAt === undefined) {
|
|
140
|
-
return skipOrder('timestamp', state.timestamp)
|
|
169
|
+
return skipOrder(market.id, 'timestamp', state.timestamp)
|
|
141
170
|
}
|
|
142
171
|
const updatedAt = rowTimestampToIsoStringOrUndefined(raw.time * 1000)
|
|
143
172
|
if (updatedAt === undefined) {
|
|
144
|
-
return skipOrder('time', raw.time)
|
|
173
|
+
return skipOrder(market.id, 'time', raw.time)
|
|
145
174
|
}
|
|
146
175
|
let status: OrderStatus
|
|
147
176
|
switch (raw.status.status) {
|
|
@@ -195,23 +224,20 @@ export const mapOrder = (
|
|
|
195
224
|
}
|
|
196
225
|
const o = 'order' in raw ? raw.order : raw
|
|
197
226
|
const venueStatus = 'order' in raw ? raw.status : 'open'
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
200
|
-
return
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (remainingSize === undefined) {
|
|
204
|
-
return skipOrder('sz', o.sz)
|
|
227
|
+
const filledSize = safeSubtractDecimalString(o.origSz, o.sz)
|
|
228
|
+
if (filledSize === undefined) {
|
|
229
|
+
return isDecimalString(o.origSz)
|
|
230
|
+
? skipOrder(market.id, 'sz', o.sz)
|
|
231
|
+
: skipOrder(market.id, 'origSz', o.origSz)
|
|
205
232
|
}
|
|
206
|
-
const filledSize = subtractDecimalString(originalSize, remainingSize)
|
|
207
233
|
const createdAt = rowTimestampToIsoStringOrUndefined(o.timestamp)
|
|
208
234
|
if (createdAt === undefined) {
|
|
209
|
-
return skipOrder('timestamp', o.timestamp)
|
|
235
|
+
return skipOrder(market.id, 'timestamp', o.timestamp)
|
|
210
236
|
}
|
|
211
237
|
const statusTimestamp = 'order' in raw ? raw.statusTimestamp : o.timestamp
|
|
212
238
|
const updatedAt = rowTimestampToIsoStringOrUndefined(statusTimestamp)
|
|
213
239
|
if (updatedAt === undefined) {
|
|
214
|
-
return skipOrder('statusTimestamp', statusTimestamp)
|
|
240
|
+
return skipOrder(market.id, 'statusTimestamp', statusTimestamp)
|
|
215
241
|
}
|
|
216
242
|
let status = mapOrderStatus(venueStatus)
|
|
217
243
|
if (parentOrderId !== undefined && !('order' in raw)) {
|
package/src/utils/mapPosition.ts
CHANGED
|
@@ -18,8 +18,12 @@ import type { HlAssetPosition } from '../types/index.js'
|
|
|
18
18
|
|
|
19
19
|
const warnNonPositiveLeverageOnce = createWarnOnce()
|
|
20
20
|
|
|
21
|
-
const skipPosition = (
|
|
22
|
-
|
|
21
|
+
const skipPosition = (
|
|
22
|
+
marketId: string,
|
|
23
|
+
field: string,
|
|
24
|
+
value: unknown
|
|
25
|
+
): undefined => {
|
|
26
|
+
warnSkippedVenueRow(PROVIDER_KEY, 'position', field, value, { marketId })
|
|
23
27
|
return undefined
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -48,7 +52,7 @@ export const mapPosition = (
|
|
|
48
52
|
): Position | undefined => {
|
|
49
53
|
const pos = ap.position
|
|
50
54
|
if (!isDecimalString(pos.szi)) {
|
|
51
|
-
return skipPosition('szi', pos.szi)
|
|
55
|
+
return skipPosition(market.id, 'szi', pos.szi)
|
|
52
56
|
}
|
|
53
57
|
const size = absDecimalString(pos.szi)
|
|
54
58
|
const positionValue = safeAbsDecimalString(pos.positionValue)
|
|
@@ -89,7 +93,7 @@ export const mapPosition = (
|
|
|
89
93
|
size,
|
|
90
94
|
entryPrice: pos.entryPx ?? '0',
|
|
91
95
|
...(markPrice === undefined ? {} : { markPrice }),
|
|
92
|
-
liquidationPrice: pos.liquidationPx
|
|
96
|
+
liquidationPrice: pos.liquidationPx || '0',
|
|
93
97
|
unrealizedPnl: pos.unrealizedPnl,
|
|
94
98
|
...(accruedFunding === undefined ? {} : { accruedFunding }),
|
|
95
99
|
...(leverage === undefined ? {} : { leverage }),
|
package/src/utils/spotBalance.ts
CHANGED
|
@@ -38,12 +38,19 @@ export const spotPriceById = (
|
|
|
38
38
|
* Convert a Hyperliquid spot balance's numeric token index into an SDK asset.
|
|
39
39
|
* The balance payload has no `fullName`, so logo resolution uses the base
|
|
40
40
|
* `_spot` URI rule rather than Unit-underlying lookup.
|
|
41
|
+
* @param registered - The registry `Asset` with the same `id`; only its `decimals` is copied.
|
|
41
42
|
* @public
|
|
42
43
|
*/
|
|
43
|
-
export const spotAssetFromToken = (
|
|
44
|
+
export const spotAssetFromToken = (
|
|
45
|
+
b: HlSpotBalance,
|
|
46
|
+
registered?: Asset
|
|
47
|
+
): Asset => ({
|
|
44
48
|
...coinAsset(b.coin),
|
|
45
49
|
id: String(b.token),
|
|
46
50
|
logoURI: spotLogoURI(b.coin),
|
|
51
|
+
...(registered?.decimals === undefined
|
|
52
|
+
? {}
|
|
53
|
+
: { decimals: registered.decimals }),
|
|
47
54
|
})
|
|
48
55
|
|
|
49
56
|
/** Assemble a typed spot {@link Balance}; `total` is native token units and its unit price and USD value use `priceById`. @public */
|
|
@@ -24,7 +24,7 @@ function requirePositionAmount(
|
|
|
24
24
|
if (!isDecimalString(value)) {
|
|
25
25
|
throw new PerpsError(
|
|
26
26
|
PerpsErrorCode.ValidationError,
|
|
27
|
-
`Invalid \`Position.${field}\`: '${value}'
|
|
27
|
+
`Invalid \`Position.${field}\`: '${value}' does not match the decimal pattern.`
|
|
28
28
|
)
|
|
29
29
|
}
|
|
30
30
|
return value
|
package/src/utils/venueTotals.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
addDecimalString,
|
|
3
|
-
|
|
3
|
+
addDecimalStrings,
|
|
4
|
+
createWarnOnce,
|
|
5
|
+
isDecimalString,
|
|
4
6
|
unknownToDecimalString,
|
|
5
7
|
} from '@lifi/perps-sdk'
|
|
6
8
|
import type {
|
|
@@ -55,35 +57,26 @@ export const perpsTotals = (
|
|
|
55
57
|
return { accountValue, marginUsed }
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
const warnSkippedTermOnce = createWarnOnce()
|
|
61
|
+
|
|
58
62
|
/**
|
|
59
|
-
* Sum the unrealized PnL of every position, in quote-asset units.
|
|
63
|
+
* Sum the unrealized PnL of every position, in quote-asset units. A term that
|
|
64
|
+
* does not match the decimal pattern adds nothing and warns once.
|
|
60
65
|
* @public
|
|
61
66
|
*/
|
|
62
67
|
export const sumUnrealizedPnl = (
|
|
63
68
|
positions: readonly Position[]
|
|
64
|
-
): DecimalString =>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Sum every term, or `undefined` with a warning when any term is not a
|
|
80
|
-
* decimal string. A bad term never gives a partial sum.
|
|
81
|
-
*/
|
|
82
|
-
export const safeSumDecimalStrings = (
|
|
83
|
-
terms: readonly string[]
|
|
84
|
-
): DecimalString | undefined =>
|
|
85
|
-
terms.reduce<DecimalString | undefined>(
|
|
86
|
-
(sum, term) =>
|
|
87
|
-
sum === undefined ? undefined : safeAddDecimalString(sum, term),
|
|
88
|
-
'0'
|
|
89
|
-
)
|
|
69
|
+
): DecimalString => {
|
|
70
|
+
const terms: DecimalString[] = []
|
|
71
|
+
for (const { unrealizedPnl } of positions) {
|
|
72
|
+
if (isDecimalString(unrealizedPnl)) {
|
|
73
|
+
terms.push(unrealizedPnl)
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
warnSkippedTermOnce(
|
|
77
|
+
`${PROVIDER_KEY}|position.unrealizedPnl`,
|
|
78
|
+
`[${PROVIDER_KEY}] skipping a \`position.unrealizedPnl\` term that does not match the decimal pattern: '${String(unrealizedPnl).slice(0, 64)}'`
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
return addDecimalStrings(terms)
|
|
82
|
+
}
|
|
@@ -25,8 +25,8 @@ import { assetIsOutcome } from './assetId.js'
|
|
|
25
25
|
* @param withdrawalFee - Flat venue fee in quote-asset units, set on every
|
|
26
26
|
* quote-asset row. Hyperliquid deducts it from the requested amount. Absent
|
|
27
27
|
* leaves every row without a fee.
|
|
28
|
-
* @throws {PerpsError} `SDKError` when the fee or a venue figure
|
|
29
|
-
* decimal.
|
|
28
|
+
* @throws {PerpsError} `SDKError` when the fee or a venue figure does not match the
|
|
29
|
+
* decimal pattern.
|
|
30
30
|
* @public
|
|
31
31
|
*/
|
|
32
32
|
export const hyperliquidWithdrawableBalances = (
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type AssetRegistry,
|
|
2
3
|
addDecimalString,
|
|
3
4
|
calculateTransferable,
|
|
4
5
|
compareDecimalStrings,
|
|
5
6
|
DecodeChain,
|
|
7
|
+
getAssetRegistry,
|
|
6
8
|
getMarketRegistry,
|
|
7
9
|
isActiveMarket,
|
|
8
10
|
isActiveOrderStatus,
|
|
9
11
|
isDecimalString,
|
|
10
|
-
isDecimalStringGreaterThan,
|
|
11
12
|
isDecimalStringZero,
|
|
12
13
|
type MarketRegistry,
|
|
13
14
|
PerpsError,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
safeIsDecimalStringZero,
|
|
25
26
|
safeMultiplyDecimalString,
|
|
26
27
|
safeNumberToDecimalString,
|
|
28
|
+
safeSubtractDecimalString,
|
|
27
29
|
subtractDecimalString,
|
|
28
30
|
toAssetDisplay,
|
|
29
31
|
toPerpsMarketDisplay,
|
|
@@ -100,6 +102,7 @@ import {
|
|
|
100
102
|
spotPriceById,
|
|
101
103
|
sumUnrealizedPnl,
|
|
102
104
|
} from '../utils/index.js'
|
|
105
|
+
import { mapOrderRow } from '../utils/mapOrder.js'
|
|
103
106
|
|
|
104
107
|
/** HL's compact `l2` snapshot carries 20 levels per side. */
|
|
105
108
|
const HL_L2_BOOK_MAX_LEVELS_PER_SIDE = 20
|
|
@@ -153,6 +156,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
153
156
|
private readonly clearinghouseRefs = new Map<string, number>()
|
|
154
157
|
private readonly client: PerpsSDKClient | undefined
|
|
155
158
|
private readonly registry: MarketRegistry | undefined
|
|
159
|
+
private readonly assetRegistry: AssetRegistry | undefined
|
|
156
160
|
private readonly accountChecksByUser = new Map<string, Promise<void>>()
|
|
157
161
|
// Spot-funded modes need both spot valuation and clearinghouse margin state.
|
|
158
162
|
private readonly abstractionByUser = new Map<
|
|
@@ -241,6 +245,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
241
245
|
)
|
|
242
246
|
this.client = client
|
|
243
247
|
this.registry = client && getMarketRegistry(client, providerKey)
|
|
248
|
+
this.assetRegistry = client && getAssetRegistry(client, providerKey)
|
|
244
249
|
const orderApiUrl = new URL(wsUrl)
|
|
245
250
|
orderApiUrl.protocol = orderApiUrl.protocol === 'wss:' ? 'https:' : 'http:'
|
|
246
251
|
this.orderApiUrl = orderApiUrl.origin
|
|
@@ -973,7 +978,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
973
978
|
|
|
974
979
|
/** Latest mid per `Market.id` across asset-context and fast feeds. */
|
|
975
980
|
/** Latest mid per `Market.id` across asset-context and fast feeds; a mid
|
|
976
|
-
* that
|
|
981
|
+
* that does not match the decimal pattern is left out. */
|
|
977
982
|
private mergedMids(): Map<string, DecimalString> {
|
|
978
983
|
const map = new Map<string, DecimalString>()
|
|
979
984
|
const setMid = (id: string, mid: string | number | null | undefined) => {
|
|
@@ -1017,10 +1022,10 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
1017
1022
|
provider: this.providerKey,
|
|
1018
1023
|
marketId: data.coin,
|
|
1019
1024
|
bids: data.levels[0]
|
|
1020
|
-
.flatMap((l) => toOrderbookLevel(l.px, l.sz))
|
|
1025
|
+
.flatMap((l) => toOrderbookLevel(data.coin, l.px, l.sz))
|
|
1021
1026
|
.slice(0, HL_L2_BOOK_MAX_LEVELS_PER_SIDE),
|
|
1022
1027
|
asks: data.levels[1]
|
|
1023
|
-
.flatMap((l) => toOrderbookLevel(l.px, l.sz))
|
|
1028
|
+
.flatMap((l) => toOrderbookLevel(data.coin, l.px, l.sz))
|
|
1024
1029
|
.slice(0, HL_L2_BOOK_MAX_LEVELS_PER_SIDE),
|
|
1025
1030
|
timestamp: data.time,
|
|
1026
1031
|
}
|
|
@@ -1064,12 +1069,14 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
1064
1069
|
provider: this.providerKey,
|
|
1065
1070
|
marketId: delta.c,
|
|
1066
1071
|
bids: applyCompressedL2Side(
|
|
1072
|
+
delta.c,
|
|
1067
1073
|
previous.bids,
|
|
1068
1074
|
delta.l[0],
|
|
1069
1075
|
delta.r?.[0] ?? [],
|
|
1070
1076
|
'bid'
|
|
1071
1077
|
),
|
|
1072
1078
|
asks: applyCompressedL2Side(
|
|
1079
|
+
delta.c,
|
|
1073
1080
|
previous.asks,
|
|
1074
1081
|
delta.l[1],
|
|
1075
1082
|
delta.r?.[1] ?? [],
|
|
@@ -1141,48 +1148,33 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
1141
1148
|
if (epoch !== this.orderUpdatesEpoch || key !== this.orderUpdatesKey) {
|
|
1142
1149
|
return
|
|
1143
1150
|
}
|
|
1151
|
+
const user = key.slice('orderUpdates:'.length)
|
|
1144
1152
|
const mapped = await Promise.all(
|
|
1145
1153
|
data.map(async (update) => {
|
|
1146
1154
|
const basic = update.order
|
|
1147
|
-
if (
|
|
1155
|
+
if (
|
|
1156
|
+
assetIsOutcome(basic.coin) ||
|
|
1157
|
+
this.registry?.get(basic.coin) === undefined
|
|
1158
|
+
) {
|
|
1148
1159
|
return undefined
|
|
1149
1160
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1161
|
+
let detail: HlOrderDetail
|
|
1162
|
+
try {
|
|
1163
|
+
detail = await this.readOrderDetail(user, client, update)
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
if (!(error instanceof PerpsError)) {
|
|
1166
|
+
throw error
|
|
1167
|
+
}
|
|
1168
|
+
wsLog.droppedRow(
|
|
1169
|
+
this.providerKey,
|
|
1170
|
+
'order',
|
|
1171
|
+
`${basic.oid}: ${error.message}`
|
|
1172
|
+
)
|
|
1152
1173
|
return undefined
|
|
1153
1174
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
{
|
|
1157
|
-
type: 'orderStatus',
|
|
1158
|
-
user: key.slice('orderUpdates:'.length),
|
|
1159
|
-
oid: basic.oid,
|
|
1160
|
-
},
|
|
1161
|
-
hlInfoOptions(client)
|
|
1175
|
+
return mapOrderRow(basic.coin, this.registry, (resolved) =>
|
|
1176
|
+
mapOrder(detail, resolved)
|
|
1162
1177
|
)
|
|
1163
|
-
if (response.status !== 'order') {
|
|
1164
|
-
throw new PerpsError(
|
|
1165
|
-
PerpsErrorCode.OrderNotFound,
|
|
1166
|
-
`Hyperliquid order metadata not found: ${basic.oid}`
|
|
1167
|
-
)
|
|
1168
|
-
}
|
|
1169
|
-
// REST supplies execution metadata; the stream owns this event's lifecycle and quantities.
|
|
1170
|
-
const detail: HlOrderDetail = {
|
|
1171
|
-
status: update.status,
|
|
1172
|
-
statusTimestamp: update.statusTimestamp,
|
|
1173
|
-
order: {
|
|
1174
|
-
...response.order.order,
|
|
1175
|
-
coin: basic.coin,
|
|
1176
|
-
side: basic.side,
|
|
1177
|
-
limitPx: basic.limitPx,
|
|
1178
|
-
sz: basic.sz,
|
|
1179
|
-
oid: basic.oid,
|
|
1180
|
-
timestamp: basic.timestamp,
|
|
1181
|
-
origSz: basic.origSz,
|
|
1182
|
-
cloid: basic.cloid ?? response.order.order.cloid,
|
|
1183
|
-
},
|
|
1184
|
-
}
|
|
1185
|
-
return mapOrder(detail, market)
|
|
1186
1178
|
})
|
|
1187
1179
|
)
|
|
1188
1180
|
if (epoch !== this.orderUpdatesEpoch || key !== this.orderUpdatesKey) {
|
|
@@ -1203,6 +1195,41 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
1203
1195
|
})
|
|
1204
1196
|
}
|
|
1205
1197
|
|
|
1198
|
+
// REST supplies execution metadata; the stream owns this event's lifecycle and quantities.
|
|
1199
|
+
private async readOrderDetail(
|
|
1200
|
+
user: string,
|
|
1201
|
+
client: PerpsSDKClient,
|
|
1202
|
+
update: HlWsOrder
|
|
1203
|
+
): Promise<HlOrderDetail> {
|
|
1204
|
+
const basic = update.order
|
|
1205
|
+
const response = await infoRequest<HlOrderStatusResponse>(
|
|
1206
|
+
this.orderApiUrl,
|
|
1207
|
+
{ type: 'orderStatus', user, oid: basic.oid },
|
|
1208
|
+
hlInfoOptions(client)
|
|
1209
|
+
)
|
|
1210
|
+
if (response.status !== 'order') {
|
|
1211
|
+
throw new PerpsError(
|
|
1212
|
+
PerpsErrorCode.OrderNotFound,
|
|
1213
|
+
`Hyperliquid order metadata not found: ${basic.oid}`
|
|
1214
|
+
)
|
|
1215
|
+
}
|
|
1216
|
+
return {
|
|
1217
|
+
status: update.status,
|
|
1218
|
+
statusTimestamp: update.statusTimestamp,
|
|
1219
|
+
order: {
|
|
1220
|
+
...response.order.order,
|
|
1221
|
+
coin: basic.coin,
|
|
1222
|
+
side: basic.side,
|
|
1223
|
+
limitPx: basic.limitPx,
|
|
1224
|
+
sz: basic.sz,
|
|
1225
|
+
oid: basic.oid,
|
|
1226
|
+
timestamp: basic.timestamp,
|
|
1227
|
+
origSz: basic.origSz,
|
|
1228
|
+
cloid: basic.cloid ?? response.order.order.cloid,
|
|
1229
|
+
},
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1206
1233
|
private handleUserFills(data: HlWsUserFillsData) {
|
|
1207
1234
|
// The snapshot frame carries up to ~2000 historical fills and is replayed
|
|
1208
1235
|
// on every reconnect via `replaySubs`; emitting them would surface old
|
|
@@ -1308,6 +1335,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
1308
1335
|
}
|
|
1309
1336
|
}
|
|
1310
1337
|
try {
|
|
1338
|
+
await this.assetRegistry?.sync()
|
|
1311
1339
|
if (count === 0) {
|
|
1312
1340
|
await this.registerSub(wireKey, payload)
|
|
1313
1341
|
}
|
|
@@ -1552,15 +1580,10 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
1552
1580
|
const priceById = spotPriceById(markets, this.mergedMids())
|
|
1553
1581
|
const balances = data.spotState.balances
|
|
1554
1582
|
.filter((balance) => !assetIsOutcome(balance.coin))
|
|
1555
|
-
.
|
|
1556
|
-
balance,
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
'spotState.balances.total',
|
|
1560
|
-
this.providerKey
|
|
1561
|
-
),
|
|
1562
|
-
}))
|
|
1563
|
-
.filter(({ total }) => isDecimalStringGreaterThan(total, '0'))
|
|
1583
|
+
.filter(
|
|
1584
|
+
(balance) => safeIsDecimalStringGreaterThan(balance.total, '0') === true
|
|
1585
|
+
)
|
|
1586
|
+
.map((balance) => ({ balance, total: balance.total }))
|
|
1564
1587
|
const pipeline = this.unifiedSummaryByUser.get(user)
|
|
1565
1588
|
// Known spot markets await a price; unlisted tokens keep their unpriced balance.
|
|
1566
1589
|
if (
|
|
@@ -1574,21 +1597,22 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
1574
1597
|
}
|
|
1575
1598
|
return
|
|
1576
1599
|
}
|
|
1577
|
-
const rows = balances.map(({ balance, total }) =>
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1600
|
+
const rows = balances.map(({ balance, total }) => {
|
|
1601
|
+
const free = safeSubtractDecimalString(total, balance.hold)
|
|
1602
|
+
return {
|
|
1603
|
+
...spotBalance(
|
|
1604
|
+
spotAssetFromToken(
|
|
1605
|
+
balance,
|
|
1606
|
+
this.assetRegistry?.get(String(balance.token))
|
|
1607
|
+
),
|
|
1582
1608
|
total,
|
|
1583
|
-
|
|
1584
|
-
balance.hold,
|
|
1585
|
-
'spotState.balances.hold',
|
|
1586
|
-
this.providerKey
|
|
1587
|
-
)
|
|
1609
|
+
priceById
|
|
1588
1610
|
),
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1611
|
+
locked: balance.hold,
|
|
1612
|
+
transferable:
|
|
1613
|
+
free === undefined ? '0' : calculateTransferable(free, total),
|
|
1614
|
+
}
|
|
1615
|
+
})
|
|
1592
1616
|
this.emit(`spotState:${user}`, {
|
|
1593
1617
|
channel: 'spotBalances',
|
|
1594
1618
|
data: rows,
|
|
@@ -1776,27 +1800,38 @@ function mapSpotMarketContext(
|
|
|
1776
1800
|
const BOOK_LEVEL_ROW = 'order book level'
|
|
1777
1801
|
|
|
1778
1802
|
/** The venue price when it is a decimal string, or `undefined` after a logged skip. */
|
|
1779
|
-
function bookLevelPrice(
|
|
1803
|
+
function bookLevelPrice(
|
|
1804
|
+
marketId: string,
|
|
1805
|
+
price: string
|
|
1806
|
+
): DecimalString | undefined {
|
|
1780
1807
|
if (!isDecimalString(price)) {
|
|
1781
|
-
wsLog.skippedRow(PROVIDER_KEY, BOOK_LEVEL_ROW, 'price', price)
|
|
1808
|
+
wsLog.skippedRow(PROVIDER_KEY, BOOK_LEVEL_ROW, 'price', price, marketId)
|
|
1782
1809
|
return undefined
|
|
1783
1810
|
}
|
|
1784
1811
|
return price
|
|
1785
1812
|
}
|
|
1786
1813
|
|
|
1787
1814
|
/** The venue size when it is a decimal string, or `undefined` after a logged skip. */
|
|
1788
|
-
function bookLevelSize(
|
|
1815
|
+
function bookLevelSize(
|
|
1816
|
+
marketId: string,
|
|
1817
|
+
size: string
|
|
1818
|
+
): DecimalString | undefined {
|
|
1789
1819
|
if (!isDecimalString(size)) {
|
|
1790
|
-
wsLog.skippedRow(PROVIDER_KEY, BOOK_LEVEL_ROW, 'size', size)
|
|
1820
|
+
wsLog.skippedRow(PROVIDER_KEY, BOOK_LEVEL_ROW, 'size', size, marketId)
|
|
1791
1821
|
return undefined
|
|
1792
1822
|
}
|
|
1793
1823
|
return size
|
|
1794
1824
|
}
|
|
1795
1825
|
|
|
1796
1826
|
/** One order-book level, or none when the venue price or size is invalid. */
|
|
1797
|
-
function toOrderbookLevel(
|
|
1798
|
-
|
|
1799
|
-
|
|
1827
|
+
function toOrderbookLevel(
|
|
1828
|
+
marketId: string,
|
|
1829
|
+
price: string,
|
|
1830
|
+
size: string
|
|
1831
|
+
): OrderbookLevel[] {
|
|
1832
|
+
const levelPrice = bookLevelPrice(marketId, price)
|
|
1833
|
+
const levelSize =
|
|
1834
|
+
levelPrice === undefined ? undefined : bookLevelSize(marketId, size)
|
|
1800
1835
|
return levelPrice === undefined || levelSize === undefined
|
|
1801
1836
|
? []
|
|
1802
1837
|
: [{ price: levelPrice, size: levelSize }]
|
|
@@ -1813,6 +1848,7 @@ function compactRemovalPrice(
|
|
|
1813
1848
|
}
|
|
1814
1849
|
|
|
1815
1850
|
function applyCompressedL2Side(
|
|
1851
|
+
marketId: string,
|
|
1816
1852
|
previous: OrderbookLevel[],
|
|
1817
1853
|
updates: HlWsCompressedL2Data['l'][number],
|
|
1818
1854
|
removals: NonNullable<HlWsCompressedL2Data['r']>[number],
|
|
@@ -1826,11 +1862,11 @@ function applyCompressedL2Side(
|
|
|
1826
1862
|
}
|
|
1827
1863
|
}
|
|
1828
1864
|
for (const update of updates) {
|
|
1829
|
-
const price = bookLevelPrice(update.p)
|
|
1865
|
+
const price = bookLevelPrice(marketId, update.p)
|
|
1830
1866
|
if (price === undefined) {
|
|
1831
1867
|
continue
|
|
1832
1868
|
}
|
|
1833
|
-
const size = bookLevelSize(update.s)
|
|
1869
|
+
const size = bookLevelSize(marketId, update.s)
|
|
1834
1870
|
// A level whose new size is unknown must not keep its old size.
|
|
1835
1871
|
if (size === undefined || safeIsDecimalStringZero(size) !== false) {
|
|
1836
1872
|
byPrice.delete(price)
|