Skip to main content

RFQ for market makers

BlankFX routes cross-currency trades to market makers as firm quotes. You price the trade and sign it; the taker locks their input in the BlankFX RFQ escrow; you fill from your own wallet. BlankFX never holds your funds or your keys.
Integration model. BlankFX calls your HTTPS server. You expose five endpoints; there is no websocket and nothing for you to push. Settlement happens on-chain against the escrow contract.
Ethereum (mainnet) is not yet available.

Eligibility

To quote, your provider address (the key that signs quotes and fills them) must be:
  • allow-listed on the escrow (providerAllowed(provider) == true), and
  • assigned the MM access tier on BlankFX and not suspended.
Both are set by BlankFX during onboarding, together with your endpoint URL and credential. There is no self-serve sign-up. Start with Broker and market maker onboarding, then email trading@blankfx.org. The provider address must be an externally owned account (quotes are verified with ECDSA; smart-contract signatures are not supported).

Pairs

RFQ is for cross-currency pairs only: the two tokens must belong to different currencies (for example USDC → EURC, EURC → XSGD). Same-currency pairs such as USDC → USDT never reach you; they trade on the BlankFX pools.

Endpoints you expose

All requests are JSON (Content-Type: application/json). Respond with JSON. On failure, return a non-2xx status with { "error": "<message>", "code": "<CODE>" }.

GET /health

status is one of healthy, degraded, offline. This request carries the same authentication headers as the others.

POST /quote

Request:
  • fromAmount is a decimal string in whole token units.
  • beneficiary is the taker’s wallet. Use it as both taker and beneficiary in the signed quote.
  • chainId may be included; when it is absent, use the settlement chain (11155111 on Sepolia).
Response:
When the taker accepts, BlankFX refuses to proceed if taker is not the taker’s wallet, beneficiary is not the requested beneficiary, or any of the ten fields is missing.

POST /quote/{quoteId}/prehedge

Sent when the taker accepts your quote, before they fund the escrow. Use it to hedge. Headers include idempotency-key. Body:
quote.id equals the path quoteId. Respond with:
Unless agreed otherwise at onboarding, the taker can fund only after your prehedge response reports one of ACCEPTED, CONFIRMED, PREHEDGED, HEDGED, FILLED, EXECUTED, COMPLETED, SUCCESS, SUCCEEDED. An error or any other status stops the taker at this step.
A taker who has accepted may still never fund the escrow. Size your prehedge with that in mind; an unfunded quote simply expires at deadline.

POST /quote/{quoteId}/accept

Sent once BlankFX has verified the taker’s QuoteEscrowed event on-chain. This is your signal to fill. Headers include idempotency-key (stable for the order, so a retry must not fill twice). Body:
Respond with:
execution.id follows the same character rules as quote.id. status can be final already, or in progress (then BlankFX polls the status endpoint).

GET /execution/{executionId}/status

Status values

BlankFX maps your status (case-insensitive) onto the taker’s order: Report a final status as soon as you have one; an order stays settling until you do.

Request authentication

Every request BlankFX sends you carries: The HMAC key is your credential (the same value as the bearer token, unless a separate signing secret is agreed at onboarding). The signed string is five fields joined by \n:
  • METHOD is upper case (GET, POST).
  • path is the endpoint path only, for example /quote or /quote/q_8fK2mPq1/accept. It excludes your host and any base path in your registered URL.
  • raw body is the exact JSON body sent, or empty for GET.
Check the bearer token, verify the HMAC, reject timestamps more than 5 minutes old, and reject repeated nonces.

Signing a firm quote

Quotes are EIP-712 typed data:
In the /quote response, send amountIn, minOut, nonce and deadline as decimal strings. You can check a hash on-chain with hashQuote(quote) on the escrow.

Quote timing

A few minutes (for example 180 seconds) is enough for most takers, including wallets that need several approvals.

Settlement

1

Taker funds the escrow

The taker approves the escrow and calls escrowQuote(quote, signature). The escrow checks your signature, your allow-listing and tier, the deadline, and that the quote was never used, then locks amountIn and emits QuoteEscrowed.
2

BlankFX confirms and calls you

BlankFX verifies a QuoteEscrowed event matching every field of your quote, then calls POST /quote/{quoteId}/accept.
3

You fill

From your provider address, call fillEscrowedQuote(quote, signature) on the escrow before deadline. You must have approved the escrow to spend tokenOut. In one transaction the escrow sends exactly minOut of tokenOut from you to the beneficiary and releases the escrowed tokenIn to you, then emits QuoteFilled.
4

If you don't fill

After deadline, the taker calls refundExpiredQuote(quoteHash) and gets their tokenIn back. You can no longer fill.
A quote hash settles once. Pausing the escrow stops new escrows, but never blocks a fill of an already-escrowed quote or a refund.

Cancelling quotes

  • invalidateQuote(quoteHash) cancels one quote.
  • invalidateUnorderedNonces(wordPos, mask) cancels a batch by nonce, Permit2-style (wordPos = nonce >> 8, bit = nonce & 0xff).
Both only stop new escrows. Once a taker has escrowed against a quote, you are committed to it until deadline.

Fees

The escrow charges no fee: the taker pays exactly amountIn, and the beneficiary receives exactly minOut. Your margin is in your price. Any maker fees are set in your term sheet.

Reference implementation

A reference market-maker server that implements these endpoints, request verification and quote signing is available as a template: benjiusi/blankfx-rfq-mm-template. Its pricing and hedging are illustrative only; replace them with your own before quoting live. Questions: trading@blankfx.org.