Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Plether Protocol

CI Coverage License: AGPL v3 Solidity

Plether is a DeFi protocol for synthetic dollar-denominated tokens with inverse and direct exposure to the US Dollar Index (USDX). Users deposit USDC to mint paired tokens that track USD strength, enabling speculation and hedging on dollar movements.

How It Works

The protocol creates two synthetic tokens from USDC collateral:

  • plDXY-BEAR - Appreciates when USD weakens (USDX falls)
  • plDXY-BULL - Appreciates when USD strengthens (USDX rises)

These tokens are always minted and burned in pairs, maintaining a zero-sum relationship. When you deposit 100 USDC, you receive equal amounts of both tokens. The combined value of a BEAR + BULL pair always equals the original USDC deposit.

User deposits USDC
        │
        ▼
┌───────────────────┐
│ SyntheticSplitter │
└───────────────────┘
        │
        ├──► plDXY-BEAR (gains when USD weakens)
        │
        └──► plDXY-BULL (gains when USD strengthens)

Architecture

Core Contracts

ContractDescription
SyntheticSplitterCentral protocol contract. Accepts USDC, mints/burns token pairs, manages yield deployment
SyntheticTokenERC20 + ERC20FlashMint implementation for plDXY-BEAR and plDXY-BULL
StakedTokenERC-4626 vault wrapper enabling yield accrual on deposited tokens

Oracle Layer

ContractDescription
BasketOracleComputes plDXY as weighted basket of 6 Chainlink feeds, with bound validation against Curve EMA price
MorphoOracleAdapts BasketOracle to Morpho Blue’s 36-decimal scale format
StakedOracleWraps underlying oracle to price ERC-4626 staked token shares

BasketOracle Design

The BasketOracle computes a USDX-like index using normalized arithmetic weighting rather than the geometric weighting of the official ICE USDX index:

Price = Σ(Weight_i × Price_i / BasePrice_i)

Each currency’s contribution is normalized by its base price, ensuring the intended USDX weights are preserved regardless of absolute FX rate scales. Without normalization, low-priced currencies like JPY ($0.007) would be nearly ignored compared to EUR ($1.08), causing severe weight distortion.

This design enables gas-efficient on-chain computation and eliminates rebalancing requirements, which guarantees protocol solvency.

Inverse Relationship: Because the oracle measures the USD value of a foreign currency basket, it moves inversely to the real USDX index. When the dollar strengthens, USDX rises but our basket value falls (foreign currencies are worth less in USD terms). This is why plDXY-BEAR appreciates when the basket value rises (dollar weakens).

Fixed Base Prices and Weights (immutable, set at deployment based on January 1, 2026 prices):

CurrencyWeightBase Price (USD)
EUR57.6%1.1750
JPY13.6%0.00638
GBP11.9%1.3448
CAD9.1%0.7288
SEK4.2%0.1086
CHF3.6%1.2610

Both weights and base prices are permanently fixed and cannot be changed after deployment.

Routing Layer

ContractDescription
ZapRouterSingle-sided plDXY-BULL minting and burning using flash mints
LeverageRouterLeveraged plDXY-BEAR positions via Morpho Blue flash loans (fee-free)
BullLeverageRouterLeveraged plDXY-BULL positions via Morpho + plDXY-BEAR flash mints

Yield Adapters (ERC-4626)

ContractDescription
MorphoAdapterERC-4626 wrapper for Morpho Blue yield generation

Staking & Rewards

ContractDescription
RewardDistributorDistributes USDC yield to StakedToken vaults, favoring the underperforming token

Ecosystem Integrations

                    ┌─────────────┐
                    │  Chainlink  │
                    │   Oracles   │
                    └──────┬──────┘
                           │ Price Feeds
                           ▼
┌─────────┐    USDC    ┌───────────────────┐
│  Users  │◄─────────►│      Plether      │
└─────────┘            │   (Splitter +     │
     │                 │    Routers)       │
     │                 └─────────┬─────────┘             ┌───────────┐
     │                           │         Yield + ────►│  Morpho   │
     │                           │         Lending      │   Blue    │
     │                           ▼                       └───────────┘
     │                 ┌─────────────────┐
     └────────────────►│   Curve AMM     │
        Swap Tokens    │  (USDC/BEAR)    │
                       └─────────────────┘
  • Chainlink - Price feeds for EUR/USD, JPY/USD, GBP/USD, CAD/USD, SEK/USD, CHF/USD
  • Curve Finance - AMM pools for USDC/plDXY-BEAR swaps
  • Morpho Blue - Lending markets for leveraged positions, yield generation on idle USDC reserves, and fee-free flash loans

Protocol Mechanics

Liquidity Management

The SyntheticSplitter maintains a 10% local buffer of USDC for redemptions, with 90% deployed to yield adapters. This generates yield while ensuring liquidity for normal operations.

If adapter liquidity is constrained (e.g., high Morpho utilization), the owner can pause the protocol and use withdrawFromAdapter() for gradual extraction as liquidity becomes available.

Leverage

Users can open leveraged positions through the routers:

  1. LeverageRouter (Bear): Morpho flash loan USDC → Swap to plDXY-BEAR → Stake → Deposit to Morpho as collateral → Borrow USDC to repay flash loan
  2. BullLeverageRouter (Bull): Morpho flash loan USDC → Mint pairs → Sell plDXY-BEAR → Stake plDXY-BULL → Deposit to Morpho → Borrow to repay

Morpho Blue provides fee-free flash loans, making leveraged positions more capital-efficient.

Both routers include MEV protection via user-defined slippage caps (max 1%).

Reward Distribution

The RewardDistributor receives yield from SyntheticSplitter and allocates it to StakedToken vaults based on the price discrepancy between the oracle and Curve EMA:

  • ≥2% discrepancy: 100% to underperforming token stakers
  • <2% discrepancy: Quadratic interpolation from 50/50 toward 100/0
  • 0% discrepancy: 50/50 split

This mechanism incentivizes arbitrageurs to correct price deviations by rewarding stakers of the underpriced token. A 0.1% caller reward incentivizes permissionless distribution.

Lifecycle States

The protocol operates in three states:

  1. ACTIVE - Normal operations (mint, burn, redeem)
  2. PAUSED - Emergency pause (minting blocked, burning allowed so users can exit, gradual adapter withdrawal enabled)
  3. SETTLED - End-of-life when plDXY hits CAP price (only redemptions allowed)

Development

Prerequisites

Build

forge build

Test

forge test              # Run all tests
forge test -vvv         # Verbose output
forge coverage          # Generate coverage report

Fork Tests

Fork tests run against mainnet state using real Chainlink oracles, Curve pools, and Morpho Blue. They require an RPC URL:

# Set RPC URL (or add to .env file)
export MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY

# Run all fork tests
forge test --match-path "test/fork/*.sol" --fork-url $MAINNET_RPC_URL -vvv

# Or source from .env
source .env && forge test --match-path "test/fork/*.sol" --fork-url $MAINNET_RPC_URL -vvv

Fork test files:

FileDescription
BaseForkTest.solShared base contract, constants, and test helpers
ZapRouterFork.t.solZapRouter integration with real Curve swaps
FullCycleFork.t.solComplete mint → yield → burn lifecycle
LeverageRouterFork.t.solBear and Bull leverage via real Morpho
SlippageProtectionFork.t.solMEV protection and slippage scenarios
LiquidationFork.t.solInterest accrual and liquidation mechanics
BasketOracleFork.t.solFull 6-feed plDXY basket oracle validation

Run a specific fork test file:

source .env && forge test --match-path test/fork/LeverageRouterFork.t.sol --fork-url $MAINNET_RPC_URL -vvv

Local Development (Anvil)

For frontend development and testing without spending real ETH:

# 1. Start local Anvil node forking Ethereum mainnet
anvil --fork-url $MAINNET_RPC_URL --chain-id 31337

# 2. Deploy all contracts (mints 100k USDC to deployer)
SEPOLIA_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
forge script script/DeployToSepolia.s.sol --tc DeployToSepolia \
  --rpc-url http://127.0.0.1:8545 \
  --broadcast

# 3. (Optional) Seed Morpho markets with 1M USDC each for leverage testing
#    Use addresses from step 2 output
USDC=<MockUSDC address> \
STAKED_BEAR=<StakedToken BEAR address> \
STAKED_BULL=<StakedToken BULL address> \
STAKED_ORACLE_BEAR=<StakedOracle BEAR address> \
STAKED_ORACLE_BULL=<StakedOracle BULL address> \
forge script script/SeedMorphoMarkets.s.sol \
  --rpc-url http://127.0.0.1:8545 \
  --broadcast

Anvil Test Accounts (pre-funded with 10,000 ETH each):

AccountAddressPrivate Key
#00xf39Fd6e51aad88F6F4ce6aB8827279cffFb922660xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
#10x70997970C51812dc3A010C7d01b50e0d17dc79C80x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d

MetaMask Setup:

  1. Import a test private key (Settings → Import Account)
  2. Add network: RPC http://127.0.0.1:8545, Chain ID 31337

Format

forge fmt               # Format code
forge fmt --check       # Check formatting

Documentation

View Reference Documentation

Generate HTML documentation locally from NatSpec comments:

forge doc               # Generate docs to ./docs
forge doc --serve       # Serve docs locally at http://localhost:3000
forge doc --build       # Build static site to ./docs/book

Security

  • All contracts use OpenZeppelin’s battle-tested implementations
  • Reentrancy protection on state-changing functions
  • 7-day timelock for critical governance changes
  • Oracle staleness checks (8-hour timeout)
  • Oracle bound validation against Curve EMA to prevent price manipulation
  • Flash loan callback validation (initiator + lender checks)
  • Yield adapter uses Morpho’s internal accounting (immune to donation attacks)

For detailed security assumptions, trust model, and emergency procedures, see SECURITY.md.

License

AGPL-3.0

Disclaimer

This software is provided “as is” without warranty of any kind. Use at your own risk. This protocol has not been audited. Do not use in production without a professional security audit.

Contents

FlashLoanBase

Git Source

Inherits: IERC3156FlashBorrower, IMorphoFlashLoanCallback

Title: FlashLoanBase

Abstract base for flash loan borrowers with validation logic.

Supports both Morpho flash loans and ERC-3156 flash mints.

State Variables

CALLBACK_SUCCESS

ERC-3156 callback success return value.

bytes32 internal constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan")

Functions

_validateFlashLoan

Validates ERC-3156 flash loan callback parameters.

function _validateFlashLoan(
    address lender,
    address expectedLender,
    address initiator
) internal view;

Parameters

NameTypeDescription
lenderaddressActual msg.sender.
expectedLenderaddressExpected flash lender address.
initiatoraddressInitiator passed to callback (must be this contract).

_validateLender

Validates that msg.sender is the expected lender.

function _validateLender(
    address lender,
    address expectedLender
) internal pure;

Parameters

NameTypeDescription
lenderaddressActual msg.sender.
expectedLenderaddressExpected flash lender address.

Errors

FlashLoan__InvalidLender

Thrown when flash loan callback called by wrong lender.

error FlashLoan__InvalidLender();

FlashLoan__InvalidInitiator

Thrown when flash loan initiator is not this contract.

