OUT_OF_ENERGY : l'erreur TRON qui dépense les frais et n'envoie rien
Un transfert USDT part, le portefeuille affiche des frais prélevés, et le destinataire n'a rien. La transaction est dans un bloc — elle n'a pas été rejetée et aucun nœud n'était en panne — et là où un transfert abouti indique SUCCESS, celui-ci indique OUT_OF_ENERGY. Sur TRON, cette erreur veut dire ce qu'elle dit : l'appel s'est retrouvé à court d'énergie en cours de route et s'est arrêté. Voici le mécanisme, les trois façons dont une campagne de paiements y tombe, et les remèdes, du plus rapide à celui qui coûte réellement le moins.
Ce que dit vraiment le résultat
Envoyer des USDT, c'est appeler un smart contract, et l'exécution d'un contrat sur TRON se compte en énergie plutôt qu'en frais forfaitaires (ce qu'est l'énergie). L'énergie propre de l'expéditeur est dépensée d'abord. Quand elle ne couvre pas l'appel, le réseau ne s'arrête pas là — il achète le reste en brûlant les TRX de l'expéditeur au tarif du protocole de 100 sun l'unité, mais seulement dans la limite de ce que permet le fee_limit de la transaction elle-même. Ce champ ne correspond à aucuns frais facturés par qui que ce soit. C'est un plafond sur la quantité de TRX de l'expéditeur que cet appel-là a le droit de brûler.
Atteignez ce plafond alors que l'appel n'est pas terminé, et l'exécution s'arrête. Tous les changements d'état qu'il avait faits sont annulés, donc aucun jeton ne bouge, et le réseau écrit son verdict sur la transaction, à l'endroit où un appel abouti porte SUCCESS. Ce verdict est le mot qu'un explorateur affiche à côté d'une transaction échouée, et le champ qu'un nœud renvoie sous le nom contractRet. Rien dans cette transaction n'a échoué à atteindre le réseau. Elle a atteint le réseau et a été exécutée jusqu'à ce que l'argent pour le faire soit épuisé.
Les TRX sont dépensés dans tous les cas
L'annulation défait le transfert. Elle ne défait pas la combustion : l'énergie consommée est de l'énergie payée, et l'expéditeur se retrouve amputé de tout ce que la tentative a brûlé en chemin vers le plafond, sans aucun jeton déplacé, avec en prime un hachage de transaction qu'un script de rapprochement classera sans hésiter comme un paiement.
Ce qui fait du réessai réflexe le geste coûteux. Le même transfert avec la même limite dépense les mêmes TRX pour le même résultat, et une boucle de paiements qui rejoue ses échecs automatiquement peut le faire plusieurs fois avant qu'un humain lise la chaîne de résultat. La plupart des échecs qu'un service de paiements rencontre coûtent de l'argent en silence. Celui-ci en coûte bruyamment et passe quand même inaperçu, parce que le portefeuille annonce des frais et que l'argent n'est nulle part.
Pourquoi une limite qui marchait hier cesse de marcher
Le destinataire a changé, la limite non. C'est celui-là qu'une campagne de paiements rencontre en premier. Un transfert vers une adresse qui détient déjà des USDT consomme environ 65,000 unités d'énergie ; vers une adresse qui n'en a jamais détenu, le transfert doit aussi créer le compte de jeton du destinataire, et cela fait environ 131,000 — deux fois l'appétit pour la même somme déplacée. Brûler pour le premier coûte 6.5 TRX au tarif du protocole, et pour le second 13.1 TRX. Un plafond taillé sur le cas ordinaire est court de moitié la première fois qu'un nouveau client est payé, et rien dans le lot n'a l'air différent : c'est une propriété du destinataire, pas du transfert.
L'énergie était là et n'y est plus. Une délégation est une quantité pour une durée, pas un abonnement. Ce que le premier transfert dépense n'est plus là pour le second, et à la fin de la durée le reste retourne à son propriétaire. Un portefeuille qui a déjà envoyé une fois dans la même fenêtre a moins que ce que suggère le chiffre affiché de la délégation, et c'est le transfert suivant qui s'arrête — ce qui, vu de l'extérieur, ressemble au même transfert qui échoue au hasard.
La limite est à zéro. Zéro est la bonne valeur quand l'énergie est certaine, et en mode B, où nous livrons l'énergie et diffusons la transaction nous-mêmes, fee_limit est justement ce que nous vous demandons de laisser vide : une limite ici ne serait rien d'autre qu'une autorisation permanente de brûler les TRX de l'expéditeur si quelque chose tournait mal. C'est aussi un arrêt net. Une autorisation nulle veut dire qu'un appel à court d'une seule unité s'arrête au lieu de la payer : une limite laissée à zéro est donc une décision qui ne tient qu'aussi longtemps que l'énergie.
Le voir avant que la transaction soit signée
La question derrière le premier déclencheur — combien d'énergie coûte ce destinataire-là — a une réponse avant que quoi que ce soit soit signé, par deux appels qui ne réservent rien et ne débitent rien.
GET /v1/address-check répond pour une adresse : activated, holds_usdt, blacklisted, is_contract, et l'expected_kind qui en découle. C'est holds_usdt qui fixe la taille de l'appel.
curl -s -H "Authorization: Bearer $KEY" \ "https://api.nrg.market/v1/address-check?address=TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9" # expected_kind vaut "single", "double" ou "custom"
POST /v1/estimate répond pour un lot — jusqu'à 500 destinataires en un appel, chacun avec un kind, les energy_units derrière lui et d'éventuels warnings. single est le transfert ordinaire et double celui qui ouvre un compte de jeton. custom est l'élément qu'aucune règle empirique ne couvre : le destinataire se révèle être un contrat avec sa propre logique de transfer, donc aucun des deux chiffres standards ne s'applique et energy_units vient d'une exécution à blanc de l'appel réel. Ce sont ces éléments-là dont il faut dimensionner la limite un par un, et l'avertissement contract_recipient à côté d'eux est souvent le premier signe qu'une adresse de paiement n'est pas du tout un portefeuille.
Les remèdes, le plus facile d'abord
Relevez la limite. Un champ, aucune infrastructure, et l'arrêt disparaît : l'appel se termine et le réseau prend ce qu'il lui faut. Le coût, lui, ne disparaît pas. Une limite est une autorisation de brûler, donc une limite généreuse est une combustion généreuse — le transfert qui s'arrêtait aboutit désormais à 13.1 TRX. Juste comme filet sous une campagne de paiements, faux comme plan.
Mettez de l'énergie sur l'expéditeur. Il ne reste alors rien que la limite ait à autoriser. L'énergie vient du gel de TRX, ce qui veut dire immobiliser un solde de travail et surveiller un budget de ressources, ou de la location d'une délégation pour les quelques minutes dont un transfert a besoin (ce que cela coûte en ce moment). Dans les deux cas, l'appel est payé en énergie et il n'y a aucune combustion.
Envoyez à l'intérieur de la fenêtre. L'énergie louée est sur le portefeuille pour une durée, et la réponse d'une commande porte send_before — l'instant où la fenêtre de livraison se ferme, trois secondes avant la fin de la durée achetée. Guettez l'apparition de ce champ plutôt que le mot ready : la commande est honorée par la livraison de l'énergie, donc completed le suit immédiatement. Envoyer après send_before, c'est mettre le transfert dans le réseau quand l'énergie est peut-être déjà rentrée chez elle, ce qui est exactement le manque dont parle cet article.
Confiez-nous la transaction signée. Le mode B vous enlève entièrement la fenêtre : vous déposez un transfert déjà signé, nous le gardons, et nous le diffusons une fois l'énergie confirmée on-chain. Si une transaction que nous diffusons atteint quand même un bloc sans s'exécuter, la position se ferme en failed avec transaction_reverted et toute la réservation est libérée — cette cause-là, comme toutes les autres de la liste, n'est jamais facturée. Lisez le nom pour ce qu'il est : transaction_reverted est notre mot pour « elle a atteint un bloc et ne s'est pas exécutée », quel qu'ait été le mot du réseau pour le refus, si bien qu'un OUT_OF_ENERGY et un REVERT propre à un contrat arrivent sous le même statut. Les TRX qu'une telle transaction a brûlés en chemin vers ce verdict sont ceux de l'expéditeur et non les nôtres, ce qui plaide pour l'estimation en haut de cette section plutôt que pour une boucle de réessai en bas.