---
name: wallet-bot-buy-gate
description: THE LEARNING from the SICAT fake-buy (3 Oct 2026): a buy is his only if he SIGNED it or a fill router logged it. isbuy.py script (verified on SI real, SICAT gift) and the numbered gate every wallet bot patch must pass. Read before touching any wallet bot, gift-fill rule, or "is this a real buy" question.
sources: [chat]
aliases: [buy gate, gift-fill gate, isbuy, signer rule, fake buy rule, wallet bot gate]
---
Fleet log in [[alert-bot-fleet-10]]. Case receipts in [[salem-wallet]]. Solana twin of the same idea (dusted wallets) in [[moonkey-alon-claim]].

## Why this file exists (repeat error, so it gets code and a gate)
- 21 Sep 2026: HELIX, FURN, ROIJIN, WISP AI landed in Salem's wallet. Fix was a PATTERN: "fomo settlement contract delivered it and no fomo router log" = gift. Correct for that route only.
- 3 Oct 2026: SICAT landed via a plain DEX router (`0x89e5db8b5aa49aa85ac63f691524311aeb649eba`, `swapExactETHForTokens`, recipient = Salem). New route, pattern missed it, alert went out as BOUGHT $503.58.
- Root cause: both fixes asked "which contract delivered it". The only question that cannot be gamed is "did HE sign it". Anyone can pay any DEX and name any recipient; nobody can sign with his key.

## THE RULE (standing, every wallet bot, every chain)
- [stated] Ben, 3 Oct 2026: "make it a learning so that it never happens again."
- A token arriving in a tracked wallet is HIS BUY only if (a) `tx.from` == the tracked wallet, or (b) a trusted relayed-fill router in `config.settlement_routers` (fomo `0x039ec98a76f111092d4751365ff09dd2aec301e8`) emitted a log in that tx.
- Everything else arriving is a GIFT: dropped, never "BOUGHT", regardless of who paid, which DEX, which contract delivered, how big the amount is.
- Sells never need this: nobody can make his tokens leave without his key.
- Solana twin: the claimed buyer must be a `signer:true` account key, see [[moonkey-alon-claim]].

## isbuy.py, verified 3 Oct 2026 on the failing case
Run before any "real buy?" verdict and as the test for any wallet-bot patch. Expected on Salem `0xb8f305f27ccc406373de0082cc06cb1d065504ea`:
- `0x8e8bd7c7bca28181203b25ce9aec4d94b0e675e7de5693f33cfd9a391808bb9a` (SI $116,441) -> HIS TRADE (he signed)
- `0xd8d003ab436e02e71bca5827dd01301f03602351b475d1b7f2fbbb3f5801c306` (SICAT $503.58) -> GIFT (sender `0x2eeea2926f6a831238fd0bec6e30ec01a80cfc46`)
- `0x928a64358403e70df0fc515db83d85611e7e62eeffae46d72568a25f419588b7` (SCHIFFY 19 Sep) -> HIS TRADE (he signed)

```python
#!/usr/bin/env python3
"""isbuy.py <wallet> <txhash> [routers...]"""
import json,sys,urllib.request
RPC="https://rpc.mainnet.chain.robinhood.com"
UA={"User-Agent":"Mozilla/5.0 (Macintosh) AppleWebKit/537.36 Chrome/120 Safari/537.36","Content-Type":"application/json"}
FOMO_ROUTER="0x039ec98a76f111092d4751365ff09dd2aec301e8"
T="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
def call(m,p):
    r=urllib.request.Request(RPC,json.dumps({"jsonrpc":"2.0","id":1,"method":m,"params":p}).encode(),UA)
    return json.load(urllib.request.urlopen(r,timeout=30))["result"]
def verdict(wallet,txh,routers=(FOMO_ROUTER,)):
    w=wallet.lower(); tx=call("eth_getTransactionByHash",[txh]); rc=call("eth_getTransactionReceipt",[txh])
    logs=rc["logs"]; emitters={l["address"].lower() for l in logs}
    xfers=[l for l in logs if l["topics"][0]==T and len(l["topics"])==3]
    out=any(("0x"+l["topics"][1][26:]).lower()==w for l in xfers)
    inn=any(("0x"+l["topics"][2][26:]).lower()==w for l in xfers)
    signed=tx["from"].lower()==w
    relayed=any(r.lower() in emitters for r in routers)
    if signed: return "HIS TRADE (he signed the tx)"
    if relayed: return "HIS TRADE (relayed fill, router log present)"
    if inn and not out: return "GIFT (sender %s, he did not sign, no fill router)"%tx["from"]
    if out: return "NOT HIS: tokens left without his signature, inspect"
    return "NOT HIS TX"
if __name__=="__main__":
    w,h=sys.argv[1],sys.argv[2]; rts=tuple(sys.argv[3:]) or (FOMO_ROUTER,)
    print(verdict(w,h,rts))
```

## PRE-DELIVERY GATE for any wallet-bot buy/sell patch (all 10 bots: alfiewatch, chjokkawatch, dougwatch, ghostwatch, grailwatch, neetwatch, pixelalert, salemwatch, ubermwatch, unicorn)
1. The classify() rule is written as SIGNER-or-ROUTER, not as a delivery pattern. If the patch names a delivery contract, a DEX, or a counterparty as the test, it is the 21 Sep mistake again: rewrite.
2. Install `isbuy.py` at `/opt/common/isbuy.py` on the droplet and run the three Salem txs above: SI and SCHIFFY = HIS TRADE, SICAT = GIFT. Anything else, stop.
3. Replay the same three through the bot's own `emit()` with `tg.send` stubbed: SI and SCHIFFY alert, SICAT silent.
4. Same patch on all 10 bots (byte-identical tracker.py is the norm), `.bak-signer-*` kept, `ast.parse` before write, base64 over ssh.
5. Restart, then prove lag: each bot's `state.json last_block` within 1 minute of `eth_blockNumber`.
6. Log the run in [[alert-bot-fleet-10]] (or its successor) and update [[salem-wallet]] status from PENDING to APPLIED.

## Status
- Rule and script: written and verified 3 Oct 2026 (phone chat, cloud container).
- Droplet patch: PENDING. Needs the Mac/Cowork session: "apply the gift-fill signer patch to the wallet bots", then walk the gate above.
