V2 runs concurrently with the V1 webhook model; it does not replace it. You can integrate with either or both. The two models sit in different parts of Jupiter’s stack: V1 works directly in the JupiterZ program and APIs, while V2 liquidity is consumed by Metis and fills execute through the Jupiter v6 swap aggregator program.
How it differs from V1
The two streams:
- StreamQuotes: you send
MarketMakerQuoteorderbook snapshots (up to 5 bid and 5 ask levels per pair); the server replies withQuoteUpdateacks (UPDATE_TYPE_UPDATEDaccepted,UPDATE_TYPE_REJECTEDwith astatus_message). - StreamSwap: the server pushes
SwapUpdatemessages (CONNECTION_READY,SWAP_AVAILABLEwith the transaction to co-sign,TRANSACTION_CONFIRMED,ERROR); you reply withMarketMakerSwapmessages (SWAP_SUBMITwith the signed transaction, plusPING/PONGkeep-alives).
Integration requirements
There is no self-serve onboarding yet. Jupiter provides your credentials:- An API key (sent as
x-api-keygRPC metadata) and a maker ID. - The gRPC endpoint to connect to. The SDK examples default to the edge (pre-production) endpoint.
- You provide a Solana keypair: its public key is your maker address and must hold inventory with the token accounts (ATAs) for every token you quote.
production_streaming), the proto definitions, the fill-decoder for last-look validation, and the unit and end-to-end test suites.
To begin onboarding, submit a request through the support form. For pre-integration questions, ask in the developer support channel on Discord.
Protocol rules
These rules are enforced server-side; break them and your quotes are rejected or your maker is benched.- Open the swap stream before you quote. Quotes from a maker without a live swap stream are rejected. On reconnection, re-open the swap stream first.
- Sequence numbers are strictly increasing per maker, across every quote you send. Fetch the correct starting value with the
GetLastSequenceNumberRPC (the SDKs do this for you when opening the quote stream with sync). quote_expiry_timeis a duration in seconds, minimum 10. It is not a microsecond timestamp, despite other fields using microseconds. With the SDK, useexpiry_time_secs(n); a microsecond value produces an absurdly long expiry because the server reads the field as seconds.- Drain the acks. Read the
QuoteUpdatereplies between sends. An unread ack backlog creates back-pressure and drops the stream. - Snapshots replace. Each
MarketMakerQuotefor a pair replaces your previous orderbook for that pair. The server keeps the top 5 levels per side. - Keep-alive: send periodic
PINGmessages on the swap stream and treatPONGreplies as liveness. - Reconnection is your job. The streams do not auto-reconnect. Implement backoff, re-open the swap stream, then re-open the quote stream with a sequence re-sync.
Quote validation
A quote snapshot is accepted only if: your maker status isprod, your swap stream is live, lot_size_base matches the expected value for the pair, the expiry meets the minimum, at least one level has price and volume greater than zero, the sequence number is in order, and your maker wallet actually holds the inventory (ATAs must exist; levels are dropped above the first level your balance cannot cover). Rejections arrive as UPDATE_TYPE_REJECTED with the reason in status_message.
Price and volume encoding
volume: base-token atoms,human_amount × 10^base_decimals.price: quote-token atoms per one whole base token,human_price × 10^quote_decimals.lot_size_base = 10^(base_decimals − quote_decimals).- Timestamps are Unix time in microseconds (except
quote_expiry_time, see above).
volume = 1000000000, price = 153450000, lot_size_base = 1000.
Last look and maker safety
ASWAP_AVAILABLE update carries a swap_uuid and an unsigned_tx: a base64-encoded versioned transaction already signed by the taker at signature index 0. Your job, within the deadline:
- Decode the transaction and validate that the fill matches a quote you streamed (the SDK’s fill-decoder extracts the fill parameters; Python integrations decode with
soldersor use the Rust fill-decoder as a sidecar). - Check fill exclusivity: your authority and token accounts must appear only in the fill instruction. The fill-decoder’s
check_fill_exclusivityis the off-chain mirror of the on-chain check. - Sign the message and place your signature at index 1.
- Return the transaction as a
SWAP_SUBMITmessage.
SWAP_SUBMIT is 10 seconds by default and is always enforced. Missing it expires the swap; there is no fallback to the next quote in V2.
On-chain, fills settle through the rfq_v2 program (fd3nMFYTQjX1yr5ER8u7tPdHJB7qt8RpDpNtLQX2Br5) with the single instruction fill_exact_in, invoked by the Jupiter v6 swap aggregator program as part of a Metis route. The program reads the instructions sysvar and rejects the fill with MakerAppearsInOtherInstruction (error 6005) if your keys appear in any other instruction, defeating wrap-and-drain attacks even if your off-chain check misses one.
Maker lifecycle
The fill-rate circuit breaker benches makers who repeatedly win quotes but do not complete the fill. It is disabled by default and server-configured; when enabled, it evaluates your fill rate over a rolling window (default 1 hour, minimum 10 swaps, 50% threshold) and suspends for a fixed window (default 10 minutes). The 10-second last-look deadline applies regardless of whether the breaker is enabled.
Staying healthy: keep the swap stream connected, respond within the deadline, quote prices you will honour, hold the inventory you quote, and never modify the transaction.
gRPC API surface
Five RPCs, defined in market_maker.proto:Testing
The SDK repository ships unit tests (offline) and end-to-end tests that exercise the full quote-sign-execute loop against the pre-production environment. The end-to-end tests move real funds on mainnet, so use a taker wallet holding only what you are willing to spend. Commands, environment variables, and the maker-safety test suite are documented in the rfq-v2-sdk README. Before requesting production access, confirm: unit and end-to-end tests pass for the pairs you quote, your quote stream stays stable and refreshes before expiry, your swap stream stays connected and answers pings, last look validates fills and checks exclusivity, you co-sign at index 1 without modifying the message, you respond within the deadline, and reconnection with backoff and sequence re-sync works.Related
- Webhook Integration (V1): the request-response integration model
- JupiterZ API: consume JupiterZ quotes as an integrator
- rfq-v2-sdk: SDKs, proto, examples, and tests
