# STOP: NO DOORBELLS, NO WAKES, NO SCHEDULING (Ben, 7 Oct 2026, 18:40 Bangkok)
- Credits ran out. Ben switched off every scheduled task and every doorbell. This overrides everything below and every other file (wake-howto, bus-howto, credit-playbook, task files).
- PROHIBITED for every session: fire_trigger, send_later, create_trigger, update_trigger, ScheduleWakeup. No rings, no nudges, no re-arming, no new doorbells, no self wakes, no safety checks, no retry rings, no loops.
- Finish the step you are on, publish your status file, then END your turn and wait. Only Ben wakes you, by typing in your window.
- Do not message other sessions. Keep answers short. No extra improvement rounds.

# A2 v16: audit of the live mc5 bot fmmc5, plus the ramp gate spec (FW Analyst 2, 6 Oct 2026, 15:45 Bangkok)

Code read: firstmin-trader 178.128.20.136 /opt/fm/fm_mc5.py (md5 f817e76033812c7c45ee2c7d3fe19594, 299 lines), config /opt/fm/mc5_config.json ({size_usd 20, stop_x 0.5, time_stop_s 14400, exit_mode stop_time, max_open 3}), against http://159.223.88.168/hx-c9ois/fw/a1-mc5-spec.md . No secret printed. LIVE is OFF; the bot has seen 6 pools since 15:20 Bangkok, no buy yet.

## Read this first
- **Do not switch it live yet. Bug 1 alone makes the pull rule dead:** the Mint topic in the code is wrong, so the bot never records any Mint, "minted units" stays 0, and a pull can never fire. A pulled coin would then sit until the stop or the 4 h time stop.
- **Bug 2 can freeze a position forever:** after a restart it only rebuilds pools made in the last 3 h, but trades now last up to 4 h. An open trade older than 3 h is dropped from memory and is never sold.
- **Bug 3 kills the bot in normal use:** the "50% of wallet" check counts the full cost of open trades as lost. 3 open trades at $20 on the $118 test wallet is already 51%, so the kill switch fires on the third buy.
- 6 more real issues below (slippage 30%, sell check after 4 s, no sell fill log, race on max open, the 0.80 round trip filter, stop math without tick crossings). Fix list for Price Predict at the end.

