Skip to main content

Market Maker Protection (MMP)

Fill-window limits and auto-cancel behavior for market makers.

Every wallet-scoped MMP endpoint requires EIP-712 authentication. Configuration writes accept the wallet owner or an authorized agent for that wallet. Reads are owner-only and reject authorized-agent signatures.

MMP applies to the trading account that owns fills. For a portfolio-margin manager, reads and writes signed for the manager wallet resolve to its Account.sol trading account, the same account that order placement uses. Responses report that account in wallet_address, and the MMP nonce sequence belongs to that account.

For read-after-write consistency, send X-Hypercall-Fence-Wait: 1 on any MMP mutation. A successful response then includes X-Hypercall-Fence; echo that value as X-Hypercall-Fence on the authenticated GET. Mutations without the opt-in wait header intentionally do not return a fence.

Overview​

MMP protects market makers from rapid adverse fills by:

  1. Tracking cumulative fill metrics within a rolling time window
  2. Canceling orders when limits are breached
  3. Auto-canceling all other MMP-enabled orders for the same wallet+underlying

Configuration​

Set MMP Config​

Endpoint: POST /mmp-config

Request: SetMmpConfigRequest

{
"wallet": "0x...",
"currency": "BTC",
"interval_ms": 60000,
"frozen_time_ms": 300000,
"qty_limit": "1000000",
"delta_limit": "10.0",
"vega_limit": "5.0",
"nonce": 1,
"signature": "0x..."
}

Fields:

  • wallet: Wallet address
  • currency: Underlying currency (e.g., "BTC", "ETH")
  • interval_ms: Rolling window length in milliseconds
  • frozen_time_ms: Freeze duration after trigger (milliseconds)
  • qty_limit: Optional quantity limit (contract units), serialized as a decimal string
  • delta_limit: Optional delta limit, serialized as a decimal string
  • vega_limit: Optional vega limit, serialized as a decimal string
  • nonce: Replay-protection nonce, exactly one greater than the latest committed nonce returned by the authenticated read
  • signature: EIP-712 SetMmpConfig signature over every request field

Response: ApiResponse<MmpConfigData>

Get MMP Config​

Endpoint: GET /mmp-config?wallet=...&currency=...

Query parameters:

  • wallet (required)
  • currency (optional filter)

Required headers:

  • X-Hypercall-Expires-At-Ms: Unix expiry in milliseconds, no more than 60 seconds in the future
  • X-Hypercall-Signature: GetMmpConfig signature over wallet, currency (empty string when omitted), and the header expiry

The recovered signer must be the wallet owner. Authorized-agent signatures are not accepted for this read.

Response: MmpConfigResponse

{
"success": true,
"data": [
{
"wallet_address": "0x...",
"currency": "BTC",
"interval_ms": 60000,
"frozen_time_ms": 300000,
"qty_limit": "1000000",
"delta_limit": "10.0",
"vega_limit": "5.0",
"enabled": true,
"nonce": 1
}
]
}

Delete MMP Config​

Endpoint: DELETE /mmp-config

Request: DeleteMmpConfigRequest

{
"wallet": "0x...",
"currency": "BTC",
"nonce": 1,
"signature": "0x..."
}

Response: ApiResponse<String>. A successful command disables the numeric wallet/underlying authority state. With request header X-Hypercall-Fence-Wait: 1, the response includes X-Hypercall-Fence for the projection-backed read.

Reset MMP State​

Endpoint: POST /mmp-config/reset

Request: ResetMmpRequest

{
"wallet": "0x...",
"currency": "BTC",
"nonce": 1,
"signature": "0x..."
}

Response: ApiResponse<String>. A successful command clears the MMP fill window and freeze state in replayable authority. With request header X-Hypercall-Fence-Wait: 1, the response includes X-Hypercall-Fence for the projection-backed read.

MMP Behavior (Critical to Understand)​

Which Fills Count​

Every fill counts toward the MMP window of each side whose order has mmp_enabled=true, whether that order was resting on the book (maker) or arriving (taker):

  • Resting quotes: when an incoming order trades against your resting MMP-enabled order, that fill is added to your window. This is the case MMP exists for: a burst of fills against your quotes trips MMP and cancels your remaining MMP-enabled quotes on that underlying.
  • Incoming orders: an MMP-enabled order that takes liquidity counts its own fills the same way.
  • Combos and implied orders: each protected participant of a package counts every leg it trades, assessed once per package.
  • RFQ and RPI fills do not count toward MMP yet. An MMP freeze does block new RFQ executions for the frozen quote provider.

Each fill is measured from your own side: a fill that sells a call adds negative delta to the seller's window and positive delta to the buyer's.

If an order has mmp_enabled=true but its wallet has no MMP configuration for that underlying, MMP fails closed: the first fill trips and cancels that wallet's MMP-enabled orders on the underlying. Configure MMP before flagging orders.

Fill Processing Order​