error FlashLoan__InvalidInitiator();

FlashLoan__InvalidOperation

Thrown when callback receives unknown operation type.

error FlashLoan__InvalidOperation();

LeverageRouterBase

Git Source

Inherits: FlashLoanBase, Ownable2Step, Pausable, ReentrancyGuard

Title: LeverageRouterBase

Abstract base for leverage routers with shared validation and admin logic.

Common infrastructure for LeverageRouter (plDXY-BEAR) and BullLeverageRouter (plDXY-BULL).

State Variables

MAX_SLIPPAGE_BPS

Maximum slippage in basis points (1% = 100 bps).

uint256 public constant MAX_SLIPPAGE_BPS = 100

USDC_INDEX

USDC index in Curve USDC/plDXY-BEAR pool.

uint256 public constant USDC_INDEX = 0

PLDXY_BEAR_INDEX

plDXY-BEAR index in Curve USDC/plDXY-BEAR pool.

uint256 public constant PLDXY_BEAR_INDEX = 1

OP_OPEN

Operation type: open leverage position.

uint8 internal constant OP_OPEN = 1

OP_CLOSE

Operation type: close leverage position.

uint8 internal constant OP_CLOSE = 2

MORPHO

Morpho Blue lending protocol.

IMorpho public immutable MORPHO

CURVE_POOL

Curve pool for USDC/plDXY-BEAR swaps.

ICurvePool public immutable CURVE_POOL

USDC

USDC stablecoin.

IERC20 public immutable USDC

PLDXY_BEAR

plDXY-BEAR token.

IERC20 public immutable PLDXY_BEAR

marketParams

Morpho market configuration.

MarketParams public marketParams

Functions

constructor

Initializes base router with core dependencies.

constructor(
    address _morpho,
    address _curvePool,
    address _usdc,
    address _plDxyBear
) Ownable(msg.sender);

Parameters

NameTypeDescription
_morphoaddressMorpho Blue protocol address.
_curvePooladdressCurve USDC/plDXY-BEAR pool address.
_usdcaddressUSDC token address.
_plDxyBearaddressplDXY-BEAR token address.

pause

Pause the router. Blocks openLeverage and closeLeverage.

function pause() external onlyOwner;

unpause

Unpause the router.

function unpause() external onlyOwner;

Errors

LeverageRouterBase__ZeroAddress

Thrown when zero address provided.

error LeverageRouterBase__ZeroAddress();

LeverageRouterBase__ZeroPrincipal

Thrown when principal is zero.

error LeverageRouterBase__ZeroPrincipal();

LeverageRouterBase__ZeroCollateral

Thrown when collateral is zero.

error LeverageRouterBase__ZeroCollateral();

LeverageRouterBase__Expired

Thrown when deadline has passed.

error LeverageRouterBase__Expired();

LeverageRouterBase__LeverageTooLow

Thrown when leverage multiplier <= 1x.

error LeverageRouterBase__LeverageTooLow();

LeverageRouterBase__SlippageExceedsMax

Thrown when slippage exceeds MAX_SLIPPAGE_BPS.

error LeverageRouterBase__SlippageExceedsMax();

LeverageRouterBase__NotAuthorized

Thrown when user hasn’t authorized router in Morpho.

error LeverageRouterBase__NotAuthorized();

LeverageRouterBase__InsufficientOutput

Thrown when swap output is insufficient.

error LeverageRouterBase__InsufficientOutput();

LeverageRouterBase__InvalidCurvePrice

Thrown when Curve price query returns zero.

error LeverageRouterBase__InvalidCurvePrice();

LeverageRouterBase__SplitterNotActive

Thrown when Splitter is not active.

error LeverageRouterBase__SplitterNotActive();

Contents

AggregatorV3Interface

Git Source

Title: AggregatorV3Interface

Chainlink price feed interface.

Standard interface for Chainlink oracles. See https://docs.chain.link/data-feeds.

Functions

decimals

Returns the number of decimals in the price.

function decimals() external view returns (uint8);

description

Returns a human-readable description of the feed.

function description() external view returns (string memory);

version

Returns the feed version number.

function version() external view returns (uint256);

getRoundData

Returns historical round data.

function getRoundData(
    uint80 _roundId
)
    external
    view
    returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

Parameters

NameTypeDescription
_roundIduint80The round ID to query.

Returns

NameTypeDescription
roundIduint80The round ID.
answerint256The price answer.
startedAtuint256Timestamp when round started.
updatedAtuint256Timestamp of last update.
answeredInRounduint80The round in which answer was computed.

latestRoundData

Returns the latest round data.

function latestRoundData()
    external
    view
    returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

Returns

NameTypeDescription
roundIduint80The current round ID.
answerint256The latest price.
startedAtuint256Timestamp when round started.
updatedAtuint256Timestamp of last update.
answeredInRounduint80The round in which answer was computed.

ICurvePool

Git Source

Title: ICurvePool

Interface for Curve StableSwap pools.

Used for USDC/plDXY-BEAR swaps. Indices: USDC=0, plDXY-BEAR=1.

Functions

get_dy

Calculates expected output for a swap.

function get_dy(
    uint256 i,
    uint256 j,
    uint256 dx
) external view returns (uint256);

Parameters

NameTypeDescription
iuint256Input token index.
juint256Output token index.
dxuint256Input amount.

Returns

NameTypeDescription
<none>uint256Expected output amount.

exchange

Executes a token swap.

function exchange(
    uint256 i,
    uint256 j,
    uint256 dx,
    uint256 min_dy
) external payable returns (uint256);

Parameters

NameTypeDescription
iuint256Input token index.
juint256Output token index.
dxuint256Input amount.
min_dyuint256Minimum output (slippage protection).

Returns

NameTypeDescription
<none>uint256Actual output amount.

price_oracle

Returns EMA oracle price (18 decimals).

function price_oracle() external view returns (uint256);

Returns

NameTypeDescription
<none>uint256Price of token1 in terms of token0.

MarketParams

Git Source

Morpho Blue market configuration.

struct MarketParams {
address loanToken;
address collateralToken;
address oracle;
address irm;
uint256 lltv;
}

Properties

NameTypeDescription
loanTokenaddressAsset being borrowed.
collateralTokenaddressAsset used as collateral.
oracleaddressPrice oracle for collateral valuation.
irmaddressInterest rate model contract.
lltvuint256Liquidation loan-to-value ratio.

IMorpho

Git Source

Title: IMorpho

Minimal interface for Morpho Blue lending protocol.

See https://docs.morpho.org for full documentation.

Functions

setAuthorization

Set authorization for an address to act on behalf of the caller

function setAuthorization(
    address authorized,
    bool newIsAuthorized
) external;

isAuthorized

Check if an address is authorized to act on behalf of another

function isAuthorized(
    address authorizer,
    address authorized
) external view returns (bool);

createMarket

Create a new market

function createMarket(
    MarketParams memory marketParams
) external;

idToMarketParams

Get market ID from params

function idToMarketParams(
    bytes32 id
) external view returns (MarketParams memory);

supply

Supply loan assets to a Morpho market as a lender

function supply(
    MarketParams memory marketParams,
    uint256 assets,
    uint256 shares,
    address onBehalfOf,
    bytes calldata data
) external returns (uint256 assetsSupplied, uint256 sharesSupplied);

withdraw

Withdraw loan assets from a Morpho market as a lender

function withdraw(
    MarketParams memory marketParams,
    uint256 assets,
    uint256 shares,
    address onBehalfOf,
    address receiver
) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn);

supplyCollateral

Supply collateral to a Morpho market as a borrower

function supplyCollateral(
    MarketParams memory marketParams,
    uint256 assets,
    address onBehalfOf,
    bytes calldata data
) external;

withdrawCollateral

Withdraw collateral from a Morpho market as a borrower

function withdrawCollateral(
    MarketParams memory marketParams,
    uint256 assets,
    address onBehalfOf,
    address receiver
) external;

borrow

Borrow assets from a Morpho market

function borrow(
    MarketParams memory marketParams,
    uint256 assets,
    uint256 shares,
    address onBehalfOf,
    address receiver
) external returns (uint256 assetsBorrowed, uint256 sharesIssued);

repay

Repay borrowed assets to a Morpho market

function repay(
    MarketParams memory marketParams,
    uint256 assets,
    uint256 shares,
    address onBehalfOf,
    bytes calldata data
) external returns (uint256 assetsRepaid, uint256 sharesRepaid);

accrueInterest

Accrue interest for a market

function accrueInterest(
    MarketParams memory marketParams
) external;

liquidate

Liquidate an unhealthy position

function liquidate(
    MarketParams memory marketParams,
    address borrower,
    uint256 seizedAssets,
    uint256 repaidShares,
    bytes calldata data
) external returns (uint256 assetsSeized, uint256 assetsRepaid);

Parameters

NameTypeDescription
marketParamsMarketParamsThe market parameters
borroweraddressThe address of the borrower to liquidate
seizedAssetsuint256The amount of collateral to seize
repaidSharesuint256The amount of debt shares to repay (alternative to seizedAssets)
databytesCallback data

flashLoan

Execute a flash loan

Morpho flash loans are fee-free. Callback must repay exact amount.

function flashLoan(
    address token,
    uint256 assets,
    bytes calldata data
) external;

Parameters

NameTypeDescription
tokenaddressThe token to flash loan
assetsuint256The amount of tokens to flash loan
databytesArbitrary data to pass to the callback

position

Get position data for a user in a market

function position(
    bytes32 id,
    address user
) external view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral);

market

Get market data

function market(
    bytes32 id
)
    external
    view
    returns (
        uint128 totalSupplyAssets,
        uint128 totalSupplyShares,
        uint128 totalBorrowAssets,
        uint128 totalBorrowShares,
        uint128 lastUpdate,
        uint128 fee
    );

IMorphoFlashLoanCallback

Git Source

Title: IMorphoFlashLoanCallback

Callback interface for Morpho flash loan receivers.

Functions

onMorphoFlashLoan

Called by Morpho during flash loan execution.

function onMorphoFlashLoan(
    uint256 assets,
    bytes calldata data
) external;

Parameters

NameTypeDescription
assetsuint256Amount of tokens borrowed.
databytesArbitrary data passed through from flashLoan call.

IRewardDistributor

Git Source

Title: IRewardDistributor

Interface for the RewardDistributor contract that allocates staking rewards based on price discrepancy between oracle and Curve pool.

Functions

distributeRewards

Permissionless function to distribute accumulated USDC rewards.

Calculates price discrepancy, acquires tokens, and donates to vaults.

function distributeRewards() external returns (uint256 callerReward);

Returns

NameTypeDescription
callerRewarduint256Amount of USDC sent to caller as incentive.

previewDistribution

Preview the distribution without executing.

function previewDistribution()
    external
    view
    returns (uint256 bearPct, uint256 bullPct, uint256 usdcBalance, uint256 callerReward);

Returns

NameTypeDescription
bearPctuint256Expected percentage to BEAR stakers (basis points).
bullPctuint256Expected percentage to BULL stakers (basis points).
usdcBalanceuint256Current USDC balance available for distribution.
callerRewarduint256Expected caller reward.

Events

RewardsDistributed

