@exponent-labs/exponent-sdk 0.9.1 → 0.9.2
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/build/client/vaults/types/kaminoObligationEntry.d.ts +21 -21
- package/build/client/vaults/types/obligationType.d.ts +1 -1
- package/build/client/vaults/types/proposalAction.d.ts +54 -54
- package/build/client/vaults/types/reserveFarmMapping.d.ts +3 -3
- package/build/client/vaults/types/strategyPosition.d.ts +1 -1
- package/build/exponentVaults/index.d.ts +1 -1
- package/build/exponentVaults/index.js +3 -2
- package/build/exponentVaults/index.js.map +1 -1
- package/build/exponentVaults/vault-instruction-types.d.ts +1 -1
- package/build/exponentVaults/vault-interaction.d.ts +58 -41
- package/build/exponentVaults/vault-interaction.js +294 -54
- package/build/exponentVaults/vault-interaction.js.map +1 -1
- package/build/exponentVaults/vault-interaction.kamino-vault.test.d.ts +1 -0
- package/build/exponentVaults/vault-interaction.kamino-vault.test.js +143 -0
- package/build/exponentVaults/vault-interaction.kamino-vault.test.js.map +1 -0
- package/build/exponentVaults/vaultTransactionBuilder.js +35 -30
- package/build/exponentVaults/vaultTransactionBuilder.js.map +1 -1
- package/build/exponentVaults/vaultTransactionBuilder.test.js +84 -1
- package/build/exponentVaults/vaultTransactionBuilder.test.js.map +1 -1
- package/package.json +34 -32
- package/src/exponentVaults/index.ts +1 -0
- package/src/exponentVaults/vault-instruction-types.ts +1 -1
- package/src/exponentVaults/vault-interaction.kamino-vault.test.ts +149 -0
- package/src/exponentVaults/vault-interaction.ts +514 -86
- package/src/exponentVaults/vaultTransactionBuilder.test.ts +93 -0
- package/src/exponentVaults/vaultTransactionBuilder.ts +47 -41
|
@@ -26,9 +26,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.SwapDirection = exports.clmmAction = exports.loopscaleAction = exports.titanAction = exports.createOrderbookSyncTransaction = exports.coreAction = exports.syAction = exports.orderbookAction = exports.buildSetupStatePriceRefreshInstructions = exports.createStrategySetupContext = exports.createVaultSyncTransaction = exports.kaminoFarmAction = exports.kaminoVaultAction = exports.kaminoAction = exports.ClmmAction = exports.LoopscaleAction = exports.TitanAction = exports.SyAction = exports.CoreAction = exports.OrderbookAction = exports.OrderbookTradeDirection = exports.KaminoFarmAction = exports.KaminoVaultAction = exports.VaultAction = void 0;
|
|
29
|
+
exports.SwapDirection = exports.clmmAction = exports.loopscaleAction = exports.titanAction = exports.createOrderbookSyncTransaction = exports.coreAction = exports.syAction = exports.orderbookAction = exports.__kaminoVaultTesting = exports.buildSetupStatePriceRefreshInstructions = exports.createStrategySetupContext = exports.createVaultSyncTransaction = exports.createVaultSyncTransactions = exports.kaminoFarmAction = exports.kaminoVaultAction = exports.kaminoAction = exports.ClmmAction = exports.LoopscaleAction = exports.TitanAction = exports.SyAction = exports.CoreAction = exports.OrderbookAction = exports.OrderbookTradeDirection = exports.KaminoFarmAction = exports.KaminoVaultAction = exports.VaultAction = void 0;
|
|
30
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
30
31
|
const kamino_reserve_deserializer_1 = require("@exponent-labs/kamino-reserve-deserializer");
|
|
31
32
|
const exponent_fetcher_1 = require("@exponent-labs/exponent-fetcher");
|
|
33
|
+
const kamino_vault_idl_1 = require("@exponent-labs/kamino-vault-idl");
|
|
32
34
|
const kamino_markets_1 = require("./kamino-markets");
|
|
33
35
|
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
34
36
|
const constants_1 = require("./../../../kamino-lend-standard/src/constants");
|
|
@@ -63,6 +65,7 @@ const KAMINO_VAULT_ALLOCATION_STRATEGY_OFFSET = 304;
|
|
|
63
65
|
const KAMINO_VAULT_ALLOCATION_SIZE = 2160;
|
|
64
66
|
const KAMINO_VAULT_ALLOCATION_CTOKEN_VAULT_OFFSET = 32;
|
|
65
67
|
const KAMINO_VAULT_GLOBAL_CONFIG_SEED = Buffer.from("global_config");
|
|
68
|
+
const KAMINO_VAULT_CODER = new anchor_1.BorshCoder(kamino_vault_idl_1.IDL);
|
|
66
69
|
// ============================================================================
|
|
67
70
|
// Vault Instruction Types (re-exported from vault-instruction-types.ts)
|
|
68
71
|
// ============================================================================
|
|
@@ -181,8 +184,8 @@ exports.kaminoVaultAction = {
|
|
|
181
184
|
},
|
|
182
185
|
/**
|
|
183
186
|
* Withdraw Kamino Vault shares back into the vault-owned token account.
|
|
184
|
-
*
|
|
185
|
-
*
|
|
187
|
+
* `reserve` is an optional override. When omitted, the SDK plans the
|
|
188
|
+
* withdraw across the vault's active reserves automatically.
|
|
186
189
|
*/
|
|
187
190
|
withdraw(params) {
|
|
188
191
|
return { action: vault_instruction_types_2.KaminoVaultAction.WITHDRAW, ...params };
|
|
@@ -218,7 +221,19 @@ exports.kaminoFarmAction = {
|
|
|
218
221
|
},
|
|
219
222
|
};
|
|
220
223
|
/**
|
|
221
|
-
* Build
|
|
224
|
+
* Build one or more vault sync transactions from high-level instruction descriptors.
|
|
225
|
+
*
|
|
226
|
+
* This is the plural companion to {@link createVaultSyncTransaction}. Most
|
|
227
|
+
* calls return a single result. Smart Kamino Vault withdraws can expand into
|
|
228
|
+
* multiple reserve-specific sync transactions when the wrapper needs to split
|
|
229
|
+
* an oversized withdraw across sequential chunks.
|
|
230
|
+
*/
|
|
231
|
+
async function createVaultSyncTransactions(params) {
|
|
232
|
+
return buildVaultSyncTransactionResults(params, true);
|
|
233
|
+
}
|
|
234
|
+
exports.createVaultSyncTransactions = createVaultSyncTransactions;
|
|
235
|
+
/**
|
|
236
|
+
* Build exactly one vault sync transaction and return its wrapped instruction set.
|
|
222
237
|
*
|
|
223
238
|
* Takes high-level `VaultInstruction` descriptors (built with `kamino.*`),
|
|
224
239
|
* resolves them to raw Solana instructions, then separates them:
|
|
@@ -228,27 +243,16 @@ exports.kaminoFarmAction = {
|
|
|
228
243
|
* KLend's `check_refresh` requires refreshReserve to be a top-level instruction
|
|
229
244
|
* so it can be found via the instruction sysvar.
|
|
230
245
|
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* @
|
|
234
|
-
* ```ts
|
|
235
|
-
* const { setupInstructions, preInstructions, instruction, postInstructions, signers, addressLookupTableAddresses } = await createVaultSyncTransaction({
|
|
236
|
-
* instructions: [
|
|
237
|
-
* kamino.initUserMetadata(KaminoMarket.MAIN),
|
|
238
|
-
* kamino.initObligation(KaminoMarket.MAIN),
|
|
239
|
-
* kamino.deposit(KaminoMarket.MAIN, "USDC", new BN(1_000_000)),
|
|
240
|
-
* ],
|
|
241
|
-
* owner: vaultPda,
|
|
242
|
-
* connection,
|
|
243
|
-
* policyPda,
|
|
244
|
-
* vaultPda,
|
|
245
|
-
* signer: wallet.publicKey,
|
|
246
|
-
* vaultAddress: VAULT_ADDRESS,
|
|
247
|
-
* })
|
|
248
|
-
* // Send: [...setupInstructions, ...preInstructions, instruction, ...postInstructions]
|
|
249
|
-
* ```
|
|
246
|
+
* When a smart Kamino Vault withdraw expands beyond one sync transaction, this
|
|
247
|
+
* singular helper throws and asks the caller to use
|
|
248
|
+
* {@link createVaultSyncTransactions} or {@link VaultTransactionBuilder}.
|
|
250
249
|
*/
|
|
251
|
-
async function createVaultSyncTransaction(
|
|
250
|
+
async function createVaultSyncTransaction(params) {
|
|
251
|
+
const results = await buildVaultSyncTransactionResults(params, false);
|
|
252
|
+
return results[0];
|
|
253
|
+
}
|
|
254
|
+
exports.createVaultSyncTransaction = createVaultSyncTransaction;
|
|
255
|
+
async function buildVaultSyncTransactionResults({ instructions, owner, connection, policyPda, vaultPda, signer, accountIndex = 0, constraintIndices, vaultAddress, leadingAccounts, preHookAccounts, postHookAccounts, squadsProgram = syncTransaction_1.SQUADS_PROGRAM_ID, autoManagePositions = false, setupContext, }, splitOversizedKaminoVaultWithdraw) {
|
|
252
256
|
vaultPda ??= owner;
|
|
253
257
|
const resolvedSetupContext = setupContext ?? createStrategySetupContext({
|
|
254
258
|
connection,
|
|
@@ -265,10 +269,92 @@ async function createVaultSyncTransaction({ instructions, owner, connection, pol
|
|
|
265
269
|
postHookAccounts,
|
|
266
270
|
autoManagePositions,
|
|
267
271
|
});
|
|
268
|
-
const
|
|
272
|
+
const buckets = await buildVaultInstructions(instructions, owner, connection, signer, policyPda, vaultPda, accountIndex, vaultAddress, leadingAccounts, preHookAccounts, postHookAccounts, squadsProgram, autoManagePositions, resolvedSetupContext);
|
|
269
273
|
const setupStatePriceRefreshInstructions = setupContext
|
|
270
274
|
? []
|
|
271
275
|
: await buildSetupStatePriceRefreshInstructions(resolvedSetupContext);
|
|
276
|
+
const fullResult = await buildWrappedVaultSyncTransactionResult({
|
|
277
|
+
connection,
|
|
278
|
+
policyPda,
|
|
279
|
+
vaultPda,
|
|
280
|
+
signer,
|
|
281
|
+
accountIndex,
|
|
282
|
+
constraintIndices,
|
|
283
|
+
vaultAddress,
|
|
284
|
+
leadingAccounts,
|
|
285
|
+
preHookAccounts,
|
|
286
|
+
postHookAccounts,
|
|
287
|
+
squadsProgram,
|
|
288
|
+
syncInstructions: buckets.syncInstructions,
|
|
289
|
+
setupInstructions: buckets.setupInstructions,
|
|
290
|
+
preInstructions: [...setupStatePriceRefreshInstructions, ...buckets.preInstructions],
|
|
291
|
+
postInstructions: buckets.postInstructions,
|
|
292
|
+
signers: buckets.signers,
|
|
293
|
+
addressLookupTableAddresses: buckets.addressLookupTableAddresses,
|
|
294
|
+
});
|
|
295
|
+
const isPlannerExpandedKaminoVaultWithdraw = instructions.length === 1
|
|
296
|
+
&& instructions[0]?.action === vault_instruction_types_2.KaminoVaultAction.WITHDRAW
|
|
297
|
+
&& !instructions[0].reserve
|
|
298
|
+
&& buckets.syncInstructions.length > 1;
|
|
299
|
+
if (!isPlannerExpandedKaminoVaultWithdraw) {
|
|
300
|
+
return [fullResult];
|
|
301
|
+
}
|
|
302
|
+
if (await vaultSyncResultFitsInSingleTransaction({
|
|
303
|
+
connection,
|
|
304
|
+
payer: signer,
|
|
305
|
+
result: fullResult,
|
|
306
|
+
})) {
|
|
307
|
+
return [fullResult];
|
|
308
|
+
}
|
|
309
|
+
if (!splitOversizedKaminoVaultWithdraw) {
|
|
310
|
+
throw new Error("Kamino Vault withdraw expands beyond one sync transaction; use createVaultSyncTransactions() or VaultTransactionBuilder.");
|
|
311
|
+
}
|
|
312
|
+
if (buckets.preInstructions.length > 0 || buckets.postInstructions.length > 0) {
|
|
313
|
+
throw new Error("Kamino Vault withdraw chunking only supports sync-only actions; use explicit reserve overrides if you need custom refresh handling.");
|
|
314
|
+
}
|
|
315
|
+
const splitResults = [];
|
|
316
|
+
for (const [index, syncInstruction] of buckets.syncInstructions.entries()) {
|
|
317
|
+
const chunkConstraintIndices = constraintIndices
|
|
318
|
+
? constraintIndices.length === buckets.syncInstructions.length
|
|
319
|
+
? [constraintIndices[index]]
|
|
320
|
+
: constraintIndices.length === 1
|
|
321
|
+
? constraintIndices
|
|
322
|
+
: (() => {
|
|
323
|
+
throw new Error("constraintIndices must contain either one entry or one entry per generated sync instruction when splitting a Kamino Vault withdraw.");
|
|
324
|
+
})()
|
|
325
|
+
: undefined;
|
|
326
|
+
const chunkResult = await buildWrappedVaultSyncTransactionResult({
|
|
327
|
+
connection,
|
|
328
|
+
policyPda,
|
|
329
|
+
vaultPda,
|
|
330
|
+
signer,
|
|
331
|
+
accountIndex,
|
|
332
|
+
constraintIndices: chunkConstraintIndices,
|
|
333
|
+
vaultAddress,
|
|
334
|
+
leadingAccounts,
|
|
335
|
+
preHookAccounts,
|
|
336
|
+
postHookAccounts,
|
|
337
|
+
squadsProgram,
|
|
338
|
+
syncInstructions: [syncInstruction],
|
|
339
|
+
setupInstructions: index === 0 ? buckets.setupInstructions : [],
|
|
340
|
+
preInstructions: index === 0 ? [...setupStatePriceRefreshInstructions] : [],
|
|
341
|
+
postInstructions: [],
|
|
342
|
+
signers: buckets.signers,
|
|
343
|
+
addressLookupTableAddresses: buckets.addressLookupTableAddresses,
|
|
344
|
+
});
|
|
345
|
+
const chunkFits = await vaultSyncResultFitsInSingleTransaction({
|
|
346
|
+
connection,
|
|
347
|
+
payer: signer,
|
|
348
|
+
result: chunkResult,
|
|
349
|
+
});
|
|
350
|
+
if (!chunkFits) {
|
|
351
|
+
throw new Error(`Kamino Vault withdraw chunk ${index + 1} still exceeds one sync transaction; use explicit reserve overrides or smaller steps.`);
|
|
352
|
+
}
|
|
353
|
+
splitResults.push(chunkResult);
|
|
354
|
+
}
|
|
355
|
+
return splitResults;
|
|
356
|
+
}
|
|
357
|
+
async function buildWrappedVaultSyncTransactionResult({ connection, policyPda, vaultPda, signer, accountIndex, constraintIndices, vaultAddress, leadingAccounts, preHookAccounts, postHookAccounts, squadsProgram, syncInstructions, setupInstructions, preInstructions, postInstructions, signers, addressLookupTableAddresses, }) {
|
|
272
358
|
let resolvedPolicyPda = policyPda;
|
|
273
359
|
let resolvedConstraintIndices = constraintIndices;
|
|
274
360
|
if (!resolvedPolicyPda) {
|
|
@@ -279,9 +365,7 @@ async function createVaultSyncTransaction({ instructions, owner, connection, pol
|
|
|
279
365
|
resolvedPolicyPda = match.policyPda;
|
|
280
366
|
resolvedConstraintIndices ??= match.constraintIndices;
|
|
281
367
|
}
|
|
282
|
-
// Auto-resolve constraint indices when not explicitly provided
|
|
283
368
|
resolvedConstraintIndices ??= await (0, policyMatcher_1.resolveConstraintIndices)(connection, resolvedPolicyPda, syncInstructions);
|
|
284
|
-
// Auto-resolve policy prefix and hook accounts when not explicitly provided
|
|
285
369
|
let resolvedLeadingAccounts = leadingAccounts;
|
|
286
370
|
let resolvedPreHookAccounts = preHookAccounts;
|
|
287
371
|
let resolvedPostHookAccounts = postHookAccounts;
|
|
@@ -305,14 +389,33 @@ async function createVaultSyncTransaction({ instructions, owner, connection, pol
|
|
|
305
389
|
});
|
|
306
390
|
return {
|
|
307
391
|
setupInstructions,
|
|
308
|
-
preInstructions
|
|
392
|
+
preInstructions,
|
|
309
393
|
instruction,
|
|
310
394
|
postInstructions,
|
|
311
395
|
signers,
|
|
312
396
|
addressLookupTableAddresses,
|
|
313
397
|
};
|
|
314
398
|
}
|
|
315
|
-
|
|
399
|
+
async function vaultSyncResultFitsInSingleTransaction(params) {
|
|
400
|
+
try {
|
|
401
|
+
const altAccounts = await resolveVaultSyncAltAccounts(params.connection, params.result.addressLookupTableAddresses);
|
|
402
|
+
const message = new web3_js_1.TransactionMessage({
|
|
403
|
+
payerKey: params.payer,
|
|
404
|
+
recentBlockhash: web3_js_1.PublicKey.default.toBase58(),
|
|
405
|
+
instructions: [...params.result.preInstructions, params.result.instruction, ...params.result.postInstructions],
|
|
406
|
+
}).compileToV0Message(altAccounts);
|
|
407
|
+
return new web3_js_1.VersionedTransaction(message).serialize().length <= 1232;
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function resolveVaultSyncAltAccounts(connection, addresses) {
|
|
414
|
+
const lookupTables = await Promise.all(addresses.map((address) => connection.getAddressLookupTable(address)));
|
|
415
|
+
return lookupTables
|
|
416
|
+
.map((entry) => entry.value)
|
|
417
|
+
.filter((entry) => entry !== null);
|
|
418
|
+
}
|
|
316
419
|
// ============================================================================
|
|
317
420
|
// Internal: Instruction Assembly
|
|
318
421
|
// ============================================================================
|
|
@@ -1095,6 +1198,9 @@ function toBn(value) {
|
|
|
1095
1198
|
}
|
|
1096
1199
|
return new bn_js_1.default(value.toString());
|
|
1097
1200
|
}
|
|
1201
|
+
function minBn(lhs, rhs) {
|
|
1202
|
+
return lhs.lte(rhs) ? lhs : rhs;
|
|
1203
|
+
}
|
|
1098
1204
|
function encodeU64InstructionData(discriminator, value) {
|
|
1099
1205
|
return Buffer.concat([discriminator, toBn(value).toArrayLike(Buffer, "le", 8)]);
|
|
1100
1206
|
}
|
|
@@ -1151,6 +1257,94 @@ function resolveKaminoVaultTrackedPriceId(params) {
|
|
|
1151
1257
|
const reservePriceIds = (0, pricePathResolver_1.extractPriceIds)(reservePriceId).map((id) => BigInt(id));
|
|
1152
1258
|
return { __kind: "Multiply", priceIds: [...reservePriceIds, params.sharePriceId] };
|
|
1153
1259
|
}
|
|
1260
|
+
function decodeKaminoVaultState(data) {
|
|
1261
|
+
const decoded = KAMINO_VAULT_CODER.accounts.decode("VaultState", data);
|
|
1262
|
+
return {
|
|
1263
|
+
tokenAvailable: decoded.token_available,
|
|
1264
|
+
sharesIssued: decoded.shares_issued,
|
|
1265
|
+
pendingFeesSf: decoded.pending_fees_sf,
|
|
1266
|
+
allocations: decoded.vault_allocation_strategy
|
|
1267
|
+
.filter((allocation) => !allocation.reserve.equals(web3_js_1.PublicKey.default))
|
|
1268
|
+
.map((allocation) => ({
|
|
1269
|
+
reserve: allocation.reserve,
|
|
1270
|
+
ctokenAllocation: allocation.ctoken_allocation,
|
|
1271
|
+
})),
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
function calculateKaminoVaultTokensPerShareFromSnapshot(snapshot) {
|
|
1275
|
+
if (snapshot.sharesIssued.isZero()) {
|
|
1276
|
+
return new decimal_js_1.default(0);
|
|
1277
|
+
}
|
|
1278
|
+
const investedTotal = snapshot.reserves.reduce((total, reserve) => total.add(reserve.investedLiquidityAmount), new decimal_js_1.default(0));
|
|
1279
|
+
const pendingFees = new kamino_reserve_deserializer_1.Fraction(snapshot.pendingFeesSf).toDecimal();
|
|
1280
|
+
const currentVaultAum = new decimal_js_1.default(snapshot.tokenAvailable.toString())
|
|
1281
|
+
.add(investedTotal)
|
|
1282
|
+
.sub(pendingFees);
|
|
1283
|
+
if (currentVaultAum.lte(0)) {
|
|
1284
|
+
return new decimal_js_1.default(0);
|
|
1285
|
+
}
|
|
1286
|
+
return currentVaultAum.div(snapshot.sharesIssued.toString());
|
|
1287
|
+
}
|
|
1288
|
+
function planKaminoVaultWithdrawLegsFromSnapshot(params) {
|
|
1289
|
+
if (params.reserve) {
|
|
1290
|
+
return [{ reserveAddress: params.reserve, sharesAmount: params.sharesAmount }];
|
|
1291
|
+
}
|
|
1292
|
+
const actualSharesToWithdraw = minBn(params.sharesAmount, params.snapshot.sharesBalance);
|
|
1293
|
+
if (actualSharesToWithdraw.isZero()) {
|
|
1294
|
+
throw new Error("Cannot withdraw zero Kamino Vault shares");
|
|
1295
|
+
}
|
|
1296
|
+
if (params.snapshot.reserves.length === 0) {
|
|
1297
|
+
throw new Error("Kamino Vault has no active reserves to anchor a reserve-specific withdraw instruction");
|
|
1298
|
+
}
|
|
1299
|
+
const withdrawAllShares = params.sharesAmount.gte(params.snapshot.sharesBalance);
|
|
1300
|
+
const tokensPerShare = calculateKaminoVaultTokensPerShareFromSnapshot(params.snapshot);
|
|
1301
|
+
if (tokensPerShare.lte(0)) {
|
|
1302
|
+
throw new Error("Kamino Vault has zero share price; cannot plan a withdraw");
|
|
1303
|
+
}
|
|
1304
|
+
const tokensToWithdraw = new decimal_js_1.default(actualSharesToWithdraw.toString()).mul(tokensPerShare);
|
|
1305
|
+
const availableTokens = new decimal_js_1.default(params.snapshot.tokenAvailable.toString());
|
|
1306
|
+
if (tokensToWithdraw.lte(availableTokens)) {
|
|
1307
|
+
return [{
|
|
1308
|
+
reserveAddress: params.snapshot.reserves[0].reserveAddress,
|
|
1309
|
+
sharesAmount: withdrawAllShares ? params.snapshot.sharesBalance : actualSharesToWithdraw,
|
|
1310
|
+
}];
|
|
1311
|
+
}
|
|
1312
|
+
let tokensRemaining = tokensToWithdraw;
|
|
1313
|
+
const plannedLegs = [];
|
|
1314
|
+
const sortedReserves = [...params.snapshot.reserves].sort((lhs, rhs) => rhs.availableLiquidityToWithdraw.comparedTo(lhs.availableLiquidityToWithdraw));
|
|
1315
|
+
for (const [index, reserve] of sortedReserves.entries()) {
|
|
1316
|
+
const legCapacity = reserve.availableLiquidityToWithdraw.add(index === 0 ? availableTokens : 0);
|
|
1317
|
+
if (legCapacity.lte(0)) {
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
const tokensForLeg = decimal_js_1.default.min(tokensRemaining, legCapacity);
|
|
1321
|
+
if (tokensForLeg.lte(0)) {
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
const sharesForLeg = withdrawAllShares
|
|
1325
|
+
? params.snapshot.sharesBalance
|
|
1326
|
+
: new bn_js_1.default(tokensForLeg.div(tokensPerShare).floor().toFixed(0));
|
|
1327
|
+
if (sharesForLeg.isZero()) {
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
plannedLegs.push({
|
|
1331
|
+
reserveAddress: reserve.reserveAddress,
|
|
1332
|
+
sharesAmount: sharesForLeg,
|
|
1333
|
+
});
|
|
1334
|
+
tokensRemaining = tokensRemaining.sub(tokensForLeg);
|
|
1335
|
+
if (tokensRemaining.lte(0)) {
|
|
1336
|
+
break;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (plannedLegs.length === 0) {
|
|
1340
|
+
throw new Error("Unable to plan a Kamino Vault withdraw across the vault's active reserves");
|
|
1341
|
+
}
|
|
1342
|
+
return plannedLegs;
|
|
1343
|
+
}
|
|
1344
|
+
exports.__kaminoVaultTesting = {
|
|
1345
|
+
calculateKaminoVaultTokensPerShareFromSnapshot,
|
|
1346
|
+
planKaminoVaultWithdrawLegsFromSnapshot,
|
|
1347
|
+
};
|
|
1154
1348
|
async function resolveKaminoVaultContext(vaultAddress, setupContext) {
|
|
1155
1349
|
const rawIndex = await (0, exponent_fetcher_1.fetchKaminoVaultIndex)({
|
|
1156
1350
|
connection: setupContext.connection,
|
|
@@ -1166,6 +1360,7 @@ async function resolveKaminoVaultContext(vaultAddress, setupContext) {
|
|
|
1166
1360
|
if (!vaultInfo?.data) {
|
|
1167
1361
|
throw new Error(`Kamino vault account not found: ${vaultAddress.toBase58()}`);
|
|
1168
1362
|
}
|
|
1363
|
+
const decodedVaultState = decodeKaminoVaultState(Buffer.from(vaultInfo.data));
|
|
1169
1364
|
const decodedReserves = reserveInfos.map((reserveInfo, index) => {
|
|
1170
1365
|
if (!reserveInfo?.data) {
|
|
1171
1366
|
throw new Error(`Missing Kamino reserve account ${reserveAddresses[index].toBase58()}`);
|
|
@@ -1175,12 +1370,17 @@ async function resolveKaminoVaultContext(vaultAddress, setupContext) {
|
|
|
1175
1370
|
const collateralMintInfos = await setupContext.connection.getMultipleAccountsInfo(decodedReserves.map((reserve) => reserve.collateral.mintPubkey));
|
|
1176
1371
|
const normalizedReserves = reserveAddresses.map((reserveAddress, index) => {
|
|
1177
1372
|
const reserveAccount = decodedReserves[index];
|
|
1373
|
+
const allocation = decodedVaultState.allocations[index];
|
|
1178
1374
|
const rawReserve = rawIndex.reserves[index];
|
|
1179
1375
|
const allocationOffset = KAMINO_VAULT_ACCOUNT_DISCRIMINATOR_LEN
|
|
1180
1376
|
+ KAMINO_VAULT_ALLOCATION_STRATEGY_OFFSET
|
|
1181
1377
|
+ (index * KAMINO_VAULT_ALLOCATION_SIZE);
|
|
1182
1378
|
const ctokenVaultOffset = allocationOffset + KAMINO_VAULT_ALLOCATION_CTOKEN_VAULT_OFFSET;
|
|
1183
1379
|
const [lendingMarketAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("lma"), reserveAccount.lendingMarket.toBuffer()], policyBuilders_1.KAMINO_LENDING_PROGRAM_ID);
|
|
1380
|
+
const investedLiquidityAmount = allocation
|
|
1381
|
+
? new decimal_js_1.default(allocation.ctokenAllocation.toString()).mul(reserveAccount.getCollateralExchangeRate())
|
|
1382
|
+
: new decimal_js_1.default(0);
|
|
1383
|
+
const availableLiquidityToWithdraw = decimal_js_1.default.min(investedLiquidityAmount, reserveAccount.getLiquidityAvailableAmount());
|
|
1184
1384
|
return {
|
|
1185
1385
|
reserveAddress,
|
|
1186
1386
|
marketAddress: rawReserve.marketAddress ?? rawReserve.reserve ?? reserveAccount.lendingMarket,
|
|
@@ -1196,6 +1396,9 @@ async function resolveKaminoVaultContext(vaultAddress, setupContext) {
|
|
|
1196
1396
|
reserveLiquiditySupply: rawReserve.reserveLiquiditySupply ?? reserveAccount.liquidity.supplyVault,
|
|
1197
1397
|
reserveCollateralMint: rawReserve.reserveCollateralMint ?? reserveAccount.collateral.mintPubkey,
|
|
1198
1398
|
reserveCollateralTokenProgram: rawReserve.reserveCollateralTokenProgram ?? collateralMintInfos[index]?.owner ?? web3_js_1.PublicKey.default,
|
|
1399
|
+
account: reserveAccount,
|
|
1400
|
+
investedLiquidityAmount,
|
|
1401
|
+
availableLiquidityToWithdraw,
|
|
1199
1402
|
};
|
|
1200
1403
|
});
|
|
1201
1404
|
const index = {
|
|
@@ -1209,7 +1412,11 @@ async function resolveKaminoVaultContext(vaultAddress, setupContext) {
|
|
|
1209
1412
|
};
|
|
1210
1413
|
const tokenAta = (0, spl_token_1.getAssociatedTokenAddressSync)(index.tokenMint, setupContext.owner, true, index.tokenProgram);
|
|
1211
1414
|
const sharesAta = (0, spl_token_1.getAssociatedTokenAddressSync)(index.sharesMint, setupContext.owner, true, index.sharesTokenProgram);
|
|
1212
|
-
|
|
1415
|
+
const sharesAtaInfo = await setupContext.connection.getAccountInfo(sharesAta);
|
|
1416
|
+
const sharesBalance = sharesAtaInfo?.data
|
|
1417
|
+
? new bn_js_1.default(spl_token_1.AccountLayout.decode(sharesAtaInfo.data).amount.toString())
|
|
1418
|
+
: new bn_js_1.default(0);
|
|
1419
|
+
return { index, tokenAta, sharesAta, sharesBalance, state: decodedVaultState };
|
|
1213
1420
|
}
|
|
1214
1421
|
async function queueKaminoVaultSharesTracking(params) {
|
|
1215
1422
|
const state = await loadStrategySetupState(params.setupContext);
|
|
@@ -1337,21 +1544,49 @@ function buildKaminoVaultDepositInstruction(params) {
|
|
|
1337
1544
|
data: encodeU64InstructionData(policyBuilders_1.KAMINO_VAULT_DISCRIMINATORS.deposit, params.amount),
|
|
1338
1545
|
});
|
|
1339
1546
|
}
|
|
1340
|
-
function
|
|
1547
|
+
function resolveExplicitKaminoVaultWithdrawReserve(ix, vaultContext) {
|
|
1548
|
+
if (!ix.reserve) {
|
|
1549
|
+
throw new Error("reserve is required when resolving an explicit Kamino Vault withdraw reserve");
|
|
1550
|
+
}
|
|
1551
|
+
const reserve = vaultContext.index.reserves.find((entry) => entry.reserveAddress.equals(ix.reserve));
|
|
1552
|
+
if (!reserve) {
|
|
1553
|
+
throw new Error(`Kamino Vault ${ix.vault.toBase58()} does not use reserve ${ix.reserve.toBase58()}`);
|
|
1554
|
+
}
|
|
1555
|
+
return reserve;
|
|
1556
|
+
}
|
|
1557
|
+
function planKaminoVaultWithdrawLegs(ix, vaultContext) {
|
|
1341
1558
|
if (ix.reserve) {
|
|
1342
|
-
|
|
1559
|
+
return [{
|
|
1560
|
+
reserve: resolveExplicitKaminoVaultWithdrawReserve(ix, vaultContext),
|
|
1561
|
+
sharesAmount: ix.sharesAmount,
|
|
1562
|
+
}];
|
|
1563
|
+
}
|
|
1564
|
+
const plannedLegs = planKaminoVaultWithdrawLegsFromSnapshot({
|
|
1565
|
+
sharesAmount: ix.sharesAmount,
|
|
1566
|
+
snapshot: {
|
|
1567
|
+
sharesBalance: vaultContext.sharesBalance,
|
|
1568
|
+
tokenAvailable: vaultContext.state.tokenAvailable,
|
|
1569
|
+
sharesIssued: vaultContext.state.sharesIssued,
|
|
1570
|
+
pendingFeesSf: vaultContext.state.pendingFeesSf,
|
|
1571
|
+
reserves: vaultContext.index.reserves.map((reserve) => ({
|
|
1572
|
+
reserveAddress: reserve.reserveAddress,
|
|
1573
|
+
investedLiquidityAmount: reserve.investedLiquidityAmount,
|
|
1574
|
+
availableLiquidityToWithdraw: reserve.availableLiquidityToWithdraw,
|
|
1575
|
+
})),
|
|
1576
|
+
},
|
|
1577
|
+
});
|
|
1578
|
+
return plannedLegs.map((leg) => {
|
|
1579
|
+
const reserve = vaultContext.index.reserves.find((entry) => entry.reserveAddress.equals(leg.reserveAddress));
|
|
1343
1580
|
if (!reserve) {
|
|
1344
|
-
throw new Error(`Kamino Vault ${ix.vault.toBase58()} does not use reserve ${
|
|
1581
|
+
throw new Error(`Kamino Vault ${ix.vault.toBase58()} does not use reserve ${leg.reserveAddress.toBase58()}`);
|
|
1345
1582
|
}
|
|
1346
|
-
return
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
}
|
|
1351
|
-
return vaultContext.index.reserves[0];
|
|
1583
|
+
return {
|
|
1584
|
+
reserve,
|
|
1585
|
+
sharesAmount: leg.sharesAmount,
|
|
1586
|
+
};
|
|
1587
|
+
});
|
|
1352
1588
|
}
|
|
1353
1589
|
function buildKaminoVaultWithdrawInstruction(params) {
|
|
1354
|
-
const reserve = resolveKaminoVaultWithdrawReserve(params.ix, params.vaultContext);
|
|
1355
1590
|
const [globalConfig] = web3_js_1.PublicKey.findProgramAddressSync([KAMINO_VAULT_GLOBAL_CONFIG_SEED], policyBuilders_1.KAMINO_VAULT_PROGRAM_ID);
|
|
1356
1591
|
return new web3_js_1.TransactionInstruction({
|
|
1357
1592
|
programId: policyBuilders_1.KAMINO_VAULT_PROGRAM_ID,
|
|
@@ -1371,13 +1606,13 @@ function buildKaminoVaultWithdrawInstruction(params) {
|
|
|
1371
1606
|
{ pubkey: KAMINO_VAULT_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
1372
1607
|
{ pubkey: policyBuilders_1.KAMINO_VAULT_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1373
1608
|
{ pubkey: params.ix.vault, isSigner: false, isWritable: true },
|
|
1374
|
-
{ pubkey: reserve.reserveAddress, isSigner: false, isWritable: true },
|
|
1375
|
-
{ pubkey: reserve.ctokenVault, isSigner: false, isWritable: true },
|
|
1376
|
-
{ pubkey: reserve.marketAddress, isSigner: false, isWritable: false },
|
|
1377
|
-
{ pubkey: reserve.lendingMarketAuthority, isSigner: false, isWritable: false },
|
|
1378
|
-
{ pubkey: reserve.reserveLiquiditySupply, isSigner: false, isWritable: true },
|
|
1379
|
-
{ pubkey: reserve.reserveCollateralMint, isSigner: false, isWritable: true },
|
|
1380
|
-
{ pubkey: reserve.reserveCollateralTokenProgram, isSigner: false, isWritable: false },
|
|
1609
|
+
{ pubkey: params.reserve.reserveAddress, isSigner: false, isWritable: true },
|
|
1610
|
+
{ pubkey: params.reserve.ctokenVault, isSigner: false, isWritable: true },
|
|
1611
|
+
{ pubkey: params.reserve.marketAddress, isSigner: false, isWritable: false },
|
|
1612
|
+
{ pubkey: params.reserve.lendingMarketAuthority, isSigner: false, isWritable: false },
|
|
1613
|
+
{ pubkey: params.reserve.reserveLiquiditySupply, isSigner: false, isWritable: true },
|
|
1614
|
+
{ pubkey: params.reserve.reserveCollateralMint, isSigner: false, isWritable: true },
|
|
1615
|
+
{ pubkey: params.reserve.reserveCollateralTokenProgram, isSigner: false, isWritable: false },
|
|
1381
1616
|
{ pubkey: web3_js_1.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
|
|
1382
1617
|
{ pubkey: KAMINO_VAULT_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
1383
1618
|
{ pubkey: policyBuilders_1.KAMINO_VAULT_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
@@ -1388,7 +1623,7 @@ function buildKaminoVaultWithdrawInstruction(params) {
|
|
|
1388
1623
|
})),
|
|
1389
1624
|
...(params.validationAccounts ?? []),
|
|
1390
1625
|
],
|
|
1391
|
-
data: encodeU64InstructionData(policyBuilders_1.KAMINO_VAULT_DISCRIMINATORS.withdraw, params.
|
|
1626
|
+
data: encodeU64InstructionData(policyBuilders_1.KAMINO_VAULT_DISCRIMINATORS.withdraw, params.sharesAmount),
|
|
1392
1627
|
});
|
|
1393
1628
|
}
|
|
1394
1629
|
async function buildKaminoVaultInstruction(ix, buckets, setupContext) {
|
|
@@ -1430,12 +1665,17 @@ async function buildKaminoVaultInstruction(ix, buckets, setupContext) {
|
|
|
1430
1665
|
}
|
|
1431
1666
|
return;
|
|
1432
1667
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1668
|
+
const withdrawLegs = planKaminoVaultWithdrawLegs(ix, vaultContext);
|
|
1669
|
+
for (const leg of withdrawLegs) {
|
|
1670
|
+
buckets.syncInstructions.push(buildKaminoVaultWithdrawInstruction({
|
|
1671
|
+
owner: setupContext.owner,
|
|
1672
|
+
ix,
|
|
1673
|
+
reserve: leg.reserve,
|
|
1674
|
+
sharesAmount: leg.sharesAmount,
|
|
1675
|
+
vaultContext,
|
|
1676
|
+
validationAccounts,
|
|
1677
|
+
}));
|
|
1678
|
+
}
|
|
1439
1679
|
}
|
|
1440
1680
|
function getOptionalReadonlyAccountMeta(account, placeholderProgram) {
|
|
1441
1681
|
return {
|