Contract a78d10c9c45e13b3e2f36255ac34e3f61159e178083121f0843fa29318b5838e

← Back to Index 📥 Download WASM

Meta

cliver 27.0.0#5a7c5fe76530bf4248477ac812fc757146b98cc4
rssdkver 25.3.1#e50d95af029c83196dd122f0154bac3f1302394b
rsver 1.96.1

Instances

  • CB7O72TTK7OISNS53HXTLCZ2EAY2F5KKYBPGI7ADSIX5KMPGVVDXAALN
  • CBZSOMWSXLNVGU6NXME6TLQUGGWHIE4SEWP2PMEJH5GVX7YN5U2VAOGZ

Interface

fn admin(env: soroban_sdk::Env) -> soroban_sdk::Address

Pause the router's entry paths. Exits stay open, and so does everything underneath — a paused router removes convenience, never access. Users can still reach Sr::deposit, SrMarket::swap_* and Yield::redeem_py directly, which is the property that makes this switch safe to flip on suspicion rather than on proof.

fn pause(env: soroban_sdk::Env)

Recover tokens donated to the router — never user funds in flight, because there are none: every entry point drains before it returns. Admin-gated and sent to the admin.

fn sweep(env: soroban_sdk::Env, tokenc: soroban_sdk::Address) -> i128
fn expiry(env: soroban_sdk::Env) -> u64
fn market(env: soroban_sdk::Env) -> soroban_sdk::Address
fn unpause(env: soroban_sdk::Env)
fn version(env: soroban_sdk::Env) -> soroban_sdk::String
fn pt_token(env: soroban_sdk::Env) -> soroban_sdk::Address
fn sr_token(env: soroban_sdk::Env) -> soroban_sdk::Address
fn timelock(env: soroban_sdk::Env) -> u64
fn code_hash(env: soroban_sdk::Env) -> soroban_sdk::BytesN<32>
fn is_paused(env: soroban_sdk::Env) -> bool

Wire the router to a market, reading everything else back from the chain.

The caller supplies one address. Engine, SR, PT, underlying and expiry are all read from the market and the contracts it names, so a fat-fingered deploy cannot produce a router pointed at a market for one series and an engine for another. This is the same construction that makes tofix.md #24 inexpressible in the vault.

fn initialize(env: soroban_sdk::Env, market: soroban_sdk::Address)
fn underlying(env: soroban_sdk::Env) -> soroban_sdk::Address
fn accept_admin(env: soroban_sdk::Env)
fn set_timelock(env: soroban_sdk::Env, secs: u64)
fn __constructor(env: soroban_sdk::Env, admin: soroban_sdk::Address)
fn apply_upgrade(env: soroban_sdk::Env)
fn pending_admin(env: soroban_sdk::Env) -> Option
fn propose_admin(env: soroban_sdk::Env, new_admin: soroban_sdk::Address)
fn cancel_upgrade(env: soroban_sdk::Env)
fn yield_contract(env: soroban_sdk::Env) -> soroban_sdk::Address
fn pending_upgrade(env: soroban_sdk::Env) -> Option

Buy PT with USDC, exact input. Spends exactly usdc_in; reverts below min_pt_out.

min_pt_out is the user's only protection here and it is a real one: it is denominated in PT, so it bounds the whole route — a bad wrap rate and a bad swap price both show up in the same number.

fn buy_pt_with_usdc(
    env: soroban_sdk::Env,
    user: soroban_sdk::Address,
    usdc_in: i128,
    min_pt_out: i128,
    deadline_ledger: u32,
) -> i128

Buy YT with plain USDC, one signature. Delivers exactly yt_out, wrapping exactly usdc_in; anything the market does not need comes back as SR.

Why usdc_in is an input rather than something we work out

This is the tightest path in the protocol for transaction budget: a Blend supply, a curve solve, a mint_py and three transfers, all in one transaction. An earlier version priced the trade itself — quote_buy_yt, then two rate conversions — and failed on live testnet with Error(Budget, ExceededLimit) while fitting comfortably against the local Blend fixture, which is far lighter than the real pool. Every cross-contract call it could shed was the difference between a path that exists and one that does not.

