Pairing

Settlement

Trades happen continuously. Pair acquisition happens in windows. A settlement converts everything a Pair Vault accumulated during one window into the Pair Asset, records it, and funds a reward epoch.

One turn of the Pair flywheel

A SettlementSettlementOne turn of a Pair Market's flywheel: the Pair Vault balance is routed, the Pair Asset acquired, accounting finalized and a reward epoch funded. is a single, ledgered conversion for one Pair Market. The engine (runSettlement in src/server/pair/settlement.ts) reads the market’s Pair Vault, asks the Pair Router for an acquisition plan, executes it, credits the Pair Asset to the vault, and allocates a configured share of the market’s Pair Asset to holders in a Reward EpochReward EpochA window over which acquired Pair Asset is allocated to eligible holders by their holder weight. Produces exact integer allocations and a Merkle root..

The sequence from trade to funded epoch looks like this inside one window.

A settlement window, from first trade to funded epochILLUSTRATIVE
00:00 · Window opensThe previous settlement reset the vault’s settlement balance to $0. nextSettlementAt is set one cadence ahead.
Times and amounts are illustrative. The window length is the market’s settlementCadenceSec; the default is 3,600 seconds. 200.00 × $25.0625 = $5,012.50.

Figure summary: 00:00 the window opens. 03:14 and 05:22 trades accrue creator fees into the Pair Vault. 11:59 the window closes with a $5,012.50 settlement balance. 12:00 the Pair Router plans the route. 12:02 200.00 units of the Pair Asset are acquired at an effective $25.0625. 12:04 accounting is finalized. 12:06 a reward epoch is funded with 50.00 units.

What runSettlement does

  1. Step 01pair_markets · pair_vaults
    Load the market
    Join the Pair Market, its vault, its Pair Instrument and its token. Read the settlement balance in raw base units and the market’s cadence.
  2. Step 02planAcquisition
    Plan
    Ask the Pair Router for a plan to convert the full settlement balance from the settlement mint into the instrument’s Pair Asset.
  3. Step 03executable
    Decide
    Zero balance: schedule the next window and stop. Positive balance with blockers: write a failed settlement row carrying the plan and the blockers, keep the balance, schedule the next window.
  4. Step 04executeAcquisition
    Execute
    Convert through the plan’s route into the vault address. Record the purchase. For Jupiter-sourced routes, write the execution price back to the instrument’s reference price.
  5. Step 05computeEpoch
    Fund the reward epoch
    Release the configured share of the market’s unallocated Pair Asset, weight eligible holders by the market’s methodology, allocate exactly, and store the Merkle root. See Rewards.
  6. Step 06pair_markets
    Update the read model
    Write unallocated balance, Pair Value, lifetime acquired, holder reward totals, settlement count and the next settlement time. Emit activity events.

All writes for one settlement happen inside a single database transaction. A settlement either lands completely or not at all.

runSettlementreturn shapes
// runSettlement(pairMarketId) resolves to one of:
{ status: "completed", sequence, units, outputUsd, allocatedUsd }  // Pair Asset acquired, epoch funded
{ status: "blocked", blockers: string[] }                         // failed row written, balance kept
{ status: "skipped" }                                             // zero balance, next window scheduled

Sizing the reward pool

The pool for the epoch is a configured share of the market’s Pair Asset that has not yet been allocated, including what this settlement just acquired. The share is the market’s epochReleaseBps. Integer division in raw base units means the pool never exceeds what the vault holds.

Epoch pool = (Unallocated + Acquired) × epochReleaseBps / 10,000
50.00 = (4,800.00 + 200.00) × 100 / 10,000
Pair Asset base units, integer division. Example from the timeline above, with epochReleaseBps = 100.

What a settlement writes

Every step leaves a row. The pair_markets aggregates are then a cache of these rows, and recomputeFromLedger rebuilds them from the ledger alone; tests prove the two match. Each row carries the plan’s Ledger modeLedger modeEvery financial ledger row carries live or development. Production reads and writes live rows only..

TableWritten whenRecords
pair_settlementsEvery settlement with a positive balancesequence, status (completed or failed), routeMode, settlementAmountRaw, settlementAmountUsd, plan, error, ledgerMode, startedAt, completedAt
pair_purchasesCompleted settlementsinstrument, provider, routeMode, inputMint, inputAmountRaw, inputUsd, outputAmountRaw, outputUsd, executionPriceUsd, txSignature, status
reward_epochsPool above zero with eligible holdersepochNumber, startAt, endAt, methodology, rewardInstrumentId, totalPoolRaw, eligibleHolderCount, totalWeight, allocationRoot, status finalized
reward_allocationsOne per eligible holder in the epochwallet, weight, amountRaw, usdValue
pair_vaultsCompleted settlementssettlement balance reset to 0; Pair Asset balance, lifetime converted and lifetime allocated incremented
pair_marketsEvery rununallocated Pair Asset, pairValueUsd, lifetime acquired, holder reward totals, eligibleHolders, settlementsCount, lastSettlementAt, nextSettlementAt
activity_eventsCompleted settlementsPAIR_ASSET_PURCHASED, PAIR_SETTLEMENT, and PAIR_REWARD_ALLOCATED when an epoch is funded

Settlement statuses

A settlement moves from scheduled to completed along one path. Two branches end it early: a plan that carries blockers, and a settlement withdrawn before it runs. The status enum on pair_settlements carries all six states.

Settlement status machineARCHITECTURE
dueexecutableblockerserrorwithdrawnSCHEDULEDPLANNINGEXECUTINGCOMPLETEDFAILEDCANCELLED

Hover a state for its meaning.

Terminal: successTerminal: failedTerminal: cancelled
The current engine runs plan, execution and accounting in one transaction and writes the row at its terminal state: completed, or failed with the blockers in error.

Figure summary: Happy path: scheduled, planning, executing, completed. A plan with blockers or an execution error ends in failed. A scheduled settlement withdrawn before it runs, for example because the market or instrument was paused, ends in cancelled.

A failed settlement does not lose value. The balance remains in the vault and the next window retries with whatever has accumulated since. Failure Modes covers detection and recovery per cause.

Why batch settlement

PairStreet settles in windows instead of acquiring the Pair Asset on every trade. This is a design choice, and the engine is built around it: one plan, one conversion and one epoch per window. The reasons:

  • ·Lower transaction overhead. One conversion per window instead of one per trade. Fixed costs per transaction are paid once.
  • ·Better routing. A larger amount gets a considered route and quote. Thin per-trade amounts would route poorly or not at all.
  • ·Reduced dust. Tiny conversions leave tiny balances and rounding remainders. A window turns them into one meaningful amount.
  • ·Clean accounting. One settlement row, one purchase row and one epoch per window. Each reconciles on its own.
  • ·Provider compatibility. Issuer mints, RFQ desks and broker routes operate on minimums and cutoffs, not per-trade streams.
  • ·Less fragmented execution. Fewer, larger conversions are easier to monitor, simulate and audit.

Settlement on mainnet

ComponentState
Settlement engine (runSettlement, planning, accounting, epochs)Implemented · tested
Route planning for USDY, CETES, GILTS, XAUt0, PAXGRoute ready
Vault custody and settlement executorActivates per route
Registry-listed instrumentsActivates per route

Settlement activates per instrument route. Live Pair Markets register with status settlement_disabled, shown as “Settlement pending”, and nextSettlementAt unset, so the scheduler does not select them. When a market’s vault custody and its instrument’s route activate, the market becomes active, receives its first nextSettlementAt, and settles on cadence from then on. Until then its Pair Vault reserve accrues to the PairStreet treasury.