Skip to main content
This guide walks through a complete tokenised repo lifecycle - from opening to maturity to closing.

Prerequisites

  • Authenticated with M2M token (Authentication)
  • A DvP settlement template
  • Both parties have wallet addresses on a supported chain
  • You know the maturity date for the repo

Flow overview

A repo consists of two linked settlements:
  1. Opening: You create a repo_open settlement with a maturity_at date
  2. Closing: KeyStone auto-creates a repo_close settlement when maturity arrives
Both settlements follow the standard DvP flow (compliance, escrow, swap).

Step 1: Create the opening settlement

Create a settlement with settlement_category: "repo_open" and a maturity_at timestamp.
curl -X POST https://api.keystoneos.xyz/v1/settlements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "repo-example-001",
    "template_id": "YOUR_DVP_TEMPLATE_ID",
    "settlement_category": "repo_open",
    "maturity_at": "2026-04-17T12:00:00Z",
    "timeout_at": "2026-03-20T12:00:00Z",
    "parties": [
      {
        "role": "seller",
        "external_reference": "bond-holder-001",
        "name": "Acme Fixed Income",
        "wallet_address": "0xSellerWallet...",
        "chain_id": 11155111
      },
      {
        "role": "buyer",
        "external_reference": "cash-lender-002",
        "name": "Beta Capital",
        "wallet_address": "0xBuyerWallet...",
        "chain_id": 11155111
      }
    ],
    "legs": [
      {
        "leg_type": "asset",
        "instrument_id": "0xBondTokenAddress",
        "quantity": "1000",
        "direction": "deliver",
        "party_role": "seller"
      },
      {
        "leg_type": "payment",
        "instrument_id": "USDC",
        "quantity": "950000",
        "direction": "deliver",
        "party_role": "buyer"
      }
    ]
  }'
timeout_at controls when the opening settlement times out if not completed. maturity_at controls when the closing settlement is created after the opening finalizes. These are independent - maturity_at should be after timeout_at.
Response:
{
  "id": "a1b2c3d4-...",
  "state": "INSTRUCTED",
  "settlement_category": "repo_open",
  "maturity_at": "2026-04-17T12:00:00Z",
  "parent_settlement_id": null,
  "parties": [...],
  "legs": [...]
}

Step 2: Confirm counterparty (cross-platform only)

If this is a cross-platform repo, the counterparty platform must confirm their participation. See Cross-Platform Settlements for the confirmation flow. For single-platform repos, skip to Step 3.

Step 3: Compliance screening

The engine automatically screens all parties through LSEG World-Check (entity) and CipherOwl (wallet). This happens without any action on your part. If a party is flagged, the settlement pauses. Submit a compliance decision to continue:
curl -X POST https://api.keystoneos.xyz/v1/settlements/$OPENING_ID/compliance-decision \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"decision": "approve"}'

Step 4: Escrow deposits

Once compliance clears, the engine registers the settlement on the escrow smart contract. Both parties deposit their assets on-chain:
  • Seller deposits bond tokens to the escrow contract
  • Buyer deposits USDC to the escrow contract
KeyStone detects deposits automatically via on-chain monitoring.
You do not need to notify KeyStone when deposits are made. On-chain monitoring detects them automatically.

Step 5: Atomic swap executes

When all deposits are confirmed, the escrow contract executes the atomic swap:
  • Bond tokens transfer to the buyer’s wallet
  • USDC transfers to the seller’s wallet
The opening settlement transitions through SETTLED to FINALIZED.

Step 6: Await maturity

The opening settlement is now complete. KeyStone monitors the maturity_at timestamp. When maturity arrives, the system automatically creates the closing settlement. You can monitor the opening settlement to check its status:
curl https://api.keystoneos.xyz/v1/settlements/$OPENING_ID \
  -H "Authorization: Bearer $TOKEN"

Triggering maturity early (admin only)

Administrators can trigger maturity before the scheduled date:
curl -X POST https://api.keystoneos.xyz/v1/admin/settlements/$OPENING_ID/trigger-maturity \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Step 7: Closing settlement auto-created

At maturity, KeyStone creates a repo_close settlement with:
  • Reversed legs (buyer returns bonds, seller returns USDC)
  • The same parties and template
  • parent_settlement_id pointing to the opening settlement
The closing settlement goes through the same compliance, escrow, and swap flow. Compliance is re-screened because party status may have changed since the opening.

Step 8: Closing settlement completes

The closing settlement follows the same Steps 3-5 as the opening:
  1. Compliance screening
  2. Escrow deposits (buyer deposits bonds, seller deposits USDC)
  3. Atomic swap (bonds return to seller, USDC returns to buyer)
  4. Settlement finalizes
Your webhook endpoint receives settlement.state.finalized for the closing settlement. At any point, you can query all settlements linked to a repo:
curl https://api.keystoneos.xyz/v1/settlements/$OPENING_ID/related \
  -H "Authorization: Bearer $TOKEN"
Response:
[
  {
    "id": "a1b2c3d4-...",
    "settlement_category": "repo_open",
    "state": "FINALIZED",
    "parent_settlement_id": null,
    "maturity_at": "2026-04-17T12:00:00Z"
  },
  {
    "id": "e5f6g7h8-...",
    "settlement_category": "repo_close",
    "state": "FINALIZED",
    "parent_settlement_id": "a1b2c3d4-..."
  }
]
This works regardless of whether you query with the opening or closing settlement ID.

Error scenarios

ScenarioResult
Opening compliance rejectionOpening settlement rolls back. No closing settlement is created.
Opening deposit timeoutOpening settlement times out. Deposits returned. No closing created.
Closing compliance rejectionClosing settlement rolls back. Opening remains finalized.
Closing deposit timeoutClosing settlement times out. Deposits returned. Manual resolution needed.
Maturity worker downtimeWorker catches up on next run. Closing settlement created when worker resumes.

Webhooks

Subscribe to settlement state events to track both opening and closing legs. The settlement_category field in webhook payloads lets you distinguish between them:
{
  "event": "settlement.state.finalized",
  "data": {
    "settlement_id": "e5f6g7h8-...",
    "settlement_category": "repo_close",
    "parent_settlement_id": "a1b2c3d4-...",
    "state": "FINALIZED"
  }
}
See Webhooks for setup instructions.