Clearing primitives for tokenized assets. Novation, multilateral netting, position limits, default waterfall.
Venues find the price. Indent Layer makes the trade settle.
This package is the part of that you can read and run today. It has no dependencies, no network calls and no build step. Clone it and the tests execute on plain Node.
git clone https://github.com/IndentLayer/indent-core
cd indent-core
node --test "test/**/*.test.js"
node examples/window.jsTo settle a trade atomically, both sides have to hold the full asset at the moment of the fill. Every open position therefore has cash frozen against it, and market size ends up bounded by how much capital participants are willing to leave idle rather than by demand.
Atomicity today is bought with idle money. These primitives are how that stops being true.
Three members, one asset, one settlement window.
| A owes B | 300 |
| B owes C | 250 |
| C owes A | 200 |
| A owes C | 150 |
| C owes B | 100 |
Net position at window close: A −250, B +150, C +100.
gross 1,000 net 250 never moved 750 compression 75.0%
Nothing was cancelled. Every obligation still stands. They simply stopped settling one at a time.
That figure is not a claim in a deck. It is
test/netting.test.js, and it runs in CI on every
push. If the number on the website is ever wrong, this build goes red.
| Module | Does |
|---|---|
src/netting.js |
Multilateral netting across a window, and the transfers that discharge it |
src/waterfall.js |
Loss absorption in fixed layer order |
src/limits.js |
Haircuts, collateral value, position limits |
src/obligation.js |
The signed obligation type and its validation |
import { net } from './src/netting.js';
const r = net([
{ from: 'A', to: 'B', amount: 300 },
{ from: 'B', to: 'C', amount: 250 },
{ from: 'C', to: 'A', amount: 200 },
]);
r.gross; // 750
r.net; // 250
r.compression; // 0.666…
r.transfers; // [{ from: 'A', to: 'C', amount: 100 }, …]Bilateral netting cancels what two members owe each other and stops there. Multilateral netting cancels across every member at once, so compression rises with the number of members rather than with the number of pairs. A single venue can only net against itself, which is why this belongs one layer down.
The book must close at zero. If it does not, net() throws rather than
settling something incoherent.
import { absorb } from './src/waterfall.js';
absorb(400, {
defaulterMargin: 100,
defaulterFundContribution: 50,
protocolInsurance: 200,
mutualisedFund: 400,
});Losses pass through five layers in a fixed order, and each must be exhausted before the next is touched:
- The defaulter's margin
- The defaulter's own default fund contribution
- The protocol insurance layer
- The mutualised fund of surviving members
- Auction of the residual position
Layer three is the one that matters. The protocol's own capital burns before
any surviving member's does, and absorb() reports survivorsTouched so that
property is asserted rather than promised.
import { positionLimit, admits } from './src/limits.js';
const limit = positionLimit([{ amount: 1000, haircut: 0 }], 0.1); // 10000
admits(9000, 2000, limit); // false, rejected at submissionCollateral is valued below market, because the collateral itself can fall while a position is being closed out. A trade that would breach a limit is rejected when it is submitted, not unwound afterwards.
import { DOMAIN, TYPES, encodeType, validate } from './src/obligation.js';DOMAIN and TYPES are plain EIP-712 data, so any wallet library can sign
against them. This package carries no signing dependency on purpose: the
definition is the contract, and the choice of library is yours.
| Stage | Status |
|---|---|
| Primitives, open and tested | this repository |
| Clearing contracts | not deployed |
| Testnet | not open |
| Mainnet | not live |
Nothing here talks to a chain yet. It is the logic that the contracts will enforce, published first so it can be read, argued with and corrected before anything holds value.
Network: Robinhood Chain, chain ID 4663.
A venue needs a matching engine that produces a firm two-sided fill and the ability to collect a signature from each side. Nothing else. No custody changes, no margin posted, no credit risk taken on the trades it submits.
| indent-sdk | Venue-side obligation building and lifecycle |
| indent-contracts | The on-chain interfaces these primitives back |
| indentlayer.xyz/docs | The model behind all of it |
Corrections to the mechanics are more welcome than features. If a property that should hold does not, open an issue with the window that breaks it, or a pull request with a failing test.
MIT licensed.