← Documentation home
Build

Quickstart

Read state, deposit, and withdraw against a pooled tranched metavault. Examples in TypeScript (viem), JavaScript (ethers), and Python (web3.py).

Install

npm install viem

Read state via the Lens

The Lens is one stateless contract per chain. One snapshot(vault) call returns every field a UI needs, atomic to a single block.

import { createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";
 
const LENS = "0x..." as const;   // see /deployments
const VAULT = "0x..." as const;  // pooled metavault address
 
const client = createPublicClient({ chain: arbitrumSepolia, transport: http() });
 
const snapshot = await client.readContract({
  address: LENS,
  abi: lensAbi,
  functionName: "snapshot",
  args: [VAULT],
});
 
console.log({
  asset: snapshot.asset,
  protectionPremiumBps: snapshot.protectionPremiumBps,
  seniorNav: snapshot.seniorNav,
  juniorNav: snapshot.juniorNav,
  seniorSharePrice: snapshot.seniorSharePrice,
  juniorSharePrice: snapshot.juniorSharePrice,
  maxJuniorWithdrawAssets: snapshot.maxJuniorWithdrawAssets,
});

Read a user's position

The Lens returns the user's lcSEN / lcJUN balances plus any outstanding withdrawal notice on each side.

const position = await client.readContract({
  address: LENS,
  abi: lensAbi,
  functionName: "userPosition",
  args: [VAULT, userAddress],
});
 
console.log({
  seniorShares: position.senior.shares,
  seniorAssets: position.senior.assetsAtCurrentNav,
  seniorNotice: position.senior.notice, // { shares, executableAt }
 
  juniorShares: position.junior.shares,
  juniorAssets: position.junior.assetsAtCurrentNav,
  juniorNotice: position.junior.notice,
});

A user has at most one outstanding notice per (address, tranche) pair. notice.shares > 0 means a notice is currently queued.

Deposit (senior)

Approve the metavault to pull the underlying asset, then call depositSenior. lcSEN is minted to receiver at the current senior unit price.

import { parseUnits, maxUint256 } from "viem";
 
// 1. Approve the metavault to pull USDC.
await walletClient.writeContract({
  address: USDC,
  abi: erc20Abi,
  functionName: "approve",
  args: [VAULT, maxUint256],
});
 
// 2. Deposit. lcSEN shares are minted to `receiver` at the current senior unit price.
const amount = parseUnits("1000", 6); // 1000 USDC
const shares = await walletClient.writeContract({
  address: VAULT,
  abi: trancheVaultAbi,
  functionName: "depositSenior",
  args: [amount, userAddress],
});

Reverts with:

Deposit (junior)

Same shape as senior — only the function name changes. Junior deposits don't have the leverage-cap revert (more junior is always fine). Blocked only by DepositsPaused.

const shares = await walletClient.writeContract({
  address: VAULT,
  abi: trancheVaultAbi,
  functionName: "depositJunior",
  args: [amount, userAddress],
});

Withdraw (senior)

Senior withdrawals are gated by a 5-day notice. File the notice, wait, then execute. The redemption is priced at execution-time NAV.

// 1. File the notice. This locks `shares` worth of lcSEN on the tranche token.
await walletClient.writeContract({
  address: VAULT,
  abi: trancheVaultAbi,
  functionName: "requestSeniorWithdrawal",
  args: [shares],
});
 
// 2. Wait 5 days (SENIOR_WITHDRAWAL_NOTICE).
//    Cancel any time with cancelSeniorWithdrawal(); refile to stack the noticed
//    amount and re-stamp executableAt.
 
// 3. Execute within the 3-day window after maturity.
const assets = await walletClient.writeContract({
  address: VAULT,
  abi: trancheVaultAbi,
  functionName: "withdrawSenior",
  args: [shares, userAddress, userAddress],
});

Reverts with:

To preview the expected payout without executing:

const expectedAssets = await client.readContract({
  address: LENS,
  abi: lensAbi,
  functionName: "previewWithdrawSenior",
  args: [VAULT, shares],
});

The loss waterfall is applied to NAV continuously, so the senior unit price already reflects whatever protection has been consumed — there is no separate "claim" path on senior exit.

Withdraw (junior)

Junior withdrawals are gated by a 10-day notice. Same shape as senior, with an additional check at execution: the redemption must not push senior past 4× junior NAV.

// 1. File the notice.
await walletClient.writeContract({
  address: VAULT,
  abi: trancheVaultAbi,
  functionName: "requestJuniorWithdrawal",
  args: [shares],
});
 
// 2. Wait 10 days (JUNIOR_WITHDRAWAL_NOTICE).
 
// 3. Execute within the 3-day window after maturity.
await walletClient.writeContract({
  address: VAULT,
  abi: trancheVaultAbi,
  functionName: "withdrawJunior",
  args: [shares, userAddress, userAddress],
});

Additional revert (on top of the senior list above):

Tranche-token surface

The two PooledTrancheToken instances (lcSEN, lcJUN) are plain ERC20Permit receipt tokens. The metavault is the only minter/burner; holder-side transfers (including notice locks) go through the standard ERC20 surface, plus EIP-2612 permit so a holder can authorise the metavault to pull shares for a withdrawal notice without a separate approve tx.

const seniorToken = "0x..." as const; // metavault.seniorToken()
 
await walletClient.writeContract({
  address: seniorToken,
  abi: erc20PermitAbi,
  functionName: "permit",
  args: [holder, metavault, value, deadline, v, r, s],
});

Pricing and capacity views live on the metavault (or the Lens), not on the receipt tokens. There is no ERC4626 deposit / redeem on the tranche tokens themselves. Always go through TranchedMetaVault for deposits, notices, and withdrawals.

Where to next