So the pricing moved off chain, where it is free. The caller quotes with [Self::quote_buy_yt_with_usdc], pads for index drift, and passes the result. This costs nothing in safety: usdc_in is a user-supplied number, which is exactly what the one rule in the module docs demands

fn buy_yt_with_usdc(
    env: soroban_sdk::Env,
    user: soroban_sdk::Address,
    yt_out: i128,
    usdc_in: i128,
    deadline_ledger: u32,
) -> i128
fn schedule_upgrade(env: soroban_sdk::Env, wasm_hash: soroban_sdk::BytesN<32>) -> u64

Sell PT for USDC. Exact input; reverts below min_usdc_out.

fn sell_pt_for_usdc(
    env: soroban_sdk::Env,
    user: soroban_sdk::Address,
    pt_in: i128,
    min_usdc_out: i128,
    deadline_ledger: u32,
) -> i128

Sell YT for USDC. Exact input; reverts below min_usdc_out.

The seller's accrued interest is settled by the YT transfer in the first line — the engine's before_yt_change hook credits it to them before the balance moves, so selling YT never forfeits yield already earned. It stays claimable afterwards via claim_yield_to_usdc.

fn sell_yt_for_usdc(
    env: soroban_sdk::Env,
    user: soroban_sdk::Address,
    yt_in: i128,
    min_usdc_out: i128,
    deadline_ledger: u32,
) -> i128

USDC a claim_yield_to_usdc would pay right now, net of the protocol's yield fee.

fn quote_claim_yield(env: soroban_sdk::Env, user: soroban_sdk::Address) -> i128

Redeem principal to USDC, at face value, through the engine rather than the market.

  • After expiry this is the exit: it burns PT only and pays face, no curve, no slippage, no liquidity requirement. Note the market refuses to trade past expiry, so sell_pt_for_usdc stops working exactly where this starts mattering.
  • Before expiry it is a recombine: it burns py_amount of both PT and YT. Useful to unwind a full position without paying the spread twice, and priced at face by definition — but it needs both legs, which is why the UI offers it separately.

Deliberately not pausable-gated beyond the router's own switch: this is an exit path.

fn redeem_py_for_usdc(
    env: soroban_sdk::Env,
    user: soroban_sdk::Address,
    py_amount: i128,
    min_usdc_out: i128,
) -> i128

Claim accrued YT yield straight to USDC.

This is the piece that makes YT legible to a normal user. Holding YT earns SR continuously; without this the holder has to claim SR, then unwrap it, then work out which of the two numbers was their actual return. Here it is one call and one number, in the unit they deposited.

The redirect to this contract is why the engine grew redeem_due_interest_to: claiming to the user and pulling the proceeds back would require the router to name an amount only known on chain inside a user-signed transfer — the forbidden shape from the module docs.

Returns the USDC paid. Claiming zero is not an error; it returns 0 and touches nothing.

fn claim_yield_to_usdc(
    env: soroban_sdk::Env,
    user: soroban_sdk::Address,
    min_usdc_out: i128,
) -> i128
fn cancel_admin_transfer(env: soroban_sdk::Env)

PT out for a given USDC input.

fn quote_buy_pt_with_usdc(env: soroban_sdk::Env, usdc_in: i128) -> i128

USDC needed for a given YT output. Pad this by the user's slippage tolerance and pass the padded figure as max_usdc_in; the pad is refunded.

fn quote_buy_yt_with_usdc(env: soroban_sdk::Env, yt_out: i128) -> i128

USDC out for a given PT input.

fn quote_sell_pt_for_usdc(env: soroban_sdk::Env, pt_in: i128) -> i128

USDC out for a given YT input.

fn quote_sell_yt_for_usdc(env: soroban_sdk::Env, yt_in: i128) -> i128

USDC a redeem_py_for_usdc of py_amount would pay — face value, no curve.

fn quote_redeem_py_for_usdc(env: soroban_sdk::Env, py_amount: i128) -> i128

Imports

WebAssembly Text (WAT) ▶