Smile

TL;DR: Standard options market potentially as popular as Robinhood, decentralized as Polymarket.

A non-custodial, parametric options marketplace that solves three interlocking problems in DeFi options: thin liquidity at each strike, yield-killing collateral lock-up, and the absence of emergent market makers. By combining 1inch Aqua, Uniswap v4 Hooks, and Chainlink CRE, LPs can quote an entire strike range from one capital pool β€” while their collateral keeps earning DeFi yield until a buyer actually matches.


Table of Contents

  1. The Thesis
  2. Architecture
  3. Mathematical Specification
  4. Flow Diagrams
  5. Deployed Addresses (Sepolia)
  6. How to Run the Project
  7. End-to-End Demo Walkthrough
  8. Glossary
  9. Project Structure
  10. Technical Stack

More docs: Aqua Incubator grant proposal Β· build notes & war stories Β· verified CRE simulation transcript


πŸš€ The Thesis

While prediction markets β€” binary options on event outcomes β€” have been widely successful in DeFi (Polymarket, Augur), standard options have not. Prediction markets do not offer many strategies retail traders have been increasingly investing in: selling covered calls to generate yield on held ETH, selling cash-secured puts to acquire ETH at a discount, buying butterflies to express a range-bound view on volatility, etc. The building blocks for this popular market requires a functioning options market with real liquidity across strikes and expiries for standard options (buys and sells of puts and calls). That market has never materialized on-chain: Ribbon and Friktion pioneered DeFi Options Vaults (DOVs) but suffer from trapped liquidity: collateral is locked per strike chosen by the vault manager, leaving the rest of the chain empty. Premia introduced RFQ-based pricing that relies on institutional market makers for quotes, creating a dependency on off-chain liquidity.

Smile attempts to overcome these limitations to on-chain standard opions trading by using Aqua's non-custodial LP to remediate:

  1. Liquidity fragmentation across strikes and expiries, until a buyer is matched. Makers can offer liquidity across a range of strikes and expiries, increasing net liquidity.
  2. Collateral lockup in LPs, and forfeited dividend yield β€” which is not a limitation of standard options writers β€” is also removed by Aqua's non-custodial LP.
  3. Standard options markets work because broker-dealers delta-hedge their books against the spot market. Smile attempts to use the trading and settlement functionality provided by Uniswap and Chainlink to allow clever LPs and arbitrageurs to continuously arbitraging away mispricings between options and the underlying. Specifically:
    • Fast trading and premium transfer via Uniswap Trading API
    • Vol surface repricing post-trade via Uniswap v4 Hooks across strikes and expiries
    • Options payoff settlement and redemption via Chainlink CRE

πŸ—οΈ Architecture

Layer Component Functionality
Pricing SmileSwapVMRouter + OptionPricingEngine Custom instruction (opcode 33) on the official 1inch SwapVM pricing off a multiparameter vol surface: Οƒ per tenor bucket + skew, Οƒstrike=Οƒtenorβ‹…(1+Ξ±β‹…ln⁑(K/S)2+Ξ²β‹…ln⁑(K/S))\sigma_{strike} = \sigma_{tenor} \cdot (1 + \alpha \cdot \ln(K/S)^2 + \beta \cdot \ln(K/S)), time-value =Sβ‹…Οƒstrikeβ‹…T= S \cdot \sigma_{strike} \cdot \sqrt{T}. The instruction is two-sided: forward direction prices the Ask, reverse the Bid. Oracle reads enforce Chainlink freshness.
Liquidity official 1inch Aqua + AquaCollateralVault LP calls authorizeRange(K_{min}, K_{max}, \text{DTE}, \text{maxCollateral}), then ships the strategy with the official Aqua.ship(). On buy(), the SwapVM swap Aqua.push()es the premium into the LP wallet and Aqua.pull()s collateral JIT into escrow. OptionToken deployed lazily per strike.
Market OptionPricingHook + Uniswap Trading API v4 Hook: beforeSwap vetoes mispriced trades; afterSwap shifts the vol surface. Trading API used for (1) live ETH/USD spot price and (2) routing the buyer's ETH→USDC premium swap via the Universal Router on each trade.
Settlement AquaOptionSettlement + Chainlink CRE Every minted series is registered at buy time. At expiry, settlement is permissionless: anyone supplies the Chainlink roundId covering expiry and the contract verifies on-chain that it is the first post-expiry round (settleWithChainlinkRound) β€” no trusted writer. The scheduled CRE DON path (settleSeries) remains as a keeper. Holders redeem() the cash-settled intrinsic from the vault; LPs reclaimCollateral() for the exact remainder.
Asset OptionToken ERC-20 option position. Vault is owner, so can burn without allowance. Tradeable on any DEX for secondary-market price discovery.

