# Integration Guide

## Overview

Hypersurface is an on-chain options protocol for **covered calls** and **cash-secured puts**. You sell options to the protocol and receive premium. The trade lifecycle is:

```
1. Price    →  GET /optionsPrices (indicative pricing + Greeks)
2. Quote    →  POST /quote (protocol co-signs the order)
3. Approve  →  ERC-20 approve on TradeExecutor (one-time per token)
4. Trade    →  HedgedPool.trade() (on-chain, collateral pulled automatically)
5. Hold     →  Position locked until 08:00 UTC expiry
6. Settle   →  Controller.operate() (unlock collateral after expiry)
7. Withdraw →  HedgedPool.withdrawVaultCollateral() (get your tokens back)
```

You can also **close early** at any time by buying back the option (step 4 with positive amount).

### Key Terms

| Term             | Description                                                             |
| ---------------- | ----------------------------------------------------------------------- |
| HedgedPool       | Entry point contract for trading                                        |
| TradeExecutor    | Handles collateral transfers (you approve this, never call it directly) |
| Controller       | Gamma protocol vault operations (settlement, reads)                     |
| oToken           | ERC-20 representing a specific option series                            |
| Vault            | Gamma margin vault holding your collateral and short position           |
| Premium          | Payment received when selling an option (in USD₮0/USDC)                 |
| Covered Call     | Sell upside on an asset you hold as collateral                          |
| Cash-Secured Put | Sell downside protection, backed by stablecoin collateral               |

***

## Setup

### Chain Configuration

#### HyperEVM

| Property        | Value                                   |
| --------------- | --------------------------------------- |
| Chain ID        | `999`                                   |
| RPC URL         | `https://rpc.hyperliquid.xyz/evm`       |
| Block Explorer  | `https://explorer.hyperliquid.xyz`      |
| Native Currency | HYPE (18 decimals)                      |
| API Base URL    | `https://market-api-sh.hypersurface.io` |

#### Base

| Property        | Value                                     |
| --------------- | ----------------------------------------- |
| Chain ID        | `8453`                                    |
| RPC URL         | `https://mainnet.base.org`                |
| Block Explorer  | `https://basescan.org`                    |
| Native Currency | ETH (18 decimals)                         |
| API Base URL    | `https://market-api-base.hypersurface.io` |

### Contract Addresses

#### HyperEVM (Chain 999)

| Contract      | Address                                      | Purpose                                                     |
| ------------- | -------------------------------------------- | ----------------------------------------------------------- |
| HedgedPool    | `0x0095aCDD705Cfcc11eAfFb6c19A28C0153ad196F` | Entry point for `trade()`, buy-back premium approval target |
| TradeExecutor | `0x25dD9EAae2e0b118a2CefdD229233654840BA7A0` | Collateral approval target for sell trades                  |
| Controller    | `0x7730e6bfbfCE8Ff0b7a50b72D0601c209Eb818C4` | Vault settlement via `operate()`, vault state reads         |
| MarginPool    | `0x7D2e4b4d7ba55C423F5CCe194ae8194eFD1C6e35` | Holds collateral and oTokens                                |
| Auction       | `0xf077393F8d2e6fB8151a53c5209686544a0E3749` | Dutch IV-auction venue for takers                           |

#### Base (Chain 8453)

| Contract   | Address                                      | Purpose                                                     |
| ---------- | -------------------------------------------- | ----------------------------------------------------------- |
| HedgedPool | `0x68893915f202e5DA2Ef01493463c50B2f68Df56d` | Entry point for `trade()`, buy-back premium approval target |
| Controller | `0x037F16e360db0053BAAcD78cDCfe735Af05f39bF` | Vault settlement via `operate()`, vault state reads         |

### Token Addresses

#### HyperEVM (Chain 999) -- Underlyings

| Asset | Symbol | Address                                      | Decimals |
| ----- | ------ | -------------------------------------------- | -------- |
| HYPE  | wHYPE  | `0x5555555555555555555555555555555555555555` | 18       |
| BTC   | UBTC   | `0x9fdbda0a5e284c32744d2f17ee5c74b284993463` | 8        |
| ETH   | UETH   | `0xbe6727b535545c67d5caa73dea54865b92cf7907` | 18       |
| SOL   | USOL   | `0x068f321fa8fb9f0d135f290ef6a3e2813e1c8a29` | 9        |
| XPL   | UXPL   | `0x33af3c2540ba72054e044efe504867b39ae421f5` | 18       |
| PUMP  | UPUMP  | `0x27ec642013bcb3d80ca3706599d3cda04f6f4452` | 6        |
| ENA   | UENA   | `0x58538e6a46e07434d7e7375bc268d3cb839c0133` | 18       |
| KNTQ  | KNTQ   | `0x000000000000780555bd0bca3791f89f9542c2d6` | 18       |
| kHYPE | kHYPE  | `0xfd739d4e423301ce9385c1fb8850539d657c296d` | 18       |

**Premium token:** USD₮0 at `0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb` (6 decimals)

#### Base (Chain 8453) -- Underlyings

| Asset | Symbol | Address                                      | Decimals |
| ----- | ------ | -------------------------------------------- | -------- |
| ETH   | WETH   | `0x4200000000000000000000000000000000000006` | 18       |
| BTC   | cbBTC  | `0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf` | 8        |

**Premium token:** USDC at `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` (6 decimals)

> **Note:** Strike increments are set on-chain and may change. To discover current active strikes and expiries, query the subgraph:
>
> ```graphql
> {
>   otokens(where: { expiryTimestamp_gt: "CURRENT_UNIX_TIMESTAMP" }) {
>     strikePrice
>     expiryTimestamp
>     isPut
>     underlyingAsset { symbol }
>   }
> }
> ```
>
> Or test strikes via the API — `GET /optionsPrices?symbol=BTC-03APR26-65500-C` will return pricing if that strike exists.

***

## API Reference

No authentication required. Trades are authenticated by the EIP-712 signature embedded in the order itself.

### Base URLs

| Chain    | Base URL                                  |
| -------- | ----------------------------------------- |
| HyperEVM | `https://market-api-sh.hypersurface.io`   |
| Base     | `https://market-api-base.hypersurface.io` |

### Endpoints

| Method | Path                | Description                                   |
| ------ | ------------------- | --------------------------------------------- |
| GET    | `/optionsPrices`    | Indicative pricing for an option symbol       |
| POST   | `/quote`            | Co-signed order ready for on-chain submission |
| GET    | `/underlyingPrices` | Spot prices for underlying assets             |
| GET    | `/health`           | Vol surface and price feed health status      |

> Use `symbol` (singular) as the query parameter, not `symbols`.

### Symbol Format

```
{UNDERLYING}-{DDMMMYY}-{STRIKE}-{C|P}
```

| Part         | Description             | Examples                                                           |
| ------------ | ----------------------- | ------------------------------------------------------------------ |
| `UNDERLYING` | Asset symbol            | `HYPE`, `ETH`, `BTC`, `SOL`, `XPL`, `PUMP`, `ENA`, `KNTQ`, `kHYPE` |
| `DDMMMYY`    | Expiry date (08:00 UTC) | Use an available expiry date                                       |
| `STRIKE`     | Strike price in USD     | `25`, `3000`, `100000`                                             |
| `C\|P`       | Call or Put             | `C`, `P`                                                           |

**Build-your-own URL guide:**

1. Pick the base URL for your chain.
2. Pick your asset, an available expiry date, an available strike (query the subgraph or test via the API), and C or P.
3. Assemble: `https://market-api-sh.hypersurface.io/optionsPrices?symbol=HYPE-28MAR26-25-C`

Strikes are set on-chain. Query the subgraph or test via the API to confirm availability. `HYPE-28MAR26-25.50-C` is invalid if that strike does not exist.

> **Note:** To discover available expiry dates, query the subgraph for active oTokens:
>
> ```graphql
> {
>   otokens(first: 100, where: { expiryTimestamp_gt: "CURRENT_UNIX_TIMESTAMP" }) {
>     expiryTimestamp
>   }
> }
> ```
>
> Or try dates programmatically — the API returns an error for invalid expiries.

### GET /optionsPrices

```bash
curl "https://market-api-sh.hypersurface.io/optionsPrices?symbol=HYPE-28MAR26-25-C"
```

