Yield Tokenization Smart Contracts
Overview
This guide explains how Pendle tokenizes yield by splitting assets into PT (Principal Token) and YT (Yield Token), and how the YT contract handles minting and redeeming, index accounting, and the distribution of interest and rewards. This documentation is for developers and partners who want a deep dive into the Pendle yield mechanism and how it works under the hood.
Key Concepts
Yield tokenization takes a yield-bearing asset, then splits that value into two claims with a fixed expiry:
- PT (Principal Token): represents the principal of the underlying yield-bearing token.
- YT (Yield Token): represents entitlement to all yield, rewards, and points of the asset until expiry.
Example: A user stakes 100 USDe in Ethena and, via Pendle, tokenizes it into 100 PT-USDe and 100 YT-USDe with a 3-month expiry. They can sell the YT-USDe to someone who wants the next three months of yield and points while keeping the PT-USDe to redeem the principal at maturity; assuming a 12% APY (~3% over three months), the position would accrue about 3 USDe - so at expiry the YT-USDe holder is entitled to ~3 USDe of accrued yield (plus any program points earned during that period), and the PT-USDe holder redeems the 100 USDe principal.
Technical Details
The Pendle yield-tokenization architecture comprises three core components:
- Standardized Yield (SY): A unified wrapper interface for yield-bearing assets; underlying yield and rewards accrue to the SY contract.
- YT contract: The core logic that splits SY into PT and YT, maintains index accounting, and accrues/distributes yield and rewards.
- PT contract: The ERC-20 representing principal; PT is minted/burned by the YT contract and is redeemable for principal at/after maturity.
Before splitting, yield-bearing assets are wrapped into SY. To tokenize yield, users deposit SY into the YT contract, which mints PT and YT. The YT contract tracks yield and rewards accrued to the SY and distributes them to YT holders. At maturity, PT holders can redeem their principal from the YT contract.
Core Logic
mintPY
/**
* @notice Tokenize SY into PT + YT of equal qty. Every unit of asset of SY will create 1 PT + 1 YT
* @dev SY must be transferred to this contract prior to calling
*/
function mintPY(address receiverPT, address receiverYT) external returns (uint256 amountPYOut);
Purpose: Mints equal amounts of PT and YT by depositing SY into the YT contract.
How it works:
- The YT contract mints using its current SY balance. Therefore, you must transfer SY into the YT contract before calling the function. The amount of PT and YT minted is calculated as:
- The YT contract mints equal quantities of PT and YT to the specified recipient addresses.
Example:
- If
1 SY-sUSDe = 1.2 USDe
(PY index = 1.2
) and a user deposits100 SY-sUSDe
, the contract mints120 PT-sUSDe
and120 YT-sUSDe
. - Since
100 SY-sUSDe
corresponds to120 USDe
of underlying value, the user receives120 PT-sUSDe
(principal exposure) and120 YT-sUSDe
(pre-expiry yield claim).
redeemPY
/**
* @notice converts PT(+YT) tokens into SY, but interests & rewards are not redeemed at the
* same time
* @dev PT/YT must be transferred to this contract prior to calling
*/
function redeemPY(address receiver) external returns (uint256 amountSyOut);
Purpose: Redeem SY by burning PT and YT. Think of this as converting back to the principal accounting unit, while interest and rewards are claimed separately.
How it works:
- You have to provide equal amounts of PT and YT to the YT contract before calling the function.
- The contract burns the tokens and returns SY according to the current PY index:
- The redeemed SY is sent to the specified receiver. Note that interest and rewards accrued to YT are not included in this redemption.
Notes:
- Pre-expiry: both PT and YT are required to redeem SY.
- Post-expiry: only PT is required (YT has no value after maturity).
Example:
Pre-expiry
- Continuing the prior example: if the user holds
120 PT-sUSDe
and120 YT-sUSDe
, and now the PY index is1.25
, thenSY_out = 120 / 1.25 = 96 SY-sUSDe
(which corresponds to 120 USDe). Interest and rewards accrued to YT are not included in this redemption.
Post-expiry
- After maturity, with
120 PT-sUSDe
and PY index1.25
,SY_out = 120 / 1.25 = 96 SY-sUSDe
(again equal to 120 USDe). The user receives96 SY-sUSDe
, which can be unwrapped or swapped back to the underlying 120 USDe principal.
redeemDueInterestAndRewards
/**
* @notice Redeems interests and rewards for `user`
* @param redeemInterest will only transfer out interest for user if true
* @param redeemRewards will only transfer out rewards for user if true
* @dev With YT yielding interest in the form of SY, which is redeemable by users, the reward
* distribution should be based on the amount of SYs that their YT currently represent, plus
* their dueInterest. It has been proven and tested that _rewardSharesUser will not change over
* time, unless users redeem their dueInterest or redeemPY. Due to this, it is required to
* update users' accruedReward STRICTLY BEFORE transferring out their interest.
*/
function redeemDueInterestAndRewards(
address user,
bool redeemInterest,
bool redeemRewards
) external returns (uint256 interestOut, uint256[] memory rewardsOut);
Purpose: Allows a YT holder to claim accrued earnings: interest (in SY) and any external reward tokens. Interest for YT is always paid in SY, but it can be swapped into your preferred token through the router.
Behavior notes:
-
Interest unit: Always SY. If you want the underlying/base asset, unwrap or swap through the router.
-
Pre- vs post-expiry:
- Pre-expiry: interest and rewards continue accruing; this function pays whatever is due up to the call.
- Post-expiry: YT no longer earns new yield. Calling still pays any remaining pre-expiry interest/rewards, if any.
-
Zero-flag calls: If both flags are
false
, no tokens are transferred (effectively a no-op, except for index synchronization) -
Token order:
rewardsOut[i]
corresponds togetRewardTokens()[i]
. Always read the list first.
Examples:
- Claim both:
User has accrued
2.5 SY
of interest and[10 X, 0.3 Y]
rewards. Calling with(true, true)
returns(2.5, [10, 0.3])
, transfers those amounts, and resets baselines. - Claim rewards only:
Calling
(false, true)
transfers only rewards. Due interest remains in SY terms and continues to count toward reward-share until it’s eventually claimed or the user redeems PY.
pyIndexCurrent
/**
* @notice updates and returns the current PY index
* @dev this function maximizes the current PY index with the previous index, guaranteeing
* non-decreasing PY index
* @dev if `doCacheIndexSameBlock` is true, PY index only updates at most once per block,
* and has no state changes on the second call onwards (within the same block).
* @dev see `pyIndexStored()` for view function for cached value.
*/
function pyIndexCurrent() external returns (uint256 currentIndex);
-
Purpose: Returns the current PY index, updating it if needed. The PY index tracks the SY exchange rate and is stored monotonically (never decreases).
-
Behavior notes:
-
The PY index is non-decreasing:
pyIndexCurrent = max(SY.exchangeRate(), pyIndexStored)
. -
If
doCacheIndexSameBlock
is enabled, the index is updated at most once per block; subsequent calls in the same block are read-only (no further state changes). -
If
SY.exchangeRate()
falls below the stored index (negative yield), the PY index does not move down. Consequences:- Pre-expiry redemptions return less SY per PY until
SY.exchangeRate()
recovers above the stored index. - YT accrual effectively pauses (no new interest) until recovery.
- In sustained drawdowns, even PT’s eventual redemption (valued in the accounting asset) can be less than previously expected because the SY backing has shrunk. See Negative Yield.
- Pre-expiry redemptions return less SY per PY until
-
-
Examples:
- Up move: Last stored
SY.exchangeRate()
=1.20
; it rises to1.25
. CallingpyIndexCurrent()
updates the PY index to1.25
. - Down move: Last stored index =
1.20
;SY.exchangeRate()
drops to1.15
. The PY index stays at1.20
. If a user minted 120 PT when the index was1.20
, their claim on SY is120 / 1.20 = 100 SY
. At maturity, if each SY equals1.15 USDe
, they redeem100 × 1.15 = 115 USDe
(less than 120 USDe), reflecting the underlying negative yield.
- Up move: Last stored
Integration Example
The snippets below are simplified for illustration and are not audited.
Do not use them in production or with real funds. If you adapt any example,
conduct a full review, add comprehensive tests, and obtain an independent security audit.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
IPYieldToken yt;
IStandardizedYield sy = IStandardizedYield(yt.SY());
IPrincipalToken pt = IPrincipalToken(yt.PT());
address receiver;
// Minting PT + YT by depositing SY
IERC20(address(sy)).transfer(address(yt), 100e18); // deposit 100 SY
uint256 amountPYOut = yt.mintPY(receiver, receiver); // receive PT + YT
// Redeeming SY by burning PT + YT (pre-expiry)
IERC20(address(pt)).transfer(address(yt), amountPYOut); // send PT
IERC20(address(yt)).transfer(address(yt), amountPYOut); // send YT
uint256 amountSyOut = yt.redeemPY(receiver); // receive SY
// Redeeming SY by burning PT only (post-expiry)
IERC20(address(pt)).transfer(address(yt), amountPYOut); // send PT
uint256 amountSyOut = yt.redeemPY(receiver); // receive SY
// Claiming accrued interest (in SY) and rewards (in reward tokens)
(uint256 interestOut, uint256[] memory rewardsOut) = yt.redeemDueInterestAndRewards(
receiver,
true, // claim interest
true // claim rewards
);
FAQ
When the underlying asset’s exchange rate increases, does Pendle buy more of the asset on the market and distribute it to YT holders?
No. Pendle’s accounting is index-based: yield accrues inside the SY balance held by the contracts as exchangeRate
rises. YT holders are entitled to the yield portion of that existing SY collateral (paid in SY), while PT holders claim principal at/after maturity; users can then unwrap or swap SY to the base asset if they wish. No open-market purchases are required.
Is 1 SY always equal to 1 PT + 1 YT?
No. PT is a principal claim in units of the accounting asset at maturity, whereas SY is a wrapper whose value floats with exchangeRate
; YT represents the pre-expiry yield claim. The amounts of PT and YT you mint depend on the current index - they collectively replicate the economic exposure of the underlying, but 1 SY ≠ 1 PT + 1 YT except in edge cases (e.g., exchangeRate == 1
).