Skip to content

TranchedVault

Senior/junior tranche wrapper over MultiStrategyVault. Splits MSV yield: fixed-APR senior paid first; junior takes first loss + surplus.

Source: khomdev-keep/src/TranchedVault.vy (Vyper 0.4.x)

Concept guide: Tranches protocol page

Wraps: MultiStrategyVault · pairs with Tranche (skUSD / jkUSD)

Implementation overview

  • Waterfallsenior_value = min(principal + accrued APR, msv_value); junior_value = remainder.
  • Loss order — junior wiped first when MSV underpays senior claim.
  • Maturity — APR stops at maturity_timestamp; early-exit penalty waived after maturity.
  • Penalties — early senior/junior exits pay penalty into MSV (benefits remaining junior holders).
flowchart TD
    ST[Senior Tranche skUSD] --> TV[TranchedVault]
    JT[Junior Tranche jkUSD] --> TV
    TV --> MSV[MultiStrategyVault]

Immutables

Name Role
asset Underlying USDC
msv Wrapped MultiStrategyVault
senior_apr_bps Fixed senior APR (capped 50%)
loss_cap_bps Informational risk label
early_redemption_bps Early exit penalty (max 5%)
start_timestamp / maturity_timestamp Accrual window

Events

Event When
Initialized Tranches wired
SeniorAccrued APR tick
SeniorDeposit / JuniorDeposit Capital in
SeniorExit / JuniorExit Redemptions + penalties

Init

wire_tranches

TranchedVault.wire_tranches(senior_tranche_, junior_tranche_)

One-shot registration of senior and junior Tranche contracts.

Param Type Description
senior_tranche_ address Senior tranche (skUSD)
junior_tranche_ address Junior tranche (jkUSD)

Returns / state: Sets senior_tranche, junior_tranche, initialized = True. Cannot call twice.

Access: DEFAULT_ADMIN_ROLE only. Must be distinct non-zero addresses.

Events: Initialized(senior, junior).

Source (khomdev-keep/src/TranchedVault.vy:165-178):

@external
def wire_tranches(senior_tranche_: address, junior_tranche_: address):
    access_control._check_role(access_control.DEFAULT_ADMIN_ROLE, msg.sender)
    assert not self.initialized, "tv: already inited"
    self.senior_tranche = senior_tranche_
    self.junior_tranche = junior_tranche_
    self.initialized = True
    log Initialized(senior=senior_tranche_, junior=junior_tranche_)

Example:

tranched_vault.wire_tranches(senior_tranche, junior_tranche)

Cross-links: Tranche · deposit_senior · deposit_junior

Deposits (tranche-only callers)

deposit_senior

TranchedVault.deposit_senior(assets)

Senior tranche hook: pull USDC from senior Tranche, deposit into underlying MSV, track principal.

Param Type Description
assets uint256 USDC amount (senior tranche already pulled from user)

Returns / state: Accrues senior APR, deposits to msv, increments senior_principal.

Access: senior_tranche only (_only_senior). Not when paused.

Events: SeniorDeposit(assets).

Source (khomdev-keep/src/TranchedVault.vy:184-202):

@external
@nonreentrant
def deposit_senior(assets: uint256):
    self._only_senior()
    pausable._require_not_paused()
    self._accrue()
    extcall IERC20(asset).transferFrom(msg.sender, self, assets, ...)
    extcall IERC4626(msv).deposit(assets, self)
    self.senior_principal += assets

Example:

# Called by Senior Tranche contract, not end users directly
tranched_vault.deposit_senior(assets)

Cross-links: Tranche · MultiStrategyVault deposit · deposit_junior

deposit_junior

TranchedVault.deposit_junior(assets)

Junior tranche hook: pull USDC from junior Tranche, deposit into underlying MSV. No separate principal ledger — value tracked via waterfall.

Param Type Description
assets uint256 USDC amount (junior tranche already pulled from user)

Returns / state: Accrues senior APR first (_accrue), deposits to msv. Increases shared MSV position → lifts junior_value.

Access: junior_tranche only (_only_junior). Not when paused.

