@velocity-exchange/sdk 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/adminClient.d.ts +13 -10
- package/lib/browser/adminClient.js +16 -21
- package/lib/browser/decode/user.js +3 -1
- package/lib/browser/equityFloorManager.d.ts +154 -0
- package/lib/browser/equityFloorManager.js +283 -0
- package/lib/browser/idl/velocity.d.ts +85 -10
- package/lib/browser/idl/velocity.json +85 -10
- package/lib/browser/index.d.ts +4 -0
- package/lib/browser/index.js +5 -0
- package/lib/browser/jupiter/jupiterClient.d.ts +31 -27
- package/lib/browser/jupiter/jupiterClient.js +84 -37
- package/lib/browser/math/margin.d.ts +28 -0
- package/lib/browser/math/margin.js +43 -1
- package/lib/browser/math/superStake.d.ts +9 -9
- package/lib/browser/math/superStake.js +8 -8
- package/lib/browser/swap/UnifiedSwapClient.d.ts +38 -76
- package/lib/browser/swap/UnifiedSwapClient.js +53 -110
- package/lib/browser/swap/routeInstructions.d.ts +19 -0
- package/lib/browser/swap/routeInstructions.js +43 -0
- package/lib/browser/swap/types.d.ts +196 -0
- package/lib/browser/swap/types.js +88 -0
- package/lib/browser/titan/titanClient.d.ts +37 -67
- package/lib/browser/titan/titanClient.js +160 -103
- package/lib/browser/types.d.ts +6 -2
- package/lib/browser/types.js +2 -0
- package/lib/browser/user.d.ts +43 -18
- package/lib/browser/user.js +94 -45
- package/lib/browser/velocityClient.d.ts +86 -103
- package/lib/browser/velocityClient.js +157 -298
- package/lib/node/adminClient.d.ts +13 -10
- package/lib/node/adminClient.d.ts.map +1 -1
- package/lib/node/adminClient.js +16 -21
- package/lib/node/decode/user.d.ts.map +1 -1
- package/lib/node/decode/user.js +3 -1
- package/lib/node/equityFloorManager.d.ts +155 -0
- package/lib/node/equityFloorManager.d.ts.map +1 -0
- package/lib/node/equityFloorManager.js +283 -0
- package/lib/node/idl/velocity.d.ts +85 -10
- package/lib/node/idl/velocity.d.ts.map +1 -1
- package/lib/node/idl/velocity.json +85 -10
- package/lib/node/index.d.ts +4 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +5 -0
- package/lib/node/jupiter/jupiterClient.d.ts +31 -27
- package/lib/node/jupiter/jupiterClient.d.ts.map +1 -1
- package/lib/node/jupiter/jupiterClient.js +84 -37
- package/lib/node/math/margin.d.ts +28 -0
- package/lib/node/math/margin.d.ts.map +1 -1
- package/lib/node/math/margin.js +43 -1
- package/lib/node/math/superStake.d.ts +9 -9
- package/lib/node/math/superStake.d.ts.map +1 -1
- package/lib/node/math/superStake.js +8 -8
- package/lib/node/swap/UnifiedSwapClient.d.ts +38 -76
- package/lib/node/swap/UnifiedSwapClient.d.ts.map +1 -1
- package/lib/node/swap/UnifiedSwapClient.js +53 -110
- package/lib/node/swap/routeInstructions.d.ts +20 -0
- package/lib/node/swap/routeInstructions.d.ts.map +1 -0
- package/lib/node/swap/routeInstructions.js +43 -0
- package/lib/node/swap/types.d.ts +197 -0
- package/lib/node/swap/types.d.ts.map +1 -0
- package/lib/node/swap/types.js +88 -0
- package/lib/node/titan/titanClient.d.ts +37 -67
- package/lib/node/titan/titanClient.d.ts.map +1 -1
- package/lib/node/titan/titanClient.js +160 -103
- package/lib/node/types.d.ts +6 -2
- package/lib/node/types.d.ts.map +1 -1
- package/lib/node/types.js +2 -0
- package/lib/node/user.d.ts +43 -18
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +94 -45
- package/lib/node/velocityClient.d.ts +86 -103
- package/lib/node/velocityClient.d.ts.map +1 -1
- package/lib/node/velocityClient.js +157 -298
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@ import { AddressLookupTableAccount, Keypair, PublicKey, TransactionInstruction,
|
|
|
17
17
|
import { FeeStructure, OracleGuardRails, OracleSource, ExchangeStatus, SolvencyStatus, MarketStatus, ContractTier, AssetTier, TxParams, AddAmmConstituentMappingDatum, SwapReduceOnly, InitializeConstituentParams, ConstituentStatus, LPPoolAccount, TransferFeeAndPnlPoolDirection, MarketType } from './types';
|
|
18
18
|
import { BN } from './isomorphic/anchor';
|
|
19
19
|
import { VelocityClient } from './velocityClient';
|
|
20
|
-
import { JupiterClient,
|
|
20
|
+
import { JupiterClient, JupiterSwapQuote } from './jupiter/jupiterClient';
|
|
21
21
|
import { SwapMode } from './swap/UnifiedSwapClient';
|
|
22
22
|
export declare class AdminClient extends VelocityClient {
|
|
23
23
|
/**
|
|
@@ -2283,7 +2283,7 @@ export declare class AdminClient extends VelocityClient {
|
|
|
2283
2283
|
slippageBps?: number;
|
|
2284
2284
|
swapMode?: SwapMode;
|
|
2285
2285
|
onlyDirectRoutes?: boolean;
|
|
2286
|
-
quote?:
|
|
2286
|
+
quote?: JupiterSwapQuote;
|
|
2287
2287
|
lpPoolId: number;
|
|
2288
2288
|
}): Promise<{
|
|
2289
2289
|
ixs: TransactionInstruction[];
|
|
@@ -2500,24 +2500,27 @@ export declare class AdminClient extends VelocityClient {
|
|
|
2500
2500
|
*/
|
|
2501
2501
|
getUpdateSpecialUserStatusIx(userAccountPublicKey: PublicKey, status: number): Promise<TransactionInstruction>;
|
|
2502
2502
|
/**
|
|
2503
|
-
* Sets a `User` account's `equityFloor
|
|
2504
|
-
*
|
|
2505
|
-
*
|
|
2506
|
-
* and deposit/position transfers out of
|
|
2507
|
-
*
|
|
2508
|
-
*
|
|
2503
|
+
* Sets a `User` account's `equityFloor` and `equityFloorBuffer`. The floor
|
|
2504
|
+
* is the minimum cross-margin total collateral (QUOTE_PRECISION) below
|
|
2505
|
+
* which the permissionless breaker can trip; risk-increasing order
|
|
2506
|
+
* placement and fills, withdrawals, and deposit/position transfers out of
|
|
2507
|
+
* the account must clear `floor + buffer` (`EquityBelowFloor` otherwise);
|
|
2508
|
+
* reduce-only activity stays allowed. Requires warm admin (`check_warm`);
|
|
2509
|
+
* the account's authority and delegate cannot change either value. Pass
|
|
2510
|
+
* `0` as the floor to disable both checks.
|
|
2509
2511
|
* @param userAccountPublicKey - `User` PDA to update.
|
|
2510
2512
|
* @param equityFloor - New floor, QUOTE_PRECISION; `0` disables.
|
|
2513
|
+
* @param equityFloorBuffer - New buffer, QUOTE_PRECISION; no effect while the floor is 0.
|
|
2511
2514
|
* @param txParams - Optional transaction-building overrides.
|
|
2512
2515
|
* @returns Transaction signature.
|
|
2513
2516
|
*/
|
|
2514
|
-
updateUserEquityFloor(userAccountPublicKey: PublicKey, equityFloor: BN, txParams?: TxParams): Promise<TransactionSignature>;
|
|
2517
|
+
updateUserEquityFloor(userAccountPublicKey: PublicKey, equityFloor: BN, equityFloorBuffer: BN, txParams?: TxParams): Promise<TransactionSignature>;
|
|
2515
2518
|
/**
|
|
2516
2519
|
* Builds the `updateUserEquityFloor` instruction without sending it. See
|
|
2517
2520
|
* `updateUserEquityFloor`.
|
|
2518
2521
|
* @returns The unsigned `updateUserEquityFloor` instruction.
|
|
2519
2522
|
*/
|
|
2520
|
-
getUpdateUserEquityFloorIx(userAccountPublicKey: PublicKey, equityFloor: BN): Promise<TransactionInstruction>;
|
|
2523
|
+
getUpdateUserEquityFloorIx(userAccountPublicKey: PublicKey, equityFloor: BN, equityFloorBuffer: BN): Promise<TransactionInstruction>;
|
|
2521
2524
|
/**
|
|
2522
2525
|
* Clears the authority-wide equity floor breaker set by the permissionless
|
|
2523
2526
|
* `tripEquityFloorBreaker` keeper instruction, unfreezing all of the
|
|
@@ -4496,21 +4496,13 @@ class AdminClient extends velocityClient_1.VelocityClient {
|
|
|
4496
4496
|
if (!quote) {
|
|
4497
4497
|
throw new Error('Could not fetch swap quote. Please try again.');
|
|
4498
4498
|
}
|
|
4499
|
+
this.assertQuoteMatchesMarkets(quote, inMarket, outMarket);
|
|
4499
4500
|
const isExactOut = swapMode === 'ExactOut' || quote.swapMode === 'ExactOut';
|
|
4500
4501
|
const amountIn = new anchor_1.BN(quote.inAmount);
|
|
4501
4502
|
const exactOutBufferedAmountIn = amountIn.muln(1001).divn(1000); // Add 10bp buffer
|
|
4502
|
-
const
|
|
4503
|
+
const { instructions: jupiterInstructions, lookupTables } = await jupiterClient.getRouteInstructions({
|
|
4503
4504
|
quote,
|
|
4504
4505
|
userPublicKey: this.provider.wallet.publicKey,
|
|
4505
|
-
slippageBps,
|
|
4506
|
-
});
|
|
4507
|
-
const { transactionMessage, lookupTables } = await jupiterClient.getTransactionMessageAndLookupTables({
|
|
4508
|
-
transaction,
|
|
4509
|
-
});
|
|
4510
|
-
const jupiterInstructions = jupiterClient.getJupiterInstructions({
|
|
4511
|
-
transactionMessage,
|
|
4512
|
-
inputMint: inMarket.mint,
|
|
4513
|
-
outputMint: outMarket.mint,
|
|
4514
4506
|
});
|
|
4515
4507
|
const preInstructions = [];
|
|
4516
4508
|
const tokenProgram = this.getTokenProgramForSpotMarket(outMarket);
|
|
@@ -4523,7 +4515,7 @@ class AdminClient extends velocityClient_1.VelocityClient {
|
|
|
4523
4515
|
const inAssociatedTokenAccount = await this.getAssociatedTokenAccount(inMarket.marketIndex, false, inTokenProgram);
|
|
4524
4516
|
const inAccountInfo = await this.connection.getAccountInfo(inAssociatedTokenAccount);
|
|
4525
4517
|
if (!inAccountInfo) {
|
|
4526
|
-
preInstructions.push(this.createAssociatedTokenAccountIdempotentInstruction(inAssociatedTokenAccount, this.provider.wallet.publicKey, this.provider.wallet.publicKey, inMarket.mint,
|
|
4518
|
+
preInstructions.push(this.createAssociatedTokenAccountIdempotentInstruction(inAssociatedTokenAccount, this.provider.wallet.publicKey, this.provider.wallet.publicKey, inMarket.mint, inTokenProgram));
|
|
4527
4519
|
}
|
|
4528
4520
|
const { beginSwapIx, endSwapIx } = await this.getSwapIx({
|
|
4529
4521
|
lpPoolId,
|
|
@@ -4936,19 +4928,22 @@ class AdminClient extends velocityClient_1.VelocityClient {
|
|
|
4936
4928
|
});
|
|
4937
4929
|
}
|
|
4938
4930
|
/**
|
|
4939
|
-
* Sets a `User` account's `equityFloor
|
|
4940
|
-
*
|
|
4941
|
-
*
|
|
4942
|
-
* and deposit/position transfers out of
|
|
4943
|
-
*
|
|
4944
|
-
*
|
|
4931
|
+
* Sets a `User` account's `equityFloor` and `equityFloorBuffer`. The floor
|
|
4932
|
+
* is the minimum cross-margin total collateral (QUOTE_PRECISION) below
|
|
4933
|
+
* which the permissionless breaker can trip; risk-increasing order
|
|
4934
|
+
* placement and fills, withdrawals, and deposit/position transfers out of
|
|
4935
|
+
* the account must clear `floor + buffer` (`EquityBelowFloor` otherwise);
|
|
4936
|
+
* reduce-only activity stays allowed. Requires warm admin (`check_warm`);
|
|
4937
|
+
* the account's authority and delegate cannot change either value. Pass
|
|
4938
|
+
* `0` as the floor to disable both checks.
|
|
4945
4939
|
* @param userAccountPublicKey - `User` PDA to update.
|
|
4946
4940
|
* @param equityFloor - New floor, QUOTE_PRECISION; `0` disables.
|
|
4941
|
+
* @param equityFloorBuffer - New buffer, QUOTE_PRECISION; no effect while the floor is 0.
|
|
4947
4942
|
* @param txParams - Optional transaction-building overrides.
|
|
4948
4943
|
* @returns Transaction signature.
|
|
4949
4944
|
*/
|
|
4950
|
-
async updateUserEquityFloor(userAccountPublicKey, equityFloor, txParams) {
|
|
4951
|
-
const ix = await this.getUpdateUserEquityFloorIx(userAccountPublicKey, equityFloor);
|
|
4945
|
+
async updateUserEquityFloor(userAccountPublicKey, equityFloor, equityFloorBuffer, txParams) {
|
|
4946
|
+
const ix = await this.getUpdateUserEquityFloorIx(userAccountPublicKey, equityFloor, equityFloorBuffer);
|
|
4952
4947
|
const tx = await this.buildTransaction(ix, txParams);
|
|
4953
4948
|
const { txSig } = await this.sendTransaction(tx, [], this.opts);
|
|
4954
4949
|
return txSig;
|
|
@@ -4958,8 +4953,8 @@ class AdminClient extends velocityClient_1.VelocityClient {
|
|
|
4958
4953
|
* `updateUserEquityFloor`.
|
|
4959
4954
|
* @returns The unsigned `updateUserEquityFloor` instruction.
|
|
4960
4955
|
*/
|
|
4961
|
-
async getUpdateUserEquityFloorIx(userAccountPublicKey, equityFloor) {
|
|
4962
|
-
return this.program.instruction.updateUserEquityFloor(equityFloor, {
|
|
4956
|
+
async getUpdateUserEquityFloorIx(userAccountPublicKey, equityFloor, equityFloorBuffer) {
|
|
4957
|
+
return this.program.instruction.updateUserEquityFloor(equityFloor, equityFloorBuffer, {
|
|
4963
4958
|
accounts: {
|
|
4964
4959
|
admin: this.useHotWalletAdmin
|
|
4965
4960
|
? this.wallet.publicKey
|
|
@@ -336,7 +336,8 @@ function decodeUser(buffer) {
|
|
|
336
336
|
offset += 3; // padding
|
|
337
337
|
const equityFloor = readUnsignedBigInt64LE(buffer, offset);
|
|
338
338
|
offset += 8;
|
|
339
|
-
|
|
339
|
+
const equityFloorBuffer = readUnsignedBigInt64LE(buffer, offset);
|
|
340
|
+
offset += 8;
|
|
340
341
|
return {
|
|
341
342
|
authority,
|
|
342
343
|
delegate,
|
|
@@ -366,6 +367,7 @@ function decodeUser(buffer) {
|
|
|
366
367
|
poolId,
|
|
367
368
|
specialUserStatus,
|
|
368
369
|
equityFloor,
|
|
370
|
+
equityFloorBuffer,
|
|
369
371
|
};
|
|
370
372
|
}
|
|
371
373
|
exports.decodeUser = decodeUser;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maker-facing abstraction over the per-subaccount equity floor. The on-chain
|
|
3
|
+
* checks are deliberately per-subaccount (each check reads only the one User
|
|
4
|
+
* account already loaded in the hot path), which leaves the burden of placing
|
|
5
|
+
* floor where the equity is on the delegate. This module removes that burden:
|
|
6
|
+
* it treats an authority's subaccounts as one pool, plans quote transfers with
|
|
7
|
+
* the exact floor delta they must carry, computes how much can leave a
|
|
8
|
+
* subaccount, and rebalances the floor split to match where the equity
|
|
9
|
+
* actually sits — so a delegate never has to reason about floor placement to
|
|
10
|
+
* stay clear of the breaker.
|
|
11
|
+
*/
|
|
12
|
+
import { PublicKey } from '@solana/web3.js';
|
|
13
|
+
import { TransactionSignature } from '@solana/web3.js';
|
|
14
|
+
import { BN } from './isomorphic/anchor';
|
|
15
|
+
import { EquityFloorLevel } from './math/margin';
|
|
16
|
+
import { VelocityClient } from './velocityClient';
|
|
17
|
+
import { TxParams } from './types';
|
|
18
|
+
/** One subaccount's standing relative to its floor. All BN values QUOTE_PRECISION. */
|
|
19
|
+
export type SubaccountFloorStatus = {
|
|
20
|
+
subAccountId: number;
|
|
21
|
+
/** Net equity (`User.getNetUsdValue`, unweighted live-oracle value), what the onchain checks see. */
|
|
22
|
+
equity: BN;
|
|
23
|
+
equityFloor: BN;
|
|
24
|
+
equityFloorBuffer: BN;
|
|
25
|
+
/** `equityFloor + equityFloorBuffer`: what risk-increasing actions must clear. */
|
|
26
|
+
bufferedFloor: BN;
|
|
27
|
+
/** `equity - equityFloor`; negative means the breaker can trip on this subaccount. */
|
|
28
|
+
headroom: BN;
|
|
29
|
+
/** `equity - bufferedFloor`; negative means risk-increasing actions are rejecting. */
|
|
30
|
+
bufferedHeadroom: BN;
|
|
31
|
+
level: EquityFloorLevel;
|
|
32
|
+
};
|
|
33
|
+
/** Authority-wide standing: aggregates plus the per-subaccount breakdown. */
|
|
34
|
+
export type EquityFloorStatus = {
|
|
35
|
+
authority: PublicKey;
|
|
36
|
+
breakerTripped: boolean;
|
|
37
|
+
totalEquity: BN;
|
|
38
|
+
totalFloor: BN;
|
|
39
|
+
totalBuffer: BN;
|
|
40
|
+
/** `totalEquity - (totalFloor + totalBuffer)`: the slack the whole pool has to allocate. */
|
|
41
|
+
totalBufferedHeadroom: BN;
|
|
42
|
+
/** Worst level across subaccounts with a floor set. */
|
|
43
|
+
level: EquityFloorLevel;
|
|
44
|
+
subaccounts: SubaccountFloorStatus[];
|
|
45
|
+
};
|
|
46
|
+
/** A floor-only rebalance step: zero-amount `transferDepositByDelegate` carrying `equityFloorDelta`. */
|
|
47
|
+
export type FloorMove = {
|
|
48
|
+
fromSubAccountId: number;
|
|
49
|
+
toSubAccountId: number;
|
|
50
|
+
equityFloorDelta: BN;
|
|
51
|
+
};
|
|
52
|
+
/** A fully resolved quote transfer ready to submit. */
|
|
53
|
+
export type QuoteTransferPlan = {
|
|
54
|
+
amount: BN;
|
|
55
|
+
marketIndex: number;
|
|
56
|
+
fromSubAccountId: number;
|
|
57
|
+
toSubAccountId: number;
|
|
58
|
+
equityFloorDelta: BN;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Splits `totalFloor` across subaccounts proportionally to their equity,
|
|
62
|
+
* clamped so every allocation is backed (`floor_i <= max(0, equity_i -
|
|
63
|
+
* buffer_i)`), with the clamped remainder water-filled into subaccounts that
|
|
64
|
+
* still have capacity. Entries in `pinned` keep their current floor and
|
|
65
|
+
* receive none of the remainder (used for breached subaccounts, which cannot
|
|
66
|
+
* shed floor on-chain). Returns `null` when no backed allocation exists, i.e.
|
|
67
|
+
* the pool's equity cannot cover `totalFloor` plus buffers.
|
|
68
|
+
*/
|
|
69
|
+
export declare function allocateEquityFloors(totalFloor: BN, subaccounts: {
|
|
70
|
+
equity: BN;
|
|
71
|
+
buffer: BN;
|
|
72
|
+
currentFloor: BN;
|
|
73
|
+
}[], pinned?: boolean[]): BN[] | null;
|
|
74
|
+
/**
|
|
75
|
+
* Turns a current → target floor split into concrete moves, greedily matching
|
|
76
|
+
* surpluses against deficits. The moves conserve the floor sum by
|
|
77
|
+
* construction and each one only ever sheds floor from a subaccount whose
|
|
78
|
+
* floor is above its target.
|
|
79
|
+
*/
|
|
80
|
+
export declare function planFloorMoves(subAccountIds: number[], current: BN[], target: BN[]): FloorMove[];
|
|
81
|
+
export type EquityFloorManagerConfig = {
|
|
82
|
+
/**
|
|
83
|
+
* Subaccount ids to manage. Defaults to every subaccount of the client's
|
|
84
|
+
* authority currently subscribed on the `VelocityClient`.
|
|
85
|
+
*/
|
|
86
|
+
subAccountIds?: number[];
|
|
87
|
+
/**
|
|
88
|
+
* Client-side equity haircut (QUOTE_PRECISION) applied when sizing floor
|
|
89
|
+
* deltas, absorbing the dust by which onchain oracle pricing can differ
|
|
90
|
+
* from the client's. Defaults to 1 quote unit ($1).
|
|
91
|
+
*/
|
|
92
|
+
collateralHaircut?: BN;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* See the module doc. All reads use the subaccounts already subscribed on the
|
|
96
|
+
* wrapped `VelocityClient` (the delegate's client, whose `authority` is the
|
|
97
|
+
* subaccounts' owner); all writes go through `transferDepositByDelegate`.
|
|
98
|
+
*/
|
|
99
|
+
export declare class EquityFloorManager {
|
|
100
|
+
private velocityClient;
|
|
101
|
+
private collateralHaircut;
|
|
102
|
+
private subAccountIds?;
|
|
103
|
+
constructor(velocityClient: VelocityClient, config?: EquityFloorManagerConfig);
|
|
104
|
+
private getManagedUsers;
|
|
105
|
+
private getSubaccountStatus;
|
|
106
|
+
/** Full authority-wide standing: aggregates, worst level, per-subaccount detail. */
|
|
107
|
+
getStatus(): EquityFloorStatus;
|
|
108
|
+
/**
|
|
109
|
+
* The most quote that can leave `subAccountId` to the outside (a
|
|
110
|
+
* withdrawal, which cannot move floor): equity above the buffered floor,
|
|
111
|
+
* less the haircut. Floor constraint only — the withdrawal itself is still
|
|
112
|
+
* subject to margin and borrow limits.
|
|
113
|
+
*/
|
|
114
|
+
getMaxWithdrawable(subAccountId: number): BN;
|
|
115
|
+
/**
|
|
116
|
+
* The most quote that can move from one subaccount to another when the
|
|
117
|
+
* transfer carries floor with it. Because floor travels with the funds
|
|
118
|
+
* (capped at the floor the debited side holds, after which its check
|
|
119
|
+
* disables entirely), this is normally the debited side's whole equity —
|
|
120
|
+
* bounded by what the credited side's equity can back. Floor constraint
|
|
121
|
+
* only; margin and borrow limits still apply on top.
|
|
122
|
+
*/
|
|
123
|
+
getMaxQuoteTransferable(fromSubAccountId: number, toSubAccountId: number): BN;
|
|
124
|
+
/**
|
|
125
|
+
* Resolves a quote transfer into the exact instruction parameters,
|
|
126
|
+
* padding the auto floor delta by the haircut so onchain pricing dust
|
|
127
|
+
* cannot fail it. The padded delta never exceeds the amount or the
|
|
128
|
+
* debited side's floor, so the credited side stays backed whenever it was
|
|
129
|
+
* before.
|
|
130
|
+
*/
|
|
131
|
+
planQuoteTransfer(amount: BN, fromSubAccountId: number, toSubAccountId: number): QuoteTransferPlan;
|
|
132
|
+
/** Plans and submits a quote transfer between two subaccounts in one call. */
|
|
133
|
+
transferQuote(amount: BN, fromSubAccountId: number, toSubAccountId: number, txParams?: TxParams): Promise<TransactionSignature>;
|
|
134
|
+
/**
|
|
135
|
+
* Plans the floor-only moves (zero-amount transfers) that re-split the
|
|
136
|
+
* total floor proportionally to where the equity currently sits, so every
|
|
137
|
+
* subaccount ends with the same relative headroom. Breached subaccounts
|
|
138
|
+
* (below their raw floor) cannot shed floor on-chain, so their floor is
|
|
139
|
+
* pinned in place and the rest is allocated around them. Throws when the
|
|
140
|
+
* pool's equity cannot back the total floor plus buffers — at that point
|
|
141
|
+
* no split works and equity must be deposited (or the admin must lower
|
|
142
|
+
* the floor). Each onchain move also carries a proportional share of the
|
|
143
|
+
* debited side's buffer, so buffers drift toward the same split as the
|
|
144
|
+
* floors; the plan sizes against current buffers and the haircut absorbs
|
|
145
|
+
* the drift dust, but a move can still revert if a credited side cannot
|
|
146
|
+
* back the buffer share it receives.
|
|
147
|
+
*/
|
|
148
|
+
planFloorRebalance(): FloorMove[];
|
|
149
|
+
/**
|
|
150
|
+
* Executes `planFloorRebalance` serially. Safe to run at any time; a
|
|
151
|
+
* no-op when the split already matches the equity distribution.
|
|
152
|
+
*/
|
|
153
|
+
rebalanceFloors(txParams?: TxParams): Promise<TransactionSignature[]>;
|
|
154
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EquityFloorManager = exports.planFloorMoves = exports.allocateEquityFloors = void 0;
|
|
4
|
+
const anchor_1 = require("./isomorphic/anchor");
|
|
5
|
+
const numericConstants_1 = require("./constants/numericConstants");
|
|
6
|
+
const numericConstants_2 = require("./constants/numericConstants");
|
|
7
|
+
const margin_1 = require("./math/margin");
|
|
8
|
+
/**
|
|
9
|
+
* Splits `totalFloor` across subaccounts proportionally to their equity,
|
|
10
|
+
* clamped so every allocation is backed (`floor_i <= max(0, equity_i -
|
|
11
|
+
* buffer_i)`), with the clamped remainder water-filled into subaccounts that
|
|
12
|
+
* still have capacity. Entries in `pinned` keep their current floor and
|
|
13
|
+
* receive none of the remainder (used for breached subaccounts, which cannot
|
|
14
|
+
* shed floor on-chain). Returns `null` when no backed allocation exists, i.e.
|
|
15
|
+
* the pool's equity cannot cover `totalFloor` plus buffers.
|
|
16
|
+
*/
|
|
17
|
+
function allocateEquityFloors(totalFloor, subaccounts, pinned = []) {
|
|
18
|
+
const n = subaccounts.length;
|
|
19
|
+
const targets = new Array(n).fill(numericConstants_1.ZERO);
|
|
20
|
+
let remaining = totalFloor;
|
|
21
|
+
for (let i = 0; i < n; i++) {
|
|
22
|
+
if (pinned[i]) {
|
|
23
|
+
targets[i] = subaccounts[i].currentFloor;
|
|
24
|
+
remaining = remaining.sub(subaccounts[i].currentFloor);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (remaining.isNeg()) {
|
|
28
|
+
// pinned floors alone exceed the total: nothing to allocate elsewhere
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const free = [...Array(n).keys()].filter((i) => !pinned[i]);
|
|
32
|
+
const caps = subaccounts.map((s, i) => pinned[i] ? numericConstants_1.ZERO : anchor_1.BN.max(s.equity.sub(s.buffer), numericConstants_1.ZERO));
|
|
33
|
+
const totalCap = free.reduce((sum, i) => sum.add(caps[i]), numericConstants_1.ZERO);
|
|
34
|
+
if (totalCap.lt(remaining)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const totalFreeEquity = free.reduce((sum, i) => sum.add(anchor_1.BN.max(subaccounts[i].equity, numericConstants_1.ZERO)), numericConstants_1.ZERO);
|
|
38
|
+
// proportional-to-equity first pass (floor division), clamped to capacity
|
|
39
|
+
let assigned = numericConstants_1.ZERO;
|
|
40
|
+
for (const i of free) {
|
|
41
|
+
const share = totalFreeEquity.gt(numericConstants_1.ZERO)
|
|
42
|
+
? remaining.mul(anchor_1.BN.max(subaccounts[i].equity, numericConstants_1.ZERO)).div(totalFreeEquity)
|
|
43
|
+
: numericConstants_1.ZERO;
|
|
44
|
+
targets[i] = anchor_1.BN.min(share, caps[i]);
|
|
45
|
+
assigned = assigned.add(targets[i]);
|
|
46
|
+
}
|
|
47
|
+
// water-fill the rounding/clamping remainder into leftover capacity
|
|
48
|
+
let leftover = remaining.sub(assigned);
|
|
49
|
+
for (const i of free) {
|
|
50
|
+
if (leftover.lte(numericConstants_1.ZERO)) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
const slack = caps[i].sub(targets[i]);
|
|
54
|
+
const add = anchor_1.BN.min(slack, leftover);
|
|
55
|
+
targets[i] = targets[i].add(add);
|
|
56
|
+
leftover = leftover.sub(add);
|
|
57
|
+
}
|
|
58
|
+
if (leftover.gt(numericConstants_1.ZERO)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return targets;
|
|
62
|
+
}
|
|
63
|
+
exports.allocateEquityFloors = allocateEquityFloors;
|
|
64
|
+
/**
|
|
65
|
+
* Turns a current → target floor split into concrete moves, greedily matching
|
|
66
|
+
* surpluses against deficits. The moves conserve the floor sum by
|
|
67
|
+
* construction and each one only ever sheds floor from a subaccount whose
|
|
68
|
+
* floor is above its target.
|
|
69
|
+
*/
|
|
70
|
+
function planFloorMoves(subAccountIds, current, target) {
|
|
71
|
+
const surpluses = [];
|
|
72
|
+
const deficits = [];
|
|
73
|
+
for (let i = 0; i < current.length; i++) {
|
|
74
|
+
const diff = current[i].sub(target[i]);
|
|
75
|
+
if (diff.gt(numericConstants_1.ZERO)) {
|
|
76
|
+
surpluses.push({ index: i, amount: diff });
|
|
77
|
+
}
|
|
78
|
+
else if (diff.lt(numericConstants_1.ZERO)) {
|
|
79
|
+
deficits.push({ index: i, amount: diff.neg() });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const moves = [];
|
|
83
|
+
let s = 0;
|
|
84
|
+
let d = 0;
|
|
85
|
+
while (s < surpluses.length && d < deficits.length) {
|
|
86
|
+
const delta = anchor_1.BN.min(surpluses[s].amount, deficits[d].amount);
|
|
87
|
+
moves.push({
|
|
88
|
+
fromSubAccountId: subAccountIds[surpluses[s].index],
|
|
89
|
+
toSubAccountId: subAccountIds[deficits[d].index],
|
|
90
|
+
equityFloorDelta: delta,
|
|
91
|
+
});
|
|
92
|
+
surpluses[s].amount = surpluses[s].amount.sub(delta);
|
|
93
|
+
deficits[d].amount = deficits[d].amount.sub(delta);
|
|
94
|
+
if (surpluses[s].amount.isZero()) {
|
|
95
|
+
s++;
|
|
96
|
+
}
|
|
97
|
+
if (deficits[d].amount.isZero()) {
|
|
98
|
+
d++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return moves;
|
|
102
|
+
}
|
|
103
|
+
exports.planFloorMoves = planFloorMoves;
|
|
104
|
+
const LEVEL_SEVERITY = {
|
|
105
|
+
breached: 4,
|
|
106
|
+
critical: 3,
|
|
107
|
+
warning: 2,
|
|
108
|
+
healthy: 1,
|
|
109
|
+
disabled: 0,
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* See the module doc. All reads use the subaccounts already subscribed on the
|
|
113
|
+
* wrapped `VelocityClient` (the delegate's client, whose `authority` is the
|
|
114
|
+
* subaccounts' owner); all writes go through `transferDepositByDelegate`.
|
|
115
|
+
*/
|
|
116
|
+
class EquityFloorManager {
|
|
117
|
+
constructor(velocityClient, config = {}) {
|
|
118
|
+
var _a;
|
|
119
|
+
this.velocityClient = velocityClient;
|
|
120
|
+
this.subAccountIds = config.subAccountIds;
|
|
121
|
+
this.collateralHaircut = (_a = config.collateralHaircut) !== null && _a !== void 0 ? _a : numericConstants_1.QUOTE_PRECISION;
|
|
122
|
+
}
|
|
123
|
+
getManagedUsers() {
|
|
124
|
+
const authority = this.velocityClient.authority;
|
|
125
|
+
let users = this.velocityClient
|
|
126
|
+
.getUsers()
|
|
127
|
+
.filter((user) => user.getUserAccountOrThrow().authority.equals(authority));
|
|
128
|
+
if (this.subAccountIds !== undefined) {
|
|
129
|
+
const wanted = new Set(this.subAccountIds);
|
|
130
|
+
users = users.filter((user) => wanted.has(user.getUserAccountOrThrow().subAccountId));
|
|
131
|
+
}
|
|
132
|
+
return users.sort((a, b) => a.getUserAccountOrThrow().subAccountId -
|
|
133
|
+
b.getUserAccountOrThrow().subAccountId);
|
|
134
|
+
}
|
|
135
|
+
getSubaccountStatus(user) {
|
|
136
|
+
const userAccount = user.getUserAccountOrThrow();
|
|
137
|
+
const equity = user.getNetUsdValue();
|
|
138
|
+
const bufferedFloor = userAccount.equityFloor.add(userAccount.equityFloorBuffer);
|
|
139
|
+
return {
|
|
140
|
+
subAccountId: userAccount.subAccountId,
|
|
141
|
+
equity,
|
|
142
|
+
equityFloor: userAccount.equityFloor,
|
|
143
|
+
equityFloorBuffer: userAccount.equityFloorBuffer,
|
|
144
|
+
bufferedFloor,
|
|
145
|
+
headroom: equity.sub(userAccount.equityFloor),
|
|
146
|
+
bufferedHeadroom: equity.sub(bufferedFloor),
|
|
147
|
+
level: (0, margin_1.getEquityFloorLevel)(equity, userAccount.equityFloor, userAccount.equityFloorBuffer),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** Full authority-wide standing: aggregates, worst level, per-subaccount detail. */
|
|
151
|
+
getStatus() {
|
|
152
|
+
var _a, _b, _c;
|
|
153
|
+
const subaccounts = this.getManagedUsers().map((user) => this.getSubaccountStatus(user));
|
|
154
|
+
const totalEquity = subaccounts.reduce((s, u) => s.add(u.equity), numericConstants_1.ZERO);
|
|
155
|
+
const totalFloor = subaccounts.reduce((s, u) => s.add(u.equityFloor), numericConstants_1.ZERO);
|
|
156
|
+
const totalBuffer = subaccounts.reduce((s, u) => s.add(u.equityFloorBuffer), numericConstants_1.ZERO);
|
|
157
|
+
const level = subaccounts.reduce((worst, u) => LEVEL_SEVERITY[u.level] > LEVEL_SEVERITY[worst] ? u.level : worst, 'disabled');
|
|
158
|
+
return {
|
|
159
|
+
authority: this.velocityClient.authority,
|
|
160
|
+
breakerTripped: ((_c = (_b = (_a = this.velocityClient.getUserStats()) === null || _a === void 0 ? void 0 : _a.getAccount()) === null || _b === void 0 ? void 0 : _b.equityBreakerTripped) !== null && _c !== void 0 ? _c : 0) !== 0,
|
|
161
|
+
totalEquity,
|
|
162
|
+
totalFloor,
|
|
163
|
+
totalBuffer,
|
|
164
|
+
totalBufferedHeadroom: totalEquity.sub(totalFloor).sub(totalBuffer),
|
|
165
|
+
level,
|
|
166
|
+
subaccounts,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The most quote that can leave `subAccountId` to the outside (a
|
|
171
|
+
* withdrawal, which cannot move floor): equity above the buffered floor,
|
|
172
|
+
* less the haircut. Floor constraint only — the withdrawal itself is still
|
|
173
|
+
* subject to margin and borrow limits.
|
|
174
|
+
*/
|
|
175
|
+
getMaxWithdrawable(subAccountId) {
|
|
176
|
+
const status = this.getSubaccountStatus(this.velocityClient.getUser(subAccountId, this.velocityClient.authority));
|
|
177
|
+
if (status.equityFloor.lte(numericConstants_1.ZERO)) {
|
|
178
|
+
return status.equity;
|
|
179
|
+
}
|
|
180
|
+
return anchor_1.BN.max(status.bufferedHeadroom.sub(this.collateralHaircut), numericConstants_1.ZERO);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The most quote that can move from one subaccount to another when the
|
|
184
|
+
* transfer carries floor with it. Because floor travels with the funds
|
|
185
|
+
* (capped at the floor the debited side holds, after which its check
|
|
186
|
+
* disables entirely), this is normally the debited side's whole equity —
|
|
187
|
+
* bounded by what the credited side's equity can back. Floor constraint
|
|
188
|
+
* only; margin and borrow limits still apply on top.
|
|
189
|
+
*/
|
|
190
|
+
getMaxQuoteTransferable(fromSubAccountId, toSubAccountId) {
|
|
191
|
+
const from = this.getSubaccountStatus(this.velocityClient.getUser(fromSubAccountId, this.velocityClient.authority));
|
|
192
|
+
const to = this.getSubaccountStatus(this.velocityClient.getUser(toSubAccountId, this.velocityClient.authority));
|
|
193
|
+
const haircut = this.collateralHaircut;
|
|
194
|
+
// how much floor the credited side can absorb beyond what the incoming
|
|
195
|
+
// funds themselves back: its own buffered headroom (delta <= amount
|
|
196
|
+
// keeps it backed; beyond that it eats into existing headroom)
|
|
197
|
+
const toSlack = anchor_1.BN.max(to.bufferedHeadroom.sub(haircut), numericConstants_1.ZERO);
|
|
198
|
+
if (from.equityFloor.lte(numericConstants_1.ZERO)) {
|
|
199
|
+
return anchor_1.BN.max(from.equity.sub(haircut), numericConstants_1.ZERO);
|
|
200
|
+
}
|
|
201
|
+
// shedding the entire floor disables the debited side's check; possible
|
|
202
|
+
// only if the credited side can absorb floor faster than the funds back
|
|
203
|
+
// it, i.e. it has slack of its own
|
|
204
|
+
const fullShedViable = from.equity
|
|
205
|
+
.sub(from.bufferedFloor)
|
|
206
|
+
.add(from.equityFloor)
|
|
207
|
+
.add(toSlack);
|
|
208
|
+
// without full shed: amount <= excess + floor (auto delta caps at floor)
|
|
209
|
+
const partialShedMax = anchor_1.BN.max(from.bufferedHeadroom, numericConstants_1.ZERO).add(from.equityFloor);
|
|
210
|
+
const floorwiseMax = anchor_1.BN.min(anchor_1.BN.max(fullShedViable, partialShedMax), from.equity);
|
|
211
|
+
return anchor_1.BN.max(floorwiseMax.sub(haircut), numericConstants_1.ZERO);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Resolves a quote transfer into the exact instruction parameters,
|
|
215
|
+
* padding the auto floor delta by the haircut so onchain pricing dust
|
|
216
|
+
* cannot fail it. The padded delta never exceeds the amount or the
|
|
217
|
+
* debited side's floor, so the credited side stays backed whenever it was
|
|
218
|
+
* before.
|
|
219
|
+
*/
|
|
220
|
+
planQuoteTransfer(amount, fromSubAccountId, toSubAccountId) {
|
|
221
|
+
const fromUser = this.velocityClient.getUser(fromSubAccountId, this.velocityClient.authority);
|
|
222
|
+
const fromAccount = fromUser.getUserAccountOrThrow();
|
|
223
|
+
const equityFloorDelta = (0, margin_1.calculateEquityFloorAutoDelta)(amount, fromUser.getNetUsdValue().sub(this.collateralHaircut), fromAccount.equityFloor, fromAccount.equityFloorBuffer);
|
|
224
|
+
return {
|
|
225
|
+
amount,
|
|
226
|
+
marketIndex: numericConstants_2.QUOTE_SPOT_MARKET_INDEX,
|
|
227
|
+
fromSubAccountId,
|
|
228
|
+
toSubAccountId,
|
|
229
|
+
equityFloorDelta: anchor_1.BN.min(equityFloorDelta, amount),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/** Plans and submits a quote transfer between two subaccounts in one call. */
|
|
233
|
+
async transferQuote(amount, fromSubAccountId, toSubAccountId, txParams) {
|
|
234
|
+
const plan = this.planQuoteTransfer(amount, fromSubAccountId, toSubAccountId);
|
|
235
|
+
return this.velocityClient.transferDepositByDelegate(plan.amount, plan.marketIndex, plan.fromSubAccountId, plan.toSubAccountId, plan.equityFloorDelta, txParams);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Plans the floor-only moves (zero-amount transfers) that re-split the
|
|
239
|
+
* total floor proportionally to where the equity currently sits, so every
|
|
240
|
+
* subaccount ends with the same relative headroom. Breached subaccounts
|
|
241
|
+
* (below their raw floor) cannot shed floor on-chain, so their floor is
|
|
242
|
+
* pinned in place and the rest is allocated around them. Throws when the
|
|
243
|
+
* pool's equity cannot back the total floor plus buffers — at that point
|
|
244
|
+
* no split works and equity must be deposited (or the admin must lower
|
|
245
|
+
* the floor). Each onchain move also carries a proportional share of the
|
|
246
|
+
* debited side's buffer, so buffers drift toward the same split as the
|
|
247
|
+
* floors; the plan sizes against current buffers and the haircut absorbs
|
|
248
|
+
* the drift dust, but a move can still revert if a credited side cannot
|
|
249
|
+
* back the buffer share it receives.
|
|
250
|
+
*/
|
|
251
|
+
planFloorRebalance() {
|
|
252
|
+
const subaccounts = this.getManagedUsers().map((user) => this.getSubaccountStatus(user));
|
|
253
|
+
if (subaccounts.length < 2) {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
const totalFloor = subaccounts.reduce((sum, u) => sum.add(u.equityFloor), numericConstants_1.ZERO);
|
|
257
|
+
if (totalFloor.lte(numericConstants_1.ZERO)) {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
const pinned = subaccounts.map((u) => u.level === 'breached');
|
|
261
|
+
const targets = allocateEquityFloors(totalFloor, subaccounts.map((u) => ({
|
|
262
|
+
equity: anchor_1.BN.max(u.equity.sub(this.collateralHaircut), numericConstants_1.ZERO),
|
|
263
|
+
buffer: u.equityFloorBuffer,
|
|
264
|
+
currentFloor: u.equityFloor,
|
|
265
|
+
})), pinned);
|
|
266
|
+
if (targets === null) {
|
|
267
|
+
throw new Error('no backed floor split exists: total equity cannot cover the total floor plus buffers; deposit equity or have the admin lower the floor');
|
|
268
|
+
}
|
|
269
|
+
return planFloorMoves(subaccounts.map((u) => u.subAccountId), subaccounts.map((u) => u.equityFloor), targets);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Executes `planFloorRebalance` serially. Safe to run at any time; a
|
|
273
|
+
* no-op when the split already matches the equity distribution.
|
|
274
|
+
*/
|
|
275
|
+
async rebalanceFloors(txParams) {
|
|
276
|
+
const sigs = [];
|
|
277
|
+
for (const move of this.planFloorRebalance()) {
|
|
278
|
+
sigs.push(await this.velocityClient.transferDepositByDelegate(numericConstants_1.ZERO, numericConstants_2.QUOTE_SPOT_MARKET_INDEX, move.fromSubAccountId, move.toSubAccountId, move.equityFloorDelta, txParams));
|
|
279
|
+
}
|
|
280
|
+
return sigs;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
exports.EquityFloorManager = EquityFloorManager;
|