RFQ Swap with Liquidity Providers
Status: Specification (LP Protocol v1, draft 2026-09-23). This section is the contract Rocky is building against. Endpoint paths, field names and status values are frozen for v1 unless a change is announced in the Changelog; anything marked v2 is not part of the first release.
Rocky supports two ways for an external firm to provide liquidity:
| Order-book market making | RFQ Swap (this section) | |
|---|---|---|
| How you provide liquidity | Rest limit orders on the Spot order book from your own party | Answer quote requests; each trade is a private, quote-driven swap |
| Who initiates a trade | Any taker hitting your order | A trader using the Swap page or a wallet's swap feature |
| Your integration surface | Spot REST/WS as a client (Market Maker Guide) | An HTTPS service you host that Rocky calls (LP Service API), plus Rocky endpoints you call (Rocky endpoints for LPs) |
| Last look | None: a resting order is a commitment | Yes: you accept or reject each trade at accept-and-allocate |
| Settlement | Two-leg DvP under your MemberAuth, netted per counterparty pair | Token Standard allocations locked by each side, executed by Rocky in one transaction under both sides' MemberAuth |
| Exchange fee | Maker/taker fee on the wallet-funded segment (see Fees) | Taker fee paid by the trader; maker fee 0 in v1 |
| What you sign on-chain | MemberAuth once | MemberAuth once at onboarding, then one AllocationFactory_Allocate per trade you accept |
The two models coexist. The same firm may run both from the same party.
Actors
| Actor | Canton party | Role in a swap |
|---|---|---|
| Trader (wallet provider) | The user's own party, keys in the wallet | Requests a quote, locks the input amount in an allocation, unlocks it if the swap does not settle. Never talks to the LP. |
| Rocky (venue and executor) | Operator party Rocky::… (executor); fee collector party for fee legs | Fans the RFQ out to LPs, picks the best quote, verifies both allocations against the frozen quote, executes the transfer legs atomically, records the trade. |
| Liquidity provider (LP) | Your party, hosted on a validator or wallet you operate | Prices requests, locks the output amount (plus maker fee, when non-zero) in an allocation when you accept a trade. |
Rocky never holds LP inventory or LP keys. The LP never holds trader funds before settlement: until Allocation_ExecuteTransfer runs, each side's allocation is still owned by that side and can be withdrawn after the settlement deadline.
Sequence
Trader wallet Rocky LP service Canton
│ GET quote │ │ │
│─────────────────────►│ POST /rfq/quotes (all LPs) │ │
│ │───────────────────────────────────►│ │
│ best quote, rfqId │◄───────────────────────────────────│ │
│◄─────────────────────│ │ │
│ POST /v1/swaps/rfq │ │ │
│─────────────────────►│ swapId, allocation to sign │ │
│ prepare → sign → execute (AllocationFactory_Allocate: input + taker fee) │
│──────────────────────────────────────────────────────────────────────────────────►│
│ │ POST /rfq/trades/{swapId}/accept-and-allocate │
│ │───────────────────────────────────►│ Allocate (output) │
│ │ │──────────────────────►│
│ │ ALLOCATED + allocation cids │ │
│ │◄───────────────────────────────────│ │
│ │ Allocation_ExecuteTransfer × all legs, one transaction │
│ │───────────────────────────────────────────────────────────►│
│ SUCCEEDED, updateId │ POST /rfq/trades/{swapId}/result │ │
│◄─────────────────────│───────────────────────────────────►│ │
- Quote. The trader asks Rocky for a quote (
symbol,side,baseQty). Rocky sends onePOST /rfq/quotesto every LP active on that symbol, waits at most the quote timeout, and returns the best executable price as anrfqIdvalid for a few seconds. The trader sees an all-in amount. - Create. The trader accepts the quote:
POST /v1/swaps/rfqfreezes the price, amounts, fees and settlement window on aswapIdand returns exactly what the wallet must allocate. - Trader allocates. The wallet exercises
AllocationFactory_Allocatefor the input amount (receiver: LP party) and the taker fee (receiver: Rocky fee collector), with Rocky's operator party asexecutorandsettlementRef.id = swapId. Rocky can prepare this transaction for external signing or the wallet can build it itself. - LP accepts and allocates. Rocky verifies the trader's allocation on ledger against the frozen row, then calls your
accept-and-allocate. You have last look: you either allocate the output amount to the trader (same executor, samesettlementRef) and answerALLOCATED, or answerREJECTED. - Settle. Rocky exercises
Auth_OpenAllocationson yourMemberAuth(peer: the trader's), which runsAllocation_ExecuteTransferon every leg inside one Canton transaction. Either all legs move or none. - Report. The trader's poll returns
SUCCEEDEDwith thecantonUpdateId; Rocky posts the result to your service and it is queryable atGET /v1/lp/trades/{swapId}.
What moves
Trader ──[baseQty or quoteQty, the input]──► LP
Trader ──[taker fee]────────────────────────► Rocky fee collector
LP ──[the output amount]────────────────► Trader
LP ──[maker fee, 0 in v1]───────────────► Rocky fee collector
Amounts are fixed when the swap is created and are never recomputed: the trader's execute and your accept-and-allocate are verified against the same frozen row, and Allocation_ExecuteTransfer moves exactly what each allocation holds. If either side allocates a different amount, instrument or receiver, Rocky does not settle.
Fees
| Leg | Payer | v1 value | Notes |
|---|---|---|---|
| Taker fee | Trader | Spot taker rate (5 bps) on the input, plus the fixed network execution cost of the Swap page (converted into the input asset at the quoted price; 0 on markets whose quote asset is not a dollar token) | Both are charged in the input asset and allocated together as one fee leg to the fee collector, so the LP always sends its full output to the trader |
| Maker fee | LP | 0 bps | The field exists in every message so a non-zero or negative (rebate) rate can be introduced without a protocol change |
| Canton traffic | Each participant for what it submits and confirms | — | You pay for your Allocate and for confirming the settlement; Rocky pays for the execution transaction. See Canton traffic. |
Timing
| Window | Default | Meaning |
|---|---|---|
| Quote timeout | 800 ms | Rocky waits this long for POST /rfq/quotes; late or non-200 answers are treated as no quote |
Quote validity (validUntil) | you choose, ≤ 5 s | Rocky will not create a swap on an expired quote |
| Accept timeout | 5 s | Rocky waits this long for accept-and-allocate |
allocateBefore | create + 60 s | Both allocations must exist on ledger by then; otherwise the swap is EXPIRED |
settleBefore | create + 120 s | After this an allocation can be withdrawn by its sender; Rocky's executor cancels what it can before that |
Status machine
QUOTED ──► TRADER_ALLOCATING ──► TRADER_ALLOCATED ──► LP_ACCEPTING ──► LP_ALLOCATED ──► SETTLING ──► SUCCEEDED
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
EXPIRED EXPIRED EXPIRED LP_ACCEPT_FAILED CANCELLED SETTLEMENT_FAILED
| Status | Terminal | Trader input | LP output | What happens next |
|---|---|---|---|---|
QUOTED | no | free | — | Wallet has not allocated yet |
TRADER_ALLOCATING | no | in flight | — | Rocky is waiting for the allocation to be visible on ledger |
TRADER_ALLOCATED | no | locked | — | Rocky is verifying the allocation |
LP_ACCEPTING | no | locked | — | Your service is being called |
LP_ALLOCATED | no | locked | locked | Rocky is submitting the settlement |
SETTLING | no | locked | locked | Settlement submitted, awaiting confirmation; retried on transient errors |
SUCCEEDED | yes | transferred | transferred | cantonUpdateId set |
EXPIRED | yes | free, or withdrawable | — | No allocation by allocateBefore |
LP_ACCEPT_FAILED | yes | withdrawable | — | You rejected, timed out, or your allocation did not match; Rocky cancels the trader allocation, the wallet can also Allocation_Withdraw |
SETTLEMENT_FAILED | yes | withdrawable | withdrawable | Execution failed terminally; Rocky cancels both allocations; either sender can withdraw after settleBefore |
CANCELLED | yes | withdrawable | withdrawable | Rocky abandoned before settling (operator action or verification mismatch) |
Retry policy on SETTLING follows the Spot settler: transient transport and contention errors are retried with backoff inside the settleBefore window; a Daml authorization failure or a missing allocation is terminal.
What is recorded
A SUCCEEDED swap is a venue trade: it is written to the trade journal in the same transaction as the settlement row, committed on-chain in the next epoch commitment, reported to the trader in GET /api/v3/myTrades with source: "RFQ" and its cantonUpdateId, and reported to you at GET /v1/lp/trades. It is not an order-book fill: it does not print on @trade streams, klines or the 24 h ticker.
Pages in this section
- LP Service API: the HTTPS service you implement and Rocky calls.
- RFQ Swap API: Rocky endpoints for LPs and for wallet providers.
- Settlement and Onboarding: on-ledger choices and signers, failure handling, verification, sandbox and go-live.