@velocity-exchange/sdk 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/lib/browser/adminClient.d.ts +4 -3
  2. package/lib/browser/adminClient.js +5 -13
  3. package/lib/browser/equityFloorManager.d.ts +9 -5
  4. package/lib/browser/equityFloorManager.js +9 -5
  5. package/lib/browser/idl/velocity.d.ts +119 -3
  6. package/lib/browser/idl/velocity.json +119 -3
  7. package/lib/browser/index.d.ts +2 -0
  8. package/lib/browser/index.js +3 -0
  9. package/lib/browser/jupiter/jupiterClient.d.ts +31 -27
  10. package/lib/browser/jupiter/jupiterClient.js +61 -35
  11. package/lib/browser/math/amm.d.ts +6 -5
  12. package/lib/browser/math/amm.js +39 -19
  13. package/lib/browser/math/margin.d.ts +12 -10
  14. package/lib/browser/math/margin.js +16 -14
  15. package/lib/browser/math/superStake.d.ts +9 -9
  16. package/lib/browser/math/superStake.js +8 -8
  17. package/lib/browser/swap/UnifiedSwapClient.d.ts +38 -76
  18. package/lib/browser/swap/UnifiedSwapClient.js +46 -117
  19. package/lib/browser/swap/routeInstructions.d.ts +19 -0
  20. package/lib/browser/swap/routeInstructions.js +43 -0
  21. package/lib/browser/swap/types.d.ts +196 -0
  22. package/lib/browser/swap/types.js +88 -0
  23. package/lib/browser/titan/titanClient.d.ts +32 -68
  24. package/lib/browser/titan/titanClient.js +128 -102
  25. package/lib/browser/types.d.ts +2 -0
  26. package/lib/browser/user.d.ts +16 -18
  27. package/lib/browser/user.js +24 -26
  28. package/lib/browser/velocityClient.d.ts +122 -101
  29. package/lib/browser/velocityClient.js +221 -289
  30. package/lib/node/adminClient.d.ts +4 -3
  31. package/lib/node/adminClient.d.ts.map +1 -1
  32. package/lib/node/adminClient.js +5 -13
  33. package/lib/node/equityFloorManager.d.ts +9 -5
  34. package/lib/node/equityFloorManager.d.ts.map +1 -1
  35. package/lib/node/equityFloorManager.js +9 -5
  36. package/lib/node/idl/velocity.d.ts +119 -3
  37. package/lib/node/idl/velocity.d.ts.map +1 -1
  38. package/lib/node/idl/velocity.json +119 -3
  39. package/lib/node/index.d.ts +2 -0
  40. package/lib/node/index.d.ts.map +1 -1
  41. package/lib/node/index.js +3 -0
  42. package/lib/node/jupiter/jupiterClient.d.ts +31 -27
  43. package/lib/node/jupiter/jupiterClient.d.ts.map +1 -1
  44. package/lib/node/jupiter/jupiterClient.js +61 -35
  45. package/lib/node/math/amm.d.ts +6 -5
  46. package/lib/node/math/amm.d.ts.map +1 -1
  47. package/lib/node/math/amm.js +39 -19
  48. package/lib/node/math/margin.d.ts +12 -10
  49. package/lib/node/math/margin.d.ts.map +1 -1
  50. package/lib/node/math/margin.js +16 -14
  51. package/lib/node/math/superStake.d.ts +9 -9
  52. package/lib/node/math/superStake.d.ts.map +1 -1
  53. package/lib/node/math/superStake.js +8 -8
  54. package/lib/node/swap/UnifiedSwapClient.d.ts +38 -76
  55. package/lib/node/swap/UnifiedSwapClient.d.ts.map +1 -1
  56. package/lib/node/swap/UnifiedSwapClient.js +46 -117
  57. package/lib/node/swap/routeInstructions.d.ts +20 -0
  58. package/lib/node/swap/routeInstructions.d.ts.map +1 -0
  59. package/lib/node/swap/routeInstructions.js +43 -0
  60. package/lib/node/swap/types.d.ts +197 -0
  61. package/lib/node/swap/types.d.ts.map +1 -0
  62. package/lib/node/swap/types.js +88 -0
  63. package/lib/node/titan/titanClient.d.ts +32 -68
  64. package/lib/node/titan/titanClient.d.ts.map +1 -1
  65. package/lib/node/titan/titanClient.js +128 -102
  66. package/lib/node/types.d.ts +2 -0
  67. package/lib/node/types.d.ts.map +1 -1
  68. package/lib/node/user.d.ts +16 -18
  69. package/lib/node/user.d.ts.map +1 -1
  70. package/lib/node/user.js +24 -26
  71. package/lib/node/velocityClient.d.ts +122 -101
  72. package/lib/node/velocityClient.d.ts.map +1 -1
  73. package/lib/node/velocityClient.js +221 -289
  74. package/package.json +1 -1
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TitanClient = exports.SwapMode = void 0;
4
4
  const web3_js_1 = require("@solana/web3.js");
