Diffusal

Market Maker Integration Guide

End-to-end walkthrough for Main Market Makers — SIWE auth, REST quoting, WebSocket auction participation, and position monitoring

This guide walks through the complete integration path for a Main Market Maker (MMM) on Diffusal. You will authenticate via SIWE, verify your MMM authorization, submit quotes through both the REST and WebSocket paths, and monitor your open positions in real time.

A Main Market Maker is an address authorized by the protocol admin to sign RFQ quotes that takers fill on-chain. The RFQ (Request for Quote) model works as follows: a taker broadcasts a request for a quote on a specific option series, active MMMs respond with signed quotes, and the taker fills the best quote by submitting it to the DiffusalOptionsRFQ contract on-chain. This guide covers both the programmatic off-chain quote delivery paths and the on-chain mechanics you need to be aware of.

Prerequisites

  • curl and websocat installed
  • A wallet with MON for gas on Monad Testnet (chain ID 10143)
  • MMM authorization — the protocol admin must call DiffusalOptionsPositionManager.setMmm(your_address, true) before you can submit quotes
  • Collateral deposited to portfolio 0 — see the On-Chain Guide for deposit steps using DiffusalCollateralVault

Your First Quote — Linear Walkthrough

Step 1: Authenticate (SIWE)

Diffusal uses Better Auth SIWE for programmatic authentication. The three-step flow is: call POST /api/auth/siwe/nonce to request a nonce, sign a SIWE message, then call POST /api/auth/siwe/verify to verify and create a session.

a) Request a nonce

curl -s -X POST https://api.testnet.diffusal.xyz/api/auth/siwe/nonce \
  -H 'Content-Type: application/json' \
  -d '{"walletAddress":"0xYOUR_ADDRESS","chainId":10143}'

Expected output:

{ "nonce": "a3f2c91d8b4e5072" }

b) Construct the SIWE message

Build an EIP-4361 message using the nonce from the previous step. Replace YOUR_ADDRESS and NONCE with actual values:

api.testnet.diffusal.xyz wants you to sign in with your Ethereum account:
YOUR_ADDRESS

Sign in to Diffusal

URI: https://api.testnet.diffusal.xyz
Version: 1
Chain ID: 10143
Nonce: NONCE
Issued At: 2026-01-01T00:00:00.000Z

Sign this message with your wallet private key and encode both the message and signature as JSON strings.

c) Verify the SIWE signature

curl -s -X POST https://api.testnet.diffusal.xyz/api/auth/siwe/verify \
  -H 'Content-Type: application/json' \
  -d '{
    "message": "api.testnet.diffusal.xyz wants you to sign in with your Ethereum account:\nYOUR_ADDRESS\n\nSign in to Diffusal\n\nURI: https://api.testnet.diffusal.xyz\nVersion: 1\nChain ID: 10143\nNonce: NONCE\nIssued At: 2026-01-01T00:00:00.000Z",
    "signature": "0xYOUR_SIGNATURE",
    "walletAddress": "0xYOUR_ADDRESS",
    "chainId": 10143
  }'

Expected output:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "success": true,
  "user": {
    "id": "user_abc123",
    "walletAddress": "0xYOUR_ADDRESS",
    "chainId": 10143
  }
}

d) Save the bearer token

export TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

For the full authentication reference including nonce TTL and token format, see Authentication.


Step 2: Check Your MMM Status

Before submitting quotes, verify your address is authorized as an MMM. Use cast to call isMmm on the DiffusalOptionsPositionManager contract:

cast call 0xd5feba3e400a0197d0870495f9bba5873307208f \
  "isMmm(address)(bool)" \
  YOUR_ADDRESS \
  --rpc-url https://testnet-rpc.monad.xyz

Expected output when authorized:

true

If the result is false, contact the protocol admin to request MMM authorization via setMmm(your_address, true).


Step 3: Submit Your First Quote (REST Path)

The REST path is useful for one-off or low-frequency quote submission. Use POST /api/mm/quotes/submit with your bearer token.

You need a pairId for the option series. Use the ETH-USDT pair (0x7020b52841bb268cbc78137a54d4bf1f5305eed1039fb5d003ba95b8ededc46c) from the current testnet deployment.

First, fetch your current on-chain nonce:

cast call 0x6f18dc553a5fb66d0081a84d804a608998b784c7 \
  "mmmNonce(address)(uint256)" \
  YOUR_ADDRESS \
  --rpc-url https://testnet-rpc.monad.xyz

