BSL Cancel-Replace Pattern
Cancel-replace lets a liquidity provider or HFT client amend price, quantity, or order flags without manually issuing separate cancel and place calls.
Why it matters
Separate cancel and place operations can expose a strategy to stale quote risk or temporary loss of queue position. Cancel-replace gives the client one operation with one acknowledgement path.
Preferred path
For new integrations, use a signed QuoteReplace or SpotQuoteReplace action
inside an order-entry batch:
{
"version": 1,
"actions": [
{
"payload": {
"account": "0x1111111111111111111111111111111111111111",
"nonce": 42,
"ts": 1781190000000,
"action": {
"SpotQuoteReplace": {
"market": 1,
"legs": [
{
"cancel_order_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"side": "Bid",
"price": 999900,
"qty": 100000,
"time_in_force": "post_only"
}
]
}
}
},
"signature": {
"scheme": "EcdsaSecp256k1",
"bytes": [1, 2, 3]
}
}
],
"idempotencyKey": "replace-bid-1"
}
In current private beta, submit it through POST /api/order-entry/binary or
the direct compatibility route POST /api/v1/mm/orders/batch.bin with
X-BSL-Result-Mode: ack and X-Senticore-Response-Mode: detailed. The
canonical POST /api/v1/bsl/orders/compact route uses the same signed compact
payload and requires institutional_agent HMAC auth or a provisioned lane key.
For QuoteReplace, a leg with qty: 0 is cancel-only. A leg with nonzero
qty places a deterministic child order id derived from the signed parent
payload and leg index.
QuoteReplace guarantees
| Contract | Guarantee |
|---|---|
| Parent action | One signed parent action controls all legs and consumes one action nonce. |
| Scope | Explicit legs only. It does not implicitly replace an entire market, book, side, or all quote-managed orders. |
| Leg order | For a leg with cancel_order_id, cancel is evaluated before that leg's replacement placement. |
| Child identity | Nonzero-quantity replacement legs derive deterministic child order ids from the signed parent payload and leg index. |
| Admission | X-BSL-Result-Mode: ack confirms the configured admission boundary only. |
| Terminal truth | Final open, filled, canceled, or rejected state comes from receipts, private stream, FIX/BSL drop-copy, or account order reads. |
Do not treat HTTP 200 as proof that every replacement order is resting. A
risk reject, market halt, post-only cross, stale cancel target, or terminal
engine reject can still appear after admission.
If local order-id tracking is incomplete, do not send a wide QuoteReplace
range and assume older quotes are removed. First reconcile open orders from
private state or run the account-safe cancel-all flow scoped by market/book/side,
then seed a new quote family and persist every returned derivedOrderIds[].
The BSL response includes quoteFamilyId on echoed quote-replace legs and
aggregated quoteFamilyScopes[] in bsl.reconciliation.quoteReplaceSummary.
Those IDs are deterministic for account + market + book + side and are meant
for reconciliation and scoped cancel-all recovery; they are not persisted
engine-side order tags.
Scoped cancel-all recovery
When a quote engine loses its local order-id map, use the BSL recovery endpoint before publishing a fresh quote family:
POST /api/v1/bsl/orders/cancel-all
Content-Type: application/json
SC-Auth-Version: 2
SC-Key: <institutional_agent key>
SC-Nonce: <monotonic auth nonce>
SC-Signature: <hmac signature>
{
"account": "0x2222222222222222222222222222222222222222",
"market": 7,
"book": "YES",
"side": "Bid"
}
account is the 20-byte engine account. With account-bound
institutional_agent auth it may be omitted; otherwise send it explicitly.
market, book, and side are optional filters. Omitting all three is a
whole-account panic cancel and should be reserved for emergency recovery.
The response is an acknowledgement, not terminal truth:
{
"ok": true,
"accepted": true,
"cancelledOrders": 1,
"operation": "cancel_all_by_market_book_side",
"ackIsTerminalState": false,
"reconciliation": {
"terminalTruth": "private_stream_dropcopy_receipts_or_account_reads",
"nextAction": "wait_for_cancel_reports_then_seed_fresh_quote_family"
}
}
After the ACK, wait for private stream, FIX/BSL drop-copy, receipts, or account order reads to confirm terminal cancel state before assuming the book is clean.
Legacy facade
The legacy-compatible HTTP facade remains available:
POST /api/v1/mm/orders/replace
POST /api/v1/mm/orders/cancel-replace
It accepts a cancelOrderId, replacement order, optional signatures, mode,
time-in-force, STP, and advanced flags. It exists for clients already wired to
the earlier compatibility facade. New quote engines should use signed BSL
action batches.
Replaceable fields
| Field | Replaceable | Notes |
|---|---|---|
| Price | Yes | Must satisfy tick size |
| Quantity | Yes | Must satisfy lot size and collateral checks |
| Time-in-force | Limited | Some transitions require cancel and new order |
| Side | No | Use cancel and new order |
| Market | No | Use cancel and new order |
| Account | No | Use cancel and new order |
Failure behavior
If the replacement is rejected, the original order remains active unless the terminal response or drop-copy event explicitly says the original order has been cancelled. Clients must reconcile final order state before updating local strategy state.
FIX and binary equivalents
- FIX uses OrderCancelReplaceRequest (
35=G). - BSL compact order entry uses
QuoteReplaceorSpotQuoteReplace. - Legacy HTTP uses the compatibility cancel-replace endpoint in the API reference.