close
Skip to content

Complete the MuSig create and take offer rewrite - #4966

Open
balag3 wants to merge 4 commits into
bisq-network:mainfrom
balag3:offer-rewrite-continuation
Open

Complete the MuSig create and take offer rewrite#4966
balag3 wants to merge 4 commits into
bisq-network:mainfrom
balag3:offer-rewrite-continuation

Conversation

@balag3

@balag3 balag3 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Completes the MuSig create/take-offer rewrite started by @HenrikJannsen (his branch Rebased-Apply-create-offer-workflow-to-UI-3), which I continued under #4883. Create-offer is Henrik's work; I added the take-offer use case and per-concern services, the two review slices, and the spec.

What's here

  • Henrik's create-offer workflow rewrite (per-concern Selection components, DraftOfferUseCase, presentation-only controllers, validation in use cases; one exception left: the create-offer review controller still assembles the offer options, tracked in Move MuSig offer construction out of the create-offer review controller #5000).
  • TakeOfferUseCase + per-concern take-offer services: init/validation, payment + eligibility, price, amount + limits, review + handoff, controller wiring.
  • Spec: docs/specifications/offer/mu-sig/take-offer.md.
  • Reconciled with current main via one merge commit (main's fixes since the branch — PriceQuote overflow, offer-list validity, amount-limit policy move — are preserved; the create-offer area is superseded by the rewrite). Not rebased, to keep Henrik's authorship intact — squash or rebase on merge if you prefer linear history.

Tests: offer, bisq-easy, trade, api, mu-sig and desktop suites green.

Deferred (product decisions, not blockers): the take-offer review shows N/A for the trade fee until the review and the protocol share one fee source — pending the fee-policy call. Tracking + open items in #4883.

Part of #4883.

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 257 files, which is 157 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 3cb7286f-21bf-43aa-81dd-513acf77d8f4

📥 Commits

Reviewing files that changed from the base of the PR and between d7fc7da and facd241.

📒 Files selected for processing (257)
  • AGENTS.md
  • api/src/main/java/bisq/api/dto/mappings/account/PaymentMethodDtoMappingHelper.java
  • api/src/test/java/bisq/api/dto/mappings/account/PaymentMethodDtoMappingHelperTest.java
  • apps/desktop/desktop-ui-harness-app/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/MuSigCreateOfferAutomationBinder.java
  • apps/desktop/desktop-ui-harness-app/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/price/MuSigCreateOfferPriceAutomationBinder.java
  • apps/desktop/desktop-ui-harness-app/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/direction_and_market/MuSigCreateOfferDirectionAndMarketAutomationBinder.java
  • apps/desktop/desktop-ui-harness-app/src/main/java/bisq/desktop/main/content/mu_sig/offer/listing/MuSigOfferbookAutomationBinder.java
  • apps/desktop/desktop-ui-harness-app/src/main/java/bisq/desktop_ui_harness_app/DesktopAutomationViewObserver.java
  • apps/desktop/desktop-ui-harness-app/src/test/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/MuSigCreateOfferAutomationBinderTest.java
  • apps/desktop/desktop-ui-harness-app/src/test/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/price/MuSigCreateOfferPriceAutomationBinderTest.java
  • apps/desktop/desktop-ui-harness-app/src/test/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/direction_and_market/MuSigCreateOfferDirectionAndMarketAutomationBinderTest.java
  • apps/desktop/desktop-ui-harness-app/src/test/java/bisq/desktop/main/content/mu_sig/offer/listing/MuSigOfferbookAutomationBinderTest.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/components/containers/BaselineHBox.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/BitcoinAmountDisplay.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/ChipButton.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/RangeSlider.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/components/offer/ReviewDataDisplay.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/create_offer/amount_and_price/amount/MuSigCreateOfferAmountController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/create_offer/amount_and_price/price/MuSigCreateOfferPriceController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/create_offer/direction_and_market/MuSigCreateOfferDirectionAndMarketController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/create_offer/payment/MuSigCreateOfferPaymentController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/amount_components/MuSigAmountInputFontSizeHelper.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/amount_components/MuSigAmountLayoutConstants.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/amount_components/SliderTrackStyleHelper.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/amount_components/passive/MuSigPassiveAmountController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/amount_components/passive/MuSigPassiveAmountModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/amount_components/passive/MuSigPassiveAmountView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/amount_components/text_input/MuSigAmountTextInputController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/amount_components/text_input/MuSigAmountTextInputModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/amount_components/text_input/MuSigAmountTextInputView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/components/MuSigPaymentMethodChipButton.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/components/MuSigPriceInput.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/components/MuSigPriceInputBox.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/components/MuSigReviewDataDisplay.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/components/amount_selection/MuSigAmountNumberBox.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/components/amount_selection/MuSigAmountSelectionController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/components/amount_selection/MuSigAmountSelectionModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/components/amount_selection/MuSigAmountSelectionView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/components/amount_selection/MuSigBigAmountNumberBox.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/components/amount_selection/MuSigSmallAmountNumberBox.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/MuSigCreateOfferController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/MuSigCreateOfferModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/MuSigCreateOfferView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/MuSigCreateOfferAmountAndPriceController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/MuSigCreateOfferAmountAndPriceModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/MuSigCreateOfferAmountAndPriceView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/MuSigCreateOfferAmountController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/MuSigCreateOfferAmountModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/MuSigCreateOfferAmountView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/MuSigAmountContainerController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/MuSigAmountContainerModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/MuSigAmountContainerView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/fix/FixAmountLayoutHelper.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/fix/MuSigFixAmountController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/fix/MuSigFixAmountModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/fix/MuSigFixAmountView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/fix/slider/MuSigFixAmountSliderController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/fix/slider/MuSigFixAmountSliderModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/fix/slider/MuSigFixAmountSliderView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/limits/MuSigAmountLimitsController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/limits/MuSigAmountLimitsModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/limits/MuSigAmountLimitsView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/range/MuSigRangeAmountController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/range/MuSigRangeAmountModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/range/MuSigRangeAmountView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/range/RangeAmountLayoutHelper.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/range/slider/MuSigRangeAmountSliderController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/range/slider/MuSigRangeAmountSliderModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/amount/container/range/slider/MuSigRangeAmountSliderView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/price/MuSigCreateOfferPriceController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/price/MuSigCreateOfferPriceModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/amount_and_price/price/MuSigCreateOfferPriceView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/direction_and_market/MarketListItem.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/direction_and_market/MarketTypeListItem.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/direction_and_market/MuSigCreateOfferDirectionAndMarketController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/direction_and_market/MuSigCreateOfferDirectionAndMarketModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/direction_and_market/MuSigCreateOfferDirectionAndMarketView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/payment/MuSigCreateOfferPaymentController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/payment/MuSigCreateOfferPaymentModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/payment/MuSigCreateOfferPaymentView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/review/MuSigCreateOfferReviewController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/review/MuSigCreateOfferReviewModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/create_offer/review/MuSigCreateOfferReviewView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/MuSigTakeOfferController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/MuSigTakeOfferModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/MuSigTakeOfferView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/MuSigTakeOfferAmountController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/MuSigTakeOfferAmountModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/MuSigTakeOfferAmountView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/MuSigAmountContainerController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/MuSigAmountContainerModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/MuSigAmountContainerView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/fix/FixAmountLayoutHelper.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/fix/MuSigFixAmountController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/fix/MuSigFixAmountModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/fix/MuSigFixAmountView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/fix/slider/MuSigFixAmountSliderController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/fix/slider/MuSigFixAmountSliderModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/fix/slider/MuSigFixAmountSliderView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/limits/MuSigAmountLimitsController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/limits/MuSigAmountLimitsModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/amount/container/limits/MuSigAmountLimitsView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/payment/MuSigTakeOfferPaymentController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/payment/MuSigTakeOfferPaymentModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/payment/MuSigTakeOfferPaymentView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/review/MuSigTakeOfferReviewController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/review/MuSigTakeOfferReviewModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/draft/take_offer/review/MuSigTakeOfferReviewView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/listing/MarketType.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/listing/MuSigOfferbookController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/listing/MuSigOfferbookView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/my_offers/MuSigMyOffersController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/take_offer/MuSigTakeOfferController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/take_offer/amount/MuSigTakeOfferAmountController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/offer/take_offer/amount/MuSigTakeOfferAmountModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/mu_sig/trade/trade_limits/simulation/MuSigTradeLimitsSimulationController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/settings/trade/TradeSettingsController.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/settings/trade/TradeSettingsModel.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/settings/trade/TradeSettingsView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/accounts/AccountDetails.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/accounts/crypto_accounts/create/summary/details/SummaryDetails.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/accounts/fiat_accounts/create/summary/details/AccountDetailsGridPane.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/top/MarketPriceComponent.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/main/top/TopPanelView.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/navigation/NavigationTarget.java
  • apps/desktop/desktop/src/main/java/bisq/desktop/overlay/OverlayController.java
  • apps/desktop/desktop/src/main/resources/css/mu_sig.css
  • apps/desktop/desktop/src/test/java/bisq/desktop/components/controls/BitcoinAmountDisplayTest.java
  • apps/desktop/desktop/src/test/java/bisq/desktop/main/content/mu_sig/offer/create_offer/amount_and_price/amount/MuSigCreateOfferAmountControllerTest.java
  • apps/desktop/desktop/src/test/java/bisq/desktop/main/content/mu_sig/offer/draft/amount_components/text_input/MuSigAmountTextInputControllerTest.java
  • bonded-roles/src/main/java/bisq/bonded_roles/BondedRolesService.java
  • bonded-roles/src/main/java/bisq/bonded_roles/market_price/MarketBasedAmountConversion.java
  • common/src/main/java/bisq/common/application/Disposable.java
  • common/src/main/java/bisq/common/application/DisposableScope.java
  • common/src/main/java/bisq/common/application/LifecycleScope.java
  • common/src/main/java/bisq/common/market/Market.java
  • common/src/main/java/bisq/common/monetary/Monetary.java
  • common/src/main/java/bisq/common/monetary/PriceQuote.java
  • common/src/main/java/bisq/common/monetary/PriceQuoteRange.java
  • common/src/main/java/bisq/common/monetary/TradeAmount.java
  • common/src/main/java/bisq/common/monetary/TradeAmountConversion.java
  • common/src/main/java/bisq/common/monetary/TradeAmountFactory.java
  • common/src/main/java/bisq/common/monetary/TradeAmountRange.java
  • common/src/main/java/bisq/common/observable/Pin.java
  • common/src/test/java/bisq/common/monetary/AmountConversionTest.java
  • common/src/test/java/bisq/common/monetary/MonetaryTest.java
  • common/src/test/java/bisq/common/monetary/PriceQuoteTest.java
  • common/src/test/java/bisq/common/monetary/TradeAmountConversionTest.java
  • contract/src/test/java/bisq/contract/mu_sig/MuSigContractTest.java
  • docs/dev/dev-guide.md
  • docs/dev/input_validation_policy.md
  • docs/specifications/offer/mu-sig/create-offer.md
  • docs/specifications/offer/mu-sig/take-offer.md
  • i18n/src/main/resources/mu_sig.properties
  • i18n/src/main/resources/settings.properties
  • mu-sig/src/main/java/bisq/mu_sig/MuSigService.java
  • mu-sig/src/main/java/bisq/mu_sig/MuSigTradeAmountLimits.java
  • offer/src/main/java/bisq/offer/Direction.java
  • offer/src/main/java/bisq/offer/amount/OfferAmountUtil.java
  • offer/src/main/java/bisq/offer/amount/spec/AmountSpecFactory.java
  • offer/src/main/java/bisq/offer/amount/spec/AmountSpecUtil.java
  • offer/src/main/java/bisq/offer/mu_sig/MuSigOffer.java
  • offer/src/main/java/bisq/offer/mu_sig/draft/AmountMappingService.java
  • offer/src/main/java/bisq/offer/mu_sig/draft/AmountUtils.java
  • offer/src/main/java/bisq/offer/mu_sig/draft/CreateOfferDraft.java
  • offer/src/main/java/bisq/offer/mu_sig/draft/CreateOfferDraftStateEngine.java
  • offer/src/main/java/bisq/offer/mu_sig/draft/CreateOfferDraftWorkflow.java
  • offer/src/main/java/bisq/offer/mu_sig/draft/PaymentMethodSelectionService.java
  • offer/src/main/java/bisq/offer/mu_sig/draft/ReadOnlyCreateOfferDraft.java
  • offer/src/main/java/bisq/offer/mu_sig/draft/TradeAmountConstraintsService.java
  • offer/src/main/java/bisq/offer/mu_sig/draft/TradeAmountLimits.java
  • offer/src/main/java/bisq/offer/mu_sig/draft/dependencies/DefaultCreateOfferDraftMarketData.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/DraftOfferUseCase.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/CreateOfferUseCase.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/DraftSnapshot.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/amount/AmountSelection.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/amount/CreateOfferAmountModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/amount/limits/AbsoluteAmountLimitsProvider.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/amount/limits/AmountLimitsProvider.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/amount/limits/PaymentMethodBasedAmountLimitsProvider.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/amount/limits/RatesCache.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/amount/limits/TradeAmountLimitUtils.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/amount/limits/UserSpecificAmountLimitsProvider.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/direction/CreateOfferDirectionModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/direction/DirectionSelection.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/market/CreateOfferMarketModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/market/MarketSelection.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/payment_method/CreateOfferPaymentMethodModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/payment_method/MarketAccounts.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/payment_method/PaymentMethodAccountSelection.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/payment_method/PaymentMethodSelection.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/payment_method/PaymentMethodSelectionResult.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/payment_method/PaymentMethodSelectionStatus.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/price/CreateOfferPriceModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/price/PriceSelection.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/create_offer/price/limits/PriceLimits.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/dependencies/AccountsProvider.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/dependencies/CreateOfferDraftCookieStore.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/dependencies/DefaultAccountsProvider.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/dependencies/DefaultCreateOfferDraftCookieStore.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/dependencies/DefaultTakeOfferDraftCookieStore.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/dependencies/TakeOfferDraftCookieStore.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/TakeOfferUseCase.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/TakeOfferValidationException.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/amount/TakeOfferAmountModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/amount/TakeOfferAmountReadOnlyModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/amount/TakeOfferAmountService.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/direction/TakeOfferDirectionService.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/market/TakeOfferMarketService.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/payment_method/AccountCompatibilityMismatch.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/payment_method/OfferAccounts.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/payment_method/PaymentMethodSelectionService.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/payment_method/TakeOfferPaymentMethodModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/payment_method/TakeOfferPaymentMethodReadOnlyModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/payment_method/TakeOfferPaymentMethodService.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/price/TakeOfferPriceModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/price/TakeOfferPriceReadOnlyModel.java
  • offer/src/main/java/bisq/offer/mu_sig/use_case/take_offer/price/TakeOfferPriceService.java
  • offer/src/main/java/bisq/offer/options/CollateralOption.java
  • offer/src/main/java/bisq/offer/price/PriceUtil.java
  • offer/src/main/java/bisq/offer/price/spec/FixPriceSpec.java
  • offer/src/test/java/bisq/offer/amount/OfferAmountUtilTest.java
  • offer/src/test/java/bisq/offer/amount/spec/AmountSpecFactoryTest.java
  • offer/src/test/java/bisq/offer/mu_sig/MuSigOfferWireTest.java
  • offer/src/test/java/bisq/offer/mu_sig/draft/AmountMappingServiceTest.java
  • offer/src/test/java/bisq/offer/mu_sig/draft/AmountUtilsTest.java
  • offer/src/test/java/bisq/offer/mu_sig/draft/CreateOfferDraftStateEngineTest.java
  • offer/src/test/java/bisq/offer/mu_sig/draft/CreateOfferDraftWorkflowTest.java
  • offer/src/test/java/bisq/offer/mu_sig/draft/PaymentMethodSelectionServiceTest.java
  • offer/src/test/java/bisq/offer/mu_sig/draft/TradeAmountConstraintsServiceTest.java
  • offer/src/test/java/bisq/offer/mu_sig/draft/TradeAmountLimitsTest.java
  • offer/src/test/java/bisq/offer/mu_sig/use_case/PaymentMethodSelectionServiceTest.java
  • offer/src/test/java/bisq/offer/mu_sig/use_case/create_offer/CreateOfferUseCaseTest.java
  • offer/src/test/java/bisq/offer/mu_sig/use_case/create_offer/amount/AmountSelectionTest.java
  • offer/src/test/java/bisq/offer/mu_sig/use_case/create_offer/amount/limits/AmountLimitsProvidersFailSoftTest.java
  • offer/src/test/java/bisq/offer/mu_sig/use_case/create_offer/amount/limits/RatesCacheTest.java
  • offer/src/test/java/bisq/offer/mu_sig/use_case/create_offer/amount/limits/TradeAmountLimitUtilsTest.java
  • offer/src/test/java/bisq/offer/mu_sig/use_case/create_offer/price/PriceSelectionTest.java
  • offer/src/test/java/bisq/offer/mu_sig/use_case/take_offer/TakeOfferUseCaseTest.java
  • offer/src/test/java/bisq/offer/mu_sig/use_case/take_offer/payment_method/TakeOfferPaymentMethodServiceTest.java
  • offer/src/test/java/bisq/offer/options/CollateralOptionTest.java
  • offer/src/test/java/bisq/offer/price/spec/FixPriceSpecTest.java
  • scripts/scenarios/mu-sig-create-offer-price.scenario
  • settings/src/main/java/bisq/settings/CookieKey.java
  • settings/src/main/java/bisq/settings/SettingsService.java
  • settings/src/main/java/bisq/settings/SettingsStore.java
  • settings/src/main/proto/settings.proto
  • support/src/test/java/bisq/support/arbitration/mu_sig/MuSigArbitratorServiceTest.java
  • support/src/test/java/bisq/support/dispute/mu_sig/MuSigDisputeContractIdentityChecksTest.java
  • support/src/test/java/bisq/support/dispute/mu_sig/MuSigDisputePaymentDetailsVerifierTest.java
  • support/src/test/java/bisq/support/mediation/mu_sig/MuSigMediatorServiceTest.java
  • support/src/test/java/bisq/support/mu_sig/MuSigRequestSizeTestFixtures.java
  • trade/src/test/java/bisq/trade/mu_sig/MuSigTradeDisputeServiceTest.java
  • trade/src/test/java/bisq/trade/mu_sig/MuSigTradeFormatterTest.java
  • trade/src/test/java/bisq/trade/mu_sig/MuSigTradeUtilsTest.java
  • trade/src/test/java/bisq/trade/mu_sig/messages/network/handler/MuSigContractVerifierTest.java
  • trade/src/test/java/bisq/trade/mu_sig/messages/network/handler/maker/MuSigTakeOfferRequestValidatorTest.java

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@balag3
balag3 marked this pull request as ready for review August 25, 2026 09:26
model.isPriceValid.set(true);
updateFromMarketPrice();

marketPricePin = marketPriceService.getMarketPriceByCurrencyMap().addObserver(() -> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we avoid writing the current market price back into PriceSelection on every market price map update?

onSetPriceQuote() also recalculates and persists the percentage, so this appears to reset a fixed price, or a non-zero floating markup, to the current market price / 0% without a user action. The same mutation also happens twice during activation above.

Should we preserve a fixed quote, and for floating pricing recompute the derived quote while preserving the selected percentage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in e4865f3 — the market observer now leaves a fixed price alone and, for a floating price, recomputes the quote from the kept percentage; activation no longer pushes the market price. While doing this I found a deeper, separate issue: the price-step controller round-trips every published quote back through onSetPricePercentage (no origin separation), which drifts the floating percentage on a market update and rewrites a fixed price on re-entry. Filed it as #4967 to fix on its own with proper testing.

userSpecificAmountLimitsProvider = new UserSpecificAmountLimitsProvider(marketPriceService, marketService, directionService, priceService);
}

@Override

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we explicitly calculate all three limit providers from the current market, direction, price, and payment state during initialization?

AbsoluteAmountLimitsProvider and UserSpecificAmountLimitsProvider only register custom listeners, but those listeners do not replay their current values. On the normal default path, those values were established before these listeners were attached, so AmountLimitsProvider can remain uninitialized and the amount input handlers silently ignore edits.

Could we also add a test proving that AmountSelection.initialize() becomes usable without manually firing dependency listeners?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in 11a20d7. Each sub-provider now computes its value in initialize() instead of waiting for a listener that never replays. Added a test.

@@ -259,10 +344,18 @@ public void takeOffer() {
delayedSuccessScheduler.stop();
}
delayedSuccessScheduler = UIScheduler.run(() -> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep this attempt in SENT or a pending state until we receive an actual protocol or delivery success signal?

MuSigTradeService.takeOffer() dispatches protocol handling asynchronously, so the absence of an error after 200 ms does not establish success. A rejection or delivery failure can arrive after this scheduler has already shown the success state and stopped the timeout.

If no completion signal is currently available, would a pending state be safer than reporting success based on elapsed time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, elapsed time isn't a real success signal. The protocol handler doesn't surface a delivery/completion event yet (that's the todo here), so there's nothing to drive a genuine pending→success transition — the 200 ms + error observer + timeout is the interim. Leaving it until the protocol reports delivery and fixing it there; I'll track it as a rewrite item on #4883.

model.clearAccountsByPaymentMethod();
}

public void putSelectedAccountByPaymentMethod(PaymentMethod<?> paymentMethod, Account<?, ?> account) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we enforce the payment-selection invariant at this public mutation boundary?

Could we verify that the account's payment method equals paymentMethod, that the account belongs to the eligible account list for that method, and that the method is offered? getSelectedPaymentMethodSpec() later derives the spec from the map key while getSelectedAccount() returns the value, so an inconsistent entry would be handed to the protocol as a mismatched spec/account pair.

The current controller passes matching values, but the take offer specification identifies the use case as the enforcement point for this guarantee.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in 9c43f9d. Checks the account matches the method key and is in the eligible list (which is offered-only, so that's covered too).

checkArgument(min.getBaseSideMonetary().getCode().equals(max.getBaseSideMonetary().getCode()),
"this and max base side codes must match. this.base=%s; max.base=%s",
min.getBaseSideMonetary().getCode(), max.getBaseSideMonetary().getCode());
checkArgument(min.getQuoteSideMonetary().getCode().equals(max.getQuoteSideMonetary().getCode()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also verify that this quote's quote side currency matches the min/max quote side currency?

The current checks compare this quote's base currency and verify that min/max match each other, but never compare this.getQuoteSideMonetary().getCode() with the limits. As a result, a BTC/EUR quote whose numeric value is inside BTC/USD limits can be returned unchanged.

Would comparing both sides of all three quotes here make this clamp fail closed across markets?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. The clamp now also compares this quote's quote side against the limits, so a quote from another market fails closed instead of being returned unchanged. Fixed in: 4d1799d

// User interaction
/* --------------------------------------------------------------------- */

public void onSetUseBaseCurrencyForAmountInput(boolean value) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we recompute the input amount range, user limit marker, and normalized slider values when the active input side changes?

At present this only changes the boolean and persists it, leaving the derived state denominated and mapped using the previous input side until another limits update occurs. The create offer specification explicitly lists this recomputation as an update trigger, and the take flow performs the corresponding recalculation in TakeOfferUseCase.setUseBaseCurrencyForAmountInput().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in c01269f. The switch now recomputes range/marker/slider on the new side, and the amount + slider controllers use the take flow's origin separation so the programmatic re-write on the switch no longer feeds back against the stale range (verified live for both fixed and range offers).

min.getQuoteSideAmount().getCode(), max.getQuoteSideAmount().getCode());
}

public String printRelevantStringa(Market market) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

printRelevantStringa appears to be a misspelled duplicate of printRelevantString immediately below it, and I could not find any callers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, removed. Dead misspelled duplicate with no callers (the same method was also in PriceQuoteRange). Fixed in: 0f0adbd


private void applyPricePercentage(double pricePercentage, boolean notifyListeners) {
if (Double.compare(pricePercentage, model.getPricePercentage()) != 0) {
pricePercentage = priceLimits.clamp(pricePercentage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the create-offer UI route out-of-range percentage input through this clamp?

MuSigCreateOfferPriceController.applyPercentageString() retains the previous behavior of validating and returning before onSetPricePercentage() is called. That means this newly added clamp, and the specification saying that UI input is clamped, does not currently apply to percentage input.

Is the intended behavior to change the UI to use this clamp, or should we retain rejection and adjust the specification accordingly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, clamp it, spec stays. Can't do it on its own though — it hits the same feedback loop as #4967: even routing the clamped percentage into the domain, the quote observer derives it back from the rounded quote and validateQuote rejects it again (a valid small percentage on a low-sat crypto market gets rewritten the same way). So I'll fold the clamp into #4967 — the origin separation there is what makes it stick.

// market price, keeping the percentage. Only when this market has a price available.
Market market = draftOfferUseCase.getMarket();
if (market != null && marketPriceService.findMarketPrice(market).isPresent()) {
priceSelection.onSetPricePercentage(priceSelection.getPricePercentage());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we refresh the price limits from the same market price update before recomputing the floating quote?

PriceLimits.initialize() only reacts to market selection, so its PriceQuoteRange remains based on the previous market price. This call computes a quote from the new market price, but PriceSelection.applyPriceQuote() then clamps it against that stale range. For example, if the market moves from 100 to 200, a 0% quote is calculated as 200 but clamped to the old +50% maximum of 150, while the stored percentage remains 0%.

Should this market-price update live in PriceSelection/PriceLimits so the limits and quote can be refreshed together? Could we also cover upward and downward market ticks, including the -10% and +50% endpoints, with domain tests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: 7674aa4. Details and testing on #4967.

}));


pins.add(priceSelection.priceQuoteObservable().addObserver(priceQuote ->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep domain published quote updates display only before this PR merges?

This observer calls onQuoteInput() → applyPercentageFromQuote() → PriceSelection.onSetPricePercentage(), writing a displayed projection back into the domain. The percentage observer has a similar path through applyPercentageString() and setQuote(). Registration and market price updates can therefore rewrite a fixed price on re-entry or drift a floating percentage through monetary rounding.

#4967 documents the reproductions, but this controller is introduced by this PR and the behavior changes trade prices without user input. Should the origin separation and its regression tests land here rather than being deferred?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed #4967 to document the issue and make scoping and review focused, since this refactor is estimated to be quite big chunk. The plan is to deliver the fix on this branch and close the issue pointing at that commit. IS that OK?

checkNotNull(inputAmount, "inputAmount must not be null");
Market market = marketSelection.getMarket();
PriceQuote priceQuote = priceSelection.getPriceQuote();
if (amountLimits.isInitialized() && market != null && priceQuote != null) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we base these input guards on the currently available limit ranges rather than the latched isInitialized() flag? The invalid-dependency path now clears effectiveTradeAmountLimits and potentialTradeAmountLimits while leaving initialized true.

After a valid USD draft is switched to EUR while BTC/USD is temporarily unavailable, entering an amount reaches clamp(..., null) and throws an NPE. I reproduced this through onSetFixTradeAmountFromInputAmount.

Could we fail softly until the current limits are available, use both ranges where required by the slider handlers, and add a market-switch regression test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: 59c4368.

// Empty when a required market price is missing (e.g. the draft started before the
// first price arrived): the default seed is applied when the limits first initialize.
try {
return MarketBasedAmountConversion.findTradeAmountFromUsdAmount(marketPriceService, market, DEFAULT_TRADE_AMOUNT_IN_USD);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we treat every non-positive required price quote as unavailable here, matching PriceSelection and TradeAmountLimitUtils.findRates? MarketPrice.verify() validates only the timestamp.

With a positive BTC/EUR quote but a negative BTC/USD quote, default seeding produces a negative TradeAmount. The limit providers reject that rate, but isDraftReadyForReview() still returns true because it checks only for non-null values; materializing the negative amount spec later fails verification.

Should we validate every conversion leg as > 0?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: 59c4368.

this.market = market;
return fresh;
}
if (rates != null && market.equals(this.market)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we invalidate the cached rates as soon as the requested market differs, even when no fresh rates are available?

With A(valid) → B(no rates), the A rates remain cached. Returning B → A while rates are still unavailable then returns the pre-switch A rates. This means retention does span a market change despite the class contract.

Could we clear the cache on the first market change and add an A → B → A regression test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: 59c4368.

// these with a snapshot so a concurrent market-price update cannot produce mixed values.
/* --------------------------------------------------------------------- */

public static Optional<Monetary> findBaseSideFixedAmount(PriceQuote resolvedPriceQuote, AmountSpec amountSpec, Market market) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we verify that resolvedPriceQuote.getMarket().equals(market) before performing these conversions?

Unlike the service-backed overloads, these six public methods accept the quote and market independently. PriceQuote conversion checks only the Fiat/Coin runtime type, not the currency code, so a BTC/USD quote passed with a BTC/EUR market silently treats an EUR amount as USD. A focused test expecting an IllegalArgumentException confirmed that no exception is currently thrown.

Should we centralize the preconditions for all six overloads and add mismatched-market coverage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: 59c4368.

// first price arrived): the default seed is applied when the limits first initialize.
try {
return MarketBasedAmountConversion.findTradeAmountFromUsdAmount(marketPriceService, market, DEFAULT_TRADE_AMOUNT_IN_USD);
} catch (ArithmeticException e) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove this catch, or update it to describe the exception it is still intended to handle? MarketBasedAmountConversion now filters zero and negative quotes to Optional.empty() before conversion, so a zero price no longer reaches this ArithmeticException branch. The current comment therefore describes control flow that no longer occurs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: bbbc435.

String maxTradeLimit = MuSigTradeAmountLimits.getFormattedMaxTradeLimitInUsd(fiatPaymentRail);
private void applySelectFiatPaymentRail(FiatPaymentRail paymentRail) {
model.getSelectedFiatPaymentRail().set(paymentRail);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove the trailing spaces from this blank line so git diff --check is clean?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: bbbc435.

@balag3

balag3 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Open product related question. None of them requires an immediate decision.

MuSig trade fee policy/schedule
Description: MuSig will have a trade fee, but the schedule and basis are undecided. The UI and the protocol have no shared fee source: the protocol
hard-codes 5,000 sats and a fixed receiver address in NonceSharesRequestUtil.
Current solution: The take-offer review shows a localized N/A for the fee until a single authoritative fee policy exists that both the review display and the
protocol consume.

Max price-deviation bound
Description: Offers priced too far from the market price should be treated as invalid in the offerbook and blocked from being taken, but the maximum
deviation value has not been chosen. (The related warning threshold is decided: user setting, default 10%.)
Current solution: The full mechanism is specified (offerbook invalidation, take-side init reject, runtime confirmation block) but stays dormant until the bound is defined; today only the non-blocking warning fires.

Reputation source for the buyer amount cap
Description: The user-specific buyer amount limit needs a real reputation input; whether the limits concept from discussion #4164 is final still has to be confirmed.
Current solution: UserSpecificAmountLimitsProvider uses a hardcoded mock value (4000 USD); replacement comes as a separate PR. The seller cap is deliberately absent by design (intent note in the spec).

Wallet affordability check
Description: The take flow should verify the taker can actually fund the trade, without blocking fundless new users from exploring the UI (so it must not act as an amount-limits provider).
Current solution: Designed as an end-of-flow confirmation check with a wallet-funding popup; dormant until a real wallet service exists to check against, currently no affordability check runs.

@balag3
balag3 force-pushed the offer-rewrite-continuation branch from bbbc435 to 42e691d Compare September 2, 2026 08:09
Monetary quoteSideAmount;
if (market.isBtcFiatMarket()) {
PriceQuote btcFiatPriceQuote = marketPriceService.getMarketPriceQuoteOrThrow(market);
quoteSideAmount = AmountConversion.usdToFiat(btcUsdPriceQuote, btcFiatPriceQuote, usdAmount);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This “exact” take-side limit can still wrap before the final checked conversion. AmountConversion.usdToFiat() uses the legacy PriceQuote conversions, which end in longValue().

A focused test with positive rates turned the intended $10,000 cap into a positive wrapped limit of €155,325,592,629,044.8384 / 1,553,255,926.29044838 BTC instead of throwing. Because the result remains positive, the downstream guard accepts it and the absolute cap can effectively be bypassed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: 95485a1.

checkNotNull(usdAmount, "usdAmount must not be null");

Market usdBitcoinMarket = MarketRepository.getUSDBitcoinMarket();
PriceQuote btcUsdPriceQuote = marketPriceService.getMarketPriceQuoteOrThrow(usdBitcoinMarket);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One amount constraint recomputation can combine different market-price snapshots. This reads BTC/USD and the offer market separately; for a BTC/USD offer it reads the same map entry twice. A sequential 100,000 → 200,000 mock turns a $100 cap into $200. computeAmountConstraints() then calls this helper independently for the absolute minimum, absolute maximum, payment-rail limit, and user limit.

findRates() above already captures and reuses one context to avoid this race. Should we capture one Rates snapshot at the start of computeAmountConstraints(), pass it through a checked overload for every limit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: 95485a1.

* Like {@link #toTradeAmount(Market, PriceQuote, Monetary)}, but fails with an
* ArithmeticException when the converted side does not fit into a long.
*/
public static TradeAmount toTradeAmountExact(Market market, PriceQuote priceQuote, Monetary amount) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toTradeAmountExact() checks that the amount belongs to market, but it does not verify that priceQuote belongs to the same market. A focused test with a BTC/EUR market, BTC/USD quote, and EUR amount succeeds and returns a BTC/EUR pair calculated using the USD rate, because PriceQuote checks only the Fiat/Coin runtime type.

The same invariant is now enforced in OfferAmountUtil. Should we validate market.equals(priceQuote.getMarket()) in both public conversion overloads and add mismatched-market coverage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: 95485a1.

formatBtcAmount(amount);
try {
formatBtcAmount(amount);
} catch (Exception ignored) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This empty broad catch leaves the previously rendered amount visible. If the property changes from a valid value to malformed text, the control is made visible before parsing fails, so stale BTC data appears current. It also hides unexpected programming failures.

Should we catch only the expected parse failure, explicitly clear or hide an invalid value, and let unexpected exceptions remain visible?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: dd3cdda.

}
return quoteSideAmount.compareToRange(quoteSideLimits);
}
//

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: 95485a1.

@KimStrand KimStrand left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Six newly added test classes start directly with the package declaration.
Can we please add the standard licence header to these files as well?

Fiat usdAmount) {
checkNotNull(rates, "rates must not be null");
checkNotNull(market, "market must not be null");
checkNotNull(priceQuote, "priceQuote must not be null");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public limit conversion currently checks only for nulls. PriceQuote.toBaseSideMonetaryExact validates the monetary class rather than the currency code, so, for example, a BTC/GBP quote can be accepted for a BTC/EUR market and produce a numerically valid but incorrect limit. This relies on every caller keeping rates, market, and priceQuote coherent, which is fragile for code used to enforce create/take amount caps.

Should we validate that priceQuote.getMarket() matches market and that the fiat rate, when present, belongs to the same market in both conversion methods?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: ac9c149.

.flatMap(stringValue -> {
try {
return Optional.of(Long.parseLong(stringValue));
} catch (Throwable t) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added long-cookie API currently has no call sites. It also extends the existing broad Throwable catch pattern, which can suppress VM or linkage failures even though malformed Long.parseLong input only requires handling NumberFormatException.

Should we remove these unused overloads for now, or, if they are intentionally retained, narrow both new catches to NumberFormatException?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Fixed in: 679c27c.

@balag3

balag3 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Confirmed. Fixed in: 4683bc5.

HenrikJannsen and others added 4 commits September 11, 2026 11:31
Restructure offer creation and taking around per-concern domain
components. Market, direction, price, amount and payment method each get
a selection model and service, orchestrated by a DraftOfferUseCase base
with explicit lifecycle scopes. Controllers become thin bindings onto
the domain observables.

The old create and take offer UI moves wholesale under the draft
packages; navigation targets stay unchanged. Specifications for the
create offer use case and the input validation policy are added
alongside the code.
Add the take offer specification covering initialization validation,
payment method eligibility, price handling, amount limits, review and
handoff.

Fix the foundations the take offer work builds on: recompute all
range-derived slider values when the amount range changes so no stale
clamp fractions survive a payment method switch, port the rounded-fiat
range-versus-fixed decision into the amount spec factory, and rebuild
the trade limit info endpoint on the new limits domain. Remove a test
orphaned by the draft refactor.
Fill in the gutted TakeOfferUseCase and its per-concern services.
Initialization validates the offer at the trust boundary (protocol type,
own offer across all local identities, market price availability, price
bounds, spec and option shape, collateral symmetry). Payment method
selection enforces account eligibility including compatibility data,
with the incompatibility explained in the no-account prompt. Price
handling resolves a single quote snapshot, warns on configurable
deviation and revalidates on market updates. Amount selection keeps the
stored denomination authoritative, intersects offer, absolute and
payment method limits, applies the buyer cap and blocks confirmation
while limits are stale or amounts invalid. Review displays a deferred
fee, guards resubmission while in flight, supports taking without a
mediator after explicit consent, and hands off a single atomic
amount-price snapshot to the protocol.

All amount and price writers are serialized, callbacks are guarded
against stale attempts and wizard close, conversions fail closed on
overflow, and fixed-price quotes are verified at deserialization. The
wizard keeps its step index valid on step removal and persists the
amount input side.
Separate price data by origin so market ticks no longer overwrite the
offer price, clear amounts on market switches, and recompute amounts on
input side switches without feedback loops. Compute amount limits during
initialization, degrade unpriced markets gracefully and gate the review
step on a complete draft.

Harden the guards: amount mutators require available limits instead of
a latched initialized flag, market based conversions require positive
quotes, the rates cache invalidates on market changes, and the resolved
quote overloads verify the quote matches the market. Add UI harness
coverage for the price step and clean up dead code left by the refactor.
@balag3
balag3 force-pushed the offer-rewrite-continuation branch from a9f3ea1 to facd241 Compare September 11, 2026 09:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants