web3qr
Langue : FR EN

← Tous les articles

BIP-21, EIP-681, Solana Pay : les URI de paiement crypto et leurs pièges

2026-07-31

Trois standards, trois conventions d'unité

Un QR code de paiement crypto ne contient pas une adresse : il contient une URI. Bitcoin, Ethereum et Solana ont chacun normalisé la leur — BIP-21, EIP-681, Solana Pay — et les trois divergent précisément là où l'erreur coûte le plus cher : l'unité dans laquelle le montant est exprimé.

Cette divergence n'est pas un détail d'implémentation. Un générateur qui traite les trois standards de la même façon produit des QR codes qui demandent le mauvais montant, sans qu'aucun message d'erreur ne le signale. Le wallet affiche un nombre, l'utilisateur le valide, et la transaction part.

BIP-21 : le plus ancien, le plus lisible

Le schéma Bitcoin ressemble à une URL classique :

bitcoin:36hvVTFfk7WJD9LFmVmnELkvQtcvSTpgEx?amount=0.01&label=Cafe&message=Table%204

Le paramètre amount est exprimé en BTC, en notation décimale simple. 0.01 veut dire un centième de bitcoin, ce qu'on lit naturellement. label identifie le bénéficiaire, message décrit la raison du paiement — les deux sont optionnels et purement informatifs.

BIP-21 accepte aussi un paramètre lightning qui embarque une invoice BOLT-11 dans la même URI. Le wallet choisit alors la voie qu'il sait emprunter : on-chain s'il ne gère pas Lightning, hors chaîne sinon. Un seul QR code couvre les deux cas.

EIP-681 : le piège du wei

Côté Ethereum, la forme de base ressemble à celle de Bitcoin :

ethereum:0x75D7b06d710B47deE8E15Db392e0e8B20345a211@137?value=10000000000000000

Le @137 est le chainId défini par l'EIP-155 — 1 pour Ethereum, 137 pour Polygon, 56 pour BNB Smart Chain, 43114 pour Avalanche. Il évite d'envoyer des fonds sur le mauvais réseau, un accident fréquent quand la même adresse existe sur plusieurs chaînes.

Mais le paramètre value ne se lit pas comme amount. EIP-681 exige un entier en wei, l'unité atomique d'Ethereum, soit 10⁻¹⁸ ETH. Le montant ci-dessus vaut 0,01 ETH, écrit 10000000000000000.

L'erreur classique consiste à recopier la valeur saisie par l'utilisateur. Un QR annonçant value=0.01 ne demande pas 0,01 ETH : il demande 0,01 wei, c'est-à-dire une somme si petite qu'elle est arrondie à zéro. Le paiement passe, le commerçant ne reçoit rien, et rien dans le processus n'a signalé le problème.

L'ABNF du standard admet aussi la notation scientifique — value=1e16 — plus compacte. Son support reste inégal selon les wallets, alors que l'entier décimal complet est compris partout, et les dix-huit caractères supplémentaires n'ont aucun effet mesurable sur la densité du QR code. Sur un chemin de paiement, la compatibilité prime sur la concision.

Pourquoi la multiplication flottante est disqualifiée

La conversion vers l'unité atomique semble triviale : multiplier par 10¹⁸. Elle ne l'est pas, parce que les nombres à virgule flottante IEEE-754 ne représentent pas exactement les décimaux.

Mesuré dans Node :

  • 1.1 * 1e18 donne 1100000000000000100 au lieu de 1100000000000000000
  • 0.07 * 1e18 donne 70000000000000010 au lieu de 70000000000000000

Cent wei d'écart sur un paiement en ETH ne changent rien en pratique. Mais le même code appliqué à un token à 6 décimales décale des centimes, et sur un chemin de paiement une erreur d'arrondi silencieuse n'est jamais acceptable.

La solution est de ne jamais passer par un nombre : on décale la virgule sur la chaîne de caractères. On complète la partie décimale de zéros jusqu'à la longueur voulue, on concatène, on retire les zéros de tête. Le résultat est exact par construction, quelle que soit la valeur.

