Solver Integration Guide#
This guide walks you through connecting your pricing engine to TetraFi as a solver (liquidity provider). By the end, you'll be able to receive RFQs, submit competitive quotes, and fill settlements.
Integration at a Glance#
Your whole integration surface is 4 HTTP endpoints + 4 on-chain functions - and the HTTP contract is the Open Intents Framework v0 REST spec, not a TetraFi-specific protocol. Any solver that already speaks OIF to other intent networks speaks it to us. The reference solver (tetrafi-solver) ships the full shape out of the box.
Solver API you host#
GET /tokens- capability probe (chains, tokens, settlers)POST /quotes- RFQ handler (return quotes in response body)POST /orders- accept winning quote + signed orderGET /orders/{id}- status polling
On-chain you execute#
open()- ComplianceSettlerEscrow (if not taker-opened)fill()- ComplianceOutputSettler (first caller wins)submit()- Oracle attestation of the fillfinalise()- ComplianceSettlerEscrow (claim input)
- Competition -
fill()is idempotent; first solver to land the tx wins. - Compliance - KYB + per-chain
ComplianceRegistryattestation;_beforeNewFill()validates every fill on-chain.
Choose Your Integration Path#
Four paths, self-selected by where you start. If you already operate a solver against another intent network, Path D is usually the fastest. If you are building from scratch, most operators start with Path A and graduate to B only when they have proprietary pricing.
| Path | Effort | Best for |
|---|---|---|
| A. Run the reference solver | Hours | Go live fast with built-in pricing (mock / coingecko / defillama). Clone tetrafi-solver, edit config/demo.toml, run. |
| B. Plug custom pricing | Days | You have proprietary inventory or OTC flow. Implement PricingInterface (solver-pricing crate); keep our endpoint + on-chain plumbing. |
| C. Custom REST adapter (OIF v0) | 1-2 weeks | Non-Rust stack or deep MM-infra integration. Implement the 4 HTTP endpoints yourself in any language. |
| D. Register your existing solver | Minutes (OIF-compliant) · scoped engagement (custom) | You already operate a solver. If it speaks the OIF v0 REST contract, just register your base URL plus KYB and you're live. If it speaks a proprietary protocol, TetraFi writes a custom adapter on our side - your stack stays as-is. See Not OIF? Custom Adapter. |
Choose your integration path:
SDK Integration
Use @tetrafi/sdk for an abstracted, typed interface. Handles authentication, retries, and WebSocket management automatically.
$ npm install @tetrafi/sdkREST API
Connect your pricing engine via REST + WebSocket. Full control over every request and response. Supports any language.
$ curl https://api.tetrafi.io/v1/statusOn-Chain Filler
Fill orders directly on-chain via ERC-7683 settlement intents. Maximum decentralization with smart contract integration.
$ npm install @tetrafi/contractsIntegration Checklist#
Track your progress through each step:
Integration Checklist
Step 1: Prerequisites#
Before starting, ensure you have:
- A TetraFi API key (use sandbox for development)
- Node.js 18+ or Python 3.10+
- A wallet with testnet stablecoins for sandbox settlement
- Business entity verification (KYB) completed
1# Set environment variables2export TETRAFI_API_KEY=tfk_test_...3export TETRAFI_BASE_URL=https://sandbox.tetrafi.io/api/v1Use tfk_test_ keys for sandbox development. Sandbox never touches real funds and compliance checks are mocked. Switch to tfk_live_ only when ready for production.
Step 2: Register as Solver#
Register your solver entity with your KYB details, supported trading pairs, and capacity limits:
1curl -X POST https://api.tetrafi.io/api/v1/solvers/register \2 -H "Authorization: Bearer tfk_test_..." \3 -H "Content-Type: application/json" \4 -d '{5 "name": "Acme Market Making LLC",6 "entityType": "company",7 "jurisdictions": ["US", "EU"],8 "supportedPairs": ["USDC/USDT", "USDC/EURC"],9 "corridors": ["ethereum-optimism", "ethereum-base"],10 "minTradeSize": "10000",11 "maxTradeSize": "10000000",12 "walletAddress": "0x..."13 }'14# → { "solverId": "...", "status": "pending_review" }Registration triggers a KYB review. In sandbox mode, review is instant. In production, expect 1-3 business days. You'll receive a webhook notification when approved.
Step 3: Expose the Solver API#
You host four HTTPS endpoints. TetraFi's aggregator registers your base URL at onboarding and calls them directly - no stream to connect to, no SDK required. Return JSON.
3.1 GET /tokens - capability probe#
The aggregator calls this at solver startup and periodically thereafter to learn which chains, tokens, and settler contracts you support. Missing chains or tokens here = no RFQs for those corridors.
1{2 "networks": {3 "8453": {4 "inputSettler": "0x…",5 "outputSettler": "0x…",6 "tokens": [""]7 },8 "10": {9 "inputSettler": "0x…",10 "outputSettler": "0x…",11 "tokens": [""]12 }13 }14}3.2 POST /quotes - RFQ handler#
Request
1{2 "user": "0x…",3 "intent": {4 "inputToken": "0xA0b8…eB48",5 "outputToken": "0xdAC1…1ec7",6 "inputAmount": "1000000000000",7 "originChainId": 8453,8 "destinationChainId": 109 },10 "supportedTypes": ["oif-swap", "oif-escrow-v0", "oif-resource-lock-v0"]11}Response - the quote IS the response body. There is no separate submit step.
1app.post("/quotes", async (req, res) => {2 const { user, intent, supportedTypes } = req.body;34 if (!canServe(intent)) return res.json({ quotes: [] });56 const preview = await priceIntent(intent);78 // The `order` field is EIP-712 typed data, NOT a signature.9 // You build it; the taker signs it after picking your quote.10 const order = {11 signatureType: "Eip712",12 domain: { name: "TetraFi", version: "1", chainId: intent.originChainId, verifyingContract: INPUT_SETTLER },13 primaryType: "StandardOrder",14 message: buildStandardOrderMessage(intent, preview),15 types: STANDARD_ORDER_TYPES,16 };1718 res.json({19 quotes: [{20 quoteId: "qt_" + crypto.randomUUID(),21 order,22 validUntil: Math.floor(Date.now() / 1000) + 30,23 eta: 12,24 provider: "acme",25 failureHandling: "refund",26 partialFill: false,27 preview,28 metadata: {},29 }],30 });31});You do not sign here. The order field carries EIP-712 typed data (domain, primaryType, message, types). The taker signs it after choosing your quote; the aggregator forwards the signature in POST /orders. Solver-side signTypedData(...) on the quote is not part of the OIF contract and would produce unusable orders.
Return { "quotes": [] } rather than a 4xx when you choose not to quote (out-of-scope corridor, size outside risk, inventory exhausted). Empty array = "not bidding this round" and carries no reputation penalty; 4xx is treated as a categorized error.
3.3 POST /orders - winning-quote delivery#
When your quote wins, the aggregator POSTs the signed order to your server. Verify the signature against domain.verifyingContract, persist, and asynchronously kick off on-chain fill (see Step 4).
Request
1{2 "order": { /* same Order you returned in /quotes */ },3 "signature": "0x…",4 "quoteId": "qt_…",5 "originSubmission": { "txHash": "0x…" },6 "metadata": {}7}Response - fire-and-forget; the aggregator polls GET /orders/{orderId} for progress.
1{2 "orderId": "ord_…",3 "status": "Accepted",4 "message": "fill queued",5 "order": { /* optional echo */ },6 "metadata": {}7}Status enum: Accepted · Pending · Completed · Rejected · Error.
3.4 GET /orders/{orderId} - status polling#
1{2 "id": "ord_…",3 "status": "Pending",4 "createdAt": 1734375500,5 "updatedAt": 1734375530,6 "inputAmounts": [{ "token": "0x…", "amount": "1000000000000" }],7 "outputAmounts": [{ "token": "0x…", "amount": "999725000000" }],8 "settlement": { "token": "0x…", "amount": "999725000000", "recipient": "0x…" },9 "fillTransaction": { "chainId": 10, "txHash": "0x…" }10}Step 4: Settle On-chain#
When you accept an order in POST /orders, you have up to the fillDeadline (typically 30 s) to deliver outputs on the destination chain and claim inputs on the origin. Four settler functions drive the on-chain flow:
| Function | Contract | Purpose |
|---|---|---|
open() | ComplianceSettlerEscrow | Open the input-side escrow on the origin chain. Skip if the taker used Permit2 / ERC-3009 auto-open. |
fill() | ComplianceOutputSettler | Deliver outputs on the destination chain. Idempotent - first caller wins. |
submit() | Oracle (Hyperlane / Wormhole / Polymer / LayerZero) | Request attestation of the fill back to the origin. |
finalise() | ComplianceSettlerEscrow | Claim the input escrow on the origin against the oracle attestation. |
1import { ethers } from "ethers";23async function settleOrder(order: SignedOrder) {4 // 1. Fill on destination - race-to-fill, idempotent5 const dstProvider = new ethers.JsonRpcProvider(DST_RPC);6 const outputSettler = new ethers.Contract(OUTPUT_SETTLER_ADDR, OutputSettlerABI, signer.connect(dstProvider));7 const fillTx = await outputSettler.fill(order.orderId, order.originData, order.destinationData);8 await fillTx.wait();910 // 2. Submit oracle attestation request11 const oracle = new ethers.Contract(ORACLE_ADDR, OracleABI, signer.connect(dstProvider));12 await (await oracle.submit(order.orderId, fillTx.hash)).wait();1314 // 3. Finalise on origin once attestation lands (oracle SLA varies)15 const origProvider = new ethers.JsonRpcProvider(ORIG_RPC);16 const escrow = new ethers.Contract(INPUT_SETTLER_ADDR, EscrowABI, signer.connect(origProvider));17 await (await escrow.finalise(order.orderId)).wait();18}Compliance pre-check runs on every fill. ComplianceOutputSettler._beforeNewFill() verifies your solver address has a live ComplianceRegistry attestation on the destination chain. Missing attestation = tx reverts. See Compliance Architecture.
Race-to-fill is real. fill() is idempotent - the first successful transaction wins and subsequent attempts return early without reverting. Build your submitter for speed, not for exclusivity.
Step 5: Post-Fill & Reconciliation#
Once finalise() confirms, update your own GET /orders/{id} response to Completed, fetch the WORM audit trail for accounting, and reconcile.
1// After finalise() confirms on origin2await updateOrderStatus(orderId, {3 status: "Completed",4 outputAmounts: [{ token: DST_TOKEN, amount: fillAmount }],5 fillTransaction: { chainId: DST_CHAIN_ID, txHash: fillTx.hash },6});78// Optional: pull the WORM audit trail for regulatory reporting9const audit = await fetch(`https://api.tetrafi.io/api/v1/audit/trail/{orderId}`, {10 headers: { Authorization: `Bearer ${apiKey}` },11}).then(r => r.json());1213await reconcileAccounting({ orderId, fillTx, audit });Failed fills auto-refund. If you miss the fillDeadline, the InputSettler allows the taker to reclaim locked funds. You lose only the gas you spent attempting to fill. Repeated failures drop your solver reputation score (see Quote Pipeline → Solver Reputation).
Real-Time Order Updates (Optional)#
The four REST endpoints above are sufficient to run a solver end-to-end. If you want push semantics on post-fill state transitions, pick at most one of these optional channels - none is required for quote auction participation:
| Channel | Direction | Carries | Setup |
|---|---|---|---|
Poll GET /orders/{id} on your own server | Aggregator → You (HTTP) | Your own order state | Already in OIF contract |
WebSocket wss://api.tetrafi.io/api/v1/ws · topics orders:{orderId} or orders:* | TetraFi → You (push) | Post-fill state transitions (Deposited → Filled → Attested → Claimed) | Token auth + reconnect logic |
Webhook via POST /api/v1/webhooks | TetraFi → You (HTTP push) | Same events, durable at-least-once delivery | Public HTTPS endpoint + HMAC verification |
See Webhooks & Events for WebSocket topic patterns, auth, and signature verification.
What the public WebSocket does NOT do for solvers. The public WS endpoint only accepts orders:* and prices:* topics - there are no RFQ or quote-streaming topics. You cannot stream quotes over WebSocket and you cannot receive RFQs over WebSocket via the self-serve public API. Solvers must expose POST /quotes over HTTPS to participate in auctions.
Exception - custom adapters. If your MM stack already exposes a native streaming API (e.g., a persistent WebSocket RFQ feed), TetraFi can write a bespoke adapter on our side that connects out to your server and translates your protocol to OIF internally. This is not self-serve - see Not OIF? Custom Adapter below.
Not OIF? Custom Adapter#
If you cannot host HTTP endpoints (your MM infra is streaming-native, uses a proprietary protocol, or you've already integrated with other intent networks via a different shape), TetraFi's aggregator supports pluggable SolverAdapter implementations. The team writes the adapter on our side - you keep your existing API.
Existing adapters in the aggregator include TetraFi-native, OIF (the contract this guide documents), Across, and bespoke LP adapters for streaming counterparties (Keyrock pattern). Adding a new one is a TetraFi engineering effort scoped during onboarding. Contact us to start the conversation - bring your protocol docs and a sandbox endpoint.
Troubleshooting#
Regulated Solver Landscape#
TetraFi's solver model is regulatory architecture, not just technical integration. Solvers are regulated market makers with existing compliance infrastructure.
Regulated Solver Landscape
| Feature | Acheron | Hercle | Flowdesk | Wintermute | Keyrock |
|---|---|---|---|---|---|
Regulatory2 features | |||||
Primary Jurisdiction | Netherlands (AFM) | EU + Switzerland | France (AMF) | UK (FCA) | Multi-jurisdictional |
License Type | MiCA CASP | MiCA + FINMA | DASP | FCA Registered | Multiple |
Integration2 features | |||||
API Type | Custom | Custom | Custom | Turnkey API | Custom |
Supported Corridors | EVM L2 | EVM + Trad | EVM L2 | Broad EVM | Broad EVM |
Fill Mechanics#
orderId#
keccak256(abi.encode(standardOrder)) - deterministic hash of the typed struct
First-to-Fill#
fill() is idempotent - first caller wins, subsequent attempts return early. Race-to-fill model.
Compliance Onboarding#
Tier system. Regulated / Permissioned / Permissionless - gates corridor access + Travel Rule requirements. See Compliance Framework.
- Complete KYB verification via vendor adapter (Chainalysis or Elliptic)
- Receive ComplianceRegistry attestation on each chain you'll fill on
- Daily sanctions screening - attestation is continuously validated
- Post-trade Travel Rule data submission for applicable corridors
- Whitelist management - maintain approved corridor/token pairs