Un portefeuille à smart contract, aussi appelé smart account ou compte intelligent, est un contrat on-chain contrôlé par l'utilisateur, et non une simple clé privée. Chaque action se présente sous la forme d'une UserOperation acheminée via un contrat EntryPoint partagé, sponsorisée par un paymaster facultatif et regroupée par un relayer (relais). Lire la trace on-chain signifie apprendre à décortiquer ces couches au lieu de les traiter comme une transaction classique.
Points clés
- Un smart account remplace une clé par un contrat : chaque action passe donc par un EntryPoint partagé au lieu de s'exécuter directement.
- Une UserOperation est une pseudo-transaction comportant ses propres champs : sender, nonce, initCode, callData, paymaster et signature.
- Les sessions, aussi appelées session keys, sont des sous-autorisations délimitées, avec des limites, une expiration et une liste de contrats cibles qui doivent être audités un par un.
- Les paymasters et les bundlers se situent entre vous et la chaîne, et un acteur malveillant ou compromis peut réordonner, front-runner ou censurer vos opérations.
- La révocation se fait essentiellement on-chain : c'est la rotation du signer ou la destruction de la session key qui met fin à un accès délégué.
Pourquoi un smart account change la manière de raisonner
Si vous avez déjà copié une adresse, vérifié celle-ci sur un explorateur de blocs et tenté de décoder ce qu'une transaction avait fait, vous partez du postulat qu'une seule clé contrôle un seul compte. Les portefeuilles à smart contract brisent ce postulat. L'adresse que vous voyez sur Etherscan ou un explorateur équivalent n'est pas une clé. C'est un contrat. Le contrat possède des actifs, impose des règles et décide quelles instructions accepter. L'utilisateur, plus exactement l'ensemble des clés ou sessions autorisées à agir à un moment donné, envoie ses instructions à ce contrat via un canal distinct.
C'est pourquoi une action on-chain isolée paraît souvent chargée sur un explorateur de blocs. Ce qui ressemble à une seule transaction est, en interne, constitué de plusieurs couches : l'intention signée de l'utilisateur, un relayer qui la soumet, un contrat EntryPoint qui la valide, la logique optionnelle d'un paymaster qui paie le gas, et la callData qui exécute l'action réelle à l'intérieur du smart account. La forme on-chain est inhabituelle au premier abord, et c'est précisément sur cette inhabitualité que s'appuient les attaquants.
Transaction versus UserOperation : la distinction fondamentale
Une transaction Ethereum classique comporte six champs lisibles : from, to, value, data, gas limit et signature. Une UserOperation, définie par la norme ERC-4337, ajoute des champs conçus pour une exécution déléguée et sponsorisée. Ceux qui comptent le plus pour la lecture sont : sender, qui est l'adresse du smart account ; nonce, propre au compte et non lié au compteur global de transactions comme l'est un nonce d'EOA ; initCode, qui déploie le compte lors de sa première utilisation ; callData, qui correspond à ce que le smart account exécutera en cas d'approbation ; et signature, qui peut être une signature ECDSA, une signature BLS agrégée provenant d'un bundler, ou un hook de validateur défini par le compte lui-même.
Il existe également une surcouche de paiement du gas, absente des transactions ordinaires. paymasterAndData pointe vers un contrat paymaster qui remboursera le bundler. Le paymaster est autorisé à inspecter votre UserOperation avant d'accepter de la sponsoriser et, dans certaines implémentations, il peut modifier l'étape postOp, qui s'exécute après la fin de votre appel. Ce callback post-op constitue une véritable surface d'attaque : un paymaster malveillant peut lire les données de retour de votre appel et les exploiter d'une manière que vous n'avez pas autorisée. Traitez les paymasters comme n'importe quelle contrepartie, car c'est strictement ce qu'ils sont.
Lire une UserOperation implique donc de vérifier au minimum quatre éléments : que sender pointe bien vers un contrat que vous possédez réellement ou attendez ; que callData vise des adresses que vous avez autorisées (session ou liste blanche validée par un guardian) ; que les paramètres de gas ne soient pas absurdement élevés ; et que le paymaster soit un paymaster auquel vous faites confiance pour se comporter correctement.
Le contrat EntryPoint : risque d'infrastructure partagée
Chaque compte intelligent ERC-4337 sur le réseau principal Ethereum partage, au moment de l'écriture, une seule adresse EntryPoint. La norme autorise explicitement plusieurs versions d'EntryPoint, mais en pratique un déploiement canonique concentre l'essentiel de l'activité. Cette centralisation est un choix de conception délibéré : un EntryPoint partagé permet aux bundlers, aux paymasters et aux implémentations de comptes d'interopérer sans intégrations deux à deux. Elle concentre aussi le risque.
Si l'EntryPoint est mis à niveau, repris par un vote de gouvernance, ou s'il s'avère contenir une vulnérabilité, tous les comptes intelligents qui y transitent sont exposés d'un seul coup. Ce n'est pas hypothétique. Fin 2023, un incident impliquant un audit d'auditeur autour d'une bibliothèque liée à EntryPoint a été divulgué et corrigé à travers les intégrations. Rien n'a été perdu à grande échelle, mais la leçon reste valable : une infrastructure partagée est un point de défaillance unique, et la sécurité de votre compte ne vaut que ce que vaut le maillon le plus faible de cette pile.
La lecture des données on-chain devrait donc inclure une vérification rapide que l'EntryPoint par lequel transite votre compte est bien la version attendue. Le réseau principal et la plupart des L2 publient des adresses EntryPoint canoniques, et des outils comme Etherscan étiquettent les déploiements audités. Si vous voyez une UserOperation envoyée à un EntryPoint non canonique, considérez cela comme un signal fort d'alerte : il pourrait s'agir d'un relayer de phishing ou d'un fork conçu pour capturer des signatures.
Clés de session : sous-autorisations cadrées et limitées dans le temps
Les sessions sont la fonctionnalité qui mérite le plus d'être comprise, parce qu'elle est aussi la partie la plus souvent détournée. Une clé de session est un signataire secondaire que vous accordez à une application spécifique pour une fenêtre de temps définie, avec un ensemble défini de permissions. En pratique, cela ressemble à une dApp qui demande l'autorisation de trader sur Uniswap en votre nom pendant la prochaine heure, jusqu'à un plafond de dépenses, contre une liste blanche de contrats de tokens.
Sur la chaîne, une session vit comme un état à l'intérieur de votre compte intelligent. Les champs pertinents, dans l'ordre où ils apparaissent généralement dans le stockage, sont : une adresse de clé de session autorisée ; un timestamp ou numéro de bloc d'expiration ; une limite de dépense par période (souvent quotidienne ou par époque) ; une liste blanche de cibles ou de sélecteurs ; et un hook de politique qui peut appliquer des règles supplémentaires. Les implémentations varient. Les Safe Modules, les Zodiac Heads, les sessions Biconomy et les validateurs de session Kernel encodent tout cela légèrement différemment, et la disposition exacte des emplacements compte quand on lit le stockage directement.
Le point à retenir est que les sessions sont bornées mais pas sûres par défaut. Une session limitée au routeur d'Uniswap ne vaut que ce que vaut le routeur d'Uniswap. Une session qui accorde une liste blanche de sélecteurs peut quand même en toucher de dangereux si l'application sait quels sélecteurs appeler. Et les sessions qui n'imposent pas une limite de dépense réinitialisée par période peuvent vider les fonds au fil du temps une fois que l'utilisateur cesse de faire attention.
Deux habitudes pratiques aident. Premièrement, lisez l'emplacement de stockage exact ou la vue décodée que l'interface du portefeuille affiche avant de signer l'octroi de la session, et traitez-la comme la source de vérité, pas le texte marketing de la dApp. Deuxièmement, privilégiez les sessions comportant à la fois un plafond par période et un plafond par appel, afin qu'un seul appel incontrôlé ne puisse dépasser un maximum raisonnable.
Lire une seule UserOperation en détail
Imaginez que vous voyez un hash 0xabc... dans votre portefeuille et que vous voulez savoir ce qu'il a réellement fait. Ouvrez la transaction dans un explorateur de blocs, puis cliquez pour ouvrir la transaction de l'EntryPoint dont callData commence par handleOps. L'onglet des transactions internes devient alors la véritable histoire.
Étape 1 : validateUserOp
L'EntryPoint appelle d'abord la fonction validateUserOp de votre compte. C'est là que la signature est vérifiée, que les nonces sont validés contre la protection contre la rejouabilité, et que les éventuelles politiques de session sont appliquées. Une validation échouée annule tout le bundle, donc une exécution réussie est la preuve qu'au moins un signataire valide a approuvé l'opération.
Étape 2 : execute
Si la validation réussit, l'EntryPoint appelle la fonction execute de votre compte avec le callData que vous avez fourni. C'est l'action visible par l'utilisateur : un swap, un transfert, une interaction avec un contrat. Lire le callData de cet appel interne vous donne la cible effective, la valeur et le sélecteur de fonction que l'utilisateur souhaitait.
Étape 3 : postOp
Après l'exécution, le postOp du paymaster s'exécute, ce qui sert notamment à régler les dettes de gaz, rembourser le gaz inutilisé ou payer le bundler. Tout changement d'état ici a été autorisé par le choix de votre paymaster, pas par vous directement. Tout ce qui semble inhabituel à ce stade, comme des transferts de tokens supplémentaires ou de nouvelles approbations, doit vous inciter à creuser davantage.
Mettre ces trois couches bout à bout indique qui a autorisé quoi, ce qui s'est exécuté et quels effets de bord sont survenus après coup. La plupart des schémas de phishing deviennent visibles à ce niveau une fois que vous savez quoi chercher.
Paymasters et bundlers : visibilité et confiance
Un bundler est la partie qui prend votre UserOperation, la regroupe avec d'autres et soumet la véritable transaction on-chain à l'EntryPoint. Comme c'est le bundler qui appelle EntryPoint.handleOps, c'est son adresse qui paie le gaz au niveau de la chaîne. Vous, en tant que propriétaire du compte intelligent, n'avez jamais besoin d'ETH pour payer le gaz si un paymaster accepte de sponsoriser. C'est pratique, et c'est aussi une relation de confiance.
L'engagement d'un paymaster est encodé on-chain de deux manières : la caution qu'il a déposée auprès de l'EntryPoint, qui peut être slashed en cas de comportement abusif selon les règles de la norme, et la politique de sa fonction validatePaymasterUserOp, qui décide de sponsoriser ou non. Le slashing existe, mais c'est un filet de sécurité grossier, pas une garantie fine. Un paymaster peut toujours observer, censurer ou devancer les opérations qu'il voit.
Le front-running est le risque le moins discuté. Un paymaster qui voit votre swap avant qu'il ne s'exécute peut copier la transaction à sa propre adresse, l'exécuter dans le même bloc et capturer l'amélioration de prix que vous attendiez. Ce n'est pas théorique ; c'est la même dynamique que la miner-extractable value, reconditionnée pour le monde de l'abstraction de compte. Des atténuations existent (mempools privés, schémas commit-reveal, couches d'intention signées), mais elles demandent une utilisation active. Les bundlers et paymasters publics par défaut n'offrent aucune protection de ce type.
La lecture des données on-chain aide aussi ici. Si vous voyez régulièrement vos opérations atterrir dans le même numéro de slot ou juste après une adresse spécifique, ce bundler ou ce paymaster vous observe de près. Passer à un mempool privé, ou utiliser des paymasters qui s'engagent explicitement à des politiques anti front-running et qui ont un historique vérifiable, est une vraie amélioration en matière de sécurité.
Révoquer les sessions et les autorisations sur la chaîne
La erreur la plus courante des utilisateurs de comptes intelligents est de traiter une session comme si la révoquer dans l'interface de l'application décentralisée suffisait. En général, ce n'est pas le cas. L'interface de l'application décentralisée peut simplement oublier la session alors que l'octroi sur la chaîne reste actif jusqu'à son expiration. Tant que le contrat du compte intelligent n'applique pas un délai d'inactivité, ou tant que vous ne révoquez pas activement, la clé de session peut toujours signer des opérations que le point d'entrée acceptera.
La véritable révocation prend trois formes concrètes. La première consiste à appeler la fonction de révocation du compte intelligent, si elle existe, avec l'identifiant de la clé de session. La deuxième consiste à faire tourner le signataire principal du compte, ce qui invalide toute session dont l'autorité était liée au signataire. La troisième consiste à laisser la session expirer naturellement, ce qui ne fonctionne que si vous avez défini au départ une fenêtre suffisamment courte.
Vérifier la révocation sur la chaîne signifie examiner deux éléments : l'emplacement de stockage de la session, qui doit refléter l'état révoqué ou expiré, et la séquence de nonces du compte, qui ne doit plus avancer pour la clé révoquée. Si vous constatez qu'une clé de session signe toujours des opérations après une prétendue révocation, c'est le moment de vous arrêter et d'enquêter. Il peut s'agir d'une interface obsolète, ou d'une session dont la portée n'a jamais été correctement définie et qui est désormais exploitée.
Comment suivre l'activité des comptes intelligents de manière éclairée
L'activité des comptes intelligents évolue rapidement, et la trace sur la chaîne n'a de sens qu'une fois que vous comprenez les couches sous-jacentes. Suivre qui achemine vos opérations, quels paymasters les parrainent et quelles sessions sont encore actives est une bataille perdue si vous le faites à la main. Zippfeed met en avant les titres des portefeuilles et des protocoles avec une notation de sentiment (haussier, neutre ou baissier) et une cote d'importance, afin que vous puissiez repérer les schémas derrière le bruit avant votre prochaine signature.