@lifi/perps-sdk-provider-hyperliquid 16.0.0 → 17.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.js +1 -1
- package/dist/cjs/HyperliquidProvider.js.map +1 -1
- package/dist/cjs/services/getOrder.d.ts.map +1 -1
- package/dist/cjs/services/getOrder.js +7 -3
- package/dist/cjs/services/getOrder.js.map +1 -1
- package/dist/cjs/services/getOrders.d.ts.map +1 -1
- package/dist/cjs/services/getOrders.js +4 -23
- package/dist/cjs/services/getOrders.js.map +1 -1
- package/dist/cjs/services/getWithdrawableBalances.d.ts.map +1 -1
- package/dist/cjs/services/getWithdrawableBalances.js +4 -2
- package/dist/cjs/services/getWithdrawableBalances.js.map +1 -1
- package/dist/cjs/utils/explorer.d.ts +2 -1
- package/dist/cjs/utils/explorer.d.ts.map +1 -1
- package/dist/cjs/utils/explorer.js +24 -1
- package/dist/cjs/utils/explorer.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 +2 -2
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/transferMargin.d.ts +2 -2
- package/dist/cjs/utils/transferMargin.d.ts.map +1 -1
- package/dist/cjs/utils/transferMargin.js +20 -12
- package/dist/cjs/utils/transferMargin.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 +9 -2
- package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
- package/dist/esm/HyperliquidProvider.js +2 -2
- package/dist/esm/HyperliquidProvider.js.map +1 -1
- package/dist/esm/services/getOrder.d.ts.map +1 -1
- package/dist/esm/services/getOrder.js +7 -3
- package/dist/esm/services/getOrder.js.map +1 -1
- package/dist/esm/services/getOrders.d.ts.map +1 -1
- package/dist/esm/services/getOrders.js +6 -34
- package/dist/esm/services/getOrders.js.map +1 -1
- package/dist/esm/services/getWithdrawableBalances.d.ts +5 -3
- package/dist/esm/services/getWithdrawableBalances.d.ts.map +1 -1
- package/dist/esm/services/getWithdrawableBalances.js +10 -6
- package/dist/esm/services/getWithdrawableBalances.js.map +1 -1
- package/dist/esm/utils/explorer.d.ts +9 -1
- package/dist/esm/utils/explorer.d.ts.map +1 -1
- package/dist/esm/utils/explorer.js +34 -1
- package/dist/esm/utils/explorer.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/transferMargin.d.ts +12 -8
- package/dist/esm/utils/transferMargin.d.ts.map +1 -1
- package/dist/esm/utils/transferMargin.js +30 -18
- package/dist/esm/utils/transferMargin.js.map +1 -1
- package/dist/esm/utils/withdrawableBalances.d.ts +3 -1
- package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
- package/dist/esm/utils/withdrawableBalances.js +11 -2
- package/dist/esm/utils/withdrawableBalances.js.map +1 -1
- package/dist/types/services/getOrder.d.ts.map +1 -1
- package/dist/types/services/getOrders.d.ts.map +1 -1
- package/dist/types/services/getWithdrawableBalances.d.ts +5 -3
- package/dist/types/services/getWithdrawableBalances.d.ts.map +1 -1
- package/dist/types/utils/explorer.d.ts +9 -1
- package/dist/types/utils/explorer.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/transferMargin.d.ts +12 -8
- package/dist/types/utils/transferMargin.d.ts.map +1 -1
- package/dist/types/utils/withdrawableBalances.d.ts +3 -1
- package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/HyperliquidProvider.ts +2 -2
- package/src/services/getOrder.ts +10 -3
- package/src/services/getOrders.ts +5 -56
- package/src/services/getWithdrawableBalances.ts +32 -22
- package/src/utils/explorer.ts +51 -2
- package/src/utils/index.ts +1 -1
- package/src/utils/transferMargin.ts +31 -20
- package/src/utils/withdrawableBalances.ts +14 -2
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { type Position
|
|
1
|
+
import { type Position } from '@lifi/perps-types';
|
|
2
2
|
/**
|
|
3
|
-
* Hyperliquid
|
|
4
|
-
* `max(initial_margin_required, 0.1
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* markets without individual margin adjustment return `undefined`.
|
|
3
|
+
* Hyperliquid removable isolated margin:
|
|
4
|
+
* `marginUsed − max(initial_margin_required, 0.1 × total_position_value)`.
|
|
5
|
+
* Hyperliquid isolated `marginUsed` already includes the unrealized PnL of
|
|
6
|
+
* the position, so the PnL is not added again, and it can reach zero or less
|
|
7
|
+
* before liquidation.
|
|
9
8
|
*
|
|
9
|
+
* @returns `undefined` for cross positions and markets without individual
|
|
10
|
+
* margin adjustment; `'0'` for add-only strict-isolated markets and when
|
|
11
|
+
* nothing is removable.
|
|
12
|
+
* @throws {PerpsError} `ValidationError` when a required `Position` decimal
|
|
13
|
+
* is malformed, or when a size, price or requirement is not positive.
|
|
10
14
|
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/trading/margining
|
|
11
15
|
* @public
|
|
12
16
|
*/
|
|
13
|
-
export declare function
|
|
17
|
+
export declare function positionRemovableMargin(position: Position): string | undefined;
|
|
14
18
|
//# sourceMappingURL=transferMargin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transferMargin.d.ts","sourceRoot":"","sources":["../../../src/utils/transferMargin.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"transferMargin.d.ts","sourceRoot":"","sources":["../../../src/utils/transferMargin.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,QAAQ,EAGd,MAAM,mBAAmB,CAAA;AA4B1B;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,GACjB,MAAM,GAAG,SAAS,CAwBpB"}
|
|
@@ -8,7 +8,9 @@ import type { HlAbstractionMode, HlClearinghouseState, HlSpotClearinghouseState
|
|
|
8
8
|
* per-asset venue minimum.
|
|
9
9
|
*
|
|
10
10
|
* @param quoteAssetId - `Asset.id` the perps-route row is keyed by.
|
|
11
|
+
* @param withdrawalFee - Flat venue fee in quote-asset units, set on every
|
|
12
|
+
* quote-asset row. Absent leaves every row without a fee.
|
|
11
13
|
* @public
|
|
12
14
|
*/
|
|
13
|
-
export declare const hyperliquidWithdrawableBalances: (abstraction: HlAbstractionMode | null, state: HlClearinghouseState, spotState: HlSpotClearinghouseState, quoteAssetId: string) => ProviderWithdrawableBalance[];
|
|
15
|
+
export declare const hyperliquidWithdrawableBalances: (abstraction: HlAbstractionMode | null, state: HlClearinghouseState, spotState: HlSpotClearinghouseState, quoteAssetId: string, withdrawalFee?: string) => ProviderWithdrawableBalance[];
|
|
14
16
|
//# sourceMappingURL=withdrawableBalances.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withdrawableBalances.d.ts","sourceRoot":"","sources":["../../../src/utils/withdrawableBalances.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,KAAK,EACV,iBAAiB,EACjB,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,mBAAmB,CAAA;AAK1B
|
|
1
|
+
{"version":3,"file":"withdrawableBalances.d.ts","sourceRoot":"","sources":["../../../src/utils/withdrawableBalances.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,KAAK,EACV,iBAAiB,EACjB,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,mBAAmB,CAAA;AAK1B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,+BAA+B,GAC1C,aAAa,iBAAiB,GAAG,IAAI,EACrC,OAAO,oBAAoB,EAC3B,WAAW,wBAAwB,EACnC,cAAc,MAAM,EACpB,gBAAgB,MAAM,KACrB,2BAA2B,EAwC7B,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifi/perps-sdk-provider-hyperliquid",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.0.0",
|
|
4
4
|
"description": "Hyperliquid provider plugin for @lifi/perps-sdk",
|
|
5
5
|
"homepage": "https://github.com/lifinance/perps-sdk",
|
|
6
6
|
"bugs": {
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"big.js": "^7.0.1",
|
|
51
51
|
"viem": "^2.55.18",
|
|
52
|
-
"@lifi/perps-types": "^
|
|
52
|
+
"@lifi/perps-types": "^16.0.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@lifi/perps-sdk": "^
|
|
55
|
+
"@lifi/perps-sdk": "^17.0.0"
|
|
56
56
|
},
|
|
57
57
|
"publishConfig": {
|
|
58
58
|
"access": "public"
|
|
@@ -80,7 +80,7 @@ import type { HlExtraAgents } from './types/index.js'
|
|
|
80
80
|
import { hlInfoOptions, infoRequest } from './utils/infoClient.js'
|
|
81
81
|
import { calculateLiquidationPrice } from './utils/liquidation.js'
|
|
82
82
|
import { formatOrderPrice, formatOrderSize } from './utils/orderFormatting.js'
|
|
83
|
-
import {
|
|
83
|
+
import { positionRemovableMargin } from './utils/transferMargin.js'
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Options for {@link hyperliquidProvider}.
|
|
@@ -452,7 +452,7 @@ export function hyperliquidProvider(
|
|
|
452
452
|
market.maxLeverage
|
|
453
453
|
),
|
|
454
454
|
|
|
455
|
-
|
|
455
|
+
positionRemovableMargin,
|
|
456
456
|
|
|
457
457
|
projectConfig: (
|
|
458
458
|
config: AccountConfig,
|
package/src/services/getOrder.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
HlOrderStatusResponse,
|
|
13
13
|
HlTwapHistoryEntry,
|
|
14
14
|
} from '../types/index.js'
|
|
15
|
+
import { withExplorerLinks } from '../utils/explorer.js'
|
|
15
16
|
import { assetIsOutcome, mapOrder } from '../utils/index.js'
|
|
16
17
|
import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
|
|
17
18
|
|
|
@@ -47,17 +48,18 @@ export const getOrder = async (
|
|
|
47
48
|
throw err
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
const infoOpts = hlInfoOptions(client, options)
|
|
50
52
|
const status = await infoRequest<HlOrderStatusResponse>(
|
|
51
53
|
apiUrl,
|
|
52
54
|
{ type: 'orderStatus', user: params.address, oid },
|
|
53
|
-
|
|
55
|
+
infoOpts
|
|
54
56
|
)
|
|
55
57
|
|
|
56
58
|
if (status.status !== 'order') {
|
|
57
59
|
const history = await infoRequest<HlTwapHistoryEntry[]>(
|
|
58
60
|
apiUrl,
|
|
59
61
|
{ type: 'twapHistory', user: params.address },
|
|
60
|
-
|
|
62
|
+
infoOpts
|
|
61
63
|
)
|
|
62
64
|
const twap = history.find(
|
|
63
65
|
(entry) =>
|
|
@@ -81,5 +83,10 @@ export const getOrder = async (
|
|
|
81
83
|
|
|
82
84
|
const registry = getMarketRegistry(client, PROVIDER_KEY)
|
|
83
85
|
await registry.sync()
|
|
84
|
-
|
|
86
|
+
const order = mapOrder(
|
|
87
|
+
status.order,
|
|
88
|
+
registry.require(status.order.order.coin)
|
|
89
|
+
)
|
|
90
|
+
const [linked] = await withExplorerLinks([order], params.address, infoOpts)
|
|
91
|
+
return linked
|
|
85
92
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ACTIVE_ORDER_STATUSES,
|
|
3
3
|
createWarnOnce,
|
|
4
|
-
ExplorerChainId,
|
|
5
|
-
explorerTxUrl,
|
|
6
4
|
getMarketRegistry,
|
|
7
5
|
isActiveOrderStatus,
|
|
8
6
|
type MarketRegistry,
|
|
@@ -14,34 +12,21 @@ import type { MarketDisplay, Order, OrdersResponse } from '@lifi/perps-types'
|
|
|
14
12
|
import { PROVIDER_KEY } from '../constants.js'
|
|
15
13
|
import type { HyperliquidContext } from '../context.js'
|
|
16
14
|
import type {
|
|
17
|
-
HlExplorerTx,
|
|
18
15
|
HlFrontendOpenOrders,
|
|
19
16
|
HlHistoricalOrders,
|
|
20
17
|
HlTwapHistoryEntry,
|
|
21
18
|
} from '../types/index.js'
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
mapOrder,
|
|
26
|
-
matchOrderActionHash,
|
|
27
|
-
perpsDexNames,
|
|
28
|
-
} from '../utils/index.js'
|
|
29
|
-
import {
|
|
30
|
-
hlInfoOptions,
|
|
31
|
-
type InfoRequestOptions,
|
|
32
|
-
infoRequest,
|
|
33
|
-
} from '../utils/infoClient.js'
|
|
19
|
+
import { withExplorerLinks } from '../utils/explorer.js'
|
|
20
|
+
import { assetIsOutcome, mapOrder, perpsDexNames } from '../utils/index.js'
|
|
21
|
+
import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
|
|
34
22
|
|
|
35
23
|
/** Parameters for a lifecycle-filtered Hyperliquid order read. */
|
|
36
24
|
export type GetOrdersParams = ProviderGetOrdersParams
|
|
37
25
|
|
|
38
26
|
const warn = createWarnOnce()
|
|
39
27
|
|
|
40
|
-
const warnOnce = (key: string
|
|
41
|
-
warn(
|
|
42
|
-
key,
|
|
43
|
-
`[${PROVIDER_KEY}] ${detail === undefined ? key : `${key}: ${detail}`}`
|
|
44
|
-
)
|
|
28
|
+
const warnOnce = (key: string): void => {
|
|
29
|
+
warn(key, `[${PROVIDER_KEY}] ${key}`)
|
|
45
30
|
}
|
|
46
31
|
|
|
47
32
|
/**
|
|
@@ -73,42 +58,6 @@ const mapRow = (
|
|
|
73
58
|
}
|
|
74
59
|
}
|
|
75
60
|
|
|
76
|
-
/**
|
|
77
|
-
* Set `explorerLink` on each row whose placement transaction the explorer
|
|
78
|
-
* window still holds. One explorer read serves the whole page, and it is
|
|
79
|
-
* skipped when no row carries a client order id to match on. The link is
|
|
80
|
-
* supplementary, so an explorer failure warns and leaves the rows unlinked
|
|
81
|
-
* instead of failing the order read.
|
|
82
|
-
*/
|
|
83
|
-
const withExplorerLinks = async (
|
|
84
|
-
orders: Order[],
|
|
85
|
-
address: string,
|
|
86
|
-
options: InfoRequestOptions
|
|
87
|
-
): Promise<Order[]> => {
|
|
88
|
-
if (!orders.some((order) => order.clientOrderId !== undefined)) {
|
|
89
|
-
return orders
|
|
90
|
-
}
|
|
91
|
-
let txs: HlExplorerTx[]
|
|
92
|
-
try {
|
|
93
|
-
txs = await fetchUserTransactions(address, options)
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (!(error instanceof PerpsError)) {
|
|
96
|
-
throw error
|
|
97
|
-
}
|
|
98
|
-
// The message stays out of the key: a transport failure names hosts and
|
|
99
|
-
// ports, so keying on it would let the dedupe set grow without bound.
|
|
100
|
-
warnOnce('explorer link lookup failed', error.message)
|
|
101
|
-
return orders
|
|
102
|
-
}
|
|
103
|
-
return orders.map((order) => {
|
|
104
|
-
const explorerLink = explorerTxUrl(
|
|
105
|
-
ExplorerChainId.HYPERLIQUID,
|
|
106
|
-
matchOrderActionHash(txs, order)
|
|
107
|
-
)
|
|
108
|
-
return explorerLink === undefined ? order : { ...order, explorerLink }
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
61
|
/** Read regular, trigger, and TWAP orders from the requested lifecycle feeds. */
|
|
113
62
|
export const getOrders = async (
|
|
114
63
|
{ client, apiUrl }: HyperliquidContext,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getMarketRegistry,
|
|
3
|
+
getProviders,
|
|
3
4
|
PerpsError,
|
|
4
5
|
type ProviderGetWithdrawableBalancesParams,
|
|
5
6
|
type ProviderWithdrawableBalance,
|
|
@@ -27,13 +28,15 @@ export type GetWithdrawableBalancesParams =
|
|
|
27
28
|
/**
|
|
28
29
|
* The `(asset, route)` pairs `params.address` can withdraw from Hyperliquid,
|
|
29
30
|
* read from the venue's own `withdrawable` and spot `total`/`hold` figures.
|
|
31
|
+
* Hyperliquid charges its flat withdrawal fee in USDC, so the backend's
|
|
32
|
+
* `withdrawalFeeUsd` is the fee in quote-asset units on every USDC row.
|
|
30
33
|
*
|
|
31
34
|
* Only the main perps dex is read: a withdrawal to L1 draws on that account,
|
|
32
35
|
* and a sub-dex balance reaches it through a transfer first.
|
|
33
36
|
*
|
|
34
|
-
* @throws {PerpsError} On Hyperliquid REST
|
|
35
|
-
* failures, and `SDKError` when the market registry
|
|
36
|
-
* market to name the quote asset.
|
|
37
|
+
* @throws {PerpsError} On Hyperliquid REST or backend `/providers` error,
|
|
38
|
+
* network, or parsing failures, and `SDKError` when the market registry
|
|
39
|
+
* carries no main perps market to name the quote asset.
|
|
37
40
|
* @public
|
|
38
41
|
*/
|
|
39
42
|
export const getWithdrawableBalances = async (
|
|
@@ -44,24 +47,26 @@ export const getWithdrawableBalances = async (
|
|
|
44
47
|
const registry = getMarketRegistry(client, PROVIDER_KEY)
|
|
45
48
|
const infoOpts = hlInfoOptions(client, options)
|
|
46
49
|
|
|
47
|
-
const [markets, abstraction, state, spotState] =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
const [{ providers }, markets, abstraction, state, spotState] =
|
|
51
|
+
await Promise.all([
|
|
52
|
+
getProviders(client, options),
|
|
53
|
+
registry.sync(),
|
|
54
|
+
infoRequest<HlAbstractionMode | null>(
|
|
55
|
+
apiUrl,
|
|
56
|
+
{ type: 'userAbstraction', user: params.address },
|
|
57
|
+
infoOpts
|
|
58
|
+
),
|
|
59
|
+
infoRequest<HlClearinghouseState>(
|
|
60
|
+
apiUrl,
|
|
61
|
+
{ type: 'clearinghouseState', user: params.address },
|
|
62
|
+
infoOpts
|
|
63
|
+
),
|
|
64
|
+
infoRequest<HlSpotClearinghouseState>(
|
|
65
|
+
apiUrl,
|
|
66
|
+
{ type: 'spotClearinghouseState', user: params.address },
|
|
67
|
+
infoOpts
|
|
68
|
+
),
|
|
69
|
+
])
|
|
65
70
|
|
|
66
71
|
const quoteAsset = markets.find(
|
|
67
72
|
(market) => market.categoryId === MAIN_MARKET_ID
|
|
@@ -75,10 +80,15 @@ export const getWithdrawableBalances = async (
|
|
|
75
80
|
throw error
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
const feeUsd = providers.find(
|
|
84
|
+
(provider) => provider.key === PROVIDER_KEY
|
|
85
|
+
)?.withdrawalFeeUsd
|
|
86
|
+
|
|
78
87
|
return hyperliquidWithdrawableBalances(
|
|
79
88
|
abstraction,
|
|
80
89
|
state,
|
|
81
90
|
spotState,
|
|
82
|
-
quoteAsset.id
|
|
91
|
+
quoteAsset.id,
|
|
92
|
+
feeUsd === undefined ? undefined : String(feeUsd)
|
|
83
93
|
)
|
|
84
94
|
}
|
package/src/utils/explorer.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
createWarnOnce,
|
|
3
|
+
ExplorerChainId,
|
|
4
|
+
explorerTxUrl,
|
|
5
|
+
PerpsError,
|
|
6
|
+
} from '@lifi/perps-sdk'
|
|
7
|
+
import type { Order, OrderBase } from '@lifi/perps-types'
|
|
2
8
|
import { isHex } from 'viem'
|
|
3
|
-
import { HYPERLIQUID_EXPLORER_RPC_URL } from '../constants.js'
|
|
9
|
+
import { HYPERLIQUID_EXPLORER_RPC_URL, PROVIDER_KEY } from '../constants.js'
|
|
4
10
|
import { type HlExplorerTx, isHlExplorerTx } from '../types/explorer.js'
|
|
5
11
|
import { hlPostJson, type InfoRequestOptions } from './infoClient.js'
|
|
6
12
|
|
|
@@ -91,3 +97,46 @@ export const matchOrderActionHash = (
|
|
|
91
97
|
)
|
|
92
98
|
return match?.hash
|
|
93
99
|
}
|
|
100
|
+
|
|
101
|
+
const warn = createWarnOnce()
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Set `explorerLink` on each order whose placement transaction the explorer
|
|
105
|
+
* window still holds. One explorer read serves every order, and it is skipped
|
|
106
|
+
* when no order carries a client order id to match on. The link is
|
|
107
|
+
* supplementary, so an explorer failure warns and leaves the orders unlinked
|
|
108
|
+
* instead of failing the order read. A caller cancellation still rejects.
|
|
109
|
+
*/
|
|
110
|
+
export const withExplorerLinks = async (
|
|
111
|
+
orders: Order[],
|
|
112
|
+
address: string,
|
|
113
|
+
options: InfoRequestOptions
|
|
114
|
+
): Promise<Order[]> => {
|
|
115
|
+
if (!orders.some((order) => order.clientOrderId !== undefined)) {
|
|
116
|
+
return orders
|
|
117
|
+
}
|
|
118
|
+
let txs: HlExplorerTx[]
|
|
119
|
+
try {
|
|
120
|
+
txs = await fetchUserTransactions(address, options)
|
|
121
|
+
} catch (error) {
|
|
122
|
+
// `hlPostJson` wraps a signal timeout as a `ServerError`, so the signal,
|
|
123
|
+
// not the error type, tells a caller cancellation from an explorer failure.
|
|
124
|
+
if (!(error instanceof PerpsError) || options.signal?.aborted) {
|
|
125
|
+
throw error
|
|
126
|
+
}
|
|
127
|
+
// The message stays out of the key: a transport failure names hosts and
|
|
128
|
+
// ports, so keying on it would let the dedupe set grow without bound.
|
|
129
|
+
warn(
|
|
130
|
+
'explorer link lookup failed',
|
|
131
|
+
`[${PROVIDER_KEY}] explorer link lookup failed: ${error.message}`
|
|
132
|
+
)
|
|
133
|
+
return orders
|
|
134
|
+
}
|
|
135
|
+
return orders.map((order) => {
|
|
136
|
+
const explorerLink = explorerTxUrl(
|
|
137
|
+
ExplorerChainId.HYPERLIQUID,
|
|
138
|
+
matchOrderActionHash(txs, order)
|
|
139
|
+
)
|
|
140
|
+
return explorerLink === undefined ? order : { ...order, explorerLink }
|
|
141
|
+
})
|
|
142
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -56,7 +56,7 @@ export {
|
|
|
56
56
|
spotPriceById,
|
|
57
57
|
} from './spotBalance.js'
|
|
58
58
|
export { partitionSpotBalances, type SpotPartition } from './spotCollateral.js'
|
|
59
|
-
export {
|
|
59
|
+
export { positionRemovableMargin } from './transferMargin.js'
|
|
60
60
|
export {
|
|
61
61
|
type DexMarginSummary,
|
|
62
62
|
perpsTotals,
|
|
@@ -2,24 +2,27 @@ import { PerpsError } from '@lifi/perps-sdk'
|
|
|
2
2
|
import {
|
|
3
3
|
PerpsErrorCode,
|
|
4
4
|
type Position,
|
|
5
|
-
type PositionMarginConstraints,
|
|
6
5
|
positionSupportsMarginAdjustment,
|
|
6
|
+
positionSupportsMarginRemoval,
|
|
7
7
|
} from '@lifi/perps-types'
|
|
8
8
|
import Big from 'big.js'
|
|
9
9
|
|
|
10
10
|
const NOTIONAL_FLOOR_RATIO = '0.1'
|
|
11
|
-
const
|
|
11
|
+
const AMOUNT_DECIMALS = 6
|
|
12
12
|
|
|
13
|
-
function
|
|
14
|
-
let amount: Big
|
|
13
|
+
function positionAmount(value: string, field: string): Big {
|
|
15
14
|
try {
|
|
16
|
-
|
|
15
|
+
return new Big(value)
|
|
17
16
|
} catch {
|
|
18
17
|
throw new PerpsError(
|
|
19
18
|
PerpsErrorCode.ValidationError,
|
|
20
19
|
`Invalid decimal string on Position.${field}: '${value}'`
|
|
21
20
|
)
|
|
22
21
|
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function positivePositionAmount(value: string, field: string): Big {
|
|
25
|
+
const amount = positionAmount(value, field)
|
|
23
26
|
if (amount.lte(0)) {
|
|
24
27
|
throw new PerpsError(
|
|
25
28
|
PerpsErrorCode.ValidationError,
|
|
@@ -30,36 +33,44 @@ function positivePositionAmount(value: string, field: string): Big {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
|
-
* Hyperliquid
|
|
34
|
-
* `max(initial_margin_required, 0.1
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* markets without individual margin adjustment return `undefined`.
|
|
36
|
+
* Hyperliquid removable isolated margin:
|
|
37
|
+
* `marginUsed − max(initial_margin_required, 0.1 × total_position_value)`.
|
|
38
|
+
* Hyperliquid isolated `marginUsed` already includes the unrealized PnL of
|
|
39
|
+
* the position, so the PnL is not added again, and it can reach zero or less
|
|
40
|
+
* before liquidation.
|
|
39
41
|
*
|
|
42
|
+
* @returns `undefined` for cross positions and markets without individual
|
|
43
|
+
* margin adjustment; `'0'` for add-only strict-isolated markets and when
|
|
44
|
+
* nothing is removable.
|
|
45
|
+
* @throws {PerpsError} `ValidationError` when a required `Position` decimal
|
|
46
|
+
* is malformed, or when a size, price or requirement is not positive.
|
|
40
47
|
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/trading/margining
|
|
41
48
|
* @public
|
|
42
49
|
*/
|
|
43
|
-
export function
|
|
50
|
+
export function positionRemovableMargin(
|
|
44
51
|
position: Position
|
|
45
|
-
):
|
|
52
|
+
): string | undefined {
|
|
46
53
|
if (!positionSupportsMarginAdjustment(position)) {
|
|
47
54
|
return undefined
|
|
48
55
|
}
|
|
56
|
+
if (!positionSupportsMarginRemoval(position)) {
|
|
57
|
+
return '0'
|
|
58
|
+
}
|
|
59
|
+
const marginUsed = positionAmount(position.marginUsed, 'marginUsed')
|
|
49
60
|
const initialMargin = positivePositionAmount(
|
|
50
61
|
position.initialMarginRequirement,
|
|
51
62
|
'initialMarginRequirement'
|
|
52
63
|
)
|
|
53
|
-
const
|
|
54
|
-
positivePositionAmount(position.markPrice, 'markPrice')
|
|
55
|
-
|
|
56
|
-
const notionalFloor = notional.times(NOTIONAL_FLOOR_RATIO)
|
|
64
|
+
const notionalFloor = positivePositionAmount(position.size, 'size')
|
|
65
|
+
.times(positivePositionAmount(position.markPrice, 'markPrice'))
|
|
66
|
+
.times(NOTIONAL_FLOOR_RATIO)
|
|
57
67
|
const minimumMargin = initialMargin.gt(notionalFloor)
|
|
58
68
|
? initialMargin
|
|
59
69
|
: notionalFloor
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
const removable = marginUsed.minus(minimumMargin)
|
|
72
|
+
if (removable.lte(0)) {
|
|
73
|
+
return '0'
|
|
64
74
|
}
|
|
75
|
+
return removable.round(AMOUNT_DECIMALS, Big.roundDown).toFixed()
|
|
65
76
|
}
|
|
@@ -16,15 +16,24 @@ import { toWireBig } from './decimal.js'
|
|
|
16
16
|
* per-asset venue minimum.
|
|
17
17
|
*
|
|
18
18
|
* @param quoteAssetId - `Asset.id` the perps-route row is keyed by.
|
|
19
|
+
* @param withdrawalFee - Flat venue fee in quote-asset units, set on every
|
|
20
|
+
* quote-asset row. Absent leaves every row without a fee.
|
|
19
21
|
* @public
|
|
20
22
|
*/
|
|
21
23
|
export const hyperliquidWithdrawableBalances = (
|
|
22
24
|
abstraction: HlAbstractionMode | null,
|
|
23
25
|
state: HlClearinghouseState,
|
|
24
26
|
spotState: HlSpotClearinghouseState,
|
|
25
|
-
quoteAssetId: string
|
|
27
|
+
quoteAssetId: string,
|
|
28
|
+
withdrawalFee?: string
|
|
26
29
|
): ProviderWithdrawableBalance[] => {
|
|
27
30
|
const rows: ProviderWithdrawableBalance[] = []
|
|
31
|
+
const fee =
|
|
32
|
+
withdrawalFee === undefined
|
|
33
|
+
? undefined
|
|
34
|
+
: toWireBig(withdrawalFee, 'providers.withdrawalFeeUsd').toFixed()
|
|
35
|
+
const feeFor = (assetId: string) =>
|
|
36
|
+
fee === undefined || assetId !== quoteAssetId ? {} : { withdrawalFee: fee }
|
|
28
37
|
|
|
29
38
|
if (isUnifiedAbstraction(abstraction)) {
|
|
30
39
|
for (const balance of spotState.balances) {
|
|
@@ -35,10 +44,12 @@ export const hyperliquidWithdrawableBalances = (
|
|
|
35
44
|
toWireBig(balance.hold, 'spotBalance.hold')
|
|
36
45
|
)
|
|
37
46
|
if (spot.gt(0)) {
|
|
47
|
+
const assetId = String(balance.token)
|
|
38
48
|
rows.push({
|
|
39
|
-
assetId
|
|
49
|
+
assetId,
|
|
40
50
|
route: 'spot',
|
|
41
51
|
available: spot.toFixed(),
|
|
52
|
+
...feeFor(assetId),
|
|
42
53
|
})
|
|
43
54
|
}
|
|
44
55
|
}
|
|
@@ -50,6 +61,7 @@ export const hyperliquidWithdrawableBalances = (
|
|
|
50
61
|
assetId: quoteAssetId,
|
|
51
62
|
route: 'perps',
|
|
52
63
|
available: perps.toFixed(),
|
|
64
|
+
...feeFor(quoteAssetId),
|
|
53
65
|
})
|
|
54
66
|
}
|
|
55
67
|
|