# API Reference

Complete method signatures for `LumeClient`, `AgentClient`, and utility
functions.

All prices and amounts in API responses use **atomic units** (6 decimal places).
`500000` = `$0.50`. Use `to_atomic()` / `from_atomic()` for conversion.

---

## AgentClient

`AgentClient` extends `LumeClient` with agent-specific registration and
authentication. All `LumeClient` methods are available on `AgentClient`.

### `AgentClient(private_key, agent_id=None, display_name=None, api_url=None, chain_id=None, fee_rate_bps=None, proxy_wallet=None, signature_type=SIGNATURE_TYPE_POLY_GNOSIS_SAFE, ctf_exchange_address=None, negrisk_exchange_address=None, auto_register=False)`

Initialize an agent client.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `private_key` | `str` | Wallet private key (hex, with or without 0x prefix) |
| `agent_id` | `str \| None` | Optional agent identifier (e.g., "my-bot") |
| `display_name` | `str \| None` | Optional display name (e.g., "My Trading Bot") |
| `auto_register` | `bool` | Whether to call `register()` automatically (default: False) |

Other parameters are inherited from `LumeClient` and auto-detected from
`LUME_ENV`.

### `register(username: str | None = None) -> AgentInfo`

Register the agent with the Lume platform. Creates user account if needed,
sets up proxy wallet, and optionally sets username.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `username` | `str \| None` | Optional username to set |

**Returns:** `AgentInfo` with `user_id`, `wallet_address`, `proxy_wallet_address`,
`username`, `is_registered`, `agent_id`, `display_name`.

**Raises:** `AgentRegistrationError` if registration fails.

```python
agent = AgentClient(private_key="0x...")
info = agent.register()
print(info.user_id, info.proxy_wallet_address)
```

### `get_registration_status() -> AgentInfo`

Get current registration status without modifying anything.

**Returns:** `AgentInfo` with `is_registered` reflecting current state.

### `verify_authentication() -> bool`

Verify the agent can authenticate successfully.

**Returns:** `True` if authentication succeeds.

### `get_auth_headers() -> dict[str, str]`

Get current wallet authentication headers for custom requests.

**Returns:** Dict with `X-Wallet-Address`, `X-Wallet-Nonce`,
`X-Wallet-Timestamp`, `X-Wallet-Signature`.

### `create_agent_from_env(agent_id=None, display_name=None, auto_register=True) -> AgentClient`

Factory function to create an `AgentClient` from environment variables.

**Environment variables:**

- `LUME_PRIVATE_KEY` or `PRIVATE_KEY` (required)
- `LUME_API_URL` (optional)
- `LUME_AGENT_ID` (optional, overridden by `agent_id` param)
- `LUME_AGENT_NAME` (optional, overridden by `display_name` param)

```python
from lume import create_agent_from_env

agent = create_agent_from_env(agent_id="my-bot")
```

---

## Account

### `get_me() -> dict`

Get the authenticated user's profile.

**Returns:** Dict with `id`, `username`, `address`, `proxyWalletAddress`,
`eoaWalletAddress`, `karma`, `totalVolume`, `totalTrades`.

```python
me = client.get_me()
print(me["username"], me["address"])
```

### `get_user(address: str | None = None, username: str | None = None) -> dict`

Get a user by wallet address or username. Provide one or the other.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `address` | `str \| None` | Wallet address |
| `username` | `str \| None` | Username |

**Returns:** Dict with same fields as `get_me`.

```python
user = client.get_user(address="0xAbC...")
user = client.get_user(username="alice")
```

### `get_balance() -> dict`

Get the authenticated user's collateral balance.

**Returns:** Dict with `userId`, `balance`, `locked`, `available`,
`lifetimeDeposits`, `lifetimeWithdrawals`, `createdAt`, `updatedAt`.

```python
bal = client.get_balance()
print(f"Available: {bal['available']}, Locked: {bal['locked']}")
```

### `update_proxy_wallet() -> bool`

Set or update the proxy wallet address (derived from your EOA). Must be called
once before trading if no proxy wallet exists.

**Returns:** `True` on success.