MMP evaluation is POST-FILL:

  1. Engine matches and accepts the fill first
  2. Then MMP checks cumulative metrics for each protected side of the fill
  3. If a side's limits are breached:
    • MMP triggers for that side's wallet and underlying
    • Engine emits MmpTriggered event
    • Engine auto-cancels all MMP-enabled open orders for that wallet+underlying
    • If the breaching side was the incoming order, its remaining quantity stops and it becomes CANCELED with reason "MMP triggered during fill processing"
    • If the breaching side was a resting maker, the incoming order keeps matching against other liquidity; the maker's other MMP-enabled quotes are already cancelled

Implication: You can receive fills that push you beyond your configured thresholds before MMP triggers.

Trigger Conditions​

MMP triggers if any configured limit is breached:

  • Quantity limit: cumulative_qty > qty_limit
  • Delta limit: |cumulative_delta| > delta_limit
  • Vega limit: |cumulative_vega| > vega_limit

Cumulative metrics are computed over the rolling window (interval_ms).

Cancel Behavior​

When MMP triggers:

  1. Incoming order that tripped: remaining quantity stops, order becomes CANCELED with reason "MMP triggered during fill processing"
  2. Resting order that tripped: the fill stands; the order's remainder, if any, is cancelled with the other MMP-enabled orders
  3. Other orders: All MMP-enabled open orders for the same wallet+underlying are auto-canceled with reason "Order canceled by MMP trigger"

Note: Only MMP-enabled orders are canceled. Orders with mmp_enabled=false are not affected.

Freeze Behavior​

After MMP triggers:

  • The wallet and underlying are frozen for frozen_time_ms milliseconds
  • While frozen, the wallet is excluded from RPI auctions and RFQs on that underlying, and combo or implied executions that would fill it are rejected
  • An outright fill on an MMP-enabled order during the freeze still stands, then trips MMP again and cancels that wallet's MMP-enabled orders on the underlying
  • POST /mmp-config/reset ends the freeze early

Fill Metrics Calculation​

Quantity​

  • Source: Fill size (contract units)
  • Cumulative: Sum of all fills in the rolling window

Delta​

  • Source: Calculated from fill symbol and GreeksCache
  • Cumulative: Sum of all fill deltas in the rolling window

Vega​

  • Source: Calculated from fill symbol and GreeksCache
  • Cumulative: Sum of all fill vegas in the rolling window

Rolling Window​

Window Eviction​

Fills older than interval_ms are automatically evicted from the window:

  • Eviction runs when the next fill is processed, before the new fill is added
  • Cumulative metrics are decremented as old fills are removed

Window Reset​

Reset MMP state via POST /mmp-config/reset:

  • Clears all fills in the window
  • Resets cumulative metrics to zero
  • Unfreezes currency

MMP Events​

MmpTriggered Event​

Internal event (not currently forwarded to WS clients):

MmpTriggeredMessage {
wallet: WalletAddress,
currency: String,
reason: String, // "MMP triggered during fill processing" | "MMP triggered by a resting order fill"
timestamp: u64,
}

Best Practices​

Configuration​

  1. Set appropriate limits: Based on your risk tolerance and typical fill sizes
  2. Use currency-specific configs: Configure per underlying (BTC, ETH, etc.)
  3. Monitor MMP triggers: High trigger rate may indicate misconfigured limits or adverse market conditions

Order Management​

  1. Enable MMP selectively: Set mmp_enabled=true only for orders you want protected
  2. Use consistent currency: Ensure all orders for the same underlying use the same currency in MMP config
  3. Handle MMP cancels: Implement logic to detect MMP-triggered cancels and adjust quoting

Monitoring​

  1. Track MMP triggers: Monitor for "MMP triggered during fill processing" and "Order canceled by MMP trigger" reasons
  2. Review fill windows: Cumulative window metrics are not exposed through the API yet; track your own fills per underlying
  3. Adjust limits: If MMP triggers too frequently, increase limits; if too infrequently, decrease limits

Common Issues​

MMP Triggers Too Frequently​

Causes:

  • Limits too low for typical fill sizes
  • Adverse market conditions
  • Rapid quoting causing many fills

Solutions:

  • Increase qty_limit, delta_limit, or vega_limit
  • Increase interval_ms to allow more fills in window
  • Reduce quoting frequency

MMP Doesn't Trigger​

Causes:

  • Limits too high
  • MMP not enabled on orders (mmp_enabled=false)
  • Currency mismatch (MMP config currency != order underlying)

Solutions:

  • Decrease limits
  • Verify mmp_enabled=true on orders
  • Verify MMP config currency matches order underlying

Unexpected Cancels​

Symptom: Orders canceled with reason "Order canceled by MMP trigger" but you didn't expect it.

Cause: Another order for the same wallet+underlying triggered MMP.

Solution: Review all MMP-enabled orders for the same underlying when one triggers.