# Pendle Developer Documentation — Full Reference > This file contains all Pendle V2 and Boros documentation concatenated for AI consumption. > Sources: https://docs.pendle.finance/pendle-v2 | https://docs.pendle.finance/pendle-v2-dev | https://docs.pendle.finance/boros-docs | https://docs.pendle.finance/boros-dev > Generated: 2026-05-14 --- # Part 1: Pendle V2 — Yield Tokenization Protocol # Introduction to Pendle How much will you earn from lending 1,000 USDC on Aave? 1%? 3%? 5%? Truth is, you can't say for sure. Yield fluctuates just like token prices. It tends to go up in bull markets, and go down in bear markets, and there are further micro-factors that cause fluctuations within those general market trends.
Compound historical yield charts
Compound historical yield charts from The Block Crypto
With Pendle, you can always maximise your yield: increase your yield exposure in bull markets and hedge against yield downturns during bear markets. ## What does Pendle do? We give users the reins to their yield. [Pendle](https://pendle.finance/) is a permissionless yield-trading protocol where users can execute various yield-management strategies. It acts as a second-order derivative layer, building upon and integrating with existing core yield-generating primitives in the DeFi ecosystem — Liquid Staking Tokens (LSTs), Liquid Restaking Tokens (LRTs), stablecoins, RWAs, and more. Pendle's smart contract architecture is **permissionless** — any user or protocol can create a new yield-trading market on-chain. While on-chain creation is open to all, the visibility of these markets on the official [Pendle UI](https://app.pendle.finance) is curated through a review process to ensure quality and safety. Community members can also leverage the [Community Listing Portal](https://listing.pendle.finance) for a streamlined listing process. There are 2 main parts to fully understand Pendle: 1. Yield Tokenization First, Pendle wrap **yield-bearing tokens** into **SY**(standardized yield tokens), which is a wrapped version of the underlying yield-bearing token that is compatible with the Pendle AMM (e.g. stETH → SY-stETH). SY is then split into its principal and yield components, **PT** (principal token) and **YT** (yield token) respectively, this process is termed as yield-tokenization, where the yield is tokenized into a separate token. 2. Pendle AMM Both **PT** and **YT** can be traded via Pendle's **AMM**. Even though this is the core engine of Pendle, understanding of the AMM is not required to trade PT and YT. As a yield derivative protocol, we are bringing the TradFi interest derivative market ([worth over $400T in notional value](https://www.bis.org/publ/otc_hy2111/intgraphs/graphA3.htm)) into DeFi, making it accessible to all. By creating a yield market in DeFi, Pendle unlocks the full potential of yield, enabling users to execute advanced yield strategies, such as: - Fixed yield (e.g. earn fixed yield on stETH) - Long yield (e.g. bet on stETH yield going up by purchasing more yield) - Earn more yield without additional risks (e.g. provide liquidity with your stETH) - A mix of any of the above strategies, learn more on how to execute these strategies at our [Pendle Academy](/pendle-academy/Introduction) --- # Overview ## Stay Connected **Important:** Stay up to date with the latest developer updates and get support from our team: - **Telegram**: Join [t.me/pendledevelopers](https://t.me/pendledevelopers) for all development updates, API changes, and important announcements - **Telegram bot**: We have a Telegram bot for developers to ask about the API at [t.me/peepo_the_engineer_bot](https://t.me/peepo_the_engineer_bot) - **Discord**: Get technical support on our [Discord Developer Channel](https://pendle.finance/discord) with responses within 24 hours ## For AI users - As many of us increasingly rely on AI tools to read and understand project documentation, we’ve added a new folder to the repository: [docs/Developers](https://github.com/pendle-finance/documentation/tree/master/docs/Developers). It contains a list of questions and answers about our system and our API, useful for AI to better understand our system and provide more accurate answers when you query them. Our in-house AI is also using these knowledge bases! - Follow the instructions in [README.md](https://github.com/pendle-finance/documentation/tree/master?tab=readme-ov-file#ai-knowledge-bases) to index the knowledge bases for your AI. ## What do you want to do? | Goal | Start here | |---|---| | **Build a trading bot or data aggregator** | [Quickstart](./Quickstart.md) → [Backend API Overview](./Backend/ApiOverview.mdx) | | **Integrate PT/YT into your protocol (on-chain)** | [Router Overview](./Contracts/PendleRouter/PendleRouterOverview.md) → [Integration Guide](./Contracts/PendleRouter/ContractIntegrationGuide.md) | | **Price PT or LP (oracle / collateral)** | [Oracle Overview](./Oracles/OracleOverview.md) → [How to Integrate](./Oracles/HowToIntegratePtAndLpOracle.md) | | **Analyze yields & APY composition** | [Market Historical Data API](./Backend/ApiOverview.mdx#market-data-endpoints) | | **Work with Limit Orders** | [Limit Order Overview](./LimitOrder/Overview.md) | | **Track sPENDLE rewards** | [sPENDLE API](./Backend/ApiOverview.mdx#spendle-api-endpoints) | ## Core Documentation - [High Level Architecture](./HighLevelArchitecture.md) - [StandardizedYield (SY)](./Contracts/StandardizedYield/StandardizedYield.md) - [Common Questions](./FAQ.md) ## Integration Guides ### On-chain Integration - **Router**: [Documentation](./Contracts/PendleRouter/PendleRouterOverview.md) | [Integration Guide](./Contracts/PendleRouter/ContractIntegrationGuide.md) - **Oracles**: [Overview](./Oracles/OracleOverview.md) | [Integration Guide](./Oracles/HowToIntegratePtAndLpOracle.md) | [PT as Collateral](./Oracles/PTAsCollateral.md) | [LP as Collateral](./Oracles/LPAsCollateral.md) - [Example Repository](https://github.com/pendle-finance/pendle-examples-public) - Various contract interaction examples ### Off-chain Integration - [Backend API overview](./Backend/ApiOverview.mdx) - [Market Historical Data & APY Breakdown](./Backend/ApiOverview.mdx#market-data-endpoints) - Time-series data with detailed APY and fee breakdowns - [RouterStatic](./Backend/RouterStatic.md) - Extensively tested contract for off-chain calculations. Not audited; should not be used for on-chain fund-related operations. - [sPENDLE API](./Backend/ApiOverview.mdx#spendle-api-endpoints) - Staking stats and per-user reward data - [Socket.IO Real-time Feeds](./Backend/SocketIO.mdx) - Pushed real-time feeds ### Limit Orders - [Contract](./LimitOrder/LimitOrderContract.md) | [Create](./LimitOrder/CreateALimitOrder.md) | [Cancel](./LimitOrder/CancelOrders.mdx) | [Fill](./LimitOrder/FillALimitOrder.md) ## Resources - [Deployed Contract Addresses](./Deployments.md) — Core contracts and market addresses by chain - [Core Contract Repository](https://github.com/pendle-finance/pendle-core-v2-public) - [SY Contract Repository](https://github.com/pendle-finance/Pendle-SY-Public) - [Example Repository](https://github.com/pendle-finance/pendle-examples-public) - [Whitepapers](https://github.com/pendle-finance/pendle-v2-resources/tree/main/whitepapers) --- # Quickstart Pendle V2 is a yield-trading protocol that lets users split yield-bearing assets into Principal Tokens (PT) and Yield Tokens (YT), trade them on AMM markets, and provide liquidity — all on-chain. The fastest way to start is the off-chain Backend API — no wallet required. ## Prerequisites - `curl` or any HTTP client (browser, `fetch`, `axios`, etc.) ## Step 1 — Browse available markets **Endpoint:** `GET https://api-v2.pendle.finance/core/v2/markets/all` Returns all Pendle markets across every supported chain. Supports `skip` and `limit` for pagination (default `limit` is 10, maximum is 100). ```bash curl "https://api-v2.pendle.finance/core/v2/markets/all?limit=10&skip=0" ``` ```js const res = await fetch( "https://api-v2.pendle.finance/core/v2/markets/all?limit=10&skip=0" ); const { markets } = await res.json(); console.log(markets); ``` Key fields in each market object: | Field | Description | |-------|-------------| | `chainId` | Chain the market is deployed on (e.g., `1` for Ethereum, `42161` for Arbitrum) | | `address` | Market contract address | | `expiry` | Unix timestamp when the market matures | | `impliedApy` | Current implied fixed APY of the market | | `pt.price.usd` | Current PT price in USD | ## Step 2 — Get market data **Endpoint:** `GET https://api-v2.pendle.finance/core/v2/markets/all` Use the `address` field from Step 1 to identify markets of interest. For filtering and additional query options (e.g., by chain, by asset category), see the [full API reference](./Backend/ApiOverview.mdx). The example below fetches all markets on Arbitrum (chainId `42161`) and filters client-side for a known market address: ```bash curl "https://api-v2.pendle.finance/core/v2/markets/all?limit=100&skip=0" \ | jq '[.markets[] | select(.chainId == 42161 and .address == "{MARKET_ADDRESS}")]' ``` Replace `{MARKET_ADDRESS}` with the address of the market you want to inspect (obtained from Step 1). ## Step 3 — Get sPENDLE staking data (optional) **Endpoint:** `GET https://api-v2.pendle.finance/core/v1/spendle/data` ```bash curl "https://api-v2.pendle.finance/core/v1/spendle/data" ``` Returns aggregate sPENDLE staking statistics including total PENDLE staked, historical APRs, per-epoch revenues, and airdrop breakdowns for the last 12 epochs. ## Next steps :::tip - **Full API reference** — endpoint details, query parameters, response schemas: [Backend API Overview](./Backend/ApiOverview.mdx) - **On-chain Router** (swaps, add/remove liquidity, mint/redeem): [Pendle Router Overview](./Contracts/PendleRouter/PendleRouterOverview.md) - **Deployed contract addresses** — all chains and environments: [Deployments](./Deployments.md) - **Developer support** — questions, announcements, deprecation notices: [t.me/pendledevelopers](https://t.me/pendledevelopers) ::: --- # High Level Architecture ![High Level Architecture](/pendle-dev-docs/imgs/Developers/high_level_architecture.png "High Level Architecture") ## Functions of Contracts ### PendleRouter PendleRouter is a contract that aggregates callers' actions with various different SYs, PTs, YTs, and Markets. It does not have any special permissions or whitelists on any contracts it interacts with. Therefore, any third-party protocols can freely embed the router's logic into their code for better gas efficiency. You can read more about the PendleRouter [here](./Contracts/PendleRouter/PendleRouterOverview.md). ### Standardized Yield (SY) SY is a wrapped version of the interest-bearing token (ibToken) that can also be staked into other protocols to earn even more interest. In the Pendle system, SY is used instead of the ibToken for all operations, including trading on PendleMarket or minting Principal Token & Yield Token. The following are true: | SY | ibToken (1 SY = 1 ib Token) | Asset (ibToken appreciates against) | | :--------------------------: | :-------------------------------------------------------------------: | :---------------------------------: | | SY GLP | `GLP` | NIL, GLP doesn't appreciate | | SY wstETH | `wstETH` | stETH | | SY ETHx | `ETHx` | ETH locked in ETHx contract* | | SY aUSDC | Not 1-1 to aUSDC since it's rebasing | aUSDC | | SY rETH-WETH_BalancerLP Aura | `rETH-WETH LP` of Balancer staked into the corresponding Aura's gauge | Liquidity of rETH-WETH pool | For `*`: ETH locked in ETHx is different from normal ETH due to withdrawal from ETHx has delay. Under normal circumstances it's also normal for ETHx to trade at a market price lower than the amount of ETH it can be withdrawn to. ### Principal Token (PT) PT is a token that represents the right to redeem for the principal amount at maturity, and is tradable on PendleMarket. While PT represents a claim to a fixed amount of assets, SY wraps an ibToken that increases in value, meaning that `1 PT != 1 SY` is usually true (except in exceptional cases). Instead, `1 PT = 1 Asset`, where "Asset" refers to what SY's ibToken is denominated in. The following are true: | PT | Asset (1 PT = 1 Asset) | | :-------: | :-------------------------: | | PT GLP | GLP | | PT wstETH | stETH | | PT ETHx | ETH locked in ETHx contract | | PT aUSDC | aUSDC | At redemption, `1 PT = X SY`, where `X` satisfies the condition that `X SY = 1 Asset`. For example, assuming `1 wstETH = 1.2 stETH` on 1/1/2024, `1 PT-wstETH-01JAN2024` will be redeemable to `0.8928 wstETH` at maturity. ### Yield Token (YT) YT is a token that represents the rights to redeem the interest generated by the SY until it reaches maturity. It's important to note that the value of YT is zero once it reaches maturity. The yield can be redeemed at any time and it can be traded on PendleMarket using special methods. ibToken generates yield in two forms, which Pendle denotes as: - **Interest** (compounding yield): The yield is denominated in the same unit as the asset of ibToken. For example, as time goes on, `wstETH` becomes worth more in terms of `stETH`, and `SY-aUSDC` becomes worth more in terms of `USDC`. - **Rewards** (yield that does not compound): The yield is given out in a different unit than the ibToken. For example, GLP generates ETH. The following are true: | YT | Interest | Rewards | | :--------------------------: | :-------------------------: | :-------: | | YT GLP | - | ETH | | YT wstETH | stETH | - | | YT ETHx | ETH locked in ETHx contract | - | | YT aUSDC | aUSDC | - | | YT rETH-WETH_BalancerLP Aura | liquidity of rETH-WETH pool | AURA, BAL | PT and YT are minted and redeemed using the YT contract. To mint PT and YT, SY is utilized. `1 SY = X PT + X YT` is created where `1 SY = X Asset`. Prior to maturity, both PT and YT must be provided to redeem the underlying SY. After maturity, only PT is required for redemption. ### PendleMarket PendleMarket (or simply Market) is a contract that enables users to trade between PT and its corresponding SY, while still allowing liquidity provision as usual. Swap fees are directly compounded into the LP. Each Market also has its own built-in geometric-mean oracle, similar to UniswapV3. Currently, there is no market to trade YT, but it is always tradable by the following algorithms: - `SY ➝ YT` = flashswap SY, mint PT & YT, payback PT, send YT to users. - `YT ➝ SY` = flashswap PT, use PT & YT, redeem SY, pay back, send excess to users. :::info YT Fungibility All Yield Tokens (YT) of the same underlying asset are **completely fungible**. It is not programmatically possible to distinguish between YT acquired through minting, swapping, or LPing. User balances are queried on the SY contract, which treats all YT of a given type as identical. Any attempt to classify users or distribute rewards based on how they acquired their YT is not feasible on-chain. ::: ## Contract Factories Pendle uses a factory pattern for deploying new markets, PTs, and YTs. This standardizes the creation process and provides a single source of truth for identifying valid protocol components. ### Factory Versions | Version | Timeframe | Key Changes | |---------|-----------|-------------| | Pre-V3 | Early deployments | Initial factory implementations | | **V3** | Late 2023 | Standard for all new deployments across all chains | | **V4/V5** | Mid-2024 | **Removed `Permit` (EIP-2612) from all new PT, YT, and LP tokens** to mitigate phishing attacks | | **V6** | Late 2025 | Current recommended version for all new market deployments | :::caution Security Note: Permit Removal Starting with V4/V5 factories, the `Permit` function (EIP-2612) was **completely removed** from all newly created PT, YT, and LP tokens. The `Permit` function, which allows gasless approvals via off-chain signatures, was identified as a significant phishing attack vector. Tokens created by V4+ factories no longer support `permit()`. ::: :::warning Always use the **latest factory version** for new market deployments. Using deprecated factories can lead to issues including improper contract verification. ::: ### Immutability vs. Upgradability - **Market contracts are immutable** once deployed. Their fundamental parameters (including the concentrated yield range) cannot be changed. If a pool goes "out of range," a brand new market must be deployed and LPs must manually migrate. - **SY contracts are upgradable proxies** (for newer deployments). This allows adding support for new deposit assets or adjusting mechanisms without requiring a full market migration. When developing an SY externally, it is recommended to deploy it as an upgradeable contract using Pendle's proxy admin and transfer ownership to Pendle's pause controller. ## Cross-Chain Principal Tokens Cross-chain PTs allow Principal Tokens to be bridged and used on networks where Pendle may not have a full deployment, while concentrating deep trading liquidity on mainnet. - **Standard:** Uses [LayerZero's Omnichain Fungible Token (OFT)](https://docs.layerzero.network/v2/home/token-standards/oft-standard) standard. - **Mechanism:** Users can "zap in" to a PT position on a mainnet pool, then bridge the PT to a destination chain (e.g., Avalanche, HyperEVM) for use as collateral in local money markets. - **Liquidation:** The system handles liquidations without relying on DEX liquidity on the destination chain — the underlying asset is converted and bridged back from mainnet. - **Initial Pilot:** sUSDe/USDe PTs, with the goal of collateral support on money markets like Morpho. --- # Glossary #### Yield-Bearing Token Yield-bearing Token is an umbrella term that refers to any token that generates yield. Examples include stETH, GLP, gDAI or even liquidity tokens such as Aura rETH-WETH. #### Accounting Asset The asset yield bearing token appreciates in value against. It appears in brackets at the end of each market name. #### SY = Standardized Yield SY is a token standard written by the Pendle team that wraps any yield-bearing token and provides a standardized interface for interacting with any yield-bearing token’s yield generating mechanism. SY is a purely technical component, the user does not interact directly with SY. #### PT = Principal Token PT entitles you to the principal of the underlying yield-bearing token, redeemable after maturity. If you own 1 PT-wstETH (stETH) with 1 year maturity, you will be able to redeem 1 stETH after 1 year. PT is tradeable anytime, even before maturity. #### YT = Yield Token YT entitles you to all the yield generated by the underlying yield-bearing token in real-time, and the yield accrued can be manually claimed *at any time* from the Pendle Dashboard. If you own 1 YT-wstETH (stETH) and stETH has an average yield of 5% through the year, you will have accrued 0.05 stETH by the end of the year. YT is tradeable anytime, even before maturity. #### Maturity Maturity is the date at which PT becomes fully redeemable for the underlying asset and YT stops accruing yield. One asset can have multiple maturity dates, with an independent market for each maturity date. As such, the implied yield of an asset can also differ across different maturities. #### Underlying APY Underlying APY represents the 7-day moving average yield rate of the underlying asset. This approach allows a more accurate indication of the underlying yield over a period of time, which can help traders to better estimate the Future Average Underlying APY. #### Implied APY Implied APY is the market consensus of the future APY of an asset. This value is calculated based on the ratio of the price of YT to PT and the formula is shown below. When used in conjunction with the Underlying APY, Implied APY can be used to establish the relative valuation of an asset such as YT and PT at their current price, and help traders determine their trading strategies. The value of Implied Yield is numerically equivalent to the to Fixed Yield APY. $$ \text{Implied APY} = \left[\left(1 + \frac{\text{YT Price}}{\text{PT Price}}\right)^{\frac{365}{\text{Days to expiry}}}\right] - 1 $$ #### Fixed APY Fixed APY is the guaranteed yield you will receive by holding PT. This value is numerically equivalent to the Implied APY. #### Long Yield APY Long Yield APY is the approximated return (annualized) from buying YT at the current price, assuming underlying APY remains constant at its current value. This value can be negative, meaning that the total value of all the future yield based on the Underlying APY will be less than the cost of buying YT. #### Exchange Rate Refers to the exchange rate between the interest-bearing token and its accounting asset #### LP = Liquidity Provider Token LP tokens represent a user's share of a Pendle liquidity pool, which is composed of PT and SY. LPs earn returns from multiple sources simultaneously: swap fees, PENDLE incentives, underlying yield from the SY portion, and an implicit fixed yield from the PT portion. #### LP Wrapper An ERC-20 token that wraps the underlying LP position on a 1:1 basis, enabling LP tokens to be used as collateral in external money markets while still accruing PENDLE rewards and off-chain points. The wrapper ensures that the original depositor continues to receive all associated rewards. #### Watermark Rate The highest recorded exchange rate between the IBT and its accounting asset. When the exchange rate falls below this level, PT will redeem below its actual value at maturity, and YT will stop earning yield. --- # SY ![SY](/pendle-docs/imgs/ProtocolMechanics/sy.png "SY") SY is a token standard that implements a standardized API for wrapped yield-bearing tokens within smart contracts. All yield-bearing tokens can be wrapped into SY, giving them a common interface that can be built upon. SY opens up Pendle’s yield-tokenization mechanism to all yield-bearing tokens in DeFi, creating a permissionless ecosystem. > For example, stETH, cDAI and yvUSDC can be wrapped into SY-stETH, SY-cDAI and SY-yvUSDC, standardizing their yield-generating mechanics to be supported on Pendle. As all SYs have the same mechanism, Pendle interacts with SY as the main interface to all yield-bearing tokens. PT and YT are minted from SY and Pendle AMM pools trade PT against SY. While this might seem daunting, Pendle automatically converts yield-bearing tokens into SY and vice versa. This process happens automatically behind the scenes, making users feel as if they’re interacting directly with their yield-bearing tokens instead of having to manually deal with SY ↔ yield-bearing token conversion. ### Key Characteristics - **1:1 Wrapping (Typically):** In most cases, 1 SY token represents 1 unit of the underlying yield-bearing asset. For instance, 1 SY-rsETH is equivalent to 1 rsETH. However, there are exceptions (e.g., mPendle, aUSDC) where the ratio is not strictly 1:1. Integrators should verify the specific wrapping mechanism for each SY token. - **No Maturity Date:** Unlike PT and YT, the SY token does not have an expiry date. It acts as a perpetual wrapper for the underlying asset as long as it is held within the Pendle ecosystem. - **Source of Yield and Points:** The SY contract holds the deposited underlying asset and is the direct recipient of all accrued yield and points from that asset. All rewards distributed within a Pendle market originate from the SY tokens held within it. - **Upgradability:** Most newer SY contracts are deployed as upgradable proxies. This allows for future enhancements — such as adding support for new deposit assets or adjusting mechanisms — without requiring a full market migration. While this standard benefits Pendle, our vision for SY extends beyond just our own protocol. SY aims to create unprecedented composability across all of DeFi, enabling developers to seamlessly build on top of existing contracts without the need for manual integration. ## SY Converter ![SY Converter](/pendle-docs/imgs/ProtocolMechanics/sy-converter.png "SY Converter") The SY Converter can be found in the trade form for any of the associated market. For example *SY-sUSDe* wrapper/unwrapper is accessible from sUSDe market of any maturity. **To use the SY Converter:** ![SY Converter Window](/pendle-docs/imgs/ProtocolMechanics/sy-converter-window.png "SY Unwrapper Window") Step 1: Select between “Unwrap” or “Wrap” mode Step 2: Select the token to wrap from or unwrap to Step 3: Check the rate and output. Step 4: Confirm and approve the transaction. --- # PT Principal Token (PT) represents the principal portion of an underlying yield-bearing asset — essentially a zero-coupon bond on the underlying asset. Upon maturity, PT can be redeemed at 1:1 for the accounting asset, which appears in brackets at the end of each PT name. This is the base, principal asset deployed in the underlying protocol such as Lido, Renzo, and Aave (e.g. stETH in stETH, ETH in ezETH, USDC in aUSDC). ![PT Mechanics](/pendle-docs/imgs/ProtocolMechanics/pt-mechanics.png "PT Mechanics") Since the collective value of its yield component has been separated, PT can be acquired at a discount relative to its accounting asset. Assuming no swaps, the value of PT will approach and ultimately match the value of accounting asset on maturity when redemption is enabled. This appreciation in value is what establishes its Fixed Yield APY. :::info Key Properties - **No Variable Yield or Points:** PT holders forgo all variable yield and points generated by the underlying asset — these are redirected entirely to YT holders. - **Use as Collateral:** PTs are increasingly used as collateral in money markets (e.g., Morpho, Silo, Euler) due to their predictable value at maturity, which minimizes liquidation risk from market price volatility. See [PT as Collateral](../../Developers/Oracles/PTAsCollateral) for integration details. ::: # Redemption Value In general, yield bearing assets can be broadly categorized as: 1. Rebasing assets - tokens that increase in count/number overtime as yield is accrued *Examples: stETH, aUSDC* 2. Interest-bearing assets - tokens that increase in value overtime as yield is accrued *Examples: ezETH, wstETH* ![Redemption Value](/pendle-docs/imgs/ProtocolMechanics/redemption-value.png "Redemption Value") In the case of reward-bearing assets, it’s particularly important to note that PT is redeemable 1:1 for the accounting asset, *NOT* the **underlying asset. For example, the value of Renzo ezETH increases overtime relative to ETH as staking and restaking rewards are accrued. For every 1 PT-ezETH you own, you’ll be able to redeem 1 ETH worth of ezETH upon maturity, *NOT* 1 ezETH which has a higher value**.** You can refer to the asset in brackets in the market name to identify the accounting asset (e.g. PT-ezETH (ETH) means 1 PT redeems to 1 ETH worth of ezETH). You can also double-check the redemption value of PT on [Pendle App](https://app.pendle.finance/trade/markets)'s individual asset pages. # How to Redeem PT To redeem your PT on maturity: 1. Visit [Pendle Markets](https://app.pendle.finance/trade/markets) and navigate to dashboard 2. Select the position you want to redeem. 3. Select an output asset. Pendle will automatically perform Redemption > Swap (if needed) for you --- # YT Yield Token (YT) represents the yield component of an underlying yield-bearing asset. By holding YT, yield from the underlying asset will be streamed to the users, up until maturity. This rate of yield production is represented as “[Underlying APY](https://docs.pendle.finance/ProtocolMechanics/Glossary)” in the Pendle app. For example, buying 10 YT-wstETH (stETH) and holding them for 5 days lets you receive all of the yield equivalent to a 10 stETH within the same period of time. ### Leveraged Yield Exposure Because YTs are purchased for a fraction of the underlying asset's price, they offer **leveraged exposure** to its yield. A small change in the underlying APY can result in a significant percentage change in the YT's return. This makes YT a powerful instrument for yield speculation and points farming. ### Value Decay The value of YT trends towards $0 as it approaches maturity (*ceteris paribus*), becoming $0 upon maturity. If the implied yield remains constant, the YT's price decreases linearly with the time remaining — a YT with 15 days left to maturity will be worth roughly half the price it was when it had 30 days left. Users profit when the total yield collected up to that point ends up being higher than the cost of YT acquisition. You can think of [Implied APY](https://docs.pendle.finance/ProtocolMechanics/Glossary) as the “rate” at which YT is priced by the market. If the average Underlying APY ends up being higher than the “rate” or Implied APY that you paid for, you will profit. As such, buying YT can be treated as “longing the yield” of an asset. ### Points Farming YTs are a popular instrument for farming points from airdrop campaigns, as they capture all points from the underlying asset, often with significant leverage. Since 1 YT earns the same points as 1 unit of the underlying asset, and YTs cost only a fraction of the underlying, users can achieve multiplied points exposure. You can learn more about yield trading on Pendle [here](https://app.pendle.finance/trade/education/learn). Note: YT yields are distributed as SY, which can be unwrapped back into the underlying asset using [SY Unwrapper](https://docs.pendle.finance/ProtocolMechanics/YieldTokenization/SY). # Claiming YT Yield ![Claiming YT Yield](/pendle-docs/imgs/ProtocolMechanics/claiming-yt-yield.png "Claiming YT Yield") You can claim any earned YT (and LP) yield and rewards from the [Pendle Dashboard](https://app.pendle.finance/trade/dashboard/overview?timeframe=allTime&inUsd=false) anytime, even before maturity. Since YT = $0 upon maturity, no further action (aside from claiming yield) is required. --- # Minting Users receive yield-bearing assets when they deposit funds into a yield-source. For example, DAI staked in Compound is represented as *cDAI*. ETH staked in Lido is represented as *stETH*. *cDAI* and *stETH* are examples of **yield-bearing assets**. In Pendle, yield-bearing assets are split into two components: Principal Tokens (**PT**) and Yield Tokens (**YT**). PT represents the principal of the underlying yield-bearing token, while YT represents entitlement to all the yield of the asset. YT and PT can be traded on Pendle. ![Yield Splitting](/pendle-docs/imgs/ProtocolMechanics/yield-splitting.png "Yield Splitting") What Pendle does is similar to bond stripping in traditional finance, where the principal and interest of bonds are separated. In this, PTs are equivalent to [zero-coupon bonds](https://www.investopedia.com/terms/z/zero-couponbond.asp), while YTs are the detached [coupon](https://www.investopedia.com/terms/c/coupon.asp) payments. Users can mint PT and YT by depositing the yield-bearing asset (e.g. stETH) into Pendle. Base assets (e.g. ETH) will be auto-converted into the yield-bearing asset before PT and YT are minted. e.g. ETH → stETH → SY-stETH → PT-stETH + YT-stETH. This function can be found in the Pendle App after selecting one of the assets. ![Yield Splitting UI](/pendle-docs/imgs/ProtocolMechanics/yield-splitting-ui.png "Yield Splitting UI") --- # AMM Pendle’s V2 AMM is designed specifically for trading yield, and takes advantage of the behaviors of PT and YT. Unlike standard AMMs that concentrate liquidity within a **price range**, Pendle’s AMM concentrates liquidity within a pre-configured **yield range** (or Implied APY range). This makes trading within the expected yield boundaries highly efficient, with lower slippage for larger trades. The AMM model was adapted from Notional Finance’s AMM. The AMM curve changes to account for yield accrued over time and narrows PT’s price range as it approaches maturity (**dynamic curve tightening**). By concentrating liquidity into a narrow, meaningful range, the capital efficiency to trade yield is increased as PT approaches maturity. This automatic tightening reflects the decreasing uncertainty of future yield and is a key factor in minimizing impermanent loss. Furthermore, we managed to create a pseudo-AMM that allows us to both facilitate PT and YT swaps using just a single pool of liquidity. With a PT/SY pool, PT can be directly traded with SY, while YT trades are also possible via flash swaps. ### Fee Calculation Unlike traditional AMMs that charge fees on the swap principal, Pendle’s AMM fee is calculated relative to the **yield** being traded. This means the fee is influenced by the time to maturity — a trade made one year before maturity will incur a significantly higher fee than the same size trade made one month before maturity. See [Fees](../Mechanisms/Fees) for the full formula. ## Liquidity Providers (LP) Liquidity on Pendle V2 comprises of PT/SY (where SY is simply a wrapped version of the underlying yield bearing asset). This means that LPs earn yields from: 1. PT fixed yield 2. Underlying yield (SY yield) 3. Swap fees (from PT and YT swaps) 4. $PENDLE incentives ## Swaps Both PT and YT are tradeable anytime on Pendle through a single pool of liquidity. This is made possible by implementing a pseudo-AMM with flash swaps. Liquidity pools in Pendle V2 are set up as PT/SY, e.g. PT-aUSDC / SY-aUSDC. Swapping PT is a straightforward process of swapping between the 2 assets in the pool, while swapping YT is enabled via flash swaps in the same pool. > Auto-routing is built in, allowing anyone to trade PTs and YTs with any major asset. ### Flash Swaps Flash swaps are possible due to the relationship between PT and YT. As PT and YT can be minted from and redeemed to its underlying SY, we can express the price relationship: $$ P(PT) + P(YT) = P(\text{Underlying}) $$ Knowing that YT price has an inverted correlation against PT price, we use this price relationship to utilise the PT/SY pool for YT swaps. Buying YT: 1. Buyer sends SY into the swap contract (auto-routed from any major token) 2. Contract withdraws more SY from the pool 3. Mint PTs and YTs from all of the SY 4. Send the YTs to the buyer 5. The PTs are sold for SY to return the amount from step 2 ![Buying YT](/pendle-docs/imgs/ProtocolMechanics/buying_yt.png "Buying YT") Selling YT: 1. Seller sends YT into the swap contract 2. Contract borrows an equivalent amount of PT from the pool 3. The YTs and PTs are used to redeem SY 4. SY is sent to the seller (or routed to any major tokens, e.g. ETH, USDC, wBTC, etc) 5. A portion of the SY is sold to the pool for PT to return the amount from step 2 ![Selling YT](/pendle-docs/imgs/ProtocolMechanics/selling_yt.png "Selling YT") ## Matured LP Upon maturity, LPs are able to Zap Out + Redeem PT for Underlying + Claim Rewards in a single transaction: 1. Visit [Pendle Trade](https://app.pendle.finance/trade/pools) and toggle to the “Inactive” pool list 2. Select a pool 3. Toggle “Claim All Pool Rewards” 4. Select an output asset. Pendle will automatically Redeem PT for Underlying > Unwrap SY > Perform Swaps (if needed) here ## Key Features ### Minimal Impermanent Loss (IL) Pendle V2 design ensures that IL is a negligible concern. Pendle’s AMM accounts for PT’s natural price appreciation by shifting the AMM curve to push PT price towards its underlying value as time passes, mitigating time-dependent IL (No IL at maturity). On top of that, IL from swaps is also mitigated as both assets LP’ed are very highly correlated against one another (e.g. PT-stETH / SY-stETH). If liquidity is provided until maturity, an LP’s position will be equivalent to fully holding the underlying asset since PT essentially appreciates towards the underlying asset. In most cases prior to maturity, PT trades within a yield range and does not fluctuate as much as an asset’s spot price. For example, it’s rational to assume that Aave’s USDC lending rate fluctuates between 0%-15% for a reasonable timeframe (and PT accordingly trades within that yield range). This premise ensures a low IL at any given time as PT price will not deviate too far from the time of liquidity provision. ### Customizable AMM ![Customizable AMM](/pendle-docs/imgs/ProtocolMechanics/customizable_amm.png "Customizable AMM") Pendle’s AMM curve can be customised to cater to tokens with varying yield volatilities. Yields are often cyclical in nature and typically swing between highs and lows. Typically, the floor and ceiling for the yield of a liquid asset are much easier to predict than its price. For example, the annual yield of staked ETH is likely to fluctuate in a band of 0.5-7%. Knowing the rough yield range of an asset enables us to concentrate liquidity within that range, enabling much larger trade sizes at a lower slippage. However, if the implied yield of the pool trades out of its set range, liquidity will be too thin to further push it in said direction. Using the above example, if the implied yield of the stETH pool goes beyond 7%, buying YT (or selling PT) might no longer be possible. To check the set yield range of the pool, click on the sign as shown in the screenshot below. ![Market Info](/pendle-docs/imgs/ProtocolMechanics/market_info.png "Market Info") ### Greater Capital Efficiency _For Liquidity Providers_ Since YT trades are routed through the same PT/SY pool, LPs earn fees from both PT and YT swaps from a single liquidity provision, doubling the yield from LPing. _For Traders_ Rather than having separate pools for YT and PT, concentrating all tokens in a PT/SY pool will result in greater liquidity. This will allow traders to make trades of greater volume without having to worry about much slippage, granting traders greater price certainty. --- # Fees Pendle protocol has 2 revenue sources: - **YT Fees** Pendle collects a 5% fee from all yield accrued (including points) by all YT in existence, and all yields (including all points negotiated) from the SYs of matured unredeemed PTs. - **YT Fees on Points** Fees on points are applied similarly as Pendle treats points as a form of yield. Since points are tracked off-chain, partner protocols deduct the 5% fee when allocating points to user wallets. The deducted points from fees are then re-allocated to the following Pendle-controlled wallets: :::info Fee wallets for pools launched BEFORE 8th October 2024 ::: | Chain | Fee Wallet Address | | :-------: | :------------------------------------------: | | Ethereum | `0x8270400d528c34e1596EF367eeDEc99080A1b592` | | Arbitrum | `0xCbcb48e22622a3778b6F14C2f5d258Ba026b05e6` | | Mantle | `0x5C30d3578A4D07a340650a76B9Ae5dF20D5bdF55` | | BNB Chain | `0xd77E9062c6DF3F2d1CB5Bf45855fa1E7712A059e` | :::info Fee wallet for pools launched AFTER 8th October 2024 ::: All chains: `0xC328dFcD2C8450e2487a91daa9B75629075b7A43` - **Swap Fees** Pendle collects a percentage-based swap fee, scaled with maturity, from all PT swaps. Each fee tier will be displayed in the dApp and is decided by the pool deployer (currently only the Pendle team deploys pools on Pendle). Pendle taxes the yield-receivables of PT when swaps occur. This creates a fair fee for all pools and maturities as it is scaled to a pool’s maturity (less time to maturity -> less yield-receivables -> lower fees in $ terms). Since YT swaps are also routed through the PT AMM, its fees are calculated based on the PT swapped. - **Fee Distribution** Pendle has 2 sources of fees **YT fees** and **Swap fees**. 20% of all swap fees are given to LP providers of the pool as yield. The remaining swap fees and all YT fees are split between PENDLE buyback fund and Pendle protocol in the following ratio - 80% for PENDLE buyback - 10% to Protocol Treasury - 10% to Protocol Operations - **Trading Fee Calculation** Trading fees are dynamically adjusted based on time remaining until maturity: `Trading Fee = (Fee Tier / 365) * Days to Maturity` The **Fee Tier** is specific to each market and can be found by clicking the "specs" button on the market's trading interface. Redeeming PT for the underlying asset *after* maturity incurs no protocol fee, only standard network gas fees. - **Post-Maturity Yield Redirection** If a user does not redeem their PT or LP position after maturity, the underlying asset remains in the SY contract and **continues to accrue yield and points**. However, all yield and points generated by these unredeemed, matured positions are automatically redirected to the **Pendle treasury fee wallet**. This incentivizes users to promptly redeem their matured assets and roll them over into new pools. --- # Pendle API Overview Pendle provides a comprehensive API system that enables developers to integrate with the Pendle protocol for trading, analytics, and portfolio management. ## Understanding Pendle's API System **API Base URL**: [https://api-v2.pendle.finance/core/docs](https://api-v2.pendle.finance/core/docs) Pendle's API consists of two complementary components: ### 1. Hosted SDK (Transaction Generation) **Purpose**: Generate transaction payloads to interact with Pendle smart contracts **Use this when you need to**: - Swap tokens (buy/sell PT, YT) - Add or remove liquidity - Mint or redeem PT/YT tokens - Transfer liquidity between pools - Roll over PT positions - ... or any other transactions that interact with Pendle smart contracts 📖 [View Hosted SDK Documentation](./HostedSdk.mdx) ### 2. Backend API (Data Queries) **Purpose**: Retrieve offchain data for markets, assets, pricing, user positions, and governance **Use this when you need to**: - Get offchain data for analytics - Get supported markets list with latest data (TVL, volume, underlying APY, swap fee, ...) - Get PT/YT/LP/SY asset prices - Get sPENDLE and governance data (staking, rewards, ...) 📖 [View Backend API Documentation](https://api-v2.pendle.finance/core/docs) ## API Entry Points All public endpoints are served under `https://api-v2.pendle.finance/core` and follow one of two routing patterns: ### Cross-chain endpoints (recommended) These endpoints return data across all chains in a single request. Preferred for new integrations: | Endpoint | Description | |---------|-------------| | `GET /v2/markets/all` | All markets across all chains — includes pagination, points, and external protocol data | | `GET /v1/assets/all` | All assets across all chains | | `GET /v1/prices/assets` | Asset prices across all chains | :::note Deprecated `GET /v1/markets/all` and `GET /v1/markets/points-market` are deprecated. Use `GET /v2/markets/all` instead — it returns the same markets with pagination support and points data included in each market object. ::: #### Markets — Pagination `GET /v2/markets/all` supports `skip` and `limit` query parameters for pagination: | Parameter | Default | Maximum | Description | |-----------|---------|---------|-------------| | `skip` | `0` | — | Number of records to skip | | `limit` | `10` | `100` | Maximum number of records to return | #### Prices — Error Handling The price response now includes an `errors` array alongside the price map: ``` { priceMap: Record, errors: Error[] } ``` Non-fatal pricing errors (e.g., assets not found for a given timestamp) are returned in `errors` rather than thrown. Callers should check and handle this field, as a partial result may be returned even when some assets could not be priced. ### Chain-scoped endpoints — `/{version}/{chainId}/...` Some endpoints are scoped to a specific chain: | Endpoint | Description | |---------|-------------| | `GET /v3/{chainId}/markets/{address}/historical-data` | **Market time-series data** with optional APY breakdown — [see Market Data Endpoints](#market-data-endpoints) | | ~~`GET /v2/{chainId}/markets/{address}/historical-data`~~ | *(Deprecated)* Use v3 instead for APY breakdown support | | ~~`GET /v2/{chainId}/markets/{address}/data`~~ | *(Deprecated)* Market data for a specific address | | ~~`GET /v2/sdk/{chainId}/convert`~~ | *(Deprecated)* Generate transaction payload — use `POST /v3/sdk/{chainId}/convert` instead | | `GET /v5/{chainId}/transactions/{address}` | Transaction history for an address | :::note Deprecated - `GET /v2/{chainId}/markets/{address}/historical-data` is deprecated. Use [`GET /v3/{chainId}/markets/{address}/historical-data`](#market-data-endpoints) instead, which supports `includeApyBreakdown` for detailed APY composition. - `GET /v2/{chainId}/markets/{address}/data` is deprecated. - `GET /v2/sdk/{chainId}/convert` (GET) is deprecated. Use `POST /v3/sdk/{chainId}/convert` instead, which accepts a JSON body and supports typed inputs. ::: For new integrations, prefer cross-chain endpoints where available — they reduce the number of calls needed when working with multiple chains. ### BFF API — `https://api-v2.pendle.finance/bff` The BFF (Backend for Frontend) API powers the official Pendle web app. It is **not intended for third-party integrations**: endpoints may change or be removed without notice. For third-party use, always use the `/core` API documented here. ## Rate Limiting All Pendle API endpoints are rate-limited to ensure service stability and fair usage. ### How Rate Limiting Works Pendle uses a **Computing Unit (CU)** based system. Every endpoint has a CU cost, which may be fixed or dynamic depending on the endpoint. The rate limit is calculated based on the total CU cost of all endpoints. Each user (IP) has a rate limit of 100 CU per minute, and 200,000 CU per week. Both limits apply simultaneously. You must stay within both the per-minute AND weekly limits to avoid rate limiting. **Example**: If an endpoint costs 5 CU, you can call it: - 20 times per minute (100 ÷ 5 = 20) - 40,000 times per week (200,000 ÷ 5 = 40,000) #### Computing Unit Costs Most RESTful API endpoints have fixed costs (typically 1-5 CU), whereas the Hosted SDK has dynamic costs depending on the number of aggregators used. Check the [API documentation](https://api-v2.pendle.finance/core/docs) for specific costs — they're displayed in the "CU" box before each endpoint description. More on computing unit costs for HostedSdk can be found in [HostedSdk.mdx](./HostedSdk.mdx#select-aggregators). #### Rate Limit Headers Our endpoints return the following headers to help you monitor your usage: | Header | Description | |--------|-------------| | `X-Computing-Unit` | CU cost of this request | | `X-RateLimit-Limit` | Maximum CU per minute (e.g., 100 for free tier) | | `X-RateLimit-Remaining` | CU remaining in current minute window | | `X-RateLimit-Reset` | Unix timestamp when minute limit resets | | `X-RateLimit-Weekly-Limit` | Maximum CU per week (e.g., 200,000 for free tier) | | `X-RateLimit-Weekly-Remaining` | CU remaining in current week window | | `X-RateLimit-Weekly-Reset` | Unix timestamp when week limit resets | Example response: ``` x-computing-unit: 25 x-ratelimit-limit: 100 x-ratelimit-remaining: 75 x-ratelimit-reset: 1724206817 x-ratelimit-weekly-limit: 200000 x-ratelimit-weekly-remaining: 175000 x-ratelimit-weekly-reset: 1724206817 ``` ### Best Practices for Rate Limiting - **Never hardcode rate limits.** Rate limits have evolved over time and may change in the future. Always handle `429` responses gracefully. - **Implement exponential backoff.** When you receive a `429 Too Many Requests` response, wait and retry with increasing delays. - **Use the right endpoint for the job.** For general price monitoring, use `getAllAssetPricesByAddresses` (updates every ~30 seconds). For pre-swap price checks, use `getMarketSpotSwappingPrice` (updates every block). - **Set generous timeouts.** Some API endpoints, particularly those querying complex data, can be slow. Set a client-side HTTP timeout of at least **120 seconds**. - **Watch out for RPC rate limits.** A `429` error is most often caused by your **RPC provider**, not the Pendle API — especially when backfilling historical data. Use a private or paid archival RPC (e.g., QuickNode) for intensive operations. - **Use pagination correctly.** For endpoints returning large lists, include the `resumeToken` from a response in your next request to fetch subsequent pages. ### I get rate limited, what should I do? Our rate limit was designed so that most users can use the API without facing any rate limiting issues, unless you are calling the API unreasonably fast. So before requesting increased rate limits, please make sure you are not calling the API at an unreasonable rate. Follow our best practices and examples. If after all that, you are still getting rate limited, you can consider upgrading your plan, details below. ### API Pricing Plans If you need higher rate limits, we offer flexible paid plans that scale with your needs. #### Pricing Structure Our pricing is simple and scalable: **$10/week = 500 CU/min + 1,000,000 CU/week** You can purchase multiple units to scale your rate limits based on your application's requirements: | Weekly Cost | CU per Minute | Weekly CU Limit | |-------------|---------------|-----------------| | **$0** (Free) | 100 | 200,000 | | **$10** | 500 | 1,000,000 | | **$20** | 1,000 | 2,000,000 | | **$30** | 1,500 | 3,000,000 | | **$40** | 2,000 | 4,000,000 | **Pricing Model:** - Each **$10/week** adds **+500 CU/min** and **+1,000,000 CU/week** to your limits - Scale up to **$40/week** (2,000 CU/min, 4M CU/week) through our standard plans **Example:** If you want to use 1,000,000 CU per week and you want to use the API for 4 weeks, it would be $10 * 4 = $40. If you want to use 2,000,000 CU per week and you want to use the API for 8 weeks (about 2 months), it would be $20 * 8 = $160. ### How to get an API key To get an API key and upgrade your plan: 1. Go to the API dashboard at: [https://api-v2.pendle.finance/dashboard](https://api-v2.pendle.finance/dashboard) 2. Login with your wallet (you will be asked to sign a message to verify your identity) 3. Top-up your account with USDC/USDT/DAI on Arbitrum 4. Choose your plan and create a new API key Your API key will be generated. Make sure to save it securely as you'll need it to authenticate your requests. #### How to use your API key Include the `Bearer ` in the `Authorization` header in your requests: ``` Authorization: Bearer ``` **Example:** ```bash curl -i -H "Authorization: Bearer your_api_key_here" \ https://api-v2.pendle.finance/core/v1/chains ``` The response will have rate limit headers as described in [Rate Limit Headers](#rate-limit-headers). :::note You may occasionally notice `X-Ratelimit-Limit: 100` in responses even when you have a higher rate limit plan. This happens when the response is served from **Cloudflare's cache** (indicated by the `CF-Cache-Status: HIT` header). When a cache hit occurs, the request never reaches our backend, so **it does not count against your rate limit quota**. The `X-Ratelimit-Limit: 100` value in this case is simply a default header from the cached response and does not reflect your actual rate limit. ::: ## API Updates and Deprecation - **Deprecation Notice**: Breaking changes are announced at least 30 days in advance. Follow the [Telegram Developer Channel](https://t.me/pendledevelopers) for announcements. ### vePENDLE Deprecation vePENDLE has been deprecated and replaced by **sPENDLE**. All endpoints listed under the **Ve Pendle** tag in the [API documentation](https://api-v2.pendle.finance/core/docs) are deprecated — they remain accessible but will not be updated to reflect the new sPENDLE staking system. The following endpoints are specifically deprecated: - `GET /v1/ve-pendle/data` — use [`GET /v1/spendle/data`](#get-v1spendledata) instead - `GET /v1/ve-pendle/market-fees-chart` — no replacement; fees data is now included in the sPENDLE historical data response ## Market Data Endpoints ### GET /v3/\{chainId\}/markets/\{address\}/historical-data Returns time-series data for a specific market, with optional detailed APY composition breakdown. | Property | Value | |----------|-------| | Path parameters | `chainId` (number), `address` (string) | | Query parameters | `time_frame`, `timestamp_start`, `timestamp_end`, `fields`, `includeFeeBreakdown`, `includeApyBreakdown` | | Cache TTL | Varies by time_frame | | CU cost | Dynamic: `max(1, ceil(total / 300))` base, `* 1.5 + 1` if `includeApyBreakdown=true`, `* 2 + 1` if `includeFeeBreakdown=true` | **Key features:** - **APY Breakdown**: Set `includeApyBreakdown=true` to get detailed yield composition for YT and LP positions, categorized by protocol yield, rewards, fixed yield, and incentives - **Flexible fields**: Select specific data fields or use `fields=all` for complete data (not recommended for large time ranges) - **Fee breakdown**: Include swap fee details with `includeFeeBreakdown=true` (daily/weekly timeframes only) **Response includes:** - Standard time-series fields: `timestamp`, `impliedApy`, `maxApy`, `tvl`, prices, liquidity metrics - Optional `ytApyBreakdown`: Structured APY breakdown for YT holders (categories: Protocol Yield, YT Bonus Rewards, YT Extra Rewards) - Optional `lpApyBreakdown`: Structured APY breakdown for LP providers (categories: Underlying Yield, External Rewards, PT Fixed Yield, LP Rewards) - Optional fee breakdown: `explicitSwapFee`, `implicitSwapFee`, `limitOrderFee` For detailed schemas, parameters, examples, and migration guide, see the [API Reference](https://api-v2.pendle.finance/core/docs#/Markets/MarketsController_marketHistoricalData_v3). :::tip New in v3 The v3 endpoint adds support for detailed APY breakdowns while maintaining full backward compatibility with v2. All existing v2 functionality works identically in v3. ::: ## sPENDLE API Endpoints These endpoints provide sPENDLE staking statistics and per-user reward data. ### GET /v1/spendle/data Returns aggregate sPENDLE staking statistics, including total PENDLE staked, historical APRs, revenues, fees, and airdrop breakdowns. Historical data covers the last 12 epochs. | Property | Value | |----------|-------| | Query parameters | None | | Cache TTL | 5 minutes | | CU cost | 1 | **Response fields:** | Field | Description | |-------|-------------| | `totalPendleStaked` | Total PENDLE currently staked | | `totalStakedInSpendle` | Total staked in the sPENDLE contract | | `virtualSpendleFromVependle` | Virtual sPENDLE balance derived from vePENDLE positions | | `sPendleHistoricalData` | Per-epoch historical data: `timestamps`, `revenues`, `aprs`, `fees`, `airdrops`, `buybackAmounts`, `airdropInUSDs`, `airdropBreakdowns`, `allTimeRevenues` | | `vependleHistoricalData` | Historical data for the legacy vePENDLE system | ### GET /v1/spendle/:address Returns claimable and historical rewards for a specific sPENDLE holder, including ETH fee rewards, multi-token merkle proofs, and all-time reward totals. | Property | Value | |----------|-------| | Path parameter | `address` — the user's Ethereum address | | Query parameters | None | | Caching | Not cached | | CU cost | 3 | **Response fields:** | Field | Description | |-------|-------------| | `ethAccruedAmount` | Accrued ETH fee rewards claimable by the address | | `multiTokenProof` | Merkle proof data — `total` and `results[]` for multi-token rewards | | `allTimeRewards` | Cumulative rewards keyed by `"chainId-tokenAddress"`, with `lastDistributionAt` timestamp | | `vePendlePositionData` | *(Optional)* Legacy vePENDLE position data, if applicable | ## Dashboard Endpoints ### GET /v1/dashboard/merkle-rewards/:user Returns both claimable and claimed merkle-distributed rewards for a user in a single call. | Property | Value | |----------|-------| | Path parameter | `user` — the user's Ethereum address | | Query parameters | None | | CU cost | 4 | **Response:** ``` { claimableRewards: [...], claimedRewards: [...] } ``` Each item in `claimableRewards` and `claimedRewards` contains: | Field | Description | |-------|-------------| | `user` | Ethereum address of the user | | `token` | Token address | | `merkleRoot` | Merkle root for the distribution | | `chainId` | Chain ID of the distribution | | `assetId` | Asset identifier | | `amount` | Reward amount | | `toTimestamp` | *(Optional)* End of the distribution period | | `fromTimestamp` | *(Optional)* Start of the distribution period | :::note Deprecated endpoints The following endpoints are deprecated and will be removed in a future release. Use `GET /v1/dashboard/merkle-rewards/:user` instead: - `GET /v1/dashboard/merkle-claimable-rewards/:user` - `GET /v1/dashboard/merkle-claimed-rewards/:user` ::: ## Code Examples and Resources ### Official Examples - [Hosted SDK Demo](https://github.com/pendle-finance/pendle-examples-public/tree/main/hosted-sdk-demo/src) - Transaction generation examples - [Backend API Demo](https://github.com/pendle-finance/pendle-examples-public/blob/main/backend-api-demo/src/index.ts) - Data query examples ## Getting Help 1. **Documentation**: Start with [API Overview](./ApiOverview.mdx), [Hosted SDK](./HostedSdk.mdx) or [Backend API](https://api-v2.pendle.finance/core/docs) docs 2. **API Reference**: Explore endpoints at [API Reference](https://api-v2.pendle.finance/core/docs) 3. **Examples**: Check [GitHub examples](https://github.com/pendle-finance/pendle-examples-public) ## Frequently Asked Questions #### **Q: When should I use Hosted SDK vs RESTful API?** A: Use Hosted SDK when you need to **send transactions** to the blockchain (swaps, liquidity operations, mints/redeems). Use RESTful API when you need to **get data** (market info, prices, positions). See [Overview](./ApiOverview.mdx#understanding-pendles-api-system). ### Hosted SDK FAQ #### **Q: How do I know the exact output amount before sending a transaction?** A: There is no way to know the exact output amount before sending a transaction. All output amounts are calculated based on current market conditions and are subject to change until the transaction is executed. You can control how much the output amount can vary compared to the expected amount by setting the `slippage` parameter. #### **Q: Why do I get different results each time I call the same endpoint?** A: Market conditions change constantly. Each call reflects the current state of liquidity pools and aggregator routes. #### **Q: Do I need to approve tokens before using the Hosted SDK?** A: Short answer: no. Long answer: yes. - Short answer: You don't need to approve or have enough token balance to call the Hosted SDK API. - Long answer: The Hosted SDK tries its best to return the most accurate route by simulating all the routes it generates. However, if you don't have enough tokens or approvals, the SDK can't simulate the call, and the returned route will be a preview route, which could differ from the actual route. In short: if you have sufficient approval and balance, the SDK will be able to simulate the transaction, resulting in a more accurate route. Otherwise, all routes are preview routes. Also, the API response includes a `requiredApprovals` field listing tokens that need approval. Approve these tokens to the router contract before sending your transaction. #### **Q: How do I reduce Computing Unit costs when using aggregators?** A: Obtain API keys from aggregator partners (KyberSwap, Odos, OKX) and pass them in request headers. This reduces that aggregator's CU cost to 0. See [Reducing Aggregator Costs](./HostedSdk.mdx#reduce-aggregator-computing-units). #### **Q: What happens if I don't specify which aggregators to use?** A: If `enableAggregator=true` but no `aggregators` parameter is provided, a preset set of aggregators will be used, which may change over time for optimization. #### **Q: Can I swap any PT to any other PT?** A: Not all PT pairs are swappable. Try using the Convert API. If the swap is not supported, the API will return an error. #### **Q: What does the `needScale` parameter do?** A: Set `needScale=true` only when your input amounts are updated on-chain (e.g., using contract balance). When enabled, buffer your input amount by ~2% to account for changes during transaction execution. Only applicable to swap actions. #### **Q: What is `priceImpact` and does it include slippage?** A: `priceImpact` is the effect your trade size has on the market price - it's included in the output amount. **Slippage** is different - it's caused by market movement between API call and transaction execution. You set `slippage` parameter as maximum tolerance (e.g., 0.01 = 1%). See the Pendle API documentation for more details on the difference between price impact and slippage. #### **Q: What's the difference between "Add liquidity" and "Add liquidity ZPI"?** A: - **Add liquidity**: Adds liquidity and receives only LP tokens - **Add liquidity ZPI** (Zero Price Impact): Adds liquidity while keeping the generated YT tokens #### **Q: Can I transfer liquidity between different pools?** A: Yes! Use the Convert API with your current position (LP + PT + YT) as `tokensIn` and the target market as `tokensOut`. #### **Q: What contract is the `to` address in the transaction response?** A: The `to` address is typically the Pendle Router contract. This contract is upgradeable, so the address may change over time. Always use the address returned by the API - never hardcode it. #### **Q: How do I decode the transaction data to see what function is being called?** A: The response includes `contractParamInfo` with: - `method`: Function name (e.g., `"swapExactTokenForPt"`) - `contractCallParamsName`: Parameter names - `contractCallParams`: Parameter values ```typescript console.log('Method:', response.contractParamInfo.method); console.log('Params:', response.contractParamInfo.contractCallParams); ``` #### **Q: Is the router address guaranteed to stay the same?** A: No, the router may be updated for protocol improvements. Always use the `tx.to` address from the API response. Changes will be announced publicly via the [Telegram Developer Channel](https://t.me/pendledevelopers). #### **Q: Can I use the same transaction data multiple times?** A: No. Transaction data is specific to current market conditions. Always generate fresh transaction data immediately before sending. Reusing old data will likely fail or give suboptimal results. --- # Pendle Hosted SDK Pendle accommodates a vast array of assets, each characterized by its unique nuances and complexities. While the Pendle protocol remains immutable, the underlying assets don't share this feature, requiring our app and SDK to be updated frequently to align with changes in these assets. To address this, Pendle has introduced a hosted version of our SDK. It ensures the output remains consistent with Pendle's UI and keeps up-to-date with the latest protocol changes. The API design prioritizes simplicity and stability, with a high rate limit to meet the needs of most users. ## Getting Started **Base URL:** `https://api-v2.pendle.finance/core` Most SDK operations go through a single universal endpoint — the [Convert API](https://api-v2.pendle.finance/core/docs#/SDK/SdkController_convert): ``` GET /v2/sdk/{chainId}/convert?tokensIn=...&tokensOut=...&amountsIn=...&receiver=...&slippage=... ``` The code examples on this page use a `callSDK` helper function that wraps HTTP requests to the base URL. A complete implementation can be found in the [Convert API demo repository](https://github.com/pendle-finance/pendle-examples-public/tree/main/hosted-sdk-demo/src). For API rate limits and computing unit costs, see [API Overview](./ApiOverview.mdx#rate-limiting). ## Supported functions - Swap - Add liquidity - Add liquidity ZPI - Remove liquidity - Mint PT & YT - Redeem PT & YT - Transfer liquidity - Transfer liquidity ZPI - Roll over PT - Add liquidity dual - Remove liquidity dual - Mint SY - Redeem SY All actions above can be accessed via one universal [Convert API](https://api-v2.pendle.finance/core/docs#/SDK/SdkController_convert). ## Examples ### Swap ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&tokensOut=0xf99985822fb361117fcf3768d34a6353e6022f5f&amountsIn=1000000000&enableAggregator=true&aggregators=kyberswap&additionalData=impliedApy,effectiveApy ``` In code: ```ts export async function swapTokenToPt() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${USDC_ADDRESS}`, amountsIn: 1000000000, tokensOut: `${PT_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, enableAggregator: true, aggregators: "kyberswap", additionalData: "impliedApy,effectiveApy", }); } ``` ### Add liquidity ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0&tokensOut=0xc374f7ec85f8c7de3207a10bb1978ba104bda3b2&amountsIn=1000000000000000000 ``` In code: ```ts export async function addLiquiditySingleToken() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${WSTETH_ADDRESS}`, amountsIn: "1000000000000000000", tokensOut: `${MARKET_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, }); } ``` ### Add liquidity ZPI ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0&tokensOut=0xc374f7ec85f8c7de3207a10bb1978ba104bda3b2,0xf3abc972a0f537c1119c990d422463b93227cd83&amountsIn=1000000000000000000 ``` In code: ```ts export async function addLiquiditySingleTokenKeepYt() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${WSTETH_ADDRESS}`, amountsIn: "1000000000000000000", tokensOut: `${MARKET_ADDRESS},${YT_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, }); } ``` ### Remove liquidity ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0xc374f7ec85f8c7de3207a10bb1978ba104bda3b2&tokensOut=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0&amountsIn=1000000000000000000&enableAggregator=true ``` In code: ```ts export async function removeLiquiditySingleToken() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${MARKET_ADDRESS}`, amountsIn: "1000000000000000000", tokensOut: `${WSTETH_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, enableAggregator: true, }); } ``` ### Mint PT & YT ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0&tokensOut=0xf99985822fb361117fcf3768d34a6353e6022f5f,0xf3abc972a0f537c1119c990d422463b93227cd83&amountsIn=1000000000000000000 ``` In code: ```ts export async function mintPyFromToken() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${WSTETH_ADDRESS}`, amountsIn: "1000000000000000000", tokensOut: `${PT_ADDRESS},${YT_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, }); } ``` ### Redeem PT & YT ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0xf99985822fb361117fcf3768d34a6353e6022f5f,0xf3abc972a0f537c1119c990d422463b93227cd83&tokensOut=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0&amountsIn=1000000000000000000,1000000000000000000 ``` In code: ```ts export async function redeemPyToToken() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${PT_ADDRESS},${YT_ADDRESS}`, amountsIn: "1000000000000000000,1000000000000000000", tokensOut: `${WSTETH_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, }); } ``` ### Transfer liquidity ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0x9bc2fb257e00468fe921635fe5a73271f385d0eb,0x21aace56a8f21210b7e76d8ef1a77253db85bf0a,0x3787c19c32e727310708c0693aec00fb37a01e7b&tokensOut=0xc374f7ec85f8c7de3207a10bb1978ba104bda3b2&amountsIn=1000000000000000000,1000000000000000000,1000000000000000000&enableAggregator=true&aggregators=kyberswap ``` In code: ```ts export async function transferLiquidity() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${FXSAVE_MARKET_ADDRESS},${FXSAVE_PT_ADDRESS},${FXSAVE_YT_ADDRESS}`, amountsIn: "1000000000000000000,1000000000000000000,1000000000000000000", tokensOut: `${MARKET_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, enableAggregator: true, aggregators: "kyberswap", }); } ``` ### Transfer liquidity ZPI ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0x9bc2fb257e00468fe921635fe5a73271f385d0eb,0x21aace56a8f21210b7e76d8ef1a77253db85bf0a,0x3787c19c32e727310708c0693aec00fb37a01e7b&tokensOut=0xc374f7ec85f8c7de3207a10bb1978ba104bda3b2,0xf3abc972a0f537c1119c990d422463b93227cd83&amountsIn=1000000000000000000,1000000000000000000,1000000000000000000&enableAggregator=true&aggregators=kyberswap ``` In code: ```ts export async function transferLiquidityKeepYt() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${FXSAVE_MARKET_ADDRESS},${FXSAVE_PT_ADDRESS},${FXSAVE_YT_ADDRESS}`, amountsIn: "1000000000000000000,1000000000000000000,1000000000000000000", tokensOut: `${MARKET_ADDRESS},${YT_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, enableAggregator: true, aggregators: "kyberswap", }); } ``` ### Roll over PT ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0x21aace56a8f21210b7e76d8ef1a77253db85bf0a&tokensOut=0xf99985822fb361117fcf3768d34a6353e6022f5f&amountsIn=1000000000000000000&enableAggregator=true ``` In code: ```ts export async function rollOverPt() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${FXSAVE_PT_ADDRESS}`, amountsIn: "1000000000000000000", tokensOut: `${PT_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, enableAggregator: true, }); } ``` ### Add liquidity dual ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0xcbc72d92b2dc8187414f6734718563898740c0bc,0xf99985822fb361117fcf3768d34a6353e6022f5f&tokensOut=0xc374f7ec85f8c7de3207a10bb1978ba104bda3b2&amountsIn=1000000000000000000,1000000000000000000 ``` In code: ```ts export async function addLiquidityDualSyAndPt() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${SY_ADDRESS},${PT_ADDRESS}`, amountsIn: "1000000000000000000,1000000000000000000", tokensOut: `${MARKET_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, }); } ``` ### Remove liquidity dual ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0xc374f7ec85f8c7de3207a10bb1978ba104bda3b2&tokensOut=0xf99985822fb361117fcf3768d34a6353e6022f5f,0xcbc72d92b2dc8187414f6734718563898740c0bc&amountsIn=1000000000000000000 ``` In code: ```ts export async function removeLiquidityDualSyAndPt() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${MARKET_ADDRESS}`, amountsIn: "1000000000000000000", tokensOut: `${PT_ADDRESS},${SY_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, }); } ``` ### Mint SY ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0&tokensOut=0xcbc72d92b2dc8187414f6734718563898740c0bc&amountsIn=1000000000000000000 ``` In code: ```ts export async function mintSyFromToken() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${WSTETH_ADDRESS}`, amountsIn: "1000000000000000000", tokensOut: `${SY_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, }); } ``` ### Redeem SY ``` GET https://api-v2.pendle.finance/core/v2/sdk/1/convert?receiver=&slippage=0.01&tokensIn=0xcbc72d92b2dc8187414f6734718563898740c0bc&tokensOut=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0&amountsIn=1000000000000000000 ``` In code: ```ts export async function redeemSyToToken() { const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${SY_ADDRESS}`, amountsIn: "1000000000000000000", tokensOut: `${WSTETH_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, }); } ``` ## Send Transaction After calling any of the Convert API functions above, you can inspect the response and send the transaction: ```ts const res: ConvertResponse; // Log the action and outputs console.log("Action: ", res.action); console.log("Outputs: ", res.routes[0].outputs); // Send the transaction getSigner().sendTransaction(res.routes[0].tx); ``` Please visit our [Convert API demo](https://github.com/pendle-finance/pendle-examples-public/tree/main/hosted-sdk-demo/src) to see more detailed examples. ### Inputs The Convert API accepts the following input parameters: | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `tokensIn` | string | Yes | - | Array of input token addresses (comma-separated) | | `amountsIn` | string | Yes | - | Array of input token amounts in wei (comma-separated) | | `tokensOut` | string | Yes | - | Array of expected output token addresses (comma-separated) | | `receiver` | string | Yes | - | Address to receive the output tokens | | `slippage` | number | Yes | - | Maximum slippage tolerance (0-1, where 0.01 = 1%) | | `enableAggregator` | boolean | No | `false` | Enable swap aggregators for token conversions | | `aggregators` | string | No | - | Specific aggregators to use (comma-separated), see [Routing](#routing) | | `redeemRewards` | boolean | No | `false` | Whether to redeem rewards during the action, applicable to actions: `transfer-liquidity` | | `needScale` | boolean | No | `false` | Aggregator needScale parameter, only set to true when amounts are updated on-chain. When enabled, please make sure to buffer the input amount by about 2%, applicable to actions: `swap` | | `additionalData` | string | No | - | Additional data to include in response (comma-separated), see [Additional data](#additional-data) | ### Outputs The Convert API returns the following response structure: | Field | Type | Required | Description | |-------|------|----------|-------------| | `action` | string | Yes | The classified and executed action (e.g., `mint-py`, `swap`, `add-liquidity`) | | `inputs` | [TokenAmountResponse[]](#tokenamountresponse) | Yes | Input tokens and amounts used in the action | | `requiredApprovals` | [TokenAmountResponse[]](#tokenamountresponse) | No | Tokens requiring approval before execution | | `routes` | [ConvertResponse[]](#convertresponse) | Yes | Array of route execution details, sorted by decreasing output amount | #### TokenAmountResponse | Field | Type | Required | Description | |-------|------|----------|-------------| | `token` | string | Yes | Token contract address (lowercase) | | `amount` | string | Yes | Token amount in wei (BigInt string) | #### ConvertResponse | Field | Type | Required | Description | |-------|------|----------|-------------| | `contractParamInfo` | [ContractParamInfo](#contractparaminfo) | Yes | Param info for the contract method called | | `tx` | [TransactionDto](#transactiondto-tx) | Yes | Complete transaction data for execution | | `outputs` | [TokenAmountResponse[]](#tokenamountresponse) | Yes | Expected output tokens and amounts | | `data` | [ConvertData](#convertdata) | Yes | Action-specific data | #### ContractParamInfo | Field | Type | Required | Description | |-------|------|----------|-------------| | `method` | string | Yes | Contract method name (e.g., `mintPyFromToken`) | | `contractCallParamsName` | string[] | Yes | Parameter names array | | `contractCallParams` | any[] | Yes | Parameter values array | #### TransactionDto {#transactiondto-tx} | Field | Type | Required | Description | |-------|------|----------|-------------| | `data` | string | Yes | Encoded transaction data (hex) | | `to` | string | Yes | Contract address to call | | `from` | string | Yes | Sender address | | `value` | string | Yes | Native token amount to send | #### ConvertData | Field | Type | Required | Description | |-------|------|----------|-------------| | `aggregatorType` | string | Yes | The aggregator used, or `VOID` if none was used | | `priceImpact` | number | Yes | Price impact | | `impliedApy` | [ImpliedApy](#impliedapy) | No | Market APY information for yield actions. (for `swap` actions) | | `effectiveApy` | number | No | User's effective APY after fees/slippage. (for `swap` actions) | | `paramsBreakdown` | [ParamsBreakdown](#paramsbreakdown) | No | Multi-step action breakdown (for `transfer-liquidity`) | #### ImpliedApy | Field | Type | Required | Description | |-------|------|----------|-------------| | `before` | number | Yes | Implied APY before transaction | | `after` | number | Yes | Implied APY after transaction | #### ParamsBreakdown | Field | Type | Required | Description | |-------|------|----------|-------------| | `selfCall1` | [ContractParamInfo](#contractparaminfo) | Yes | Params info for selfCall1 | | `selfCall2` | [ContractParamInfo](#contractparaminfo) | No | Params info for selfCall2 | | `reflectCall` | [ContractParamInfo](#contractparaminfo) | Yes | Params info for reflectCall | ## Features ### Routing Routing is a feature of the Pendle SDK that helps users find the most optimal route to interact with the Pendle system. This feature ensures that users can efficiently execute their transactions by identifying the best paths for their specific needs, whether it's swapping assets, adding or removing liquidity, or any other supported function. To take advantage of the routing feature, users need to set the `enableAggregator` option to `true`. When this option is enabled, the system will automatically perform routing to find the most optimal route for the user's transaction. This ensures that users always get the best possible outcome when interacting with the Pendle system. #### Select aggregators When `enableAggregator` is set to `true`, you can control which aggregators are used by specifying the `aggregators` parameter. This parameter accepts a comma-separated list of aggregator names (e.g. "kyberswap,odos"). If not specified, the system will use all available aggregators to find the optimal route. Using more aggregators generally results in better optimized routes since there are more options to choose from. However, each aggregator adds to the computing unit cost of the request (see [rate limiting](./ApiOverview.mdx#rate-limiting)). You can balance between optimization and cost by selectively enabling only the aggregators you want to use. Currently supported aggregators (can be fetched from [fetch supported aggregators](https://api-v2.pendle.finance/core/docs#/SDK/SdkController_getSupportedAggregators) endpoint), this list is subject to change: | Aggregator | Cost (Computing Units) | | ----------- | ---------------------- | | `kyberswap` | 1 | | `odos` | 10 | | `okx` | 2 | | `paraswap` | 15 | For example, this request will use KyberSwap and Odos aggregators to find the optimal route, and it costs 16 computing units (1 from kyberswap, 10 from odos, and 5 from the base computing cost): ```ts export async function addLiquiditySingleToken() { // Use 1 ETH to add liquidity to stETH pool with 1% slippage const res = await callSDK(`/v2/sdk/${CHAIN_ID}/convert`, { tokensIn: `${ETH_ADDRESS}`, amountsIn: '1000000000000000000', tokensOut: `${STETH_LP_ADDRESS}`, receiver: RECEIVER_ADDRESS, slippage: 0.01, enableAggregator: true, aggregators: "kyberswap,odos", }); console.log("Action: ", res.action); console.log("Outputs: ", res.routes[0].outputs); console.log("Price impact: ", res.routes[0].data.priceImpact); // Send tx getSigner().sendTransaction(res.routes[0].tx); } ``` ### Reduce aggregator computing units By default, when using aggregators, the system will use public API keys for each aggregator. This means that the computing unit cost for each aggregator will be added to the total cost of the request. To reduce the computing unit cost, you can use your own API key for each aggregator. This way, the computing unit cost for that aggregator will be reduced to 0. For example, if you use your own API key for Odos, the total computing unit cost will be 5 (base cost) + 1 (kyberswap cost) + 0 (odos cost with custom API key) = 6 computing units. Currently, you can use an aggregator with your own API key by specifying the corresponding field in the request headers. | Aggregator | Header Key | Note | | ----------- | -------------------- | ------------------------------------ | | `kyberswap` | `KYBERSWAP-API-KEY` | | | `odos` | `ODOS-API-KEY` | | | `okx` | `OKX-ACCESS-KEY`, `OKX-ACCESS-SECRET`, `OKX-PASSPHRASE` | Need all three to return a valid response | | `paraswap` | `PARASWAP-API-KEY` | | When using custom aggregator keys, make sure to include the required headers in your request. If the headers are not provided, the system will use the public API key for that aggregator. ### Additional data When an endpoint has an `additionalData` field, users can pass in some fields to receive more data, but it will cost more computing units. For example, the **swap** action has `additionalData` with two available fields: `impliedApy` and `effectiveApy`. If the query parameters have `additionalData=impliedApy`, the response will have the implied APY before and after the swap action. For additional usage, please refer to the [API documentation](https://api-v2.pendle.finance/core/docs) to explore more. ## Migrating from individual endpoints to Convert The Convert API replaces all of the individual SDK endpoints (`/swap`, `/add-liquidity`, `/mint`, `/redeem`, etc.). The table below maps each old endpoint to the equivalent Convert call. | Old endpoint | Convert equivalent | |---|---| | `GET /v2/sdk/{chainId}/markets/{market}/swap` | `tokensIn=[token/PT/YT]`, `tokensOut=[token/PT/YT]` | | `GET /v2/sdk/{chainId}/markets/{market}/add-liquidity` | `tokensIn=[token]`, `tokensOut=[LP]` (standard) or `tokensOut=[LP,YT]` (ZPI) | | `GET /v2/sdk/{chainId}/markets/{market}/add-liquidity-dual` | `tokensIn=[token,PT]`, `tokensOut=[LP]` | | `GET /v2/sdk/{chainId}/markets/{market}/remove-liquidity` | `tokensIn=[LP]`, `tokensOut=[token]` | | `GET /v2/sdk/{chainId}/markets/{market}/remove-liquidity-dual` | `tokensIn=[LP]`, `tokensOut=[token,PT]` | | `GET /v2/sdk/{chainId}/mint` | `tokensIn=[token]`, `tokensOut=[PT,YT]` | | `GET /v2/sdk/{chainId}/redeem` | `tokensIn=[PT,YT]` (or just `[PT]` after expiry), `tokensOut=[token]` | | `GET /v2/sdk/{chainId}/mint-sy` | `tokensIn=[token]`, `tokensOut=[SY]` | | `GET /v2/sdk/{chainId}/redeem-sy` | `tokensIn=[SY]`, `tokensOut=[token]` | | `GET /v2/sdk/{chainId}/markets/{market}/transfer-liquidity` | `tokensIn=[LP,PT,YT]` (any subset), `tokensOut=[destination LP]` | | `GET /v2/sdk/{chainId}/markets/{market}/roll-over-pt` | `tokensIn=[source PT]`, `tokensOut=[destination PT]` | | `GET /v2/sdk/{chainId}/markets/{market}/exit-positions` | `tokensIn=[LP,PT,YT]` (any subset), `tokensOut=[token]` | ### Which version should I use? Use **v3 (POST `/v3/sdk/{chainId}/convert`)** for new integrations. It accepts a JSON body with a typed `inputs` array instead of comma-separated query strings, which is easier to construct programmatically. Use **v2 (GET `/v2/sdk/{chainId}/convert`)** if you need a fully query-parameter-based URL (e.g. for simple curl testing or when an HTTP client doesn't support request bodies on GET). Both variants execute the same logic and return the same response. --- # RouterStatic ## Overview :::info Please note that this RouterStatic should not be used for fund-sensitive/on-chain transactions. If you or your team needs to use any functions on-chain, please let us know. ::: This docs assume you have read Pendle's [High Level Architecture](../HighLevelArchitecture.md). The RouterStatic addresses across the different chains can be found under Deployments on the navigation sidebar on the left. RouterStatic is a contract designed for off-chain computations. It's a multi-facet proxy (ERC2535), so the easiest way to use it is by using the ABI of `contracts/interfaces/IPRouterStatic.sol`. The Router will resolve the call accordingly when any function is called. Most functions are straightforward to use. Below, we discuss some less straightforward functions: ```solidity function getLpToSyRate(address market) external view returns (uint256); function getPtToSyRate(address market) external view returns (uint256); function getLpToAssetRate(address market) external view returns (uint256); function getPtToAssetRate(address market) external view returns (uint256); ``` - `getLpToSyRate`: Retrieves the **spot** price of LP in terms of its corresponding SY. - `getLpToAssetRate`: Serves the same purpose, but in terms of SY's asset. Let's consider the following example: ![RouterStatic Example](/pendle-dev-docs/imgs/Developers/routerstatic_example.png "RouterStatic Example") The total value of LP is 4.90M USD, and the total supply of LP is 1,373.11. ⇒ The price of one LP is 3568 USD. By calling **`getLpToSyRate`**, we receive `1817546249542325054`, which is 1.817 SY-wstETH == 1.817 wstETH. At this time, the price of one token is 1,966 USD, so one LP is roughly 3572 USD. By calling **`getLpToAssetRate`**, we receive `2049787610667181659`, which is 2.049 stETH == one LP is roughly 3567 USD. --- # Limit Orders Pendle's Limit Order system enables gasless, off-chain order creation with secure on-chain settlement. It allows users to place orders at specific implied APY rates, which are matched and settled through the Limit Order smart contract. ## How It Works 1. **Makers** create limit orders by either (a) signing off-chain with no gas — the recommended path for EOAs — or (b) pre-signing on-chain via `preSignSingle` / `preSignBatch`, which is intended for smart-contract makers. Orders specify a desired implied APY rate and are stored on Pendle's backend. 2. **Takers** query available orders via the API and fill them on-chain by calling the Limit Order contract. Pre-signed orders (and orders already partially filled) can be filled with an empty signature; ERC-1271 contract signatures are also supported. 3. The **Limit Order contract** settles orders atomically, transferring tokens between makers and takers at the agreed rates. Limit orders are also integrated into the [Pendle Hosted SDK](../Backend/HostedSdk.mdx) — when enabled, the SDK automatically includes limit order liquidity alongside AMM liquidity to improve execution prices, especially for large trades. ## Order Types | Order Type | Description | |------------|-------------| | `SY_FOR_PT` | Swap SY (or a supported token) for PT | | `PT_FOR_SY` | Swap PT for SY (or a supported token) | | `SY_FOR_YT` | Swap SY (or a supported token) for YT | | `YT_FOR_SY` | Swap YT for SY (or a supported token) | ## Sections - [**Limit Order Contract**](./LimitOrderContract.md) — Smart contract reference: order struct, method definitions, and callback mechanism - [**Create a Limit Order**](./CreateALimitOrder.md) — Guide for makers: generate, sign, and submit orders - [**Cancel Limit Orders**](./CancelOrders.mdx) — Guide for makers: cancel specific orders or invalidate all via nonce - [**Fill a Limit Order**](./FillALimitOrder.md) — Guide for takers: query and fill orders on-chain ## API Reference - [Maker APIs](https://api-v2.pendle.finance/limit-order/docs#/Maker) — Generate order data, submit orders, view active orders - [Taker APIs](https://api-v2.pendle.finance/limit-order/docs#/Taker) — Query available orders for filling - [Order Book Socket.IO feed](../Backend/SocketIO.mdx#order-book) — Pushed order-book snapshots every 5s instead of polling REST ## Maker Incentives Certain markets offer token rewards to makers who provide liquidity through limit orders. ### How Incentives Work - **Epochs**: Incentives are distributed on a weekly basis. - **Qualifying orders**: To be eligible, an order must be placed within the market's configured APY range and remain active for a minimum period. Orders that are placed and immediately canceled do not qualify. - **Reward calculation**: Rewards are proportional to your order's effective making amount relative to the total qualifying volume within the epoch. ### Incentive Modes | Mode | Description | |------|-------------| | `RELATIVE` | Rewards apply to orders placed within a configured APY range relative to the current market rate | | `ABSOLUTE` | Rewards apply to orders placed within a fixed APY range | ### Qualifying for Rewards 1. Check whether the market has an active incentive program. 2. Place an order within the market's configured APY range. 3. Keep the order active — rewards accumulate proportionally to your share of total qualifying making volume within the epoch. --- # Create a Limit Order Pendle Limit Order system allows makers to create limit orders without using gas. To achieve this, makers' signed orders are stored off-chain and will be filled by the takers on-chain. To be able to create a limit order and submit it to the Pendle Limit Order system, you can follow these steps: 1. Generate the limit order data 2. Sign the limit order data 3. Post the limit order data and its signature to the Pendle Limit Order system Pendle exposes 2 APIs to help makers create orders more easily 1. [Generate limit order data](https://api-v2.pendle.finance/core/docs#tag/limit-orders/post/v1/limit-orders/makers/generate-limit-order-data) 2. [Post limit order](https://api-v2.pendle.finance/core/docs#tag/limit-orders/post/v1/limit-orders/makers/limit-orders) ## TypeScript Example **Note:** The code examples in the guide below are taken from our demo GitHub repository, which demonstrates the complete end-to-end Limit Order processes in a TypeScript environment. [Repo](https://github.com/pendle-finance/pendle-examples-public/tree/main/limit-order-api-demo) ### Step 1: Generate limit order data [Order data definition](./LimitOrderContract.md#order-struct-definition) To place a limit order, the maker needs to generate this Order struct, which requires several details. Pendle provides a backend API that helps generate this data more easily. By providing the necessary information (`orderType`, `token`, `maker`, `impliedAPY`), the API returns the full limit order data for the maker. Makers can generate the complete data themselves, but the API simplifies the process by handling complex fields like `salt`, `failSafeRate`, `nonce`, etc. ```ts const requestBody: GenerateLimitOrderDataRequest = { chainId: ChainId.ARBITRUM, YT: aUSDC_MARKET.yt, maker: signerAddress, orderType: LimitOrderType.TOKEN_FOR_PT, token: aUSDC_MARKET.tokenIn.usdc, // Use USDC as token in to swap to PT makingAmount: '10000000', // 10 USDC impliedApy: 0.1, // 10% implied APY expiry: String(Math.floor(Date.now() / 1000) + 20 * 60), // order will be expired in 20 minutes }; ``` Full details of all limit order data can be found via the `GenerateLimitOrderDataResponse` on our [API specification](https://api-v2.pendle.finance/limit-order/docs#/) Note that you need to ensure you have sufficient balance and allowance to create the limit order; otherwise, the API will return a 400 error. ### Step 2: Sign limit order data ```ts const signature = await signer.signTypedData(limitOrderDomainArbitrum, typesLimitOrder, data); ``` You can find the full example of signing a limit order using the ethers.js library on our [API demo repository](https://github.com/pendle-finance/pendle-examples-public/tree/main/limit-order-api-demo) ### Step 3: Post the limit order After signing the limit order data, you can send the limit order data along with the signature to our Backend API ```ts const requestBody: CreateLimitOrderRequest = { ...generatedLimitOrderData, yt: generatedLimitOrderData.YT, type: generatedLimitOrderData.orderType, signature, }; ``` All the implementation details can be found in the [API demo repository](https://github.com/pendle-finance/pendle-examples-public/tree/main/limit-order-api-demo) ## Alternative: Create an Order On-Chain In addition to off-chain signing, makers can register an order directly on-chain by calling `preSignSingle` (or `preSignBatch` for multiple orders) on the Limit Router. This is mainly useful for: - **Smart-contract makers** that cannot produce an ECDSA signature off-chain. - **ERC-1271 contracts** that prefer to register orders explicitly instead of relying on `isValidSignature` at fill time. > **ERC-1271 contracts can skip the on-chain pre-sign step entirely.** When the taker passes an empty signature, the Limit Router's ECDSA recovery fails and falls back to `IERC1271(maker).isValidSignature(hash, signature)`. A maker contract can therefore authorize an order simply by whitelisting its hash internally — no transaction to the Limit Router is required. See [Signature Validation](./LimitOrderContract.md#signature-validation) for the full flow. ### Steps 1. Generate the order data (same `Order` struct as the off-chain flow — see [Order Struct Definition](./LimitOrderContract.md#order-struct-definition)). You can still use the [generate-limit-order-data API](https://api-v2.pendle.finance/core/docs#tag/limit-orders/post/v1/limit-orders/makers/generate-limit-order-data) — just skip the signing step. 2. Call `preSignSingle(order)` from the `maker` address. Once an order is pre-signed, takers fill it the same way as a normally signed order, but they may pass an empty signature (`'0x'`) in `FillOrderParams.signature`. See [Signature Validation](./LimitOrderContract.md#signature-validation) for details. --- # Fill a Limit Order Takers can fill any signed limit order from the Pendle Limit Order order books by executing the fill order on the smart contract. By using Pendle Limit Order APIs, takers can access liquidity sources without slippage, ensuring secure on-chain settlements. Pendle exposes 1 API for takers to view active orders to fill them on-chain [Get limit orders](https://api-v2.pendle.finance/limit-order/docs#/Taker/TakersController_generateLimitOrderData) ## TypeScript Example **Note:** The code examples in the guide below are taken from our demo GitHub repository, which demonstrates the complete end-to-end Limit Order processes in a TypeScript environment. [Repo](https://github.com/pendle-finance/pendle-examples-public/tree/main/limit-order-api-demo) ### Step 1: Fetch the limit orders Takers can use Pendle's API to fetch the limit orders and use any fetched orders to fill. ```ts const requestQuery: LimitOrderQuery = { skip: 0, limit: 10, // Use skip and limit to fetch the orders, you can fetch up to 100 orders per request chainId: ChainId.ARBITRUM, yt: aUSDC_MARKET.yt, type: LimitOrderType.TOKEN_FOR_PT, sortBy: 'Implied Rate', sortOrder: 'asc', }; ``` A single API response can return up to 100 orders. Takers can use the skip and limit parameters to find more orders. Takers can sort the orders by implied rate (in ascending or descending order) to find the best orders. For example: If a taker wants to find orders selling tokens for YT, the orders with a lower implied rate will be more profitable than those with a higher implied rate. So takers can fetch the orders sorted by `Implied Rate` and `sortOrder` set to `asc`. The returned data will include `netFromTaker`, `netToTaker`, indicating the amount the taker will receive and the amount that will be taken from the taker. ### Step 2: Fill the order ```ts const sumNetFromTaker = limitOrdersInfo.reduce((acc, limitOrderInfo) => { return acc + BigInt(limitOrderInfo.netFromTaker); }, 0n); // Maximum amount to be used to fill the order // We recommend buffer the returned value from BE by 1% because // the netFromTaker amount will change by time const maxTaking = (sumNetFromTaker * 101n) / 100n; const tx = await contract.fill( fillParams, // limit of order to fill signer.getAddress(), // receiver maxTaking, '0x', '0x' ); ``` The `signature` inside each `fillParams` entry may be empty (`"0x"`) when the order was pre-signed on-chain or has already been partially filled — for fresh off-chain orders, pass the maker's signature returned by the API. See [Signature Validation](./LimitOrderContract.md#signature-validation) for details. There are three main params that you need to fill the limit orders 1. `fillParams`: The list of limit orders and the amount you want to fill (you can partially fill the order) 2. `receiver`: The address that will receive the maker amount from the limit orders. 3. `maxTaking`: The maximum amount the limit order contract can take from the Taker to fill the limit orders. The maxTaking value indicates the maximum amount the limit order contract can take from the Taker to fill the limit orders. Because the `netFromTaker` data received from Pendle's backend can change over time, it's recommended to buffer the maxTaking by 1% of the sumNetFromTaker. You can find more implementation details in our [demo repository](https://github.com/pendle-finance/pendle-examples-public/tree/main/limit-order-api-demo) --- # Cancel Limit Orders Makers can send a transaction to the Limit Order contract to cancel their orders. This action prevents those orders from being settled by the contract. There are two methods for makers to cancel their orders: 1. Cancel specific orders: Target individual orders for cancellation. 2. Increase nonce: Cancel all orders with a nonce less than the current maker's nonce. 3. Pendle provides an API to help makers find their active orders, allowing them to cancel orders more easily. [Get maker's active orders](https://api-v2.pendle.finance/limit-order/docs#/Maker/MakersController_getMakerLimitOrder) ## TypeScript Example
Note: The code examples in the guide below are taken from our demo GitHub repository, which demonstrates the complete end-to-end Limit Order processes in a TypeScript environment. [View Repository →](https://github.com/pendle-finance/pendle-examples-public/tree/main/limit-order-api-demo)
## Cancel specific order ### Step 1: Find the maker's active orders To cancel specific orders, you need the data for those orders. Makers can use the Pendle API to retrieve their active order data. ```ts const requestQuery: LimitOrderMakerQuery = { skip: 0, limit: 10, chainId: ChainId.ARBITRUM, maker: await getSigner().getAddress(), isActive: true } ``` You can find a complete example of how to get a maker's active orders in our [API demo repository](https://github.com/pendle-finance/pendle-examples-public/tree/main/limit-order-api-demo) ### Step 2: Cancel the orders Once you have the limit order data, you can send this data to the Limit Order contract to cancel specific orders. ```ts const tx = await contract.cancelSingle(order); ``` You can find a complete example of how to cancel an order in our [API demo repository](https://github.com/pendle-finance/pendle-examples-public/tree/main/limit-order-api-demo) ## Increase nonce Each order has a `nonce` field. When creating a limit order, this field is typically set to the current maker's nonce. All orders with a nonce lower than the current maker's nonce become invalid. Makers can increase their nonce in the Limit Order contract to cancel all orders with a lower nonce (assuming they were created with the maker's nonce at the time of creation). ```ts const tx = await contract.increaseNonces(); ``` You can find a complete example of how to increase the nonce in our [API demo repository](https://github.com/pendle-finance/pendle-examples-public/tree/main/limit-order-api-demo) --- # Limit Order Contract The Limit Order contract is where limit orders are settled, allowing orders to be generated off-chain and settled on-chain. The Limit Order contract provides methods to support: 1. Makers canceling their orders. 2. Takers filling orders. 3. A callback function to support arbitrageurs arbitraging limit orders. You can find the contract's implementation [here](https://github.com/pendle-finance/pendle-core-v2/tree/main/contracts/limit). ## Order Struct Definition ```sol interface IPLimitOrderType { enum OrderType { SY_FOR_PT, PT_FOR_SY, SY_FOR_YT, YT_FOR_SY } } struct Order { uint256 salt; uint256 expiry; uint256 nonce; IPLimitOrderType.OrderType orderType; address token; address YT; address maker; address receiver; uint256 makingAmount; uint256 lnImpliedRate; uint256 failSafeRate; bytes permit; } ``` You can find the contract's implementation [here](https://github.com/pendle-finance/pendle-core-v2/tree/main/contracts/limit). ## Field Explanations - `salt`: A randomly generated number that differentiates between orders. - `expiry`: The expiration timestamp of the order. Orders cannot be settled on-chain after this point. - `nonce`: This field allows makers to cancel all their created orders by simply increasing the nonce. All orders with a nonce less than the current nonce will be invalid. Orders are typically created with a nonce equal to the current nonce at the time of creation. - `orderType`: Indicates the type of trade. There are four types of limit orders: 1. `SY_FOR_PT`: Swap SY (or another SY's token in) for PT. 2. `PT_FOR_SY`: Swap PT for SY (or another SY's token out). 3. `SY_FOR_YT`: Swap SY (or another SY's token in) for YT. 4. `YT_FOR_SY`: Swap YT for SY (or another SY's token out). - `token`: Specifies which token to use for the order. If the orderType is SY_FOR_PT or SY_FOR_YT, this is the token-in address. Otherwise, it's the token-out address. - `YT`: The YT address for this limit order. From this address, you can derive the SY and PT addresses. - `maker`: The address of the maker who created the order. - `receiver`: The address of the receiver, who will get the output amount if the order is settled. - `makingAmount`: The amount of input token used to create the order. If the orderType is SY_FOR_PT or SY_FOR_YT, the makingAmount is the SY amount. If PT_FOR_SY or YT_FOR_SY, it refers to the PT or YT amount, respectively. - `lnImpliedRate`: The natural logarithm of the implied rate, formatted as a uint256 by multiplying by 10^18 and then rounding to an integer. You can find the actual implied APY with the formula: $exp(lnImpliedRate/10^{18}) - 1$. - `failSafeRate`: If at the time the limit order is settled, the rate of converting input token to SY or converting from SY to output token (based on the type of order) is lower than this failSafeRate, the order will not be settled. - `permit`: Reserved for future use. ## Method Definitions ### hashOrder This function returns a unique hash for a given order, allowing you to get the order's status later. ```sol function hashOrder(Order memory order) external view returns (bytes32); ``` #### Parameters: - `order`: The order to be hashed. #### Returns: - The unique hash of the order. ### cancelSingle This method cancels a specific limit order. Once canceled, the order cannot be filled or settled. ```sol function cancelSingle(Order calldata order) external; ``` #### Parameters: - `order`: The limit order to be canceled. ### cancelBatch This method allows you to cancel multiple limit orders in a single transaction. ```sol function cancelBatch(Order[] calldata orders) external; ``` #### Parameters: - `orders`: An array of limit orders to be canceled. ### preSignSingle Registers an order on-chain by setting its remaining amount to `makingAmount`. After pre-signing, takers can fill the order without supplying a signature. Only the order's `maker` can pre-sign. The order must have a valid `nonce`, an `expiry` strictly between `block.timestamp` and `YT.expiry()`, and must not already exist on-chain. ```sol function preSignSingle(Order calldata order) external; ``` #### Parameters: - `order`: The limit order to be pre-signed. ### preSignBatch Pre-signs multiple orders in a single transaction. Each order is validated and registered the same as `preSignSingle`. ```sol function preSignBatch(Order[] calldata orders) external; ``` #### Parameters: - `orders`: An array of limit orders to be pre-signed. ### orderStatusesRaw This method retrieves raw remaining and filled amounts for specified orders. ```sol function orderStatusesRaw( bytes32[] memory orderHashes ) external view returns (uint256[] memory remainingsRaw, uint256[] memory filledAmounts); ``` #### Parameters: - `orderHashes`: An array of hashes identifying the orders for which statuses are requested. #### Returns: - `remainingsRaw`: The raw remaining amounts for each order. If `remainingsRaw` is zero, the order is unknown to the contract. To distinguish between unknown orders and fully filled orders, known orders have `remainingsRaw` increased by one. For example, if an order has a real remaining of `100`, its `remainingsRaw` will be `101`. Fully filled or canceled orders will have `remainingsRaw` set to one. - `filledAmounts`: The filled amounts for each order. ### fill The `fill` function allows you to fill one or more limit orders. This is a key operation in a limit order system, where takers fill the orders submitted by makers. It has several parameters and returns multiple values, indicating the outcome of the fill operation. ```sol function fill( FillOrderParams[] memory params, address receiver, uint256 maxTaking, bytes calldata optData, bytes calldata callback ) external returns (uint256 actualMaking, uint256 actualTaking, uint256 totalFee, bytes memory callbackReturn); ``` #### Parameters: - `params`: An array of `FillOrderParams`, specifying the orders to be filled, including order data, signatures, and the amount the taker intends to fill. - `receiver`: The address that receives the output tokens when the orders are filled, typically the taker's address. - `maxTaking`: The maximum amount of tokens that can be taken from the taker. - `optData`: Reserved for future use. Pass empty bytes (`'0x'`). - `callback`: Optional callback data for executing additional logic. For most cases, you can pass empty bytes. See the callback part below for more details. #### Returns: - `actualMaking`: The total amount of tokens received by the taker from the fill operation. - `actualTaking`: The total amount of tokens taken from the taker to complete the fill operation. - `totalFee`: The total fee incurred during the fill operation. - `callbackReturn`: Data returned from the callback function, if used. ## Signature Validation When `fill` is called, the contract validates each `FillOrderParams.signature` against the on-chain status of the order: 1. **Order is already known on-chain** — pre-signed via `preSignSingle` / `preSignBatch`, or partially filled in a previous transaction. Signature validation is **skipped**; the `signature` field can be left empty (`"0x"`). 2. **Order is unknown on-chain** — the contract calls [`SignatureChecker.isValidSignatureNow(maker, orderHash, signature)`](https://docs.openzeppelin.com/contracts/4.x/api/utils#SignatureChecker), which validates in this order: 1. **ECDSA recovery** — recovers the signer from `signature` and checks it matches `maker`. Used for EOA makers. 2. **ERC-1271 fallback** — if ECDSA recovery fails (including when `signature` is empty or the wrong length), the contract calls `IERC1271(maker).isValidSignature(orderHash, signature)` on the maker contract. The order is accepted iff the call returns the magic value `0x1626ba7e`. ### Implications for makers - **EOA makers**: sign the order off-chain via EIP-712; the taker submits that signature with the fill. - **Smart-contract makers** never need to produce an ECDSA signature. Two patterns are supported: - **Whitelist via ERC-1271 (no router state changes)**: implement `isValidSignature(hash, signature)` so that it returns `0x1626ba7e` whenever `hash` is in an internal whitelist — the `signature` argument can be ignored entirely. Takers pass `"0x"`, the ECDSA branch fails immediately, and execution falls through to the maker's `isValidSignature`. Authorizing an order is then just a matter of writing the order hash to the maker contract's whitelist (e.g. via a governance vote, multi-sig approval, or any custom logic) — no call to the Limit Router is needed. - **Pre-sign on the Limit Router**: call `preSignSingle` (or `preSignBatch`) once from the maker. After that the order is "already known", and any subsequent fill works with an empty signature regardless of how the maker implements ERC-1271. ## Callback Mechanism The `fill` function supports a callback mechanism for executing additional logic during the filling process, making it versatile for arbitrage and other custom operations. ### Callback Interface The callback mechanism allows additional logic to be executed during the fill operation. Here's the interface for the callback function: ```sol interface IPLimitRouterCallback { function limitRouterCallback( uint256 actualMaking, uint256 actualTaking, uint256 totalFee, bytes memory data ) external returns (bytes memory); } ``` #### Parameters: - `actualMaking`: The amount of tokens received by the taker's contract from the fill operation. This amount is in SY if the orderType is `SY_FOR_PT` or `SY_FOR_YT`, in PT if it's `PT_FOR_SY`, and in YT if it's `YT_FOR_SY`. - `actualTaking`: The amount of tokens the taker's contract must send to the limit order router to complete the fill. This amount is in SY if `PT_FOR_SY` or `YT_FOR_SY`, in PT if it's `SY_FOR_PT`, and in YT if it's `SY_FOR_YT`. - `totalFee`: The total fee for the operation. - `data`: Additional data provided during the `fill` operation. This corresponds to the callback parameter in the `fill` function. #### Returns: - `bytes`: Optional return data from the callback function. ### Callback Flow and Arbitrage Use Cases The callback mechanism enables complex interactions and arbitrage opportunities. Here's a simplified flow for using the callback feature: 1. **Taker Contract Calls `fill`**: The `fill` function is called with the specified parameters and the callback data. 2. **Tokens Are Transferred**: The `actualMaking` amount is transferred to the receiver (typically the taker's contract). 3. **Callback Function Is Invoked**: The callback function (`limitRouterCallback`) is called with the `actualMaking`, `actualTaking`, and `totalFee` values, along with the callback parameter in `fill`. 4. **Callback Logic Executes**: The taker's contract can perform additional operations during the callback, such as arbitrage with Pendle's AMM or other limit orders. The goal is to use the `actualMaking` amount to generate more value than the `actualTaking` amount, creating a profit. 5. **Send Tokens to Complete**: The taker's contract must send back the `actualTaking` amount to ensure the fill operation completes successfully. 6. **Limit Order Contract Sends Output**: Once the taker's contract sends the required tokens, the limit order contract transfers the agreed output to the limit order receivers. ![Limit Order Callback Flow](/pendle-dev-docs/imgs/Developers/limit_order_callback_flow.png "Limit Order Callback Flow") This flow allows for flexible and creative use of the `fill` function, providing opportunities for arbitrage and custom contract logic. Arbitrageurs can take advantage of this mechanism to execute strategies that generate profit by finding discrepancies in token values or other market inefficiencies. --- # 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)](../../../ProtocolMechanics/YieldTokenization/PT): represents the principal of the underlying yield-bearing token. - [YT (Yield Token)](../../../ProtocolMechanics/YieldTokenization/YT): 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. ![PT and YT Yield Stream](/pendle-dev-docs/imgs/Developers/sy_pt_yt_yield_stream.png) 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`](https://github.com/pendle-finance/pendle-core-v2-public/blob/ba53685767bc16e070136b9dbfe02a5dd6258c61/contracts/core/YieldContracts/PendleYieldToken.sol#L84-L100) ```solidity /** * @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: $$ PY \; minted = SY \; deposited \times current \; PY \; index $$ - 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 deposits `100 SY-sUSDe`, the contract mints `120 PT-sUSDe` and `120 YT-sUSDe`. - Since `100 SY-sUSDe` corresponds to `120 USDe` of underlying value, the user receives `120 PT-sUSDe` (principal exposure) and `120 YT-sUSDe` (pre-expiry yield claim). ### [`redeemPY`](https://github.com/pendle-finance/pendle-core-v2-public/blob/ba53685767bc16e070136b9dbfe02a5dd6258c61/contracts/core/YieldContracts/PendleYieldToken.sol#L120-L134) ```solidity /** * @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: $$ SY \; redeemed = PY \; burned \; \div 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` and `120 YT-sUSDe`, and now the PY index is `1.25`, then `SY_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 index `1.25`, `SY_out = 120 / 1.25 = 96 SY-sUSDe` (again equal to **120 USDe**). The user receives `96 SY-sUSDe`, which can be unwrapped or swapped back to the underlying **120 USDe** principal. ### [`redeemDueInterestAndRewards`](https://github.com/pendle-finance/pendle-core-v2-public/blob/ba53685767bc16e070136b9dbfe02a5dd6258c61/contracts/core/YieldContracts/PendleYieldToken.sol#L150-L186) ```solidity /** * @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](../PendleRouter/ApiReference/MiscFunctions#redeemdueinterestandrewardsv2). **Behavior notes:** * **Interest unit:** Always **SY**. If you want the underlying/base asset, unwrap or swap through the [router](../PendleRouter/ApiReference/MiscFunctions#redeemdueinterestandrewardsv2). * **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`, the call **reverts** with `YCNothingToRedeem`. At least one flag must be `true`. * **Token order:** `rewardsOut[i]` corresponds to `getRewardTokens()[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. ### `mintPYMulti` ```solidity /** * @notice Tokenize SY into PT + YT for multiple receivers in a single transaction. * @dev SY must be transferred to this contract prior to calling. * The sum of `amountSyToMints` must not exceed the floating SY balance. */ function mintPYMulti( address[] calldata receiverPTs, address[] calldata receiverYTs, uint256[] calldata amountSyToMints ) external returns (uint256[] memory amountPYOuts); ``` **Purpose:** Batch version of `mintPY`. Mints PT and YT for multiple receivers in a single transaction, saving gas when distributing to many addresses. **How it works:** - Transfer the total required SY to the YT contract before calling. - Pass parallel arrays: each `(receiverPTs[i], receiverYTs[i])` pair receives `amountPYOuts[i]` PT and YT minted from `amountSyToMints[i]` SY. - The sum of `amountSyToMints` must equal the floating SY balance (total SY transferred in). ### [`pyIndexCurrent`](https://github.com/pendle-finance/pendle-core-v2-public/blob/ba53685767bc16e070136b9dbfe02a5dd6258c61/contracts/core/YieldContracts/PendleYieldToken.sol#L226-L236) ```solidity /** * @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](../../../ProtocolMechanics/NegativeYield). * **Examples:** * **Up move:** Last stored `SY.exchangeRate()` = `1.20`; it rises to `1.25`. Calling `pyIndexCurrent()` updates the PY index to `1.25`. * **Down move:** Last stored index = `1.20`; `SY.exchangeRate()` drops to `1.15`. The PY index **stays at `1.20`**. If a user minted **120 PT** when the index was `1.20`, their claim on SY is `120 / 1.20 = 100 SY`. At maturity, if each SY equals `1.15 USDe`, they redeem `100 × 1.15 = 115 USDe` (less than 120 USDe), reflecting the underlying negative yield. ### `setPostExpiryData` ```solidity /** * @notice Triggers the post-expiry data initialization if the market has expired. * @dev Has no effect if called pre-expiry. */ function setPostExpiryData() external; ``` **Purpose:** Manually triggers the post-expiry settlement. Normally this is called automatically on the first interaction after expiry (via the `updateData` modifier), but this can be called explicitly to ensure the post-expiry index is snapshotted before further redemptions. ### `getPostExpiryData` ```solidity /** * @notice Returns the post-expiry data snapshot. * @dev Reverts if post-expiry data has not been set yet (see `setPostExpiryData()`). */ function getPostExpiryData() external view returns ( uint256 firstPYIndex, uint256 totalSyInterestForTreasury, uint256[] memory firstRewardIndexes, uint256[] memory userRewardOwed ); ``` **Purpose:** Returns the post-expiry data snapshot. Useful for understanding what index was locked in at expiry and verifying post-expiry redemption amounts. **Returns:** - `firstPYIndex`: The PY index snapshotted at expiry. PT redemptions post-expiry use this index. - `totalSyInterestForTreasury`: Accumulated SY interest accrued after expiry (goes to treasury, not users). - `firstRewardIndexes`: Per-reward-token indexes snapshotted at expiry. - `userRewardOwed`: Total unclaimed user reward balances at time of snapshot. --- # StandardizedYield (SY) **Contract:** [`IStandardizedYield`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/interfaces/IStandardizedYield.sol) ## Overview StandardizedYield (SY) is Pendle's **adapter layer** for heterogeneous yield-bearing tokens. Every yield source integrated into Pendle — whether it's Aave aTokens, Lido wstETH, or GMX GLP — is wrapped into an SY contract that exposes a uniform interface for depositing, redeeming, querying exchange rates, and claiming rewards. SY is the foundational token of the Pendle system. All other Pendle primitives build on top of it: - **PT and YT** are minted by splitting SY (see [YieldTokenization](/pendle-v2/Developers/Contracts/YieldTokenization)) - **PendleMarket** trades PT against SY - **PYLpOracle** prices PT, YT, and LP in SY or asset terms Integrators interact with SY when wrapping/unwrapping yield tokens, querying supported deposit/withdrawal tokens, reading exchange rates for pricing, or claiming external protocol rewards. ## Core Concepts ### Exchange Rate Model Every SY has an **exchange rate** that represents how much of the underlying asset one unit of SY is worth: ``` value_in_asset = amountSY × exchangeRate / 1e18 ``` For appreciating yield tokens (e.g. wstETH, sDAI), the exchange rate increases over time as yield accrues. A 2× increase in exchange rate means 1 SY is now worth double the underlying asset. The exchange rate is central to: - **PT/YT pricing** — PT converges to `1 / exchangeRate` at expiry - **Oracle rates** — `PYLpOracle` uses this to convert between SY and asset denominations - **Reward share accounting** — YT reward shares are calculated using the SY exchange rate ### Asset Info and Pricing `assetInfo()` returns metadata about what asset the SY appreciates against: ```solidity (AssetType assetType, address assetAddress, uint8 assetDecimals) = sy.assetInfo(); ``` | Field | Description | |-------|-------------| | `assetType` | `0` = TOKEN, `1` = LIQUIDITY | | `assetAddress` | The reference asset the SY appreciates against. May not exist on the current chain (e.g. stETH address on Ethereum returned for SY-wstETH on Arbitrum). | | `assetDecimals` | Decimal precision of the asset | :::note The asset address is a **best estimation** — it aims to enable rough on-chain valuation. For many SYs, a true 1:1 asset doesn't exist or isn't on the same chain. ::: ## Functions ### `deposit` Wraps `tokenIn` into SY and sends the minted shares to `receiver`. ```solidity function deposit( address receiver, address tokenIn, uint256 amountTokenToDeposit, uint256 minSharesOut ) external payable returns (uint256 amountSharesOut); ``` | Parameter | Type | Description | |-----------|------|-------------| | `receiver` | `address` | Address to receive the minted SY | | `tokenIn` | `address` | Token to deposit (must be in `getTokensIn()`) | | `amountTokenToDeposit` | `uint256` | Amount of `tokenIn` to deposit | | `minSharesOut` | `uint256` | Minimum SY to mint (reverts if output is less) | **Returns:** `amountSharesOut` — the amount of SY minted. :::note Minting quirks SY wraps the underlying yield protocol, so minting/redeeming behavior varies by protocol: - **GLP**: Caps on certain tokens (ETH, USDC) are frequently reached, preventing their use despite being listed in `getTokensIn()` - **ankrBNB**: Minimum mint of 0.1 BNB — smaller amounts revert. Redemption may fail if the quick-withdrawal pool lacks liquidity - **wstETH**: ETH is in `getTokensIn()` (stake ETH → wstETH), but not in `getTokensOut()` (no direct unstake) The most reliable deposit token is the protocol's yield token itself, but hardcoding this is not recommended — always check `getTokensIn()`. ::: ### `redeem` Burns SY shares and sends the underlying `tokenOut` to `receiver`. ```solidity function redeem( address receiver, uint256 amountSharesToRedeem, address tokenOut, uint256 minTokenOut, bool burnFromInternalBalance ) external returns (uint256 amountTokenOut); ``` | Parameter | Type | Description | |-----------|------|-------------| | `receiver` | `address` | Address to receive the redeemed tokens | | `amountSharesToRedeem` | `uint256` | Amount of SY to burn | | `tokenOut` | `address` | Token to receive (must be in `getTokensOut()`) | | `minTokenOut` | `uint256` | Minimum output amount (reverts if less) | | `burnFromInternalBalance` | `bool` | If `true`, burns from SY transferred to the contract; if `false`, burns from `msg.sender`'s balance | **Returns:** `amountTokenOut` — the amount of `tokenOut` received. ### `previewDeposit` Estimates the amount of SY that would be minted for a given deposit. Best-effort approximation — **not audited for on-chain use**. ```solidity function previewDeposit( address tokenIn, uint256 amountTokenToDeposit ) external view returns (uint256 amountSharesOut); ``` | Parameter | Type | Description | |-----------|------|-------------| | `tokenIn` | `address` | Token to deposit | | `amountTokenToDeposit` | `uint256` | Amount to deposit | ### `previewRedeem` Estimates the amount of `tokenOut` received for burning a given amount of SY. Best-effort approximation — **not audited for on-chain use**. ```solidity function previewRedeem( address tokenOut, uint256 amountSharesToRedeem ) external view returns (uint256 amountTokenOut); ``` | Parameter | Type | Description | |-----------|------|-------------| | `tokenOut` | `address` | Token to receive | | `amountSharesToRedeem` | `uint256` | Amount of SY to burn | :::warning Preview functions — off-chain only The underlying protocols often lack explicit preview functions. SY's `previewDeposit` and `previewRedeem` are best-effort approximations by the Pendle team. While verified through testing, they are **not audited** and should not be relied upon on-chain. Use them via `eth_call` / `staticCall` for off-chain estimation only. ::: ### `getTokensIn` Returns all tokens that can be used to mint this SY. ```solidity function getTokensIn() external view returns (address[] memory res); ``` ### `getTokensOut` Returns all tokens that can be received when redeeming this SY. ```solidity function getTokensOut() external view returns (address[] memory res); ``` ### `exchangeRate` Returns the current exchange rate: how much asset 1 SY is worth (scaled by `1e18`). See [Unit and Decimals](/pendle-v2/Developers/Contracts/UnitAndDecimals) for decimal handling details. ```solidity function exchangeRate() external view returns (uint256 res); ``` ### `assetInfo` Returns the asset type, address, and decimals that the SY appreciates against. ```solidity function assetInfo() external view returns (AssetType assetType, address assetAddress, uint8 assetDecimals); ``` See [Core Concepts — Asset Info and Pricing](#asset-info-and-pricing) above for field descriptions. ### `claimRewards` Claims all accrued external protocol rewards for `user` and transfers them. ```solidity function claimRewards(address user) external returns (uint256[] memory rewardAmounts); ``` ### `accruedRewards` Returns the currently credited reward amounts for `user` **without** triggering a state update. To get the latest results, simulate `claimRewards(user)` via `eth_call` or `staticCall`. ```solidity function accruedRewards(address user) external view returns (uint256[] memory rewardAmounts); ``` :::note `accruedRewards` only reflects rewards credited as of the last on-chain interaction. It does **not** include rewards pending since the last block update. For accurate off-chain reads, simulate `claimRewards` instead. ::: ### Extended: `pricingInfo()` ```solidity function pricingInfo() external view returns (address refToken, bool refStrictlyEqual); ``` This optional function (defined in `IStandardizedYieldExtended`) describes the recommended pricing method for this SY: | Return Value | Description | |--------------|-------------| | `refToken` | The reference token to use when pricing this SY | | `refStrictlyEqual` | Whether `1 natural unit of SY == 1 natural unit of refToken` | **How to use for pricing PT & YT:** - `refStrictlyEqual = true`: use `PYLpOracle.get{Token}ToSyRate()` and multiply by `refToken`'s price. _Note: SY and refToken may have different decimals — see [Unit and Decimals](/pendle-v2/Developers/Contracts/UnitAndDecimals)._ - `refStrictlyEqual = false`: use `PYLpOracle.get{Token}ToAssetRate()` and multiply by `refToken`'s price. Not all SYs implement `pricingInfo()`. Two common cases where it is overridden: **Rebasing yield tokens** — Rebasing tokens (e.g. stETH, stHYPE) adjust holder balances on every rebase, so the SY's `exchangeRate` does not track 1:1 with the yield token in raw-unit terms. In this case `refStrictlyEqual = false`. ```solidity // PendleStakedHYPESY — yieldToken = stHYPE (rebasing) function pricingInfo() external view override returns (address refToken, bool refStrictlyEqual) { return (yieldToken, false); } ``` **Scaled18 SY** — For assets with fewer than 18 decimals (e.g. LBTC at 8 decimals), Pendle deploys a decimal-wrapping contract and a corresponding `Scaled18` SY. Because 1 natural unit of the SY equals 1 natural unit of the original token, `refStrictlyEqual = true`. ```solidity // PendleLBTCBaseSYScaled18 address public constant LBTC = 0xecAc9C5F704e954931349Da37F60E39f515c11c1; function pricingInfo() external pure returns (address refToken, bool refStrictlyEqual) { return (LBTC, true); // original LBTC (8 decimals), not the scaled18 wrapper } ``` :::tip If you're building a money market or lending protocol, check whether the SY implements `pricingInfo()` before choosing your oracle path. Contact the Pendle team for discussion when integrating a new token type. ::: ## Pricing Guide When pricing PT/YT/LP positions, the choice between `getPtToSy` and `getPtToAsset` depends on the SY type. ### Standard SYs Most SYs are a 1:1 wrap of the yield token. **Value PT/YT/LP by yield token** (`getPtToSy` / `getLpToSy`). If not possible, value by asset but account for the withdrawal risk from yield token to asset (`getPtToAsset` / `getLpToAsset`). | Market | Recommended way to get price | Unit of price | yieldToken of SY (1-1 wrap) | Note | Asset of SY | | --------------- | ---------------------------- | ------------- | --------------------------- | -------------------------------- | -------------------------------- | | LBTC | getPtToSy | LBTC | LBTC | - | BTC staked on Lombard | | sUSDe | getPtToSy | sUSDe | sUSDe | - | USDe | | USDe | getPtToSy | USDe | USDe | - | USDe | | eBTC | getPtToSy | eBTC | eBTC | - | eBTC (constant exchange rate) | | USD0++ | getPtToSy | USD0++ | USD0++ | - | USD0++ (constant exchange rate) | | sENA | getPtToSy | sENA | sENA | - | ENA staked on Ethena | | SolvBTC.BBN | getPtToSy | SolvBTC.BBN | SolvBTC.BBN | - | BTC staked on Solv | | PumpBTC | getPtToSy | PumpBTC | PumpBTC | - | BTC staked on Pump | | weETH | getPtToSy | weETH | weETH | - | eETH | | weETHs | getPtToSy | weETHs | weETHs | limited liquidity to market sell | weETHs (constant exchange rate) | | weETHk | getPtToSy | weETHk | weETHk | limited liquidity to market sell | weETHk (constant exchange rate) | | weETH (Karak) | getPtToSy | weETH (Karak) | weETHk (Karak) | can't market sell | eETH | | pufETH | getPtToSy | pufETH | pufETH | - | ETH staked on Puffer | | pzETH | getPtToSy | pzETH | pzETH | limited liquidity to market sell | pzETH (constant exchange rate) | | wstETH | getPtToSy | wstETH | wstETH | - | stETH | | GLP | getPtToSy | GLP | GLP | - | GLP | | MUXLP | getPtToSy | MUXLP | MUXLP | can't market sell | MUXLP | | HLP | getPtToSy | HLP | HLP | can't market sell | HLP | | ezETH | getPtToSy | ezETH | ezETH | - | ETH staked on Renzo | | rETH | getPtToSy | rETH | rETH | - | ETH staked on Rocket | | ezETH (Zircuit) | getPtToSy | ezETH | ezETH | - | ETH staked on Renzo then Zircuit | | uniETH | getPtToSy | uniETH | uniETH | - | ETH staked on Bedrock | | rswETH | getPtToSy | rswETH | rswETH | - | ETH staked on Swell | | rswETH Swell L2 | getPtToSy | rswETH | rswETH | - | ETH staked on Swell | | swETH | getPtToSy | swETH | swETH | - | ETH staked on Swell | | ETHx | getPtToSy | ETHx | ETHx | - | ETH staked on Stadler | | rsETH | getPtToSy | rsETH | rsETH | - | ETH staked on Kelp | | sDAI | **getPtToAsset** | DAI | sDAI | - | DAI | | crvUSD Silo | **getPtToAsset** | crvUSD | scrvUSD | - | crvUSD | | gDAI | **getPtToAsset** | DAI | gDAI | can't market sell | DAI staked in Gains | **Notes on Assets of SYs:** - All Liquid Staking Tokens (LRTs) except for weETH have assets listed as "ETH staked in xyz." This is different from ETH, as it takes 7 days or more to withdraw, and these staked ETH are subject to slashing if it occurs. As a result, LRT always trades at a slight depeg compared to the amount of ETH withdrawable from it. Hence, it's not safe to value these markets directly in ETH (which would not account for this depeg). Always value these in the original LRT form. - sDAI, scrvUSD, and gDAI tokens are not tradable, so they must be valued directly in their underlying asset. Similar to LRT, valuing them directly in the underlying asset will skip the protocol's risk of not being able to withdraw from yieldToken to Asset. Please keep that in mind! - All in all, it's safe to value tokens in yieldToken, whereas valuing them in assets carries additional conversion risk that you should be aware of. ### Non-standard SYs Other SYs that are not 1-1 wrap of yieldToken: | Market | Recommended way to get price | Unit of price | yieldToken of SY (NOT 1-1 Wrap) | Asset of SY | | ------- | ---------------------------- | ------------- | ------------------------------- | ----------- | | aUSDT | getPtToAsset | USDT | - | USDT | | aUSDC | getPtToAsset | USDC | - | USDC | | ePENDLE | getPtToAsset | ePENDLE | - | ePENDLE | | mPENDLE | getPtToAsset | mPENDLE | - | mPENDLE | For aUSDT and aUSDC, similar considerations apply as for sDAI, scrvUSD, and gDAI. Value them directly in their underlying asset. ## FAQ ### What does `exchangeRate()` represent? It returns the amount of the underlying asset that 1 SY is worth, scaled by `1e18`. For example, if `exchangeRate()` returns `1.05e18`, then 1 SY = 1.05 units of the asset. The rate increases over time as yield accrues. ### Why can't I always deposit with any token listed in `getTokensIn()`? `getTokensIn()` lists tokens that the SY *can* accept, but the underlying protocol may have dynamic constraints — capacity caps (GLP), minimum amounts (ankrBNB), or liquidity limits — that cause deposits to revert at any given time. Always handle reverts gracefully. ### How do I price PT/YT if my SY is non-standard? For non-standard SYs (not a 1:1 wrap of the yield token), use `getPtToAsset` / `getLpToAsset` instead of the SY-denominated variants. Check the [Pricing Guide](#pricing-guide) tables above. If the SY implements `pricingInfo()`, follow the `refStrictlyEqual` flag to choose the correct oracle path. ### Should I use `getPtToSy` or `getPtToAsset`? Prefer `getPtToSy` for maximum trustlessness — the PT→SY rate is natively guaranteed by the Pendle AMM. Use `getPtToAsset` only when your protocol explicitly needs asset-denominated pricing and you understand the additional SY→asset conversion risk (exchange rate dependency, potential depegs). See [PYLpOracle](/pendle-v2/Developers/Contracts/Oracle/PYLpOracle) for details. ## Further Reading - [CommonSY](/pendle-v2/Developers/Contracts/StandardizedYield/CommonSY) — deployed SY addresses per chain - [DecimalsWrapper](/pendle-v2/Developers/Contracts/StandardizedYield/DecimalsWrapper) — how Pendle handles tokens with non-18 decimals - [Unit and Decimals](/pendle-v2/Developers/Contracts/UnitAndDecimals) — decimal handling across PT, YT, SY, and asset - [YieldTokenization](/pendle-v2/Developers/Contracts/YieldTokenization) — how SY is split into PT and YT - [PYLpOracle](/pendle-v2/Developers/Contracts/Oracle/PYLpOracle) — TWAP oracle for pricing PT, YT, and LP - [Rewards](/pendle-v2/Developers/Contracts/LiquidityMining/Rewards) — full reward accounting model for SY, YT, and LP holders --- # Pendle Market Smart Contracts **Contract:** [`PendleMarketV7`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol) ## Introduction Pendle Market is a specialized Automated Market Maker (AMM) pool designed for trading yields. Each market enables efficient trading between: - **Principal Tokens (PT)** - Tokens that represent the principal component of yield-bearing assets - **Standardized Yield (SY)** - Wrapped yield-bearing assets that standardize yield mechanics ## Pendle AMM Overview Traditional AMMs like Uniswap use constant product formulas (`x × y = k`) that do not account for the unique properties of fixed-yield assets. Pendle Markets implement a sophisticated time-aware AMM based on Notional Finance's AMM model that: - **Recognizes time decay**: PT prices naturally converge to 1 as they approach expiry - **Optimizes for yield trading**: Pricing curves are tailored for interest rate movements - **Maximizes capital efficiency**: Concentrates liquidity around expected yield ranges - **Minimizes Impermanent Loss (IL)**: Pendle's AMM accounts for PT's natural price appreciation by shifting the AMM curve to push PT price towards its underlying value as time passes, mitigating time-dependent IL (No IL at maturity). > **Deep Dive**: For complete mathematical analysis and comparisons, see the [Pendle V2 AMM Whitepaper](https://github.com/pendle-finance/pendle-v2-resources/blob/main/whitepapers/V2_AMM.pdf) ## Market Parameters - **Expiry**: The timestamp when PT tokens can be redeemed 1:1 for underlying assets - drives PT price convergence to 1 as expiry approaches. - **Scalar Root**: Controls the trade-off between capital efficiency and tradeable interest rate range - higher values create tighter, more efficient markets. - **Initial Anchor**: Sets the interest rate around which trading is most capital efficient at market launch - centers liquidity around expected yield levels. - **Fee Rate Root**: Dynamic fees based on interest rate impact rather than token amounts - larger market movements incur proportionally higher fees. ## Core Logic ### [`mint`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L105-L136) Adds liquidity using PT & SY; mints LP shares proportional to the amounts used. ```solidity /** * @notice PendleMarket allows users to provide in PT & SY in exchange for LPs, which * will grant LP holders more exchange fee over time * @dev will mint as much LP as possible such that the corresponding SY and PT used do * not exceed `netSyDesired` and `netPtDesired`, respectively * @dev PT and SY should be transferred to this contract prior to calling * @dev will revert if PT is expired */ function mint( address receiver, uint256 netSyDesired, uint256 netPtDesired ) external returns (uint256 netLpOut, uint256 netSyUsed, uint256 netPtUsed); ``` **Note:** - Caller must transfer PT and SY to the Market before calling. The function mints as many LPs as possible without exceeding `netSyDesired`/`netPtDesired`. ### [`burn`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L142-L159) Removes liquidity by burning LP shares for pro-rata SY and PT. ```solidity /** * @notice LP Holders can burn their LP to receive back SY & PT proportionally * to their share of the market */ function burn( address receiverSy, address receiverPt, uint256 netLpToBurn ) external returns (uint256 netSyOut, uint256 netPtOut); ``` **Note:** caller must transfer LP to the Market before calling. ### [`swapExactPtForSy`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L172-L202) Swaps an exact amount of PT for SY. ```solidity /** * @notice Pendle Market allows swaps between PT & SY it is holding. This function * aims to swap an exact amount of PT to SY. * @dev steps working of this contract - The outcome amount of SY will be precomputed by MarketMathLib - Release the calculated amount of SY to receiver - Callback to msg.sender if data.length > 0 - Ensure exactPtIn amount of PT has been transferred to this address * @dev will revert if PT is expired * @param data bytes data to be sent in the callback (if any) */ function swapExactPtForSy( address receiver, uint256 exactPtIn, bytes calldata data ) external nonReentrant notExpired returns (uint256 netSyOut, uint256 netSyFee); ``` **Note:** caller must transfer PT to the Market first; the Market then sends out the computed SY and (optionally) invokes a callback if data is non-empty. For a deeper understanding of the math behind it, refer to the [`Pendle V2 AMM Whitepaper`](https://github.com/pendle-finance/pendle-v2-resources/blob/main/whitepapers/V2_AMM.pdf) and [`MarketMathCore Contract`](https://github.com/pendle-finance/pendle-core-v2-public/blob/ba53685767bc16e070136b9dbfe02a5dd6258c61/contracts/core/Market/MarketMathCore.sol#L193-L217). ### [`swapSyForExactPt`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L214-L246) Swaps SY for an exact amount of PT. ```solidity /** * @notice Pendle Market allows swaps between PT & SY it is holding. This function * aims to swap SY for an exact amount of PT. * @dev steps working of this function - The exact outcome amount of PT will be transferred to receiver - Callback to msg.sender if data.length > 0 - Ensure the calculated required amount of SY is transferred to this address * @dev will revert if PT is expired * @param data bytes data to be sent in the callback (if any) */ function swapSyForExactPt( address receiver, uint256 exactPtOut, bytes calldata data ) external returns (uint256 netSyIn, uint256 netSyFee); ``` **Note:** the Market sends out exactPtOut to receiver, optionally callbacks msg.sender, and then enforces that the required SY has been provided (typically via `transfer` in the callback/Router). For a deeper understanding of the math behind it, refer to the [`Pendle V2 AMM Whitepaper`](https://github.com/pendle-finance/pendle-v2-resources/blob/main/whitepapers/V2_AMM.pdf) and [`MarketMathCore Contract`](https://github.com/pendle-finance/pendle-core-v2-public/blob/ba53685767bc16e070136b9dbfe02a5dd6258c61/contracts/core/Market/MarketMathCore.sol#L193-L217). ### [`redeemRewards`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L261-L263) Claims accumulated swap fees/protocol rewards for user (in the order of `getRewardTokens()`). ```solidity /** * @notice redeems the user's reward * @return amount of reward token redeemed, in the same order as `getRewardTokens()` */ function redeemRewards(address user) external returns (uint256[] memory); ``` ### [`increaseObservationsCardinalityNext`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L284-L291) Grows the oracle ring buffer to support longer TWAP windows. ```solidity function increaseObservationsCardinalityNext(uint16 cardinalityNext) external; ``` **Note:** The ring buffer starts with a cardinality of 1. To query TWAP windows longer than the current buffer covers, call this function first to pre-expand the buffer. Anyone can call this; it only increases (never decreases) the cardinality. ### [`skim`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L249-L255) Forces internal reserves to match actual token balances. ```solidity /// @notice forces balances to match reserves function skim() external; ``` **Note:** If tokens are accidentally sent directly to the market contract (not via a swap/mint), they accumulate as excess. `skim` transfers this excess to the treasury. This is an emergency recovery function; normal integrations do not need to call it. ### [`readState`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L300-L315) Returns the current market state and pricing metadata. ```solidity struct MarketState { int256 totalPt; // total PT in the market int256 totalSy; // total SY in the market int256 totalLp; // total LP minted address treasury; // treasury address to receive protocol fees int256 scalarRoot; // variable to control the capital efficiency of the market uint256 expiry; // timestamp when market expires /// fee data /// uint256 lnFeeRateRoot; // fee rate in ln uint256 reserveFeePercent; // reserve fee in base 100 /// last trade data /// uint256 lastLnImpliedRate; // last ln(implied rate) observed } /** * @notice read the state of the market from storage into memory for gas-efficient manipulation */ function readState(address router) external view returns (MarketState memory market); ``` **Note:** - `lnFeeRateRoot` and `lastLnImpliedRate` are stored/returned as natural-log values in fixed-point form. - The router parameter allows the function to reflect router-specific settings (e.g., fee discounts if applicable). ### [`readTokens`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L348-L352) Returns the three token addresses of the market. ```solidity function readTokens() external view returns ( IStandardizedYield SY, IPPrincipalToken PT, IPYieldToken YT ); ``` **Note:** Use this as the canonical way to discover the SY, PT, and YT addresses associated with any market address. ### [`isExpired`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L354-L356) Returns whether the market has passed its expiry timestamp. ```solidity function isExpired() external view returns (bool); ``` **Note:** Swaps and liquidity minting revert after expiry. LP burning and reward claiming remain available post-expiry. ### [`getRewardTokens`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L266-L268) Lists all reward tokens distributed to LP holders. ```solidity function getRewardTokens() external view returns (address[] memory); ``` **Note:** The list includes both SY external rewards (e.g., AAVE incentives passed through the underlying SY) and PENDLE incentives distributed via the gauge controller. Always call this first to map indices before calling `redeemRewards`. ### [`observe`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L274-L282) Oracle API — reads cumulative implied rates at past time deltas. ```solidity function observe(uint32[] memory secondsAgos) external view returns (uint216[] memory lnImpliedRateCumulative); ``` **Note:** Each element of `secondsAgos` is a lookback in seconds from the current block (e.g., `[300, 0]` = 5 minutes ago and now). Returns the cumulative `lnImpliedRate` at each point, from which a TWAP can be derived. See the [Oracle section](#oracle) below and [About the PT Oracle](../../Oracles/OracleOverview.md#about-the-pt-oracle) for details. ### [`getNonOverrideLnFeeRateRoot`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/core/Market/PendleMarketV7.sol#L340-L342) Returns the base fee rate before any router-specific override. ```solidity function getNonOverrideLnFeeRateRoot() external view returns (uint80); ``` **Note:** `readState(router)` may return a lower `lnFeeRateRoot` if an override is set for that router. This function always returns the original fee set at market creation. ## Fee & Rewards ### `reserveFeePercent` (Treasury fee) A percentage of each swap's fee is sent to Pendle's treasury. The remainder goes to LP providers. `reserveFeePercent` is stored in `MarketState` and is configurable by the factory owner. It is expressed as a value in base 100 (e.g., `80` = 80% of the fee goes to the treasury, 20% to LPs). When `readState(router)` is called, the current `reserveFeePercent` is fetched from the factory's `getMarketConfig` and included in the returned `MarketState`. ### Fee Override Mechanism (Router discount) `PendleMarketFactoryV7Upg.setOverriddenFee(router, market, newFee)` sets a lower fee for a specific router on a specific market. When `readState(router)` is called with that router address, the overridden fee is returned in `MarketState.lnFeeRateRoot` instead of the base fee. This is how the Pendle Router earns a fee discount compared to direct market interaction. - The overridden fee must be strictly lower than `getNonOverrideLnFeeRateRoot()`. - Setting `newFee = 0` is allowed (zero fee for that router). - Only the factory owner can set overrides. ### Reward Mechanism (Gauge) The market inherits from `PendleGauge`. LP holders accumulate two types of rewards passively: - **SY external rewards** — incentives from the underlying protocol (e.g., AAVE rewards) that are passed through the SY contract. - **PENDLE incentives** — distributed via the `gaugeController` based on gauge weights. Both types accumulate in proportion to the LP balance held. Call `redeemRewards(user)` to claim all pending rewards. `getRewardTokens()` returns the full ordered list of reward token addresses. ## Integration Example :::danger Example code only 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**. ::: ### Discover Market Tokens ```solidity // Discover SY, PT, YT from a market address (IStandardizedYield sy, IPPrincipalToken pt, IPYieldToken yt) = market.readTokens(); ``` ### Basic Swap ```solidity // Transfer PT to market first pt.transfer(address(market), ptAmount); // Execute swap (exact PT in → SY out) (uint256 syOut,) = market.swapExactPtForSy( msg.sender, ptAmount, "" ); ``` ### Basic Mint ```solidity // Transfer tokens first sy.transfer(address(market), syAmount); pt.transfer(address(market), ptAmount); // Mint LP tokens (uint256 lpOut,,) = market.mint( msg.sender, syAmount, ptAmount ); ``` ### Claiming LP Rewards ```solidity // Discover what reward tokens this market distributes address[] memory rewardTokens = market.getRewardTokens(); // Claim all accumulated rewards for msg.sender uint256[] memory rewardAmounts = market.redeemRewards(msg.sender); // rewardAmounts[i] corresponds to rewardTokens[i] for (uint256 i = 0; i < rewardTokens.length; i++) { // handle rewardTokens[i] with amount rewardAmounts[i] } ``` ## Flash Swap Similar to Uniswap V2, **all Pendle swaps are actually flash swaps**. The Market sends output tokens to the receiver first, then enforces that sufficient input tokens have been received by the end of the transaction. This facilitates advanced use cases like arbitrage, liquidation, and in Pendle specifically, YT trading. ### How It Works 1. **Market sends output tokens** to receiver immediately 2. **Callback executed** (if data provided) - this is where you implement your logic. You can use the received tokens for arbitrage, liquidation, or other strategies, but you must ensure you transfer the required input tokens back to the Market before the callback ends. 3. **Market checks input tokens** - verifies required tokens were transferred during the transaction Because Ethereum transactions are atomic, the entire swap reverts if the Market doesn't receive enough input tokens. ### Flash Swap Example :::danger Example code only 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**. ::: ```solidity contract FlashSwapExample is IPMarketSwapCallback { IPendleMarket public market; function flashSwap(uint256 ptAmount) external { // Market will send SY first, then callback to this contract market.swapExactPtForSy( address(this), ptAmount, abi.encode(msg.sender) // data for callback ); } // Callback - Market has already sent SY to this contract function swapCallback( int256 ptToAccount, // negative = we owe PT to market int256 syToAccount, // positive = we received SY from market bytes calldata data ) external { require(msg.sender == address(market)); // Your custom logic here (arbitrage, liquidation, etc.) // ... // Must transfer required PT to market before callback ends uint256 ptOwed = uint256(-ptToAccount); IERC20(market.PT()).transfer(address(market), ptOwed); } } ``` ### Interface ```solidity interface IPMarketSwapCallback { function swapCallback(int256 ptToAccount, int256 syToAccount, bytes calldata data) external; } ``` ## Oracle Pendle Markets provide oracle functionality for PT pricing and implied yield rates, adapted from Uniswap V3's time-weighted average price (TWAP) oracle. ### How It Works The oracle stores **implied rate observations** over time, which are then used to calculate manipulation-resistant PT prices and yields. **Key Formula:** $$ \text{lnImpliedRateCumulative}_i = \text{lnImpliedRateCumulative}_{i-1} + \text{lnImpliedRate} \times \Delta t $$ where **lnImpliedRate** is the natural logarithm of the implied interest rate at the current market state, and **Δt** is the time elapsed. From these cumulative values, you can compute the **geometric mean price of PT** over a given interval (see [About the PT Oracle](../../Oracles/OracleOverview.md#about-the-pt-oracle) for details). ### Integration Guide See [How to Integrate PT and LP Oracles](../../Oracles/HowToIntegratePtAndLpOracle) for implementation details. ## FAQ ### Why is there no swapExactSy function? Unlike standard AMMs, Pendle's AMM only allows swapping exact PT in/out. Therefore, functions like `swapExactSyForPt` and `swapPtForExactSy` should generally be avoided. If necessary, use PendleRouter's `swapExactSyForPt` with approx parameters. Refer to the [PendleRouter documentation](../PendleRouter/ApiReference/PtFunctions#swapexactsyforpt) for details. ### How can I trade YT tokens when the Market only contains PT and SY? YT tokens can be traded via [flash swaps](../../../ProtocolMechanics/LiquidityEngines/AMM#flash-swaps). Use the PendleRouter's `swapExactTokenForYt` or `swapExactYtForToken` functions, which handle the necessary flash swap logic and token transfers. Refer to the [PendleRouter documentation](../PendleRouter/ApiReference/YtFunctions#swapexacttokenforyt) for details. ### Why can't I swap PT after expiry? At expiry, PT can be redeemed for the underlying asset. Market-making no longer makes economic sense at this point and would enable circular arbitrage. To redeem PT post-expiry, use the [Router](../PendleRouter/ApiReference/LiquidityFunctions#removeliquiditysingletoken). ### Should I use the Router or interact with the Market directly? * Use the Router for: * Any operations involving YT. * Swaps using tokens other than the underlying (since additional swaps are required). * Fee discounts, slippage protection, etc. * Interact directly with the Market for: * Simple PT ↔ SY swaps. * Flash swaps. * Highly gas-optimized integrations where you manage token transfers and slippage manually. --- # Pendle Router Overview ## Quick Start - To see examples of how to use Pendle Router, check out the [Integration Guide](#integration-guide). ## Overview PendleRouter is a contract that aggregates callers' actions with various SYs, PTs, YTs, and markets. It does not have any special permissions or whitelists on any contracts it interacts with. However, it is recommended that third parties use the Router to enjoy the fee discount while trading with the pool, as opposed to directly interacting with the pools themselves. The lnFeeRateRoot in the pool will be reduced when the Router is used to trade. The Router has had three historical versions, with **RouterV4** very likely being the final version: | Version | Deployed | Address | Notes | |---------|----------|---------|-------| | RouterV1 | Nov 23, 2022 | `0x41FAD93F225b5C1C95f2445A5d7fcB85bA46713f` | Initial release | | RouterV2 | Feb 21, 2023 | `0x0000000001e4ef00d069e71d6ba041b0a16f7ea0` | 15–20% gas optimization | | RouterV3 | Dec 18, 2023 | `0x00000000005BBB0EF59571E58418F9a4357b68A0` | Added limit order support | | **RouterV4** | Apr 29, 2024 | `0x888888888889758F76e7103c6CbF23ABbF58F946` | **Upgradable** — new features and optimizations are added gradually without requiring partners to migrate | Since PendleRouter is a proxy to multiple implementations (using the [EIP-2535 Diamond Standard](https://eips.ethereum.org/EIPS/eip-2535)), the caller can call the desired functions, and the Router will resolve to the correct implementation. Please refer to the [list of callable functions](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/interfaces/IPAllActionV3.sol). :::caution Block Explorer Limitation Because the Router uses the Diamond proxy pattern, block explorers like Etherscan **cannot correctly display all available functions**. They typically only show the ABI of the base proxy contract, not the aggregated functions from all its facets (implementations). To interact with the full range of Router functions, use the complete combined ABI from [`IPAllActionV3.sol`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/interfaces/IPAllActionV3.sol). ::: **Important notes on older Router versions:** - **RouterV2** may have issues with newer markets where a necessary approval step is not being called. On some networks like Mantle, RouterV2 is not supported and will revert. - **RouterV3** and earlier versions remain functional but lack the latest optimizations. Upgrading to **RouterV4** is highly recommended for all integrations. For a comprehensive understanding of the Pendle ecosystem, including key concepts and terminology, please refer to the [High Level Architecture](../../HighLevelArchitecture). ## Integration Guide This section covers how to interact with the Pendle Router to buy and sell Principal Tokens (PTs) and Yield Tokens (YTs) using two methods: 1. **Pendle's Hosted SDK**: Recommended for optimized liquidity, gas efficiency, and broader token support. Hosted SDK is publicly available at [https://api-v2.pendle.finance/core/docs](https://api-v2.pendle.finance/core/docs). More about it at [Pendle Hosted SDK](../../Backend/HostedSdk.mdx). 2. **Direct Interaction with the Pendle Router**: Offers direct contract interaction, all data is generated on-chain. We highly recommend using Pendle's SDK to generate calldata for several reasons: 1. **Gas Efficiency**: Currently, Pendle's AMM only supports the built-in `swapExactPtForSy` and `swapSyForExactPt`. To execute a `swapExactTokenForPt` (which is essentially the same as `swapExactSyForPt`), the router will conduct a binary search to determine the amount of PT to swap. While the binary search can be done entirely on-chain, limiting the search range off-chain will result in significantly less gas consumption for this function. The SDK leverages off-chain data to optimize gas usage, potentially reducing gas costs significantly. 2. **Accurate Price Impacts**: The SDK provides precise calculations for swaps, ensuring better price impacts for users. 3. **Limit Order System**: The limit order system of Pendle exists solely off-chain. Including these limit orders in on-chain swaps can significantly improve the price impact for users, particularly during large-size swaps. 4. **Ease of Integration**: By using the SDK, developers can seamlessly integrate Pendle's functionality into their applications, leveraging the full power of the swap aggregator, limit order system, and off-chain data preparation. 5. **Convenient zapping with any ERC20 token**: Liquidity is currently fragmented across a large number of pools across various DEXes. Integrating only Uniswap or Balancer has proven to be insufficient. As a result, PendleRouter has natively integrated [KyberSwap](https://kyberswap.com/) to swap from any ERC20 token to another. For KyberSwap to work, the routing algorithm must be called off-chain and then pass the routing results to the Router to execute. We'll explore both methods, including example code, for each approach. ### Method 1: Using the Pendle Hosted SDK (Recommended) The Hosted SDK handles off-chain optimization, aggregator routing, and limit order integration automatically. See the [Hosted SDK Documentation](../../Backend/HostedSdk.mdx) for full details, examples, and supported operations. ### Method 2: Direct Interaction with the Pendle Router For fully on-chain integrations without the SDK, see the [Contract Integration Guide](./ContractIntegrationGuide) for step-by-step Solidity examples covering PT/YT trading, liquidity management, and minting/redeeming. Key struct types used by the Router (`TokenInput`, `TokenOutput`, `ApproxParams`, `LimitOrderData`) are documented in the [Types and Utility Functions](./ApiReference/Types) reference, along with helper functions for on-chain parameter generation. ### Available Router Functions - **Trading**: `swapExactTokenForPt`, `swapExactPtForToken`, `swapExactTokenForYt`, `swapExactYtForToken` - **Liquidity**: `addLiquiditySingleToken`, `removeLiquiditySingleToken` - **Minting/Redeeming**: `mintPyFromToken`, `redeemPyToToken` - **Rewards**: `redeemDueInterestAndRewards` For the full list, see [`IPAllActionV3.sol`](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/interfaces/IPAllActionV3.sol). Example code: [RouterSample.sol](https://github.com/pendle-finance/pendle-examples-public/blob/main/test/RouterSample.sol). --- # Pendle Router Contract Integration Guide ## Overview This guide shows how to interact with the Pendle Router for common on-chain actions—buying/selling PT or YT, adding/removing liquidity, minting/redeeming PT and YT, and claiming rewards. It’s intended for developers who want to integrate Pendle fully on-chain without using the Pendle SDK. For most use cases, we recommend the Pendle SDK for a better developer and end-user experience. The SDK supports zap-in/zap-out from any token, limit-order filling, and off-chain quoting, among other features. See the [Pendle SDK documentation](../../Backend/HostedSdk.mdx) for details. The full tutorial source code is available in [RouterSample.sol](https://github.com/pendle-finance/pendle-examples-public/blob/main/test/RouterSample.sol). The examples target the wstETH market, but you can adapt them to other markets by changing the market and underlying asset addresses. ## Quick Navigation ### Helper functions - [Helper Functions](#helper-functions) ### Trading Operations - [PT Trading](#pt-trading) - [YT Trading](#yt-trading) ### Liquidity Operations - [Add Liquidity (Zap In) & Remove Liquidity (Zap Out)](#liquidity-management) - [Add Liquidity (Keep YT)](#add-liquidity-keep-yt) ### Other Operations - [Mint/Redeem SY](#mint-sy-from-token) - [Mint/Redeem PT & YT](#mint-pt--yt-from-token) ## YT Trading ### Buy YT with Token Buying YT with the underlying token. This example uses wstETH to buy YT-wstETH. ```solidity address market = 0xD0354D4e7bCf345fB117cabe41aCaDb724eccCa2; address wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; address ytReceiver = address(this); IERC20(wstETH).approve(address(router), tokenAmount); (uint256 netYtOut, , ) = router.swapExactTokenForYt( ytReceiver, // receiver address(market), // market address 0, // minYtOut defaultApprox, // approximation params createTokenInputStruct(wstETH, tokenAmount), // token input emptyLimit // limit order data ); ``` ### Sell YT for Token Selling YT for the underlying token. This example uses YT-wstETH to sell for wstETH. ```solidity address market = 0xD0354D4e7bCf345fB117cabe41aCaDb724eccCa2; address wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; (,, address YT) = IPMarket(market).readTokens(); address tokenReceiver = address(this); IERC20(YT).approve(address(router), ytAmount); (uint256 netTokenOut, , ) = router.swapExactYtForToken( tokenReceiver, // receiver address(market), // market address ytAmount, // exact YT amount to sell createTokenOutputStruct(wstETH, 0), // token output (0 = no minimum) emptyLimit // limit order data ); ``` --- # Types and Utility Functions This document covers all struct types used throughout the Pendle Router and utility functions for generating parameters on-chain. ## Core Parameter Types ### TokenInput Defines input token configuration for functions that accept tokens. ```solidity struct TokenInput { address tokenIn; uint256 netTokenIn; address tokenMintSy; address pendleSwap; SwapData swapData; } ``` **Fields** | Name | Type | Description | |------|------|-------------| | tokenIn | `address` | Address of the input token (can be any ERC20 token supported by Pendle) | | netTokenIn | `uint256` | Amount of input tokens | | tokenMintSy | `address` | Token used to mint SY (must be supported by target SY contract) | | pendleSwap | `address` | Swap aggregator address (use `address(0)` for direct SY token input) | | swapData | `SwapData` | Swap configuration data for external aggregation | ### Usage Patterns **Direct SY Token Input (Simple):** When `tokenIn` is already a token supported by the target SY contract: - Set `tokenIn = tokenMintSy` (same token) - Set `pendleSwap = address(0)` (no external swap needed) - Set `swapData` to empty (no aggregation) **Any ERC20 Token Input (Zap In):** When `tokenIn` is any ERC20 token that needs to be swapped to a supported SY token: - Set `tokenIn` to the user's input token (e.g., USDC, DAI, WETH) - Set `tokenMintSy` to a token supported by the target SY - Set `pendleSwap` to the swap aggregator address - Set `swapData` with proper aggregator configuration. **E.g**: When you have USDC and want to mint SY-sUSDe: - Set `tokenIn = USDC` - Set `tokenMintSy = sUSDe` (a token accepted by SY-sUSDe's `deposit()` function) - Set `pendleSwap = 0xd4F480965D2347d421F1bEC7F545682E5Ec2151D` - Set `swapData` to proper data configuration obtained from [SDK](../../../Backend/HostedSdk#supported-functions) **Benefits of Zap In:** - Users can interact with any Pendle market using any ERC20 token they hold - No need to manually swap tokens before interacting with Pendle - Optimal routing through multiple DEXes for best price execution - Single transaction for swap + Pendle operation **SDK Integration:** The [Pendle Hosted SDK](../../../Backend/HostedSdk#features) automatically handles all TokenInput configuration when you enable routing. When you set `enableAggregator: true` in SDK calls, it: - Automatically selects the best swap aggregator (KyberSwap, ODOS, 1inch, etc.) - Generates optimal `swapData` for the chosen route - Handles all token conversions transparently - Provides the best possible price execution across multiple DEXes For direct contract interaction, you need to manually configure these fields or use utility functions like `createTokenInputSimple()` for basic operations. ### TokenOutput Defines output token configuration for functions that return tokens. ```solidity struct TokenOutput { address tokenOut; uint256 minTokenOut; address tokenRedeemSy; address pendleSwap; SwapData swapData; } ``` **Fields** | Name | Type | Description | |------|------|-------------| | tokenOut | `address` | Address of the desired output token (can be any ERC20 token supported by Pendle) | | minTokenOut | `uint256` | Minimum amount of output tokens to receive (slippage protection) | | tokenRedeemSy | `address` | Token to redeem SY to (must be supported by target SY contract) | | pendleSwap | `address` | Swap aggregator address (use `address(0)` for direct SY token output) | | swapData | `SwapData` | Swap configuration data for external aggregation | ### Usage Patterns **Direct SY Token Output (Simple):** When `tokenOut` is already a token supported by the target SY contract: - Set `tokenOut = tokenRedeemSy` (same token) - Set `pendleSwap = address(0)` (no external swap needed) - Set `swapData` to empty (no aggregation) **Any ERC20 Token Output (Zap Out):** When `tokenOut` is any ERC20 token different from supported SY tokens: - Set `tokenOut` to the user's desired token (e.g., USDC, DAI, WETH) - Set `tokenRedeemSy` to a token supported by the target SY - Set `pendleSwap` to the swap aggregator address - Set `swapData` with proper aggregator configuration (KyberSwap, 1inch, etc.) **Benefits of Zap Out:** - Users can receive any ERC20 token as output from Pendle operations - No need to manually swap tokens after exiting Pendle positions - Optimal routing through multiple DEXes for best price execution - Single transaction for Pendle operation + swap - Built-in slippage protection with `minTokenOut` **SDK Integration:** The [Pendle Hosted SDK](../../../Backend/HostedSdk#features) automatically handles all TokenOutput configuration when you enable routing. When you set `enableAggregator: true` in SDK calls, it: - Automatically selects the best swap aggregator for output token conversion - Generates optimal `swapData` for the chosen route - Calculates appropriate slippage protection - Handles all token conversions transparently For direct contract interaction, you need to manually configure these fields or use utility functions like `createTokenOutputSimple()` for basic operations. ### ApproxParams Parameters for approximation algorithms used when swapping exact tokens to PT or YT. **ApproxParams is required when the exact output amount needs to be determined through iterative approximation** because the Pendle AMM formula cannot be directly inverted. ```solidity struct ApproxParams { uint256 guessMin; uint256 guessMax; uint256 guessOffchain; uint256 maxIteration; uint256 eps; } ``` **Fields** | Name | Type | Description | |------|------|-------------| | guessMin | `uint256` | Minimum bound for binary search | | guessMax | `uint256` | Maximum bound for binary search (use `type(uint256).max` for auto) | | guessOffchain | `uint256` | Initial guess from off-chain calculation (use `0` for on-chain) | | maxIteration | `uint256` | Maximum iterations for binary search (recommended: `256`) | | eps | `uint256` | Precision tolerance (recommended: `1e14`) | **When ApproxParams is Needed:** ApproxParams is required for functions that swap an exact amount of tokens to PT or YT, such as: - [`swapExactTokenForPt`](./PtFunctions#swapexacttokenforpt) - Convert exact token amount to PT - [`swapExactSyForPt`](./PtFunctions#swapexactsyforpt) - Convert exact SY amount to PT - [`swapExactTokenForYt`](./YtFunctions#swapexacttokenforyt) - Convert exact token amount to YT - [`swapExactSyForYt`](./YtFunctions#swapexactsyforyt) - Convert exact SY amount to YT **Why Approximation is Required:** The Pendle AMM natively supports functions like `swapExactPtForSy` and `swapSyForExactPt`, but does NOT have `swapExactSyForPt`. When you want to swap an exact amount of tokens/SY for PT/YT, the router must use binary search to determine how much PT/YT can be obtained, since the AMM can only calculate the reverse (exact PT amounts). ### LimitOrderData Configuration for limit order functionality that **provides better prices and reduces slippage** by filling user orders at predetermined rates before routing to the AMM. ```solidity struct LimitOrderData { address limitRouter; uint256 epsSkipMarket; FillOrderParams[] normalFills; FillOrderParams[] flashFills; bytes optData; } ``` **Fields** | Name | Type | Description | |------|------|-------------| | limitRouter | `address` | Address of limit order router | | epsSkipMarket | `uint256` | Threshold to skip market operations | | normalFills | `FillOrderParams[]` | Normal limit order fills | | flashFills | `FillOrderParams[]` | Flash loan limit order fills | | optData | `bytes` | Additional optimization data | ### RedeemYtIncomeToTokenStruct Configuration for YT income redemption with token conversion. ```solidity struct RedeemYtIncomeToTokenStruct { IPYieldToken yt; bool doRedeemInterest; bool doRedeemRewards; address tokenRedeemSy; uint256 minTokenRedeemOut; } ``` **Fields** | Name | Type | Description | |------|------|-------------| | yt | `IPYieldToken` | YT token contract interface | | doRedeemInterest | `bool` | Whether to redeem accrued interest | | doRedeemRewards | `bool` | Whether to redeem reward tokens | | tokenRedeemSy | `address` | Token to convert SY interest to | | minTokenRedeemOut | `uint256` | Minimum tokens to receive from conversion | ### SwapData Configuration for external swap aggregation functionality. ```solidity struct SwapData { SwapType swapType; address extRouter; bytes extCalldata; bool needScale; } ``` **Fields** | Name | Type | Description | |------|------|-------------| | swapType | `SwapType` | Type of swap aggregator to use | | extRouter | `address` | Address of the external swap router | | extCalldata | `bytes` | Encoded calldata for the external swap | | needScale | `bool` | Whether the swap amount needs scaling | **Use Case** Enables integration with external swap aggregators like KyberSwap, 1inch, Paraswap, etc. This allows Pendle to support zapping in/out with any ERC20 token by routing through external DEXes. ### SwapType Enumeration defining supported swap aggregator types. See the [complete SwapType definition](https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/swap-aggregator/IPSwapAggregator.sol#L18-L31) in the smart contract. ```solidity enum SwapType { NONE, KYBERSWAP, ODOS, ETH_WETH, OKX, ONE_INCH, PARASWAP } ``` **Values** | Value | Description | |-------|-------------| | `NONE` | No external swap aggregation | | `KYBERSWAP` | KyberSwap aggregation | | `ODOS` | ODOS aggregation | | `ETH_WETH` | ETH/WETH conversion | | `OKX` | OKX DEX aggregation | | `ONE_INCH` | 1inch aggregation | | `PARASWAP` | Paraswap aggregation | **Use Case** Specifies which external aggregator to use for token swaps, enabling Pendle to leverage the best available liquidity across different DEXes. The [Pendle Hosted SDK](../../../Backend/HostedSdk#features) automatically selects the optimal SwapType based on available liquidity and routing efficiency. ### ExitPreExpReturnParams Detailed breakdown of returns from pre-expiry position exit operations. ```solidity struct ExitPreExpReturnParams { uint256 netPtFromRemove; uint256 netSyFromRemove; uint256 netPyRedeem; uint256 netSyFromRedeem; uint256 netPtSwap; uint256 netYtSwap; uint256 netSyFromSwap; uint256 netSyFee; uint256 totalSyOut; } ``` **Fields** | Name | Type | Description | |------|------|-------------| | netPtFromRemove | `uint256` | PT tokens obtained from LP removal | | netSyFromRemove | `uint256` | SY tokens obtained from LP removal | | netPyRedeem | `uint256` | PT+YT pairs redeemed to SY | | netSyFromRedeem | `uint256` | SY tokens from PT+YT redemption | | netPtSwap | `uint256` | PT tokens swapped to SY | | netYtSwap | `uint256` | YT tokens swapped to SY | | netSyFromSwap | `uint256` | SY tokens from PT/YT swaps | | netSyFee | `uint256` | Trading fees paid in SY | | totalSyOut | `uint256` | Total SY tokens received | **Use Case** Provides detailed breakdown of complex exit operations before market expiry, helping users understand exactly how their positions were unwound and what fees were paid. ### ExitPostExpReturnParams Breakdown of returns from post-expiry position exit operations. ```solidity struct ExitPostExpReturnParams { uint256 netPtFromRemove; uint256 netSyFromRemove; uint256 netPtRedeem; uint256 netSyFromRedeem; uint256 totalSyOut; } ``` **Fields** | Name | Type | Description | |------|------|-------------| | netPtFromRemove | `uint256` | PT tokens obtained from LP removal | | netSyFromRemove | `uint256` | SY tokens obtained from LP removal | | netPtRedeem | `uint256` | PT tokens redeemed at maturity | | netSyFromRedeem | `uint256` | SY tokens from PT redemption | | totalSyOut | `uint256` | Total SY tokens received | **Use Case** Provides breakdown of exit operations after market expiry when PT tokens can be redeemed 1:1 for underlying assets. Simpler than pre-expiry exits since no swapping is required. ## Utility Functions These functions generate the parameters described above on-chain, offering an alternative for users who do not utilize the Pendle SDK. ### createTokenInputSimple Creates a simple TokenInput struct without external swapping. ```solidity function createTokenInputSimple(address tokenIn, uint256 netTokenIn) pure returns (TokenInput memory) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | tokenIn | `address` | Input token address (must be supported by target SY) | | netTokenIn | `uint256` | Amount of input tokens | **Return Values** | Name | Type | Description | |------|------|-------------| | - | `TokenInput` | Configured TokenInput struct | **Use Case** Most common way to create TokenInput for standard operations. The tokenIn must be one of the tokens accepted by the target SY contract (check via `IStandardizedYield.getTokensIn()`). ### createTokenOutputSimple Creates a simple TokenOutput struct without external swapping. ```solidity function createTokenOutputSimple(address tokenOut, uint256 minTokenOut) pure returns (TokenOutput memory) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | tokenOut | `address` | Output token address (must be supported by target SY) | | minTokenOut | `uint256` | Minimum amount of output tokens | **Return Values** | Name | Type | Description | |------|------|-------------| | - | `TokenOutput` | Configured TokenOutput struct | **Use Case** Most common way to create TokenOutput for standard operations. The tokenOut must be one of the tokens that the target SY can redeem to (check via `IStandardizedYield.getTokensOut()`). ### createDefaultApproxParams Creates default approximation parameters suitable for most operations. ```solidity function createDefaultApproxParams() pure returns (ApproxParams memory) ``` **Return Values** | Name | Type | Description | |------|------|-------------| | - | `ApproxParams` | Configured ApproxParams with optimal defaults | **Configuration** - guessMin: `0` - guessMax: `type(uint256).max` (auto-detection) - guessOffchain: `0` (pure on-chain calculation) - maxIteration: `256` (sufficient for most cases) - eps: `1e14` (0.01% precision) **Use Case** Recommended for all operations requiring approximation. These parameters provide good balance between accuracy and gas costs. ### createEmptyLimitOrderData Creates an empty LimitOrderData struct for operations without limit orders. ```solidity function createEmptyLimitOrderData() pure returns (LimitOrderData memory) ``` **Return Values** | Name | Type | Description | |------|------|-------------| | - | `LimitOrderData` | Empty LimitOrderData struct | **Use Case** Use when you don't need limit order functionality. Required by many functions but can be empty for standard market operations. ## Integration Examples ### Basic Parameter Creation ```solidity // Create input parameters for 1000 USDC TokenInput memory input = createTokenInputSimple(USDC_ADDRESS, 1000e6); // Create output parameters expecting at least 950 USDC TokenOutput memory output = createTokenOutputSimple(USDC_ADDRESS, 950e6); // Create default approximation parameters ApproxParams memory approx = createDefaultApproxParams(); // Create empty limit order data LimitOrderData memory limit = createEmptyLimitOrderData(); ``` ### Complete Function Call Example ```solidity // Add liquidity with properly configured parameters router.addLiquiditySingleToken( msg.sender, // receiver MARKET_ADDRESS, // market minLpOut, // minLpOut createDefaultApproxParams(), // guessPtReceivedFromSy createTokenInputSimple(USDC_ADDRESS, 1000e6), // input createEmptyLimitOrderData() // limit ); ``` ### Custom ApproxParams for Advanced Use ```solidity // Create custom approximation parameters for high precision ApproxParams memory customApprox = ApproxParams({ guessMin: 0, guessMax: type(uint256).max, guessOffchain: estimatedOutput, // Use off-chain calculation if available maxIteration: 512, // Higher precision eps: 1e15 // Looser tolerance (0.1%) }); ``` ## Best Practices **For Standard Operations:** - Always use `createDefaultApproxParams()` unless you have specific precision requirements - Use `createEmptyLimitOrderData()` for simple market operations - Ensure token addresses are supported by target SY contracts **For Performance:** - Off-chain approximation (`guessOffchain`) can reduce gas costs significantly - Higher `maxIteration` values increase precision but cost more gas - Tighter `eps` values improve precision but may increase iterations --- # Simple Functions This document lists simplified versions of Pendle Router functions that use on-chain approximation algorithms instead of requiring complex parameters. These functions are automatically used by the main router when conditions allow for simplified execution. ## Overview The Pendle Router includes simplified versions of complex functions that: - Use on-chain approximation algorithms (no `ApproxParams` needed) - Skip limit order functionality (no `LimitOrderData` needed) - Provide streamlined interfaces for common operations - Are automatically selected when no off-chain guess is provided and limit order data is empty ## Important Notes ⚠️ **Limited Flexibility**: Simple functions don't support limit orders or custom approximation parameters. ⚠️ **Market Dependent**: Effectiveness depends on current market conditions and may not always be available. The simple functions provide a streamlined interface for common operations while maintaining the full functionality of the Pendle trading system through automated approximation algorithms. ## PT Trading Functions ### swapExactTokenForPtSimple Simplified version of `swapExactTokenForPt` using on-chain approximation. ```solidity function swapExactTokenForPtSimple( address receiver, address market, uint256 minPtOut, TokenInput calldata input ) external payable returns (uint256 netPtOut, uint256 netSyFee, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive PT tokens | | market | `address` | Pendle market address | | minPtOut | `uint256` | Minimum PT tokens to receive | | input | `TokenInput` | Token input configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netPtOut | `uint256` | PT tokens received | | netSyFee | `uint256` | Trading fees paid in SY | | netSyInterm | `uint256` | SY tokens generated from input token | ### swapExactSyForPtSimple Simplified version of `swapExactSyForPt` using on-chain approximation. ```solidity function swapExactSyForPtSimple( address receiver, address market, uint256 exactSyIn, uint256 minPtOut ) external returns (uint256 netPtOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive PT tokens | | market | `address` | Pendle market address | | exactSyIn | `uint256` | Exact amount of SY tokens to swap | | minPtOut | `uint256` | Minimum PT tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netPtOut | `uint256` | PT tokens received | | netSyFee | `uint256` | Trading fees paid in SY | ## YT Trading Functions ### swapExactTokenForYtSimple Simplified version of `swapExactTokenForYt` using on-chain approximation. ```solidity function swapExactTokenForYtSimple( address receiver, address market, uint256 minYtOut, TokenInput calldata input ) external payable returns (uint256 netYtOut, uint256 netSyFee, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive YT tokens | | market | `address` | Pendle market address | | minYtOut | `uint256` | Minimum YT tokens to receive | | input | `TokenInput` | Token input configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netYtOut | `uint256` | YT tokens received | | netSyFee | `uint256` | Trading fees paid in SY | | netSyInterm | `uint256` | SY tokens generated from input token | ### swapExactSyForYtSimple Simplified version of `swapExactSyForYt` using on-chain approximation. ```solidity function swapExactSyForYtSimple( address receiver, address market, uint256 exactSyIn, uint256 minYtOut ) external returns (uint256 netYtOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive YT tokens | | market | `address` | Pendle market address | | exactSyIn | `uint256` | Exact amount of SY tokens to swap | | minYtOut | `uint256` | Minimum YT tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netYtOut | `uint256` | YT tokens received | | netSyFee | `uint256` | Trading fees paid in SY | ## Liquidity Management Functions ### addLiquiditySingleTokenSimple Simplified version of `addLiquiditySingleToken` using on-chain approximation. ```solidity function addLiquiditySingleTokenSimple( address receiver, address market, uint256 minLpOut, TokenInput calldata input ) external payable returns (uint256 netLpOut, uint256 netSyFee, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive LP tokens | | market | `address` | Pendle market address | | minLpOut | `uint256` | Minimum LP tokens to receive | | input | `TokenInput` | Token input configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netLpOut | `uint256` | LP tokens received | | netSyFee | `uint256` | Trading fees paid in SY | | netSyInterm | `uint256` | SY tokens generated from input token | ### addLiquiditySingleSySimple Simplified version of `addLiquiditySingleSy` using on-chain approximation. ```solidity function addLiquiditySingleSySimple( address receiver, address market, uint256 netSyIn, uint256 minLpOut ) external returns (uint256 netLpOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive LP tokens | | market | `address` | Pendle market address | | netSyIn | `uint256` | Amount of SY tokens to use | | minLpOut | `uint256` | Minimum LP tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netLpOut | `uint256` | LP tokens received | | netSyFee | `uint256` | Trading fees paid in SY | ### addLiquiditySinglePtSimple Simplified version of `addLiquiditySinglePt` using on-chain approximation. ```solidity function addLiquiditySinglePtSimple( address receiver, address market, uint256 netPtIn, uint256 minLpOut ) external returns (uint256 netLpOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive LP tokens | | market | `address` | Pendle market address | | netPtIn | `uint256` | Amount of PT tokens to use | | minLpOut | `uint256` | Minimum LP tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netLpOut | `uint256` | LP tokens received | | netSyFee | `uint256` | Trading fees paid in SY | ### removeLiquiditySinglePtSimple Simplified version of `removeLiquiditySinglePt` using on-chain approximation. ```solidity function removeLiquiditySinglePtSimple( address receiver, address market, uint256 netLpToRemove, uint256 minPtOut ) external returns (uint256 netPtOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive PT tokens | | market | `address` | Pendle market address | | netLpToRemove | `uint256` | Amount of LP tokens to burn | | minPtOut | `uint256` | Minimum PT tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netPtOut | `uint256` | PT tokens received | | netSyFee | `uint256` | Trading fees paid in SY | ## When Simple Functions Are Used The Pendle Router automatically determines when to use simplified functions. Simple functions are used when: 1. **No limit orders**: `LimitOrderData` is empty 2. **No off-chain guess**: `ApproxParams` doesn't include off-chain calculated estimates 3. **Default parameters**: Standard approximation parameters are used ## Advantages of Simple Functions **Ease of Use**: Require fewer parameters and no complex configuration. **Reliability**: Built-in approximation algorithms are optimized for common trading scenarios. **Automatic Selection**: The main router functions automatically delegate to simple versions when no off-chain guess is provided and limit order data is empty. ## Integration Approach **Recommended Pattern**: Always use the main router functions (e.g., `swapExactTokenForPt`) with default parameters. The router will automatically use simple versions when optimal. ```solidity // This may automatically use the simple version router.swapExactTokenForPt( receiver, market, minPtOut, createDefaultApproxParams(), createTokenInputSimple(tokenIn, amountIn), createEmptyLimitOrderData() ); ``` **Direct Usage**: Only call simple functions directly if you're building custom routing logic and want to force the use of on-chain approximation. --- # Liquidity Management Functions This document covers all functions for managing liquidity positions in Pendle markets. These functions allow adding and removing liquidity in various token combinations. ## Add Liquidity Functions ### addLiquidityDualTokenAndPt Adds liquidity using a token and PT token in exact amounts. ```solidity function addLiquidityDualTokenAndPt( address receiver, address market, TokenInput calldata input, uint256 netPtDesired, uint256 minLpOut ) external payable returns (uint256 netLpOut, uint256 netPtUsed, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive LP tokens | | market | `address` | Pendle market address | | input | [`TokenInput`](./Types#tokeninput) | Token input configuration | | netPtDesired | `uint256` | Desired amount of PT tokens to use | | minLpOut | `uint256` | Minimum LP tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netLpOut | `uint256` | LP tokens received | | netPtUsed | `uint256` | PT tokens actually used | | netSyInterm | `uint256` | SY tokens generated from input token | **Use Case** When you have both the underlying token and PT tokens and want to add liquidity using exact amounts of both. The function will mint SY from your token input and combine it with your PT tokens. ### addLiquidityDualSyAndPt Adds liquidity using both SY and PT tokens in exact amounts. ```solidity function addLiquidityDualSyAndPt( address receiver, address market, uint256 netSyDesired, uint256 netPtDesired, uint256 minLpOut ) external returns (uint256 netLpOut, uint256 netSyUsed, uint256 netPtUsed) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive LP tokens | | market | `address` | Pendle market address | | netSyDesired | `uint256` | Desired amount of SY tokens to use | | netPtDesired | `uint256` | Desired amount of PT tokens to use | | minLpOut | `uint256` | Minimum LP tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netLpOut | `uint256` | LP tokens received | | netSyUsed | `uint256` | SY tokens actually used | | netPtUsed | `uint256` | PT tokens actually used | **Use Case** When you already have both SY and PT tokens and want to add liquidity directly. ### addLiquiditySinglePt Adds liquidity using only PT tokens by internally swapping some PT for SY. ```solidity function addLiquiditySinglePt( address receiver, address market, uint256 netPtIn, uint256 minLpOut, ApproxParams calldata guessPtSwapToSy, LimitOrderData calldata limit ) external returns (uint256 netLpOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive LP tokens | | market | `address` | Pendle market address | | netPtIn | `uint256` | Amount of PT tokens to use | | minLpOut | `uint256` | Minimum LP tokens to receive | | guessPtSwapToSy | [`ApproxParams`](./Types#approxparams) | Approximation parameters | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netLpOut | `uint256` | LP tokens received | | netSyFee | `uint256` | Trading fees paid in SY | **Use Case** When you only have PT tokens and want to add liquidity. The function will automatically determine the optimal amount of PT to swap for SY to achieve balanced liquidity provision. **Simple Version Available** For basic operations without custom parameters, use [`addLiquiditySinglePtSimple`](./SimpleFunctions#addliquiditysingleptsimple) which automatically handles approximation and skips limit order functionality. ### addLiquiditySingleToken Adds liquidity using any supported token by converting it to SY and PT as needed. ```solidity function addLiquiditySingleToken( address receiver, address market, uint256 minLpOut, ApproxParams calldata guessPtReceivedFromSy, TokenInput calldata input, LimitOrderData calldata limit ) external payable returns (uint256 netLpOut, uint256 netSyFee, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive LP tokens | | market | `address` | Pendle market address | | minLpOut | `uint256` | Minimum LP tokens to receive | | guessPtReceivedFromSy | [`ApproxParams`](./Types#approxparams) | Approximation parameters | | input | [`TokenInput`](./Types#tokeninput) | Token input configuration | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netLpOut | `uint256` | LP tokens received | | netSyFee | `uint256` | Trading fees paid in SY | | netSyInterm | `uint256` | SY tokens generated from input token | **Use Case** Most convenient method when you have any supported token and want to add liquidity. The function handles all necessary conversions and swaps automatically. **Simple Version Available** For basic operations without custom parameters, use [`addLiquiditySingleTokenSimple`](./SimpleFunctions#addliquiditysingletokensimple) which automatically handles approximation and skips limit order functionality. ### addLiquiditySingleSy Adds liquidity using only SY tokens by converting some to PT through market operations. ```solidity function addLiquiditySingleSy( address receiver, address market, uint256 netSyIn, uint256 minLpOut, ApproxParams calldata guessPtReceivedFromSy, LimitOrderData calldata limit ) external returns (uint256 netLpOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive LP tokens | | market | `address` | Pendle market address | | netSyIn | `uint256` | Amount of SY tokens to use | | minLpOut | `uint256` | Minimum LP tokens to receive | | guessPtReceivedFromSy | [`ApproxParams`](./Types#approxparams) | Approximation parameters | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netLpOut | `uint256` | LP tokens received | | netSyFee | `uint256` | Trading fees paid in SY | **Use Case** When you have SY tokens and want to add liquidity. The function will swap some SY for PT to achieve optimal liquidity provision. **Simple Version Available** For basic operations without custom parameters, use [`addLiquiditySingleSySimple`](./SimpleFunctions#addliquiditysinglesysimple) which automatically handles approximation and skips limit order functionality. ### addLiquiditySingleTokenKeepYt Adds liquidity while keeping the generated YT tokens instead of selling them. ```solidity function addLiquiditySingleTokenKeepYt( address receiver, address market, uint256 minLpOut, uint256 minYtOut, TokenInput calldata input ) external payable returns (uint256 netLpOut, uint256 netYtOut, uint256 netSyMintPy, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive LP and YT tokens | | market | `address` | Pendle market address | | minLpOut | `uint256` | Minimum LP tokens to receive | | minYtOut | `uint256` | Minimum YT tokens to receive | | input | [`TokenInput`](./Types#tokeninput) | Token input configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netLpOut | `uint256` | LP tokens received | | netYtOut | `uint256` | YT tokens received | | netSyMintPy | `uint256` | SY used to mint PT/YT | | netSyInterm | `uint256` | SY tokens generated from input token | **Use Case** When you want to add liquidity and also keep YT tokens for yield farming. This strategy avoids price impact since no swapping occurs - the underlying asset is converted to SY, then PT/YT pairs are minted directly. The PT and remaining SY are used for liquidity provision while YT is returned to you. ### addLiquiditySingleSyKeepYt Adds liquidity using SY tokens while keeping the generated YT tokens. ```solidity function addLiquiditySingleSyKeepYt( address receiver, address market, uint256 netSyIn, uint256 minLpOut, uint256 minYtOut ) external returns (uint256 netLpOut, uint256 netYtOut, uint256 netSyMintPy) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive LP and YT tokens | | market | `address` | Pendle market address | | netSyIn | `uint256` | Amount of SY tokens to use | | minLpOut | `uint256` | Minimum LP tokens to receive | | minYtOut | `uint256` | Minimum YT tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netLpOut | `uint256` | LP tokens received | | netYtOut | `uint256` | YT tokens received | | netSyMintPy | `uint256` | SY used to mint PT/YT | **Use Case** When you have SY tokens and want both LP tokens and YT tokens. This method avoids price impact since no swapping occurs - SY is used to mint PT/YT pairs directly, with PT used for liquidity provision and YT returned to you. ## Remove Liquidity Functions ### removeLiquidityDualTokenAndPt Removes liquidity and receives both the underlying token and PT tokens. ```solidity function removeLiquidityDualTokenAndPt( address receiver, address market, uint256 netLpToRemove, TokenOutput calldata output, uint256 minPtOut ) external returns (uint256 netTokenOut, uint256 netPtOut, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive tokens | | market | `address` | Pendle market address | | netLpToRemove | `uint256` | Amount of LP tokens to burn | | output | [`TokenOutput`](./Types#tokenoutput) | Token output configuration | | minPtOut | `uint256` | Minimum PT tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netTokenOut | `uint256` | Amount of tokens received | | netPtOut | `uint256` | PT tokens received | | netSyInterm | `uint256` | SY tokens converted to underlying | **Use Case** When you want to exit a liquidity position and receive both the underlying token and PT tokens separately. ### removeLiquidityDualSyAndPt Removes liquidity and receives both SY and PT tokens. ```solidity function removeLiquidityDualSyAndPt( address receiver, address market, uint256 netLpToRemove, uint256 minSyOut, uint256 minPtOut ) external returns (uint256 netSyOut, uint256 netPtOut) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive tokens | | market | `address` | Pendle market address | | netLpToRemove | `uint256` | Amount of LP tokens to burn | | minSyOut | `uint256` | Minimum SY tokens to receive | | minPtOut | `uint256` | Minimum PT tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netSyOut | `uint256` | SY tokens received | | netPtOut | `uint256` | PT tokens received | **Use Case** Most efficient method when you want both SY and PT tokens. No additional conversions are performed. ### removeLiquiditySinglePt Removes liquidity and converts everything to PT tokens. ```solidity function removeLiquiditySinglePt( address receiver, address market, uint256 netLpToRemove, uint256 minPtOut, ApproxParams calldata guessPtReceivedFromSy, LimitOrderData calldata limit ) external returns (uint256 netPtOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive PT tokens | | market | `address` | Pendle market address | | netLpToRemove | `uint256` | Amount of LP tokens to burn | | minPtOut | `uint256` | Minimum PT tokens to receive | | guessPtReceivedFromSy | [`ApproxParams`](./Types#approxparams) | Approximation parameters | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netPtOut | `uint256` | PT tokens received | | netSyFee | `uint256` | Trading fees paid in SY | **Use Case** When you want to exit liquidity and hold only PT tokens, maximizing your PT position. **Simple Version Available** For basic operations without custom parameters, use [`removeLiquiditySinglePtSimple`](./SimpleFunctions#removeliquiditysingleptsimple) which automatically handles approximation and skips limit order functionality. ### removeLiquiditySingleToken Removes liquidity and converts everything to a specified underlying token. ```solidity function removeLiquiditySingleToken( address receiver, address market, uint256 netLpToRemove, TokenOutput calldata output, LimitOrderData calldata limit ) external returns (uint256 netTokenOut, uint256 netSyFee, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive tokens | | market | `address` | Pendle market address | | netLpToRemove | `uint256` | Amount of LP tokens to burn | | output | [`TokenOutput`](./Types#tokenoutput) | Token output configuration | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netTokenOut | `uint256` | Amount of tokens received | | netSyFee | `uint256` | Trading fees paid in SY | | netSyInterm | `uint256` | SY tokens before conversion | **Use Case** Most convenient exit method when you want to receive a specific underlying token. Handles all necessary conversions automatically. ### removeLiquiditySingleSy Removes liquidity and converts everything to SY tokens. ```solidity function removeLiquiditySingleSy( address receiver, address market, uint256 netLpToRemove, uint256 minSyOut, LimitOrderData calldata limit ) external returns (uint256 netSyOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive SY tokens | | market | `address` | Pendle market address | | netLpToRemove | `uint256` | Amount of LP tokens to burn | | minSyOut | `uint256` | Minimum SY tokens to receive | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netSyOut | `uint256` | SY tokens received | | netSyFee | `uint256` | Trading fees paid in SY | **Use Case** When you want to exit liquidity and hold SY tokens, useful for further operations within the Pendle ecosystem. ## Integration Examples ### Basic Liquidity Addition ```solidity // Add liquidity with USDe router.addLiquiditySingleToken( msg.sender, PT_USDE_MARKET_ADDRESS, minLpOut, createDefaultApproxParams(), createTokenInputSimple(USDE_ADDRESS, 1000e18), createEmptyLimitOrderData() ); ``` ### Basic Liquidity Removal ```solidity // Remove liquidity to USDe router.removeLiquiditySingleToken( msg.sender, PT_USDE_MARKET_ADDRESS, lpAmount, createTokenOutputSimple(USDE_ADDRESS, minUsdeOut), createEmptyLimitOrderData() ); ``` --- # Principal Token (PT) Trading Functions This document covers all functions for trading Principal Tokens (PT) in Pendle markets. PT tokens represent the principal portion of yield-bearing assets and can be traded against other tokens or SY tokens. ## Token to PT Trading Since the AMM only supports swaps by exact PT, to swap exact tokens for PT requires binary search approximation to find the correct amount of SY needed to achieve the desired PT output. For best usage, use the [SDK](../../../Backend/HostedSdk#features) for better approximation since running binary search on-chain is costly. ### swapExactTokenForPt Swaps an exact amount of any supported token for PT tokens. ```solidity function swapExactTokenForPt( address receiver, address market, uint256 minPtOut, ApproxParams calldata guessPtOut, TokenInput calldata input, LimitOrderData calldata limit ) external payable returns (uint256 netPtOut, uint256 netSyFee, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive PT tokens | | market | `address` | Pendle market address | | minPtOut | `uint256` | Minimum PT tokens to receive | | guessPtOut | [`ApproxParams`](./Types#approxparams) | Approximation parameters | | input | [`TokenInput`](./Types#tokeninput) | Token input configuration | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netPtOut | `uint256` | PT tokens received | | netSyFee | `uint256` | Trading fees paid in SY | | netSyInterm | `uint256` | SY tokens generated from input token | **Use Case** Most common function for buying PT tokens with any supported token. The function converts your token to SY and then swaps SY for PT, first filling available limit orders, then using the AMM for any remaining amount. **Simple Version Available** For basic operations without custom parameters, use [`swapExactTokenForPtSimple`](./SimpleFunctions#swapexacttokenforptsimple) which automatically handles approximation and skips limit order functionality. ### swapExactSyForPt Swaps an exact amount of SY tokens for PT tokens. ```solidity function swapExactSyForPt( address receiver, address market, uint256 exactSyIn, uint256 minPtOut, ApproxParams calldata guessPtOut, LimitOrderData calldata limit ) external returns (uint256 netPtOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive PT tokens | | market | `address` | Pendle market address | | exactSyIn | `uint256` | Exact amount of SY tokens to swap | | minPtOut | `uint256` | Minimum PT tokens to receive | | guessPtOut | [`ApproxParams`](./Types#approxparams) | Approximation parameters | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netPtOut | `uint256` | PT tokens received | | netSyFee | `uint256` | Trading fees paid in SY | **Use Case** Direct and efficient method when you already have SY tokens and want to buy PT tokens. **Simple Version Available** For basic operations without custom parameters, use [`swapExactSyForPtSimple`](./SimpleFunctions#swapexactsyforptsimple) which automatically handles approximation and skips limit order functionality. ## PT to Token Trading ### swapExactPtForToken Swaps an exact amount of PT tokens for any supported token. ```solidity function swapExactPtForToken( address receiver, address market, uint256 exactPtIn, TokenOutput calldata output, LimitOrderData calldata limit ) external returns (uint256 netTokenOut, uint256 netSyFee, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive tokens | | market | `address` | Pendle market address | | exactPtIn | `uint256` | Exact amount of PT tokens to swap | | output | [`TokenOutput`](./Types#tokenoutput) | Token output configuration | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netTokenOut | `uint256` | Amount of tokens received | | netSyFee | `uint256` | Trading fees paid in SY | | netSyInterm | `uint256` | SY tokens before conversion | **Use Case** Most common function for selling PT tokens to receive tokens. The function swaps PT for SY (first filling available limit orders, then using the AMM), then converts SY to your desired token. ### swapExactPtForSy Swaps an exact amount of PT tokens for SY tokens. ```solidity function swapExactPtForSy( address receiver, address market, uint256 exactPtIn, uint256 minSyOut, LimitOrderData calldata limit ) external returns (uint256 netSyOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive SY tokens | | market | `address` | Pendle market address | | exactPtIn | `uint256` | Exact amount of PT tokens to swap | | minSyOut | `uint256` | Minimum SY tokens to receive | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netSyOut | `uint256` | SY tokens received | | netSyFee | `uint256` | Trading fees paid in SY | **Use Case** Direct and efficient method for selling PT tokens to receive SY tokens. ## Integration Examples ### Buying PT-sUSDe with USDe ```solidity // Swap 1000 USDe for PT-sUSDe tokens router.swapExactTokenForPt( msg.sender, PT_SUSDE_MARKET_ADDRESS, minPtOut, createDefaultApproxParams(), createTokenInputSimple(USDE_ADDRESS, 1000e18), createEmptyLimitOrderData() ); ``` ### Selling PT-sUSDe for USDe ```solidity // Swap PT-sUSDe tokens for USDe router.swapExactPtForToken( msg.sender, PT_SUSDE_MARKET_ADDRESS, ptAmount, createTokenOutputSimple(USDE_ADDRESS, minUsdeOut), createEmptyLimitOrderData() ); ``` ### Direct SY to PT Trading ```solidity // Swap SY tokens directly for PT tokens router.swapExactSyForPt( msg.sender, MARKET_ADDRESS, syAmount, minPtOut, createDefaultApproxParams(), createEmptyLimitOrderData() ); ``` --- # Yield Token (YT) Trading Functions This document covers all functions for trading Yield Tokens (YT) in Pendle markets. YT tokens represent the yield portion of yield-bearing assets and can be traded against other tokens or SY tokens. ## Token to YT Trading Since the AMM only supports swaps by exact YT, to swap exact tokens for YT requires binary search approximation to find the correct amount of SY needed to achieve the desired YT output. For best usage, use the [SDK](../../../Backend/HostedSdk#features) for better approximation since running binary search on-chain is costly. ### swapExactTokenForYt Swaps an exact amount of any supported token for YT tokens. ```solidity function swapExactTokenForYt( address receiver, address market, uint256 minYtOut, ApproxParams calldata guessYtOut, TokenInput calldata input, LimitOrderData calldata limit ) external payable returns (uint256 netYtOut, uint256 netSyFee, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive YT tokens | | market | `address` | Pendle market address | | minYtOut | `uint256` | Minimum YT tokens to receive | | guessYtOut | [`ApproxParams`](./Types#approxparams) | Approximation parameters | | input | [`TokenInput`](./Types#tokeninput) | Token input configuration | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netYtOut | `uint256` | YT tokens received | | netSyFee | `uint256` | Trading fees paid in SY | | netSyInterm | `uint256` | SY tokens generated from input token | **Use Case** Most common function for buying YT tokens with any supported token. The function converts your token to SY and then swaps SY for YT, first filling available limit orders, then using the AMM for any remaining amount. **Simple Version Available** For basic operations without custom parameters, use [`swapExactTokenForYtSimple`](./SimpleFunctions#swapexacttokenforytsimple) which automatically handles approximation and skips limit order functionality. ### swapExactSyForYt Swaps an exact amount of SY tokens for YT tokens. ```solidity function swapExactSyForYt( address receiver, address market, uint256 exactSyIn, uint256 minYtOut, ApproxParams calldata guessYtOut, LimitOrderData calldata limit ) external returns (uint256 netYtOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive YT tokens | | market | `address` | Pendle market address | | exactSyIn | `uint256` | Exact amount of SY tokens to swap | | minYtOut | `uint256` | Minimum YT tokens to receive | | guessYtOut | [`ApproxParams`](./Types#approxparams) | Approximation parameters | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netYtOut | `uint256` | YT tokens received | | netSyFee | `uint256` | Trading fees paid in SY | **Use Case** Direct and efficient method when you already have SY tokens and want to buy YT tokens. **Simple Version Available** For basic operations without custom parameters, use [`swapExactSyForYtSimple`](./SimpleFunctions#swapexactsyforytsimple) which automatically handles approximation and skips limit order functionality. ## YT to Token Trading ### swapExactYtForToken Swaps an exact amount of YT tokens for any supported token. ```solidity function swapExactYtForToken( address receiver, address market, uint256 exactYtIn, TokenOutput calldata output, LimitOrderData calldata limit ) external returns (uint256 netTokenOut, uint256 netSyFee, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive tokens | | market | `address` | Pendle market address | | exactYtIn | `uint256` | Exact amount of YT tokens to swap | | output | [`TokenOutput`](./Types#tokenoutput) | Token output configuration | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netTokenOut | `uint256` | Amount of tokens received | | netSyFee | `uint256` | Trading fees paid in SY | | netSyInterm | `uint256` | SY tokens before conversion | **Use Case** Most common function for selling YT tokens to receive tokens. The function swaps YT for SY (first filling available limit orders, then using the AMM), then converts SY to your desired token. ### swapExactYtForSy Swaps an exact amount of YT tokens for SY tokens. ```solidity function swapExactYtForSy( address receiver, address market, uint256 exactYtIn, uint256 minSyOut, LimitOrderData calldata limit ) external returns (uint256 netSyOut, uint256 netSyFee) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive SY tokens | | market | `address` | Pendle market address | | exactYtIn | `uint256` | Exact amount of YT tokens to swap | | minSyOut | `uint256` | Minimum SY tokens to receive | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netSyOut | `uint256` | SY tokens received | | netSyFee | `uint256` | Trading fees paid in SY | **Use Case** Direct and efficient method for selling YT tokens to receive SY tokens. ## Integration Examples ### Buying YT-sUSDe with USDe ```solidity // Swap 1000 USDe for YT-sUSDe tokens router.swapExactTokenForYt( msg.sender, PT_SUSDE_MARKET_ADDRESS, minYtOut, createDefaultApproxParams(), createTokenInputSimple(USDE_ADDRESS, 1000e18), createEmptyLimitOrderData() ); ``` ### Selling YT-sUSDe for USDe ```solidity // Swap YT-sUSDe tokens for USDe router.swapExactYtForToken( msg.sender, PT_SUSDE_MARKET_ADDRESS, ytAmount, createTokenOutputSimple(USDE_ADDRESS, minUsdeOut), createEmptyLimitOrderData() ); ``` ### Direct SY to YT Trading ```solidity // Swap SY tokens directly for YT tokens router.swapExactSyForYt( msg.sender, MARKET_ADDRESS, syAmount, minYtOut, createDefaultApproxParams(), createEmptyLimitOrderData() ); ``` --- # Misc Functions :::caution Interface Stability Some functions on this page have interfaces that may change in the future. Check with the Pendle team before using in production. ::: This document covers core functions for SY/PY operations, reward claiming, exit strategies, and utility functions in Pendle Router. ## SY Operations ### mintSyFromToken Mints SY tokens from any supported tokens. ```solidity function mintSyFromToken( address receiver, address SY, uint256 minSyOut, TokenInput calldata input ) external payable returns (uint256 netSyOut) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive SY tokens | | SY | `address` | SY token contract address | | minSyOut | `uint256` | Minimum SY tokens to receive | | input | [`TokenInput`](./Types#tokeninput) | Token input configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netSyOut | `uint256` | SY tokens received | **Use Case** Convert any supported token into SY tokens, which are standardized yield-bearing tokens used throughout the Pendle ecosystem. ### redeemSyToToken Redeems SY tokens back to any supported tokens. ```solidity function redeemSyToToken( address receiver, address SY, uint256 netSyIn, TokenOutput calldata output ) external returns (uint256 netTokenOut) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive tokens | | SY | `address` | SY token contract address | | netSyIn | `uint256` | Amount of SY tokens to redeem | | output | [`TokenOutput`](./Types#tokenoutput) | Token output configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netTokenOut | `uint256` | Tokens received | **Use Case** Convert SY tokens back to tokens when you want. ## Principal/Yield Token Operations ### mintPyFromToken Mints PT and YT tokens (collectively called PY) from any supported tokens. ```solidity function mintPyFromToken( address receiver, address YT, uint256 minPyOut, TokenInput calldata input ) external payable returns (uint256 netPyOut, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive PT and YT tokens | | YT | `address` | YT token contract address | | minPyOut | `uint256` | Minimum PY tokens to mint | | input | [`TokenInput`](./Types#tokeninput) | Token input configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netPyOut | `uint256` | PT and YT tokens minted (equal amounts) | | netSyInterm | `uint256` | SY tokens generated as intermediate step | **Use Case** Split any supported token into separate principal and yield components. ### redeemPyToToken Redeems equal amounts of PT and YT tokens back to any supported tokens. ```solidity function redeemPyToToken( address receiver, address YT, uint256 netPyIn, TokenOutput calldata output ) external returns (uint256 netTokenOut, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive tokens | | YT | `address` | YT token contract address | | netPyIn | `uint256` | Amount of PT+YT pairs to redeem | | output | [`TokenOutput`](./Types#tokenoutput) | Token output configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | netTokenOut | `uint256` | Tokens received | | netSyInterm | `uint256` | SY tokens generated as intermediate step | **Use Case** Recombine PT and YT tokens back into any supported token. Requires holding equal amounts of both PT and YT. ### mintPyFromSy Mints PT and YT tokens directly from SY tokens. ```solidity function mintPyFromSy( address receiver, address YT, uint256 netSyIn, uint256 minPyOut ) external returns (uint256 netPyOut) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive PT and YT tokens | | YT | `address` | YT token contract address | | netSyIn | `uint256` | Amount of SY tokens to use | | minPyOut | `uint256` | Minimum PY tokens to mint | **Return Values** | Name | Type | Description | |------|------|-------------| | netPyOut | `uint256` | PT and YT tokens minted (equal amounts) | **Use Case** Efficient splitting of SY tokens into PT and YT when you already have SY tokens. ### redeemPyToSy Redeems equal amounts of PT and YT tokens back to SY tokens. ```solidity function redeemPyToSy( address receiver, address YT, uint256 netPyIn, uint256 minSyOut ) external returns (uint256 netSyOut) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive SY tokens | | YT | `address` | YT token contract address | | netPyIn | `uint256` | Amount of PT+YT pairs to redeem | | minSyOut | `uint256` | Minimum SY tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netSyOut | `uint256` | SY tokens received | **Use Case** Recombine PT and YT tokens into SY tokens for further operations within Pendle ecosystem. ## Reward Functions ### redeemDueInterestAndRewards Claims all pending rewards and interest from SY tokens, YT tokens, and LP positions. ```solidity function redeemDueInterestAndRewards( address user, address[] calldata sys, address[] calldata yts, address[] calldata markets ) external ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | user | `address` | Address to receive rewards | | sys | `address[]` | Array of SY token addresses | | yts | `address[]` | Array of YT token addresses | | markets | `address[]` | Array of market addresses | **Use Case** Batch claim all rewards across multiple positions. This is the most gas-efficient way to claim rewards from multiple sources. ### redeemDueInterestAndRewardsV2 Advanced reward claiming with token swapping capabilities. ```solidity function redeemDueInterestAndRewardsV2( IStandardizedYield[] calldata SYs, RedeemYtIncomeToTokenStruct[] calldata YTs, IPMarket[] calldata markets, IPSwapAggregator pendleSwap, SwapDataExtra[] calldata swaps ) external returns (uint256[] memory netOutFromSwaps, uint256[] memory netInterests) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | SYs | `IStandardizedYield[]` | Array of SY contracts | | YTs | [`RedeemYtIncomeToTokenStruct[]`](./Types#redeemytincometotokenstruct) | YT redemption configurations | | markets | `IPMarket[]` | Array of market contracts | | pendleSwap | `IPSwapAggregator` | Swap aggregator for token conversions | | swaps | [`SwapDataExtra[]`](./Types#swapdata) | Swap configurations | **Return Values** | Name | Type | Description | |------|------|-------------| | netOutFromSwaps | `uint256[]` | Tokens received from swaps | | netInterests | `uint256[]` | Interest tokens received | **Use Case** Advanced reward claiming that can automatically swap reward tokens to desired tokens. Useful for complex strategies and automated systems. ## Token Swapping ### swapTokensToTokens Performs multiple token-to-token swaps using external aggregators. ```solidity function swapTokensToTokens( IPSwapAggregator pendleSwap, SwapDataExtra[] calldata swaps, uint256[] calldata netSwaps ) external payable returns (uint256[] memory netOutFromSwaps) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | pendleSwap | `IPSwapAggregator` | Swap aggregator contract | | swaps | `SwapDataExtra[]` | Array of swap configurations | | netSwaps | `uint256[]` | Array of input amounts for each swap | **Return Values** | Name | Type | Description | |------|------|-------------| | netOutFromSwaps | `uint256[]` | Output amounts from each swap | **Use Case** Batch multiple token swaps for gas efficiency. ### swapTokenToTokenViaSy Swaps tokens using SY as an intermediate step. ```solidity function swapTokenToTokenViaSy( address receiver, address SY, TokenInput calldata input, address tokenRedeemSy, uint256 minTokenOut ) external payable returns (uint256 netTokenOut, uint256 netSyInterm) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive output tokens | | SY | `address` | SY token contract address | | input | `TokenInput` | Input token configuration | | tokenRedeemSy | `address` | Output token address | | minTokenOut | `uint256` | Minimum output tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | netTokenOut | `uint256` | Output tokens received | | netSyInterm | `uint256` | SY tokens used as intermediate | **Use Case** Swap between tokens that both support the same SY token, often providing better rates than external DEXes. ## Exit Strategies ### exitPreExpToToken Comprehensive position exit before market expiry, converting everything to a single token. ```solidity function exitPreExpToToken( address receiver, address market, uint256 netPtIn, uint256 netYtIn, uint256 netLpIn, TokenOutput calldata output, LimitOrderData calldata limit ) external returns (uint256 totalTokenOut, ExitPreExpReturnParams memory params) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive tokens | | market | `address` | Pendle market address | | netPtIn | `uint256` | Amount of PT tokens to exit | | netYtIn | `uint256` | Amount of YT tokens to exit | | netLpIn | `uint256` | Amount of LP tokens to exit | | output | `TokenOutput` | Output token configuration | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | totalTokenOut | `uint256` | Total tokens received | | params | [`ExitPreExpReturnParams`](./Types#exitpreexpreturnparams) | Detailed breakdown of exit operations | **Use Case** Complete portfolio liquidation before expiry. Optimally combines PT+YT pairs and swaps remaining tokens. ### exitPreExpToSy Comprehensive position exit before market expiry, converting everything to SY tokens. ```solidity function exitPreExpToSy( address receiver, address market, uint256 netPtIn, uint256 netYtIn, uint256 netLpIn, uint256 minSyOut, LimitOrderData calldata limit ) external returns (ExitPreExpReturnParams memory params) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive SY tokens | | market | `address` | Pendle market address | | netPtIn | `uint256` | Amount of PT tokens to exit | | netYtIn | `uint256` | Amount of YT tokens to exit | | netLpIn | `uint256` | Amount of LP tokens to exit | | minSyOut | `uint256` | Minimum SY tokens to receive | | limit | [`LimitOrderData`](./Types#limitorderdata) | Limit order configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | params | [`ExitPreExpReturnParams`](./Types#exitpreexpreturnparams) | Detailed breakdown of exit operations | **Use Case** Portfolio liquidation to SY tokens, useful when you want to stay within Pendle ecosystem or perform further operations. ### exitPostExpToToken Position exit after market expiry, when PT tokens can be redeemed 1:1. ```solidity function exitPostExpToToken( address receiver, address market, uint256 netPtIn, uint256 netLpIn, TokenOutput calldata output ) external returns (uint256 totalTokenOut, ExitPostExpReturnParams memory params) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive tokens | | market | `address` | Pendle market address | | netPtIn | `uint256` | Amount of PT tokens to redeem | | netLpIn | `uint256` | Amount of LP tokens to exit | | output | `TokenOutput` | Output token configuration | **Return Values** | Name | Type | Description | |------|------|-------------| | totalTokenOut | `uint256` | Total tokens received | | params | [`ExitPostExpReturnParams`](./Types#exitpostexpreturnparams) | Breakdown of redemption operations | **Use Case** Clean exit after maturity when PT tokens are worth face value. Much simpler than pre-expiry exits. ### exitPostExpToSy Position exit after market expiry, converting to SY tokens. ```solidity function exitPostExpToSy( address receiver, address market, uint256 netPtIn, uint256 netLpIn, uint256 minSyOut ) external returns (ExitPostExpReturnParams memory params) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | receiver | `address` | Address to receive SY tokens | | market | `address` | Pendle market address | | netPtIn | `uint256` | Amount of PT tokens to redeem | | netLpIn | `uint256` | Amount of LP tokens to exit | | minSyOut | `uint256` | Minimum SY tokens to receive | **Return Values** | Name | Type | Description | |------|------|-------------| | params | [`ExitPostExpReturnParams`](./Types#exitpostexpreturnparams) | Breakdown of redemption operations | **Use Case** Post-expiry exit to SY tokens, maintaining position within Pendle ecosystem. ## Utility Functions ### boostMarkets Triggers market updates to refresh boost multipliers. ```solidity function boostMarkets(address[] memory markets) external ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | markets | `address[]` | Array of market addresses to boost | **Use Case** Refresh boost calculations for multiple markets in a single transaction. ### multicall Executes multiple function calls in a single transaction. ```solidity function multicall(Call3[] calldata calls) external payable returns (Result[] memory res) ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | calls | `Call3[]` | Array of function calls to execute | **Return Values** | Name | Type | Description | |------|------|-------------| | res | `Result[]` | Results from each function call | **Use Case** Batch multiple operations for gas efficiency and atomic execution. ### simulate :::caution Off-chain use only `simulate` always reverts — it executes the call and surfaces the return data through a revert. It must be called via `eth_call` / `staticCall`, never as an on-chain transaction. ::: Simulates function execution and returns the output data without committing state changes. ```solidity function simulate(address target, bytes calldata data) external payable ``` **Input Parameters** | Name | Type | Description | |------|------|-------------| | target | `address` | Contract address to simulate | | data | `bytes` | Encoded function call data | ## Integration Examples ### Basic SY Operations ```solidity // Mint SY-sUSDe from USDe router.mintSyFromToken( msg.sender, SY_USDE_ADDRESS, minSyOut, createTokenInputSimple(USDE_ADDRESS, 1000e18) ); // Redeem SY-sUSDe back to USDE router.redeemSyToToken( msg.sender, SY_USDE_ADDRESS, syAmount, createTokenOutputSimple(USDE_ADDRESS, minUsdeOut) ); ``` ### Reward Claiming ```solidity // Claim all rewards address[] memory sys = new address[](1); address[] memory yts = new address[](1); address[] memory markets = new address[](1); sys[0] = SY_ADDRESS; yts[0] = YT_ADDRESS; markets[0] = MARKET_ADDRESS; router.redeemDueInterestAndRewards(msg.sender, sys, yts, markets); ``` ### Complete Position Exit ```solidity // Exit all positions before expiry router.exitPreExpToToken( msg.sender, MARKET_ADDRESS, ptAmount, ytAmount, lpAmount, createTokenOutputSimple(USDC_ADDRESS, minUsdcOut), createEmptyLimitOrderData() ); ``` --- # sPENDLE ## Overview [sPENDLE](https://etherscan.io/address/0x999999999991E178D52Cd95AFd4b00d066664144) is the staked version of [PENDLE](https://etherscan.io/token/0x808507121b80c02388fad14726482e061b8da827), deployed on Ethereum at **[`0x999999999991E178D52Cd95AFd4b00d066664144`](https://etherscan.io/address/0x999999999991E178D52Cd95AFd4b00d066664144)**. Users stake PENDLE to receive sPENDLE at a 1:1 ratio. sPENDLE does not increase in value over time. sPENDLE held in a user’s wallet is eligible for: - Voting power within the Pendle ecosystem - Pro-rata share of reward distributions if they meet the active participation criteria For details on governance rights and rewards distribution, see the [sPENDLE mechanism documentation](../../ProtocolMechanics/Mechanisms/sPENDLE). ### Audit Reports The sPENDLE contract has been audited. Full audit reports are available [here](https://github.com/pendle-finance/pendle-core-v2-public/tree/main/audits). ## Staking PENDLE To stake PENDLE and receive sPENDLE ```solidity function stake(uint256 amount) external; ``` ## Unstaking sPENDLE There are two ways to unstake sPENDLE back to PENDLE. ### Option 1: Cooldown Flow (Fee-Free) Initiate a cooldown to unlock your PENDLE. After the cooldown period (14 days, readable via `cooldownDuration()`), finalize to receive your PENDLE. ```solidity /// @notice Initiate cooldown, returning the specified amount of sPENDLE function cooldown(uint256 amount) external; /// @notice Claim PENDLE after the cooldown period ends function finalizeCooldown() external returns (uint256 amount); /// @notice Cancel the cooldown and return sPENDLE to your wallet function cancelCooldown() external; ``` ### Option 2: Instant Unstake (5% Fee) For immediate access to PENDLE with a fee ```solidity function instantUnstake(uint256 amount) external returns (uint256 amountAfterFee, uint256 fee); ``` The fee rate is readable via `instantUnstakeFeeRate()`. Currently it is set to 5%. ### Comparison
| Method | Fee | Wait Time | |--------|-----|-----------| | Cooldown | None | 14 days | | Instant | 5% | None |
## Events ```solidity /// @notice Emitted when a user stakes PENDLE event Staked(address indexed user, uint256 amount); /// @notice Emitted when cooldown is initiated event CooldownInitiated(address indexed user, uint256 amount, uint256 cooldownStart); /// @notice Emitted when cooldown is canceled event CooldownCanceled(address indexed user, uint256 amount); /// @notice Emitted on finalizeCooldown() or instantUnstake() event Unstaked(address indexed user, uint256 amountAfterFee, uint256 fee); ``` ## View Functions ```solidity /// @notice Returns the cooldown duration in seconds (14 days = 1209600) function cooldownDuration() external view returns (uint24); /// @notice Returns the instant unstake fee rate (base 1e18, e.g., 5e16 = 5%) function instantUnstakeFeeRate() external view returns (uint64); /// @notice Returns a user's pending cooldown state /// @return cooldownStart The timestamp when cooldown was initiated /// @return amount The amount of sPENDLE in cooldown function userCooldown(address user) external view returns (uint104 cooldownStart, uint152 amount); ``` ## Full Interface ```solidity interface IPStakedPendle { struct UserCooldown { uint104 cooldownStart; uint152 amount; } event Staked(address indexed user, uint256 amount); event Unstaked(address indexed user, uint256 amountAfterFee, uint256 fee); event CooldownCanceled(address indexed user, uint256 amount); event CooldownInitiated(address indexed user, uint256 amount, uint256 cooldownStart); function cooldownDuration() external view returns (uint24); function instantUnstakeFeeRate() external view returns (uint64); function userCooldown(address user) external view returns (uint104 cooldownStart, uint152 amount); function stake(uint256 amount) external; function cooldown(uint256 amount) external; function cancelCooldown() external; // fee-free, after cooldown function finalizeCooldown() external returns (uint256 amount); // instant, but with fee function instantUnstake(uint256 amount) external returns (uint256 amountAfterFee, uint256 fee); } ``` --- # Unit, Decimals and Scaled18 As Pendle supports a wide range of assets, it is important to understand how to handle units and decimals correctly. ## Definition - **Decimals** of a token is the number of digits to the right of the decimal point in the token's smallest unit. It is given by the `decimals` method of the token contract. For example: - **Raw unit** of a token X is the smallest indivisible unit of that token, often referred to as "wei" in the context of Ethereum-based tokens. This unit is used in smart contracts and calculations. - **Natural unit** of a token X is the unit that is most commonly used by users, which is typically the token's decimal representation. For example: Examples: - ETH has decimals of 18, so the natural unit (1 ETH) is $10^{18}$ raw unit. - USDC has decimals of 6, so the natural unit (1 USDC) is $10^{6}$ raw unit. - BTC has decimals of 8, so the natural unit (1 BTC) is $10^{8}$ raw unit. ## Decimals of PT, YT, SY and LP - PT decimals and YT decimals are **the same**, and they are **equal** to the decimals of the _underlying asset_. - SY decimals equal to the decimals of the _yield token_. - Pendle LP decimals is always 18, regardless of the underlying asset's decimals. A reminder that the _underlying asset_ can be obtained using the function `SY.assetInfo()`, while the _yield token_ can be obtained using the function `SY.yieldToken()`. ## Scaled18 SY As mentioned in the previous section, PT, YT and SY decimals are based on the underlying asset's decimals. However, for assets with small decimals (such as BTC), the margin for rounding error can be significant when performing calculations. To mitigate this, we introduced a _decimal-wrapping_ mechanism. For assets with decimals less than 18, we will deploy an ERC20 wrapper, both for the the underlying asset and the yield token. The wrapper will scale the asset to 18 decimals, allowing for more precise calculations. PT, YT and SY can then be of the wrapped asset, and they will have 18 decimals. ### Convention For assets that are wrapped to have 18 decimals: - The function `ERC20.decimals()` will return 18. - The function `ERC20.name()` will the original asset's name, concatenated with the suffix `scaled18`. - The function `ERC20.symbol()` will the original asset's symbol, concatenated with the suffix `-scaled18`. - They will have an additional function `rawToken`, returning the address of the original asset. - One **natural unit** of the **wrapped** asset will equal to one **natural unit** of the original asset. - This fact can be used to convert between the **raw unit** of the wrapped asset and the original one. $$ \begin{array}{rrcl} & 10^{\mathrm{decimals}}\ \mathrm{original} & = & 10^{18}\ \mathrm{wrapped} \\ \Leftrightarrow & 1\ \mathrm{original} & = & 10^{18 - decimals}\ \mathrm{wrapped} \end{array} $$ For SY of such wrapped assets: - The contract name will have the suffix `Scaled18`, indicating that it is a scaled version of the original asset. - The `SY.assetInfo()` function will return the **wrapped** asset address. - The `SY.yieldToken()` function will return the **wrapped** yield token address. - Each `SY` will provide custom functionalities to obtain the original asset address. But the uniform way to obtain the original asset address is by calling the `rawToken` function on of the **wrapped** asset/yield token. - The `wrapped` yield token can be in the list of `tokensIn` and `tokensOut` (`SY.getTokensIn()` and `SY.getTokensOut()`). - See next section for the note on the `exchangeRate()` function. ## Conversion rates For on-chain purposes, there are ways to get conversion rates between 2 different tokens, such as: - `SY.exchangeRate()` - returns the exchange rate between SY and the underlying asset. - Using on-chain [Oracle](../Oracles/HowToIntegratePtAndLpOracle.md), the conversion rate between PT/YT/LP to SY/underlying asset can be obtained. An important fact about the conversion rate is that the number returned by these function **do NOT** operate on the _natural unit_, but on raw unit. :::tip Suppose that the function `SY.exchangeRate()` returns $\mathrm{rate}$. To convert from $x$ **raw unit** SY to the underlying asset, you can use the formula: $x \cdot \mathrm{rate} / 10^{18}$. Note that $10^{18}$ is a constant. Conversion between PT/YT/LP to SY/underlying using the oracle rate is done similarly. ::: :::tip If you want to convert between **natural unit**, please convert the input into **raw unit** first, and convert the result back to **natural unit** after the calculation. ::: ### scaled18 SY `exchangeRate()` For scaled18 SY, the `exchangeRate()` function will return the exchange rate between the the SY and the **wrapped** underlying asset. To convert scaled SY of wrapped asset to the **original** underlying asset, we can multiply the `exchangeRate()` by $10^{18 - decimals}$ to get the conversion rate. --- # Pendle Oracle Overview Pendle offers two oracle types for pricing PT and LP tokens. For most new integrations, **the Linear Discount Oracle is the recommended choice** — it has been widely adopted by top Aave and Morpho curators including Gauntlet and Steakhouse due to its manipulation-resistant design and alignment with PT's guaranteed 1:1 redemption to underlying at maturity. The TWAP oracle remains available for integrations that require a market-derived price. ## Which oracle should I use? | Oracle type | Status | Best for | Docs | |---|---|---|---| | **Deterministic (Linear Discount)** | ✅ **Recommended** | Money markets, lending protocols, collateral pricing | [LinearDiscountOracle](./DeterministicOracles/LinearDiscountOracle.md) · [LP variant](./DeterministicOracles/LPLinearDiscountOracle.md) · [Choosing params](./DeterministicOracles/ChoosingLinearDiscountParams.md) | | TWAP PT/LP Oracle | Available | Integrations requiring a live market-derived price | [HowToIntegratePtAndLpOracle](./HowToIntegratePtAndLpOracle.md) | ## Why the Linear Discount Oracle is recommended PT is **guaranteed to redeem 1:1 to its underlying asset at maturity**. This hard floor means a linear discount model — which prices PT as a fraction that converges to 1.0 at expiry — is fundamentally sound and tightly tracks fair value. In contrast, TWAP oracles derive price from AMM activity, which introduces AMM manipulation surface and requires careful initialization and liquidity depth assessment. The Linear Discount Oracle has no external dependencies during reads (`block.timestamp` only), making it liveness-risk-free and manipulation-resistant. This is why it has become the oracle of choice for top risk curators integrating PT across Aave and Morpho markets. ## About the PT Oracle In the Pendle system, $PT$ can be freely traded from and to $SY$ utilizing our AMM. With the built-in TWAP oracle library, the geometric mean price of $PT$ in terms of SY or asset can be derived from our `PendleMarket` contracts fully on-chain. Please refer to the [StandardizedYield doc](../Contracts/StandardizedYield/StandardizedYield) for more details of SY & asset. ### Oracle design Pendle's oracle implementation is inspired by the idea of the UniswapV3 Oracle (see [here](https://docs.uniswap.org/concepts/protocol/oracle)) with a slight difference in how we define the cumulative rate. In short, our oracle stores the cumulative logarithm of implied APY (the interest rate implied by $PT/asset$ pricing). From the cumulative logarithm of Implied APY, we can calculate the geometric mean of Implied APY, which will be used to derive the mean $PT$ price. In a way, the Pendle AMM contract has a built-in oracle of interest rate, which can be used to derive $PT$ prices. ### Formulas Our oracle storage is in the following form: ```sol struct Observation { // the block timestamp of the observation uint32 blockTimestamp; // the tick logarithm accumulator, i.e., ln(impliedRate) * time elapsed since the pool was first initialized uint216 lnImpliedRateCumulative; // whether or not the observation is initialized bool initialized; } ``` The geometric mean price of $PT$ for the time interval of $[t_0, t_1]$ is: $$ lnImpliedRate = \frac{lnImpliedRateCumulative_1 - lnImpliedRateCumulative_0}{t_1 - t_0} $$ $$ impliedRate = e^{lnImpliedRate} $$ $$ assetToPtPrice = impliedRate^{\frac{timeToMaturity}{oneYear}} $$ $$ ptToAssetPrice = 1 / assetToPtPrice $$ See [How to Integrate](./HowToIntegratePtAndLpOracle.md) for a step-by-step guide. ## About the LP Oracle Pendle's LP token represents a user's share in Pendle AMM which pairs up PT and SY. SY is the interest-bearing token wrapper which enables depositing from and redeeming from underlying asset with no additional fee or price impact. PT can be traded to and from SY/underlying asset using our AMM, with a built-in geometric mean pricing module. LP oracle returns the estimated TWAP exchange rate between LP token and underlying asset. Our approach for LP pricing is to simulate a hypothetical trade on the AMM so that its PT spot price (and the implied rate) matches PT price (and the implied rate) from PT oracle before using market state to calculate LP price. For example: * The 1hr TWAP PT price is 0.90 asset. * The current state of the market is (x PT, y SY), where PT price is 0.92 asset * We calculate a hypothetical, zero-fee swap that brings PT price to 0.90 asset, to reach a state of (x' PT, y' SY) * We calculate the estimated TWAP LP token price based on the hypothetical state (x' PT, y' SY) and PT price of 0.90 asset Detailed documentation on the math for this approach can be found [here](https://github.com/pendle-finance/pendle-v2-resources/blob/main/docs/LP_Oracle_Doc.pdf). See [How to Integrate](./HowToIntegratePtAndLpOracle.md) for a step-by-step guide. ## Recommended reading order ### If using the Linear Discount Oracle (recommended) 1. This page — understand which oracle type fits your use case 2. [Linear Discount Oracle](./DeterministicOracles/LinearDiscountOracle.md) — how the oracle works and how to deploy it 3. [Choosing Linear Discount Parameters](./DeterministicOracles/ChoosingLinearDiscountParams.md) — how to select the right discount rate 4. If pricing LP tokens: [LP Linear Discount Oracle](./DeterministicOracles/LPLinearDiscountOracle.md) 5. If using as collateral: [PT as Collateral](./PTAsCollateral.md) or [LP as Collateral](./LPAsCollateral.md) ### If using the TWAP Oracle 1. This page — understand which oracle type fits your use case 2. [How to Integrate PT and LP Oracle](./HowToIntegratePtAndLpOracle.md) — hands-on TWAP integration with code 3. [PT Sanity Checks](./PTSanityChecks.md) — validate your integration before going live 4. If using as collateral: [PT as Collateral](./PTAsCollateral.md) or [LP as Collateral](./LPAsCollateral.md) --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # How to Integrate PT and LP Oracle :::tip Looking for the recommended oracle? For most new integrations, Pendle now recommends the **[Linear Discount Oracle](./DeterministicOracles/LinearDiscountOracle.md)** — adopted by top Aave and Morpho curators (Gauntlet, Steakhouse) for its manipulation-resistant, AMM-independent design. This page covers the TWAP oracle, which remains fully supported for integrations that require a market-derived price. ::: Integrating PT and LP oracles into your system can be accomplished in the following steps. This document provides detailed instructions along with runnable examples. If you need personalized assistance, don't hesitate to contact us via our Developers channel on [Pendle Discord]. ## Prerequisite ### Understand SY, PT, LP - Read [High Level Architecture](../HighLevelArchitecture.md) and [StandardizedYield docs] to understand the Pendle system. - Refer to the examples in the following repository for further understanding: - https://github.com/pendle-finance/pendle-examples/tree/main/test - https://github.com/pendle-finance/pendle-core-v2-public/tree/main/contracts/oracles ### Follow security advisories As Pendle becomes more permissionless, certain security assumptions of SY will change in the future, and the Pendle team will verify for all teams currently integrating the LP Oracle that their integration meets the new **security assumptions** of SY. Please reach out to us on [Pendle Discord] if you have any questions. ## Choose a Market The _Using Pendle Oracle_ and _Using Pendle Library_ tabs use EtherFi's [weETH market] on Arbitrum (70M USD liquidity at block 192 001 277). The _Using ChainLink oracle_ tab demonstrates with an LBTC market to show how `PendleChainlinkOracleWithQuote` works with external price feeds. [weETH market]: https://arbiscan.io/address/0x952083cde7aaa11AB8449057F7de23A970AA8472 We recommend choosing a market with high trading activities & deep liquidity. For a detailed guide on assessing the risk, depth of liquidity & twap duration, refer to the corresponding risk assessment docs. - [For using PT as collateral](./PTAsCollateral.md#risk-analysis-for-pt-as-a-collateral) - [For using LP as collateral](./LPAsCollateral.md#risk-analysis-for-lp-as-a-collateral) ## TWAP Duration and Oracle Initialization By default, markets' oracles are **NOT initialized**. The oracle's status can be checked using the `_test_oracle_ready` function ([source](https://github.com/pendle-finance/pendle-examples-public/blob/642b1ab2784b3015691d6c26a2684cd5f7585b0d/test/OracleSample.sol#L75-L91)). - The passed in `duration` is the TWAP duration (in seconds) you want to use. - The recommended TWAP duration is 900 seconds (15 minutes) for most markets or 1800 seconds (30 minutes). ```solidity function _test_oracle_ready(address marketToCheck, uint32 duration) public view { (bool increaseCardinalityRequired, , bool oldestObservationSatisfied) = oracle.getOracleState(marketToCheck, duration); if (increaseCardinalityRequired) { // <=============== (1) - Oracle needs to be initialized } if (!oldestObservationSatisfied) { // <=============== (2) - Need to wait for data to be available } // <=============== (3) - Oracle is ready assert(!increaseCardinalityRequired); assert(oldestObservationSatisfied); } ``` ### Point (1) - Oracle needs to be initialized At this point, the oracle needs to be initialized. This can be done by calling the following yourself: ```solidity IPMarket(market).increaseObservationsCardinalityNext(cardinalityRequired) ``` Here are possible values for `cardinalityRequired`: | `duration` (seconds) | `900`| `1800`| | -------------------- | -----| ----- | | For Ethereum | 85 | 165 | | For Arbitrum | 900 | 1800 | So on Ethereum, for `duration` of 900 seconds, `cardinalityRequired` can be 85.
Calculate cardinalityRequired In general, it can be calculated like this $$ \mathtt{cardinalityRequired} \approx \frac {\mathtt{duration}} {\max\{\mathrm{\text{chain block time}}, 1\}} $$
After that, you need **to wait** for `duration` seconds for the first data to be available. ### Point (2) - Need to wait for data to be available Similar to the previous point, you need **to wait** for `duration` seconds for the first data to be available. ### Point (3) - Oracle is ready Now you can call functions to get the observed price! ## Price Retrieval We have provided a few ways to obtain the price. Select one of the following tabs to choose the one that best suits your needs. We have provided a way to get the price, with the same interface as ChainLink oracles. The source code of the whole example can be found here: - https://github.com/pendle-finance/pendle-examples-public/blob/main/test/ChainlinkOracleSample.sol ### Step 1. Deploy the oracle (if not already deployed) If the oracle is not already deployed, you can self-deploy it via the [Pendle Public DApp](https://dapp-public.pendle.finance/), or deploy it yourself the same as this `setUp` function ([source](https://github.com/pendle-finance/pendle-examples-public/blob/642b1ab2784b3015691d6c26a2684cd5f7585b0d/test/ChainlinkOracleSample.sol#L30-L41)). ```solidity title="code fragment of setUp function" factory = new PendleChainlinkOracleFactory(0x5542be50420E88dd7D5B4a3D488FA6ED82F6DAc2); PT_LBTC_oracle = PendleChainlinkOracle( factory.createOracle(address(market), twapDuration, PendleOracleType.PT_TO_SY) ); PT_USD_oracle = PendleChainlinkOracleWithQuote( factory.createOracleWithQuote(address(market), twapDuration, PendleOracleType.PT_TO_SY, address(LBTC_USD_feed)) ); ```
Parameters summary - `market` is the market address you want to observe the price. - `twapDuration` is the TWAP duration you want to use, chosen in the previous section. - `0x5542be50420E88dd7D5B4a3D488FA6ED82F6DAc2` is the address of the deployed Pendle Oracle. - It was deployed to have the same address on all networks. - Refer to [Deployments on GitHub](https://github.com/pendle-finance/pendle-core-v2-public/tree/main/deployments) section for the full list of addresses. - The deployed ChainLink oracles **wrap** this oracle. - Please refer to the _Using Pendle Oracle_ way if you want to use it directly.
When deploying the oracle, you need to specify the type: - `PendleOracleType.PT_TO_SY` - to get the price of PT in SY. - `PendleOracleType.PT_TO_ASSET` - to get the price of PT in the underlying asset. We support 2 contracts for obtaining the price: - `PendleChainlinkOracle` - to get the price of PT in SY/asset. - `PendleChainlinkOracleWithQuote` - to get the price of PT in a different token if you have the ChainLink oracle of that token with the underlying asset. ### Step 2. Call the oracle Price can be obtained simply by calling the `getLatestRoundData` function ([source](https://github.com/pendle-finance/pendle-examples-public/blob/642b1ab2784b3015691d6c26a2684cd5f7585b0d/test/ChainlinkOracleSample.sol#L43-L54)). ```solidity function test_get_prices_in_SY() external view { (uint80 roundId, int256 answer, , uint256 updatedAt, uint80 answeredInRound) = PT_LBTC_oracle.latestRoundData(); console.log("PT LBTC to SY-LBTC"); console.log(uint256(roundId), uint256(answer), updatedAt, uint256(answeredInRound)); // answer = 992893819205953801, meaning 1 PT = 0.992893819205953801 SY-LBTC } function test_get_prices_in_quote() external view { (uint80 roundId, int256 answer, , uint256 updatedAt, uint80 answeredInRound) = PT_USD_oracle.latestRoundData(); console.log("PT LBTC to BTC"); console.log(uint256(roundId), uint256(answer), updatedAt, uint256(answeredInRound)); // answer = 990999427443599801, meaning 1 PT = 0.9909994274435997 BTC } ```
The source code of the whole example can be found here: - https://github.com/pendle-finance/pendle-examples-public/blob/main/test/OracleSample.sol We have deployed a contract that helps obtain the price of PT/YT/LP token in SY or asset. The contract is at address `0x5542be50420E88dd7D5B4a3D488FA6ED82F6DAc2`. - It was deployed to have the same address on all networks. - Refer to [Deployments on GitHub](https://github.com/pendle-finance/pendle-core-v2-public/tree/main/deployments) section for the full list of addresses. Getting the price can be done simply by calling the corresponding function ([source](https://github.com/pendle-finance/pendle-examples-public/blob/642b1ab2784b3015691d6c26a2684cd5f7585b0d/test/OracleSample.sol#L38-L46)). ```solidity function test_get_price_LRT_in_underlying() external view { uint256 ptRateInWeEth = oracle.getPtToSyRate(address(market), twapDuration); uint256 ytRateInWeEth = oracle.getYtToSyRate(address(market), twapDuration); uint256 lpRateInWeEth = oracle.getLpToSyRate(address(market), twapDuration); console.log("1 PT = %s Wrapped eEth (base 1e18)", ptRateInWeEth); console.log("1 YT = %s Wrapped eEth (base 1e18)", ytRateInWeEth); console.log("1 LP = %s Wrapped eEth (base 1e18)", lpRateInWeEth); } ``` ### Optional: Convert the price to a different asset Additionally, if you have an on-chain feed for the price of the underlying asset with a different token, you can **convert** the price using multiplication. ```solidity function test_get_price_LRT_with_external_oracle() external view { uint256 ptRateInWeEth = oracle.getPtToSyRate(address(market), twapDuration); // 1 SY-weETH = 1 weETH uint256 ptRateInEth = (ptRateInWeEth * uint256(weETH_ETH_feed.latestAnswer())) / (10 ** weETH_ETH_feed.decimals()); console.log("1 PT = %s ETH (base 1e18)", ptRateInEth); // 1 PT = 0.980103943942239852 ETH uint256 ptRateInUsd = (ptRateInEth * uint256(ETH_USD_feed.latestAnswer())) / (10 ** ETH_USD_feed.decimals()); console.log("1 PT = %s USD (base 1e18)", ptRateInUsd); // 1 PT = 3714.1302603652102 USD } ``` The source code of the whole example can be found here: - https://github.com/pendle-finance/pendle-examples-public/blob/main/test/OracleSample.sol This library is used in the Pendle Oracle (refer to _Using Pendle Oracle_ section). But it can also be used directly in your contract source code. This can help save **about 4k gas**! Before using it, import it as follows: ```solidity import { PendlePYOracleLib, PendleLpOracleLib } from "@pendle/core-v2/contracts/oracles/PtYtLpOracle/PendlePYLpOracle.sol"; contract YourContract { using PendlePYOracleLib for IPMarket; using PendleLpOracleLib for IPMarket; // ... } ``` Then you can call the functions directly on the market address. ```solidity function test_get_price_LRT_in_underlying_with_lib() external view { uint256 ptRateInWeEth = market.getPtToSyRate(twapDuration); uint256 ytRateInWeEth = market.getYtToSyRate(twapDuration); uint256 lpRateInWeEth = market.getLpToSyRate(twapDuration); console.log("1 PT = %s Wrapped eEth (base 1e18)", ptRateInWeEth); console.log("1 YT = %s Wrapped eEth (base 1e18)", ytRateInWeEth); console.log("1 LP = %s Wrapped eEth (base 1e18)", lpRateInWeEth); } ``` Refer to [StandardizedYield docs] to call the correct function. ### Optional: Convert the price to a different asset Additionally, if you have an on-chain feed for the price of the underlying asset with a different token, you can **convert** the price using multiplication. ```solidity function test_get_price_LRT_with_external_oracle_with_lib() external view { uint256 ptRateInWeEth = market.getPtToSyRate(twapDuration); // 1 SY-weETH = 1 weETH uint256 ptRateInEth = (ptRateInWeEth * uint256(weETH_ETH_feed.latestAnswer())) / (10 ** weETH_ETH_feed.decimals()); console.log("1 PT = %s ETH (base 1e18)", ptRateInEth); // 1 PT = 0.980103943942239852 ETH uint256 ptRateInUsd = (ptRateInEth * uint256(ETH_USD_feed.latestAnswer())) / (10 ** ETH_USD_feed.decimals()); console.log("1 PT = %s USD (base 1e18)", ptRateInUsd); // 1 PT = 3714.1302603652102 USD } ```
:::warning We recommend pricing PT to SY instead of asset. For SY to any other units, the integrator can choose an appropriate method based on whether the asset can be directly redeemed from the SY or if there is a slashing risk, etc. Pendle can not provide a perfect PT to Asset price because Asset price is not well defined.
Pendle can not provide a perfect PT to asset price Let's take a look at the example of PT-sUSDe/SY-sUSDe with asset being USDe. Pendle can guarantee 1 PT-sUSDe can be traded to $X$ SY-sUSDe = $X$ sUSDe. PT to SY price **exists natively**. On the other hand, Pendle **can not** guarantee sUSDe is redeemable to some amount of USDe. Which now traced back to: SY-sUSDe’s asset is not USDe, but USDe staked in Ethena, hence the price of this is not **well defined**. --- # PT as Collateral in a Money Market PTs are good collaterals in a money market since it is a fixed rate position (or a zero coupon bond) on an asset. This document discusses the use cases for PT as a collateral, as well as considerations for a money market when integrating PT as a collateral. ## Main Use Cases #### 1. Get leveraged APY doing yield arbitrage Example: PT-stETH gives a fixed 5% APY, but the ETH borrow rate is only 3% in the money market. * In this case, a user can deposit PT-stETH as collateral, borrow ETH, swap borrowed ETH to more PT-stETH to use as more collateral, and so on. * As a result, the user will get a leveraged APY in ETH terms, benefiting from the difference between the PT fixed rate and ETH borrow rate * If the collateral factor is 0.80, the user can leverage 5x their capital to get a maximum APY of 5 * (5-3) = 10% This use case is similar to depositing a yield bearing asset (like wstETH) and borrowing (ETH) in a money market, but is better due to the certainty from the fixed rate in PT. #### 2. Leverage short yield * If a user thinks the current fixed yield in Pendle is overvalued (PT is undervalued), they can do the exact step as the previous section (for example, use PT-stETH as collateral to borrow ETH to buy more PT-stETH) to short yield. * If the fixed yield indeed goes down, the user can unwind their position (sell PT to repay the borrowed ETH) for a profit. #### 3. Leverage long assets while earning fixed yield * If a user is bullish on an asset in the long term, they can use its PT as a collateral to borrow stables to buy more PT * For example, a long term ETH holder can use PT-stETH as collateral to borrow USDC to buy more PT-stETH * Essentially, the user will be getting a fixed APY (from PT) on top of their leveraged long position on ETH. ## Risk analysis for PT as a collateral ### 1. Smart contract vulnerability in Pendle contracts: * If Pendle contracts malfunctions or gets exploited, PT could lose value significantly in a short duration, leading to bad debt for the money market protocol. * Assessment: * Pendle V2 contracts have been audited by 6 auditors, with 3 of the top 4 C4 auditors. Find the audit reports [here](https://github.com/pendle-finance/pendle-core-v2-public/tree/main/audits). * Pendle V2 contract system's components (SY, YT-PT, Market, sPENDLE) are decoupled from each other, only interacting with the other components via interfaces, decreasing the chance for bugs due to complexity and interwoven logic. * Lindy-ness: There have been no incidents so far in Pendle contracts since June 2021 * Pendle V2 contracts have been live since November 2022 (peak TVL of $93M) * Pendle V1 contracts (with many similar mechanisms to V2) have been live since June 2021, with a peak TVL of $37M * Apart from the core Pendle contracts (for YT-PT and Pendle Market) which remain the same throughout, risk assessment needs to be done on the SY implementation for the particular PT pool. ### 2. Smart contract vulnerability in underlying protocols: * Each PT is built on top of an underlying yield bearing token (like stETH, USDT staked in Stargate). If the underlying protocol malfunctions or gets exploited, PT could lose value significantly in a short duration, leading to bad debt for the money market * As such, risk assessment should be made for each underlying protocol for each PT. ### 3. Oracle exploit: * If the oracle for PT price is easily manipulated or exploited, PT price could inflate unnaturally (leading to an attack of using over-priced PT to borrow, and get away with free money leading to bad protocol debt after PT price drops sharply after), or drops sharply (leading to bad debt for the protocol) * Assessment: * Pendle's oracle for PT/asset is permissionless and built into the contract (no maintenance needed), hence liveness and correctness is not a concern. * The PT/asset oracle can return TWAP prices for customisable durations (within 65536 blocks, which is ~9 days for Ethereum), hence is not susceptible to short term or within-a-block manipulation of prices if the TWAP duration used is sufficient. ### 4. Insufficient PT liquidity for liquidation in a short duration When PT price drops significantly vs the borrowAsset (say for 20%) and doesn't bounce back, there might not be enough liquidity to liquidate PT collaterals for liquidatable loans, which might lead to bad debt. #### Assessment - Study 1: we want to make sure if PT/borrowAsset price drops significantly in one go, liquidators can liquidate the maximum possible liquidatable PT collaterals in profit * Assume the following parameters in the money market: * Collateral factor for PT: `cRatio` * Deposit limit for PT collateral: `dCap` (in dollars) * Let's say after a significant price drop, the maximum portion of loans (using PT collaterals) that will become liquidatable will be `k` (`k` = 0.3 might be a reasonable number. 30% of the borrow becomes liquidate-able in one single moment) * When a position becomes liquidatable, liquidators can liquidate a portion `f` of the liquidatable position (usually 50% in most money markets) * The profit for liquidators is `p` (usually ~5-10% in money markets) * When PT/borrowAsset price drops significantly, we will need to liquidate a maximum of `dCap * cRatio * k` worth of borrow. The maximum amount liquidators can repay and liquidate is `dCap * cRatio * k * f`. The collateral dollar value that liquidators will get (and need to sell) is `dCap * cRatio * k * f * (1+p)` * To make a profitable liquidation, we need to be able to sell these PT collaterals for < `p` price impact (since the profit is `p`) * To sell PT collaterals to borrowAsset, we need to sell PT to SY (a yield bearing position of PT's asset) via Pendle's market, convert SY to asset and sell asset for borrowAsset * Assuming minimal price impact for selling asset to borrowAsset (might not hold for non-bluechip borrow asset), **we just need to make sure that we could sell `dCap * cRatio * k * f * (1+p)` worth of PT with less than `p` price impact.** * Example for listing PT-stETH-Dec2025 in CompoundV2 with a collateral factor `cRatio` of 0.70 * `cRatio = 0.70` * Assume `k = 0.30` * `f = 0.5` in Compound * `p = 0.08` in Compound * Hence, selling `dCap * 0.70 * 0.30 * 0.5 * (1+0.08) = 0.1134 dCap` worth of PT should have a price impact of lower than 8% * As of writing (2 Jun 2023), selling $1M of PT-stETH-Dec2025 has a price impact of 2.5% * Therefore, a `dCap` of $1M / 0.1134 = $8.8M is already pretty safe (since it will be corresponding to a sell of $1M of PT, which only has a 2.5% price impact, well less than 8%) * This could be used as a framework to gauge how reasonable certain numbers for `dCap`, `cRatio` could be for certain PT collateral. * **Important note**: There is a cascading effect, where PT price dropping due to the collateral sell could lead to more liquidation. Ideally, the factor `k` should already take this into account (basically, after all the cascading effect from PT price dropping due to liquidations, what's the proportion of PT-collateralised loans that will become liquidatable). In the most extreme assumption, we could assume `k=1` for the strictest analysis. #### Assessment - On collateral factor for PT * Study 1 is already the strictest analysis, where all the liquidatble PT collaterals can be liquidated in one go. * Due to the nature of Pendle AMM which is specialised for trading PTs (it concentrates liquidity, taking into account how PT will converge to the underlying asset), price impact for selling an amount of PT is much smaller than a normal pool of the same liquidity can provide. * When setting collateral factor for PT, the bottom line is to protect the money market from bad debt, in case PT/borrowAsset price drops so fast that liquidations can't happen fast enough to liquidate the liquidatable accounts. * In terms of "being able to liquidate PTs fast enough", there are two factors: 1. Liquidity for liquidating PTs, to support liquidating PTs in a short duration. 2. The liquidation system which needs to be functional and reacts fast enough to liquidate in time * For i: it's already covered in Study 1, so theoretically if the equation (taking into account the important note) in Study 1 holds, it's good (even if cRatio is 0.90). This is assuming a highly efficient liquidation system that could liquidate immediately. * For ii: it depends on how mature and decentralised the liquidation ecosystem for the money market is. If the liquidators are highly active/efficient, collateral factors could be set higher * In another approach for thinking about setting collateral factor for PT: it could be similar to the collateral factor for PT's asset in the money market, since PT price will fluctuate along the asset's price. * Since it's generally troublesome to decrease collateral factor and much easier to increase it, it's generally a good approach to start with more conservative collateral factors ### 5. Highly volatile PT price could liquidate users unnecessarily * If PT prices are too volatile, a temporary dip in PT price could liquidate certain users unnecessarily * Assessment: * In normal circumstances, PT price in terms of asset should not fluctuate as wildly as normal asset prices since it's based on people trading interest rates (which don't change too often). * Depending on the nature of the PT collateral (nature of the interest rate, and liquidity of the pool), an appropriate TWAP duration could be used in the oracle, to minimise liquidations due to temporary dips in PT prices. --- # LP as Collateral in a Money Market Pendle pools' LP tokens are good collaterals in a money market since it is a yield bearing position on the asset. This document discusses the use cases for LP as a collateral, as well as considerations for a money market when integrating LP as a collateral. ## Main Use Cases #### 1. Leverage farming Example: LP-stETH gives a 12% APY, but the ETH borrow rate is only 3% in the money market. * In this case, a user can deposit LP-stETH as collateral, borrow ETH, swap borrowed ETH to more LP-stETH to use as more collateral, and so on. * As a result, the user will get a leveraged APY in ETH terms, benefiting from the difference between the Pendle LP APY and ETH borrow rate * If the collateral factor is 0.80, the user can leverage 5x their capital to get a maximum APY of 5 * (12-3) = 45% This use case is similar to depositing a yield bearing asset like wstETH and borrowing (ETH) in a money market. #### 2. Leverage long assets while earning yields on Pendle * If a user is bullish on an asset in the long term, they can use its Pendle LP as a collateral to borrow stables to buy more LP * For example, a long term ETH holder can use LP-stETH as collateral to borrow USDC to buy more LP-stETH * Essentially, the user will be getting a good returns from the Pendle LP position on top of their leveraged long position on ETH. ## Risk analysis for LP as a collateral ### 1. Smart contract vulnerability in Pendle contracts: * This is similar to the analysis in the [risk analysis for PT as a collateral](./PTAsCollateral.md#1-smart-contract-vulnerability-in-pendle-contracts) ### 2. Smart contract vulnerability in underlying protocols: * This is similar to the analysis in the [risk analysis for PT as a collateral](./PTAsCollateral.md#2-smart-contract-vulnerability-in-underlying-protocols) ### 3. Oracle exploit: * If the oracle for LP price is easily manipulated or exploited, LP price could inflate unnaturally (leading to an attack of using over-priced LP to borrow, and get away with free money leading to bad protocol debt after LP price drops sharply after), or drops sharply (leading to bad debt for the protocol) * Assessment: * Pendle's oracle for LP/asset builds on top of the PT/asset oracle. The PT/asset oracle is permissionless and built into the contract (no maintenance needed), hence liveness and correctness is not a concern. * The LP/asset oracle returns TWAP prices for any customisable duration (within 65536 blocks, which is ~9 days for Ethereum), hence is not susceptible to short term or within-a-block manipulation of prices if the TWAP duration used is sufficient. * **Important note**: * You should only use the current LP oracle is the `SY` contract doesn't have a callback function. If the `SY` contract has a callback function, it is technically possible for the oracle to return an incorrect LP price, if it's called inside a SY's callback function. * It should be very rare to have a SY with a callback function. If you do integrate with one, you can contact us. We can deploy a specialised oracle to deal with a SY with a call back function. ### 4. Insufficient LP liquidity for liquidation in a short duration The considerations for this part is the same as the ones for integrating PT as collateral [here](./PTAsCollateral.md#4-insufficient-pt-liquidity-for-liquidation-in-a-short-duration). The difference between LP and PT is just that LP prices fluctuate less than PT prices (because LP = PT + SY). Therefore, with the same pool, the parameters for supporting LP as a collateral could more more aggressive than that for supporting PT as a collateral. ### 5. Highly volatile LP price could liquidate users unnecessarily * This is similar to the analysis in the [risk analysis for PT as a collateral](./PTAsCollateral.md#5-highly-volatile-pt-price-could-liquidate-users-unnecessarily) --- # Sanity checks for PT Integrations ### Sanity check for PT Oracle After integrating an oracle for PT price (assuming it's a PT/stable price), you should do these steps to quickly check the oracle's correctness: 1. Get the TWAP PT price from your oracle implementation. 2. Go to Pendle Market page for the PT, click the "Price" tab and check the PT price in terms of the underlying asset. In almost all cases, PT absolute price (in terms of the underlying asset) does not fluctuate that much over 30m-1hr. 3. Multiply the price in step 2 with the price of the underlying asset. Please take note which underlying asset it is from the Pendle Frontend. ![Step 3](/pendle-dev-docs/imgs/checkPtPrice.jpg "Step 3") 4. Compare the price in step 1 with the price in step 3. They should be almost identical. If there's a difference of more than 0.2%, something is likely wrong and please ask the Pendle team to help double check. # Sanity checks for PT liquidity If you need to assess PT liquidity and PT price volatility (when doing risk assessment for PT as a collateral or other similar use cases), you should do these sanity checks: ### Check PT/underlying asset price volatility 1. Go to Pendle Market page for the PT, click the "Price" tab and check the volatility of PT/Underlying asset price 2. From there, you could gauge the volatility of PT/stable price ### Check PT liquidity You can check the price impact of different swap sizes (in either direction) for PT 1. Go to Pendle Market page for the PT, click the "Price" tab 2. Simulate a trade to sell or buy PT from the underlying asset 3. Check the price impact ![Check price impact](/pendle-dev-docs/imgs/PTPriceImpact.jpg "Check price impact") --- # Pendle Spark Linear Discount Oracle ## Overview **PendleSparkLinearDiscountOracle** is a simple, deterministic feed that returns a **discount factor** for a given PT. The factor increases linearly as time passes and converges to 1.0 at maturity, so integrators can price PT conservatively without relying on external market data. The contract exposes familiar read functions (`latestRoundData`, `decimals`) so money markets can plug it in like a Chainlink-compatible feed. The returned value is a **multiplier in 18 decimals**, not a USD price. Please see the [Choosing Linear Discount Parameters](./ChoosingLinearDiscountParams.md) documentation for guidance on selecting appropriate linear discount parameters. ## How to price PT The oracle provides the PT price denominated in the **accounting asset staked in the underlying protocol** (indicated in brackets within the PT name). To convert this value to USD, you need to account for the SY-to-accounting-asset exchange rate using the PY Index. ### Formula ``` Price of PT (USD) = (Oracle discount factor) / (PYIndex) × (Yield Token USD Price) ``` The yield token price can be sourced from market data or derived from exchange rate to accounting asset - the choice depends on your requirements. #### Where to get the PYIndex The PYIndex can be retrieved from the YT (Yield Token) contract: - **`pyIndexCurrent()`**: Returns the most up-to-date PY index. This is a state-changing function. - **`pyIndexStored()`**: Returns the cached PY index without updating. This is a view function. See the [Yield Tokenization documentation](../../Contracts/YieldTokenization/YieldTokenization) for more details on PY index behavior. ### Simplified Formula If your use case does not require accounting for yield token depeg risks, you can ignore the PYIndex and use this simplified version: ``` Price of PT (USD) = (Oracle discount factor) × (Accounting Asset USD Price) ``` ### Pricing Examples - **`PT-kHYPE (HYPE)`**: The oracle returns the price in HYPE staked in Kinetiq. To convert to USD: ``` PT-kHYPE USD price = (Oracle discount factor) / (kHYPE PYIndex) × (kHYPE USD price) ``` Assuming kHYPE can always be redeemed fully for HYPE (no exchange rate depeg), you can use the simplified formula: ``` PT-kHYPE USD price = (Oracle discount factor) × (HYPE USD price) ``` - **`PT-sUSDe (USDe)`**: The oracle returns the price in USDe staked in the sUSDe contract. To convert to USD: ``` PT-sUSDe USD price = (Oracle discount factor) / (sUSDe PYIndex) × (sUSDe USD price) ``` Assuming sUSDe can always be redeemed fully for USDe (no exchange rate depeg), you can use the simplified formula: ``` PT-sUSDe USD price = (Oracle discount factor) × (USDe USD price) ``` - **`PT-USDe (USDe)`**: The oracle returns the price directly in USDe. Since the PYIndex is always 1, we can simplify the formula to: ``` PT-USDe USD price = (Oracle discount factor) × (USDe USD price) ``` ## Use cases For money markets listing PTs as collateral or borrowable assets, the oracle provides a stable, predictable valuation path. Integrators can use the discount factor to calculate conservative PT valuations without relying on AMM prices. Because the factor converges linearly to 1.0 at expiry, LTVs and liquidation thresholds behave smoothly instead of whipsawing with AMM volatility. ## Usage ### Deployment The oracle can be deployed using the `sparkLinearDiscountOracleFactory`. The address of the factory can be found in the [Deployments on GitHub](https://github.com/pendle-finance/pendle-core-v2-public/tree/main/deployments), under the `"sparkLinearDiscountOracleFactory"` field. The factory has two methods to help with oracle deployment: ```sol function createWithPt(address pt, uint256 baseDiscountPerYear) external returns (address); function createWithMarket(address market, uint256 baseDiscountPerYear) external returns (address); ``` The second method will automatically derive the PT address from the market, which is useful for Pendle markets where the PT is derived from the underlying asset. ### Feed retrieval ```sol function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) ``` Since the oracle is deterministic, all fields except `answer` are returned as `0`. #### Feed properties - **Decimals**: The oracle returns values in 18 decimals. Integrators should multiply this discount factor by the PT's redemption value (in 18 decimals) to calculate the PT's current value. - **Deterministic**: The oracle is deterministic, meaning the same inputs will always yield the same output. This allows for predictable valuation paths. - **Linear Discount**: The discount factor increases linearly over time, converging to 1.0 at maturity. This means the factor is always between 0 and 1, and it never goes negative. - **No External Calls**: The oracle does not make any external calls during reads, ensuring minimal liveness or manipulation surface. It relies solely on `block.timestamp` for time calculations. - **Clamping**: The oracle clamps the discount factor to never exceed 100% (1e18), ensuring it remains a valid multiplier. Here is a graphic showing how different `baseDiscountPerYear` values affect the discount factor over time, with the x-axis representing time left until maturity in years and the y-axis showing the discount factor (1.0 = 100%):
How answer is deterministically calculated The `answer` relies on two parameters: - `baseDiscountPerYear` - the annual discount slope, expressed in wad (1e18 = 100%/year). - `maturity` - the PT maturity timestamp in seconds (`PT.expiry()`). The `answer` at a given time `t` (in seconds) is calculated as follows: $$ \text{answer} = \min\left( 10^{18}, 10^{18} - \frac{(\text{maturity} - t) \cdot \text{baseDiscountPerYear}}{365 \cdot 24 \cdot 60 \cdot 60} \right) $$
--- # Choosing Linear Discount Parameters In this documentation, we provide a Desmos graphic that let you fill in parameters to help visualizing and choosing a good linear discount rate for the linear discount oracle. Desmos link: https://www.desmos.com/calculator/xmtoxy6lkt The Desmos link contains the example for the [USDe 25 SEP 2025 market]. - PT linear discount rate can be chosen to be $18\%$. - LP linear discount rate can be chosen to be $7\%$. See this in the Desmos graphic by changing $r_{discount}$ in the corresponding folder. ## Parameters ### Market internal state The Desmos graphic requires some internal state of the Pendle market. The following notebook can be used to get the market’s internal state: https://colab.research.google.com/drive/1CKltGGLWrdUoCVRpV1U9ZV2BqjMkN_OE?usp=sharing For example, if the chosen market is [USDe 25 SEP 2025 market], the parameters can be obtained like the image on the left, and be filled as the image on the right. | Running the notebook | Filling the parameters in Desmos | |----------------------|----------------------------------| | ![Params notebook](/pendle-dev-docs/imgs/Oracles/ChoosingLinearDiscountParams/params-notebook.png) | ![Params Desmos](/pendle-dev-docs/imgs/Oracles/ChoosingLinearDiscountParams/params-desmos.png) | ### Market yield range These parameters are opinionated. Please choose a yield range that you think the market will be trading in until maturity. The lower the $\text{rateMax}$, the less underprice the oracle will be, and the more capital efficient for the users. Pendle Market also allows trading within a pre-determined yield range. If you are not sure about the yield range, use this. This yield range can be obtained from the Pendle DApp UI: | Click the spec button | Getting the yield range | |------------------------|----------------------------------| | ![image.png](/pendle-dev-docs/imgs/Oracles/ChoosingLinearDiscountParams/ui-market-specs.png) | ![image.png](/pendle-dev-docs/imgs/Oracles/ChoosingLinearDiscountParams/ui-market-yield-range.png) | And filled it as the following: ![image.png](/pendle-dev-docs/imgs/Oracles/ChoosingLinearDiscountParams/yield-range-desmos.png) ## Choosing between PT linear oracle and LP linear oracle The Desmos graphic can help visualize both linear discount. The corresponding graphic can be toggled by clicking the corresponding folder