Skip to main content

Documentation Index

Fetch the complete documentation index at: https://kamino.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks through switching a vault into permissioned mode and managing the depositor allowlist. For the conceptual model — the cosigner pattern, the on-chain pieces, and what each operation gates — read Permissioned vaults first.
kperm is live for Kamino lending markets today. Vault-side support ships with the next kvault release; the SDK, CLI, and on-chain mechanics described here mirror the existing permissioned markets flow.

Before you start

You’ll need:
  • An existing vault. See Create a vault.
  • The vault admin keypair, or — in production — a Squads multisig with the admin role. See Transfer admin to multisig.
  • A list of wallet addresses to whitelist, or an automated source feeding them (e.g. a KYC backend that signals verified wallets to your service).

Curator workflow

1

Decide what to permission

For most vaults, the answer is DEPOSIT. That gates entry; withdrawals remain open so depositors can always redeem their shares. Add KEYRING if your gating logic lives in an external policy program. You can change the gated set later by re-running the update with a different bitfield.
2

Switch the vault into permissioned mode

Set permissioning_authority to the Vault Permissioner PDA and permissioned_ops to the chosen bitfield. From this point, kvault requires the kperm cosigner for every gated action.
3

Whitelist users

Use the SDK, CLI, or REST API (see tabs below). All three issue the same init_user_permission (first grant) or update_user_permission (subsequent grants) instruction on-chain.
4

Test the rejection path

Send a deposit transaction from an un-whitelisted wallet on staging and confirm it reverts. Add that wallet to the allowlist and retry — it should succeed.

Configure permissioning via SDK

The kperm-aware methods live in @kamino-finance/klend-sdk alongside KaminoManager and KaminoVault.

Curator: switch the vault into permissioned mode

import {
  KaminoManager,
  KaminoVault,
  DEFAULT_RECENT_SLOT_DURATION_MS,
  PermissionedOp,
  getVaultPermissionerPda,
} from '@kamino-finance/klend-sdk';
import { PROGRAM_ID as KPERM_PROGRAM_ID } from '@kamino-finance/klend-sdk/dist/@codegen/kperm/programId';
import { address } from '@solana/kit';

const kaminoManager = new KaminoManager(rpc, DEFAULT_RECENT_SLOT_DURATION_MS);

const vaultAddress = address('<VAULT_ADDRESS>');
const vault = new KaminoVault(rpc, vaultAddress);

const vaultPermissioner = await getVaultPermissionerPda(vaultAddress, KPERM_PROGRAM_ID);
const permissionedOpsBitfield = PermissionedOp.fromString('DEPOSIT');

const ixs = await kaminoManager.updateVaultPermissionIxs(
  adminSigner,
  vault,
  vaultPermissioner,
  permissionedOpsBitfield,
);
// submit each ix in order
PermissionedOp.fromString accepts pipe-separated names: 'DEPOSIT', 'DEPOSIT|KEYRING', etc.

Whitelist a user (initial grant or update)

import { initPermissionIx, updatePermissionIx, getPermissionPda } from '@kamino-finance/klend-sdk';
import { UserPermission } from '@kamino-finance/klend-sdk/dist/@codegen/kperm/accounts';

const userAddress = address('<USER_WALLET>');
const grantedOps = PermissionedOp.fromString('DEPOSIT');

const userPermissionPda = await getPermissionPda(vaultAddress, userAddress, KPERM_PROGRAM_ID);
const existing = await UserPermission.fetch(rpc, userPermissionPda, KPERM_PROGRAM_ID);

const ix = existing
  ? await updatePermissionIx(globalAdminSigner, vaultAddress, userAddress, grantedOps, /* overwrite */ false)
  : await initPermissionIx(globalAdminSigner, vaultAddress, userAddress, grantedOps);
// Sign and submit with the global admin's signer
updatePermissionIx takes an overwrite flag: true replaces the user’s allowed ops with grantedOps; false bitwise-ORs grantedOps into the existing set.

Revoke a user

import { removePermissionIx } from '@kamino-finance/klend-sdk';

const ix = await removePermissionIx(globalAdminSigner, vaultAddress, userAddress, KPERM_PROGRAM_ID);
// Sign and submit
The UserPermission account stays on-chain with permissioned_ops = 0. Re-granting later avoids paying rent again.

User flow: deposit into a permissioned vault

The standard vault deposit builders detect when the target vault is permissioned and automatically wrap the produced kvault instructions with permissioned_fwd_to_kvault. Apps integrating against a permissioned vault continue to call the standard deposit builders — the SDK handles the wrap.For instructions built outside of the standard builders, wrap manually:
import { permissionedFwdToKvault } from '@kamino-finance/klend-sdk/dist/@codegen/kperm/instructions';
import { KVAULT_PROGRAM_ID, getPermissionPda } from '@kamino-finance/klend-sdk';

const userPermissionPda = await getPermissionPda(vaultAddress, userSigner.address, KPERM_PROGRAM_ID);

const wrapped = permissionedFwdToKvault(
  { ixData: kvaultIx.data },
  {
    user: userSigner,
    targetProgram: KVAULT_PROGRAM_ID,
    userPermission: userPermissionPda,
  },
  /* remainingAccounts = */ [...kvaultIx.accounts],
  KPERM_PROGRAM_ID,
);
// Submit `wrapped` as the user's transaction

On-chain accounts

AccountProgramPDA seedsStores
VaultStatekvault(vault account)permissioning_authority (Vault Permissioner PDA), permissioned_ops (u64 bitfield)
UserPermissionkperm["permission", vault, user]vault, user, permissioned_ops, cached vaultPermissioner, bumps
Vault Permissionerkperm["vault", vault]Authority PDA the kperm program signs as when CPI’ing into kvault (signature-only; the PDA stores no data)
GlobalConfigkperm["global_config"]globalAdmin, pendingAdmin for the kperm program
Program ID for kperm: KPermUZsf9tu4cSd9LNcMojCbiHfdbJXv9dr3pAUzz1.

Operational considerations

TopicDetail
PerformancePermissioned actions add one CPI hop (kperm → kvault). Compute-unit cost rises modestly; users may need to bump priority fees on busy slots
MultisigWhen the vault admin is a Squads multisig, update-vault-permission becomes a multisig proposal subject to the standard threshold and review flow
Whitelist authorityThe kperm globalAdmin is shared across markets and vaults. For most curator vaults, a Kamino-controlled admin holds it; the curator delegates whitelisting to the REST API. For curators that want full sovereignty, coordinate with the team to use a curator-owned admin
ComposabilityPermissioning is enforced at the kvault-instruction boundary. Programs that integrate with kvault by composing instructions (third-party UIs, aggregators) must use permissioned_fwd_to_kvault to interact with a permissioned vault
Exit semanticsWithdrawals are always open. To shape the exit flow, use the standard vault liquidity and withdrawal primitives
Stacking with Whitelisted ReservesPermissioning controls who deposits; Whitelisted Reserves controls where capital flows. Both can be enabled together for the strongest institutional configuration

Reference