Skip to main content

RFQ Swap API

Status: Specification (LP Protocol v1, draft 2026-09-23). Endpoints Rocky exposes. The first half is for liquidity providers, the second for wallet providers integrating the trader side.

Base URL: https://api.rocky.exchange. Conventions, error envelope and rate limits follow Basic Information.

Endpoints for liquidity providers​

Signed with your Spot API key exactly like USER_DATA endpoints (Authentication): X-MBX-APIKEY, timestamp, signature. The key must belong to the account Rocky registered as your LP (-2110 otherwise); LP keys are issued on the ext-mm rate tier.

GET /v1/lp/signing-keys​

Public. Rocky's Ed25519 keys used to sign calls to your service.

{
"keys": [
{ "keyId": "rocky-lp-2026-09", "algorithm": "ED25519", "publicKey": "base64-32-bytes", "fingerprint": "sha256-first-16-hex", "validFrom": "2026-09-23T00:00:00Z", "validUntil": null }
]
}

Rotation: a new key is published here at least 7 days before it starts signing; the old key keeps validUntil set for 7 days after. Accept any key in this list that is valid at the request timestamp.

GET /v1/lp/me​

Your LP registration as Rocky sees it.

{
"lpId": "acme",
"party": "lp-acme::1220…",
"serviceUrl": "https://rfq.acme.example/rocky",
"status": "ACTIVE",
"symbols": [
{ "symbol": "CBTC-USDCB", "minBaseQty": "0.0001", "maxBaseQty": "0.5", "enabled": true }
],
"quoteTimeoutMs": 800,
"acceptTimeoutMs": 5000,
"makerFeeBps": "0",
"health": { "lastOkAt": "2026-09-23T08:00:00Z", "consecutiveFailures": 0 },
"fanOutShare": "1.00"
}

