@metamask-previews/perps-controller 22.1.1-preview-c3d36da85 → 22.1.1-preview-d44afa95d

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.
@@ -69,7 +69,9 @@ export declare function addSpotBalanceToAccountState(accountState: AccountState,
69
69
  */
70
70
  export declare function sumCrossMarginSummaries(states: AccountState[]): CrossMarginSummary | undefined;
71
71
  /**
72
- * Aggregate multiple per-DEX AccountState objects into one by summing numeric fields.
72
+ * Aggregate multiple per-DEX AccountState objects into one by summing numeric
73
+ * fields exactly. Collateral subtotals retain their full precision so spot
74
+ * collateral can be added before the final USDC canonicalization.
73
75
  * ROE is recalculated as (totalUnrealizedPnl / totalMarginUsed) * 100.
74
76
  *
75
77
  * @param states - The array of per-DEX account states to aggregate.
@@ -1 +1 @@
1
- {"version":3,"file":"accountUtils.d.ts","sourceRoot":"","sources":["../../src/utils/accountUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGtE,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAC;AACpF,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAQ3B,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,CAAC,eAAe,GAAG,oBAAoB,CAAC,EAAE,GACnD,eAAe,GAAG,oBAAoB,GAAG,IAAI,CAM/C;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,KAAK,2BAA2B,GAAG;IACjC,IAAI,CACF,UAAU,EACN,uCAAuC,GACvC,2DAA2D,GAC9D,OAAO,CAAC;CACZ,CAAC;AAeF,wBAAgB,yCAAyC,CACvD,SAAS,EAAE,2BAA2B,GACrC,eAAe,GAAG,oBAAoB,GAAG,SAAS,CA0BpD;AAED,wBAAgB,kCAAkC,CAChD,SAAS,EAAE,2BAA2B,GACrC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIjC;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;AAMD,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;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,YAAY,EAC1B,SAAS,CAAC,EAAE,8BAA8B,GAAG,IAAI,EACjD,OAAO,CAAC,EAAE,qBAAqB,GAC9B,YAAY,CA0Dd;AAsBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,YAAY,EAAE,GACrB,kBAAkB,GAAG,SAAS,CAiBhC;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CA0D3E"}
1
+ {"version":3,"file":"accountUtils.d.ts","sourceRoot":"","sources":["../../src/utils/accountUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAKtE,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAC;AACpF,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAQ3B,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,CAAC,eAAe,GAAG,oBAAoB,CAAC,EAAE,GACnD,eAAe,GAAG,oBAAoB,GAAG,IAAI,CAM/C;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,KAAK,2BAA2B,GAAG;IACjC,IAAI,CACF,UAAU,EACN,uCAAuC,GACvC,2DAA2D,GAC9D,OAAO,CAAC;CACZ,CAAC;AAeF,wBAAgB,yCAAyC,CACvD,SAAS,EAAE,2BAA2B,GACrC,eAAe,GAAG,oBAAoB,GAAG,SAAS,CA0BpD;AAED,wBAAgB,kCAAkC,CAChD,SAAS,EAAE,2BAA2B,GACrC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIjC;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;AAuBD,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;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,YAAY,EAC1B,SAAS,CAAC,EAAE,8BAA8B,GAAG,IAAI,EACjD,OAAO,CAAC,EAAE,qBAAqB,GAC9B,YAAY,CA8Dd;AAyBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,YAAY,EAAE,GACrB,kBAAkB,GAAG,SAAS,CAiBhC;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAiE3E"}
@@ -1,3 +1,5 @@
1
+ import { BigNumber } from 'bignumber.js';
2
+ import { USDC_DECIMALS } from '../constants/hyperLiquidConfig.js';
1
3
  import { PERPS_CONSTANTS } from '../constants/perpsConfig.js';
2
4
  const EVM_ACCOUNT_TYPES = new Set(['eip155:eoa', 'eip155:erc4337']);
3
5
  function isEvmAccountType(type) {
@@ -85,29 +87,23 @@ export function calculateWeightedReturnOnEquity(accounts) {
85
87
  // The release-branch balance bridge is USDC-only. Non-USDC spot assets must
86
88
  // not inflate the balances shown or validated by withdraw/payment flows.
87
89
  const SPOT_COLLATERAL_COINS = new Set(['USDC']);
88
- export function getSpotBalance(spotState) {
90
+ function getSpotCollateralAmount(spotState, field) {
89
91
  if (!spotState?.balances || !Array.isArray(spotState.balances)) {
90
- return 0;
92
+ return new BigNumber(0);
91
93
  }
92
94
  return spotState.balances.reduce((sum, balance) => {
93
95
  if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {
94
96
  return sum;
95
97
  }
96
- const value = parseFloat(balance.total ?? '0');
97
- return Number.isFinite(value) ? sum + value : sum;
98
- }, 0);
98
+ const value = new BigNumber(balance[field] ?? '0');
99
+ return value.isFinite() ? sum.plus(value) : sum;
100
+ }, new BigNumber(0));
101
+ }
102
+ export function getSpotBalance(spotState) {
103
+ return getSpotCollateralAmount(spotState, 'total').toNumber();
99
104
  }
100
105
  export function getSpotHold(spotState) {
101
- if (!spotState?.balances || !Array.isArray(spotState.balances)) {
102
- return 0;
103
- }
104
- return spotState.balances.reduce((sum, balance) => {
105
- if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {
106
- return sum;
107
- }
108
- const value = parseFloat(balance.hold ?? '0');
109
- return Number.isFinite(value) ? sum + value : sum;
110
- }, 0);
106
+ return getSpotCollateralAmount(spotState, 'hold').toNumber();
111
107
  }
112
108
  /**
113
109
  * Add spot USDC to the AccountState contract. Caller decides whether the
@@ -124,20 +120,15 @@ export function addSpotBalanceToAccountState(accountState, spotState, options) {
124
120
  // A caller that omits `options` should NOT silently fold spot — that would
125
121
  // over-report withdrawable funds for Standard / default users.
126
122
  const foldIntoCollateral = options?.foldIntoCollateral ?? false;
127
- const spotBalance = getSpotBalance(spotState);
128
- const spotHold = getSpotHold(spotState);
129
- const freeSpot = Math.max(0, spotBalance - spotHold);
130
- const currentTotal = parseFloat(accountState.totalBalance);
131
- const currentSpendable = parseFloat(accountState.spendableBalance);
132
- const currentWithdrawable = parseFloat(accountState.withdrawableBalance);
123
+ const spotBalance = getSpotCollateralAmount(spotState, 'total');
124
+ const spotHold = getSpotCollateralAmount(spotState, 'hold');
125
+ const freeSpot = BigNumber.max(0, spotBalance.minus(spotHold));
126
+ const currentTotal = new BigNumber(accountState.totalBalance);
127
+ const currentSpendable = new BigNumber(accountState.spendableBalance);
128
+ const currentWithdrawable = new BigNumber(accountState.withdrawableBalance);
133
129
  // Preserve sentinel totals (e.g. PERPS_CONSTANTS.FallbackDataDisplay '--')
134
130
  // rather than coercing them to NaN.
135
- if (!Number.isFinite(currentTotal)) {
136
- return accountState;
137
- }
138
- if (spotBalance === 0) {
139
- // No spot wealth means no hold either in the HL payload shape, so the
140
- // later totalBalance adjustment would also be a no-op.
131
+ if (!currentTotal.isFinite()) {
141
132
  return accountState;
142
133
  }
143
134
  // Folding is gated strictly on the resolved abstraction mode (see callers'
@@ -153,27 +144,33 @@ export function addSpotBalanceToAccountState(accountState, spotState, options) {
153
144
  // double-counting on Unified/PM accounts where marginSummary.accountValue
154
145
  // already includes the margin that HL surfaces via spot.hold. Standard
155
146
  // mode has spotHold = 0 by construction, so the subtraction is a no-op.
156
- const nextTotal = currentTotal + spotBalance - spotHold;
147
+ const nextTotal = canonicalizeUsdc(currentTotal.plus(spotBalance).minus(spotHold));
148
+ if (nextTotal === accountState.totalBalance &&
149
+ nextSpendable === accountState.spendableBalance &&
150
+ nextWithdrawable === accountState.withdrawableBalance) {
151
+ return accountState;
152
+ }
157
153
  return {
158
154
  ...accountState,
159
- totalBalance: nextTotal.toString(),
155
+ totalBalance: nextTotal,
160
156
  spendableBalance: nextSpendable,
161
157
  withdrawableBalance: nextWithdrawable,
162
158
  };
163
159
  }
164
160
  function resolveFoldedBalance(currentNumeric, currentRaw, freeSpot, foldIntoCollateral) {
165
- if (!foldIntoCollateral) {
161
+ if (!currentNumeric.isFinite()) {
162
+ // Non-finite currentNumeric means the adapter passed a sentinel like
163
+ // `PERPS_CONSTANTS.FallbackDataDisplay` ("--") during loading. Preserve
164
+ // the sentinel rather than synthesising a numeric fold; the caller's
165
+ // UI treats "--" as "loading" and would otherwise show a misleading
166
+ // spot-only figure while the per-DEX perps fetch is still in flight.
166
167
  return currentRaw;
167
168
  }
168
- if (Number.isFinite(currentNumeric)) {
169
- return (currentNumeric + freeSpot).toString();
170
- }
171
- // Non-finite currentNumeric means the adapter passed a sentinel like
172
- // `PERPS_CONSTANTS.FallbackDataDisplay` ("--") during loading. Preserve
173
- // the sentinel rather than synthesising a numeric fold; the caller's
174
- // UI treats "--" as "loading" and would otherwise show a misleading
175
- // spot-only figure while the per-DEX perps fetch is still in flight.
176
- return currentRaw;
169
+ const spotAmount = foldIntoCollateral ? freeSpot : new BigNumber(0);
170
+ return canonicalizeUsdc(currentNumeric.plus(spotAmount));
171
+ }
172
+ function canonicalizeUsdc(value) {
173
+ return value.decimalPlaces(USDC_DECIMALS, BigNumber.ROUND_DOWN).toFixed();
177
174
  }
178
175
  /**
179
176
  * Sum per-DEX cross-margin summaries. Undefined unless every state carries
@@ -190,8 +187,8 @@ export function sumCrossMarginSummaries(states) {
190
187
  return undefined;
191
188
  }
192
189
  const sum = (field) => summaries
193
- .reduce((total, summary) => total + parseFloat(summary[field]), 0)
194
- .toString();
190
+ .reduce((total, summary) => total.plus(summary[field]), new BigNumber(0))
191
+ .toFixed();
195
192
  return {
196
193
  accountValue: sum('accountValue'),
197
194
  maintenanceMargin: sum('maintenanceMargin'),
@@ -200,7 +197,9 @@ export function sumCrossMarginSummaries(states) {
200
197
  };
201
198
  }
202
199
  /**
203
- * Aggregate multiple per-DEX AccountState objects into one by summing numeric fields.
200
+ * Aggregate multiple per-DEX AccountState objects into one by summing numeric
201
+ * fields exactly. Collateral subtotals retain their full precision so spot
202
+ * collateral can be added before the final USDC canonicalization.
204
203
  * ROE is recalculated as (totalUnrealizedPnl / totalMarginUsed) * 100.
205
204
  *
206
205
  * @param states - The array of per-DEX account states to aggregate.
@@ -223,15 +222,17 @@ export function aggregateAccountStates(states) {
223
222
  return { ...state };
224
223
  }
225
224
  return {
226
- spendableBalance: (parseFloat(acc.spendableBalance) + parseFloat(state.spendableBalance)).toString(),
227
- withdrawableBalance: (parseFloat(acc.withdrawableBalance) +
228
- parseFloat(state.withdrawableBalance)).toString(),
229
- totalBalance: (parseFloat(acc.totalBalance) + parseFloat(state.totalBalance)).toString(),
225
+ spendableBalance: addDecimalValues(acc.spendableBalance, state.spendableBalance),
226
+ withdrawableBalance: addDecimalValues(acc.withdrawableBalance, state.withdrawableBalance),
227
+ totalBalance: addDecimalValues(acc.totalBalance, state.totalBalance),
230
228
  marginUsed: (parseFloat(acc.marginUsed) + parseFloat(state.marginUsed)).toString(),
231
229
  unrealizedPnl: (parseFloat(acc.unrealizedPnl) + parseFloat(state.unrealizedPnl)).toString(),
232
230
  returnOnEquity: '0',
233
231
  };
234
232
  }, fallback);
233
+ aggregated.spendableBalance = normalizeDecimalString(aggregated.spendableBalance);
234
+ aggregated.withdrawableBalance = normalizeDecimalString(aggregated.withdrawableBalance);
235
+ aggregated.totalBalance = normalizeDecimalString(aggregated.totalBalance);
235
236
  // Recalculate ROE across all DEXs
236
237
  const totalMarginUsed = parseFloat(aggregated.marginUsed);
237
238
  const totalUnrealizedPnl = parseFloat(aggregated.unrealizedPnl);
@@ -248,4 +249,11 @@ export function aggregateAccountStates(states) {
248
249
  }
249
250
  return aggregated;
250
251
  }
252
+ function addDecimalValues(left, right) {
253
+ return new BigNumber(left).plus(right).toFixed();
254
+ }
255
+ function normalizeDecimalString(value) {
256
+ const amount = new BigNumber(value);
257
+ return amount.isFinite() ? amount.toFixed() : value;
258
+ }
251
259
  //# sourceMappingURL=accountUtils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"accountUtils.js","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.js';\nimport type { SpotClearinghouseStateResponse } from '../types/hyperliquid-types.js';\nimport type {\n AccountState,\n CrossMarginSummary,\n PerpsInternalAccount,\n} from '../types/index.js';\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): InternalAccount | PerpsInternalAccount | 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\ntype SelectedEvmAccountMessenger = {\n call(\n actionType:\n | 'AccountsController:getSelectedAccount'\n | 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n ): unknown;\n};\n\nfunction isAccountLike(\n value: unknown,\n): value is InternalAccount | PerpsInternalAccount {\n const account = value as { address?: unknown; type?: unknown } | null;\n\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof account?.address === 'string' &&\n typeof account.type === 'string'\n );\n}\n\nexport function getSelectedEvmAccountDetailsFromMessenger(\n messenger: SelectedEvmAccountMessenger,\n): InternalAccount | PerpsInternalAccount | undefined {\n try {\n const selectedAccount = messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n if (isAccountLike(selectedAccount)) {\n const evmAccount = findEvmAccount([selectedAccount]);\n if (evmAccount) {\n return evmAccount;\n }\n }\n } catch {\n // Fall back to the selected account group if the direct lookup is unavailable.\n }\n\n try {\n const selectedAccountGroup = messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n return Array.isArray(selectedAccountGroup)\n ? (findEvmAccount(selectedAccountGroup.filter(isAccountLike)) ??\n undefined)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function getSelectedEvmAccountFromMessenger(\n messenger: SelectedEvmAccountMessenger,\n): { address: string } | undefined {\n const evmAccount = getSelectedEvmAccountDetailsFromMessenger(messenger);\n\n return evmAccount ? { address: evmAccount.address } : undefined;\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// The release-branch balance bridge is USDC-only. Non-USDC spot assets must\n// not inflate the balances shown or validated by withdraw/payment flows.\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\n/**\n * Options controlling how `addSpotBalanceToAccountState` folds spot balance\n * into the three-field AccountState contract.\n */\nexport type AddSpotBalanceOptions = {\n /**\n * When `true`, free spot USDC contributes to both `spendableBalance` and\n * `withdrawableBalance` in addition to `totalBalance` — appropriate for\n * venues where spot is automatically used as perps collateral (e.g.\n * HyperLiquid Unified/Portfolio mode, where `withdraw3` draws from the\n * unified ledger).\n *\n * When `false`, free spot contributes to `totalBalance` only; spendable\n * and withdrawable stay perps-only — appropriate for venues where spot\n * is a separate ledger the backend cannot auto-draw from (e.g. HL\n * Standard mode). The caller is responsible for translating\n * provider-specific state into this flag.\n *\n * Defaults to `true` for backward compatibility with call sites that\n * haven't yet been wired with provider-specific context.\n */\n foldIntoCollateral?: boolean;\n};\n\n/**\n * Add spot USDC to the AccountState contract. Caller decides whether the\n * spot balance counts as perps collateral via `options.foldIntoCollateral`\n * — the util stays provider-agnostic.\n *\n * @param accountState - Base AccountState produced by a provider adapter.\n * @param spotState - Raw spot clearinghouse response (HL-shaped); null or missing means no spot balance and the state is returned unchanged.\n * @param options - See {@link AddSpotBalanceOptions}.\n * @returns AccountState with spot folded into `totalBalance` always, and into spendable/withdrawable when `foldIntoCollateral` is true.\n */\nexport function addSpotBalanceToAccountState(\n accountState: AccountState,\n spotState?: SpotClearinghouseStateResponse | null,\n options?: AddSpotBalanceOptions,\n): AccountState {\n // Fail-closed default: align with `hyperLiquidModeFoldsSpot(null) → false`.\n // A caller that omits `options` should NOT silently fold spot — that would\n // over-report withdrawable funds for Standard / default users.\n const foldIntoCollateral = options?.foldIntoCollateral ?? false;\n\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 currentSpendable = parseFloat(accountState.spendableBalance);\n const currentWithdrawable = parseFloat(accountState.withdrawableBalance);\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 // No spot wealth means no hold either in the HL payload shape, so the\n // later totalBalance adjustment would also be a no-op.\n return accountState;\n }\n\n // Folding is gated strictly on the resolved abstraction mode (see callers'\n // `foldIntoCollateral` argument). Standard / DEX-abstraction users keep\n // perps and spot independent, so spot must NOT surface as a perps-\n // withdrawable balance for them — withdraw3 only draws from the perps\n // ledger in those modes. Unified / portfolio-margin users get the fold;\n // live callers fail-CLOSED via `hyperLiquidModeFoldsSpot` when mode is\n // unresolved.\n const nextSpendable = resolveFoldedBalance(\n currentSpendable,\n accountState.spendableBalance,\n freeSpot,\n foldIntoCollateral,\n );\n const nextWithdrawable = resolveFoldedBalance(\n currentWithdrawable,\n accountState.withdrawableBalance,\n freeSpot,\n foldIntoCollateral,\n );\n\n // Total always reflects combined wealth: subtract spotHold to avoid\n // double-counting on Unified/PM accounts where marginSummary.accountValue\n // already includes the margin that HL surfaces via spot.hold. Standard\n // mode has spotHold = 0 by construction, so the subtraction is a no-op.\n const nextTotal = currentTotal + spotBalance - spotHold;\n\n return {\n ...accountState,\n totalBalance: nextTotal.toString(),\n spendableBalance: nextSpendable,\n withdrawableBalance: nextWithdrawable,\n };\n}\n\nfunction resolveFoldedBalance(\n currentNumeric: number,\n currentRaw: string,\n freeSpot: number,\n foldIntoCollateral: boolean,\n): string {\n if (!foldIntoCollateral) {\n return currentRaw;\n }\n if (Number.isFinite(currentNumeric)) {\n return (currentNumeric + freeSpot).toString();\n }\n // Non-finite currentNumeric means the adapter passed a sentinel like\n // `PERPS_CONSTANTS.FallbackDataDisplay` (\"--\") during loading. Preserve\n // the sentinel rather than synthesising a numeric fold; the caller's\n // UI treats \"--\" as \"loading\" and would otherwise show a misleading\n // spot-only figure while the per-DEX perps fetch is still in flight.\n return currentRaw;\n}\n\n/**\n * Sum per-DEX cross-margin summaries. Undefined unless every state carries\n * one, so a partial sum never reads as the whole account.\n *\n * @param states - The per-DEX account states.\n * @returns The summed summary, or undefined.\n */\nexport function sumCrossMarginSummaries(\n states: AccountState[],\n): CrossMarginSummary | undefined {\n const summaries = states\n .map((state) => state.crossMarginSummary)\n .filter((summary): summary is CrossMarginSummary => summary !== undefined);\n if (summaries.length === 0 || summaries.length !== states.length) {\n return undefined;\n }\n const sum = (field: keyof CrossMarginSummary): string =>\n summaries\n .reduce((total, summary) => total + parseFloat(summary[field]), 0)\n .toString();\n return {\n accountValue: sum('accountValue'),\n maintenanceMargin: sum('maintenanceMargin'),\n marginUsed: sum('marginUsed'),\n notional: sum('notional'),\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 spendableBalance: PERPS_CONSTANTS.FallbackDataDisplay,\n withdrawableBalance: 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\n return {\n spendableBalance: (\n parseFloat(acc.spendableBalance) + parseFloat(state.spendableBalance)\n ).toString(),\n withdrawableBalance: (\n parseFloat(acc.withdrawableBalance) +\n parseFloat(state.withdrawableBalance)\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 const crossMarginSummary = sumCrossMarginSummaries(states);\n if (crossMarginSummary) {\n aggregated.crossMarginSummary = crossMarginSummary;\n }\n\n return aggregated;\n}\n"]}
1
+ {"version":3,"file":"accountUtils.js","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';\nimport { BigNumber } from 'bignumber.js';\n\nimport { USDC_DECIMALS } from '../constants/hyperLiquidConfig.js';\nimport { PERPS_CONSTANTS } from '../constants/perpsConfig.js';\nimport type { SpotClearinghouseStateResponse } from '../types/hyperliquid-types.js';\nimport type {\n AccountState,\n CrossMarginSummary,\n PerpsInternalAccount,\n} from '../types/index.js';\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): InternalAccount | PerpsInternalAccount | 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\ntype SelectedEvmAccountMessenger = {\n call(\n actionType:\n | 'AccountsController:getSelectedAccount'\n | 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n ): unknown;\n};\n\nfunction isAccountLike(\n value: unknown,\n): value is InternalAccount | PerpsInternalAccount {\n const account = value as { address?: unknown; type?: unknown } | null;\n\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof account?.address === 'string' &&\n typeof account.type === 'string'\n );\n}\n\nexport function getSelectedEvmAccountDetailsFromMessenger(\n messenger: SelectedEvmAccountMessenger,\n): InternalAccount | PerpsInternalAccount | undefined {\n try {\n const selectedAccount = messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n if (isAccountLike(selectedAccount)) {\n const evmAccount = findEvmAccount([selectedAccount]);\n if (evmAccount) {\n return evmAccount;\n }\n }\n } catch {\n // Fall back to the selected account group if the direct lookup is unavailable.\n }\n\n try {\n const selectedAccountGroup = messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n return Array.isArray(selectedAccountGroup)\n ? (findEvmAccount(selectedAccountGroup.filter(isAccountLike)) ??\n undefined)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function getSelectedEvmAccountFromMessenger(\n messenger: SelectedEvmAccountMessenger,\n): { address: string } | undefined {\n const evmAccount = getSelectedEvmAccountDetailsFromMessenger(messenger);\n\n return evmAccount ? { address: evmAccount.address } : undefined;\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// The release-branch balance bridge is USDC-only. Non-USDC spot assets must\n// not inflate the balances shown or validated by withdraw/payment flows.\nconst SPOT_COLLATERAL_COINS = new Set<string>(['USDC']);\n\nfunction getSpotCollateralAmount(\n spotState: SpotClearinghouseStateResponse | null | undefined,\n field: 'hold' | 'total',\n): BigNumber {\n if (!spotState?.balances || !Array.isArray(spotState.balances)) {\n return new BigNumber(0);\n }\n\n return spotState.balances.reduce((sum, balance) => {\n if (!balance.coin || !SPOT_COLLATERAL_COINS.has(balance.coin)) {\n return sum;\n }\n const value = new BigNumber(balance[field] ?? '0');\n return value.isFinite() ? sum.plus(value) : sum;\n }, new BigNumber(0));\n}\n\nexport function getSpotBalance(\n spotState?: SpotClearinghouseStateResponse | null,\n): number {\n return getSpotCollateralAmount(spotState, 'total').toNumber();\n}\n\nexport function getSpotHold(\n spotState?: SpotClearinghouseStateResponse | null,\n): number {\n return getSpotCollateralAmount(spotState, 'hold').toNumber();\n}\n\n/**\n * Options controlling how `addSpotBalanceToAccountState` folds spot balance\n * into the three-field AccountState contract.\n */\nexport type AddSpotBalanceOptions = {\n /**\n * When `true`, free spot USDC contributes to both `spendableBalance` and\n * `withdrawableBalance` in addition to `totalBalance` — appropriate for\n * venues where spot is automatically used as perps collateral (e.g.\n * HyperLiquid Unified/Portfolio mode, where `withdraw3` draws from the\n * unified ledger).\n *\n * When `false`, free spot contributes to `totalBalance` only; spendable\n * and withdrawable stay perps-only — appropriate for venues where spot\n * is a separate ledger the backend cannot auto-draw from (e.g. HL\n * Standard mode). The caller is responsible for translating\n * provider-specific state into this flag.\n *\n * Defaults to `true` for backward compatibility with call sites that\n * haven't yet been wired with provider-specific context.\n */\n foldIntoCollateral?: boolean;\n};\n\n/**\n * Add spot USDC to the AccountState contract. Caller decides whether the\n * spot balance counts as perps collateral via `options.foldIntoCollateral`\n * — the util stays provider-agnostic.\n *\n * @param accountState - Base AccountState produced by a provider adapter.\n * @param spotState - Raw spot clearinghouse response (HL-shaped); null or missing means no spot balance and the state is returned unchanged.\n * @param options - See {@link AddSpotBalanceOptions}.\n * @returns AccountState with spot folded into `totalBalance` always, and into spendable/withdrawable when `foldIntoCollateral` is true.\n */\nexport function addSpotBalanceToAccountState(\n accountState: AccountState,\n spotState?: SpotClearinghouseStateResponse | null,\n options?: AddSpotBalanceOptions,\n): AccountState {\n // Fail-closed default: align with `hyperLiquidModeFoldsSpot(null) → false`.\n // A caller that omits `options` should NOT silently fold spot — that would\n // over-report withdrawable funds for Standard / default users.\n const foldIntoCollateral = options?.foldIntoCollateral ?? false;\n\n const spotBalance = getSpotCollateralAmount(spotState, 'total');\n const spotHold = getSpotCollateralAmount(spotState, 'hold');\n const freeSpot = BigNumber.max(0, spotBalance.minus(spotHold));\n\n const currentTotal = new BigNumber(accountState.totalBalance);\n const currentSpendable = new BigNumber(accountState.spendableBalance);\n const currentWithdrawable = new BigNumber(accountState.withdrawableBalance);\n\n // Preserve sentinel totals (e.g. PERPS_CONSTANTS.FallbackDataDisplay '--')\n // rather than coercing them to NaN.\n if (!currentTotal.isFinite()) {\n return accountState;\n }\n\n // Folding is gated strictly on the resolved abstraction mode (see callers'\n // `foldIntoCollateral` argument). Standard / DEX-abstraction users keep\n // perps and spot independent, so spot must NOT surface as a perps-\n // withdrawable balance for them — withdraw3 only draws from the perps\n // ledger in those modes. Unified / portfolio-margin users get the fold;\n // live callers fail-CLOSED via `hyperLiquidModeFoldsSpot` when mode is\n // unresolved.\n const nextSpendable = resolveFoldedBalance(\n currentSpendable,\n accountState.spendableBalance,\n freeSpot,\n foldIntoCollateral,\n );\n const nextWithdrawable = resolveFoldedBalance(\n currentWithdrawable,\n accountState.withdrawableBalance,\n freeSpot,\n foldIntoCollateral,\n );\n\n // Total always reflects combined wealth: subtract spotHold to avoid\n // double-counting on Unified/PM accounts where marginSummary.accountValue\n // already includes the margin that HL surfaces via spot.hold. Standard\n // mode has spotHold = 0 by construction, so the subtraction is a no-op.\n const nextTotal = canonicalizeUsdc(\n currentTotal.plus(spotBalance).minus(spotHold),\n );\n\n if (\n nextTotal === accountState.totalBalance &&\n nextSpendable === accountState.spendableBalance &&\n nextWithdrawable === accountState.withdrawableBalance\n ) {\n return accountState;\n }\n\n return {\n ...accountState,\n totalBalance: nextTotal,\n spendableBalance: nextSpendable,\n withdrawableBalance: nextWithdrawable,\n };\n}\n\nfunction resolveFoldedBalance(\n currentNumeric: BigNumber,\n currentRaw: string,\n freeSpot: BigNumber,\n foldIntoCollateral: boolean,\n): string {\n if (!currentNumeric.isFinite()) {\n // Non-finite currentNumeric means the adapter passed a sentinel like\n // `PERPS_CONSTANTS.FallbackDataDisplay` (\"--\") during loading. Preserve\n // the sentinel rather than synthesising a numeric fold; the caller's\n // UI treats \"--\" as \"loading\" and would otherwise show a misleading\n // spot-only figure while the per-DEX perps fetch is still in flight.\n return currentRaw;\n }\n\n const spotAmount = foldIntoCollateral ? freeSpot : new BigNumber(0);\n return canonicalizeUsdc(currentNumeric.plus(spotAmount));\n}\n\nfunction canonicalizeUsdc(value: BigNumber): string {\n return value.decimalPlaces(USDC_DECIMALS, BigNumber.ROUND_DOWN).toFixed();\n}\n\n/**\n * Sum per-DEX cross-margin summaries. Undefined unless every state carries\n * one, so a partial sum never reads as the whole account.\n *\n * @param states - The per-DEX account states.\n * @returns The summed summary, or undefined.\n */\nexport function sumCrossMarginSummaries(\n states: AccountState[],\n): CrossMarginSummary | undefined {\n const summaries = states\n .map((state) => state.crossMarginSummary)\n .filter((summary): summary is CrossMarginSummary => summary !== undefined);\n if (summaries.length === 0 || summaries.length !== states.length) {\n return undefined;\n }\n const sum = (field: keyof CrossMarginSummary): string =>\n summaries\n .reduce((total, summary) => total.plus(summary[field]), new BigNumber(0))\n .toFixed();\n return {\n accountValue: sum('accountValue'),\n maintenanceMargin: sum('maintenanceMargin'),\n marginUsed: sum('marginUsed'),\n notional: sum('notional'),\n };\n}\n\n/**\n * Aggregate multiple per-DEX AccountState objects into one by summing numeric\n * fields exactly. Collateral subtotals retain their full precision so spot\n * collateral can be added before the final USDC canonicalization.\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 spendableBalance: PERPS_CONSTANTS.FallbackDataDisplay,\n withdrawableBalance: 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\n return {\n spendableBalance: addDecimalValues(\n acc.spendableBalance,\n state.spendableBalance,\n ),\n withdrawableBalance: addDecimalValues(\n acc.withdrawableBalance,\n state.withdrawableBalance,\n ),\n totalBalance: addDecimalValues(acc.totalBalance, state.totalBalance),\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 aggregated.spendableBalance = normalizeDecimalString(\n aggregated.spendableBalance,\n );\n aggregated.withdrawableBalance = normalizeDecimalString(\n aggregated.withdrawableBalance,\n );\n aggregated.totalBalance = normalizeDecimalString(aggregated.totalBalance);\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 const crossMarginSummary = sumCrossMarginSummaries(states);\n if (crossMarginSummary) {\n aggregated.crossMarginSummary = crossMarginSummary;\n }\n\n return aggregated;\n}\n\nfunction addDecimalValues(left: string, right: string): string {\n return new BigNumber(left).plus(right).toFixed();\n}\n\nfunction normalizeDecimalString(value: string): string {\n const amount = new BigNumber(value);\n return amount.isFinite() ? amount.toFixed() : value;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/perps-controller",
3
- "version": "22.1.1-preview-c3d36da85",
3
+ "version": "22.1.1-preview-d44afa95d",
4
4
  "description": "Controller for perpetual trading functionality in MetaMask",
5
5
  "keywords": [
6
6
  "Ethereum",