@pear-protocol/hyperliquid-sdk 0.0.65 → 0.0.66-usdh-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/clients/hyperliquid.d.ts +1 -1
- package/dist/clients/orders.d.ts +41 -0
- package/dist/clients/positions.d.ts +3 -2
- package/dist/clients/watchlist.d.ts +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useMarketData.d.ts +9 -7
- package/dist/hooks/useSpotBalances.d.ts +7 -0
- package/dist/hooks/useSpotOrder.d.ts +13 -0
- package/dist/hooks/useTrading.d.ts +0 -3
- package/dist/hooks/useWebData.d.ts +21 -3
- package/dist/index.d.ts +239 -41
- package/dist/index.js +1307 -274
- package/dist/provider.d.ts +1 -1
- package/dist/store/tokenSelectionMetadataStore.d.ts +1 -1
- package/dist/store/userSelection.d.ts +2 -1
- package/dist/types.d.ts +74 -20
- package/dist/utils/position-validator.d.ts +20 -0
- package/dist/utils/symbol-translator.d.ts +32 -3
- package/dist/utils/token-metadata-extractor.d.ts +9 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const useUserData = create((set) => ({
|
|
|
23
23
|
twapDetails: null,
|
|
24
24
|
notifications: null,
|
|
25
25
|
userExtraAgents: null,
|
|
26
|
+
spotState: null,
|
|
26
27
|
setAccessToken: (token) => set({ accessToken: token }),
|
|
27
28
|
setRefreshToken: (token) => set({ refreshToken: token }),
|
|
28
29
|
setIsAuthenticated: (value) => set({ isAuthenticated: value }),
|
|
@@ -43,6 +44,7 @@ const useUserData = create((set) => ({
|
|
|
43
44
|
setAccountSummary: (value) => set({ accountSummary: value }),
|
|
44
45
|
setTwapDetails: (value) => set({ twapDetails: value }),
|
|
45
46
|
setNotifications: (value) => set({ notifications: value }),
|
|
47
|
+
setSpotState: (value) => set({ spotState: value }),
|
|
46
48
|
clean: () => set({
|
|
47
49
|
accessToken: null,
|
|
48
50
|
refreshToken: null,
|
|
@@ -54,6 +56,7 @@ const useUserData = create((set) => ({
|
|
|
54
56
|
accountSummary: null,
|
|
55
57
|
twapDetails: null,
|
|
56
58
|
notifications: null,
|
|
59
|
+
spotState: null,
|
|
57
60
|
}),
|
|
58
61
|
setUserExtraAgents: (value) => set({ userExtraAgents: value }),
|
|
59
62
|
}));
|
|
@@ -71,18 +74,71 @@ const useMarketData = create((set) => ({
|
|
|
71
74
|
* Convert a full/prefixed symbol (e.g., "xyz:XYZ100") to a display symbol (e.g., "XYZ100").
|
|
72
75
|
*/
|
|
73
76
|
function toDisplaySymbol(symbol) {
|
|
74
|
-
const parts = symbol.split(
|
|
77
|
+
const parts = symbol.split(':');
|
|
75
78
|
return parts.length > 1 ? parts.slice(-1)[0] : symbol;
|
|
76
79
|
}
|
|
77
80
|
/**
|
|
78
81
|
* Convert a display symbol back to backend form using a provided map.
|
|
79
82
|
* If mapping is missing, returns the original symbol.
|
|
80
|
-
*
|
|
81
|
-
* @param
|
|
83
|
+
* For multi-market assets, returns the first available market.
|
|
84
|
+
* @param displaySymbol e.g., "TSLA"
|
|
85
|
+
* @param hip3Assets map of display -> all full market names (e.g., "TSLA" -> ["xyz:TSLA", "flx:TSLA"])
|
|
82
86
|
*/
|
|
83
|
-
function toBackendSymbol(displaySymbol,
|
|
87
|
+
function toBackendSymbol(displaySymbol, hip3Assets) {
|
|
88
|
+
const markets = hip3Assets.get(displaySymbol);
|
|
89
|
+
// Return first market if available, otherwise return original symbol
|
|
90
|
+
return markets && markets.length > 0 ? markets[0] : displaySymbol;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Convert a display symbol to backend form for a specific market prefix.
|
|
94
|
+
* This is useful when an asset is available on multiple markets (e.g., xyz:TSLA and flx:TSLA).
|
|
95
|
+
* @param displaySymbol e.g., "TSLA"
|
|
96
|
+
* @param marketPrefix e.g., "xyz" or "flx"
|
|
97
|
+
* @param hip3Assets map of display -> all full market names
|
|
98
|
+
* @returns Full market name if found, null if prefix not specified for multi-market asset, otherwise displaySymbol with prefix
|
|
99
|
+
*/
|
|
100
|
+
function toBackendSymbolWithMarket(displaySymbol, marketPrefix, hip3Assets) {
|
|
101
|
+
const availableMarkets = hip3Assets.get(displaySymbol);
|
|
102
|
+
if (!availableMarkets || availableMarkets.length === 0) {
|
|
103
|
+
// Not a HIP-3 asset, return as-is or with prefix if provided
|
|
104
|
+
return marketPrefix ? `${marketPrefix}:${displaySymbol}` : displaySymbol;
|
|
105
|
+
}
|
|
106
|
+
if (marketPrefix) {
|
|
107
|
+
// Find the market with the specified prefix
|
|
108
|
+
const targetMarket = availableMarkets.find((market) => market.toLowerCase().startsWith(`${marketPrefix.toLowerCase()}:`));
|
|
109
|
+
if (targetMarket) {
|
|
110
|
+
return targetMarket;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// No prefix specified or not found, return null to force explicit market selection
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get all available markets for a display symbol.
|
|
118
|
+
* @param displaySymbol e.g., "TSLA"
|
|
119
|
+
* @param hip3Assets map of display -> all full market names
|
|
120
|
+
* @returns Array of full market names, e.g., ["xyz:TSLA", "flx:TSLA"]
|
|
121
|
+
*/
|
|
122
|
+
function getAvailableMarkets(displaySymbol, hip3Assets) {
|
|
84
123
|
var _a;
|
|
85
|
-
return (_a =
|
|
124
|
+
return (_a = hip3Assets.get(displaySymbol)) !== null && _a !== void 0 ? _a : [];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Extract the market prefix from a full market name.
|
|
128
|
+
* @param fullSymbol e.g., "xyz:TSLA"
|
|
129
|
+
* @returns The prefix (e.g., "xyz") or undefined if no prefix
|
|
130
|
+
*/
|
|
131
|
+
function getMarketPrefix(fullSymbol) {
|
|
132
|
+
const parts = fullSymbol.split(':');
|
|
133
|
+
return parts.length > 1 ? parts[0] : undefined;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check if a symbol is a HIP-3 market (has a prefix).
|
|
137
|
+
* @param symbol e.g., "xyz:TSLA" or "TSLA"
|
|
138
|
+
* @returns true if the symbol has a market prefix
|
|
139
|
+
*/
|
|
140
|
+
function isHip3Market(symbol) {
|
|
141
|
+
return symbol.includes(':');
|
|
86
142
|
}
|
|
87
143
|
|
|
88
144
|
const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
@@ -99,7 +155,8 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
99
155
|
try {
|
|
100
156
|
const message = JSON.parse(event.data);
|
|
101
157
|
// Handle subscription responses (only if they don't have channel data)
|
|
102
|
-
if (('success' in message || 'error' in message) &&
|
|
158
|
+
if (('success' in message || 'error' in message) &&
|
|
159
|
+
!('channel' in message)) {
|
|
103
160
|
if (message.error) {
|
|
104
161
|
setLastError(message.error);
|
|
105
162
|
}
|
|
@@ -118,12 +175,21 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
118
175
|
switch (dataMessage.channel) {
|
|
119
176
|
case 'trade-histories':
|
|
120
177
|
{
|
|
178
|
+
const mapAsset = (a) => {
|
|
179
|
+
var _a, _b;
|
|
180
|
+
const extractedPrefix = getMarketPrefix(a.coin);
|
|
181
|
+
return {
|
|
182
|
+
...a,
|
|
183
|
+
coin: toDisplaySymbol(a.coin),
|
|
184
|
+
marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
|
|
185
|
+
};
|
|
186
|
+
};
|
|
121
187
|
const list = dataMessage.data.map((item) => {
|
|
122
188
|
var _a, _b;
|
|
123
189
|
return ({
|
|
124
190
|
...item,
|
|
125
|
-
closedLongAssets: item.closedLongAssets.map(
|
|
126
|
-
closedShortAssets: item.closedShortAssets.map(
|
|
191
|
+
closedLongAssets: item.closedLongAssets.map(mapAsset),
|
|
192
|
+
closedShortAssets: item.closedShortAssets.map(mapAsset),
|
|
127
193
|
positionLongAssets: (_a = item.positionLongAssets) === null || _a === void 0 ? void 0 : _a.map((a) => toDisplaySymbol(a)),
|
|
128
194
|
positionShortAssets: (_b = item.positionShortAssets) === null || _b === void 0 ? void 0 : _b.map((a) => toDisplaySymbol(a)),
|
|
129
195
|
});
|
|
@@ -133,10 +199,19 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
133
199
|
break;
|
|
134
200
|
case 'open-positions':
|
|
135
201
|
{
|
|
202
|
+
const enrichAsset = (a) => {
|
|
203
|
+
var _a, _b;
|
|
204
|
+
const extractedPrefix = getMarketPrefix(a.coin);
|
|
205
|
+
return {
|
|
206
|
+
...a,
|
|
207
|
+
coin: toDisplaySymbol(a.coin),
|
|
208
|
+
marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
|
|
209
|
+
};
|
|
210
|
+
};
|
|
136
211
|
const list = dataMessage.data.map((pos) => ({
|
|
137
212
|
...pos,
|
|
138
|
-
longAssets: pos.longAssets.map(
|
|
139
|
-
shortAssets: pos.shortAssets.map(
|
|
213
|
+
longAssets: pos.longAssets.map(enrichAsset),
|
|
214
|
+
shortAssets: pos.shortAssets.map(enrichAsset),
|
|
140
215
|
}));
|
|
141
216
|
setRawOpenPositions(list);
|
|
142
217
|
}
|
|
@@ -145,8 +220,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
145
220
|
{
|
|
146
221
|
const list = dataMessage.data.map((order) => ({
|
|
147
222
|
...order,
|
|
148
|
-
longAssets: order.longAssets.map((a) => ({
|
|
149
|
-
|
|
223
|
+
longAssets: order.longAssets.map((a) => ({
|
|
224
|
+
...a,
|
|
225
|
+
asset: toDisplaySymbol(a.asset),
|
|
226
|
+
})),
|
|
227
|
+
shortAssets: order.shortAssets.map((a) => ({
|
|
228
|
+
...a,
|
|
229
|
+
asset: toDisplaySymbol(a.asset),
|
|
230
|
+
})),
|
|
150
231
|
}));
|
|
151
232
|
setOpenOrders(list);
|
|
152
233
|
}
|
|
@@ -156,10 +237,20 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
156
237
|
break;
|
|
157
238
|
case 'twap-details':
|
|
158
239
|
{
|
|
240
|
+
const mapTwapAsset = (a) => {
|
|
241
|
+
var _a, _b, _c;
|
|
242
|
+
const extractedPrefix = getMarketPrefix(a.asset);
|
|
243
|
+
return {
|
|
244
|
+
...a,
|
|
245
|
+
asset: toDisplaySymbol(a.asset),
|
|
246
|
+
marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
|
|
247
|
+
collateralToken: (_c = a.collateralToken) !== null && _c !== void 0 ? _c : undefined,
|
|
248
|
+
};
|
|
249
|
+
};
|
|
159
250
|
const list = dataMessage.data.map((twap) => ({
|
|
160
251
|
...twap,
|
|
161
|
-
longAssets: twap.longAssets.map(
|
|
162
|
-
shortAssets: twap.shortAssets.map(
|
|
252
|
+
longAssets: twap.longAssets.map(mapTwapAsset),
|
|
253
|
+
shortAssets: twap.shortAssets.map(mapTwapAsset),
|
|
163
254
|
}));
|
|
164
255
|
setTwapDetails(list);
|
|
165
256
|
}
|
|
@@ -172,8 +263,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
172
263
|
const md = dataMessage.data;
|
|
173
264
|
const mapGroup = (g) => ({
|
|
174
265
|
...g,
|
|
175
|
-
longAssets: g.longAssets.map((a) => ({
|
|
176
|
-
|
|
266
|
+
longAssets: g.longAssets.map((a) => ({
|
|
267
|
+
...a,
|
|
268
|
+
asset: toDisplaySymbol(a.asset),
|
|
269
|
+
})),
|
|
270
|
+
shortAssets: g.shortAssets.map((a) => ({
|
|
271
|
+
...a,
|
|
272
|
+
asset: toDisplaySymbol(a.asset),
|
|
273
|
+
})),
|
|
177
274
|
});
|
|
178
275
|
const mapped = {
|
|
179
276
|
active: md.active.map(mapGroup),
|
|
@@ -191,7 +288,15 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
191
288
|
catch (error) {
|
|
192
289
|
setLastError(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`);
|
|
193
290
|
}
|
|
194
|
-
}, [
|
|
291
|
+
}, [
|
|
292
|
+
setTradeHistories,
|
|
293
|
+
setRawOpenPositions,
|
|
294
|
+
setOpenOrders,
|
|
295
|
+
setAccountSummary,
|
|
296
|
+
setTwapDetails,
|
|
297
|
+
setNotifications,
|
|
298
|
+
setMarketData,
|
|
299
|
+
]);
|
|
195
300
|
const connect = useCallback(() => {
|
|
196
301
|
if (!enabled || !wsUrl)
|
|
197
302
|
return;
|
|
@@ -260,7 +365,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
260
365
|
'open-orders',
|
|
261
366
|
'twap-details',
|
|
262
367
|
'fills-checkpoint',
|
|
263
|
-
'notifications'
|
|
368
|
+
'notifications',
|
|
264
369
|
];
|
|
265
370
|
const globalChannels = ['market-data'];
|
|
266
371
|
if (address && address !== lastSubscribedAddress) {
|
|
@@ -269,14 +374,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
269
374
|
sendMessage(JSON.stringify({
|
|
270
375
|
action: 'unsubscribe',
|
|
271
376
|
address: lastSubscribedAddress,
|
|
272
|
-
channels: addressSpecificChannels
|
|
377
|
+
channels: addressSpecificChannels,
|
|
273
378
|
}));
|
|
274
379
|
}
|
|
275
380
|
// Subscribe to all channels (global + address-specific)
|
|
276
381
|
sendMessage(JSON.stringify({
|
|
277
382
|
action: 'subscribe',
|
|
278
383
|
address: address,
|
|
279
|
-
channels: [...globalChannels, ...addressSpecificChannels]
|
|
384
|
+
channels: [...globalChannels, ...addressSpecificChannels],
|
|
280
385
|
}));
|
|
281
386
|
setLastSubscribedAddress(address);
|
|
282
387
|
setLastError(null);
|
|
@@ -286,7 +391,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
286
391
|
sendMessage(JSON.stringify({
|
|
287
392
|
action: 'unsubscribe',
|
|
288
393
|
address: lastSubscribedAddress,
|
|
289
|
-
channels: addressSpecificChannels
|
|
394
|
+
channels: addressSpecificChannels,
|
|
290
395
|
}));
|
|
291
396
|
setLastSubscribedAddress(null);
|
|
292
397
|
}
|
|
@@ -294,7 +399,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
294
399
|
// If no address but connected, subscribe to global channels only
|
|
295
400
|
sendMessage(JSON.stringify({
|
|
296
401
|
action: 'subscribe',
|
|
297
|
-
channels: globalChannels
|
|
402
|
+
channels: globalChannels,
|
|
298
403
|
}));
|
|
299
404
|
}
|
|
300
405
|
}, [isConnected, address, lastSubscribedAddress, sendMessage]);
|
|
@@ -318,10 +423,12 @@ const useHyperliquidData = create((set, get) => ({
|
|
|
318
423
|
finalAtOICaps: null,
|
|
319
424
|
aggregatedClearingHouseState: null,
|
|
320
425
|
perpMetaAssets: null,
|
|
321
|
-
|
|
426
|
+
allPerpMetaAssets: null,
|
|
427
|
+
hip3Assets: new Map(),
|
|
428
|
+
hip3MarketPrefixes: new Map(),
|
|
322
429
|
setAllMids: (value) => set({ allMids: value }),
|
|
323
430
|
setActiveAssetData: (value) => set((state) => ({
|
|
324
|
-
activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value
|
|
431
|
+
activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value,
|
|
325
432
|
})),
|
|
326
433
|
deleteActiveAssetData: (key) => {
|
|
327
434
|
set((state) => {
|
|
@@ -356,15 +463,130 @@ const useHyperliquidData = create((set, get) => ({
|
|
|
356
463
|
activeAssetData: {
|
|
357
464
|
...state.activeAssetData,
|
|
358
465
|
[key]: value,
|
|
359
|
-
}
|
|
466
|
+
},
|
|
360
467
|
})),
|
|
361
468
|
setFinalAssetContexts: (value) => set({ finalAssetContexts: value }),
|
|
362
469
|
setFinalAtOICaps: (value) => set({ finalAtOICaps: value }),
|
|
363
470
|
setAggregatedClearingHouseState: (value) => set({ aggregatedClearingHouseState: value }),
|
|
364
471
|
setPerpMetaAssets: (value) => set({ perpMetaAssets: value }),
|
|
365
|
-
|
|
472
|
+
setAllPerpMetaAssets: (value) => set({ allPerpMetaAssets: value }),
|
|
473
|
+
setHip3Assets: (value) => set({ hip3Assets: value }),
|
|
474
|
+
setHip3MarketPrefixes: (value) => set({ hip3MarketPrefixes: value }),
|
|
366
475
|
}));
|
|
367
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Minimum USD value required per asset when creating a position
|
|
479
|
+
*/
|
|
480
|
+
const MINIMUM_ASSET_USD_VALUE = 11;
|
|
481
|
+
/**
|
|
482
|
+
* Maximum number of assets allowed per leg (long or short) in a position
|
|
483
|
+
*/
|
|
484
|
+
const MAX_ASSETS_PER_LEG = 15;
|
|
485
|
+
/**
|
|
486
|
+
* Validation error for minimum position size
|
|
487
|
+
*/
|
|
488
|
+
class MinimumPositionSizeError extends Error {
|
|
489
|
+
constructor(assetName, assetValue, minimumRequired) {
|
|
490
|
+
super(`Asset "${assetName}" has a USD value of $${assetValue.toFixed(2)}, which is below the minimum required value of $${minimumRequired.toFixed(2)}`);
|
|
491
|
+
this.assetName = assetName;
|
|
492
|
+
this.assetValue = assetValue;
|
|
493
|
+
this.minimumRequired = minimumRequired;
|
|
494
|
+
this.name = "MinimumPositionSizeError";
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Validation error for exceeding maximum assets per leg
|
|
499
|
+
*/
|
|
500
|
+
class MaxAssetsPerLegError extends Error {
|
|
501
|
+
constructor(leg, assetCount, maxAllowed) {
|
|
502
|
+
super(`Maximum ${maxAllowed} assets allowed per leg. Your ${leg} leg has ${assetCount} assets. Please reduce the number of assets to continue.`);
|
|
503
|
+
this.leg = leg;
|
|
504
|
+
this.assetCount = assetCount;
|
|
505
|
+
this.maxAllowed = maxAllowed;
|
|
506
|
+
this.name = "MaxAssetsPerLegError";
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Validates that each leg doesn't exceed the maximum number of assets
|
|
511
|
+
* @param longAssets Array of long assets
|
|
512
|
+
* @param shortAssets Array of short assets
|
|
513
|
+
* @throws MaxAssetsPerLegError if any leg exceeds the maximum allowed assets
|
|
514
|
+
*/
|
|
515
|
+
function validateMaxAssetsPerLeg(longAssets, shortAssets) {
|
|
516
|
+
const longCount = (longAssets === null || longAssets === void 0 ? void 0 : longAssets.length) || 0;
|
|
517
|
+
const shortCount = (shortAssets === null || shortAssets === void 0 ? void 0 : shortAssets.length) || 0;
|
|
518
|
+
if (longCount > MAX_ASSETS_PER_LEG) {
|
|
519
|
+
throw new MaxAssetsPerLegError("long", longCount, MAX_ASSETS_PER_LEG);
|
|
520
|
+
}
|
|
521
|
+
if (shortCount > MAX_ASSETS_PER_LEG) {
|
|
522
|
+
throw new MaxAssetsPerLegError("short", shortCount, MAX_ASSETS_PER_LEG);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Validates that each asset in a position has at least the minimum USD value
|
|
527
|
+
* @param usdValue Total USD value for the position
|
|
528
|
+
* @param longAssets Array of long assets with weights
|
|
529
|
+
* @param shortAssets Array of short assets with weights
|
|
530
|
+
* @throws MinimumPositionSizeError if any asset has less than the minimum USD value
|
|
531
|
+
*/
|
|
532
|
+
function validateMinimumAssetSize(usdValue, longAssets, shortAssets) {
|
|
533
|
+
var _a;
|
|
534
|
+
const allAssets = [...(longAssets || []), ...(shortAssets || [])];
|
|
535
|
+
if (allAssets.length === 0) {
|
|
536
|
+
return; // No assets to validate
|
|
537
|
+
}
|
|
538
|
+
// Calculate total weight
|
|
539
|
+
const totalWeight = allAssets.reduce((sum, asset) => { var _a; return sum + ((_a = asset.weight) !== null && _a !== void 0 ? _a : 0); }, 0);
|
|
540
|
+
// If weights are not provided or sum to 0, assume equal distribution
|
|
541
|
+
const hasWeights = totalWeight > 0;
|
|
542
|
+
const equalWeight = hasWeights ? 0 : 1 / allAssets.length;
|
|
543
|
+
// Validate each asset
|
|
544
|
+
for (const asset of allAssets) {
|
|
545
|
+
const weight = hasWeights ? (_a = asset.weight) !== null && _a !== void 0 ? _a : 0 : equalWeight;
|
|
546
|
+
const assetUsdValue = usdValue * weight;
|
|
547
|
+
if (assetUsdValue < MINIMUM_ASSET_USD_VALUE) {
|
|
548
|
+
throw new MinimumPositionSizeError(asset.asset, assetUsdValue, MINIMUM_ASSET_USD_VALUE);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Calculates the minimum USD value required for a position based on the number of assets
|
|
554
|
+
* @param longAssets Array of long assets
|
|
555
|
+
* @param shortAssets Array of short assets
|
|
556
|
+
* @returns The minimum total USD value required
|
|
557
|
+
*/
|
|
558
|
+
function calculateMinimumPositionValue(longAssets, shortAssets) {
|
|
559
|
+
const totalAssets = ((longAssets === null || longAssets === void 0 ? void 0 : longAssets.length) || 0) + ((shortAssets === null || shortAssets === void 0 ? void 0 : shortAssets.length) || 0);
|
|
560
|
+
if (totalAssets === 0) {
|
|
561
|
+
return 0;
|
|
562
|
+
}
|
|
563
|
+
return MINIMUM_ASSET_USD_VALUE * totalAssets;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Validates and provides a user-friendly error message with suggestions
|
|
567
|
+
* @param usdValue Total USD value for the position
|
|
568
|
+
* @param longAssets Array of long assets with weights
|
|
569
|
+
* @param shortAssets Array of short assets with weights
|
|
570
|
+
* @returns Validation result with success flag and optional error message
|
|
571
|
+
*/
|
|
572
|
+
function validatePositionSize(usdValue, longAssets, shortAssets) {
|
|
573
|
+
try {
|
|
574
|
+
validateMinimumAssetSize(usdValue, longAssets, shortAssets);
|
|
575
|
+
return { valid: true };
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
if (error instanceof MinimumPositionSizeError) {
|
|
579
|
+
const minimumRequired = calculateMinimumPositionValue(longAssets, shortAssets);
|
|
580
|
+
return {
|
|
581
|
+
valid: false,
|
|
582
|
+
error: error.message,
|
|
583
|
+
minimumRequired,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
throw error;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
368
590
|
const DEFAULT_STATE = {
|
|
369
591
|
longTokens: [
|
|
370
592
|
{ symbol: "HYPE", weight: 25 },
|
|
@@ -413,14 +635,23 @@ const useUserSelection$1 = create((set, get) => ({
|
|
|
413
635
|
}
|
|
414
636
|
});
|
|
415
637
|
},
|
|
638
|
+
canAddToken: (isLong) => {
|
|
639
|
+
const currentTokens = isLong ? get().longTokens : get().shortTokens;
|
|
640
|
+
return currentTokens.length < MAX_ASSETS_PER_LEG;
|
|
641
|
+
},
|
|
416
642
|
addToken: (isLong) => {
|
|
417
643
|
const currentTokens = isLong ? get().longTokens : get().shortTokens;
|
|
644
|
+
// Check if we've reached the maximum number of assets per leg
|
|
645
|
+
if (currentTokens.length >= MAX_ASSETS_PER_LEG) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
418
648
|
const newIndex = currentTokens.length;
|
|
419
649
|
set((prev) => ({
|
|
420
650
|
...prev,
|
|
421
651
|
selectorConfig: { isLong, index: newIndex },
|
|
422
652
|
openTokenSelector: true,
|
|
423
653
|
}));
|
|
654
|
+
return true;
|
|
424
655
|
},
|
|
425
656
|
removeToken: (isLong, index) => {
|
|
426
657
|
set((prev) => {
|
|
@@ -503,11 +734,12 @@ const useUserSelection$1 = create((set, get) => ({
|
|
|
503
734
|
}));
|
|
504
735
|
|
|
505
736
|
const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
506
|
-
const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState } = useHyperliquidData();
|
|
737
|
+
const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState, } = useHyperliquidData();
|
|
738
|
+
const { setSpotState } = useUserData();
|
|
507
739
|
const { candleInterval } = useUserSelection$1();
|
|
508
740
|
const longTokens = useUserSelection$1((s) => s.longTokens);
|
|
509
741
|
const shortTokens = useUserSelection$1((s) => s.shortTokens);
|
|
510
|
-
const selectedTokenSymbols = useMemo(() =>
|
|
742
|
+
const selectedTokenSymbols = useMemo(() => [...longTokens, ...shortTokens].map((t) => t.symbol), [longTokens, shortTokens]);
|
|
511
743
|
const [lastError, setLastError] = useState(null);
|
|
512
744
|
const [subscribedAddress, setSubscribedAddress] = useState(null);
|
|
513
745
|
const [subscribedTokens, setSubscribedTokens] = useState([]);
|
|
@@ -556,7 +788,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
556
788
|
.map(([, s]) => s)
|
|
557
789
|
.filter(Boolean);
|
|
558
790
|
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v || '0') || 0), 0);
|
|
559
|
-
const toStr = (n) =>
|
|
791
|
+
const toStr = (n) => Number.isFinite(n) ? n.toString() : '0';
|
|
560
792
|
const assetPositions = states.flatMap((s) => s.assetPositions || []);
|
|
561
793
|
const crossMaintenanceMarginUsed = toStr(sum(states.map((s) => s.crossMaintenanceMarginUsed)));
|
|
562
794
|
const crossMarginSummary = {
|
|
@@ -587,19 +819,42 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
587
819
|
case 'allMids':
|
|
588
820
|
{
|
|
589
821
|
const data = response.data;
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
|
|
822
|
+
// Keep BOTH normalized prefixed keys AND display symbol keys
|
|
823
|
+
// This ensures xyz:TSLA and flx:TSLA are stored separately,
|
|
824
|
+
// while also maintaining backward compatibility with non-prefixed lookups
|
|
825
|
+
const mids = {};
|
|
826
|
+
Object.entries(data.mids || {}).forEach(([k, v]) => {
|
|
827
|
+
// Normalize prefixed keys to lowercase prefix (e.g., "XYZ:TSLA" -> "xyz:TSLA")
|
|
828
|
+
// This matches how we look up tokens in the SDK
|
|
829
|
+
let normalizedKey = k;
|
|
830
|
+
if (k.includes(':')) {
|
|
831
|
+
const [prefix, ...rest] = k.split(':');
|
|
832
|
+
normalizedKey = `${prefix.toLowerCase()}:${rest.join(':')}`;
|
|
833
|
+
}
|
|
834
|
+
// Store with normalized key
|
|
835
|
+
mids[normalizedKey] = v;
|
|
836
|
+
// Also store with original key for backward compatibility
|
|
837
|
+
if (k !== normalizedKey) {
|
|
838
|
+
mids[k] = v;
|
|
839
|
+
}
|
|
840
|
+
// Also store with display symbol for backward compatibility
|
|
841
|
+
const displayKey = toDisplaySymbol(k);
|
|
842
|
+
// Only set display key if it doesn't already exist (avoid overwriting market-specific prices)
|
|
843
|
+
if (!(displayKey in mids)) {
|
|
844
|
+
mids[displayKey] = v;
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
setAllMids({ mids });
|
|
596
848
|
}
|
|
597
849
|
break;
|
|
598
850
|
case 'activeAssetData':
|
|
599
851
|
{
|
|
600
852
|
const assetData = response.data;
|
|
601
853
|
const symbol = toDisplaySymbol(assetData.coin);
|
|
602
|
-
const normalized = {
|
|
854
|
+
const normalized = {
|
|
855
|
+
...assetData,
|
|
856
|
+
coin: symbol,
|
|
857
|
+
};
|
|
603
858
|
upsertActiveAssetData(symbol, normalized);
|
|
604
859
|
}
|
|
605
860
|
break;
|
|
@@ -611,6 +866,14 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
611
866
|
addCandleData(symbol, normalized);
|
|
612
867
|
}
|
|
613
868
|
break;
|
|
869
|
+
case 'spotState':
|
|
870
|
+
{
|
|
871
|
+
const spotStateData = response.data;
|
|
872
|
+
if (spotStateData === null || spotStateData === void 0 ? void 0 : spotStateData.spotState) {
|
|
873
|
+
setSpotState(spotStateData.spotState);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
break;
|
|
614
877
|
default:
|
|
615
878
|
console.warn(`[HyperLiquid WS] Unknown channel: ${response.channel}`);
|
|
616
879
|
}
|
|
@@ -621,7 +884,15 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
621
884
|
console.error('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', event.data);
|
|
622
885
|
setLastError(errorMessage);
|
|
623
886
|
}
|
|
624
|
-
}, [
|
|
887
|
+
}, [
|
|
888
|
+
setAllMids,
|
|
889
|
+
upsertActiveAssetData,
|
|
890
|
+
addCandleData,
|
|
891
|
+
setFinalAssetContexts,
|
|
892
|
+
setFinalAtOICaps,
|
|
893
|
+
setAggregatedClearingHouseState,
|
|
894
|
+
setSpotState,
|
|
895
|
+
]);
|
|
625
896
|
const connect = useCallback(() => {
|
|
626
897
|
if (!enabled)
|
|
627
898
|
return;
|
|
@@ -719,6 +990,17 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
719
990
|
},
|
|
720
991
|
};
|
|
721
992
|
sendJsonMessage(unsubscribeMessage);
|
|
993
|
+
// Unsubscribe from spotState for previous address
|
|
994
|
+
if (subscribedAddress !== DEFAULT_ADDRESS) {
|
|
995
|
+
const unsubscribeSpotState = {
|
|
996
|
+
method: 'unsubscribe',
|
|
997
|
+
subscription: {
|
|
998
|
+
type: 'spotState',
|
|
999
|
+
user: subscribedAddress,
|
|
1000
|
+
},
|
|
1001
|
+
};
|
|
1002
|
+
sendJsonMessage(unsubscribeSpotState);
|
|
1003
|
+
}
|
|
722
1004
|
const unsubscribeAllDexsClearinghouseState = {
|
|
723
1005
|
method: 'unsubscribe',
|
|
724
1006
|
subscription: {
|
|
@@ -762,13 +1044,34 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
762
1044
|
sendJsonMessage(subscribeAllDexsClearinghouseState);
|
|
763
1045
|
sendJsonMessage(subscribeAllMids);
|
|
764
1046
|
sendJsonMessage(subscribeAllDexsAssetCtxs);
|
|
1047
|
+
// Subscribe to spotState for real-time spot balances (USDH, USDC, etc.)
|
|
1048
|
+
// Only subscribe if we have a real user address (not the default)
|
|
1049
|
+
if (userAddress !== DEFAULT_ADDRESS) {
|
|
1050
|
+
const subscribeSpotState = {
|
|
1051
|
+
method: 'subscribe',
|
|
1052
|
+
subscription: {
|
|
1053
|
+
type: 'spotState',
|
|
1054
|
+
user: userAddress,
|
|
1055
|
+
},
|
|
1056
|
+
};
|
|
1057
|
+
sendJsonMessage(subscribeSpotState);
|
|
1058
|
+
}
|
|
765
1059
|
setSubscribedAddress(userAddress);
|
|
766
1060
|
// Clear previous data when address changes
|
|
767
1061
|
if (subscribedAddress && subscribedAddress !== userAddress) {
|
|
768
1062
|
// clear aggregatedClearingHouseState
|
|
769
1063
|
setAggregatedClearingHouseState(null);
|
|
1064
|
+
// clear spotState
|
|
1065
|
+
setSpotState(null);
|
|
770
1066
|
}
|
|
771
|
-
}, [
|
|
1067
|
+
}, [
|
|
1068
|
+
isConnected,
|
|
1069
|
+
address,
|
|
1070
|
+
subscribedAddress,
|
|
1071
|
+
sendJsonMessage,
|
|
1072
|
+
setAggregatedClearingHouseState,
|
|
1073
|
+
setSpotState,
|
|
1074
|
+
]);
|
|
772
1075
|
// Handle token subscriptions for activeAssetData
|
|
773
1076
|
useEffect(() => {
|
|
774
1077
|
if (!isConnected || !address)
|
|
@@ -777,7 +1080,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
777
1080
|
const tokensToSubscribe = effectiveTokens.filter((token) => token && !subscribedTokens.includes(token));
|
|
778
1081
|
const tokensToUnsubscribe = subscribedTokens.filter((token) => !effectiveTokens.includes(token));
|
|
779
1082
|
// Unsubscribe from tokens no longer in the list
|
|
780
|
-
tokensToUnsubscribe.forEach(token => {
|
|
1083
|
+
tokensToUnsubscribe.forEach((token) => {
|
|
781
1084
|
const unsubscribeMessage = {
|
|
782
1085
|
method: 'unsubscribe',
|
|
783
1086
|
subscription: {
|
|
@@ -789,7 +1092,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
789
1092
|
sendJsonMessage(unsubscribeMessage);
|
|
790
1093
|
});
|
|
791
1094
|
// Subscribe to new tokens
|
|
792
|
-
tokensToSubscribe.forEach(token => {
|
|
1095
|
+
tokensToSubscribe.forEach((token) => {
|
|
793
1096
|
const subscribeMessage = {
|
|
794
1097
|
method: 'subscribe',
|
|
795
1098
|
subscription: {
|
|
@@ -804,7 +1107,14 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
804
1107
|
setSubscribedTokens(effectiveTokens.filter((token) => token));
|
|
805
1108
|
tokensToSubscribe.forEach((token) => deleteActiveAssetData(token));
|
|
806
1109
|
}
|
|
807
|
-
}, [
|
|
1110
|
+
}, [
|
|
1111
|
+
isConnected,
|
|
1112
|
+
address,
|
|
1113
|
+
selectedTokenSymbols,
|
|
1114
|
+
subscribedTokens,
|
|
1115
|
+
sendJsonMessage,
|
|
1116
|
+
setActiveAssetData,
|
|
1117
|
+
]);
|
|
808
1118
|
// Handle candle subscriptions for tokens and interval changes
|
|
809
1119
|
useEffect(() => {
|
|
810
1120
|
if (!isConnected)
|
|
@@ -813,7 +1123,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
813
1123
|
// Unsubscribe from previous candle subscriptions if interval changed
|
|
814
1124
|
const prevInterval = prevCandleIntervalRef.current;
|
|
815
1125
|
if (prevInterval && prevInterval !== candleInterval) {
|
|
816
|
-
subscribedCandleTokens.forEach(token => {
|
|
1126
|
+
subscribedCandleTokens.forEach((token) => {
|
|
817
1127
|
const unsubscribeMessage = {
|
|
818
1128
|
method: 'unsubscribe',
|
|
819
1129
|
subscription: {
|
|
@@ -854,12 +1164,21 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
854
1164
|
sendJsonMessage(subscribeMessage);
|
|
855
1165
|
});
|
|
856
1166
|
// Update subscribed state
|
|
857
|
-
if (tokensToSubscribe.length > 0 ||
|
|
1167
|
+
if (tokensToSubscribe.length > 0 ||
|
|
1168
|
+
tokensToUnsubscribe.length > 0 ||
|
|
1169
|
+
prevInterval !== candleInterval) {
|
|
858
1170
|
setSubscribedCandleTokens(effectiveTokens.filter((token) => token));
|
|
859
1171
|
prevCandleIntervalRef.current = candleInterval;
|
|
860
1172
|
tokensToUnsubscribe.forEach((token) => deleteCandleSymbol(token));
|
|
861
1173
|
}
|
|
862
|
-
}, [
|
|
1174
|
+
}, [
|
|
1175
|
+
isConnected,
|
|
1176
|
+
selectedTokenSymbols,
|
|
1177
|
+
candleInterval,
|
|
1178
|
+
subscribedCandleTokens,
|
|
1179
|
+
sendJsonMessage,
|
|
1180
|
+
setCandleData,
|
|
1181
|
+
]);
|
|
863
1182
|
return {
|
|
864
1183
|
isConnected,
|
|
865
1184
|
lastError,
|
|
@@ -948,20 +1267,112 @@ const useAccountSummary = () => {
|
|
|
948
1267
|
return { data: calculated, isLoading };
|
|
949
1268
|
};
|
|
950
1269
|
|
|
1270
|
+
function findAssetMeta$4(coinName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
1271
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1272
|
+
if (!perpMetaAssets) {
|
|
1273
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
1274
|
+
}
|
|
1275
|
+
if (desiredCollateral) {
|
|
1276
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === coinName && a.collateralToken === desiredCollateral);
|
|
1277
|
+
if (collateralMatch) {
|
|
1278
|
+
return {
|
|
1279
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
1280
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (coinName.includes(':')) {
|
|
1285
|
+
const [prefix, symbol] = coinName.split(':');
|
|
1286
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
1287
|
+
var _a;
|
|
1288
|
+
return a.name === symbol &&
|
|
1289
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
1290
|
+
});
|
|
1291
|
+
if (exactMatch) {
|
|
1292
|
+
return {
|
|
1293
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
1294
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
if (knownPrefix) {
|
|
1299
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
1300
|
+
var _a;
|
|
1301
|
+
return a.name === coinName &&
|
|
1302
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
1303
|
+
});
|
|
1304
|
+
if (exactMatch) {
|
|
1305
|
+
return {
|
|
1306
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
1307
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
const exactMatch = perpMetaAssets.find((a) => a.name === coinName && !a.marketPrefix);
|
|
1312
|
+
if (exactMatch) {
|
|
1313
|
+
return {
|
|
1314
|
+
collateralToken: (_g = exactMatch.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
1315
|
+
marketPrefix: (_h = exactMatch.marketPrefix) !== null && _h !== void 0 ? _h : null,
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
const hip3Matches = perpMetaAssets.filter((a) => a.name === coinName && a.marketPrefix);
|
|
1319
|
+
if (hip3Matches.length > 0) {
|
|
1320
|
+
if (desiredCollateral) {
|
|
1321
|
+
const collateralMatch = hip3Matches.find((a) => a.collateralToken === desiredCollateral);
|
|
1322
|
+
if (collateralMatch) {
|
|
1323
|
+
return {
|
|
1324
|
+
collateralToken: (_j = collateralMatch.collateralToken) !== null && _j !== void 0 ? _j : 'USDC',
|
|
1325
|
+
marketPrefix: (_k = collateralMatch.marketPrefix) !== null && _k !== void 0 ? _k : null,
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
const usdHMatch = hip3Matches.find((a) => a.collateralToken === 'USDH');
|
|
1330
|
+
const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Matches[0];
|
|
1331
|
+
return {
|
|
1332
|
+
collateralToken: (_l = chosen.collateralToken) !== null && _l !== void 0 ? _l : 'USDC',
|
|
1333
|
+
marketPrefix: (_m = chosen.marketPrefix) !== null && _m !== void 0 ? _m : null,
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
1337
|
+
}
|
|
1338
|
+
function enrichTradeHistoryAssets(assets, perpMetaAssets) {
|
|
1339
|
+
return assets.map((asset) => {
|
|
1340
|
+
var _a;
|
|
1341
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
1342
|
+
return asset;
|
|
1343
|
+
}
|
|
1344
|
+
const meta = findAssetMeta$4(asset.coin, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
1345
|
+
return {
|
|
1346
|
+
...asset,
|
|
1347
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
1348
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
1349
|
+
};
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
function enrichTradeHistories(histories, perpMetaAssets) {
|
|
1353
|
+
return histories.map((history) => ({
|
|
1354
|
+
...history,
|
|
1355
|
+
closedLongAssets: enrichTradeHistoryAssets(history.closedLongAssets, perpMetaAssets),
|
|
1356
|
+
closedShortAssets: enrichTradeHistoryAssets(history.closedShortAssets, perpMetaAssets),
|
|
1357
|
+
}));
|
|
1358
|
+
}
|
|
951
1359
|
const useTradeHistories = () => {
|
|
952
1360
|
const context = useContext(PearHyperliquidContext);
|
|
953
1361
|
if (!context) {
|
|
954
1362
|
throw new Error('useTradeHistories must be used within a PearHyperliquidProvider');
|
|
955
1363
|
}
|
|
956
1364
|
const tradeHistories = useUserData((state) => state.tradeHistories);
|
|
1365
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
957
1366
|
const isLoading = useMemo(() => {
|
|
958
1367
|
return tradeHistories === null && context.isConnected;
|
|
959
1368
|
}, [tradeHistories, context.isConnected]);
|
|
960
|
-
|
|
1369
|
+
const enrichedTradeHistories = useMemo(() => {
|
|
1370
|
+
if (!tradeHistories)
|
|
1371
|
+
return null;
|
|
1372
|
+
return enrichTradeHistories(tradeHistories, allPerpMetaAssets);
|
|
1373
|
+
}, [tradeHistories, allPerpMetaAssets]);
|
|
1374
|
+
return { data: enrichedTradeHistories, isLoading };
|
|
961
1375
|
};
|
|
962
|
-
/**
|
|
963
|
-
* Hook to access open orders with loading state
|
|
964
|
-
*/
|
|
965
1376
|
const useOpenOrders = () => {
|
|
966
1377
|
const context = useContext(PearHyperliquidContext);
|
|
967
1378
|
if (!context) {
|
|
@@ -990,21 +1401,51 @@ const useWebData = () => {
|
|
|
990
1401
|
const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
|
|
991
1402
|
const aggregatedClearinghouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
|
|
992
1403
|
const finalAtOICaps = useHyperliquidData((state) => state.finalAtOICaps);
|
|
993
|
-
const hip3Assets = useHyperliquidData((state) => state.
|
|
1404
|
+
const hip3Assets = useHyperliquidData((state) => state.hip3Assets);
|
|
1405
|
+
const hip3MarketPrefixes = useHyperliquidData((state) => state.hip3MarketPrefixes);
|
|
994
1406
|
let marketDataBySymbol = {};
|
|
995
1407
|
if (finalAssetContexts && perpMetaAssets) {
|
|
996
1408
|
const result = {};
|
|
1409
|
+
// Build a map of display name -> asset context index (for unique display names)
|
|
1410
|
+
const displayNameToContextIndex = new Map();
|
|
1411
|
+
const seenNames = new Set();
|
|
1412
|
+
let contextIndex = 0;
|
|
1413
|
+
// First pass: map unique display names to their context index
|
|
997
1414
|
for (let index = 0; index < perpMetaAssets.length; index++) {
|
|
998
1415
|
const name = perpMetaAssets[index].name;
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1416
|
+
if (!seenNames.has(name)) {
|
|
1417
|
+
seenNames.add(name);
|
|
1418
|
+
if (contextIndex < finalAssetContexts.length) {
|
|
1419
|
+
displayNameToContextIndex.set(name, contextIndex);
|
|
1420
|
+
contextIndex++;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
// Second pass: create nested entries for all market variants
|
|
1425
|
+
for (let index = 0; index < perpMetaAssets.length; index++) {
|
|
1426
|
+
const universeAsset = perpMetaAssets[index];
|
|
1427
|
+
const displayName = universeAsset.name;
|
|
1428
|
+
const marketPrefix = universeAsset.marketPrefix;
|
|
1429
|
+
const ctxIndex = displayNameToContextIndex.get(displayName);
|
|
1430
|
+
if (ctxIndex !== undefined) {
|
|
1431
|
+
const assetContext = finalAssetContexts[ctxIndex];
|
|
1432
|
+
// Initialize the symbol entry if it doesn't exist
|
|
1433
|
+
if (!result[displayName]) {
|
|
1434
|
+
result[displayName] = {};
|
|
1435
|
+
}
|
|
1436
|
+
// Use marketPrefix as key for HIP-3 assets, "default" for regular assets
|
|
1437
|
+
const variantKey = marketPrefix || 'default';
|
|
1438
|
+
result[displayName][variantKey] = {
|
|
1439
|
+
asset: assetContext,
|
|
1440
|
+
universe: universeAsset,
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1003
1443
|
}
|
|
1004
1444
|
marketDataBySymbol = result;
|
|
1005
1445
|
}
|
|
1006
1446
|
return {
|
|
1007
1447
|
hip3Assets,
|
|
1448
|
+
hip3MarketPrefixes,
|
|
1008
1449
|
clearinghouseState: aggregatedClearinghouseState,
|
|
1009
1450
|
perpsAtOpenInterestCap: finalAtOICaps,
|
|
1010
1451
|
marketDataBySymbol,
|
|
@@ -1019,30 +1460,60 @@ const useWebData = () => {
|
|
|
1019
1460
|
class TokenMetadataExtractor {
|
|
1020
1461
|
/**
|
|
1021
1462
|
* Extracts comprehensive token metadata
|
|
1022
|
-
* @param symbol - Token symbol
|
|
1463
|
+
* @param symbol - Token symbol (base symbol without prefix, e.g., "TSLA")
|
|
1023
1464
|
* @param perpMetaAssets - Aggregated universe assets (flattened across dexes)
|
|
1024
1465
|
* @param finalAssetContexts - Aggregated asset contexts (flattened across dexes)
|
|
1025
1466
|
* @param allMids - AllMids data containing current prices
|
|
1026
1467
|
* @param activeAssetData - Optional active asset data containing leverage information
|
|
1468
|
+
* @param marketPrefix - Optional market prefix (e.g., "xyz", "flx") for HIP3 multi-market assets
|
|
1027
1469
|
* @returns TokenMetadata or null if token not found
|
|
1028
1470
|
*/
|
|
1029
|
-
static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
|
|
1471
|
+
static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketPrefix) {
|
|
1030
1472
|
if (!perpMetaAssets || !finalAssetContexts || !allMids) {
|
|
1031
1473
|
return null;
|
|
1032
1474
|
}
|
|
1033
1475
|
// Find token index in aggregated universe
|
|
1034
|
-
|
|
1035
|
-
|
|
1476
|
+
// For HIP3 assets, match both name AND marketPrefix
|
|
1477
|
+
const universeIndex = perpMetaAssets.findIndex((asset) => {
|
|
1478
|
+
if (asset.name !== symbol)
|
|
1479
|
+
return false;
|
|
1480
|
+
// If marketPrefix is specified, match it; otherwise match assets without prefix
|
|
1481
|
+
if (marketPrefix) {
|
|
1482
|
+
return asset.marketPrefix === marketPrefix;
|
|
1483
|
+
}
|
|
1484
|
+
// No prefix specified - match non-HIP3 asset (no marketPrefix) or first matching asset
|
|
1485
|
+
return !asset.marketPrefix;
|
|
1486
|
+
});
|
|
1487
|
+
// If no exact match found and prefix was specified, try finding the specific market variant
|
|
1488
|
+
const finalIndex = universeIndex === -1 && marketPrefix
|
|
1489
|
+
? perpMetaAssets.findIndex((asset) => asset.name === symbol && asset.marketPrefix === marketPrefix)
|
|
1490
|
+
: universeIndex;
|
|
1491
|
+
// Fallback: if still not found and no prefix, find first matching by name (for backward compatibility)
|
|
1492
|
+
const resolvedIndex = finalIndex === -1
|
|
1493
|
+
? perpMetaAssets.findIndex((asset) => asset.name === symbol)
|
|
1494
|
+
: finalIndex;
|
|
1495
|
+
if (resolvedIndex === -1) {
|
|
1036
1496
|
return null;
|
|
1037
1497
|
}
|
|
1038
|
-
const universeAsset = perpMetaAssets[
|
|
1039
|
-
const assetCtx = finalAssetContexts[
|
|
1498
|
+
const universeAsset = perpMetaAssets[resolvedIndex];
|
|
1499
|
+
const assetCtx = finalAssetContexts[resolvedIndex];
|
|
1040
1500
|
if (!assetCtx) {
|
|
1041
1501
|
return null;
|
|
1042
1502
|
}
|
|
1043
|
-
// Get current price
|
|
1044
|
-
|
|
1045
|
-
const
|
|
1503
|
+
// Get current price - prefer assetCtx.midPx as it's already index-matched,
|
|
1504
|
+
// fall back to allMids lookup if midPx is null
|
|
1505
|
+
const prefixedKeyColon = marketPrefix ? `${marketPrefix}:${symbol}` : null;
|
|
1506
|
+
let currentPrice = 0;
|
|
1507
|
+
// Primary source: assetCtx.midPx (already properly indexed)
|
|
1508
|
+
if (assetCtx.midPx) {
|
|
1509
|
+
currentPrice = parseFloat(assetCtx.midPx);
|
|
1510
|
+
}
|
|
1511
|
+
// Fallback: allMids lookup with multiple key formats for HIP3 markets
|
|
1512
|
+
if (!currentPrice || isNaN(currentPrice)) {
|
|
1513
|
+
const currentPriceStr = (prefixedKeyColon && allMids.mids[prefixedKeyColon]) ||
|
|
1514
|
+
allMids.mids[symbol];
|
|
1515
|
+
currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
|
|
1516
|
+
}
|
|
1046
1517
|
// Get previous day price
|
|
1047
1518
|
const prevDayPrice = parseFloat(assetCtx.prevDayPx);
|
|
1048
1519
|
// Calculate 24h price change
|
|
@@ -1053,7 +1524,11 @@ class TokenMetadataExtractor {
|
|
|
1053
1524
|
const markPrice = parseFloat(assetCtx.markPx);
|
|
1054
1525
|
const oraclePrice = parseFloat(assetCtx.oraclePx);
|
|
1055
1526
|
// Extract leverage info from activeAssetData if available
|
|
1056
|
-
|
|
1527
|
+
// Try prefixed key first (e.g., "xyz:TSLA"), then fall back to plain symbol
|
|
1528
|
+
const activeDataKey = prefixedKeyColon && (activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[prefixedKeyColon])
|
|
1529
|
+
? prefixedKeyColon
|
|
1530
|
+
: symbol;
|
|
1531
|
+
const tokenActiveData = activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[activeDataKey];
|
|
1057
1532
|
const leverage = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.leverage;
|
|
1058
1533
|
const maxTradeSzs = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.maxTradeSzs;
|
|
1059
1534
|
const availableToTrade = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.availableToTrade;
|
|
@@ -1071,21 +1546,27 @@ class TokenMetadataExtractor {
|
|
|
1071
1546
|
leverage,
|
|
1072
1547
|
maxTradeSzs,
|
|
1073
1548
|
availableToTrade,
|
|
1549
|
+
collateralToken: universeAsset.collateralToken,
|
|
1074
1550
|
};
|
|
1075
1551
|
}
|
|
1076
1552
|
/**
|
|
1077
1553
|
* Extracts metadata for multiple tokens
|
|
1078
|
-
* @param
|
|
1554
|
+
* @param tokens - Array of token objects with symbol and optional marketPrefix
|
|
1079
1555
|
* @param perpMetaAssets - Aggregated universe assets
|
|
1080
1556
|
* @param finalAssetContexts - Aggregated asset contexts
|
|
1081
1557
|
* @param allMids - AllMids data
|
|
1082
1558
|
* @param activeAssetData - Optional active asset data containing leverage information
|
|
1083
|
-
* @returns Record of
|
|
1559
|
+
* @returns Record of unique key to TokenMetadata. Key is "{prefix}:{symbol}" for HIP3 assets, or just "{symbol}" otherwise
|
|
1084
1560
|
*/
|
|
1085
|
-
static extractMultipleTokensMetadata(
|
|
1561
|
+
static extractMultipleTokensMetadata(tokens, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
|
|
1086
1562
|
const result = {};
|
|
1087
|
-
for (const
|
|
1088
|
-
|
|
1563
|
+
for (const token of tokens) {
|
|
1564
|
+
// Use a unique key that includes the prefix for HIP3 assets
|
|
1565
|
+
// This ensures xyz:TSLA and flx:TSLA get separate entries
|
|
1566
|
+
const resultKey = token.marketPrefix
|
|
1567
|
+
? `${token.marketPrefix}:${token.symbol}`
|
|
1568
|
+
: token.symbol;
|
|
1569
|
+
result[resultKey] = this.extractTokenMetadata(token.symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, token.marketPrefix);
|
|
1089
1570
|
}
|
|
1090
1571
|
return result;
|
|
1091
1572
|
}
|
|
@@ -1098,10 +1579,30 @@ class TokenMetadataExtractor {
|
|
|
1098
1579
|
static isTokenAvailable(symbol, perpMetaAssets) {
|
|
1099
1580
|
if (!perpMetaAssets)
|
|
1100
1581
|
return false;
|
|
1101
|
-
return perpMetaAssets.some(asset => asset.name === symbol);
|
|
1582
|
+
return perpMetaAssets.some((asset) => asset.name === symbol);
|
|
1102
1583
|
}
|
|
1103
1584
|
}
|
|
1104
1585
|
|
|
1586
|
+
/**
|
|
1587
|
+
* Parse a token string that may have a market prefix (e.g., "xyz:GOOGL" -> { prefix: "xyz", symbol: "GOOGL" })
|
|
1588
|
+
* This allows us to keep the full name (xyz:GOOGL) for URLs/tags while extracting just the symbol for SDK lookups.
|
|
1589
|
+
*/
|
|
1590
|
+
function parseTokenWithPrefix(token) {
|
|
1591
|
+
if (token.includes(':')) {
|
|
1592
|
+
const [prefix, ...rest] = token.split(':');
|
|
1593
|
+
const symbol = rest.join(':').toUpperCase();
|
|
1594
|
+
return {
|
|
1595
|
+
prefix: prefix.toLowerCase(),
|
|
1596
|
+
symbol,
|
|
1597
|
+
fullName: `${prefix.toLowerCase()}:${symbol}`,
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
return {
|
|
1601
|
+
prefix: null,
|
|
1602
|
+
symbol: token.toUpperCase(),
|
|
1603
|
+
fullName: token.toUpperCase(),
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1105
1606
|
const useTokenSelectionMetadataStore = create((set) => ({
|
|
1106
1607
|
isPriceDataReady: false,
|
|
1107
1608
|
isLoading: true,
|
|
@@ -1111,23 +1612,65 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1111
1612
|
weightedRatio24h: 1,
|
|
1112
1613
|
priceRatio: 1,
|
|
1113
1614
|
priceRatio24h: 1,
|
|
1114
|
-
openInterest:
|
|
1115
|
-
volume:
|
|
1615
|
+
openInterest: '0',
|
|
1616
|
+
volume: '0',
|
|
1116
1617
|
sumNetFunding: 0,
|
|
1117
1618
|
maxLeverage: 0,
|
|
1118
1619
|
minMargin: 0,
|
|
1119
1620
|
leverageMatched: true,
|
|
1120
|
-
recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens }) => {
|
|
1121
|
-
const isPriceDataReady = !!(perpMetaAssets &&
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1621
|
+
recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens, }) => {
|
|
1622
|
+
const isPriceDataReady = !!(perpMetaAssets &&
|
|
1623
|
+
finalAssetContexts &&
|
|
1624
|
+
allMids);
|
|
1625
|
+
// Parse tokens - handle prefixed tokens like "xyz:GOOGL" by extracting the symbol and market prefix
|
|
1626
|
+
// The full name (xyz:GOOGL) is kept as the metadata key for UI consistency
|
|
1627
|
+
const parsedLongTokens = longTokens.map((t) => ({
|
|
1628
|
+
...t,
|
|
1629
|
+
parsed: parseTokenWithPrefix(t.symbol),
|
|
1630
|
+
}));
|
|
1631
|
+
const parsedShortTokens = shortTokens.map((t) => ({
|
|
1632
|
+
...t,
|
|
1633
|
+
parsed: parseTokenWithPrefix(t.symbol),
|
|
1634
|
+
}));
|
|
1635
|
+
// Extract base symbols with their market prefixes for SDK lookups
|
|
1636
|
+
// This ensures xyz:TSLA and flx:TSLA get different market data
|
|
1637
|
+
const longTokensForLookup = parsedLongTokens.map((t) => ({
|
|
1638
|
+
symbol: t.parsed.symbol,
|
|
1639
|
+
marketPrefix: t.parsed.prefix,
|
|
1640
|
+
}));
|
|
1641
|
+
const shortTokensForLookup = parsedShortTokens.map((t) => ({
|
|
1642
|
+
symbol: t.parsed.symbol,
|
|
1643
|
+
marketPrefix: t.parsed.prefix,
|
|
1644
|
+
}));
|
|
1645
|
+
// Also extract just the base symbols (without prefix) for lookups that don't support prefixes
|
|
1646
|
+
const longBaseSymbols = longTokensForLookup.map((t) => t.symbol);
|
|
1647
|
+
const shortBaseSymbols = shortTokensForLookup.map((t) => t.symbol);
|
|
1648
|
+
// Get metadata using base symbols with market prefix for proper market differentiation
|
|
1649
|
+
const longBaseMetadata = isPriceDataReady
|
|
1650
|
+
? TokenMetadataExtractor.extractMultipleTokensMetadata(longTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
|
|
1127
1651
|
: {};
|
|
1128
|
-
const
|
|
1129
|
-
? TokenMetadataExtractor.extractMultipleTokensMetadata(
|
|
1652
|
+
const shortBaseMetadata = isPriceDataReady
|
|
1653
|
+
? TokenMetadataExtractor.extractMultipleTokensMetadata(shortTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
|
|
1130
1654
|
: {};
|
|
1655
|
+
// Re-map metadata using original full names (with prefix) as keys for UI consistency
|
|
1656
|
+
// The extractor now keys by "{prefix}:{symbol}" for prefixed tokens, which matches our parsed.fullName
|
|
1657
|
+
const longTokensMetadata = {};
|
|
1658
|
+
parsedLongTokens.forEach((t) => {
|
|
1659
|
+
var _a;
|
|
1660
|
+
// Use the full name (e.g., "xyz:TSLA") as the lookup key since extractor uses the same format
|
|
1661
|
+
const lookupKey = t.parsed.prefix
|
|
1662
|
+
? `${t.parsed.prefix}:${t.parsed.symbol}`
|
|
1663
|
+
: t.parsed.symbol;
|
|
1664
|
+
longTokensMetadata[t.symbol] = (_a = longBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
|
|
1665
|
+
});
|
|
1666
|
+
const shortTokensMetadata = {};
|
|
1667
|
+
parsedShortTokens.forEach((t) => {
|
|
1668
|
+
var _a;
|
|
1669
|
+
const lookupKey = t.parsed.prefix
|
|
1670
|
+
? `${t.parsed.prefix}:${t.parsed.symbol}`
|
|
1671
|
+
: t.parsed.symbol;
|
|
1672
|
+
shortTokensMetadata[t.symbol] = (_a = shortBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
|
|
1673
|
+
});
|
|
1131
1674
|
// Determine loading state
|
|
1132
1675
|
const allTokens = [...longTokens, ...shortTokens];
|
|
1133
1676
|
const isLoading = (() => {
|
|
@@ -1135,26 +1678,33 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1135
1678
|
return true;
|
|
1136
1679
|
if (allTokens.length === 0)
|
|
1137
1680
|
return false;
|
|
1138
|
-
const allMetadata = {
|
|
1681
|
+
const allMetadata = {
|
|
1682
|
+
...longTokensMetadata,
|
|
1683
|
+
...shortTokensMetadata,
|
|
1684
|
+
};
|
|
1139
1685
|
return allTokens.some((token) => !allMetadata[token.symbol]);
|
|
1140
1686
|
})();
|
|
1141
1687
|
// Open interest and volume (from market data for matching asset basket)
|
|
1688
|
+
// Use base symbols (without prefix) for matching against market data
|
|
1142
1689
|
const { openInterest, volume } = (() => {
|
|
1143
|
-
const empty = { openInterest:
|
|
1690
|
+
const empty = { openInterest: '0', volume: '0' };
|
|
1144
1691
|
if (!(marketData === null || marketData === void 0 ? void 0 : marketData.active) || (!longTokens.length && !shortTokens.length))
|
|
1145
1692
|
return empty;
|
|
1146
|
-
const selectedLong =
|
|
1147
|
-
const selectedShort =
|
|
1693
|
+
const selectedLong = longBaseSymbols.slice().sort();
|
|
1694
|
+
const selectedShort = shortBaseSymbols.slice().sort();
|
|
1148
1695
|
const match = marketData.active.find((item) => {
|
|
1149
1696
|
const longs = [...item.longAssets].sort();
|
|
1150
1697
|
const shorts = [...item.shortAssets].sort();
|
|
1151
|
-
if (longs.length !== selectedLong.length ||
|
|
1698
|
+
if (longs.length !== selectedLong.length ||
|
|
1699
|
+
shorts.length !== selectedShort.length)
|
|
1152
1700
|
return false;
|
|
1153
1701
|
const longsEqual = longs.every((s, i) => s.asset === selectedLong[i]);
|
|
1154
1702
|
const shortsEqual = shorts.every((s, i) => s.asset === selectedShort[i]);
|
|
1155
1703
|
return longsEqual && shortsEqual;
|
|
1156
1704
|
});
|
|
1157
|
-
return match
|
|
1705
|
+
return match
|
|
1706
|
+
? { openInterest: match.openInterest, volume: match.volume }
|
|
1707
|
+
: empty;
|
|
1158
1708
|
})();
|
|
1159
1709
|
// Price ratio (only when exactly one long and one short)
|
|
1160
1710
|
const { priceRatio, priceRatio24h } = (() => {
|
|
@@ -1234,17 +1784,27 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1234
1784
|
return totalFunding;
|
|
1235
1785
|
})();
|
|
1236
1786
|
// Max leverage (minimum across all tokens)
|
|
1787
|
+
// Use tokens with their market prefixes for proper lookup in perpMetaAssets
|
|
1237
1788
|
const maxLeverage = (() => {
|
|
1238
1789
|
if (!perpMetaAssets)
|
|
1239
1790
|
return 0;
|
|
1240
|
-
const
|
|
1241
|
-
|
|
1791
|
+
const allTokensForLookup = [
|
|
1792
|
+
...longTokensForLookup,
|
|
1793
|
+
...shortTokensForLookup,
|
|
1794
|
+
];
|
|
1795
|
+
if (allTokensForLookup.length === 0)
|
|
1242
1796
|
return 0;
|
|
1243
1797
|
let minLev = Infinity;
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1798
|
+
allTokensForLookup.forEach(({ symbol, marketPrefix }) => {
|
|
1799
|
+
// Match by both name AND marketPrefix for HIP3 assets
|
|
1800
|
+
const tokenUniverse = perpMetaAssets.find((u) => u.name === symbol &&
|
|
1801
|
+
(marketPrefix
|
|
1802
|
+
? u.marketPrefix === marketPrefix
|
|
1803
|
+
: !u.marketPrefix));
|
|
1804
|
+
// Fallback to just matching by name if no exact match
|
|
1805
|
+
const fallbackUniverse = tokenUniverse || perpMetaAssets.find((u) => u.name === symbol);
|
|
1806
|
+
if (fallbackUniverse === null || fallbackUniverse === void 0 ? void 0 : fallbackUniverse.maxLeverage)
|
|
1807
|
+
minLev = Math.min(minLev, fallbackUniverse.maxLeverage);
|
|
1248
1808
|
});
|
|
1249
1809
|
return minLev === Infinity ? 0 : minLev;
|
|
1250
1810
|
})();
|
|
@@ -1256,7 +1816,10 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1256
1816
|
// Whether all tokens have matching leverage
|
|
1257
1817
|
const leverageMatched = (() => {
|
|
1258
1818
|
const allTokensArr = [...longTokens, ...shortTokens];
|
|
1259
|
-
const allMetadata = {
|
|
1819
|
+
const allMetadata = {
|
|
1820
|
+
...longTokensMetadata,
|
|
1821
|
+
...shortTokensMetadata,
|
|
1822
|
+
};
|
|
1260
1823
|
if (allTokensArr.length === 0)
|
|
1261
1824
|
return true;
|
|
1262
1825
|
const tokensWithLev = allTokensArr.filter((token) => { var _a; return (_a = allMetadata[token.symbol]) === null || _a === void 0 ? void 0 : _a.leverage; });
|
|
@@ -5441,8 +6004,8 @@ function addAuthInterceptors(params) {
|
|
|
5441
6004
|
/**
|
|
5442
6005
|
* Fetch historical candle data from HyperLiquid API
|
|
5443
6006
|
*/
|
|
5444
|
-
const fetchHistoricalCandles = async (coin, startTime, endTime, interval,
|
|
5445
|
-
const backendCoin = toBackendSymbol(coin,
|
|
6007
|
+
const fetchHistoricalCandles = async (coin, startTime, endTime, interval, hip3Assets) => {
|
|
6008
|
+
const backendCoin = toBackendSymbol(coin, hip3Assets);
|
|
5446
6009
|
const request = {
|
|
5447
6010
|
req: { coin: backendCoin, startTime, endTime, interval },
|
|
5448
6011
|
type: 'candleSnapshot',
|
|
@@ -5601,10 +6164,10 @@ const useHistoricalPriceData = () => {
|
|
|
5601
6164
|
setTokenLoading(token.symbol, true);
|
|
5602
6165
|
});
|
|
5603
6166
|
try {
|
|
5604
|
-
const
|
|
6167
|
+
const hip3Assets = useHyperliquidData.getState().hip3Assets;
|
|
5605
6168
|
const fetchPromises = tokensToFetch.map(async (token) => {
|
|
5606
6169
|
try {
|
|
5607
|
-
const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval,
|
|
6170
|
+
const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval, hip3Assets);
|
|
5608
6171
|
addHistoricalPriceData(token.symbol, interval, response.data, { start: startTime, end: endTime });
|
|
5609
6172
|
return { symbol: token.symbol, candles: response.data, success: true };
|
|
5610
6173
|
}
|
|
@@ -6288,97 +6851,19 @@ function useAutoSyncFills(options) {
|
|
|
6288
6851
|
}
|
|
6289
6852
|
|
|
6290
6853
|
/**
|
|
6291
|
-
*
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
* Validation error for minimum position size
|
|
6296
|
-
*/
|
|
6297
|
-
class MinimumPositionSizeError extends Error {
|
|
6298
|
-
constructor(assetName, assetValue, minimumRequired) {
|
|
6299
|
-
super(`Asset "${assetName}" has a USD value of $${assetValue.toFixed(2)}, which is below the minimum required value of $${minimumRequired.toFixed(2)}`);
|
|
6300
|
-
this.assetName = assetName;
|
|
6301
|
-
this.assetValue = assetValue;
|
|
6302
|
-
this.minimumRequired = minimumRequired;
|
|
6303
|
-
this.name = "MinimumPositionSizeError";
|
|
6304
|
-
}
|
|
6305
|
-
}
|
|
6306
|
-
/**
|
|
6307
|
-
* Validates that each asset in a position has at least the minimum USD value
|
|
6308
|
-
* @param usdValue Total USD value for the position
|
|
6309
|
-
* @param longAssets Array of long assets with weights
|
|
6310
|
-
* @param shortAssets Array of short assets with weights
|
|
6311
|
-
* @throws MinimumPositionSizeError if any asset has less than the minimum USD value
|
|
6854
|
+
* Create a position (MARKET/LIMIT/TWAP) using Pear Hyperliquid service
|
|
6855
|
+
* Authorization is derived from headers (Axios defaults or browser localStorage fallback)
|
|
6856
|
+
* @throws MinimumPositionSizeError if any asset has less than $11 USD value
|
|
6857
|
+
* @throws MaxAssetsPerLegError if any leg exceeds the maximum allowed assets (15)
|
|
6312
6858
|
*/
|
|
6313
|
-
function
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
if (allAssets.length === 0) {
|
|
6317
|
-
return; // No assets to validate
|
|
6318
|
-
}
|
|
6319
|
-
// Calculate total weight
|
|
6320
|
-
const totalWeight = allAssets.reduce((sum, asset) => { var _a; return sum + ((_a = asset.weight) !== null && _a !== void 0 ? _a : 0); }, 0);
|
|
6321
|
-
// If weights are not provided or sum to 0, assume equal distribution
|
|
6322
|
-
const hasWeights = totalWeight > 0;
|
|
6323
|
-
const equalWeight = hasWeights ? 0 : 1 / allAssets.length;
|
|
6324
|
-
// Validate each asset
|
|
6325
|
-
for (const asset of allAssets) {
|
|
6326
|
-
const weight = hasWeights ? (_a = asset.weight) !== null && _a !== void 0 ? _a : 0 : equalWeight;
|
|
6327
|
-
const assetUsdValue = usdValue * weight;
|
|
6328
|
-
if (assetUsdValue < MINIMUM_ASSET_USD_VALUE) {
|
|
6329
|
-
throw new MinimumPositionSizeError(asset.asset, assetUsdValue, MINIMUM_ASSET_USD_VALUE);
|
|
6330
|
-
}
|
|
6331
|
-
}
|
|
6332
|
-
}
|
|
6333
|
-
/**
|
|
6334
|
-
* Calculates the minimum USD value required for a position based on the number of assets
|
|
6335
|
-
* @param longAssets Array of long assets
|
|
6336
|
-
* @param shortAssets Array of short assets
|
|
6337
|
-
* @returns The minimum total USD value required
|
|
6338
|
-
*/
|
|
6339
|
-
function calculateMinimumPositionValue(longAssets, shortAssets) {
|
|
6340
|
-
const totalAssets = ((longAssets === null || longAssets === void 0 ? void 0 : longAssets.length) || 0) + ((shortAssets === null || shortAssets === void 0 ? void 0 : shortAssets.length) || 0);
|
|
6341
|
-
if (totalAssets === 0) {
|
|
6342
|
-
return 0;
|
|
6343
|
-
}
|
|
6344
|
-
return MINIMUM_ASSET_USD_VALUE * totalAssets;
|
|
6345
|
-
}
|
|
6346
|
-
/**
|
|
6347
|
-
* Validates and provides a user-friendly error message with suggestions
|
|
6348
|
-
* @param usdValue Total USD value for the position
|
|
6349
|
-
* @param longAssets Array of long assets with weights
|
|
6350
|
-
* @param shortAssets Array of short assets with weights
|
|
6351
|
-
* @returns Validation result with success flag and optional error message
|
|
6352
|
-
*/
|
|
6353
|
-
function validatePositionSize(usdValue, longAssets, shortAssets) {
|
|
6354
|
-
try {
|
|
6355
|
-
validateMinimumAssetSize(usdValue, longAssets, shortAssets);
|
|
6356
|
-
return { valid: true };
|
|
6357
|
-
}
|
|
6358
|
-
catch (error) {
|
|
6359
|
-
if (error instanceof MinimumPositionSizeError) {
|
|
6360
|
-
const minimumRequired = calculateMinimumPositionValue(longAssets, shortAssets);
|
|
6361
|
-
return {
|
|
6362
|
-
valid: false,
|
|
6363
|
-
error: error.message,
|
|
6364
|
-
minimumRequired,
|
|
6365
|
-
};
|
|
6366
|
-
}
|
|
6367
|
-
throw error;
|
|
6368
|
-
}
|
|
6369
|
-
}
|
|
6370
|
-
|
|
6371
|
-
/**
|
|
6372
|
-
* Create a position (MARKET/LIMIT/TWAP) using Pear Hyperliquid service
|
|
6373
|
-
* Authorization is derived from headers (Axios defaults or browser localStorage fallback)
|
|
6374
|
-
* @throws MinimumPositionSizeError if any asset has less than $11 USD value
|
|
6375
|
-
*/
|
|
6376
|
-
async function createPosition(baseUrl, payload, displayToFull) {
|
|
6859
|
+
async function createPosition(baseUrl, payload, hip3Assets) {
|
|
6860
|
+
// Validate maximum assets per leg before creating position
|
|
6861
|
+
validateMaxAssetsPerLeg(payload.longAssets, payload.shortAssets);
|
|
6377
6862
|
// Validate minimum asset size before creating position
|
|
6378
6863
|
validateMinimumAssetSize(payload.usdValue, payload.longAssets, payload.shortAssets);
|
|
6379
6864
|
const url = joinUrl(baseUrl, "/positions");
|
|
6380
6865
|
// Translate display symbols to backend format
|
|
6381
|
-
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset,
|
|
6866
|
+
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
6382
6867
|
const translatedPayload = {
|
|
6383
6868
|
...payload,
|
|
6384
6869
|
longAssets: mapAssets(payload.longAssets),
|
|
@@ -6477,9 +6962,9 @@ async function adjustPosition(baseUrl, positionId, payload) {
|
|
|
6477
6962
|
throw toApiError(error);
|
|
6478
6963
|
}
|
|
6479
6964
|
}
|
|
6480
|
-
async function adjustAdvancePosition(baseUrl, positionId, payload,
|
|
6965
|
+
async function adjustAdvancePosition(baseUrl, positionId, payload, hip3Assets) {
|
|
6481
6966
|
const url = joinUrl(baseUrl, `/positions/${positionId}/adjust-advance`);
|
|
6482
|
-
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset,
|
|
6967
|
+
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
6483
6968
|
const translatedPayload = (payload || []).map((item) => ({
|
|
6484
6969
|
longAssets: mapAssets(item.longAssets),
|
|
6485
6970
|
shortAssets: mapAssets(item.shortAssets),
|
|
@@ -6542,6 +7027,9 @@ const calculatePositionAsset = (asset, currentPrice, totalInitialPositionSize, l
|
|
|
6542
7027
|
entryPositionValue: entryNotional,
|
|
6543
7028
|
initialWeight: totalInitialPositionSize > 0 ? entryNotional / totalInitialPositionSize : 0,
|
|
6544
7029
|
fundingPaid: (_a = asset.fundingPaid) !== null && _a !== void 0 ? _a : 0,
|
|
7030
|
+
// Preserve market metadata from raw asset (if provided by backend)
|
|
7031
|
+
marketPrefix: asset.marketPrefix,
|
|
7032
|
+
collateralToken: asset.collateralToken,
|
|
6545
7033
|
};
|
|
6546
7034
|
};
|
|
6547
7035
|
const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
|
|
@@ -6604,57 +7092,151 @@ const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
|
|
|
6604
7092
|
});
|
|
6605
7093
|
};
|
|
6606
7094
|
|
|
7095
|
+
function findAssetMeta$3(coinName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
7096
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
7097
|
+
if (!perpMetaAssets) {
|
|
7098
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7099
|
+
}
|
|
7100
|
+
if (desiredCollateral) {
|
|
7101
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === coinName && a.collateralToken === desiredCollateral);
|
|
7102
|
+
if (collateralMatch) {
|
|
7103
|
+
return {
|
|
7104
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7105
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7106
|
+
};
|
|
7107
|
+
}
|
|
7108
|
+
}
|
|
7109
|
+
if (coinName.includes(':')) {
|
|
7110
|
+
const [prefix, symbol] = coinName.split(':');
|
|
7111
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7112
|
+
var _a;
|
|
7113
|
+
return a.name === symbol &&
|
|
7114
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
7115
|
+
});
|
|
7116
|
+
if (exactMatch) {
|
|
7117
|
+
return {
|
|
7118
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7119
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7120
|
+
};
|
|
7121
|
+
}
|
|
7122
|
+
}
|
|
7123
|
+
if (knownPrefix) {
|
|
7124
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7125
|
+
var _a;
|
|
7126
|
+
return a.name === coinName &&
|
|
7127
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
7128
|
+
});
|
|
7129
|
+
if (exactMatch) {
|
|
7130
|
+
return {
|
|
7131
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
7132
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
7133
|
+
};
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
const regularAsset = perpMetaAssets.find((a) => a.name === coinName && !a.marketPrefix);
|
|
7137
|
+
if (regularAsset) {
|
|
7138
|
+
return {
|
|
7139
|
+
collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
7140
|
+
marketPrefix: null,
|
|
7141
|
+
};
|
|
7142
|
+
}
|
|
7143
|
+
const hip3Asset = perpMetaAssets.find((a) => a.name === coinName && a.marketPrefix);
|
|
7144
|
+
if (hip3Asset) {
|
|
7145
|
+
return {
|
|
7146
|
+
collateralToken: (_h = hip3Asset.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
|
|
7147
|
+
marketPrefix: (_j = hip3Asset.marketPrefix) !== null && _j !== void 0 ? _j : null,
|
|
7148
|
+
};
|
|
7149
|
+
}
|
|
7150
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7151
|
+
}
|
|
7152
|
+
function enrichPositionAssets(assets, perpMetaAssets) {
|
|
7153
|
+
return assets.map((asset) => {
|
|
7154
|
+
var _a;
|
|
7155
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
7156
|
+
return asset;
|
|
7157
|
+
}
|
|
7158
|
+
const meta = findAssetMeta$3(asset.coin, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
7159
|
+
return {
|
|
7160
|
+
...asset,
|
|
7161
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
7162
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
7163
|
+
};
|
|
7164
|
+
});
|
|
7165
|
+
}
|
|
7166
|
+
function enrichPositions(positions, perpMetaAssets) {
|
|
7167
|
+
return positions.map((position) => ({
|
|
7168
|
+
...position,
|
|
7169
|
+
longAssets: enrichPositionAssets(position.longAssets, perpMetaAssets),
|
|
7170
|
+
shortAssets: enrichPositionAssets(position.shortAssets, perpMetaAssets),
|
|
7171
|
+
}));
|
|
7172
|
+
}
|
|
6607
7173
|
function usePosition() {
|
|
6608
7174
|
const context = useContext(PearHyperliquidContext);
|
|
6609
7175
|
if (!context) {
|
|
6610
7176
|
throw new Error('usePosition must be used within a PearHyperliquidProvider');
|
|
6611
7177
|
}
|
|
6612
7178
|
const { apiBaseUrl, isConnected } = context;
|
|
6613
|
-
const
|
|
6614
|
-
// Create position API action
|
|
7179
|
+
const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
|
|
6615
7180
|
const createPosition$1 = async (payload) => {
|
|
6616
|
-
return createPosition(apiBaseUrl, payload,
|
|
7181
|
+
return createPosition(apiBaseUrl, payload, hip3Assets);
|
|
6617
7182
|
};
|
|
6618
|
-
// Update TP/SL risk parameters for a position
|
|
6619
7183
|
const updateRiskParameters$1 = async (positionId, payload) => {
|
|
6620
7184
|
return updateRiskParameters(apiBaseUrl, positionId, payload);
|
|
6621
7185
|
};
|
|
6622
|
-
// Close a position (MARKET or TWAP)
|
|
6623
7186
|
const closePosition$1 = async (positionId, payload) => {
|
|
6624
7187
|
return closePosition(apiBaseUrl, positionId, payload);
|
|
6625
7188
|
};
|
|
6626
|
-
// Close all positions (MARKET or TWAP)
|
|
6627
7189
|
const closeAllPositions$1 = async (payload) => {
|
|
6628
7190
|
return closeAllPositions(apiBaseUrl, payload);
|
|
6629
7191
|
};
|
|
6630
|
-
// Adjust a position (REDUCE/INCREASE by %; MARKET or LIMIT)
|
|
6631
7192
|
const adjustPosition$1 = async (positionId, payload) => {
|
|
6632
7193
|
return adjustPosition(apiBaseUrl, positionId, payload);
|
|
6633
7194
|
};
|
|
6634
|
-
// Adjust to absolute target sizes per asset, optionally adding new assets
|
|
6635
7195
|
const adjustAdvancePosition$1 = async (positionId, payload) => {
|
|
6636
|
-
return adjustAdvancePosition(apiBaseUrl, positionId, payload,
|
|
7196
|
+
return adjustAdvancePosition(apiBaseUrl, positionId, payload, hip3Assets);
|
|
6637
7197
|
};
|
|
6638
|
-
// Open positions using WS data, with derived values
|
|
6639
7198
|
const userOpenPositions = useUserData((state) => state.rawOpenPositions);
|
|
6640
7199
|
const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
|
|
6641
7200
|
const allMids = useHyperliquidData((state) => state.allMids);
|
|
7201
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
6642
7202
|
const isLoading = useMemo(() => {
|
|
6643
7203
|
return userOpenPositions === null && isConnected;
|
|
6644
7204
|
}, [userOpenPositions, isConnected]);
|
|
6645
7205
|
const openPositions = useMemo(() => {
|
|
6646
7206
|
if (!userOpenPositions || !aggregatedClearingHouseState || !allMids)
|
|
6647
7207
|
return null;
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
7208
|
+
const positions = buildPositionValue(userOpenPositions, aggregatedClearingHouseState, allMids);
|
|
7209
|
+
return enrichPositions(positions, allPerpMetaAssets);
|
|
7210
|
+
}, [
|
|
7211
|
+
userOpenPositions,
|
|
7212
|
+
aggregatedClearingHouseState,
|
|
7213
|
+
allMids,
|
|
7214
|
+
allPerpMetaAssets,
|
|
7215
|
+
]);
|
|
7216
|
+
return {
|
|
7217
|
+
createPosition: createPosition$1,
|
|
7218
|
+
updateRiskParameters: updateRiskParameters$1,
|
|
7219
|
+
closePosition: closePosition$1,
|
|
7220
|
+
closeAllPositions: closeAllPositions$1,
|
|
7221
|
+
adjustPosition: adjustPosition$1,
|
|
7222
|
+
adjustAdvancePosition: adjustAdvancePosition$1,
|
|
7223
|
+
openPositions,
|
|
7224
|
+
isLoading,
|
|
7225
|
+
};
|
|
6651
7226
|
}
|
|
6652
7227
|
|
|
6653
7228
|
async function adjustOrder(baseUrl, orderId, payload) {
|
|
6654
7229
|
const url = joinUrl(baseUrl, `/orders/${orderId}/adjust`);
|
|
6655
7230
|
try {
|
|
6656
|
-
const resp = await apiClient.put(url, payload, {
|
|
6657
|
-
|
|
7231
|
+
const resp = await apiClient.put(url, payload, {
|
|
7232
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7233
|
+
timeout: 60000,
|
|
7234
|
+
});
|
|
7235
|
+
return {
|
|
7236
|
+
data: resp.data,
|
|
7237
|
+
status: resp.status,
|
|
7238
|
+
headers: resp.headers,
|
|
7239
|
+
};
|
|
6658
7240
|
}
|
|
6659
7241
|
catch (error) {
|
|
6660
7242
|
throw toApiError(error);
|
|
@@ -6663,8 +7245,14 @@ async function adjustOrder(baseUrl, orderId, payload) {
|
|
|
6663
7245
|
async function cancelOrder(baseUrl, orderId) {
|
|
6664
7246
|
const url = joinUrl(baseUrl, `/orders/${orderId}/cancel`);
|
|
6665
7247
|
try {
|
|
6666
|
-
const resp = await apiClient.delete(url, {
|
|
6667
|
-
|
|
7248
|
+
const resp = await apiClient.delete(url, {
|
|
7249
|
+
timeout: 60000,
|
|
7250
|
+
});
|
|
7251
|
+
return {
|
|
7252
|
+
data: resp.data,
|
|
7253
|
+
status: resp.status,
|
|
7254
|
+
headers: resp.headers,
|
|
7255
|
+
};
|
|
6668
7256
|
}
|
|
6669
7257
|
catch (error) {
|
|
6670
7258
|
throw toApiError(error);
|
|
@@ -6674,19 +7262,129 @@ async function cancelTwapOrder(baseUrl, orderId) {
|
|
|
6674
7262
|
const url = joinUrl(baseUrl, `/orders/${orderId}/twap/cancel`);
|
|
6675
7263
|
try {
|
|
6676
7264
|
const resp = await apiClient.post(url, {}, { headers: { 'Content-Type': 'application/json' }, timeout: 60000 });
|
|
6677
|
-
return {
|
|
7265
|
+
return {
|
|
7266
|
+
data: resp.data,
|
|
7267
|
+
status: resp.status,
|
|
7268
|
+
headers: resp.headers,
|
|
7269
|
+
};
|
|
7270
|
+
}
|
|
7271
|
+
catch (error) {
|
|
7272
|
+
throw toApiError(error);
|
|
7273
|
+
}
|
|
7274
|
+
}
|
|
7275
|
+
/**
|
|
7276
|
+
* Execute a spot order (swap) using Pear Hyperliquid service
|
|
7277
|
+
* POST /orders/spot
|
|
7278
|
+
*/
|
|
7279
|
+
async function executeSpotOrder(baseUrl, payload) {
|
|
7280
|
+
const url = joinUrl(baseUrl, '/orders/spot');
|
|
7281
|
+
try {
|
|
7282
|
+
const resp = await apiClient.post(url, payload, {
|
|
7283
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7284
|
+
timeout: 60000,
|
|
7285
|
+
});
|
|
7286
|
+
return {
|
|
7287
|
+
data: resp.data,
|
|
7288
|
+
status: resp.status,
|
|
7289
|
+
headers: resp.headers,
|
|
7290
|
+
};
|
|
6678
7291
|
}
|
|
6679
7292
|
catch (error) {
|
|
6680
7293
|
throw toApiError(error);
|
|
6681
7294
|
}
|
|
6682
7295
|
}
|
|
6683
7296
|
|
|
7297
|
+
function findAssetMeta$2(assetName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
7298
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
7299
|
+
if (!perpMetaAssets) {
|
|
7300
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7301
|
+
}
|
|
7302
|
+
if (desiredCollateral) {
|
|
7303
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === assetName && a.collateralToken === desiredCollateral);
|
|
7304
|
+
if (collateralMatch) {
|
|
7305
|
+
return {
|
|
7306
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7307
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7308
|
+
};
|
|
7309
|
+
}
|
|
7310
|
+
}
|
|
7311
|
+
if (assetName.includes(':')) {
|
|
7312
|
+
const [prefix, symbol] = assetName.split(':');
|
|
7313
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7314
|
+
var _a;
|
|
7315
|
+
return a.name === symbol &&
|
|
7316
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
7317
|
+
});
|
|
7318
|
+
if (exactMatch) {
|
|
7319
|
+
return {
|
|
7320
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7321
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7322
|
+
};
|
|
7323
|
+
}
|
|
7324
|
+
}
|
|
7325
|
+
if (knownPrefix) {
|
|
7326
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7327
|
+
var _a;
|
|
7328
|
+
return a.name === assetName &&
|
|
7329
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
7330
|
+
});
|
|
7331
|
+
if (exactMatch) {
|
|
7332
|
+
return {
|
|
7333
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
7334
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
7335
|
+
};
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7338
|
+
const regularAsset = perpMetaAssets.find((a) => a.name === assetName && !a.marketPrefix);
|
|
7339
|
+
if (regularAsset) {
|
|
7340
|
+
return {
|
|
7341
|
+
collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
7342
|
+
marketPrefix: null,
|
|
7343
|
+
};
|
|
7344
|
+
}
|
|
7345
|
+
const hip3Assets = perpMetaAssets.filter((a) => a.name === assetName && a.marketPrefix);
|
|
7346
|
+
if (hip3Assets.length > 0) {
|
|
7347
|
+
if (desiredCollateral) {
|
|
7348
|
+
const collateralMatch = hip3Assets.find((a) => a.collateralToken === desiredCollateral);
|
|
7349
|
+
if (collateralMatch) {
|
|
7350
|
+
return {
|
|
7351
|
+
collateralToken: (_h = collateralMatch.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
|
|
7352
|
+
marketPrefix: (_j = collateralMatch.marketPrefix) !== null && _j !== void 0 ? _j : null,
|
|
7353
|
+
};
|
|
7354
|
+
}
|
|
7355
|
+
}
|
|
7356
|
+
const usdHMatch = hip3Assets.find((a) => a.collateralToken === 'USDH');
|
|
7357
|
+
const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Assets[0];
|
|
7358
|
+
return {
|
|
7359
|
+
collateralToken: (_k = chosen.collateralToken) !== null && _k !== void 0 ? _k : 'USDC',
|
|
7360
|
+
marketPrefix: (_l = chosen.marketPrefix) !== null && _l !== void 0 ? _l : null,
|
|
7361
|
+
};
|
|
7362
|
+
}
|
|
7363
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7364
|
+
}
|
|
7365
|
+
function enrichOrderAssets$1(assets, perpMetaAssets) {
|
|
7366
|
+
if (!assets)
|
|
7367
|
+
return [];
|
|
7368
|
+
return assets.map((asset) => {
|
|
7369
|
+
var _a;
|
|
7370
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
7371
|
+
return asset;
|
|
7372
|
+
}
|
|
7373
|
+
const meta = findAssetMeta$2(asset.asset, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
7374
|
+
return {
|
|
7375
|
+
...asset,
|
|
7376
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
7377
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
7378
|
+
};
|
|
7379
|
+
});
|
|
7380
|
+
}
|
|
6684
7381
|
function useOrders() {
|
|
6685
7382
|
const context = useContext(PearHyperliquidContext);
|
|
6686
7383
|
if (!context)
|
|
6687
7384
|
throw new Error('useOrders must be used within a PearHyperliquidProvider');
|
|
6688
7385
|
const { apiBaseUrl } = context;
|
|
6689
7386
|
const openOrders = useUserData((state) => state.openOrders);
|
|
7387
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
6690
7388
|
const isLoading = useMemo(() => openOrders === null && context.isConnected, [openOrders, context.isConnected]);
|
|
6691
7389
|
const { openPositions } = usePosition();
|
|
6692
7390
|
const positionsById = useMemo(() => {
|
|
@@ -6705,19 +7403,27 @@ function useOrders() {
|
|
|
6705
7403
|
const isTpSl = ord.orderType === 'TP' || ord.orderType === 'SL';
|
|
6706
7404
|
const hasAssets = ((_b = (_a = ord.longAssets) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 || ((_d = (_c = ord.shortAssets) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0;
|
|
6707
7405
|
const pos = positionsById.get((_e = ord.positionId) !== null && _e !== void 0 ? _e : '');
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
7406
|
+
let enrichedOrd = {
|
|
7407
|
+
...ord,
|
|
7408
|
+
longAssets: enrichOrderAssets$1(ord.longAssets, allPerpMetaAssets),
|
|
7409
|
+
shortAssets: enrichOrderAssets$1(ord.shortAssets, allPerpMetaAssets),
|
|
7410
|
+
};
|
|
7411
|
+
if (isTpSl && !hasAssets && pos) {
|
|
7412
|
+
const mapAssets = (arr) => arr.map((a) => ({
|
|
7413
|
+
asset: a.coin,
|
|
7414
|
+
weight: a.initialWeight,
|
|
7415
|
+
marketPrefix: a.marketPrefix,
|
|
7416
|
+
collateralToken: a.collateralToken,
|
|
7417
|
+
}));
|
|
7418
|
+
enrichedOrd = {
|
|
7419
|
+
...enrichedOrd,
|
|
6714
7420
|
longAssets: mapAssets(pos.longAssets),
|
|
6715
7421
|
shortAssets: mapAssets(pos.shortAssets),
|
|
6716
7422
|
};
|
|
6717
7423
|
}
|
|
6718
|
-
return
|
|
7424
|
+
return enrichedOrd;
|
|
6719
7425
|
});
|
|
6720
|
-
}, [openOrders, positionsById]);
|
|
7426
|
+
}, [openOrders, positionsById, allPerpMetaAssets]);
|
|
6721
7427
|
const adjustOrder$1 = async (orderId, payload) => {
|
|
6722
7428
|
return adjustOrder(apiBaseUrl, orderId, payload);
|
|
6723
7429
|
};
|
|
@@ -6727,16 +7433,156 @@ function useOrders() {
|
|
|
6727
7433
|
const cancelTwapOrder$1 = async (orderId) => {
|
|
6728
7434
|
return cancelTwapOrder(apiBaseUrl, orderId);
|
|
6729
7435
|
};
|
|
6730
|
-
return {
|
|
7436
|
+
return {
|
|
7437
|
+
adjustOrder: adjustOrder$1,
|
|
7438
|
+
cancelOrder: cancelOrder$1,
|
|
7439
|
+
cancelTwapOrder: cancelTwapOrder$1,
|
|
7440
|
+
openOrders: enrichedOpenOrders,
|
|
7441
|
+
isLoading,
|
|
7442
|
+
};
|
|
6731
7443
|
}
|
|
6732
7444
|
|
|
7445
|
+
/**
|
|
7446
|
+
* Hook for executing spot orders (swaps) on Hyperliquid
|
|
7447
|
+
* Use this to swap between USDC and USDH or other spot assets
|
|
7448
|
+
*/
|
|
7449
|
+
function useSpotOrder() {
|
|
7450
|
+
const context = useContext(PearHyperliquidContext);
|
|
7451
|
+
if (!context) {
|
|
7452
|
+
throw new Error('useSpotOrder must be used within a PearHyperliquidProvider');
|
|
7453
|
+
}
|
|
7454
|
+
const { apiBaseUrl } = context;
|
|
7455
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
7456
|
+
const [error, setError] = useState(null);
|
|
7457
|
+
const resetError = useCallback(() => {
|
|
7458
|
+
setError(null);
|
|
7459
|
+
}, []);
|
|
7460
|
+
const executeSpotOrder$1 = useCallback(async (payload) => {
|
|
7461
|
+
setIsLoading(true);
|
|
7462
|
+
setError(null);
|
|
7463
|
+
try {
|
|
7464
|
+
const response = await executeSpotOrder(apiBaseUrl, payload);
|
|
7465
|
+
return response;
|
|
7466
|
+
}
|
|
7467
|
+
catch (err) {
|
|
7468
|
+
const apiError = err;
|
|
7469
|
+
setError(apiError);
|
|
7470
|
+
throw apiError;
|
|
7471
|
+
}
|
|
7472
|
+
finally {
|
|
7473
|
+
setIsLoading(false);
|
|
7474
|
+
}
|
|
7475
|
+
}, [apiBaseUrl]);
|
|
7476
|
+
return {
|
|
7477
|
+
executeSpotOrder: executeSpotOrder$1,
|
|
7478
|
+
isLoading,
|
|
7479
|
+
error,
|
|
7480
|
+
resetError,
|
|
7481
|
+
};
|
|
7482
|
+
}
|
|
7483
|
+
|
|
7484
|
+
function findAssetMeta$1(assetName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
7485
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
7486
|
+
if (!perpMetaAssets) {
|
|
7487
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7488
|
+
}
|
|
7489
|
+
if (desiredCollateral) {
|
|
7490
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === assetName && a.collateralToken === desiredCollateral);
|
|
7491
|
+
if (collateralMatch) {
|
|
7492
|
+
return {
|
|
7493
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7494
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7495
|
+
};
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7498
|
+
if (assetName.includes(':')) {
|
|
7499
|
+
const [prefix, symbol] = assetName.split(':');
|
|
7500
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7501
|
+
var _a;
|
|
7502
|
+
return a.name === symbol &&
|
|
7503
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
7504
|
+
});
|
|
7505
|
+
if (exactMatch) {
|
|
7506
|
+
return {
|
|
7507
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7508
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7509
|
+
};
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7512
|
+
if (knownPrefix) {
|
|
7513
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7514
|
+
var _a;
|
|
7515
|
+
return a.name === assetName &&
|
|
7516
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
7517
|
+
});
|
|
7518
|
+
if (exactMatch) {
|
|
7519
|
+
return {
|
|
7520
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
7521
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
7522
|
+
};
|
|
7523
|
+
}
|
|
7524
|
+
}
|
|
7525
|
+
const regularAsset = perpMetaAssets.find((a) => a.name === assetName && !a.marketPrefix);
|
|
7526
|
+
if (regularAsset) {
|
|
7527
|
+
return {
|
|
7528
|
+
collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
7529
|
+
marketPrefix: null,
|
|
7530
|
+
};
|
|
7531
|
+
}
|
|
7532
|
+
const hip3Assets = perpMetaAssets.filter((a) => a.name === assetName && a.marketPrefix);
|
|
7533
|
+
if (hip3Assets.length > 0) {
|
|
7534
|
+
if (desiredCollateral) {
|
|
7535
|
+
const collateralMatch = hip3Assets.find((a) => a.collateralToken === desiredCollateral);
|
|
7536
|
+
if (collateralMatch) {
|
|
7537
|
+
return {
|
|
7538
|
+
collateralToken: (_h = collateralMatch.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
|
|
7539
|
+
marketPrefix: (_j = collateralMatch.marketPrefix) !== null && _j !== void 0 ? _j : null,
|
|
7540
|
+
};
|
|
7541
|
+
}
|
|
7542
|
+
}
|
|
7543
|
+
const usdHMatch = hip3Assets.find((a) => a.collateralToken === 'USDH');
|
|
7544
|
+
const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Assets[0];
|
|
7545
|
+
return {
|
|
7546
|
+
collateralToken: (_k = chosen.collateralToken) !== null && _k !== void 0 ? _k : 'USDC',
|
|
7547
|
+
marketPrefix: (_l = chosen.marketPrefix) !== null && _l !== void 0 ? _l : null,
|
|
7548
|
+
};
|
|
7549
|
+
}
|
|
7550
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7551
|
+
}
|
|
7552
|
+
function enrichOrderAssets(assets, perpMetaAssets) {
|
|
7553
|
+
if (!assets)
|
|
7554
|
+
return [];
|
|
7555
|
+
return assets.map((asset) => {
|
|
7556
|
+
var _a;
|
|
7557
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
7558
|
+
return asset;
|
|
7559
|
+
}
|
|
7560
|
+
const meta = findAssetMeta$1(asset.asset, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
7561
|
+
return {
|
|
7562
|
+
...asset,
|
|
7563
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
7564
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
7565
|
+
};
|
|
7566
|
+
});
|
|
7567
|
+
}
|
|
7568
|
+
function enrichTwapOrders(orders, perpMetaAssets) {
|
|
7569
|
+
return orders.map((order) => ({
|
|
7570
|
+
...order,
|
|
7571
|
+
longAssets: enrichOrderAssets(order.longAssets, perpMetaAssets),
|
|
7572
|
+
shortAssets: enrichOrderAssets(order.shortAssets, perpMetaAssets),
|
|
7573
|
+
}));
|
|
7574
|
+
}
|
|
6733
7575
|
function useTwap() {
|
|
6734
|
-
const twapDetails = useUserData(state => state.twapDetails);
|
|
7576
|
+
const twapDetails = useUserData((state) => state.twapDetails);
|
|
7577
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
6735
7578
|
const context = useContext(PearHyperliquidContext);
|
|
6736
7579
|
if (!context)
|
|
6737
7580
|
throw new Error('useTwap must be used within a PearHyperliquidProvider');
|
|
6738
7581
|
const { apiBaseUrl } = context;
|
|
6739
|
-
const orders = useMemo(() =>
|
|
7582
|
+
const orders = useMemo(() => {
|
|
7583
|
+
const rawOrders = twapDetails !== null && twapDetails !== void 0 ? twapDetails : [];
|
|
7584
|
+
return enrichTwapOrders(rawOrders, allPerpMetaAssets);
|
|
7585
|
+
}, [twapDetails, allPerpMetaAssets]);
|
|
6740
7586
|
const cancelTwap$1 = async (orderId) => {
|
|
6741
7587
|
return cancelTwap(apiBaseUrl, orderId);
|
|
6742
7588
|
};
|
|
@@ -6819,59 +7665,170 @@ function useNotifications() {
|
|
|
6819
7665
|
};
|
|
6820
7666
|
}
|
|
6821
7667
|
|
|
6822
|
-
//
|
|
7668
|
+
// Helper to find asset metadata from perpMetaAssets
|
|
7669
|
+
function findAssetMeta(assetName, perpMetaAssets) {
|
|
7670
|
+
var _a, _b, _c, _d;
|
|
7671
|
+
if (!perpMetaAssets) {
|
|
7672
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7673
|
+
}
|
|
7674
|
+
// Try exact match first (for prefixed assets like "xyz:TSLA")
|
|
7675
|
+
const exactMatch = perpMetaAssets.find((a) => a.name === assetName);
|
|
7676
|
+
if (exactMatch) {
|
|
7677
|
+
return {
|
|
7678
|
+
collateralToken: (_a = exactMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7679
|
+
marketPrefix: (_b = exactMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7680
|
+
};
|
|
7681
|
+
}
|
|
7682
|
+
// Try matching by base symbol (for non-prefixed names in data)
|
|
7683
|
+
const baseMatch = perpMetaAssets.find((a) => {
|
|
7684
|
+
const baseName = a.name.includes(':') ? a.name.split(':')[1] : a.name;
|
|
7685
|
+
return baseName === assetName;
|
|
7686
|
+
});
|
|
7687
|
+
if (baseMatch) {
|
|
7688
|
+
return {
|
|
7689
|
+
collateralToken: (_c = baseMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7690
|
+
marketPrefix: (_d = baseMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7691
|
+
};
|
|
7692
|
+
}
|
|
7693
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7694
|
+
}
|
|
7695
|
+
// Enrich a single asset with metadata
|
|
7696
|
+
function enrichAsset(asset, perpMetaAssets) {
|
|
7697
|
+
const meta = findAssetMeta(asset.asset, perpMetaAssets);
|
|
7698
|
+
return {
|
|
7699
|
+
...asset,
|
|
7700
|
+
collateralToken: meta.collateralToken,
|
|
7701
|
+
marketPrefix: meta.marketPrefix,
|
|
7702
|
+
};
|
|
7703
|
+
}
|
|
7704
|
+
// Enrich a basket item with collateral info
|
|
7705
|
+
function enrichBasketItem(item, perpMetaAssets) {
|
|
7706
|
+
const enrichedLongs = item.longAssets.map((a) => enrichAsset(a, perpMetaAssets));
|
|
7707
|
+
const enrichedShorts = item.shortAssets.map((a) => enrichAsset(a, perpMetaAssets));
|
|
7708
|
+
// Determine collateral type
|
|
7709
|
+
const allAssets = [...enrichedLongs, ...enrichedShorts];
|
|
7710
|
+
const hasUsdc = allAssets.some((a) => a.collateralToken === 'USDC');
|
|
7711
|
+
const hasUsdh = allAssets.some((a) => a.collateralToken === 'USDH');
|
|
7712
|
+
let collateralType = 'USDC';
|
|
7713
|
+
if (hasUsdc && hasUsdh) {
|
|
7714
|
+
collateralType = 'MIXED';
|
|
7715
|
+
}
|
|
7716
|
+
else if (hasUsdh) {
|
|
7717
|
+
collateralType = 'USDH';
|
|
7718
|
+
}
|
|
7719
|
+
return {
|
|
7720
|
+
...item,
|
|
7721
|
+
longAssets: enrichedLongs,
|
|
7722
|
+
shortAssets: enrichedShorts,
|
|
7723
|
+
collateralType,
|
|
7724
|
+
};
|
|
7725
|
+
}
|
|
7726
|
+
/**
|
|
7727
|
+
* Filter baskets by collateral type
|
|
7728
|
+
* - 'USDC': Only baskets where ALL assets use USDC (collateralType === 'USDC')
|
|
7729
|
+
* - 'USDH': Only baskets where ALL assets use USDH (collateralType === 'USDH')
|
|
7730
|
+
* - 'ALL' or undefined: No filtering, returns all baskets
|
|
7731
|
+
*/
|
|
7732
|
+
function filterByCollateral(baskets, filter) {
|
|
7733
|
+
if (!filter || filter === 'ALL') {
|
|
7734
|
+
return baskets;
|
|
7735
|
+
}
|
|
7736
|
+
return baskets.filter((basket) => {
|
|
7737
|
+
if (filter === 'USDC') {
|
|
7738
|
+
// Include baskets that are purely USDC or have USDC assets
|
|
7739
|
+
return (basket.collateralType === 'USDC' || basket.collateralType === 'MIXED');
|
|
7740
|
+
}
|
|
7741
|
+
if (filter === 'USDH') {
|
|
7742
|
+
// Include baskets that are purely USDH or have USDH assets
|
|
7743
|
+
return (basket.collateralType === 'USDH' || basket.collateralType === 'MIXED');
|
|
7744
|
+
}
|
|
7745
|
+
return true;
|
|
7746
|
+
});
|
|
7747
|
+
}
|
|
7748
|
+
// Base selector for the full market-data payload (raw from WS)
|
|
6823
7749
|
const useMarketDataPayload = () => {
|
|
6824
7750
|
return useMarketData((s) => s.marketData);
|
|
6825
7751
|
};
|
|
6826
|
-
// Full payload for 'market-data-all' channel
|
|
7752
|
+
// Full payload for 'market-data-all' channel (raw from WS)
|
|
6827
7753
|
const useMarketDataAllPayload = () => {
|
|
6828
7754
|
return useMarketData((s) => s.marketDataAll);
|
|
6829
7755
|
};
|
|
6830
|
-
//
|
|
6831
|
-
const
|
|
6832
|
-
|
|
7756
|
+
// Access perpMetaAssets for enrichment
|
|
7757
|
+
const usePerpMetaAssets = () => {
|
|
7758
|
+
return useHyperliquidData((s) => s.perpMetaAssets);
|
|
7759
|
+
};
|
|
7760
|
+
// Active baskets (with collateral and market prefix info)
|
|
7761
|
+
const useActiveBaskets = (collateralFilter) => {
|
|
6833
7762
|
const data = useMarketDataPayload();
|
|
6834
|
-
|
|
7763
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7764
|
+
return useMemo(() => {
|
|
7765
|
+
if (!(data === null || data === void 0 ? void 0 : data.active))
|
|
7766
|
+
return [];
|
|
7767
|
+
const enriched = data.active.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7768
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7769
|
+
}, [data, perpMetaAssets, collateralFilter]);
|
|
6835
7770
|
};
|
|
6836
|
-
// Top gainers (
|
|
6837
|
-
const useTopGainers = (limit) => {
|
|
7771
|
+
// Top gainers (with collateral and market prefix info)
|
|
7772
|
+
const useTopGainers = (limit, collateralFilter) => {
|
|
6838
7773
|
const data = useMarketDataPayload();
|
|
7774
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
6839
7775
|
return useMemo(() => {
|
|
6840
7776
|
var _a;
|
|
6841
7777
|
const list = (_a = data === null || data === void 0 ? void 0 : data.topGainers) !== null && _a !== void 0 ? _a : [];
|
|
6842
|
-
|
|
6843
|
-
|
|
7778
|
+
const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
|
|
7779
|
+
const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7780
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7781
|
+
}, [data, perpMetaAssets, limit, collateralFilter]);
|
|
6844
7782
|
};
|
|
6845
|
-
// Top losers (
|
|
6846
|
-
const useTopLosers = (limit) => {
|
|
7783
|
+
// Top losers (with collateral and market prefix info)
|
|
7784
|
+
const useTopLosers = (limit, collateralFilter) => {
|
|
6847
7785
|
const data = useMarketDataPayload();
|
|
7786
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
6848
7787
|
return useMemo(() => {
|
|
6849
7788
|
var _a;
|
|
6850
7789
|
const list = (_a = data === null || data === void 0 ? void 0 : data.topLosers) !== null && _a !== void 0 ? _a : [];
|
|
6851
|
-
|
|
6852
|
-
|
|
7790
|
+
const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
|
|
7791
|
+
const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7792
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7793
|
+
}, [data, perpMetaAssets, limit, collateralFilter]);
|
|
6853
7794
|
};
|
|
6854
|
-
// Highlighted baskets
|
|
6855
|
-
const useHighlightedBaskets = () => {
|
|
6856
|
-
var _a;
|
|
7795
|
+
// Highlighted baskets (with collateral and market prefix info)
|
|
7796
|
+
const useHighlightedBaskets = (collateralFilter) => {
|
|
6857
7797
|
const data = useMarketDataPayload();
|
|
6858
|
-
|
|
7798
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7799
|
+
return useMemo(() => {
|
|
7800
|
+
if (!(data === null || data === void 0 ? void 0 : data.highlighted))
|
|
7801
|
+
return [];
|
|
7802
|
+
const enriched = data.highlighted.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7803
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7804
|
+
}, [data, perpMetaAssets, collateralFilter]);
|
|
6859
7805
|
};
|
|
6860
|
-
// Watchlist baskets (
|
|
6861
|
-
const useWatchlistBaskets = () => {
|
|
6862
|
-
var _a;
|
|
7806
|
+
// Watchlist baskets (with collateral and market prefix info)
|
|
7807
|
+
const useWatchlistBaskets = (collateralFilter) => {
|
|
6863
7808
|
const data = useMarketDataPayload();
|
|
6864
|
-
|
|
7809
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7810
|
+
return useMemo(() => {
|
|
7811
|
+
if (!(data === null || data === void 0 ? void 0 : data.watchlist))
|
|
7812
|
+
return [];
|
|
7813
|
+
const enriched = data.watchlist.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7814
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7815
|
+
}, [data, perpMetaAssets, collateralFilter]);
|
|
6865
7816
|
};
|
|
6866
|
-
// All baskets (
|
|
6867
|
-
const useAllBaskets = () => {
|
|
6868
|
-
var _a;
|
|
7817
|
+
// All baskets (with collateral and market prefix info)
|
|
7818
|
+
const useAllBaskets = (collateralFilter) => {
|
|
6869
7819
|
const dataAll = useMarketDataAllPayload();
|
|
6870
|
-
|
|
7820
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7821
|
+
return useMemo(() => {
|
|
7822
|
+
if (!(dataAll === null || dataAll === void 0 ? void 0 : dataAll.all))
|
|
7823
|
+
return [];
|
|
7824
|
+
const enriched = dataAll.all.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7825
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7826
|
+
}, [dataAll, perpMetaAssets, collateralFilter]);
|
|
6871
7827
|
};
|
|
6872
7828
|
// Find a basket by its exact asset composition (order-insensitive)
|
|
6873
7829
|
const useFindBasket = (longs, shorts) => {
|
|
6874
7830
|
const data = useMarketDataPayload();
|
|
7831
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
6875
7832
|
return useMemo(() => {
|
|
6876
7833
|
if (!data)
|
|
6877
7834
|
return undefined;
|
|
@@ -6885,17 +7842,28 @@ const useFindBasket = (longs, shorts) => {
|
|
|
6885
7842
|
: '';
|
|
6886
7843
|
const lKey = normalize(longs);
|
|
6887
7844
|
const sKey = normalize(shorts);
|
|
6888
|
-
const match = (item) => normalize(item.longAssets) === lKey &&
|
|
6889
|
-
|
|
6890
|
-
|
|
7845
|
+
const match = (item) => normalize(item.longAssets) === lKey &&
|
|
7846
|
+
normalize(item.shortAssets) === sKey;
|
|
7847
|
+
const found = data.active.find(match) || data.highlighted.find(match);
|
|
7848
|
+
return found
|
|
7849
|
+
? enrichBasketItem(found, perpMetaAssets)
|
|
7850
|
+
: undefined;
|
|
7851
|
+
}, [data, longs, shorts, perpMetaAssets]);
|
|
6891
7852
|
};
|
|
6892
7853
|
|
|
6893
|
-
async function toggleWatchlist(baseUrl, longAssets, shortAssets,
|
|
7854
|
+
async function toggleWatchlist(baseUrl, longAssets, shortAssets, hip3Assets) {
|
|
6894
7855
|
const url = joinUrl(baseUrl, '/watchlist');
|
|
6895
|
-
const mapAssets = (arr) => arr.map(a => ({ ...a, asset: toBackendSymbol(a.asset,
|
|
7856
|
+
const mapAssets = (arr) => arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
6896
7857
|
try {
|
|
6897
|
-
const response = await apiClient.post(url, {
|
|
6898
|
-
|
|
7858
|
+
const response = await apiClient.post(url, {
|
|
7859
|
+
longAssets: mapAssets(longAssets),
|
|
7860
|
+
shortAssets: mapAssets(shortAssets),
|
|
7861
|
+
}, { headers: { 'Content-Type': 'application/json' } });
|
|
7862
|
+
return {
|
|
7863
|
+
data: response.data,
|
|
7864
|
+
status: response.status,
|
|
7865
|
+
headers: response.headers,
|
|
7866
|
+
};
|
|
6899
7867
|
}
|
|
6900
7868
|
catch (error) {
|
|
6901
7869
|
throw toApiError(error);
|
|
@@ -6907,11 +7875,11 @@ function useWatchlist() {
|
|
|
6907
7875
|
if (!context)
|
|
6908
7876
|
throw new Error('useWatchlist must be used within a PearHyperliquidProvider');
|
|
6909
7877
|
const { apiBaseUrl, isConnected } = context;
|
|
6910
|
-
const
|
|
7878
|
+
const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
|
|
6911
7879
|
const marketData = useMarketDataPayload();
|
|
6912
7880
|
const isLoading = useMemo(() => !marketData && isConnected, [marketData, isConnected]);
|
|
6913
7881
|
const toggle = async (longAssets, shortAssets) => {
|
|
6914
|
-
const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets,
|
|
7882
|
+
const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, hip3Assets);
|
|
6915
7883
|
// Server will push updated market-data over WS; nothing to set here
|
|
6916
7884
|
return resp;
|
|
6917
7885
|
};
|
|
@@ -7165,16 +8133,48 @@ function useAuth() {
|
|
|
7165
8133
|
};
|
|
7166
8134
|
}
|
|
7167
8135
|
|
|
8136
|
+
const useSpotBalances = () => {
|
|
8137
|
+
const spotState = useUserData((state) => state.spotState);
|
|
8138
|
+
return useMemo(() => {
|
|
8139
|
+
if (!spotState) {
|
|
8140
|
+
return {
|
|
8141
|
+
usdhBalance: undefined,
|
|
8142
|
+
spotUsdcBalance: undefined,
|
|
8143
|
+
isLoading: true,
|
|
8144
|
+
};
|
|
8145
|
+
}
|
|
8146
|
+
const balances = spotState.balances || [];
|
|
8147
|
+
let usdhBal = 0;
|
|
8148
|
+
let spotUsdcBal = 0;
|
|
8149
|
+
for (const balance of balances) {
|
|
8150
|
+
const total = parseFloat(balance.total || '0');
|
|
8151
|
+
if (balance.coin === 'USDH') {
|
|
8152
|
+
usdhBal = total;
|
|
8153
|
+
}
|
|
8154
|
+
if (balance.coin === 'USDC') {
|
|
8155
|
+
spotUsdcBal = total;
|
|
8156
|
+
}
|
|
8157
|
+
}
|
|
8158
|
+
return {
|
|
8159
|
+
usdhBalance: usdhBal,
|
|
8160
|
+
spotUsdcBalance: spotUsdcBal,
|
|
8161
|
+
isLoading: false,
|
|
8162
|
+
};
|
|
8163
|
+
}, [spotState]);
|
|
8164
|
+
};
|
|
8165
|
+
|
|
7168
8166
|
const PearHyperliquidContext = createContext(undefined);
|
|
7169
8167
|
/**
|
|
7170
8168
|
* React Provider for PearHyperliquidClient
|
|
7171
8169
|
*/
|
|
7172
|
-
const PearHyperliquidProvider = ({ children, apiBaseUrl =
|
|
8170
|
+
const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearprotocol.io', clientId = 'PEARPROTOCOLUI', wsUrl = 'wss://hl-ui.pearprotocol.io/ws', }) => {
|
|
7173
8171
|
const address = useUserData((s) => s.address);
|
|
7174
8172
|
const setAddress = useUserData((s) => s.setAddress);
|
|
7175
8173
|
const perpsMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
|
|
7176
8174
|
const setPerpMetaAssets = useHyperliquidData((state) => state.setPerpMetaAssets);
|
|
7177
|
-
const
|
|
8175
|
+
const setAllPerpMetaAssets = useHyperliquidData((state) => state.setAllPerpMetaAssets);
|
|
8176
|
+
const setHip3Assets = useHyperliquidData((state) => state.setHip3Assets);
|
|
8177
|
+
const setHip3MarketPrefixes = useHyperliquidData((state) => state.setHip3MarketPrefixes);
|
|
7178
8178
|
const websocketsEnabled = useMemo(() => Array.isArray(perpsMetaAssets) && perpsMetaAssets.length > 0, [perpsMetaAssets]);
|
|
7179
8179
|
const { isConnected, lastError } = useHyperliquidWebSocket({
|
|
7180
8180
|
wsUrl,
|
|
@@ -7189,32 +8189,65 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearpro
|
|
|
7189
8189
|
if (perpsMetaAssets === null) {
|
|
7190
8190
|
fetchAllPerpMetas()
|
|
7191
8191
|
.then((res) => {
|
|
7192
|
-
|
|
7193
|
-
const
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
const
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
8192
|
+
const assetToMarkets = new Map();
|
|
8193
|
+
const marketPrefixes = new Map();
|
|
8194
|
+
const cleanedPerpMetas = [];
|
|
8195
|
+
const allPerpMetas = [];
|
|
8196
|
+
const FILTERED_PREFIXES = ['vntl', 'hyna'];
|
|
8197
|
+
res.data.forEach((item) => {
|
|
8198
|
+
const collateralToken = item.collateralToken === 360 ? 'USDH' : 'USDC';
|
|
8199
|
+
item.universe.forEach((asset) => {
|
|
8200
|
+
var _a;
|
|
8201
|
+
const [maybePrefix, maybeMarket] = asset.name.split(':');
|
|
8202
|
+
if (maybeMarket) {
|
|
8203
|
+
const prefix = maybePrefix.toLowerCase();
|
|
8204
|
+
const displayName = maybeMarket;
|
|
8205
|
+
const fullName = `${prefix}:${displayName}`;
|
|
8206
|
+
marketPrefixes.set(fullName, prefix);
|
|
8207
|
+
if (!FILTERED_PREFIXES.includes(prefix)) {
|
|
8208
|
+
const existingMarkets = (_a = assetToMarkets.get(displayName)) !== null && _a !== void 0 ? _a : [];
|
|
8209
|
+
if (!existingMarkets.includes(fullName)) {
|
|
8210
|
+
assetToMarkets.set(displayName, [
|
|
8211
|
+
...existingMarkets,
|
|
8212
|
+
fullName,
|
|
8213
|
+
]);
|
|
8214
|
+
}
|
|
8215
|
+
}
|
|
8216
|
+
const assetWithMeta = {
|
|
8217
|
+
...asset,
|
|
8218
|
+
name: displayName,
|
|
8219
|
+
marketPrefix: prefix,
|
|
8220
|
+
collateralToken,
|
|
8221
|
+
};
|
|
8222
|
+
allPerpMetas.push(assetWithMeta);
|
|
8223
|
+
if (!FILTERED_PREFIXES.includes(prefix)) {
|
|
8224
|
+
cleanedPerpMetas.push(assetWithMeta);
|
|
8225
|
+
}
|
|
8226
|
+
}
|
|
8227
|
+
else {
|
|
8228
|
+
const assetWithMeta = {
|
|
8229
|
+
...asset,
|
|
8230
|
+
collateralToken,
|
|
8231
|
+
};
|
|
8232
|
+
cleanedPerpMetas.push(assetWithMeta);
|
|
8233
|
+
allPerpMetas.push(assetWithMeta);
|
|
8234
|
+
}
|
|
8235
|
+
});
|
|
7210
8236
|
});
|
|
7211
|
-
|
|
8237
|
+
setHip3Assets(assetToMarkets);
|
|
8238
|
+
setHip3MarketPrefixes(marketPrefixes);
|
|
7212
8239
|
setPerpMetaAssets(cleanedPerpMetas);
|
|
8240
|
+
setAllPerpMetaAssets(allPerpMetas);
|
|
7213
8241
|
})
|
|
7214
8242
|
.catch(() => { });
|
|
7215
8243
|
}
|
|
7216
|
-
}, [
|
|
7217
|
-
|
|
8244
|
+
}, [
|
|
8245
|
+
perpsMetaAssets,
|
|
8246
|
+
setPerpMetaAssets,
|
|
8247
|
+
setAllPerpMetaAssets,
|
|
8248
|
+
setHip3Assets,
|
|
8249
|
+
setHip3MarketPrefixes,
|
|
8250
|
+
]);
|
|
7218
8251
|
useAutoSyncFills({
|
|
7219
8252
|
baseUrl: apiBaseUrl,
|
|
7220
8253
|
address,
|
|
@@ -7252,7 +8285,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearpro
|
|
|
7252
8285
|
function usePearHyperliquid() {
|
|
7253
8286
|
const ctx = useContext(PearHyperliquidContext);
|
|
7254
8287
|
if (!ctx)
|
|
7255
|
-
throw new Error(
|
|
8288
|
+
throw new Error('usePearHyperliquid must be used within a PearHyperliquidProvider');
|
|
7256
8289
|
return ctx;
|
|
7257
8290
|
}
|
|
7258
8291
|
|
|
@@ -7343,4 +8376,4 @@ function mapCandleIntervalToTradingViewInterval(interval) {
|
|
|
7343
8376
|
}
|
|
7344
8377
|
}
|
|
7345
8378
|
|
|
7346
|
-
export { AccountSummaryCalculator, ConflictDetector, MINIMUM_ASSET_USD_VALUE, MinimumPositionSizeError, PearHyperliquidProvider, TokenMetadataExtractor, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, getCompleteTimestamps, getPortfolio, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toggleWatchlist, updateRiskParameters, useAccountSummary, useActiveBaskets, useAgentWallet, useAllBaskets, useAuth, useAutoSyncFills, useBasketCandles, useFindBasket, useHighlightedBaskets, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMarketData, useMarketDataAllPayload, useMarketDataPayload, useNotifications, useOpenOrders, useOrders, usePearHyperliquid, usePerformanceOverlays, usePortfolio, usePosition, useTokenSelectionMetadata, useTopGainers, useTopLosers, useTradeHistories, useTwap, useUserSelection, useWatchlist, useWatchlistBaskets, useWebData, validateMinimumAssetSize, validatePositionSize };
|
|
8379
|
+
export { AccountSummaryCalculator, ConflictDetector, MAX_ASSETS_PER_LEG, MINIMUM_ASSET_USD_VALUE, MaxAssetsPerLegError, MinimumPositionSizeError, PearHyperliquidProvider, TokenMetadataExtractor, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, executeSpotOrder, getAvailableMarkets, getCompleteTimestamps, getMarketPrefix, getPortfolio, isHip3Market, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toBackendSymbol, toBackendSymbolWithMarket, toDisplaySymbol, toggleWatchlist, updateRiskParameters, useAccountSummary, useActiveBaskets, useAgentWallet, useAllBaskets, useAuth, useAutoSyncFills, useBasketCandles, useFindBasket, useHighlightedBaskets, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMarketData, useMarketDataAllPayload, useMarketDataPayload, useNotifications, useOpenOrders, useOrders, usePearHyperliquid, usePerformanceOverlays, usePerpMetaAssets, usePortfolio, usePosition, useSpotBalances, useSpotOrder, useTokenSelectionMetadata, useTopGainers, useTopLosers, useTradeHistories, useTwap, useUserSelection, useWatchlist, useWatchlistBaskets, useWebData, validateMaxAssetsPerLeg, validateMinimumAssetSize, validatePositionSize };
|