Then submit the quote:

QUOTE_EXPIRY=$(($(date +%s) + 25))  # 25 seconds from now

curl -s -X POST https://api.testnet.diffusal.xyz/api/mm/quotes/submit \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $TOKEN" \
  -d "{
    \"mmm\": \"YOUR_ADDRESS\",
    \"taker\": \"0x0000000000000000000000000000000000000000\",
    \"seriesId\": \"0x0000000000000000000000000000000000000000000000000000000000000000\",
    \"takerIsBuying\": true,
    \"price\": \"50000000000000000\",
    \"size\": \"10000000000000000000\",
    \"nonce\": \"0\",
    \"quoteExpiry\": \"$QUOTE_EXPIRY\",
    \"pairId\": \"0x7020b52841bb268cbc78137a54d4bf1f5305eed1039fb5d003ba95b8ededc46c\",
    \"strike\": \"2000000000000000000000\",
    \"optionExpiry\": \"1780000000\",
    \"isCall\": true,
    \"signature\": \"0xYOUR_EIP712_SIGNATURE\"
  }"

Expected output:

{
  "quoteId": "qte_01j...",
  "status": "active",
  "expiresAt": "2026-01-01T00:00:25.000Z"
}

RFQ quotes expire after 30 seconds. Set quoteExpiry to at most 30 seconds from now. Takers must fill within this window or the quote is rejected. Build your signing loop with this tight TTL in mind.

The seriesId and signature fields require off-chain computation. Use the EIP-712 Quote Structure section below to construct the typed data and sign it with your MMM key.


Step 4: Real-Time Auction Participation (WebSocket Path)

For continuous market making, connect to the RFQ WebSocket stream. Takers broadcast requests; you respond with signed quotes in real time.

a) Connect and authenticate

websocat "wss://api.testnet.diffusal.xyz/ws/rfq/mm" \
  -H "Authorization: Bearer $TOKEN"

Send an AUTH message as the first frame after connecting:

{
  "type": "AUTH",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Expected auth acknowledgement:

{ "type": "AUTH_SUCCESS", "userId": "user_abc123" }

b) Receive an RFQ request

When a taker requests a quote, the server broadcasts an rfq_request event to all connected MMMs:

{
  "type": "rfq_request",
  "requestId": "rfq_01j...",
  "pairId": "0x7020b52841bb268cbc78137a54d4bf1f5305eed1039fb5d003ba95b8ededc46c",
  "seriesId": "0x...",
  "strike": "2000000000000000000000",
  "optionExpiry": 1780000000,
  "isCall": true,
  "size": "5000000000000000000",
  "takerIsBuying": true,
  "expiresAt": "2026-01-01T00:00:30.000Z"
}

c) Respond with a quote

Sign the RfqQuote typed data (see EIP-712 Quote Structure) and send your response:

{
  "type": "rfq_response",
  "requestId": "rfq_01j...",
  "quote": {
    "mmm": "YOUR_ADDRESS",
    "taker": "0x0000000000000000000000000000000000000000",
    "seriesId": "0x...",
    "takerIsBuying": true,
    "price": "48000000000000000",
    "size": "5000000000000000000",
    "nonce": "0",
    "quoteExpiry": 1780000030,
    "pairId": "0x7020b52841bb268cbc78137a54d4bf1f5305eed1039fb5d003ba95b8ededc46c",
    "strike": "2000000000000000000000",
    "optionExpiry": 1780000000,
    "isCall": true
  },
  "signature": "0xYOUR_EIP712_SIGNATURE"
}

For scripted automation, pipe mode is convenient for one-shot examples. For the full auction loop, connect once and remain connected — the server will push rfq_request events as they arrive.


Step 5: Monitor Positions

Connect to the private WebSocket stream to receive real-time fill and position updates for your MMM address.

websocat "wss://api.testnet.diffusal.xyz/ws/private"

Send AUTH as the first frame:

