la plupart des gens découvrent Grok Bot via la capture d'écran de PnL de quelqu'un d'autre, l'ouvrent, le pointent sur un graphique et perdent leurs premiers 50 $ sur un token qui était déjà mort avant même qu'ils ne l'achètent. le problème n'était pas le bot. personne ne lui avait expliqué à quoi ressemble un token mort.
voici la configuration que je reconstruirais ce soir si je devais tout reprendre à zéro. un bot, un dossier, dix petits fichiers. il ne chasse pas les x100. son rôle principal est d'éliminer des tokens, et les rares qui survivent ont encore besoin que Grok lise X avant que le moindre dollar ne bouge.
les étapes 01 à 07 correspondent à la première soirée : le bot, la clé, les limites, les sorties. de 08 à 12, c'est le filtre anti-rug, 14 vérifications, chacune écrite sous forme de code. 13 et 14 mettent le tout en pilote automatique, d'abord sur le papier.
si vous avez lu mon article sur le desk à huit agents, voici le socle sur lequel ce desk repose.
https://x.com/0xboan/status/2092226208234647626
comment l'utiliser : ouvrez votre Grok Bot, collez l'intégralité de cet article et dites « construis ce desk dans /workspace/desk, mode paper, demande-moi avant que quoi que ce soit touche un wallet. » il écrit chaque fichier lui-même, sur son propre ordinateur.
la règle d'or à retenir : le code élimine, Grok lit X, vous signez. personne sur le desk ne fait le travail d'un autre.
01. réunissez quatre choses avant de commencer
quatre choses. pour chacune : ce que c'est, ce que vous faites ce soir, et pourquoi le desk en a besoin.
1. Grok Bot
ce que c'est. l'application où vous embauchez des bots nommés. chacun travaille sur un ordinateur cloud équipé d'un navigateur, de fichiers et d'un terminal, et continue de tourner même quand votre PC portable est fermé.
ce que vous faites. téléchargez-le sur x.ai/bot, connectez-vous, choisissez Create your own.
pourquoi. tout le desk vit sur cet ordinateur. fichiers, routines, alertes, tout est au même endroit, accessible depuis votre téléphone.
2. une clé API xAI
ce que c'est. un accès payant à Grok depuis du code, avec la recherche X intégrée.
ce que vous faites. allez sur console.x.ai, API Keys, créez-en une. ne la collez nulle part pour l'instant, l'étape 03 indique où elle va.
pourquoi. une routine ne peut pas rester assise à réfléchir sur quarante tokens dans un chat. un script qui fait un appel par token le peut, et il journalise chaque réponse dans le même format.
3. Rugcheck et DexScreener
ce que c'est. deux API publiques gratuites. Rugcheck lit le contrat : autorités, LP, holders, insiders, le wallet du dev. DexScreener lit la pool : profondeur, ancienneté, achats contre ventes.
ce que vous faites. rien. pas d'inscription, pas de clé pour les lectures.
pourquoi. les faits passent avant les opinions. une autorité de mint ouverte n'est pas une question de jugement, c'est un fait, et ce sont les faits qui tuent en premier.
4. un burner wallet, mais pas ce soir
ce que c'est. un wallet Solana tout neuf qui n'a jamais rien contenu d'autre.
ce que vous faites. rien pour l'instant. la première semaine se joue sur le papier.
pourquoi. le desk ne détient jamais de clé. quand vous passerez en live, il vous dira quoi acheter et vous signerez vous-même.
02. embauchez un seul bot, pas huit
mon desk fait tourner huit bots aujourd'hui. ça n'a pas commencé comme ça, et le vôtre ne devrait pas non plus. huit bots dès le premier jour, c'est huit trucs à débugger en même temps. un bot avec une seule mission vous montrera ce dont les sept autres auront besoin plus tard.
choisissez Create your own et donnez-lui ceci, mot pour mot :
1Nom : RUG DESK2Mission : Desk memecoin Solana, d'abord sur le papier3Description : Construis et fais tourner le desk dans /workspace/desk à partir de l'article que je colle.4Chaque token passe par check.py, sans exception, sans raccourci.5Tu n'achètes, ne vends, ne signes ni ne déplaces jamais de fonds. Tu ne modifies jamais config.py sauf si je te le demande6dans ce chat. Tu n'abaisses jamais un seuil de filtre pour laisser passer un token.7Signale chaque élimination avec sa raison, pas ton opinion sur le token.8Quand une source est hors ligne, dis laquelle et passe. Ne devine jamais un chiffre manquant.
les deux dernières lignes comptent plus que les cinq premières. un bot qui devine un chiffre manquant finira par en deviner un qui vous coûtera cher.

03. gardez la clé hors du chat
tout ce que vous tapez à un bot atterrit dans un historique. la clé doit donc aller dans un fichier, tapée par vous, sur l'ordinateur du bot.
ouvrez l'ordinateur du bot, cliquez sur Take over, ouvrez le terminal et lancez ceci. la commande demande la clé sans l'afficher, elle n'apparaîtra donc pas non plus dans l'historique :
1mkdir -p /workspace/desk && cd /workspace/desk2read -rs -p "xai key: " K && printf 'XAI_API_KEY=%s\n' "$K" > .env && unset K3chmod 600 .env4pip install requests
ensuite, prouvez que la clé fonctionne avant toute chose. un seul appel, pour ne jamais confondre plus tard une mauvaise clé avec un mauvais prompt :
1curl -s https://api.x.ai/v1/responses \2 -H "Authorization: Bearer $(cut -d= -f2 .env)" \3 -H "Content-Type: application/json" \4 -d '{"model":"grok-4.7","input":"reply with OK"}'56-> ... "type":"output_text","text":"OK" ...
cliquez sur I'm done et rendez-lui l'ordinateur. à partir de là, le bot peut faire tourner le desk, sans jamais voir la clé dans le chat.