Events: JuniorDeposit(assets).

Source (khomdev-keep/src/TranchedVault.vy:204-216):

@external
@nonreentrant
def deposit_junior(assets: uint256):
    self._only_junior()
    pausable._require_not_paused()
    self._accrue()
    extcall IERC20(asset).transferFrom(msg.sender, self, assets, ...)
    extcall IERC4626(msv).deposit(assets, self)
    log JuniorDeposit(assets=assets)

Example:

# Called by Junior Tranche contract
tranched_vault.deposit_junior(assets)

Cross-links: deposit_senior · junior_value · MultiStrategyVault

Exits (tranche-only callers)

process_senior_exit

TranchedVault.process_senior_exit(shares_burned, supply_pre_burn, receiver) -> uint256

Senior tranche exit settlement after shares burned. Pro-rata claim vs senior_value, early penalty if pre-maturity.

Param Type Description
shares_burned uint256 Senior shares already burned by Tranche
supply_pre_burn uint256 Total senior supply before burn
receiver address USDC payout recipient

Returns: uint256 — USDC paid (final_paid = value_share - penalty).

Returns / state: Decrements senior_principal and senior_accrued pro-rata. Withdraws from MSV. Penalty stays in MSV (benefits junior).

Access: senior_tranche only. Not blocked by pause.

Events: SeniorExit(shares_burned, paid, penalty).

Source (khomdev-keep/src/TranchedVault.vy:218-252):

@external
@nonreentrant
def process_senior_exit(shares_burned, supply_pre_burn, receiver) -> uint256:
    self._only_senior()
    self._accrue()
    value_share = senior_value * shares_burned // supply_pre_burn
    if block.timestamp < maturity_timestamp:
        penalty = value_share * early_redemption_bps // MAX_BPS
    final_paid = value_share - penalty
    extcall IERC4626(msv).withdraw(final_paid, receiver, self)
    return final_paid

Example:

paid = tranched_vault.process_senior_exit(shares, supply, user)

Cross-links: senior_value · is_matured · process_junior_exit

process_junior_exit

TranchedVault.process_junior_exit(shares_burned, supply_pre_burn, receiver) -> uint256

Junior tranche exit settlement after shares burned. Pro-rata claim vs junior_value.

Param Type Description
shares_burned uint256 Junior shares already burned
supply_pre_burn uint256 Total junior supply before burn
receiver address USDC payout recipient

Returns: uint256 — USDC paid after optional early penalty.

Returns / state: Withdraws final_paid from MSV. Penalty remains in MSV (increases residual junior_value for holders).

Access: junior_tranche only. Not blocked by pause.

Events: JuniorExit(shares_burned, paid, penalty).

Source (khomdev-keep/src/TranchedVault.vy:254-280):

@external
@nonreentrant
def process_junior_exit(shares_burned, supply_pre_burn, receiver) -> uint256:
    self._only_junior()
    self._accrue()
    value_share = junior_value * shares_burned // supply_pre_burn
    if block.timestamp < maturity_timestamp:
        penalty = value_share * early_redemption_bps // MAX_BPS
    final_paid = value_share - penalty
    extcall IERC4626(msv).withdraw(final_paid, receiver, self)
    return final_paid

Example:

paid = tranched_vault.process_junior_exit(shares, supply, user)

Cross-links: junior_value · process_senior_exit · is_matured

Views

senior_value

TranchedVault.senior_value() -> uint256

Live senior tranche claim in USDC terms: min(principal + accrued_now, msv_value).

Returns Type Description
uint256 Senior waterfall cap; never exceeds MSV mark

Access: Public view. Does not mutate state (uses live accrual math, not stored senior_accrued).

Events: None.

Source (khomdev-keep/src/TranchedVault.vy:287-292, _senior_value_view 389-393):

@external
@view
def senior_value() -> uint256:
    return self._senior_value_view()

# internal:
s_owed = senior_principal + _accrued_now()
return min(s_owed, _msv_value())

Example:

owed = tranched_vault.senior_value()

Cross-links: accrued_now · msv_value · junior_value · Tranches concept

