Plether Protocol
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
| Contract | Description |
|---|---|
SyntheticSplitter | Central protocol contract. Accepts USDC, mints/burns token pairs, manages yield deployment |
SyntheticToken | ERC20 + ERC20FlashMint implementation for plDXY-BEAR and plDXY-BULL |
StakedToken | ERC-4626 vault wrapper enabling yield accrual on deposited tokens |
Oracle Layer
| Contract | Description |
|---|---|
BasketOracle | Computes plDXY as weighted basket of 6 Chainlink feeds, with bound validation against Curve EMA price |
MorphoOracle | Adapts BasketOracle to Morpho Blue’s 36-decimal scale format |
StakedOracle | Wraps 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):
| Currency | Weight | Base Price (USD) |
|---|---|---|
| EUR | 57.6% | 1.1750 |
| JPY | 13.6% | 0.00638 |
| GBP | 11.9% | 1.3448 |
| CAD | 9.1% | 0.7288 |
| SEK | 4.2% | 0.1086 |
| CHF | 3.6% | 1.2610 |
Both weights and base prices are permanently fixed and cannot be changed after deployment.
Routing Layer
| Contract | Description |
|---|---|
ZapRouter | Single-sided plDXY-BULL minting and burning using flash mints |
LeverageRouter | Leveraged plDXY-BEAR positions via Morpho Blue flash loans (fee-free) |
BullLeverageRouter | Leveraged plDXY-BULL positions via Morpho + plDXY-BEAR flash mints |
Yield Adapters (ERC-4626)
| Contract | Description |
|---|---|
MorphoAdapter | ERC-4626 wrapper for Morpho Blue yield generation |
Staking & Rewards
| Contract | Description |
|---|---|
RewardDistributor | Distributes 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:
- LeverageRouter (Bear): Morpho flash loan USDC → Swap to plDXY-BEAR → Stake → Deposit to Morpho as collateral → Borrow USDC to repay flash loan
- 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:
- ACTIVE - Normal operations (mint, burn, redeem)
- PAUSED - Emergency pause (minting blocked, burning allowed so users can exit, gradual adapter withdrawal enabled)
- 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:
| File | Description |
|---|---|
BaseForkTest.sol | Shared base contract, constants, and test helpers |
ZapRouterFork.t.sol | ZapRouter integration with real Curve swaps |
FullCycleFork.t.sol | Complete mint → yield → burn lifecycle |
LeverageRouterFork.t.sol | Bear and Bull leverage via real Morpho |
SlippageProtectionFork.t.sol | MEV protection and slippage scenarios |
LiquidationFork.t.sol | Interest accrual and liquidation mechanics |
BasketOracleFork.t.sol | Full 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):
| Account | Address | Private Key |
|---|---|---|
| #0 | 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 | 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 |
| #1 | 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 | 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d |
MetaMask Setup:
- Import a test private key (Settings → Import Account)
- Add network: RPC
http://127.0.0.1:8545, Chain ID31337
Format
forge fmt # Format code
forge fmt --check # Check formatting
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
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
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
| Name | Type | Description |
|---|---|---|
lender | address | Actual msg.sender. |
expectedLender | address | Expected flash lender address. |
initiator | address | Initiator 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
| Name | Type | Description |
|---|---|---|
lender | address | Actual msg.sender. |
expectedLender | address | Expected 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
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
| Name | Type | Description |
|---|---|---|
_morpho | address | Morpho Blue protocol address. |
_curvePool | address | Curve USDC/plDXY-BEAR pool address. |
_usdc | address | USDC token address. |
_plDxyBear | address | plDXY-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
- ICurvePool
- MarketParams
- IMorpho
- IMorphoFlashLoanCallback
- IRewardDistributor
- ISyntheticSplitter
AggregatorV3Interface
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
| Name | Type | Description |
|---|---|---|
_roundId | uint80 | The round ID to query. |
Returns
| Name | Type | Description |
|---|---|---|
roundId | uint80 | The round ID. |
answer | int256 | The price answer. |
startedAt | uint256 | Timestamp when round started. |
updatedAt | uint256 | Timestamp of last update. |
answeredInRound | uint80 | The 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
| Name | Type | Description |
|---|---|---|
roundId | uint80 | The current round ID. |
answer | int256 | The latest price. |
startedAt | uint256 | Timestamp when round started. |
updatedAt | uint256 | Timestamp of last update. |
answeredInRound | uint80 | The round in which answer was computed. |
ICurvePool
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
| Name | Type | Description |
|---|---|---|
i | uint256 | Input token index. |
j | uint256 | Output token index. |
dx | uint256 | Input amount. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Expected output amount. |
exchange
Executes a token swap.
function exchange(
uint256 i,
uint256 j,
uint256 dx,
uint256 min_dy
) external payable returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
i | uint256 | Input token index. |
j | uint256 | Output token index. |
dx | uint256 | Input amount. |
min_dy | uint256 | Minimum output (slippage protection). |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Actual output amount. |
price_oracle
Returns EMA oracle price (18 decimals).
function price_oracle() external view returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Price of token1 in terms of token0. |
MarketParams
Morpho Blue market configuration.
struct MarketParams {
address loanToken;
address collateralToken;
address oracle;
address irm;
uint256 lltv;
}
Properties
| Name | Type | Description |
|---|---|---|
loanToken | address | Asset being borrowed. |
collateralToken | address | Asset used as collateral. |
oracle | address | Price oracle for collateral valuation. |
irm | address | Interest rate model contract. |
lltv | uint256 | Liquidation loan-to-value ratio. |
IMorpho
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
| Name | Type | Description |
|---|---|---|
marketParams | MarketParams | The market parameters |
borrower | address | The address of the borrower to liquidate |
seizedAssets | uint256 | The amount of collateral to seize |
repaidShares | uint256 | The amount of debt shares to repay (alternative to seizedAssets) |
data | bytes | Callback 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
| Name | Type | Description |
|---|---|---|
token | address | The token to flash loan |
assets | uint256 | The amount of tokens to flash loan |
data | bytes | Arbitrary 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
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
| Name | Type | Description |
|---|---|---|
assets | uint256 | Amount of tokens borrowed. |
data | bytes | Arbitrary data passed through from flashLoan call. |
IRewardDistributor
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
| Name | Type | Description |
|---|---|---|
callerReward | uint256 | Amount 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
| Name | Type | Description |
|---|---|---|
bearPct | uint256 | Expected percentage to BEAR stakers (basis points). |
bullPct | uint256 | Expected percentage to BULL stakers (basis points). |
usdcBalance | uint256 | Current USDC balance available for distribution. |
callerReward | uint256 | Expected caller reward. |
Events
RewardsDistributed
Emitted when rewards are distributed to staking vaults.
event RewardsDistributed(uint256 bearAmount, uint256 bullAmount, uint256 bearPct, uint256 bullPct);
Parameters
| Name | Type | Description |
|---|---|---|
bearAmount | uint256 | Amount of plDXY-BEAR donated to StakedToken. |
bullAmount | uint256 | Amount of plDXY-BULL donated to StakedToken. |
bearPct | uint256 | Percentage of rewards allocated to BEAR stakers (basis points). |
bullPct | uint256 | Percentage 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
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
| Name | Type | Description |
|---|---|---|
amount | uint256 | The 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | The 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | The amount of plDXY-BEAR tokens to burn. |
currentStatus
Returns the current protocol lifecycle status.
function currentStatus() external view returns (Status);
Returns
| Name | Type | Description |
|---|---|---|
<none> | Status | The current Status enum value. |
CAP
Returns the protocol CAP price (8 decimals, oracle format).
function CAP() external view returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The 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
| Name | Description |
|---|---|
ACTIVE | Normal operations. Minting and burning enabled. |
PAUSED | Security pause. Minting disabled, burn may be restricted if insolvent. |
SETTLED | End of life. Cap breached. Only emergencyRedeem enabled. |
Contents
DecimalConstants
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_TO_MORPHO_SCALE
Chainlink (8 dec) -> Morpho (36 dec): 10^28.
uint256 internal constant CHAINLINK_TO_MORPHO_SCALE = 1e28
CHAINLINK_TO_TOKEN_SCALE
Chainlink (8 dec) -> Token (18 dec): 10^10.
uint256 internal constant CHAINLINK_TO_TOKEN_SCALE = 1e10
OracleLib
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
| Name | Type | Description |
|---|---|---|
sequencerFeed | AggregatorV3Interface | The Chainlink sequencer uptime feed. |
gracePeriod | uint256 | The 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
| Name | Type | Description |
|---|---|---|
updatedAt | uint256 | The timestamp when the price was last updated. |
timeout | uint256 | The 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
| Name | Type | Description |
|---|---|---|
oracle | AggregatorV3Interface | The price oracle. |
sequencerFeed | AggregatorV3Interface | The sequencer uptime feed (can be address(0) to skip). |
gracePeriod | uint256 | The sequencer grace period in seconds. |
timeout | uint256 | The staleness timeout in seconds. |
Returns
| Name | Type | Description |
|---|---|---|
price | uint256 | The 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
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
| Name | Type | Description |
|---|---|---|
_feeds | address[] | Array of Chainlink feed addresses. |
_quantities | uint256[] | Array of basket weights (1e18 precision). |
_basePrices | uint256[] | Array of base prices for normalization (8 decimals). |
_maxDeviationBps | uint256 | Maximum deviation from Curve (e.g., 200 = 2%). |
_cap | uint256 | Protocol CAP price (8 decimals). |
_owner | address | Admin address for Curve pool management. |
setCurvePool
Sets the Curve pool for deviation validation (initial setup only).
function setCurvePool(
address _curvePool
) external onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
_curvePool | address | Curve USDC/plDXY-BEAR pool address. |
proposeCurvePool
Proposes a new Curve pool (requires 7-day timelock).
function proposeCurvePool(
address _newPool
) external onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
_newPool | address | New 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
| Name | Type | Description |
|---|---|---|
<none> | uint80 | roundId Mock round ID (always 1). |
<none> | int256 | answer The calculated basket price in 8 decimals. |
<none> | uint256 | startedAt Timestamp of oldest component update. |
<none> | uint256 | updatedAt Timestamp of oldest component update (weakest link). |
<none> | uint80 | answeredInRound Mock answered round (always 1). |
_checkDeviation
Validates basket price against Curve spot. Reverts on excessive deviation.
function _checkDeviation(
uint256 theoreticalDxy8Dec
) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
theoreticalDxy8Dec | uint256 | Computed 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
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
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
| Name | Type | Description |
|---|---|---|
_basketOracle | address | BasketOracle address. |
_cap | uint256 | Protocol CAP (8 decimals, e.g., 2e8 = $2.00). |
_isInverse | bool | True for plDXY-BULL (CAP - Price), false for plDXY-BEAR. |
price
Returns collateral price scaled to 1e36.
function price() external view override returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Price 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
Interface for price oracles.
Functions
price
Returns price of 1 collateral unit in loan asset terms.
function price() external view returns (uint256);
StakedOracle
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
| Name | Type | Description |
|---|---|---|
_vault | address | ERC4626 staking vault address. |
_underlyingOracle | address | Price oracle for the underlying plDXY token. |
price
Returns price of 1 vault share including accrued yield.
function price() external view override returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Price 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
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
| Name | Type | Description |
|---|---|---|
_morpho | address | Morpho Blue protocol address. |
_splitter | address | SyntheticSplitter contract address. |
_curvePool | address | Curve USDC/plDXY-BEAR pool address. |
_usdc | address | USDC token address. |
_plDxyBear | address | plDXY-BEAR token address. |
_plDxyBull | address | plDXY-BULL token address. |
_stakedPlDxyBull | address | splDXY-BULL staking vault address. |
_marketParams | MarketParams | Morpho 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
| Name | Type | Description |
|---|---|---|
principal | uint256 | Amount of USDC user sends. |
leverage | uint256 | Multiplier (e.g. 3x = 3e18). |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points (e.g., 50 = 0.5%). Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction. |
deadline | uint256 | Unix 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
| Name | Type | Description |
|---|---|---|
collateralToWithdraw | uint256 | Amount 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. |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points. Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction. |
deadline | uint256 | Unix 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
| Name | Type | Description |
|---|---|---|
user | address | The address to query debt for. |
Returns
| Name | Type | Description |
|---|---|---|
debt | uint256 | The 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | Amount of USDC borrowed. |
data | bytes | Encoded 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
| Name | Type | Description |
|---|---|---|
initiator | address | Address that initiated the flash loan (must be this contract). |
<none> | address | |
amount | uint256 | Amount of plDXY-BEAR borrowed. |
fee | uint256 | Flash loan fee (always 0 for SyntheticToken). |
data | bytes | Encoded close operation parameters. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | CALLBACK_SUCCESS on successful execution. |
_executeOpen
Executes open leverage operation within Morpho flash loan callback.
function _executeOpen(
uint256 loanAmount,
bytes calldata data
) private;
Parameters
| Name | Type | Description |
|---|---|---|
loanAmount | uint256 | Amount of USDC borrowed from Morpho. |
data | bytes | Encoded 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
| Name | Type | Description |
|---|---|---|
flashAmount | uint256 | Amount of plDXY-BEAR flash minted. |
flashFee | uint256 | Flash mint fee (always 0). |
data | bytes | Encoded 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
| Name | Type | Description |
|---|---|---|
principal | uint256 | Amount of USDC user will send. |
leverage | uint256 | Multiplier (e.g. 3x = 3e18). |
Returns
| Name | Type | Description |
|---|---|---|
loanAmount | uint256 | Amount of USDC to flash loan. |
totalUSDC | uint256 | Total USDC for minting pairs (principal + loan). |
expectedPlDxyBull | uint256 | Expected plDXY-BULL tokens received. |
expectedDebt | uint256 | Expected 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
| Name | Type | Description |
|---|---|---|
debtToRepay | uint256 | Amount of USDC debt to repay. |
collateralToWithdraw | uint256 | Amount of plDXY-BULL collateral to withdraw. |
Returns
| Name | Type | Description |
|---|---|---|
expectedUSDC | uint256 | Expected USDC from redeeming pairs. |
usdcForBearBuyback | uint256 | Expected USDC needed to buy back plDXY-BEAR. |
expectedReturn | uint256 | Expected 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
| Name | Type | Description |
|---|---|---|
bearAmount | uint256 | Target plDXY-BEAR amount to acquire. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Estimated 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
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
| Name | Type | Description |
|---|---|---|
_morpho | address | Morpho Blue protocol address. |
_curvePool | address | Curve USDC/plDXY-BEAR pool address. |
_usdc | address | USDC token address. |
_plDxyBear | address | plDXY-BEAR token address. |
_stakedPlDxyBear | address | splDXY-BEAR staking vault address. |
_marketParams | MarketParams | Morpho 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
| Name | Type | Description |
|---|---|---|
principal | uint256 | Amount of USDC user sends. |
leverage | uint256 | Multiplier (e.g. 3x = 3e18). |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points (e.g., 50 = 0.5%). Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction. |
deadline | uint256 | Unix 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
| Name | Type | Description |
|---|---|---|
collateralToWithdraw | uint256 | Amount 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. |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points (e.g., 50 = 0.5%). Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction. |
deadline | uint256 | Unix 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
| Name | Type | Description |
|---|---|---|
user | address | The address to query debt for. |
Returns
| Name | Type | Description |
|---|---|---|
debt | uint256 | The 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | Amount of USDC borrowed. |
data | bytes | Encoded 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
| Name | Type | Description |
|---|---|---|
loanAmount | uint256 | Amount of USDC borrowed from Morpho. |
data | bytes | Encoded 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
| Name | Type | Description |
|---|---|---|
loanAmount | uint256 | Amount of USDC borrowed from Morpho to repay debt. |
data | bytes | Encoded 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
| Name | Type | Description |
|---|---|---|
user | address | Position owner receiving USDC. |
collateralToWithdraw | uint256 | Amount of splDXY-BEAR shares to withdraw. |
maxSlippageBps | uint256 | Maximum slippage for Curve swap. |
minUsdcOut | uint256 | Minimum 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
| Name | Type | Description |
|---|---|---|
principal | uint256 | Amount of USDC user will send. |
leverage | uint256 | Multiplier (e.g. 3x = 3e18). |
Returns
| Name | Type | Description |
|---|---|---|
loanAmount | uint256 | Amount of USDC to flash loan. |
totalUSDC | uint256 | Total USDC to swap (principal + loan). |
expectedPlDxyBear | uint256 | Expected plDXY-BEAR (based on current curve price). |
expectedDebt | uint256 | Expected 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
| Name | Type | Description |
|---|---|---|
debtToRepay | uint256 | Amount of USDC debt to repay. |
collateralToWithdraw | uint256 | Amount of plDXY-BEAR collateral to withdraw. |
Returns
| Name | Type | Description |
|---|---|---|
expectedUSDC | uint256 | Expected USDC from swap (based on current curve price). |
flashFee | uint256 | Flash loan fee (always 0 with Morpho). |
expectedReturn | uint256 | Expected 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
Functions
claim
function claim(
address account,
address reward,
uint256 claimable,
bytes32[] calldata proof
) external returns (uint256 amount);
MorphoAdapter
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
| Name | Type | Description |
|---|---|---|
_asset | IERC20 | Underlying asset (USDC). |
_morpho | address | Morpho Blue protocol address. |
_marketParams | MarketParams | Market parameters (must have loanToken == _asset). |
_owner | address | Admin address for rewards and rescue. |
_splitter | address | SyntheticSplitter authorized to deposit. |
totalAssets
Returns total USDC value of this adapter’s Morpho position.
function totalAssets() public view override returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Total 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
| Name | Type | Description |
|---|---|---|
caller | address | Must be SPLITTER. |
receiver | address | Receiver of vault shares. |
assets | uint256 | Amount of USDC to deposit. |
shares | uint256 | Amount 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
| Name | Type | Description |
|---|---|---|
caller | address | Caller requesting withdrawal. |
receiver | address | Receiver of withdrawn assets. |
owner | address | Owner of vault shares being burned. |
assets | uint256 | Amount of USDC to withdraw. |
shares | uint256 | Amount of vault shares burned. |
_computeMarketId
Computes market ID from parameters (keccak256 hash).
function _computeMarketId(
MarketParams memory params
) internal pure returns (bytes32);
Parameters
| Name | Type | Description |
|---|---|---|
params | MarketParams | Market parameters struct. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | Market identifier used by Morpho. |
_convertMorphoSharesToAssets
Converts Morpho supply shares to asset amount.
function _convertMorphoSharesToAssets(
uint256 shares
) internal view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
shares | uint256 | Morpho supply shares. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Equivalent asset amount. |
rescueToken
Recovers stuck tokens (excluding the underlying asset).
function rescueToken(
address token,
address to
) external onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
token | address | Token to rescue. |
to | address | Recipient address. |
setUrd
Sets the Universal Rewards Distributor address.
function setUrd(
address _urd
) external onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
_urd | address | URD 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
| Name | Type | Description |
|---|---|---|
reward | address | Reward token address. |
claimable | uint256 | Total claimable amount from merkle tree. |
proof | bytes32[] | Merkle proof for claim. |
to | address | Recipient of claimed rewards. |
Returns
| Name | Type | Description |
|---|---|---|
claimed | uint256 | Amount 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
| Name | Type | Description |
|---|---|---|
reward | address | Reward token address. |
claimable | uint256 | Total claimable amount from merkle tree. |
proof | bytes32[] | Merkle proof for claim. |
Returns
| Name | Type | Description |
|---|---|---|
claimed | uint256 | Amount 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
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
| Name | Type | Description |
|---|---|---|
_splitter | address | SyntheticSplitter contract address. |
_usdc | address | USDC token address. |
_plDxyBear | address | plDXY-BEAR token address. |
_plDxyBull | address | plDXY-BULL token address. |
_stakedBear | address | StakedToken vault for BEAR. |
_stakedBull | address | StakedToken vault for BULL. |
_curvePool | address | Curve USDC/plDXY-BEAR pool. |
_zapRouter | address | ZapRouter for BULL acquisition. |
_oracle | address | BasketOracle 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
| Name | Type | Description |
|---|---|---|
callerReward | uint256 | Amount 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
| Name | Type | Description |
|---|---|---|
bearPct | uint256 | Expected percentage to BEAR stakers (basis points). |
bullPct | uint256 | Expected percentage to BULL stakers (basis points). |
usdcBalance | uint256 | Current USDC balance available for distribution. |
callerReward | uint256 | Expected caller reward. |
_calculateSplit
Calculates reward split based on price discrepancy.
function _calculateSplit() internal view returns (uint256 bearPct, uint256 bullPct);
Returns
| Name | Type | Description |
|---|---|---|
bearPct | uint256 | Percentage for BEAR stakers (basis points). |
bullPct | uint256 | Percentage 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
| Name | Type | Description |
|---|---|---|
totalUsdc | uint256 | Total USDC to convert to tokens. |
bearPct | uint256 | Target BEAR percentage (basis points). |
bullPct | uint256 | Target BULL percentage (basis points). |
Returns
| Name | Type | Description |
|---|---|---|
bearAmount | uint256 | Amount of plDXY-BEAR acquired. |
bullAmount | uint256 | Amount of plDXY-BULL acquired. |
_calculateMintAmount
Converts USDC amount to 18-decimal mint amount.
function _calculateMintAmount(
uint256 usdcAmount
) internal view returns (uint256 mintAmount);
Parameters
| Name | Type | Description |
|---|---|---|
usdcAmount | uint256 | USDC amount (6 decimals). |
Returns
| Name | Type | Description |
|---|---|---|
mintAmount | uint256 | Token amount to mint (18 decimals). |
StakedToken
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
| Name | Type | Description |
|---|---|---|
_asset | IERC20 | The underlying plDXY token to stake (plDXY-BEAR or plDXY-BULL). |
_name | string | Vault share name (e.g., “Staked plDXY-BEAR”). |
_symbol | string | Vault 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | The 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
| Name | Type | Description |
|---|---|---|
assets | uint256 | Amount of underlying tokens to deposit |
receiver | address | Address to receive the vault shares |
deadline | uint256 | Permit signature expiration timestamp |
v | uint8 | Signature recovery byte |
r | bytes32 | Signature r component |
s | bytes32 | Signature s component |
Returns
| Name | Type | Description |
|---|---|---|
shares | uint256 | Amount of vault shares minted |
_decimalsOffset
Virtual share offset (10^3 = 1000x) to prevent inflation attacks.
function _decimalsOffset() internal pure override returns (uint8);
SyntheticSplitter
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
| Name | Type | Description |
|---|---|---|
_oracle | address | Chainlink-compatible price feed for plDXY basket. |
_usdc | address | USDC token address (6 decimals). |
_yieldAdapter | address | ERC4626-compliant yield adapter for USDC deposits. |
_cap | uint256 | Maximum plDXY price (8 decimals). Triggers liquidation when breached. |
_treasury | address | Treasury address for fee distribution. |
_sequencerUptimeFeed | address | L2 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
| Name | Type | Description |
|---|---|---|
mintAmount | uint256 | The amount of TokenA/B the user wants to mint |
Returns
| Name | Type | Description |
|---|---|---|
usdcRequired | uint256 | Total USDC needed from user |
depositToAdapter | uint256 | Amount that will be sent to Yield Source |
keptInBuffer | uint256 | Amount 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | The 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
| Name | Type | Description |
|---|---|---|
burnAmount | uint256 | The amount of TokenA/B the user wants to burn |
Returns
| Name | Type | Description |
|---|---|---|
usdcRefund | uint256 | Total USDC user will receive |
withdrawnFromAdapter | uint256 | Amount 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | The amount of token pairs to burn (18 decimals). |
_withdrawFromAdapter
Withdraws USDC from yield adapter with redeem fallback.
function _withdrawFromAdapter(
uint256 amount
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
amount | uint256 | USDC 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | The 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | Desired 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
| Name | Type | Description |
|---|---|---|
canHarvest | bool | True if surplus exceeds MIN_SURPLUS_THRESHOLD. |
totalSurplus | uint256 | Available surplus (total assets - liabilities). |
callerReward | uint256 | Caller incentive (0.1% of harvest). |
treasuryShare | uint256 | Treasury allocation (20% of remaining). |
stakingShare | uint256 | Staking 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
| Name | Type | Description |
|---|---|---|
_treasury | address | New treasury address. |
_staking | address | New 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
| Name | Type | Description |
|---|---|---|
_newAdapter | address | Address 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
| Name | Type | Description |
|---|---|---|
token | address | The ERC20 token to rescue. |
to | address | The recipient address. |
currentStatus
Returns the current protocol lifecycle status.
function currentStatus() external view override returns (Status);
Returns
| Name | Type | Description |
|---|---|---|
<none> | Status | The current Status enum value (ACTIVE, PAUSED, or SETTLED). |
getSystemStatus
Returns comprehensive system metrics for dashboards.
function getSystemStatus() external view returns (SystemStatus memory status);
Returns
| Name | Type | Description |
|---|---|---|
status | SystemStatus | Struct 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
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
| Name | Type | Description |
|---|---|---|
_name | string | Token name (e.g., “Plether Dollar Index Bear”). |
_symbol | string | Token symbol (e.g., “plDXY-BEAR”). |
_splitter | address | Address of the SyntheticSplitter contract. |
mint
Mint tokens to an address. Only callable by Splitter.
function mint(
address to,
uint256 amount
) external onlySplitter;
Parameters
| Name | Type | Description |
|---|---|---|
to | address | Recipient address. |
amount | uint256 | Amount to mint. |
burn
Burn tokens from an address. Only callable by Splitter.
function burn(
address from,
uint256 amount
) external onlySplitter;
Parameters
| Name | Type | Description |
|---|---|---|
from | address | Address to burn from. |
amount | uint256 | Amount 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
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
| Name | Type | Description |
|---|---|---|
_splitter | address | SyntheticSplitter contract address. |
_plDxyBear | address | plDXY-BEAR token address. |
_plDxyBull | address | plDXY-BULL token address. |
_usdc | address | USDC token address. |
_curvePool | address | Curve 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
| Name | Type | Description |
|---|---|---|
usdcAmount | uint256 | The amount of USDC the user is sending. |
minAmountOut | uint256 | Minimum amount of plDXY-BULL tokens to receive. |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points (e.g., 100 = 1%). Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction. |
deadline | uint256 | Unix 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
| Name | Type | Description |
|---|---|---|
bullAmount | uint256 | Amount of plDXY-BULL to sell. |
minUsdcOut | uint256 | Minimum USDC to receive (slippage protection). |
deadline | uint256 | Unix 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
| Name | Type | Description |
|---|---|---|
bullAmount | uint256 | Amount of plDXY-BULL to sell. |
minUsdcOut | uint256 | Minimum USDC to receive (slippage protection). |
deadline | uint256 | Unix timestamp after which the transaction reverts. |
v | uint8 | Signature recovery byte. |
r | bytes32 | Signature r component. |
s | bytes32 | Signature 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
| Name | Type | Description |
|---|---|---|
initiator | address | Address that initiated the flash loan (must be this contract). |
<none> | address | |
amount | uint256 | Amount of plDXY-BEAR borrowed. |
fee | uint256 | Flash loan fee (always 0 for SyntheticToken). |
data | bytes | Encoded operation parameters. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | CALLBACK_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
| Name | Type | Description |
|---|---|---|
loanAmount | uint256 | Amount of plDXY-BEAR borrowed. |
fee | uint256 | Flash loan fee (always 0). |
data | bytes | Encoded 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
| Name | Type | Description |
|---|---|---|
loanAmount | uint256 | Amount of plDXY-BEAR borrowed. |
fee | uint256 | Flash loan fee (always 0). |
data | bytes | Encoded 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
| Name | Type | Description |
|---|---|---|
usdcAmount | uint256 | The amount of USDC the user will send. |
Returns
| Name | Type | Description |
|---|---|---|
flashAmount | uint256 | Amount of plDXY-BEAR to flash mint. |
expectedSwapOut | uint256 | Expected USDC from selling flash-minted plDXY-BEAR. |
totalUSDC | uint256 | Total USDC for minting pairs (user + swap). |
expectedTokensOut | uint256 | Expected plDXY-BULL tokens to receive. |
flashFee | uint256 | Flash 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
| Name | Type | Description |
|---|---|---|
bullAmount | uint256 | The amount of plDXY-BULL tokens to sell. |
Returns
| Name | Type | Description |
|---|---|---|
expectedUsdcFromBurn | uint256 | USDC received from burning pairs via Splitter. |
usdcForBearBuyback | uint256 | USDC needed to buy back plDXY-BEAR for flash loan repayment. |
expectedUsdcOut | uint256 | Net USDC the user will receive. |
flashFee | uint256 | Flash 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();