npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

perena-vault-sdk

v1.0.10

Published

Vault program helpers for Bankineco integrations.

Readme

Vault SDK

TypeScript helpers for the Bankineco vault program. The SDK wraps PDA derivation, account fetching, and instruction building behind a single VaultClient. Every on-chain instruction has a transaction builder on client.tx that returns a VaultTransactionPlan you can sign and send.

For runnable CLI examples, see scripts/README.md.

Install

From the monorepo (workspace package name is vault):

pnpm install
pnpm --filter vault run build

In another workspace package:

{
  "dependencies": {
    "vault": "workspace:*"
  }
}

Quick start

Option A — createVaultClient (scripts and backend services)

Use this when targeting deployed test or prod networks. It wires RPC, keypair, and program id from env vars (see .env.example).

import { createVaultClient } from "vault";

const { client, payer, signer } = createVaultClient("test", {
  role: "vault", // default keypair: ~/.config/solana/vault-test.json
});

const vault = "..." as Address; // vault PDA
const vaultAccount = await client.account.fetchVault(vault);

const plan = await client.tx.executeDeposit.getTx({
  user: signer,
  vault,
  assetMint: vaultAccount.config.baseAssetMint,
  shareMint: vaultAccount.config.shareMint,
  amount: 1_000_000n, // base units (6 decimals → 1.0 UI)
});

const signature = await client.sendTransaction(payer, plan);
console.log(signature);

Option B — manual VaultClient (tests, custom integrations)

Use this when you already have a Connection and signer:

import { AnchorProvider } from "@anchor-lang/core";
import { Connection, Keypair } from "@solana/web3.js";
import { fromWeb3Pk } from "common";
import { VaultClient, makeProvider } from "vault";

const connection = new Connection("http://127.0.0.1:8899", "confirmed");
const payer = Keypair.generate();
const client = new VaultClient(makeProvider(connection, payer));

const [vault] = await client.pda.deriveVaultPda(0); // vault id

Creating transactions

The builder pattern

Every instruction lives on client.tx.<builderName>. Builders follow the same contract:

| Method | Returns | When to use | | --------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | getTx(txArgs) | VaultTransactionPlan | Preferred. Derives PDAs/ATAs, prepends setup ixs (e.g. idempotent ATA creates), and attaches cache invalidation metadata. | | getIx(ixArgs) | Instruction | Low-level. You supply every account address yourself. |

TxArgs types are the high-level inputs (curator, vault, amounts as bigint, optional PDAs). IxArgs types are the fully-resolved account sets the program expects. Type definitions live in src/client/builders/args.ts.

A typical flow:

// 1. Build a plan
const plan = await client.tx.executeDeposit.getTx({
  user: signer,
  vault,
  assetMint,
  shareMint,
  amount: 500_000n,
});

// 2. Send it (signs, confirms, clears relevant account cache)
const sig = await client.sendTransaction(payer, plan);

VaultTransactionPlan contains:

  • instructions — ordered @solana/kit instructions (preamble + program ix)
  • lookupTables? — optional address lookup tables for v0 transactions
  • postSuccessCacheInvalidations? — vault cache keys to clear after success

Direct Rust CPI integrations

For integrations that build a Rust CPI into the vault program, some builders also expose the resolved Anchor account map and method arguments separately:

  • getIxAccounts(ixArgs) — named accounts passed to Anchor's accountsPartial
  • getIxData(ixArgs) — instruction method arguments, such as amounts and tranche kind

These helpers are currently available on:

  • client.tx.executeDeposit
  • client.tx.executeWithdraw
  • client.tx.executeTrancheDeposit
  • client.tx.executeTrancheWithdraw

Use these when a TypeScript integration wants the SDK to stay the source of truth for the account layout and argument shape, but your on-chain Rust program performs the final CPI. They accept fully-resolved IxArgs, so derive PDAs/ATAs with client.pda, account fetches, or getAtaAddress(...) before calling them. The returned data is not serialized instruction bytes; it is the same account map and argument values that getIx(...) uses internally.