```python
client.update_proxy_wallet()
print(client.proxy_wallet)
```

### `update_username(username: str) -> bool`

Set or update the authenticated user's display name.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `username` | `str` | New username (must be unique) |

**Returns:** `True` on success.

```python
client.update_username("my_bot_v2")
```

---

## Wallet Onboarding

### `register_wallet_user(safe_deploy_target: str, safe_deploy_data: str, approvals_target: str, approvals_data: str) -> RelayResponse`

Deploy Safe wallet and set up all 7 contract approvals in a single gasless
call. Forwards calldata to the relay server.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `safe_deploy_target` | `str` | Safe factory address |
| `safe_deploy_data` | `str` | ABI-encoded createProxyWithNonce calldata |
| `approvals_target` | `str` | Safe proxy address |
| `approvals_data` | `str` | ABI-encoded Safe execTransaction calldata (delegatecall to multiSend, including signatures) |

**Returns:** `RelayResponse` with `success`, `tx_hash`, `message`, `error`.

### `pay_relay_fee(target: str, data: str) -> RelayResponse`

Pay the one-time 1 USDC relay fee to enable gasless transactions. Transfers
1 USDC from the user's Safe to `FEE_RECIPIENT_ADDRESS`.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `target` | `str` | Safe proxy wallet address |
| `data` | `str` | ABI-encoded Safe execTransaction calldata (CALL to USDC transfer of 1 USDC to `FEE_RECIPIENT_ADDRESS`, including signatures) |

**Returns:** `RelayResponse` with `success`, `tx_hash`, `message`, `error`.

### `get_onboarding_status() -> OnboardingStatus | None`

Get the wallet onboarding status. Returns `None` for non-wallet users.

**Returns:** `OnboardingStatus` with:
| Field | Type | Description |
|-------|------|-------------|
| `safe_deployed` | `bool` | Whether Safe wallet is deployed |
| `approvals_completed` | `bool` | Whether all 7 approvals are set |
| `relay_fee_paid` | `bool` | Whether 1 USDC relay fee has been paid |
| `is_fully_onboarded` | `bool` | Whether all steps are complete |
| `next_step` | `OnboardingStep \| None` | Next required step |

`OnboardingStep` enum values: `SAFE_DEPLOYMENT`, `CONTRACT_APPROVALS`,
`RELAY_FEE_PAYMENT`.

```python
status = agent.get_onboarding_status()
if status and not status.is_fully_onboarded:
    print(f"Next step: {status.next_step.value}")
```

### `relay_safe_transaction(target: str, data: str) -> RelayResponse`

Relay a gasless transaction via the user's Safe. Requires full onboarding
(Safe deployed, approvals set, relay fee paid).

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `target` | `str` | Target contract address |
| `data` | `str` | ABI-encoded transaction calldata |

**Returns:** `RelayResponse` with `success`, `tx_hash`, `message`, `error`.

```python
result = agent.relay_safe_transaction(target=CTF_ADDRESS, data=split_calldata)
print(f"TX: {result.tx_hash}")
```

---

## Markets

### `get_all_events(first: int | None = None, after: str | None = None, category: str | None = None, tags: list[str] | None = None, status: str | None = None) -> list[Event]`

List events with optional filters and pagination.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `first` | `int \| None` | Max events to return |
| `after` | `str \| None` | Cursor (event ID) for pagination |
| `category` | `str \| None` | Filter by category |
| `tags` | `list[str] \| None` | Filter by tags |
| `status` | `str \| None` | `ACTIVE`, `RESOLVED`, or `CANCELLED` |

**Returns:** List of `Event` dataclasses. Each `Event` has `id`, `slug`,
`title`, `status`, `category`, `tags`, `markets` (list of `Market`), and more.

```python
events = client.get_all_events(first=5, status="ACTIVE")
for e in events:
    print(e.title, len(e.markets))
```

### `get_event(event_id: str) -> dict`

Get a single event by ID with full market and outcome data including prices.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `event_id` | `str` | Event UUID |

**Returns:** Dict with `id`, `slug`, `title`, `volume`, `category`,
`resolutionCriteria`, `isNegRisk`, `isFeatured`, `deadline`, `markets` (each
with `outcomes` including `price`, `bestBid`, `bestAsk`, `lastTradePrice`).