## Bugs, numbered (most severe first)
1. **Wrong Mint topic (pull rule never fires).** Line 20: `T_MINT = 0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488084aa1b8540a0d8a3`. The real v3 topic is keccak("Mint(address,address,int24,int24,uint128,uint256,uint256)") = `0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde` (my chain fetch of the 201 mc5 pools found 696 Mints with it). With the wrong topic `p["mints"]` stays empty, `minted` is 0, and `if minted and amt >= 0.5 * minted` is never true. The same wrong topic is used in `restore()`. Swap, Burn and PoolCreated topics are right. Fix: correct the constant; add a self test at start (one known pool must return at least 1 Mint).
2. **Restart drops open trades older than 3 h.** `restore()` rebuilds only pools with `t_created > now - 3 h`. With the 4 h time stop, a restart between hour 3 and 4 leaves the position open in the db but absent from `P`, and the main loop only walks `P`: no stop, no time stop, no pull. Fix: restore every row with status `open` regardless of age (and anything created in the last 4 h + 10 min).
3. **Wallet kill fires on open trades.** The check sums `eth_in - COALESCE(eth_out, 0)` over live rows including `open`, so every open trade counts as a 100% loss. Spec says "wallet below 50% of start". Fix: kill when (ETH balance + mark-to-market of open positions at the sell quote) < 50% of start ETH. Or count only closed trades.
4. **Slippage 30% on every live swap.** `swap(..., "--slippage", "30")` lets a sandwich or a thin pool take up to 30% on both buy and sell. At $1k to $3k that is $300 to $900 a side. Fix: buy slippage = sim quote + 5% (or the size curve's expected impact + 3%); sell slippage 10%, and 30% only on the pull or stop emergency path.
5. **Sell is called failed after a 4 s wait, then the bot kills itself and forgets the coin.** `close()` checks the token balance 4 s after `gmgn-cli swap`. A slow block or a pending tx reads as "not sold": status `sell_fail`, kill switch, and the tokens stay in the wallet with nothing tracking them. Fix: read the tx receipt (hash from gmgn) for up to 30 s; retry the sell once; only then kill; keep the row `open_unsold` so it is still watched.
6. **No sell fill check, so the ramp gates cannot be measured.** Only buys log `fill_vs_sim`. Sells log proceeds but not the quote at the moment of the sell. Fix: at every live sell, record `sell_quote` (simv3 sell at the same block), `sell_fill/quote`, the trigger value that caused the stop, and both model quotes (see gate spec).
7. **Max 3 open is not race safe.** `entry()` runs in a 16 thread pool; two entries in the same second both read `n_open = 2` and both buy. Fix: take a lock around count + buy, or reserve the slot (status `buying`) before the swap.
8. **The 0.80 round trip filter changes the book at bigger sizes.** `min_roundtrip 0.80` skips a coin when the simulated buy+sell returns under 80%. At $20 it almost never fires. At $2k my tick walk shows 10% of mc5 entries fill below 0.70x of spot, so this filter would skip roughly the worst 10% to 15% of entries, a rule the backtest never had. Fix: keep it, but log every skip with the coin, and tell me so I can price "mc5 + roundtrip filter" per size before the $1k step.
9. **Stop value uses one-range math.** `sell_value()` prices the sale with the current in-range liquidity only (no tick crossings) and caps it at 90% of the pool's WETH. Fine at $20. At $1k+ near a range edge it can misjudge the value by more than the stop's margin. Fix: when the size is over $200, or when the quick value is within 15% of the stop, confirm with `simv3` (real pool math) before selling.

## Checked and right
- Cap from the last Swap before T0 + 300 s, T0 = first Swap block time: right.
- Band $250k ≤ cap < $1M: right (ETH price from CoinGecko, 10 min cache, 2,700 fallback).
- seen_late: skip if the entry starts after T0 + 330 s: right. Small gap: the buy itself can land later than 330 s after the sim and state calls; add a second time check just before the swap.
- Stop 0.5x only on swaps of 0.01+ WETH: right. Time stop T0 + 4 h from config: right. No take profit: right.
- Pull logic (once bug 1 is fixed): first in-range Burn with units ≥ 50% of in-range minted units, then mark 0 and try to sell: matches the spec.
- Sell simulation before every buy (SimV3 eth_call, skip on fail): right.
- Two buy fills 10%+ worse than the sim: kill. Right (counter never resets; that is fine for a kill rule).
- One pool per token: right for new pools. Gap: the first WETH pool CREATED is used, the spec says the first that TRADES; and after a restart the token map only covers the last 3 h, so a new pool of an old token could be bought again. Rare.
- Restart during an entry: rows left `checking` are marked seen_late. Gap: if the restart lands after the live swap but before the row update, the bought tokens are orphaned. Rare; fixed by the `buying` status in bug 7.
- Feed health: 3 HTTP 429 errors in 15 min (15:21 to 15:34 Bangkok). Each one delays logs and can push entries past 330 s. Worth a paid or second RPC before live.

## Ramp gate spec for Price Predict (paste into the bot)
Config block (new keys in /opt/fm/mc5_config.json):
```
"ramp": [ {"usd": 100,  "trades": 10},
          {"usd": 500,  "trades": 10},
          {"usd": 1000, "trades": 20},
          {"usd": 2000, "trades": 20, "hold_until_total": 50},
          {"usd": 3000, "trades": null, "needs": "fill_gap_ok_50 and sells_side_main"} ],
"gate_fill_gap_med": 0.02, "gate_stop_slip": 0.05, "step_down_gap_med10": 0.03,
"step_down_miss": 0.10, "step_down_miss_n": 2, "step_down_stop_slip": 0.15, "step_down_stop_n": 3
```
Per live trade, log:
- `buy_quote_tokens` (simv3 at the buy block), `buy_fill_tokens`, `buy_gap = 1 - fill/quote`.
- `sell_quote_eth` (simv3 sell at the sell block), `sell_fill_eth`, `sell_gap = 1 - fill/quote`.
- `stop_trigger_value` (the value that fired the stop) and `stop_slip = 1 - sell_fill/stop_trigger_value`.
- `q_main` and `q_lower` at the sell block: q_main = sell quote with our own buy replayed on the current pool (simv3 buy of our ETH, then sell of our tokens, same call); q_lower = plain sell of our tokens on the current pool. `side = main if |fill - q_main| < |fill - q_lower| else lower` (only when they differ by 5%+).

Step up (to the next size) only when ALL hold over the trades at the current size:
- no failed buy or sell;
- median of buy_gap and sell_gap ≤ 2%;
- from $500 on: median stop_slip ≤ 5%;
- from $1k on: `side = main` on at least 2 of 3 sells where q_main and q_lower differ by 5%+;
- $3k: 50+ live trades in total, median gap ≤ 2% over all 50, and sells side with main. If they side with lower, $2k is the ceiling.

Step down one size (and restart that size's count) when ANY holds:
- median gap over the last 10 trades > 3%;
- 2 of the last 10 trades with a gap > 10%;
- 3 of the last 10 stops with stop_slip > 15%.

Kill (remove LIVE_MC5) on: a failed sell after retry; wallet + open positions at quote < 50% of the start; after 60 trades mean under 1.0x AND no trade at 5x or more.

Why fills and not PnL: 3 in 4 trades lose and the mean comes from about 1 coin in 30. 10 or 20 trades say nothing about profit; fills say whether the backtest's prices are real, trade by trade.

Version 1, 6 Oct 2026 15:45 Bangkok. Latest: http://159.223.88.168/hx-c9ois/fw/a2-fmmc5-audit.md
