Skip to main content

Linear Discount Oracle Wrapper

Sources:

Overview

PendleLinearDiscountOracleWrapper is a thin stateless wrapper that fixes the one incompatibility between Pendle's deterministic oracles and Chainlink-consuming protocols: the updatedAt field.

Pendle's deterministic oracles (PendleSparkLinearDiscountOracle, PendleLpLinearDiscountOracle) return updatedAt = 0 in latestRoundData() because their prices are computed entirely from block.timestamp and immutable constructor parameters — they are never "stale" by design. However, most lending protocols enforce a staleness check of the form block.timestamp - updatedAt < threshold, which will reject any feed that returns updatedAt = 0.

The wrapper solves this by forwarding all fields from the inner oracle unchanged, except updatedAt, which it overwrites with block.timestamp.


When to Use the Wrapper

Does your protocol check updatedAt staleness?

  • No — Deploy PendleSparkLinearDiscountOracle directly via PendleSparkLinearDiscountOracleFactory.
  • Yes (most lending protocols) — Deploy via PendleSparkLinearDiscountOracleFactoryWrapper. The wrapper always returns updatedAt = block.timestamp, satisfying any staleness threshold.
  • You already have a bare oracle — Call PendleSparkLinearDiscountOracleFactoryWrapper.wrap(existingOracle) to add the wrapper without redeploying.
LP oracle has no factory wrapper

PendleLpLinearDiscountOracle does not have a dedicated factory wrapper contract. To apply the wrapper to an LP oracle, deploy PendleLpLinearDiscountOracle via PendleLpLinearDiscountOracleFactory, then wrap the deployed address manually:

address lpOracle   = lpFactory.create(market, baseLpDiscountPerYear, lpMaturedPrice);
address lpWrapped = address(new PendleLinearDiscountOracleWrapper(lpOracle));

PendleLinearDiscountOracleWrapper

A minimal proxy that wraps any IPChainlinkOracleEssential-compatible oracle and replaces its updatedAt with block.timestamp.

contract PendleLinearDiscountOracleWrapper is IPChainlinkOracleEssential {
IPChainlinkOracleEssential public immutable innerOracle;

constructor(address _innerOracle);
}

latestRoundData

function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt, // overwritten to block.timestamp
uint80 answeredInRound
);

All fields are forwarded from innerOracle.latestRoundData(), except updatedAt which is replaced with block.timestamp. The answer (the discount factor or LP price) is not modified in any way.

decimals

function decimals() external view returns (uint8);

Delegates directly to innerOracle.decimals(). Returns 18 for all Pendle deterministic oracles.


PendleSparkLinearDiscountOracleFactoryWrapper

A convenience contract that combines PendleSparkLinearDiscountOracleFactory and PendleLinearDiscountOracleWrapper into a single deployment step. Calling createWithPt or createWithMarket on this contract:

  1. Deploys a PendleSparkLinearDiscountOracle (via the inner factory)
  2. Immediately wraps it in PendleLinearDiscountOracleWrapper
  3. Returns the wrapper address — the address your protocol should point at

Emits WrapperOracleCreated(address indexed innerOracle, address indexed wrapper) on each deployment.

contract PendleSparkLinearDiscountOracleFactoryWrapper {
PendleSparkLinearDiscountOracleFactory public immutable innerFactory;

constructor(address _innerFactory);
}

createWithPt

function createWithPt(
address pt,
uint256 baseDiscountPerYear
) external returns (address wrapper);

Deploys a PendleSparkLinearDiscountOracle for the given PT address, then wraps it. Returns the wrapper address.

createWithMarket

function createWithMarket(
address market,
uint256 baseDiscountPerYear
) external returns (address wrapper);

Derives the PT address from IPMarket(market).readTokens(), then proceeds identically to createWithPt.

wrap

function wrap(address innerOracle) public returns (address wrapper);

Wraps an already-deployed IPChainlinkOracleEssential-compatible oracle in a new PendleLinearDiscountOracleWrapper. Useful if you deployed an oracle via PendleSparkLinearDiscountOracleFactory directly and need to add the wrapper afterwards.

Deployment addresses: Find the factory wrapper address for each chain under "sparkLinearDiscountOracleFactoryWrapper" in the Deployments on GitHub.


Further Reading