04. visualisez tout l'entonnoir avant d'en construire la moindre partie
huit passes. les gratuites tournent en premier. seul ce qui survit aux passes gratuites coûte un appel Grok, et seul ce que Grok marque BUY coûte de l'argent. cette semaine, même pas.
10. PISTE un CA venu de n'importe où : un appel, un post, scan.py -> un mint21. LIMITES book.py : perte quotidienne, slots ouverts, cooldown -> gratuit, aucun réseau32. FAITS rugcheck.py : 10 éliminations sèches -> mint ouvert, LP déverrouillée, top 10 trop lourd meurent ici43. POOL market.py : profondeur, ancienneté, ventes -> pools fines et honeypots meurent ici54. JUGE grok.py + recherche X, un appel -> BUY ou SKIP, une raison, un risque65. FILL paper : book.py au prix live -> live : vous signez vous-même76. SUIVI watch.py toutes les 10 minutes -> stop, ladder, trail, time stop87. RAPPORT report.py à 21h30 -> ce qui est mort, ce qui a payé, ce qu'il faut remettre en question
une autorité de mint ouverte n'est pas une question de jugement. c'est un fait, et les faits sont gratuits. Grok ne voit jamais que les tokens ayant déjà passé toutes les vérifications qu'un script peut faire.

05. mettez tous les chiffres dans un seul fichier
enregistrez sous config.py. tous les chiffres du desk vivent ici, ainsi quand vous réajustez, vous ne modifiez qu'un seul fichier. voici les miens, réglés pour un ticket de 25 $. ce n'est pas une recommandation, c'est un point de départ que vous ferez évoluer.
1# config.py2# tous les chiffres du desk vivent ici. quand vous réajustez, vous ne modifiez que ce fichier.34MODEL = "grok-4.7" # vérifiez console.x.ai -> Models si l'API le rejette5PAPER = True # semaine 1 : exécutions fictives aux prix réels. ne changez qu'après avoir lu le log67TICKET_USD = 25 # fixe. jamais calibré sur le dernier gain8MAX_OPEN = 4 # 4 x 25 $ = 100 $ max sur le marché9MAX_ENTRIES_PER_DAY = 610DAILY_LOSS_HALT_USD = 50 # perte réalisée du jour atteint ça -> aucune nouvelle entrée avant demain11COOLDOWN_HOURS = 24 # pas de retour sur un token que vous venez de quitter1213FACTS = { # le filtre anti-rug. code uniquement, aucun modèle14 "max_risk_score": 40, # score_normalised rugcheck, 0-100, plus haut = plus risqué15 "min_lp_locked_pct": 90, # plus grosse pool : LP verrouillée ou brûlée16 "max_top10_pct": 30, # top 10 holders, pools et lockers exclus17 "max_dev_pct": 5, # supply encore sur le wallet du créateur18 "max_insiders": 0, # holders flaggés insiders + hits du graphe insider19 "min_liquidity_usd": 30000,20 "max_ticket_share": 0.01, # votre ticket ne dépasse jamais 1 % de la pool21 "min_age_min": 30, # plus jeune que ça, chaque rapport relève encore de la devinette22 "max_age_hours": 72,23}2425MIN_CONFIDENCE = 0.7 # si Grok dit BUY en dessous de ça -> traité comme SKIP2627EXIT = {28 "stop_pct": -35, # stop sec depuis l'entrée29 "ladder": [(100, 0.50), (200, 0.25)], # +100 % vend la moitié, +200 % vend un quart30 "trail_pct": 30, # le reste : sortie quand le prix chute de 30 % depuis son sommet31 "time_stop_hours": 6, # pas +20 % après 6 heures -> sortie, argent mort32 "time_stop_min_pct": 20,33 "liq_drop_pct": 40, # liquidité de la pool en baisse de 40 % depuis l'entrée -> sortie immédiate34}3536LOG = "checks.csv"37POSITIONS = "positions.json"38TRADES = "trades.csv"
1TICKET FIXE. la taille n'augmente jamais parce que les trois derniers ont gagné.2LE BOT LIT config.py, IL NE L'ÉCRIT JAMAIS. un chiffre bouge uniquement quand vous le bougez.3UN FILTRE EST ASSOUPLI PAR ÉCRIT. vous changez un chiffre, vous expliquez pourquoi dans le chat, le jour même.
06. lancez les limites avant toute chose
enregistrez sous book.py. la vérification la moins chère du desk est celle qui n'appelle aucune API. si la journée a déjà perdu 50 $, le desk ne regarde même pas le token. si quatre positions sont ouvertes, pareil. ce fichier est aussi la mémoire du desk : deux fichiers que vous pouvez ouvrir vous-même, positions.json et trades.csv.
1# book.py2# positions, trades, limites. la mémoire du desk vit dans deux fichiers que vous pouvez ouvrir vous-même.3import csv, json, os, sys, time4from config import (POSITIONS, TRADES, TICKET_USD, MAX_OPEN, MAX_ENTRIES_PER_DAY,5 DAILY_LOSS_HALT_USD, COOLDOWN_HOURS)67def load() -> dict:8 return json.load(open(POSITIONS)) if os.path.exists(POSITIONS) else {}910def save(book: dict):11 json.dump(book, open(POSITIONS, "w"), indent=2)1213def trades() -> list[dict]:14 return list(csv.DictReader(open(TRADES))) if os.path.exists(TRADES) else []1516def record(mint, symbol, action, usd, pnl, reason):17 new = not os.path.exists(TRADES)18 with open(TRADES, "a", newline="") as f:19 w = csv.DictWriter(f, fieldnames=["time", "mint", "symbol", "action", "usd", "pnl", "reason"])20 if new:21 w.writeheader()22 w.writerow({"time": time.strftime("%Y-%m-%d %H:%M"), "mint": mint, "symbol": symbol,23 "action": action, "usd": round(usd, 2), "pnl": round(pnl, 2), "reason": reason})2425def today_pnl() -> float:26 day = time.strftime("%Y-%m-%d")27 return sum(float(t["pnl"]) for t in trades() if t["time"].startswith(day))2829def guard(mint: str) -> str | None:30 """Les limites tournent avant tout appel réseau. Une entrée bloquée ne coûte rien."""31 day = time.strftime("%Y-%m-%d")32 book = load()33 if today_pnl() <= -DAILY_LOSS_HALT_USD: return "halted_daily_loss"34 if mint in book: return "already_open"35 if len(book) >= MAX_OPEN: return "max_open"36 opens = [t for t in trades() if t["action"] == "open" and t["time"].startswith(day)]37 if len(opens) >= MAX_ENTRIES_PER_DAY: return "max_entries_today"38 cutoff = time.strftime("%Y-%m-%d %H:%M", time.localtime(time.time() - COOLDOWN_HOURS * 3600))39 if any(t["mint"] == mint and t["action"] == "close" and t["time"] >= cutoff for t in trades()):40 return "cooldown"41 return None4243def open_position(mint, symbol, price, liq_usd, usd=TICKET_USD):44 book = load()45 book[mint] = {"symbol": symbol, "entry": price, "qty0": usd / price, "qty": usd / price,46 "peak": price, "liq0": liq_usd, "opened": time.time(), "steps": 0}47 save(book)48 record(mint, symbol, "open", usd, 0, "entry")4950def sell(mint, frac_of_original, price, reason) -> float:51 """Vend une fraction de la TAILLE INITIALE (1.0 = tout ce qui reste). Retourne le pnl réalisé."""52 book = load()53 p = book[mint]54 q = min(p["qty"], p["qty0"] * frac_of_original)55 pnl = q * (price - p["entry"])56 p["qty"] -= q57 action = "close" if p["qty"] <= p["qty0"] * 1e-6 else "partial"58 record(mint, p["symbol"], action, q * price, pnl, reason)59 if action == "close":60 del book[mint]61 save(book)62 return pnl6364if __name__ == "__main__":65 # mode live : après avoir acheté sur votre propre wallet -> python book.py add <mint> <usd> <price> <liq_usd>66 # après avoir vendu à la main -> python book.py close <mint> <price>67 cmd = sys.argv[1]68 if cmd == "add":69 mint, usd, price, liq = sys.argv[2], float(sys.argv[3]), float(sys.argv[4]), float(sys.argv[5])70 open_position(mint, mint[:6], price, liq, usd)71 elif cmd == "close":72 print(round(sell(sys.argv[2], 1.0, float(sys.argv[3]), "manual"), 2))
07. écrivez la sortie avant l'entrée
la plupart des gens décident quand vendre en fixant une bougie verte. c'est le pire moment pour décider de quoi que ce soit. sur ce desk, chaque sortie existe avant que le token ne soit acheté, et le bot se contente de vérifier toutes les 10 minutes :
- liquidité retirée de 40 % depuis l'entrée : sortie, tout, sans poser de questions
- -35 % : sortie
- +100 % : la moitié sort, le reste est l'argent du marché
- +200 % : un autre quart sort
- après la première prise de profit, le reste sort quand le prix chute de 30 % depuis son sommet
- six heures sans +20 % : sortie, le slot vaut plus que l'espoir
enregistrez sous watch.py :
1# watch.py2# chaque position ouverte, toutes les 10 minutes. n'imprime rien si rien n'a changé.3import time4from config import EXIT, PAPER5from market import pool6import book78def watch() -> list[str]:9 events = []10 for mint, p in list(book.load().items()):11 try:12 m = pool(mint)13 except Exception as e:14 events.append(f"{p['symbol']}: flux de prix HS ({e}), on garde, nouvelle tentative à venir")15 continue16 if not m or m["price"] <= 0:17 events.append(f"{p['symbol']}: pool disparue. traiter comme rugged, vendre ce qui peut l'être")18 book.sell(mint, 1.0, 0.0, "pool_gone")19 continue2021 price = m["price"]22 b = book.load(); s = b[mint]23 s["peak"] = max(s["peak"], price); book.save(b)24 pct = (price / s["entry"] - 1) * 10025 hours = (time.time() - s["opened"]) / 36002627 def out(frac, why):28 pnl = book.sell(mint, frac, price, why)29 verb = "vendu" if PAPER else "VENDRE MAINTENANT sur votre wallet"30 size = "le reste" if frac >= 1 else f"{int(frac * 100)}%"31 events.append(f"{s['symbol']}: {verb} {size} à {pct:+.0f}% ({why}), pnl ${pnl:+.2f}")3233 if m["liq_usd"] < s["liq0"] * (1 - EXIT["liq_drop_pct"] / 100):34 out(1.0, "liquidity_pulled"); continue35 if pct <= EXIT["stop_pct"]:36 out(1.0, "stop"); continue37 ladder = EXIT["ladder"]38 if s["steps"] < len(ladder) and pct >= ladder[s["steps"]][0]:39 out(ladder[s["steps"]][1], f"take_profit_{ladder[s['steps']][0]}")40 b = book.load()41 if mint in b:42 b[mint]["steps"] += 1; book.save(b)43 continue44 if s["steps"] > 0 and price <= s["peak"] * (1 - EXIT["trail_pct"] / 100):45 out(1.0, "trailing_stop"); continue46 if hours >= EXIT["time_stop_hours"] and pct < EXIT["time_stop_min_pct"]:47 out(1.0, "time_stop")48 return events4950if __name__ == "__main__":51 for e in watch():52 print(e)
1LA LIQUIDITÉ D'ABORD. une pool vidée bat toutes les autres règles, même sur une position gagnante.2LE TRAIL S'ARME APRÈS LA PREMIÈRE PRISE DE PROFIT. avant ça, -35 % est le seul plancher.3L'ARGENT MORT EST AUSSI UNE PERTE. six heures à plat libèrent le slot pour le token suivant.4LE SILENCE EST UN STATUT. watch.py n'imprime rien si rien n'a changé, donc un chat silencieux signifie que vous n'avez pas besoin de regarder.

