FomoAI — Technical Architecture
A non-custodial, parameter-gated copy-trading agent that monitors on-chain activity of target wallets on the Fomo platform and mirrors their trades in real time according to user-defined execution rules.
Overview
FomoAI is a third-party execution layer built on top of the Fomo trading app — a consumer-grade on-chain trading terminal founded by ex-dYdX engineers that routes trades directly through each chain's native liquidity pools. As of mid-2026 Fomo has processed over $4B in cumulative volume and supports Solana, Robinhood Chain, Base, BNB, Monad, and Ethereum.
FomoAI adds a copy-trading layer: the user specifies one or more Fomo @handles to track, sets their risk parameters, and the agent automatically mirrors every qualifying fill the target makes. The user's capital stays in their own self-custodial wallet — FomoAI never takes custody.
Why Fomo specifically
Fomo's architecture is uniquely suited to copy-trading because all trades settle on-chain and are fully observable via standard RPC methods. Unlike CEX copy-trading, there is no API permission required from the trader being followed — their on-chain footprint is public by design. Every buy and sell is emitted as a verifiable on-chain event that FomoAI's detection layer subscribes to.
FomoAI does not require any cooperation from Fomo Labs or the traders being followed. It operates entirely on public on-chain data.
System Architecture
FomoAI is composed of five loosely coupled layers — each independently scalable and connected via a message queue. The frontend communicates with the backend over WebSocket for real-time position and fill updates.
| Layer | Responsibility |
|---|---|
| Frontend | Wallet management, parameter config, AI chat, position display, real-time fill feed |
| AI Assistant | Cloudflare Workers AI — session-context-aware strategy and parameter advice |
| Detection Service | Persistent WebSocket + gRPC subscriptions per tracked wallet; emits normalised fill events |
| Signal Pipeline | Receives fill events, applies all active parameter filters, enqueues qualifying fills |
| Execution Engine | Dequeues fills, constructs swap transactions, signs with user key, broadcasts to chain |
| Position Manager | Tracks open positions, running P&L, stop-loss triggers, take-profit exits |
Trade Detection Engine
Fomo routes every trade on-chain through the chain's native liquidity pools. This means all trade activity is fully observable via standard RPC subscriptions — no Fomo API key or permission is required. The detection service resolves each tracked @handle to its on-chain wallet address and maintains persistent subscriptions across both supported chains simultaneously.
Solana — gRPC Geyser subscription
Solana trades on Fomo execute through the chain's DEX ecosystem. The primary listener transport is Yellowstone gRPC (Geyser plugin), which pushes every transaction involving a target account to the detection service with sub-100ms latency. A WebSocket blockSubscribe fallback is used when gRPC is unavailable.
Each Fomo trade on Solana produces one of three event types:
- SWAP — a complete trade that stays on Solana: both the input token and output token are named in the same transaction. This is the primary signal for copy-trading.
- PAY — the cash leg of a cross-chain buy landing from another network. Indicates a buy routed from a different chain.
- IN / OUT — plain token movements (transfers). Used for position tracking, not for triggering copy fills.
// Yellowstone gRPC subscription filter (simplified)
const filter = {
accountInclude: trackedWallets, // array of base58 pubkeys
commitment: "confirmed",
includeTransactions: true,
transactionDetails: "full",
}
// Parse each transaction for swap instruction data
function parseSolanaFill(tx) {
const accounts = tx.transaction.message.accountKeys;
const logs = tx.meta.logMessages;
const preTokens = tx.meta.preTokenBalances;
const postTokens = tx.meta.postTokenBalances;
// Derive token in/out from balance deltas
const deltas = computeTokenDeltas(preTokens, postTokens);
return {
wallet: accounts[0],
tokenIn: deltas.decreased.mint,
tokenOut: deltas.increased.mint,
amountIn: deltas.decreased.amount,
amountOut: deltas.increased.amount,
signature: tx.transaction.signatures[0],
slot: tx.slot,
};
}
Robinhood Chain — event log subscription
Robinhood Chain (chain ID 4663, Arbitrum Orbit L2) runs EVM-compatible smart contracts. Fomo executes trades through Relay settlement contracts using ERC-4337 smart contract wallets with EIP-7702 delegation. The detection service subscribes to Relay's settlement event logs using eth_subscribe with a logs filter.
Three Relay event types are relevant:
- BUY — a token delivered by a Relay solver. Signals a completed buy by the target wallet.
- SELL — a token swapped and handed to Relay for settlement. Signals a completed sell.
- PAY — cash deposited into Relay. Used for cross-chain order correlation via the 32-byte Relay order ID.
A 32-byte Relay order ID is the primary key linking the two legs of any cross-chain trade. This allows the detection service to correlate a Solana PAY event with its corresponding RH Chain BUY fill without requiring Fomo's servers as an intermediary.
// eth_subscribe logs filter (WebSocket)
const filter = {
address: RELAY_SETTLEMENT_CONTRACT, // Relay contract on RH Chain
topics: [
[TOPIC_BUY, TOPIC_SELL], // event selectors
null,
paddedAddress(targetWallet), // filter by tracked wallet
]
};
ws.send(JSON.stringify({
jsonrpc: "2.0", id: 1,
method: "eth_subscribe",
params: ["logs", filter]
}));
// Decode event
function decodeRelayFill(log) {
const iface = new ethers.Interface(RELAY_ABI);
const decoded = iface.parseLog(log);
return {
wallet: decoded.args.recipient,
token: decoded.args.outputToken,
amountIn: decoded.args.inputAmount, // USDC/USDG in
amountOut:decoded.args.outputAmount, // token out
orderId: decoded.args.orderId, // 32-byte Relay ID
txHash: log.transactionHash,
block: log.blockNumber,
};
}
Polling fallback: When WebSocket connectivity is degraded, the service falls back to newHeads subscription + eth_getLogs per block, introducing ~1–2 block delay (~0.5–1s on Robinhood Chain).
Signal Processing Pipeline
Every raw fill event emitted by the detection service passes through the signal pipeline before any execution decision is made. The pipeline is a sequential filter chain — a fill must pass every active filter or it is logged as skipped with the failing reason.
type RawFill = {
wallet: string; // source wallet address
chain: "sol" | "rh";
token: string; // output token mint / contract
direction: "buy" | "sell";
amountUSDC:number; // USD value of the fill
mcap: number; // market cap at fill time ($)
liquidity: number; // pool liquidity at fill time ($)
timestamp: number;
}
async function processFill(fill: RawFill, params: UserParams): Promise {
// 1. Chain active check
if (!params.activeChains.includes(fill.chain))
return log(fill, "SKIP", "chain_inactive");
// 2. Direction filter (buys only by default; sells optional)
if (fill.direction === "sell" && !params.mirrorSells)
return log(fill, "SKIP", "sell_not_mirrored");
// 3. Market cap filter
if (params.mcapMin && fill.mcap < params.mcapMin)
return log(fill, "SKIP", `mcap_below_min: ${fill.mcap}`);
if (params.mcapMax && fill.mcap > params.mcapMax)
return log(fill, "SKIP", `mcap_above_max: ${fill.mcap}`);
// 4. Liquidity floor
if (params.liqFloor && fill.liquidity < params.liqFloor)
return log(fill, "SKIP", `liquidity_below_floor: ${fill.liquidity}`);
// 5. Daily cap
const fillsToday = await db.countFillsToday(params.userId);
if (fillsToday >= params.dailyCap)
return log(fill, "SKIP", "daily_cap_reached");
// 6. Capital check
const balance = await getUSDCBalance(params.walletAddress);
if (balance < params.tradeSize)
return log(fill, "SKIP", "insufficient_usdc");
// All filters passed — enqueue execution
await executionQueue.push({ fill, params });
log(fill, "QUEUED", `size: $${params.tradeSize}`);
}
Every skipped or executed fill writes a Decision Log entry visible in the dashboard. The log includes the token, reason, and parameter values at decision time so users can audit and tune their filters.
Execution Engine
Once a fill clears the signal pipeline, the execution engine constructs and broadcasts a mirrored swap transaction. The user's locally-held private key signs the transaction. The per-trade size from the user's parameters (not the original fill amount) is used — this is proportional to the user's chosen risk tolerance, not a fixed copy of the source trade size.
Solana execution via Jupiter
All Solana swaps are routed through Jupiter Aggregator, which provides best-price routing across all major Solana liquidity pools (Raydium, Meteora, Orca, etc.).
// 1. Get quote from Jupiter v6
const quote = await fetch(
`https://quote-api.jup.ag/v6/quote?` +
`inputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` + // USDC
`&outputMint=${fill.tokenMint}` +
`&amount=${tradeSize * 1e6}` + // USDC decimals = 6
`&slippageBps=${params.slippage * 100}` +
`&maxAccounts=64`
).then(r => r.json());
// 2. Build swap transaction
const { swapTransaction } = await fetch("https://quote-api.jup.ag/v6/swap", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
quoteResponse: quote,
userPublicKey: wallet.publicKey.toString(),
prioritizationFeeLamports: {
priorityLevelWithMaxLamports: {
maxLamports: params.priorityFee * 1e9,
priorityLevel: "high"
}
}
})
}).then(r => r.json());
// 3. Sign and send
const tx = VersionedTransaction.deserialize(
Buffer.from(swapTransaction, "base64")
);
tx.sign([wallet.keypair]);
const sig = await connection.sendTransaction(tx, {
skipPreflight: false,
maxRetries: 3,
});
Priority fee: Users set a SOL priority fee cap. This is passed to Jupiter as maxLamports. Higher priority fees increase the probability of landing in the same block as the source trade, reducing execution slippage versus the original fill price.
Robinhood Chain execution via ERC-4337
Fomo users on Robinhood Chain transact via ERC-4337 smart contract wallets with EIP-7702 delegation, and their trades route through Relay's settlement layer. FomoAI mirrors the token purchase by submitting a direct DEX swap transaction from the user's EVM wallet to the same pool, using the Relay or native DEX router.
// Construct EVM swap calldata for Robinhood Chain
const provider = new ethers.JsonRpcProvider(RH_RPC);
const wallet = new ethers.Wallet(privateKey, provider);
// EIP-1559 fee estimation
const feeData = await provider.getFeeData();
const maxFee = feeData.maxFeePerGas * 120n / 100n; // 20% buffer
// Approve USDC spend
const usdc = new ethers.Contract(USDC_CONTRACT, ERC20_ABI, wallet);
await usdc.approve(DEX_ROUTER, amountIn);
// Swap USDC → token via DEX router
const router = new ethers.Contract(DEX_ROUTER, ROUTER_ABI, wallet);
const deadline = Math.floor(Date.now() / 1000) + 60;
const tx = await router.exactInputSingle({
tokenIn: USDC_CONTRACT,
tokenOut: fill.tokenAddress,
fee: 3000,
recipient: wallet.address,
deadline,
amountIn: ethers.parseUnits(String(params.tradeSize), 6),
amountOutMinimum: applySlippage(expectedOut, params.slippage),
sqrtPriceLimitX96: 0n,
}, { maxFeePerGas: maxFee, maxPriorityFeePerGas: feeData.maxPriorityFeePerGas });
await tx.wait(1);
Slippage protection: amountOutMinimum is computed from the quoted price with the user's slippage cap applied. If pool price moves more than the cap between quote and execution, the transaction reverts — protecting against sandwich attacks and front-running.
Execution Parameters
Users configure execution parameters that act as both a risk management layer and a trade filter. Each parameter can be individually toggled on or off. Disabled parameters are excluded from the filter chain — that check is skipped entirely.
| Parameter | Type | Effect on Pipeline |
|---|---|---|
| per-trade size | USDC amount | Fixed USD amount spent per mirrored buy, regardless of the source trade size. Sets exposure per fill. |
| slippage cap | % max price impact | Passed as slippageBps to Jupiter (Solana) or amountOutMinimum to the DEX router (RH Chain). Fills that cannot execute within this tolerance revert on-chain. |
| priority fee | SOL (Solana only) | Max compute unit price paid to validators. Higher values increase the likelihood of landing in the same block as the source trade, minimising price drift. |
| daily trade cap | integer (fills/day) | Hard ceiling on fills per UTC calendar day. Prevents runaway execution in high-frequency windows. |
| liquidity floor | USD ($K) | Pool TVL at fill time, queried from the DEX contract. Fills in pools below this floor are skipped — protects against thin-market manipulation. |
| min market cap | USD ($K) | Token market cap at fill time, fetched from on-chain supply × price. Fills below this floor are skipped — filters out sub-micro-cap tokens. |
| max market cap | USD ($M) | Upper market cap ceiling. Allows focusing exclusively on early-stage tokens if desired. |
| mirror sells | toggle | When enabled, the agent also mirrors SELL events from tracked wallets — submitting an exit transaction when the source wallet closes a position. |
| copy size mode | fixed / % of balance | Fixed: spend exactly N USDC per fill. Percent: spend N% of current wallet balance per fill — auto-scales with capital growth or drawdown. |
| mirror delay | milliseconds | Delay between detecting a source fill and submitting the mirror transaction. A small delay (500–2000ms) reduces sandwich exposure on Solana; zero delay maximises price proximity. |
| stop-loss | % drawdown | Per-position exit trigger. If a position's unrealised loss exceeds this percentage, the execution engine submits a sell automatically, independent of the source wallet's behaviour. |
| take-profit | % gain | Per-position profit lock. Automatically exits when unrealised gain reaches the threshold, regardless of whether the source wallet has sold. |
| max open positions | integer | Hard ceiling on concurrent open positions. New fills are skipped if the portfolio already holds this many open positions — prevents over-diversification. |
Capital gate
The agent only activates when three conditions are simultaneously true: (1) a wallet is connected or imported, (2) that wallet holds a non-zero USDC balance on Robinhood Chain, (3) at least one account to track is configured. This prevents activation without real capital behind it.
Wallet Architecture
FomoAI is fully non-custodial. Users choose one of two wallet modes:
Browser extension wallet (read mode)
Connects via window.ethereum (MetaMask, Rabby, or any EVM wallet). Used primarily to read USDC balance on Robinhood Chain. The extension wallet can optionally sign execution transactions if the user prefers not to import a private key — but this requires a manual approval in the wallet for every fill, which defeats the purpose of automated trading.
Imported session wallet (execution mode)
The user generates or imports an EVM private key. The key is held in browser memory for the duration of the session using ethers.Wallet — it is never transmitted to any server, persisted to localStorage, or written to disk. The execution engine signs transactions locally before broadcasting the signed bytes to the RPC node.
// Key is held only in memory as an ethers.Wallet instance
let localWallet = new ethers.Wallet(privateKey);
// Signing happens in-browser, only signed bytes leave the client
const signedTx = await localWallet.signTransaction(unsignedTx);
await provider.broadcastTransaction(signedTx);
Security notice: Because the agent signs transactions autonomously, the imported wallet should be a dedicated trading wallet funded only with the intended capital. Do not import a wallet holding large reserves. Users are advised to fund a fresh wallet with their desired trading amount.
Key export
Users can export the private key of the in-session wallet at any time via the Export key function. This renders it in the browser and allows it to be imported into Phantom, MetaMask, or any other EVM-compatible wallet.
Position Manager
Every executed fill opens a position entry tracked by the frontend. Positions record:
- Token address and ticker symbol
- Entry price — price at fill time, sourced from the DEX quote
- Amount held — token quantity in the user's wallet
- Current price — polled periodically from the DEX or a price oracle
- Unrealised P&L —
(currentPrice − entryPrice) × amount - Source fill — the original transaction that triggered the mirror
When the tracked wallet closes a position (sells the token), FomoAI's signal pipeline emits a SELL signal. If mirror-sells are enabled, the execution engine submits a corresponding sell transaction, closing the position.
Stop-loss and take-profit
Each position carries optional stop-loss and take-profit thresholds. When the unrealised P&L crosses either threshold, the position manager submits an exit transaction automatically — independent of whether the tracked wallet has sold.
AI Assistant
The embedded AI assistant is powered by Cloudflare Workers AI running @cf/meta/llama-3.2-3b-instruct via a Cloudflare Worker endpoint. It is session-context-aware: every message sent to the assistant is prepended with a live snapshot of the user's current state.
// Context injected before every user message
const context = [
`Wallet: ${activeAddr}`,
`USDC: $${usdgBalance.toFixed(2)}`,
`Tracking: ${trackedAccounts.join(", ")}`,
`Agent: ${agentActive ? "active" : "inactive"}`,
`Per-trade: $${tradeSize} USDC`,
`Slippage: ${slippage}%`,
`Priority fee: ${priorityFee} SOL`,
`Daily cap: ${dailyCap} fills`,
`Liq floor: $${liqFloor}K`,
`Min mcap: $${mcapMin}K`,
`Max mcap: $${mcapMax}M`,
].join(" | ");
const payload = `[${context}]\n${userMessage}`;
The assistant understands copy-trading strategy on Fomo, parameter tuning, risk management, and can answer questions like "is my slippage cap too tight for sub-$500K mcap tokens?" in the context of the user's live session values.
| Component | Detail |
|---|---|
| Model | meta/llama-3.2-3b-instruct via Cloudflare Workers AI |
| Endpoint | Cloudflare Worker · fomoai-chat.deincognigo.workers.dev |
| History window | Last 12 message turns (rolling) |
| Max tokens | 180 per reply — enforces concise responses |
| Context refresh | Every message — reads live DOM input values |
Infrastructure
- Frontend: Static HTML/JS on Cloudflare Pages (
fomoai.net) — zero server-side rendering, instant global CDN delivery - AI Worker: Cloudflare Worker running
llama-3.2-3b-instruct— serverless, deployed at every CF edge node worldwide - Detection service: Node.js persistent process maintaining long-lived WebSocket + gRPC subscriptions per tracked wallet. Runs on a dedicated server — cannot be serverless due to connection lifetime requirements
- Signal queue: Cloudflare Queues — decouples detection from execution, provides replay capability and backpressure handling
- Execution workers: Cloudflare Workers triggered by queue messages — stateless, horizontally scalable per-user, sub-second cold starts
- Key management: Private keys encrypted client-side with AES-256-GCM (password-derived key via PBKDF2) before transmission. Server holds only ciphertext; plaintext is reconstructed inside an isolated worker context at signing time
- Database: Cloudflare D1 (SQLite at the edge) — positions, fill history, decision log, user parameter storage
- Real-time push: Cloudflare Durable Objects WebSocket — persistent per-user connection pushes position updates and fill events to the dashboard in real time
- RPC: Alchemy (Robinhood Chain mainnet) · Helius or QuickNode (Solana) — configurable per-session via the custom RPC panel
// Detection service — subscription registry pattern
const registry = {
"rh-logs": { transport: "eth_subscribe", chain: "rh" },
"rh-blocks": { transport: "newHeads+getLogs", chain: "rh" },
"sol-grpc": { transport: "yellowstone-grpc", chain: "sol" },
"sol-blocks": { transport: "blockSubscribe", chain: "sol" },
};
// Three parallel subscription slots per session:
// 1. Robinhood Chain fills → rh-logs (primary) or rh-blocks (fallback)
// 2. Solana fills → sol-grpc (primary) or sol-blocks (fallback)
// 3. Relay payouts → payouts-grpc or payouts-blocks
Security Model
| Threat | Mitigation |
|---|---|
| Key exfiltration | Private keys are AES-256-GCM encrypted with a user-derived password before transmission. The server holds only ciphertext and never reconstructs the plaintext key outside an isolated execution context. |
| Sandwich / front-run | Slippage cap enforced on-chain via amountOutMinimum. Transactions that fail the slippage check revert — user loses gas only. |
| Runaway execution | Daily fill cap hard-stops agent after N fills per day. Capital gate prevents activation without verified USDC balance. |
| Thin-market manipulation | Liquidity floor filter skips fills in pools below the configured TVL threshold. |
| Rug / honeypot tokens | Min/max mcap filters narrow the token universe. On-chain contract analysis (sell tax detection, ownership renounce check) runs before execution — tokens that fail are logged and skipped. |
| API key exposure | Alchemy RPC key is inlined in client JS (acceptable for free-tier; domain-lock enforced in Alchemy dashboard). AI Worker endpoint uses CORS + Cloudflare WAF. |
What's Next
The core system is live. The following capabilities are actively being expanded:
Chain expansion
- Base and BNB Chain — detection listeners and execution routing already architected, awaiting Fomo's full rollout on both networks
- Cross-chain Relay order correlation — link a Solana PAY event to its corresponding Robinhood Chain BUY settlement via the 32-byte Relay order ID
Intelligence layer
- Wallet scoring — rank tracked wallets by historical performance: win rate, average hold duration, realised P&L, drawdown. Displayed alongside each tracked account in the dashboard
- Auto-weighting — optionally scale per-trade size by the source wallet's conviction score, so higher-performing wallets get larger mirror allocations automatically
- Token fingerprinting — on-chain contract analysis before every execution: sell tax detection, ownership renounce status, honeypot simulation. Tokens that fail are blocked and logged
Alerts and mobile
- Telegram bot — push notification for every fill, position change, and stop-loss trigger
- Mobile PWA — full dashboard accessible from any device, persistent agent session