Skip to main content

CSA Model

The Credit Support Annex (CSA) governs variation margin between a pair of counterparties. IRSForge implements the signed Credit Support Balance (CSB) convention used by Bloomberg MARS, AcadiaSoft, and real ISDA CSAs.

One CSA per pair

There is exactly one Csa contract per (partyA, partyB) pair, signed jointly by operator, partyA, partyB, with regulators and scheduler as observers. It's created at init time from irsforge.yaml (see Demo vs Production). The operator is co-signatory because dispute adjudication requires non-trader authority — see Operator Role.

Signed Credit Support Balance

The CSA tracks one signed per-currency balance, csb, representing net collateral A has pledged toward B:

  • csb > 0 ⇒ A is the pledgor, B holds the collateral
  • csb < 0 ⇒ B is the pledgor, A holds the collateral
  • csb == 0 ⇒ no pledge outstanding

Why signed (not two-map)

An earlier iteration tracked postedByA and postedByB separately. That admitted a state — both sides simultaneously posted — that real CSAs never reach, and produced phantom margin calls on the wrong side when one side over-posted. The signed model is the same one Bloomberg MARS exposes; it makes "both sides posted" structurally impossible.

The TS shim at app/src/features/csa/decode.ts derives postedByA / postedByB from the signed CSB so existing UI components keep working without each one knowing the convention.

Parameters (from csa: block)

FieldMeaning
threshold.{DirA,DirB}Per-direction tolerance — exposure within threshold ⇒ no call
mtaMinimum Transfer Amount — calls below this are gated to zero
roundingCall increment — non-zero calls are snapped to the nearest multiple
valuationCcySingle reporting currency for the CSA
eligibleCollateral[]Whitelist of { currency, haircut } — Phase 5 ships haircut == 1.0

Margin call computation

exposure = NPV(swaps in netting set, valued in valuationCcy)
required.fromA = max(0, exposure - threshold.DirB)
required.fromB = max(0, -exposure - threshold.DirA)
targetCsb = required.fromA - required.fromB # signed
call = gateCall(targetCsb - currentCsb, mta, rounding)

gateCall is a no-op below mta and snaps to rounding otherwise.

Lifecycle states

StateMeaningRecoverable by
ActiveNormal operation
MarginCallOutstandingCall published, awaiting postPledgor posts (PostCollateral)
MarkDisputedOne side disputed the latest markOperator (AcknowledgeDispute)
TerminatedCSA closed (all swaps matured/unwound)

State machine note: only states that need human recovery gate re-entry. An earlier bug asserted state == Active in the choice that produced MarginCallOutstanding, pinning the CSA forever — fixed by gating only on MarkDisputed.

Choices

Defined on Csa.Csa.Csa (contracts/src/Csa/Csa.daml):

ChoiceControllerEffect
PostCollateralposter (A or B)Increments signed CSB in poster's direction
WithdrawExcesseither partyDecrements CSB if over-collateralised
PublishMarkoperatorRecords new mark, computes call, may flip state to MarginCallOutstanding
PublishMarkBySchedulerschedulerSame body as PublishMark, scheduler-driven (sister choice — Daml 2.x has no disjunctive controllers)
SettleVmoperatorTransfers pledged collateral to satisfy the call
SettleVmBySchedulerschedulerSister choice
Disputeeither partyFlags the mark, transitions to MarkDisputed
AcknowledgeDisputeoperatorResolves dispute, returns to Active

Contract identity

Every choice rotates the ContractId (Daml templates are immutable). Frontend code must look up the CSA by stable pair key (partyA + partyB), not by cached cid. Mutating choices use exerciseCsaWithRetry (app/src/features/csa/ledger/csa-actions.ts) which retries on CONTRACT_NOT_FOUND to handle racing rotations.

Production hardening

For live multi-tenant onboarding, use the CsaProposal template (Csa.Proposal:CsaProposal). It mirrors the CdsProposal pattern:

  • Signatories: proposer + operator (both must authorize creation)
  • Observers: counterparty + regulators
  • Choices: Accept (counterparty agrees, CSA is created), Reject (counterparty declines), Withdraw (proposer retracts before acceptance)

The init-time submitMulti [partyA, partyB, operator] path remains available for sandbox and reference deployments. The Operator console exposes the proposal workflow UI — see Operator view.