Emitted when rewards are distributed to staking vaults.

event RewardsDistributed(uint256 bearAmount, uint256 bullAmount, uint256 bearPct, uint256 bullPct);

Parameters

NameTypeDescription
bearAmountuint256Amount of plDXY-BEAR donated to StakedToken.
bullAmountuint256Amount of plDXY-BULL donated to StakedToken.
bearPctuint256Percentage of rewards allocated to BEAR stakers (basis points).
bullPctuint256Percentage of rewards allocated to BULL stakers (basis points).

Errors

RewardDistributor__DistributionTooSoon

Thrown when distribution is attempted before cooldown expires.

error RewardDistributor__DistributionTooSoon();

RewardDistributor__NoRewards

Thrown when there are no rewards to distribute.

error RewardDistributor__NoRewards();

RewardDistributor__SplitterNotActive

Thrown when the SyntheticSplitter is not in ACTIVE status.

error RewardDistributor__SplitterNotActive();

RewardDistributor__ZeroAddress

Thrown when a constructor parameter is zero address.

error RewardDistributor__ZeroAddress();

ISyntheticSplitter

Git Source

Title: ISyntheticSplitter

Minimal interface for external contracts to interact with SyntheticSplitter.

Used by ZapRouter and other integrations.

Functions

mint

Deposits collateral to mint equal amounts of plDXY-BEAR and plDXY-BULL tokens.

Requires approval on USDC. Amount is in 18-decimal token units.

function mint(
    uint256 amount
) external;

Parameters

NameTypeDescription
amountuint256The amount of token pairs to mint.

burn

Burns equal amounts of plDXY-BEAR and plDXY-BULL tokens to retrieve collateral.

Works when not liquidated. May be restricted when paused and insolvent.

function burn(
    uint256 amount
) external;

Parameters

NameTypeDescription
amountuint256The amount of token pairs to burn.

emergencyRedeem

Emergency exit after liquidation. Burns plDXY-BEAR for its full CAP value.

Only works when protocol is liquidated (price >= CAP).

function emergencyRedeem(
    uint256 amount
) external;

Parameters

NameTypeDescription
amountuint256The amount of plDXY-BEAR tokens to burn.

currentStatus

Returns the current protocol lifecycle status.

function currentStatus() external view returns (Status);

Returns

NameTypeDescription
<none>StatusThe current Status enum value.

CAP

Returns the protocol CAP price (8 decimals, oracle format).

function CAP() external view returns (uint256);

Returns

NameTypeDescription
<none>uint256The CAP value in 8 decimal format (e.g., 2e8 = $2.00).

Enums

Status

Defines the current lifecycle state of the protocol.

enum Status {
    ACTIVE,
    PAUSED,
    SETTLED
}

Variants

NameDescription
ACTIVENormal operations. Minting and burning enabled.
PAUSEDSecurity pause. Minting disabled, burn may be restricted if insolvent.
SETTLEDEnd of life. Cap breached. Only emergencyRedeem enabled.

Contents

DecimalConstants

Git Source

Title: DecimalConstants

Shared decimal scaling constants for the Plether protocol.

Centralizes decimal conversions to prevent scaling bugs.

State Variables

ONE_WAD

One unit with 18 decimals (standard ERC20/leverage scale).

uint256 internal constant ONE_WAD = 1e18

ONE_USDC

One USDC (6 decimals).

uint256 internal constant ONE_USDC = 1e6

USDC_TO_TOKEN_SCALE

USDC (6 dec) + Chainlink (8 dec) -> Token (18 dec): 10^20.

uint256 internal constant USDC_TO_TOKEN_SCALE = 1e20

Chainlink (8 dec) -> Morpho (36 dec): 10^28.

uint256 internal constant CHAINLINK_TO_MORPHO_SCALE = 1e28

Chainlink (8 dec) -> Token (18 dec): 10^10.

uint256 internal constant CHAINLINK_TO_TOKEN_SCALE = 1e10

OracleLib

Git Source

Title: OracleLib

Library for common oracle validation patterns.

Provides reusable functions for sequencer checks, staleness validation, and price validation.

Functions

checkSequencer

Check if the L2 sequencer is up and grace period has passed.

Skips check if sequencerFeed is address(0) (e.g., on L1 or testnets).

function checkSequencer(
    AggregatorV3Interface sequencerFeed,
    uint256 gracePeriod
) internal view;

Parameters

NameTypeDescription
sequencerFeedAggregatorV3InterfaceThe Chainlink sequencer uptime feed.
gracePerioduint256The grace period in seconds after sequencer comes back up.

checkStaleness

Check if the oracle price is stale.

function checkStaleness(
    uint256 updatedAt,
    uint256 timeout
) internal view;

Parameters

NameTypeDescription
updatedAtuint256The timestamp when the price was last updated.
timeoutuint256The maximum age in seconds for a valid price.

getValidatedPrice

Get a validated price from an oracle with staleness and sequencer checks.

Reverts on zero or negative prices to prevent operations during oracle failures.

function getValidatedPrice(
    AggregatorV3Interface oracle,
    AggregatorV3Interface sequencerFeed,
    uint256 gracePeriod,
    uint256 timeout
) internal view returns (uint256 price);

Parameters

NameTypeDescription
oracleAggregatorV3InterfaceThe price oracle.
sequencerFeedAggregatorV3InterfaceThe sequencer uptime feed (can be address(0) to skip).
gracePerioduint256The sequencer grace period in seconds.
timeoutuint256The staleness timeout in seconds.

Returns

NameTypeDescription
priceuint256The validated price.

Errors

OracleLib__SequencerDown

error OracleLib__SequencerDown();

OracleLib__SequencerGracePeriod

error OracleLib__SequencerGracePeriod();

OracleLib__StalePrice

error OracleLib__StalePrice();

OracleLib__InvalidPrice

error OracleLib__InvalidPrice();

Contents

BasketOracle

Git Source

Inherits: AggregatorV3Interface, Ownable2Step

Title: BasketOracle

Aggregates multiple Chainlink feeds into a normalized weighted plDXY basket price.

Price = Sum(Weight_i * Price_i / BasePrice_i). Normalization preserves intended currency weights.

State Variables

components

Array of currency components (EUR, JPY, GBP, CAD, SEK, CHF).

Component[] public components

DECIMALS

Chainlink standard decimals for fiat/USD pairs.

uint8 public constant DECIMALS = 8

DESCRIPTION

Oracle description string.

string public constant DESCRIPTION = "plDXY Fixed Basket (Bounded)"

curvePool

Curve pool for deviation validation.

ICurvePool public curvePool

pendingCurvePool

Pending Curve pool for timelock-protected updates.

address public pendingCurvePool

curvePoolActivationTime

Timestamp when pending Curve pool can be finalized.

uint256 public curvePoolActivationTime

TIMELOCK_DELAY

Timelock delay for Curve pool updates (7 days).

uint256 public constant TIMELOCK_DELAY = 7 days

MAX_DEVIATION_BPS

Maximum allowed deviation from Curve spot (basis points).

uint256 public immutable MAX_DEVIATION_BPS

CAP

Protocol CAP price (8 decimals).

uint256 public immutable CAP

Functions

constructor

Creates basket oracle with currency components.

constructor(
    address[] memory _feeds,
    uint256[] memory _quantities,
    uint256[] memory _basePrices,
    uint256 _maxDeviationBps,
    uint256 _cap,
    address _owner
) Ownable(_owner);

Parameters

NameTypeDescription
_feedsaddress[]Array of Chainlink feed addresses.
_quantitiesuint256[]Array of basket weights (1e18 precision).
_basePricesuint256[]Array of base prices for normalization (8 decimals).
_maxDeviationBpsuint256Maximum deviation from Curve (e.g., 200 = 2%).
_capuint256Protocol CAP price (8 decimals).
_owneraddressAdmin address for Curve pool management.

setCurvePool

Sets the Curve pool for deviation validation (initial setup only).

function setCurvePool(
    address _curvePool
) external onlyOwner;

Parameters

NameTypeDescription
_curvePooladdressCurve USDC/plDXY-BEAR pool address.

proposeCurvePool

Proposes a new Curve pool (requires 7-day timelock).

function proposeCurvePool(
    address _newPool
) external onlyOwner;

Parameters

NameTypeDescription
_newPooladdressNew Curve pool address.

finalizeCurvePool

Finalizes the Curve pool update after timelock expires.

function finalizeCurvePool() external onlyOwner;

latestRoundData

Returns the aggregated basket price from all component feeds.

function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80);

Returns

NameTypeDescription
<none>uint80roundId Mock round ID (always 1).
<none>int256answer The calculated basket price in 8 decimals.
<none>uint256startedAt Timestamp of oldest component update.
<none>uint256updatedAt Timestamp of oldest component update (weakest link).
<none>uint80answeredInRound Mock answered round (always 1).

_checkDeviation

Validates basket price against Curve spot. Reverts on excessive deviation.

function _checkDeviation(
    uint256 theoreticalDxy8Dec
) internal view;

Parameters

NameTypeDescription
theoreticalDxy8Decuint256Computed basket price (8 decimals).

decimals

Returns oracle decimals (8).

function decimals() external pure returns (uint8);

description

Returns oracle description.

function description() external pure returns (string memory);

version

Returns oracle version (1).

function version() external pure returns (uint256);

getRoundData

Returns latest data for any round ID (delegates to latestRoundData).

function getRoundData(
    uint80
) external view returns (uint80, int256, uint256, uint256, uint80);

Events

CurvePoolProposed

Emitted when a new Curve pool is proposed.

event CurvePoolProposed(address indexed newPool, uint256 activationTime);

CurvePoolUpdated

Emitted when Curve pool is updated.

event CurvePoolUpdated(address indexed oldPool, address indexed newPool);

Errors

BasketOracle__InvalidPrice

Thrown when a component feed returns invalid price.

error BasketOracle__InvalidPrice(address feed);

BasketOracle__LengthMismatch

Thrown when feeds and quantities arrays have different lengths.

error BasketOracle__LengthMismatch();

BasketOracle__PriceDeviation

Thrown when basket price deviates too far from Curve spot.

error BasketOracle__PriceDeviation(uint256 theoretical, uint256 spot);

BasketOracle__AlreadySet

Thrown when Curve pool is already configured.

error BasketOracle__AlreadySet();

BasketOracle__TimelockActive

Thrown when timelock period has not elapsed.

error BasketOracle__TimelockActive();

BasketOracle__InvalidProposal

Thrown when no pending proposal exists.

error BasketOracle__InvalidProposal();

BasketOracle__InvalidDeviation

Thrown when max deviation is zero.

error BasketOracle__InvalidDeviation();

BasketOracle__InvalidBasePrice

Thrown when a base price is zero.

error BasketOracle__InvalidBasePrice();

Structs

Component

Component feed with its basket weight and base price for normalization.

struct Component {
    AggregatorV3Interface feed;
    uint256 quantity;
    uint256 basePrice;
}

IMorphoOracle

Git Source

Interface for Morpho-compatible price oracles.

Functions

price

Returns price of 1 collateral unit in loan asset terms (1e36 scale).

function price() external view returns (uint256);

MorphoOracle

Git Source

