@perena/vault-sdk 1.0.56 → 1.0.58

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/README.md CHANGED
@@ -370,6 +370,33 @@ and pass the data fields into the generated CPI method. For example,
370
370
  `addressSeeds`, it corresponds to `execute_deposit`; with seeds, the builder
371
371
  selects `execute_deposit_fee_exempt`.
372
372
 
373
+ For regular and instant tranche withdrawals, use `getCpiPlan(txArgs)` to resolve
374
+ liquidity and split the requested shares across external positions:
375
+
376
+ ```typescript
377
+ const cpis = await client.tx.executeWithdraw.getCpiPlan({
378
+ user: programAuthority,
379
+ vault,
380
+ assetMint,
381
+ shareMint,
382
+ shareAmount: 500_000n,
383
+ addressSeeds,
384
+ });
385
+ // Invoke each entry in order within the same transaction.
386
+ for (const { accounts, data, remainingAccounts, lookupTables } of cpis) {
387
+ // Pass accounts, data, and remainingAccounts to your Rust CPI integration.
388
+ // Include lookupTables in the outer transaction and allow enough compute.
389
+ }
390
+ ```
391
+
392
+ `executeTrancheWithdraw.getCpiPlan(...)` has the same behavior for junior and
393
+ senior redemptions. `getIxArgs(txArgs)` returns the resolved arguments for each
394
+ leg; `getIxs(txArgs)` returns serialized vault instructions without ATA creation
395
+ or compute-budget instructions. `getTx(txArgs)` adds those transaction setup
396
+ instructions and combines lookup tables. The synchronous `getIxData(ixArgs)` and
397
+ `getIxAccounts(ixArgs)` helpers describe a single resolved leg; they do not fetch
398
+ balances or split it.
399
+
373
400
  ### Fee-exempt program integrations
374
401
 
375
402
  The deposit, withdrawal, tranche deposit, tranche withdrawal, and queued junior
