How to Integrate PT and LP Oracle
For most new integrations, Pendle now recommends the Linear Discount Oracle — 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 and StandardizedYield docs to understand the Pendle system.
- Refer to the examples in the following repository for further understanding:
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.
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.
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).
- The passed in
durationis 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).
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:
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
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.
- Using ChainLink oracle
- Using Pendle Oracle
- Using Pendle Library
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:
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, or deploy it yourself the same as this setUp function (source).
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
marketis the market address you want to observe the price.twapDurationis the TWAP duration you want to use, chosen in the previous section.0x5542be50420E88dd7D5B4a3D488FA6ED82F6DAc2is the address of the deployed Pendle Oracle.- It was deployed to have the same address on all networks.
- Refer to Deployments on GitHub 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).
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:
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 section for the full list of addresses.
Getting the price can be done simply by calling the corresponding function (source).
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.
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:
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:
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.
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.