Inherits: IMorphoOracle

Title: MorphoOracle

Adapts BasketOracle price to Morpho Blue’s 1e36 scale format.

Supports both plDXY-BEAR (direct) and plDXY-BULL (inverse) pricing.

State Variables

BASKET_ORACLE

Source price feed (BasketOracle).

AggregatorV3Interface public immutable BASKET_ORACLE

CAP

Protocol CAP price (8 decimals).

uint256 public immutable CAP

IS_INVERSE

If true, returns CAP - Price (for plDXY-BULL).

bool public immutable IS_INVERSE

STALENESS_TIMEOUT

Maximum age for valid oracle price.

uint256 public constant STALENESS_TIMEOUT = 8 hours

Functions

constructor

Creates Morpho-compatible oracle wrapper.

constructor(
    address _basketOracle,
    uint256 _cap,
    bool _isInverse
) ;

Parameters

NameTypeDescription
_basketOracleaddressBasketOracle address.
_capuint256Protocol CAP (8 decimals, e.g., 2e8 = $2.00).
_isInverseboolTrue for plDXY-BULL (CAP - Price), false for plDXY-BEAR.

price

Returns collateral price scaled to 1e36.

function price() external view override returns (uint256);

Returns

NameTypeDescription
<none>uint256Price of 1 plDXY token in USDC terms (1e36 scale).

Errors

MorphoOracle__InvalidPrice

Thrown when source oracle returns zero or negative price.

error MorphoOracle__InvalidPrice();

MorphoOracle__StalePrice

Thrown when source oracle data is stale.

error MorphoOracle__StalePrice();

MorphoOracle__PriceExceedsCap

Thrown when basket price exceeds protocol CAP (liquidation state).

error MorphoOracle__PriceExceedsCap();

MorphoOracle__ZeroAddress

Thrown when zero address provided to constructor.

error MorphoOracle__ZeroAddress();

IOracle

Git Source

Interface for price oracles.

Functions

price

Returns price of 1 collateral unit in loan asset terms.

function price() external view returns (uint256);

StakedOracle

Git Source

Inherits: IOracle

Title: StakedOracle

Prices ERC4626 vault shares by combining underlying price with exchange rate.

Price = UnderlyingPrice * ExchangeRate. Used for splDXY-BEAR/splDXY-BULL in Morpho.

State Variables

VAULT

The staking vault (splDXY-BEAR or splDXY-BULL).

IERC4626 public immutable VAULT

UNDERLYING_ORACLE

Oracle for the underlying plDXY token.

IOracle public immutable UNDERLYING_ORACLE

UNDERLYING_DECIMALS

Decimal multiplier for the underlying asset.

uint256 public immutable UNDERLYING_DECIMALS

Functions

constructor

Creates staked oracle for a vault.

constructor(
    address _vault,
    address _underlyingOracle
) ;

Parameters

NameTypeDescription
_vaultaddressERC4626 staking vault address.
_underlyingOracleaddressPrice oracle for the underlying plDXY token.

price

Returns price of 1 vault share including accrued yield.

function price() external view override returns (uint256);

Returns

NameTypeDescription
<none>uint256Price scaled to 1e36 (underlying price * exchange rate).

Errors

StakedOracle__InvalidPrice

Thrown when underlying oracle returns zero price.

error StakedOracle__InvalidPrice();

StakedOracle__ZeroAddress

Thrown when zero address provided to constructor.

error StakedOracle__ZeroAddress();

BullLeverageRouter

Git Source

Inherits: LeverageRouterBase

Title: BullLeverageRouter

Leverage router for plDXY-BULL positions via Morpho Blue.

Uses Morpho flash loans + Splitter minting to acquire plDXY-BULL, then deposits as Morpho collateral. Close operation uses a single plDXY-BEAR flash mint for simplicity and gas efficiency. Uses Morpho’s fee-free flash loans for capital efficiency.

STATE MACHINE - OPEN LEVERAGE: ┌─────────────────────────────────────────────────────────────────────────┐ │ openLeverage(principal, leverage) │ │ 1. Pull USDC from user │ │ 2. Flash loan additional USDC from Morpho (fee-free) │ │ └──► onMorphoFlashLoan(OP_OPEN) │ │ └──► _executeOpen() │ │ 1. Mint plDXY-BEAR + plDXY-BULL pairs via Splitter │ │ 2. Sell plDXY-BEAR on Curve → USDC │ │ 3. Stake plDXY-BULL → splDXY-BULL │ │ 4. Deposit splDXY-BULL to Morpho (user’s collateral) │ │ 5. Borrow USDC from Morpho to cover flash repayment │ │ 6. Emit LeverageOpened event │ └─────────────────────────────────────────────────────────────────────────┘

STATE MACHINE - CLOSE LEVERAGE (Single Flash Mint): ┌─────────────────────────────────────────────────────────────────────────┐ │ closeLeverage(debtToRepay, collateralToWithdraw) │ │ 1. Flash mint plDXY-BEAR (collateral + extra for debt repayment) │ │ └──► onFlashLoan(OP_CLOSE) │ │ └──► _executeClose() │ │ 1. Sell extra plDXY-BEAR on Curve → USDC │ │ 2. Repay user’s Morpho debt with USDC from sale │ │ 3. Withdraw user’s splDXY-BULL from Morpho │ │ 4. Unstake splDXY-BULL → plDXY-BULL │ │ 5. Redeem plDXY-BEAR + plDXY-BULL → USDC │ │ 6. Buy plDXY-BEAR on Curve to repay flash mint │ │ 7. Transfer remaining USDC to user │ │ 8. Emit LeverageClosed event │ └─────────────────────────────────────────────────────────────────────────┘

State Variables

SPLITTER

SyntheticSplitter for minting/burning token pairs.

ISyntheticSplitter public immutable SPLITTER

PLDXY_BULL

plDXY-BULL token (collateral for bull positions).

IERC20 public immutable PLDXY_BULL

STAKED_PLDXY_BULL

StakedToken vault for plDXY-BULL (used as Morpho collateral).

IERC4626 public immutable STAKED_PLDXY_BULL

CAP

Protocol CAP price (8 decimals, oracle format).

uint256 public immutable CAP

EXCHANGE_RATE_BUFFER_BPS

Buffer for exchange rate drift between previewRedeem and redeem (1%).

Protects against DoS attacks via yield donation front-running.

uint256 public constant EXCHANGE_RATE_BUFFER_BPS = 100

Functions

constructor

Deploys BullLeverageRouter with Morpho market configuration.

constructor(
    address _morpho,
    address _splitter,
    address _curvePool,
    address _usdc,
    address _plDxyBear,
    address _plDxyBull,
    address _stakedPlDxyBull,
    MarketParams memory _marketParams
) LeverageRouterBase(_morpho, _curvePool, _usdc, _plDxyBear);

Parameters

NameTypeDescription
_morphoaddressMorpho Blue protocol address.
_splitteraddressSyntheticSplitter contract address.
_curvePooladdressCurve USDC/plDXY-BEAR pool address.
_usdcaddressUSDC token address.
_plDxyBearaddressplDXY-BEAR token address.
_plDxyBulladdressplDXY-BULL token address.
_stakedPlDxyBulladdresssplDXY-BULL staking vault address.
_marketParamsMarketParamsMorpho market parameters for splDXY-BULL/USDC.

openLeverage

Open a Leveraged plDXY-BULL Position in one transaction.

Mints pairs via Splitter, sells plDXY-BEAR on Curve, deposits plDXY-BULL to Morpho.

function openLeverage(
    uint256 principal,
    uint256 leverage,
    uint256 maxSlippageBps,
    uint256 deadline
) external nonReentrant whenNotPaused;

Parameters

NameTypeDescription
principaluint256Amount of USDC user sends.
leverageuint256Multiplier (e.g. 3x = 3e18).
maxSlippageBpsuint256Maximum slippage tolerance in basis points (e.g., 50 = 0.5%). Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction.
deadlineuint256Unix timestamp after which the transaction reverts.

closeLeverage

Close a Leveraged plDXY-BULL Position in one transaction.

Uses a single plDXY-BEAR flash mint to unwind positions efficiently. Queries actual debt from Morpho to ensure full repayment even if interest accrued.

function closeLeverage(
    uint256 collateralToWithdraw,
    uint256 maxSlippageBps,
    uint256 deadline
) external nonReentrant whenNotPaused;

Parameters

NameTypeDescription
collateralToWithdrawuint256Amount of splDXY-BULL shares to withdraw from Morpho. NOTE: This is staked token shares, not underlying plDXY-BULL amount. Use STAKED_PLDXY_BULL.previewRedeem() to convert shares to underlying.
maxSlippageBpsuint256Maximum slippage tolerance in basis points. Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction.
deadlineuint256Unix timestamp after which the transaction reverts.

getActualDebt

Returns the user’s current debt in this market (includes accrued interest).

function getActualDebt(
    address user
) external view returns (uint256 debt);

Parameters

NameTypeDescription
useraddressThe address to query debt for.

Returns

NameTypeDescription
debtuint256The actual debt amount in USDC (rounded up).

_getActualDebt

Computes actual debt from Morpho position, rounded up to ensure full repayment.

function _getActualDebt(
    address user
) internal view returns (uint256);

_getBorrowShares

Returns user’s borrow shares from Morpho position.

Used for shares-based repayment to avoid Morpho edge case when repaying exact debt.

function _getBorrowShares(
    address user
) internal view returns (uint256);

_marketId

Computes market ID from marketParams.

function _marketId() internal view returns (bytes32);

onMorphoFlashLoan

Morpho flash loan callback for USDC flash loans (OP_OPEN only).

function onMorphoFlashLoan(
    uint256 amount,
    bytes calldata data
) external override;

Parameters

NameTypeDescription
amountuint256Amount of USDC borrowed.
databytesEncoded open operation parameters.

onFlashLoan

ERC-3156 flash loan callback for plDXY-BEAR flash mints (OP_CLOSE only).

function onFlashLoan(
    address initiator,
    address,
    uint256 amount,
    uint256 fee,
    bytes calldata data
) external override returns (bytes32);

Parameters

NameTypeDescription
initiatoraddressAddress that initiated the flash loan (must be this contract).
<none>address
amountuint256Amount of plDXY-BEAR borrowed.
feeuint256Flash loan fee (always 0 for SyntheticToken).
databytesEncoded close operation parameters.

Returns

NameTypeDescription
<none>bytes32CALLBACK_SUCCESS on successful execution.

_executeOpen

Executes open leverage operation within Morpho flash loan callback.

function _executeOpen(
    uint256 loanAmount,
    bytes calldata data
) private;

Parameters

NameTypeDescription
loanAmountuint256Amount of USDC borrowed from Morpho.
databytesEncoded parameters (op, user, deadline, principal, leverage, maxSlippageBps, minSwapOut).

_executeClose

Executes close leverage operation within plDXY-BEAR flash mint callback.

function _executeClose(
    uint256 flashAmount,
    uint256 flashFee,
    bytes calldata data
) private;

Parameters

NameTypeDescription
flashAmountuint256Amount of plDXY-BEAR flash minted.
flashFeeuint256Flash mint fee (always 0).
databytesEncoded parameters (op, user, deadline, collateralToWithdraw, debtToRepay, borrowShares, extraBearForDebt, maxSlippageBps).

