A third-party smart contract vault can integrate with Kasu to give its depositors access to Kasu lending pool yields. The vault is KYB'd (allowlisted) on Kasu's side, so individual vault depositors do not need to complete KYC — the vault itself is the single, verified depositor.
The vault maintains a USDC liquidity buffer for instant user deposits and withdrawals, since Kasu deposits/withdrawals are processed during clearing (weekly epochs).
The vault is a single address allowlisted via KasuAllowList.allowUser(). No Compilot/NexeraID KYC signature is needed per transaction.
The vault operator configures which pools and tranches the vault allocates to. This can be a one-time configuration or dynamically managed.
All interactions go through LendingPoolManager — the vault calls the same user-facing functions as any other depositor.
Prerequisites
Before the vault can interact with Kasu:
1. Kasu Admin Allowlists the Vault
The Kasu Admin must call allowUser() on the KasuAllowList contract to allowlist the vault address:
Once allowlisted, the vault can call requestDeposit() directly (without the WithKyc variant and without KYC signatures appended to calldata).
2. USDC Approval
Before each deposit, the vault must approve the LendingPoolManager to spend USDC:
3. Contract Addresses
The vault needs to know the addresses of:
Contract
Purpose
LendingPoolManager
Entry point for all deposit/withdrawal operations
USDC
The deposit token
Target lending pool(s)
The pools to deposit into
Pool and tranche addresses can be discovered on-chain via ILendingPool.lendingPoolInfo() (see Reading Pool State).
All vault interactions go through LendingPoolManager. The vault calls these functions as msg.sender.
Requesting a Deposit
Deposits are not instant. They are queued for the current epoch and processed at the next clearing.
Parameters:
Parameter
Value for Vault
lendingPool
Address of the target lending pool
tranche
Address of the target tranche (get from lendingPoolInfo().trancheAddresses)
maxAmount
USDC amount to deposit (6 decimals)
swapData
"" (empty bytes) — vault deposits USDC directly
fixedTermConfigId
0 for flexible deposit, or a valid config ID for fixed-term
depositData
"" (empty bytes) or any custom data for off-chain tracking
Returns: A D-NFT ID (dNftID) representing the pending deposit request. The vault receives this ERC-721 token from the pool's PendingPool contract.
Example:
Important: Deposits submitted during the clearing period (~48 hours at the end of each epoch) are accepted but queued for the next epoch, not the current one. Cancellations are not allowed during the clearing period.
Cancelling a Deposit Request
A pending deposit can be cancelled before clearing processes it. USDC is refunded to the vault.
Cancellations cannot be submitted during the clearing period.
Requesting a Withdrawal
Withdrawals are also queued and processed at the next clearing.
Parameters:
Parameter
Description
lendingPool
Address of the lending pool
tranche
Address of the tranche
amount
Number of tranche shares to withdraw (not USDC). Use ILendingPoolTranche.userActiveShares(vault) to get the vault's share balance.
Returns: A W-NFT ID (wNftID) representing the pending withdrawal request.
The USDC value of shares depends on the current exchange rate. Use ILendingPoolTranche.convertToAssets(shares) to estimate the USDC equivalent.
Important: Withdrawals may be partially filled during clearing if there isn't enough liquidity. Unfilled shares remain in the vault's position and can be re-requested in the next epoch.
Cancelling a Withdrawal Request
Cancellations cannot be submitted during the clearing period.
Fixed-Term Deposits
The vault can lock deposits for a fixed duration with a guaranteed interest rate. Fixed-term deposits cannot be withdrawn early.
Locking an existing deposit into a fixed term:
Requesting withdrawal of a matured fixed-term deposit:
Cancelling a fixed-term withdrawal request:
See Fixed Term Deposits for details on fixed-term configurations and maturity.
Claiming Repaid Losses
If a loss event occurs and is later repaid by the pool operator, the vault can claim its share:
The vault should read on-chain state to make informed allocation decisions and track its positions.
Pool Information
Vault Position (Per Tranche)
Each tranche is an ERC-4626 vault. The vault's position is tracked as tranche shares:
Pending Requests
APY & Yield
Reading the current interest rate (APY):
Each tranche has an epoch interest rate stored on-chain. This is the rate applied per epoch (7 days), not annualized:
To convert to APY (annualized):
Where 52.17857 is the number of 7-day epochs per year (365.25 / 7).
Accumulated yield for the vault:
There is no single on-chain view function that returns accumulated yield. Yield is embedded in the ERC-4626 share price — as interest accrues each epoch, each tranche share becomes worth more USDC.
To calculate the vault's accumulated yield, track deposits and withdrawals and compare against the current position value:
totalDeposited = sum of all accepted deposit amounts (from DepositAccepted events)
totalWithdrawn = sum of all accepted withdrawal amounts (from WithdrawalAccepted events)
The vault contract should maintain its own accounting of deposited and withdrawn amounts, or index the relevant events off-chain:
Overall pool yield:
Total interest distributed to a pool's LPs is available via InterestApplied events emitted by the LendingPool contract at each clearing:
Epoch Timing
The vault should be aware of epoch boundaries and clearing periods. Deposits and withdrawals submitted during clearing are queued for the next epoch. Cancellations are blocked during clearing. The SystemVariables contract provides epoch timing:
Vault Lifecycle
1. Setup
Deploy the vault contract with references to LendingPoolManager, USDC, and target pool addresses
Complete KYB — the vault operator undergoes KYB verification with Kasu
Kasu Admin allowlists the vault address via kasuAllowList.allowUser(vaultAddress)
Configure allocations — define which pools and tranches to deposit into
2. Depositing into Kasu
Vault accumulates USDC from its own depositors
Based on allocation strategy, vault calls usdc.approve() then requestDeposit() for each target pool/tranche
Deposit is queued — vault receives a D-NFT
At the next clearing (end of epoch), the deposit is processed:
If accepted: USDC is transferred, vault receives tranche shares
If rejected (e.g., pool capacity reached): USDC is returned, D-NFT is burned
Vault can cancel before clearing via cancelDepositRequest()
3. Earning Yield
Interest accrues automatically each epoch. Tranche shares increase in value relative to USDC.
The vault's position value can be tracked via userActiveAssets(vault).
No action is required from the vault to earn yield — it accrues passively.
4. Withdrawing from Kasu
Vault calls requestWithdrawal() specifying the tranche shares to withdraw
Withdrawal is queued — vault receives a W-NFT
At the next clearing:
If accepted: tranche shares are burned, USDC is returned to the vault
If partially accepted: remaining shares stay in the vault's position
Vault distributes received USDC to its own users
5. Handling Losses
If a loss event is reported on a pool where the vault has a position:
Loss tokens (ERC-1155) may be minted to the vault address
Loss tokens represent the vault's share of unrealized losses
If the loss is later repaid, vault calls claimRepaidLoss() to recover USDC
~48 hours at end of epoch — deposits/withdrawals queue for next epoch, cancellations blocked
The vault should maintain a USDC buffer large enough to service its own user withdrawals between Kasu clearing cycles.
Deposit Limits
Each tranche has minimum and maximum deposit amounts. The vault must respect these:
These limits apply per deposit request, not cumulative. Multiple deposit requests can be submitted in the same epoch.
NFT Management
The vault receives ERC-721 tokens (D-NFTs and W-NFTs) from the PendingPool contract representing pending requests. The vault contract must be able to receive ERC-721 tokens (implement IERC721Receiver or use onERC721Received).
Similarly, loss tokens are ERC-1155. The vault must implement IERC1155Receiver if it needs to handle loss scenarios.
Gas Considerations
Each requestDeposit() and requestWithdrawal() call operates on a single pool and tranche. If the vault allocates across multiple pools/tranches, each requires a separate transaction.
Clearing Failures
If clearing does not execute for a given epoch (e.g., the pool's clearing manager does not trigger it), pending requests remain queued. They carry over to the next epoch's clearing. The vault can cancel pending requests at any time outside of the clearing period.
// Called by Kasu Admin (ROLE_KASU_ADMIN)
kasuAllowList.allowUser(vaultAddress);
usdc.approve(lendingPoolManagerAddress, amount);
function requestDeposit(
address lendingPool, // Target lending pool address
address tranche, // Target tranche address within the pool
uint256 maxAmount, // Maximum USDC amount to deposit
bytes calldata swapData, // Pass empty bytes (0x) — no swap needed for USDC
uint256 fixedTermConfigId, // 0 for flexible deposits, or a fixed-term config ID
bytes calldata depositData // Optional arbitrary data (emitted as event, not stored)
) external payable returns (uint256 dNftID);
// Approve USDC first
usdc.approve(address(lendingPoolManager), 100_000e6);
// Request deposit of 100,000 USDC to Pool A, Tranche 0 (Junior)
uint256 dNftId = lendingPoolManager.requestDeposit(
poolA,
poolATranches[0], // Junior tranche
100_000e6, // 100,000 USDC
"", // No swap
0, // Flexible deposit (no fixed term)
"" // No deposit data
);
function cancelDepositRequest(
address lendingPool, // The lending pool
uint256 dNftID // The D-NFT ID from requestDeposit
) external;
function requestWithdrawal(
address lendingPool, // The lending pool
address tranche, // The tranche to withdraw from
uint256 amount // Amount of tranche shares to withdraw
) external returns (uint256 wNftID);
function cancelWithdrawalRequest(
address lendingPool, // The lending pool
uint256 wNftID // The W-NFT ID from requestWithdrawal
) external;
function lockDepositForFixedTerm(
address lendingPool, // The lending pool
address tranche, // The tranche
uint256 amount, // Tranche shares to lock
uint256 fixedTermConfigId // The fixed-term configuration ID
) external;
function requestFixedTermDepositWithdrawal(
address lendingPool, // The lending pool
uint256 fixedTermDepositId // The fixed-term deposit ID
) external;
function cancelFixedTermDepositWithdrawalRequest(
address lendingPool, // The lending pool
uint256 fixedTermDepositId // The fixed-term deposit ID
) external;
function claimRepaidLoss(
address lendingPool, // The lending pool
address tranche, // The tranche
uint256 lossId // The loss event ID
) external returns (uint256 claimedAmount);
// Get tranche addresses and pending pool address
ILendingPool(lendingPool).lendingPoolInfo()
returns (LendingPoolInfo memory)
// .trancheAddresses — address[] of tranches (index 0 = Junior)
// .pendingPool — PendingPool contract address
// Get full pool configuration
ILendingPool(lendingPool).poolConfiguration()
returns (PoolConfiguration memory)
// .tranches[] — tranche configs (ratio, interestRate, minDeposit, maxDeposit)
// .drawRecipient — where drawn funds go
// .desiredDrawAmount — how much the pool wants to deploy
// .targetExcessLiquidityPercentage
// .minimumExcessLiquidityPercentage
// Check deposit limits for a tranche
ILendingPool(lendingPool).trancheConfigurationDepositLimits(tranche)
returns (uint256 minDepositAmount, uint256 maxDepositAmount)
// Available USDC in the pool (not drawn)
ILendingPool(lendingPool).availableFunds()
returns (uint256)
// Number of tranches
ILendingPool(lendingPool).lendingPoolTrancheCount()
returns (uint256)
// All tranche addresses
ILendingPool(lendingPool).lendingPoolTranches()
returns (address[] memory)
// Verify a pool exists
ILendingPoolManager(lendingPoolManager).isLendingPool(address)
returns (bool)
// Vault's active tranche shares (excludes shares locked in pending withdrawals)
ILendingPoolTranche(tranche).userActiveShares(vaultAddress)
returns (uint256)
// Vault's active position in USDC terms
ILendingPoolTranche(tranche).userActiveAssets(vaultAddress)
returns (uint256)
// Convert between shares and assets
ILendingPoolTranche(tranche).convertToAssets(shares)
returns (uint256 assets)
ILendingPoolTranche(tranche).convertToShares(assets)
returns (uint256 shares)
// Vault's total tranche share balance (including shares locked in pending withdrawals)
ILendingPoolTranche(tranche).balanceOf(vaultAddress)
returns (uint256)
// Vault's pending deposit amount for a given epoch
IPendingPool(pendingPool).userPendingDepositAmount(vaultAddress, epochId)
returns (uint256)
// Total pending deposits across all users for current epoch
IPendingPool(pendingPool).pendingDepositAmountForCurrentEpoch()
returns (uint256)
// Details of a specific D-NFT
IPendingPool(pendingPool).trancheDepositNftDetails(dNftId)
returns (DepositNftDetails memory)
// .assetAmount — USDC amount
// .tranche — target tranche
// .epochId — request epoch
// Details of a specific W-NFT
IPendingPool(pendingPool).trancheWithdrawalNftDetails(wNftId)
returns (WithdrawalNftDetails memory)
// .sharesAmount — tranche shares
// .tranche — target tranche
// .epochId — request epoch
// Get the epoch interest rate for each tranche
PoolConfiguration memory config = ILendingPool(lendingPool).poolConfiguration();
uint256 epochRate = config.tranches[trancheIndex].interestRate;
// epochRate uses 18 decimals: 1e18 = 100%, 1e16 = 1%
// Current epoch number
ISystemVariables(systemVariables).getCurrentEpoch()
returns (uint256)
// Whether clearing is currently pending
// (check the lending pool's clearing state)