Spire ProtocolSPIRE PROTOCOLDOCS

Errors and edge cases

Codes are stable. Messages are not, so branch on the code. What to alert on and what to do about it is in Operations.

try {
  await clearing.obligations.fromFill({ fill });
} catch (e) {
  if (e.code === 'SPIRE-3001') {
    // limit breached, this is a business condition, not an outage
  }
  throw e;
}

1xxx validation

CodeMeaningWhat to do
SPIRE-1001Malformed fillFix the payload, do not retry as is
SPIRE-1002Signature does not recover to the venue keyCheck the signing key and the domain
SPIRE-1003matchedAt more than 60s from chain timeFix venue clock drift
SPIRE-1004fillId reused with different contentsIdempotency violation, investigate
SPIRE-1101Unknown obligationId
SPIRE-1204Asset not clearableStop routing this asset, alert
SPIRE-1205Member not onboarded

SPIRE-1004 deserves attention. Reusing a fillId with the same contents is safe and returns the original obligation. Reusing it with different contents means two different trades were given one identity somewhere upstream.

2xxx authentication

CodeMeaningWhat to do
SPIRE-2001Unknown venueId
SPIRE-2002Venue suspendedContact operations, do not retry
SPIRE-2003Signature replayedAlready consumed, treat as a duplicate

3xxx limits and collateral

CodeMeaningWhat to do
SPIRE-3001Position limit breachedReject the fill upstream or post collateral
SPIRE-3002Withdrawal exceeds freeReduce the amount
SPIRE-3003Concentration cap breachedSame asset is over 25% of the limit
SPIRE-3004Below minCollateralMember cannot clear until topped up
SPIRE-3005Member is in a margin callNew notional refused until cured

A limit breach is checked at novation, before anything is owed. That is deliberate: it is much cheaper to refuse a fill than to unwind a novated obligation.

4xxx windows

CodeMeaningWhat to do
SPIRE-4001Window is finalisingRetry, the fill lands in the next window
SPIRE-4002Explicit windowId is closedSend with "current"
SPIRE-4003Explicit windowId is too far aheadOnly the next window may be named

SPIRE-4001 is the one to handle properly. It is not a failure, it is a 30 second door closing. Retry once and the fill is accepted into the next window.

5xxx settlement

CodeMeaningWhat to do
SPIRE-5001Net not delivered before deadlineLate fee applies, cure window opens
SPIRE-5002Delivery does not match the published netDeliver the exact net
SPIRE-5003Settlement asset transfer failedCheck allowance on the settlement contract

Edge cases worth designing for

A fill arrives during finalisation

The 30 second finalisation phase accepts nothing. The SDK retries once and the obligation lands in the next window. The trade is not lost and the price does not change. What changes is which net it belongs to.

The window rolls while a member is in a margin call

The call does not reset. curesAt is an absolute timestamp and it survives the window boundary. A member that cures at second 550 of a 600 second cure period keeps every position it had.

One side of a fill defaults

The obligations are independent from the moment of novation. The non-defaulting side settles normally against Spire Protocol and does not learn that anything happened. The loss is absorbed by the default waterfall, not by the other participant.

A member defaults with a net of zero

It still defaults, because the default is about margin, not about the net. A member whose net is zero but whose margin has evaporated is removed from the next window and its collateral is released after the auction closes.

The same asset is cleared by two venues

Netting is per member per asset, not per venue. A member that buys on one venue and sells on another inside the same window nets across both. Neither venue can see the other side.

A tier reclassification during an open position

The new rate applies from tierEffectiveAt, which is always the start of a later window. Positions opened under the old rate keep it until the window they were novated into closes. Nobody is margin called by a change they could not have seen.

Last updated 31 August 2026 Docs source on GitHub