# 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                                |

#### 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
) public nonReentrant;
```

> `trade()` has **no return value**. Do not expect a return from `eth_call` simulation. Send the transaction and check the receipt.

| 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": [],
        "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);
}
```

***

## 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, currentExposure) |
| `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": [],
        "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;

    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)`.
