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.
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.
| Contract | Description |
|---|---|
SyntheticSplitter |
Central protocol contract. Accepts USDC, mints/burns token pairs. Permissionless deployToAdapter() pushes idle USDC to yield. EIP-2612 permit support on mint. |
SyntheticToken |
ERC20 + ERC20FlashMint implementation for plDXY-BEAR and plDXY-BULL |
| Contract | Description |
|---|---|
StakedToken |
ERC-4626 vault wrapper (splDXY-BEAR, splDXY-BULL) with streaming rewards to prevent reward sniping |
RewardDistributor |
Distributes USDC yield to StakedToken vaults, favoring the underperforming token |
| Contract | Description |
|---|---|
BasketOracle |
Computes plDXY as weighted basket of 6 price feeds, with bound validation against Curve EMA price |
PythAdapter |
Adapts Pyth Network feeds to Chainlink's AggregatorV3Interface (used for SEK/USD) |
MorphoOracle |
Adapts BasketOracle to Morpho Blue's oracle scale (24 decimals for USDC/plDXY) |
StakedOracle |
Wraps underlying oracle to price ERC-4626 staked token shares |
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.
| Contract | Description |
|---|---|
ZapRouter |
Single-sided plDXY-BULL minting and burning using flash mints. Permit support. |
LeverageRouter |
Leveraged plDXY-BEAR positions via Morpho Blue flash loans. Open/close/add/remove collateral. Permit support. |
BullLeverageRouter |
Leveraged plDXY-BULL positions via Morpho + plDXY-BEAR flash mints. Open/close/add/remove collateral. Permit support. |
| Contract | Description |
|---|---|
VaultAdapter |
ERC-4626 wrapper for Morpho Vault vault yield. Owner can claimRewards() from external distributors (Merkl, URD). |
| Contract | Description |
|---|---|
InvarCoin |
Global purchasing power vault backed 50/50 by USDC + plDXY-BEAR via Curve LP |
InvarCoin is a passive savings token that maintains exposure to a basket of global currencies. Users deposit USDC, which the vault pairs with plDXY-BEAR through Curve to create a balanced position that hedges against USD weakness.
Deposit flow: USDC in → mint INVAR shares (priced against optimistic NAV to prevent dilution).
Withdraw flow: Burn INVAR → receive USDC from local buffer + JIT Curve LP unwinding. An EMA-based slippage floor prevents MEV sandwich amplification on the Curve leg.
LP deposit: Advanced path for depositing USDC + BEAR directly into Curve LP.
LP withdraw: Balanced exit returning pro-rata USDC + BEAR. lpWithdraw intentionally works when paused, serving as the emergency exit.
Yield: Curve LP trading fees accrue as virtual price growth. Keepers call harvest() to mint INVAR proportional to the fee yield and donate it to sINVAR (StakedToken) stakers via a 1-hour streaming window. Only fee yield is captured — price appreciation of the underlying assets is excluded via VP-based cost tracking.
Keeper operations:
deployToCurve()— pushes excess USDC buffer (>2% target) into single-sided Curve LPreplenishBuffer()— burns Curve LP to restore the 2% USDC bufferharvest()— captures LP fee yield and streams to sINVAR stakers
Safety:
- Dual LP pricing: pessimistic (min of EMA, oracle) for withdrawals, optimistic (max) for deposits
- Spot-vs-EMA deviation guard (0.5%) blocks deposits/deployments during pool manipulation
- Virtual shares (1e18/1e6) prevent first-depositor inflation attacks
_harvestSafe()wraps all Curve + oracle calls in try/catch to guarantee withdrawal livenesssetEmergencyMode()resets LP tracking without touching Curve, enabling exits even if the pool is fully bricked- L2 sequencer uptime validation on all oracle reads including harvest
- Chainlink - Price feeds for EUR/USD, JPY/USD, GBP/USD, CAD/USD, CHF/USD
- Pyth Network - Price feed for SEK/USD (via PythAdapter)
- Curve Finance - AMM pools for USDC/plDXY-BEAR swaps
- Morpho Vault - Yield generation on idle USDC reserves via VaultAdapter
Flash loan USDC from Morpho → swap to plDXY-BEAR on Curve → stake → deposit splDXY-BEAR as Morpho collateral.
Flash loan USDC from Morpho → mint BEAR+BULL pairs → sell BEAR on Curve → stake BULL → deposit splDXY-BULL as Morpho collateral.
Both routers use fee-free Morpho flash loans and a fixed debt model: debt = principal × (leverage - 1).
- StakedToken - ERC-4626 vaults (splDXY-BEAR, splDXY-BULL) that receive streaming USDC rewards
- RewardDistributor - Permissionless
distributeRewards()allocates yield from SyntheticSplitter, favoring the underperforming token's stakers to incentivize price convergence
The SyntheticSplitter maintains a 10% local buffer of USDC for redemptions. Anyone can call deployToAdapter() to push excess USDC to yield adapters, targeting 90% deployment. 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.
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
Both routers use a fixed debt model: debt = principal × (leverage - 1). For 2x leverage with $100 principal, Morpho debt is always $100.
Morpho Blue provides fee-free flash loans, making leveraged positions more capital-efficient.
After opening, users can adjust positions with addCollateral() and removeCollateral() without closing.
Both routers include MEV protection via user-defined slippage caps (max 1%). All USDC entry points support EIP-2612 permits for gasless approvals.
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.
The protocol operates in three states:
- ACTIVE - Normal operations (mint, burn, swap). If Chainlink and Curve EMA prices diverge >2%, BasketOracle reverts, blocking minting, leverage, and reward distribution until prices converge. Burns and swaps remain available—the 10% liquid buffer ensures users can always exit.
- PAUSED - Emergency pause (minting and reward distribution blocked, burning allowed so users can exit, gradual adapter withdrawal enabled)
- SETTLED - End-of-life when plDXY hits CAP price (only redemptions allowed)
forge buildforge test # Run all tests
forge test -vvv # Verbose output
forge coverage # Generate coverage reportFork 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 -vvvFork 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 |
RewardDistributorFork.t.sol |
Reward distribution with real oracle prices |
YieldIntegrationFork.t.sol |
E2E yield pipeline: Morpho vault → harvest → distribute → staker share price |
PermitFork.t.sol |
EIP-2612 permit-based deposits |
SlippageReport.t.sol |
Slippage analysis across trade sizes |
Run a specific fork test file:
source .env && forge test --match-path test/fork/LeverageRouterFork.t.sol --fork-url $MAINNET_RPC_URL -vvvDeploy to Sepolia testnet with a custom Morpho Blue instance (the public Morpho on Sepolia has no enabled IRMs/LLTVs):
# Required environment variables in .env:
# TEST_PRIVATE_KEY=0x... (your deployer private key)
# SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_KEY
# Deploy (43 transactions - may take a few minutes)
source .env && forge script script/DeployToSepolia.s.sol --tc DeployToSepolia \
--rpc-url $SEPOLIA_RPC_URL \
--broadcast
# Verify contracts on Etherscan (optional)
# ETHERSCAN_API_KEY=... in .env
source .env && forge verify-contract <ADDRESS> <CONTRACT> \
--chain sepolia --etherscan-api-key $ETHERSCAN_API_KEYThe Sepolia deployment script:
- Deploys its own Morpho Blue instance with a ZeroRateIrm (0% interest for testnet)
- Creates all protocol contracts, oracles, and routers
- Seeds Curve pool and Morpho markets with liquidity
- Mints 100k MockUSDC to the deployer
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 with real Chainlink/Pyth oracles (mints 100k USDC to deployer)
TEST_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
forge script script/DeployToAnvilFork.s.sol --tc DeployToAnvilFork \
--rpc-url http://127.0.0.1:8545 \
--broadcast
# 3. (Optional) Simulate yield accrual (1% of adapter assets)
cast send <MOCK_YIELD_ADAPTER> "generateYield()" \
--rpc-url http://127.0.0.1:8545 \
--private-key 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
# 4. (Optional) Seed Morpho markets for leverage testing
# Morpho seeding is built into DeployToTest.s.sol for testnet deploys.
# For Anvil fork, create markets manually using cast commands.The Anvil fork deployment uses real mainnet oracles (Chainlink for EUR/JPY/GBP/CAD/CHF, Pyth for SEK) with prices frozen at the fork block. MockUSDC and MockYieldAdapter are still used for flexible testing.
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
forge fmt # Format code
forge fmt --check # Check formattingGenerate 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- 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–24 hour timeouts depending on context)
- Oracle bound validation against Curve EMA to prevent price manipulation
- Flash loan callback validation (initiator + lender checks)
- Yield adapter uses Morpho Vault vault accounting for yield generation
For detailed security assumptions, trust model, and emergency procedures, see SECURITY.md.
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.