On-chain perpetual autocallable structured products backed by real assets
Chainlink Convergence Hackathon 2026 | DeFi & Tokenization Track
Phoenix uses Chainlink's Compute Runtime Environment (CRE) as the core automation engine:
- 19 vaults observed by a single CRE workflow across 6 data sources
- DON consensus via
consensusMedianAggregation()-- no single point of failure - Cron-triggered barrier evaluation -> signed
onReport()delivery - Defense-in-depth: circuit breakers in both CRE workflow AND on-chain contracts
Structured products are a multi-trillion dollar market in traditional finance. Autocalls, reverse convertibles, and capital-protected notes make up a significant share of institutional derivative trading. Yet in DeFi, these products are virtually nonexistent -- the ecosystem has basic yield vaults and vanilla options, but nothing that replicates the richness and conditional payoff mechanics of TradFi structured products.
Phoenix Protocol brings perpetual autocall vaults on-chain, powered by Chainlink CRE (Compute Runtime Environment) to automate the entire observation-evaluation-settlement pipeline. A single CRE workflow observes prices from five different data sources, evaluates three-tier barrier conditions, and writes settlement actions directly to the blockchain -- all without manual intervention or centralized keepers.
The vault holds the actual underlying -- tokenized equities (xStocks via Backed Finance) or crypto assets (wstETH) -- not a USDC pool tracking a price feed. Two sides take opposing views: Side A deposits the asset and earns USDC coupons, Side B deposits USDC and buys crash protection. The smart contract is the clearinghouse. No bank, no issuer.
Frontend
(React + Vite + wagmi)
|
v
+-------------------------------+
| Smart Contracts |
| Factory + Vault + Settlement |
+-------------------------------+
| | |
v v v
Chainlink CRE CowSwap Side A / Side B
(Cron + HTTP + EVM) GPv2 (asset + USDC pools)
|
+---------+---------+---------+---------+
| | | | |
Pendle Hyperliquid Aave DeFi Llama xStocks
(YT APY) (perp mid) (rates) (prices) (stocks)
Side A (Yield) Side B (Hedge)
deposits underlying asset deposits USDC
earns USDC coupons from B pays fixed coupons to A
risk: partial liquidation on KI reward: USDC from KI liquidation
| |
+---------> PerpetualVault <-------------+
|
PerpetualSettlement
|
CRE Oracle (onReport)
|
Barrier evaluation
|
CONTINUE | PAY_COUPON | CYCLE_RESET | KNOCK_IN
Side A (Yield) -- Deposits the underlying asset (xTSLA, wstETH, etc.). Earns periodic USDC coupons from Side B. On knock-in, a portion of holdings is liquidated via CowSwap. "I already hold this asset. I want yield on it."
Side B (Hedge) -- Deposits USDC. Pays fixed coupons. On knock-in, receives USDC from the liquidation of A's asset. "I want defined-cost crash protection."
When a coupon is missed (price falls between the knock-in and coupon barriers), the missed coupon is not lost -- it is remembered. On the next observation where a coupon is paid or the cycle resets, all previously missed coupons are paid retroactively. A knock-in event forfeits all accumulated missed coupons. Memory is capped at a configurable depth cap per product.
Example scenario (2% coupon per period, $1.00 strike):
| Period | Price | Action | Missed Count | Payout |
|---|---|---|---|---|
| 1 | $0.96 | PAY_COUPON | 0 | $0.02 (current) |
| 2 | $0.70 | CONTINUE | 1 | $0.00 (missed, remembered) |
| 3 | $0.72 | CONTINUE | 2 | $0.00 (missed, remembered) |
| 4 | $0.96 | PAY_COUPON | 0 | $0.06 (current + 2 memory) |
A single CRE workflow routes to five different external APIs via a discriminated union pattern in the configuration. Each vault specifies its data source type and parameters, and the workflow dynamically dispatches to the correct fetch function at runtime.
The full observation lifecycle runs through Chainlink CRE: cron trigger fires, HTTP capabilities fetch prices with DON consensus, barrier logic executes in WASM, and the signed report is written on-chain via IReceiver.onReport(). No manual keepers, no centralized servers. The settlement contract validates CRE workflow metadata (author + name hash) and re-validates the submitted action against on-chain barriers.
No expiry. After max periods or cycle reset, the cycle resets with the strike price set to the current market price. Four actions: CONTINUE (coupon missed, remembered), PAY_COUPON (pays current + memory), CYCLE_RESET (settles and resets), KNOCK_IN (liquidation, memory forfeited). No dead vaults, no redeployment.
Knock-in settlement uses CowSwap's GPv2Settlement for on-chain liquidation:
- Initiate: Calculate loss ratio, approve GPv2 VaultRelayer, set liquidation PENDING, block vault operations
- PreSign: Validate order parameters against pending liquidation, presign on GPv2Settlement
- Finalize: Verify USDC proceeds meet slippage tolerance, credit Side B (capped at expected to prevent donation attacks)
- Cancel: If order unfilled after 1-hour deadline, save for retry, revoke approval, resume
- Retry: Re-initiate a previously failed/cancelled liquidation
The PerpetualFactory contract creates new vaults with fully configurable parameters: underlying asset, barrier levels, coupon rate, periods per cycle, memory depth cap, and slippage tolerance. Each vault is a standalone contract registered with the shared settlement engine. Validates parameter ordering (autocall > coupon > knock-in), coupon rate 0--50%, and memory depth > 0.
An autocall is a structured product with periodic observations. At each observation date, the CRE workflow fetches the current price and evaluates it against three barrier levels:
Observation Date (CRE Cron fires)
|
Fetch price from data source
|
v
price >= autocall barrier (e.g. 105%)
|
+----+----+
| YES | NO
v v
CYCLE_RESET price >= coupon barrier (e.g. 95%)
(all coupons AND price < autocall barrier
+ memory, |
cycle resets) +----+----+
| YES | NO
v v
PAY_COUPON price <= knock-in barrier (e.g. 75%)
(+ memory |
coupons) +----+----+
| YES | NO
v v
KNOCK_IN CONTINUE
(liquidation, (missed,
memory remembered)
forfeited)
| Source | Type | API | Assets |
|---|---|---|---|
| Pendle | Implied APY | api-v2.pendle.finance |
uniETH YT, sUSDai YT, weETH YT, wstETH YT |
| Hyperliquid | Perp Mid-Price | api.hyperliquid.xyz |
ETH, BTC, SOL |
| xStocks | Stock Price | xstocks.finance |
TSLA, AAPL, NVDA |
| Aave (DeFi Llama) | Supply Rate | yields.llama.fi |
WETH, USDC, USDT, sGHO |
| DeFi Llama | Token Price | coins.llama.fi |
ETH, BTC, SOL, stETH, wstETH |
All sources are fetched through the CRE HTTPClient with DON median aggregation for consensus.
| Category | Vaults | Data Source | Autocall | Coupon | Knock-in | Coupon Rate |
|---|---|---|---|---|---|---|
| Spot | ETH, BTC, SOL, stETH, wstETH | DeFi Llama | 105% | 95% | 75% | 2--2.5% |
| Perp | ETH, BTC, SOL | Hyperliquid | 105% | 95% | 75% | 2--2.5% |
| Yield | uniETH YT, sUSDai YT, weETH YT, wstETH YT | Pendle | 110% | 90% | 60% | 2--2.5% |
| Rate | Aave WETH, USDC, USDT, sGHO | DeFi Llama | 110% | 90% | 60% | 1.5% |
| Equity | TSLA, AAPL, NVDA | xStocks | 110% | 90% | 70% | 2.5--3% |
All vaults use 6 periods per cycle and memory depth cap of 4.
| Layer | Technology | Purpose |
|---|---|---|
| Contracts | Solidity 0.8.24, Foundry, OpenZeppelin v5 | Vault, settlement, factory |
| Oracle | Chainlink CRE, TypeScript/WASM | Price observation, barrier evaluation, settlement |
| Liquidation | CowSwap GPv2Settlement | On-chain knock-in asset liquidation |
| Frontend | React 18, Vite, Tailwind CSS, wagmi v2, RainbowKit | Wallet interaction, vault browsing |
| Network | Ethereum Sepolia (chainId 11155111) | Deployment target |
contracts/
src/
PerpetualSettlement.sol -- CRE receiver, perpetual cycles, memory coupons
PerpetualVault.sol -- Two-sided vault (asset + USDC), CowSwap liquidation
PerpetualFactory.sol -- Vault creation with asset-backed params
MockUSDC.sol -- Test token
MockAsset.sol -- Mock underlying asset for testing
MockCowSwap.sol -- Mock CowSwap for testing
interfaces/
IReceiver.sol -- CRE-mandated receiver interface
IPerpetualSettlement.sol -- Settlement interface and types
ICowSwapSettlement.sol -- Legacy CowSwap interface
IGPv2Settlement.sol -- GPv2Settlement interface
libraries/
GPv2OrderLib.sol -- Order hashing and UID computation
test/
PerpetualSettlement.t.sol -- 50 tests
PerpetualVault.t.sol -- 68 tests
PerpetualVaultCow.t.sol -- 31 tests (CowSwap two-phase liquidation)
PerpetualIntegration.t.sol -- 6 tests
script/
DeployPerpetualCow.s.sol -- Deployment script (GPv2 mode, 19 vaults)
cre-workflow/
src/
workflows/
perpetual-observation/ -- CRE workflow
index.ts -- Perpetual workflow (dynamic strike from getCycleState)
abi.ts -- Perpetual settlement ABI
config.json -- 19-product configuration
workflow.yaml -- CRE workflow settings
project.yaml -- CRE project settings (RPCs, chain config)
frontend/
src/
config/
perpetualAddresses.ts -- Deployed contract addresses + vault metadata
lib/
perpetualTypes.ts -- TypeScript interfaces for on-chain structs
hooks/
usePerpetualVaults.ts -- Multicall: factory -> vault -> settlement
usePerpetualVaultDetails.ts -- Single vault detail + cycle history
components/
TwoSidedDeposit.tsx -- Side A/B deposit widget
CycleHistory.tsx -- Archived cycle records table
pages/
VaultListPage.tsx -- Vault list (earn/hedge mode)
VaultDetailPage.tsx -- Vault detail (metrics, barriers, deposit, history)
docs/ -- Documentation pages
cd contracts
forge build
forge test -vvvcd cre-workflow
bun install
cre workflow simulate . -T local-simulation --non-interactive --trigger-index 0cd frontend
bun install
bun dev| Contract | Address |
|---|---|
| PerpetualSettlement | 0xcbe9B34CDbD9Bb38b853fb58E0016A6AC0FC06Bb |
| PerpetualFactory | 0x8840b23aFb4E5696312B7Fb24ea01EFB8c90D2b2 |
| MockUSDC | 0x5b564dcC5BeBdC348650F0FCcCa71FA9a7fF101b |
Deployer: 0x8Fd983b62Ab8Cb7df019fD8B5B5D333E3D9A16AD
19 vaults deployed across 5 categories, each seeded with initial liquidity on both sides.
219 tests across 6 test files:
| Test File | Tests | Coverage |
|---|---|---|
PerpetualSettlement.t.sol |
50 | All 4 actions, cycle management, memory depth cap, multi-cycle, knock-in recovery, relay auth, ERC165, barrier validation, circuit breaker, minimum observation interval |
PerpetualVault.t.sol |
68 | Deposits (A/B), withdrawals, dynamic coupon rate scaling, share accounting, multi-user pro-rata, donation attack mitigation, rebasing, soft cap, fuzz tests |
PerpetualVaultCow.t.sol |
31 | Two-phase CowSwap liquidation, preSignOrder validation, finalize with proceeds, cancel after deadline, retry liquidation, deposits/withdrawals/observations blocked during pending liquidation, fuzz tests |
PerpetualIntegration.t.sol |
6 | Full lifecycle happy path, knock-in liquidation + withdrawal, multi-user pro-rata, mid-cycle deposits, 3-cycle run, factory creates vault |
cd contracts
forge test -vvvAll 219 tests pass.
| File | Role |
|---|---|
cre-workflow/src/workflows/perpetual-observation/index.ts |
Main entry point -- cron trigger, 5 data source handlers (DeFi Llama, Hyperliquid, Pendle, Aave, xStocks), barrier evaluation, consensusMedianAggregation(), on-chain write via IReceiver.onReport() |
cre-workflow/src/workflows/perpetual-observation/logic.ts |
Pure barrier logic -- evaluateBarriers(), validatePrice() (circuit breakers: max price, 50% deviation), toBigInt18(), fetchWithRetry() |
cre-workflow/src/workflows/perpetual-observation/abi.ts |
Settlement contract ABI for getCycleState() and getProductParams() reads, Action enum |
cre-workflow/src/workflows/perpetual-observation/config-batch-1.json |
Batch 1 config: PIDs 0-4 (DeFi Llama spot: ETH, BTC, SOL, stETH, wstETH) |
cre-workflow/src/workflows/perpetual-observation/config-batch-2.json |
Batch 2 config: PIDs 5-9 (Hyperliquid perps + Pendle YT) |
cre-workflow/src/workflows/perpetual-observation/config-batch-3.json |
Batch 3 config: PIDs 10-14 (Pendle YT + Aave rates) |
cre-workflow/src/workflows/perpetual-observation/config-batch-4.json |
Batch 4 config: PIDs 15-18 (Aave sGHO + xStocks equities) |
cre-workflow/src/workflows/perpetual-observation/workflow.yaml |
CRE workflow settings (8 targets: 4 local + 4 staging batches) |
cre-workflow/project.yaml.example |
CRE project settings template (RPC endpoints per batch) |
| File | Tests |
|---|---|
cre-workflow/src/workflows/perpetual-observation/__tests__/barriers.test.ts |
20 tests -- all barrier regions, exact boundaries, custom params |
cre-workflow/src/workflows/perpetual-observation/__tests__/validation.test.ts |
32 tests -- zero/negative prices, max bounds, deviation circuit breaker |
cre-workflow/src/workflows/perpetual-observation/__tests__/utils.test.ts |
20 tests -- toBigInt18 precision, fetchWithRetry logic |
| File | Role |
|---|---|
contracts/src/interfaces/IReceiver.sol |
CRE-mandated receiver interface: onReport(bytes metadata, bytes report) |
contracts/src/PerpetualSettlement.sol |
IReceiver implementation -- validates CRE workflow author + name hash from metadata, decodes report (productId, price, action), enforces circuit breakers (MAX_PRICE, 50% deviation, 1h min interval), re-validates action against on-chain barriers, routes to PAY_COUPON / CYCLE_RESET / KNOCK_IN / CONTINUE with memory coupon logic |
contracts/src/interfaces/IPerpetualSettlement.sol |
Settlement types: ProductParams, CycleState, Action enum, CycleRecord |
The CRE workflow (index.ts) implements a four-step pipeline executed by the Chainlink DON:
Step 0 -- Gate check: Read getCycleState() from the settlement contract. Read dynamic strikePrice for barrier evaluation (resets each cycle).
Step 1 -- Fetch price: Route to the correct data source handler based on the discriminated union config:
pendle-- GET request to Pendle Finance API, extract implied APYhyperliquid_perp-- POST to Hyperliquid info endpoint withallMids, extract mid-price for the assetaave_rate-- GET from DeFi Llama Yields API, find pool by UUID, extract APYdefillama_price-- GET from DeFi Llama Coins API, extract token pricexstocks-- GET from xStocks Finance API, extract stock price
Step 2 -- Read params: Call getProductParams() on the settlement contract to get barrier levels and coupon rate.
Step 3 -- Evaluate barriers: Compare the fetched price against autocall, coupon, and knock-in barrier prices. >= for autocall/coupon, <= for knock-in. Return the appropriate Action.
Step 4 -- Write on-chain: ABI-encode the report payload (productId, price, action), prepare a signed report via runtime.report(), and submit via evmClient.writeReport() to the settlement contract's onReport() function.
All HTTP fetches use consensusMedianAggregation() so that DON nodes independently fetch prices and reach consensus on the median value before writing on-chain.
Without Chainlink CRE, building this protocol would require:
- A centralized keeper service to trigger observations on schedule
- Manual price fetching with no consensus or verification
- Trust assumptions around price data integrity
- Custom infrastructure for report signing and on-chain delivery
CRE provides all of this out of the box: cron scheduling, HTTP fetching with DON consensus, cryptographic report signing, and verified on-chain delivery via IReceiver. The workflow compiles to WASM for deterministic execution across all DON nodes, and the median aggregation ensures no single node can manipulate the price observation.
| Scenario | Video |
|---|---|
| Full Protocol Overview | video.mp4 |
| Bull Market -- Early Redemption | bull-market-early-redemption.mp4 |
| Bear Market -- Capital Protection (Side B) | bear-market-capital-protection.mp4 |
| Bear Market -- Capital Loss (Knock-in) | bear-market-capital-loss.mp4 |
- Visit the live app and connect your wallet (Ethereum Sepolia)
- Browse vaults at
/app/earn(Side A) or/app/hedge(Side B) - Open a vault detail page to view barrier levels, cycle progress, and Side A/B ratio
- Mint test USDC and approve the vault
- Deposit into your chosen side (underlying asset for A, USDC for B)
- CRE workflow observes prices on cron schedule and evaluates barriers
- View the observation timeline showing each period's action and Phoenix memory coupon behavior
- Request a withdrawal, wait 1 hour, then execute to receive your proportional payout
MIT