status is one of TESTING (go-live runs: only Rocky's test traders reach you), ACTIVE, PAUSED (by you), DEGRADED (health failures), SUSPENDED (by Rocky operations). Public RFQs reach ACTIVE LPs only.

POST /v1/lp/pause and POST /v1/lp/resume​

Your kill switch. pause removes you from fan-out immediately and is acknowledged in under one second; swaps already in LP_ALLOCATED or SETTLING still settle. resume returns you to fan-out once GET /health on your service succeeds.

GET /v1/lp/trades​

ParameterTypeRequiredNotes
symbolstringno
statusstringnoany status from the status machine
startTime, endTimemsnoby createdAt; window ≤ 7 days
fromIdstringnoswapId to page from
limitintnodefault 500, max 1000

Response is an array of the trade object below, oldest first.

GET /v1/lp/trades/{swapId}​

{
"swapId": "01a0d0f2-6c3a-7b1e-8f2a-3b4c5d6e7f80",
"symbol": "CBTC-USDCB",
"side": "SELL",
"price": "86206.60",
"baseQty": "0.0125",
"quoteQty": "1077.5825",
"quoteId": "acme-q-000123",
"status": "SUCCEEDED",
"reason": null,
"traderParty": "rockywallet-user::1220…",
"legs": {
"traderSends": { "instrument": "CBTC", "amount": "0.0125" },
"traderFee": { "instrument": "CBTC", "amount": "0.00001206" },
"lpSends": { "instrument": "USDC.B", "amount": "1077.5825" },
"lpFee": null
},
"allocations": {
"trader": [ { "contractId": "00a1…", "state": "EXECUTED" } ],
"lp": [ { "contractId": "00b2…", "state": "EXECUTED" } ]
},
"settlement": { "executor": "Rocky::1220…", "allocateBefore": "…", "settleBefore": "…" },
"cantonUpdateId": "1220…",
"createdAt": "2026-09-23T08:00:05.000Z",
"updatedAt": "2026-09-23T08:00:07.410Z"
}

Allocation state is one of PENDING, LOCKED, EXECUTED, CANCELLED, WITHDRAWN, EXPIRED. side is the trader's side, as in the quote request.

LP error codes​

CodeMessageWhen
-2110LP endpoint requires an LP-tier keyKey not on an LP-registered account
-2111LP is suspendedContact Rocky operations
-2112Unknown swapswapId not yours or not found

Endpoints for wallet providers (trader side)​

Authenticated with the exchange wallet-session bearer token, the same session used for /v1/spot-chain/…. These endpoints are what the Rocky web app's Swap page uses; a wallet can call them directly to offer RFQ swaps in its own UI.

GET /v1/swaps/rfq/quote/{symbol}/{side}?baseQty=​

Fans out to active LPs and returns the best all-in quote, or -2100 when nobody quoted.

{
"rfqId": "7d2f5a4e-1b0e-4f2a-9a5b-1c2d3e4f5a6b",
"symbol": "CBTC-USDCB",
"side": "SELL",
"baseQty": "0.0125",
"price": "86206.60",
"grossQuoteQty": "1077.5825",
"fees": {
"takerFee": { "instrument": "CBTC", "amount": "0.00000625", "bps": "5" },
"networkFee": { "instrument": "CBTC", "amount": "0.00000581" }
},
"traderSends": { "instrument": "CBTC", "amount": "0.01251206" },
"traderReceives": { "instrument": "USDC.B", "amount": "1077.5825" },
"validUntil": "2026-09-23T08:00:04.000Z",
"lpCount": 3,
"source": "RFQ"
}

Every Rocky fee is charged in the input asset: for SELL in the base asset on top of baseQty (the trader sends baseQty + takerFee + networkFee), for BUY in the quote asset on top of grossQuoteQty. networkFee is the Swap page's fixed execution cost converted into the input asset at the quoted price. The trader always receives the LP's full output. traderSends and traderReceives are the numbers to show the user.

POST /v1/swaps/rfq​

Accept a quote. Body: { "rfqId": "…", "party": "<trader party>" }. The session's party must equal party.

{
"swapId": "01a0d0f2-6c3a-7b1e-8f2a-3b4c5d6e7f80",
"status": "QUOTED",
"allocations": [
{ "purpose": "INPUT", "instrument": { "admin": "…", "id": "CBTC" }, "amount": "0.0125", "receiver": "lp-acme::1220…" },
{ "purpose": "TAKER_FEE", "instrument": { "admin": "…", "id": "CBTC" }, "amount": "0.00001206", "receiver": "Rocky-fees::1220…" }
],
"settlement": {
"executor": "Rocky::1220…",
"settlementRef": { "id": "01a0d0f2-6c3a-7b1e-8f2a-3b4c5d6e7f80", "cid": null },
"requestedAt": "2026-09-23T08:00:05.000Z",
"allocateBefore": "2026-09-23T08:01:05.000Z",
"settleBefore": "2026-09-23T08:02:05.000Z",
"meta": { "values": { "rocky.swapId": "01a0d0f2-…", "rocky.symbol": "CBTC-USDCB" } }
}
}

The wallet must create exactly these allocations. There are two ways.

POST /v1/swaps/rfq/{swapId}/prepare and …/execute (Rocky-prepared)​

For wallets that sign externally (Rocky Wallet, Loop, Console, Send), Rocky prepares the AllocationFactory_Allocate command(s) through Canton interactive submission, the wallet signs the hash, Rocky executes:

Canton prepares one command per interactive submission, so a swap with an input leg and a fee leg takes two rounds. Each round:

  • POST /v1/swaps/rfq/{swapId}/prepare → { "commandId", "purpose", "remaining", "preparedTransaction", "preparedTransactionHash", "hashingSchemeVersion", "commands", "actAs", "readAs", "disclosedContracts", "synchronizerId" } — the same shape as the MemberAuth prepare step plus purpose (INPUT or TAKER_FEE, input first) and remaining (allocations still to submit, this one included). commands is included so a wallet that prefers to submit itself can do so.
  • POST /v1/swaps/rfq/{swapId}/execute with { "signature": "<base64 or hex ed25519 over preparedTransactionHash>" } → the trade object with status at least TRADER_ALLOCATING and remaining; call prepare again while remaining > 0. Both allocations must be on ledger before allocateBefore.

POST /v1/swaps/rfq/{swapId}/attach (wallet-submitted)​

For wallets that build and submit the allocation themselves: { "updateId": "1220…", "allocationContractIds": ["00a1…", "00a3…"] }. Rocky reads the allocations and verifies them against the swap. A mismatch returns -2102 and the swap becomes CANCELLED; the wallet should then withdraw its allocations.

Either path leads to the same place: once the trader allocations are verified, Rocky calls the LP and settles without further wallet involvement.

GET /v1/swaps/rfq/{swapId}​

The trade object (as in GET /v1/lp/trades/{swapId}, without the LP's private fields). Poll every 1–2 s until terminal; typical time from execute to SUCCEEDED is under 10 s.

POST /v1/swaps/rfq/{swapId}/withdraw/prepare and …/execute​

When a swap ends in EXPIRED, LP_ACCEPT_FAILED, SETTLEMENT_FAILED or CANCELLED and the trade object shows a trader allocation still LOCKED, Rocky first tries Allocation_Cancel as executor. If an allocation is still LOCKED after settleBefore, these two calls prepare and execute Allocation_Withdraw for the wallet, one allocation per round (purpose, remaining as above). A wallet that submits itself can exercise Allocation_Withdraw directly.

GET /v1/swaps/rfq?status=&limit=​

The session party's RFQ swaps, newest first.

Trader-side error codes​

CodeMessageWhen
-2100No liquidity for this requestNo LP answered with a valid quote
-2101Quote expiredrfqId past validUntil at POST /v1/swaps/rfq
-2102Allocation does not match the swapWrong instrument, amount, receiver, executor or settlementRef
-2103Liquidity provider rejected the tradeTerminal LP_ACCEPT_FAILED; ask for a new quote
-2104Settlement failedTerminal SETTLEMENT_FAILED; input is withdrawable
-2105Size out of rangebaseQty below minNotional or above every LP's maxBaseQty
-2106Party mismatchBody party differs from the session party

Rate limits​

LP endpoints count against your key's tier like other signed endpoints (weight 1; GET /v1/lp/trades weight 5). Trader-side quote requests are limited per session to 1 per second and 30 per minute; the fan-out cost is Rocky's.