Skip to main contentSkip to FAQSkip to contact
For SolversHands-on· 30 min

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#

Integration Checklist#

Track your progress through each step:

Integration Checklist

Core loop - subscribe to the order stream
events.on.ts
const unsubscribe = tetrafi.events.on("settlement.*", (event) => {
console.log(`[{event.type}]`, event.payload);
});
// Later, to stop listening:
// unsubscribe();
6 linestypescript

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
Bash
1# Set environment variables
2export TETRAFI_API_KEY=tfk_test_...
3export TETRAFI_BASE_URL=https://sandbox.tetrafi.io/api/v1
3 linesbash

Use 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:

Bash
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" }
14 linesbash

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.

TypeScript
1import WebSocket from "ws";
2
3const ws = new WebSocket(
4 `wss://api.tetrafi.io/api/v1/ws?token=${process.env.TETRAFI_API_KEY}`
5);
6
7ws.on("open", () => {
8 ws.send(JSON.stringify({
9 type: "subscribe",
10 topics: ["orders:*"],
11 }));
12});
13
14ws.on("message", (data) => {
15 const event = JSON.parse(data.toString());
16 if (event.type === "order.created") {
17 handleOrder(event.order);
18 }
19});
20
21ws.on("ping", () => ws.pong());
21 linestypescript

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:

TypeScript
1interface RFQ {
2 id: string;
3 pair: string; // e.g., "USDC/USDT"
4 side: "buy" | "sell";
5 amount: string; // In source token
6 corridor: string; // e.g., "ethereum-optimism"
7 deadline: number; // Unix timestamp
8 complianceAttestation: string; // Hash proving taker is verified
9}
10
11async function handleRFQ(rfq: RFQ) {
12 // Validate pair support
13 if (!SUPPORTED_PAIRS.includes(rfq.pair)) {
14 console.log("Pair not supported, skipping:", rfq.pair);
15 return;
16 }
17
18 // Validate size
19 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 }
24
25 // Price and submit quote
26 const price = await calculatePrice(rfq);
27 await submitQuote(rfq.id, price);
28}
29
30const SUPPORTED_PAIRS = ["USDC/USDT", "USDC/EURC"];
31const MIN_TRADE_SIZE = ;
32const MAX_TRADE_SIZE = ;
32 linestypescript

Step 5: Submit Quotes#

Submit sealed quotes using EIP-712 signatures. Competing solvers cannot see your bids:

TypeScript
1import { ethers } from "ethers";
2
3const 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};
13
14async 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 expires
21 minAmount: "10000",
22 maxAmount: "10000000",
23 nonce: Date.now(),
24 };
25
26 // EIP-712 typed signature - sealed, not visible to other solvers
27 const signature = await signer.signTypedData(
28 { name: "TetraFi", version: "1", chainId: 1 },
29 QUOTE_TYPE,
30 quote
31 );
32
33 await tetrafi.quotes.submit({ ...quote, signature });
34 console.log("Quote submitted:", price);
35}
35 linestypescript

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:

TypeScript
1// Listen for escrow lock events
2ws.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});
14
15async function fillSettlement(settlement: Settlement) {
16 // Monitor ERC-7683 fill events on destination chain
17 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 chain
21 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}
29 linestypescript

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:

TypeScript
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 trail
12 const audit = await tetrafi.audit.getTrail(settlement.id);
13 console.log("WORM audit entries:", audit.length);
14
15 // Update your internal accounting
16 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 taker
24 }
25});
25 linestypescript

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

FeatureAcheronHercleFlowdeskWintermuteKeyrock
Regulatory2 features
Primary Jurisdiction
Netherlands (AFM)EU + SwitzerlandFrance (AMF)UK (FCA)Multi-jurisdictional
License Type
MiCA CASPMiCA + FINMADASPFCA RegisteredMultiple
Integration2 features
API Type
CustomCustomCustomTurnkey APICustom
Supported Corridors
EVM L2EVM + TradEVM L2Broad EVMBroad 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.

  1. Complete KYB verification via vendor adapter (Chainalysis or Elliptic)
  2. Receive ComplianceRegistry attestation on each chain you'll fill on
  3. Daily sanctions screening - attestation is continuously validated
  4. Post-trade Travel Rule data submission for applicable corridors
  5. Whitelist management - maintain approved corridor/token pairs

See Also#

I'm a SolverStep 5 of 7

Related topics