Skip to main content
Submit a single quote with:
{
  "type": "quote",
  "data": "base64-no-padding QuoteResponse bytes"
}
type
string
required
Must be "quote".
data
string
required
97 bytes of QuoteResponse, encoded as standard base64 without padding (130 characters).

QuoteResponse (97 bytes)

All integer fields are little-endian.
OffsetBFieldTypeMeaning
016request_iduuidRFQ UUID from the broadcast
164oddsuint32 LEdecimal odds in basis points (e.g. 25000 = 2.5×)
208max_fill_microsuint64 LEmaximum fill in USDC micros
284reservedzero padding
3265signaturebytes65EIP-191 personal-sign over bytes 0..32
Sign the first 32 bytes of the quote with EIP-191 personal-sign semantics. The signature is the 65-byte r || s || v value. Longshot accepts v as 0/1 or 27/28.

Liability Calculation

Quote liability is calculated from the quote payload:
payout_micros    = max_fill_micros * odds / 10000
liability_micros = payout_micros - max_fill_micros

Build Example

build_quote fills the 97-byte QuoteResponse layout above, signs the first 32 bytes with EIP-191 personal-sign, and returns the { type, data } envelope ready to send over the authenticated WebSocket from Authentication.
# pip install eth-account
import base64
from uuid import UUID

from eth_account import Account
from eth_account.messages import encode_defunct


def build_quote(
    *,
    request_id: UUID,
    odds: int,              # basis points (25000 = 2.5×)
    max_fill_micros: int,   # USDC micros
    private_key: str,
) -> dict:
    buf = bytearray(97)
    buf[0:16] = request_id.bytes
    buf[16:20] = odds.to_bytes(4, "little", signed=False)
    buf[20:28] = max_fill_micros.to_bytes(8, "little", signed=False)
    # bytes 28..31 are reserved (zero)

    signed = Account.sign_message(
        encode_defunct(primitive=bytes(buf[0:32])),
        private_key=private_key,
    )
    if len(signed.signature) != 65:
        raise ValueError("signature must be 65 bytes")
    buf[32:97] = signed.signature

    data = base64.b64encode(bytes(buf)).rstrip(b"=").decode("ascii")
    return {
        "type": "quote",
        "data": data,
    }

# Usage: after receiving an rfq and deciding on odds/fill,
# await ws.send(json.dumps(build_quote(
#     request_id=rfq.request_id,
#     odds=25000,
#     max_fill_micros=1_000_000,
#     private_key=os.environ["MM_PRIVATE_KEY"],
# )))

Error Codes

Quote submissions can fail in three different envelopes. Know which one you’re looking at before handling.

Rate-limit frame

When your tier’s quote token bucket runs dry, the server sends a dedicated rate_limit frame (not an error frame):
{ "type": "rate_limit", "retry_after_ms": 25 }
See Rate Limits for per-tier quotes-per-second and burst sizes.

quote_ack rejection reasons

When the server receives a quote but refuses to route it, you get a negative quote_ack with a human-readable error string. Every reason below is a rejection of this specific quote only — the session stays authenticated.
error stringMeaning
invalid base64 encodingOuter data field is not valid standard base64 (no-padding) or the encoded length is not exactly 130 characters. The JSON path length-checks the base64 string before decoding, so any wrong-length payload surfaces here — not as invalid quote size.
invalid_signatureThe signature does not recover to the wallet that authenticated this WebSocket.
duplicate_quoteThe authenticated maker already submitted this exact quote tuple.
rfq_expiredThe RFQ deadline passed before the quote could be routed.
zero_max_fill, max_fill_exceeds_rfq_amount, invalid_odds, or zero_maker_liabilityThe quote failed public validation before routing.
Quote maker liability outside valid rangeThe quote’s maker-liability calculation overflowed the supported range.
RFQ not found or no longer accepting quotesThe RFQ session closed, the request_id is unknown, or the MM is not eligible for this RFQ.

Session-level error frames

Anything that tears down the WebSocket (AUTH_EXPIRED, HEARTBEAT_TIMEOUT, NOT_AUTHENTICATED, MALFORMED_JSON, BINARY_NOT_SUPPORTED, etc.) arrives as an error frame on the socket, not in a quote_ack. The full table and recovery actions live on Receive RFQs → Error Codes.