@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.
Files changed (26) hide show
  1. package/build/client/vaults/types/kaminoObligationEntry.d.ts +21 -21
  2. package/build/client/vaults/types/obligationType.d.ts +1 -1
  3. package/build/client/vaults/types/proposalAction.d.ts +54 -54
  4. package/build/client/vaults/types/reserveFarmMapping.d.ts +3 -3
  5. package/build/client/vaults/types/strategyPosition.d.ts +1 -1
  6. package/build/exponentVaults/index.d.ts +1 -1
  7. package/build/exponentVaults/index.js +3 -2
  8. package/build/exponentVaults/index.js.map +1 -1
  9. package/build/exponentVaults/vault-instruction-types.d.ts +1 -1
  10. package/build/exponentVaults/vault-interaction.d.ts +58 -41
  11. package/build/exponentVaults/vault-interaction.js +294 -54
  12. package/build/exponentVaults/vault-interaction.js.map +1 -1
  13. package/build/exponentVaults/vault-interaction.kamino-vault.test.d.ts +1 -0
  14. package/build/exponentVaults/vault-interaction.kamino-vault.test.js +143 -0
  15. package/build/exponentVaults/vault-interaction.kamino-vault.test.js.map +1 -0
  16. package/build/exponentVaults/vaultTransactionBuilder.js +35 -30
  17. package/build/exponentVaults/vaultTransactionBuilder.js.map +1 -1
  18. package/build/exponentVaults/vaultTransactionBuilder.test.js +84 -1
  19. package/build/exponentVaults/vaultTransactionBuilder.test.js.map +1 -1
  20. package/package.json +34 -32
  21. package/src/exponentVaults/index.ts +1 -0
  22. package/src/exponentVaults/vault-instruction-types.ts +1 -1
  23. package/src/exponentVaults/vault-interaction.kamino-vault.test.ts +149 -0
  24. package/src/exponentVaults/vault-interaction.ts +514 -86
  25. package/src/exponentVaults/vaultTransactionBuilder.test.ts +93 -0
  26. 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
- * When the Kamino Vault currently allocates across multiple reserves,
185
- * specify the reserve to disinvest from.
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 vault instructions and wrap them in a Squads sync transaction.
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
- * @returns `{ setupInstructions, preInstructions, instruction, postInstructions, signers, addressLookupTableAddresses }`
232
- *
233
- * @example
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({ instructions, owner, connection, policyPda, vaultPda, signer, accountIndex = 0, constraintIndices, vaultAddress, leadingAccounts, preHookAccounts, postHookAccounts, squadsProgram = syncTransaction_1.SQUADS_PROGRAM_ID, autoManagePositions = false, setupContext, }) {
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 { setupInstructions, syncInstructions, preInstructions, postInstructions, signers, addressLookupTableAddresses } = await buildVaultInstructions(instructions, owner, connection, signer, policyPda, vaultPda, accountIndex, vaultAddress, leadingAccounts, preHookAccounts, postHookAccounts, squadsProgram, autoManagePositions, resolvedSetupContext);
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: [...setupStatePriceRefreshInstructions, ...preInstructions],
392
+ preInstructions,
309
393
  instruction,
310
394
  postInstructions,
311
395
  signers,
312
396
  addressLookupTableAddresses,
313
397
  };
314
398
  }
315
- exports.createVaultSyncTransaction = createVaultSyncTransaction;
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
- return { index, tokenAta, sharesAta };
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 resolveKaminoVaultWithdrawReserve(ix, vaultContext) {
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
- const reserve = vaultContext.index.reserves.find((entry) => entry.reserveAddress.equals(ix.reserve));
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 ${ix.reserve.toBase58()}`);
1581
+ throw new Error(`Kamino Vault ${ix.vault.toBase58()} does not use reserve ${leg.reserveAddress.toBase58()}`);
1345
1582
  }
1346
- return reserve;
1347
- }
1348
- if (vaultContext.index.reserves.length !== 1) {
1349
- throw new Error(`Kamino Vault ${ix.vault.toBase58()} uses ${vaultContext.index.reserves.length} reserves; specify withdraw.reserve`);
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.ix.sharesAmount),
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
- buckets.syncInstructions.push(buildKaminoVaultWithdrawInstruction({
1434
- owner: setupContext.owner,
1435
- ix,
1436
- vaultContext,
1437
- validationAccounts,
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 {