import { BN } from "@anchor-lang/core";

const ixArgs = {
  user,
  vault,
  vaultOracle,
  vaultTrancheState: null,
  assetMint,
  shareMint,
  amount: new BN("1000000"),
  assetTokenProgram,
  shareTokenProgram,
  userAssetAta,
  vaultAssetAta,
  feeVault,
  feeVaultAta,
  userShareAta,
};

const accounts = client.tx.executeDeposit.getIxAccounts(ixArgs);
const data = client.tx.executeDeposit.getIxData(ixArgs);

// Example payload to hand to your Rust integration layer.
const cpiPayload = {
  accounts: Object.fromEntries(
    Object.entries(accounts).map(([name, pubkey]) => [
      name,
      pubkey?.toString() ?? null,
    ])
  ),
  data: {
    amount: data.amount.toString(),
  },
};

In Rust, map those account addresses into the matching Anchor CPI account struct and pass the data fields into the generated CPI method. For example, executeDeposit.getIxData(...) returns { amount }, which corresponds to the amount argument on the vault program's execute_deposit CPI.

Quoting conversions

Use client.quote.quote(...) to calculate expected output amounts before building a transaction. Quotes fetch the current vault/tranche state, calculate using the same fee formulas as the program, and require a signer address so the SDK can check whether the signer is owned by a fee-exempt program.

All amounts are base units. expectedAmountOut is the net amount after fees; grossAmountOut, feeAmount, feeBps, and feeExempt explain the quote.

Regular share mint

// Asset -> regular vault shares
const depositQuote = await client.quote.quote({
  shareClass: "regular",
  direction: "deposit",
  signer,
  vault,
  assetMint,
  amount: 1_000_000n,
});

console.log(depositQuote.expectedAmountOut); // regular shares to receive
console.log(depositQuote.feeExempt); // true when signer owner is whitelisted

// Regular vault shares -> asset
const withdrawQuote = await client.quote.quote({
  shareClass: "regular",
  direction: "withdraw",
  signer,
  vault,
  assetMint,
  amount: 500_000n, // share amount to burn
});

console.log(withdrawQuote.expectedAmountOut); // asset tokens to receive

Junior and senior tranches

// Asset -> junior tranche shares
const juniorDepositQuote = await client.quote.quote({
  shareClass: "junior",
  direction: "deposit",
  signer,
  vault,
  assetMint,
  amount: 1_000_000n,
});

// Senior works the same way.
const seniorDepositQuote = await client.quote.quote({
  shareClass: "senior",
  direction: "deposit",
  signer,
  vault,
  assetMint,
  amount: 1_000_000n,
});

For junior withdrawals, pass withdrawalMode: "instant" to quote executeTrancheWithdraw, or withdrawalMode: "queued" to quote fulfillJuniorTrancheWithdraw after lockup. Senior withdrawals are already fee-free, but still support the same quote shape.

// Junior tranche shares -> asset through instant withdrawal.
const instantJuniorWithdrawQuote = await client.quote.quote({
  shareClass: "junior",
  direction: "withdraw",
  withdrawalMode: "instant",
  signer,
  vault,
  assetMint,
  amount: 100_000n, // junior shares to burn
});

// Junior tranche shares -> asset through queued fulfillment.
const queuedJuniorWithdrawQuote = await client.quote.quote({
  shareClass: "junior",
  direction: "withdraw",
  withdrawalMode: "queued",
  signer,
  vault,
  assetMint,
  amount: 100_000n,
});

// Senior tranche shares -> asset.
const seniorWithdrawQuote = await client.quote.quote({
  shareClass: "senior",
  direction: "withdraw",
  signer,
  vault,
  assetMint,
  amount: 100_000n,
});

If you already derived the tranche state PDA, pass vaultTrancheState to avoid an extra PDA derivation/fetch. Pass fresh: true when quoting immediately after another transaction and you want to bypass the SDK account cache.

Example: deposit

