@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
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BorshCoder, Idl } from "@coral-xyz/anchor"
|
|
2
|
+
import { Fraction, Reserve } from "@exponent-labs/kamino-reserve-deserializer"
|
|
2
3
|
import { fetchKaminoVaultIndex } from "@exponent-labs/exponent-fetcher"
|
|
4
|
+
import { IDL as KaminoVaultIdl } from "@exponent-labs/kamino-vault-idl"
|
|
3
5
|
import { KAMINO_MARKETS, KAMINO_RESERVES, KaminoMarket } from "./kamino-markets"
|
|
4
6
|
import Decimal from "decimal.js"
|
|
5
7
|
import {
|
|
@@ -50,6 +52,7 @@ import {
|
|
|
50
52
|
refreshObligation,
|
|
51
53
|
} from "@exponent-labs/klend-idl/instructions"
|
|
52
54
|
import {
|
|
55
|
+
AccountLayout,
|
|
53
56
|
TOKEN_PROGRAM_ID,
|
|
54
57
|
NATIVE_MINT,
|
|
55
58
|
getAssociatedTokenAddressSync,
|
|
@@ -63,7 +66,18 @@ import {
|
|
|
63
66
|
} from "./policyMatcher"
|
|
64
67
|
import { buildScopeRefreshInstructions } from "./scope-refresh"
|
|
65
68
|
import * as web3 from "@solana/web3.js"
|
|
66
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
AccountMeta,
|
|
71
|
+
AddressLookupTableAccount,
|
|
72
|
+
Connection,
|
|
73
|
+
PublicKey,
|
|
74
|
+
SystemProgram,
|
|
75
|
+
SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
76
|
+
SYSVAR_RENT_PUBKEY,
|
|
77
|
+
TransactionInstruction,
|
|
78
|
+
TransactionMessage,
|
|
79
|
+
VersionedTransaction,
|
|
80
|
+
} from "@solana/web3.js"
|
|
67
81
|
import BN from "bn.js"
|
|
68
82
|
|
|
69
83
|
const KAMINO_FARMS_PROGRAM_ID = new PublicKey("FarmsPZpWu9i7Kky8tPN37rs2TpmMrAZrC7S7vJa91Hr")
|
|
@@ -73,6 +87,7 @@ const KAMINO_VAULT_ALLOCATION_STRATEGY_OFFSET = 304
|
|
|
73
87
|
const KAMINO_VAULT_ALLOCATION_SIZE = 2160
|
|
74
88
|
const KAMINO_VAULT_ALLOCATION_CTOKEN_VAULT_OFFSET = 32
|
|
75
89
|
const KAMINO_VAULT_GLOBAL_CONFIG_SEED = Buffer.from("global_config")
|
|
90
|
+
const KAMINO_VAULT_CODER = new BorshCoder(KaminoVaultIdl as Idl)
|
|
76
91
|
|
|
77
92
|
// ============================================================================
|
|
78
93
|
// Vault Instruction Types (re-exported from vault-instruction-types.ts)
|
|
@@ -306,8 +321,8 @@ export const kaminoVaultAction = {
|
|
|
306
321
|
|
|
307
322
|
/**
|
|
308
323
|
* Withdraw Kamino Vault shares back into the vault-owned token account.
|
|
309
|
-
*
|
|
310
|
-
*
|
|
324
|
+
* `reserve` is an optional override. When omitted, the SDK plans the
|
|
325
|
+
* withdraw across the vault's active reserves automatically.
|
|
311
326
|
*/
|
|
312
327
|
withdraw(params: {
|
|
313
328
|
vault: PublicKey
|
|
@@ -403,54 +418,7 @@ export interface VaultSyncTransactionResult {
|
|
|
403
418
|
addressLookupTableAddresses: PublicKey[]
|
|
404
419
|
}
|
|
405
420
|
|
|
406
|
-
|
|
407
|
-
* Build vault instructions and wrap them in a Squads sync transaction.
|
|
408
|
-
*
|
|
409
|
-
* Takes high-level `VaultInstruction` descriptors (built with `kamino.*`),
|
|
410
|
-
* resolves them to raw Solana instructions, then separates them:
|
|
411
|
-
* - Permissionless refresh instructions → `preInstructions` (top-level)
|
|
412
|
-
* - Vault-signed instructions → `instruction` (Squads sync transaction)
|
|
413
|
-
*
|
|
414
|
-
* KLend's `check_refresh` requires refreshReserve to be a top-level instruction
|
|
415
|
-
* so it can be found via the instruction sysvar.
|
|
416
|
-
*
|
|
417
|
-
* @returns `{ setupInstructions, preInstructions, instruction, postInstructions, signers, addressLookupTableAddresses }`
|
|
418
|
-
*
|
|
419
|
-
* @example
|
|
420
|
-
* ```ts
|
|
421
|
-
* const { setupInstructions, preInstructions, instruction, postInstructions, signers, addressLookupTableAddresses } = await createVaultSyncTransaction({
|
|
422
|
-
* instructions: [
|
|
423
|
-
* kamino.initUserMetadata(KaminoMarket.MAIN),
|
|
424
|
-
* kamino.initObligation(KaminoMarket.MAIN),
|
|
425
|
-
* kamino.deposit(KaminoMarket.MAIN, "USDC", new BN(1_000_000)),
|
|
426
|
-
* ],
|
|
427
|
-
* owner: vaultPda,
|
|
428
|
-
* connection,
|
|
429
|
-
* policyPda,
|
|
430
|
-
* vaultPda,
|
|
431
|
-
* signer: wallet.publicKey,
|
|
432
|
-
* vaultAddress: VAULT_ADDRESS,
|
|
433
|
-
* })
|
|
434
|
-
* // Send: [...setupInstructions, ...preInstructions, instruction, ...postInstructions]
|
|
435
|
-
* ```
|
|
436
|
-
*/
|
|
437
|
-
export async function createVaultSyncTransaction({
|
|
438
|
-
instructions,
|
|
439
|
-
owner,
|
|
440
|
-
connection,
|
|
441
|
-
policyPda,
|
|
442
|
-
vaultPda,
|
|
443
|
-
signer,
|
|
444
|
-
accountIndex = 0,
|
|
445
|
-
constraintIndices,
|
|
446
|
-
vaultAddress,
|
|
447
|
-
leadingAccounts,
|
|
448
|
-
preHookAccounts,
|
|
449
|
-
postHookAccounts,
|
|
450
|
-
squadsProgram = SQUADS_PROGRAM_ID,
|
|
451
|
-
autoManagePositions = false,
|
|
452
|
-
setupContext,
|
|
453
|
-
}: {
|
|
421
|
+
type CreateVaultSyncTransactionParams = {
|
|
454
422
|
instructions: VaultInstruction[]
|
|
455
423
|
owner: PublicKey
|
|
456
424
|
connection: Connection
|
|
@@ -469,8 +437,66 @@ export async function createVaultSyncTransaction({
|
|
|
469
437
|
autoManagePositions?: boolean
|
|
470
438
|
/** Optional shared setup context — when provided, setup state (tracked accounts, positions) is shared across calls, preventing duplicate setup instructions. */
|
|
471
439
|
setupContext?: StrategySetupContext
|
|
472
|
-
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Build one or more vault sync transactions from high-level instruction descriptors.
|
|
444
|
+
*
|
|
445
|
+
* This is the plural companion to {@link createVaultSyncTransaction}. Most
|
|
446
|
+
* calls return a single result. Smart Kamino Vault withdraws can expand into
|
|
447
|
+
* multiple reserve-specific sync transactions when the wrapper needs to split
|
|
448
|
+
* an oversized withdraw across sequential chunks.
|
|
449
|
+
*/
|
|
450
|
+
export async function createVaultSyncTransactions(
|
|
451
|
+
params: CreateVaultSyncTransactionParams,
|
|
452
|
+
): Promise<VaultSyncTransactionResult[]> {
|
|
453
|
+
return buildVaultSyncTransactionResults(params, true)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Build exactly one vault sync transaction and return its wrapped instruction set.
|
|
458
|
+
*
|
|
459
|
+
* Takes high-level `VaultInstruction` descriptors (built with `kamino.*`),
|
|
460
|
+
* resolves them to raw Solana instructions, then separates them:
|
|
461
|
+
* - Permissionless refresh instructions → `preInstructions` (top-level)
|
|
462
|
+
* - Vault-signed instructions → `instruction` (Squads sync transaction)
|
|
463
|
+
*
|
|
464
|
+
* KLend's `check_refresh` requires refreshReserve to be a top-level instruction
|
|
465
|
+
* so it can be found via the instruction sysvar.
|
|
466
|
+
*
|
|
467
|
+
* When a smart Kamino Vault withdraw expands beyond one sync transaction, this
|
|
468
|
+
* singular helper throws and asks the caller to use
|
|
469
|
+
* {@link createVaultSyncTransactions} or {@link VaultTransactionBuilder}.
|
|
470
|
+
*/
|
|
471
|
+
export async function createVaultSyncTransaction(
|
|
472
|
+
params: CreateVaultSyncTransactionParams,
|
|
473
|
+
): Promise<VaultSyncTransactionResult> {
|
|
474
|
+
const results = await buildVaultSyncTransactionResults(params, false)
|
|
475
|
+
return results[0]!
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async function buildVaultSyncTransactionResults(
|
|
479
|
+
{
|
|
480
|
+
instructions,
|
|
481
|
+
owner,
|
|
482
|
+
connection,
|
|
483
|
+
policyPda,
|
|
484
|
+
vaultPda,
|
|
485
|
+
signer,
|
|
486
|
+
accountIndex = 0,
|
|
487
|
+
constraintIndices,
|
|
488
|
+
vaultAddress,
|
|
489
|
+
leadingAccounts,
|
|
490
|
+
preHookAccounts,
|
|
491
|
+
postHookAccounts,
|
|
492
|
+
squadsProgram = SQUADS_PROGRAM_ID,
|
|
493
|
+
autoManagePositions = false,
|
|
494
|
+
setupContext,
|
|
495
|
+
}: CreateVaultSyncTransactionParams,
|
|
496
|
+
splitOversizedKaminoVaultWithdraw: boolean,
|
|
497
|
+
): Promise<VaultSyncTransactionResult[]> {
|
|
473
498
|
vaultPda ??= owner
|
|
499
|
+
|
|
474
500
|
const resolvedSetupContext = setupContext ?? createStrategySetupContext({
|
|
475
501
|
connection,
|
|
476
502
|
env: LOCAL_ENV,
|
|
@@ -486,7 +512,8 @@ export async function createVaultSyncTransaction({
|
|
|
486
512
|
postHookAccounts,
|
|
487
513
|
autoManagePositions,
|
|
488
514
|
})
|
|
489
|
-
|
|
515
|
+
|
|
516
|
+
const buckets = await buildVaultInstructions(
|
|
490
517
|
instructions,
|
|
491
518
|
owner,
|
|
492
519
|
connection,
|
|
@@ -502,10 +529,149 @@ export async function createVaultSyncTransaction({
|
|
|
502
529
|
autoManagePositions,
|
|
503
530
|
resolvedSetupContext,
|
|
504
531
|
)
|
|
532
|
+
|
|
505
533
|
const setupStatePriceRefreshInstructions = setupContext
|
|
506
534
|
? []
|
|
507
535
|
: await buildSetupStatePriceRefreshInstructions(resolvedSetupContext)
|
|
508
536
|
|
|
537
|
+
const fullResult = await buildWrappedVaultSyncTransactionResult({
|
|
538
|
+
connection,
|
|
539
|
+
policyPda,
|
|
540
|
+
vaultPda,
|
|
541
|
+
signer,
|
|
542
|
+
accountIndex,
|
|
543
|
+
constraintIndices,
|
|
544
|
+
vaultAddress,
|
|
545
|
+
leadingAccounts,
|
|
546
|
+
preHookAccounts,
|
|
547
|
+
postHookAccounts,
|
|
548
|
+
squadsProgram,
|
|
549
|
+
syncInstructions: buckets.syncInstructions,
|
|
550
|
+
setupInstructions: buckets.setupInstructions,
|
|
551
|
+
preInstructions: [...setupStatePriceRefreshInstructions, ...buckets.preInstructions],
|
|
552
|
+
postInstructions: buckets.postInstructions,
|
|
553
|
+
signers: buckets.signers,
|
|
554
|
+
addressLookupTableAddresses: buckets.addressLookupTableAddresses,
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
const isPlannerExpandedKaminoVaultWithdraw =
|
|
558
|
+
instructions.length === 1
|
|
559
|
+
&& instructions[0]?.action === KaminoVaultAction.WITHDRAW
|
|
560
|
+
&& !(instructions[0] as KaminoVaultWithdrawInstruction).reserve
|
|
561
|
+
&& buckets.syncInstructions.length > 1
|
|
562
|
+
|
|
563
|
+
if (!isPlannerExpandedKaminoVaultWithdraw) {
|
|
564
|
+
return [fullResult]
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (await vaultSyncResultFitsInSingleTransaction({
|
|
568
|
+
connection,
|
|
569
|
+
payer: signer,
|
|
570
|
+
result: fullResult,
|
|
571
|
+
})) {
|
|
572
|
+
return [fullResult]
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (!splitOversizedKaminoVaultWithdraw) {
|
|
576
|
+
throw new Error(
|
|
577
|
+
"Kamino Vault withdraw expands beyond one sync transaction; use createVaultSyncTransactions() or VaultTransactionBuilder.",
|
|
578
|
+
)
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (buckets.preInstructions.length > 0 || buckets.postInstructions.length > 0) {
|
|
582
|
+
throw new Error(
|
|
583
|
+
"Kamino Vault withdraw chunking only supports sync-only actions; use explicit reserve overrides if you need custom refresh handling.",
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const splitResults: VaultSyncTransactionResult[] = []
|
|
588
|
+
for (const [index, syncInstruction] of buckets.syncInstructions.entries()) {
|
|
589
|
+
const chunkConstraintIndices = constraintIndices
|
|
590
|
+
? constraintIndices.length === buckets.syncInstructions.length
|
|
591
|
+
? [constraintIndices[index]!]
|
|
592
|
+
: constraintIndices.length === 1
|
|
593
|
+
? constraintIndices
|
|
594
|
+
: (() => {
|
|
595
|
+
throw new Error(
|
|
596
|
+
"constraintIndices must contain either one entry or one entry per generated sync instruction when splitting a Kamino Vault withdraw.",
|
|
597
|
+
)
|
|
598
|
+
})()
|
|
599
|
+
: undefined
|
|
600
|
+
|
|
601
|
+
const chunkResult = await buildWrappedVaultSyncTransactionResult({
|
|
602
|
+
connection,
|
|
603
|
+
policyPda,
|
|
604
|
+
vaultPda,
|
|
605
|
+
signer,
|
|
606
|
+
accountIndex,
|
|
607
|
+
constraintIndices: chunkConstraintIndices,
|
|
608
|
+
vaultAddress,
|
|
609
|
+
leadingAccounts,
|
|
610
|
+
preHookAccounts,
|
|
611
|
+
postHookAccounts,
|
|
612
|
+
squadsProgram,
|
|
613
|
+
syncInstructions: [syncInstruction],
|
|
614
|
+
setupInstructions: index === 0 ? buckets.setupInstructions : [],
|
|
615
|
+
preInstructions: index === 0 ? [...setupStatePriceRefreshInstructions] : [],
|
|
616
|
+
postInstructions: [],
|
|
617
|
+
signers: buckets.signers,
|
|
618
|
+
addressLookupTableAddresses: buckets.addressLookupTableAddresses,
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
const chunkFits = await vaultSyncResultFitsInSingleTransaction({
|
|
622
|
+
connection,
|
|
623
|
+
payer: signer,
|
|
624
|
+
result: chunkResult,
|
|
625
|
+
})
|
|
626
|
+
if (!chunkFits) {
|
|
627
|
+
throw new Error(
|
|
628
|
+
`Kamino Vault withdraw chunk ${index + 1} still exceeds one sync transaction; use explicit reserve overrides or smaller steps.`,
|
|
629
|
+
)
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
splitResults.push(chunkResult)
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return splitResults
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async function buildWrappedVaultSyncTransactionResult({
|
|
639
|
+
connection,
|
|
640
|
+
policyPda,
|
|
641
|
+
vaultPda,
|
|
642
|
+
signer,
|
|
643
|
+
accountIndex,
|
|
644
|
+
constraintIndices,
|
|
645
|
+
vaultAddress,
|
|
646
|
+
leadingAccounts,
|
|
647
|
+
preHookAccounts,
|
|
648
|
+
postHookAccounts,
|
|
649
|
+
squadsProgram,
|
|
650
|
+
syncInstructions,
|
|
651
|
+
setupInstructions,
|
|
652
|
+
preInstructions,
|
|
653
|
+
postInstructions,
|
|
654
|
+
signers,
|
|
655
|
+
addressLookupTableAddresses,
|
|
656
|
+
}: {
|
|
657
|
+
connection: Connection
|
|
658
|
+
policyPda?: PublicKey
|
|
659
|
+
vaultPda: PublicKey
|
|
660
|
+
signer: PublicKey
|
|
661
|
+
accountIndex: number
|
|
662
|
+
constraintIndices?: number[]
|
|
663
|
+
vaultAddress?: PublicKey
|
|
664
|
+
leadingAccounts?: PublicKey[] | AccountMeta[]
|
|
665
|
+
preHookAccounts?: PublicKey[] | AccountMeta[]
|
|
666
|
+
postHookAccounts?: PublicKey[] | AccountMeta[]
|
|
667
|
+
squadsProgram: PublicKey
|
|
668
|
+
syncInstructions: TransactionInstruction[]
|
|
669
|
+
setupInstructions: TransactionInstruction[]
|
|
670
|
+
preInstructions: TransactionInstruction[]
|
|
671
|
+
postInstructions: TransactionInstruction[]
|
|
672
|
+
signers: web3.Signer[]
|
|
673
|
+
addressLookupTableAddresses: PublicKey[]
|
|
674
|
+
}): Promise<VaultSyncTransactionResult> {
|
|
509
675
|
let resolvedPolicyPda = policyPda
|
|
510
676
|
let resolvedConstraintIndices = constraintIndices
|
|
511
677
|
if (!resolvedPolicyPda) {
|
|
@@ -517,14 +683,12 @@ export async function createVaultSyncTransaction({
|
|
|
517
683
|
resolvedConstraintIndices ??= match.constraintIndices
|
|
518
684
|
}
|
|
519
685
|
|
|
520
|
-
// Auto-resolve constraint indices when not explicitly provided
|
|
521
686
|
resolvedConstraintIndices ??= await resolveConstraintIndices(
|
|
522
687
|
connection,
|
|
523
688
|
resolvedPolicyPda,
|
|
524
689
|
syncInstructions,
|
|
525
690
|
)
|
|
526
691
|
|
|
527
|
-
// Auto-resolve policy prefix and hook accounts when not explicitly provided
|
|
528
692
|
let resolvedLeadingAccounts = leadingAccounts
|
|
529
693
|
let resolvedPreHookAccounts = preHookAccounts
|
|
530
694
|
let resolvedPostHookAccounts = postHookAccounts
|
|
@@ -550,7 +714,7 @@ export async function createVaultSyncTransaction({
|
|
|
550
714
|
|
|
551
715
|
return {
|
|
552
716
|
setupInstructions,
|
|
553
|
-
preInstructions
|
|
717
|
+
preInstructions,
|
|
554
718
|
instruction,
|
|
555
719
|
postInstructions,
|
|
556
720
|
signers,
|
|
@@ -558,6 +722,38 @@ export async function createVaultSyncTransaction({
|
|
|
558
722
|
}
|
|
559
723
|
}
|
|
560
724
|
|
|
725
|
+
async function vaultSyncResultFitsInSingleTransaction(params: {
|
|
726
|
+
connection: Connection
|
|
727
|
+
payer: PublicKey
|
|
728
|
+
result: VaultSyncTransactionResult
|
|
729
|
+
}): Promise<boolean> {
|
|
730
|
+
try {
|
|
731
|
+
const altAccounts = await resolveVaultSyncAltAccounts(
|
|
732
|
+
params.connection,
|
|
733
|
+
params.result.addressLookupTableAddresses,
|
|
734
|
+
)
|
|
735
|
+
const message = new TransactionMessage({
|
|
736
|
+
payerKey: params.payer,
|
|
737
|
+
recentBlockhash: PublicKey.default.toBase58(),
|
|
738
|
+
instructions: [...params.result.preInstructions, params.result.instruction, ...params.result.postInstructions],
|
|
739
|
+
}).compileToV0Message(altAccounts)
|
|
740
|
+
|
|
741
|
+
return new VersionedTransaction(message).serialize().length <= 1232
|
|
742
|
+
} catch {
|
|
743
|
+
return false
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async function resolveVaultSyncAltAccounts(
|
|
748
|
+
connection: Connection,
|
|
749
|
+
addresses: PublicKey[],
|
|
750
|
+
): Promise<AddressLookupTableAccount[]> {
|
|
751
|
+
const lookupTables = await Promise.all(addresses.map((address) => connection.getAddressLookupTable(address)))
|
|
752
|
+
return lookupTables
|
|
753
|
+
.map((entry) => entry.value)
|
|
754
|
+
.filter((entry): entry is AddressLookupTableAccount => entry !== null)
|
|
755
|
+
}
|
|
756
|
+
|
|
561
757
|
// ============================================================================
|
|
562
758
|
// Internal: Instruction Assembly
|
|
563
759
|
// ============================================================================
|
|
@@ -1698,10 +1894,32 @@ const KAMINO_VAULT_EVENT_AUTHORITY = emitEventAuthority(KAMINO_VAULT_PROGRAM_ID)
|
|
|
1698
1894
|
const KAMINO_FARM_USER_STATE_SIZE = 920
|
|
1699
1895
|
const KAMINO_STAKE_ALL_AMOUNT = new BN("18446744073709551615")
|
|
1700
1896
|
|
|
1897
|
+
type KaminoVaultIndex = Awaited<ReturnType<typeof fetchKaminoVaultIndex>>
|
|
1898
|
+
|
|
1899
|
+
type KaminoVaultDecodedState = {
|
|
1900
|
+
tokenAvailable: BN
|
|
1901
|
+
sharesIssued: BN
|
|
1902
|
+
pendingFeesSf: BN
|
|
1903
|
+
allocations: Array<{
|
|
1904
|
+
reserve: PublicKey
|
|
1905
|
+
ctokenAllocation: BN
|
|
1906
|
+
}>
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
type KaminoVaultResolvedReserve = KaminoVaultIndex["reserves"][number] & {
|
|
1910
|
+
account: Reserve
|
|
1911
|
+
investedLiquidityAmount: Decimal
|
|
1912
|
+
availableLiquidityToWithdraw: Decimal
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1701
1915
|
type KaminoVaultContext = {
|
|
1702
|
-
index:
|
|
1916
|
+
index: Omit<KaminoVaultIndex, "reserves"> & {
|
|
1917
|
+
reserves: KaminoVaultResolvedReserve[]
|
|
1918
|
+
}
|
|
1703
1919
|
tokenAta: PublicKey
|
|
1704
1920
|
sharesAta: PublicKey
|
|
1921
|
+
sharesBalance: BN
|
|
1922
|
+
state: KaminoVaultDecodedState
|
|
1705
1923
|
}
|
|
1706
1924
|
|
|
1707
1925
|
type KaminoFarmContext = {
|
|
@@ -1720,6 +1938,10 @@ function toBn(value: BN | bigint | number): BN {
|
|
|
1720
1938
|
return new BN(value.toString())
|
|
1721
1939
|
}
|
|
1722
1940
|
|
|
1941
|
+
function minBn(lhs: BN, rhs: BN): BN {
|
|
1942
|
+
return lhs.lte(rhs) ? lhs : rhs
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1723
1945
|
function encodeU64InstructionData(discriminator: Buffer, value: BN | bigint | number): Buffer {
|
|
1724
1946
|
return Buffer.concat([discriminator, toBn(value).toArrayLike(Buffer, "le", 8)])
|
|
1725
1947
|
}
|
|
@@ -1814,6 +2036,151 @@ function resolveKaminoVaultTrackedPriceId(params: {
|
|
|
1814
2036
|
return { __kind: "Multiply", priceIds: [...reservePriceIds, params.sharePriceId] }
|
|
1815
2037
|
}
|
|
1816
2038
|
|
|
2039
|
+
type KaminoVaultWithdrawPlanningSnapshot = {
|
|
2040
|
+
sharesBalance: BN
|
|
2041
|
+
tokenAvailable: BN
|
|
2042
|
+
sharesIssued: BN
|
|
2043
|
+
pendingFeesSf: BN
|
|
2044
|
+
reserves: Array<{
|
|
2045
|
+
reserveAddress: PublicKey
|
|
2046
|
+
investedLiquidityAmount: Decimal
|
|
2047
|
+
availableLiquidityToWithdraw: Decimal
|
|
2048
|
+
}>
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
type KaminoVaultWithdrawPlanningLeg = {
|
|
2052
|
+
reserveAddress: PublicKey
|
|
2053
|
+
sharesAmount: BN
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
function decodeKaminoVaultState(data: Buffer): KaminoVaultDecodedState {
|
|
2057
|
+
const decoded = KAMINO_VAULT_CODER.accounts.decode("VaultState", data) as {
|
|
2058
|
+
token_available: BN
|
|
2059
|
+
shares_issued: BN
|
|
2060
|
+
pending_fees_sf: BN
|
|
2061
|
+
vault_allocation_strategy: Array<{
|
|
2062
|
+
reserve: PublicKey
|
|
2063
|
+
ctoken_allocation: BN
|
|
2064
|
+
}>
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
return {
|
|
2068
|
+
tokenAvailable: decoded.token_available,
|
|
2069
|
+
sharesIssued: decoded.shares_issued,
|
|
2070
|
+
pendingFeesSf: decoded.pending_fees_sf,
|
|
2071
|
+
allocations: decoded.vault_allocation_strategy
|
|
2072
|
+
.filter((allocation) => !allocation.reserve.equals(PublicKey.default))
|
|
2073
|
+
.map((allocation) => ({
|
|
2074
|
+
reserve: allocation.reserve,
|
|
2075
|
+
ctokenAllocation: allocation.ctoken_allocation,
|
|
2076
|
+
})),
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
function calculateKaminoVaultTokensPerShareFromSnapshot(
|
|
2081
|
+
snapshot: KaminoVaultWithdrawPlanningSnapshot,
|
|
2082
|
+
): Decimal {
|
|
2083
|
+
if (snapshot.sharesIssued.isZero()) {
|
|
2084
|
+
return new Decimal(0)
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
const investedTotal = snapshot.reserves.reduce(
|
|
2088
|
+
(total, reserve) => total.add(reserve.investedLiquidityAmount),
|
|
2089
|
+
new Decimal(0),
|
|
2090
|
+
)
|
|
2091
|
+
const pendingFees = new Fraction(snapshot.pendingFeesSf).toDecimal()
|
|
2092
|
+
const currentVaultAum = new Decimal(snapshot.tokenAvailable.toString())
|
|
2093
|
+
.add(investedTotal)
|
|
2094
|
+
.sub(pendingFees)
|
|
2095
|
+
|
|
2096
|
+
if (currentVaultAum.lte(0)) {
|
|
2097
|
+
return new Decimal(0)
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
return currentVaultAum.div(snapshot.sharesIssued.toString())
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
function planKaminoVaultWithdrawLegsFromSnapshot(params: {
|
|
2104
|
+
sharesAmount: BN
|
|
2105
|
+
reserve?: PublicKey
|
|
2106
|
+
snapshot: KaminoVaultWithdrawPlanningSnapshot
|
|
2107
|
+
}): KaminoVaultWithdrawPlanningLeg[] {
|
|
2108
|
+
if (params.reserve) {
|
|
2109
|
+
return [{ reserveAddress: params.reserve, sharesAmount: params.sharesAmount }]
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
const actualSharesToWithdraw = minBn(params.sharesAmount, params.snapshot.sharesBalance)
|
|
2113
|
+
if (actualSharesToWithdraw.isZero()) {
|
|
2114
|
+
throw new Error("Cannot withdraw zero Kamino Vault shares")
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
if (params.snapshot.reserves.length === 0) {
|
|
2118
|
+
throw new Error("Kamino Vault has no active reserves to anchor a reserve-specific withdraw instruction")
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
const withdrawAllShares = params.sharesAmount.gte(params.snapshot.sharesBalance)
|
|
2122
|
+
const tokensPerShare = calculateKaminoVaultTokensPerShareFromSnapshot(params.snapshot)
|
|
2123
|
+
if (tokensPerShare.lte(0)) {
|
|
2124
|
+
throw new Error("Kamino Vault has zero share price; cannot plan a withdraw")
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
const tokensToWithdraw = new Decimal(actualSharesToWithdraw.toString()).mul(tokensPerShare)
|
|
2128
|
+
const availableTokens = new Decimal(params.snapshot.tokenAvailable.toString())
|
|
2129
|
+
|
|
2130
|
+
if (tokensToWithdraw.lte(availableTokens)) {
|
|
2131
|
+
return [{
|
|
2132
|
+
reserveAddress: params.snapshot.reserves[0]!.reserveAddress,
|
|
2133
|
+
sharesAmount: withdrawAllShares ? params.snapshot.sharesBalance : actualSharesToWithdraw,
|
|
2134
|
+
}]
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
let tokensRemaining = tokensToWithdraw
|
|
2138
|
+
const plannedLegs: KaminoVaultWithdrawPlanningLeg[] = []
|
|
2139
|
+
const sortedReserves = [...params.snapshot.reserves].sort((lhs, rhs) =>
|
|
2140
|
+
rhs.availableLiquidityToWithdraw.comparedTo(lhs.availableLiquidityToWithdraw),
|
|
2141
|
+
)
|
|
2142
|
+
|
|
2143
|
+
for (const [index, reserve] of sortedReserves.entries()) {
|
|
2144
|
+
const legCapacity = reserve.availableLiquidityToWithdraw.add(index === 0 ? availableTokens : 0)
|
|
2145
|
+
if (legCapacity.lte(0)) {
|
|
2146
|
+
continue
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
const tokensForLeg = Decimal.min(tokensRemaining, legCapacity)
|
|
2150
|
+
if (tokensForLeg.lte(0)) {
|
|
2151
|
+
continue
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
const sharesForLeg = withdrawAllShares
|
|
2155
|
+
? params.snapshot.sharesBalance
|
|
2156
|
+
: new BN(tokensForLeg.div(tokensPerShare).floor().toFixed(0))
|
|
2157
|
+
if (sharesForLeg.isZero()) {
|
|
2158
|
+
continue
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
plannedLegs.push({
|
|
2162
|
+
reserveAddress: reserve.reserveAddress,
|
|
2163
|
+
sharesAmount: sharesForLeg,
|
|
2164
|
+
})
|
|
2165
|
+
|
|
2166
|
+
tokensRemaining = tokensRemaining.sub(tokensForLeg)
|
|
2167
|
+
if (tokensRemaining.lte(0)) {
|
|
2168
|
+
break
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
if (plannedLegs.length === 0) {
|
|
2173
|
+
throw new Error("Unable to plan a Kamino Vault withdraw across the vault's active reserves")
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
return plannedLegs
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
export const __kaminoVaultTesting = {
|
|
2180
|
+
calculateKaminoVaultTokensPerShareFromSnapshot,
|
|
2181
|
+
planKaminoVaultWithdrawLegsFromSnapshot,
|
|
2182
|
+
}
|
|
2183
|
+
|
|
1817
2184
|
async function resolveKaminoVaultContext(
|
|
1818
2185
|
vaultAddress: PublicKey,
|
|
1819
2186
|
setupContext: StrategySetupContext,
|
|
@@ -1833,6 +2200,7 @@ async function resolveKaminoVaultContext(
|
|
|
1833
2200
|
throw new Error(`Kamino vault account not found: ${vaultAddress.toBase58()}`)
|
|
1834
2201
|
}
|
|
1835
2202
|
|
|
2203
|
+
const decodedVaultState = decodeKaminoVaultState(Buffer.from(vaultInfo.data))
|
|
1836
2204
|
const decodedReserves = reserveInfos.map((reserveInfo, index) => {
|
|
1837
2205
|
if (!reserveInfo?.data) {
|
|
1838
2206
|
throw new Error(`Missing Kamino reserve account ${reserveAddresses[index]!.toBase58()}`)
|
|
@@ -1844,6 +2212,7 @@ async function resolveKaminoVaultContext(
|
|
|
1844
2212
|
)
|
|
1845
2213
|
const normalizedReserves = reserveAddresses.map((reserveAddress, index) => {
|
|
1846
2214
|
const reserveAccount = decodedReserves[index]!
|
|
2215
|
+
const allocation = decodedVaultState.allocations[index]
|
|
1847
2216
|
const rawReserve = rawIndex.reserves[index] as typeof rawIndex.reserves[number] & {
|
|
1848
2217
|
reserve?: PublicKey
|
|
1849
2218
|
marketAddress?: PublicKey
|
|
@@ -1866,6 +2235,13 @@ async function resolveKaminoVaultContext(
|
|
|
1866
2235
|
[Buffer.from("lma"), reserveAccount.lendingMarket.toBuffer()],
|
|
1867
2236
|
KAMINO_LENDING_PROGRAM_ID,
|
|
1868
2237
|
)
|
|
2238
|
+
const investedLiquidityAmount = allocation
|
|
2239
|
+
? new Decimal(allocation.ctokenAllocation.toString()).mul(reserveAccount.getCollateralExchangeRate())
|
|
2240
|
+
: new Decimal(0)
|
|
2241
|
+
const availableLiquidityToWithdraw = Decimal.min(
|
|
2242
|
+
investedLiquidityAmount,
|
|
2243
|
+
reserveAccount.getLiquidityAvailableAmount(),
|
|
2244
|
+
)
|
|
1869
2245
|
|
|
1870
2246
|
return {
|
|
1871
2247
|
reserveAddress,
|
|
@@ -1886,6 +2262,9 @@ async function resolveKaminoVaultContext(
|
|
|
1886
2262
|
reserveCollateralMint: rawReserve.reserveCollateralMint ?? reserveAccount.collateral.mintPubkey,
|
|
1887
2263
|
reserveCollateralTokenProgram:
|
|
1888
2264
|
rawReserve.reserveCollateralTokenProgram ?? collateralMintInfos[index]?.owner ?? PublicKey.default,
|
|
2265
|
+
account: reserveAccount,
|
|
2266
|
+
investedLiquidityAmount,
|
|
2267
|
+
availableLiquidityToWithdraw,
|
|
1889
2268
|
}
|
|
1890
2269
|
})
|
|
1891
2270
|
const index = {
|
|
@@ -1901,7 +2280,11 @@ async function resolveKaminoVaultContext(
|
|
|
1901
2280
|
}
|
|
1902
2281
|
const tokenAta = getAssociatedTokenAddressSync(index.tokenMint, setupContext.owner, true, index.tokenProgram)
|
|
1903
2282
|
const sharesAta = getAssociatedTokenAddressSync(index.sharesMint, setupContext.owner, true, index.sharesTokenProgram)
|
|
1904
|
-
|
|
2283
|
+
const sharesAtaInfo = await setupContext.connection.getAccountInfo(sharesAta)
|
|
2284
|
+
const sharesBalance = sharesAtaInfo?.data
|
|
2285
|
+
? new BN(AccountLayout.decode(sharesAtaInfo.data).amount.toString())
|
|
2286
|
+
: new BN(0)
|
|
2287
|
+
return { index, tokenAta, sharesAta, sharesBalance, state: decodedVaultState }
|
|
1905
2288
|
}
|
|
1906
2289
|
|
|
1907
2290
|
async function queueKaminoVaultSharesTracking(params: {
|
|
@@ -2060,36 +2443,76 @@ function buildKaminoVaultDepositInstruction(params: {
|
|
|
2060
2443
|
})
|
|
2061
2444
|
}
|
|
2062
2445
|
|
|
2063
|
-
function
|
|
2446
|
+
function resolveExplicitKaminoVaultWithdrawReserve(
|
|
2064
2447
|
ix: KaminoVaultWithdrawInstruction,
|
|
2065
2448
|
vaultContext: KaminoVaultContext,
|
|
2066
2449
|
): KaminoVaultContext["index"]["reserves"][number] {
|
|
2067
|
-
if (ix.reserve) {
|
|
2068
|
-
|
|
2069
|
-
if (!reserve) {
|
|
2070
|
-
throw new Error(
|
|
2071
|
-
`Kamino Vault ${ix.vault.toBase58()} does not use reserve ${ix.reserve.toBase58()}`,
|
|
2072
|
-
)
|
|
2073
|
-
}
|
|
2074
|
-
return reserve
|
|
2450
|
+
if (!ix.reserve) {
|
|
2451
|
+
throw new Error("reserve is required when resolving an explicit Kamino Vault withdraw reserve")
|
|
2075
2452
|
}
|
|
2076
2453
|
|
|
2077
|
-
|
|
2454
|
+
const reserve = vaultContext.index.reserves.find((entry) => entry.reserveAddress.equals(ix.reserve))
|
|
2455
|
+
if (!reserve) {
|
|
2078
2456
|
throw new Error(
|
|
2079
|
-
`Kamino Vault ${ix.vault.toBase58()}
|
|
2457
|
+
`Kamino Vault ${ix.vault.toBase58()} does not use reserve ${ix.reserve.toBase58()}`,
|
|
2080
2458
|
)
|
|
2081
2459
|
}
|
|
2082
2460
|
|
|
2083
|
-
return
|
|
2461
|
+
return reserve
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
function planKaminoVaultWithdrawLegs(
|
|
2465
|
+
ix: KaminoVaultWithdrawInstruction,
|
|
2466
|
+
vaultContext: KaminoVaultContext,
|
|
2467
|
+
): Array<{
|
|
2468
|
+
reserve: KaminoVaultContext["index"]["reserves"][number]
|
|
2469
|
+
sharesAmount: BN
|
|
2470
|
+
}> {
|
|
2471
|
+
if (ix.reserve) {
|
|
2472
|
+
return [{
|
|
2473
|
+
reserve: resolveExplicitKaminoVaultWithdrawReserve(ix, vaultContext),
|
|
2474
|
+
sharesAmount: ix.sharesAmount,
|
|
2475
|
+
}]
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
const plannedLegs = planKaminoVaultWithdrawLegsFromSnapshot({
|
|
2479
|
+
sharesAmount: ix.sharesAmount,
|
|
2480
|
+
snapshot: {
|
|
2481
|
+
sharesBalance: vaultContext.sharesBalance,
|
|
2482
|
+
tokenAvailable: vaultContext.state.tokenAvailable,
|
|
2483
|
+
sharesIssued: vaultContext.state.sharesIssued,
|
|
2484
|
+
pendingFeesSf: vaultContext.state.pendingFeesSf,
|
|
2485
|
+
reserves: vaultContext.index.reserves.map((reserve) => ({
|
|
2486
|
+
reserveAddress: reserve.reserveAddress,
|
|
2487
|
+
investedLiquidityAmount: reserve.investedLiquidityAmount,
|
|
2488
|
+
availableLiquidityToWithdraw: reserve.availableLiquidityToWithdraw,
|
|
2489
|
+
})),
|
|
2490
|
+
},
|
|
2491
|
+
})
|
|
2492
|
+
|
|
2493
|
+
return plannedLegs.map((leg) => {
|
|
2494
|
+
const reserve = vaultContext.index.reserves.find((entry) => entry.reserveAddress.equals(leg.reserveAddress))
|
|
2495
|
+
if (!reserve) {
|
|
2496
|
+
throw new Error(
|
|
2497
|
+
`Kamino Vault ${ix.vault.toBase58()} does not use reserve ${leg.reserveAddress.toBase58()}`,
|
|
2498
|
+
)
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
return {
|
|
2502
|
+
reserve,
|
|
2503
|
+
sharesAmount: leg.sharesAmount,
|
|
2504
|
+
}
|
|
2505
|
+
})
|
|
2084
2506
|
}
|
|
2085
2507
|
|
|
2086
2508
|
function buildKaminoVaultWithdrawInstruction(params: {
|
|
2087
2509
|
owner: PublicKey
|
|
2088
2510
|
ix: KaminoVaultWithdrawInstruction
|
|
2511
|
+
reserve: KaminoVaultContext["index"]["reserves"][number]
|
|
2512
|
+
sharesAmount: BN
|
|
2089
2513
|
vaultContext: KaminoVaultContext
|
|
2090
2514
|
validationAccounts?: AccountMeta[]
|
|
2091
2515
|
}): TransactionInstruction {
|
|
2092
|
-
const reserve = resolveKaminoVaultWithdrawReserve(params.ix, params.vaultContext)
|
|
2093
2516
|
const [globalConfig] = PublicKey.findProgramAddressSync(
|
|
2094
2517
|
[KAMINO_VAULT_GLOBAL_CONFIG_SEED],
|
|
2095
2518
|
KAMINO_VAULT_PROGRAM_ID,
|
|
@@ -2112,13 +2535,13 @@ function buildKaminoVaultWithdrawInstruction(params: {
|
|
|
2112
2535
|
{ pubkey: KAMINO_VAULT_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
2113
2536
|
{ pubkey: KAMINO_VAULT_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
2114
2537
|
{ pubkey: params.ix.vault, isSigner: false, isWritable: true },
|
|
2115
|
-
{ pubkey: reserve.reserveAddress, isSigner: false, isWritable: true },
|
|
2116
|
-
{ pubkey: reserve.ctokenVault, isSigner: false, isWritable: true },
|
|
2117
|
-
{ pubkey: reserve.marketAddress, isSigner: false, isWritable: false },
|
|
2118
|
-
{ pubkey: reserve.lendingMarketAuthority, isSigner: false, isWritable: false },
|
|
2119
|
-
{ pubkey: reserve.reserveLiquiditySupply, isSigner: false, isWritable: true },
|
|
2120
|
-
{ pubkey: reserve.reserveCollateralMint, isSigner: false, isWritable: true },
|
|
2121
|
-
{ pubkey: reserve.reserveCollateralTokenProgram, isSigner: false, isWritable: false },
|
|
2538
|
+
{ pubkey: params.reserve.reserveAddress, isSigner: false, isWritable: true },
|
|
2539
|
+
{ pubkey: params.reserve.ctokenVault, isSigner: false, isWritable: true },
|
|
2540
|
+
{ pubkey: params.reserve.marketAddress, isSigner: false, isWritable: false },
|
|
2541
|
+
{ pubkey: params.reserve.lendingMarketAuthority, isSigner: false, isWritable: false },
|
|
2542
|
+
{ pubkey: params.reserve.reserveLiquiditySupply, isSigner: false, isWritable: true },
|
|
2543
|
+
{ pubkey: params.reserve.reserveCollateralMint, isSigner: false, isWritable: true },
|
|
2544
|
+
{ pubkey: params.reserve.reserveCollateralTokenProgram, isSigner: false, isWritable: false },
|
|
2122
2545
|
{ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
|
|
2123
2546
|
{ pubkey: KAMINO_VAULT_EVENT_AUTHORITY, isSigner: false, isWritable: false },
|
|
2124
2547
|
{ pubkey: KAMINO_VAULT_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
@@ -2129,7 +2552,7 @@ function buildKaminoVaultWithdrawInstruction(params: {
|
|
|
2129
2552
|
})),
|
|
2130
2553
|
...(params.validationAccounts ?? []),
|
|
2131
2554
|
],
|
|
2132
|
-
data: encodeU64InstructionData(KAMINO_VAULT_DISCRIMINATORS.withdraw, params.
|
|
2555
|
+
data: encodeU64InstructionData(KAMINO_VAULT_DISCRIMINATORS.withdraw, params.sharesAmount),
|
|
2133
2556
|
})
|
|
2134
2557
|
}
|
|
2135
2558
|
|
|
@@ -2193,12 +2616,17 @@ async function buildKaminoVaultInstruction(
|
|
|
2193
2616
|
return
|
|
2194
2617
|
}
|
|
2195
2618
|
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2619
|
+
const withdrawLegs = planKaminoVaultWithdrawLegs(ix, vaultContext)
|
|
2620
|
+
for (const leg of withdrawLegs) {
|
|
2621
|
+
buckets.syncInstructions.push(buildKaminoVaultWithdrawInstruction({
|
|
2622
|
+
owner: setupContext.owner,
|
|
2623
|
+
ix,
|
|
2624
|
+
reserve: leg.reserve,
|
|
2625
|
+
sharesAmount: leg.sharesAmount,
|
|
2626
|
+
vaultContext,
|
|
2627
|
+
validationAccounts,
|
|
2628
|
+
}))
|
|
2629
|
+
}
|
|
2202
2630
|
}
|
|
2203
2631
|
|
|
2204
2632
|
function getOptionalReadonlyAccountMeta(account: PublicKey | null, placeholderProgram: PublicKey): AccountMeta {
|