Transférer un token : la cible s'inverse

Envoyer des USDC plutôt que de l'ETH change la structure de l'URI, et c'est le point que la plupart des générateurs ignorent :

ethereum:0xCONTRAT@137/transfer?address=0xDESTINATAIRE&uint256=12500000

Trois différences, chacune source d'erreur :

  • La cible de l'URI est le contrat du token, pas le destinataire. Celui-ci passe dans le paramètre address. Inverser les deux envoie les fonds au contrat lui-même, où ils sont irrécupérables.
  • Le montant s'appelle uint256, du nom du type Solidity attendu par la fonction transfer.
  • Il n'y a pas de value. L'actif natif ne bouge pas ; seul le token est transféré. Un value résiduel enverrait de l'ETH en plus du token.

Les décimales du token : l'erreur à 10¹²

uint256 est exprimé dans l'unité atomique du token, et chaque token définit ses propres décimales dans son contrat. USDC et USDT en ont 6. DAI, WETH et la majorité des autres en ont 18. WBTC en a 8.

Cette variabilité est ce qui rend l'erreur si vicieuse : contrairement au wei, le montant affiché reste un nombre plausible. L'écart joue dans les deux sens, avec un facteur 10¹² à chaque fois :

  • Déclarer 18 décimales sur un token qui en a 6 demande 12 500 000 000 000 USDC au lieu de 12,50. C'est le cas heureux : la transaction échoue faute de solde.
  • Déclarer 6 décimales sur un token qui en a 18 demande 0,0000000000125 DAI au lieu de 12,50. Et là, elle passe.

Il n'existe aucun moyen de deviner les décimales hors ligne : il faut les lire dans le contrat, ou les connaître. C'est pourquoi web3qr demande explicitement cette valeur plutôt que d'en supposer une, et n'embarque aucune adresse de contrat en dur — une adresse de token erronée dans le code enverrait les fonds vers le mauvais contrat, sans qu'aucune vérification locale puisse le détecter.

Solana Pay : une troisième convention

Solana adopte une position intermédiaire :

solana:B38he25bGWrwuvAYa3dPDKVkXGzSYftxqoC8sitkGg1r?amount=12.5&spl-token=EPjFWdd5...

Le paramètre amount reste décimal, comme BIP-21 — la spécification l'exprime en unité usuelle. Et surtout, l'ajout de spl-token ne change pas la cible : l'URI vise toujours le destinataire, jamais le mint. Le wallet lit les décimales du token sur la chaîne, ce qui supprime entièrement la classe d'erreur décrite plus haut.

Solana Pay prévoit aussi reference, une clé publique servant d'identifiant unique pour retrouver la transaction, et memo, inscrit dans le registre. Un commerçant peut ainsi rapprocher un paiement d'une commande sans base de données intermédiaire.

Ce qu'il faut retenir sur les unités

  • BIP-21 : amount en BTC, décimal.
  • EIP-681 natif : value en wei, entier, 18 décimales.
  • EIP-681 token : uint256 en unité atomique du token, entier, décimales variables.
  • Solana Pay : amount en unité usuelle, décimal, que ce soit du SOL ou un token SPL.

Valider l'adresse, pas seulement sa forme

Beaucoup de générateurs vérifient les adresses avec une expression régulière : bonne longueur, bon alphabet, bon préfixe. C'est insuffisant, parce que ces trois propriétés survivent à une faute de frappe.

Les formats d'adresse embarquent tous un checksum, précisément pour détecter ce cas :

  • Base58Check pour les adresses Bitcoin historiques — quatre octets de double SHA-256.
  • Bech32 et Bech32m pour SegWit, définis par les BIP-173 et BIP-350.
  • EIP-55 pour les adresses EVM : la casse mixte encode un keccak256 de l'adresse en minuscules.
  • Les clés publiques Solana sont des points ed25519 encodés en Base58, dont le décodage échoue si un caractère change.

web3qr vérifie ces checksums localement, dans le navigateur, avant de générer le QR code. Une adresse dont un caractère a été altéré est rejetée à la saisie — pas après l'impression de mille affiches.