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:
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/contractsTrack your progress through each step:
Integration Checklist
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# Install SDK2npm install @tetrafi/sdk34# Set environment variables5export TETRAFI_API_KEY=sk_test_...6export TETRAFI_BASE_URL=https://sandbox.tetrafi.io/v17export TETRAFI_ENVIRONMENT=sandboxUse sk_test_ keys for sandbox development. Sandbox never touches real funds and compliance checks are mocked. Switch to sk_live_ only when ready for production.
Register your solver entity with your KYB details, supported trading pairs, and capacity limits:
1import { TetraFi } from "@tetrafi/sdk";23const tetrafi = new TetraFi({4 apiKey: process.env.TETRAFI_API_KEY!,5 environment: "sandbox",6});78const registration = await tetrafi.solvers.register({9 name: "Acme Market Making LLC",10 entityType: "company",11 jurisdictions: ["US", "EU"],12 supportedPairs: ["USDC/USDT", "USDC/EURC", "USDT/EURC"],13 corridors: ["ethereum-optimism", "ethereum-base"],14 minTradeSize: "10000", // USD equivalent15 maxTradeSize: "10000000", // USD equivalent16 walletAddress: "0x...",17});1819console.log("Solver ID:", registration.solverId);20console.log("Status:", registration.status); // "pending_review" | "approved"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.
Subscribe to incoming RFQs via WebSocket. Your pricing engine must respond within the auction window (typically 5 seconds):
1import WebSocket from "ws";23const ws = new WebSocket("wss://api.tetrafi.io/v1/solver/stream");45ws.on("open", () => {6 // Authenticate7 ws.send(JSON.stringify({8 type: "auth",9 token: process.env.TETRAFI_API_KEY,10 }));1112 // Subscribe to RFQ events13 ws.send(JSON.stringify({14 type: "subscribe",15 channels: ["rfq.*"],16 corridors: ["ethereum-optimism", "ethereum-base"],17 }));1819 console.log("Connected to TetraFi RFQ stream");20});2122// Heartbeat - server sends ping every 30s23ws.on("ping", () => ws.pong());2425ws.on("message", (data) => {26 const event = JSON.parse(data.toString());27 if (event.type === "rfq.created") {28 handleRFQ(event.rfq);29 }30});3132// Reconnect with exponential backoff33ws.on("close", () => {34 setTimeout(() => reconnect(), 1000 * Math.min(30, 2 ** retryCount++));35});Implement WebSocket reconnection with exponential backoff. A dropped connection means missed RFQs. The server sends a ping every 30 seconds - you must respond with pong to stay connected.
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 = ;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.
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.
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 HTLC timelock mechanism. Your solver reputation score may decrease for repeated failures.