const plan = await client.tx.executeDeposit.getTx({
  user: signer,
  vault,
  assetMint,
  shareMint,
  amount: 1_000_000n,
});
await client.sendTransaction(payer, plan);

The builder automatically:

  • Derives vaultOracle from the vault PDA
  • Computes user/vault ATAs
  • Prepends idempotent ATA-create instructions where needed

Example: withdraw

const plan = await client.tx.executeWithdraw.getTx({
  user: signer,
  vault,
  assetMint,
  shareMint,
  shareAmount: 500_000n,
});
await client.sendTransaction(payer, plan);

For withdrawals routed through external liquidity (CPI), pass externalWithdrawIxRefs, externalWithdrawAccounts, and optionally lookupTables — see ExecuteWithdrawTxArgs in args.ts.

Example: vault setup (multi-step)

Deploy flows compose several builders in sequence:

const curator = fromWeb3Pk(payer.publicKey);

await client.sendTransaction(
  payer,
  await client.tx.createVault.getTx({
    curator,
    shareMint,
    strictAssetMint: assetMint,
    assetDecimals: 6,
  })
);

await client.sendTransaction(
  payer,
  await client.tx.createTrancheState.getTx({
    curator,
    vault,
    juniorShareMint,
    seniorShareMint,
    seniorFixedApyBps: 1_000,
  })
);

await client.sendTransaction(
  payer,
  await client.tx.updateConsensusSigners.getTx({
    curator,
    vault,
    signers: [curator],
  })
);

Example: tranche deposit

const plan = await client.tx.executeTrancheDeposit.getTx({
  user: signer,
  vault,
  assetMint,
  trancheShareMint: juniorMint,
  kind: { junior: {} }, // or { senior: {} }
  amount: 1_000_000n,
});
await client.sendTransaction(payer, plan);

Example: junior withdrawal queue

// User queues a locked junior withdraw
await client.sendTransaction(
  payer,
  await client.tx.requestJuniorTrancheWithdraw.getTx({
    owner: signer,
    vault,
    juniorMint,
    queueId: 0,
    shareAmount: 100_000n,
  })
);

// Fulfiller pays out after lockup (see WithdrawalQueueService for batch logic)
await client.sendTransaction(
  fulfiller,
  await client.tx.fulfillJuniorTrancheWithdraw.getTx({
    fulfiller: fromWeb3Pk(fulfiller.publicKey),
    owner,
    vault,
    queueId: 0,
  })
);

Sending transactions

Built-in helper

VaultClient.sendTransaction builds a legacy Transaction, signs with the fee payer, sends, confirms, and applies cache invalidations from the plan:

const signature = await client.sendTransaction(payer, plan);

Versioned transactions (lookup tables)

Some builders (e.g. executeWithdraw, protocolInteraction, jupiterSwap) return lookupTables on the plan. sendTransaction does not compile v0 messages — use sendVersionedTransaction from common instead:

import { fromKitInstruction, sendVersionedTransaction } from "common";

const plan = await client.tx.protocolInteraction.getTx({
  /* ... */
});

const signature = await sendVersionedTransaction({
  connection: client.provider.connection,
  payer,
  instructions: plan.instructions.map(fromKitInstruction),
  lookupTables: plan.lookupTables ?? [],
});

client.applyCacheInvalidations(plan);

Composing your own transaction

You can merge instructions from multiple builders or mix in custom ixs:

const depositPlan = await client.tx.executeDeposit.getTx({ /* ... */ });
const customIx = /* your instruction */;

const combined: VaultTransactionPlan = {
  instructions: [...depositPlan.instructions, customIx],
  postSuccessCacheInvalidations: depositPlan.postSuccessCacheInvalidations,
};

Or call getIx directly when you only need the program instruction:

const ix = await client.tx.executeDeposit.getIx({
  user: signer,
  vault,
  vaultOracle,
  vaultTrancheState: null,
  assetMint,
  shareMint,
  amount: new BN("1000000"),
  assetTokenProgram: TOKEN_PROGRAM_ID,
  shareTokenProgram: TOKEN_PROGRAM_ID,
  userAssetAta,
  vaultAssetAta,
  userShareAta,
});

