@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.
- package/lib/browser/adminClient.d.ts +4 -3
- package/lib/browser/adminClient.js +5 -13
- package/lib/browser/equityFloorManager.d.ts +9 -5
- package/lib/browser/equityFloorManager.js +9 -5
- package/lib/browser/idl/velocity.d.ts +119 -3
- package/lib/browser/idl/velocity.json +119 -3
- package/lib/browser/index.d.ts +2 -0
- package/lib/browser/index.js +3 -0
- package/lib/browser/jupiter/jupiterClient.d.ts +31 -27
- package/lib/browser/jupiter/jupiterClient.js +61 -35
- package/lib/browser/math/amm.d.ts +6 -5
- package/lib/browser/math/amm.js +39 -19
- package/lib/browser/math/margin.d.ts +12 -10
- package/lib/browser/math/margin.js +16 -14
- 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 +46 -117
- 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 +32 -68
- package/lib/browser/titan/titanClient.js +128 -102
- package/lib/browser/types.d.ts +2 -0
- package/lib/browser/user.d.ts +16 -18
- package/lib/browser/user.js +24 -26
- package/lib/browser/velocityClient.d.ts +122 -101
- package/lib/browser/velocityClient.js +221 -289
- package/lib/node/adminClient.d.ts +4 -3
- package/lib/node/adminClient.d.ts.map +1 -1
- package/lib/node/adminClient.js +5 -13
- package/lib/node/equityFloorManager.d.ts +9 -5
- package/lib/node/equityFloorManager.d.ts.map +1 -1
- package/lib/node/equityFloorManager.js +9 -5
- package/lib/node/idl/velocity.d.ts +119 -3
- package/lib/node/idl/velocity.d.ts.map +1 -1
- package/lib/node/idl/velocity.json +119 -3
- package/lib/node/index.d.ts +2 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +3 -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 +61 -35
- package/lib/node/math/amm.d.ts +6 -5
- package/lib/node/math/amm.d.ts.map +1 -1
- package/lib/node/math/amm.js +39 -19
- package/lib/node/math/margin.d.ts +12 -10
- package/lib/node/math/margin.d.ts.map +1 -1
- package/lib/node/math/margin.js +16 -14
- 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 +46 -117
- 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 +32 -68
- package/lib/node/titan/titanClient.d.ts.map +1 -1
- package/lib/node/titan/titanClient.js +128 -102
- package/lib/node/types.d.ts +2 -0
- package/lib/node/types.d.ts.map +1 -1
- package/lib/node/user.d.ts +16 -18
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +24 -26
- package/lib/node/velocityClient.d.ts +122 -101
- package/lib/node/velocityClient.d.ts.map +1 -1
- package/lib/node/velocityClient.js +221 -289
- 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 && {
|
|
36
|
-
|
|
37
|
-
|
|
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 && {
|
|
41
|
-
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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: ((
|
|
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
|
|
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
|
-
*
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
226
|
-
|
|
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
|
|
245
|
-
|
|
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;
|
package/lib/browser/types.d.ts
CHANGED
|
@@ -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 */
|
package/lib/browser/user.d.ts
CHANGED
|
@@ -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
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
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(
|
|
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
|
|
430
|
-
*
|
|
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(
|
|
437
|
+
isBelowBufferedEquityFloor(): boolean;
|
|
438
438
|
/**
|
|
439
|
-
*
|
|
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(
|
|
444
|
+
getEquityAboveFloor(): BN | null;
|
|
446
445
|
/**
|
|
447
|
-
*
|
|
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(
|
|
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
|
package/lib/browser/user.js
CHANGED
|
@@ -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
|
|
901
|
-
*
|
|
902
|
-
*
|
|
903
|
-
*
|
|
904
|
-
*
|
|
905
|
-
*
|
|
906
|
-
|
|
907
|
-
|
|
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.
|
|
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
|
|
926
|
-
*
|
|
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(
|
|
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.
|
|
938
|
+
return this.getNetUsdValue().lt(this.getBufferedEquityFloor());
|
|
939
939
|
}
|
|
940
940
|
/**
|
|
941
|
-
*
|
|
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(
|
|
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.
|
|
951
|
+
return anchor_1.BN.max(this.getNetUsdValue().sub(equityFloor), numericConstants_1.ZERO);
|
|
953
952
|
}
|
|
954
953
|
/**
|
|
955
|
-
*
|
|
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(
|
|
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.
|
|
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
|
|
2433
|
-
//
|
|
2434
|
-
const equityAboveFloor = this.getEquityAboveFloor(
|
|
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
|
}
|