Scripts crypto : Stellar XLM & Ethereum

Je ne fais pas confiance aux applications avec ma crypto. Chaque wallet, exchange, et dapp est une boîte noire. Vous confiez votre clé privée, cliquez sur « envoyer », et espérez que le code fait ce qu'il prétend. Donc j'ai construit mes propres scripts à la place : de petits programmes Python auditables que je peux lire et comprendre complètement.
Les différences entre Stellar et Ethereum forcent à confronter la philosophie de conception de chaque blockchain. Voici ce que j'ai appris en construisant des scripts pour les deux.
Commencer : génération de paires de clés
La première différence m'a frappé immédiatement : comment financer un compte testnet ?
Avec Stellar, c'est presque trop facile. Générer une paire de clés, puis appeler Friendbot une fois :
keypair = Keypair.random()
public_key = keypair.public_key
url = f"https://friendbot.stellar.org?addr={public_key}"
response = urllib.request.urlopen(url)
body = json.loads(response.read())
funding_tx = body.get("hash")
Boom. 10 000 XLM, instantanément. Stellar a été conçu pour les développeurs.
Ethereum ? Une histoire différente.
account = Account.create()
address = account.address
private_key = account.key.hex()
Puis il faut aller trouver un faucet qui permettra d'alimenter le compte sur le réseau testnet. J'ai opté pour l'Ethereum Sepolia Faucet de Google qui m'a permis de commencer rapidement avec 0.05 Sepolia ETH.
Le testnet d'Ethereum vous demande de prouver que vous êtes humain. Ce n'est pas un défaut, c'est un choix de conception. Sepolia est rare par conception, et cette rareté vous force à être intentionnel à propos des tests.
Le problème des décimales
J'ai découvert le problème des limites de décimales par accident. J'ai essayé d'envoyer 10.12345678 XLM et Stellar l'a rejeté silencieusement au niveau RPC. Il s'avère que Stellar s'arrête à 7 décimales :
value = Decimal(amount)
decimals = abs(value.as_tuple().exponent) # Stellar : max 7
Ethereum va beaucoup plus loin. 18 décimales, parce que wei (10^-18 ETH) est la plus petite unité :
value = Decimal(amount)
amount_wei = Web3.to_wei(value, "ether") # Ethereum : max 18
Cela m'a enseigné quelque chose : les blockchains ne sont pas interchangeables. Elles ont des contraintes dures intégrées dans leur protocole. On ne peut pas simplement déplacer du code de l'une à l'autre, il faut comprendre et respecter les limites de chaque système.
Mémos : différents compromis
Je voulais inclure des mémos descriptifs dans les transactions, quelque chose comme « facture #12345 » pour suivre les paiements. Stellar l'a rendu simple :
memo = "payment"
builder.add_text_memo(memo) # 28 octets UTF-8, gratuit
28 octets, inclus dans les frais de base. Terminé.
Ethereum n'a pas de mémos natifs. À la place, vous encodez les données dans la transaction elle-même :
memo = "payment"
data = "0x" + memo.encode("utf-8").hex()
tx["data"] = data # Chaque octet ≈ 16 gaz
C'est coûteux. Un mémo de 256 octets coûte ~4 000 gaz, ce qui aux taux actuels représente environ 0,10 $. Donc sur Ethereum, vous y pensez à deux fois avant d'ajouter des métadonnées à une transaction. Sur Stellar, vous le mettez sans y penser. Conception différente, compromis différents.
Le problème de destination
Quand j'ai testé l'envoi vers un compte inexistant, j'ai obtenu des comportements complètement différents.
Sur Stellar, cela échoue :
server.load_account(destination) # Vérifier si le compte existe
Stellar vous oblige à prouver que le compte de destination est réel. S'il n'existe pas, vous ne pouvez pas envoyer. Vous avez besoin d'une opération create_account d'abord, puis d'un envoi. Deux étapes. Cela vous force à réfléchir à ce que vous faites.
Sur Ethereum, n'importe quelle adresse est valide :
destination = Web3.to_checksum_address(destination) # Normaliser avec checksum
Ethereum enverra volontiers des fonds vers une adresse qui n'existe pas encore. L'adresse est valide, la transaction réussit, et maintenant ces fonds sont verrouillés dans une adresse contractuelle que personne ne possède. Pour toujours. Le checksum vous aide à éviter les fautes de frappe, mais il ne peut pas vous sauver d'une mauvaise adresse.
Modèles de frais : la plus grande différence
C'est ici que Stellar et Ethereum ont révélé leurs philosophies fondamentalement différentes.
Le modèle de frais de Stellar est d'une simplicité désarmante :
base_fee = server.fetch_base_fee()
ops_count = len(transaction.transaction.operations)
fee = base_fee * ops_count / 10_000_000
print(f"Frais : {fee} XLM ({ops_count} opérations)")
Récupérer les frais de base, multiplier par le nombre d'opérations, terminé. Les frais sont prévisibles. Ils s'ajustent doucement avec la charge du réseau. Vous pouvez estimer les coûts de gaz avec précision avant de soumettre.
Le modèle d'Ethereum est... plus complexe :
gas_price = w3.eth.gas_price
tx = {"from": source, "to": dest, "value": amount_wei}
gas_limit = w3.eth.estimate_gas(tx)
fee = gas_limit * gas_price
print(f"Frais : ~{Web3.from_wei(fee, 'ether'):.8f} ETH")
Vous devez estimer. Et l'estimation peut changer. Le ~ dans ma sortie n'est pas cosmétique, c'est un avertissement que ce frais est approximatif. Les prix du gaz augmentent. Les estimations sont incorrectes. Vous devez vérifier à nouveau juste avant de soumettre. Cela impose un workflow différent : toujours faire un dry-run d'abord.
Séquences et nonces : le problème de synchronisation
Les deux blockchains utilisent des compteurs pour garantir que les transactions ne soient pas dupliquées ou réordonnées. Mais elles exposent le problème différemment.
Les numéros de séquence de Stellar s'auto-incrémentent :
source_account = server.load_account(public_key)
builder = TransactionBuilder(source_account=source_account, ...)
transaction = builder.build()
Commode, mais aussi dangereux. Si vous soumettez deux transactions simultanément, elles auront le même numéro de séquence et l'une échouera. Pour les scripts, je le documente simplement comme une limitation. Pour les systèmes de production, vous auriez besoin d'une file d'attente ou d'un mutex.
Le nonce d'Ethereum est quelque chose que vous récupérez :
nonce = w3.eth.get_transaction_count(source_address)
tx = {"nonce": nonce, ...}
Même problème, même solution nécessaire, mais maintenant c'est explicite. Vous êtes responsable de la récupération du nonce. Vous voyez le problème immédiatement. D'une certaine manière, Ethereum vous force à réfléchir à la concurrence.
Gestion des clés : deux philosophies
Pour les tests, les deux blockchains vous permettent d'utiliser des clés privées en clair. Mais Ethereum a également un format de clé prêt pour la production : le keystore chiffré.
Stellar : texte en clair avec permissions restrictives :
with open(f"keys/{public_key}.secret", "w") as f:
os.chmod(f.name, 0o600) # chmod 600
f.write(secret_key)
Pour testnet, c'est correct. Pour mainnet, vous êtes livré à vous-même.
Ethereum : chiffrement intégré :
password = getpass.getpass("Choisir un mot de passe :")
keystore = Account.encrypt(private_key, password)
# L'utiliser plus tard
python send_eth.py --keystore keystores/0x123....json
# Demande le mot de passe lors de l'exécution
Donc j'ai construit les deux workflows. Pour Ethereum mainnet, j'utilise toujours des keystores chiffrés. Le mot de passe n'est jamais stocké, je le tape à chaque utilisation. C'est une étape supplémentaire, mais cela compte.
Fiabilité RPC : Stellar vs Ethereum
Stellar a un seul point de terminaison officiel : Horizon. Solide comme le roc.
Ethereum en a des centaines de publics, et ils sont tous peu fiables :
rpcs = ["https://ethereum-sepolia-rpc.publicnode.com", "https://rpc.sepolia.org", ...]
for rpc_url in rpcs:
w3 = Web3(Web3.HTTPProvider(rpc_url))
if w3.is_connected():
break
Les RPC publics tombent. Ils limitent le débit. Ils sont peu fiables. Donc je réessaie plusieurs points de terminaison. C'est ennuyeux, mais nécessaire.
Messages d'erreur : structurés vs analysés
Quand une transaction échoue, Stellar est explicite :
# Horizon retourne des codes structurés comme :
# "tx_insufficient_balance", "tx_bad_seq", "op_no_destination"
Lire le code, comprendre ce qui ne va pas, le corriger.
Ethereum retourne des messages d'erreur :
# Les erreurs RPC arrivent comme des chaînes :
# "insufficient funds", "nonce too low", "gas too low"
J'ai dû écrire une logique d'analyse de chaînes pour extraire l'erreur réelle. Pas élégant, mais ça fonctionne.
Journalisation d'audit : non-négociable
Après une transaction réussie sur l'une ou l'autre blockchain, je la journalise immédiatement :
timestamp = datetime.now(timezone.utc).isoformat()
log_line = f"{timestamp} | {network} | from={source} | to={dest} | amount={amount} | tx={tx_hash}\n"
with open(f"transactions.{network}.log", "a") as f:
f.write(log_line)
Ne jamais journaliser les clés privées, juste la source, la destination, le montant, et le hash de transaction. C'est votre reçu. Des mois plus tard, vous aurez besoin de la preuve de ce qui s'est passé. Le journal est cette preuve.
Dry-run : toujours tester d'abord
Les deux scripts supportent un drapeau --dry-run qui simule la transaction sans la soumettre :
if dry_run:
print(f"De : {source}")
print(f"À : {destination}")
print(f"Montant : {amount}")
print(f"Frais : {fee}")
print(f"Solde : {balance}")
return
Sur Ethereum surtout, c'est critique. Vous voyez le coût du gaz avant de vous engager. Vous vérifiez le solde. Vous vérifiez l'adresse de destination. Le workflow devient : dry-run testnet, dry-run mainnet, soumission mainnet.
La vraie leçon
J'ai construit ces scripts parce que je ne fais pas confiance aux boîtes noires avec ma crypto. Chaque exchange, chaque wallet, chaque dapp est un point centralisé de défaillance. N'importe lequel d'eux pourrait avoir un bug qui vide votre compte. N'importe lequel pourrait être piraté. N'importe lequel pourrait disparaître.
Mais quand je lis mon propre code, quand j'ai écrit chaque ligne, je sais exactement ce qui se passe quand j'appuie sur Entrée. Pas de points de terminaison cachés. Pas d'intermédiaires. Juste Python + bibliothèques standard + appels blockchain directs.
Stellar et Ethereum sont des plateformes solides. Mais elles ne peuvent pas vous protéger de :
- Un wallet buggué qui calcule mal le gaz
- Un exchange qui verrouille votre compte
- Une dapp qui demande trop de permissions
- Une clé privée volée du cache d'un navigateur
L'auto-garde via du code auditable est la réponse.
Si vous construisez ceci aussi
- Faire confiance au code, pas aux interfaces, Vous devriez comprendre chaque ligne qui déplace vos actifs.
- Commencer sur testnet, Fonds gratuits. Zéro risque. Parfait pour apprendre.
- Tout journaliser, Vous avez besoin de la preuve. Le journal est votre reçu.
- Dry-run d'abord, Voir les frais. Voir la structure. Avant de vous engager.
- Garder simple, Pas d'abstractions. Pas de magie. Juste des étapes simples.
Si vous n'êtes pas à l'aise avec le code qui déplace vos actifs, ne l'utilisez pas. Et si vous utilisez un service dont vous ne pouvez pas lire le code, vous faites confiance au jugement de quelqu'un d'autre avec votre argent.
Lire le code. L'auditer. Le modifier. Le posséder.
Code source
Les deux scripts sont open source :
- Stellar XLM: https://github.com/Lunik/stellar_xlm
- Ethereum: https://github.com/Lunik/ethereum