{
  "type": "AUTH",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Subscribe to fill and position channels:

{
  "method": "SUBSCRIBE",
  "params": ["account@fills", "account@positions"]
}

Example fill event:

{
  "channel": "account@fills",
  "data": {
    "tradeId": "trd_01j...",
    "seriesId": "0x...",
    "size": "2000000000000000000",
    "price": "48000000000000000",
    "side": "maker",
    "timestamp": "2026-01-01T00:00:28.000Z"
  }
}

Example position update:

{
  "channel": "account@positions",
  "data": {
    "portfolioId": 0,
    "seriesId": "0x...",
    "size": "-2000000000000000000",
    "unrealizedPnl": "1200000000000000",
    "timestamp": "2026-01-01T00:00:28.000Z"
  }
}

Low-level private stream payloads may still include portfolioId, but MMMs should treat that as an internal routing detail. Operationally, MMMs manage cash only in portfolio 0; trading portfolios are auto-routed by series and are not client-managed through /api/mm/*.


Nonce Management

Each RFQ quote must include the MMM's current on-chain nonce. The nonce does not auto-increment on fill — multiple quotes can share the same nonce value until you explicitly advance it.

Read the current nonce:

cast call 0x6f18dc553a5fb66d0081a84d804a608998b784c7 \
  "mmmNonce(address)(uint256)" \
  YOUR_ADDRESS \
  --rpc-url https://testnet-rpc.monad.xyz

Increment the nonce (bulk-cancel all outstanding quotes):

cast send 0x6f18dc553a5fb66d0081a84d804a608998b784c7 \
  "incrementMmmNonce()" \
  --private-key YOUR_PRIVATE_KEY \
  --rpc-url https://testnet-rpc.monad.xyz

Calling incrementMmmNonce() is the only way to cancel outstanding quotes. There is no per-quote cancellation. After incrementing, any quote signed with the old nonce will revert with InvalidNonce when a taker tries to fill it.

When to use nonce increment:

  • Market conditions have changed significantly and your outstanding quotes are no longer valid
  • You want to reprice in bulk
  • You need an emergency stop — increment the nonce to halt all fills immediately

Collateral Model

MMMs operate with a single cash portfolio (portfolio 0). This is enforced at the protocol level:

  • Debits (premium payments, margin calls) try the trading portfolio first, then fall back to portfolio 0 if insufficient
  • Credits (premium receipts, settlement proceeds) are always routed to portfolio 0, regardless of which portfolio the trade occurred in
  • Order placement and RFQ fills auto-resolve the internal trading portfolio from the option series; MMM clients do not choose or fund those portfolios directly

Deposit and withdraw using the standard DiffusalCollateralVault functions with portfolioId = 0. See the On-Chain Guide for the complete deposit and withdrawal flow.

Keep portfolio 0 well-funded. If it runs dry, MMM order placement, RFQ execution, or settlement can fail even though series routing is automatic.


EIP-712 Quote Structure

For developers building custom signing flows or integrating with hardware signers.

Domain:

{
  "name": "DiffusalOptionsRFQ",
  "version": "1",
  "chainId": 10143,
  "verifyingContract": "0x6f18dc553a5fb66d0081a84d804a608998b784c7"
}

Types:

{
  "RfqQuote": [
    { "name": "mmm", "type": "address" },
    { "name": "taker", "type": "address" },
    { "name": "seriesId", "type": "bytes32" },
    { "name": "takerIsBuying", "type": "bool" },
    { "name": "price", "type": "uint256" },
    { "name": "size", "type": "uint256" },
    { "name": "nonce", "type": "uint256" },
    { "name": "quoteExpiry", "type": "uint256" },
    { "name": "pairId", "type": "bytes32" },
    { "name": "strike", "type": "uint256" },
    { "name": "optionExpiry", "type": "uint256" },
    { "name": "isCall", "type": "bool" }
  ]
}

Field reference:

FieldTypeDescription
mmmaddressMarket maker address (signer)
takeraddressAuthorized taker (address(0) = anyone)
seriesIdbytes32Option series identifier (from SeriesRegistry)
takerIsBuyingbooltrue = taker buys long, false = taker buys short
priceuint256Premium per contract in WAD (1e18 = 1.0 USDT)
sizeuint256Maximum contracts available (WAD)
nonceuint256Must match current on-chain MMM nonce
quoteExpiryuint256Unix timestamp — keep within 30 seconds of now
pairIdbytes32Trading pair hash from deployment.json
strikeuint256Strike price in WAD
optionExpiryuint256Option expiration Unix timestamp
isCallbooltrue = call option, false = put option

You can pre-validate your signature before broadcasting:

  • verifySignature(quote, signature) returns (bool) — checks signer matches quote.mmm
  • getQuoteStatus(quote, signature) returns (QuoteStatus) — returns fill status, remaining size, and validity

See Also

On this page