Diffusal
Contracts

DiffusalOptionsQuoter

Read-only Black-Scholes option pricing with Greeks calculation

The DiffusalOptionsQuoter contract provides indicative option pricing using the Black-Scholes model. It's a read-only contract—all functions are view-only and don't modify state. The quoter calculates theoretical option prices and Greeks (delta, gamma, vega, theta, rho) using market data from the unified oracle.


Overview

The quoter serves two primary purposes:

PurposeDescription
Price DiscoveryCalculate theoretical Black-Scholes prices for any option
Risk MetricsCompute Greeks for portfolio risk management

All prices returned are indicative only. Actual trade execution happens through the OrderBook or RFQ contracts at market-determined prices.

API Variants

VariantGreeksUse Case
getOptionQuoteNoGas-optimized price-only queries
getOptionQuoteWithGreeksYesFull pricing with risk metrics
*ByNameEitherHuman-readable pair names (e.g., "ETH-USD")
*BatchEitherMultiple quotes in one call

Key Concepts

Black-Scholes Model

The quoter implements the standard Black-Scholes formula:

C=SN(d1)KerTN(d2)C = S \cdot N(d_1) - K \cdot e^{-rT} \cdot N(d_2) P=KerTN(d2)SN(d1)P = K \cdot e^{-rT} \cdot N(-d_2) - S \cdot N(-d_1)

Where:

  • d1=ln(S/K)+(r+σ2/2)TσTd_1 = \frac{\ln(S/K) + (r + \sigma^2/2)T}{\sigma\sqrt{T}}
  • d2=d1σTd_2 = d_1 - \sigma\sqrt{T}

Greeks

GreekSymbolMeasuresRange
DeltaΔPrice sensitivity to spot-1 to 1
GammaΓDelta sensitivity to spot≥ 0
VegaνPrice sensitivity to volatility≥ 0
ThetaΘTime decay (per year)Usually < 0
RhoρPrice sensitivity to interest rateVaries

Precision

All inputs and outputs use WAD precision (1e18 = 1.0):

ValueWAD Representation
$3,000 spot3000e18
50% volatility0.5e18 = 500000000000000000
0.25 delta0.25e18 = 250000000000000000

Storage & State

The quoter has minimal storage—just owner and oracle reference:

/// @custom:storage-location erc7201:diffusal.storage.DiffusalOptionsQuoter
struct DiffusalOptionsQuoterStorage {
    address owner;
    address oracle;  // Unified DiffusalOracle
}

Structs

OptionRequest

Parameters for pricing an option:

struct OptionRequest {
    bytes32 pairId;  // Trading pair identifier
    uint256 strike;  // Strike price (WAD)
    uint256 expiry;  // Expiry timestamp (unix seconds)
    bool isCall;     // true = call, false = put
}

OptionRequestByName

Same as above but with human-readable pair name:

struct OptionRequestByName {
    string pairName;  // e.g., "ETH-USD"
    uint256 strike;
    uint256 expiry;
    bool isCall;
}

OptionQuote

Gas-optimized quote (price only):

struct OptionQuote {
    uint256 price;  // Black-Scholes price (WAD)
}

OptionQuoteWithGreeks

Full quote with all Greeks:

struct OptionQuoteWithGreeks {
    uint256 price;   // Black-Scholes price (WAD)
    int256 delta;    // Delta (-1e18 to 1e18)
    int256 gamma;    // Gamma (WAD)
    int256 vega;     // Vega (WAD)
    int256 theta;    // Theta per year (WAD)
    int256 rho;      // Rho (WAD)
}

OracleQuote

Raw oracle data used for pricing:

struct OracleQuote {
    int64 price;           // Spot price (8 decimals)
    int64 volatility;      // Implied volatility (8 decimals)
    int64 premium;         // Premium rate (8 decimals)
    int64 riskFreeRate;    // Risk-free rate (8 decimals)
    int32 priceExpo;       // Price exponent
    uint256 pricePublishTime;  // Price timestamp
}

View Functions

Option Pricing

getOptionQuote

Returns gas-optimized price without Greeks.

function getOptionQuote(OptionRequest calldata request) external view returns (OptionQuote memory quote)

Example:

OptionRequest memory req = OptionRequest({
    pairId: keccak256(bytes("ETH-USD")),
    strike: 3000e18,      // $3000 strike
    expiry: block.timestamp + 7 days,
    isCall: true
});

OptionQuote memory quote = quoter.getOptionQuote(req);
// quote.price = theoretical call price in WAD

getOptionQuoteByName

Same as above but uses human-readable pair name.

function getOptionQuoteByName(OptionRequestByName calldata request) external view returns (OptionQuote memory quote)

Example:

OptionRequestByName memory req = OptionRequestByName({
    pairName: "ETH-USD",
    strike: 3000e18,
    expiry: block.timestamp + 7 days,
    isCall: true
});

OptionQuote memory quote = quoter.getOptionQuoteByName(req);

getOptionQuoteBatch

Returns multiple quotes in a single call.

function getOptionQuoteBatch(OptionRequest[] calldata requests) external view returns (OptionQuote[] memory quotes)

getOptionQuoteWithGreeks

Returns full pricing with all Greeks.

function getOptionQuoteWithGreeks(OptionRequest calldata request) external view returns (OptionQuoteWithGreeks memory quote)

Example:

OptionQuoteWithGreeks memory quote = quoter.getOptionQuoteWithGreeks(req);

