@lifi/perps-sdk-provider-hyperliquid 30.0.0 → 30.0.2
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 +22 -7
- package/dist/cjs/services/getOrder.d.ts.map +1 -1
- package/dist/cjs/services/getOrder.js +7 -2
- package/dist/cjs/services/getOrder.js.map +1 -1
- package/dist/cjs/services/getOrders.d.ts +1 -1
- package/dist/cjs/services/getOrders.d.ts.map +1 -1
- package/dist/cjs/services/getOrders.js +8 -1
- package/dist/cjs/services/getOrders.js.map +1 -1
- package/dist/cjs/types/fill.d.ts +4 -0
- package/dist/cjs/types/fill.d.ts.map +1 -1
- package/dist/cjs/types/ledger.d.ts +1 -1
- package/dist/cjs/types/ledger.d.ts.map +1 -1
- package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
- package/dist/cjs/utils/mapOrder.js +73 -18
- package/dist/cjs/utils/mapOrder.js.map +1 -1
- package/dist/cjs/utils/twapHistory.d.ts +4 -0
- package/dist/cjs/utils/twapHistory.d.ts.map +1 -0
- package/dist/cjs/utils/twapHistory.js +47 -0
- package/dist/cjs/utils/twapHistory.js.map +1 -0
- package/dist/esm/services/getOrder.d.ts.map +1 -1
- package/dist/esm/services/getOrder.js +7 -2
- package/dist/esm/services/getOrder.js.map +1 -1
- package/dist/esm/services/getOrders.d.ts +1 -1
- package/dist/esm/services/getOrders.d.ts.map +1 -1
- package/dist/esm/services/getOrders.js +8 -1
- package/dist/esm/services/getOrders.js.map +1 -1
- package/dist/esm/types/fill.d.ts +9 -0
- package/dist/esm/types/fill.d.ts.map +1 -1
- package/dist/esm/types/ledger.d.ts +1 -1
- package/dist/esm/types/ledger.d.ts.map +1 -1
- package/dist/esm/utils/mapOrder.d.ts +4 -1
- package/dist/esm/utils/mapOrder.d.ts.map +1 -1
- package/dist/esm/utils/mapOrder.js +78 -19
- package/dist/esm/utils/mapOrder.js.map +1 -1
- package/dist/esm/utils/twapHistory.d.ts +13 -0
- package/dist/esm/utils/twapHistory.d.ts.map +1 -0
- package/dist/esm/utils/twapHistory.js +51 -0
- package/dist/esm/utils/twapHistory.js.map +1 -0
- package/dist/types/services/getOrder.d.ts.map +1 -1
- package/dist/types/services/getOrders.d.ts +1 -1
- package/dist/types/services/getOrders.d.ts.map +1 -1
- package/dist/types/types/fill.d.ts +9 -0
- package/dist/types/types/fill.d.ts.map +1 -1
- package/dist/types/types/ledger.d.ts +1 -1
- package/dist/types/types/ledger.d.ts.map +1 -1
- package/dist/types/utils/mapOrder.d.ts +4 -1
- package/dist/types/utils/mapOrder.d.ts.map +1 -1
- package/dist/types/utils/twapHistory.d.ts +13 -0
- package/dist/types/utils/twapHistory.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/services/getOrder.ts +16 -2
- package/src/services/getOrders.ts +20 -2
- package/src/types/fill.ts +10 -0
- package/src/types/ledger.ts +1 -1
- package/src/utils/mapOrder.ts +94 -19
- package/src/utils/twapHistory.ts +63 -0
package/src/types/fill.ts
CHANGED
|
@@ -53,3 +53,13 @@ export type HlUserFills = HlUserFill[]
|
|
|
53
53
|
|
|
54
54
|
/** Fills returned by the time-bounded `userFillsByTime` query. @public */
|
|
55
55
|
export type HlUserFillsByTime = HlUserFill[]
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* One row of the `userTwapSliceFills` query: a fill that a TWAP slice made.
|
|
59
|
+
* The venue returns at most 2,000 recent rows, newest first.
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
export type HlTwapSliceFill = {
|
|
63
|
+
fill: HlUserFill
|
|
64
|
+
twapId: number
|
|
65
|
+
}
|
package/src/types/ledger.ts
CHANGED
package/src/utils/mapOrder.ts
CHANGED
|
@@ -58,13 +58,54 @@ export const mapOrderType = (
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const STATUS_REASONS = {
|
|
62
|
+
canceled: 'Cancelled.',
|
|
63
|
+
scheduledCancel: 'Cancelled: the scheduled cancel deadline passed.',
|
|
64
|
+
marginCanceled: 'Cancelled: not enough margin to fill the order.',
|
|
65
|
+
vaultWithdrawalCanceled: 'Cancelled: a user withdrew from the vault.',
|
|
66
|
+
openInterestCapCanceled:
|
|
67
|
+
'Cancelled: the price was too aggressive at the open interest cap.',
|
|
68
|
+
selfTradeCanceled:
|
|
69
|
+
'Cancelled: the order would have traded with your own order.',
|
|
70
|
+
reduceOnlyCanceled:
|
|
71
|
+
'Cancelled: the reduce-only order would not reduce the position.',
|
|
72
|
+
siblingFilledCanceled: 'Cancelled: the linked TP/SL order filled.',
|
|
73
|
+
delistedCanceled: 'Cancelled: the market was delisted.',
|
|
74
|
+
liquidatedCanceled: 'Cancelled: the account was liquidated.',
|
|
75
|
+
rejected: 'Rejected.',
|
|
76
|
+
tickRejected: 'Rejected: the price does not match the tick size.',
|
|
77
|
+
minTradeNtlRejected: 'Rejected: the order value is below the minimum.',
|
|
78
|
+
perpMarginRejected: 'Rejected: not enough margin for this order.',
|
|
79
|
+
reduceOnlyRejected:
|
|
80
|
+
'Rejected: the reduce-only order would not reduce the position.',
|
|
81
|
+
badAloPxRejected:
|
|
82
|
+
'Rejected: the post-only order would have filled immediately.',
|
|
83
|
+
iocCancelRejected: 'Rejected: the immediate-or-cancel order could not fill.',
|
|
84
|
+
badTriggerPxRejected: 'Rejected: the TP/SL trigger price is not valid.',
|
|
85
|
+
marketOrderNoLiquidityRejected:
|
|
86
|
+
'Rejected: not enough liquidity for the market order.',
|
|
87
|
+
positionIncreaseAtOpenInterestCapRejected:
|
|
88
|
+
'Rejected: the market is at its open interest cap.',
|
|
89
|
+
positionFlipAtOpenInterestCapRejected:
|
|
90
|
+
'Rejected: the market is at its open interest cap.',
|
|
91
|
+
tooAggressiveAtOpenInterestCapRejected:
|
|
92
|
+
'Rejected: the price was too aggressive at the open interest cap.',
|
|
93
|
+
openInterestIncreaseRejected:
|
|
94
|
+
'Rejected: the market is at its open interest cap.',
|
|
95
|
+
insufficientSpotBalanceRejected: 'Rejected: not enough spot balance.',
|
|
96
|
+
oracleRejected: 'Rejected: the price is too far from the oracle price.',
|
|
97
|
+
perpMaxPositionRejected:
|
|
98
|
+
'Rejected: the position is larger than the limit for the current leverage.',
|
|
99
|
+
} as const
|
|
100
|
+
|
|
101
|
+
const mapOrderLifecycle = (
|
|
102
|
+
status: string
|
|
103
|
+
): Pick<OrderBase, 'status' | 'statusReason'> => {
|
|
63
104
|
switch (status) {
|
|
64
105
|
case 'open':
|
|
65
|
-
return OrderStatus.OPEN
|
|
106
|
+
return { status: OrderStatus.OPEN }
|
|
66
107
|
case 'filled':
|
|
67
|
-
return OrderStatus.FILLED
|
|
108
|
+
return { status: OrderStatus.FILLED }
|
|
68
109
|
case 'canceled':
|
|
69
110
|
case 'scheduledCancel':
|
|
70
111
|
case 'marginCanceled':
|
|
@@ -75,7 +116,10 @@ export const mapOrderStatus = (status: string): OrderStatus => {
|
|
|
75
116
|
case 'siblingFilledCanceled':
|
|
76
117
|
case 'delistedCanceled':
|
|
77
118
|
case 'liquidatedCanceled':
|
|
78
|
-
return
|
|
119
|
+
return {
|
|
120
|
+
status: OrderStatus.CANCELLED,
|
|
121
|
+
statusReason: STATUS_REASONS[status],
|
|
122
|
+
}
|
|
79
123
|
case 'rejected':
|
|
80
124
|
case 'tickRejected':
|
|
81
125
|
case 'minTradeNtlRejected':
|
|
@@ -92,14 +136,37 @@ export const mapOrderStatus = (status: string): OrderStatus => {
|
|
|
92
136
|
case 'insufficientSpotBalanceRejected':
|
|
93
137
|
case 'oracleRejected':
|
|
94
138
|
case 'perpMaxPositionRejected':
|
|
95
|
-
return
|
|
139
|
+
return {
|
|
140
|
+
status: OrderStatus.REJECTED,
|
|
141
|
+
statusReason: STATUS_REASONS[status],
|
|
142
|
+
}
|
|
96
143
|
case 'triggered':
|
|
97
|
-
return OrderStatus.TRIGGERED
|
|
144
|
+
return { status: OrderStatus.TRIGGERED }
|
|
98
145
|
default:
|
|
146
|
+
// Documented cancel and reject reasons use these suffixes; an undocumented one keeps the row.
|
|
147
|
+
if (status.endsWith('Canceled')) {
|
|
148
|
+
return {
|
|
149
|
+
status: OrderStatus.CANCELLED,
|
|
150
|
+
statusReason: 'Cancelled by the venue.',
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (status.endsWith('Rejected')) {
|
|
154
|
+
return {
|
|
155
|
+
status: OrderStatus.REJECTED,
|
|
156
|
+
statusReason: 'Rejected by the venue.',
|
|
157
|
+
}
|
|
158
|
+
}
|
|
99
159
|
throw venueError(`Unknown Hyperliquid order status: ${status}`)
|
|
100
160
|
}
|
|
101
161
|
}
|
|
102
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Translate venue lifecycle statuses. An unknown `...Canceled` or `...Rejected`
|
|
165
|
+
* status maps by its suffix; any other unknown status is an error.
|
|
166
|
+
*/
|
|
167
|
+
export const mapOrderStatus = (status: string): OrderStatus =>
|
|
168
|
+
mapOrderLifecycle(status).status
|
|
169
|
+
|
|
103
170
|
const skipOrder = (
|
|
104
171
|
marketId: string,
|
|
105
172
|
field: string,
|
|
@@ -172,13 +239,20 @@ export const mapOrder = (
|
|
|
172
239
|
if (updatedAt === undefined) {
|
|
173
240
|
return skipOrder(market.id, 'time', raw.time)
|
|
174
241
|
}
|
|
242
|
+
const hasRemaining =
|
|
243
|
+
safeIsDecimalStringGreaterThan(remainingSize, '0') === true
|
|
175
244
|
let status: OrderStatus
|
|
245
|
+
let statusReason: string | undefined
|
|
176
246
|
switch (raw.status.status) {
|
|
177
247
|
case 'activated':
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
248
|
+
if (!hasRemaining) {
|
|
249
|
+
status = OrderStatus.FILLED
|
|
250
|
+
} else {
|
|
251
|
+
status =
|
|
252
|
+
safeIsDecimalStringGreaterThan(state.executedSz, '0') === true
|
|
253
|
+
? OrderStatus.PARTIALLY_FILLED
|
|
254
|
+
: OrderStatus.OPEN
|
|
255
|
+
}
|
|
182
256
|
break
|
|
183
257
|
case 'waitingForTrigger':
|
|
184
258
|
status = OrderStatus.OPEN
|
|
@@ -189,9 +263,11 @@ export const mapOrder = (
|
|
|
189
263
|
case 'terminated':
|
|
190
264
|
case 'stopped':
|
|
191
265
|
status = OrderStatus.CANCELLED
|
|
266
|
+
statusReason = STATUS_REASONS.canceled
|
|
192
267
|
break
|
|
193
268
|
case 'error':
|
|
194
269
|
status = OrderStatus.REJECTED
|
|
270
|
+
statusReason = STATUS_REASONS.rejected
|
|
195
271
|
break
|
|
196
272
|
default:
|
|
197
273
|
throw venueError(
|
|
@@ -208,14 +284,12 @@ export const mapOrder = (
|
|
|
208
284
|
type: OrderType.TWAP,
|
|
209
285
|
side: state.side === 'B' ? OrderSide.BUY : OrderSide.SELL,
|
|
210
286
|
originalSize: state.sz,
|
|
211
|
-
remainingSize,
|
|
287
|
+
remainingSize: hasRemaining ? remainingSize : '0',
|
|
212
288
|
filledSize: state.executedSz,
|
|
213
289
|
...(averagePrice === undefined ? {} : { averagePrice }),
|
|
214
290
|
reduceOnly: state.reduceOnly,
|
|
215
291
|
status,
|
|
216
|
-
...(
|
|
217
|
-
? { statusReason: raw.status.description ?? raw.status.status }
|
|
218
|
-
: {}),
|
|
292
|
+
...(statusReason === undefined ? {} : { statusReason }),
|
|
219
293
|
createdAt,
|
|
220
294
|
updatedAt,
|
|
221
295
|
startedAt: createdAt,
|
|
@@ -239,7 +313,8 @@ export const mapOrder = (
|
|
|
239
313
|
if (updatedAt === undefined) {
|
|
240
314
|
return skipOrder(market.id, 'statusTimestamp', statusTimestamp)
|
|
241
315
|
}
|
|
242
|
-
|
|
316
|
+
const lifecycle = mapOrderLifecycle(venueStatus)
|
|
317
|
+
let status = lifecycle.status
|
|
243
318
|
if (parentOrderId !== undefined && !('order' in raw)) {
|
|
244
319
|
status = OrderStatus.PENDING
|
|
245
320
|
} else if (
|
|
@@ -254,9 +329,9 @@ export const mapOrder = (
|
|
|
254
329
|
...(typeof o.cloid === 'string' ? { clientOrderId: o.cloid } : {}),
|
|
255
330
|
side: o.side === 'B' ? OrderSide.BUY : OrderSide.SELL,
|
|
256
331
|
status,
|
|
257
|
-
...(
|
|
258
|
-
? {
|
|
259
|
-
: {}),
|
|
332
|
+
...(lifecycle.statusReason === undefined
|
|
333
|
+
? {}
|
|
334
|
+
: { statusReason: lifecycle.statusReason }),
|
|
260
335
|
originalSize: o.origSz,
|
|
261
336
|
remainingSize: o.sz,
|
|
262
337
|
filledSize,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
safeAddDecimalStrings,
|
|
3
|
+
safeCalculateNotionalValue,
|
|
4
|
+
} from '@lifi/perps-sdk'
|
|
5
|
+
import type { HlTwapHistoryEntry, HlTwapSliceFill } from '../types/index.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Keep the newest `twapHistory` row of each TWAP, in first-seen id order. The
|
|
9
|
+
* venue adds one row per status change; on equal `time` the earlier row wins.
|
|
10
|
+
*/
|
|
11
|
+
export const resolveLatestTwapEntries = (
|
|
12
|
+
history: readonly HlTwapHistoryEntry[]
|
|
13
|
+
): HlTwapHistoryEntry[] => {
|
|
14
|
+
const latest = new Map<number | undefined, HlTwapHistoryEntry>()
|
|
15
|
+
for (const entry of history) {
|
|
16
|
+
const kept = latest.get(entry.twapId)
|
|
17
|
+
if (kept === undefined || entry.time > kept.time) {
|
|
18
|
+
latest.set(entry.twapId, entry)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return [...latest.values()]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Take the executed size and notional of each `activated` row from the slice
|
|
26
|
+
* fills of its `twapId`. The venue writes that row once, at activation. A row
|
|
27
|
+
* with no slice fill, or with a slice fill that does not read, keeps its state.
|
|
28
|
+
*/
|
|
29
|
+
export const resolveTwapExecution = (
|
|
30
|
+
entries: readonly HlTwapHistoryEntry[],
|
|
31
|
+
sliceFills: readonly HlTwapSliceFill[]
|
|
32
|
+
): HlTwapHistoryEntry[] => {
|
|
33
|
+
const fillsByTwap = new Map<number, HlTwapSliceFill['fill'][]>()
|
|
34
|
+
for (const { twapId, fill } of sliceFills) {
|
|
35
|
+
const fills = fillsByTwap.get(twapId)
|
|
36
|
+
if (fills === undefined) {
|
|
37
|
+
fillsByTwap.set(twapId, [fill])
|
|
38
|
+
} else {
|
|
39
|
+
fills.push(fill)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return entries.map((entry) => {
|
|
43
|
+
const fills =
|
|
44
|
+
entry.status.status === 'activated' && entry.twapId !== undefined
|
|
45
|
+
? fillsByTwap.get(entry.twapId)
|
|
46
|
+
: undefined
|
|
47
|
+
if (fills === undefined) {
|
|
48
|
+
return entry
|
|
49
|
+
}
|
|
50
|
+
const notionals = fills.map((fill) =>
|
|
51
|
+
safeCalculateNotionalValue(fill.sz, fill.px)
|
|
52
|
+
)
|
|
53
|
+
if (!notionals.every((notional) => notional !== undefined)) {
|
|
54
|
+
return entry
|
|
55
|
+
}
|
|
56
|
+
const executedSz = safeAddDecimalStrings(fills.map((fill) => fill.sz))
|
|
57
|
+
const executedNtl = safeAddDecimalStrings(notionals)
|
|
58
|
+
if (executedSz === undefined || executedNtl === undefined) {
|
|
59
|
+
return entry
|
|
60
|
+
}
|
|
61
|
+
return { ...entry, state: { ...entry.state, executedSz, executedNtl } }
|
|
62
|
+
})
|
|
63
|
+
}
|