Official 1inch Aqua + SwapVM integration

The liquidity layer runs on the official contracts β€” 1inch/aqua and 1inch/swap-vm (release/1.2), vendored under lib/ and compiled unmodified:

Revenue model (protocol fee via the official fee opcode)

Every option buy carries a protocol fee (default 1%, capped at 5%) that accrues to a fee recipient β€” e.g. the 1inch DAO treasury β€” routed through the official SwapVM fee instruction, not custom plumbing:

Collateralization Model

V1 (current) β€” Cash-Secured / Covered (Fully Collateralized)

The simplest and safest model. To mint an ETH Call at a 3,500 strike, the LP backs it with 1 WETH (Covered Call). To mint a Put, the LP backs it with 3,500 USDC (Cash-Secured Put). The collateral is authorized JIT via Aqua β€” it never leaves the LP's wallet until a buyer matches β€” but it is always fully present and earmarked.

Solvency is trivially guaranteed: if the option expires in-the-money, the locked assets are delivered to the buyer. No price oracle is needed for margining and no liquidation engine exists β€” there is nothing to liquidate.

V2 (out of scope) β€” Margin & Liquidation (Under-Collateralized)

Advanced platforms like Derive allow LPs to post fractional collateral (e.g., 500 USDC to back a 3,500 ETH Call). As spot moves against the LP, the liability grows. If collateral falls below a safety threshold (e.g., 110% of the option's current market value), external liquidation bots forcibly close the position β€” buying back the option from the market using the LP's remaining collateral before bad debt accrues.

This model unlocks capital efficiency but requires an on-chain margin engine, a liquidation keeper network, and robust price feeds at sub-second granularity. It is explicitly out of scope for V1.


πŸ“ Mathematical Specification

1. Multiparameter Volatility Surface

Οƒstrike(T)=Οƒtenor(T)β‹…max⁑ ⁣(0.1,β€…β€Š1+Ξ±β‹…ln⁑(K/S)2+Ξ²β‹…ln⁑(K/S))\sigma_{strike}(T) = \sigma_{tenor}(T) \cdot \max\!\big(0.1,\; 1 + \alpha \cdot \ln(K/S)^2 + \beta \cdot \ln(K/S)\big)

2. Premium Calculation

P=max⁑(Β±(Sβˆ’K), 0)⏟intrinsicΒ (call/put)+Sβ‹…Οƒstrikeβ‹…Tβ‹…min⁑(S,K)max⁑(S,K)⏟moneyness-dampedΒ time-valueP = \underbrace{\max(\pm(S - K),\, 0)}_{\text{intrinsic (call/put)}} + \underbrace{S \cdot \sigma_{strike} \cdot \sqrt{T} \cdot \tfrac{\min(S,K)}{\max(S,K)}}_{\text{moneyness-damped time-value}}

Gas-efficient on-chain approximation β€” omits N(d1)N(d_1) and N(d2)N(d_2) to avoid square-root-heavy distributions.

3. Οƒ Feedback Loop (tenor-aware)

Οƒtenor, t+1=Οƒtenor, t+Ξ³β‹…sign(trade)\sigma_{tenor,\,t+1} = \sigma_{tenor,\,t} + \gamma \cdot \text{sign}(\text{trade})

Design note β€” why pricing is on-chain, and why Οƒtenor\sigma_{tenor} is a step function. OptionPricingHook.sigmaFor is read atomically inside the same swap that buys or sells the option, so the price a trader gets is exactly whatever the bucket lookup returns at that block β€” no off-chain quote to go stale or be front-run. This isn't architecturally required: AquaOptionSettlement already sources its expiry price off-chain via the Chainlink CRE forwarder (Β§5), so an RFQ-style premium quote (a signed off-chain price, verified on-chain much like a CRE report) is possible in principle. The tradeoff:

V1 keeps pricing on-chain and discrete; smooth interpolation is left for a future RFQ-style quoting layer.

4. Black-Scholes Delta (Frontend)

Delta (Ξ”\Delta) is computed client-side for the matrix display. Not used in on-chain pricing.

Ξ”=N(d1),d1=ln⁑(S/K)+12Οƒstrike2β‹…TΟƒstrikeβ‹…T\Delta = N(d_1), \qquad d_1 = \frac{\ln(S/K) + \frac{1}{2}\sigma_{strike}^2 \cdot T}{\sigma_{strike} \cdot \sqrt{T}}

N(β‹…)N(\cdot) is approximated via Abramowitz & Stegun 26.2.17 (max error 1.5Γ—10βˆ’71.5 \times 10^{-7}, no lookup tables). Οƒstrike\sigma_{strike} from Β§1 is used β€” ensuring delta reflects the vol surface curvature, not flat vol.

Delta ranges 0–1 for calls (0 = deep OTM, 1 = deep ITM). A 0.5-delta call is approximately ATM.

5. Expiry Settlement β€” Permissionless + Chainlink CRE

A parametric option is only as trustworthy as the price it settles against. At expiry every open series needs one final spot price SfinalS_{final} written on-chain, because that single number decides every payout: holders redeem the in-the-money intrinsic and the LP reclaims the remainder (see Β§6 flow). Every series is registered with AquaOptionSettlement at first mint, and can then be settled by either of two paths:

Path A β€” permissionless Chainlink-round settlement (trustless). Anyone β€” a keeper, the holder, the LP β€” calls settleWithChainlinkRound(seriesId, roundId) with the Chainlink ETH/USD round covering expiry. The contract verifies on-chain that the round was updated at/after expiry and that its predecessor was updated before expiry (i.e. it is the first post-expiry round), so nobody can cherry-pick a later, more favorable price:

Sfinal=getRoundData(roundId).answerβ€…β€Šβ€…β€Š(8-dec)β€…β€Šβ†’β€…β€ŠWADΒ 18-decS_{final} = \mathtt{getRoundData(roundId).answer} \;\; (\text{8-dec}) \;\rightarrow\; \text{WAD 18-dec}

No trusted writer exists on this path β€” settlement liveness reduces to the feed's.

Path B β€” Chainlink CRE (scheduled keeper). A CRE cron trigger fires the settlement workflow at expiry: the DON reads the same aggregator at the last finalized block (every node observes an identical value), reaches consensus, and the DON-signed report calls settleSeries(seriesId, S_final) through the CRE forwarder β€” so a series settles on schedule even if nobody races to call Path A.

In short: 1inch Aqua holds the collateral, Uniswap prices and routes the trade, and settlement is available trustlessly to anyone with Chainlink CRE as the scheduled closer.

Design note. Because both paths resolve to the on-chain Chainlink feed, the DON's role is a scheduled, trust-minimized keeper (deterministic read + signed write) rather than novel off-chain data sourcing.


πŸ”„ Flow Diagrams

Color key: 🟒 1inch Aqua Β· 🩷 Uniswap Β· πŸ”΅ Chainlink

0. System Overview

sequenceDiagram
    participant Trader
    participant Frontend
    participant SwapVM as 🟒 SwapVM (OptionPricingEngine)
    participant Aqua as 🟒 1inch Aqua (AquaCollateralVault)
    participant Maker as 🟒 Maker (LP Wallet)
    participant CRE as πŸ”΅ Chainlink CRE DON
    participant Settle as 🟒 AquaOptionSettlement

    rect rgba(60,80,120,0.12)
    Note over Trader,Maker: Trade (pre-expiry)
    Trader->>Frontend: Select strike & expiry (or compose a strategy)
    Frontend->>SwapVM: quote(order, tokens, amount)
    SwapVM-->>Frontend: Ask (fee-inclusive)
    Frontend-->>Trader: Option matrix / strategy builder
    Trader->>Aqua: buy(authId, K, amount, maxPremium)
    Aqua->>SwapVM: swap β€” custom optionPremium opcode prices at Ask
    SwapVM->>Maker: Aqua.push premium β†’ LP wallet
    SwapVM->>Maker: Aqua.pull collateral JIT β†’ vault escrow
    Note over SwapVM: Aqua.pull fee β†’ DAO treasury (1%)
    Aqua-->>Trader: OptionToken minted
    end

    rect rgba(40,90,140,0.18)
    Note over CRE,Settle: Expiry settlement (DTE = 0) β€” two paths
    Trader->>Settle: settleWithChainlinkRound(seriesId, roundId) β€” ANYONE, trustless
    Note over Settle: verifies first post-expiry round on-chain
    CRE->>Settle: …or settleSeries(S_final) via scheduled DON (onlyCRE)
    Note over Settle: series settled=true β†’ redemption unlocked
    Trader->>Aqua: redeem(optionToken, amount) β†’ ITM intrinsic from escrow
    Maker->>Aqua: reclaimCollateral(optionToken) β†’ exact remainder
    end

1. Range Authorization + Ship (LP)

The LP authorizes a strike range from one collateral pool, then ships it on the official Aqua registry. No collateral moves at any stage β€” it stays in the LP's wallet earning yield; Aqua.ship() only records virtual balances.

sequenceDiagram
    participant LP
    participant Frontend
    participant Vault as 🟒 AquaCollateralVault
    participant Aqua as 🟒 official 1inch Aqua

    LP->>Frontend: K_min, K_max, DTE, maxCollateral
    Frontend->>Aqua: ERC20.approve(Aqua) β€” collateral + premium token
    Note over LP: collateral stays in wallet β€” earns yield until matched
    Frontend->>Vault: authorizeRange(...) β†’ authId
    Vault-->>Frontend: getShipParams(authId) β€” exact official calldata
    Frontend->>Aqua: ship(app, strategy, tokens, amounts)
    Note over Aqua: strategy = real SwapVM bytecode (salt β†’ deadline β†’ jumpIfTokenIn β†’ fee β†’ optionPremium)
    Aqua-->>LP: βœ“ range live β€” one balance quotes the whole chain

2. Primary Market Buy (Trader)

Premium payment is routed through the Uniswap Trading API (EXACT_OUTPUT ETH→USDC), giving an on-chain Uniswap tx before the vault call.

sequenceDiagram
    participant Buyer
    participant UniAPI as 🩷 Uniswap Trading API
    participant Vault as 🟒 AquaCollateralVault
    participant Router as 🟒 SmileSwapVMRouter (official SwapVM)
    participant LP as 🟒 LP Wallet
    participant DAO as 🟒 Fee Recipient (DAO)

    Buyer->>UniAPI: EXACT_OUTPUT ETH→USDC (premium funding)
    UniAPI-->>Buyer: Universal Router calldata
    Buyer->>Vault: buy(authId, K, amount, maxPremium)
    Vault->>Router: quote + swap β€” taker picks K via instruction args
    Note over Router: custom opcode 33 prices Ask off the live vol surface
    Router->>LP: Aqua.push premium (ask + fee) β†’ LP wallet
    Router->>DAO: Aqua.pull fee (1% gross-up) β†’ treasury
    Router->>Vault: Aqua.pull collateral JIT β†’ escrow
    Vault->>Buyer: mint OptionToken
    Note over Vault: Hook bumps the traded tenor bucket Οƒ += Ξ³

3. Close Position = Sellback at Bid (Holder)

A holder exits before expiry by selling the option back at the live Bid β€” a reverse swap through the same shipped SwapVM strategy. Escrowed collateral returns to the LP wallet via Aqua.push(), which restores the range's JIT capacity; the Bid premium is Aqua.pull()ed from the LP wallet straight to the holder (funded by premiums the LP already earned). Sellbacks are fee-free β€” the strategy bytecode jumps past the fee opcode in the reverse direction. This is also the path arbitrageurs use to monetize Οƒ corrections.

sequenceDiagram
    participant Holder
    participant Vault as 🟒 AquaCollateralVault
    participant Router as 🟒 SmileSwapVMRouter (official SwapVM)
    participant LP as 🟒 LP Wallet
    participant Hook as 🩷 OptionPricingHook

    Holder->>Vault: close(optionToken, lp, amount, minPayout)
    Vault->>Vault: OptionToken.burn(holder, amount)
    Vault->>Router: reverse swap (collateral in β†’ premium out)
    Note over Router: jumpIfTokenIn skips the fee β€” Bid priced by opcode 33
    Router->>LP: Aqua.push collateral β†’ wallet (capacity self-restores)
    Router->>Holder: Aqua.pull Bid premium β†’ holder
    Vault->>Hook: bumpSigma(false, timeToExpiry)
    Note over Hook: traded tenor bucket Οƒ -= Ξ³
    Vault-->>Holder: βœ“ sold back at Bid

4. Arbitrageur as Emergent Market Maker

When on-chain Οƒ diverges from market IV, arbitrageurs capture the spread by buying at the Ask, delta-hedging on spot, and selling back at the Bid once their own demand re-rates Οƒ. The sellback mechanism is what makes the round trip monetizable β€” their activity is the correction. This is the emergent market-making loop.

sequenceDiagram
    participant Arb as Arbitrageur
    participant Vault as 🟒 AquaCollateralVault
    participant UniPool as 🩷 Uniswap v4 ETH/USDC
    participant Hook as 🩷 OptionPricingHook

    Note over Arb: Οƒ_tenor < market IV β€” options underpriced
    Arb->>Vault: buy(authId, K, amount, maxPremium) β€” at Ask
    Arb->>UniPool: short ETH delta hedge
    Note over Arb: Οƒ_tenor rises toward market IV
    Arb->>Vault: close(optionToken, lp, amount, minPayout) β€” at Bid
    Arb->>UniPool: unwind delta hedge
    Note over Arb: profit = (Οƒ_market - Οƒ_entry) * vega

5. Secondary Market Swap (Uniswap v4)

Existing OptionTokens can be resold. The hook vetoes mispriced swaps and adjusts Οƒ. Secondary market only β€” ERC-20 ownership transfers, no minting.

sequenceDiagram
    participant Seller
    participant Pool as 🩷 Uniswap v4 Pool
    participant Hook as 🩷 OptionPricingHook
    participant Oracle as πŸ”΅ Chainlink Price Feed

    Seller->>Pool: swap(OptionToken β†’ USDC)
    Pool->>Hook: beforeSwap(params, hookData)
    Hook->>Oracle: fetch S
    Note over Hook: Veto if |P_exec - P_fair| > 5%
    Hook-->>Pool: βœ“ OK
    Pool->>Pool: OptionToken transfers to buyer
    Pool->>Hook: afterSwap(params)
    Note over Hook: exactIn (sell) β†’ Οƒ -= Ξ³
    Hook-->>Pool: βœ“ Οƒ updated

6. Settlement & Redemption

Every series is registered at first mint. At expiry it settles through either path β€” permissionlessly with the Chainlink round covering expiry (verified on-chain, no trusted writer), or via the scheduled CRE DON. Holders then redeem the cash-settled intrinsic from the vault's escrow; the LP reclaims the exact remainder β€” in any order, with full conservation.

sequenceDiagram
    participant Anyone
    participant CRE as πŸ”΅ Chainlink CRE DON
    participant Feed as πŸ”΅ Chainlink ETH/USD Feed
    participant Settlement as 🟒 AquaOptionSettlement (price registry)
    participant Vault as 🟒 AquaCollateralVault (escrow)
    participant Maker as 🟒 Maker (LP)
    participant Holder as Trader (Holder)

    alt Path A β€” permissionless (trustless)
        Anyone->>Settlement: settleWithChainlinkRound(seriesId, roundId)
        Settlement->>Feed: getRoundData(roundId) + predecessor
        Note over Settlement: verify FIRST round at/after expiry β†’ S_final (WAD)
    else Path B β€” scheduled CRE keeper
        CRE->>Feed: latestRoundData() @ finalized block
        Note over CRE: DON consensus β†’ S_final (WAD)
        CRE->>Settlement: settleSeries(seriesId, S_final) Β· onlyCRE forwarder
    end
    Note over Settlement: settled=true β€” price written exactly once

    Holder->>Vault: redeem(optionToken, amount)
    Note over Vault: call β†’ (Sβˆ’K)/S of collateral per unit Β· put β†’ (Kβˆ’S) USDC
    Vault->>Holder: transfer intrinsic from escrow
    Maker->>Vault: reclaimCollateral(optionToken)
    Vault->>Maker: everything not owed to outstanding holders

πŸ“ Deployed Addresses (Sepolia)

⚠️ Stale β€” v1 deployment. These are the original hackathon contracts, deployed before the official Aqua/SwapVM integration, the two-sided sellback, trustless settlement, and the protocol fee. Their ABIs are incompatible with the current frontend; a redeploy of the new stack (Aqua, SmileSwapVMRouter, oracle, vault, settlement) is pending. Until then, use the local Anvil path, which deploys and exercises everything.

Contract Address
OptionPricingEngine 0x3f5a5b1972Ddac7E81fdf7F6AEFC2633Fa8FF532
OptionPricingHook 0x15578B9248b574194867Ab204bE4161213Acf194
AquaCollateralVault 0x5115fbdb810D1dB316034fF670c65c45d875f887
AquaOptionSettlement 0x5c9E7BB8db084A955acD519f61287d24Ff24F211
USDC (Circle Sepolia) 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238
WETH (canonical Sepolia) 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9

Frontend deployed at https://oslinin.github.io/Smile (WalletConnect enabled). The live site currently targets the v1 contracts above β€” on-chain interactions there will work again after the Sepolia redeploy.


πŸ› οΈ How to Run the Project

1. View Live Site (GitHub Pages)

URL: https://oslinin.github.io/Smile

2. Local Frontend Development

cd frontend
pnpm install
pnpm run dev

Or, from the repo root (no cd needed β€” pnpm targets the workspace by name):

pnpm --filter frontend dev

Open http://localhost:3000. The UI includes the option-chain matrix, the LP range-authorization flow, and an OptionStrat-style strategy builder β€” 20 named strategies (spreads, condors, butterflies, straddles, backspreads, calendars) grouped by market outlook, with up to 6 custom legs, per-leg expiry, a T+0 value curve, breakevens, probability of profit, and net greeks. Entry premiums are quoted with the same smile the on-chain instruction charges, so what you see is what vault.buy() costs.

The Vol Surface Β· Python tab renders the live 3-D volatility surface Οƒstrike(K,T)\sigma_{strike}(K,T) with matplotlib (a Flask service in volsurface/). Every confirmed buy/sell POSTs to the renderer, which bumps the traded tenor bucket by Β±Ξ³\pm\gamma β€” the same feedback loop the on-chain OptionPricingHook.bumpSigma applies β€” so the surface visibly re-rates as order flow arrives. Start it standalone with ./volsurface/run.sh (it also comes up automatically with ./local.sh); the tab shows a hint instead of a broken image when the service is offline.

3. Smart Contract Development (Foundry)

forge build   # compile all contracts (incl. the vendored official 1inch stack)
forge test    # run the 82-test suite

4. Deploy to Anvil (Local)

The quickest path: ./local.sh starts Anvil, deploys all contracts, writes frontend/.env.local, launches the vol-surface renderer, and starts the dev server in one step.

When you're done, stop everything it started:

fuser -k 8545/tcp 3000/tcp 8000/tcp

(This is the same port-based cleanup local.sh runs on every invocation, so it's safe even if a previous run didn't finish cleanly β€” unlike kill $(cat /tmp/*-options.pid), it won't error out on a missing PID file.)

Re-running ./local.sh also stops any previous instances automatically before starting fresh.

To deploy manually (e.g., to iterate on the script):

# Terminal 1 β€” start Anvil
anvil --chain-id 31337 --block-time 1 --port 8545

# Terminal 2 β€” deploy (uses Anvil's pre-funded account 0)
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
  forge script script/Deploy.s.sol:Deploy \
  --rpc-url http://localhost:8545 \
  --broadcast \
  --skip-simulation

5. Deploy to Sepolia

Copy .env.example β†’ .env, fill in PRIVATE_KEY and RPC_SEPOLIA, then:

source .env
PRIVATE_KEY="$PRIVATE_KEY" \
FEE_RECIPIENT="$FEE_RECIPIENT" \
forge script script/Deploy.s.sol:Deploy \
  --rpc-url "$RPC_SEPOLIA" \
  --broadcast

FEE_RECIPIENT is where the 1% protocol fee accrues (e.g. a DAO treasury); it defaults to the deployer if unset. The script outputs NEXT_PUBLIC_* addresses; copy them into frontend/.env.local (or set NEXT_PUBLIC_CHAIN_ID=11155111).

To verify contracts on Etherscan at the same time:

source .env
PRIVATE_KEY="$PRIVATE_KEY" forge script script/Deploy.s.sol:Deploy \
  --rpc-url "$RPC_SEPOLIA" \
  --broadcast \
  --verify \
  --etherscan-api-key "$ETHERSCAN_API_KEY"

6. Chainlink CRE Workflow

The required Chainlink integration is a CRE workflow that performs an on-chain state change: on a cron schedule the DON reads the Chainlink ETH/USD feed at the last finalized block, reaches consensus, and the DON-signed report calls AquaOptionSettlement.settleSeries(seriesId, spotPrice) on-chain.

File Role
cre-workflow/settlement/workflow.ts The workflow itself β€” cron trigger β†’ callContract(latestRoundData) on the Chainlink feed β†’ DON consensus β†’ DON-signed writeReport β†’ settleSeries(). Compiles to WASM.
cre-workflow/settlement/config.json Runtime config: schedule, seriesId, target settlementAddress, priceFeedAddress (Chainlink ETH/USD), gasLimit, and chain selector.
cre-workflow/settlement/package.json Deps + typecheck script. Build + simulate run via the cre CLI (cre workflow build|simulate settlement).

Access control. settleSeries() carries an onlyCRE modifier (AquaOptionSettlement.sol:38) β€” only the CRE forwarder address passed to the constructor at deploy time can write the settlement price. The simulator uses a local forwarder; the live path requires the contract to be deployed with your registered CRE forwarder address.

Prerequisites

The current CRE CLI (v1.20.x) reworks several commands; this workflow is pinned to v1.11.0 to match @chainlink/cre-sdk@^1.11.0. The CLI is a binary installed via Chainlink's official script (it is not an npm package).

# 1. CRE CLI v1.11.0 β€” installs to ~/.cre/bin and appends it to PATH in ~/.bashrc.
curl -sSL https://app.chain.link/cre/install.sh | bash -s -- v1.11.0
source ~/.bashrc          # or open a new shell, so `cre` is on PATH
cre version               # β†’ CRE CLI version v1.11.0

# 2. Bun β‰₯ 1.0 β€” cre-compile uses it to build the WASM target.
curl -fsSL https://bun.sh/install | bash

# 3. Authenticate β€” required even for local simulation.
cre login                 # opens a browser; or, non-interactively:
# echo 'CRE_API_KEY=<key from Account Settings at https://app.chain.link>' >> cre-workflow/.env

# 4. Install workflow + contract-binding deps (each folder has its own package.json).
cd cre-workflow
( cd settlement && bun install )
( cd contracts  && bun install )

# 5. (Optional) Regenerate the typed contract binding from the Foundry ABI.
#    Already committed under contracts/evm/ts/generated/; only needed if the ABI changes.
cp ../out/AquaOptionSettlement.sol/AquaOptionSettlement.json contracts/evm/src/abi/
cre generate-bindings evm --language typescript

Edit cre-workflow/settlement/config.json so seriesId matches the series you registered on-chain (copy it from the On-Chain Proof tab or from the forge script deploy logs), evm.settlementAddress points at your deployed AquaOptionSettlement, and evm.priceFeedAddress is the Chainlink ETH/USD feed for the chain (Sepolia: 0x694AA1769357215DE4FAC081bf1f309aDC325306). The active CRE target is read from CRE_TARGET in cre-workflow/.env (staging-settings β†’ Sepolia RPC in project.yaml).

Build needs no auth; simulate does. cre workflow build compiles the WASM locally. cre workflow simulate gates on auth β€” run cre login (or set CRE_API_KEY) first.


6a. Demonstrate a successful CRE CLI simulation (the verified path)

First confirm it compiles (no auth needed):

cd cre-workflow
cre workflow build settlement
# βœ“ Workflow compiled successfully
# βœ“ Build output written to settlement/binary.wasm

Then run the simulation. The simulator spins up a local CRE runtime, fires the cron trigger, runs the workflow's on-chain feed read + DON consensus, and signs the report β€” all against the Sepolia RPC from project.yaml:

cre workflow simulate settlement --non-interactive --trigger-index 0
# `--non-interactive --trigger-index 0` selects the single cron trigger;
# omit both to pick it from an interactive menu.

Verified: CLI v1.11.0 reads the live Sepolia ETH/USD feed, runs DON consensus and signs the report, exiting 0. Full annotated transcript: docs/cre-simulation.md.

Note β€” live broadcast is out of scope here. CRE delivers DON-signed reports through a KeystoneForwarder that calls onReport(bytes,bytes) on the receiver, whereas AquaOptionSettlement exposes a plain settleSeries(bytes32,uint256) guarded by onlyCRE. Wiring the live on-chain write (an onReport entrypoint + registered forwarder) is a follow-up; the CRE CLI simulation above is the demonstrated path.


End-to-End Demo Walkthrough

Step Actor Action Contract call
1 LP Connect wallet β†’ Authorize Strike Range β†’ approve official Aqua β†’ register β†’ ship ERC20.approve(Aqua) + AquaCollateralVault.authorizeRange() + Aqua.ship()
2 Trader Click Buy on a strike within LP's range β†’ approve USDC β†’ buy ERC20.approve(USDC) + AquaCollateralVault.buy(authId, K, amount, maxPremium) (SwapVM-priced, 1% fee β†’ DAO)
2a Trader Compose a multi-leg position in the Strategy Builder (20 named strategies or custom legs) β€” each buy leg is a vault.buy() frontend only β€” payoff, T+0 curve, greeks, POP
3 Trader Click Close to sell back at the live Bid (reverse SwapVM swap; LP capacity restores) AquaCollateralVault.close(optionToken, lp, amount, minPayout)
4 β€” Settle at expiry β€” anyone may supply the Chainlink round covering expiry; or run cre workflow simulate settlement AquaOptionSettlement.settleWithChainlinkRound() / settleSeries() via CRE
5 Trader Call redeem() to collect the cash-settled intrinsic (ITM only) AquaCollateralVault.redeem(optionToken, amount)
6 LP Call reclaimCollateral() to recover everything not owed to holders AquaCollateralVault.reclaimCollateral(optionToken)

πŸ“– Glossary

Ethereum / Blockchain Terms

DeFi Terms

Options Terms

Protocol-Specific Terms


πŸ—οΈ Project Structure

β”œβ”€β”€ lib/                      # Vendored official contracts (compiled unmodified)
β”‚   β”œβ”€β”€ aqua/                 # 1inch/aqua β€” registry, AquaApp base, IAqua
β”‚   β”œβ”€β”€ swap-vm/              # 1inch/swap-vm release/1.2 β€” VM core, opcodes, routers
β”‚   └── forge-std/            # forge-std v1.11.0
β”œβ”€β”€ src/                      # Smile contracts (Solidity 0.8.30)
β”‚   β”œβ”€β”€ swapvm/               # SmileSwapVMRouter (opcode 33) + OptionPremiumInstruction
β”‚   β”‚                         #   + SmileMath + OptionPricingEngine (quoting facade)
β”‚   β”œβ”€β”€ vaults/               # AquaCollateralVault (escrow/lifecycle)
β”‚   β”‚                         #   + AquaOptionSettlement (expiry-price registry)
β”‚   β”œβ”€β”€ hooks/                # OptionPricingHook (Uniswap v4 + the vol surface)
β”‚   β”œβ”€β”€ mocks/                # MockV3Aggregator (local Chainlink feed)
β”‚   └── OptionToken.sol       # ERC-20 option position
β”œβ”€β”€ frontend/                 # Next.js app
β”‚   β”œβ”€β”€ components/           # OptionMatrix, AuthorizeRange, PayoffBuilder, LPDashboard, VolSurface
β”‚   β”œβ”€β”€ lib/                  # options engine + 20-strategy catalog
β”‚   └── config/               # Wagmi + contract addresses + official Aqua ABI
β”œβ”€β”€ volsurface/               # Python (Flask + matplotlib) 3-D vol-surface renderer
β”‚                             #   β€” evolves with each trade via the Οƒ feedback loop
β”œβ”€β”€ cre-workflow/             # Chainlink CRE workflow (TypeScript β†’ WASM)
β”œβ”€β”€ script/                   # Deploy.s.sol + DemoTrade.s.sol (live-node demo)
β”œβ”€β”€ docs/                     # grant proposal, build notes, CRE transcript
β”œβ”€β”€ test/                     # Foundry tests (82 passing)
└── foundry.toml              # solc 0.8.30, via_ir

Technical Stack


Built for the 1inch + Uniswap + Chainlink Hackathon.