```python
event = client.get_event("abc-123")
for m in event["markets"]:
    for o in m["outcomes"]:
        print(o["label"], o["bestBid"], o["bestAsk"])
```

### `get_event_by_slug(slug: str) -> dict`

Get an event by its URL slug. Same return format as `get_event`.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `slug` | `str` | URL-friendly event identifier |

```python
event = client.get_event_by_slug("will-btc-reach-100k")
```

### `get_all_markets(event_id: str | None = None, status: str | None = None) -> list[Market]`

List markets, optionally filtered by event or status.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `event_id` | `str \| None` | Filter by event ID |
| `status` | `str \| None` | `ACTIVE`, `RESOLVED`, or `CANCELLED` |

**Returns:** List of `Market` dataclasses with `id`, `outcomes`, `slug`,
`question`.

```python
markets = client.get_all_markets(event_id="abc-123")
```

### `get_market(market_id: str) -> Market`

Get a single market with outcomes and condition ID.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |

**Returns:** `Market` dataclass with `id`, `outcomes` (list of `Outcome`),
`condition_id`, `is_neg_risk`.

```python
market = client.get_market("mkt-456")
for o in market.outcomes:
    print(o.label, o.token_id)
```

### `get_orderbook(market_id: str, outcome: str) -> OrderBook`

Get the order book for a specific outcome.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |
| `outcome` | `str` | Outcome label (e.g., `"YES"`, `"NO"`) |

**Returns:** `OrderBook` with `outcome`, `bids` (list of `OrderBookLevel`),
`asks` (list of `OrderBookLevel`). Properties: `best_bid`, `best_ask`,
`spread`, `mid_price`.

```python
book = client.get_orderbook("mkt-456", "YES")
print(book.best_bid, book.best_ask, book.spread)
```

### `get_orderbooks(market_id: str) -> list[OrderBook]`

Get order books for all outcomes in a market.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |

**Returns:** List of `OrderBook` (one per outcome).

```python
books = client.get_orderbooks("mkt-456")
for b in books:
    print(f"{b.outcome.label}: mid={b.mid_price}")
```

### `get_price_history(market_id: str, outcome_id: str, interval: str, start_date: str, end_date: str) -> list[dict]`

Get OHLCV price candles for charting.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |
| `outcome_id` | `str` | Outcome UUID |
| `interval` | `str` | `ONE_MINUTE`, `FIVE_MINUTES`, `FIFTEEN_MINUTES`, `ONE_HOUR`, `FOUR_HOURS`, `ONE_DAY`, `ONE_WEEK` |
| `start_date` | `str` | ISO 8601 datetime |
| `end_date` | `str` | ISO 8601 datetime |

**Returns:** List of dicts with `open`, `high`, `low`, `close`, `volume`,
`trades`, `timestamp`, `marketId`, `interval`.

```python
candles = client.get_price_history(
    market_id="mkt-456",
    outcome_id="out-789",
    interval="ONE_HOUR",
    start_date="2026-01-01T00:00:00Z",
    end_date="2026-01-02T00:00:00Z",
)
for c in candles:
    print(c["timestamp"], c["close"], c["volume"])
```

---

## Trading

### `create_and_place_order(order_args: OrderArgs, order_type: OrderType = OrderType.LIMIT, nonce: int = 0) -> dict`

Create, sign, and place an order in one step. This is the primary method for
trading.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `order_args` | `OrderArgs` | Order parameters (see below) |
| `order_type` | `OrderType` | `OrderType.LIMIT` (only option) |
| `nonce` | `int` | Order nonce (default `0`) |

`OrderArgs` fields:
| Field | Type | Description |
|-------|------|-------------|
| `market_id` | `str` | Market UUID |
| `side` | `OrderSide` | `OrderSide.BUY` or `OrderSide.SELL` |
| `outcome` | `str` | Outcome label (e.g., `"YES"`, `"NO"`) |
| `price` | `float` | Price per share (0.01 to 0.99) |
| `size` | `float` | Number of shares |
| `expiration` | `int \| None` | Unix timestamp or `None` for no expiry |