// quote.price  = theoretical price
// quote.delta  = 0.65e18 means 65% delta
// quote.gamma  = change in delta per $1 spot move
// quote.vega   = price change per 1% vol increase
// quote.theta  = daily decay = quote.theta / 365
// quote.rho    = price change per 1% rate increase

getOptionQuoteWithGreeksByName

Full pricing with Greeks using pair name.

function getOptionQuoteWithGreeksByName(OptionRequestByName calldata request) external view returns (OptionQuoteWithGreeks memory quote)

getOptionQuoteWithGreeksBatch

Batch full pricing with Greeks.

function getOptionQuoteWithGreeksBatch(OptionRequest[] calldata requests) external view returns (OptionQuoteWithGreeks[] memory quotes)

Oracle Data

getOracleQuote

Returns raw oracle data for a trading pair.

function getOracleQuote(bytes32 pairId) external view returns (OracleQuote memory quote)

getOracleQuoteByName

Oracle data using human-readable pair name.

function getOracleQuoteByName(string calldata pairName) external view returns (OracleQuote memory quote)

getOracleQuoteNoOlderThan

Oracle data with staleness check.

function getOracleQuoteNoOlderThan(bytes32 pairId, uint256 maxPriceAge) external view returns (OracleQuote memory quote)

Reverts: StalePrice if price is older than maxPriceAge seconds.


getVolatility

Returns only the volatility for a pair.

function getVolatility(bytes32 pairId) external view returns (int64 volatility)

getPrice

Returns only the spot price components.

function getPrice(bytes32 pairId) external view returns (int64 price, int32 expo, uint256 publishTime)

getPremium

Returns only the premium rate.

function getPremium(bytes32 pairId) external view returns (int64 premium)

Utility

getPairId

Generates a pair ID from asset symbols.

function getPairId(string calldata base, string calldata quote) external pure returns (bytes32 pairId)

Example:

bytes32 pairId = quoter.getPairId("ETH", "USD");
// Returns keccak256("ETH-USD")

Owner Functions

setOracle

Updates the unified oracle address.

function setOracle(address _oracle) external onlyOwner

Emits: DiffusalOracleUpdated, PriceOracleUpdated


transferOwnership

Transfers contract ownership.

function transferOwnership(address newOwner) external onlyOwner

Emits: OwnershipTransferred


Events

EventParametersDescription
OwnershipTransferredpreviousOwner, newOwnerOwnership changed
DiffusalOracleUpdatedoldOracle, newOracleOracle address updated
PriceOracleUpdatedoldOracle, newOracleOracle address updated

Integration Points

Depends On

ContractPurpose
DiffusalOracleSpot price, volatility, premium, risk-free rate
BlackScholesWadBlack-Scholes pricing library

Used By

ContractPurpose
DiffusalCollateralVaultMark price for unrealized PnL
DiffusalLiquidationEngineMark price for liquidation
Frontend / Off-chainPrice discovery, portfolio analytics

Gas Considerations

FunctionApprox. GasNotes
getOptionQuote~50kPrice only, no Greeks
getOptionQuoteWithGreeks~120kFull Greeks calculation
normalCdf (internal)~20kMost expensive operation

Optimization tips:

  • Use getOptionQuote instead of getOptionQuoteWithGreeks when you don't need Greeks
  • Use batch functions to share oracle calls across multiple quotes
  • Cache pair IDs instead of using *ByName variants repeatedly

Security Considerations

Read-Only Design

The quoter is intentionally stateless (aside from owner/oracle config). All pricing functions are view-only, so:

  • No reentrancy risks from pricing calls
  • No state manipulation possible
  • Safe to call from any context

Price Staleness

The quoter doesn't enforce price staleness by default. Use getOracleQuoteNoOlderThan when freshness matters:

// Enforce 60-second freshness
OracleQuote memory quote = quoter.getOracleQuoteNoOlderThan(pairId, 60);

Indicative Prices Only

Important: Quotes from this contract are theoretical Black-Scholes prices, not execution prices. Actual trade prices are determined by:

  • Order book market makers (bid/ask spread)
  • RFQ quotes from the Main Market Maker

The theoretical price serves as a reference, but expect execution prices to differ based on market conditions.

Precision Boundaries

The quoter handles precision conversions internally:

Oracle (8 decimals) → WAD (18 decimals) → 64.64 fixed-point → WAD output

Edge cases to be aware of:

  • Very small time-to-expiry values (near expiration)
  • Extreme spot/strike ratios (deep ITM/OTM)
  • Very low or very high volatility values

Code Reference

Source: packages/contracts/src/DiffusalOptionsQuoter.sol

Interface: packages/contracts/src/interfaces/IDiffusalOptionsQuoter.sol

Testnet: View on MonadVision

Internal Pricing Flow

// 1. Build Black-Scholes parameters
BlackScholesWad.ParamsWad memory params = _buildBsParams(pairId, strike, expiry);

// 2. Calculate price (with or without Greeks)
uint256 price = BlackScholesWad.priceOnly(params, isCall);
// or
BlackScholesWad.PriceResultWad memory result = BlackScholesWad.priceCall(params);

Time to Expiry Calculation

function _calculateTimeToExpiry(uint256 expiry) internal view returns (uint256) {
    if (expiry <= block.timestamp) revert Errors.OptionExpired();
    uint256 timeRemaining = expiry - block.timestamp;
    // Convert seconds to years as WAD
    return (timeRemaining * Constants.WAD) / Constants.SECONDS_PER_YEAR;
}

On this page