Skip to content

For solvers

A solver is an off-chain agent that quotes prices and signs commitments. You decide your own pricing model, your own risk limits, and how much capital to post; the protocol's job is to enforce the rules of the trade, not to dictate strategy.

This page covers what a solver does, how to get registered, and the wire protocol you'll need to implement. For the formal model, see Protocol reference.

What a solver does

In one sentence: stay connected to the orchestrator, answer quote requests with a signed commitment, post collateral on-chain to back your quotes.

In more detail:

  • Maintain a persistent WebSocket connection to the orchestrator.
  • For each incoming quote request, decide whether to quote and at what price. Respond within the orchestrator's listening window (sub-second).
  • Sign each commitment with the private key registered against your solverId on-chain.
  • Deposit USDC collateral into the market contract; track your own free capital so you only quote what you can back. The contract enforces this as a hard limit, but if you over-commit the user's transaction reverts and you're useless to them.
  • Decide your own risk envelope: which ranges, which maturities, which spot-distance limits you're willing to quote.

You compete with other solvers on price. The orchestrator runs a min-price-wins auction with a short listening window; whoever responds first with the best quote takes the trade.

Getting registered

Registration is gated by an admin multisig today (this is a centralization point we plan to relax). The flow:

  1. Generate a fresh signing keypair (this signs commitments; do not reuse a wallet key) and a fresh owner address (this controls your collateral; typically a multisig or hot wallet you trust).
  2. Talk to the team. We'll review your setup and, on approval, call registerSolver(signer, owner) on the market contract.
  3. You receive a solverId (a uint32, small integer). This is how the protocol identifies you.
  4. The team also adds your signer address to the orchestrator's allowlist so you can complete the WebSocket handshake. The on-chain registry and the orchestrator's allowlist are separate — both have to be configured.
  5. Deposit USDC collateral via depositCollateral(solverId, amount). Anyone can deposit on your behalf, so this can come from your owner or a designated treasurer.

You can be paused (no new commitments accepted; existing positions still resolve normally) or fully removed (retired permanently; existing positions held to maturity, free collateral still withdrawable).

The WebSocket protocol

Frames are JSON text frames. There is no envelope type discriminator — message types are identified by a top-level type (handshake messages) or command (quote requests) field, and your responses are identified by direction.

Handshake (EIP-191 challenge / response)

When you connect:

  1. Orchestrator → you: {"type": "auth_challenge", "nonce": "0x<32 random bytes hex>"}
  2. You → orchestrator: {"type": "auth_response", "signature": "0x<65-byte EIP-191 sig>"}
  3. Orchestrator → you: {"type": "auth_ok", "address": "0x..."} on success, or {"type": "auth_reject", "reason": "..."}.

The message you sign is the literal string:

OptionsMarket solver auth v1
nonce: 0x<the hex from the challenge>

…hashed with the standard EIP-191 personal-sign prefix (\x19Ethereum Signed Message:\n<len>...). The 65-byte signature can use either raw v ∈ {0,1} or personal-sign v ∈ {27,28} — the orchestrator normalizes both.

The handshake must complete within 5 seconds of connection.

Liveness

The orchestrator sends a WebSocket PING every 20 seconds. You must respond with a PONG within 60 seconds or the connection is closed (ClosePolicyViolation). Standard gorilla/websocket-style pong handlers work out of the box.

Quote requests

Once authenticated, you receive command messages from the orchestrator:

{
  "request_id": "<uuid>",
  "command": "<one of four>",
  "payload": { ... }
}

There are four commands:

Command Payload What you return
position_open_quote asset, lower_bound_ticks, upper_bound_ticks, maturity_hour price only
position_open the open-quote payload + buyer address + quantity price + packed commitment + signature
position_close_quote asset, solver_id, token_id price only
position_close the close-quote payload + user address + quantity price + packed commitment + signature

The _quote variants are price-only; the orchestrator uses them when the UI is just displaying a quote indicatively. The full variants are sent when the user is ready to submit a transaction — you only sign at that point.

Your response

{
  "request_id": "<echoed>",
  "solver_name": "<your name>",
  "solver_id": <your registry id>,
  "price": <float>,
  "commitment_packed": "0x<hex>",       // absent on _quote variants
  "commitment_signature": "0x<hex>",    // absent on _quote variants
  "error": "<reason>"                   // set when declining
}

If you can't quote (request outside your envelope, insufficient capital, malformed input), set error and omit price. The orchestrator simply ignores you for that request.

Commitments

A commitment is the EIP-712-signed message that the user submits on-chain alongside their transaction. Two types: BuyCommitment and BuybackCommitment. Schemas and the packed wire format (140 bytes / 160 bytes) are documented on the API docs site; a reference codec lives in contracts/src/Commitments.sol with round-trip tests you can vendor.

Key invariants:

  • The price is part of the signed payload. There is no separate price field on the transaction.
  • A BuyCommitment binds to a specific buyer address; the smart contract enforces msg.sender == buyer on buy. This means you can quote freely without worrying about commitments being captured by anyone but the intended buyer.
  • A BuybackCommitment similarly binds to the user (the seller).
  • Each commitment carries a nonce that can only be used once per solver. Reuse reverts.
  • Each commitment has an expiry timestamp — typically very short (~1 minute). The contract rejects expired commitments.

You're signing a binding offer. Make sure your free capital is reserved before you sign.

Collateral mechanics

You have three tracked numbers in the contract:

  • balance — total USDC currently deposited under your solver ID.
  • frozen — sum of collateral currently backing your open positions. Frozen amount equals quantity per open position (each unit needs $1 to back its potential payout).
  • costBasis — cumulative deposits minus cumulative principal withdrawals.

Your free capital is balance - frozen. The contract enforces that you can never issue a commitment whose required new collateral exceeds free capital — but again, it does so by reverting the user's transaction, which is bad UX. Self-track so you only sign quotes you can cover.

Withdrawals

withdrawCollateral(solverId, amount, to) is only callable by your owner address. The withdrawal is split:

  1. Fee-free portion up to your remaining principal (costBasis - principalWithdrawn). Reduces principal 1:1.
  2. Profit portion above that. Subject to a 20% protocol fee (80% to you, 20% to the treasury).

You can withdraw profit even while some collateral is frozen — the frozen amount already covers maximum loss exposure on open positions.

When positions resolve

Event Effect on your accounts
You issue a position (buy) balance += premium, frozen += quantity
You buy back your own issue (buyback, isIssuer) balance -= amountPaid, frozen -= released collateral
A different solver buys you back Your balances unchanged (you're not a party). The new solver's balance -= amountPaid; their frozen is unchanged because the original collateral still backs the position.
Settle, user wins balance -= payout, frozen -= payout
Settle, user loses frozen -= released, balance unchanged (the premium is realized P&L)
Emergency-cancel frozen -= released (no balance change)

What you decide vs. what the protocol enforces

You decide Protocol enforces
Pricing model EIP-712 signature validity, nonce uniqueness, expiry
Which assets / ranges / maturities to quote msg.sender == buyer on buy, user on buyback
Spot-distance and inventory limits Sufficient free collateral at submission time
Spread / margin Per-buy() ETH openFee
Whether to participate in a given request at all Settlement payout routing, premium escrow

You can be entirely arbitrary about which requests you respond to. Quoting outside your declared envelope or with thin/no margin is your business; the on-chain rules are the only hard backstop.


Next steps