Pulls sy_amount SY from from into escrow and mints equal PT and YT,
denominated in asset units: face = sy_amount * rate / WAD. At rate 1.00
this equals sy_amount. PT is the fixed principal claim; YT is the yield
claim. The escrow holds the SY shares; their asset value at the current
rate equals the PT face exactly at mint, which is the coverage invariant.
fn split(
env: soroban_sdk::Env,
from: soroban_sdk::Address,
sy_amount: i128,
) -> Result<(i128, i128), soroban_sdk::Error>
fn config(env: soroban_sdk::Env) -> Result
fn maturity(env: soroban_sdk::Env) -> Result
PT and YT balances the holder currently owns, read from the token contracts.
fn position(
env: soroban_sdk::Env,
holder: soroban_sdk::Address,
) -> Result
Burns equal PT and YT (asset units) from from and returns principal in SY
shares: pt_amount * WAD / rate, capped to the holder's pro-rata share of
escrow under a shortfall (identical cap to redeem_at_maturity). Burning the
YT settles the holder's accrued yield first (the YT burn hook banks it into
the holder's claim ledger), so recombine returns only principal and the
banked yield stays owed and covered by the remaining escrow. Never reverts on
collateralization: a shortfall is priced as a haircut, matching Pendle.
fn recombine(
env: soroban_sdk::Env,
from: soroban_sdk::Address,
pt_amount: i128,
yt_amount: i128,
) -> Result
fn initialize(
env: soroban_sdk::Env,
admin: soroban_sdk::Address,
sy_token: soroban_sdk::Address,
pt_token: soroban_sdk::Address,
yt_token: soroban_sdk::Address,
maturity: u64,
) -> Result<(), soroban_sdk::Error>
fn is_matured(env: soroban_sdk::Env) -> Result
Pays holder their accrued YT yield in SY out of escrow, capped so PT
principal is always senior to banked YT yield, and returns the SY amount
paid. Allowed any time, including after maturity, so a holder can always
collect yield earned over the term.
PT-senior surplus cap. The YT contract settles the holder and reports the
banked total owed WITHOUT zeroing it (settle). The tokenizer then pays
only min(owed, surplus), where
surplus = max(0, escrow_shares - pt_face_reservation)
and pt_face_reservation = ceil(pt_supply * WAD / rate) is the SY escrow
needed to redeem every outstanding PT at its face at rate. The
reservation is rounded UP, so PT is never shorted by a rounding notch and
the surplus is the conservative (smaller) amount. It then consumes
exactly pay from the YT ledger and pushes pay SY. Anything owed beyond
pay stays banked in the YT ledger, claimable later once the rate
recovers (a transient sub-stroop dip) or, under a permanent slash, capped
there forever by the short escrow:
fn claim_yield(
env: soroban_sdk::Env,
holder: soroban_sdk::Address,
) -> Result
SY the tokenizer custodies, equal to the outstanding PT (and YT) supply.
fn escrowed_sy(env: soroban_sdk::Env) -> Result
Permissionless: before maturity, read the live SY rate and record it as the latest observation the maturity freeze may use. Every mutating operation records one as a side effect; this poke exists so anyone (a keeper, or a YT holder who wants the freeze to credit yield accrued right up to maturity) can refresh the observation on an otherwise idle market without moving tokens. Returns the observed rate.
fn observe_rate(env: soroban_sdk::Env) -> Result
The frozen maturity rate, or 0 if not yet snapshotted.
fn maturity_rate(env: soroban_sdk::Env) -> Result
PT and YT minted for sy_amount SY at the current rate, in asset units.
fn preview_split(
env: soroban_sdk::Env,
sy_amount: i128,
) -> Result<(i128, i128), soroban_sdk::Error>
SY shares returned for recombining equal PT and YT (asset units) at the
current rate. This is the principal only; any accrued YT yield is settled
separately into the holder's claim ledger. Mirrors recombine exactly,
including the pro-rata escrow cap, so the preview never overquotes
during a rate-regression shortfall.
Point-in-time read of the live Blend SY rate: if the rate moves between
this quote and submission, the executed recombine share count can
differ. The underlying value redeemed does not — recombine always
returns pt_face worth of principal regardless of rate, so a moved rate
changes the SY share count, not what it's worth. recombine has no
on-chain min_sy_out floor by design; a caller needing an exact share
count should compare this preview to its bound client-side before
submitting.
fn preview_recombine(
env: soroban_sdk::Env,
pt_amount: i128,
yt_amount: i128,
) -> Result
After maturity, burns pt_amount PT (asset units) from from and returns
principal in SY shares: pt_amount * WAD / rate, capped to the holder's
pro-rata share of escrow.
Insolvency guard: if a rate regression (negative yield, a slash) has left
the escrow unable to cover all PT principal, the payout is capped to
escrow_shares * pt_amount / pt_supply, so PT holders share the shortfall
pro-rata rather than letting the first redeemers drain the escrow at the
expense of the last. When solvent, the ideal payout is the smaller of the
two, so this pays principal in full. Capping preserves the escrow/PT ratio,
keeping every later redeemer's share fair.
The rate read here is the current SY rate; Phase 3 step 9 snapshots a maturity rate so post-maturity rate moves do not change redemption.
fn redeem_at_maturity(
env: soroban_sdk::Env,
from: soroban_sdk::Address,
pt_amount: i128,
) -> Result
Permissionless: after maturity, snapshot and return the SY rate used for
all redemption. Any caller may poke this so the maturity rate is captured
promptly; redemption also snapshots it lazily on first use. Idempotent
once set. The snapshot is the last rate observed at or before maturity,
never a live post-maturity read (see effective_rate), so the timing of
this call cannot move value between PT and YT.
fn freeze_maturity_rate(env: soroban_sdk::Env) -> Result