Pair Router
The Pair Router is PairStreet’s execution layer. It turns “this Pair Market holds X of the settlement asset” into an acquisition plan for the market’s Pair Asset, then hands the plan to the executor for its route.
Plan, then execute
The Pair RouterPair RouterThe execution layer that converts a Pair Vault's settlement balance into an acquisition plan for the Pair Asset and hands it to the right executor. has two functions. planAcquisition resolves the market’s Pair Instrument to a provider, asks that provider for a purchase route, and returns an AcquisitionPlan. executeAcquisition takes an executable plan and the vault address and returns a PurchaseExecution: units received, execution price and transaction signature.
Separating the two is deliberate. A plan is cheap, deterministic and safe to compute at any time: it is what the market page shows as the instrument’s route. Execution spends vault funds, so it runs only when the plan says it can and the vault has a signer.
Inputs and route modes
- Settlement asset
- The mint the vault holds before conversion. USDC by default; recorded on the plan as settlementMint.
- Amount
- The vault’s settlement balance in raw base units (inputAmountRaw). A settlement converts the full balance.
- Pair Instrument
- providerReference, providerId, status, reference price, decimals. The instrument names its provider.
- Provider adapters
- Resolved from the registry by providerId. Each returns a PurchaseRoute for the instrument.
- Eligibility context
- Instrument status is checked in every plan. Region and KYC rules are enforced at launch and claim by the provider’s checkEligibility.
- Execution parameters
- Slippage tolerance on the quote request (slippageBps, 50 bps default on onchain routes) and the market’s ledger mode.
- · Settlement asset
- · Amount
- · Pair Instrument
- · Provider adapters
- · Eligibility context
- · Execution parameters
- ONCHAIN_SWAPSwap the settlement asset into an SPL Pair Asset. Live Jupiter quotes for USDY, CETES, GILTS, XAUt0 and PAXG; execution activates with vault custody.Route ready
- PROVIDER_APIAcquire through an issuer or platform API. Registry instruments plan against this mode while their route connects.Interface defined
- RFQRequest quotes from liquidity desks for size or thin markets.Interface defined
- ISSUER_MINTSubscribe directly with the issuer and receive newly minted Pair Asset.Interface defined
- BROKER_ROUTEAcquire an offchain instrument through a broker that delivers a tokenized representation.Interface defined
Figure summary: Inputs: settlement asset, amount, Pair Instrument, provider adapters, eligibility context and execution parameters feed the Pair Router, which branches to five route modes. ONCHAIN_SWAP is route ready: planned with live Jupiter quotes for the five SPL instruments, with execution activating with Pair Vault custody. PROVIDER_API, RFQ, ISSUER_MINT and BROKER_ROUTE have their interfaces defined and activate per provider.
A sixth mode, DEV_SIMULATED, exists for the development network. The router refuses it outright for live markets: a live Pair Market can never settle through a simulated route, and a mock execution against a live plan throws.
The acquisition plan
These are the router’s types as they appear in the code. The plan carries everything needed to audit the decision later: which provider, which route, which quote, and whether it is executable.
// src/server/pair/router.ts
export type AcquisitionPlan = {
pairMarketId: string;
instrumentRef: string;
providerId: string;
settlementMint: string;
inputAmountRaw: string;
mode: RouteMode;
route: PurchaseRoute;
executable: boolean;
blockers: string[];
ledgerMode: LedgerMode;
plannedAt: string;
};
export async function planAcquisition(input: PlanInput): Promise<AcquisitionPlan>;
export async function executeAcquisition(plan: AcquisitionPlan, vaultAddress: string): Promise<PurchaseExecution>;// src/server/adapters/providers/types.ts
export type RouteMode = "ONCHAIN_SWAP" | "PROVIDER_API" | "RFQ" | "ISSUER_MINT" | "BROKER_ROUTE" | "DEV_SIMULATED";
export type RouteStep = { kind: "swap" | "mint" | "rfq" | "transfer" | "simulated"; label: string; venue?: string };
export type PurchaseRoute = {
mode: RouteMode;
available: boolean;
steps: RouteStep[];
quote?: InstrumentQuote;
/** Reasons the route cannot execute right now (missing signer, no liquidity, provider paused). */
blockers: string[];
};
export type PurchaseExecution = {
outputAmountRaw: bigint;
executionPriceUsd: number;
txSignature: string | null;
isMock: boolean;
};| Field | Meaning |
|---|---|
| instrumentRef | The provider’s reference for the instrument, for example spl:<mint> or registry:<slug>. |
| settlementMint | Mint of the settlement asset being spent. |
| inputAmountRaw | Amount to convert, in base units, as a string to avoid float drift. |
| mode | The route mode the provider returned. |
| route | The provider’s PurchaseRoute: steps, optional quote and its own blockers. |
| executable | True only when the route is available and the plan has no blockers. |
| blockers | Reasons this plan is not executable yet, de-duplicated. |
| ledgerMode | live, or dev_simulated for development-network routes. Copied onto every ledger row the plan produces. |
Blockers
A plan is always produced, executable or not. Instead of failing, the router collects blockers: plain-language reasons from the provider’s route plus the router’s own checks. A plan is executable exactly when its route is available and the blocker list is empty.
- ·Provider blockers. Returned in the route, for example “Pair Vault signer not configured (program PDA / multisig custody)”, or for a registry instrument “Settlement route for Tokyo Residential is connecting”.
- ·Instrument status. Any status other than active adds “Instrument is paused” (or the current status).
- ·Executor availability. A route mode without an executor in the deployed build adds a blocker naming the mode.
- ·Ledger separation. Simulated routes outside development, or on a live market, are blocked.
Below is the shape of the plan the router produces today for a $10,000 settlement into UK Government Gilts. The route and quote are live; the blockers state exactly what activation requires. Quote values change with every request and are shown as placeholders.
{
"pairMarketId": "<pair-market-id>",
"instrumentRef": "spl:GiLTSeSFnNse7xQVYeKdMyckGw66AoRmyggGg1NNd4yr",
"providerId": "etherfuse",
"settlementMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"inputAmountRaw": "10000000000",
"mode": "ONCHAIN_SWAP",
"route": {
"mode": "ONCHAIN_SWAP",
"available": false,
"steps": [{ "kind": "swap", "label": "Swap via <venue>", "venue": "<venue>" }],
"quote": {
"inputAmountRaw": "10000000000",
"outputAmountRaw": "<quoted units, 6 decimals>",
"outputDecimals": 6,
"priceUsd": "<input USD / output units>",
"priceImpactPct": "<from quote>",
"source": "jupiter",
"isMock": false
},
"blockers": [
"Pair Vault signer not configured (program PDA / multisig custody)",
"Settlement executor not deployed"
]
},
"executable": false,
"blockers": [
"Pair Vault signer not configured (program PDA / multisig custody)",
"Settlement executor not deployed",
"Route mode ONCHAIN_SWAP has no executor in this build"
],
"ledgerMode": "live",
"plannedAt": "<ISO timestamp>"
}When a settlement runs against a non-executable plan with a positive balance, the engine writes a failed settlement row whose error is the joined blocker list, and the balance stays in the vault. The plan is stored on the row, so every attempt is auditable. See Settlement.
Route selection
Today each Pair Instrument names one provider (providerId), and the router asks that provider for its route. The router’s design extends to instruments that can be reached more than one way: each candidate route is evaluated for availability, eligibility and liquidity, quoted execution is compared, and one route is selected for the plan.
Figure summary: Request: acquire $10,000 of Tokyo Residential exposure. Provider A is available through a provider API with estimated execution of $9,968.40 and fees of $31.60, and is selected. Provider B is restricted for the market’s eligibility context. Provider C has no liquidity for the requested size. The selected route is Provider A.
Selection order for multi-route instruments
- 01Drop candidates whose instrument status or eligibility rules exclude the market.
- 02Drop candidates that return no route or no liquidity for the amount.
- 03Compare remaining quotes on Pair Asset delivered per unit of settlement asset, net of fees and price impact.
- 04Write the selected route and its quote into the AcquisitionPlan, where it is stored with the settlement row.
Execution
executeAcquisition re-checks that the plan is executable, calls the provider’s purchase with the route, the vault address and the input amount, and returns the execution. For ONCHAIN_SWAP that is a Jupiter swap signed by the vault’s custody signer, which is why execution activates with Pair Vault custody.
| Route mode | Planning | Execution |
|---|---|---|
| ONCHAIN_SWAP | Live quotes | Activates per route |
| PROVIDER_API | Interface defined | Activates per route |
| RFQ | Interface defined | Activates per route |
| ISSUER_MINT | Interface defined | Activates per route |
| BROKER_ROUTE | Interface defined | Activates per route |
Each execution becomes a pair_purchases row with input, output, execution price and transaction signature. When the quote came from Jupiter, the execution price is also written back as the instrument’s reference price, so Pair Value tracks prices the vault actually traded at.