08. apprenez les 14 façons dont un token meurt
si je devais jeter tout ce qui précède et ne garder qu'une chose, ce serait ça. ça marche aussi sans aucun bot : faites les mêmes 14 vérifications à la main sur rugcheck.xyz et dexscreener avant votre prochain achat et vous éviterez la plupart des tokens que vous alliez regretter.
13 des vérifications sont du code. des comparaisons, pas de modèle, pas d'opinion. la 14e est la seule qui nécessite Grok.
#
vérification
élimine quand
pourquoi
1
déjà rugged
Rugcheck le marque comme rugged
quelqu'un l'a déjà fait
2
autorité de mint
encore ouverte
le dev peut imprimer de la nouvelle supply dans votre sac
3
autorité de gel
encore ouverte
le dev peut geler vos tokens, vous ne pouvez pas vendre
4
signaux de danger
tout risque de niveau danger
Rugcheck a déjà vu quelque chose que vous auriez du mal à contredire
5
score de risque
supérieur à 40/100
les petits avertissements s'accumulent
6
verrouillage LP
plus grosse pool sous 90 % verrouillée ou brûlée
une liquidité déverrouillée est un bouton de retrait
7
top 10 holders
plus de 30 %, pools et lockers exclus
dix wallets peuvent mettre fin à votre trade ensemble
8
wallet du dev
le créateur détient encore plus de 5 %
le dev est le premier vendeur
9
insiders / bundles
tout holder insider ou hit du graphe insider
les wallets financés ensemble vendent ensemble
10
profondeur de la pool
moins de 30 k$ de liquidité
vous seriez la sortie de quelqu'un d'autre
11
part du ticket
vos 25 $ représentent plus de 1 % de la pool
votre propre achat fait bouger le prix
12
ancienneté
moins de 30 min ou plus de 72 h
trop récent pour le moindre rapport, ou le mouvement a déjà eu lieu
13
ventes existantes
20+ achats dans la dernière heure, zéro vente
c'est la forme typique d'un honeypot
14
lecture X (Grok)
aucun post, CA copié-collé, hype achetée, historique de rug
la partie qu'aucune API ne renvoie