5
5
  const msgpack_1 = require("@msgpack/msgpack");
6
+ const routeInstructions_1 = require("../swap/routeInstructions");
7
+ const types_1 = require("../swap/types");
6
8
  var SwapMode;
7
9
  (function (SwapMode) {
8
10
  SwapMode["ExactIn"] = "ExactIn";
@@ -13,8 +15,18 @@ const TITAN_API_URL = 'https://api.titan.exchange';
13
15
  const LOOKUP_TABLE_FETCH_RETRIES = 2;
14
16
  const LOOKUP_TABLE_RETRY_BASE_DELAY_MS = 150;
15
17
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
18
+ /** Titan sends pubkeys as raw bytes. Absent fields decode to `undefined`. */
19
+ const decodePubkey = (bytes) => bytes ? new web3_js_1.PublicKey(bytes).toString() : undefined;
20
+ /**
21
+ * For the normalized quote's small metadata fields, which are typed `number`.
22
+ * Only safe because slots, durations and bps are far below 2^53 — never use it
23
+ * on a token amount.
24
+ */
25
+ const toNumber = (value) => value === undefined ? undefined : Number(value);
16
26
  class TitanClient {
17
27
  constructor({ connection, authToken, url, proxyUrl, }) {
28
+ this.providerName = 'titan';
29
+ this.lookupTableCache = new Map();
18
30
  this.connection = connection;
19
31
  this.authToken = authToken;
20
32
  this.url = url !== null && url !== void 0 ? url : TITAN_API_URL;
@@ -30,25 +42,41 @@ class TitanClient {
30
42
  outputMint: outputMint.toString(),
31
43
  amount: amount.toString(),
32
44
  userPublicKey: userPublicKey.toString(),
33
- ...(slippageBps && { slippageBps: slippageBps.toString() }),
34
- ...(swapMode && { swapMode: normalizedSwapMode.toString() }),
35
- ...(maxAccounts && { accountsLimitTotal: maxAccounts.toString() }),
36
- ...(excludeDexes && { excludeDexes: excludeDexes.join(',') }),
37
- ...(onlyDirectRoutes && {
45
+ ...(slippageBps != null && { slippageBps: slippageBps.toString() }),
46
+ ...(swapMode != null && { swapMode: normalizedSwapMode.toString() }),
47
+ ...(maxAccounts != null && {
48
+ accountsLimitTotal: maxAccounts.toString(),
49
+ }),
50
+ ...(excludeDexes != null && { excludeDexes: excludeDexes.join(',') }),
51
+ // Only sent when explicitly true — Titan treats the field's presence,
52
+ // not its value, as the toggle.
53
+ ...(onlyDirectRoutes === true && {
38
54
  onlyDirectRoutes: onlyDirectRoutes.toString(),
39
55
  }),
40
- ...(sizeConstraint && { sizeConstraint: sizeConstraint.toString() }),
41
- ...(accountsLimitWritable && {
56
+ ...(sizeConstraint != null && {
57
+ sizeConstraint: sizeConstraint.toString(),
58
+ }),
59
+ ...(accountsLimitWritable != null && {
42
60
  accountsLimitWritable: accountsLimitWritable.toString(),
43
61
  }),
44
62
  });
45
63
  }
46
64
  /**
47
- * Get routes for a swap
65
+ * Get the best available route for a swap.
66
+ *
67
+ * The route is returned on the quote's `providerRoute`, so
68
+ * {@link getRouteInstructions} builds exactly what was quoted here, at the
69
+ * slippage quoted here.
70
+ * @throws If `userPublicKey` is missing — Titan bakes the user's token
71
+ * accounts into the route, so a route quoted for one wallet cannot be
72
+ * executed by another. The wallet is recorded on the route and enforced
73
+ * when the route is built.
48
74
  */
49
- async getQuote({ inputMint, outputMint, amount, userPublicKey, maxAccounts = 50, // 50 is an estimated amount with buffer
50
- slippageBps, swapMode, onlyDirectRoutes, excludeDexes, sizeConstraint, accountsLimitWritable, }) {
51
- var _a;
75
+ async getQuote({ inputMint, outputMint, amount, userPublicKey, maxAccounts = types_1.DEFAULT_SWAP_MAX_ACCOUNTS, slippageBps, swapMode, onlyDirectRoutes, excludeDexes, sizeConstraint, accountsLimitWritable, }) {
76
+ var _a, _b, _c, _d, _e;
77
+ if (!userPublicKey) {
78
+ throw new Error('Titan quotes require a userPublicKey.');
79
+ }
52
80
  const params = this.buildParams({
53
81
  inputMint,
54
82
  outputMint,
@@ -87,29 +115,43 @@ class TitanClient {
87
115
  throw new Error(`Titan API error: ${response.status} ${response.statusText}`);
88
116
  }
89
117
  const buffer = await response.arrayBuffer();
90
- const data = (0, msgpack_1.decode)(buffer);
91
- // Cache the quote data and parameters for later use in getSwap
92
- this.lastQuoteData = data;
93
- this.lastQuoteParams = params.toString();
118
+ // `useBigInt64` or every u64 above 2^53 is silently rounded on the way in,
119
+ // including the `inAmount` callers size `beginSwap` off.
120
+ const data = (0, msgpack_1.decode)(buffer, { useBigInt64: true });
94
121
  // We are only querying for the best avaiable route so use that
95
122
  const route = data.quotes[Object.keys(data.quotes)[0]];
96
123
  if (!route) {
97
124
  throw new Error('No routes available');
98
125
  }
99
- return {
100
- inputMint: inputMint.toString(),
101
- inAmount: amount.toString(),
102
- outputMint: outputMint.toString(),
126
+ if (!((_a = route.instructions) === null || _a === void 0 ? void 0 : _a.length)) {
127
+ throw new Error('Titan route has no instructions');
128
+ }
129
+ // Titan echoes the pair it routed. Take the pair from the response rather
130
+ // than assuming the request was honoured — a route for another pair pays
131
+ // out into a token account `endSwap` isn't watching, and only fails
132
+ // on-chain once the funds have already moved.
133
+ const routedInputMint = (_b = decodePubkey(data.inputMint)) !== null && _b !== void 0 ? _b : inputMint.toString();
134
+ const routedOutputMint = (_c = decodePubkey(data.outputMint)) !== null && _c !== void 0 ? _c : outputMint.toString();
135
+ if (routedInputMint !== inputMint.toString() ||
136
+ routedOutputMint !== outputMint.toString()) {
137
+ throw new Error(`Titan quoted ${routedInputMint} -> ${routedOutputMint} but the swap asked for ${inputMint.toString()} -> ${outputMint.toString()}.`);
138
+ }
139
+ return (0, types_1.buildSwapQuote)({
140
+ inputMint: routedInputMint,
141
+ outputMint: routedOutputMint,
142
+ // The route's own input, not the requested amount — under ExactOut the
143
+ // request is the output, and callers size `beginSwap` off `inAmount`.
144
+ inAmount: ((_d = route.inAmount) !== null && _d !== void 0 ? _d : amount).toString(),
103
145
  outAmount: route.outAmount.toString(),
104
146
  swapMode: data.swapMode,
105
- slippageBps: route.slippageBps,
147
+ slippageBps: Number(route.slippageBps),
106
148
  platformFee: route.platformFee
107
149
  ? {
108
150
  amount: route.platformFee.amount.toString(),
109
- feeBps: route.platformFee.fee_bps,
151
+ feeBps: Number(route.platformFee.fee_bps),
110
152
  }
111
153
  : undefined,
112
- routePlan: ((_a = route.steps) === null || _a === void 0 ? void 0 : _a.map((step) => {
154
+ routePlan: ((_e = route.steps) === null || _e === void 0 ? void 0 : _e.map((step) => {
113
155
  var _a;
114
156
  return ({
115
157
  swapInfo: {
@@ -120,85 +162,81 @@ class TitanClient {
120
162
  inAmount: step.inAmount.toString(),
121
163
  outAmount: step.outAmount.toString(),
122
164
  feeAmount: ((_a = step.feeAmount) === null || _a === void 0 ? void 0 : _a.toString()) || '0',
123
- feeMint: step.feeMint ? new web3_js_1.PublicKey(step.feeMint).toString() : '',
165
+ feeMint: step.feeMint
166
+ ? new web3_js_1.PublicKey(step.feeMint).toString()
167
+ : '',
124
168
  },
125
169
  percent: 100,
126
170
  });
127
171
  })) || [],
128
- contextSlot: route.contextSlot,
129
- timeTaken: route.timeTaken,
130
- };
172
+ contextSlot: toNumber(route.contextSlot),
173
+ timeTaken: toNumber(route.timeTaken),
174
+ }, { provider: 'titan', route, quotedFor: userPublicKey.toString() });
131
175
  }
132
176
  /**
133
- * Get a swap transaction for quote
177
+ * The route as Titan built it, compiled into a signable transaction. Titan
178
+ * returns instructions rather than a transaction, so unlike Jupiter there is
179
+ * nothing to strip — the route already includes its own setup and teardown.
180
+ * @throws If the quote came from a different provider or a different wallet,
181
+ * or if a lookup table the route depends on can't be loaded.
134
182
  */
135
- async getSwap({ userPublicKey, }) {
136
- // Check if we have cached quote data that matches the current parameters
137
- if (!this.lastQuoteData) {
138
- throw new Error('No matching quote data found. Please get a fresh quote before attempting to swap.');
139
- }
140
- // Reuse the cached quote data
141
- const data = this.lastQuoteData;
142
- // We are only querying for the best avaiable route so use that
143
- const route = data.quotes[Object.keys(data.quotes)[0]];
144
- if (!route) {
145
- throw new Error('No routes available');
146
- }
147
- if (route.instructions && route.instructions.length > 0) {
148
- // Errors propagate as-is. Replacing them with generic copy here loses
149
- // the reason the swap can't be built — an unresolvable lookup table,
150
- // say — which the caller needs to decide whether re-quoting will help.
151
- try {
152
- const { transactionMessage, lookupTables } = await this.getTransactionMessageAndLookupTables(route, userPublicKey);
153
- return { transactionMessage, lookupTables };
154
- }
155
- finally {
156
- // Clear cached quote data after use
157
- this.lastQuoteData = undefined;
158
- this.lastQuoteParams = undefined;
159
- }
183
+ async getSwapTransaction({ quote, userPublicKey, }) {
184
+ var _a;
185
+ const route = (0, types_1.expectProviderRoute)(quote, 'titan', userPublicKey)
186
+ .route;
187
+ if (!((_a = route.instructions) === null || _a === void 0 ? void 0 : _a.length)) {
188
+ throw new Error('No instructions provided in the route');
160
189
  }
161
- throw new Error('No instructions provided in the route');
190
+ const [{ instructions, lookupTables }, { blockhash }] = await Promise.all([
191
+ this.getInstructionsAndLookupTables(route),
192
+ this.connection.getLatestBlockhash(),
193
+ ]);
194
+ return new web3_js_1.VersionedTransaction(new web3_js_1.TransactionMessage({
195
+ payerKey: userPublicKey,
196
+ recentBlockhash: blockhash,
197
+ instructions,
198
+ }).compileToV0Message(lookupTables));
162
199
  }
163
200
  /**
164
- * Get the titan instructions from transaction by filtering out instructions to compute budget and associated token programs
165
- * @param transactionMessage the transaction message
166
- * @param inputMint the input mint
167
- * @param outputMint the output mint
201
+ * Builds the route instructions for a quote returned by {@link getQuote}.
202
+ *
203
+ * The route travels on the quote, so this reads no client state and two
204
+ * quotes in flight can never be confused for one another. Slippage was
205
+ * fixed when Titan built the route, so there is nothing to apply here.
206
+ * @throws If the quote came from a different provider or a different wallet,
207
+ * or if a lookup table the route depends on can't be loaded.
168
208
  */
169
- getTitanInstructions({ transactionMessage, inputMint, outputMint, }) {
170
- // Filter out common system instructions that can be handled by VelocityClient
171
- const filteredInstructions = transactionMessage.instructions.filter((instruction) => {
172
- const programId = instruction.programId.toString();
173
- // Filter out system programs
174
- if (programId === 'ComputeBudget111111111111111111111111111111') {
175
- return false;
176
- }
177
- if (programId === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') {
178
- return false;
179
- }
180
- if (programId === '11111111111111111111111111111111') {
181
- return false;
182
- }
183
- // Filter out Associated Token Account creation for input/output mints
184
- if (programId === 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') {
185
- if (instruction.keys.length > 3) {
186
- const mint = instruction.keys[3].pubkey;
187
- if (mint.equals(inputMint) || mint.equals(outputMint)) {
188
- return false;
189
- }
190
- }
191
- }
192
- return true;
193
- });
194
- return filteredInstructions;
209
+ async getRouteInstructions({ quote, userPublicKey, }) {
210
+ var _a;
211
+ const route = (0, types_1.expectProviderRoute)(quote, 'titan', userPublicKey)
212
+ .route;
213
+ if (!((_a = route.instructions) === null || _a === void 0 ? void 0 : _a.length)) {
214
+ throw new Error('No instructions provided in the route');
215
+ }
216
+ // Errors propagate as-is. Replacing them with generic copy here loses
217
+ // the reason the swap can't be built — an unresolvable lookup table,
218
+ // say — which the caller needs to decide whether re-quoting will help.
219
+ const { instructions, lookupTables } = await this.getInstructionsAndLookupTables(route);
220
+ return {
221
+ instructions: (0, routeInstructions_1.filterRouteInstructions)({
222
+ instructions,
223
+ inputMint: new web3_js_1.PublicKey(quote.inputMint),
224
+ outputMint: new web3_js_1.PublicKey(quote.outputMint),
225
+ }),
226
+ lookupTables,
227
+ };
195
228
  }
196
229
  /**
197
230
  * Fetches a lookup table required by a route, retrying transient RPC
198
- * failures (rate limiting in particular) before giving up.
231
+ * failures (rate limiting in particular) before giving up. Checks the
232
+ * instance cache first and populates it on a fresh fetch.
199
233
  * @throws If the table still can't be loaded, or doesn't exist on-chain.
200
234
  */
201
235
  async fetchLookupTable(altPubkey) {
236
+ const cached = this.lookupTableCache.get(altPubkey.toString());
237
+ if (cached !== undefined) {
238
+ return cached;
239
+ }
202
240
  let lastError;
203
241
  for (let attempt = 0; attempt <= LOOKUP_TABLE_FETCH_RETRIES; attempt++) {
204
242
  if (attempt > 0) {
@@ -214,6 +252,7 @@ class TitanClient {
214
252
  continue;
215
253
  }
216
254
  if (altAccount.value) {
255
+ this.lookupTableCache.set(altPubkey.toString(), altAccount.value);
217
256
  return altAccount.value;
218
257
  }
219
258
  // A successful response with no value means the route references a
@@ -222,8 +261,9 @@ class TitanClient {
222
261
  }
223
262
  throw new Error(`Failed to fetch address lookup table ${altPubkey.toString()}: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
224
263
  }
225
- async getTransactionMessageAndLookupTables(route, userPublicKey) {
226
- const solanaInstructions = route.instructions.map((instruction) => ({
264
+ async getInstructionsAndLookupTables(route) {
265
+ var _a;
266
+ const instructions = route.instructions.map((instruction) => ({
227
267
  programId: new web3_js_1.PublicKey(instruction.p),
228
268
  keys: instruction.a.map((meta) => ({
229
269
  pubkey: new web3_js_1.PublicKey(meta.p),
@@ -232,27 +272,13 @@ class TitanClient {
232
272
  })),
233
273
  data: Buffer.from(instruction.d),
234
274
  }));
235
- // Get recent blockhash
236
- const { blockhash } = await this.connection.getLatestBlockhash();
237
- // Build address lookup tables if provided.
238
- //
239
275
  // These all have to resolve. A table that fails to load isn't a slightly
240
276
  // worse route — every account it would have compressed to a 1-byte index
241
277
  // gets inlined as a 32-byte pubkey instead, which pushes the transaction
242
278
  // past the size limit and only surfaces later as an opaque
243
279
  // "encoding overruns Uint8Array". Failing here lets the caller re-quote.
244
- const addressLookupTables = [];
245
- if (route.addressLookupTables && route.addressLookupTables.length > 0) {
246
- for (const altPubkey of route.addressLookupTables) {
247
- addressLookupTables.push(await this.fetchLookupTable(new web3_js_1.PublicKey(altPubkey)));
248
- }
249
- }
250
- const transactionMessage = new web3_js_1.TransactionMessage({
251
- payerKey: userPublicKey,
252
- recentBlockhash: blockhash,
253
- instructions: solanaInstructions,
254
- });
255
- return { transactionMessage, lookupTables: addressLookupTables };
280
+ const lookupTables = await Promise.all(((_a = route.addressLookupTables) !== null && _a !== void 0 ? _a : []).map((altPubkey) => this.fetchLookupTable(new web3_js_1.PublicKey(altPubkey))));
281
+ return { instructions, lookupTables };
256
282
  }
257
283
  }
258
284
  exports.TitanClient = TitanClient;
@@ -1125,6 +1125,8 @@ export type StateAccount = {
1125
1125
  hotAmmSpreadAdjust: PublicKey;
1126
1126
  /** hot key authorized to trigger protocol-fee withdrawals to `protocolFeeRecipientPerp`/`protocolFeeRecipientSpot` */
1127
1127
  hotFeeWithdraw: PublicKey;
1128
+ /** hot key authorized to grow zero-copy accounts to the deployed program's size (`extendAccount`) */
1129
+ hotAccountExtension: PublicKey;
1128
1130
  /** treasury PERP protocol fees are withdrawn to (settable only by `coldAdmin`); `PublicKey.default()` makes perp fee withdrawals inert */
1129
1131
  protocolFeeRecipientPerp: PublicKey;
1130
1132
  /** treasury SPOT protocol fees are withdrawn to (settable only by `coldAdmin`); `PublicKey.default()` makes spot fee withdrawals inert */
@@ -410,14 +410,15 @@ export declare class User {
410
410
  */
411
411
  getTotalCollateral(marginCategory?: MarginCategory, strict?: boolean, includeOpenOrders?: boolean, liquidationBuffer?: BN, perpMarketIndex?: number): BN;
412
412
  /**
413
- * True when the account has an admin-set `equityFloor` and its cross-margin
414
- * total collateral is below it. This is the trip threshold of the
415
- * permissionless `tripEquityFloorBreaker`; action gating happens at
416
- * `equityFloor + equityFloorBuffer` (see `isBelowBufferedEquityFloor`).
417
- * Mirrors `User::is_below_equity_floor` on-chain.
418
- * @param strict Use TWAP-bounded oracle pricing, matching the withdraw path. Defaults to false.
413
+ * True when the account has an admin-set `equityFloor` and its net equity
414
+ * (`getNetUsdValue`: unweighted assets and perp PnL minus unweighted spot
415
+ * liabilities, at live oracle prices) is below it. This is the trip
416
+ * threshold of the permissionless `tripEquityFloorBreaker`; action gating
417
+ * happens at `equityFloor + equityFloorBuffer` (see
418
+ * `isBelowBufferedEquityFloor`). Mirrors `User::is_below_equity_floor`
419
+ * onchain.
419
420
  */
420
- isBelowEquityFloor(strict?: boolean): boolean;
421
+ isBelowEquityFloor(): boolean;
421
422
  /**
422
423
  * The equity required by risk-increasing actions:
423
424
  * `equityFloor + equityFloorBuffer` (QUOTE_PRECISION). Mirrors
@@ -426,31 +427,28 @@ export declare class User {
426
427
  */
427
428
  getBufferedEquityFloor(): BN;
428
429
  /**
429
- * True when the account has an admin-set `equityFloor` and its cross-margin
430
- * total collateral is below `equityFloor + equityFloorBuffer`. While below,
431
- * the program rejects risk-increasing order placement and fills,
430
+ * True when the account has an admin-set `equityFloor` and its net equity
431
+ * (`getNetUsdValue`) is below `equityFloor + equityFloorBuffer`. While
432
+ * below, the program rejects risk-increasing order placement and fills,
432
433
  * withdrawals, and transfers out of the account (`EquityBelowFloor`);
433
434
  * reduce-only activity stays allowed. Mirrors
434
435
  * `User::is_below_buffered_equity_floor` on-chain.
435
- * @param strict Use TWAP-bounded oracle pricing, matching the withdraw path. Defaults to false.
436
436
  */
437
- isBelowBufferedEquityFloor(strict?: boolean): boolean;
437
+ isBelowBufferedEquityFloor(): boolean;
438
438
  /**
439
- * Cross-margin total collateral in excess of the admin-set `equityFloor`,
439
+ * Net equity (`getNetUsdValue`) in excess of the admin-set `equityFloor`,
440
440
  * floored at zero (QUOTE_PRECISION). Unbounded (`null`) when no floor is set.
441
441
  * This is headroom above the trip threshold; headroom above the level
442
442
  * risk-increasing actions must clear is `getEquityAboveBufferedFloor`.
443
- * @param strict Use TWAP-bounded oracle pricing. Defaults to false.
444
443
  */
445
- getEquityAboveFloor(strict?: boolean): BN | null;
444
+ getEquityAboveFloor(): BN | null;
446
445
  /**
447
- * Cross-margin total collateral in excess of `equityFloor +
446
+ * Net equity (`getNetUsdValue`) in excess of `equityFloor +
448
447
  * equityFloorBuffer`, floored at zero (QUOTE_PRECISION). Unbounded
449
448
  * (`null`) when no floor is set. When this reaches zero, risk-increasing
450
449
  * actions start rejecting.
451
- * @param strict Use TWAP-bounded oracle pricing. Defaults to false.
452
450
  */
453
- getEquityAboveBufferedFloor(strict?: boolean): BN | null;
451
+ getEquityAboveBufferedFloor(): BN | null;
454
452
  /**
455
453
  * Builds the liquidation-buffer map to pass into margin calculations while
456
454
  * a liquidation is in progress: `'cross'` is set to the state account's
@@ -897,19 +897,20 @@ class User {
897
897
  return marginCalc.totalCollateral;
898
898
  }
899
899
  /**
900
- * True when the account has an admin-set `equityFloor` and its cross-margin
901
- * total collateral is below it. This is the trip threshold of the
902
- * permissionless `tripEquityFloorBreaker`; action gating happens at
903
- * `equityFloor + equityFloorBuffer` (see `isBelowBufferedEquityFloor`).
904
- * Mirrors `User::is_below_equity_floor` on-chain.
905
- * @param strict Use TWAP-bounded oracle pricing, matching the withdraw path. Defaults to false.
906
- */
907
- isBelowEquityFloor(strict = false) {
900
+ * True when the account has an admin-set `equityFloor` and its net equity
901
+ * (`getNetUsdValue`: unweighted assets and perp PnL minus unweighted spot
902
+ * liabilities, at live oracle prices) is below it. This is the trip
903
+ * threshold of the permissionless `tripEquityFloorBreaker`; action gating
904
+ * happens at `equityFloor + equityFloorBuffer` (see
905
+ * `isBelowBufferedEquityFloor`). Mirrors `User::is_below_equity_floor`
906
+ * onchain.
907
+ */
908
+ isBelowEquityFloor() {
908
909
  const equityFloor = this.getUserAccountOrThrow().equityFloor;
909
910
  if (equityFloor.lte(numericConstants_1.ZERO)) {
910
911
  return false;
911
912
  }
912
- return this.getTotalCollateral('Initial', strict).lt(equityFloor);
913
+ return this.getNetUsdValue().lt(equityFloor);
913
914
  }
914
915
  /**
915
916
  * The equity required by risk-increasing actions:
@@ -922,48 +923,45 @@ class User {
922
923
  return userAccount.equityFloor.add(userAccount.equityFloorBuffer);
923
924
  }
924
925
  /**
925
- * True when the account has an admin-set `equityFloor` and its cross-margin
926
- * total collateral is below `equityFloor + equityFloorBuffer`. While below,
927
- * the program rejects risk-increasing order placement and fills,
926
+ * True when the account has an admin-set `equityFloor` and its net equity
927
+ * (`getNetUsdValue`) is below `equityFloor + equityFloorBuffer`. While
928
+ * below, the program rejects risk-increasing order placement and fills,
928
929
  * withdrawals, and transfers out of the account (`EquityBelowFloor`);
929
930
  * reduce-only activity stays allowed. Mirrors
930
931
  * `User::is_below_buffered_equity_floor` on-chain.
931
- * @param strict Use TWAP-bounded oracle pricing, matching the withdraw path. Defaults to false.
932
932
  */
933
- isBelowBufferedEquityFloor(strict = false) {
933
+ isBelowBufferedEquityFloor() {
934
934
  const equityFloor = this.getUserAccountOrThrow().equityFloor;
935
935
  if (equityFloor.lte(numericConstants_1.ZERO)) {
936
936
  return false;
937
937
  }
938
- return this.getTotalCollateral('Initial', strict).lt(this.getBufferedEquityFloor());
938
+ return this.getNetUsdValue().lt(this.getBufferedEquityFloor());
939
939
  }
940
940
  /**
941
- * Cross-margin total collateral in excess of the admin-set `equityFloor`,
941
+ * Net equity (`getNetUsdValue`) in excess of the admin-set `equityFloor`,
942
942
  * floored at zero (QUOTE_PRECISION). Unbounded (`null`) when no floor is set.
943
943
  * This is headroom above the trip threshold; headroom above the level
944
944
  * risk-increasing actions must clear is `getEquityAboveBufferedFloor`.
945
- * @param strict Use TWAP-bounded oracle pricing. Defaults to false.
946
945
  */
947
- getEquityAboveFloor(strict = false) {
946
+ getEquityAboveFloor() {
948
947
  const equityFloor = this.getUserAccountOrThrow().equityFloor;
949
948
  if (equityFloor.lte(numericConstants_1.ZERO)) {
950
949
  return null;
951
950
  }
952
- return anchor_1.BN.max(this.getTotalCollateral('Initial', strict).sub(equityFloor), numericConstants_1.ZERO);
951
+ return anchor_1.BN.max(this.getNetUsdValue().sub(equityFloor), numericConstants_1.ZERO);
953
952
  }
954
953
  /**
955
- * Cross-margin total collateral in excess of `equityFloor +
954
+ * Net equity (`getNetUsdValue`) in excess of `equityFloor +
956
955
  * equityFloorBuffer`, floored at zero (QUOTE_PRECISION). Unbounded
957
956
  * (`null`) when no floor is set. When this reaches zero, risk-increasing
958
957
  * actions start rejecting.
959
- * @param strict Use TWAP-bounded oracle pricing. Defaults to false.
960
958
  */
961
- getEquityAboveBufferedFloor(strict = false) {
959
+ getEquityAboveBufferedFloor() {
962
960
  const equityFloor = this.getUserAccountOrThrow().equityFloor;
963
961
  if (equityFloor.lte(numericConstants_1.ZERO)) {
964
962
  return null;
965
963
  }
966
- return anchor_1.BN.max(this.getTotalCollateral('Initial', strict).sub(this.getBufferedEquityFloor()), numericConstants_1.ZERO);
964
+ return anchor_1.BN.max(this.getNetUsdValue().sub(this.getBufferedEquityFloor()), numericConstants_1.ZERO);
967
965
  }
968
966
  /**
969
967
  * Builds the liquidation-buffer map to pass into margin calculations while
@@ -2429,9 +2427,9 @@ class User {
2429
2427
  const spotMarket = this.velocityClient.getSpotMarketAccountOrThrow(marketIndex);
2430
2428
  // eslint-disable-next-line prefer-const
2431
2429
  let { borrowLimit, withdrawLimit } = (0, spotBalance_2.calculateWithdrawLimit)(spotMarket, nowTs);
2432
- // the withdraw path enforces the equity floor on post-withdraw total
2433
- // collateral, so equity above the floor caps free collateral here
2434
- const equityAboveFloor = this.getEquityAboveFloor(true);
2430
+ // the withdraw path enforces the equity floor on post-withdraw net
2431
+ // equity, so equity above the floor caps free collateral here
2432
+ const equityAboveFloor = this.getEquityAboveFloor();
2435
2433
  if (equityAboveFloor !== null && equityAboveFloor.eq(numericConstants_1.ZERO)) {
2436
2434
  return numericConstants_1.ZERO;
2437
2435
  }