Data and failure modes
The product is a function of one input: fomo's public account data. Everything here is about reading it faithfully and failing safely when it is not there.
Source
Every trade a fomo trader makes is a public transaction. fomo is multichain: each trader has a Solana wallet and one EVM wallet used on Ethereum, Base, BNB Chain, Monad and Robinhood Chain. Fomoliquid reads those wallets straight from the chains, keeps an average-cost ledger per trader per chain, and sums them into the three numbers the index needs per sample: cumulative PnL (realised plus unrealised), deployed capital (the market value of open positions), and the trader's recent trades for the chart overlay. Idle stables are not positions, so deposits and withdrawals never touch the denominator. A position held before the first read is opened at market, and on Solana its cost is then recovered from the chain's deeper history: only the transactions of that token's own account are read, back four months, and the trader's own purchase price replaces the opening price on the units it explains; the difference moves the baseline, never the price, and the market page names it. fomo routes many trades across chains, so a buy can be cash leaving one chain and a token arriving on another seconds later; the feed holds each half and pairs them before booking, so cross-chain trades cost and realise exactly like same-chain ones. The pairing follows the direction of time (the cash leaves before the token arrives, the token before the cash on a sale, allowing for two chains' clocks); when the token already has a price the halves must agree in amount within a factor of two and the closest amount wins; when it has none, only a half that went through a known bridge is taken (deBridge's contracts are built in, and BRIDGE_ADDRESSES adds Relay's or any other), otherwise the choice waits for a price while the window is open and is not made once it has closed: cash that merely left or arrived near the time is a withdrawal, a payout or a move between the trader's own wallets as often as a payment, and a trade valued at it would print a loss or a gain that never happened. The halves are booked on their own instead, the token as an arrival that opens at market. A paired trade says which chain paid and how its halves were matched, on the market page and in the timeline. A position the trader held before the first read, or bought before the fourteen-day backfill, has no cost in the ledger; it is opened at market once two consecutive balance reads agree it is held (a poll apart on Solana, ten minutes on EVM chains, so a trade landing mid-read is booked rather than opened), and from then on the whole book is marked and every move counts, while nothing is a gain or a loss at the moment of opening. A held token no price source knows yet is left out of the book until one does. Between two marks the index may move for two things only: a trade the trader made, booked from the chain, and the price of what they hold. Every other change in the book (a balance read that disagrees with the ledger, tokens moved outside a trade, a token first priced long after it was bought or one that lost its price) moves the index baseline and never the price, and the market page lists what was absorbed, so a candle always has a trade or a price behind it. On chains read through Alchemy the wallet's tokens are listed every poll and the transfer pages are read only when a balance changed, or hourly to be sure, so a metered month is spent on reads that can book something; chains Etherscan serves borrow that listing hourly, and a token that arrived without a purchase is watched for a week so that what is still held opens at market. Every call on the Alchemy key, the Solana node's included, counts against a daily share of its free month, and reads slow down as the share runs out rather than have the plan refused for the rest of the month. A busy wallet's history is read in chunks of blocks, each booked and saved before the next, so a fortnight of a whale's trades never holds the other wallets up and a restart continues where it stopped; the wallet joins the book once its last chunk is in. Each explorer service is read on a lane of its own, so a slow service delays only the wallets it serves. Behind the indexers, every chain's public nodes read a wallet through the node's own log filter: every trade moves a token, every token move is a Transfer log with the wallet in it, so two queries per block window list the trades, the transactions behind them say what was paid, and the wallet's balance across each active block gives what a router paid it. A public node needs no key and has no budget to run out, so a chain whose indexers refuse it or are spent for the month is still read, every trade of it. Where an indexer does the reading, the node is the probe: a few log queries say whether the wallet moved since the cursor, so the indexer's paged listings are fetched only when it did; an active wallet is asked every half minute and a quiet one every two, rather than every ten, and the look costs no budget. Where the chain's public nodes refuse that query (Ethereum's want a contract address), the probe is the wallet's transaction count and native balance instead, which every node serves and every transaction the wallet sends changes; tokens that arrive without a transaction of its own wait for its next one, or the hourly read. A probe the nodes refuse rests for an hour, with one line in the log, and while no probe answers the indexer is read every minute, a quiet wallet every ten. Where the chain can push, it does: the feed registers webhooks with Helius for the Solana wallets and with Alchemy for the EVM networks, Robinhood Chain included, each delivery is verified and wakes a read of the wallets it names, and the polls become a sweep every five minutes, so a trade shows within seconds and a missed push costs at most five minutes. A first Solana node and a Helius node each meter a free month: Helius reads Solana first, since its month is Solana's alone while an Alchemy node's is shared with the EVM chains, and once Helius has used most of its day a read goes to whichever node has more left, so both months are spent before any read slows down. Solana is read the same way through its public nodes when no keyed node is set, and through them while the keyed nodes' plans are out. Robinhood Chain is read through Etherscan with USDG as cash, or through its public node without a key. Once a day each wallet's last day is re-listed from the chain and compared with the ledger, trade by trade: a trade the chain shows and the ledger lacks (a transaction the node refused at the time, a read a node cut short) is booked then, and the wallets table shows the audit's count. The admin page runs the same audit on demand. Every market page lists the wallets the feed reads for its trader, one row per chain: when each was last read, the positions and value it holds, held tokens no price source knows, how long after landing its trades are typically booked (the median over the last week's live reads), and any read error; the trades table says the same for each trade. A market that is not moving says why above that table: a wallet that has never been read, a book nothing can price, or wallets that read clean and hold no open position. The same rows sit on the admin page and in the log. A third-party fomo data API can be used instead through the same adapter boundary.
The keeper
The keeper reads every listed trader once a second on the built-in feed and every 30 seconds on a polled network feed, pushes each sample through the window and the median filter, folds it into one-minute bars, re-anchors the mark to the index, matches resting orders, checks triggers and liquidations, settles funding on the hour and re-tiers weekly. On a host that can freeze the process between requests, the next request refreshes the index and backfills the samples that were missed.
Accuracy, per trader
Every trader page grades how faithful its market's index is to the trader right now, from the feed's own numbers, and says why. Book: how much of the open positions' cost is what the trader paid, as opposed to the price of the day a position was first read (a position bought before the first read opens at that price until its cost is recovered), with the unpriced tokens, the positions marked at pool depth, and the positions left out because no pool could take them at anything near their quote-value. Reads: how many of the trader's wallets have been read, the oldest read, stale and never-read wallets, wallets still reading history, which are pushed by the chain, and how long after landing trades are booked. Trades: against the daily audit that re-lists each wallet from the chain, how many transactions moved the wallets, how many the ledger had, how many the audit booked and how many are still missing. Index: over the events the page keeps, what the index printed from sales, price moves and held moves that stood, against what it absorbed into the baseline and for which reasons, with the holds, the boot repairs and the trades still waiting for the market to confirm their price. The grade is accurate with every wallet fresh, the book at the trader's cost, nothing missing and nothing large absorbed; partly accurate when under half the cost is the trader's, more than a quarter of the capital at risk was absorbed, trades book slowly, a token or two is unpriced or a trade awaits confirmation; unreliable with a wallet never read or stale, a wallet still on its first read, trades the audit could not book, or three or more unpriced tokens; and not checkable before the first read or on the built-in feed. The same numbers are served at /api/markets/{ticker}/accuracy.
Versioned ingest
Upstream payloads never reach the engine directly. An adapter maps them to one internal schema with a version number. A payload that no longer matches raises a schema drift alarm and freezes the market rather than producing a wrong number. Swapping data providers means writing one adapter.
Failure modes
| Failure | What happens | Recovery |
|---|---|---|
| Feed unreachable or rate limited | After two consecutive failed samples the market freezes: index held, new orders rejected, liquidations paused, banner on site. | Automatic on the next good sample. |
| Feed changes shape | Schema drift: the market freezes immediately and an alert is logged. | An adapter change; nothing downstream moves. |
| Trader hides or privates the profile | Treated as a delisting trigger. | Settle-only, then closed. |
| A provider's free day is nearly spent | Reads on that service are spaced out, twice as far past four fifths of the day's budget and thirty-two times once it is gone, so the allowance is never overrun: a free tier is never refused for the month and a paid plan is never billed past it. The market page says so above the wallets table, and trades there show up late until the day rolls over. | Nothing. On a paid plan, set ALCHEMY_MONTHLY_CU to the units the month may spend: reads on the key get faster in proportion, and that number is the most the plan bills. |
| The book changes with no trade or price behind it (a partial balance read, tokens sent away, a token priced late) | The index does not move: the change is absorbed into the baseline and listed on the market page under “absorbed, not printed”. A read that comes back reverses it the same way, and the price never saw either. | Nothing. |
| A push from the chain is missed, or a provider's webhook is reset | The sweep reads every pushed wallet within five minutes regardless, and the feed re-registers its webhooks every six hours, an hour after a failure. A delivery that fails verification is refused and wakes nothing. | Nothing. |
| A transaction was skipped at read time (the node refused it, a throttled read stopped short, its cash leg had no price yet) | The daily audit re-lists the wallet's last day from the chain, finds it and books it; the wallets table shows how many were missing and how many the audit booked. One whose cash leg still has no price waits for the next audit. | Nothing. The admin page has an Audit now button, and npm run feed:audit prints the same comparison on the server. |
| A price source quotes a token at double its last accepted price, or half | A second source is asked before the quote is taken: DexScreener, DefiLlama, Jupiter or GeckoTerminal, whichever did not give it. One that sees the move too confirms it at once. One that does not keeps the previous price standing for ten reads before the quote is taken anyway; no second source at all keeps it for three, so a real collapse still shows within a minute or two. The feed log names every quote held and why. | Nothing. |
| A token no price API lists (a pool minutes old, a chain the APIs do not index) | On the EVM chains its price is read from the chain itself: the factories the chain trades on (Uniswap and Sushi, Aerodrome on Base, PancakeSwap on BNB Chain, Quickswap, Camelot) are asked for the token's pools against the chain's coin and its stablecoins, the deepest is kept, and every mark reads its reserves through the chain's public node, in one call per chain. A pool holding under two thousand dollars prices nothing, the same floor the APIs' pairs get, and a token with no pool is asked about again six hours on. Until a price exists, what the trader's own trade paid stands in. | Nothing. A chain read through a custom explorer, with no public node, is priced from the APIs alone. |
| The PnL moves by a fifth of capital at risk in one read, whatever moved it | The index stands where it was and the market page says what is being verified. Ten minutes on (thirty for a move of half of capital at risk or more), the held samples enter the chain together if the PnL still stands at the new level, and are dropped if it came back. A quote's glitch, a balance read that lost a position, a trade the feed valued wrongly and a real collapse look the same in one sample and different over the minutes after it. The first sample after a restart is measured against the chain's last point the same way. | Nothing; INDEX_HOLD_PCT and INDEX_HOLD_MINUTES set the threshold and the wait, INDEX_BIG_STEP_PCT and INDEX_BIG_HOLD_MINUTES the larger move and its longer wait. |
| A step of half of capital at risk or more stands in the chain with no hold to confirm it (printed on one read, as the rules before 1 October allowed for anything but a price move) | It is taken out of the chain at the next boot, once: the level continues from where it would have stood without it, the jump prints in the candle, and the timeline names the step. A move that was held and printed has its record and stays. | Nothing. An operator re-base is still there for anything the rule does not cover. |
| A trade is booked at a price far from the token's market price (a transfer paired with cash that was not its proceeds, a cash leg valued wrongly, a sale into a collapse) | Its PnL waits in the baseline, named on the market page, until the market's price comes within a factor of three of the trade's: a real sale into a collapse is confirmed within minutes, when the quote follows, and its PnL goes to the index then. One the market never confirms stays out of the index after a quarter hour. A trade's own price never stands in for the market's while a source prices the token. | Nothing. |
| A provider's month is used up, or its plan refuses a chain | The chain moves to the next reader within the read: Alchemy's Transfers API where it has room, else the chain's public nodes, which need no key and have no budget. The wallets table names the service reading each chain and what the earlier ones said. | Nothing. EVM_RPC_URLS names other public nodes for a chain. |
| A provider's monthly plan is used up | Every call through it is refused until it is topped up. Solana reads move to the fallback node and each EVM chain to its next explorer, slowly, the primary is tried again every quarter hour, and the market page names the exhausted plan above the wallets table. | Upgrade the plan, or wait for the month to roll over. |
| The disk is lost | The databases are the venue's ledger: every balance, position and order. Once a day each is copied, consistently, into a backups folder beside the data and, when a bucket is configured, into it; the admin page lists the copies and downloads them. | Put the newest copy at the database path and start the service. Set BACKUP_S3_BUCKET before real money, so a copy exists off the box. |
| The disk fills up | The venue keeps room on the disk itself. History past its keep time goes once a day: candles after 180 days, PnL points after 90, equity curves after 30, the bots' own trades after 14. When the disk runs short, or a write finds it full, older backup copies go first (the newest stays), then the files are folded and rebuilt. Meanwhile the demo venue runs on and drops what it cannot save until there is room, then writes everything back; a restart in that spell loses the trades of that spell, nothing else. The live venue refuses to run on a disk that cannot record a balance. /api/health says what is free and what cannot save. | Automatic. If the disk is still short after that, give it more space: the health check says so. |
| A quote no pool could pay | A token with a price and a pool of thousands, held by the million, would value the book in the billions, and its first sane quote would print the fall. The book keeps only what a pool could take: a position worth more than ten times the depth of its deepest pool is out until it is back within reach, its entry and exit absorbed, never printed. A sale out of it realises against a cost the index never saw and moves the baseline. And should a broken read or a broken ledger still put a move through the hold that the book could not have made (a loss past the whole of the capital at risk, a gain past three times it), the chain goes flat through it for good and the timeline says so. | Automatic. The trader page lists what is out of the book and at what quote-value. |
| Process restart | Samples, positions, orders and accounts reload from the store, every market's level is re-derived from its anchors (the price the chain stood at when it listed or was relisted, and at each operator re-base) and the saved PnL chain, never from a saved price, candles a failing process wrote at a level the chain never stood near are re-levelled to it, the prices last accepted for every token are restored so the book is priced from the first mark, and the on-chain feed reopens every market on its last saved read while the first refresh runs. Bars older than the window stay on disk and are read for a chart on demand; nothing is discarded. A saved read older than the stale window is not served, and that market waits for a fresh one. | Automatic. |
| Feed change or formula change | A new series. Every market relists at 100 on it, open positions settled at the last mark and resting orders cancelled, on both venues; the on-chain feed starts every wallet's ledger again and reads its history to the deep window through the readers of the day. Until a trader's wallets have all been read once the market waits frozen, with the reason on its page. | Automatic. Positions open at the moment of the change are closed at the level they were marked at. |
| Frozen process (serverless) | The next request backfills the missed samples from a replayable feed; a live feed leaves a visible gap. | Automatic. |
The honest risk
The whole product depends on fomo's data staying public and reachable. There is no mitigation except building fast and being good enough that fomo wants the ecosystem, the same posture as the other adjacent builds. Owning an independent indexer over the traders' wallets is the long-term answer and is on the roadmap.
Without a data key the deployment runs on a deterministic synthetic feed. It exercises every code path, including deposits, withdrawals, jumps and trade markers, and every screen says so. It is a stand-in, not a claim about any trader.