@lifi/perps-sdk-provider-hyperliquid 20.0.0 → 21.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/README.md +45 -0
- package/dist/cjs/HyperliquidProvider.d.ts.map +1 -1
- package/dist/cjs/HyperliquidProvider.js +2 -2
- package/dist/cjs/HyperliquidProvider.js.map +1 -1
- package/dist/cjs/services/getAccount.d.ts.map +1 -1
- package/dist/cjs/services/getAccount.js +5 -7
- package/dist/cjs/services/getAccount.js.map +1 -1
- package/dist/cjs/services/getActivity.d.ts.map +1 -1
- package/dist/cjs/services/getActivity.js +68 -44
- package/dist/cjs/services/getActivity.js.map +1 -1
- package/dist/cjs/services/getFills.d.ts.map +1 -1
- package/dist/cjs/services/getFills.js +30 -38
- package/dist/cjs/services/getFills.js.map +1 -1
- package/dist/cjs/services/getWithdrawableBalances.js +1 -1
- package/dist/cjs/services/getWithdrawableBalances.js.map +1 -1
- package/dist/cjs/services/historyPagination.d.ts +22 -0
- package/dist/cjs/services/historyPagination.d.ts.map +1 -0
- package/dist/cjs/services/historyPagination.js +125 -0
- package/dist/cjs/services/historyPagination.js.map +1 -0
- 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 -3
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/liquidation.js +1 -1
- package/dist/cjs/utils/liquidation.js.map +1 -1
- package/dist/cjs/utils/orderFormatting.d.ts +3 -2
- package/dist/cjs/utils/orderFormatting.d.ts.map +1 -1
- package/dist/cjs/utils/orderFormatting.js +4 -4
- package/dist/cjs/utils/orderFormatting.js.map +1 -1
- package/dist/cjs/utils/spotBalance.d.ts.map +1 -1
- package/dist/cjs/utils/spotBalance.js +2 -2
- package/dist/cjs/utils/spotBalance.js.map +1 -1
- package/dist/cjs/utils/withdrawableBalances.d.ts +1 -1
- package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
- package/dist/cjs/utils/withdrawableBalances.js +7 -4
- package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
- package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +6 -0
- package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/HyperliquidWsProvider.js +135 -37
- package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
- package/dist/esm/HyperliquidProvider.d.ts.map +1 -1
- package/dist/esm/HyperliquidProvider.js +3 -3
- package/dist/esm/HyperliquidProvider.js.map +1 -1
- package/dist/esm/services/getAccount.d.ts.map +1 -1
- package/dist/esm/services/getAccount.js +6 -5
- package/dist/esm/services/getAccount.js.map +1 -1
- package/dist/esm/services/getActivity.d.ts +3 -13
- package/dist/esm/services/getActivity.d.ts.map +1 -1
- package/dist/esm/services/getActivity.js +74 -64
- package/dist/esm/services/getActivity.js.map +1 -1
- package/dist/esm/services/getFills.d.ts +2 -14
- package/dist/esm/services/getFills.d.ts.map +1 -1
- package/dist/esm/services/getFills.js +32 -58
- package/dist/esm/services/getFills.js.map +1 -1
- package/dist/esm/services/getWithdrawableBalances.js +1 -1
- package/dist/esm/services/getWithdrawableBalances.js.map +1 -1
- package/dist/esm/services/historyPagination.d.ts +22 -0
- package/dist/esm/services/historyPagination.d.ts.map +1 -0
- package/dist/esm/services/historyPagination.js +121 -0
- package/dist/esm/services/historyPagination.js.map +1 -0
- 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.js +2 -2
- package/dist/esm/utils/liquidation.js.map +1 -1
- package/dist/esm/utils/orderFormatting.d.ts +14 -12
- package/dist/esm/utils/orderFormatting.d.ts.map +1 -1
- package/dist/esm/utils/orderFormatting.js +14 -14
- package/dist/esm/utils/orderFormatting.js.map +1 -1
- package/dist/esm/utils/spotBalance.d.ts.map +1 -1
- package/dist/esm/utils/spotBalance.js +3 -3
- package/dist/esm/utils/spotBalance.js.map +1 -1
- package/dist/esm/utils/withdrawableBalances.d.ts +1 -1
- package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
- package/dist/esm/utils/withdrawableBalances.js +7 -4
- package/dist/esm/utils/withdrawableBalances.js.map +1 -1
- package/dist/esm/websocket/HyperliquidWsProvider.d.ts +6 -0
- package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/HyperliquidWsProvider.js +138 -50
- package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
- package/dist/types/HyperliquidProvider.d.ts.map +1 -1
- package/dist/types/services/getAccount.d.ts.map +1 -1
- package/dist/types/services/getActivity.d.ts +3 -13
- package/dist/types/services/getActivity.d.ts.map +1 -1
- package/dist/types/services/getFills.d.ts +2 -14
- package/dist/types/services/getFills.d.ts.map +1 -1
- package/dist/types/services/historyPagination.d.ts +22 -0
- package/dist/types/services/historyPagination.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +1 -1
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/orderFormatting.d.ts +14 -12
- package/dist/types/utils/orderFormatting.d.ts.map +1 -1
- package/dist/types/utils/spotBalance.d.ts.map +1 -1
- package/dist/types/utils/withdrawableBalances.d.ts +1 -1
- package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
- package/dist/types/websocket/HyperliquidWsProvider.d.ts +6 -0
- package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/HyperliquidProvider.ts +6 -5
- package/src/services/getAccount.ts +18 -18
- package/src/services/getActivity.ts +133 -104
- package/src/services/getFills.ts +42 -95
- package/src/services/getWithdrawableBalances.ts +1 -1
- package/src/services/historyPagination.ts +169 -0
- package/src/utils/index.ts +2 -2
- package/src/utils/liquidation.ts +2 -2
- package/src/utils/orderFormatting.ts +20 -16
- package/src/utils/spotBalance.ts +5 -3
- package/src/utils/withdrawableBalances.ts +12 -7
- package/src/websocket/HyperliquidWsProvider.ts +151 -63
package/src/services/getFills.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { getMarketRegistry, type SDKRequestOptions } from '@lifi/perps-sdk'
|
|
2
2
|
import type { Fill, FillsResponse } from '@lifi/perps-types'
|
|
3
3
|
import type { Address } from 'viem'
|
|
4
|
-
import {
|
|
5
|
-
DEFAULT_HISTORY_LIMIT,
|
|
6
|
-
MAX_HISTORY_LIMIT,
|
|
7
|
-
PROVIDER_KEY,
|
|
8
|
-
} from '../constants.js'
|
|
4
|
+
import { PROVIDER_KEY } from '../constants.js'
|
|
9
5
|
import type { HyperliquidContext } from '../context.js'
|
|
10
|
-
import type {
|
|
11
|
-
HlUserFill,
|
|
12
|
-
HlUserFills,
|
|
13
|
-
HlUserFillsByTime,
|
|
14
|
-
} from '../types/index.js'
|
|
6
|
+
import type { HlUserFills } from '../types/index.js'
|
|
15
7
|
import { assetIsOutcome, mapFill } from '../utils/index.js'
|
|
16
8
|
import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
|
|
9
|
+
import { getHistoryLimit, historyPage } from './historyPagination.js'
|
|
17
10
|
|
|
18
11
|
/**
|
|
19
12
|
* Parameters for {@link getFills}.
|
|
@@ -22,7 +15,7 @@ import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
|
|
|
22
15
|
*/
|
|
23
16
|
export interface GetFillsParams {
|
|
24
17
|
address: Address
|
|
25
|
-
/**
|
|
18
|
+
/** Positive safe integer; defaults to 50 and is capped at 200. */
|
|
26
19
|
limit?: number
|
|
27
20
|
/** Opaque cursor returned in the previous page's `pagination.cursor`. */
|
|
28
21
|
cursor?: string
|
|
@@ -32,101 +25,55 @@ export interface GetFillsParams {
|
|
|
32
25
|
endTime?: number
|
|
33
26
|
}
|
|
34
27
|
|
|
35
|
-
|
|
36
|
-
time: number
|
|
37
|
-
tid: number
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// HL's docs specify neither newest-first ordering nor monotonic `tid` for
|
|
41
|
-
// `userFills`/`userFillsByTime`, so pagination can't rely on upstream order —
|
|
42
|
-
// it must impose its own (time desc, tid desc as tiebreaker for same-time fills).
|
|
43
|
-
const compareFillsDesc = (a: FillCursor, b: FillCursor): number =>
|
|
44
|
-
b.time - a.time || b.tid - a.tid
|
|
45
|
-
|
|
46
|
-
const encodeCursor = (fill: FillCursor): string => `${fill.time}:${fill.tid}`
|
|
47
|
-
|
|
48
|
-
const decodeCursor = (cursor: string): FillCursor => {
|
|
49
|
-
const [time, tid] = cursor.split(':').map(Number)
|
|
50
|
-
return { time, tid }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Fetch the user's fills history. When `startTime` or `endTime` is provided,
|
|
55
|
-
* Hyperliquid's `userFillsByTime` endpoint is used; otherwise the unbounded
|
|
56
|
-
* `userFills` endpoint returns the full history. The backend's enriched asset
|
|
57
|
-
* list supplies the display fields; only the fills endpoint is read direct
|
|
58
|
-
* from HL.
|
|
59
|
-
*
|
|
60
|
-
* The response is sorted (time desc, `tid` as tiebreaker) before pagination —
|
|
61
|
-
* see {@link compareFillsDesc}. The cursor encodes the last returned fill's
|
|
62
|
-
* `(time, tid)`; the next page keeps fills that sort strictly after it.
|
|
63
|
-
* @throws {PerpsError} On Hyperliquid REST error, network, or parsing failures.
|
|
64
|
-
* @public
|
|
65
|
-
*/
|
|
28
|
+
/** Fetch retained fills newest-first, using opaque cursors across inclusive time ranges. */
|
|
66
29
|
export const getFills = async (
|
|
67
30
|
{ client, apiUrl }: HyperliquidContext,
|
|
68
31
|
params: GetFillsParams,
|
|
69
32
|
options?: SDKRequestOptions
|
|
70
33
|
): Promise<FillsResponse> => {
|
|
34
|
+
const limit = getHistoryLimit(params.limit)
|
|
71
35
|
const registry = getMarketRegistry(client, PROVIDER_KEY)
|
|
72
36
|
await registry.sync()
|
|
73
37
|
const infoOpts = hlInfoOptions(client, options)
|
|
74
38
|
|
|
75
|
-
|
|
76
|
-
params.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
? await infoRequest<HlUserFillsByTime>(
|
|
85
|
-
apiUrl,
|
|
86
|
-
{
|
|
87
|
-
type: 'userFillsByTime',
|
|
88
|
-
user: params.address,
|
|
89
|
-
startTime: params.startTime ?? 0,
|
|
90
|
-
...(params.endTime !== undefined ? { endTime: params.endTime } : {}),
|
|
91
|
-
},
|
|
92
|
-
infoOpts
|
|
93
|
-
)
|
|
94
|
-
: await infoRequest<HlUserFills>(
|
|
39
|
+
let useRecent =
|
|
40
|
+
params.cursor === undefined &&
|
|
41
|
+
params.startTime === undefined &&
|
|
42
|
+
params.endTime === undefined
|
|
43
|
+
const page = await historyPage(
|
|
44
|
+
params,
|
|
45
|
+
limit,
|
|
46
|
+
async (startTime, endTime) => {
|
|
47
|
+
const fills = await infoRequest<HlUserFills>(
|
|
95
48
|
apiUrl,
|
|
96
|
-
|
|
49
|
+
useRecent
|
|
50
|
+
? { type: 'userFills', user: params.address }
|
|
51
|
+
: {
|
|
52
|
+
type: 'userFillsByTime',
|
|
53
|
+
user: params.address,
|
|
54
|
+
startTime,
|
|
55
|
+
endTime,
|
|
56
|
+
},
|
|
97
57
|
infoOpts
|
|
98
58
|
)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// `get`, not `require`: a coin the backend market list does not hold drops
|
|
112
|
-
// only its own row instead of rejecting the whole page. The registry warns
|
|
113
|
-
// once per unresolved id. A delisted market still resolves, so its rows stay.
|
|
114
|
-
const items = page.flatMap((f): Fill[] => {
|
|
115
|
-
if (assetIsOutcome(f.coin)) {
|
|
116
|
-
return []
|
|
117
|
-
}
|
|
118
|
-
const market = registry.get(f.coin)
|
|
119
|
-
return market === undefined ? [] : [mapFill(f, market)]
|
|
120
|
-
})
|
|
121
|
-
const lastPageFill: HlUserFill | undefined = page[page.length - 1]
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
provider: PROVIDER_KEY,
|
|
125
|
-
items,
|
|
126
|
-
pagination: {
|
|
127
|
-
limit,
|
|
128
|
-
hasMore,
|
|
129
|
-
cursor: lastPageFill ? encodeCursor(lastPageFill) : undefined,
|
|
59
|
+
useRecent = false
|
|
60
|
+
if (fills.length >= 2000) {
|
|
61
|
+
return { items: [], saturated: true }
|
|
62
|
+
}
|
|
63
|
+
const items = fills.flatMap((fill): Fill[] => {
|
|
64
|
+
if (assetIsOutcome(fill.coin)) {
|
|
65
|
+
return []
|
|
66
|
+
}
|
|
67
|
+
const market = registry.get(fill.coin)
|
|
68
|
+
return market === undefined ? [] : [mapFill(fill, market)]
|
|
69
|
+
})
|
|
70
|
+
return { items, saturated: false }
|
|
130
71
|
},
|
|
131
|
-
|
|
72
|
+
(fill) => ({
|
|
73
|
+
time: new Date(fill.createdAt).getTime(),
|
|
74
|
+
key: Number(fill.id),
|
|
75
|
+
}),
|
|
76
|
+
options?.signal
|
|
77
|
+
)
|
|
78
|
+
return { provider: PROVIDER_KEY, ...page }
|
|
132
79
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { PerpsError } from '@lifi/perps-sdk'
|
|
2
|
+
import { PerpsErrorCode } from '@lifi/perps-types'
|
|
3
|
+
import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT } from '../constants.js'
|
|
4
|
+
|
|
5
|
+
type HistoryKey = string | number
|
|
6
|
+
interface HistoryBoundary {
|
|
7
|
+
time: number
|
|
8
|
+
key: HistoryKey
|
|
9
|
+
startTime: number
|
|
10
|
+
endTime: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const compareKeys = (a: HistoryKey, b: HistoryKey): number =>
|
|
14
|
+
typeof a === 'number' && typeof b === 'number'
|
|
15
|
+
? b - a
|
|
16
|
+
: a < b
|
|
17
|
+
? 1
|
|
18
|
+
: a > b
|
|
19
|
+
? -1
|
|
20
|
+
: 0
|
|
21
|
+
|
|
22
|
+
export const getHistoryLimit = (limit = DEFAULT_HISTORY_LIMIT): number => {
|
|
23
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
24
|
+
throw new PerpsError(
|
|
25
|
+
PerpsErrorCode.ValidationError,
|
|
26
|
+
'Hyperliquid history limit must be a positive safe integer'
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
return Math.min(limit, MAX_HISTORY_LIMIT)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const decodeCursor = (cursor: string): HistoryBoundary => {
|
|
33
|
+
let value: unknown
|
|
34
|
+
try {
|
|
35
|
+
value = JSON.parse(cursor)
|
|
36
|
+
} catch {
|
|
37
|
+
throw new PerpsError(
|
|
38
|
+
PerpsErrorCode.ValidationError,
|
|
39
|
+
'Invalid Hyperliquid history cursor'
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
if (
|
|
43
|
+
!Array.isArray(value) ||
|
|
44
|
+
value.length !== 4 ||
|
|
45
|
+
!Number.isSafeInteger(value[0]) ||
|
|
46
|
+
(typeof value[1] !== 'string' && typeof value[1] !== 'number') ||
|
|
47
|
+
!Number.isSafeInteger(value[2]) ||
|
|
48
|
+
value[2] > value[0] ||
|
|
49
|
+
!Number.isSafeInteger(value[3]) ||
|
|
50
|
+
value[3] < value[0]
|
|
51
|
+
) {
|
|
52
|
+
throw new PerpsError(
|
|
53
|
+
PerpsErrorCode.ValidationError,
|
|
54
|
+
'Invalid Hyperliquid history cursor'
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
time: value[0],
|
|
59
|
+
key: value[1],
|
|
60
|
+
startTime: value[2],
|
|
61
|
+
endTime: value[3],
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function historyPage<T>(
|
|
66
|
+
params: { startTime?: number; endTime?: number; cursor?: string },
|
|
67
|
+
limit: number,
|
|
68
|
+
fetchWindow: (
|
|
69
|
+
startTime: number,
|
|
70
|
+
endTime: number,
|
|
71
|
+
snapshotEnd: number
|
|
72
|
+
) => Promise<{ items: T[]; saturated: boolean }>,
|
|
73
|
+
identify: (item: T) => { time: number; key: HistoryKey },
|
|
74
|
+
signal?: AbortSignal
|
|
75
|
+
): Promise<{
|
|
76
|
+
items: T[]
|
|
77
|
+
pagination: { limit: number; hasMore: boolean; cursor?: string }
|
|
78
|
+
}> {
|
|
79
|
+
const boundary =
|
|
80
|
+
params.cursor === undefined ? undefined : decodeCursor(params.cursor)
|
|
81
|
+
const lower = params.startTime ?? 0
|
|
82
|
+
const snapshotEnd = boundary?.endTime ?? params.endTime ?? Date.now()
|
|
83
|
+
const upper = Math.min(snapshotEnd, boundary?.time ?? Infinity)
|
|
84
|
+
const windows: { startTime: number; endTime: number }[] = []
|
|
85
|
+
if (boundary && boundary.startTime > lower) {
|
|
86
|
+
windows.push({ startTime: lower, endTime: boundary.startTime - 1 })
|
|
87
|
+
}
|
|
88
|
+
windows.push({
|
|
89
|
+
startTime: Math.max(lower, boundary?.startTime ?? lower),
|
|
90
|
+
endTime: upper,
|
|
91
|
+
})
|
|
92
|
+
const items: T[] = []
|
|
93
|
+
let last: HistoryBoundary | undefined
|
|
94
|
+
while (windows.length > 0) {
|
|
95
|
+
signal?.throwIfAborted()
|
|
96
|
+
const window = windows.pop()!
|
|
97
|
+
if (window.startTime > window.endTime) {
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
const result = await fetchWindow(
|
|
101
|
+
window.startTime,
|
|
102
|
+
window.endTime,
|
|
103
|
+
snapshotEnd
|
|
104
|
+
)
|
|
105
|
+
signal?.throwIfAborted()
|
|
106
|
+
if (result.saturated) {
|
|
107
|
+
if (window.startTime === window.endTime) {
|
|
108
|
+
throw new PerpsError(
|
|
109
|
+
PerpsErrorCode.ThirdPartyError,
|
|
110
|
+
'Hyperliquid history saturates a single millisecond; its time-only API cannot paginate this timestamp safely'
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
// Time APIs truncate from the start, so a capped response cannot supply a newest page.
|
|
114
|
+
const midpoint =
|
|
115
|
+
window.startTime + Math.floor((window.endTime - window.startTime) / 2)
|
|
116
|
+
windows.push({ startTime: window.startTime, endTime: midpoint })
|
|
117
|
+
windows.push({ startTime: midpoint + 1, endTime: window.endTime })
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
const sorted = result.items.sort((a, b) => {
|
|
121
|
+
const left = identify(a)
|
|
122
|
+
const right = identify(b)
|
|
123
|
+
return right.time - left.time || compareKeys(left.key, right.key)
|
|
124
|
+
})
|
|
125
|
+
for (const item of sorted) {
|
|
126
|
+
const identity = identify(item)
|
|
127
|
+
if (identity.time < window.startTime || identity.time > window.endTime) {
|
|
128
|
+
continue
|
|
129
|
+
}
|
|
130
|
+
if (
|
|
131
|
+
boundary &&
|
|
132
|
+
(identity.time > boundary.time ||
|
|
133
|
+
(identity.time === boundary.time &&
|
|
134
|
+
compareKeys(identity.key, boundary.key) <= 0))
|
|
135
|
+
) {
|
|
136
|
+
continue
|
|
137
|
+
}
|
|
138
|
+
if (items.length === limit) {
|
|
139
|
+
return {
|
|
140
|
+
items,
|
|
141
|
+
pagination: {
|
|
142
|
+
limit,
|
|
143
|
+
hasMore: true,
|
|
144
|
+
cursor:
|
|
145
|
+
last &&
|
|
146
|
+
JSON.stringify([
|
|
147
|
+
last.time,
|
|
148
|
+
last.key,
|
|
149
|
+
last.startTime,
|
|
150
|
+
last.endTime,
|
|
151
|
+
]),
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
items.push(item)
|
|
156
|
+
last = { ...identity, startTime: window.startTime, endTime: snapshotEnd }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
items,
|
|
161
|
+
pagination: {
|
|
162
|
+
limit,
|
|
163
|
+
hasMore: false,
|
|
164
|
+
cursor:
|
|
165
|
+
last &&
|
|
166
|
+
JSON.stringify([last.time, last.key, last.startTime, last.endTime]),
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -45,9 +45,9 @@ export {
|
|
|
45
45
|
export { isOpenAssetPosition, mapPosition } from './mapPosition.js'
|
|
46
46
|
export { coinAsset, perpsDexNames } from './marketDisplay.js'
|
|
47
47
|
export {
|
|
48
|
-
formatOrderPrice,
|
|
49
|
-
formatOrderSize,
|
|
50
48
|
getMaxPriceDecimals,
|
|
49
|
+
snapOrderPrice,
|
|
50
|
+
snapOrderSize,
|
|
51
51
|
} from './orderFormatting.js'
|
|
52
52
|
export { priceStepToAggregation } from './priceStepToAggregation.js'
|
|
53
53
|
export {
|
package/src/utils/liquidation.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - margin_available = isolated_margin - maintenance_margin_required
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { estimateLiquidationPrice } from '@lifi/perps-sdk'
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Calculate the maintenance margin fraction for a Hyperliquid asset.
|
|
@@ -65,7 +65,7 @@ export function calculateLiquidationPrice(
|
|
|
65
65
|
if (mmr === undefined) {
|
|
66
66
|
return undefined
|
|
67
67
|
}
|
|
68
|
-
return
|
|
68
|
+
return estimateLiquidationPrice({
|
|
69
69
|
entryPrice,
|
|
70
70
|
leverage,
|
|
71
71
|
isLong,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hyperliquid order size and price
|
|
2
|
+
* Hyperliquid order size and price snapping for exchange submission.
|
|
3
3
|
*
|
|
4
|
-
* These encode Hyperliquid-specific order submission rules.
|
|
5
|
-
*
|
|
4
|
+
* These encode Hyperliquid-specific order submission rules. Snapping a size or
|
|
5
|
+
* a price onto the wrong grid causes rejected orders.
|
|
6
6
|
*
|
|
7
7
|
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { DecimalString } from '@lifi/perps-types'
|
|
10
11
|
import Big from 'big.js'
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -36,28 +37,30 @@ export function getMaxPriceDecimals(
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
|
-
*
|
|
40
|
+
* Snap a size onto the asset's lot grid for order submission.
|
|
40
41
|
*
|
|
41
42
|
* Per Hyperliquid docs (tick-and-lot-size):
|
|
42
43
|
* - Size must be rounded to the asset's szDecimals
|
|
43
44
|
* - Trailing zeroes must be removed for signing
|
|
44
45
|
*
|
|
45
|
-
* @param size - The size
|
|
46
|
+
* @param size - The size to snap, as a decimal string
|
|
46
47
|
* @param szDecimals - Number of decimal places allowed for this asset (from meta)
|
|
47
|
-
* @returns Size as a string with correct precision, no trailing zeros
|
|
48
|
+
* @returns Size as a decimal string with correct precision, no trailing zeros
|
|
48
49
|
* @public
|
|
49
50
|
*/
|
|
50
|
-
export function
|
|
51
|
+
export function snapOrderSize(
|
|
52
|
+
size: DecimalString,
|
|
53
|
+
szDecimals: number
|
|
54
|
+
): DecimalString {
|
|
51
55
|
// Big.roundDown truncates toward zero — never round a size up, it could
|
|
52
|
-
// exceed available balance.
|
|
53
|
-
// product dropped a lot step (0.29 * 100 === 28.999999999999996).
|
|
56
|
+
// exceed available balance.
|
|
54
57
|
const truncated = new Big(size).round(szDecimals, Big.roundDown)
|
|
55
58
|
// toFixed() with no dp always emits plain notation; eq(0) guards '-0'
|
|
56
59
|
return truncated.eq(0) ? '0' : truncated.toFixed()
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
/**
|
|
60
|
-
*
|
|
63
|
+
* Snap a price onto the asset's tick grid for order submission.
|
|
61
64
|
*
|
|
62
65
|
* Per Hyperliquid docs (tick-and-lot-size):
|
|
63
66
|
* - Maximum 5 significant figures
|
|
@@ -66,19 +69,20 @@ export function formatOrderSize(size: number, szDecimals: number): string {
|
|
|
66
69
|
* - Trailing zeroes must be removed for signing
|
|
67
70
|
*
|
|
68
71
|
* Rounding is half-up (away from zero at exact halfway) on the true decimal
|
|
69
|
-
* value,
|
|
72
|
+
* value, which is why the price arrives as a string: `(1.005).toFixed(2)`
|
|
73
|
+
* would give `'1.00'`.
|
|
70
74
|
*
|
|
71
|
-
* @param price - The price
|
|
75
|
+
* @param price - The price to snap, as a decimal string
|
|
72
76
|
* @param szDecimals - The asset's szDecimals (affects max price decimals)
|
|
73
77
|
* @param market - Optional market type (e.g. 'spot'). Defaults to perps rules.
|
|
74
|
-
* @returns Price as a string with correct precision, no trailing zeros
|
|
78
|
+
* @returns Price as a decimal string with correct precision, no trailing zeros
|
|
75
79
|
* @public
|
|
76
80
|
*/
|
|
77
|
-
export function
|
|
78
|
-
price:
|
|
81
|
+
export function snapOrderPrice(
|
|
82
|
+
price: DecimalString,
|
|
79
83
|
szDecimals: number,
|
|
80
84
|
market?: string
|
|
81
|
-
):
|
|
85
|
+
): DecimalString {
|
|
82
86
|
const maxPriceDecimals = getMaxPriceDecimals(szDecimals, market)
|
|
83
87
|
|
|
84
88
|
let rounded = new Big(price).round(maxPriceDecimals, Big.roundHalfUp)
|
package/src/utils/spotBalance.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { numberToDecimalString, parseDecimal } from '@lifi/perps-sdk'
|
|
2
2
|
import type { Asset, Balance, Market } from '@lifi/perps-types'
|
|
3
3
|
import { SPOT_MARKET_ID } from '../constants.js'
|
|
4
4
|
import type { HlSpotBalance } from '../types/index.js'
|
|
@@ -52,8 +52,10 @@ export const spotBalance = (
|
|
|
52
52
|
categoryId: SPOT_MARKET_ID,
|
|
53
53
|
asset,
|
|
54
54
|
units: total,
|
|
55
|
-
valueUsd: (
|
|
55
|
+
valueUsd: numberToDecimalString(
|
|
56
|
+
(parseDecimal(total) ?? Number.NaN) * price
|
|
57
|
+
),
|
|
56
58
|
// A zero entry means the map holds no mark for the asset, not a free asset.
|
|
57
|
-
...(price > 0 ? { price: price
|
|
59
|
+
...(price > 0 ? { price: numberToDecimalString(price) } : {}),
|
|
58
60
|
}
|
|
59
61
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
calculateWithdrawMax,
|
|
3
|
+
type ProviderWithdrawableBalance,
|
|
4
|
+
} from '@lifi/perps-sdk'
|
|
2
5
|
import type {
|
|
3
6
|
HlAbstractionMode,
|
|
4
7
|
HlClearinghouseState,
|
|
@@ -50,24 +53,26 @@ export const hyperliquidWithdrawableBalances = (
|
|
|
50
53
|
)
|
|
51
54
|
if (spot.gt(0)) {
|
|
52
55
|
const assetId = String(balance.token)
|
|
53
|
-
|
|
56
|
+
const row = {
|
|
54
57
|
assetId,
|
|
55
|
-
route: 'spot',
|
|
58
|
+
route: 'spot' as const,
|
|
56
59
|
available: spot.toFixed(),
|
|
57
60
|
...feeFor(assetId),
|
|
58
|
-
}
|
|
61
|
+
}
|
|
62
|
+
rows.push({ ...row, max: calculateWithdrawMax(row) })
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
const perps = toWireBig(state.withdrawable, 'clearinghouseState.withdrawable')
|
|
64
68
|
if (perps.gt(0)) {
|
|
65
|
-
|
|
69
|
+
const row = {
|
|
66
70
|
assetId: quoteAssetId,
|
|
67
|
-
route: 'perps',
|
|
71
|
+
route: 'perps' as const,
|
|
68
72
|
available: perps.toFixed(),
|
|
69
73
|
...feeFor(quoteAssetId),
|
|
70
|
-
}
|
|
74
|
+
}
|
|
75
|
+
rows.push({ ...row, max: calculateWithdrawMax(row) })
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
return rows
|