@metamask-previews/perps-controller 3.2.0-preview-6ce5d58fa → 4.0.0-preview-1e2fe74a0
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/CHANGELOG.md +29 -1
- package/dist/PerpsController-method-action-types.cjs.map +1 -1
- package/dist/PerpsController-method-action-types.d.cts +9 -0
- package/dist/PerpsController-method-action-types.d.cts.map +1 -1
- package/dist/PerpsController-method-action-types.d.mts +9 -0
- package/dist/PerpsController-method-action-types.d.mts.map +1 -1
- package/dist/PerpsController-method-action-types.mjs.map +1 -1
- package/dist/PerpsController.cjs +15 -3
- package/dist/PerpsController.cjs.map +1 -1
- package/dist/PerpsController.d.cts +18 -3
- package/dist/PerpsController.d.cts.map +1 -1
- package/dist/PerpsController.d.mts +18 -3
- package/dist/PerpsController.d.mts.map +1 -1
- package/dist/PerpsController.mjs +15 -3
- package/dist/PerpsController.mjs.map +1 -1
- package/dist/constants/perpsConfig.cjs +28 -0
- package/dist/constants/perpsConfig.cjs.map +1 -1
- package/dist/constants/perpsConfig.d.cts +3 -0
- package/dist/constants/perpsConfig.d.cts.map +1 -1
- package/dist/constants/perpsConfig.d.mts +3 -0
- package/dist/constants/perpsConfig.d.mts.map +1 -1
- package/dist/constants/perpsConfig.mjs +28 -0
- package/dist/constants/perpsConfig.mjs.map +1 -1
- package/dist/providers/AggregatedPerpsProvider.cjs +16 -6
- package/dist/providers/AggregatedPerpsProvider.cjs.map +1 -1
- package/dist/providers/AggregatedPerpsProvider.d.cts +12 -4
- package/dist/providers/AggregatedPerpsProvider.d.cts.map +1 -1
- package/dist/providers/AggregatedPerpsProvider.d.mts +12 -4
- package/dist/providers/AggregatedPerpsProvider.d.mts.map +1 -1
- package/dist/providers/AggregatedPerpsProvider.mjs +16 -6
- package/dist/providers/AggregatedPerpsProvider.mjs.map +1 -1
- package/dist/providers/HyperLiquidProvider.cjs +56 -20
- package/dist/providers/HyperLiquidProvider.cjs.map +1 -1
- package/dist/providers/HyperLiquidProvider.d.cts +29 -4
- package/dist/providers/HyperLiquidProvider.d.cts.map +1 -1
- package/dist/providers/HyperLiquidProvider.d.mts +29 -4
- package/dist/providers/HyperLiquidProvider.d.mts.map +1 -1
- package/dist/providers/HyperLiquidProvider.mjs +57 -21
- package/dist/providers/HyperLiquidProvider.mjs.map +1 -1
- package/dist/services/HyperLiquidClientService.cjs +131 -60
- package/dist/services/HyperLiquidClientService.cjs.map +1 -1
- package/dist/services/HyperLiquidClientService.d.cts +23 -0
- package/dist/services/HyperLiquidClientService.d.cts.map +1 -1
- package/dist/services/HyperLiquidClientService.d.mts +23 -0
- package/dist/services/HyperLiquidClientService.d.mts.map +1 -1
- package/dist/services/HyperLiquidClientService.mjs +132 -61
- package/dist/services/HyperLiquidClientService.mjs.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.cjs +193 -11
- package/dist/services/HyperLiquidSubscriptionService.cjs.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.d.cts.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.d.mts.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.mjs +194 -12
- package/dist/services/HyperLiquidSubscriptionService.mjs.map +1 -1
- package/dist/services/MarketDataService.cjs +89 -6
- package/dist/services/MarketDataService.cjs.map +1 -1
- package/dist/services/MarketDataService.d.cts +19 -0
- package/dist/services/MarketDataService.d.cts.map +1 -1
- package/dist/services/MarketDataService.d.mts +19 -0
- package/dist/services/MarketDataService.d.mts.map +1 -1
- package/dist/services/MarketDataService.mjs +89 -6
- package/dist/services/MarketDataService.mjs.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +21 -3
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.mts +21 -3
- package/dist/types/index.d.mts.map +1 -1
- package/dist/types/index.mjs.map +1 -1
- package/dist/utils/accountUtils.cjs +74 -1
- package/dist/utils/accountUtils.cjs.map +1 -1
- package/dist/utils/accountUtils.d.cts +4 -0
- package/dist/utils/accountUtils.d.cts.map +1 -1
- package/dist/utils/accountUtils.d.mts +4 -0
- package/dist/utils/accountUtils.d.mts.map +1 -1
- package/dist/utils/accountUtils.mjs +70 -0
- package/dist/utils/accountUtils.mjs.map +1 -1
- package/dist/utils/coalescePerpsRestRequest.cjs +71 -0
- package/dist/utils/coalescePerpsRestRequest.cjs.map +1 -0
- package/dist/utils/coalescePerpsRestRequest.d.cts +32 -0
- package/dist/utils/coalescePerpsRestRequest.d.cts.map +1 -0
- package/dist/utils/coalescePerpsRestRequest.d.mts +32 -0
- package/dist/utils/coalescePerpsRestRequest.d.mts.map +1 -0
- package/dist/utils/coalescePerpsRestRequest.mjs +66 -0
- package/dist/utils/coalescePerpsRestRequest.mjs.map +1 -0
- package/dist/utils/hyperLiquidAdapter.cjs +7 -7
- package/dist/utils/hyperLiquidAdapter.cjs.map +1 -1
- package/dist/utils/hyperLiquidAdapter.d.cts +2 -2
- package/dist/utils/hyperLiquidAdapter.d.cts.map +1 -1
- package/dist/utils/hyperLiquidAdapter.d.mts +2 -2
- package/dist/utils/hyperLiquidAdapter.d.mts.map +1 -1
- package/dist/utils/hyperLiquidAdapter.mjs +7 -7
- package/dist/utils/hyperLiquidAdapter.mjs.map +1 -1
- package/dist/utils/perpsFormatters.cjs +5 -1
- package/dist/utils/perpsFormatters.cjs.map +1 -1
- package/dist/utils/perpsFormatters.d.cts.map +1 -1
- package/dist/utils/perpsFormatters.d.mts.map +1 -1
- package/dist/utils/perpsFormatters.mjs +5 -1
- package/dist/utils/perpsFormatters.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.aggregateAccountStates = exports.calculateWeightedReturnOnEquity = exports.getSelectedEvmAccount = exports.getEvmAccountFromAccountGroup = exports.findEvmAccount = void 0;
|
|
3
|
+
exports.aggregateAccountStates = exports.addSpotBalanceToAccountState = exports.getSpotHold = exports.getSpotBalance = exports.calculateWeightedReturnOnEquity = exports.getSelectedEvmAccount = exports.getEvmAccountFromAccountGroup = exports.findEvmAccount = void 0;
|
|
4
4
|
const perpsConfig_1 = require("../constants/perpsConfig.cjs");
|
|
5
5
|
const EVM_ACCOUNT_TYPES = new Set(['eip155:eoa', 'eip155:erc4337']);
|
|
6
6
|
function isEvmAccountType(type) {
|
|
@@ -54,6 +54,70 @@ function calculateWeightedReturnOnEquity(accounts) {
|
|
|
54
54
|
return weightedROE.toString();
|
|
55
55
|
}
|
|
56
56
|
exports.calculateWeightedReturnOnEquity = calculateWeightedReturnOnEquity;
|
|
57
|
+
// Spot coins counted toward currently supported funded-state gating.
|
|
58
|
+
// Today the in-app HyperLiquid market surface is USDC-collateralized only,
|
|
59
|
+
// so USDH must not inflate the shared funded-state path that hides Add Funds.
|
|
60
|
+
// Non-stablecoin spot assets (HYPE, PURR, …) also remain excluded.
|
|
61
|
+
const SPOT_COLLATERAL_COINS = new Set(['USDC']);
|
|
62
|
+
function getSpotBalance(spotState) {
|
|
63
|
+
if (!spotState?.balances || !Array.isArray(spotState.balances)) {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
return spotState.balances.reduce((sum, balance) => {
|
|
67
|
+
if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {
|
|
68
|
+
return sum;
|
|
69
|
+
}
|
|
70
|
+
const value = parseFloat(balance.total ?? '0');
|
|
71
|
+
return Number.isFinite(value) ? sum + value : sum;
|
|
72
|
+
}, 0);
|
|
73
|
+
}
|
|
74
|
+
exports.getSpotBalance = getSpotBalance;
|
|
75
|
+
function getSpotHold(spotState) {
|
|
76
|
+
if (!spotState?.balances || !Array.isArray(spotState.balances)) {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
return spotState.balances.reduce((sum, balance) => {
|
|
80
|
+
if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {
|
|
81
|
+
return sum;
|
|
82
|
+
}
|
|
83
|
+
const value = parseFloat(balance.hold ?? '0');
|
|
84
|
+
return Number.isFinite(value) ? sum + value : sum;
|
|
85
|
+
}, 0);
|
|
86
|
+
}
|
|
87
|
+
exports.getSpotHold = getSpotHold;
|
|
88
|
+
function addSpotBalanceToAccountState(accountState, spotState) {
|
|
89
|
+
const spotBalance = getSpotBalance(spotState);
|
|
90
|
+
const spotHold = getSpotHold(spotState);
|
|
91
|
+
const freeSpot = Math.max(0, spotBalance - spotHold);
|
|
92
|
+
const currentTotal = parseFloat(accountState.totalBalance);
|
|
93
|
+
const currentAvailable = parseFloat(accountState.availableBalance);
|
|
94
|
+
// Preserve sentinel totals (e.g. PERPS_CONSTANTS.FallbackDataDisplay '--')
|
|
95
|
+
// rather than coercing them to NaN.
|
|
96
|
+
if (!Number.isFinite(currentTotal)) {
|
|
97
|
+
return accountState;
|
|
98
|
+
}
|
|
99
|
+
if (spotBalance === 0) {
|
|
100
|
+
return {
|
|
101
|
+
...accountState,
|
|
102
|
+
availableToTradeBalance: Number.isFinite(currentAvailable)
|
|
103
|
+
? currentAvailable.toString()
|
|
104
|
+
: accountState.availableBalance,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const availableToTrade = Number.isFinite(currentAvailable)
|
|
108
|
+
? (currentAvailable + freeSpot).toString()
|
|
109
|
+
: freeSpot.toString();
|
|
110
|
+
// Subtract spotHold to avoid double-counting on Unified/PM accounts:
|
|
111
|
+
// marginSummary.accountValue already includes the margin that HL
|
|
112
|
+
// surfaces via spot.hold. Standard mode has spotHold = 0, no-op.
|
|
113
|
+
const nextTotal = currentTotal + spotBalance - spotHold;
|
|
114
|
+
return {
|
|
115
|
+
...accountState,
|
|
116
|
+
totalBalance: nextTotal.toString(),
|
|
117
|
+
availableToTradeBalance: availableToTrade,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
exports.addSpotBalanceToAccountState = addSpotBalanceToAccountState;
|
|
57
121
|
/**
|
|
58
122
|
* Aggregate multiple per-DEX AccountState objects into one by summing numeric fields.
|
|
59
123
|
* ROE is recalculated as (totalUnrealizedPnl / totalMarginUsed) * 100.
|
|
@@ -76,8 +140,17 @@ function aggregateAccountStates(states) {
|
|
|
76
140
|
if (index === 0) {
|
|
77
141
|
return { ...state };
|
|
78
142
|
}
|
|
143
|
+
const accAvailableToTrade = parseFloat(acc.availableToTradeBalance ?? acc.availableBalance);
|
|
144
|
+
const stateAvailableToTrade = parseFloat(state.availableToTradeBalance ?? state.availableBalance);
|
|
145
|
+
const availableToTradeSum = Number.isFinite(accAvailableToTrade) &&
|
|
146
|
+
Number.isFinite(stateAvailableToTrade)
|
|
147
|
+
? (accAvailableToTrade + stateAvailableToTrade).toString()
|
|
148
|
+
: undefined;
|
|
79
149
|
return {
|
|
80
150
|
availableBalance: (parseFloat(acc.availableBalance) + parseFloat(state.availableBalance)).toString(),
|
|
151
|
+
...(availableToTradeSum !== undefined && {
|
|
152
|
+
availableToTradeBalance: availableToTradeSum,
|
|
153
|
+
}),
|
|
81
154
|
totalBalance: (parseFloat(acc.totalBalance) + parseFloat(state.totalBalance)).toString(),
|
|
82
155
|
marginUsed: (parseFloat(acc.marginUsed) + parseFloat(state.marginUsed)).toString(),
|
|
83
156
|
unrealizedPnl: (parseFloat(acc.unrealizedPnl) + parseFloat(state.unrealizedPnl)).toString(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accountUtils.cjs","sourceRoot":"","sources":["../../src/utils/accountUtils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account utilities for Perps components\n * Handles account selection and EVM account filtering\n */\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\n\nimport { PERPS_CONSTANTS } from '../constants/perpsConfig';\nimport type { AccountState, PerpsInternalAccount } from '../types';\n\nconst EVM_ACCOUNT_TYPES = new Set(['eip155:eoa', 'eip155:erc4337']);\n\nfunction isEvmAccountType(type: string): boolean {\n return EVM_ACCOUNT_TYPES.has(type);\n}\n\nexport function findEvmAccount(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string; type: string } | null {\n const evmAccount = accounts.find(\n (account) =>\n account && isEvmAccountType(account.type as InternalAccount['type']),\n );\n return evmAccount ?? null;\n}\n\nexport function getEvmAccountFromAccountGroup(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string } | undefined {\n const evmAccount = findEvmAccount(accounts);\n return evmAccount ? { address: evmAccount.address } : undefined;\n}\n\nexport function getSelectedEvmAccount(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string } | undefined {\n return getEvmAccountFromAccountGroup(accounts);\n}\n\nexport type ReturnOnEquityInput = {\n unrealizedPnl: string | number;\n returnOnEquity: string | number;\n};\n\nexport function calculateWeightedReturnOnEquity(\n accounts: ReturnOnEquityInput[],\n): string {\n if (accounts.length === 0) {\n return '0';\n }\n\n let totalWeightedROE = 0;\n let totalMarginUsed = 0;\n\n for (const account of accounts) {\n const unrealizedPnl =\n typeof account.unrealizedPnl === 'string'\n ? Number.parseFloat(account.unrealizedPnl)\n : account.unrealizedPnl;\n const returnOnEquity =\n typeof account.returnOnEquity === 'string'\n ? Number.parseFloat(account.returnOnEquity)\n : account.returnOnEquity;\n\n if (Number.isNaN(unrealizedPnl) || Number.isNaN(returnOnEquity)) {\n continue;\n }\n\n if (returnOnEquity === 0) {\n continue;\n }\n\n const marginUsed = (unrealizedPnl / returnOnEquity) * 100;\n\n if (Number.isNaN(marginUsed) || marginUsed <= 0) {\n continue;\n }\n\n const roeDecimal = returnOnEquity / 100;\n\n totalWeightedROE += roeDecimal * marginUsed;\n totalMarginUsed += marginUsed;\n }\n\n if (totalMarginUsed <= 0) {\n return '0';\n }\n\n const weightedROE = (totalWeightedROE / totalMarginUsed) * 100;\n return weightedROE.toString();\n}\n\n/**\n * Aggregate multiple per-DEX AccountState objects into one by summing numeric fields.\n * ROE is recalculated as (totalUnrealizedPnl / totalMarginUsed) * 100.\n *\n * @param states - The array of per-DEX account states to aggregate.\n * @returns The combined account state with summed balances and recalculated ROE.\n */\nexport function aggregateAccountStates(states: AccountState[]): AccountState {\n const fallback: AccountState = {\n availableBalance: PERPS_CONSTANTS.FallbackDataDisplay,\n totalBalance: PERPS_CONSTANTS.FallbackDataDisplay,\n marginUsed: PERPS_CONSTANTS.FallbackDataDisplay,\n unrealizedPnl: PERPS_CONSTANTS.FallbackDataDisplay,\n returnOnEquity: PERPS_CONSTANTS.FallbackDataDisplay,\n };\n\n if (states.length === 0) {\n return fallback;\n }\n\n const aggregated = states.reduce<AccountState>((acc, state, index) => {\n if (index === 0) {\n return { ...state };\n }\n return {\n availableBalance: (\n parseFloat(acc.availableBalance) + parseFloat(state.availableBalance)\n ).toString(),\n totalBalance: (\n parseFloat(acc.totalBalance) + parseFloat(state.totalBalance)\n ).toString(),\n marginUsed: (\n parseFloat(acc.marginUsed) + parseFloat(state.marginUsed)\n ).toString(),\n unrealizedPnl: (\n parseFloat(acc.unrealizedPnl) + parseFloat(state.unrealizedPnl)\n ).toString(),\n returnOnEquity: '0',\n };\n }, fallback);\n\n // Recalculate ROE across all DEXs\n const totalMarginUsed = parseFloat(aggregated.marginUsed);\n const totalUnrealizedPnl = parseFloat(aggregated.unrealizedPnl);\n if (totalMarginUsed > 0) {\n aggregated.returnOnEquity = (\n (totalUnrealizedPnl / totalMarginUsed) *\n 100\n ).toString();\n } else {\n aggregated.returnOnEquity = '0';\n }\n\n return aggregated;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"accountUtils.cjs","sourceRoot":"","sources":["../../src/utils/accountUtils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account utilities for Perps components\n * Handles account selection and EVM account filtering\n */\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\n\nimport { PERPS_CONSTANTS } from '../constants/perpsConfig';\nimport type { AccountState, PerpsInternalAccount } from '../types';\nimport type { SpotClearinghouseStateResponse } from '../types/hyperliquid-types';\n\nconst EVM_ACCOUNT_TYPES = new Set(['eip155:eoa', 'eip155:erc4337']);\n\nfunction isEvmAccountType(type: string): boolean {\n return EVM_ACCOUNT_TYPES.has(type);\n}\n\nexport function findEvmAccount(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string; type: string } | null {\n const evmAccount = accounts.find(\n (account) =>\n account && isEvmAccountType(account.type as InternalAccount['type']),\n );\n return evmAccount ?? null;\n}\n\nexport function getEvmAccountFromAccountGroup(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string } | undefined {\n const evmAccount = findEvmAccount(accounts);\n return evmAccount ? { address: evmAccount.address } : undefined;\n}\n\nexport function getSelectedEvmAccount(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string } | undefined {\n return getEvmAccountFromAccountGroup(accounts);\n}\n\nexport type ReturnOnEquityInput = {\n unrealizedPnl: string | number;\n returnOnEquity: string | number;\n};\n\nexport function calculateWeightedReturnOnEquity(\n accounts: ReturnOnEquityInput[],\n): string {\n if (accounts.length === 0) {\n return '0';\n }\n\n let totalWeightedROE = 0;\n let totalMarginUsed = 0;\n\n for (const account of accounts) {\n const unrealizedPnl =\n typeof account.unrealizedPnl === 'string'\n ? Number.parseFloat(account.unrealizedPnl)\n : account.unrealizedPnl;\n const returnOnEquity =\n typeof account.returnOnEquity === 'string'\n ? Number.parseFloat(account.returnOnEquity)\n : account.returnOnEquity;\n\n if (Number.isNaN(unrealizedPnl) || Number.isNaN(returnOnEquity)) {\n continue;\n }\n\n if (returnOnEquity === 0) {\n continue;\n }\n\n const marginUsed = (unrealizedPnl / returnOnEquity) * 100;\n\n if (Number.isNaN(marginUsed) || marginUsed <= 0) {\n continue;\n }\n\n const roeDecimal = returnOnEquity / 100;\n\n totalWeightedROE += roeDecimal * marginUsed;\n totalMarginUsed += marginUsed;\n }\n\n if (totalMarginUsed <= 0) {\n return '0';\n }\n\n const weightedROE = (totalWeightedROE / totalMarginUsed) * 100;\n return weightedROE.toString();\n}\n\n// Spot coins counted toward currently supported funded-state gating.\n// Today the in-app HyperLiquid market surface is USDC-collateralized only,\n// so USDH must not inflate the shared funded-state path that hides Add Funds.\n// Non-stablecoin spot assets (HYPE, PURR, …) also remain excluded.\nconst SPOT_COLLATERAL_COINS = new Set<string>(['USDC']);\n\nexport function getSpotBalance(\n spotState?: SpotClearinghouseStateResponse | null,\n): number {\n if (!spotState?.balances || !Array.isArray(spotState.balances)) {\n return 0;\n }\n\n return spotState.balances.reduce(\n (sum: number, balance: { coin?: string; total?: string }) => {\n if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {\n return sum;\n }\n const value = parseFloat(balance.total ?? '0');\n return Number.isFinite(value) ? sum + value : sum;\n },\n 0,\n );\n}\n\nexport function getSpotHold(\n spotState?: SpotClearinghouseStateResponse | null,\n): number {\n if (!spotState?.balances || !Array.isArray(spotState.balances)) {\n return 0;\n }\n\n return spotState.balances.reduce(\n (sum: number, balance: { coin?: string; hold?: string }) => {\n if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {\n return sum;\n }\n const value = parseFloat(balance.hold ?? '0');\n return Number.isFinite(value) ? sum + value : sum;\n },\n 0,\n );\n}\n\nexport function addSpotBalanceToAccountState(\n accountState: AccountState,\n spotState?: SpotClearinghouseStateResponse | null,\n): AccountState {\n const spotBalance = getSpotBalance(spotState);\n const spotHold = getSpotHold(spotState);\n const freeSpot = Math.max(0, spotBalance - spotHold);\n\n const currentTotal = parseFloat(accountState.totalBalance);\n const currentAvailable = parseFloat(accountState.availableBalance);\n\n // Preserve sentinel totals (e.g. PERPS_CONSTANTS.FallbackDataDisplay '--')\n // rather than coercing them to NaN.\n if (!Number.isFinite(currentTotal)) {\n return accountState;\n }\n\n if (spotBalance === 0) {\n return {\n ...accountState,\n availableToTradeBalance: Number.isFinite(currentAvailable)\n ? currentAvailable.toString()\n : accountState.availableBalance,\n };\n }\n\n const availableToTrade = Number.isFinite(currentAvailable)\n ? (currentAvailable + freeSpot).toString()\n : freeSpot.toString();\n\n // Subtract spotHold to avoid double-counting on Unified/PM accounts:\n // marginSummary.accountValue already includes the margin that HL\n // surfaces via spot.hold. Standard mode has spotHold = 0, no-op.\n const nextTotal = currentTotal + spotBalance - spotHold;\n\n return {\n ...accountState,\n totalBalance: nextTotal.toString(),\n availableToTradeBalance: availableToTrade,\n };\n}\n\n/**\n * Aggregate multiple per-DEX AccountState objects into one by summing numeric fields.\n * ROE is recalculated as (totalUnrealizedPnl / totalMarginUsed) * 100.\n *\n * @param states - The array of per-DEX account states to aggregate.\n * @returns The combined account state with summed balances and recalculated ROE.\n */\nexport function aggregateAccountStates(states: AccountState[]): AccountState {\n const fallback: AccountState = {\n availableBalance: PERPS_CONSTANTS.FallbackDataDisplay,\n totalBalance: PERPS_CONSTANTS.FallbackDataDisplay,\n marginUsed: PERPS_CONSTANTS.FallbackDataDisplay,\n unrealizedPnl: PERPS_CONSTANTS.FallbackDataDisplay,\n returnOnEquity: PERPS_CONSTANTS.FallbackDataDisplay,\n };\n\n if (states.length === 0) {\n return fallback;\n }\n\n const aggregated = states.reduce<AccountState>((acc, state, index) => {\n if (index === 0) {\n return { ...state };\n }\n const accAvailableToTrade = parseFloat(\n acc.availableToTradeBalance ?? acc.availableBalance,\n );\n const stateAvailableToTrade = parseFloat(\n state.availableToTradeBalance ?? state.availableBalance,\n );\n const availableToTradeSum =\n Number.isFinite(accAvailableToTrade) &&\n Number.isFinite(stateAvailableToTrade)\n ? (accAvailableToTrade + stateAvailableToTrade).toString()\n : undefined;\n\n return {\n availableBalance: (\n parseFloat(acc.availableBalance) + parseFloat(state.availableBalance)\n ).toString(),\n ...(availableToTradeSum !== undefined && {\n availableToTradeBalance: availableToTradeSum,\n }),\n totalBalance: (\n parseFloat(acc.totalBalance) + parseFloat(state.totalBalance)\n ).toString(),\n marginUsed: (\n parseFloat(acc.marginUsed) + parseFloat(state.marginUsed)\n ).toString(),\n unrealizedPnl: (\n parseFloat(acc.unrealizedPnl) + parseFloat(state.unrealizedPnl)\n ).toString(),\n returnOnEquity: '0',\n };\n }, fallback);\n\n // Recalculate ROE across all DEXs\n const totalMarginUsed = parseFloat(aggregated.marginUsed);\n const totalUnrealizedPnl = parseFloat(aggregated.unrealizedPnl);\n if (totalMarginUsed > 0) {\n aggregated.returnOnEquity = (\n (totalUnrealizedPnl / totalMarginUsed) *\n 100\n ).toString();\n } else {\n aggregated.returnOnEquity = '0';\n }\n\n return aggregated;\n}\n"]}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { InternalAccount } from "@metamask/keyring-internal-api";
|
|
6
6
|
import type { AccountState, PerpsInternalAccount } from "../types/index.cjs";
|
|
7
|
+
import type { SpotClearinghouseStateResponse } from "../types/hyperliquid-types.cjs";
|
|
7
8
|
export declare function findEvmAccount(accounts: (InternalAccount | PerpsInternalAccount)[]): {
|
|
8
9
|
address: string;
|
|
9
10
|
type: string;
|
|
@@ -19,6 +20,9 @@ export type ReturnOnEquityInput = {
|
|
|
19
20
|
returnOnEquity: string | number;
|
|
20
21
|
};
|
|
21
22
|
export declare function calculateWeightedReturnOnEquity(accounts: ReturnOnEquityInput[]): string;
|
|
23
|
+
export declare function getSpotBalance(spotState?: SpotClearinghouseStateResponse | null): number;
|
|
24
|
+
export declare function getSpotHold(spotState?: SpotClearinghouseStateResponse | null): number;
|
|
25
|
+
export declare function addSpotBalanceToAccountState(accountState: AccountState, spotState?: SpotClearinghouseStateResponse | null): AccountState;
|
|
22
26
|
/**
|
|
23
27
|
* Aggregate multiple per-DEX AccountState objects into one by summing numeric fields.
|
|
24
28
|
* ROE is recalculated as (totalUnrealizedPnl / totalMarginUsed) * 100.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accountUtils.d.cts","sourceRoot":"","sources":["../../src/utils/accountUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,uCAAuC;AAGtE,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,2BAAiB;
|
|
1
|
+
{"version":3,"file":"accountUtils.d.cts","sourceRoot":"","sources":["../../src/utils/accountUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,uCAAuC;AAGtE,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,2BAAiB;AACnE,OAAO,KAAK,EAAE,8BAA8B,EAAE,uCAAmC;AAQjF,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,CAAC,eAAe,GAAG,oBAAoB,CAAC,EAAE,GACnD;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAM1C;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,CAAC,eAAe,GAAG,oBAAoB,CAAC,EAAE,GACnD;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAGjC;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,eAAe,GAAG,oBAAoB,CAAC,EAAE,GACnD;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAEjC;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC;CACjC,CAAC;AAEF,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,mBAAmB,EAAE,GAC9B,MAAM,CA4CR;AAQD,wBAAgB,cAAc,CAC5B,SAAS,CAAC,EAAE,8BAA8B,GAAG,IAAI,GAChD,MAAM,CAeR;AAED,wBAAgB,WAAW,CACzB,SAAS,CAAC,EAAE,8BAA8B,GAAG,IAAI,GAChD,MAAM,CAeR;AAED,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,YAAY,EAC1B,SAAS,CAAC,EAAE,8BAA8B,GAAG,IAAI,GAChD,YAAY,CAqCd;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CA8D3E"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { InternalAccount } from "@metamask/keyring-internal-api";
|
|
6
6
|
import type { AccountState, PerpsInternalAccount } from "../types/index.mjs";
|
|
7
|
+
import type { SpotClearinghouseStateResponse } from "../types/hyperliquid-types.mjs";
|
|
7
8
|
export declare function findEvmAccount(accounts: (InternalAccount | PerpsInternalAccount)[]): {
|
|
8
9
|
address: string;
|
|
9
10
|
type: string;
|
|
@@ -19,6 +20,9 @@ export type ReturnOnEquityInput = {
|
|
|
19
20
|
returnOnEquity: string | number;
|
|
20
21
|
};
|
|
21
22
|
export declare function calculateWeightedReturnOnEquity(accounts: ReturnOnEquityInput[]): string;
|
|
23
|
+
export declare function getSpotBalance(spotState?: SpotClearinghouseStateResponse | null): number;
|
|
24
|
+
export declare function getSpotHold(spotState?: SpotClearinghouseStateResponse | null): number;
|
|
25
|
+
export declare function addSpotBalanceToAccountState(accountState: AccountState, spotState?: SpotClearinghouseStateResponse | null): AccountState;
|
|
22
26
|
/**
|
|
23
27
|
* Aggregate multiple per-DEX AccountState objects into one by summing numeric fields.
|
|
24
28
|
* ROE is recalculated as (totalUnrealizedPnl / totalMarginUsed) * 100.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accountUtils.d.mts","sourceRoot":"","sources":["../../src/utils/accountUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,uCAAuC;AAGtE,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,2BAAiB;
|
|
1
|
+
{"version":3,"file":"accountUtils.d.mts","sourceRoot":"","sources":["../../src/utils/accountUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,uCAAuC;AAGtE,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,2BAAiB;AACnE,OAAO,KAAK,EAAE,8BAA8B,EAAE,uCAAmC;AAQjF,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,CAAC,eAAe,GAAG,oBAAoB,CAAC,EAAE,GACnD;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAM1C;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,CAAC,eAAe,GAAG,oBAAoB,CAAC,EAAE,GACnD;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAGjC;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,eAAe,GAAG,oBAAoB,CAAC,EAAE,GACnD;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAEjC;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC;CACjC,CAAC;AAEF,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,mBAAmB,EAAE,GAC9B,MAAM,CA4CR;AAQD,wBAAgB,cAAc,CAC5B,SAAS,CAAC,EAAE,8BAA8B,GAAG,IAAI,GAChD,MAAM,CAeR;AAED,wBAAgB,WAAW,CACzB,SAAS,CAAC,EAAE,8BAA8B,GAAG,IAAI,GAChD,MAAM,CAeR;AAED,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,YAAY,EAC1B,SAAS,CAAC,EAAE,8BAA8B,GAAG,IAAI,GAChD,YAAY,CAqCd;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CA8D3E"}
|
|
@@ -47,6 +47,67 @@ export function calculateWeightedReturnOnEquity(accounts) {
|
|
|
47
47
|
const weightedROE = (totalWeightedROE / totalMarginUsed) * 100;
|
|
48
48
|
return weightedROE.toString();
|
|
49
49
|
}
|
|
50
|
+
// Spot coins counted toward currently supported funded-state gating.
|
|
51
|
+
// Today the in-app HyperLiquid market surface is USDC-collateralized only,
|
|
52
|
+
// so USDH must not inflate the shared funded-state path that hides Add Funds.
|
|
53
|
+
// Non-stablecoin spot assets (HYPE, PURR, …) also remain excluded.
|
|
54
|
+
const SPOT_COLLATERAL_COINS = new Set(['USDC']);
|
|
55
|
+
export function getSpotBalance(spotState) {
|
|
56
|
+
if (!spotState?.balances || !Array.isArray(spotState.balances)) {
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
return spotState.balances.reduce((sum, balance) => {
|
|
60
|
+
if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {
|
|
61
|
+
return sum;
|
|
62
|
+
}
|
|
63
|
+
const value = parseFloat(balance.total ?? '0');
|
|
64
|
+
return Number.isFinite(value) ? sum + value : sum;
|
|
65
|
+
}, 0);
|
|
66
|
+
}
|
|
67
|
+
export function getSpotHold(spotState) {
|
|
68
|
+
if (!spotState?.balances || !Array.isArray(spotState.balances)) {
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
return spotState.balances.reduce((sum, balance) => {
|
|
72
|
+
if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {
|
|
73
|
+
return sum;
|
|
74
|
+
}
|
|
75
|
+
const value = parseFloat(balance.hold ?? '0');
|
|
76
|
+
return Number.isFinite(value) ? sum + value : sum;
|
|
77
|
+
}, 0);
|
|
78
|
+
}
|
|
79
|
+
export function addSpotBalanceToAccountState(accountState, spotState) {
|
|
80
|
+
const spotBalance = getSpotBalance(spotState);
|
|
81
|
+
const spotHold = getSpotHold(spotState);
|
|
82
|
+
const freeSpot = Math.max(0, spotBalance - spotHold);
|
|
83
|
+
const currentTotal = parseFloat(accountState.totalBalance);
|
|
84
|
+
const currentAvailable = parseFloat(accountState.availableBalance);
|
|
85
|
+
// Preserve sentinel totals (e.g. PERPS_CONSTANTS.FallbackDataDisplay '--')
|
|
86
|
+
// rather than coercing them to NaN.
|
|
87
|
+
if (!Number.isFinite(currentTotal)) {
|
|
88
|
+
return accountState;
|
|
89
|
+
}
|
|
90
|
+
if (spotBalance === 0) {
|
|
91
|
+
return {
|
|
92
|
+
...accountState,
|
|
93
|
+
availableToTradeBalance: Number.isFinite(currentAvailable)
|
|
94
|
+
? currentAvailable.toString()
|
|
95
|
+
: accountState.availableBalance,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const availableToTrade = Number.isFinite(currentAvailable)
|
|
99
|
+
? (currentAvailable + freeSpot).toString()
|
|
100
|
+
: freeSpot.toString();
|
|
101
|
+
// Subtract spotHold to avoid double-counting on Unified/PM accounts:
|
|
102
|
+
// marginSummary.accountValue already includes the margin that HL
|
|
103
|
+
// surfaces via spot.hold. Standard mode has spotHold = 0, no-op.
|
|
104
|
+
const nextTotal = currentTotal + spotBalance - spotHold;
|
|
105
|
+
return {
|
|
106
|
+
...accountState,
|
|
107
|
+
totalBalance: nextTotal.toString(),
|
|
108
|
+
availableToTradeBalance: availableToTrade,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
50
111
|
/**
|
|
51
112
|
* Aggregate multiple per-DEX AccountState objects into one by summing numeric fields.
|
|
52
113
|
* ROE is recalculated as (totalUnrealizedPnl / totalMarginUsed) * 100.
|
|
@@ -69,8 +130,17 @@ export function aggregateAccountStates(states) {
|
|
|
69
130
|
if (index === 0) {
|
|
70
131
|
return { ...state };
|
|
71
132
|
}
|
|
133
|
+
const accAvailableToTrade = parseFloat(acc.availableToTradeBalance ?? acc.availableBalance);
|
|
134
|
+
const stateAvailableToTrade = parseFloat(state.availableToTradeBalance ?? state.availableBalance);
|
|
135
|
+
const availableToTradeSum = Number.isFinite(accAvailableToTrade) &&
|
|
136
|
+
Number.isFinite(stateAvailableToTrade)
|
|
137
|
+
? (accAvailableToTrade + stateAvailableToTrade).toString()
|
|
138
|
+
: undefined;
|
|
72
139
|
return {
|
|
73
140
|
availableBalance: (parseFloat(acc.availableBalance) + parseFloat(state.availableBalance)).toString(),
|
|
141
|
+
...(availableToTradeSum !== undefined && {
|
|
142
|
+
availableToTradeBalance: availableToTradeSum,
|
|
143
|
+
}),
|
|
74
144
|
totalBalance: (parseFloat(acc.totalBalance) + parseFloat(state.totalBalance)).toString(),
|
|
75
145
|
marginUsed: (parseFloat(acc.marginUsed) + parseFloat(state.marginUsed)).toString(),
|
|
76
146
|
unrealizedPnl: (parseFloat(acc.unrealizedPnl) + parseFloat(state.unrealizedPnl)).toString(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accountUtils.mjs","sourceRoot":"","sources":["../../src/utils/accountUtils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account utilities for Perps components\n * Handles account selection and EVM account filtering\n */\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\n\nimport { PERPS_CONSTANTS } from '../constants/perpsConfig';\nimport type { AccountState, PerpsInternalAccount } from '../types';\n\nconst EVM_ACCOUNT_TYPES = new Set(['eip155:eoa', 'eip155:erc4337']);\n\nfunction isEvmAccountType(type: string): boolean {\n return EVM_ACCOUNT_TYPES.has(type);\n}\n\nexport function findEvmAccount(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string; type: string } | null {\n const evmAccount = accounts.find(\n (account) =>\n account && isEvmAccountType(account.type as InternalAccount['type']),\n );\n return evmAccount ?? null;\n}\n\nexport function getEvmAccountFromAccountGroup(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string } | undefined {\n const evmAccount = findEvmAccount(accounts);\n return evmAccount ? { address: evmAccount.address } : undefined;\n}\n\nexport function getSelectedEvmAccount(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string } | undefined {\n return getEvmAccountFromAccountGroup(accounts);\n}\n\nexport type ReturnOnEquityInput = {\n unrealizedPnl: string | number;\n returnOnEquity: string | number;\n};\n\nexport function calculateWeightedReturnOnEquity(\n accounts: ReturnOnEquityInput[],\n): string {\n if (accounts.length === 0) {\n return '0';\n }\n\n let totalWeightedROE = 0;\n let totalMarginUsed = 0;\n\n for (const account of accounts) {\n const unrealizedPnl =\n typeof account.unrealizedPnl === 'string'\n ? Number.parseFloat(account.unrealizedPnl)\n : account.unrealizedPnl;\n const returnOnEquity =\n typeof account.returnOnEquity === 'string'\n ? Number.parseFloat(account.returnOnEquity)\n : account.returnOnEquity;\n\n if (Number.isNaN(unrealizedPnl) || Number.isNaN(returnOnEquity)) {\n continue;\n }\n\n if (returnOnEquity === 0) {\n continue;\n }\n\n const marginUsed = (unrealizedPnl / returnOnEquity) * 100;\n\n if (Number.isNaN(marginUsed) || marginUsed <= 0) {\n continue;\n }\n\n const roeDecimal = returnOnEquity / 100;\n\n totalWeightedROE += roeDecimal * marginUsed;\n totalMarginUsed += marginUsed;\n }\n\n if (totalMarginUsed <= 0) {\n return '0';\n }\n\n const weightedROE = (totalWeightedROE / totalMarginUsed) * 100;\n return weightedROE.toString();\n}\n\n/**\n * Aggregate multiple per-DEX AccountState objects into one by summing numeric fields.\n * ROE is recalculated as (totalUnrealizedPnl / totalMarginUsed) * 100.\n *\n * @param states - The array of per-DEX account states to aggregate.\n * @returns The combined account state with summed balances and recalculated ROE.\n */\nexport function aggregateAccountStates(states: AccountState[]): AccountState {\n const fallback: AccountState = {\n availableBalance: PERPS_CONSTANTS.FallbackDataDisplay,\n totalBalance: PERPS_CONSTANTS.FallbackDataDisplay,\n marginUsed: PERPS_CONSTANTS.FallbackDataDisplay,\n unrealizedPnl: PERPS_CONSTANTS.FallbackDataDisplay,\n returnOnEquity: PERPS_CONSTANTS.FallbackDataDisplay,\n };\n\n if (states.length === 0) {\n return fallback;\n }\n\n const aggregated = states.reduce<AccountState>((acc, state, index) => {\n if (index === 0) {\n return { ...state };\n }\n return {\n availableBalance: (\n parseFloat(acc.availableBalance) + parseFloat(state.availableBalance)\n ).toString(),\n totalBalance: (\n parseFloat(acc.totalBalance) + parseFloat(state.totalBalance)\n ).toString(),\n marginUsed: (\n parseFloat(acc.marginUsed) + parseFloat(state.marginUsed)\n ).toString(),\n unrealizedPnl: (\n parseFloat(acc.unrealizedPnl) + parseFloat(state.unrealizedPnl)\n ).toString(),\n returnOnEquity: '0',\n };\n }, fallback);\n\n // Recalculate ROE across all DEXs\n const totalMarginUsed = parseFloat(aggregated.marginUsed);\n const totalUnrealizedPnl = parseFloat(aggregated.unrealizedPnl);\n if (totalMarginUsed > 0) {\n aggregated.returnOnEquity = (\n (totalUnrealizedPnl / totalMarginUsed) *\n 100\n ).toString();\n } else {\n aggregated.returnOnEquity = '0';\n }\n\n return aggregated;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"accountUtils.mjs","sourceRoot":"","sources":["../../src/utils/accountUtils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account utilities for Perps components\n * Handles account selection and EVM account filtering\n */\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\n\nimport { PERPS_CONSTANTS } from '../constants/perpsConfig';\nimport type { AccountState, PerpsInternalAccount } from '../types';\nimport type { SpotClearinghouseStateResponse } from '../types/hyperliquid-types';\n\nconst EVM_ACCOUNT_TYPES = new Set(['eip155:eoa', 'eip155:erc4337']);\n\nfunction isEvmAccountType(type: string): boolean {\n return EVM_ACCOUNT_TYPES.has(type);\n}\n\nexport function findEvmAccount(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string; type: string } | null {\n const evmAccount = accounts.find(\n (account) =>\n account && isEvmAccountType(account.type as InternalAccount['type']),\n );\n return evmAccount ?? null;\n}\n\nexport function getEvmAccountFromAccountGroup(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string } | undefined {\n const evmAccount = findEvmAccount(accounts);\n return evmAccount ? { address: evmAccount.address } : undefined;\n}\n\nexport function getSelectedEvmAccount(\n accounts: (InternalAccount | PerpsInternalAccount)[],\n): { address: string } | undefined {\n return getEvmAccountFromAccountGroup(accounts);\n}\n\nexport type ReturnOnEquityInput = {\n unrealizedPnl: string | number;\n returnOnEquity: string | number;\n};\n\nexport function calculateWeightedReturnOnEquity(\n accounts: ReturnOnEquityInput[],\n): string {\n if (accounts.length === 0) {\n return '0';\n }\n\n let totalWeightedROE = 0;\n let totalMarginUsed = 0;\n\n for (const account of accounts) {\n const unrealizedPnl =\n typeof account.unrealizedPnl === 'string'\n ? Number.parseFloat(account.unrealizedPnl)\n : account.unrealizedPnl;\n const returnOnEquity =\n typeof account.returnOnEquity === 'string'\n ? Number.parseFloat(account.returnOnEquity)\n : account.returnOnEquity;\n\n if (Number.isNaN(unrealizedPnl) || Number.isNaN(returnOnEquity)) {\n continue;\n }\n\n if (returnOnEquity === 0) {\n continue;\n }\n\n const marginUsed = (unrealizedPnl / returnOnEquity) * 100;\n\n if (Number.isNaN(marginUsed) || marginUsed <= 0) {\n continue;\n }\n\n const roeDecimal = returnOnEquity / 100;\n\n totalWeightedROE += roeDecimal * marginUsed;\n totalMarginUsed += marginUsed;\n }\n\n if (totalMarginUsed <= 0) {\n return '0';\n }\n\n const weightedROE = (totalWeightedROE / totalMarginUsed) * 100;\n return weightedROE.toString();\n}\n\n// Spot coins counted toward currently supported funded-state gating.\n// Today the in-app HyperLiquid market surface is USDC-collateralized only,\n// so USDH must not inflate the shared funded-state path that hides Add Funds.\n// Non-stablecoin spot assets (HYPE, PURR, …) also remain excluded.\nconst SPOT_COLLATERAL_COINS = new Set<string>(['USDC']);\n\nexport function getSpotBalance(\n spotState?: SpotClearinghouseStateResponse | null,\n): number {\n if (!spotState?.balances || !Array.isArray(spotState.balances)) {\n return 0;\n }\n\n return spotState.balances.reduce(\n (sum: number, balance: { coin?: string; total?: string }) => {\n if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {\n return sum;\n }\n const value = parseFloat(balance.total ?? '0');\n return Number.isFinite(value) ? sum + value : sum;\n },\n 0,\n );\n}\n\nexport function getSpotHold(\n spotState?: SpotClearinghouseStateResponse | null,\n): number {\n if (!spotState?.balances || !Array.isArray(spotState.balances)) {\n return 0;\n }\n\n return spotState.balances.reduce(\n (sum: number, balance: { coin?: string; hold?: string }) => {\n if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {\n return sum;\n }\n const value = parseFloat(balance.hold ?? '0');\n return Number.isFinite(value) ? sum + value : sum;\n },\n 0,\n );\n}\n\nexport function addSpotBalanceToAccountState(\n accountState: AccountState,\n spotState?: SpotClearinghouseStateResponse | null,\n): AccountState {\n const spotBalance = getSpotBalance(spotState);\n const spotHold = getSpotHold(spotState);\n const freeSpot = Math.max(0, spotBalance - spotHold);\n\n const currentTotal = parseFloat(accountState.totalBalance);\n const currentAvailable = parseFloat(accountState.availableBalance);\n\n // Preserve sentinel totals (e.g. PERPS_CONSTANTS.FallbackDataDisplay '--')\n // rather than coercing them to NaN.\n if (!Number.isFinite(currentTotal)) {\n return accountState;\n }\n\n if (spotBalance === 0) {\n return {\n ...accountState,\n availableToTradeBalance: Number.isFinite(currentAvailable)\n ? currentAvailable.toString()\n : accountState.availableBalance,\n };\n }\n\n const availableToTrade = Number.isFinite(currentAvailable)\n ? (currentAvailable + freeSpot).toString()\n : freeSpot.toString();\n\n // Subtract spotHold to avoid double-counting on Unified/PM accounts:\n // marginSummary.accountValue already includes the margin that HL\n // surfaces via spot.hold. Standard mode has spotHold = 0, no-op.\n const nextTotal = currentTotal + spotBalance - spotHold;\n\n return {\n ...accountState,\n totalBalance: nextTotal.toString(),\n availableToTradeBalance: availableToTrade,\n };\n}\n\n/**\n * Aggregate multiple per-DEX AccountState objects into one by summing numeric fields.\n * ROE is recalculated as (totalUnrealizedPnl / totalMarginUsed) * 100.\n *\n * @param states - The array of per-DEX account states to aggregate.\n * @returns The combined account state with summed balances and recalculated ROE.\n */\nexport function aggregateAccountStates(states: AccountState[]): AccountState {\n const fallback: AccountState = {\n availableBalance: PERPS_CONSTANTS.FallbackDataDisplay,\n totalBalance: PERPS_CONSTANTS.FallbackDataDisplay,\n marginUsed: PERPS_CONSTANTS.FallbackDataDisplay,\n unrealizedPnl: PERPS_CONSTANTS.FallbackDataDisplay,\n returnOnEquity: PERPS_CONSTANTS.FallbackDataDisplay,\n };\n\n if (states.length === 0) {\n return fallback;\n }\n\n const aggregated = states.reduce<AccountState>((acc, state, index) => {\n if (index === 0) {\n return { ...state };\n }\n const accAvailableToTrade = parseFloat(\n acc.availableToTradeBalance ?? acc.availableBalance,\n );\n const stateAvailableToTrade = parseFloat(\n state.availableToTradeBalance ?? state.availableBalance,\n );\n const availableToTradeSum =\n Number.isFinite(accAvailableToTrade) &&\n Number.isFinite(stateAvailableToTrade)\n ? (accAvailableToTrade + stateAvailableToTrade).toString()\n : undefined;\n\n return {\n availableBalance: (\n parseFloat(acc.availableBalance) + parseFloat(state.availableBalance)\n ).toString(),\n ...(availableToTradeSum !== undefined && {\n availableToTradeBalance: availableToTradeSum,\n }),\n totalBalance: (\n parseFloat(acc.totalBalance) + parseFloat(state.totalBalance)\n ).toString(),\n marginUsed: (\n parseFloat(acc.marginUsed) + parseFloat(state.marginUsed)\n ).toString(),\n unrealizedPnl: (\n parseFloat(acc.unrealizedPnl) + parseFloat(state.unrealizedPnl)\n ).toString(),\n returnOnEquity: '0',\n };\n }, fallback);\n\n // Recalculate ROE across all DEXs\n const totalMarginUsed = parseFloat(aggregated.marginUsed);\n const totalUnrealizedPnl = parseFloat(aggregated.unrealizedPnl);\n if (totalMarginUsed > 0) {\n aggregated.returnOnEquity = (\n (totalUnrealizedPnl / totalMarginUsed) *\n 100\n ).toString();\n } else {\n aggregated.returnOnEquity = '0';\n }\n\n return aggregated;\n}\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resetPerpsRestCacheForTests = exports.coalescePerpsRestRequest = void 0;
|
|
4
|
+
const perpsConfig_1 = require("../constants/perpsConfig.cjs");
|
|
5
|
+
const inflight = new Map();
|
|
6
|
+
const cache = new Map();
|
|
7
|
+
/**
|
|
8
|
+
* Coalesce an idempotent perps REST call.
|
|
9
|
+
*
|
|
10
|
+
* - If a fresh cached value exists for `key`, it is returned immediately.
|
|
11
|
+
* - If an in-flight promise exists for `key`, it is shared with the caller.
|
|
12
|
+
* - Otherwise, `fetcher` runs once; the result populates the cache for `ttlMs`.
|
|
13
|
+
*
|
|
14
|
+
* `forceRefresh` skips both cache and in-flight dedup.
|
|
15
|
+
*
|
|
16
|
+
* @param key - Stable cache key identifying this logical REST call.
|
|
17
|
+
* @param fetcher - Thunk that performs the REST call when no cache/inflight hit.
|
|
18
|
+
* @param options - Optional overrides for TTL and forceRefresh behavior.
|
|
19
|
+
* @returns The fetched (or cached) value.
|
|
20
|
+
*/
|
|
21
|
+
function coalescePerpsRestRequest(key, fetcher, options = {}) {
|
|
22
|
+
const ttlMs = options.ttlMs ?? perpsConfig_1.PERFORMANCE_CONFIG.PerpsRestCoalesceTtlMs;
|
|
23
|
+
const forceRefresh = options.forceRefresh ?? false;
|
|
24
|
+
if (forceRefresh) {
|
|
25
|
+
cache.delete(key);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const cached = cache.get(key);
|
|
30
|
+
if (cached) {
|
|
31
|
+
if (cached.expiresAt > now) {
|
|
32
|
+
return Promise.resolve(cached.value);
|
|
33
|
+
}
|
|
34
|
+
// Evict expired entry so callers with per-call-unique keys (e.g.
|
|
35
|
+
// CandleStreamChannel historical paging with per-page endTime) do
|
|
36
|
+
// not accumulate dead blobs for the life of the process.
|
|
37
|
+
cache.delete(key);
|
|
38
|
+
}
|
|
39
|
+
const existing = inflight.get(key);
|
|
40
|
+
if (existing !== undefined) {
|
|
41
|
+
return existing;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const run = fetcher().then((value) => {
|
|
45
|
+
// Only the currently-tracked in-flight promise writes to cache. A stale
|
|
46
|
+
// in-flight (e.g. one that was racing a later forceRefresh=true caller)
|
|
47
|
+
// must not clobber the fresh value once it finally resolves.
|
|
48
|
+
if (inflight.get(key) === run) {
|
|
49
|
+
cache.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
50
|
+
inflight.delete(key);
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}, (error) => {
|
|
54
|
+
if (inflight.get(key) === run) {
|
|
55
|
+
inflight.delete(key);
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
});
|
|
59
|
+
inflight.set(key, run);
|
|
60
|
+
return run;
|
|
61
|
+
}
|
|
62
|
+
exports.coalescePerpsRestRequest = coalescePerpsRestRequest;
|
|
63
|
+
/**
|
|
64
|
+
* Test-only: wipe all cached entries and in-flight promises.
|
|
65
|
+
*/
|
|
66
|
+
function resetPerpsRestCacheForTests() {
|
|
67
|
+
cache.clear();
|
|
68
|
+
inflight.clear();
|
|
69
|
+
}
|
|
70
|
+
exports.resetPerpsRestCacheForTests = resetPerpsRestCacheForTests;
|
|
71
|
+
//# sourceMappingURL=coalescePerpsRestRequest.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coalescePerpsRestRequest.cjs","sourceRoot":"","sources":["../../src/utils/coalescePerpsRestRequest.ts"],"names":[],"mappings":";;;AAAA,8DAA8D;AAkB9D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;AACrD,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+B,CAAC;AAerD;;;;;;;;;;;;;GAaG;AACH,SAAgB,wBAAwB,CACtC,GAAW,EACX,OAA8B,EAC9B,UAA2B,EAAE;IAE7B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,gCAAkB,CAAC,sBAAsB,CAAC;IACzE,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,KAAK,CAAC;IAEnD,IAAI,YAAY,EAAE,CAAC;QACjB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACpB,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAmC,CAAC;QAChE,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;gBAC3B,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvC,CAAC;YACD,iEAAiE;YACjE,kEAAkE;YAClE,yDAAyD;YACzD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAgC,CAAC;QAClE,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC,IAAI,CACxB,CAAC,KAAK,EAAE,EAAE;QACR,wEAAwE;QACxE,wEAAwE;QACxE,6DAA6D;QAC7D,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC;YAC9B,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;YACzD,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;QACR,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC;YAC9B,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC,CACF,CAAC;IAEF,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACvB,OAAO,GAAG,CAAC;AACb,CAAC;AAjDD,4DAiDC;AAED;;GAEG;AACH,SAAgB,2BAA2B;IACzC,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC;AAHD,kEAGC","sourcesContent":["import { PERFORMANCE_CONFIG } from '../constants/perpsConfig';\n\n// Rapid perps market switching (candle bridge + activity tab burst) drives\n// duplicate REST calls against api.hyperliquid.xyz and occasionally trips 429.\n// This helper collapses concurrent identical calls into one in-flight promise\n// and serves a short TTL cache to absorb the burst. Explicit refresh bypasses\n// the cache so user-initiated refetch still re-runs.\n//\n// Lives at the service layer (MarketDataService) rather than per-hook so every\n// current and future caller — hooks, controller, aggregated provider — is\n// deduped automatically. Centralized at MarketDataService because it is\n// already a single choke point.\n\ntype CacheEntry<TValue> = {\n value: TValue;\n expiresAt: number;\n};\n\nconst inflight = new Map<string, Promise<unknown>>();\nconst cache = new Map<string, CacheEntry<unknown>>();\n\nexport type CoalesceOptions = {\n /**\n * Cache TTL in milliseconds. Defaults to\n * {@link PERFORMANCE_CONFIG.PerpsRestCoalesceTtlMs}.\n */\n ttlMs?: number;\n /**\n * Bypass the cache and any in-flight promise. The fresh result will be\n * written back to the cache under the same key.\n */\n forceRefresh?: boolean;\n};\n\n/**\n * Coalesce an idempotent perps REST call.\n *\n * - If a fresh cached value exists for `key`, it is returned immediately.\n * - If an in-flight promise exists for `key`, it is shared with the caller.\n * - Otherwise, `fetcher` runs once; the result populates the cache for `ttlMs`.\n *\n * `forceRefresh` skips both cache and in-flight dedup.\n *\n * @param key - Stable cache key identifying this logical REST call.\n * @param fetcher - Thunk that performs the REST call when no cache/inflight hit.\n * @param options - Optional overrides for TTL and forceRefresh behavior.\n * @returns The fetched (or cached) value.\n */\nexport function coalescePerpsRestRequest<TValue>(\n key: string,\n fetcher: () => Promise<TValue>,\n options: CoalesceOptions = {},\n): Promise<TValue> {\n const ttlMs = options.ttlMs ?? PERFORMANCE_CONFIG.PerpsRestCoalesceTtlMs;\n const forceRefresh = options.forceRefresh ?? false;\n\n if (forceRefresh) {\n cache.delete(key);\n } else {\n const now = Date.now();\n const cached = cache.get(key) as CacheEntry<TValue> | undefined;\n if (cached) {\n if (cached.expiresAt > now) {\n return Promise.resolve(cached.value);\n }\n // Evict expired entry so callers with per-call-unique keys (e.g.\n // CandleStreamChannel historical paging with per-page endTime) do\n // not accumulate dead blobs for the life of the process.\n cache.delete(key);\n }\n const existing = inflight.get(key) as Promise<TValue> | undefined;\n if (existing !== undefined) {\n return existing;\n }\n }\n\n const run = fetcher().then(\n (value) => {\n // Only the currently-tracked in-flight promise writes to cache. A stale\n // in-flight (e.g. one that was racing a later forceRefresh=true caller)\n // must not clobber the fresh value once it finally resolves.\n if (inflight.get(key) === run) {\n cache.set(key, { value, expiresAt: Date.now() + ttlMs });\n inflight.delete(key);\n }\n return value;\n },\n (error) => {\n if (inflight.get(key) === run) {\n inflight.delete(key);\n }\n throw error;\n },\n );\n\n inflight.set(key, run);\n return run;\n}\n\n/**\n * Test-only: wipe all cached entries and in-flight promises.\n */\nexport function resetPerpsRestCacheForTests(): void {\n cache.clear();\n inflight.clear();\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type CoalesceOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* Cache TTL in milliseconds. Defaults to
|
|
4
|
+
* {@link PERFORMANCE_CONFIG.PerpsRestCoalesceTtlMs}.
|
|
5
|
+
*/
|
|
6
|
+
ttlMs?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Bypass the cache and any in-flight promise. The fresh result will be
|
|
9
|
+
* written back to the cache under the same key.
|
|
10
|
+
*/
|
|
11
|
+
forceRefresh?: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Coalesce an idempotent perps REST call.
|
|
15
|
+
*
|
|
16
|
+
* - If a fresh cached value exists for `key`, it is returned immediately.
|
|
17
|
+
* - If an in-flight promise exists for `key`, it is shared with the caller.
|
|
18
|
+
* - Otherwise, `fetcher` runs once; the result populates the cache for `ttlMs`.
|
|
19
|
+
*
|
|
20
|
+
* `forceRefresh` skips both cache and in-flight dedup.
|
|
21
|
+
*
|
|
22
|
+
* @param key - Stable cache key identifying this logical REST call.
|
|
23
|
+
* @param fetcher - Thunk that performs the REST call when no cache/inflight hit.
|
|
24
|
+
* @param options - Optional overrides for TTL and forceRefresh behavior.
|
|
25
|
+
* @returns The fetched (or cached) value.
|
|
26
|
+
*/
|
|
27
|
+
export declare function coalescePerpsRestRequest<TValue>(key: string, fetcher: () => Promise<TValue>, options?: CoalesceOptions): Promise<TValue>;
|
|
28
|
+
/**
|
|
29
|
+
* Test-only: wipe all cached entries and in-flight promises.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resetPerpsRestCacheForTests(): void;
|
|
32
|
+
//# sourceMappingURL=coalescePerpsRestRequest.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coalescePerpsRestRequest.d.cts","sourceRoot":"","sources":["../../src/utils/coalescePerpsRestRequest.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAC7C,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAC9B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CA6CjB;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAGlD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type CoalesceOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* Cache TTL in milliseconds. Defaults to
|
|
4
|
+
* {@link PERFORMANCE_CONFIG.PerpsRestCoalesceTtlMs}.
|
|
5
|
+
*/
|
|
6
|
+
ttlMs?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Bypass the cache and any in-flight promise. The fresh result will be
|
|
9
|
+
* written back to the cache under the same key.
|
|
10
|
+
*/
|
|
11
|
+
forceRefresh?: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Coalesce an idempotent perps REST call.
|
|
15
|
+
*
|
|
16
|
+
* - If a fresh cached value exists for `key`, it is returned immediately.
|
|
17
|
+
* - If an in-flight promise exists for `key`, it is shared with the caller.
|
|
18
|
+
* - Otherwise, `fetcher` runs once; the result populates the cache for `ttlMs`.
|
|
19
|
+
*
|
|
20
|
+
* `forceRefresh` skips both cache and in-flight dedup.
|
|
21
|
+
*
|
|
22
|
+
* @param key - Stable cache key identifying this logical REST call.
|
|
23
|
+
* @param fetcher - Thunk that performs the REST call when no cache/inflight hit.
|
|
24
|
+
* @param options - Optional overrides for TTL and forceRefresh behavior.
|
|
25
|
+
* @returns The fetched (or cached) value.
|
|
26
|
+
*/
|
|
27
|
+
export declare function coalescePerpsRestRequest<TValue>(key: string, fetcher: () => Promise<TValue>, options?: CoalesceOptions): Promise<TValue>;
|
|
28
|
+
/**
|
|
29
|
+
* Test-only: wipe all cached entries and in-flight promises.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resetPerpsRestCacheForTests(): void;
|
|
32
|
+
//# sourceMappingURL=coalescePerpsRestRequest.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coalescePerpsRestRequest.d.mts","sourceRoot":"","sources":["../../src/utils/coalescePerpsRestRequest.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAC7C,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAC9B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CA6CjB;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAGlD"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PERFORMANCE_CONFIG } from "../constants/perpsConfig.mjs";
|
|
2
|
+
const inflight = new Map();
|
|
3
|
+
const cache = new Map();
|
|
4
|
+
/**
|
|
5
|
+
* Coalesce an idempotent perps REST call.
|
|
6
|
+
*
|
|
7
|
+
* - If a fresh cached value exists for `key`, it is returned immediately.
|
|
8
|
+
* - If an in-flight promise exists for `key`, it is shared with the caller.
|
|
9
|
+
* - Otherwise, `fetcher` runs once; the result populates the cache for `ttlMs`.
|
|
10
|
+
*
|
|
11
|
+
* `forceRefresh` skips both cache and in-flight dedup.
|
|
12
|
+
*
|
|
13
|
+
* @param key - Stable cache key identifying this logical REST call.
|
|
14
|
+
* @param fetcher - Thunk that performs the REST call when no cache/inflight hit.
|
|
15
|
+
* @param options - Optional overrides for TTL and forceRefresh behavior.
|
|
16
|
+
* @returns The fetched (or cached) value.
|
|
17
|
+
*/
|
|
18
|
+
export function coalescePerpsRestRequest(key, fetcher, options = {}) {
|
|
19
|
+
const ttlMs = options.ttlMs ?? PERFORMANCE_CONFIG.PerpsRestCoalesceTtlMs;
|
|
20
|
+
const forceRefresh = options.forceRefresh ?? false;
|
|
21
|
+
if (forceRefresh) {
|
|
22
|
+
cache.delete(key);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const cached = cache.get(key);
|
|
27
|
+
if (cached) {
|
|
28
|
+
if (cached.expiresAt > now) {
|
|
29
|
+
return Promise.resolve(cached.value);
|
|
30
|
+
}
|
|
31
|
+
// Evict expired entry so callers with per-call-unique keys (e.g.
|
|
32
|
+
// CandleStreamChannel historical paging with per-page endTime) do
|
|
33
|
+
// not accumulate dead blobs for the life of the process.
|
|
34
|
+
cache.delete(key);
|
|
35
|
+
}
|
|
36
|
+
const existing = inflight.get(key);
|
|
37
|
+
if (existing !== undefined) {
|
|
38
|
+
return existing;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const run = fetcher().then((value) => {
|
|
42
|
+
// Only the currently-tracked in-flight promise writes to cache. A stale
|
|
43
|
+
// in-flight (e.g. one that was racing a later forceRefresh=true caller)
|
|
44
|
+
// must not clobber the fresh value once it finally resolves.
|
|
45
|
+
if (inflight.get(key) === run) {
|
|
46
|
+
cache.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
47
|
+
inflight.delete(key);
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}, (error) => {
|
|
51
|
+
if (inflight.get(key) === run) {
|
|
52
|
+
inflight.delete(key);
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
});
|
|
56
|
+
inflight.set(key, run);
|
|
57
|
+
return run;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Test-only: wipe all cached entries and in-flight promises.
|
|
61
|
+
*/
|
|
62
|
+
export function resetPerpsRestCacheForTests() {
|
|
63
|
+
cache.clear();
|
|
64
|
+
inflight.clear();
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=coalescePerpsRestRequest.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coalescePerpsRestRequest.mjs","sourceRoot":"","sources":["../../src/utils/coalescePerpsRestRequest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,qCAAiC;AAkB9D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;AACrD,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+B,CAAC;AAerD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,wBAAwB,CACtC,GAAW,EACX,OAA8B,EAC9B,UAA2B,EAAE;IAE7B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,kBAAkB,CAAC,sBAAsB,CAAC;IACzE,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,KAAK,CAAC;IAEnD,IAAI,YAAY,EAAE,CAAC;QACjB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACpB,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAmC,CAAC;QAChE,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;gBAC3B,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvC,CAAC;YACD,iEAAiE;YACjE,kEAAkE;YAClE,yDAAyD;YACzD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAgC,CAAC;QAClE,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC,IAAI,CACxB,CAAC,KAAK,EAAE,EAAE;QACR,wEAAwE;QACxE,wEAAwE;QACxE,6DAA6D;QAC7D,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC;YAC9B,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;YACzD,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;QACR,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC;YAC9B,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC,CACF,CAAC;IAEF,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACvB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,2BAA2B;IACzC,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC","sourcesContent":["import { PERFORMANCE_CONFIG } from '../constants/perpsConfig';\n\n// Rapid perps market switching (candle bridge + activity tab burst) drives\n// duplicate REST calls against api.hyperliquid.xyz and occasionally trips 429.\n// This helper collapses concurrent identical calls into one in-flight promise\n// and serves a short TTL cache to absorb the burst. Explicit refresh bypasses\n// the cache so user-initiated refetch still re-runs.\n//\n// Lives at the service layer (MarketDataService) rather than per-hook so every\n// current and future caller — hooks, controller, aggregated provider — is\n// deduped automatically. Centralized at MarketDataService because it is\n// already a single choke point.\n\ntype CacheEntry<TValue> = {\n value: TValue;\n expiresAt: number;\n};\n\nconst inflight = new Map<string, Promise<unknown>>();\nconst cache = new Map<string, CacheEntry<unknown>>();\n\nexport type CoalesceOptions = {\n /**\n * Cache TTL in milliseconds. Defaults to\n * {@link PERFORMANCE_CONFIG.PerpsRestCoalesceTtlMs}.\n */\n ttlMs?: number;\n /**\n * Bypass the cache and any in-flight promise. The fresh result will be\n * written back to the cache under the same key.\n */\n forceRefresh?: boolean;\n};\n\n/**\n * Coalesce an idempotent perps REST call.\n *\n * - If a fresh cached value exists for `key`, it is returned immediately.\n * - If an in-flight promise exists for `key`, it is shared with the caller.\n * - Otherwise, `fetcher` runs once; the result populates the cache for `ttlMs`.\n *\n * `forceRefresh` skips both cache and in-flight dedup.\n *\n * @param key - Stable cache key identifying this logical REST call.\n * @param fetcher - Thunk that performs the REST call when no cache/inflight hit.\n * @param options - Optional overrides for TTL and forceRefresh behavior.\n * @returns The fetched (or cached) value.\n */\nexport function coalescePerpsRestRequest<TValue>(\n key: string,\n fetcher: () => Promise<TValue>,\n options: CoalesceOptions = {},\n): Promise<TValue> {\n const ttlMs = options.ttlMs ?? PERFORMANCE_CONFIG.PerpsRestCoalesceTtlMs;\n const forceRefresh = options.forceRefresh ?? false;\n\n if (forceRefresh) {\n cache.delete(key);\n } else {\n const now = Date.now();\n const cached = cache.get(key) as CacheEntry<TValue> | undefined;\n if (cached) {\n if (cached.expiresAt > now) {\n return Promise.resolve(cached.value);\n }\n // Evict expired entry so callers with per-call-unique keys (e.g.\n // CandleStreamChannel historical paging with per-page endTime) do\n // not accumulate dead blobs for the life of the process.\n cache.delete(key);\n }\n const existing = inflight.get(key) as Promise<TValue> | undefined;\n if (existing !== undefined) {\n return existing;\n }\n }\n\n const run = fetcher().then(\n (value) => {\n // Only the currently-tracked in-flight promise writes to cache. A stale\n // in-flight (e.g. one that was racing a later forceRefresh=true caller)\n // must not clobber the fresh value once it finally resolves.\n if (inflight.get(key) === run) {\n cache.set(key, { value, expiresAt: Date.now() + ttlMs });\n inflight.delete(key);\n }\n return value;\n },\n (error) => {\n if (inflight.get(key) === run) {\n inflight.delete(key);\n }\n throw error;\n },\n );\n\n inflight.set(key, run);\n return run;\n}\n\n/**\n * Test-only: wipe all cached entries and in-flight promises.\n */\nexport function resetPerpsRestCacheForTests(): void {\n cache.clear();\n inflight.clear();\n}\n"]}
|
|
@@ -183,7 +183,11 @@ function adaptMarketFromSDK(sdkMarket) {
|
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
185
|
exports.adaptMarketFromSDK = adaptMarketFromSDK;
|
|
186
|
-
|
|
186
|
+
// Perps-only account adapter. Spot balances are layered on afterwards by
|
|
187
|
+
// addSpotBalanceToAccountState, which enforces the USDC-only policy via
|
|
188
|
+
// SPOT_COLLATERAL_COINS. Keeping spot logic out of here preserves a single
|
|
189
|
+
// source of truth for spot balance math.
|
|
190
|
+
function adaptAccountStateFromSDK(perpsState) {
|
|
187
191
|
const { totalUnrealizedPnl, weightedReturnOnEquity } = perpsState.assetPositions.reduce((acc, assetPos) => {
|
|
188
192
|
const unrealizedPnl = parseFloat(assetPos.position.unrealizedPnl || '0');
|
|
189
193
|
const marginUsed = parseFloat(assetPos.position.marginUsed || '0');
|
|
@@ -200,14 +204,10 @@ function adaptAccountStateFromSDK(perpsState, spotState) {
|
|
|
200
204
|
? ((weightedReturnOnEquity / totalMarginUsed) * 100).toString()
|
|
201
205
|
: '0';
|
|
202
206
|
const perpsBalance = parseFloat(perpsState.marginSummary.accountValue);
|
|
203
|
-
let spotBalance = 0;
|
|
204
|
-
if (spotState?.balances && Array.isArray(spotState.balances)) {
|
|
205
|
-
spotBalance = spotState.balances.reduce((sum, balance) => sum + parseFloat(balance.total ?? '0'), 0);
|
|
206
|
-
}
|
|
207
|
-
const totalBalance = (spotBalance + perpsBalance).toString();
|
|
208
207
|
const accountState = {
|
|
209
208
|
availableBalance: perpsState.withdrawable || '0',
|
|
210
|
-
|
|
209
|
+
availableToTradeBalance: perpsState.withdrawable || '0',
|
|
210
|
+
totalBalance: perpsBalance.toString() || '0',
|
|
211
211
|
marginUsed: perpsState.marginSummary.totalMarginUsed || '0',
|
|
212
212
|
unrealizedPnl: totalUnrealizedPnl.toString() || '0',
|
|
213
213
|
returnOnEquity: totalReturnOnEquityPercentage || '0',
|