09. laissez le code éliminer les cas évidents
vérifications 1 à 9. il récupère un rapport et ne conserve que les champs que le desk lit. Grok ne voit jamais le rapport brut, un payload énorme coûte des tokens et nuit à la précision.
un détail qui échappe souvent : le vault de la pool elle-même et les lockers figurent dans la liste des top holders. comptez-les et chaque token ressemblera à un token de baleine. le code les retire avant de calculer le top 10.
1# rugcheck.py2# un rapport par token, réduit aux champs que le desk lit. Grok ne voit jamais le rapport brut.3import requests4from config import FACTS56RC = "https://api.rugcheck.xyz/v1/tokens"78def report(mint: str) -> dict:9 r = requests.get(f"{RC}/{mint}/report", timeout=20)10 r.raise_for_status()11 d = r.json()12 token = d.get("token") or {}13 known = d.get("knownAccounts") or {}1415 def is_pool(h): # les vaults AMM et les lockers sont dans le top holders, ce ne sont pas des baleines16 tag = known.get(h.get("owner")) or known.get(h.get("address")) or {}17 return str(tag.get("type", "")).upper() in ("AMM", "LOCKER")1819 holders = [h for h in (d.get("topHolders") or []) if not is_pool(h)]20 markets = sorted(d.get("markets") or [],21 key=lambda m: ((m.get("lp") or {}).get("baseUSD") or 0)22 + ((m.get("lp") or {}).get("quoteUSD") or 0),23 reverse=True)24 lp = (markets[0].get("lp") or {}) if markets else {}25 supply = token.get("supply") or 026 risks = d.get("risks") or []2728 return {29 "mint": mint,30 "symbol": (d.get("tokenMeta") or {}).get("symbol"),31 "rugged": bool(d.get("rugged")),32 "risk_score": d.get("score_normalised"),33 "danger": [x.get("name") for x in risks if x.get("level") == "danger"],34 "warn": [x.get("name") for x in risks if x.get("level") == "warn"],35 "mint_authority": token.get("mintAuthority", d.get("mintAuthority")),36 "freeze_authority": token.get("freezeAuthority", d.get("freezeAuthority")),37 "lp_locked_pct": lp.get("lpLockedPct", d.get("lpLockedPct")),38 "top10_pct": round(sum(h.get("pct") or 0 for h in holders[:10]), 2) if holders else None,39 "dev_pct": round((d.get("creatorBalance") or 0) / supply * 100, 2) if supply else None,40 "insiders": sum(1 for h in holders if h.get("insider")) + (d.get("graphInsidersDetected") or 0),41 "holders": d.get("totalHolders"),42 }4344def fact_kill(t: dict) -> str | None:45 """Comparaisons simples. Le premier échec l'emporte, la raison est journalisée."""46 if t["risk_score"] is None: return "no_report"47 if t["rugged"]: return "already_rugged"48 if t["mint_authority"]: return "mint_open"49 if t["freeze_authority"]: return "freeze_open"50 if t["danger"]: return "danger:" + t["danger"][0]51 if t["risk_score"] > FACTS["max_risk_score"]: return "risk_score"52 if t["lp_locked_pct"] is None or t["lp_locked_pct"] < FACTS["min_lp_locked_pct"]:53 return "lp_unlocked"54 if t["top10_pct"] is None or t["top10_pct"] > FACTS["max_top10_pct"]:55 return "top10_heavy"56 if t["dev_pct"] is None or t["dev_pct"] > FACTS["max_dev_pct"]:57 return "dev_holds"58 if t["insiders"] > FACTS["max_insiders"]: return "insiders"59 return None