previewOpenLeverage

Preview the result of opening a leveraged plDXY-BULL position.

function previewOpenLeverage(
    uint256 principal,
    uint256 leverage
) external view returns (uint256 loanAmount, uint256 totalUSDC, uint256 expectedPlDxyBull, uint256 expectedDebt);

Parameters

NameTypeDescription
principaluint256Amount of USDC user will send.
leverageuint256Multiplier (e.g. 3x = 3e18).

Returns

NameTypeDescription
loanAmountuint256Amount of USDC to flash loan.
totalUSDCuint256Total USDC for minting pairs (principal + loan).
expectedPlDxyBulluint256Expected plDXY-BULL tokens received.
expectedDebtuint256Expected Morpho debt (flash repayment - USDC from plDXY-BEAR sale).

previewCloseLeverage

Preview the result of closing a leveraged plDXY-BULL position.

function previewCloseLeverage(
    uint256 debtToRepay,
    uint256 collateralToWithdraw
) external view returns (uint256 expectedUSDC, uint256 usdcForBearBuyback, uint256 expectedReturn);

Parameters

NameTypeDescription
debtToRepayuint256Amount of USDC debt to repay.
collateralToWithdrawuint256Amount of plDXY-BULL collateral to withdraw.

Returns

NameTypeDescription
expectedUSDCuint256Expected USDC from redeeming pairs.
usdcForBearBuybackuint256Expected USDC needed to buy back plDXY-BEAR.
expectedReturnuint256Expected USDC returned to user after all repayments.

_estimateUsdcForBearBuyback

Estimates USDC needed to buy BEAR using binary search on Curve.

function _estimateUsdcForBearBuyback(
    uint256 bearAmount
) private view returns (uint256);

Parameters

NameTypeDescription
bearAmountuint256Target plDXY-BEAR amount to acquire.

Returns

NameTypeDescription
<none>uint256Estimated USDC needed (with slippage margin).

Events

LeverageOpened

Emitted when a leveraged plDXY-BULL position is opened.

event LeverageOpened(
    address indexed user,
    uint256 principal,
    uint256 leverage,
    uint256 loanAmount,
    uint256 plDxyBullReceived,
    uint256 debtIncurred,
    uint256 maxSlippageBps
);

LeverageClosed

Emitted when a leveraged plDXY-BULL position is closed.

event LeverageClosed(
    address indexed user,
    uint256 debtRepaid,
    uint256 collateralWithdrawn,
    uint256 usdcReturned,
    uint256 maxSlippageBps
);

LeverageRouter

Git Source

Inherits: LeverageRouterBase

Title: LeverageRouter

Leverage router for plDXY-BEAR positions via Morpho Blue.

Flash loans USDC from Morpho → swaps to plDXY-BEAR on Curve → stakes → deposits to Morpho as collateral. Requires user to authorize this contract in Morpho before use. Uses Morpho’s fee-free flash loans for capital efficiency.

State Variables

STAKED_PLDXY_BEAR

StakedToken vault for plDXY-BEAR (used as Morpho collateral).

IERC4626 public immutable STAKED_PLDXY_BEAR

EXCHANGE_RATE_BUFFER_BPS

Buffer for exchange rate drift protection (1% = 100 bps).

uint256 public constant EXCHANGE_RATE_BUFFER_BPS = 100

Functions

constructor

Deploys LeverageRouter with Morpho market configuration.

constructor(
    address _morpho,
    address _curvePool,
    address _usdc,
    address _plDxyBear,
    address _stakedPlDxyBear,
    MarketParams memory _marketParams
) LeverageRouterBase(_morpho, _curvePool, _usdc, _plDxyBear);

Parameters

NameTypeDescription
_morphoaddressMorpho Blue protocol address.
_curvePooladdressCurve USDC/plDXY-BEAR pool address.
_usdcaddressUSDC token address.
_plDxyBearaddressplDXY-BEAR token address.
_stakedPlDxyBearaddresssplDXY-BEAR staking vault address.
_marketParamsMarketParamsMorpho market parameters for splDXY-BEAR/USDC.

openLeverage

Open a Leveraged Position in one transaction.

function openLeverage(
    uint256 principal,
    uint256 leverage,
    uint256 maxSlippageBps,
    uint256 deadline
) external nonReentrant whenNotPaused;

Parameters

NameTypeDescription
principaluint256Amount of USDC user sends.
leverageuint256Multiplier (e.g. 3x = 3e18).
maxSlippageBpsuint256Maximum slippage tolerance in basis points (e.g., 50 = 0.5%). Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction.
deadlineuint256Unix timestamp after which the transaction reverts.

closeLeverage

Close a Leveraged Position in one transaction.

Queries actual debt from Morpho to ensure full repayment even if interest accrued.

function closeLeverage(
    uint256 collateralToWithdraw,
    uint256 maxSlippageBps,
    uint256 deadline
) external nonReentrant whenNotPaused;

Parameters

NameTypeDescription
collateralToWithdrawuint256Amount of splDXY-BEAR shares to withdraw from Morpho. NOTE: This is staked token shares, not underlying plDXY-BEAR amount. Use STAKED_PLDXY_BEAR.previewRedeem() to convert shares to underlying.
maxSlippageBpsuint256Maximum slippage tolerance in basis points (e.g., 50 = 0.5%). Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction.
deadlineuint256Unix timestamp after which the transaction reverts.

getActualDebt

Returns the user’s current debt in this market (includes accrued interest).

function getActualDebt(
    address user
) external view returns (uint256 debt);

Parameters

NameTypeDescription
useraddressThe address to query debt for.

Returns

NameTypeDescription
debtuint256The actual debt amount in USDC (rounded up).

_getActualDebt

Computes actual debt from Morpho position, rounded up to ensure full repayment.

function _getActualDebt(
    address user
) internal view returns (uint256);

_getBorrowShares

Returns user’s borrow shares from Morpho position.

Used for shares-based repayment to avoid Morpho edge case when repaying exact debt.

function _getBorrowShares(
    address user
) internal view returns (uint256);

_marketId

Computes market ID from marketParams.

function _marketId() internal view returns (bytes32);

onMorphoFlashLoan

Morpho flash loan callback. Routes to open or close handler.

function onMorphoFlashLoan(
    uint256 amount,
    bytes calldata data
) external override;

Parameters

NameTypeDescription
amountuint256Amount of USDC borrowed.
databytesEncoded operation parameters.

onFlashLoan

ERC-3156 flash loan callback - not used by LeverageRouter.

Always reverts as LeverageRouter only uses Morpho flash loans.

function onFlashLoan(
    address,
    address,
    uint256,
    uint256,
    bytes calldata
) external pure override returns (bytes32);

_executeOpen

Executes open leverage operation within Morpho flash loan callback.

function _executeOpen(
    uint256 loanAmount,
    bytes calldata data
) private;

Parameters

NameTypeDescription
loanAmountuint256Amount of USDC borrowed from Morpho.
databytesEncoded parameters (op, user, deadline, principal, leverage, maxSlippageBps, minPlDxyBear).

_executeClose

Executes close leverage operation within Morpho flash loan callback.

function _executeClose(
    uint256 loanAmount,
    bytes calldata data
) private;

Parameters

NameTypeDescription
loanAmountuint256Amount of USDC borrowed from Morpho to repay debt.
databytesEncoded parameters (op, user, deadline, collateralToWithdraw, borrowShares, maxSlippageBps, minUsdcOut).

_executeCloseNoDebt

Closes position without flash loan when user has no Morpho debt.

function _executeCloseNoDebt(
    address user,
    uint256 collateralToWithdraw,
    uint256 maxSlippageBps,
    uint256 minUsdcOut
) private;

Parameters

NameTypeDescription
useraddressPosition owner receiving USDC.
collateralToWithdrawuint256Amount of splDXY-BEAR shares to withdraw.
maxSlippageBpsuint256Maximum slippage for Curve swap.
minUsdcOutuint256Minimum USDC to receive after swap.

previewOpenLeverage

Preview the result of opening a leveraged position.

function previewOpenLeverage(
    uint256 principal,
    uint256 leverage
) external view returns (uint256 loanAmount, uint256 totalUSDC, uint256 expectedPlDxyBear, uint256 expectedDebt);

Parameters

NameTypeDescription
principaluint256Amount of USDC user will send.
leverageuint256Multiplier (e.g. 3x = 3e18).

Returns

NameTypeDescription
loanAmountuint256Amount of USDC to flash loan.
totalUSDCuint256Total USDC to swap (principal + loan).
expectedPlDxyBearuint256Expected plDXY-BEAR (based on current curve price).
expectedDebtuint256Expected debt incurred (equals loan amount, no flash fee with Morpho).

previewCloseLeverage

Preview the result of closing a leveraged position.

function previewCloseLeverage(
    uint256 debtToRepay,
    uint256 collateralToWithdraw
) external view returns (uint256 expectedUSDC, uint256 flashFee, uint256 expectedReturn);

Parameters

NameTypeDescription
debtToRepayuint256Amount of USDC debt to repay.
collateralToWithdrawuint256Amount of plDXY-BEAR collateral to withdraw.

Returns

NameTypeDescription
expectedUSDCuint256Expected USDC from swap (based on current curve price).
flashFeeuint256Flash loan fee (always 0 with Morpho).
expectedReturnuint256Expected USDC returned to user after repaying flash loan.

Events

LeverageOpened

Emitted when a leveraged plDXY-BEAR position is opened.

event LeverageOpened(
    address indexed user,
    uint256 principal,
    uint256 leverage,
    uint256 loanAmount,
    uint256 plDxyBearReceived,
    uint256 debtIncurred,
    uint256 maxSlippageBps
);

LeverageClosed

Emitted when a leveraged plDXY-BEAR position is closed.

event LeverageClosed(
    address indexed user,
    uint256 debtRepaid,
    uint256 collateralWithdrawn,
    uint256 usdcReturned,
    uint256 maxSlippageBps
);

IUniversalRewardsDistributor

Git Source

Functions

claim

function claim(
    address account,
    address reward,
    uint256 claimable,
    bytes32[] calldata proof
) external returns (uint256 amount);

MorphoAdapter

Git Source

Inherits: ERC4626, Ownable2Step

Title: MorphoAdapter

ERC4626-compliant wrapper for Morpho Blue lending.

Interchangeable with other yield adapters. Only accepts deposits from SyntheticSplitter.

State Variables

MORPHO

Morpho Blue protocol contract.

IMorpho public immutable MORPHO

marketParams

Morpho market parameters for this adapter.

MarketParams public marketParams

MARKET_ID

Computed market ID (keccak256 of marketParams).

bytes32 public immutable MARKET_ID

SPLITTER

SyntheticSplitter authorized to deposit/withdraw.

address public immutable SPLITTER

urd

Universal Rewards Distributor for Morpho incentives.

address public urd

Functions

constructor

Deploys adapter with Morpho market configuration.

constructor(
    IERC20 _asset,
    address _morpho,
    MarketParams memory _marketParams,
    address _owner,
    address _splitter
) ERC4626(_asset) ERC20("Morpho Yield Wrapper", "myUSDC") Ownable(_owner);