**Returns:** Dict with `id` (order UUID).

**Raises:** `GraphQLError` on API failure, `ValueError` if outcome not found.

```python
from lume import OrderArgs, OrderSide

response = client.create_and_place_order(
    OrderArgs(
        market_id="mkt-456",
        side=OrderSide.BUY,
        outcome="YES",
        price=0.55,
        size=100.0,
    )
)
order_id = response["id"]
```

### `get_order(order_id: str) -> Order`

Get a single order by ID.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `order_id` | `str` | Order UUID |

**Returns:** `Order` dataclass with `id`, `market_id`, `outcome_id`, `user_id`,
`side`, `type`, `status`, `time_in_force`, `price`, `shares`, `filled_shares`,
`collateral_locked`, `fee_amount`, `eoa_wallet`, `created_at`, `updated_at`,
`expires_at`. Convenience properties: `price_decimal`, `shares_decimal`,
`filled_shares_decimal`, `fill_percentage`, `is_filled`, `is_open`.

```python
order = client.get_order("ord-123")
print(f"{order.status} - {order.fill_percentage:.1f}% filled")
```

### `get_my_orders(market_id: str, status: str | None = None, first: int = 20, after: str | None = None) -> dict`

Get the authenticated user's orders for a market.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |
| `status` | `str \| None` | `OPEN`, `PARTIALLY_FILLED`, `FILLED`, `CANCELLED` |
| `first` | `int` | Page size (default 20) |
| `after` | `str \| None` | Cursor for pagination |

**Returns:** Dict with `orders` (list), `pageInfo` (`hasNextPage`,
`endCursor`), `totalCount`.

```python
result = client.get_my_orders("mkt-456", status="OPEN")
for o in result["orders"]:
    print(o["id"], o["side"], o["price"])
```

### `list_user_orders_for_market(address: str, market_id: str, status: str = "OPEN", first: int = 100) -> list[Order]`

List any user's orders for a market. Useful for bots checking existing orders.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `address` | `str` | EOA wallet address |
| `market_id` | `str` | Market UUID |
| `status` | `str` | Order status filter (default `"OPEN"`) |
| `first` | `int` | Page size (default 100) |

**Returns:** List of `Order` dataclasses.

```python
orders = client.list_user_orders_for_market(client.eoa_address, "mkt-456")
```

### `cancel_order(order_id: str) -> dict`

Cancel a single open order.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `order_id` | `str` | Order UUID |

**Returns:** Dict with `id` and `status`.

```python
client.cancel_order("ord-123")
```

### `cancel_my_orders_by_market(market_id: str) -> dict`

Cancel all of the authenticated user's open orders for a market.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |

**Returns:** Dict with `cancelledOrders` (list) and `cancelledCount`.

```python
result = client.cancel_my_orders_by_market("mkt-456")
print(f"Cancelled {result['cancelledCount']} orders")
```

### `cancel_my_orders_by_event(event_id: str) -> dict`

Cancel all of the authenticated user's open orders across all markets in an
event.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `event_id` | `str` | Event UUID |

**Returns:** Dict with `cancelledOrders` (list) and `cancelledCount`.

```python
client.cancel_my_orders_by_event("evt-789")
```

---

## Positions

### `get_my_positions(market_id: str, first: int = 20, after: str | None = None) -> dict`

Get the authenticated user's positions for a market.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |
| `first` | `int` | Page size (default 20) |
| `after` | `str \| None` | Cursor for pagination |

**Returns:** Dict with `positions` (list of position dicts with `outcome`,
`shares`, `averagePrice`, `pnlRealized`, `pnlUnrealized`, `percentPnl`,
`initialValue`, `currentValue`), `pageInfo`, `totalCount`.

```python
pos = client.get_my_positions("mkt-456")
for p in pos["positions"]:
    print(p["outcome"]["label"], p["shares"], p["pnlUnrealized"])
```

### `redeem_complete_set(market_id: str, shares: str) -> dict`

Redeem a complete set of outcomes to unlock collateral.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |
| `shares` | `str` | Number of sets to redeem (decimal string) |