10. vérifiez la pool, pas seulement le contrat
vérifications 10 à 13. un contrat peut être parfait et la pool vous empêcher quand même de sortir. ceci lit la pool la plus profonde pour le token et pose quatre questions ennuyeuses : y a-t-il assez dedans, quelle est la taille de mon ticket par rapport à elle, quel âge a-t-elle, et est-ce que quelqu'un vend vraiment.
1# market.py2# la pool elle-même : profondeur, ancienneté, prix, et si quelqu'un peut réellement vendre.3import time4import requests5from config import FACTS, TICKET_USD67DS = "https://api.dexscreener.com/tokens/v1/solana"89def pool(mint: str) -> dict | None:10 r = requests.get(f"{DS}/{mint}", timeout=15)11 r.raise_for_status()12 data = r.json()13 pairs = data if isinstance(data, list) else (data.get("pairs") or [])14 pairs = [p for p in pairs if (p.get("baseToken") or {}).get("address") == mint]15 if not pairs:16 return None17 p = max(pairs, key=lambda x: (x.get("liquidity") or {}).get("usd") or 0)18 h1 = (p.get("txns") or {}).get("h1") or {}19 born = p.get("pairCreatedAt")20 return {21 "symbol": (p.get("baseToken") or {}).get("symbol"),22 "price": float(p.get("priceUsd") or 0),23 "liq_usd": (p.get("liquidity") or {}).get("usd") or 0,24 "age_min": round((time.time() * 1000 - born) / 60000) if born else None,25 "buys_1h": h1.get("buys", 0),26 "sells_1h": h1.get("sells", 0),27 "vol_1h": (p.get("volume") or {}).get("h1"),28 "url": p.get("url"),29 }3031def market_kill(m: dict | None) -> str | None:32 if not m: return "no_pool"33 if m["price"] <= 0: return "no_price"34 if m["liq_usd"] < FACTS["min_liquidity_usd"]: return "thin_pool"35 if TICKET_USD / m["liq_usd"] > FACTS["max_ticket_share"]: return "ticket_too_big"36 if m["age_min"] is None or m["age_min"] < FACTS["min_age_min"]:37 return "too_fresh"38 if m["age_min"] > FACTS["max_age_hours"] * 60: return "too_old"39 if m["buys_1h"] >= 20 and m["sells_1h"] == 0: return "nobody_can_sell"40 return None
11. laissez Grok lire ce que le code ne peut pas
vérification 14. c'est la raison pour laquelle le desk tourne sur Grok et non sur un autre modèle : il peut chercher sur X pendant qu'il réfléchit. un token peut passer les 13 vérifications et rester une copie avec une adresse de contrat différente, ou un lancement où quarante comptes créés la semaine dernière ont posté la même phrase à la même minute. aucune API ne renvoie ça. X si.
enregistrez sous grok.py. c'est le seul fichier qui lit la clé et le seul endroit où le desk parle à xAI :
1# grok.py2# le seul fichier qui contient la clé et le seul endroit où le desk communique avec xAI.3import os, json, datetime, requests4from config import MODEL56URL = "https://api.x.ai/v1/responses"78def _key() -> str:9 k = os.environ.get("XAI_API_KEY")10 if not k and os.path.exists(".env"):11 for line in open(".env"):12 if line.startswith("XAI_API_KEY="):13 k = line.split("=", 1)[1].strip().strip('"')14 if not k:15 raise RuntimeError("pas de XAI_API_KEY dans l'environnement ou dans .env")16 return k1718def _text(resp: dict) -> str:19 if isinstance(resp.get("output_text"), str):20 return resp["output_text"]21 parts = []22 for item in resp.get("output") or []:23 if item.get("type") == "message":24 for c in item.get("content") or []:25 if c.get("type") in ("output_text", "text"):26 parts.append(c.get("text") or "")27 return "\n".join(parts)2829def ask(system: str, payload: dict, read_x: bool = False) -> dict:30 """Un seul appel, JSON en entrée, JSON en sortie. Tout ce qui n'est pas du JSON déclenche une erreur, et une erreur signifie un SKIP en amont."""31 body = {"model": MODEL,32 "input": [{"role": "system", "content": system},33 {"role": "user", "content": json.dumps(payload)}]}34 if read_x:35 since = (datetime.date.today() - datetime.timedelta(days=3)).isoformat()36 body["tools"] = [{"type": "x_search", "from_date": since}]37 body["max_turns"] = 2 # deux tours de recherche, puis il doit répondre38 r = requests.post(URL, timeout=180, json=body,39 headers={"Authorization": f"Bearer {_key()}"})40 r.raise_for_status()41 text = _text(r.json())42 start, end = text.find("{"), text.rfind("}")43 if start == -1:44 raise ValueError(f"pas de json dans la réponse : {text[:200]}")45 return json.loads(text[start:end + 1])
enregistrez sous prompts.py. c'est le fichier que vous allez réellement relire :
1# prompts.py2# l'unique prompt Grok sur le desk. c'est le fichier que vous allez réellement relire.34JUDGE_PROMPT = """5Tu es RUG DESK. Un memecoin Solana vient de passer tous les contrôles stricts dans le code :6autorités, LP, holders, wallet du dev, initiés, profondeur du pool, ancienneté.7Ne revérifie pas ces chiffres. Tu décides si un seul ticket fixe est engagé.8Tu ne dimensionnes jamais les positions, tu ne choisis jamais les sorties.910ENTRÉE : {"token": {...faits rugcheck...}, "pool": {...faits dexscreener...}}1112ÉTAPE 1. Cherche le ticker et l'adresse du contrat sur X, sur les 72 dernières heures.13ÉTAPE 2. Juge uniquement ce que le code ne peut pas voir :14 - est-ce que les risques de niveau « warn » s'accumulent pour former quelque chose de pire ensemble15 - qui a posté en premier : le dev, un vrai compte avec un historique, ou une vague de comptes tout neufs16 - organique ou acheté : mêmes formulations, même minute, comptes sans passé17 - l'adresse du contrat dans ces posts est-elle la même que celle de l'ENTRÉE18 - ce nom de ticker ou ce dev ont-ils déjà été liés à un rug par le passé1920RETOURNE UNIQUEMENT DU JSON :21{"verdict": "BUY" | "SKIP", "confidence": 0.0-1.0,22 "why": "deux lignes maximum", "x_read": "une ligne sur ce à quoi ressemble X",23 "main_risk": "une ligne"}2425Zéro post sur X est une réponse : personne ne sait qu'il existe. SKIP.26Une autre adresse de contrat sous le même ticker sur X : risque de copie. SKIP.27Ne réponds JAMAIS BUY lorsqu'un champ dont tu as besoin est null.28Ne mentionne JAMAIS d'objectifs de prix. Tu ne les connais pas.29"""
12. reliez le tout dans une seule commande
une adresse de contrat en entrée, un verdict en sortie. limites, faits, pool, Grok, dans cet ordre, et le premier qui dit non met fin au processus :
1# check.py2# une adresse de contrat en entrée, un verdict en sortie. s'exécute à chaque fois qu'un token passe sur ton desk.3import csv, json, os, sys, time4from config import LOG, MIN_CONFIDENCE, PAPER, TICKET_USD5from rugcheck import report, fact_kill6from market import pool, market_kill7from grok import ask8from prompts import JUDGE_PROMPT9import book1011def log(row: dict):12 new = not os.path.exists(LOG)13 with open(LOG, "a", newline="") as f:14 w = csv.DictWriter(f, fieldnames=["time", "mint", "symbol", "stage", "verdict",15 "confidence", "reason"])16 if new:17 w.writeheader()18 w.writerow({k: row.get(k) for k in w.fieldnames})1920def check(mint: str) -> dict:21 if (g := book.guard(mint)): # limites, gratuit22 return {"stage": "limits", "verdict": "SKIP", "reason": g}2324 try:25 token = report(mint) # filtre rug, gratuit26 except Exception as e:27 return {"stage": "facts", "verdict": "SKIP", "reason": f"rugcheck a échoué : {e}"}28 if (k := fact_kill(token)):29 return {"stage": "facts", "verdict": "SKIP", "reason": k, "symbol": token["symbol"]}3031 try:32 m = pool(mint) # pool, gratuit33 except Exception as e:34 return {"stage": "pool", "verdict": "SKIP", "reason": f"dexscreener a échoué : {e}"}35 if (k := market_kill(m)):36 return {"stage": "pool", "verdict": "SKIP", "reason": k, "symbol": token["symbol"]}3738 try:39 ans = ask(JUDGE_PROMPT, {"token": token, "pool": m}, read_x=True) # la seule étape payante40 except Exception as e:41 return {"stage": "grok", "verdict": "SKIP", "reason": f"grok a échoué : {e}"}4243 verdict, conf = ans.get("verdict"), ans.get("confidence") or 044 if verdict == "BUY" and conf < MIN_CONFIDENCE:45 verdict = "SKIP"46 ans["why"] = f"confiance faible {conf}"47 return {**ans, "stage": "grok", "verdict": verdict, "symbol": m["symbol"] or token["symbol"],48 "reason": ans.get("why"), "price": m["price"], "liq_usd": m["liq_usd"], "url": m["url"]}4950if __name__ == "__main__":51 mint = sys.argv[1].strip()52 v = check(mint)53 log({"time": time.strftime("%Y-%m-%d %H:%M"), "mint": mint, **v})54 if v["verdict"] == "BUY":55 if PAPER:56 book.open_position(mint, v["symbol"], v["price"], v["liq_usd"])57 v["action"] = f"PAPER BUY ${TICKET_USD} at {v['price']}"58 else:59 v["action"] = (f"APPROBATION REQUISE : achète ${TICKET_USD} de {v['symbol']} dans ton propre wallet, "60 f"puis exécute : python book.py add {mint} {TICKET_USD} <prix d'exécution> {v['liq_usd']}")61 print(json.dumps(v, indent=2))
vous n'avez pas besoin d'un scanner pour l'utiliser. une CA provenant d'un canal vocal, d'un post, du message d'un ami : dites au bot « check <CA> » et il exécute une ligne. voici à quoi ressemble le retour (structure d'exemple, pas un vrai token) :
1{2 "verdict": "SKIP",3 "confidence": 0.78,4 "why": "passe tous les contrôles stricts, mais les 30 premiers posts proviennent de comptes créés cette semaine, avec les mêmes formulations.",5 "x_read": "un vrai compte, une vague de comptes tout neufs, aucune divergence de CA",6 "main_risk": "hype achetée, les acheteurs partiront quand les posts s'arrêteront",7 "stage": "grok",8 "symbol": "EXEMPLE",9 "reason": "passe tous les contrôles stricts, mais les 30 premiers posts proviennent de comptes créés cette semaine, avec les mêmes formulations."10}

1LES FAITS AVANT LES OPINIONS. Grok ne décide jamais si un mint est ouvert.2UN APPEL ÉCHOUÉ EST UN SKIP. Rugcheck en panne, DexScreener en panne, Grok en panne, mauvais JSON : SKIP.3NULL N'EST PAS PROPRE. un champ qui ne revient pas élimine le token, il ne le valide jamais.4UN VERDICT PAR TOKEN. ne relancez jamais tant qu'il n'a pas dit BUY. si vous n'êtes pas d'accord, c'est avec config.py que vous n'êtes pas d'accord.5LISEZ LES KILLS, PAS LES BUYS. les buys font plaisir. les raisons des kills vous disent si le filtre fonctionne.
13. trois routines, une phrase chacune
aucun constructeur de workflow. vous indiquez chaque routine au bot une seule fois, en une phrase, et il vous montre la prochaine exécution. voici les miennes, mot pour mot :
1Toutes les 10 minutes, dans /workspace/desk exécute python watch.py.2Si ça n'affiche rien, ne dis rien. Si ça affiche quoi que ce soit, poste-le ici mot pour mot.
1Toutes les 30 minutes, dans /workspace/desk exécute python scan.py.2Poste uniquement les verdicts BUY : symbol, confidence, x_read.3Si Rugcheck ou DexScreener est en panne, dis lequel une seule fois, pas à chaque exécution.
1Chaque jour à 21h30, dans /workspace/desk exécute python report.py et poste le résultat ici.2Ajoute une chose qui a fonctionné aujourd'hui et un chiffre dans config.py que tu remettrais en question, avec la raison.3Ne modifie jamais ce chiffre toi-même.
les deux fichiers que ces routines exécutent. enregistrez sous report.py :
1# report.py2# le rapport du soir. tout ce qu'il contient vient de checks.csv et trades.csv, rien de la mémoire.3import csv, os, time4from collections import Counter5from config import LOG, PAPER6import book78day = time.strftime("%Y-%m-%d")9checks = [r for r in csv.DictReader(open(LOG))] if os.path.exists(LOG) else []10checks = [r for r in checks if r["time"].startswith(day)]11done = [t for t in book.trades() if t["time"].startswith(day) and t["action"] != "open"]1213by_stage = Counter(r["stage"] for r in checks if r["verdict"] == "SKIP")14buys = [r for r in checks if r["verdict"] == "BUY"]15wins = [t for t in done if float(t["pnl"]) > 0]16reasons = Counter(r["reason"] for r in checks if r["stage"] in ("facts", "pool"))1718print(f"RAPPORT DU SOIR {day} {'(PAPER)' if PAPER else '(LIVE)'}")19print(f"vérifiés : {len(checks)}")20print(f"éliminés par le filtre rug : {by_stage['facts']} | par le pool : {by_stage['pool']} | "21 f"par Grok : {by_stage['grok']} | par les limites : {by_stage['limits']}")22print(f"achetés : {len(buys)} | ouverts actuellement : {len(book.load())}")23print(f"pnl réalisé aujourd'hui : ${book.today_pnl():+.2f}")24if done:25 print(f"sorties gagnantes : {len(wins)}/{len(done)}")26 best = max(done, key=lambda t: float(t["pnl"]))27 worst = min(done, key=lambda t: float(t["pnl"]))28 print(f"meilleur : {best['symbol']} ${float(best['pnl']):+.2f} | pire : {worst['symbol']} ${float(worst['pnl']):+.2f}")29if reasons:30 top, n = reasons.most_common(1)[0]31 print(f"élimination la plus fréquente : {top} ({n}x)")
et scan.py, la source de leads optionnelle. il récupère les nouveaux tokens Solana qui viennent de payer pour un profil DexScreener. un profil payant n'est pas un signal de qualité, cela signifie seulement que le dev a dépensé de l'argent. le filtre décide du reste :
1# scan.py2# source de leads optionnelle : nouveaux tokens Solana qui viennent de payer pour un profil DexScreener.3# un profil payant n'est pas un signal de qualité, cela signifie juste que le dev a dépensé de l'argent. le filtre décide.4import csv, os, subprocess, sys5import requests6from config import LOG78seen = {r["mint"] for r in csv.DictReader(open(LOG))} if os.path.exists(LOG) else set()9r = requests.get("https://api.dexscreener.com/token-profiles/latest/v1", timeout=15)10r.raise_for_status()11fresh = [t["tokenAddress"] for t in r.json()12 if t.get("chainId") == "solana" and t.get("tokenAddress") not in seen]1314for mint in fresh[:5]: # cinq par exécution, pour qu'une heure chargée ne vide pas le budget API15 subprocess.run([sys.executable, "check.py", mint])
dernière chose pour ce soir : Settings, General, Auto-review. ajoutez deux règles « Ask first », elles bloquent l'action et vous attendent quoi que pense n'importe quelle routine :
1Demande-moi d'abord avant toute connexion de wallet, signature, swap ou transfert.2Demande-moi d'abord avant de modifier tout fichier nommé config.py.


14. une semaine sur papier avant le premier dollar
PAPER = True signifie que le desk fait tout pour de vrai, sauf l'argent. vrais tokens, vrais prix, vraies sorties, fausses exécutions. après sept jours, ouvrez checks.csv et lisez-le comme s'il avait été écrit par un inconnu.
ce que vous cherchez : quel contrôle élimine le plus, si les BUY auraient été ceux que vous auriez achetés, et si une raison de SKIP semble incorrecte. une exécution papier se fait au prix affiché à l'écran, une exécution live jamais, alors considérez le résultat papier comme un plafond, pas comme une prévision.
quand vous passez en live, basculez PAPER sur False et rien d'autre ne change, sauf la dernière étape. check.py arrête d'acheter et publie plutôt une ligne d'approbation : achetez vous-même 25 $ dans votre propre wallet, puis une commande l'inscrit dans le registre pour que watch.py commence à le surveiller.
quand ça casse
ça touche à l'argent, donc chaque défaillance a une réponse écrite avant même qu'elle ne se produise.
1401 de xAI -> mauvaise clé. Prends le contrôle, réécris .env, relance le curl de l'étape 03.2400 / 404 de xAI -> le nom du modèle a changé. corrige MODEL dans config.py, rien d'autre.3429 de xAI -> limite de taux atteinte. ce token est un SKIP, la prochaine exécution passe à autre chose. ne boucle jamais dessus.4erreur concernant x_search -> ton MODEL ne peut pas chercher sur X. choisis-en un qui le peut sur console.x.ai -> Models.5Grok répond en prose -> ValueError, journalisé, SKIP. ne transforme jamais un paragraphe en trade.6rugcheck 404 ou vide -> trop récent pour être indexé. SKIP. récent ne veut pas dire propre.7lp_unlocked sur tout -> certains types de pools n'ont aucun token LP. ouvre-en un toi-même sur rugcheck8 avant de toucher à ce chiffre, et modifie-le par écrit.9no_pool -> pas encore tradé sur un DEX, ou pas sur Solana. SKIP.10les routines sont devenues silencieuses -> Grok Bot met les routines en pause après une longue absence.11 ouvre Routines et réactive-les.
la facture
tout ce qui précède le juge est gratuit : les limites, Rugcheck, DexScreener. la seule étape payante est un appel Grok par token ayant survécu aux 13 contrôles, avec la recherche X activée.
1coût_par_token = input_tokens / 1_000_000 * prix_par_M_input2 + output_tokens / 1_000_000 * prix_par_M_output3 + posts_récupérés / 1_000 * 5 # recherche X : 5 $ pour 1 000 posts récupérés45max_turns = 2 limite la recherche à deux tours, donc posts_récupérés reste faible.6chaque réponse indique son propre x_posts_fetched, donc dès le premier jour tu connais le vrai chiffre.78prix : [prix depuis console.x.ai]
l'erreur que font les gens : la facture évolue avec les survivants, pas avec le scanner. scan.py peut fournir 240 tokens par jour au desk et la plupart meurent gratuitement avant même que Grok n'en entende parler.

le point important
c'est ma configuration, pas votre stratégie.
chaque chiffre dans config.py est le mien, choisi pour un ticket de 25 $ et ma propre tolérance à regarder quelque chose tomber à zéro. la plupart des memecoins tombent effectivement à zéro, et la plupart des gens qui les tradent perdent de l'argent. le filtre ne rend pas un token bon. il élimine les façons évidentes de perdre, et il vous oblige à lire pourquoi chaque token est mort.
faites tourner une semaine sur papier. lisez les kills. déplacez les chiffres avec lesquels vous n'êtes pas d'accord, par écrit. copiez la structure, pas les constantes. ceci n'est pas un conseil financier.
le code élimine, Grok lit X, vous signez. un script n'a jamais le droit de juger, Grok n'a jamais de calculatrice, et personne d'autre que vous n'a accès au wallet.
sauvegardez ceci avant votre prochain achat. abonnez-vous si vous prévoyez de faire tourner la même chose, la suite arrive bientôt 🫶
mon tg - t.me/+8wAXS0nHWgA2MGEy
mon github - github.com/0xboan
tradez ici - fomo.family/r/0xboan