@@ -560,23 +587,26 @@ const plan = await client.tx.executeWithdraw.getTx({
560
587
  await client.sendTransaction(payer, plan);
561
588
  ```
562
589
 
563
- Prefer the external-liquidity route when transaction size is not a constraint.
564
- The builder auto-detects external liquidity slot 0 and, when it finds an active
565
- Marginfi position for the asset, emits `execute_withdraw_from_external` instead
566
- of `execute_withdraw`. That route can pull from the vault's external position in
567
- the same transaction, so it draws on far more liquidity than the plain
568
- `execute_withdraw`: the external-liquidity integrity service targets local USDC
569
- worth 1.5% of total vault TVL (`DEFAULT_TARGET_LOCAL_USDC_TVL_BPS`) and ~0.5% of
570
- each other holding (`DEFAULT_TARGET_LOCAL_BPS`), deploying the rest into Marginfi.
571
- It rebalances once local balances drift 20% from those targets, so a plain
572
- withdrawal is capped at that small local buffer and will fail for anything
573
- larger.
574
-
575
- The cost is transaction size — the Marginfi withdraw CPI adds its bank, oracle,
576
- and liquidity-vault accounts on top of the withdrawal's own accounts. Pass
577
- `lookupTables` to keep the transaction within the packet limit; only fall back
578
- to the local-only path (`skipExternalLiquidity: true`) when you are size-bound
579
- or the vault has no funded external position.
590
+ Regular and instant tranche withdrawal builders first count the live vault ATA
591
+ balance, then visit external liquidity slots in index order. Capacity is bounded
592
+ by the vault's deposit and the bank/reserve's available cash, so borrowed
593
+ liquidity is not counted. Kamino capacity also respects withdrawal caps, queued
594
+ redemptions, and cToken rounding. Automatic routing supports supply-only
595
+ Marginfi and Kamino positions.
596
+
597
+ If local cash plus slot 0 cannot fund the redemption, the builder splits the
598
+ requested shares into sequential withdrawal instructions, continuing with slot
599
+ 1 and later slots until funded. It accounts for fees and integer rounding after
600
+ each burn, skips empty positions, and rejects the whole plan if aggregate
601
+ liquidity is insufficient. Splitting can slightly change rounding compared with
602
+ a single burn. The instructions belong in one atomic transaction.
603
+
604
+ The builder adds protocol lookup tables and one compute-budget instruction.
605
+ Additional `lookupTables` may be needed for large routes. Solana transaction
606
+ size and compute limits still apply. Capacity is a build-time snapshot; simulate
607
+ before sending and rebuild if balances change. `skipExternalLiquidity: true`
608
+ requires enough local cash, while `externalLiquiditySource: 1` pins routing to
609
+ slot 1 instead of scanning all positions.
580
610
 
581
611
  To supply the external route explicitly instead of relying on auto-detection,
582
612
  pass `externalWithdrawIxRefs`, `externalWithdrawAccounts`, and optionally
@@ -673,9 +703,51 @@ const signature = await client.sendTransaction(payer, plan);
673
703
 
674
704
  ### Versioned transactions (lookup tables)
675
705
 
676
- Some builders (e.g. `executeWithdraw`, `protocolInteraction`, `jupiterSwap`)
677
- return `lookupTables` on the plan. `sendTransaction` does **not** compile v0
678
- messages — use `sendVersionedTransaction` from `common` instead:
706
+ All vault transaction builders include the vault-specific LUT from
707
+ `VAULT_LOOKUP_TABLES` when configured, alongside protocol/caller lookup tables.
708
+ Multi-vault operations include both vaults' tables. `sendTransaction` and
709
+ `simulateTransaction` use v0 messages when the plan contains lookup tables;
710
+ they also apply vault mappings to manually assembled vault instructions.
711
+
712
+ Create or update a vault's table from the repository root:
713
+
714
+ ```bash
715
+ pnpm vault-lut prod --vault-id 0 --dry-run
716
+ pnpm vault-lut prod --vault-id 0
717
+ pnpm build:vault-sdk
718
+ ```
719
+
720
+ The script defaults to `~/.config/solana/hw-manager-prod.json` as LUT authority
721
+ and rent payer; `--keypair PATH` selects another signer. This signer owns the
722
+ LUT, independently of vault roles. It discovers an existing table for this
723
+ authority/vault or uses the saved mapping, creates a table if absent, and adds
724
+ only missing addresses in batches of 20. It gathers vault PDAs, holding/share
725
+ mints and ATAs, Marginfi withdrawal/health accounts, and Kamino
726
+ obligation/reserve/oracle/farm accounts, including an empty SOL/BTC position.
727
+ `--user KEY` adds user ATAs; `--accounts A,B,...` adds explicit addresses.
728
+
729
+ The mapping is saved in `src/vaultLookupTables.json` as soon as the table is
730
+ created, so an interrupted extension resumes on the next run. Commit this file
731
+ and rebuild/publish the SDK for downstream consumers. Tables are limited to 256
732
+ addresses; setup fails before extension if the table lacks room. Newly added
733
+ entries must be active in a later slot; the CLI waits for that slot.
734
+
735
+ Applications can supply a mapping immediately without waiting for a new SDK
736
+ release:
737
+
738
+ ```typescript
739
+ const { client } = createVaultClient("prod", {
740
+ keypair: payer,
741
+ vaultLookupTables: { [vault]: vaultLutAddress },
742
+ });
743
+ const plan = await client.tx.executeWithdraw.getTx(withdrawArgs);
744
+ await client.sendTransaction(payer, plan);
745
+ ```
746
+
747
+ Withdrawal `getCpiPlan(...)` and `getIxArgs(...)` also return the vault LUT.
748
+ Single raw `getIx(...)` instructions have no LUT field: use
749
+ `getVaultLookupTables(vault)` or `withVaultLookupTables(plan, programId)` when
750
+ composing outside `VaultClient`. For a custom sender:
679
751
 
680
752
  ```typescript
681
753
  import { fromKitInstruction, sendVersionedTransaction } from "common";
@@ -837,3 +909,46 @@ See [`scripts/README.md`](scripts/README.md) for the full command list.
837
909
  - Public keys use `@solana/kit` `Address` strings in builder args.
838
910
  - Convert from web3.js: `fromWeb3Pk(keypair.publicKey)` (from `common`).
839
911
  - Amounts in `TxArgs` are `bigint` base units unless noted otherwise.
912
+
913
+ ## External liquidity allocation (1.0.57)
914
+
915
+ `ExternalLiquidityIntegrityService` powers the
916
+ `backend-services/vault-external-liquidity` Lambda. For USDC with both protocols
917
+ registered, it targets **35% Marginfi / 65% Kamino SOL/BTC**, excluding the local
918
+ vault buffer. An external rebalance fires outside **30–40% Marginfi / 60–70%
919
+ Kamino** and restores 35/65. Exactly on a boundary remains inside the band.
920
+ `marginfiWeightBps` (default `3500`) and `externalWeightBandBps` (default `500`)
921
+ configure the target and absolute percentage-point tolerance.
922
+
923
+ The existing buffer remains: local USDC worth 1.5% of total vault TVL, or 0.5%
924
+ of each other supported holding, with a 20%-of-target buffer band. Buffer
925
+ replenishment takes priority. A buffer adjustment also distributes remaining
926
+ external funds toward the target weights. Single-protocol holdings continue to
927
+ use that protocol; both initialized, registered positions are required for the
928
+ USDC split. Other configured stablecoins continue to use Marginfi.
929
+
930
+ Allocation uses each live position's supplied balance, including accrued value,
931
+ and the actual vault token balance. Withdrawal capacity also checks bank/reserve
932
+ cash, Kamino withdrawal caps and queued redemptions. Utilization can limit a
933
+ rebalance to a partial transfer; subsequent runs retry if weights remain outside
934
+ the band. Each
935
+ mint's withdrawals and deposits share one atomic transaction and use the vault's
936
+ configured LUT. If a destination rejects a deposit, the whole transaction fails
937
+ and is reported for retry. No transfer is left half-completed.
938
+
939
+ Kamino deposits use `buildKaminoDepositInteraction` and the appended
940
+ `KLEND_DEPOSIT_AND_COLLATERALIZE_V2` CPI. The vault validates the registered
941
+ obligation, vault-owned source ATA, reserve, market, token programs, farms and
942
+ absence of borrowing. Only the hardware manager can deposit. **Upgrade the vault
943
+ program with this CPI support before deploying the Lambda with SDK 1.0.57.**
944
+ Publish the SDK and then rebuild/deploy the backend consumer; changing the local
945
+ package version alone does not update a running Lambda.
946
+
947
+ ```sh
948
+ # Simulate all eligible vaults; no transactions are submitted.
949
+ pnpm run-external-liquidity-service prod --dry-run
950
+
951
+ # Explicitly supply the default allocation policy.
952
+ pnpm run-external-liquidity-service prod --dry-run \
953
+ --marginfi-weight-bps 3500 --external-weight-band-bps 500
954
+ ```
@@ -155,6 +155,8 @@ interface PreparedSquadsTransaction {
155
155
  transactionIndex: bigint;
156
156
  proposalPda: PublicKey;
157
157
  vaultTransactionPda: PublicKey;
158
+ /** Failed inner-instruction simulation explicitly allowed by the caller. */
159
+ executionSimulationError?: SquadsProposalExecutionSimulationError;
158
160
  }
159
161
  type PreparedVaultTransaction = PreparedDirectTransaction | PreparedSquadsTransaction;
160
162
  type SquadsSimulationTransactionError = NonNullable<SimulatedTransactionResponse["err"]>;
@@ -209,6 +211,8 @@ declare function prepareVaultTransaction(args: {
209
211
  squadsRoutes?: readonly SquadsWalletRouteConfig[];
210
212
  /** Skip execution simulation when proposing a transaction that depends on future funding. */
211
213
  skipSimulation?: boolean;
214
+ /** Still prepare the proposal if its inner instructions fail simulation; return the failure for display. */
215
+ allowExecutionSimulationFailure?: boolean;
212
216
  addressLookupTableAccounts?: AddressLookupTableAccount[];
213
217
  /** Temporary signer keys used only in account metas, replaced by Squads PDAs. */
214
218
  ephemeralSignerKeys?: readonly PublicKey[];
@@ -746,14 +746,22 @@ async function prepareVaultTransaction(args) {
746
746
  );
747
747
  }
748
748
  const { blockhash } = await connection.getLatestBlockhash("confirmed");
749
+ let executionSimulationError;
749
750
  if (!args.skipSimulation) {
750
- await simulateSquadsProposalExecution({
751
- connection,
752
- feePayer: proposer,
753
- instructions: instructions2,
754
- recentBlockhash: blockhash,
755
- addressLookupTableAccounts: args.addressLookupTableAccounts
756
- });
751
+ try {
752
+ await simulateSquadsProposalExecution({
753
+ connection,
754
+ feePayer: proposer,
755
+ instructions: instructions2,
756
+ recentBlockhash: blockhash,
757
+ addressLookupTableAccounts: args.addressLookupTableAccounts
758
+ });
759
+ } catch (error) {
760
+ if (!args.allowExecutionSimulationFailure || !(error instanceof SquadsProposalExecutionSimulationError)) {
761
+ throw error;
762
+ }
763
+ executionSimulationError = error;
764
+ }
757
765
  }
758
766
  const transactionMessage = new TransactionMessage4({
759
767
  payerKey: route.vaultPda,
@@ -794,7 +802,8 @@ async function prepareVaultTransaction(args) {
794
802
  vaultIndex: route.vaultIndex,
795
803
  transactionIndex,
796
804
  proposalPda,
797
- vaultTransactionPda
805
+ vaultTransactionPda,
806
+ ...executionSimulationError ? { executionSimulationError } : {}
798
807
  };
799
808
  }
800
809