Parameters

NameTypeDescription
_assetIERC20Underlying asset (USDC).
_morphoaddressMorpho Blue protocol address.
_marketParamsMarketParamsMarket parameters (must have loanToken == _asset).
_owneraddressAdmin address for rewards and rescue.
_splitteraddressSyntheticSplitter authorized to deposit.

totalAssets

Returns total USDC value of this adapter’s Morpho position.

function totalAssets() public view override returns (uint256);

Returns

NameTypeDescription
<none>uint256Total assets including accrued interest.

_deposit

Deposits assets to Morpho after ERC4626 share minting.

function _deposit(
    address caller,
    address receiver,
    uint256 assets,
    uint256 shares
) internal override;

Parameters

NameTypeDescription
calleraddressMust be SPLITTER.
receiveraddressReceiver of vault shares.
assetsuint256Amount of USDC to deposit.
sharesuint256Amount of vault shares minted.

_withdraw

Withdraws assets from Morpho before ERC4626 share burning.

function _withdraw(
    address caller,
    address receiver,
    address owner,
    uint256 assets,
    uint256 shares
) internal override;

Parameters

NameTypeDescription
calleraddressCaller requesting withdrawal.
receiveraddressReceiver of withdrawn assets.
owneraddressOwner of vault shares being burned.
assetsuint256Amount of USDC to withdraw.
sharesuint256Amount of vault shares burned.

_computeMarketId

Computes market ID from parameters (keccak256 hash).

function _computeMarketId(
    MarketParams memory params
) internal pure returns (bytes32);

Parameters

NameTypeDescription
paramsMarketParamsMarket parameters struct.

Returns

NameTypeDescription
<none>bytes32Market identifier used by Morpho.

_convertMorphoSharesToAssets

Converts Morpho supply shares to asset amount.

function _convertMorphoSharesToAssets(
    uint256 shares
) internal view returns (uint256);

Parameters

NameTypeDescription
sharesuint256Morpho supply shares.

Returns

NameTypeDescription
<none>uint256Equivalent asset amount.

rescueToken

Recovers stuck tokens (excluding the underlying asset).

function rescueToken(
    address token,
    address to
) external onlyOwner;

Parameters

NameTypeDescription
tokenaddressToken to rescue.
toaddressRecipient address.

setUrd

Sets the Universal Rewards Distributor address.

function setUrd(
    address _urd
) external onlyOwner;

Parameters

NameTypeDescription
_urdaddressURD contract address (cannot be zero).

claimRewards

Claims rewards and transfers to specified address.

function claimRewards(
    address reward,
    uint256 claimable,
    bytes32[] calldata proof,
    address to
) external onlyOwner returns (uint256 claimed);

Parameters

NameTypeDescription
rewardaddressReward token address.
claimableuint256Total claimable amount from merkle tree.
proofbytes32[]Merkle proof for claim.
toaddressRecipient of claimed rewards.

Returns

NameTypeDescription
claimeduint256Amount successfully claimed and transferred.

claimRewardsToSelf

Claims rewards to this contract for compounding.

function claimRewardsToSelf(
    address reward,
    uint256 claimable,
    bytes32[] calldata proof
) external onlyOwner returns (uint256 claimed);

Parameters

NameTypeDescription
rewardaddressReward token address.
claimableuint256Total claimable amount from merkle tree.
proofbytes32[]Merkle proof for claim.

Returns

NameTypeDescription
claimeduint256Amount successfully claimed.

Events

UrdUpdated

Emitted when URD address is updated.

event UrdUpdated(address indexed oldUrd, address indexed newUrd);

Errors

MorphoAdapter__OnlySplitter

Thrown when caller is not the SyntheticSplitter.

error MorphoAdapter__OnlySplitter();

MorphoAdapter__InvalidAddress

Thrown when a zero address is provided.

error MorphoAdapter__InvalidAddress();

MorphoAdapter__InvalidMarket

Thrown when market loan token doesn’t match asset.

error MorphoAdapter__InvalidMarket();

MorphoAdapter__CannotRescueUnderlying

Thrown when attempting to rescue the underlying asset.

error MorphoAdapter__CannotRescueUnderlying();

RewardDistributor

Git Source

Inherits: IRewardDistributor, ReentrancyGuard

Title: RewardDistributor

Distributes staking rewards based on price discrepancy between oracle and Curve EMA.

Receives USDC from SyntheticSplitter.harvestYield() and allocates to StakedToken vaults proportionally based on which token is underperforming relative to theoretical price.

State Variables

DISCREPANCY_THRESHOLD_BPS

Discrepancy threshold for 100% allocation (2% = 200 bps).

uint256 public constant DISCREPANCY_THRESHOLD_BPS = 200

MIN_DISTRIBUTION_INTERVAL

Minimum time between distributions (1 hour).

uint256 public constant MIN_DISTRIBUTION_INTERVAL = 1 hours

CALLER_REWARD_BPS

Reward for calling distributeRewards (0.1% = 10 bps).

uint256 public constant CALLER_REWARD_BPS = 10

USDC_INDEX

USDC index in Curve pool.

uint256 public constant USDC_INDEX = 0

PLDXY_BEAR_INDEX

plDXY-BEAR index in Curve pool.

uint256 public constant PLDXY_BEAR_INDEX = 1

MAX_SWAP_SLIPPAGE_BPS

Maximum slippage for Curve swaps (1% = 100 bps).

uint256 public constant MAX_SWAP_SLIPPAGE_BPS = 100

SPLITTER

SyntheticSplitter contract.

ISyntheticSplitter public immutable SPLITTER

USDC

USDC stablecoin.

IERC20 public immutable USDC

PLDXY_BEAR

plDXY-BEAR token.

IERC20 public immutable PLDXY_BEAR

PLDXY_BULL

plDXY-BULL token.

IERC20 public immutable PLDXY_BULL

STAKED_BEAR

Staked plDXY-BEAR vault.

StakedToken public immutable STAKED_BEAR

STAKED_BULL

Staked plDXY-BULL vault.

StakedToken public immutable STAKED_BULL

CURVE_POOL

Curve USDC/plDXY-BEAR pool.

ICurvePool public immutable CURVE_POOL

ZAP_ROUTER

ZapRouter for acquiring plDXY-BULL.

ZapRouter public immutable ZAP_ROUTER

ORACLE

BasketOracle for theoretical price.

AggregatorV3Interface public immutable ORACLE

CAP

Protocol CAP price (8 decimals).

uint256 public immutable CAP

lastDistributionTime

Timestamp of last distribution.

uint256 public lastDistributionTime

Functions

constructor

Deploys RewardDistributor with all required dependencies.

constructor(
    address _splitter,
    address _usdc,
    address _plDxyBear,
    address _plDxyBull,
    address _stakedBear,
    address _stakedBull,
    address _curvePool,
    address _zapRouter,
    address _oracle
) ;

Parameters

NameTypeDescription
_splitteraddressSyntheticSplitter contract address.
_usdcaddressUSDC token address.
_plDxyBearaddressplDXY-BEAR token address.
_plDxyBulladdressplDXY-BULL token address.
_stakedBearaddressStakedToken vault for BEAR.
_stakedBulladdressStakedToken vault for BULL.
_curvePooladdressCurve USDC/plDXY-BEAR pool.
_zapRouteraddressZapRouter for BULL acquisition.
_oracleaddressBasketOracle for price data.

distributeRewards

Permissionless function to distribute accumulated USDC rewards.

Calculates price discrepancy, acquires tokens, and donates to vaults.

function distributeRewards() external nonReentrant returns (uint256 callerReward);

Returns

NameTypeDescription
callerRewarduint256Amount of USDC sent to caller as incentive.

previewDistribution

Preview the distribution without executing.

function previewDistribution()
    external
    view
    returns (uint256 bearPct, uint256 bullPct, uint256 usdcBalance, uint256 callerReward);

Returns

NameTypeDescription
bearPctuint256Expected percentage to BEAR stakers (basis points).
bullPctuint256Expected percentage to BULL stakers (basis points).
usdcBalanceuint256Current USDC balance available for distribution.
callerRewarduint256Expected caller reward.

_calculateSplit

Calculates reward split based on price discrepancy.

function _calculateSplit() internal view returns (uint256 bearPct, uint256 bullPct);

Returns

NameTypeDescription
bearPctuint256Percentage for BEAR stakers (basis points).
bullPctuint256Percentage for BULL stakers (basis points).

_acquireTokens

Acquires tokens using optimal combination of minting and swapping/zapping.

function _acquireTokens(
    uint256 totalUsdc,
    uint256 bearPct,
    uint256 bullPct
) internal returns (uint256 bearAmount, uint256 bullAmount);

Parameters

NameTypeDescription
totalUsdcuint256Total USDC to convert to tokens.
bearPctuint256Target BEAR percentage (basis points).
bullPctuint256Target BULL percentage (basis points).

Returns

NameTypeDescription
bearAmountuint256Amount of plDXY-BEAR acquired.
bullAmountuint256Amount of plDXY-BULL acquired.

_calculateMintAmount

Converts USDC amount to 18-decimal mint amount.

function _calculateMintAmount(
    uint256 usdcAmount
) internal view returns (uint256 mintAmount);

Parameters

NameTypeDescription
usdcAmountuint256USDC amount (6 decimals).

Returns

NameTypeDescription
mintAmountuint256Token amount to mint (18 decimals).

StakedToken

Git Source

Inherits: ERC4626

Title: StakedToken

ERC4626 vault for staking plDXY-BEAR or plDXY-BULL tokens.

Used as Morpho collateral. Exchange rate increases via yield donations. Implements 1000x virtual share offset to prevent inflation attacks.

Functions

constructor

Creates a new staking vault for a synthetic token.

constructor(
    IERC20 _asset,
    string memory _name,
    string memory _symbol
) ERC4626(_asset) ERC20(_name, _symbol);

Parameters

NameTypeDescription
_assetIERC20The underlying plDXY token to stake (plDXY-BEAR or plDXY-BULL).
_namestringVault share name (e.g., “Staked plDXY-BEAR”).
_symbolstringVault share symbol (e.g., “splDXY-BEAR”).

donateYield

Allows anyone to inject yield into the vault.

Increases the share price for all stakers immediately.

function donateYield(
    uint256 amount
) external;

Parameters

NameTypeDescription
amountuint256The amount of underlying tokens to donate

depositWithPermit

Deposit assets with a permit signature (gasless approval).

Combines EIP-2612 permit with ERC-4626 deposit in a single transaction.

function depositWithPermit(
    uint256 assets,
    address receiver,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) external returns (uint256 shares);

Parameters

NameTypeDescription
assetsuint256Amount of underlying tokens to deposit
receiveraddressAddress to receive the vault shares
deadlineuint256Permit signature expiration timestamp
vuint8Signature recovery byte
rbytes32Signature r component
sbytes32Signature s component

Returns

NameTypeDescription
sharesuint256Amount of vault shares minted

_decimalsOffset

Virtual share offset (10^3 = 1000x) to prevent inflation attacks.

function _decimalsOffset() internal pure override returns (uint8);

SyntheticSplitter

