@lifi/perps-sdk-provider-hyperliquid 12.0.0 → 13.0.0
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/HyperliquidProvider.d.ts.map +1 -1
- package/dist/cjs/HyperliquidProvider.js +2 -0
- package/dist/cjs/HyperliquidProvider.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/services/getAccount.d.ts.map +1 -1
- package/dist/cjs/services/getAccount.js +9 -6
- package/dist/cjs/services/getAccount.js.map +1 -1
- package/dist/cjs/services/getPortfolioHistory.d.ts +10 -0
- package/dist/cjs/services/getPortfolioHistory.d.ts.map +1 -0
- package/dist/cjs/services/getPortfolioHistory.js +59 -0
- package/dist/cjs/services/getPortfolioHistory.js.map +1 -0
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +1 -0
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/portfolio.d.ts +9 -0
- package/dist/cjs/types/portfolio.d.ts.map +1 -0
- package/dist/cjs/types/portfolio.js +3 -0
- package/dist/cjs/types/portfolio.js.map +1 -0
- package/dist/cjs/utils/abstractionMode.d.ts +3 -0
- package/dist/cjs/utils/abstractionMode.d.ts.map +1 -0
- package/dist/cjs/utils/abstractionMode.js +8 -0
- package/dist/cjs/utils/abstractionMode.js.map +1 -0
- package/dist/cjs/utils/decimal.d.ts +3 -0
- package/dist/cjs/utils/decimal.d.ts.map +1 -0
- package/dist/cjs/utils/decimal.js +19 -0
- package/dist/cjs/utils/decimal.js.map +1 -0
- package/dist/cjs/utils/mapMarketContext.d.ts +2 -2
- package/dist/cjs/utils/mapMarketContext.d.ts.map +1 -1
- package/dist/cjs/utils/mapMarketContext.js +21 -4
- package/dist/cjs/utils/mapMarketContext.js.map +1 -1
- package/dist/cjs/utils/spotCollateral.d.ts.map +1 -1
- package/dist/cjs/utils/spotCollateral.js +4 -0
- package/dist/cjs/utils/spotCollateral.js.map +1 -1
- package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/HyperliquidWsProvider.js +8 -10
- package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
- package/dist/esm/HyperliquidProvider.d.ts.map +1 -1
- package/dist/esm/HyperliquidProvider.js +2 -0
- package/dist/esm/HyperliquidProvider.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/services/getAccount.d.ts.map +1 -1
- package/dist/esm/services/getAccount.js +9 -6
- package/dist/esm/services/getAccount.js.map +1 -1
- package/dist/esm/services/getPortfolioHistory.d.ts +18 -0
- package/dist/esm/services/getPortfolioHistory.d.ts.map +1 -0
- package/dist/esm/services/getPortfolioHistory.js +71 -0
- package/dist/esm/services/getPortfolioHistory.js.map +1 -0
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/types/index.js.map +1 -1
- package/dist/esm/types/portfolio.d.ts +17 -0
- package/dist/esm/types/portfolio.d.ts.map +1 -0
- package/dist/esm/types/portfolio.js +3 -0
- package/dist/esm/types/portfolio.js.map +1 -0
- package/dist/esm/utils/abstractionMode.d.ts +8 -0
- package/dist/esm/utils/abstractionMode.d.ts.map +1 -0
- package/dist/esm/utils/abstractionMode.js +9 -0
- package/dist/esm/utils/abstractionMode.js.map +1 -0
- package/dist/esm/utils/decimal.d.ts +10 -0
- package/dist/esm/utils/decimal.d.ts.map +1 -0
- package/dist/esm/utils/decimal.js +19 -0
- package/dist/esm/utils/decimal.js.map +1 -0
- package/dist/esm/utils/mapMarketContext.d.ts +7 -4
- package/dist/esm/utils/mapMarketContext.d.ts.map +1 -1
- package/dist/esm/utils/mapMarketContext.js +27 -6
- package/dist/esm/utils/mapMarketContext.js.map +1 -1
- package/dist/esm/utils/spotCollateral.d.ts.map +1 -1
- package/dist/esm/utils/spotCollateral.js +4 -0
- package/dist/esm/utils/spotCollateral.js.map +1 -1
- package/dist/esm/websocket/HyperliquidWsProvider.d.ts +1 -1
- package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/HyperliquidWsProvider.js +9 -11
- package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
- package/dist/types/HyperliquidProvider.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/services/getAccount.d.ts.map +1 -1
- package/dist/types/services/getPortfolioHistory.d.ts +18 -0
- package/dist/types/services/getPortfolioHistory.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/types/portfolio.d.ts +17 -0
- package/dist/types/types/portfolio.d.ts.map +1 -0
- package/dist/types/utils/abstractionMode.d.ts +8 -0
- package/dist/types/utils/abstractionMode.d.ts.map +1 -0
- package/dist/types/utils/decimal.d.ts +10 -0
- package/dist/types/utils/decimal.d.ts.map +1 -0
- package/dist/types/utils/mapMarketContext.d.ts +7 -4
- package/dist/types/utils/mapMarketContext.d.ts.map +1 -1
- package/dist/types/utils/spotCollateral.d.ts.map +1 -1
- package/dist/types/websocket/HyperliquidWsProvider.d.ts +1 -1
- package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/HyperliquidProvider.ts +13 -0
- package/src/index.ts +2 -0
- package/src/services/getAccount.ts +11 -9
- package/src/services/getPortfolioHistory.ts +118 -0
- package/src/types/index.ts +1 -0
- package/src/types/portfolio.ts +29 -0
- package/src/utils/abstractionMode.ts +12 -0
- package/src/utils/decimal.ts +21 -0
- package/src/utils/mapMarketContext.ts +32 -7
- package/src/utils/spotCollateral.ts +4 -0
- package/src/websocket/HyperliquidWsProvider.ts +22 -15
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type ProviderGetMarketSettingsParams,
|
|
15
15
|
type ProviderGetOrderParams,
|
|
16
16
|
type ProviderGetOrdersParams,
|
|
17
|
+
type ProviderGetPortfolioHistoryParams,
|
|
17
18
|
type ProviderGetPositionsParams,
|
|
18
19
|
type ProviderGetQuoteParams,
|
|
19
20
|
type ProviderGetRunningTwapsParams,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
PerpsErrorCode,
|
|
39
40
|
type PerpsMarket,
|
|
40
41
|
PerpsSigner,
|
|
42
|
+
type PortfolioHistoryResponse,
|
|
41
43
|
type Position,
|
|
42
44
|
type PositionsResponse,
|
|
43
45
|
type ProviderAction,
|
|
@@ -63,6 +65,7 @@ import { getFills } from './services/getFills.js'
|
|
|
63
65
|
import { getMarketSettings } from './services/getMarketSettings.js'
|
|
64
66
|
import { getOrder } from './services/getOrder.js'
|
|
65
67
|
import { getOrders } from './services/getOrders.js'
|
|
68
|
+
import { getPortfolioHistory } from './services/getPortfolioHistory.js'
|
|
66
69
|
import { getPositions } from './services/getPositions.js'
|
|
67
70
|
import { getRunningTwaps } from './services/getRunningTwaps.js'
|
|
68
71
|
import {
|
|
@@ -387,6 +390,16 @@ export function hyperliquidProvider(
|
|
|
387
390
|
opts
|
|
388
391
|
),
|
|
389
392
|
|
|
393
|
+
getPortfolioHistory: (
|
|
394
|
+
params: ProviderGetPortfolioHistoryParams,
|
|
395
|
+
opts?: SDKRequestOptions
|
|
396
|
+
): Promise<PortfolioHistoryResponse> =>
|
|
397
|
+
getPortfolioHistory(
|
|
398
|
+
contextRef.require(),
|
|
399
|
+
{ address: params.address, range: params.range },
|
|
400
|
+
opts
|
|
401
|
+
),
|
|
402
|
+
|
|
390
403
|
getQuote: (
|
|
391
404
|
params: ProviderGetQuoteParams,
|
|
392
405
|
opts?: SDKRequestOptions
|
package/src/index.ts
CHANGED
|
@@ -35,6 +35,8 @@ export type { GetOrderParams } from './services/getOrder.js'
|
|
|
35
35
|
export { getOrder } from './services/getOrder.js'
|
|
36
36
|
export type { GetOrdersParams } from './services/getOrders.js'
|
|
37
37
|
export { getOrders } from './services/getOrders.js'
|
|
38
|
+
export type { GetPortfolioHistoryParams } from './services/getPortfolioHistory.js'
|
|
39
|
+
export { getPortfolioHistory } from './services/getPortfolioHistory.js'
|
|
38
40
|
export type { GetPositionsParams } from './services/getPositions.js'
|
|
39
41
|
export { getPositions } from './services/getPositions.js'
|
|
40
42
|
export type { GetRunningTwapsParams } from './services/getRunningTwaps.js'
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
HyperliquidAccountConfig,
|
|
14
14
|
Position,
|
|
15
15
|
} from '@lifi/perps-types'
|
|
16
|
+
import type Big from 'big.js'
|
|
16
17
|
import { PROVIDER_KEY } from '../constants.js'
|
|
17
18
|
import type { HyperliquidContext } from '../context.js'
|
|
18
19
|
import {
|
|
@@ -22,6 +23,8 @@ import {
|
|
|
22
23
|
type HlSpotClearinghouseState,
|
|
23
24
|
type HlUserFees,
|
|
24
25
|
} from '../types/index.js'
|
|
26
|
+
import { isUnifiedAbstraction } from '../utils/abstractionMode.js'
|
|
27
|
+
import { toWireBig } from '../utils/decimal.js'
|
|
25
28
|
import {
|
|
26
29
|
partitionSpotBalances,
|
|
27
30
|
perpsDexNames,
|
|
@@ -42,8 +45,8 @@ export type GetAccountParams = ProviderGetAccountParams
|
|
|
42
45
|
// `marginSummary` covers the whole account (cross AND isolated positions);
|
|
43
46
|
// `crossMarginSummary` is the cross-only subset and would drop isolated
|
|
44
47
|
// equity/margin.
|
|
45
|
-
const getAccountValue = (state: HlClearinghouseState):
|
|
46
|
-
|
|
48
|
+
const getAccountValue = (state: HlClearinghouseState): Big =>
|
|
49
|
+
toWireBig(state.marginSummary.accountValue, 'marginSummary.accountValue')
|
|
47
50
|
|
|
48
51
|
const getTotalMarginUsed = (state: HlClearinghouseState): number =>
|
|
49
52
|
Number.parseFloat(state.marginSummary.totalMarginUsed)
|
|
@@ -81,10 +84,6 @@ const getMarginUsed = (
|
|
|
81
84
|
return getTotalMarginUsed(mainState).toString()
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
const isUnifiedMode = (abstraction: HlAbstractionMode | null): boolean =>
|
|
85
|
-
abstraction === HlAbstractionMode.UNIFIED_ACCOUNT ||
|
|
86
|
-
abstraction === HlAbstractionMode.PORTFOLIO_MARGIN
|
|
87
|
-
|
|
88
87
|
interface BalancePartition {
|
|
89
88
|
balances: Balance[]
|
|
90
89
|
collateralBalances: Balance[]
|
|
@@ -110,17 +109,20 @@ const buildBalances = (
|
|
|
110
109
|
// double-count. Only disabled/dexAbstraction carry separate venue collateral.
|
|
111
110
|
// `accountValue` is the dex's TOTAL equity: locked margin and unrealized
|
|
112
111
|
// PnL are already included, so summaries must not add them on top.
|
|
113
|
-
if (!
|
|
112
|
+
if (!isUnifiedAbstraction(abstraction)) {
|
|
114
113
|
for (const [dex, state] of stateByDex) {
|
|
115
114
|
const categoryId = dex || PROVIDER_KEY
|
|
116
115
|
const value = getAccountValue(state)
|
|
116
|
+
if (!value.gt(0)) {
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
117
119
|
collateralBalances.push({
|
|
118
120
|
categoryId,
|
|
119
121
|
// Always present: every dex in `stateByDex` derives from `markets`,
|
|
120
122
|
// which is what populates `quoteAssetByCategory`.
|
|
121
123
|
asset: quoteAssetByCategory.get(categoryId)!,
|
|
122
|
-
units: value.
|
|
123
|
-
valueUsd: value.
|
|
124
|
+
units: value.toFixed(),
|
|
125
|
+
valueUsd: value.toFixed(),
|
|
124
126
|
})
|
|
125
127
|
}
|
|
126
128
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { PerpsError, type SDKRequestOptions } from '@lifi/perps-sdk'
|
|
2
|
+
import {
|
|
3
|
+
PerpsErrorCode,
|
|
4
|
+
type PortfolioHistoryPoint,
|
|
5
|
+
type PortfolioHistoryRange,
|
|
6
|
+
type PortfolioHistoryResponse,
|
|
7
|
+
} from '@lifi/perps-types'
|
|
8
|
+
import type { Address } from 'viem'
|
|
9
|
+
import type { HyperliquidContext } from '../context.js'
|
|
10
|
+
import type {
|
|
11
|
+
HlAbstractionMode,
|
|
12
|
+
HlPortfolio,
|
|
13
|
+
HlPortfolioPeriod,
|
|
14
|
+
HlPortfolioWindow,
|
|
15
|
+
} from '../types/index.js'
|
|
16
|
+
import { isUnifiedAbstraction } from '../utils/abstractionMode.js'
|
|
17
|
+
import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
|
|
18
|
+
|
|
19
|
+
/** Parameters for {@link getPortfolioHistory}. @public */
|
|
20
|
+
export interface GetPortfolioHistoryParams {
|
|
21
|
+
address: Address
|
|
22
|
+
range: PortfolioHistoryRange
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const COMBINED_PERIOD_BY_RANGE: Record<
|
|
26
|
+
PortfolioHistoryRange,
|
|
27
|
+
HlPortfolioPeriod
|
|
28
|
+
> = {
|
|
29
|
+
'24h': 'day',
|
|
30
|
+
'7d': 'week',
|
|
31
|
+
'30d': 'month',
|
|
32
|
+
all: 'allTime',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const PERP_PERIOD_BY_RANGE: Record<PortfolioHistoryRange, HlPortfolioPeriod> = {
|
|
36
|
+
'24h': 'perpDay',
|
|
37
|
+
'7d': 'perpWeek',
|
|
38
|
+
'30d': 'perpMonth',
|
|
39
|
+
all: 'perpAllTime',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Join the window's two series on their timestamps. `pnlHistory` is cumulative
|
|
44
|
+
* from the window's start, so a point with no PnL sample carries the last
|
|
45
|
+
* sampled value — `'0'` before the first one.
|
|
46
|
+
*/
|
|
47
|
+
const joinWindow = (
|
|
48
|
+
window: HlPortfolioWindow,
|
|
49
|
+
period: HlPortfolioPeriod
|
|
50
|
+
): PortfolioHistoryPoint[] => {
|
|
51
|
+
const pnlByTimestamp = new Map(window.pnlHistory)
|
|
52
|
+
let matched = 0
|
|
53
|
+
let cumulative = '0'
|
|
54
|
+
const points = window.accountValueHistory.map(([timestamp, accountValue]) => {
|
|
55
|
+
const sample = pnlByTimestamp.get(timestamp)
|
|
56
|
+
if (sample !== undefined) {
|
|
57
|
+
matched += 1
|
|
58
|
+
cumulative = sample
|
|
59
|
+
}
|
|
60
|
+
return { timestamp, accountValue, pnl: cumulative }
|
|
61
|
+
})
|
|
62
|
+
if (points.length > 0 && window.pnlHistory.length > 0 && matched === 0) {
|
|
63
|
+
throw new PerpsError(
|
|
64
|
+
PerpsErrorCode.ThirdPartyError,
|
|
65
|
+
`Hyperliquid portfolio window '${period}' samples PnL on no ` +
|
|
66
|
+
'account-value timestamp.'
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
return points
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read the account's portfolio value and cumulative PnL for `params.range`
|
|
74
|
+
* from one `portfolio` info request. An account whose collateral lives
|
|
75
|
+
* per-dex reads the perps-only window; a unified or portfolio-margin account
|
|
76
|
+
* holds its collateral in spot, so it reads the combined window.
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
export async function getPortfolioHistory(
|
|
80
|
+
ctx: HyperliquidContext,
|
|
81
|
+
params: GetPortfolioHistoryParams,
|
|
82
|
+
options?: SDKRequestOptions
|
|
83
|
+
): Promise<PortfolioHistoryResponse> {
|
|
84
|
+
const { client, apiUrl } = ctx
|
|
85
|
+
const infoOpts = hlInfoOptions(client, options)
|
|
86
|
+
const [portfolio, abstraction] = await Promise.all([
|
|
87
|
+
infoRequest<HlPortfolio>(
|
|
88
|
+
apiUrl,
|
|
89
|
+
{ type: 'portfolio', user: params.address },
|
|
90
|
+
infoOpts
|
|
91
|
+
),
|
|
92
|
+
// "Never set abstraction" is a successful 200 `null` body, not an error —
|
|
93
|
+
// so a fetch failure must propagate, never be coerced to `null` (which
|
|
94
|
+
// would silently read the combined window for a perps-only account).
|
|
95
|
+
infoRequest<HlAbstractionMode | null>(
|
|
96
|
+
apiUrl,
|
|
97
|
+
{ type: 'userAbstraction', user: params.address },
|
|
98
|
+
infoOpts
|
|
99
|
+
),
|
|
100
|
+
])
|
|
101
|
+
const period = isUnifiedAbstraction(abstraction)
|
|
102
|
+
? COMBINED_PERIOD_BY_RANGE[params.range]
|
|
103
|
+
: PERP_PERIOD_BY_RANGE[params.range]
|
|
104
|
+
const window = portfolio.find(([key]) => key === period)?.[1]
|
|
105
|
+
if (window === undefined) {
|
|
106
|
+
throw new PerpsError(
|
|
107
|
+
PerpsErrorCode.ThirdPartyError,
|
|
108
|
+
`Hyperliquid portfolio response has no '${period}' window.`
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
range: params.range,
|
|
114
|
+
points: joinWindow(window, period),
|
|
115
|
+
volume: window.vlm,
|
|
116
|
+
totalPnl: window.pnlHistory.at(-1)?.[1],
|
|
117
|
+
}
|
|
118
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// `portfolio` info response shapes.
|
|
2
|
+
|
|
3
|
+
/** `[Unix milliseconds, decimal string]` sample of one portfolio series. @public */
|
|
4
|
+
export type HlPortfolioSample = [number, string]
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One window of the Hyperliquid `portfolio` info response. `vlm` is the
|
|
8
|
+
* traded volume in the window; `pnlHistory` is cumulative from its start.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export type HlPortfolioWindow = {
|
|
12
|
+
accountValueHistory: HlPortfolioSample[]
|
|
13
|
+
pnlHistory: HlPortfolioSample[]
|
|
14
|
+
vlm: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Hyperliquid `portfolio` window keys. @public */
|
|
18
|
+
export type HlPortfolioPeriod =
|
|
19
|
+
| 'day'
|
|
20
|
+
| 'week'
|
|
21
|
+
| 'month'
|
|
22
|
+
| 'allTime'
|
|
23
|
+
| 'perpDay'
|
|
24
|
+
| 'perpWeek'
|
|
25
|
+
| 'perpMonth'
|
|
26
|
+
| 'perpAllTime'
|
|
27
|
+
|
|
28
|
+
/** Hyperliquid `portfolio` info response: a list of `[period, window]` pairs. @public */
|
|
29
|
+
export type HlPortfolio = [HlPortfolioPeriod, HlPortfolioWindow][]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { HlAbstractionMode } from '../types/account.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether the mode holds the account's collateral in spot. Unified and
|
|
5
|
+
* portfolio-margin accounts do; every other mode keeps perps collateral
|
|
6
|
+
* per-dex, apart from the spot balances.
|
|
7
|
+
*/
|
|
8
|
+
export const isUnifiedAbstraction = (
|
|
9
|
+
abstraction: HlAbstractionMode | null
|
|
10
|
+
): boolean =>
|
|
11
|
+
abstraction === HlAbstractionMode.UNIFIED_ACCOUNT ||
|
|
12
|
+
abstraction === HlAbstractionMode.PORTFOLIO_MARGIN
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PerpsError } from '@lifi/perps-sdk'
|
|
2
|
+
import { PerpsErrorCode } from '@lifi/perps-types'
|
|
3
|
+
import Big from 'big.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Exact-decimal parse of a Hyperliquid wire decimal string. `field` names the
|
|
7
|
+
* offending field on failure, so a venue contract breach is diagnosable
|
|
8
|
+
* instead of surfacing as an anonymous big.js throw or an `NaN` unit.
|
|
9
|
+
*
|
|
10
|
+
* @throws {PerpsError} `SDKError` when `value` is not a decimal numeric string.
|
|
11
|
+
*/
|
|
12
|
+
export const toWireBig = (value: string, field: string): Big => {
|
|
13
|
+
try {
|
|
14
|
+
return new Big(value)
|
|
15
|
+
} catch {
|
|
16
|
+
throw new PerpsError(
|
|
17
|
+
PerpsErrorCode.SDKError,
|
|
18
|
+
`Hyperliquid field \`${field}\` is not a valid decimal: '${value}'`
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,30 +1,55 @@
|
|
|
1
1
|
import type { MarketContext } from '@lifi/perps-types'
|
|
2
|
-
import
|
|
2
|
+
import Big from 'big.js'
|
|
3
|
+
import type { HlWsFastAssetCtx, HlWsPerpAssetCtx } from '../types/index.js'
|
|
3
4
|
|
|
4
5
|
const NEXT_FUNDING_INTERVAL_MS = 60 * 60 * 1000 // 1 hour
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Convert venue base-unit open interest to a quote-asset notional. A mark that
|
|
9
|
+
* is not a positive number leaves the notional unknown rather than zero.
|
|
10
|
+
*/
|
|
11
|
+
const toOpenInterestNotional = (
|
|
12
|
+
openInterest: string,
|
|
13
|
+
markPrice: string
|
|
14
|
+
): string | undefined => {
|
|
15
|
+
try {
|
|
16
|
+
const mark = new Big(markPrice)
|
|
17
|
+
if (mark.lte(0)) {
|
|
18
|
+
return undefined
|
|
19
|
+
}
|
|
20
|
+
return new Big(openInterest).times(mark).toFixed()
|
|
21
|
+
} catch {
|
|
22
|
+
return undefined
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
6
26
|
/**
|
|
7
27
|
* Map a Hyperliquid perp asset context from the `allDexsAssetCtxs` feed to a
|
|
8
|
-
* {@link MarketContext}
|
|
9
|
-
*
|
|
28
|
+
* {@link MarketContext}, with mid and mark overlaid from a `fastAssetCtxs`
|
|
29
|
+
* context where one is given. `midPx` is null when the book is empty; mid then
|
|
30
|
+
* falls back to the mark so the context always carries a usable mid. The
|
|
31
|
+
* open-interest notional follows the emitted mark, so it never mixes the two
|
|
32
|
+
* feeds' prices.
|
|
10
33
|
* @public
|
|
11
34
|
*/
|
|
12
35
|
export const mapMarketContext = (
|
|
13
36
|
marketId: string,
|
|
14
|
-
ctx: HlWsPerpAssetCtx
|
|
37
|
+
ctx: HlWsPerpAssetCtx,
|
|
38
|
+
fast?: HlWsFastAssetCtx
|
|
15
39
|
): MarketContext => {
|
|
16
40
|
const now = Date.now()
|
|
17
41
|
const nextFundingTime =
|
|
18
42
|
Math.ceil(now / NEXT_FUNDING_INTERVAL_MS) * NEXT_FUNDING_INTERVAL_MS
|
|
43
|
+
const markPrice = fast?.markPx != null ? fast.markPx : ctx.markPx
|
|
19
44
|
|
|
20
45
|
return {
|
|
21
46
|
marketId,
|
|
22
|
-
midPrice: ctx.midPx ?? ctx.markPx,
|
|
23
|
-
markPrice
|
|
47
|
+
midPrice: fast?.midPx != null ? fast.midPx : (ctx.midPx ?? ctx.markPx),
|
|
48
|
+
markPrice,
|
|
24
49
|
oraclePrice: ctx.oraclePx,
|
|
25
50
|
prevDayPrice: ctx.prevDayPx,
|
|
26
51
|
volume24h: ctx.dayNtlVlm,
|
|
27
|
-
openInterest: ctx.openInterest,
|
|
52
|
+
openInterest: toOpenInterestNotional(ctx.openInterest, markPrice),
|
|
28
53
|
funding: {
|
|
29
54
|
rate: ctx.funding,
|
|
30
55
|
nextFundingTime,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Balance } from '@lifi/perps-types'
|
|
2
|
+
import { toWireBig } from './decimal.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Portfolio-margin collateral beyond the category quote assets, keyed by spot
|
|
@@ -37,6 +38,9 @@ export const partitionSpotBalances = (
|
|
|
37
38
|
const collateralBalances: Balance[] = []
|
|
38
39
|
const balances: Balance[] = []
|
|
39
40
|
for (const balance of spotBalances) {
|
|
41
|
+
if (!toWireBig(balance.units, 'spotBalance.units').gt(0)) {
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
40
44
|
if (quoteAssetIds.has(balance.asset.id)) {
|
|
41
45
|
collateralBalances.push(balance)
|
|
42
46
|
continue
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
toPerpsMarketDisplay,
|
|
14
14
|
WsProviderBase,
|
|
15
15
|
type WsProviderFactory,
|
|
16
|
+
type WsProviderFactoryParams,
|
|
16
17
|
type WsStatusListener,
|
|
17
18
|
wsLog,
|
|
18
19
|
} from '@lifi/perps-sdk'
|
|
@@ -58,6 +59,7 @@ import type {
|
|
|
58
59
|
HlWsUserFillsData,
|
|
59
60
|
} from '../types/index.js'
|
|
60
61
|
import { HlAbstractionMode } from '../types/index.js'
|
|
62
|
+
import { toWireBig } from '../utils/decimal.js'
|
|
61
63
|
import {
|
|
62
64
|
decodeCompressedJson,
|
|
63
65
|
decodeFastAssetCtxs,
|
|
@@ -94,13 +96,15 @@ const normalizeHlAddress = (address: string): string =>
|
|
|
94
96
|
*
|
|
95
97
|
* @public
|
|
96
98
|
*/
|
|
97
|
-
export const hyperliquidWsProvider =
|
|
98
|
-
(
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
export const hyperliquidWsProvider = (): WsProviderFactory =>
|
|
100
|
+
Object.assign(
|
|
101
|
+
({ provider, wsUrl, client }: WsProviderFactoryParams) =>
|
|
102
|
+
new HyperliquidWsProvider(wsUrl, provider, client),
|
|
103
|
+
{ streamsCandles: true }
|
|
104
|
+
)
|
|
101
105
|
|
|
102
106
|
/**
|
|
103
|
-
* Hyperliquid
|
|
107
|
+
* Hyperliquid WebSocket {@link WsProvider}: multiplexes markets context,
|
|
104
108
|
* positions, orders, fills and spot balances over a single
|
|
105
109
|
* {@link ReconnectingWebSocket}.
|
|
106
110
|
* Construct via {@link hyperliquidWsProvider}.
|
|
@@ -786,12 +790,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
786
790
|
perpCtx = byMarketId[marketId] ?? perpCtx
|
|
787
791
|
}
|
|
788
792
|
if (perpCtx !== undefined) {
|
|
789
|
-
|
|
790
|
-
return {
|
|
791
|
-
...base,
|
|
792
|
-
midPrice: fast?.midPx != null ? fast.midPx : base.midPrice,
|
|
793
|
-
markPrice: fast?.markPx != null ? fast.markPx : base.markPrice,
|
|
794
|
-
}
|
|
793
|
+
return mapMarketContext(marketId, perpCtx, fast)
|
|
795
794
|
}
|
|
796
795
|
const midPrice = fast?.midPx ?? fast?.markPx
|
|
797
796
|
const markPrice = fast?.markPx ?? fast?.midPx
|
|
@@ -1254,10 +1253,18 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
1254
1253
|
const user = data.user.toLowerCase()
|
|
1255
1254
|
const markets = this.registry?.activeMarkets ?? []
|
|
1256
1255
|
const priceById = spotPriceById(markets, this.mergedMids())
|
|
1257
|
-
const rows = data.spotState.balances
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1256
|
+
const rows = data.spotState.balances
|
|
1257
|
+
.filter((balance) =>
|
|
1258
|
+
toWireBig(balance.total, 'spotState.balances.total').gt(0)
|
|
1259
|
+
)
|
|
1260
|
+
.map((balance) => ({
|
|
1261
|
+
balance: spotBalance(
|
|
1262
|
+
spotAssetFromToken(balance),
|
|
1263
|
+
balance.total,
|
|
1264
|
+
priceById
|
|
1265
|
+
),
|
|
1266
|
+
hold: balance.hold,
|
|
1267
|
+
}))
|
|
1261
1268
|
this.emit(`spotState:${user}`, {
|
|
1262
1269
|
channel: 'spotBalances',
|
|
1263
1270
|
data: rows.map(({ balance, hold }) => ({ ...balance, locked: hold })),
|