Fix native coin forward gas buffer#3529
Merged
TaprootFreak merged 5 commits intodevelopfrom Mar 31, 2026
Merged
Conversation
Increase gas fee buffer from 1.00001x to 2x when calculating the send amount for native coin forwards. The previous 0.001% buffer was insufficient to handle gas price fluctuations between the cached fee estimation and actual send, causing value + gas to exceed the wallet balance. This resulted in transactions being dropped from the mempool and an infinite forward/timeout/reset loop.
❌ Security: 1 critical vulnerabilities |
Instead of relying on a cached fee estimate (30s TTL) to calculate the send amount, fetch the current gas cost at dispatch time. This eliminates the race condition where gas price changes between the cached estimation and actual send, causing value + gas > balance and the transaction to be dropped from the mempool.
Fresh gas cost is fetched milliseconds before the actual send, so only a minimal buffer is needed for potential block boundary gas price changes (max 12.5% per block via EIP-1559).
The return path for native coins had no gas deduction at all, which would cause the same value + gas > balance issue when chargebackAmount equals the full deposit amount.
Resolves 8 security advisories including JS injection, prototype pollution, and DoS via malformed decorator syntax.
davidleomay
approved these changes
Mar 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Native coin forwards (ETH, MATIC, etc.) können in einer Endlosschleife stecken bleiben, wenn der Deposit-Betrag klein ist.
Ursache
Beim Forward von nativen Coins wird der Send-Betrag berechnet als:
Die
estimatedGasFeestammt aus einem 30-Sekunden-Cache (CacheItemResetPeriod.EVERY_30_SECONDSin der Payout-Strategy). Beim tatsächlichen Senden insendNativeCoinwird aber ein frischer Gas-Preis viagetRecommendedGasPrice()geholt. Zwischen dem gecachten Wert und dem frischen Wert kann sich der Gas-Preis ändern, und der bisherige Puffer von1.00001(0.001%) reicht nicht aus, um das abzufangen:Die Transaktion wird zwar signiert und an den RPC-Node geschickt (TX-Hash wird zurückgegeben), aber sofort aus dem Mempool gedroppt weil die Balance nicht reicht.
Auswirkung
outTxIdwird gespeichert, Status =ForwardedgetTxReceipt()gibtnull→ Confirmation schlägt fehlAcknowledged,outTxIdgelöschtZusätzlich: Falls ein Pay-In im
PREPARED-Status stecken bleibt (dispatch Exception), wird beim nächsten Cron-Lauf die alte gespeicherte Fee wiederverwendet, die noch stärker veraltet sein kann.Konkretes Beispiel (Transaction 307721)
sendAmount = 0.01 - 0.000003 × 1.00001 = 0.009997 ETHactualGasCost = 21000 × 0.18 gwei = 0.0000039 ETHsendAmount + actualGasCost = 0.010001 ETH > 0.01 ETH❌Verifiziert on-chain:
0xdaee...d71ec) hat noch exakt 0.01 ETH (unverändert)Fix
Statt die gecachte Fee-Schätzung mit einem minimalen Puffer zu verwenden, wird jetzt der frische Gas-Preis zum Zeitpunkt des Sendens geholt:
Warum das funktioniert
getGasCostForCoinTransaction()ruftgasLimit × getRecommendedGasPrice()direkt am RPC-Node ab — kein CachegetGasCostForCoinTransaction()undsendNativeCoin()vergehen nur Millisekunden (gleicher Code-Pfad). Gas ändert sich auf Ethereum nur an Block-Grenzen (~12s)Math.max(freshGasCost, estimatedNativeFee)nimmt den höheren WertFür das konkrete Beispiel:
freshGasCost = 0.00000378 ETH(21000 × 0.18 gwei)gasCost = max(0.00000378, 0.000003) = 0.00000378reserved = 0.00000378 × 1.5 = 0.00000567sendAmount = 0.01 - 0.00000567 = 0.00999433 ETHtotal = 0.00999433 + 0.00000378 = 0.00999811 ETH < 0.01 ETH✅Änderungen
payin-evm.service.tsgetGasCostForCoinTransaction()die den frischen Gas-Preis vom Client holtevm-coin.strategy.tsdispatchSendverwendet frischen Gas-Preis statt gecachter SchätzungTest plan