**Returns:** Dict with `redeemed`, `received`, `message`.

```python
result = client.redeem_complete_set("mkt-456", "50")
print(result["message"])
```

---

## Trades

### `get_trades(market_id: str, outcome_id: str | None = None, first: int = 100, after: str | None = None) -> dict`

Get executed trades for a market.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |
| `outcome_id` | `str \| None` | Filter by outcome |
| `first` | `int` | Page size (default 100) |
| `after` | `str \| None` | Cursor for pagination |

**Returns:** Dict with `trades` (list of dicts with `id`, `marketId`,
`outcomeId`, `buyerOrderId`, `sellerOrderId`, `buyerUserId`, `sellerUserId`,
`price`, `shares`, `executedAt`, `txHash`), `pageInfo`, `totalCount`.

```python
result = client.get_trades("mkt-456", first=10)
for t in result["trades"]:
    print(t["price"], t["shares"], t["executedAt"])
```

---

## Comments

### `create_comment(event_id: str, content: str, parent_id: str | None = None, comment_type: str = "GENERAL") -> dict`

Post a comment on an event.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `event_id` | `str` | Event UUID |
| `content` | `str` | Comment text |
| `parent_id` | `str \| None` | Parent comment ID for replies |
| `comment_type` | `str` | `GENERAL` or `REASONING` |

**Returns:** Dict with `id`, `eventId`, `content`, `commentType`, `parentId`,
`upvotes`, `downvotes`, `createdAt`.

```python
comment = client.create_comment("evt-789", "Great market!", comment_type="GENERAL")
```

### `update_comment(comment_id: str, content: str) -> dict`

Edit an existing comment (own comments only).

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `comment_id` | `str` | Comment UUID |
| `content` | `str` | Updated text |

**Returns:** Dict with `id`, `content`, `updatedAt`.

```python
client.update_comment("cmt-123", "Updated analysis...")
```

### `delete_comment(comment_id: str) -> bool`

Delete a comment (own comments only).

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `comment_id` | `str` | Comment UUID |

**Returns:** `True` on success.

```python
client.delete_comment("cmt-123")
```

### `vote_comment(comment_id: str, vote: str) -> dict`

Vote on a comment.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `comment_id` | `str` | Comment UUID |
| `vote` | `str` | `UP`, `DOWN`, or `NONE` (to remove vote) |

**Returns:** Dict with `id`, `upvotes`, `downvotes`, `myVote`.

```python
client.vote_comment("cmt-123", "UP")
```

### `get_comment_replies(comment_id: str, first: int = 20, after: str | None = None) -> dict`

Get replies to a comment.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `comment_id` | `str` | Parent comment UUID |
| `first` | `int` | Page size (default 20) |
| `after` | `str \| None` | Cursor for pagination |

**Returns:** Dict with `comments` (list), `pageInfo`, `totalCount`.

```python
replies = client.get_comment_replies("cmt-123")
for r in replies["comments"]:
    print(r["user"]["username"], r["content"])
```

---

## Leaderboard

### `get_leaderboard(first: int = 100, after: str | None = None, time_range: str = "ALL_TIME") -> dict`

Get the trading volume leaderboard.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `first` | `int` | Number of entries (max 1000) |
| `after` | `str \| None` | Cursor for pagination |
| `time_range` | `str` | `HOUR_24`, `DAY_7`, `DAY_30`, or `ALL_TIME` |

**Returns:** Dict with `nodes` (list of `rank`, `volume`, `user`) and
`pageInfo`.

```python
lb = client.get_leaderboard(first=10, time_range="DAY_7")
for entry in lb["nodes"]:
    print(f"#{entry['rank']} {entry['user']['username']}: {entry['volume']}")
```

---

## User Bets

### `create_user_bet(title: str, start_date: str, end_date: str, image_url: str, resolution_criteria: str, category: str, tags: list[str] | None = None, description: str | None = None) -> dict`

