Release: develop -> main - #55
Merged
Merged
Conversation
…rary default (#54) * fix(minter-guard): take the deny fee from the chain, not from the library default Measured against production: the guard reported "Low gas" and would have refused to deny for a signer that in fact holds enough for dozens of denies. The pinned ethers version does not ask the chain for a priority fee. In getFeeData() it hardcodes one gwei whenever the latest block carries a base fee: maxPriorityFeePerGas = BigInt("1000000000"); maxFeePerGas = (block.baseFeePerGas * BN_2) + maxPriorityFeePerGas; On this deployment the base fee is 0.001 gwei and the chain's own suggested tip, via eth_maxPriorityFeePerGas, is 100 wei. The library therefore produced roughly a thousand times the fee the chain asks for, and the guard used that number both to price a deny and to let the library compose the transaction. That is not a wrong check. EIP-1559 makes the sender reserve gasLimit multiplied by maxFeePerGas, so a transaction carrying an inflated maxFeePerGas genuinely cannot be submitted by an account that cannot cover the reservation — the pre-check was right to refuse. What was wrong is composing the transaction with a fee nobody asked for: with a 208,000 gas ceiling the reservation came to 0.000208 cBTC against a real cost of 0.000000208, while the signer holds 0.0000093. The guard was inert on a cheap chain unless its signer was funded about a thousandfold, and the dashboard said "Low gas" about a funded signer. A single new resolveDenyFee() now reads the fee from the chain once and returns both the number used for the arithmetic and the overrides put on the transaction, so the fee that was verified affordable is by construction the fee that gets sent. It uses the base fee plus the chain's suggested tip; if that RPC is unsupported it falls back to the library's tip and then to zero, logging at each step, since a zero tip can leave a deny unmined; on a chain without a base fee it uses the legacy gas price; and it still throws when nothing usable comes back. Gas amounts and the ceiling are untouched — this is only about the price per gas. Effect with the values measured on the live chain: the reservation drops from 0.000208416 to 0.000000416 cBTC, gasEnough flips from false to true, and the current balance covers 22 denies instead of none. * fix(minter-guard): price each send against the block it goes out with Review of the previous commit on this branch. The fee cap was resolved once per cycle and reused for every send, but sends are spread across minutes — each one waits for its confirmation — while this chain produces a block every couple of seconds. A base fee that rose during the cycle therefore left later candidates going out with a stale cap: rejected as underpriced, or unmined until the confirmation timeout, burning an attempt while the veto window runs. The split now follows what actually moves. The parts that do not move are resolved once per cycle: the chain's suggested tip, or the legacy gas price on a chain without a base fee. The cap itself is built per send from the base fee of the block that send is priced against — and that block is already in hand, because the just-in-time window check fetched it a few lines earlier. No extra call, and the cap can no longer drift away from the chain state the send decision was made on. Two smaller corrections from the same review: - The cycle-level resolver makes up to three sequential chain calls and had no deadline check between them, while every other sequential chain in this file checks before each call. It now does the same, and defers the cycle instead of overrunning it. The read-only status path calls it without a deadline, because a single request is not a cycle. - Two comments claimed the pre-check and the send "can no longer disagree" and that the node "cannot reject as underfunded" afterwards. That holds for the first send of a cycle only: the floor prices one deny while a cycle can send several, so a signer funded for exactly one will have its second send rejected for funds, surfacing as an ordinary per-candidate failure. Multiplying the floor by the candidate count was deliberately not done — it would let one underfunded signer block denies that are individually affordable — and that trade-off is now written down where the choice is made. * fix(minter-guard): check affordability against the fee the send carries The previous commit made each send price itself from the block fetched for its own window check, but affordability was still verified in the pre-check, priced from a different block read two chain calls earlier. The node enforces the reservation against the fee the transaction actually carries, so a base fee that rose in that window could make even the first send fail on funds after a green pre-check — precisely what the comment there claimed could not happen. The balance needed to close this was already in hand: the pre-check reads it. It is now returned and checked in the send loop against the very fee that send will carry, computed from the same block. No additional chain call. A shortfall defers the candidate rather than failing it — unmarked, still a candidate next cycle — and pages through the existing rate-limited gas kind. The pre-check floor stays where it is, as the cheap cycle-level gate that avoids per-candidate work for an obviously unfunded signer; the per-send check is now the guarantee. The comments say what is true and name what is not: a base-fee rise between the pre-check and a send can no longer cause a funds rejection, for the first send or any later one. What remains is inherent to any read-then-broadcast scheme — the base fee can move between that final read and inclusion. The doubling cap absorbs the ordinary case; beyond it the transaction is simply not mined at that cap and is retried next cycle, which is not a funds rejection. The balance itself is the pre-check's snapshot, which holds because within a cycle it only decreases through this guard's own serial denies.
TaprootFreak
approved these changes
Jul 28, 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.
Automatic Release PR
This PR was automatically created after changes were pushed to develop.
Commits: 1 new commit(s)
Checklist