Repository navigation
cancel_offers: build the batch as one spend so no fee coin is picked twice - #894
Open
awizardxch wants to merge 1 commit into
Open
awizardxch wants to merge 1 commit into
awizardxch wants to merge 1 commit into
Conversation
…twice cancel_offers cancelled each offer in its own SpendContext and joined the results. When an offer holds no XCH of its own (a CAT or NFT offer), each cancel selected an XCH fee coin independently, so two cancels could pick the same coin and the joined bundle spent it twice (MEMPOOL_CONFLICT / DOUBLE_SPEND). The wallet now cancels every offer in one spend: one coin per offer, and the fee coins selected once. The fee stays per offer (fee x count), as the UI labels it; cancel_offer is the one-offer case of the same code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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
cancel_offerscancels each offer in its ownSpendContextand then joins the coin spends into one bundle. When an offer holds no XCH of its own (a CAT or NFT offer), its cancel needs an XCH coin for the fee, and each cancel selects one on its own. Nothing marks a coin as used between them, so two cancels can pick the same fee coin. The joined bundle then spends it twice and the node refuses it (DOUBLE_SPEND/MEMPOOL_CONFLICT).This is one of the failures described in #780 ("Sage's internal coin selector can pick the same fee coin for two different transactions"). Within a single
cancel_offerscall it happens without any concurrency: a wallet with one spare XCH coin and two CAT offers is enough.Fix
Wallet::cancel_offersbuilds every cancellation in one spend: one cancellable coin per offer, plus a single fee action. Selection inside one spend already skips coins it has picked (select_spends_excluding), so the fee coins are selected once.Wallet::cancel_offeris now the one-offer case of the same code.The fee stays per offer: the endpoint passes
fee × offer count, matching the UI's "Network Fee (per offer)" label and the amount users pay today. The multiplication is checked for overflow.Test
test_cancel_offers_share_one_fee_coinsets up a wallet with one XCH coin and two CAT-only offers.cancel_offersspends it once. The bundle confirms on the simulator, both CAT coins come back, and the XCH balance drops by the fee exactly once.cargo test -p sage-wallet offer: 22 passed.cargo fmt --checkandcargo clippy -p sage-wallet -p sage --all-targetsare clean.🤖 Generated with Claude Code