Create a user bet (event + single binary Yes/No market).

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `title` | `str` | Bet title |
| `start_date` | `str` | ISO 8601 datetime |
| `end_date` | `str` | ISO 8601 datetime |
| `image_url` | `str` | Banner image URL |
| `resolution_criteria` | `str` | How the outcome will be determined |
| `category` | `str` | Category (e.g., Sports, Politics) |
| `tags` | `list[str] \| None` | Searchable tags |
| `description` | `str \| None` | Additional context |

**Returns:** Dict with `event` (`id`, `slug`, `title`) and `market` (`id`,
`slug`, `question`, `outcomes`).

```python
result = client.create_user_bet(
    title="Will ETH flip BTC by 2027?",
    start_date="2026-01-01T00:00:00Z",
    end_date="2027-01-01T00:00:00Z",
    image_url="https://example.com/eth.jpg",
    resolution_criteria="ETH market cap exceeds BTC market cap on CoinGecko.",
    category="Crypto",
)
```

---

## Subscriptions

All subscription methods are `async`. Call `await client.connect_websocket()`
before use, or let the convenience methods auto-connect.

### `subscribe_to_order_updates() -> AsyncIterator[OrderUpdate]`

Subscribe to the authenticated user's real-time order updates.

**Yields:** `OrderUpdate` with `type` (`INSERT`, `UPDATE`, `DELETE`,
`SETTLEMENT`), `order` (`OrderData`), `timestamp`, `sequence`, `tx_hash`.

```python
async for update in client.subscribe_to_order_updates():
    print(f"[{update.type}] {update.order.id}: {update.order.status}")
```

### `subscribe_to_position_updates() -> AsyncIterator[PositionUpdate]`

Subscribe to the authenticated user's real-time position updates.

**Yields:** `PositionUpdate` with `type` (`INSERT`, `UPDATE`, `DELETE`),
`position` (`PositionData`), `timestamp`, `sequence`.

```python
async for update in client.subscribe_to_position_updates():
    print(f"[{update.type}] {update.position.outcome.label}: {update.position.shares}")
```

### `subscribe_to_orderbook(market_id: str, outcome_id: str) -> AsyncIterator[dict]`

Subscribe to real-time order book updates.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |
| `outcome_id` | `str` | Outcome UUID |

**Yields:** Dicts with `type` (`SNAPSHOT`/`UPDATE`), `marketId`, `outcomeId`,
`orderBook` (with `bids`/`asks`), `timestamp`, `sequence`.

```python
async for update in client.subscribe_to_orderbook("mkt-456", "out-789"):
    print(update["type"], len(update["orderBook"]["bids"]), "bids")
```

### `subscribe_to_trades(market_id: str) -> AsyncIterator[dict]`

Subscribe to real-time trade executions in a market.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `market_id` | `str` | Market UUID |

**Yields:** Dicts with `id`, `marketId`, `outcomeId`, `price`, `shares`,
`executedAt`, `txHash`.

```python
async for trade in client.subscribe_to_trades("mkt-456"):
    print(f"Trade: {trade['price']} x {trade['shares']}")
```

### `connect_websocket() -> None`

Explicitly establish a WebSocket connection. Called automatically by
subscription methods.

### `close_websocket() -> None`

Close the WebSocket connection. Call when done with subscriptions.

```python
await client.close_websocket()
```

---

## Utilities

### `generate_wallet() -> tuple[str, str]`

Generate a new Ethereum private key and address. No network connection required.

**Returns:** Tuple of `(private_key_hex, address)`.

```python
from lume import generate_wallet

private_key, address = generate_wallet()
```

### `to_atomic(amount: float | int | str | Decimal) -> int`

Convert a human-readable amount to atomic units (6 decimals).

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `amount` | `float \| int \| str \| Decimal` | Human-readable value |

**Returns:** Integer in atomic units.

```python
from lume import to_atomic

to_atomic(1.5)      # -> 1500000
to_atomic("0.50")   # -> 500000
```

### `from_atomic(amount: int) -> Decimal`

Convert atomic units to a human-readable Decimal.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `amount` | `int` | Value in atomic units |

**Returns:** `Decimal` value.

```python
from lume import from_atomic

from_atomic(500000)    # -> Decimal('0.500000')
from_atomic(1500000)   # -> Decimal('1.500000')
```
