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
PendleSparkLinearDiscountOracledirectly viaPendleSparkLinearDiscountOracleFactory. - Yes (most lending protocols) — Deploy via
PendleSparkLinearDiscountOracleFactoryWrapper. The wrapper always returnsupdatedAt = block.timestamp, satisfying any staleness threshold. - You already have a bare oracle — Call
PendleSparkLinearDiscountOracleFactoryWrapper.wrap(existingOracle)to add the wrapper without redeploying.
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:
- Deploys a
PendleSparkLinearDiscountOracle(via the inner factory) - Immediately wraps it in
PendleLinearDiscountOracleWrapper - 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
- Spark Linear Discount Oracle — full contract reference for the PT oracle
- LP Linear Discount Oracle — full contract reference for the LP oracle
- Choosing Linear Discount Parameters — guidance on setting
baseDiscountPerYear