Collateralisation
A specific kind of structured deal artifact — the contingent-contract model. Full repo lifecycle, rolls, and rehypothecation chains. Where a confirmed Intent becomes an on-chain collateralised position.
Where this sits in the alignment economy. Collateralisation is a specific shape of structure stage — the operational role where a confirmed joint prompt (Intent) becomes an on-chain artifact. A repo is one of the most useful artifact shapes available: an asset locked under a repurchase obligation, with deterministic forfeiture if repurchase fails. Multi-party deals often resolve into repos (collateralised lending, securitisation construction, structured escrow) because they let a party access funding without losing custody of the underlying position. Read the alignment-economy guide for context; this page is the specific structural mechanic.
YieldFabric's collateral primitive turns the bilateral atomic swap into a programmable repo — locking assets on one or both sides, with deterministic repurchase or forfeiture by deadline. Because the lender's position is itself a typed, referenceable contract (a contingent contract), the lender can use it as collateral in a further repo, building collateral chains of arbitrary depth.
A note on terminology. What this guide calls "rehypothecation" is structurally a matched-book / back-to-back repo: the lender re-pledges its own position (
Cont_A1) in a new swap, while the borrower's underlying collateral stays locked beneath. Traditional rehypothecation directly re-pledges the client's collateral; the economic effect is the same, but the legal mechanism here is position-pledging via a referenceable contingent contract.
This page covers:
- The contingent-contract data model — what gets created when a repo activates, and how the pieces fit together.
- The full lifecycle: creation, completion, repurchase, expiry, cancellation.
- Rolls — atomically moving a live repo to a new counterparty.
- Rehypothecation — using a contingent contract as collateral in a new repo, and how the unwind cascades.
- Bilateral and mixed-asset collateral structures.
Background:
/docs/swapscovers the atomic-swap primitive in general. Read that first if "swap" is new — this page assumes you've seencreateSwap/completeSwaponce.
The contingent-contract model
A repo isn't a single record. When completeSwap settles a swap
that has collateral slots populated, the consumer pipeline writes a
small hierarchy of composed contracts into the off-chain
projection:
Cont_A ← top-level contingent contract
│ (has both swap_id AND repurchase_contract_id)
├── Col_A ← collateral wrapper (has parent = Cont_A)
│ │
│ └── <underlying> ← the original obligations / payments
│ locked as collateral
│
└── Rep_A ← repurchase wrapper (referenced by
Cont_A.repurchase_contract_id)
│
└── <payments> ← what the borrower owes back
composed_contractsswap_idCont_X.composed_contractsrepurchase_contract_idRep_X — what the borrower owes to unwind. Set on Cont_X.composed_contractsparent_composed_contract_idCol_A and Rep_A have parent = Cont_A. Original obligations have parent = Col_A. Rehypothecation also flows through here: when Cont_A1 is collateral in SWAP-A2, its parent_composed_contract_id is set to Col_A2.The GraphQL ComposedContract type adds derived fields on top of
those columns:
type ComposedContract {
id: String!
name: String!
isContingent: Boolean! # true iff swap_id IS NOT NULL
swapId: String # the repo this contract represents
parentId: String # nesting link (stored)
# The following three are NOT stored columns — they're resolved at query time
# by walking the swap store for swaps that reference this contract's
# underlying contracts as collateral. Re-fetched on every query.
collateralInSwapId: String # set when THIS contingent is itself being used
# as collateral in another swap
collateralInSwapStatus: String
collateralSwapExpiry: DateTime
category: String! # "CONTINGENT" | "COLLATERAL" | "REPURCHASE" | "STANDARD"
displaySection: String # "COLLATERAL" | "CONTINGENT" | "REGULAR" — UI hint
isUserInitiator: Boolean # who am I in this swap?
children: [ComposedContract!]!
contracts: [Contract!]! # leaf-level obligations
}
The contingent contract is the lender's handle on the repo position.
It is the asset that exists once the repo is active, with the
repurchase obligation baked into its repurchase_contract_id
pointer. Treating it as a first-class contract (and not a hidden
side-effect of the swap) is what lets it participate in another
swap as collateral.
State machine
createSwap
│
▼
┌──────────┐
│ PENDING │ initiator signed,
└────┬─────┘ initiator collateral locked
│
┌────────────────┼────────────────┐
│ │ │
completeSwap cancelSwap deadline passes
│ │ │
▼ ▼ ▼
┌────────┐ ┌──────────┐ (cancelable
│ ACTIVE │ │CANCELLED │ by either side)
└───┬────┘ └──────────┘
│
│ Cont_X, Col_X, Rep_X composed contracts now exist
│
┌──────────┼──────────┐
│ │ │
expiry repurchase no repo
passes Swap paid (one-shot)
│ │ │
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌──────────┐
│EXPIRED │ │REPURCHASED│ │COMPLETED │
└───┬────┘ └──────────┘ └──────────┘
│
expireCollateral
│
▼
┌──────────┐
│FORFEITED │
└──────────┘
Terminal states: COMPLETED, REPURCHASED, CANCELLED, FORFEITED.
EXPIRED is a transient label — once expireCollateral lands the
swap is FORFEITED.
Pattern 1 — Canonical repo (one-sided collateral)
Borrower locks collateral, receives funding upfront, repurchases by deadline OR forfeits.
Slot mapping:
initiatorCollateralContractReferences / initiatorCollateralPaymentscounterpartyExpectedPayments (settles at completeSwap)initiatorRepurchasePayments (the amount + denomination borrower must pay back)expiry (top-level, distinct from deadline which times completeSwap){
"input": {
"swapId": "REPO-2027-Q1-001",
"name": "Q1 2027 Repo — bond AAA for AUD",
"counterparty": "ENT-lender-456",
"deadline": "2027-01-15T00:00:00Z",
"expiry": "2027-04-15T00:00:00Z",
"initiatorCollateralContractReferences": [
{ "contractId": "CTR-bond-AAA-tranche-1" }
],
"counterpartyExpectedPayments": {
"denomination": "aud-token-asset",
"amount": "990000",
"payments": [ { /* … vault-payment input … */ } ]
},
"initiatorRepurchasePayments": {
"denomination": "aud-token-asset",
"amount": "1000000",
"payments": [ { /* … vault-payment input … */ } ]
},
"idempotencyKey": "repo-q1-2027-001"
}
}
The 10 000 AUD spread (≈ 1.0% over 90 days, ≈ 4.1% annualised on a 365-day simple basis) is the lender's yield and the borrower's cost — in line with typical short-term repo pricing. No external trustee, no manual unwind step — the chain itself enforces "repay or forfeit".
A note on collateral economics. Real-world repo lenders apply a
haircut — they lend less than the collateral's market value to
absorb price moves before expiry, and they may issue margin
calls when the collateral re-prices. YieldFabric's mechanism
locks collateral at inception with a static haircut (the
difference between collateral value and counterpartyExpectedPayments
is set by the parties at swap creation) and no dynamic margining.
Operators wanting variation-margin behaviour layer it on as
separate payment streams between the parties, scheduled
out-of-band — the platform doesn't ship it as a primitive.
Two distinct timestamps:
deadline— until this, the counterparty (lender) can callcompleteSwap. After it, onlycancelSwapworks.expiry— oncecompleteSwaplands, the borrower has untilexpiryto callrepurchaseSwap. Afterexpiry, the forfeit side (typically the lender) callsexpireCollateralto settle the transfer. The YF MQ pipeline accepts the call from any authenticated user; the on-chain contract enforces who's actually permitted to trigger the forfeit.
Settlement timeline:
t=0 createSwap PENDING
— bond locked on-chain
— initiator collateral payment positions written off-chain
t≤d completeSwap ACTIVE
— lender pays 990 000 AUD upfront
— Cont_A, Col_A, Rep_A composed contracts created
— bond now visible as Col_A → underlying
t≤e repurchaseSwap REPURCHASED
— borrower pays 1 000 000 AUD
— bond returns to borrower
— Cont_A's repurchase obligation is satisfied
OR
t>e expireCollateral FORFEITED
— bond transfers to lender's wallet
— Cont_A marks the repurchase as defaulted
Pattern 2 — Bilateral collateral / structured escrow
Both sides post collateral, both have repurchase obligations. Use this for mutual margin, securitisation construction, or conditional service escrow where neither party trusts the other to settle.
{
"input": {
"swapId": "ESCROW-2027-A",
"name": "Escrow — securitisation construction round A",
"counterparty": "ENT-investor-789",
"deadline": "2027-01-10T00:00:00Z",
"expiry": "2027-02-10T00:00:00Z",
"initiatorCollateralContractReferences": [
{ "contractId": "CTR-tranche-A-obligations" }
],
"counterpartyCollateralPayments": {
"denomination": "usd-token-asset",
"amount": "500000",
"payments": [ { /* … */ } ]
},
"initiatorRepurchasePayments": { /* what initiator pays to get its obligations back */ },
"counterpartyRepurchasePayments": { /* what counterparty pays to get its cash back */ },
"idempotencyKey": "escrow-2027-a-create"
}
}
If both sides pay their repurchase by expiry → both collaterals
return (effectively an unwind). If only one pays → that side gets
its collateral back, the other side forfeits. If neither → both
forfeit to the counterparty.
The contingent-contract structure mirrors the asymmetry: each side
gets its own Cont_X referencing its own Rep_X and pointing at
its own Col_X. Both contingent contracts share the same swap_id.
Pattern 3 — Mixed obligations + payments
Any collateral slot accepts both *ContractReferences (obligations)
AND *Payments (cash). A common shape is an invoice-financing repo
where the borrower posts a basket of invoice tokens plus a partial
cash margin:
{
"initiatorCollateralContractReferences": [
{ "contractId": "CTR-invoice-A" },
{ "contractId": "CTR-invoice-B" }
],
"initiatorCollateralPayments": {
"denomination": "aud-token-asset",
"amount": "50000",
"payments": [ /* … */ ]
},
"counterpartyExpectedPayments": { "amount": "900000", /* … */ },
"initiatorRepurchasePayments": { "amount": "950000", /* … */ }
}
The ContractReference shape is itself a union: pass contractId
(a single leaf contract) OR composedContractId (a previously
composed hierarchy you want to treat as one collateral unit). Mixing
these is how rehypothecation (next section) works.
Rolls — atomically moving a repo to a new counterparty
What a roll is, and why you'd do one
A repo roll transfers a live position from one counterparty-and-terms set to another, without taking the collateral out of lock at any point. The borrower's underlying stays continuously encumbered; only the contract structure around it changes. Three canonical uses:
- Funding rollover — your repo was a 30-day term, expiry is
tomorrow, and you want another 30 days of funding. Rather than
repurchaseSwap(which requires you to source the principal from somewhere) followed by a brand-newcreateSwap(which requires fresh collateral establishment), you roll — the collateral never lifts and you avoid two round-trips of on-chain settlement. - Counterparty substitution — your current lender is exiting the position, but another lender (with better terms, or simply willing to take it on) will step in. The roll pays out the old lender and brings the new one in atomically. This is the bread-and-butter activity of every dealer's repo desk.
- Term restructuring — same parties, new dates. The roll lets
you change
deadlineandexpiry, the upfront amount, and the repurchase amount in one event.
The state machine atom on the swap is REPURCHASED, not CANCELLED — from the audit point of view the old repo terminated through repayment, not default. The repayment just happened to be funded by the new lender.
The two-step mechanics
t=0 t=1 t≤new_expiry
┌──────────┐ ┌──────────┐
OLD SWAP (R1) │ ACTIVE │ ──────► │REPURCHASED│
└──────────┘ └──────────┘
│ ▲
initiateRoll completeRoll
│ │
▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐
NEW SWAP (RR1) │ PENDING │ ──────► │ ACTIVE │ ──────► │REPURCHASED│
└──────────┘ └──────────┘ └──────────┘
(or expires
→ forfeited)
Collateral hierarchy: Cont_R1 Cont_RR1 Cont_RR1
│ │ │
Col_R1 Col_RR1 Col_RR1
│ │ │
underlying ─── REPARENTED ──────► same underlying
(never lifts) (returns to
borrower)
Step 1 — initiateRoll:
mutation {
initiateRoll(input: {
oldSwapId: "REPO-2027-Q1-001",
newSwapId: "REPO-2027-Q2-001",
newCounterparty: "ENT-new-lender-999",
newDeadline: "2027-04-20T00:00:00Z",
newExpiry: "2027-07-20T00:00:00Z",
# what the new lender will pay upfront when they complete:
newCounterpartyExpectedPayments: { amount: "1000000", denomination: "aud-token-asset", payments: [ /* … */ ] },
# what the initiator will repurchase from the new lender:
newInitiatorRepurchasePayments: { amount: "1010000", denomination: "aud-token-asset", payments: [ /* … */ ] },
# what's paid back to the OLD lender — typically equals the old
# repurchase amount, may be prorated for time-weighted repos:
oldSwapRepurchasePayments: { amount: "1000000", denomination: "aud-token-asset", payments: [ /* … */ ] }
}) { success newSwapId messageId }
}
What this actually does, on-chain and in the projection:
- New swap row written with status
PENDINGandsource_swap_id = oldSwapId(the lineage link). - Borrower pre-funds the old repo's repurchase by signing
payments that the on-chain contract escrows. Their id_hashes are
stored in
Swap.repurchase_prefund_id_hashesfor retrieval at step 2. - Upfront payment record created for the new counterparty to see as Incoming and accept at step 2.
- The old swap is NOT touched. It stays
ACTIVE. If step 2 never happens, the old repo continues to its originalexpiryunaffected.
Step 2 — completeRoll:
mutation {
completeRoll(input: {
newSwapId: "REPO-2027-Q2-001",
idempotencyKey: "roll-q2-complete"
}) { success messageId }
}
This is the atomic moment. On the consumer side, all of the following happen in one transaction:
- New counterparty's upfront settles — they pay
newCounterpartyExpectedPayments(the 1 000 000 AUD). - Old swap marked
REPURCHASED. The borrower's pre-funded repurchase from step 1 settles to the old lender; the on-chain contract zeros R1's repurchase obligations. - R1's composed-contract hierarchy is dismantled. Old
Cont_R1/Col_R1/Rep_R1are removed. The leaf collateral contracts are unlinked from R1 before RR1's hierarchy is created — to prevent the new contingent from accidentally walking up into the old (deleted) tree. - Collateral migrates to the new swap. The same underlying
obligations/payments that were in R1's
Col_R1are now in RR1'sCol_RR1. They never become "loose" between the two — the on-chain lock is continuous. - New swap status flips
PENDING → ACTIVE. RR1'sCont_RR1/Col_RR1/Rep_RR1are created with their parent-child links established. The new repurchase obligation becomes payable by the borrower to the new lender bynewExpiry.
After step 2 the borrower's position looks indistinguishable from
opening a new repurchase trade with the new lender — except that
Swap.sourceSwapId on RR1 still points at R1, preserving the audit
trail.
Worked example (single counterparty substitution)
Borrower has a 90-day repo with Lender A: 1 000 000 AUD lent against a bond, 1 010 000 AUD due back at expiry. 80 days have elapsed. Lender A wants out. Lender B is willing to take the position to maturity (10 days remaining) at a 30 bps premium over the original terms.
Step 1 — initiateRoll (called by Borrower)
pre-funds the 1 010 000 AUD owed to Lender A (escrowed on-chain)
creates RR1 in PENDING:
newCounterpartyExpectedPayments = 1 010 000 AUD (Lender B's upfront)
newInitiatorRepurchasePayments = 1 010 084 AUD (premium for 10 days)
newDeadline = NOW + 1 day (Lender B must accept)
newExpiry = old expiry + 10 days
R1 unchanged, still ACTIVE.
Step 2 — completeRoll (called by Lender B)
Lender B pays 1 010 000 AUD upfront for RR1
Borrower's pre-fund (from step 1) pays 1 010 000 AUD to Lender A
R1 → REPURCHASED. Lender A is out.
Bond migrates from Cont_R1 to Cont_RR1. Borrower's lock unchanged.
RR1 → ACTIVE.
Step 3 — 10 days later: Borrower → repurchaseSwap(RR1)
Pays 1 010 084 AUD to Lender B → bond returns.
Borrower's net for the full 90 days: lost 84 AUD (the rolling premium) over what they'd have paid Lender A. Lender A exits early and gets paid in full. Lender B earns the 84 AUD on the 10-day exposure to a fully-collateralised position — a competitive bid even at low spreads.
Time-weighted / prorated repurchase
oldSwapRepurchasePayments.amount is carried through verbatim
from the message into process_completed_roll. The pipeline
explicitly stores the message-time amount in
repurchase_prefund_id_hashes JSON so that recomputing at step 2
can't drift:
"Critical for time-weighted repos where the amount was prorated at initiateRoll time — re-computing at completeRoll would give a different value and cause a balance-hash mismatch."
If the parties agree the borrower has only used 80/90 of the term
when rolling, the oldSwapRepurchasePayments.amount reflects the
prorated principal + accrued. The new lender doesn't see the
full original repurchase — they fund whatever the rolled-in
position is worth at the roll moment.
Failure modes
The two-step roll is not atomic across both messages. There's real time between step 1 and step 2:
completeRoll never lands by newDeadlinePENDING past its deadline; R1 is still ACTIVE (untouched). Borrower's pre-funded payments are escrowed.completeRoll lands or (b) the position is unwound by repurchaseSwap on R1 directly (which requires the pre-fund to be reclaimable — coordinate with the platform team for the recovery procedure).completeRoll lands but on-chain executes a partial statesource_swap_id set, PENDING, AND the old swap is REPURCHASED, that's a bug — file a ticket.cancelSwap on RR1 while it's PENDING (cancel pre-completion is allowed). The pre-fund escrow needs unwinding — same caveat as the first row.newDeadline.newSwapId used twicemessages.idempotency_key UNIQUE prevents the second submission from reaching execution.newSwapId per roll attempt.Lineage
Every rolled swap carries sourceSwapId pointing at the predecessor.
Chains of rolls form a linked list — to audit a position's full
history:
query Trace($swapId: String!) {
swaps {
byId(id: $swapId) {
id
sourceSwapId # the previous swap in the chain (null = original)
status
deadline
expiry
parties { entity { name } role }
}
}
}
Recurse on sourceSwapId to walk back to the original repo. Each
node in the chain is its own bitemporal record — versions, status
transitions, party changes are all queryable independently.
Rehypothecation
Here's the structural insight that makes the platform a real repo market and not just a one-shot loan tool:
The contingent contract
Cont_Aproduced by a repo is itself a first-class composed contract. It can be used as collateral in a new repo. That second repo produces aCont_A2referencing the first one. Repeat as needed.
The data model encodes this through parent_composed_contract_id:
when Cont_A1 is collateral in SWAP-A2, the consumer pipeline sets
Cont_A1.parent_composed_contract_id = Col_A2 (the outer swap's
collateral wrapper). Nested chains form a tree:
Cont_A2 ← Lender B's view
│ (the repo between Lender A and Lender B)
┌───────────────┤
│ │
Col_A2 Rep_A2 ← Lender A's repurchase obligation to Lender B
│
▼
Cont_A1 ← Lender A's view of the underlying repo
│ Stored: parent_composed_contract_id = Col_A2
│ swap_id = SWAP-A1
│ repurchase_contract_id = Rep_A1
│ Derived: collateralInSwapId = SWAP-A2
│ (resolver walks the swap store)
│
├── Col_A1
│ │
│ └── Corporate Bond ← the original asset
│ ├── Coupon stream
│ └── Redemption
│
└── Rep_A1 ← Borrower's repurchase obligation to Lender A
How you set it up
When Lender A wants to rehypothecate Cont_A1 to Lender B, Lender A
calls createSwap from its own wallet, passing Cont_A1's composed
contract id as a collateral reference:
{
"input": {
"swapId": "SWAP-A2",
"name": "Rehypo — Cont_A1 for AUD funding",
"counterparty": "ENT-lender-B",
"deadline": "2027-01-20T00:00:00Z",
"expiry": "2027-04-10T00:00:00Z",
"initiatorCollateralContractReferences": [
{ "composedContractId": "CONT-A1-COMPOSED-ID" }
],
"counterpartyExpectedPayments": {
"denomination": "aud-token-asset",
"amount": "985000",
"payments": [ /* … */ ]
},
"initiatorRepurchasePayments": {
"denomination": "aud-token-asset",
"amount": "993000",
"payments": [ /* … */ ]
},
"idempotencyKey": "rehypo-A2-create"
}
}
Operator discipline: the outer expiry should be ≤ the inner
repo's expiry. Otherwise Lender B's claim can outlive the
underlying collateral lock — once the borrower repurchases, Cont_A1
no longer wraps the bond, and the outer position has nothing
substantive behind it. YieldFabric does not currently validate
this constraint at createSwap — clients must enforce it.
Lender A's economics on this rehypothecation:
Lender A lent Borrower 990 000 AUD (inner upfront)
Lender A receives back 1 000 000 AUD (inner repurchase)
Lender A borrowed from Lender B 985 000 AUD (outer upfront)
Lender A repays Lender B -993 000 AUD (outer repurchase)
────────────────────────────────────────────────
Net to Lender A +7 000 AUD on 5 000 AUD net capital
= leveraged carry
Lender A funded only the 5 000 AUD difference between the inner upfront and outer upfront, earning the 2 000 AUD differential between the inner and outer spreads. This is the canonical prime-brokerage leverage pattern.
Lifecycle — three independent unwinds, ordered by inner-most first
Once the chain is up, every level has its own state machine. They unwind independently in time but the substance of each level's asset depends on what's still inside it.
Scenario A — happy path, full unwind
When the inner repo is repurchased, the borrower's repurchase payment is substituted as the outer swap's collateral — Cont_A1 still wraps something, just not the original bond anymore.
t1: Borrower → repurchaseSwap(SWAP-A1)
→ Cont_A1's repurchase obligation satisfied
→ Bond would normally return to Borrower, BUT Cont_A1 is still
locked inside Cont_A2 as part of Col_A2's collateral
→ repurchase_swap_processor substitutes: repo_payment_A
becomes the new collateral content inside Cont_A1
→ Bond physically flows back to Borrower
t2: Lender A → repurchaseSwap(SWAP-A2)
→ Cont_A2's repurchase obligation satisfied
→ Cont_A1 (now wrapping repo_payment_A) returns to Lender A
→ Lender A receives the borrower's repurchase payment via Cont_A1
After both repurchases the bond is back with the Borrower; Lender A holds the inner repurchase payment net of what was paid to Lender B; Lender B has its principal plus the outer spread.
Scenarios B and C — defaults (mechanism partially verified)
The on-chain contract and consumer pipeline encode specific rules
for what transfers on expireCollateral when the forfeit-recipient
target is a contingent contract: when the root is a contingent
contract, its internal collateral subtree is skipped. Only the
repurchase subtree is transferred.
That means:
-
Scenario B — outer default, inner cured. Lender B calls
expireCollateral(SWAP-A2). The forfeit target is Cont_A1. Per the rule above, Lender B receives Rep_A1 (which now contains the borrower's repurchase payment from the cured inner repo) but NOT Col_A1 (which is empty anyway — the bond has been released to the borrower). Lender B is made whole in cash equivalent, the borrower keeps the bond, Lender A loses its outer-leverage profit plus its haircut. -
Scenario C — inner default, outer outcome. Lender A calls
expireCollateral(SWAP-A1). The forfeit target is the bond (STANDARDcontract, not a contingent). The standard transfer path applies — but Cont_A1 itself is still locked in Col_A2 of SWAP-A2. Whether the bond physically flows to Lender A's wallet or stays attached to Cont_A1 (and therefore inside Cont_A2's collateral) is governed by the on-chain contract; the off-chain pipeline tracks the resulting state via the contract-store-walks drivingcollateralInSwapId.Outcomes from this point:
- If Lender A then repurchases the outer (
repurchaseSwap(SWAP-A2)) — Cont_A1 (with the realised bond effectively inside) returns to Lender A. Lender A is whole. - If SWAP-A2 expires unredeemed — Lender B forfeit-receives Rep_A1, and the realised bond's destination depends on the on-chain rules for nested-forfeiture. Verify with a contract-level test before relying on a specific outcome here; the off-chain processors leave the precise on-chain flow to the contract.
- If Lender A then repurchases the outer (
Bottom line: Scenario A is the path the pipeline explicitly handles and is well-trodden. Defaults in nested chains follow the on-chain contract rules; the off-chain projection tracks the resulting state but the platform doesn't synthesise additional unwind logic on top of what the contract dictates. For production use of complex chains, write contract-level integration tests for the specific default sequences your book is exposed to.
Querying a rehypothecation chain
query Chain($contractId: String!) {
composedContracts {
byId(id: $contractId) {
id
name
isContingent
swapId
collateralInSwapId # set if THIS contingent is itself collateral
collateralInSwapStatus # status of the outer swap
collateralSwapExpiry # outer expiry — watch this!
children { # the underlying — recurse for nested contingents
id
name
isContingent
collateralInSwapId
children { id name isContingent }
}
}
}
}
The recursion bottoms out at a STANDARD contract (no swap_id).
Constraint discipline
Three constraints matter for nested chains. The current platform
does not enforce them at createSwap validation time — clients
must encode them, and operators should reject swaps that violate
them.
- Outer expiry ≤ inner expiry. A rehypothecation that outlives
its underlying collateral lock has no underlying recourse —
once the borrower repurchases, Cont_A1 no longer wraps the bond.
No platform-side check today; verify client-side before
submitting
createSwap. - Only the contingent's holder should rehypothecate it.
Possession is signalled by
Cont_A1.isUserInitiator = falsefor the lender (i.e. the lender is the counterparty of the inner repo). The on-chain contract enforces that a participant owns what they're posting as collateral; the right test before submitting isCont_A1.isUserInitiator === falsefrom the rehypothecating party's perspective. collateralInSwapIdis effectively single-valued. A contingent should be collateralised in at most one outer swap at a time. SincecollateralInSwapIdis derived by walking the swap store, a chain that tries to re-pledge an already-pledged contingent will be visible — and the on-chain contract checks ownership at execution. Don't rely on validation; structure your flow so the previous outer swap terminates before re-pledging.
Mutation reference (collateral lifecycle)
createSwapcomposed_contractsCont_X yet.completeSwapcomposed_contractsCont_X, Col_X, Rep_X rows created; nesting links established.repurchaseSwapcomposed_contractsCont_X.repurchase_contract_id's payments settle; underlying flows back.expireCollateralcomposed_contractsCont_X updated to FORFEITED. When the forfeit target is itself a Cont_X, only its Rep_X subtree transfers — Col_X is left for the inner-repo parties.cancelSwapcomposed_contractsCont_X created (cancel only valid pre-completion).initiateRollcomposed_contractsPENDING; new Cont_X not yet materialised.completeRollcomposed_contractsCont_X marked REPURCHASED; new Cont_X created; collateral re-parented.swapObligorPaymentcomposed_contractsCommon pitfalls
deadline and expirycompleteSwap, the other times repurchaseSwap.deadline = "accept by", expiry = "repurchase by". For non-repo swaps leave expiry null.expiry > inner expiryexpiry ≤ inner expiry. The chain is only economically sound when repurchase windows nest in time.repurchaseSwap before expiryrepurchaseSwap automatically if pre-arranged.Cont_X like a static recordcollateralInSwapId flips on rehypothecation; its repurchase_contract_id payment status changes on repurchase.ContractReference is XOR — contractId OR composedContractId, not both. Single contracts vs composed bundles.composedContractId (the Cont_X id). When posting a fresh asset, you pass contractId.cancelSwap after completeSwapCont_X exists, the only unwinds are repurchase / forfeit.Swap's output (e.g. initiatorCollateralPayments: String).InitialPaymentsInput.idempotencyKey on a rollReference
Status enum
enum SwapStatus {
PENDING // createSwap landed; collateral locked, awaiting completeSwap
ACTIVE // completeSwap settled; Cont_X exists, repurchase window open
COMPLETED // one-shot exchange finished (used when there's no repurchase obligation)
REPURCHASED // repurchaseSwap settled; collateral returned
CANCELLED // cancelSwap landed before completion
EXPIRED // expiry passed but expireCollateral not yet called (transient label)
FORFEITED // expireCollateral settled; collateral transferred to opposite side
}
ComposedContract category derivation
category is derived purely from stored columns —
swap_id and repurchase_contract_id:
swap_id?repurchase_contract_id?categoryCONTINGENTswap_id?repurchase_contract_id?categoryREPURCHASE (the Rep_X subtree)swap_id?repurchase_contract_id?categorySTANDARD or COLLATERAL *displaySection is then derived per-user, additionally
consulting the GraphQL-resolved collateralInSwapId (which walks
the swap store):
categoryCONTINGENTcollateralInSwapId set?displaySection for holderCONTINGENTcategoryCONTINGENTcollateralInSwapId set?displaySection for holderCOLLATERAL (this contingent is itself rehypothecated)categoryREPURCHASEcollateralInSwapId set?displaySection for holderCont_X)categorySTANDARDcollateralInSwapId set?displaySection for holderCOLLATERAL (it's locked in some swap)categorySTANDARDcollateralInSwapId set?displaySection for holderREGULAR(*) COLLATERAL is structurally the same as STANDARD — the
category is just a hint about how the UI should group it. Borrowers
see their posted collateral in the COLLATERAL section; lenders see
their corresponding contingent in CONTINGENT.
Full collateral input slots in CreateSwapInput
input CreateSwapInput {
swapId: String!
counterparty: String!
deadline: String! # completeSwap deadline
expiry: String # repurchase deadline (omit for one-shot swaps)
# Collateral — locked at swap creation (initiator) or completion (counterparty),
# returned on repurchase or transferred on forfeiture.
initiatorCollateralContractReferences: [ContractReference!]
initiatorCollateralPayments: InitialPaymentsInput
counterpartyCollateralContractReferences: [ContractReference!]
counterpartyCollateralPayments: InitialPaymentsInput
# Repurchase — what each side owes to retrieve its collateral.
initiatorRepurchaseContractReferences: [ContractReference!]
initiatorRepurchasePayments: InitialPaymentsInput
counterpartyRepurchaseContractReferences: [ContractReference!]
counterpartyRepurchasePayments: InitialPaymentsInput
# (Plus expected/upfront slots, idempotencyKey, walletId, etc. —
# see /docs/swaps for the full shape including the non-collateral fields.)
}
input ContractReference {
contractId: String # leaf contract — XOR with composedContractId
composedContractId: String # bundled / Cont_X composed contract — XOR with contractId
}
See also
/docs/swaps— the underlying atomic-swap primitive./docs/contracts— obligations and composed contracts./docs/guides/cross-service-walkthrough— an end-to-end repo flow as part of a deal./docs/api/payments— every collateral mutation with full input / response schemas, code samples, and the GraphQL input/output types referenced here.