Git Source

Inherits: ISyntheticSplitter, Ownable2Step, Pausable, ReentrancyGuard

Title: SyntheticSplitter

Core protocol contract for minting/burning synthetic plDXY tokens.

Accepts USDC collateral to mint equal amounts of plDXY-BEAR + plDXY-BULL tokens. Maintains 10% liquidity buffer locally, 90% deployed to yield adapters. Three lifecycle states: ACTIVE → PAUSED → SETTLED (liquidated).

State Variables

TOKEN_A

SyntheticToken public immutable TOKEN_A

TOKEN_B

SyntheticToken public immutable TOKEN_B

USDC

IERC20 public immutable USDC

ORACLE

AggregatorV3Interface public immutable ORACLE

CAP

uint256 public immutable CAP

USDC_MULTIPLIER

uint256 public immutable USDC_MULTIPLIER

BUFFER_PERCENT

uint256 public constant BUFFER_PERCENT = 10

yieldAdapter

IERC4626 public yieldAdapter

pendingAdapter

address public pendingAdapter

adapterActivationTime

uint256 public adapterActivationTime

treasury

address public treasury

staking

address public staking

pendingFees

FeeConfig public pendingFees

feesActivationTime

uint256 public feesActivationTime

ORACLE_TIMEOUT

uint256 public constant ORACLE_TIMEOUT = 8 hours

TIMELOCK_DELAY

uint256 public constant TIMELOCK_DELAY = 7 days

lastUnpauseTime

uint256 public lastUnpauseTime

HARVEST_REWARD_BPS

uint256 public constant HARVEST_REWARD_BPS = 10

MIN_SURPLUS_THRESHOLD

uint256 public constant MIN_SURPLUS_THRESHOLD = 50 * 1e6

isLiquidated

bool public isLiquidated

SEQUENCER_UPTIME_FEED

AggregatorV3Interface public immutable SEQUENCER_UPTIME_FEED

SEQUENCER_GRACE_PERIOD

uint256 public constant SEQUENCER_GRACE_PERIOD = 1 hours

Functions

constructor

Deploys the SyntheticSplitter and creates plDXY-BEAR and plDXY-BULL tokens.

constructor(
    address _oracle,
    address _usdc,
    address _yieldAdapter,
    uint256 _cap,
    address _treasury,
    address _sequencerUptimeFeed
) Ownable(msg.sender);

Parameters

NameTypeDescription
_oracleaddressChainlink-compatible price feed for plDXY basket.
_usdcaddressUSDC token address (6 decimals).
_yieldAdapteraddressERC4626-compliant yield adapter for USDC deposits.
_capuint256Maximum plDXY price (8 decimals). Triggers liquidation when breached.
_treasuryaddressTreasury address for fee distribution.
_sequencerUptimeFeedaddressL2 sequencer uptime feed (address(0) for L1/testnets).

previewMint

Simulates a mint to see required USDC input

function previewMint(
    uint256 mintAmount
) external view returns (uint256 usdcRequired, uint256 depositToAdapter, uint256 keptInBuffer);

Parameters

NameTypeDescription
mintAmountuint256The amount of TokenA/B the user wants to mint

Returns

NameTypeDescription
usdcRequireduint256Total USDC needed from user
depositToAdapteruint256Amount that will be sent to Yield Source
keptInBufferuint256Amount that will stay in Splitter contract

mint

Mint plDXY-BEAR and plDXY-BULL tokens by depositing USDC collateral.

function mint(
    uint256 amount
) external nonReentrant whenNotPaused;

Parameters

NameTypeDescription
amountuint256The amount of token pairs to mint (18 decimals).

previewBurn

Simulates a burn to see USDC return

function previewBurn(
    uint256 burnAmount
) external view returns (uint256 usdcRefund, uint256 withdrawnFromAdapter);

Parameters

NameTypeDescription
burnAmountuint256The amount of TokenA/B the user wants to burn

Returns

NameTypeDescription
usdcRefunduint256Total USDC user will receive
withdrawnFromAdapteruint256Amount pulled from Yield Source to cover shortage

burn

Burn plDXY-BEAR and plDXY-BULL tokens to redeem USDC collateral.

function burn(
    uint256 amount
) external nonReentrant;

Parameters

NameTypeDescription
amountuint256The amount of token pairs to burn (18 decimals).

_withdrawFromAdapter

Withdraws USDC from yield adapter with redeem fallback.

function _withdrawFromAdapter(
    uint256 amount
) internal;

Parameters

NameTypeDescription
amountuint256USDC amount to withdraw (6 decimals).

triggerLiquidation

Locks the protocol into liquidated state when price >= CAP.

Permissionless. Prevents system revival if price drops after breach.

function triggerLiquidation() external nonReentrant;

emergencyRedeem

Emergency redemption when protocol is liquidated (price >= CAP).

Only burns plDXY-BEAR tokens at CAP price. plDXY-BULL becomes worthless.

function emergencyRedeem(
    uint256 amount
) external nonReentrant;

Parameters

NameTypeDescription
amountuint256The amount of plDXY-BEAR tokens to redeem (18 decimals).

ejectLiquidity

Emergency exit: withdraws all funds from yield adapter.

Bypasses timelock. Auto-pauses protocol. Use if adapter is compromised.

function ejectLiquidity() external onlyOwner;

withdrawFromAdapter

Withdraws a specific amount from yield adapter while paused.

Requires protocol to be paused. Use for gradual liquidity extraction when full ejectLiquidity() fails due to adapter liquidity constraints.

function withdrawFromAdapter(
    uint256 amount
) external nonReentrant onlyOwner;

Parameters

NameTypeDescription
amountuint256Desired USDC amount to withdraw. Capped by adapter’s maxWithdraw.

previewHarvest

Previews yield harvest amounts and eligibility.

function previewHarvest()
    external
    view
    returns (
        bool canHarvest,
        uint256 totalSurplus,
        uint256 callerReward,
        uint256 treasuryShare,
        uint256 stakingShare
    );

Returns

NameTypeDescription
canHarvestboolTrue if surplus exceeds MIN_SURPLUS_THRESHOLD.
totalSurplusuint256Available surplus (total assets - liabilities).
callerRewarduint256Caller incentive (0.1% of harvest).
treasuryShareuint256Treasury allocation (20% of remaining).
stakingShareuint256Staking allocation (79.9% of remaining).

harvestYield

Permissionless yield harvesting from the adapter.

Distributes surplus: 0.1% to caller, 20% to treasury, 79.9% to staking.

function harvestYield() external nonReentrant whenNotPaused;

_checkLiveness

Enforces 7-day cooldown after unpause for governance actions.

function _checkLiveness() internal view;

proposeFeeReceivers

Propose new fee receiver addresses (7-day timelock).

function proposeFeeReceivers(
    address _treasury,
    address _staking
) external onlyOwner;

Parameters

NameTypeDescription
_treasuryaddressNew treasury address.
_stakingaddressNew staking address (can be zero to send all to treasury).

finalizeFeeReceivers

Finalize pending fee receiver change after timelock expires.

function finalizeFeeReceivers() external onlyOwner;

proposeAdapter

Propose a new yield adapter (7-day timelock).

function proposeAdapter(
    address _newAdapter
) external onlyOwner;

Parameters

NameTypeDescription
_newAdapteraddressAddress of the new ERC4626-compliant adapter.

finalizeAdapter

Finalize adapter migration after timelock. Migrates all funds atomically.

function finalizeAdapter() external nonReentrant onlyOwner;

pause

Pause the protocol. Blocks minting and harvesting.

function pause() external onlyOwner;

unpause

Unpause the protocol. Starts 7-day governance cooldown.

function unpause() external onlyOwner;

rescueToken

Rescue accidentally sent tokens. Cannot rescue core assets.

function rescueToken(
    address token,
    address to
) external onlyOwner;

Parameters

NameTypeDescription
tokenaddressThe ERC20 token to rescue.
toaddressThe recipient address.

currentStatus

Returns the current protocol lifecycle status.

function currentStatus() external view override returns (Status);

Returns

NameTypeDescription
<none>StatusThe current Status enum value (ACTIVE, PAUSED, or SETTLED).

getSystemStatus

Returns comprehensive system metrics for dashboards.

function getSystemStatus() external view returns (SystemStatus memory status);

Returns

NameTypeDescription
statusSystemStatusStruct containing price, collateral ratio, and liquidity data.

_getTotalLiabilities

Calculates total liabilities based on TOKEN_A supply at CAP price.

function _getTotalLiabilities() internal view returns (uint256);

_getTotalAssets

Calculates total assets (local USDC + adapter value).

function _getTotalAssets() internal view returns (uint256);

_requireSolventIfPaused

Reverts if paused and insolvent (assets < liabilities).

function _requireSolventIfPaused() internal view;

_getOraclePrice

Oracle price validation using OracleLib.

function _getOraclePrice() internal view returns (uint256);

Events

Minted

event Minted(address indexed user, uint256 amount);

Burned

event Burned(address indexed user, uint256 amount);

AdapterProposed

event AdapterProposed(address indexed newAdapter, uint256 activationTime);

AdapterMigrated

event AdapterMigrated(address indexed oldAdapter, address indexed newAdapter, uint256 transferredAmount);

LiquidationTriggered

event LiquidationTriggered(uint256 price);

EmergencyRedeemed

event EmergencyRedeemed(address indexed user, uint256 amount);

YieldHarvested

event YieldHarvested(uint256 totalSurplus, uint256 treasuryAmt, uint256 stakingAmt);

FeesProposed

event FeesProposed(address indexed treasury, address indexed staking, uint256 activationTime);

FeesUpdated

event FeesUpdated(address indexed treasury, address indexed staking);

EmergencyEjected

event EmergencyEjected(uint256 amountRecovered);

AdapterWithdrawn

event AdapterWithdrawn(uint256 requested, uint256 withdrawn);

TokenRescued

event TokenRescued(address indexed token, address indexed to, uint256 amount);

Errors

Splitter__ZeroAddress

error Splitter__ZeroAddress();

Splitter__InvalidCap

error Splitter__InvalidCap();

Splitter__ZeroAmount

error Splitter__ZeroAmount();

Splitter__ZeroRefund

error Splitter__ZeroRefund();

Splitter__AdapterNotSet

error Splitter__AdapterNotSet();

Splitter__AdapterInsufficientLiquidity

error Splitter__AdapterInsufficientLiquidity();

Splitter__LiquidationActive

error Splitter__LiquidationActive();

Splitter__NotLiquidated

error Splitter__NotLiquidated();

Splitter__TimelockActive

error Splitter__TimelockActive();

Splitter__InvalidProposal

error Splitter__InvalidProposal();

Splitter__NoSurplus

error Splitter__NoSurplus();

Splitter__GovernanceLocked

error Splitter__GovernanceLocked();

Splitter__InsufficientHarvest

error Splitter__InsufficientHarvest();

Splitter__AdapterWithdrawFailed

error Splitter__AdapterWithdrawFailed();

Splitter__Insolvent

error Splitter__Insolvent();

Splitter__NotPaused

error Splitter__NotPaused();

Splitter__CannotRescueCoreAsset

error Splitter__CannotRescueCoreAsset();

