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.
Choose Your Integration Path#
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
const unsubscribe = tetrafi.events.on("settlement.*", (event) => { console.log(`[{event.type}]`, event.payload);});// Later, to stop listening:// unsubscribe();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: Connect to Order Stream#
Subscribe to incoming orders via WebSocket. Auth via ?token= query param. The server pings every 30 seconds - respond with pong to stay connected.
1import WebSocket from "ws";23const ws = new WebSocket(4 `wss://api.tetrafi.io/api/v1/ws?token=${process.env.TETRAFI_API_KEY}`5);67ws.on("open", () => {8 ws.send(JSON.stringify({9 type: "subscribe",10 topics: ["orders:*"],11 }));12});1314ws.on("message", (data) => {15 const event = JSON.parse(data.toString());16 if (event.type === "order.created") {17 handleOrder(event.order);18 }19});2021ws.on("ping", () => ws.pong());Implement reconnection with exponential backoff. A dropped connection means missed orders. 30-second heartbeat, 5-minute idle timeout.
Step 4: Handle Incoming RFQs#
Validate each RFQ against your supported pairs and size limits before pricing:
1interface RFQ {2 id: string;3 pair: string; // e.g., "USDC/USDT"4 side: "buy" | "sell";5 amount: string; // In source token6 corridor: string; // e.g., "ethereum-optimism"7 deadline: number; // Unix timestamp8 complianceAttestation: string; // Hash proving taker is verified9}1011async function handleRFQ(rfq: RFQ) {12 // Validate pair support13 if (!SUPPORTED_PAIRS.includes(rfq.pair)) {14 console.log("Pair not supported, skipping:", rfq.pair);15 return;16 }1718 // Validate size19 const amount = parseFloat(rfq.amount);20 if (amount < MIN_TRADE_SIZE || amount > MAX_TRADE_SIZE) {21 console.log("Trade size out of range:", amount);22 return;23 }2425 // Price and submit quote26 const price = await calculatePrice(rfq);27 await submitQuote(rfq.id, price);28}2930const SUPPORTED_PAIRS = ["USDC/USDT", "USDC/EURC"];31const MIN_TRADE_SIZE = ;32const MAX_TRADE_SIZE = ;Step 5: Submit Quotes#
Submit sealed quotes using EIP-712 signatures. Competing solvers cannot see your bids:
1import { ethers } from "ethers";23const QUOTE_TYPE = {4 Quote: [5 { name: "rfqId", type: "string" },6 { name: "price", type: "string" },7 { name: "ttl", type: "uint256" },8 { name: "minAmount", type: "string" },9 { name: "maxAmount", type: "string" },10 { name: "nonce", type: "uint256" },11 ]12};1314async function submitQuote(rfqId: string, price: string) {15 const signer = new ethers.Wallet(process.env.SOLVER_PRIVATE_KEY!);16 17 const quote = {18 rfqId,19 price, // e.g., "0.9995"20 ttl: 30, // seconds until quote expires21 minAmount: "10000",22 maxAmount: "10000000",23 nonce: Date.now(),24 };2526 // EIP-712 typed signature - sealed, not visible to other solvers27 const signature = await signer.signTypedData(28 { name: "TetraFi", version: "1", chainId: 1 },29 QUOTE_TYPE,30 quote31 );3233 await tetrafi.quotes.submit({ ...quote, signature });34 console.log("Quote submitted:", price);35}Submit quotes within 2 seconds of RFQ receipt for optimal fill rates. The auction window is typically 5 seconds, and faster responses indicate higher solver reliability.
Step 6: Manage Escrow#
When your quote is accepted, monitor escrow events and fill on the destination chain:
1// Listen for escrow lock events2ws.on("message", async (data) => {3 const event = JSON.parse(data.toString());4 5 if (event.type === "settlement.pending") {6 const settlement = event.settlement;7 console.log("Escrow locked:", settlement.escrowTxHash);8 console.log("Destination chain:", settlement.destinationChainId);9 10 // Fill on destination chain - must complete within window (typically 30s)11 await fillSettlement(settlement);12 }13});1415async function fillSettlement(settlement: Settlement) {16 // Monitor ERC-7683 fill events on destination chain17 const provider = new ethers.JsonRpcProvider(DEST_RPC_URL);18 const settler = new ethers.Contract(OUTPUT_SETTLER_ADDR, OutputSettlerABI, signer);19 20 // Deliver funds on destination chain21 const tx = await settler.fill(22 settlement.orderId,23 settlement.originData,24 settlement.destinationData,25 );26 27 await tx.wait();28 console.log("Fill TX:", tx.hash);29}You must fill within the settlement window (typically 30 seconds). If you fail to fill, the escrow automatically refunds the taker and your solver reputation score decreases.
Step 7: Settlement Confirmation#
Verify DvP completion and update your internal accounting:
1ws.on("message", async (data) => {2 const event = JSON.parse(data.toString());3 4 if (event.type === "settlement.complete") {5 const { settlement } = event;6 console.log("Settlement complete!");7 console.log(" Origin TX:", settlement.originTxHash);8 console.log(" Destination TX:", settlement.destinationTxHash);9 console.log(" Amount:", settlement.amount);10 11 // Fetch full audit trail12 const audit = await tetrafi.audit.getTrail(settlement.id);13 console.log("WORM audit entries:", audit.length);14 15 // Update your internal accounting16 await reconcileSettlement(settlement);17 }18 19 if (event.type === "settlement.failed") {20 const { settlement } = event;21 console.error("Settlement failed:", settlement.error);22 console.log("Taker refunded via:", settlement.refundTxHash);23 // No action needed - escrow auto-refunds taker24 }25});DvP atomicity guarantees that either both legs settle or neither does. If you experience a failed fill, the taker is automatically refunded via the escrow timeout mechanism - if the fill deadline expires, the InputSettler contract allows the taker to reclaim their locked funds. Your solver reputation score may decrease for repeated failures.
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