[Try it live](https://market-api-sh.hypersurface.io/optionsPrices?symbol=HYPE-28MAR26-25-C)

**Response:**

```json
{
  "result": {
    "HYPE-28MAR26-25-C": {
      "ask": 3.45,
      "bid": 2.81,
      "premium": 3.10,
      "probability_otm": 0.72
    }
  }
}
```

| Field             | Type  | Description                                         |
| ----------------- | ----- | --------------------------------------------------- |
| `ask`             | float | Ask price in USD (cost to buy back)                 |
| `bid`             | float | Bid price in USD (premium you receive when selling) |
| `premium`         | float | Mid/mark premium in USD                             |
| `probability_otm` | float | Probability of expiring out-of-the-money (0 to 1)   |

Add `&greeks=true` for delta, gamma, theta, vega, and implied volatility:

```bash
curl "https://market-api-sh.hypersurface.io/optionsPrices?symbol=HYPE-28MAR26-25-C&greeks=true"
```

[Try it live](https://market-api-sh.hypersurface.io/optionsPrices?symbol=HYPE-28MAR26-25-C\&greeks=true)

### POST /quote

`POST /quote` returns a co-signed order ready for on-chain execution.

**Covered call request:**

```bash
curl -X POST "https://market-api-sh.hypersurface.io/quote" \
  -H "Content-Type: application/json" \
  -d '{
    "sign": true,
    "order": {
      "account": "0xYourWalletAddress",
      "poolAddress": "0x0095aCDD705Cfcc11eAfFb6c19A28C0153ad196F",
      "underlying": "0x5555555555555555555555555555555555555555",
      "collateral": "0x5555555555555555555555555555555555555555",
      "referrer": "0x0000000000000000000000000000000000000000",
      "legs": [
        {
          "symbol": "HYPE-28MAR26-25-C",
          "amount": -100000000
        }
      ]
    }
  }'
```

**Request Fields:**

| Field                  | Type    | Required | Description                                                              |
| ---------------------- | ------- | -------- | ------------------------------------------------------------------------ |
| `sign`                 | boolean | Yes      | Must be `true` to receive a co-signed order                              |
| `order.account`        | address | Yes      | Your wallet address (EOA) or your contract address                       |
| `order.poolAddress`    | address | Yes      | HedgedPool address for the target chain                                  |
| `order.underlying`     | address | Yes      | Underlying token contract address                                        |
| `order.collateral`     | address | Yes      | Collateral token address (underlying for calls, stablecoin for puts)     |
| `order.referrer`       | address | Yes      | Referral address, or `0x0000000000000000000000000000000000000000`        |
| `order.legs[0].symbol` | string  | Yes      | Option symbol in `UNDERLYING-DDMMMYY-STRIKE-C/P` format                  |
| `order.legs[0].amount` | integer | Yes      | Quantity scaled by 1e8. Negative = sell to protocol, Positive = buy back |

> The `legs` array must contain exactly 1 element. Multi-leg trades are not supported.

**Response:**

```json
{
  "result": {
    "order": {
      "account": "0xYourWalletAddress",
      "poolAddress": "0x0095aCDD705Cfcc11eAfFb6c19A28C0153ad196F",
      "underlying": "0x5555555555555555555555555555555555555555",
      "collateral": "0x5555555555555555555555555555555555555555",
      "referrer": "0x0000000000000000000000000000000000000000",
      "validUntil": 1711612500,
      "nonce": "72800786012994460315784275982664160146348565358057253425559922099161226915767",
      "legs": [
        {
          "strike": "2500000000",
          "expiration": 1711612800,
          "isPut": false,
          "amount": "-100000000",
          "premium": "-3100000",
          "fee": "0"
        }
      ],
      "signature": { "r": "0x...", "s": "0x...", "v": "0x1b" },
      "coSignatures": []
    }
  }
}
```

**Response Fields:**

| Field        | Scale   | Description                                                             |
| ------------ | ------- | ----------------------------------------------------------------------- |
| `strike`     | 1e8     | Strike price. `"2500000000"` = $25.00                                   |
| `expiration` | unix    | Expiry timestamp (08:00 UTC)                                            |
| `isPut`      | boolean | `true` = put, `false` = call                                            |
| `amount`     | 1e8     | Signed quantity. `"-100000000"` = selling 1 option                      |
| `premium`    | 1e6     | Signed premium. `"-3100000"` = you receive $3.10                        |
| `fee`        | 1e6     | Protocol fee in premium token units                                     |
| `validUntil` | unix    | Quote expiry. Request a fresh quote immediately before your on-chain tx |
| `nonce`      | uint256 | Unique nonce for replay protection                                      |
| `signature`  | -       | Protocol's EIP-712 co-signature                                         |

**Put request** -- set `collateral` to the stablecoin address:

```json
{
  "sign": true,
  "order": {
    "account": "0xYourWalletAddress",
    "poolAddress": "0x0095aCDD705Cfcc11eAfFb6c19A28C0153ad196F",
    "underlying": "0x5555555555555555555555555555555555555555",
    "collateral": "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
    "referrer": "0x0000000000000000000000000000000000000000",
    "legs": [
      {
        "symbol": "HYPE-28MAR26-25-P",
        "amount": -100000000
      }
    ]
  }
}
```

### GET /health

```bash
curl "https://market-api-sh.hypersurface.io/health?symbol=HYPE"
```

[Try it live](https://market-api-sh.hypersurface.io/health?symbol=HYPE)

```json
{
  "HYPE": {
    "model": { "status": "OK", "timestamp": 1774634618 },
    "underlyingPrice": { "status": "OK", "timestamp": 1774634578 }
  },
  "currentTimestamp": 1774634622
}
```

> If a model or price feed status is not `"OK"`, pricing for that asset may be unavailable. Wait for recovery before trading.

### GET /underlyingPrices

```bash
curl "https://market-api-sh.hypersurface.io/underlyingPrices?symbol=HYPE&symbol=ETH&symbol=BTC"
```

[Try it live](https://market-api-sh.hypersurface.io/underlyingPrices?symbol=HYPE)

```json
{
  "result": {
    "HYPE": { "price": 38.21, "timestamp": 1774634639 },
    "ETH": { "price": 1978.00, "timestamp": 1774634639 },
    "BTC": { "price": 65666.93, "timestamp": 1774634639 }
  }
}
```

***

## EOA Trading Guide

Complete start-to-finish guide for wallet users. See the [Full Python Example](#full-example-python-trading-bot) for a copy-paste-ready version with all ABIs inline.

{% stepper %}
{% step %}

### Approval Targets

| Action                    | Token to Approve        | Approve To    | HyperEVM Address                             |
| ------------------------- | ----------------------- | ------------- | -------------------------------------------- |
| Sell covered call         | Underlying (e.g. wHYPE) | TradeExecutor | `0x25dD9EAae2e0b118a2CefdD229233654840BA7A0` |
| Sell cash-secured put     | Stablecoin (USD₮0)      | TradeExecutor | `0x25dD9EAae2e0b118a2CefdD229233654840BA7A0` |
| Buy back (close position) | Premium token (USD₮0)   | HedgedPool    | `0x0095aCDD705Cfcc11eAfFb6c19A28C0153ad196F` |

> Collateral approvals go to **TradeExecutor**. Buy-back premium approvals go to **HedgedPool**. Approving the wrong contract will revert your transaction.
> {% endstep %}

{% step %}

### Get a Signed Quote

```python
import requests
from web3 import Web3

API_URL = "https://market-api-sh.hypersurface.io"
HEDGED_POOL = "0x0095aCDD705Cfcc11eAfFb6c19A28C0153ad196F"
WHYPE = "0x5555555555555555555555555555555555555555"
TRADE_EXECUTOR = "0x25dD9EAae2e0b118a2CefdD229233654840BA7A0"
ADDRESS_ZERO = "0x0000000000000000000000000000000000000000"

w3 = Web3(Web3.HTTPProvider("https://rpc.hyperliquid.xyz/evm"))
account = w3.eth.account.from_key("YOUR_PRIVATE_KEY")

quote_resp = requests.post(f"{API_URL}/quote", json={
    "sign": True,
    "order": {
        "account": account.address,
        "poolAddress": HEDGED_POOL,
        "underlying": WHYPE,
        "collateral": WHYPE,
        "referrer": ADDRESS_ZERO,
        "legs": [{"symbol": "HYPE-28MAR26-25-C", "amount": -100000000}],
    },
})
quote_resp.raise_for_status()
api_order = quote_resp.json()["result"]["order"]
```

{% endstep %}

{% step %}

### Approve Collateral

```python
ERC20_ABI = [
    {
        "inputs": [
            {"name": "spender", "type": "address"},
            {"name": "amount", "type": "uint256"},
        ],
        "name": "approve",
        "outputs": [{"name": "", "type": "bool"}],
        "stateMutability": "nonpayable",
        "type": "function",
    }
]

token = w3.eth.contract(address=Web3.to_checksum_address(WHYPE), abi=ERC20_ABI)

tx = token.functions.approve(
    Web3.to_checksum_address(TRADE_EXECUTOR),
    10 * 10**18,  # 10 wHYPE
).build_transaction({
    "from": account.address,
    "nonce": w3.eth.get_transaction_count(account.address),
    "chainId": 999,
})

signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
w3.eth.wait_for_transaction_receipt(tx_hash)
```

{% endstep %}

{% step %}

### Execute Trade

`trade()` function signature:

```solidity
function trade(
    IOrderUtil.Order calldata order,
    uint256 traderVaultId,
    bool autoCreateVault
) external nonReentrant returns (address oToken);
```

> `trade()` returns the address of the oToken that was traded. An `eth_call` simulation returns it directly; from a sent transaction, read it from the emitted events.

| Parameter         | Type    | Description                                  |
| ----------------- | ------- | -------------------------------------------- |
| `order`           | Order   | Complete signed order from `/quote` response |
| `traderVaultId`   | uint256 | Your vault ID. Use `0` on first trade        |
| `autoCreateVault` | bool    | `true` on first trade to auto-create a vault |

```python
contract_order = format_order_for_contract(api_order)  # See Reference section

HEDGED_POOL_ABI = [
    {
        "inputs": [
            {
                "components": [
                    {"name": "account", "type": "address"},
                    {"name": "poolAddress", "type": "address"},
                    {"name": "underlying", "type": "address"},
                    {"name": "collateral", "type": "address"},
                    {"name": "referrer", "type": "address"},
                    {"name": "validUntil", "type": "uint256"},
                    {"name": "nonce", "type": "uint256"},
                    {
                        "name": "legs",
                        "type": "tuple[]",
                        "components": [
                            {"name": "strike", "type": "uint256"},
                            {"name": "expiration", "type": "uint256"},
                            {"name": "isPut", "type": "bool"},
                            {"name": "amount", "type": "int256"},
                            {"name": "premium", "type": "int256"},
                            {"name": "fee", "type": "uint256"},
                        ],
                    },
                    {
                        "name": "signature",
                        "type": "tuple",
                        "components": [
                            {"name": "v", "type": "uint8"},
                            {"name": "r", "type": "bytes32"},
                            {"name": "s", "type": "bytes32"},
                        ],
                    },
                    {
                        "name": "coSignatures",
                        "type": "tuple[]",
                        "components": [
                            {"name": "v", "type": "uint8"},
                            {"name": "r", "type": "bytes32"},
                            {"name": "s", "type": "bytes32"},
                        ],
                    },
                ],
                "name": "order",
                "type": "tuple",
            },
            {"name": "traderVaultId", "type": "uint256"},
            {"name": "autoCreateVault", "type": "bool"},
        ],
        "name": "trade",
        "outputs": [{"name": "", "type": "address"}],
        "stateMutability": "nonpayable",
        "type": "function",
    },
]

hedged_pool = w3.eth.contract(
    address=Web3.to_checksum_address(HEDGED_POOL),
    abi=HEDGED_POOL_ABI,
)

tx = hedged_pool.functions.trade(
    contract_order,
    0,     # traderVaultId (0 = auto-create)
    True,  # autoCreateVault
).build_transaction({
    "from": account.address,
    "nonce": w3.eth.get_transaction_count(account.address),
    "chainId": 999,
    "gas": 1_500_000,
})

signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Trade tx: {tx_hash.hex()}")
```

{% endstep %}

{% step %}

### Parse Vault ID from Receipt

```python
trade_executor = w3.eth.contract(
    address=Web3.to_checksum_address(TRADE_EXECUTOR),
    abi=[{
        "anonymous": False,
        "inputs": [
            {"indexed": True, "name": "trader", "type": "address"},
            {"indexed": False, "name": "vaultId", "type": "uint256"},
        ],
        "name": "MarginVaultOpened",
        "type": "event",
    }],
)
events = trade_executor.events.MarginVaultOpened().process_receipt(receipt)
vault_id = events[0]["args"]["vaultId"]
print(f"Vault ID: {vault_id}")
```

{% endstep %}

{% step %}

### Buy Back (Close Early)

Buy back your option using `trade()` with a **positive** amount. You pay the current premium to close, then withdraw your collateral.

**Approve HedgedPool for premium payment:**

```python
USDT0 = "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb"

premium_token = w3.eth.contract(
    address=Web3.to_checksum_address(USDT0), abi=ERC20_ABI,
)

tx = premium_token.functions.approve(
    Web3.to_checksum_address(HEDGED_POOL),
    100 * 10**6,  # 100 USD₮0
).build_transaction({
    "from": account.address,
    "nonce": w3.eth.get_transaction_count(account.address),
    "chainId": 999,
})

signed = account.sign_transaction(tx)
w3.eth.send_raw_transaction(signed.raw_transaction)
```

**Request a buy-back quote (positive amount):**

```python
quote_resp = requests.post(f"{API_URL}/quote", json={
    "sign": True,
    "order": {
        "account": account.address,
        "poolAddress": HEDGED_POOL,
        "underlying": WHYPE,
        "collateral": WHYPE,
        "referrer": ADDRESS_ZERO,
        "legs": [{"symbol": "HYPE-28MAR26-25-C", "amount": 100000000}],  # positive = buy back
    },
})
api_order = quote_resp.json()["result"]["order"]
contract_order = format_order_for_contract(api_order)
```

**Execute the buy-back:**

```python
tx = hedged_pool.functions.trade(
    contract_order,
    vault_id,  # your existing vault ID
    False,     # do NOT auto-create
).build_transaction({
    "from": account.address,
    "nonce": w3.eth.get_transaction_count(account.address),
    "chainId": 999,
    "gas": 500_000,
})

signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
w3.eth.wait_for_transaction_receipt(tx_hash)
```

Then [withdraw your collateral](#9-withdraw-collateral).
{% endstep %}

{% step %}

### Settlement After Expiry

Options expire at 08:00 UTC on their expiry date. After expiry, you must settle your vault before you can withdraw collateral.

Settlement uses `Controller.operate()` with the `SettleVault` action type (7).

```python
CONTROLLER = "0x7730e6bfbfCE8Ff0b7a50b72D0601c209Eb818C4"
ADDRESS_ZERO = "0x0000000000000000000000000000000000000000"

CONTROLLER_ABI = [
    {
        "inputs": [
            {
                "components": [
                    {"name": "actionType", "type": "uint8"},
                    {"name": "owner", "type": "address"},
                    {"name": "secondAddress", "type": "address"},
                    {"name": "asset", "type": "address"},
                    {"name": "vaultId", "type": "uint256"},
                    {"name": "amount", "type": "uint256"},
                    {"name": "index", "type": "uint256"},
                    {"name": "data", "type": "bytes"},
                ],
                "name": "actions",
                "type": "tuple[]",
            }
        ],
        "name": "operate",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function",
    }
]

controller = w3.eth.contract(
    address=Web3.to_checksum_address(CONTROLLER),
    abi=CONTROLLER_ABI,
)

# Settle one or more vaults in a single transaction
actions = [
    {
        "actionType": 7,  # SettleVault
        "owner": account.address,
        "secondAddress": account.address,  # payout destination
        "asset": Web3.to_checksum_address(ADDRESS_ZERO),
        "vaultId": vault_id,
        "amount": 0,
        "index": 0,
        "data": b"",
    }
    # Add more entries to batch-settle multiple vaults
]

tx = controller.functions.operate(actions).build_transaction({
    "from": account.address,
    "nonce": w3.eth.get_transaction_count(account.address),
    "chainId": 999,
})

signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
w3.eth.wait_for_transaction_receipt(tx_hash)
```

{% endstep %}

{% step %}

### Settlement Payout

| Option Type      | Condition           | You Receive                                               |
| ---------------- | ------------------- | --------------------------------------------------------- |
| Covered Call     | OTM (spot < strike) | Your underlying collateral returned                       |
| Covered Call     | ITM (spot > strike) | Strike price in stablecoin (you delivered the underlying) |
| Cash-Secured Put | OTM (spot > strike) | Your stablecoin collateral returned                       |
| Cash-Secured Put | ITM (spot < strike) | Underlying asset (you bought at the strike price)         |
| {% endstep %}    |                     |                                                           |

{% step %}

### Withdraw Collateral

After settlement or a buy-back, withdraw collateral from your vault.

```python
# Read vault state to get collateral info
CONTROLLER_READ_ABI = [
    {
        "inputs": [
            {"name": "owner", "type": "address"},
            {"name": "vaultId", "type": "uint256"},
        ],
        "name": "getVault",
        "outputs": [
            {
                "components": [
                    {"name": "shortOtokens", "type": "address[]"},
                    {"name": "longOtokens", "type": "address[]"},
                    {"name": "collateralAssets", "type": "address[]"},
                    {"name": "shortAmounts", "type": "uint256[]"},
                    {"name": "longAmounts", "type": "uint256[]"},
                    {"name": "collateralAmounts", "type": "uint256[]"},
                ],
                "name": "vault",
                "type": "tuple",
            }
        ],
        "stateMutability": "view",
        "type": "function",
    }
]

controller = w3.eth.contract(
    address=Web3.to_checksum_address(CONTROLLER),
    abi=CONTROLLER_READ_ABI,
)

vault = controller.functions.getVault(account.address, vault_id).call()
collateral_asset = vault[2][0]   # collateralAssets[0]
collateral_amount = vault[5][0]  # collateralAmounts[0]

# Withdraw
WITHDRAW_ABI = [
    {
        "inputs": [
            {"name": "vaultId", "type": "uint256"},
            {"name": "collateralAsset", "type": "address"},
            {"name": "withdrawalAmount", "type": "uint256"},
        ],
        "name": "withdrawVaultCollateral",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function",
    }
]

pool = w3.eth.contract(
    address=Web3.to_checksum_address(HEDGED_POOL),
    abi=WITHDRAW_ABI,
)

tx = pool.functions.withdrawVaultCollateral(
    vault_id,
    collateral_asset,
    collateral_amount,
).build_transaction({
    "from": account.address,
    "nonce": w3.eth.get_transaction_count(account.address),
    "chainId": 999,
})

signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
w3.eth.wait_for_transaction_receipt(tx_hash)
```

{% endstep %}

{% step %}

### Position Tracking

**Get vault state:**

```solidity
Controller.getVault(address owner, uint256 vaultId) returns (Vault memory)
```

```solidity
struct Vault {
    address[] shortOtokens;
    address[] longOtokens;
    address[] collateralAssets;
    uint256[] shortAmounts;
    uint256[] longAmounts;
    uint256[] collateralAmounts;
}
```

All fields are arrays, but in practice each contains 0 or 1 element.

**Vault ID discovery:**

* **First trade:** Use `autoCreateVault=true` and parse `MarginVaultOpened` event from the receipt.
* **After the fact:** Call `Controller.getAccountVaultCounter(yourAddress)`. Vault IDs are sequential starting from 1.

**Vault status check:**

```python
import time

OTOKEN_ABI = [
    {
        "inputs": [],
        "name": "expiryTimestamp",
        "outputs": [{"type": "uint256"}],
        "stateMutability": "view",
        "type": "function",
    },
]

vault = controller.functions.getVault(account.address, vault_id).call()
has_short = len(vault[3]) > 0 and vault[3][0] > 0
has_collateral = any(a > 0 for a in vault[5])

if has_short:
    otoken = w3.eth.contract(address=vault[0][0], abi=OTOKEN_ABI)
    expiry = otoken.functions.expiryTimestamp().call()
    if expiry < int(time.time()):
        print("Expired -- needs settlement")
    else:
        print("Open position -- not yet expired")
elif has_collateral:
    print("Settled -- ready to withdraw collateral")
else:
    print("Fully settled and withdrawn")
```

{% endstep %}
{% endstepper %}

## Contract Integration Guide

Complete guide for smart contracts and vaults. See the [Full Solidity Example](#full-example-solidity-vault-contract) for a compilable contract with all interfaces inlined.

### How It Works

1. Your backend calls `POST /quote` with `account` = your contract address (the API co-signs the order for that address).
2. Your keeper/owner calls your contract, passing in the signed order.
3. Your contract calls `HedgedPool.trade(order, vaultId, autoCreateVault)`.
4. The TradeExecutor pulls collateral from `msg.sender` (your contract), not from the original caller.

> Your contract must hold the collateral tokens and have approved TradeExecutor before the trade call. The `/quote` API co-signs the order -- your contract does not need to produce its own signature.

### Setup (One-Time)

1. Your contract approves TradeExecutor to spend its collateral tokens.
2. Your contract calls `Controller.setOperator(hedgedPoolAddress, true)` so the protocol can manage vaults on its behalf.

### Trading from a Contract

```solidity
// Your contract calls this -- the order comes from your backend via /quote API
function executeTrade(
    IOrderUtil.Order calldata order,
    uint256 vaultId,
    bool autoCreateVault
) external onlyOwner {
    IHedgedPool(HEDGED_POOL).trade(order, vaultId, autoCreateVault);
}
```

### Buy Back from a Contract

Your backend gets a buy-back quote (positive amount) from `/quote`, and your contract forwards it to `HedgedPool.trade()`.

Your contract must have approved HedgedPool for the premium token (USD₮0 or USDC) before the buy-back call.

```solidity
function executeBuyBack(
    IOrderUtil.Order calldata order,
    uint256 vaultId
) external onlyOwner {
    IHedgedPool(HEDGED_POOL).trade(order, vaultId, false);
}
```

### Settlement from a Contract

Your contract calls `Controller.operate()` directly.

```solidity
function settleVault(uint256 vaultId) external onlyOwner {
    IController.ActionArgs[] memory actions = new IController.ActionArgs[](1);
    actions[0] = IController.ActionArgs(
        IController.ActionType.SettleVault,
        address(this),  // owner
        address(this),  // payout destination
        address(0),     // not used
        vaultId,
        0,              // not used
        0,              // not used
        ""              // not used
    );
    IController(CONTROLLER).operate(actions);
}
```

### Collateral Withdrawal from a Contract

```solidity
function withdrawCollateral(
    uint256 vaultId,
    address collateralAsset,
    uint256 amount
) external onlyOwner {
    IHedgedPool(HEDGED_POOL).withdrawVaultCollateral(vaultId, collateralAsset, amount);
}
```

***

## Auctions

The guide above covers selling options to the protocol. Hypersurface also runs **auctions**, where the protocol sells option inventory back to the market and any address can buy it.

The format is a Dutch auction priced in implied volatility: the offered price descends over a short window. A take is a single transaction: you pay premium in USDT0 and receive the oTokens. Access is permissionless, with no whitelist or sign-up. See [Auctions](/overview/auctions.md) for the user-facing overview.

### Integration paths

* **On-chain.** The `Auction` contract on HyperEVM is permissionless. Any address can read the current price and submit a take directly. The address is listed under [Contract Addresses](#contract-addresses).
* **API.** The same API that powers the auctions page is available to programmatic takers. The full auction API specification is provided on request while it is being finalized, and will be added to this guide after initial production use.

A take can revert even on a live auction. The most common case is `NoEligibleHolders`, which fires when the option series has no short holders to receive the surplus. Programmatic takers should simulate or handle reverts rather than assume a take always succeeds.

To request the auction API specification, or to discuss pricing and settlement, contact the team through [Community & Support](/references/community-and-support.md).

***

## Subgraph (Goldsky)

Public GraphQL endpoints, no authentication required.

| Chain    | Endpoint                                                                                                              |
| -------- | --------------------------------------------------------------------------------------------------------------------- |
| HyperEVM | `https://api.goldsky.com/api/public/project_clysuc3c7f21y01ub6hd66nmp/subgraphs/hypersurface-sh-subgraph/latest/gn`   |
| Base     | `https://api.goldsky.com/api/public/project_clysuc3c7f21y01ub6hd66nmp/subgraphs/hypersurface-base-subgraph/latest/gn` |

**Sample trades query (HyperEVM):**

```bash
curl -X POST \
  "https://api.goldsky.com/api/public/project_clysuc3c7f21y01ub6hd66nmp/subgraphs/hypersurface-sh-subgraph/latest/gn" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ trades(first: 10, orderBy: timestamp, orderDirection: desc) { id account totalPremium totalFee totalNotional underlyingPrice timestamp legs { oToken amount premium fee } } }"
  }'
```

***

## Reference

### Order Struct (Solidity)

These are the Solidity structs passed to `HedgedPool.trade()`. The `/quote` API returns these fields as JSON strings which you convert before submitting on-chain.

```solidity
struct Order {
    address account;            // Your wallet or contract address
    address poolAddress;        // HedgedPool address
    address underlying;         // Underlying token contract address
    address collateral;         // Collateral token contract address
    address referrer;           // Referral address (address(0) if none)
    uint256 validUntil;         // Unix timestamp -- order expires after this
    uint256 nonce;              // Unique nonce for replay protection
    OptionLeg[] legs;           // Must contain exactly 1 element
    Signature signature;        // Protocol's EIP-712 co-signature
    Signature[] coSignatures;   // Additional co-signer signatures (usually empty)
}

struct OptionLeg {
    uint256 strike;             // Strike price, 1e8 scaled ($25 = 2500000000)
    uint256 expiration;         // Expiry timestamp (08:00 UTC)
    bool isPut;                 // true = put, false = call
    int256 amount;              // Signed, 1e8 scaled. Negative = selling
    int256 premium;             // Signed, 1e6 scaled. Negative = you receive
    uint256 fee;                // Protocol fee, 1e6 scaled
}

struct Signature {
    uint8 v;
    bytes32 r;
    bytes32 s;
}
```

### Collateral Requirements

The contract automatically calculates and pulls collateral from `msg.sender` during `trade()`. You do not pass a collateral amount.

| Option Type      | Collateral Token              | Amount Required                        | Example                                                        |
| ---------------- | ----------------------------- | -------------------------------------- | -------------------------------------------------------------- |
| Covered Call     | Underlying asset (e.g. wHYPE) | 1:1 with option quantity               | Sell 1 HYPE call = deposit 1 wHYPE (1e18 units)                |
| Cash-Secured Put | Stablecoin (USD₮0 or USDC)    | Strike price x quantity, minus premium | Sell 1 put at $25 strike = deposit up to 25 USD₮0 (25e6 units) |

> For puts, the premium is netted against the collateral requirement. If you sell a $25 put and receive $3.10 premium, the protocol pulls approximately $21.90 from your wallet as collateral (the exact amount depends on the margin calculator).

### Decimal Conventions

| Data Type                             | Scale     | Example               | Meaning                    |
| ------------------------------------- | --------- | --------------------- | -------------------------- |
| Strike (on-chain)                     | 1e8       | `2500000000`          | $25.00                     |
| Premium (`/quote`)                    | 1e6       | `"-3100000"`          | -$3.10 (you receive $3.10) |
| Premium (`/optionsPrices`)            | float USD | `3.10`                | $3.10                      |
| Amount (quantity)                     | 1e8       | `-100000000`          | Sell 1 option              |
| HYPE / ETH / ENA / XPL / KNTQ / kHYPE | 1e18      | `1000000000000000000` | 1.0 token                  |
| BTC (UBTC / cbBTC)                    | 1e8       | `100000000`           | 1.0 BTC                    |
| SOL (USOL)                            | 1e9       | `1000000000`          | 1.0 SOL                    |
| PUMP (UPUMP)                          | 1e6       | `1000000`             | 1.0 PUMP                   |
| USD₮0 / USDC                          | 1e6       | `1000000`             | $1.00                      |

### On-Chain Errors

| Error                                    | Description                                               |
| ---------------------------------------- | --------------------------------------------------------- |
| `SignatureInvalid()`                     | Order signature verification failed                       |
| `NonceAlreadyUsed(uint256)`              | Nonce has already been consumed (stale quote)             |
| `TooCloseToExpiration()`                 | Trade rejected -- within 24h of expiry                    |
| `InventoryCapExceeded(uint256, uint256)` | Pool inventory cap reached. Args: (cap, inventoryBalance) |
| `InvalidOrder()`                         | Invalid amount/premium combination                        |
| `InvalidUnderlying()`                    | Underlying token not supported by the pool                |
| `InvalidPoolAddress()`                   | Order's poolAddress does not match the contract           |
| `InvalidAccount()`                       | `order.account` does not match `msg.sender`               |
| `Unauthorized()`                         | Signer is not a registered quote provider                 |
| `SeriesExpired()`                        | Option expiry is in the past                              |
| `InvalidLegsCount()`                     | `legs` array must contain exactly 1 element               |
| `InsufficientBalance()`                  | Insufficient token balance                                |
| `ZeroValue()`                            | Zero amount not allowed                                   |

### API Errors

| HTTP Code | Scenario           | Description                                           |
| --------- | ------------------ | ----------------------------------------------------- |
| 400       | Invalid symbol     | Symbol format is malformed or unrecognized            |
| 400       | Invalid parameters | Missing or incorrect query/body parameters            |
| 400       | Expired option     | The requested expiry date is in the past              |
| 400       | No oToken          | No oToken contract exists for this option series      |
| 403       | Access restricted  | The requested action is not permitted for this wallet |
| 404       | Asset not found    | The underlying asset is not supported on this chain   |
| 422       | ITM rejection      | Selling in-the-money options is not allowed           |
| 500       | Internal error     | API encountered an unexpected error                   |
| 503       | Unhealthy feed     | Vol surface or price feed is stale or unavailable     |

Error response format:

```json
{
  "error": {
    "code": 400,
    "message": "Invalid symbol format"
  }
}
```

### format\_order\_for\_contract() Python Helper

Converts the `/quote` API response into the format expected by `HedgedPool.trade()`.

```python
def format_order_for_contract(api_order: dict) -> dict:
    """
    Convert the /quote API response order into the format expected
    by HedgedPool.trade() on-chain.

    Key transformations:
    - KEEP 'account' field (it IS part of the Solidity struct)
    - Convert string numbers to int (strike, amount, premium, fee, nonce, validUntil)
    - Convert signature v from hex string to int, r/s from hex to bytes
    """
    legs = []
    for leg in api_order["legs"]:
        legs.append({
            "strike": int(leg["strike"]),
            "expiration": int(leg["expiration"]),
            "isPut": leg["isPut"],
            "amount": int(leg["amount"]),
            "premium": int(leg["premium"]),
            "fee": int(leg["fee"]),
        })

    signature = api_order.get("signature", {})
    formatted_sig = {
        "v": int(signature["v"], 16) if isinstance(signature["v"], str) else signature["v"],
        "r": bytes.fromhex(signature["r"][2:]) if isinstance(signature["r"], str) else signature["r"],
        "s": bytes.fromhex(signature["s"][2:]) if isinstance(signature["s"], str) else signature["s"],
    }

    co_sigs = []
    for cs in api_order.get("coSignatures", []):
        co_sigs.append({
            "v": int(cs["v"], 16) if isinstance(cs["v"], str) else cs["v"],
            "r": bytes.fromhex(cs["r"][2:]) if isinstance(cs["r"], str) else cs["r"],
            "s": bytes.fromhex(cs["s"][2:]) if isinstance(cs["s"], str) else cs["s"],
        })

    return {
        "account": api_order["account"],
        "poolAddress": api_order["poolAddress"],
        "underlying": api_order["underlying"],
        "collateral": api_order["collateral"],
        "referrer": api_order["referrer"],
        "validUntil": int(api_order["validUntil"]),
        "nonce": int(api_order["nonce"]),
        "legs": legs,
        "signature": formatted_sig,
        "coSignatures": co_sigs,
    }
```

### ActionArgs Struct

```solidity
struct ActionArgs {
    ActionType actionType;  // enum: 0=OpenVault, 6=WithdrawCollateral, 7=SettleVault, etc.
    address owner;          // vault owner address
    address secondAddress;  // payout destination
    address asset;          // not used for SettleVault (set to address(0))
    uint256 vaultId;        // vault to settle
    uint256 amount;         // not used for SettleVault (set to 0)
    uint256 index;          // not used for SettleVault (set to 0)
    bytes data;             // not used for SettleVault (set to empty)
}
```

**Action type reference:**

| Action             | Type | Purpose                          |
| ------------------ | ---- | -------------------------------- |
| OpenVault          | 0    | Open a new margin vault          |
| MintShortOption    | 1    | Mint (write) an option           |
| BurnShortOption    | 2    | Burn (close) an option           |
| DepositCollateral  | 5    | Deposit collateral into a vault  |
| WithdrawCollateral | 6    | Withdraw collateral from a vault |
| SettleVault        | 7    | Settle an expired vault          |
| Redeem             | 8    | Redeem expired long oTokens      |

## Full Example: Python Trading Bot

`HypersurfaceClient` class with all ABIs inline. `pip install web3 requests` then run.

```python
"""
Hypersurface Protocol -- Python Trading Bot
Covers: pricing, quoting, sell, buy-back, settlement, withdrawal.
Target: HyperEVM (Chain 999)

Dependencies:
    pip install web3 requests
"""

import time
import requests
from datetime import datetime, timezone
from web3 import Web3

# ── Configuration ─────────────────────────────────────────────────
HYPERSURFACE_API = "https://market-api-sh.hypersurface.io"
RPC_URL = "https://rpc.hyperliquid.xyz/evm"
CHAIN_ID = 999

HEDGED_POOL = "0x0095aCDD705Cfcc11eAfFb6c19A28C0153ad196F"
TRADE_EXECUTOR = "0x25dD9EAae2e0b118a2CefdD229233654840BA7A0"
CONTROLLER = "0x7730e6bfbfCE8Ff0b7a50b72D0601c209Eb818C4"
PREMIUM_TOKEN = "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb"  # USD₮0
ADDRESS_ZERO = "0x0000000000000000000000000000000000000000"

SETTLE_VAULT = 7

TOKENS = {
    "HYPE":  "0x5555555555555555555555555555555555555555",  # wHYPE, 18 decimals
    "BTC":   "0x9fdbda0a5e284c32744d2f17ee5c74b284993463",  # UBTC, 8 decimals
    "ETH":   "0xbe6727b535545c67d5caa73dea54865b92cf7907",  # UETH, 18 decimals
    "SOL":   "0x068f321fa8fb9f0d135f290ef6a3e2813e1c8a29",  # USOL, 9 decimals
    "XPL":   "0x33af3c2540ba72054e044efe504867b39ae421f5",  # UXPL, 18 decimals
    "PUMP":  "0x27ec642013bcb3d80ca3706599d3cda04f6f4452",  # UPUMP, 6 decimals
    "ENA":   "0x58538e6a46e07434d7e7375bc268d3cb839c0133",  # UENA, 18 decimals
    "KNTQ":  "0x000000000000780555bd0bca3791f89f9542c2d6",  # KNTQ, 18 decimals
    "kHYPE": "0xfd739d4e423301ce9385c1fb8850539d657c296d",  # kHYPE, 18 decimals
}

# ── ABIs ──────────────────────────────────────────────────────────

HEDGED_POOL_ABI = [
    {
        "inputs": [
            {
                "components": [
                    {"name": "account", "type": "address"},
                    {"name": "poolAddress", "type": "address"},
                    {"name": "underlying", "type": "address"},
                    {"name": "collateral", "type": "address"},
                    {"name": "referrer", "type": "address"},
                    {"name": "validUntil", "type": "uint256"},
                    {"name": "nonce", "type": "uint256"},
                    {
                        "name": "legs",
                        "type": "tuple[]",
                        "components": [
                            {"name": "strike", "type": "uint256"},
                            {"name": "expiration", "type": "uint256"},
                            {"name": "isPut", "type": "bool"},
                            {"name": "amount", "type": "int256"},
                            {"name": "premium", "type": "int256"},
                            {"name": "fee", "type": "uint256"},
                        ],
                    },
                    {
                        "name": "signature",
                        "type": "tuple",
                        "components": [
                            {"name": "v", "type": "uint8"},
                            {"name": "r", "type": "bytes32"},
                            {"name": "s", "type": "bytes32"},
                        ],
                    },
                    {
                        "name": "coSignatures",
                        "type": "tuple[]",
                        "components": [
                            {"name": "v", "type": "uint8"},
                            {"name": "r", "type": "bytes32"},
                            {"name": "s", "type": "bytes32"},
                        ],
                    },
                ],
                "name": "order",
                "type": "tuple",
            },
            {"name": "traderVaultId", "type": "uint256"},
            {"name": "autoCreateVault", "type": "bool"},
        ],
        "name": "trade",
        "outputs": [{"name": "", "type": "address"}],
        "stateMutability": "nonpayable",
        "type": "function",
    },
    {
        "inputs": [
            {"name": "vaultId", "type": "uint256"},
            {"name": "collateralAsset", "type": "address"},
            {"name": "withdrawalAmount", "type": "uint256"},
        ],
        "name": "withdrawVaultCollateral",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function",
    },
]

CONTROLLER_ABI = [
    {
        "inputs": [
            {
                "components": [
                    {"name": "actionType", "type": "uint8"},
                    {"name": "owner", "type": "address"},
                    {"name": "secondAddress", "type": "address"},
                    {"name": "asset", "type": "address"},
                    {"name": "vaultId", "type": "uint256"},
                    {"name": "amount", "type": "uint256"},
                    {"name": "index", "type": "uint256"},
                    {"name": "data", "type": "bytes"},
                ],
                "name": "actions",
                "type": "tuple[]",
            }
        ],
        "name": "operate",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function",
    },
    {
        "inputs": [
            {"name": "owner", "type": "address"},
            {"name": "vaultId", "type": "uint256"},
        ],
        "name": "getVault",
        "outputs": [
            {
                "components": [
                    {"name": "shortOtokens", "type": "address[]"},
                    {"name": "longOtokens", "type": "address[]"},
                    {"name": "collateralAssets", "type": "address[]"},
                    {"name": "shortAmounts", "type": "uint256[]"},
                    {"name": "longAmounts", "type": "uint256[]"},
                    {"name": "collateralAmounts", "type": "uint256[]"},
                ],
                "name": "vault",
                "type": "tuple",
            }
        ],
        "stateMutability": "view",
        "type": "function",
    },
    {
        "inputs": [{"name": "owner", "type": "address"}],
        "name": "getAccountVaultCounter",
        "outputs": [{"name": "", "type": "uint256"}],
        "stateMutability": "view",
        "type": "function",
    },
]

ERC20_ABI = [
    {
        "inputs": [
            {"name": "spender", "type": "address"},
            {"name": "amount", "type": "uint256"},
        ],
        "name": "approve",
        "outputs": [{"name": "", "type": "bool"}],
        "stateMutability": "nonpayable",
        "type": "function",
    },
    {
        "inputs": [
            {"name": "owner", "type": "address"},
            {"name": "spender", "type": "address"},
        ],
        "name": "allowance",
        "outputs": [{"name": "", "type": "uint256"}],
        "stateMutability": "view",
        "type": "function",
    },
    {
        "inputs": [{"name": "account", "type": "address"}],
        "name": "balanceOf",
        "outputs": [{"name": "", "type": "uint256"}],
        "stateMutability": "view",
        "type": "function",
    },
]

TRADE_EXECUTOR_ABI = [
    {
        "anonymous": False,
        "inputs": [
            {"indexed": True, "name": "trader", "type": "address"},
            {"indexed": False, "name": "vaultId", "type": "uint256"},
        ],
        "name": "MarginVaultOpened",
        "type": "event",
    },
]

OTOKEN_ABI = [
    {
        "inputs": [],
        "name": "expiryTimestamp",
        "outputs": [{"type": "uint256"}],
        "stateMutability": "view",
        "type": "function",
    },
]

# ── Helpers ───────────────────────────────────────────────────────

def format_expiry_code(expiry: datetime) -> str:
    """Format a supported expiry datetime as DDMMMYY. Expiries occur at 08:00 UTC."""
    return expiry.astimezone(timezone.utc).strftime("%d%b%y").upper()


def format_order_for_contract(api_order: dict) -> dict:
    """
    Convert /quote API response into the format expected by
    HedgedPool.trade() on-chain.
    """
    legs = []
    for leg in api_order["legs"]:
        legs.append(
            {
                "strike": int(leg["strike"]),
                "expiration": int(leg["expiration"]),
                "isPut": leg["isPut"],
                "amount": int(leg["amount"]),
                "premium": int(leg["premium"]),
                "fee": int(leg["fee"]),
            }
        )

    signature = api_order.get("signature", {})
    formatted_sig = {
        "v": int(signature["v"], 16)
        if isinstance(signature["v"], str)
        else signature["v"],
        "r": bytes.fromhex(signature["r"][2:])
        if isinstance(signature["r"], str)
        else signature["r"],
        "s": bytes.fromhex(signature["s"][2:])
        if isinstance(signature["s"], str)
        else signature["s"],
    }

    co_sigs = []
    for cs in api_order.get("coSignatures", []):
        co_sigs.append(
            {
                "v": int(cs["v"], 16)
                if isinstance(cs["v"], str)
                else cs["v"],
                "r": bytes.fromhex(cs["r"][2:])
                if isinstance(cs["r"], str)
                else cs["r"],
                "s": bytes.fromhex(cs["s"][2:])
                if isinstance(cs["s"], str)
                else cs["s"],
            }
        )

    return {
        "account": api_order["account"],
        "poolAddress": api_order["poolAddress"],
        "underlying": api_order["underlying"],
        "collateral": api_order["collateral"],
        "referrer": api_order["referrer"],
        "validUntil": int(api_order["validUntil"]),
        "nonce": int(api_order["nonce"]),
        "legs": legs,
        "signature": formatted_sig,
        "coSignatures": co_sigs,
    }


# ── Client Class ──────────────────────────────────────────────────

class HypersurfaceClient:
    """Full trade lifecycle client for Hypersurface Protocol on HyperEVM."""

    def __init__(self, private_key: str):
        self.w3 = Web3(Web3.HTTPProvider(RPC_URL))
        self.account = self.w3.eth.account.from_key(private_key)

        self.hedged_pool = self.w3.eth.contract(
            address=Web3.to_checksum_address(HEDGED_POOL),
            abi=HEDGED_POOL_ABI,
        )
        self.controller = self.w3.eth.contract(
            address=Web3.to_checksum_address(CONTROLLER),
            abi=CONTROLLER_ABI,
        )
        self.trade_executor = self.w3.eth.contract(
            address=Web3.to_checksum_address(TRADE_EXECUTOR),
            abi=TRADE_EXECUTOR_ABI,
        )

    # ── Pricing ───────────────────────────────────────────────────

    def get_option_price(self, symbol: str) -> dict:
        """Fetch option pricing. Returns dict with ask, bid, premium, probability_otm."""
        resp = requests.get(
            f"{HYPERSURFACE_API}/optionsPrices",
            params={"symbol": symbol},
        )
        resp.raise_for_status()
        return resp.json()["result"][symbol]

    def get_option_chain(
        self, underlying: str, expiry: str, strikes: list[float]
    ) -> list[dict]:
        """Scan calls and puts across multiple strikes for a given expiry."""
        chain = []
        for strike in strikes:
            strike_str = f"{strike:g}"
            for opt_type in ["C", "P"]:
                symbol = f"{underlying}-{expiry}-{strike_str}-{opt_type}"
                try:
                    price = self.get_option_price(symbol)
                    chain.append(
                        {
                            "symbol": symbol,
                            "ask": price["ask"],
                            "bid": price["bid"],
                            "premium": price["premium"],
                            "type": "call" if opt_type == "C" else "put",
                        }
                    )
                except requests.HTTPError:
                    continue
        return chain

    # ── Quoting ───────────────────────────────────────────────────

    def get_quote(
        self,
        symbol: str,
        amount: int,
        underlying: str,
        collateral: str,
        is_sell: bool,
    ) -> dict:
        """
        Request a signed quote from the API.

        Args:
            symbol: e.g. "HYPE-28MAR26-25-C"
            amount: Number of options (positive integer, e.g. 1)
            underlying: Underlying token address
            collateral: Collateral token address (underlying for calls, stablecoin for puts)
            is_sell: True to sell (open), False to buy back (close)
        """
        scaled_amount = amount * 100_000_000
        if is_sell:
            scaled_amount = -scaled_amount

        payload = {
            "sign": True,
            "order": {
                "account": self.account.address,
                "poolAddress": HEDGED_POOL,
                "underlying": Web3.to_checksum_address(underlying),
                "collateral": Web3.to_checksum_address(collateral),
                "referrer": ADDRESS_ZERO,
                "legs": [{"symbol": symbol, "amount": scaled_amount}],
            },
        }

        resp = requests.post(f"{HYPERSURFACE_API}/quote", json=payload)
        resp.raise_for_status()
        return resp.json()

    # ── Approvals ─────────────────────────────────────────────────

    def _ensure_allowance(self, token_address: str, spender: str, required: int):
        """Approve spender if current allowance is insufficient."""
        token = self.w3.eth.contract(
            address=Web3.to_checksum_address(token_address),
            abi=ERC20_ABI,
        )
        current = token.functions.allowance(
            self.account.address, Web3.to_checksum_address(spender)
        ).call()
        if current < required:
            tx = token.functions.approve(
                Web3.to_checksum_address(spender), 2**128
            ).build_transaction(
                {
                    "from": self.account.address,
                    "nonce": self.w3.eth.get_transaction_count(self.account.address),
                    "chainId": CHAIN_ID,
                }
            )
            signed = self.account.sign_transaction(tx)
            tx_hash = self.w3.eth.send_raw_transaction(signed.raw_transaction)
            self.w3.eth.wait_for_transaction_receipt(tx_hash)

    # ── Trade Execution ───────────────────────────────────────────

    def execute_trade(self, quote: dict, vault_id: int = 0) -> dict:
        """
        Execute a sell trade on-chain. Returns dict with tx_hash and vault_id.

        Args:
            quote: Full API response from get_quote()
            vault_id: Existing vault ID, or 0 to auto-create
        """
        api_order = quote["result"]["order"]
        auto_create = vault_id == 0

        # Approve TradeExecutor for collateral
        self._ensure_allowance(api_order["collateral"], TRADE_EXECUTOR, 2**128)

        contract_order = format_order_for_contract(api_order)

        tx = self.hedged_pool.functions.trade(
            contract_order, vault_id, auto_create
        ).build_transaction(
            {
                "from": self.account.address,
                "nonce": self.w3.eth.get_transaction_count(self.account.address),
                "chainId": CHAIN_ID,
                "gas": 1_500_000,
            }
        )

        signed = self.account.sign_transaction(tx)
        tx_hash = self.w3.eth.send_raw_transaction(signed.raw_transaction)
        receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)

        result_vault_id = vault_id
        if auto_create:
            events = self.trade_executor.events.MarginVaultOpened().process_receipt(
                receipt
            )
            if events:
                result_vault_id = events[0]["args"]["vaultId"]

        return {"tx_hash": tx_hash.hex(), "vault_id": result_vault_id}

    # ── Buy Back ──────────────────────────────────────────────────

    def buy_back(
        self,
        symbol: str,
        underlying: str,
        collateral: str,
        amount: int = 1,
        vault_id: int = 0,
    ) -> dict:
        """Buy back (close) an existing short position. Returns dict with tx_hash."""
        quote = self.get_quote(symbol, amount, underlying, collateral, is_sell=False)
        api_order = quote["result"]["order"]

        # Approve HedgedPool for premium token (buy-back costs premium)
        self._ensure_allowance(PREMIUM_TOKEN, HEDGED_POOL, 2**128)

        contract_order = format_order_for_contract(api_order)

        tx = self.hedged_pool.functions.trade(
            contract_order, vault_id, False
        ).build_transaction(
            {
                "from": self.account.address,
                "nonce": self.w3.eth.get_transaction_count(self.account.address),
                "chainId": CHAIN_ID,
                "gas": 500_000,
            }
        )

        signed = self.account.sign_transaction(tx)
        tx_hash = self.w3.eth.send_raw_transaction(signed.raw_transaction)
        self.w3.eth.wait_for_transaction_receipt(tx_hash)

        return {"tx_hash": tx_hash.hex()}

    # ── Vault Reads ───────────────────────────────────────────────

    def get_vault_state(self, vault_id: int) -> dict:
        """Read a vault's current state from the Controller."""
        vault = self.controller.functions.getVault(
            self.account.address, vault_id
        ).call()
        return {
            "short_otokens": vault[0],
            "long_otokens": vault[1],
            "collateral_assets": vault[2],
            "short_amounts": vault[3],
            "long_amounts": vault[4],
            "collateral_amounts": vault[5],
        }

    def get_vault_count(self) -> int:
        """Return the total number of vaults owned by this wallet."""
        return self.controller.functions.getAccountVaultCounter(
            self.account.address
        ).call()

    # ── Withdrawal ────────────────────────────────────────────────

    def withdraw_collateral(self, vault_id: int, asset: str, amount: int) -> str:
        """Withdraw collateral from a vault. Returns tx hash."""
        tx = self.hedged_pool.functions.withdrawVaultCollateral(
            vault_id,
            Web3.to_checksum_address(asset),
            amount,
        ).build_transaction(
            {
                "from": self.account.address,
                "nonce": self.w3.eth.get_transaction_count(self.account.address),
                "chainId": CHAIN_ID,
            }
        )

        signed = self.account.sign_transaction(tx)
        tx_hash = self.w3.eth.send_raw_transaction(signed.raw_transaction)
        self.w3.eth.wait_for_transaction_receipt(tx_hash)
        return tx_hash.hex()

    # ── Settlement ────────────────────────────────────────────────

    def settle_vaults(self, vault_ids: list[int]) -> str:
        """Settle one or more expired vaults. Returns tx hash."""
        actions = [
            {
                "actionType": SETTLE_VAULT,
                "owner": self.account.address,
                "secondAddress": self.account.address,
                "asset": Web3.to_checksum_address(ADDRESS_ZERO),
                "vaultId": vid,
                "amount": 0,
                "index": 0,
                "data": b"",
            }
            for vid in vault_ids
        ]

        tx = self.controller.functions.operate(actions).build_transaction(
            {
                "from": self.account.address,
                "nonce": self.w3.eth.get_transaction_count(self.account.address),
                "chainId": CHAIN_ID,
            }
        )

        signed = self.account.sign_transaction(tx)
        tx_hash = self.w3.eth.send_raw_transaction(signed.raw_transaction)
        self.w3.eth.wait_for_transaction_receipt(tx_hash)
        return tx_hash.hex()

    # ── Full Post-Expiry Sweep ────────────────────────────────────

    def settle_and_withdraw_all(self):
        """Find all vaults, settle expired ones, withdraw all available collateral."""
        vault_count = self.get_vault_count()
        print(f"You have {vault_count} vault(s)")

        expired_vaults = []
        withdrawable_vaults = []

        for vid in range(1, vault_count + 1):
            state = self.get_vault_state(vid)
            has_short = len(state["short_amounts"]) > 0 and state["short_amounts"][0] > 0
            has_collateral = any(a > 0 for a in state["collateral_amounts"])

            if has_short:
                otoken_addr = state["short_otokens"][0]
                otoken = self.w3.eth.contract(address=otoken_addr, abi=OTOKEN_ABI)
                expiry = otoken.functions.expiryTimestamp().call()
                if expiry < int(time.time()):
                    expired_vaults.append(vid)
                    print(f"  Vault {vid}: expired, needs settlement")
                else:
                    print(f"  Vault {vid}: still active (expires {expiry})")
            elif has_collateral:
                withdrawable_vaults.append((vid, state))
                print(f"  Vault {vid}: settled, ready to withdraw")
            else:
                print(f"  Vault {vid}: empty")

        # Step 1: Settle expired vaults in a single transaction
        if expired_vaults:
            print(f"\nSettling {len(expired_vaults)} vault(s)...")
            tx = self.settle_vaults(expired_vaults)
            print(f"Settlement tx: {tx}")

            for vid in expired_vaults:
                state = self.get_vault_state(vid)
                if any(a > 0 for a in state["collateral_amounts"]):
                    withdrawable_vaults.append((vid, state))

        # Step 2: Withdraw collateral from each vault
        for vid, state in withdrawable_vaults:
            for asset, amount in zip(
                state["collateral_assets"], state["collateral_amounts"]
            ):
                if amount > 0:
                    print(f"\nWithdrawing from vault {vid} (asset: {asset})...")
                    tx = self.withdraw_collateral(vid, asset, amount)
                    print(f"Withdrawal tx: {tx}")

        print("\nDone.")


# ── Usage ─────────────────────────────────────────────────────────

if __name__ == "__main__":
    client = HypersurfaceClient("YOUR_PRIVATE_KEY")

    # 1. Check prices
    price = client.get_option_price("HYPE-28MAR26-25-C")
    print(f"Ask: ${price['ask']:.2f}, Bid: ${price['bid']:.2f}")

    # 2. Scan an option chain
    chain = client.get_option_chain("HYPE", "28MAR26", [23, 24, 25, 26, 27])
    for opt in chain:
        print(f"  {opt['symbol']}: ask={opt['ask']:.4f}, bid={opt['bid']:.4f}")

    # 3. Get a quote for selling 1 covered call
    quote = client.get_quote(
        symbol="HYPE-28MAR26-25-C",
        amount=1,
        underlying=TOKENS["HYPE"],
        collateral=TOKENS["HYPE"],
        is_sell=True,
    )
    premium = quote["result"]["order"]["legs"][0]["premium"]
    print(f"Premium (1e6 scaled): {premium}")

    # 4. Execute the trade (uncomment when ready)
    # result = client.execute_trade(quote)
    # print(f"Trade tx: {result['tx_hash']}, Vault ID: {result['vault_id']}")

    # 5. Format a supported expiry date
    example_expiry = datetime(2026, 3, 28, 8, 0, tzinfo=timezone.utc)
    print("Example expiry code:", format_expiry_code(example_expiry))

    # 6. After expiry, settle and withdraw all
    # client.settle_and_withdraw_all()
```

***

## Full Example: Solidity Vault Contract

Solidity vault that trades options via Hypersurface. All interfaces inlined -- only needs OpenZeppelin.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18; // Requires OpenZeppelin v4.x

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

// ── Interfaces (inlined) ─────────────────────────────────────────

interface IOrderUtil {
    struct Order {
        address account;
        address poolAddress;
        address underlying;
        address collateral;
        address referrer;
        uint256 validUntil;
        uint256 nonce;
        OptionLeg[] legs;
        Signature signature;
        Signature[] coSignatures;
    }

    struct OptionLeg {
        uint256 strike;
        uint256 expiration;
        bool isPut;
        int256 amount;
        int256 premium;
        uint256 fee;
    }

    struct Signature {
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    function cancel(uint256[] calldata nonces) external;
    function cancelUpTo(uint256 minimumNonce) external;
    function nonceUsed(address, uint256) external view returns (bool);
}

interface IHedgedPool {
    function trade(
        IOrderUtil.Order calldata order,
        uint256 traderVaultId,
        bool autoCreateVault
    ) external returns (address);

    function withdrawVaultCollateral(
        uint256 vaultId,
        address collateralAsset,
        uint256 withdrawalAmount
    ) external;
}

interface IController {
    enum ActionType {
        OpenVault,
        MintShortOption,
        BurnShortOption,
        DepositLongOption,
        WithdrawLongOption,
        DepositCollateral,
        WithdrawCollateral,
        SettleVault,
        Redeem,
        Call,
        Liquidate
    }

    struct ActionArgs {
        ActionType actionType;
        address owner;
        address secondAddress;
        address asset;
        uint256 vaultId;
        uint256 amount;
        uint256 index;
        bytes data;
    }

    function operate(ActionArgs[] calldata _actions) external;
    function getAccountVaultCounter(address owner) external view returns (uint256);
    function setOperator(address _operator, bool _isOperator) external;
}

// ── Vault Contract ───────────────────────────────────────────────

contract MinimalOptionsVault is Ownable {
    using SafeERC20 for IERC20;

    // HyperEVM addresses
    address public constant HEDGED_POOL = 0x0095aCDD705Cfcc11eAfFb6c19A28C0153ad196F;
    address public constant TRADE_EXECUTOR = 0x25dD9EAae2e0b118a2CefdD229233654840BA7A0;
    address public constant CONTROLLER = 0x7730e6bfbfCE8Ff0b7a50b72D0601c209Eb818C4;
    address public constant PREMIUM_TOKEN = 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb;

    uint256 public currentVaultId;

    constructor() {
        // Allow HedgedPool to manage vaults on behalf of this contract
        IController(CONTROLLER).setOperator(HEDGED_POOL, true);
    }

    /// @notice Approve TradeExecutor to spend a collateral token (call once per token)
    function approveCollateral(address token, uint256 amount) external onlyOwner {
        IERC20(token).safeApprove(TRADE_EXECUTOR, amount);
    }

    /// @notice Approve HedgedPool to spend the premium token (for buy-backs)
    function approvePremium(uint256 amount) external onlyOwner {
        IERC20(PREMIUM_TOKEN).safeApprove(HEDGED_POOL, amount);
    }

    /// @notice Deposit collateral tokens into this contract
    function deposit(address token, uint256 amount) external onlyOwner {
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
    }

    /// @notice Execute a sell trade. The order comes from your backend via POST /quote.
    /// @param order The signed order from the Hypersurface API
    /// @param autoCreateVault True on the first trade to auto-create a margin vault
    function executeTrade(
        IOrderUtil.Order calldata order,
        bool autoCreateVault
    ) external onlyOwner {
        IHedgedPool(HEDGED_POOL).trade(order, currentVaultId, autoCreateVault);

        // On first trade, discover the vault ID
        if (autoCreateVault) {
            currentVaultId = IController(CONTROLLER).getAccountVaultCounter(address(this));
        }
    }

    /// @notice Buy back (close) a position before expiry
    function executeBuyBack(
        IOrderUtil.Order calldata order
    ) external onlyOwner {
        IHedgedPool(HEDGED_POOL).trade(order, currentVaultId, false);
    }

    /// @notice Settle an expired vault
    function settleVault(uint256 vaultId) external onlyOwner {
        IController.ActionArgs[] memory actions = new IController.ActionArgs[](1);
        actions[0] = IController.ActionArgs(
            IController.ActionType.SettleVault,
            address(this),  // owner
            address(this),  // payout destination
            address(0),
            vaultId,
            0,
            0,
            ""
        );
        IController(CONTROLLER).operate(actions);
    }

    /// @notice Withdraw collateral from a settled vault
    function withdrawCollateral(
        uint256 vaultId,
        address collateralAsset,
        uint256 amount
    ) external onlyOwner {
        IHedgedPool(HEDGED_POOL).withdrawVaultCollateral(vaultId, collateralAsset, amount);
    }

    /// @notice Withdraw any ERC-20 token from this contract to the owner
    function sweep(address token, uint256 amount) external onlyOwner {
        IERC20(token).safeTransfer(owner(), amount);
    }

    /// @notice Accept native currency
    receive() external payable {}
}
```

**Deployment and usage:**

1. Deploy `MinimalOptionsVault` on HyperEVM.
2. Call `approveCollateral(wHYPE_address, type(uint256).max)` for each collateral token.
3. Call `approvePremium(type(uint256).max)` for buy-backs.
4. Call `deposit(wHYPE_address, amount)` to fund the contract with collateral.
5. Your backend calls `POST /quote` with `account` = your contract address.
6. Call `executeTrade(order, true)` for the first trade, `executeTrade(order, false)` for subsequent trades into the same vault.
7. After expiry, call `settleVault(vaultId)`, then `withdrawCollateral(...)`, then `sweep(token, amount)`.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.hypersurface.io/integration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
