Repository navigation
Add coin_ids to send_xch, send_cat, cancel_offer and cancel_offers - #895
Open
awizardxch wants to merge 2 commits into
Open
awizardxch wants to merge 2 commits into
awizardxch wants to merge 2 commits 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>
Callers can choose which coins pay, as make_offer already allows (xch-dev#780): a market maker keeps a pool of small fee coins, and concurrent sends and cancels each get their own instead of the selector picking the same one, or a large trading coin, for a tiny fee. Chosen coins are spent first and coin selection only adds what they do not cover. Each must be one the wallet would select by itself: its own, confirmed, unspent, not in the mempool and not locked in an open offer, and of a kind the transaction spends (XCH; coins of the CAT for send_cat). Anything else is refused with CoinNotSelectable instead of being spent, so a chosen coin can never cancel an open offer or pull an NFT or DID into a send. App-bridge sends keep automatic selection. 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.
Closes #780 (priorities 1 and 2: the cancel and send endpoints).
What
An optional
coin_ids: Option<Vec<String>>onSendXch,SendCat,CancelOfferandCancelOffers, wired the same wayMakeOffer.coin_idsis. Chosen coins are spent first, and coin selection only adds what they don't cover. When the field is left out, behavior is unchanged.send_xch: XCH coins for the amount and the fee.send_cat: coins of that CAT for the amount, and XCH coins for the fee.cancel_offer/cancel_offers: XCH coins for the fee, spent beside the offers' own coins (all in one spend, see cancel_offers: build the batch as one spend so no fee coin is picked twice #894).A market maker can now give every concurrent cancel or send its own fee coin. Without that, the selector picks the same fee coin for two transactions, or uses a large trading coin for a tiny fee and breaks a prepared coin layout. Those are the two failures in #780.
Security: chosen coins must be selectable
prepare_spends_for_selectionlooks a chosen coin up inwallet_coins. That table also holds coins that are spent, in the mempool, or locked in an open offer. Taking caller ids as-is would let a send spend the coin behind one of the wallet's own open offers, which silently cancels that offer, or pull an NFT or DID coin into a fee.So each requested id has to pass a check at the RPC boundary,
selectable_coin_idsincrates/sage/src/utils/coin_selection.rs:selectable_xch_coins/selectable_cat_coins): the wallet's own coins that are confirmed, unspent, not in the mempool and not locked in an offer;Anything else is refused with a new
CoinNotSelectableerror (an API error, HTTP 400) instead of being spent. Duplicate ids are de-duplicated.The check reuses existing queries, so no new
sqlxquery or.sqlxentry is needed. The app bridge'ssend_xchkeeps automatic selection (coin_ids: None), so in-wallet apps don't get coin choice.Wallet API
Wallet::send_xch_with_coinsandWallet::send_cat_with_coinstake the chosen coins.send_xchandsend_catcall them with none, so existing callers are untouched.Wallet::cancel_offersgains afee_coin_idsargument.Tests
sage-rpctest_send_xch_with_coin_idsruns through the real router:CoinNotSelectable;make_offerwithauto_import) is refused, and the free coin still sends.With the selectability check disabled, this test fails.
Also run:
cargo test -p sage-wallet50 passed,cargo test -p sage-rpc test_send_xch2 passed,cargo clippy --workspace --all-targetsandcargo fmt --checkclean.src/bindings.tsis updated by hand in the generator's format (coin_ids?: string[] | null).Not in this PR: #780's priority 3 (fee coin choice for split/combine).
🤖 Generated with Claude Code