Splitter__MigrationLostFunds

error Splitter__MigrationLostFunds();

Structs

FeeConfig

struct FeeConfig {
    address treasuryAddr;
    address stakingAddr;
}

SystemStatus

struct SystemStatus {
    uint256 currentPrice;
    uint256 capPrice;
    bool liquidated;
    bool isPaused;
    uint256 totalAssets; // Local + Adapter
    uint256 totalLiabilities; // Bear Supply * CAP
    uint256 collateralRatio; // Basis points
    uint256 adapterAssets; // USDC value held in yield adapter
}

SyntheticToken

Git Source

Inherits: ERC20, ERC20Permit, ERC20FlashMint

Title: SyntheticToken

ERC20 token with flash mint and permit capability, controlled by SyntheticSplitter.

Used for plDXY-BEAR and plDXY-BULL tokens. Only the Splitter can mint/burn. Inherits ERC20FlashMint for fee-free flash loans used by routers.

State Variables

SPLITTER

The SyntheticSplitter contract that controls minting and burning.

address public immutable SPLITTER

Functions

onlySplitter

Restricts function access to the Splitter contract only.

modifier onlySplitter() ;

constructor

Creates a new SyntheticToken.

constructor(
    string memory _name,
    string memory _symbol,
    address _splitter
) ERC20(_name, _symbol) ERC20Permit(_name);

Parameters

NameTypeDescription
_namestringToken name (e.g., “Plether Dollar Index Bear”).
_symbolstringToken symbol (e.g., “plDXY-BEAR”).
_splitteraddressAddress of the SyntheticSplitter contract.

mint

Mint tokens to an address. Only callable by Splitter.

function mint(
    address to,
    uint256 amount
) external onlySplitter;

Parameters

NameTypeDescription
toaddressRecipient address.
amountuint256Amount to mint.

burn

Burn tokens from an address. Only callable by Splitter.

function burn(
    address from,
    uint256 amount
) external onlySplitter;

Parameters

NameTypeDescription
fromaddressAddress to burn from.
amountuint256Amount to burn.

Errors

SyntheticToken__Unauthorized

Thrown when a non-Splitter address attempts to mint or burn.

error SyntheticToken__Unauthorized();

SyntheticToken__ZeroAddress

Thrown when zero address provided for splitter.

error SyntheticToken__ZeroAddress();

ZapRouter

Git Source

Inherits: FlashLoanBase, Ownable2Step, Pausable, ReentrancyGuard

Title: ZapRouter

Efficient router for acquiring plDXY-BULL tokens using flash mints.

Flash mints plDXY-BEAR → swaps to USDC via Curve → mints pairs → keeps plDXY-BULL. For plDXY-BEAR, users should swap directly on Curve instead.

State Variables

MAX_SLIPPAGE_BPS

Maximum allowed slippage in basis points (1% = 100 bps).

uint256 public constant MAX_SLIPPAGE_BPS = 100

SAFETY_BUFFER_BPS

Safety buffer for flash loan repayment calculations (0.5% = 50 bps).

uint256 public constant SAFETY_BUFFER_BPS = 50

USDC_INDEX

USDC index in the Curve USDC/plDXY-BEAR pool.

uint256 public constant USDC_INDEX = 0

PLDXY_BEAR_INDEX

plDXY-BEAR index in the Curve USDC/plDXY-BEAR pool.

uint256 public constant PLDXY_BEAR_INDEX = 1

SPLITTER

SyntheticSplitter contract for minting/burning pairs.

ISyntheticSplitter public immutable SPLITTER

PLDXY_BEAR

plDXY-BEAR token (flash minted for swaps).

IERC20 public immutable PLDXY_BEAR

PLDXY_BULL

plDXY-BULL token (output of zap operations).

IERC20 public immutable PLDXY_BULL

USDC

USDC stablecoin.

IERC20 public immutable USDC

CURVE_POOL

Curve pool for USDC/plDXY-BEAR swaps.

ICurvePool public immutable CURVE_POOL

CAP

Protocol CAP price (8 decimals, oracle format).

uint256 public immutable CAP

CAP_PRICE

CAP price scaled for Curve comparison (6 decimals).

uint256 public immutable CAP_PRICE

ACTION_MINT

Flash loan action: mint plDXY-BULL.

uint256 private constant ACTION_MINT = 0

ACTION_BURN

Flash loan action: burn plDXY-BULL.

uint256 private constant ACTION_BURN = 1

Functions

constructor

Deploys ZapRouter with required protocol dependencies.

constructor(
    address _splitter,
    address _plDxyBear,
    address _plDxyBull,
    address _usdc,
    address _curvePool
) Ownable(msg.sender);

Parameters

NameTypeDescription
_splitteraddressSyntheticSplitter contract address.
_plDxyBearaddressplDXY-BEAR token address.
_plDxyBulladdressplDXY-BULL token address.
_usdcaddressUSDC token address.
_curvePooladdressCurve USDC/plDXY-BEAR pool address.

zapMint

Buy plDXY-BULL using USDC with flash mint efficiency.

For plDXY-BEAR, swap directly on Curve instead.

function zapMint(
    uint256 usdcAmount,
    uint256 minAmountOut,
    uint256 maxSlippageBps,
    uint256 deadline
) external nonReentrant whenNotPaused;

Parameters

NameTypeDescription
usdcAmountuint256The amount of USDC the user is sending.
minAmountOutuint256Minimum amount of plDXY-BULL tokens to receive.
maxSlippageBpsuint256Maximum slippage tolerance in basis points (e.g., 100 = 1%). Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction.
deadlineuint256Unix timestamp after which the transaction reverts.

zapBurn

Sell plDXY-BULL tokens for USDC using flash mint efficiency.

function zapBurn(
    uint256 bullAmount,
    uint256 minUsdcOut,
    uint256 deadline
) external nonReentrant whenNotPaused;

Parameters

NameTypeDescription
bullAmountuint256Amount of plDXY-BULL to sell.
minUsdcOutuint256Minimum USDC to receive (slippage protection).
deadlineuint256Unix timestamp after which the transaction reverts.

zapBurnWithPermit

Sell plDXY-BULL tokens for USDC with a permit signature (gasless approval).

function zapBurnWithPermit(
    uint256 bullAmount,
    uint256 minUsdcOut,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) external nonReentrant whenNotPaused;

Parameters

NameTypeDescription
bullAmountuint256Amount of plDXY-BULL to sell.
minUsdcOutuint256Minimum USDC to receive (slippage protection).
deadlineuint256Unix timestamp after which the transaction reverts.
vuint8Signature recovery byte.
rbytes32Signature r component.
sbytes32Signature s component.

_zapBurnCore

function _zapBurnCore(
    uint256 bullAmount,
    uint256 minUsdcOut,
    uint256 deadline
) internal;

onFlashLoan

ERC-3156 flash loan callback. Routes to mint or burn handler.

function onFlashLoan(
    address initiator,
    address,
    uint256 amount,
    uint256 fee,
    bytes calldata data
) external override returns (bytes32);

Parameters

NameTypeDescription
initiatoraddressAddress that initiated the flash loan (must be this contract).
<none>address
amountuint256Amount of plDXY-BEAR borrowed.
feeuint256Flash loan fee (always 0 for SyntheticToken).
databytesEncoded operation parameters.

Returns

NameTypeDescription
<none>bytes32CALLBACK_SUCCESS on successful execution.

onMorphoFlashLoan

Morpho flash loan callback - not used by ZapRouter.

Always reverts as ZapRouter only uses ERC-3156 flash mints.

function onMorphoFlashLoan(
    uint256,
    bytes calldata
) external pure override;

_handleMint

Executes mint operation within flash loan callback.

function _handleMint(
    uint256 loanAmount,
    uint256 fee,
    bytes calldata data
) internal;

Parameters

NameTypeDescription
loanAmountuint256Amount of plDXY-BEAR borrowed.
feeuint256Flash loan fee (always 0).
databytesEncoded mint parameters (action, user, usdcAmount, minSwapOut, minAmountOut, maxSlippageBps).

_handleBurn

Executes burn operation within flash loan callback.

function _handleBurn(
    uint256 loanAmount,
    uint256 fee,
    bytes calldata data
) internal;

Parameters

NameTypeDescription
loanAmountuint256Amount of plDXY-BEAR borrowed.
feeuint256Flash loan fee (always 0).
databytesEncoded burn parameters (action, user, bullAmount, minUsdcOut).

previewZapMint

Preview the result of a zapMint operation.

function previewZapMint(
    uint256 usdcAmount
)
    external
    view
    returns (
        uint256 flashAmount,
        uint256 expectedSwapOut,
        uint256 totalUSDC,
        uint256 expectedTokensOut,
        uint256 flashFee
    );

Parameters

NameTypeDescription
usdcAmountuint256The amount of USDC the user will send.

Returns

NameTypeDescription
flashAmountuint256Amount of plDXY-BEAR to flash mint.
expectedSwapOutuint256Expected USDC from selling flash-minted plDXY-BEAR.
totalUSDCuint256Total USDC for minting pairs (user + swap).
expectedTokensOutuint256Expected plDXY-BULL tokens to receive.
flashFeeuint256Flash mint fee (if any).

previewZapBurn

Preview the result of a zapBurn operation.

function previewZapBurn(
    uint256 bullAmount
)
    external
    view
    returns (uint256 expectedUsdcFromBurn, uint256 usdcForBearBuyback, uint256 expectedUsdcOut, uint256 flashFee);

Parameters

NameTypeDescription
bullAmountuint256The amount of plDXY-BULL tokens to sell.

Returns

NameTypeDescription
expectedUsdcFromBurnuint256USDC received from burning pairs via Splitter.
usdcForBearBuybackuint256USDC needed to buy back plDXY-BEAR for flash loan repayment.
expectedUsdcOutuint256Net USDC the user will receive.
flashFeeuint256Flash mint fee (if any).

pause

Pause the router. Blocks zapMint and zapBurn.

function pause() external onlyOwner;

unpause

Unpause the router.

function unpause() external onlyOwner;

Events

ZapMint

Emitted when user acquires plDXY-BULL via zapMint.

event ZapMint(
    address indexed user, uint256 usdcIn, uint256 tokensOut, uint256 maxSlippageBps, uint256 actualSwapOut
);

ZapBurn

Emitted when user sells plDXY-BULL via zapBurn.

event ZapBurn(address indexed user, uint256 tokensIn, uint256 usdcOut);

Errors

ZapRouter__ZeroAddress

error ZapRouter__ZeroAddress();

ZapRouter__ZeroAmount

error ZapRouter__ZeroAmount();

ZapRouter__Expired

error ZapRouter__Expired();

ZapRouter__SlippageExceedsMax

error ZapRouter__SlippageExceedsMax();

ZapRouter__SplitterNotActive

error ZapRouter__SplitterNotActive();

ZapRouter__BearPriceAboveCap

error ZapRouter__BearPriceAboveCap();

ZapRouter__InsufficientOutput

error ZapRouter__InsufficientOutput();

ZapRouter__InvalidCurvePrice

error ZapRouter__InvalidCurvePrice();

ZapRouter__SolvencyBreach

error ZapRouter__SolvencyBreach();