Skip to main content

How to Integrate PT and LP Oracle

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

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

In this documentation and the code example, EtherFi's weETH market on Arbitrum, which has 70M USD liquidity at block 192 001 277, is chosen.

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 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).
function _test_oracle_ready(address marketToCheck, uint32 duration) public view {
(bool increaseCardinalityRequired, , bool oldestObservationSatisfied) =
oracle.getOracleState(marketToCheck, duration);

if (increaseCardinalityRequired) {
// <=============== (1) - Oracle need 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)9001800
For Ethereum85165
For Arbitrum9001800

So on Ethereum, for duration of 900 seconds, cardinalityRequired can be 85.

Calculate cardinalityRequired

In general, it can calculated like this

cardinalityRequireddurationmax{chain block time,1}\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 tab to choose the one that best suits your needs.

We have provided a way to 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 are not already deployed, you can deploy yourself the same as this setUp function (source).

code fragment of setUp function
factory = new PendleChainlinkOracleFactory(0x9a9Fa8338dd5E5B2188006f1Cd2Ef26d921650C2);
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.
  • 0x9a9Fa8338dd5E5B2188006f1Cd2Ef26d921650C2 is the address of the deployed Pendle Oracle.
    • It was deployed to have the same address for all network.
    • Refer to Deployment 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 deploy the oracle, you need to specified 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 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
}
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 XX SY-sUSDe = XX 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.


Because of this, in case when sUSDe depegged from USDe, the conversion rate between PT-sUSDe to sUSDe is still correct, because the conversion rate is based on SY.exchangeRate(), which is provided by the underlying contract.

About unit and decimals

As the decimals is a token metadata, and only be used to show the amount to the users, Pendle contract does not operate with decimals. See the documentation about unit and decimals for more info.

Let's say you want to know how much XX LP is worth in SY.

First, we convert XX into the raw format so that the contract can understand.

rawX=X×10LP.decimals\mathtt{rawX} = X \times 10^{\mathtt{LP}.\mathtt{decimals}}

After obtaining the lpToSyRate, we convert rawX to the raw form of SY as follows:

rawY=rawX×lpToSyRate/1018\mathtt{rawY} = \mathtt{rawX} \times \mathtt{lpToSyRate} / 10^{18}

Note that 101810^{18} is a fixed number in the formula, and does not depend on any decimals data.

Finally, we can convert rawY to the human-readable format:

Y=rawY/10SY.decimalsY = \mathtt{rawY} / 10^{\mathtt{SY}.\mathtt{decimals}}

Conversion between PT/YT and SY is done similarly.

As Pendle support a wide variety of assets, each with a different decimals, this fact is important to remember.

Handle liquidation & LP's rewards

For PT liquidation

  • When a liquidation with PTPT as collateral occurs, commonly, the liquidator will have to sell PTPT into common asset to pay their debt.
  • In Pendle's system, we support converting PTPT back to SYSY by selling PTPT on our AMM (before maturity) or redeeming directly from PendleYieldToken contract (post maturity). This will then allow the liquidator to redeem their SYSY into one of the output token of SYSY (see EIP-5115).
  • For reference, we have written the BoringPtSeller contract to sell PTPT into one of the output token.
  • You can extend this abstract contract to use in a liquidation system.

For LP liquidation

  • When a liquidation with LPLP as collateral occurs, commonly, the liquidator will have to sell LPLP into common asset to pay their debt.
  • In Pendle's system, we support converting LPLP back to SYSY by removing liquidity single-sided into SYSY on our AMM (before maturity) or redeeming PTPT + SYSY and redeeming PTPT to SYSY directly from PendleYieldToken contract (post maturity). This will then allow the liquidator to redeem their SYSY into one of the output token of SYSY (see EIP-5115).
  • For reference, we have written the BoringLpSeller contract to sell LPLP into one of SY's output tokens.
  • You can extend this abstract contract to use in a liquidation system.

Handling of Pendle LP's rewards:

  • Holding Pendle LP tokens will generate PENDLE incentives and potential reward tokens (like WETH for LP for GLP pool)
  • The money market contracts will need to redeem these rewards by calling the redeemRewards function and implement logic to distribute these rewards to their users