junior_value

TranchedVault.junior_value() -> uint256

Live junior residual: msv_value - senior_value (floored at 0).

Returns Type Description
uint256 Junior claim after senior waterfall

Access: Public view.

Events: None.

Source (khomdev-keep/src/TranchedVault.vy:296-300, _junior_value_view 397-402):

@external
@view
def junior_value() -> uint256:
    return self._junior_value_view()

# internal:
v = _msv_value()
s = _senior_value_view()
return 0 if v <= s else v - s

Example:

residual = tranched_vault.junior_value()

Cross-links: senior_value · msv_value · process_junior_exit

msv_value

TranchedVault.msv_value() -> uint256

Mark-to-market USDC value of the vault's MSV share balance.

Returns Type Description
uint256 convertToAssets(msv.balanceOf(self))

Access: Public view.

Events: None.

Source (khomdev-keep/src/TranchedVault.vy:304-308, _msv_value 381-385):

@external
@view
def msv_value() -> uint256:
    return self._msv_value()

# internal:
our_shares = IERC20(msv).balanceOf(self)
return 0 if our_shares == 0 else IERC4626(msv).convertToAssets(our_shares)

Example:

nav = tranched_vault.msv_value()

Cross-links: MultiStrategyVault · senior_value · junior_value

accrued_now

TranchedVault.accrued_now() -> uint256

Pending senior interest accrued through current block (view-only; does not call _accrue()).

Returns Type Description
uint256 senior_accrued + linear APR delta since last_accrue, capped at maturity_timestamp

Access: Public view.

Events: None.

Source (khomdev-keep/src/TranchedVault.vy:312-316, _accrued_now 370-377):

@external
@view
def accrued_now() -> uint256:
    return self._accrued_now()

# internal:
now_capped = min(block.timestamp, maturity_timestamp)
if now_capped <= last_accrue:
    return senior_accrued
dt = now_capped - last_accrue
return senior_accrued + senior_principal * senior_apr_bps * dt // SECONDS_PER_YEAR // MAX_BPS

Example:

pending = tranched_vault.accrued_now()

Cross-links: senior_value · is_matured · process_senior_exit

is_matured

TranchedVault.is_matured() -> bool

Whether the tranche has reached maturity_timestamp (APR stops; early-exit penalties waived).

Returns Type Description
bool block.timestamp >= maturity_timestamp

Access: Public view.

Events: None.

Source (khomdev-keep/src/TranchedVault.vy:320-324):

@external
@view
def is_matured() -> bool:
    return block.timestamp >= maturity_timestamp

Example:

if tranched_vault.is_matured():
    # no early redemption penalty on exits
    ...

Cross-links: accrued_now · process_senior_exit · process_junior_exit

Emergency

pause

TranchedVault.pause()

Pause new senior/junior deposits. Exits (process_senior_exit, process_junior_exit) stay open.

Returns / state: Sets paused = True (snekmate pausable).

Access: DEFAULT_ADMIN_ROLE only.

Events: Paused(account) (snekmate pausable).

Source (khomdev-keep/src/TranchedVault.vy:331-336):

@external
def pause():
    access_control._check_role(access_control.DEFAULT_ADMIN_ROLE, msg.sender)
    pausable._pause()

Blocked when paused: deposit_senior, deposit_junior.

Example:

tranched_vault.pause()  # DEFAULT_ADMIN_ROLE holder

Cross-links: unpause · deposit_senior · deposit_junior

unpause

TranchedVault.unpause()

Resume senior/junior deposits after emergency pause.

Returns / state: Sets paused = False (snekmate pausable).

Access: DEFAULT_ADMIN_ROLE only.

Events: Unpaused(account) (snekmate pausable).

Source (khomdev-keep/src/TranchedVault.vy:339-344):

@external
def unpause():
    access_control._check_role(access_control.DEFAULT_ADMIN_ROLE, msg.sender)
    pausable._unpause()

Example:

tranched_vault.unpause()  # DEFAULT_ADMIN_ROLE holder

Cross-links: pause · deposit_senior · deposit_junior