Reading on-chain state

Use client.account to fetch decoded vault accounts (cached by default, TTL 60s):

const vaultAccount = await client.account.fetchVault(vault);
const oracle = await client.account.fetchVaultOracle(vaultOraclePda);
const trancheState = await client.account.fetchVaultTrancheState(tranchePda);

// Force a fresh read after an external tx
const fresh = await client.account.fetchVault(vault, { fresh: true });
client.clearAllCache();

Derive PDAs with client.pda:

const [vault] = await client.pda.deriveVaultPda(vaultId);
const [vaultOracle] = await client.pda.deriveVaultOraclePda(vault);
const [trancheState] = await client.pda.deriveVaultTrancheStatePda(vault);
const [queue] = await client.pda.deriveWithdrawalQueuePda(
  vault,
  owner,
  queueId
);

Available transaction builders

All builders are on client.tx:

| Builder | Program instruction | Typical signer | | ------------------------------ | --------------------------------- | ---------------- | | createVault | create_vault | Curator | | createAssetHolding | create_asset_holding | HW manager | | removeAssetHolding | remove_asset_holding | HW manager | | updateConsensusSigners | update_consensus_signers | Curator | | createTrancheState | create_tranche_state | Curator | | setVaultConfig | set_vault_config | Curator | | updateTrancheConfig | update_tranche_config | Curator | | activateCircuitBreaker | activate_circuit_breaker | CB trigger | | disableCircuitBreaker | disable_circuit_breaker | Curator | | updateConsensusOracle | update_consensus_oracle | Consensus signer | | executeDeposit | execute_deposit | User | | executeTrancheDeposit | execute_tranche_deposit | User | | executeTrancheWithdraw | execute_tranche_withdraw | User | | requestJuniorTrancheWithdraw | request_junior_tranche_withdraw | Owner | | cancelJuniorTrancheWithdraw | cancel_junior_tranche_withdraw | Owner | | fulfillJuniorTrancheWithdraw | fulfill_junior_tranche_withdraw | Fulfiller | | executeWithdraw | execute_withdraw | User | | managerWithdrawAsset | manager_withdraw_asset | HW manager | | managerRedepositAsset | manager_redeposit_asset | HW manager | | protocolInteraction | protocol_interaction | HW manager | | jupiterSwap | jupiter_swap | HW manager | | setExternalLiquidity | set_external_liquidity | HW manager | | setAssetPriceOracle | set_asset_price_oracle | Curator | | updateAssetPrice | update_asset_price | Oracle keeper | | vaultReallocation | vault_reallocation | HW manager | | withdrawProtocolFees | withdraw_protocol_fees | Curator | | withdrawTrancheFees | withdraw_tranche_fees | Curator |

Argument shapes for each builder: src/client/builders/args.ts.

Higher-level services

For recurring backend work, prefer the services layer over reimplementing discovery logic in your app:

  • WithdrawalQueueService — scan and fulfill eligible junior withdrawal queues (src/services/withdrawalQueueService.ts)
  • OracleService — consensus oracle / asset price updates (src/services/oracleService.ts)

These services call the same client.tx.*.getTx builders internally.

Environment and RPC

Copy .env.example to .env. RPC resolution order:

  1. VAULT_RPC_URL_TEST / VAULT_RPC_URL_PROD
  2. RPC_URL
  3. HELIUS_API_KEY (auto-built Helius URL)

Program ids per environment: getVaultProgramId("test" | "prod") in src/env.ts.

CLI scripts

Thin wrappers around the SDK live in scripts/. From the repo root:

pnpm deposit test <VAULT_PDA> 100
pnpm inspect prod <VAULT_PDA>

See scripts/README.md for the full command list.

Types and addresses

  • Public keys use @solana/kit Address strings in builder args.
  • Convert from web3.js: fromWeb3Pk(keypair.publicKey) (from common).
  • Amounts in TxArgs are bigint base units unless noted otherwise.