From a22edd60f81cb6f70cbad633614e2ce6d5574101 Mon Sep 17 00:00:00 2001 From: woodser Date: Tue, 4 May 2021 20:20:30 -0400 Subject: [PATCH] Haveno --- .gitattributes | 2 +- .github/ISSUE_TEMPLATE/new_asset.md | 18 - .github/boring-cyborg.yml | 15 - .github/stale.yml | 35 - .github/workflows/build.yml | 35 + .gitignore | 1 + CODEOWNERS | 14 +- CONTRIBUTING.md | 101 --- LICENSE | 1 + README.md | 122 +++- .../method/trade/TakeBuyBSQOfferTest.java | 6 +- .../method/trade/TakeBuyBTCOfferTest.java | 6 +- .../method/trade/TakeSellBSQOfferTest.java | 6 +- .../method/trade/TakeSellBTCOfferTest.java | 6 +- .../scenario/bot/protocol/BotProtocol.java | 6 +- .../java/bisq/asset/coins/IridiumTest.java | 2 + build.gradle | 14 +- .../main/java/bisq/cli/CurrencyFormat.java | 9 + cli/src/main/java/bisq/cli/TradeFormat.java | 22 +- .../java/bisq/common/taskrunner/Task.java | 2 +- .../witness/AccountAgeWitnessService.java | 3 + .../java/bisq/core/api/CoreTradesService.java | 1 - .../bisq/core/api/CoreWalletsService.java | 1 - .../bisq/core/api/model/ContractInfo.java | 16 +- .../java/bisq/core/api/model/TradeInfo.java | 36 +- .../java/bisq/core/app/BisqExecutable.java | 2 + .../main/java/bisq/core/app/BisqSetup.java | 27 +- .../java/bisq/core/app/WalletAppSetup.java | 7 +- .../app/misc/ExecutableForAppWithP2p.java | 2 + .../src/main/java/bisq/core/btc/Balances.java | 65 +- .../java/bisq/core/btc/BitcoinModule.java | 4 + .../btc/listeners/XmrBalanceListener.java | 38 + .../bisq/core/btc/model/XmrAddressEntry.java | 150 ++++ .../core/btc/model/XmrAddressEntryList.java | 235 ++++++ .../btc/setup/MoneroWalletRpcManager.java | 128 ++++ .../bisq/core/btc/setup/WalletConfig.java | 100 +++ .../bisq/core/btc/setup/WalletsSetup.java | 17 +- .../core/btc/wallet/TradeWalletService.java | 36 + .../bisq/core/btc/wallet/WalletService.java | 22 +- .../core/btc/wallet/XmrWalletService.java | 460 ++++++++++++ .../java/bisq/core/locale/CurrencyUtil.java | 4 +- core/src/main/java/bisq/core/locale/Res.java | 18 +- .../bisq/core/offer/OpenOfferManager.java | 12 +- .../availability/DisputeAgentSelection.java | 17 +- .../ProcessOfferAvailabilityResponse.java | 2 +- .../offer/placeoffer/PlaceOfferModel.java | 10 + .../offer/placeoffer/PlaceOfferProtocol.java | 4 +- .../bisq/core/payment/JapanBankAccount.java | 11 +- .../core/payment/payload/PaymentMethod.java | 3 +- .../presentation/BalancePresentation.java | 28 +- .../network/CoreNetworkProtoResolver.java | 24 + .../CorePersistenceProtoResolver.java | 9 +- .../core/provider/mempool/MempoolService.java | 5 +- .../core/setup/CorePersistedDataHost.java | 2 + .../bisq/core/support/dispute/Dispute.java | 11 + .../core/support/dispute/DisputeManager.java | 94 ++- .../core/support/dispute/DisputeResult.java | 26 +- .../arbitration/ArbitrationManager.java | 461 ++++++++++-- .../PeerPublishedDisputePayoutTxMessage.java | 27 +- .../dispute/mediation/MediationManager.java | 6 +- .../messages/ArbitratorPayoutTxRequest.java | 107 +++ .../messages/ArbitratorPayoutTxResponse.java | 101 +++ .../messages/OpenNewDisputeMessage.java | 18 +- .../support/dispute/refund/RefundManager.java | 9 +- .../java/bisq/core/trade/ArbitratorTrade.java | 84 +++ .../bisq/core/trade/BuyerAsMakerTrade.java | 30 +- .../bisq/core/trade/BuyerAsTakerTrade.java | 28 +- .../main/java/bisq/core/trade/BuyerTrade.java | 32 +- .../main/java/bisq/core/trade/Contract.java | 54 +- .../bisq/core/trade/SellerAsMakerTrade.java | 26 +- .../bisq/core/trade/SellerAsTakerTrade.java | 28 +- .../java/bisq/core/trade/SellerTrade.java | 32 +- .../java/bisq/core/trade/TradableList.java | 12 +- core/src/main/java/bisq/core/trade/Trade.java | 412 +++++++---- .../bisq/core/trade/TradeDataValidation.java | 37 +- .../java/bisq/core/trade/TradeManager.java | 472 ++++++++---- .../main/java/bisq/core/trade/TradeUtil.java | 65 +- .../main/java/bisq/core/trade/TradeUtils.java | 81 +++ .../trade/failed/FailedTradesManager.java | 14 +- ...CounterCurrencyTransferStartedMessage.java | 19 +- .../core/trade/messages/DepositTxMessage.java | 56 +- .../trade/messages/InitMultisigMessage.java | 106 +++ .../core/trade/messages/InitTradeRequest.java | 171 +++++ .../MakerReadyToFundMultisigRequest.java | 79 ++ .../MakerReadyToFundMultisigResponse.java | 118 +++ .../messages/PayoutTxPublishedMessage.java | 19 +- .../trade/messages/UpdateMultisigRequest.java | 99 +++ .../messages/UpdateMultisigResponse.java | 99 +++ .../trade/protocol/ArbitratorProtocol.java | 96 +++ .../trade/protocol/BuyerAsMakerProtocol.java | 131 +++- .../trade/protocol/BuyerAsTakerProtocol.java | 242 ++++++- .../core/trade/protocol/BuyerProtocol.java | 77 +- .../core/trade/protocol/DisputeProtocol.java | 44 +- .../core/trade/protocol/MakerProtocol.java | 8 +- .../core/trade/protocol/ProcessModel.java | 135 +++- .../protocol/ProcessModelServiceProvider.java | 5 +- .../trade/protocol/SellerAsMakerProtocol.java | 136 ++-- .../trade/protocol/SellerAsTakerProtocol.java | 242 ++++++- .../core/trade/protocol/SellerProtocol.java | 67 +- .../core/trade/protocol/TakerProtocol.java | 12 +- .../trade/protocol/TradeMessageListener.java | 12 + .../core/trade/protocol/TradeProtocol.java | 109 ++- .../trade/protocol/TradeProtocolFactory.java | 3 + .../core/trade/protocol/TradeTaskRunner.java | 2 +- .../bisq/core/trade/protocol/TradingPeer.java | 12 + .../protocol/tasks/BroadcastPayoutTx.java | 60 +- .../tasks/ProcessInitMultisigMessage.java | 228 ++++++ .../tasks/ProcessInitTradeRequest.java | 133 ++++ ...ssPeerPublishedDelayedPayoutTxMessage.java | 36 +- .../tasks/ProcessUpdateMultisigRequest.java | 115 +++ .../tasks/PublishTradeStatistics.java | 82 +++ .../tasks/SetupDepositTxsListener.java | 98 +++ .../protocol/tasks/SetupPayoutTxListener.java | 92 ++- .../tasks/UpdateMultisigWithTradingPeer.java | 125 ++++ .../arbitration/PublishedDelayedPayoutTx.java | 63 +- .../buyer/BuyerCreateAndSignPayoutTx.java | 245 +++++++ ...essDepositTxAndDelayedPayoutTxMessage.java | 79 +- .../BuyerProcessPayoutTxPublishedMessage.java | 22 +- ...CounterCurrencyTransferStartedMessage.java | 13 +- .../buyer/BuyerSetupDepositTxListener.java | 290 ++++---- .../tasks/buyer/BuyerSignPayoutTx.java | 92 --- .../BuyerVerifiesFinalDelayedPayoutTx.java | 37 +- .../BuyerAsTakerCreatesDepositTxInputs.java | 33 +- .../BuyerAsTakerSendsDepositTxMessage.java | 79 +- .../BuyerAsTakerSignsDepositTx.java | 89 +-- .../maker/MakerCreateAndPublishDepositTx.java | 186 +++++ .../maker/MakerCreateAndSignContract.java | 39 +- .../tasks/maker/MakerCreateFeeTx.java | 151 ++++ .../maker/MakerSendsInitTradeRequest.java | 150 ++++ ...MakerSendsReadyToFundMultisigResponse.java | 173 +++++ .../maker/MakerSetupDepositTxsListener.java | 19 + .../maker/MakerVerifyTakerDepositTx.java | 79 ++ .../mediation/FinalizeMediatedPayoutTx.java | 141 ++-- ...ocessMediatedPayoutTxPublishedMessage.java | 79 +- .../SendMediatedPayoutTxPublishedMessage.java | 22 +- .../tasks/mediation/SignMediatedPayoutTx.java | 119 ++- .../seller/SellerCreatesDelayedPayoutTx.java | 1 + ...CounterCurrencyTransferStartedMessage.java | 6 +- .../seller/SellerPublishesDepositTx.java | 68 +- .../SellerPublishesTradeStatistics.java | 69 +- .../SellerSendPayoutTxPublishedMessage.java | 10 +- ...ndsDepositTxAndDelayedPayoutTxMessage.java | 191 ----- .../seller/SellerSignAndFinalizePayoutTx.java | 112 --- .../seller/SellerSignAndPublishPayoutTx.java | 187 +++++ .../SellerAsMakerFinalizesDepositTx.java | 3 +- .../SellerAsMakerProcessDepositTxMessage.java | 47 +- .../SellerAsTakerCreatesDepositTxInputs.java | 45 +- .../SellerAsTakerSignsDepositTx.java | 1 + .../tasks/taker/CreateTakerFeeTx.java | 117 --- .../protocol/tasks/taker/FundMultisig.java | 152 ++++ .../tasks/taker/TakerCreateFeeTx.java | 76 ++ .../TakerProcessesMakerDepositTxMessage.java | 54 ++ .../tasks/taker/TakerPublishFeeTx.java | 94 +-- .../taker/TakerSendInitMultisigMessages.java | 125 ++++ .../taker/TakerSendInitTradeRequests.java | 161 +++++ .../TakerSendInputsForDepositTxRequest.java | 2 +- .../TakerSendReadyToFundMultisigRequest.java | 77 ++ .../taker/TakerSetupDepositTxsListener.java | 19 + .../taker/TakerVerifyAndSignContract.java | 53 +- .../trade/statistics/TradeStatistics2.java | 33 +- .../trade/statistics/TradeStatistics3.java | 65 +- .../statistics/TradeStatisticsConverter.java | 4 +- .../java/bisq/core/util/ParsingUtils.java | 20 + .../resources/i18n/displayStrings.properties | 658 ++++++++--------- .../i18n/displayStrings_cs.properties | 2 +- .../i18n/displayStrings_de.properties | 2 +- .../i18n/displayStrings_es.properties | 2 +- .../i18n/displayStrings_fa.properties | 2 +- .../i18n/displayStrings_fr.properties | 2 +- .../i18n/displayStrings_it.properties | 2 +- .../i18n/displayStrings_ja.properties | 2 +- .../i18n/displayStrings_pt-br.properties | 2 +- .../i18n/displayStrings_pt.properties | 2 +- .../i18n/displayStrings_ru.properties | 2 +- .../i18n/displayStrings_th.properties | 2 +- .../i18n/displayStrings_vi.properties | 2 +- .../i18n/displayStrings_zh-hans.properties | 2 +- .../i18n/displayStrings_zh-hant.properties | 2 +- .../witness/AccountAgeWitnessServiceTest.java | 2 + .../bisq/core/offer/OpenOfferManagerTest.java | 3 + .../java/bisq/desktop/app/BisqAppMain.java | 2 +- .../main/java/bisq/desktop/main/MainView.java | 22 +- .../bonding/reputation/MyReputationView.java | 2 +- .../bisq/desktop/main/debug/DebugView.java | 46 +- .../main/funds/deposit/DepositView.java | 2 +- .../main/funds/locked/LockedListItem.java | 37 +- .../transactions/DisplayedTransactions.java | 18 +- .../DisplayedTransactionsFactory.java | 10 +- .../DummyTransactionAwareTradable.java | 6 +- .../TransactionAwareOpenOffer.java | 8 +- .../TransactionAwareTradable.java | 6 +- .../TransactionAwareTradableFactory.java | 10 +- .../transactions/TransactionAwareTrade.java | 168 ++--- .../TransactionListItemFactory.java | 18 +- .../transactions/TransactionsListItem.java | 419 ++++++----- .../funds/withdrawal/WithdrawalListItem.java | 32 +- .../main/funds/withdrawal/WithdrawalView.java | 324 ++++----- .../main/offer/MutableOfferDataModel.java | 31 +- .../desktop/main/offer/MutableOfferView.java | 5 +- .../desktop/main/offer/OfferDataModel.java | 16 +- .../createoffer/CreateOfferDataModel.java | 6 +- .../offer/takeoffer/TakeOfferDataModel.java | 66 +- .../main/offer/takeoffer/TakeOfferView.java | 7 +- .../offer/takeoffer/TakeOfferViewModel.java | 9 +- .../main/overlays/windows/ContractWindow.java | 19 +- .../windows/DisputeSummaryWindow.java | 676 ++++++++---------- .../windows/ManualPayoutTxWindow.java | 37 +- .../overlays/windows/TradeDetailsWindow.java | 47 +- .../closedtrades/ClosedTradesView.java | 5 +- .../DuplicateOfferDataModel.java | 4 +- .../editoffer/EditOfferDataModel.java | 6 +- .../failedtrades/FailedTradesView.java | 9 +- .../pendingtrades/PendingTradesDataModel.java | 368 +++++----- .../pendingtrades/PendingTradesView.java | 10 +- .../pendingtrades/PendingTradesViewModel.java | 69 +- .../pendingtrades/steps/TradeStepView.java | 301 ++++---- .../steps/buyer/BuyerStep1View.java | 58 +- .../steps/buyer/BuyerStep2View.java | 33 +- .../steps/buyer/BuyerStep4View.java | 143 ++-- .../steps/seller/SellerStep1View.java | 28 +- .../dispute/DisputeSummaryVerification.java | 34 +- .../agent/arbitration/ArbitratorView.java | 10 +- .../client/mediation/MediationClientView.java | 3 +- .../client/refund/RefundClientView.java | 3 +- .../DisplayedTransactionsTest.java | 27 +- .../TransactionAwareTradableFactoryTest.java | 8 +- .../TransactionAwareTradeTest.java | 31 +- .../trades/TradesChartsViewModelTest.java | 5 - .../createoffer/CreateOfferDataModelTest.java | 8 +- .../createoffer/CreateOfferViewModelTest.java | 42 +- .../editoffer/EditOfferDataModelTest.java | 40 +- .../util/ImmutableCoinFormatterTest.java | 4 +- gradle/witness/gradle-witness.gradle | 7 +- .../bisq/monitor/metric/P2PNetworkLoad.java | 3 +- .../metric/P2PSeedNodeSnapshotBase.java | 2 +- .../bisq/network/p2p/network/Connection.java | 2 +- ...untAgeWitnessStore_XMR_MAINNET_placeholder | 1 + proto/src/main/proto/grpc.proto | 12 +- proto/src/main/proto/pb.proto | 239 +++++-- settings.gradle | 2 +- todo.txt | 30 + 241 files changed, 10631 insertions(+), 4905 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/new_asset.md delete mode 100644 .github/boring-cyborg.yml delete mode 100644 .github/stale.yml create mode 100644 .github/workflows/build.yml delete mode 100644 CONTRIBUTING.md create mode 100644 core/src/main/java/bisq/core/btc/listeners/XmrBalanceListener.java create mode 100644 core/src/main/java/bisq/core/btc/model/XmrAddressEntry.java create mode 100644 core/src/main/java/bisq/core/btc/model/XmrAddressEntryList.java create mode 100644 core/src/main/java/bisq/core/btc/setup/MoneroWalletRpcManager.java create mode 100644 core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java create mode 100644 core/src/main/java/bisq/core/support/dispute/messages/ArbitratorPayoutTxRequest.java create mode 100644 core/src/main/java/bisq/core/support/dispute/messages/ArbitratorPayoutTxResponse.java create mode 100644 core/src/main/java/bisq/core/trade/ArbitratorTrade.java create mode 100644 core/src/main/java/bisq/core/trade/TradeUtils.java create mode 100644 core/src/main/java/bisq/core/trade/messages/InitMultisigMessage.java create mode 100644 core/src/main/java/bisq/core/trade/messages/InitTradeRequest.java create mode 100644 core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigRequest.java create mode 100644 core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigResponse.java create mode 100644 core/src/main/java/bisq/core/trade/messages/UpdateMultisigRequest.java create mode 100644 core/src/main/java/bisq/core/trade/messages/UpdateMultisigResponse.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/TradeMessageListener.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigMessage.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/ProcessUpdateMultisigRequest.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/SetupDepositTxsListener.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerCreateAndSignPayoutTx.java delete mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndPublishDepositTx.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateFeeTx.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequest.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsReadyToFundMultisigResponse.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxsListener.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerVerifyTakerDepositTx.java delete mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java delete mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndPublishPayoutTx.java delete mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/taker/FundMultisig.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerCreateFeeTx.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesMakerDepositTxMessage.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitMultisigMessages.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitTradeRequests.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendReadyToFundMultisigRequest.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSetupDepositTxsListener.java create mode 100644 p2p/src/main/resources/AccountAgeWitnessStore_XMR_MAINNET_placeholder create mode 100644 todo.txt diff --git a/.gitattributes b/.gitattributes index 923d5445..54522ef5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,4 +12,4 @@ *.jpg binary *.jpeg binary *.png binary -p2p/src/main/resources/*BTC_MAINNET filter=lfs diff=lfs merge=lfs -text +p2p/src/main/resources/*XMR_MAINNET filter=lfs diff=lfs merge=lfs -text diff --git a/.github/ISSUE_TEMPLATE/new_asset.md b/.github/ISSUE_TEMPLATE/new_asset.md deleted file mode 100644 index 7f69fae8..00000000 --- a/.github/ISSUE_TEMPLATE/new_asset.md +++ /dev/null @@ -1,18 +0,0 @@ -Please fill in the following data to request for a new asset to be listed on Bisq. For more details, be sure to read [the full documentation](https://docs.bisq.network/exchange/howto/list-asset.html) on adding a new asset. - -### 1. Asset name - - -### 2. Ticker -_Your asset's ticker must not conflict with any national currency tickers (per [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217)) or any of the [top 100 cryptocurrency tickers](https://coinmarketcap.com/coins/)._ - - -### 3. Block explorer URL -_Your asset's block explorer must be active and publicly available so that transactions can be verified with a receiver's address...if this isn't possible, [see workarounds here](file:///home/steve/wb/bisq/bisq-docs/build/asciidoc/html5/exchange/howto/list-asset.html#arbitrators-must-be-able-to-look-up-transactions-in-the-asset-block-explorer)._ - -### 4. Additional technical requirements (yes/no) -_Your asset should not have any additional requirements (for example, needing input fields for anything other than an address)._ - - -### 5. Initial coin offering (yes/no) -_Bisq will not list your token if it has taken part in an initial coin offering (ICO)_ diff --git a/.github/boring-cyborg.yml b/.github/boring-cyborg.yml deleted file mode 100644 index 3c462f9a..00000000 --- a/.github/boring-cyborg.yml +++ /dev/null @@ -1,15 +0,0 @@ -labelPRBasedOnFilePath: - in:altcoins: - - assets/**/* - - is:no-priority: - - assets/**/* - -firstPRWelcomeComment: > - **Thanks for opening this pull request!**

Please check out our [contributor checklist](https://docs.bisq.network/contributor-checklist.html) and check if *Travis* or *Codacy* found any issues with your PR. Also make sure your commits are signed, and that you applied [Bisq's code style](https://github.com/bisq-network/style/issues) and [formatting](.editorconfig).

A maintainer will add an `is:priority` label to your PR if it is up for compensation. Please see our [Bisq Q1 2020 Update post](https://bisq.network/blog/q1-2020-update/) for more details. - -firstPRMergeComment: > - Awesome work, congrats on your first merged pull request! - -firstIssueWelcomeComment: > - **Thanks for opening your first issue here!**

Be sure to follow the issue template. Your issue will be reviewed by a maintainer and labeled for further action. diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 16e5fa42..00000000 --- a/.github/stale.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 90 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - a:bug - - re:security - - re:privacy - - re:Tor - - in:dao - - $BSQ bounty - - good first issue - - Epic - - a:feature - - is:priority -# Label to use when marking an issue as stale -staleLabel: was:dropped -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: > - This issue has been automatically closed because of inactivity. - Feel free to reopen it if you think it is still relevant. - -pulls: - daysUntilStale: 30 - markComment: > - This pull request has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. - diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..c073c5cc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + pull_request: + paths-ignore: + - 'docs/**' + - '**/README.md' + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + with: + lfs: true + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + - name: Pull lfs + run: git lfs pull + - name: Build with Gradle + run: ./gradlew build --stacktrace --scan + - uses: actions/upload-artifact@v2 + if: failure() + with: + name: gradlew-report + path: 'desktop/build/reports/tests/test/index.html' + retention-days: 30 \ No newline at end of file diff --git a/.gitignore b/.gitignore index f3d52c30..d5dc2055 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ deploy .java-version .localnet /apitest/src/main/resources/dao-setup* +/monero-wallet-rpc diff --git a/CODEOWNERS b/CODEOWNERS index c5ee421e..ddc64a6f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,14 +1,2 @@ # This doc specifies who gets requested to review GitHub pull requests. -# See https://help.github.com/articles/about-codeowners/. - -/core/main/java/bisq/core/dao/ @ManfredKarrer - -# For seednode configuration changes -/seednode/bisq-seednode.env @wiz -/seednode/bisq-seednode.service @wiz -/seednode/bitcoin.conf @wiz -/seednode/bitcoin.service @wiz -/seednode/docker-compose.yml @wiz -/seednode/install_seednode_debian.sh @wiz -/seednode/torrc @wiz -/seednode/uninstall_seednode_debian.sh @wiz +# See https://help.github.com/articles/about-codeowners/. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index c7e0a43c..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,101 +0,0 @@ -# Contributing to Bisq - -Anyone is welcome to contribute to Bisq. This document provides an overview of how we work. If you're looking for somewhere to start contributing, check out [critical bugs](https://bisq.wiki/Critical_Bugs) or see [good first issue](https://github.com/bisq-network/bisq/issues?q=is%3Aopen+is%3Aissue+label%3A"good+first+issue") list. - - -## Communication Channels - -Most communication about Bisq happens on [Keybase](https://keybase.io). - -Install Keybase and enter "bisq" from the teams tab. This is an "open" team, which means the admins will auto-accept any request to join, and you can get in fast. - -Discussion about code changes happens in GitHub issues and pull requests. - -Discussion about larger changes to the way Bisq works happens in issues the [bisq-network/proposals](https://github.com/bisq-network/proposals/issues) repository. See https://docs.bisq.network/proposals.html for details. - - -## Contributor Workflow - -All Bisq contributors submit changes via pull requests. The workflow is as follows: - - - Fork the repository - - Create a topic branch from the `master` branch - - Commit patches - - Squash redundant or unnecessary commits - - Submit a pull request from your topic branch back to the `master` branch of the main repository - - Make changes to the pull request if reviewers request them and __**request a re-review**__ - -Pull requests should be focused on a single change. Do not mix, for example, refactorings with a bug fix or implementation of a new feature. This practice makes it easier for fellow contributors to review each pull request on its merits and to give a clear ACK/NACK (see below). - - -## Reviewing Pull Requests - -Bisq follows the review workflow established by the Bitcoin Core project. The following is adapted from the [Bitcoin Core contributor documentation](https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#peer-review): - -Anyone may participate in peer review which is expressed by comments in the pull request. Typically reviewers will review the code for obvious errors, as well as test out the patch set and opine on the technical merits of the patch. Project maintainers take into account the peer review when determining if there is consensus to merge a pull request (remember that discussions may have been spread out over GitHub, mailing list and IRC discussions). The following language is used within pull-request comments: - - - `ACK` means "I have tested the code and I agree it should be merged"; - - `NACK` means "I disagree this should be merged", and must be accompanied by sound technical justification. NACKs without accompanying reasoning may be disregarded; - - `utACK` means "I have not tested the code, but I have reviewed it and it looks OK, I agree it can be merged"; - - `Concept ACK` means "I agree in the general principle of this pull request"; - - `Nit` refers to trivial, often non-blocking issues. - -Please note that Pull Requests marked `NACK` and/or GitHub's `Change requested` are closed after 30 days if not addressed. - - -## Compensation - -Bisq is not a company, but operates as a _decentralized autonomous organization_ (DAO). - -Since our [Q1 2020 update](https://bisq.network/blog/q1-2020-update/) contributions are NOT eligible for compensation unless they are allocated as part of the development budget. Fixes for [critical bugs](https://bisq.wiki/Critical_Bugs) are eligible for compensation when delivered. -In any case please contact the team lead for development (@ripcurlx) upfront if you want to get compensated for your contributions. - -For any work that was approved and merged into Bisq's `master` branch, you can [submit a compensation request](https://docs.bisq.network/dao/phase-zero.html#how-to-request-compensation) and earn BSQ (the Bisq DAO native token). Learn more about the Bisq DAO and BSQ [here](https://docs.bisq.network/dao/phase-zero.html). - - -## Style and Coding Conventions - -### Configure Git user name and email metadata - -See https://help.github.com/articles/setting-your-username-in-git/ for instructions. - -### Write well-formed commit messages - -From https://chris.beams.io/posts/git-commit/#seven-rules: - - 1. Separate subject from body with a blank line - 2. Limit the subject line to 50 characters (*) - 3. Capitalize the subject line - 4. Do not end the subject line with a period - 5. Use the imperative mood in the subject line - 6. Wrap the body at 72 characters (*) - 7. Use the body to explain what and why vs. how - -*) See [here](https://stackoverflow.com/a/45563628/8340320) for how to enforce these two checks in IntelliJ IDEA. - -See also [bisq-network/style#9](https://github.com/bisq-network/style/issues/9). - -### Sign your commits with GPG - -See https://github.com/blog/2144-gpg-signature-verification for background and -https://help.github.com/articles/signing-commits-with-gpg/ for instructions. - -### Use an editor that supports Editorconfig - -The [.editorconfig](.editorconfig) settings in this repository ensure consistent management of whitespace, line endings and more. Most modern editors support it natively or with plugin. See http://editorconfig.org for details. See also [bisq-network/style#10](https://github.com/bisq-network/style/issues/10). - -### Keep the git history clean - -It's very important to keep the git history clear, light and easily browsable. This means contributors must make sure their pull requests include only meaningful commits (if they are redundant or were added after a review, they should be removed) and _no merge commits_. - -### Additional style guidelines - -See the issues in the [bisq-network/style](https://github.com/bisq-network/style/issues) repository. - - -## See also - - - [contributor checklist](https://docs.bisq.network/contributor-checklist.html) - - [developer docs](docs#readme) including build and dev environment setup instructions - - [project management process](https://bisq.wiki/Project_management) - diff --git a/LICENSE b/LICENSE index dba13ed2..93a5d000 100644 --- a/LICENSE +++ b/LICENSE @@ -2,6 +2,7 @@ Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2020 Haveno Dex Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. diff --git a/README.md b/README.md index b76c4d0e..addf52d1 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,124 @@ -# Bisq +
+ Haveno logo +
-[![Build Status](https://travis-ci.org/bisq-network/bisq.svg?branch=master)](https://travis-ci.org/bisq-network/bisq) +## What is Haveno? +Haveno (pronounced ha‧ve‧no) is a private and decentralized way to exchange Monero for national currencies or other cryptocurrencies. Haveno uses peer-to-peer networking and multi-signature escrow to facilitate trading without a trusted third party custodian. Disputes can be resolved using non-custodial arbitration. Everything is built around Monero and Tor. -## What is Bisq? +Haveno is the Esperanto word for "Harbor". The project is stewarded by a core Team, currently formed by 2 people: ErCiccione and Woodser. -Bisq is a safe, private and decentralized way to exchange bitcoin for national currencies and other digital assets. Bisq uses peer-to-peer networking and multi-signature escrow to facilitate trading without a third party. Bisq is non-custodial and incorporates a human arbitration system to resolve disputes. +## Why a new platform? -To learn more, see the doc and video at https://bisq.network/intro. +Haveno is a fork of Bisq, the Bitcoin based decentralized exchange. We believe Bisq is not enough for Monero users, which badly need a private way to exchange Monero for other (crypto)currencies. +Haveno is built on Monero, which means all transactions between users are obfuscated by default. Bisq's system is based on Bitcoin and inherits all its design flaws, for example: -## Get started using Bisq +- All Bisq's in-platform transactions are based on Bitcoin, which make them slow and fully traceable. +- Bisq transactions are unique and easily visible on the blockchain. This means it's trivial to check which Bitcoin transactions are the result of a trade on Bisq. -Follow the step-by-step instructions at https://bisq.network/get-started. +Trade fees will also be drastically lower, as Monero has much lower transaction fees compared to bitcoin (average transaction fee: XMR=$0.003 BTC=$9 ). +Even if XMR transactions compose the vast majority of Bisq's activity, Bisq's team haven't displayed much interest in improving their Monero support. The important privacy issues mentioned above will be solved by simply having Monero as a base currency instead of Bitcoin. -## Contribute to Bisq +We acknowledge and thank Bisq for their efforts, but we think the Monero community needs a native, private way to exchange XMR for other currencies without passing through Bitcoin first and Haveno is here to fill that gap! We commit to contribute back to Bisq when possible. -See [CONTRIBUTING.md](CONTRIBUTING.md) and the [developer docs](docs/README.md). +## Status of the project + +At the moment Haveno is only a Proof of Concept. It's already possible to initiate crypto <-> XMR and fiat <-> XMR trades, but the platform still needs a lot of work before being available for public use. + +There is a lot in progress and a lot to do. To make contributions easier, we use some of github's tools, like labels and projects. We set up a [labelling system](https://github.com/haveno-dex/haveno/wiki/Labelling-system) which should make easier for people to contribute. Problems and requests about the Haveno platform are tracked on this repository. For general discussions and proposals that affect the entire Haveno ecosystem, please open an issue in the [haveno-meta repository](https://github.com/haveno-dex/haveno-meta). + +These are the main priorities for the near future: + +- The User Interface is basically still Bisq. Needs to be completely reworked and adapted for Monero as base currency. The new design is discussed and developed in [haveno-design](https://github.com/haveno-dex/haveno-design) +- Cleanup the repository from Bisq-specific content (https://github.com/haveno-dex/haveno/projects/1) + +### Bounties + +To incentivize development we adopt a simple bounty system. Contributors may be awarded bounties after completing a task (resolving an issue). [More details in the docs](https://github.com/erciccione/haveno/blob/master/docs/bounties.md). + +## Keep in touch and help out! + +Haveno is a community-driven project. For it to be succesful it's fundamental to have the support and help of the Monero community. We have our own Matrix server. Registrations are not open at the moment, but the rooms are public and can be joined from any matrix client (like Element). We look forward to hearing from you! + +- General discussions: **Haveno** (`#haveno:haveno.network`) relayed on Freenode (`#haveno`) +- Development discussions: **Haveno Development** (`#haveno-dev:haveno.network`) relayed on Freenode (`#haveno-dev`) + +Temporary email: havenodex@protonmail.com + +## FAQ + +See the [FAQ in the wiki](https://github.com/haveno-dex/haveno/wiki/FAQ). + +## Running a local Haveno test network + +1. Download [Monero CLI](https://www.getmonero.org/downloads/) for your system and sync Monero stagenet: `./monerod --stagenet --rpc-login superuser:abctesting123`, or alternatively, [set up a local Monero stagenet network](#running-a-local-monero-stagenet-network) (recommended) +3. Download and install [Bitcoin-Qt](https://bitcoin.org/en/download) +4. Run Bitcoin-Qt in regtest mode, e.g.: `./Bitcoin-Qt -regtest -peerbloomfilters=1` +5. In Bitcoin-Qt console, mine BTC regtest blocks: `generatetoaddress 101 bcrt1q6j90vywv8x7eyevcnn2tn2wrlg3vsjlsvt46qz` +6. Install [git lfs](https://git-lfs.github.com) for your system
+ Ubuntu: `sudo apt install git-lfs` +7. `git clone https://github.com/Haveno-Dex/haveno` +8. Copy monero-wallet-rpc from step 1 to the haveno project root +9. Apply permission to run monero-wallet-rpc, e.g. `chmod 777 monero-wallet-rpc` +10. Optionally modify [WalletConfig.java](core/src/main/java/bisq/core/btc/setup/WalletConfig.java) with custom settings +11. `cd haveno` +12. `./gradlew build` +13. Start seed node, arbitrator, Alice, and Bob: + 1. `./bisq-seednode --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=2002 --appName=bisq-BTC_REGTEST_Seed_2002 --daoActivated=false` + 2. `./bisq-desktop --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=4444 --appName=bisq-BTC_REGTEST_arbitrator --daoActivated=false --apiPassword=apitest --apiPort=9998` + 3. `./bisq-desktop --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=5555 --appName=bisq-BTC_REGTEST_Alice --daoActivated=false --apiPassword=apitest --apiPort=9999` + 4. `./bisq-desktop --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=6666 --appName=bisq-BTC_REGTEST_Bob --daoActivated=false --apiPassword=apitest --apiPort=10000` +14. Arbitrator window > Account > cmd+n to register a new arbitrator +15. Arbitrator window > Account > cmd+d to register a new mediator +16. Deposit stagenet XMR to Alice and Bob's Haveno wallets (wallet address printed to terminal) +17. When deposited XMR is available, proceed to post offers, etc + +### Running a local Monero stagenet network + +1. Build [monero-project](https://github.com/monero-project/monero) with the following modification to the bottom of hardforks.cpp: + ```c++ + const hardfork_t stagenet_hard_forks[] = { + // version 1 from the start of the blockchain + { 1, 1, 0, 1341378000 }, + + // versions 2-7 in rapid succession from March 13th, 2018 + { 2, 10, 0, 1521000000 }, + { 3, 20, 0, 1521120000 }, + { 4, 30, 0, 1521240000 }, + { 5, 40, 0, 1521360000 }, + { 6, 50, 0, 1521480000 }, + { 7, 60, 0, 1521600000 }, + { 8, 70, 0, 1537821770 }, + { 9, 80, 0, 1537821771 }, + { 10, 90, 0, 1550153694 }, + { 11, 100, 0, 1550225678 }, + { 12, 110, 0, 1571419280 }, + { 13, 120, 0, 1598180817 }, + { 14, 130, 0, 1598180818 } + }; + ``` +2. Using the executables built in step 1: + * `./monerod --stagenet --no-igd --hide-my-port --data-dir node1 --p2p-bind-ip 127.0.0.1 --p2p-bind-port 48080 --rpc-bind-port 48081 --zmq-rpc-bind-port 48082 --add-exclusive-node 127.0.0.1:38080 --rpc-login superuser:abctesting123 --rpc-access-control-origins http://localhost:8080` + * `./monerod --stagenet --no-igd --hide-my-port --data-dir node2 --p2p-bind-ip 127.0.0.1 --rpc-bind-ip 0.0.0.0 --confirm-external-bind --add-exclusive-node 127.0.0.1:48080 --rpc-login superuser:abctesting123 --rpc-access-control-origins http://localhost:8080` +4. Mine the first 130 blocks to a random address before using so wallets only use the latest output type. For example, in a daemon: `start_mining 56k9Yra1pxwcTYzqKcnLip8mymSQdEfA6V7476W9XhSiHPp1hAboo1F6na7kxTxwvXU6JjDQtu8VJdGj9FEcjkxGJfsyyah 1` + +## Sponsors + +Would you like to help us build Haveno? Become a sponsor! We will show your logo here. Contact us at havenodex@protonmail.com. + +Monero community logo +Samourai wallet logo +Cake wallet logo +Don Yakka logo + +## Support + +To bring Haveno to life, we need resources. If you have the possibility, please consider donating to the project. At this stage, donations are fundamental: + +`42sjokkT9FmiWPqVzrWPFE5NCJXwt96bkBozHf4vgLR9hXyJDqKHEHKVscAARuD7in5wV1meEcSTJTanCTDzidTe2cFXS1F` + +![Qr code](https://raw.githubusercontent.com/haveno-dex/haveno/master/media/qrhaveno.png) + +If you are using a wallet that supports Openalias (like the 'official' CLI and GUI wallets), you can simply put `donations@haveno.network` as the "receiver" address. \ No newline at end of file diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java index fc365931..5b327ccb 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java @@ -103,7 +103,6 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest { var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE); assertNotNull(trade); assertEquals(offerId, trade.getTradeId()); - assertFalse(trade.getIsCurrencyForTakerFeeBtc()); // Cache the trade id for the other tests. tradeId = trade.getTradeId(); @@ -115,9 +114,10 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest { trade = bobClient.getTrade(trade.getTradeId()); if (!trade.getIsDepositConfirmed()) { - log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}", + log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}", trade.getShortId(), - trade.getDepositTxId(), + trade.getMakerDepositTxId(), + trade.getTakerDepositTxId(), i); genBtcBlocksThenWait(1, 4000); continue; diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java index 93d9b1b9..0f0091fc 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java @@ -86,7 +86,6 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest { var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE); assertNotNull(trade); assertEquals(offerId, trade.getTradeId()); - assertFalse(trade.getIsCurrencyForTakerFeeBtc()); // Cache the trade id for the other tests. tradeId = trade.getTradeId(); @@ -100,9 +99,10 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest { trade = bobClient.getTrade(trade.getTradeId()); if (!trade.getIsDepositConfirmed()) { - log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}", + log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}", trade.getShortId(), - trade.getDepositTxId(), + trade.getMakerDepositTxId(), + trade.getTakerDepositTxId(), i); genBtcBlocksThenWait(1, 4000); continue; diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java index 786601e6..1ffa318a 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java @@ -104,7 +104,6 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest { var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE); assertNotNull(trade); assertEquals(offerId, trade.getTradeId()); - assertTrue(trade.getIsCurrencyForTakerFeeBtc()); // Cache the trade id for the other tests. tradeId = trade.getTradeId(); @@ -116,9 +115,10 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest { trade = bobClient.getTrade(trade.getTradeId()); if (!trade.getIsDepositConfirmed()) { - log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}", + log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}", trade.getShortId(), - trade.getDepositTxId(), + trade.getMakerDepositTxId(), + trade.getTakerDepositTxId(), i); genBtcBlocksThenWait(1, 4000); continue; diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java index ece34321..1e5b9544 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java @@ -90,7 +90,6 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest { var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE); assertNotNull(trade); assertEquals(offerId, trade.getTradeId()); - assertTrue(trade.getIsCurrencyForTakerFeeBtc()); // Cache the trade id for the other tests. tradeId = trade.getTradeId(); @@ -104,9 +103,10 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest { trade = bobClient.getTrade(trade.getTradeId()); if (!trade.getIsDepositConfirmed()) { - log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}", + log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}", trade.getShortId(), - trade.getDepositTxId(), + trade.getMakerDepositTxId(), + trade.getTakerDepositTxId(), i); genBtcBlocksThenWait(1, 4000); continue; diff --git a/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/BotProtocol.java b/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/BotProtocol.java index 51d59e75..1435d61d 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/BotProtocol.java +++ b/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/BotProtocol.java @@ -304,7 +304,7 @@ public abstract class BotProtocol { throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex)); } } // end while - throw new IllegalStateException(stoppedWaitingForDepositFeeTxMsg(this.getBotClient().getTrade(tradeId).getDepositTxId())); + throw new IllegalStateException(stoppedWaitingForDepositFeeTxMsg(this.getBotClient().getTrade(tradeId).getTakerDepositTxId())); } catch (ManualBotShutdownException ex) { throw ex; // not an error, tells bot to shutdown } catch (Exception ex) { @@ -314,10 +314,10 @@ public abstract class BotProtocol { private final Predicate isDepositFeeTxStepComplete = (trade) -> { if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) && trade.getIsDepositPublished()) { - log.info("Taker deposit fee tx {} has been published.", trade.getDepositTxId()); + log.info("Taker deposit fee tx {} has been published.", trade.getTakerDepositTxId()); return true; } else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED) && trade.getIsDepositConfirmed()) { - log.info("Taker deposit fee tx {} has been confirmed.", trade.getDepositTxId()); + log.info("Taker deposit fee tx {} has been confirmed.", trade.getTakerDepositTxId()); return true; } else { return false; diff --git a/assets/src/test/java/bisq/asset/coins/IridiumTest.java b/assets/src/test/java/bisq/asset/coins/IridiumTest.java index c9572aaa..aedfd589 100644 --- a/assets/src/test/java/bisq/asset/coins/IridiumTest.java +++ b/assets/src/test/java/bisq/asset/coins/IridiumTest.java @@ -16,7 +16,9 @@ */ package bisq.asset.coins; + import bisq.asset.AbstractAssetTest; + import org.junit.Test; public class IridiumTest extends AbstractAssetTest { diff --git a/build.gradle b/build.gradle index 1556a6aa..049bc350 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,8 @@ configure(subprojects) { grpcVersion = '1.25.0' gsonVersion = '2.8.5' guavaVersion = '28.2-jre' + moneroJavaVersion = '0.5.3' + httpclient5Version = '5.0' guiceVersion = '4.2.2' hamcrestVersion = '1.3' httpclientVersion = '4.5.12' @@ -77,7 +79,9 @@ configure(subprojects) { repositories { mavenCentral() + //mavenLocal() maven { url 'https://jitpack.io' } + maven { url 'https://mvnrepository.com' } } dependencies { @@ -293,7 +297,7 @@ configure(project(':p2p')) { // If they have not, e.g. because Git LFS is not installed, they will be text files // containing a sha256 hash of the remote object, indicating we should stop the // build and inform the user how to fix the problem. - if (file('src/main/resources/ProposalStore_BTC_MAINNET').text.contains("oid sha256:")) + if (file('src/main/resources/AccountAgeWitnessStore_XMR_MAINNET_placeholder').text.contains("oid sha256:")) throw new GradleException("p2p data store files have not been synchronized. " + "To fix this, ensure you have Git LFS installed and run `git lfs pull`. " + "See docs/build.md for more information.") @@ -317,6 +321,13 @@ configure(project(':core')) { exclude(module: 'base64') exclude(module: 'httpcore-nio') } + compile("io.github.monero-ecosystem:monero-java:$moneroJavaVersion") { + exclude(module: 'jackson-core') + exclude(module: 'jackson-annotations') + exclude(module: 'jackson-databind') + exclude(module: 'bcprov-jdk15on') + } + implementation("org.apache.httpcomponents.client5:httpclient5:$httpclient5Version") compile "com.fasterxml.jackson.core:jackson-core:$jacksonVersion" compile "com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion" compile("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") { @@ -343,6 +354,7 @@ configure(project(':cli')) { dependencies { compile project(':proto') + compile project(':core') implementation "net.sf.jopt-simple:jopt-simple:$joptVersion" implementation "com.google.guava:guava:$guavaVersion" implementation "com.google.protobuf:protobuf-java:$protobufVersion" diff --git a/cli/src/main/java/bisq/cli/CurrencyFormat.java b/cli/src/main/java/bisq/cli/CurrencyFormat.java index 4abf2027..c3070a98 100644 --- a/cli/src/main/java/bisq/cli/CurrencyFormat.java +++ b/cli/src/main/java/bisq/cli/CurrencyFormat.java @@ -25,6 +25,7 @@ import java.text.DecimalFormat; import java.text.NumberFormat; import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Locale; @@ -32,6 +33,10 @@ import static java.lang.String.format; import static java.math.RoundingMode.HALF_UP; import static java.math.RoundingMode.UNNECESSARY; + + +import monero.common.MoneroUtils; + @VisibleForTesting public class CurrencyFormat { @@ -57,6 +62,10 @@ public class CurrencyFormat { return BSQ_FORMAT.format(BigDecimal.valueOf(sats).divide(BSQ_SATOSHI_DIVISOR)); } + public static String formatXmr(BigInteger amount) { + return "" + MoneroUtils.atomicUnitsToXmr(amount); + } + public static String formatBsqAmount(long bsqSats) { // BSQ sats = trade.getOffer().getVolume() NUMBER_FORMAT.setMinimumFractionDigits(2); diff --git a/cli/src/main/java/bisq/cli/TradeFormat.java b/cli/src/main/java/bisq/cli/TradeFormat.java index dbf8dbf4..ded61548 100644 --- a/cli/src/main/java/bisq/cli/TradeFormat.java +++ b/cli/src/main/java/bisq/cli/TradeFormat.java @@ -17,6 +17,8 @@ package bisq.cli; +import bisq.core.util.ParsingUtils; + import bisq.proto.grpc.ContractInfo; import bisq.proto.grpc.TradeInfo; @@ -133,7 +135,7 @@ public class TradeFormat { bsqReceiveAddress.apply(tradeInfo, showBsqBuyerAddress)); } - private static final Function priceHeader = (t) -> + private static final Function priceHeader = (t) -> // TODO (woodser): update these to XMR t.getOffer().getBaseCurrencyCode().equals("BTC") ? COL_HEADER_PRICE : COL_HEADER_PRICE_OF_ALTCOIN; @@ -144,11 +146,7 @@ public class TradeFormat { : t.getOffer().getBaseCurrencyCode(); private static final BiFunction makerTakerFeeHeaderCurrencyCode = (t, isTaker) -> { - if (isTaker) { - return t.getIsCurrencyForTakerFeeBtc() ? "BTC" : "BSQ"; - } else { - return t.getOffer().getIsCurrencyForMakerFeeBtc() ? "BTC" : "BSQ"; - } + return "XMR"; }; private static final Function paymentStatusHeaderCurrencyCode = (t) -> @@ -163,7 +161,7 @@ public class TradeFormat { private static final Function amountFormat = (t) -> t.getOffer().getBaseCurrencyCode().equals("BTC") - ? formatSatoshis(t.getTradeAmountAsLong()) + ? formatSatoshis(t.getTradeAmountAsLong()) // TODO (woodser): delete formatSatoshis(), formatBsq() and change base currency code to XMR : formatCryptoCurrencyOfferVolume(t.getOffer().getVolume()); private static final BiFunction makerTakerMinerTxFeeFormat = (t, isTaker) -> { @@ -175,15 +173,7 @@ public class TradeFormat { }; private static final BiFunction makerTakerFeeFormat = (t, isTaker) -> { - if (isTaker) { - return t.getIsCurrencyForTakerFeeBtc() - ? formatSatoshis(t.getTakerFeeAsLong()) - : formatBsq(t.getTakerFeeAsLong()); - } else { - return t.getOffer().getIsCurrencyForMakerFeeBtc() - ? formatSatoshis(t.getOffer().getMakerFee()) - : formatBsq(t.getOffer().getMakerFee()); - } + return formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTakerFeeAsLong())); }; private static final Function tradeCostFormat = (t) -> diff --git a/common/src/main/java/bisq/common/taskrunner/Task.java b/common/src/main/java/bisq/common/taskrunner/Task.java index e6fce415..79bff399 100644 --- a/common/src/main/java/bisq/common/taskrunner/Task.java +++ b/common/src/main/java/bisq/common/taskrunner/Task.java @@ -25,7 +25,7 @@ public abstract class Task { public static Class taskToIntercept; - private final TaskRunner taskHandler; + protected final TaskRunner taskHandler; protected final T model; protected String errorMessage = "An error occurred at task: " + getClass().getSimpleName(); protected boolean completed; diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java index b634f01a..69c123bd 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java @@ -33,6 +33,7 @@ import bisq.core.payment.payload.PaymentMethod; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeResult; import bisq.core.support.dispute.arbitration.TraderDataItem; +import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.Contract; import bisq.core.trade.Trade; import bisq.core.trade.protocol.TradingPeer; @@ -300,6 +301,7 @@ public class AccountAgeWitnessService { } private Optional findTradePeerWitness(Trade trade) { + if (trade instanceof ArbitratorTrade) return Optional.empty(); // TODO (woodser): arbitrator trade has two peers TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer(); return (tradingPeer == null || tradingPeer.getPaymentAccountPayload() == null || @@ -842,6 +844,7 @@ public class AccountAgeWitnessService { } public SignState getSignState(Trade trade) { + if (trade instanceof ArbitratorTrade) return SignState.UNSIGNED; // TODO (woodser): arbitrator has two peers return findTradePeerWitness(trade) .map(this::getSignState) .orElse(SignState.UNSIGNED); diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java index 2f568310..21942a60 100644 --- a/core/src/main/java/bisq/core/api/CoreTradesService.java +++ b/core/src/main/java/bisq/core/api/CoreTradesService.java @@ -109,7 +109,6 @@ class CoreTradesService { tradeManager.onTakeOffer(offer.getAmount(), takeOfferModel.getTxFeeFromFeeService(), takeOfferModel.getTakerFee(), - takeOfferModel.isCurrencyForTakerFeeBtc(), offer.getPrice().getValue(), takeOfferModel.getFundsNeededForTrade(), offer, diff --git a/core/src/main/java/bisq/core/api/CoreWalletsService.java b/core/src/main/java/bisq/core/api/CoreWalletsService.java index b9ea896e..69b113e3 100644 --- a/core/src/main/java/bisq/core/api/CoreWalletsService.java +++ b/core/src/main/java/bisq/core/api/CoreWalletsService.java @@ -84,7 +84,6 @@ import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; -import static bisq.common.config.BaseCurrencyNetwork.BTC_DAO_REGTEST; import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput; import static bisq.core.util.ParsingUtils.parseToCoin; import static java.lang.String.format; diff --git a/core/src/main/java/bisq/core/api/model/ContractInfo.java b/core/src/main/java/bisq/core/api/model/ContractInfo.java index 404335c9..c741cec5 100644 --- a/core/src/main/java/bisq/core/api/model/ContractInfo.java +++ b/core/src/main/java/bisq/core/api/model/ContractInfo.java @@ -34,8 +34,7 @@ public class ContractInfo implements Payload { private final String buyerNodeAddress; private final String sellerNodeAddress; - private final String mediatorNodeAddress; - private final String refundAgentNodeAddress; + private final String arbitratorNodeAddress; private final boolean isBuyerMakerAndSellerTaker; private final String makerAccountId; private final String takerAccountId; @@ -47,8 +46,7 @@ public class ContractInfo implements Payload { public ContractInfo(String buyerNodeAddress, String sellerNodeAddress, - String mediatorNodeAddress, - String refundAgentNodeAddress, + String arbitratorNodeAddress, boolean isBuyerMakerAndSellerTaker, String makerAccountId, String takerAccountId, @@ -59,8 +57,7 @@ public class ContractInfo implements Payload { long lockTime) { this.buyerNodeAddress = buyerNodeAddress; this.sellerNodeAddress = sellerNodeAddress; - this.mediatorNodeAddress = mediatorNodeAddress; - this.refundAgentNodeAddress = refundAgentNodeAddress; + this.arbitratorNodeAddress = arbitratorNodeAddress; this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker; this.makerAccountId = makerAccountId; this.takerAccountId = takerAccountId; @@ -75,7 +72,6 @@ public class ContractInfo implements Payload { // For transmitting TradeInfo messages when no contract is available. public static Supplier emptyContract = () -> new ContractInfo("", - "", "", "", false, @@ -94,8 +90,7 @@ public class ContractInfo implements Payload { public static ContractInfo fromProto(bisq.proto.grpc.ContractInfo proto) { return new ContractInfo(proto.getBuyerNodeAddress(), proto.getSellerNodeAddress(), - proto.getMediatorNodeAddress(), - proto.getRefundAgentNodeAddress(), + proto.getArbitratorNodeAddress(), proto.getIsBuyerMakerAndSellerTaker(), proto.getMakerAccountId(), proto.getTakerAccountId(), @@ -111,8 +106,7 @@ public class ContractInfo implements Payload { return bisq.proto.grpc.ContractInfo.newBuilder() .setBuyerNodeAddress(buyerNodeAddress) .setSellerNodeAddress(sellerNodeAddress) - .setMediatorNodeAddress(mediatorNodeAddress) - .setRefundAgentNodeAddress(refundAgentNodeAddress) + .setArbitratorNodeAddress(arbitratorNodeAddress) .setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker) .setMakerAccountId(makerAccountId) .setTakerAccountId(takerAccountId) diff --git a/core/src/main/java/bisq/core/api/model/TradeInfo.java b/core/src/main/java/bisq/core/api/model/TradeInfo.java index 078e5ee4..8ba70a02 100644 --- a/core/src/main/java/bisq/core/api/model/TradeInfo.java +++ b/core/src/main/java/bisq/core/api/model/TradeInfo.java @@ -47,7 +47,8 @@ public class TradeInfo implements Payload { private final long txFeeAsLong; private final long takerFeeAsLong; private final String takerFeeTxId; - private final String depositTxId; + private final String makerDepositTxId; + private final String takerDepositTxId; private final String payoutTxId; private final long tradeAmountAsLong; private final long tradePrice; @@ -74,7 +75,8 @@ public class TradeInfo implements Payload { this.txFeeAsLong = builder.txFeeAsLong; this.takerFeeAsLong = builder.takerFeeAsLong; this.takerFeeTxId = builder.takerFeeTxId; - this.depositTxId = builder.depositTxId; + this.makerDepositTxId = builder.makerDepositTxId; + this.takerDepositTxId = builder.takerDepositTxId; this.payoutTxId = builder.payoutTxId; this.tradeAmountAsLong = builder.tradeAmountAsLong; this.tradePrice = builder.tradePrice; @@ -102,8 +104,7 @@ public class TradeInfo implements Payload { Contract contract = trade.getContract(); contractInfo = new ContractInfo(contract.getBuyerPayoutAddressString(), contract.getSellerPayoutAddressString(), - contract.getMediatorNodeAddress().getFullAddress(), - contract.getRefundAgentNodeAddress().getFullAddress(), + contract.getArbitratorNodeAddress().getFullAddress(), contract.isBuyerMakerAndSellerTaker(), contract.getMakerAccountId(), contract.getTakerAccountId(), @@ -122,12 +123,12 @@ public class TradeInfo implements Payload { .withShortId(trade.getShortId()) .withDate(trade.getDate().getTime()) .withRole(role == null ? "" : role) - .withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc()) .withTxFeeAsLong(trade.getTxFeeAsLong()) .withTakerFeeAsLong(trade.getTakerFeeAsLong()) .withTakerFeeAsLong(trade.getTakerFeeAsLong()) .withTakerFeeTxId(trade.getTakerFeeTxId()) - .withDepositTxId(trade.getDepositTxId()) + .withMakerDepositTxId(trade.getMakerDepositTxId()) + .withTakerDepositTxId(trade.getTakerDepositTxId()) .withPayoutTxId(trade.getPayoutTxId()) .withTradeAmountAsLong(trade.getTradeAmountAsLong()) .withTradePrice(trade.getTradePrice().getValue()) @@ -159,11 +160,11 @@ public class TradeInfo implements Payload { .setShortId(shortId) .setDate(date) .setRole(role) - .setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc) .setTxFeeAsLong(txFeeAsLong) .setTakerFeeAsLong(takerFeeAsLong) .setTakerFeeTxId(takerFeeTxId == null ? "" : takerFeeTxId) - .setDepositTxId(depositTxId == null ? "" : depositTxId) + .setMakerDepositTxId(makerDepositTxId == null ? "" : makerDepositTxId) + .setTakerDepositTxId(takerDepositTxId == null ? "" : takerDepositTxId) .setPayoutTxId(payoutTxId == null ? "" : payoutTxId) .setTradeAmountAsLong(tradeAmountAsLong) .setTradePrice(tradePrice) @@ -189,11 +190,11 @@ public class TradeInfo implements Payload { .withShortId(proto.getShortId()) .withDate(proto.getDate()) .withRole(proto.getRole()) - .withIsCurrencyForTakerFeeBtc(proto.getIsCurrencyForTakerFeeBtc()) .withTxFeeAsLong(proto.getTxFeeAsLong()) .withTakerFeeAsLong(proto.getTakerFeeAsLong()) .withTakerFeeTxId(proto.getTakerFeeTxId()) - .withDepositTxId(proto.getDepositTxId()) + .withMakerDepositTxId(proto.getMakerDepositTxId()) + .withTakerDepositTxId(proto.getTakerDepositTxId()) .withPayoutTxId(proto.getPayoutTxId()) .withTradeAmountAsLong(proto.getTradeAmountAsLong()) .withTradePrice(proto.getTradePrice()) @@ -228,7 +229,8 @@ public class TradeInfo implements Payload { private long txFeeAsLong; private long takerFeeAsLong; private String takerFeeTxId; - private String depositTxId; + private String makerDepositTxId; + private String takerDepositTxId; private String payoutTxId; private long tradeAmountAsLong; private long tradePrice; @@ -290,8 +292,13 @@ public class TradeInfo implements Payload { return this; } - public TradeInfoBuilder withDepositTxId(String depositTxId) { - this.depositTxId = depositTxId; + public TradeInfoBuilder withMakerDepositTxId(String makerDepositTxId) { + this.makerDepositTxId = makerDepositTxId; + return this; + } + + public TradeInfoBuilder withTakerDepositTxId(String takerDepositTxId) { + this.takerDepositTxId = takerDepositTxId; return this; } @@ -386,7 +393,8 @@ public class TradeInfo implements Payload { ", txFeeAsLong='" + txFeeAsLong + '\'' + "\n" + ", takerFeeAsLong='" + takerFeeAsLong + '\'' + "\n" + ", takerFeeTxId='" + takerFeeTxId + '\'' + "\n" + - ", depositTxId='" + depositTxId + '\'' + "\n" + + ", makerDepositTxId='" + makerDepositTxId + '\'' + "\n" + + ", takerDepositTxId='" + takerDepositTxId + '\'' + "\n" + ", payoutTxId='" + payoutTxId + '\'' + "\n" + ", tradeAmountAsLong='" + tradeAmountAsLong + '\'' + "\n" + ", tradePrice='" + tradePrice + '\'' + "\n" + diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java index 980875cd..355483ce 100644 --- a/core/src/main/java/bisq/core/app/BisqExecutable.java +++ b/core/src/main/java/bisq/core/app/BisqExecutable.java @@ -20,6 +20,7 @@ package bisq.core.app; import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoSetup; import bisq.core.dao.node.full.RpcService; import bisq.core.offer.OpenOfferManager; @@ -235,6 +236,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet injector.getInstance(RpcService.class).shutDown(); injector.getInstance(DaoSetup.class).shutDown(); injector.getInstance(AvoidStandbyModeService.class).shutDown(); + injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc? log.info("OpenOfferManager shutdown started"); injector.getInstance(OpenOfferManager.class).shutDown(() -> { log.info("OpenOfferManager shutdown completed"); diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index de6db2c2..ee212e4f 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -28,6 +28,7 @@ import bisq.core.btc.nodes.LocalBitcoinNode; import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletsManager; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster; import bisq.core.dao.governance.voteresult.VoteResultException; import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService; @@ -126,6 +127,7 @@ public class BisqSetup { private final WalletsManager walletsManager; private final WalletsSetup walletsSetup; private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final P2PService p2PService; private final SignedWitnessStorageService signedWitnessStorageService; private final TradeManager tradeManager; @@ -210,6 +212,7 @@ public class BisqSetup { WalletAppSetup walletAppSetup, WalletsManager walletsManager, WalletsSetup walletsSetup, + XmrWalletService xmrWalletService, BtcWalletService btcWalletService, P2PService p2PService, SignedWitnessStorageService signedWitnessStorageService, @@ -231,6 +234,7 @@ public class BisqSetup { this.walletAppSetup = walletAppSetup; this.walletsManager = walletsManager; this.walletsSetup = walletsSetup; + this.xmrWalletService = xmrWalletService; this.btcWalletService = btcWalletService; this.p2PService = p2PService; this.signedWitnessStorageService = signedWitnessStorageService; @@ -514,17 +518,18 @@ public class BisqSetup { // We check if we have open offers with no confidence object at the maker fee tx. That can happen if the // miner fee was too low and the transaction got removed from mempool and got out from our wallet after a // resync. - openOfferManager.getObservableList().forEach(e -> { - String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId(); - if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) { - String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx", - e.getOffer().getShortId(), offerFeePaymentTxId); - log.warn(message); - if (lockedUpFundsHandler != null) { - lockedUpFundsHandler.accept(message); - } - } - }); + // TODO (woodser): check for invalid maker fee txs with xmr? +// openOfferManager.getObservableList().forEach(e -> { +// String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId(); +// if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) { // TODO (woodser): needed for xmr base? +// String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx", +// e.getOffer().getShortId(), offerFeePaymentTxId); +// log.warn(message); +// if (lockedUpFundsHandler != null) { +// lockedUpFundsHandler.accept(message); +// } +// } +// }); } @Nullable diff --git a/core/src/main/java/bisq/core/app/WalletAppSetup.java b/core/src/main/java/bisq/core/app/WalletAppSetup.java index b54e6706..a5cb49ac 100644 --- a/core/src/main/java/bisq/core/app/WalletAppSetup.java +++ b/core/src/main/java/bisq/core/app/WalletAppSetup.java @@ -249,9 +249,12 @@ public class WalletAppSetup { .filter(trade -> trade.getOffer() != null) .forEach(trade -> { String details = null; - if (txId.equals(trade.getDepositTxId())) { - details = Res.get("popup.warning.trade.txRejected.deposit"); + if (txId.equals(trade.getMakerDepositTxId())) { + details = Res.get("popup.warning.trade.txRejected.deposit"); // TODO (woodser): txRejected.maker_deposit, txRejected.taker_deposit } + if (txId.equals(trade.getTakerDepositTxId())) { + details = Res.get("popup.warning.trade.txRejected.deposit"); + } if (txId.equals(trade.getOffer().getOfferFeePaymentTxId()) || txId.equals(trade.getTakerFeeTxId())) { details = Res.get("popup.warning.trade.txRejected.tradeFee"); } diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java index ccca2a6a..9ad33d9b 100644 --- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java @@ -21,6 +21,7 @@ import bisq.core.app.BisqExecutable; import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoSetup; import bisq.core.dao.node.full.RpcService; import bisq.core.offer.OpenOfferManager; @@ -101,6 +102,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable { }); }); injector.getInstance(WalletsSetup.class).shutDown(); + injector.getInstance(XmrWalletService.class).shutDown(); // TODO (woodser): this is not actually called, perhaps because WalletsSetup.class completes too quick so its listener calls System.exit(0) injector.getInstance(BtcWalletService.class).shutDown(); injector.getInstance(BsqWalletService.class).shutDown(); })); diff --git a/core/src/main/java/bisq/core/btc/Balances.java b/core/src/main/java/bisq/core/btc/Balances.java index 5d3ddd4e..bb38c1ef 100644 --- a/core/src/main/java/bisq/core/btc/Balances.java +++ b/core/src/main/java/bisq/core/btc/Balances.java @@ -17,9 +17,7 @@ package bisq.core.btc; -import bisq.core.btc.listeners.BalanceListener; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; import bisq.core.support.dispute.Dispute; @@ -32,7 +30,6 @@ import bisq.core.trade.failed.FailedTradesManager; import bisq.common.UserThread; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; import javax.inject.Inject; @@ -41,16 +38,23 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ListChangeListener; -import java.util.Objects; -import java.util.stream.Stream; +import java.math.BigInteger; + +import java.util.List; import lombok.Getter; import lombok.extern.slf4j.Slf4j; + + +import monero.wallet.model.MoneroAccount; +import monero.wallet.model.MoneroOutputWallet; +import monero.wallet.model.MoneroWalletListener; + @Slf4j public class Balances { private final TradeManager tradeManager; - private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final OpenOfferManager openOfferManager; private final ClosedTradableManager closedTradableManager; private final FailedTradesManager failedTradesManager; @@ -65,13 +69,13 @@ public class Balances { @Inject public Balances(TradeManager tradeManager, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, OpenOfferManager openOfferManager, ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager, RefundManager refundManager) { this.tradeManager = tradeManager; - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.openOfferManager = openOfferManager; this.closedTradableManager = closedTradableManager; this.failedTradesManager = failedTradesManager; @@ -82,13 +86,11 @@ public class Balances { openOfferManager.getObservableList().addListener((ListChangeListener) c -> updateBalance()); tradeManager.getObservableList().addListener((ListChangeListener) change -> updateBalance()); refundManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> updateBalance()); - btcWalletService.addBalanceListener(new BalanceListener() { - @Override - public void onBalanceChanged(Coin balance, Transaction tx) { - updateBalance(); - } + xmrWalletService.getWallet().addListener(new MoneroWalletListener() { + @Override public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { updateBalance(); } + @Override public void onOutputReceived(MoneroOutputWallet output) { updateBalance(); } + @Override public void onOutputSpent(MoneroOutputWallet output) { updateBalance(); } }); - updateBalance(); } @@ -101,31 +103,24 @@ public class Balances { }); } + // TODO (woodser): reserved balance = reserved for trade, locked balance = locked in multisig + private void updateAvailableBalance() { - long sum = btcWalletService.getAddressEntriesForAvailableBalanceStream() - .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).value) - .sum(); - availableBalance.set(Coin.valueOf(sum)); + availableBalance.set(Coin.valueOf(xmrWalletService.getWallet().getUnlockedBalance(0).longValue())); } private void updateReservedBalance() { - long sum = openOfferManager.getObservableList().stream() - .map(openOffer -> btcWalletService.getAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE) - .orElse(null)) - .filter(Objects::nonNull) - .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).value) - .sum(); - reservedBalance.set(Coin.valueOf(sum)); + BigInteger sum = new BigInteger("0"); + List accounts = xmrWalletService.getWallet().getAccounts(); + for (MoneroAccount account : accounts) { + if (account.getIndex() != 0) sum = sum.add(account.getBalance()); + } + reservedBalance.set(Coin.valueOf(sum.longValue())); } - + private void updateLockedBalance() { - Stream lockedTrades = Stream.concat(closedTradableManager.getTradesStreamWithFundsLockedIn(), failedTradesManager.getTradesStreamWithFundsLockedIn()); - lockedTrades = Stream.concat(lockedTrades, tradeManager.getTradesStreamWithFundsLockedIn()); - long sum = lockedTrades.map(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG) - .orElse(null)) - .filter(Objects::nonNull) - .mapToLong(AddressEntry::getCoinLockedInMultiSig) - .sum(); - lockedBalance.set(Coin.valueOf(sum)); + BigInteger balance = xmrWalletService.getWallet().getBalance(0); + BigInteger unlockedBalance = xmrWalletService.getWallet().getUnlockedBalance(0); + lockedBalance.set(Coin.valueOf(balance.subtract(unlockedBalance).longValue())); } } diff --git a/core/src/main/java/bisq/core/btc/BitcoinModule.java b/core/src/main/java/bisq/core/btc/BitcoinModule.java index 3f733fb5..18e4b891 100644 --- a/core/src/main/java/bisq/core/btc/BitcoinModule.java +++ b/core/src/main/java/bisq/core/btc/BitcoinModule.java @@ -18,6 +18,7 @@ package bisq.core.btc; import bisq.core.btc.model.AddressEntryList; +import bisq.core.btc.model.XmrAddressEntryList; import bisq.core.btc.nodes.BtcNodes; import bisq.core.btc.setup.RegTestHost; import bisq.core.btc.setup.WalletsSetup; @@ -26,6 +27,7 @@ import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.NonBsqCoinSelector; import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.provider.ProvidersRepository; import bisq.core.provider.fee.FeeProvider; import bisq.core.provider.fee.FeeService; @@ -85,7 +87,9 @@ public class BitcoinModule extends AppModule { bind(new TypeLiteral>(){}).annotatedWith(named(PROVIDERS)).toInstance(config.providers); bind(AddressEntryList.class).in(Singleton.class); + bind(XmrAddressEntryList.class).in(Singleton.class); bind(WalletsSetup.class).in(Singleton.class); + bind(XmrWalletService.class).in(Singleton.class); bind(BtcWalletService.class).in(Singleton.class); bind(BsqWalletService.class).in(Singleton.class); bind(TradeWalletService.class).in(Singleton.class); diff --git a/core/src/main/java/bisq/core/btc/listeners/XmrBalanceListener.java b/core/src/main/java/bisq/core/btc/listeners/XmrBalanceListener.java new file mode 100644 index 00000000..2c824745 --- /dev/null +++ b/core/src/main/java/bisq/core/btc/listeners/XmrBalanceListener.java @@ -0,0 +1,38 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.btc.listeners; + +import java.math.BigInteger; + +public class XmrBalanceListener { + private Integer accountIndex; + + public XmrBalanceListener() { + } + + public XmrBalanceListener(Integer accountIndex) { + this.accountIndex = accountIndex; + } + + public Integer getAccountIndex() { + return accountIndex; + } + + public void onBalanceChanged(BigInteger balance) { + } +} diff --git a/core/src/main/java/bisq/core/btc/model/XmrAddressEntry.java b/core/src/main/java/bisq/core/btc/model/XmrAddressEntry.java new file mode 100644 index 00000000..aaba5fec --- /dev/null +++ b/core/src/main/java/bisq/core/btc/model/XmrAddressEntry.java @@ -0,0 +1,150 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.btc.model; + +import bisq.common.proto.ProtoUtil; +import bisq.common.proto.persistable.PersistablePayload; +import bisq.common.util.Utilities; + +import org.bitcoinj.core.Coin; + +import java.util.Optional; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; + +/** + * Every trade uses a XmrAddressEntry with a dedicated address for all transactions related to the trade. + * That way we have a kind of separated trade wallet, isolated from other transactions and avoiding coin merge. + * If we would not avoid coin merge the user would lose privacy between trades. + */ +@EqualsAndHashCode +@Slf4j +public final class XmrAddressEntry implements PersistablePayload { + public enum Context { + ARBITRATOR, + AVAILABLE, + OFFER_FUNDING, + RESERVED_FOR_TRADE, + MULTI_SIG, + TRADE_PAYOUT + } + + // keyPair can be null in case the object is created from deserialization as it is transient. + // It will be restored when the wallet is ready at setDeterministicKey + // So after startup it must never be null + + @Nullable + @Getter + private final String offerId; + @Getter + private final Context context; + @Getter + private final int accountIndex; + @Getter + private final String addressString; + + private long coinLockedInMultiSig; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, initialization + /////////////////////////////////////////////////////////////////////////////////////////// + + public XmrAddressEntry(int accountIndex, String address, Context context) { + this(accountIndex, address, context, null, null); + } + + public XmrAddressEntry(int accountIndex, String address, Context context, @Nullable String offerId, Coin coinLockedInMultiSig) { + this.accountIndex = accountIndex; + this.addressString = address; + this.offerId = offerId; + this.context = context; + if (coinLockedInMultiSig != null) this.coinLockedInMultiSig = coinLockedInMultiSig.value; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + public static XmrAddressEntry fromProto(protobuf.XmrAddressEntry proto) { + return new XmrAddressEntry(proto.getAccountIndex(), + ProtoUtil.stringOrNullFromProto(proto.getAddressString()), + ProtoUtil.enumFromProto(XmrAddressEntry.Context.class, proto.getContext().name()), + ProtoUtil.stringOrNullFromProto(proto.getOfferId()), + Coin.valueOf(proto.getCoinLockedInMultiSig())); + } + + @Override + public protobuf.XmrAddressEntry toProtoMessage() { + protobuf.XmrAddressEntry.Builder builder = protobuf.XmrAddressEntry.newBuilder() + .setAccountIndex(accountIndex) + .setAddressString(addressString) + .setContext(protobuf.XmrAddressEntry.Context.valueOf(context.name())) + .setCoinLockedInMultiSig(coinLockedInMultiSig); + Optional.ofNullable(offerId).ifPresent(builder::setOfferId); + return builder.build(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void setCoinLockedInMultiSig(@NotNull Coin coinLockedInMultiSig) { + this.coinLockedInMultiSig = coinLockedInMultiSig.value; + } + + // For display we usually only display the first 8 characters. + @Nullable + public String getShortOfferId() { + return offerId != null ? Utilities.getShortId(offerId) : null; + } + + public boolean isOpenOffer() { + return context == Context.OFFER_FUNDING || context == Context.RESERVED_FOR_TRADE; + } + + public boolean isTrade() { + return context == Context.MULTI_SIG || context == Context.TRADE_PAYOUT; + } + + public boolean isTradable() { + return isOpenOffer() || isTrade(); + } + + public Coin getCoinLockedInMultiSig() { + return Coin.valueOf(coinLockedInMultiSig); + } + + @Override + public String toString() { + return "XmrAddressEntry{" + + "offerId='" + getOfferId() + '\'' + + ", context=" + context + + ", accountIndex=" + getAccountIndex() + + ", address=" + getAddressString() + + '}'; + } +} diff --git a/core/src/main/java/bisq/core/btc/model/XmrAddressEntryList.java b/core/src/main/java/bisq/core/btc/model/XmrAddressEntryList.java new file mode 100644 index 00000000..59c0212f --- /dev/null +++ b/core/src/main/java/bisq/core/btc/model/XmrAddressEntryList.java @@ -0,0 +1,235 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.btc.model; + +import bisq.common.persistence.PersistenceManager; +import bisq.common.proto.persistable.PersistableEnvelope; +import bisq.common.proto.persistable.PersistedDataHost; + +import com.google.protobuf.Message; + +import com.google.inject.Inject; + +import com.google.common.collect.ImmutableList; + +import java.math.BigInteger; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroAccount; +import monero.wallet.model.MoneroOutputWallet; +import monero.wallet.model.MoneroWalletListener; + +/** + * The AddressEntries was previously stored as list, now as hashSet. We still keep the old name to reflect the + * associated protobuf message. + */ +@Slf4j +public final class XmrAddressEntryList implements PersistableEnvelope, PersistedDataHost { + transient private PersistenceManager persistenceManager; + transient private MoneroWallet wallet; + private final Set entrySet = new CopyOnWriteArraySet<>(); + + @Inject + public XmrAddressEntryList(PersistenceManager persistenceManager) { + this.persistenceManager = persistenceManager; + + this.persistenceManager.initialize(this, PersistenceManager.Source.PRIVATE); + } + + @Override + public void readPersisted(Runnable completeHandler) { + persistenceManager.readPersisted(persisted -> { + entrySet.clear(); + entrySet.addAll(persisted.entrySet); + completeHandler.run(); + }, + completeHandler); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private XmrAddressEntryList(Set entrySet) { + this.entrySet.addAll(entrySet); + } + + public static XmrAddressEntryList fromProto(protobuf.XmrAddressEntryList proto) { + Set entrySet = proto.getXmrAddressEntryList().stream() + .map(XmrAddressEntry::fromProto) + .collect(Collectors.toSet()); + return new XmrAddressEntryList(entrySet); + } + + @Override + public Message toProtoMessage() { + Set addressEntries = entrySet.stream() + .map(XmrAddressEntry::toProtoMessage) + .collect(Collectors.toSet()); + return protobuf.PersistableEnvelope.newBuilder() + .setXmrAddressEntryList(protobuf.XmrAddressEntryList.newBuilder() + .addAllXmrAddressEntry(addressEntries)) + .build(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void onWalletReady(MoneroWallet wallet) { + this.wallet = wallet; + + if (!entrySet.isEmpty()) { +// Set toBeRemoved = new HashSet<>(); +// entrySet.forEach(addressEntry -> { +// DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubKeyHash( +// addressEntry.getPubKeyHash(), +// Script.ScriptType.P2PKH); +// if (keyFromPubHash != null) { +// Address addressFromKey = LegacyAddress.fromKey(Config.baseCurrencyNetworkParameters(), keyFromPubHash); +// // We want to ensure key and address matches in case we have address in entry available already +// if (addressEntry.getAddress() == null || addressFromKey.equals(addressEntry.getAddress())) { +// addressEntry.setDeterministicKey(keyFromPubHash); +// } else { +// log.error("We found an address entry without key but cannot apply the key as the address " + +// "is not matching. " + +// "We remove that entry as it seems it is not compatible with our wallet. " + +// "addressFromKey={}, addressEntry.getAddress()={}", +// addressFromKey, addressEntry.getAddress()); +// toBeRemoved.add(addressEntry); +// } +// } else { +// log.error("Key from addressEntry {} not found in that wallet. We remove that entry. " + +// "This is expected at restore from seeds.", addressEntry.toString()); +// toBeRemoved.add(addressEntry); +// } +// }); +// +// toBeRemoved.forEach(entrySet::remove); + } else { + // As long the old arbitration domain is not removed from the code base we still support it here. + MoneroAccount account = wallet.createAccount(); + entrySet.add(new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), XmrAddressEntry.Context.ARBITRATOR)); + } + + // In case we restore from seed words and have balance we need to add the relevant addresses to our list. + // IssuedReceiveAddresses does not contain all addresses where we expect balance so we need to listen to + // incoming txs at blockchain sync to add the rest. + if (wallet.getBalance().compareTo(new BigInteger("0")) > 0) { + wallet.getAccounts().forEach(acct -> { + log.info("Create XmrAddressEntry for IssuedReceiveAddress. address={}", acct.getPrimaryAddress()); + if (acct.getIndex() != 0) entrySet.add(new XmrAddressEntry(acct.getIndex(), acct.getPrimaryAddress(), XmrAddressEntry.Context.AVAILABLE)); + }); + } + + // We add those listeners to get notified about potential new transactions and + // add an address entry list in case it does not exist yet. This is mainly needed for restore from seed words + // but can help as well in case the addressEntry list would miss an address where the wallet was received + // funds (e.g. if the user sends funds to an address which has not been provided in the main UI - like from the + // wallet details window). + wallet.addListener(new MoneroWalletListener() { + @Override public void onOutputReceived(MoneroOutputWallet output) { maybeAddNewAddressEntry(output); } + @Override public void onOutputSpent(MoneroOutputWallet output) { maybeAddNewAddressEntry(output); } + }); + + requestPersistence(); + } + + public ImmutableList getAddressEntriesAsListImmutable() { + return ImmutableList.copyOf(entrySet); + } + + public void addAddressEntry(XmrAddressEntry addressEntry) { + boolean entryWithSameOfferIdAndContextAlreadyExist = entrySet.stream().anyMatch(e -> { + if (addressEntry.getOfferId() != null) { + return addressEntry.getOfferId().equals(e.getOfferId()) && addressEntry.getContext() == e.getContext(); + } + return false; + }); + if (entryWithSameOfferIdAndContextAlreadyExist) { + log.error("We have an address entry with the same offer ID and context. We do not add the new one. " + + "addressEntry={}, entrySet={}", addressEntry, entrySet); + if (true) throw new RuntimeException("why?"); + return; + } + + boolean setChangedByAdd = entrySet.add(addressEntry); + if (setChangedByAdd) + requestPersistence(); + } + + public void swapToAvailable(XmrAddressEntry addressEntry) { + boolean setChangedByRemove = entrySet.remove(addressEntry); + boolean setChangedByAdd = entrySet.add(new XmrAddressEntry(addressEntry.getAccountIndex(), addressEntry.getAddressString(), + XmrAddressEntry.Context.AVAILABLE)); + if (setChangedByRemove || setChangedByAdd) { + requestPersistence(); + } + } + + public XmrAddressEntry swapAvailableToAddressEntryWithOfferId(XmrAddressEntry addressEntry, + XmrAddressEntry.Context context, + String offerId) { + boolean setChangedByRemove = entrySet.remove(addressEntry); + final XmrAddressEntry newAddressEntry = new XmrAddressEntry(addressEntry.getAccountIndex(), addressEntry.getAddressString(), context, offerId, null); + boolean setChangedByAdd = entrySet.add(newAddressEntry); + if (setChangedByRemove || setChangedByAdd) + requestPersistence(); + + return newAddressEntry; + } + + public void requestPersistence() { + persistenceManager.requestPersistence(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void maybeAddNewAddressEntry(MoneroOutputWallet output) { + if (output.getAccountIndex() == 0) return; + String address = wallet.getAddress(output.getAccountIndex(), output.getSubaddressIndex()); + if (!isAddressInEntries(address)) addAddressEntry(new XmrAddressEntry(output.getAccountIndex(), address, XmrAddressEntry.Context.AVAILABLE)); + } + + private boolean isAddressInEntries(String address) { + for (XmrAddressEntry entry : entrySet) { + if (entry.getAddressString().equals(address)) return true; + } + return false; + } + + @Override + public String toString() { + return "XmrAddressEntryList{" + + ",\n entrySet=" + entrySet + + "\n}"; + } +} diff --git a/core/src/main/java/bisq/core/btc/setup/MoneroWalletRpcManager.java b/core/src/main/java/bisq/core/btc/setup/MoneroWalletRpcManager.java new file mode 100644 index 00000000..2bb0b559 --- /dev/null +++ b/core/src/main/java/bisq/core/btc/setup/MoneroWalletRpcManager.java @@ -0,0 +1,128 @@ +package bisq.core.btc.setup; + +import java.net.ServerSocket; + +import java.io.IOException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + + +import monero.common.MoneroError; +import monero.wallet.MoneroWalletRpc; + +/** + * Manages monero-wallet-rpc processes bound to ports. + */ +public class MoneroWalletRpcManager { + + private static int NUM_ALLOWED_ATTEMPTS = 1; // allow this many attempts to bind to an assigned port + private Integer startPort; + private Map registeredPorts = new HashMap(); + + /** + * Manage monero-wallet-rpc instances by auto-assigning ports. + */ + public MoneroWalletRpcManager() { } + + /** + * Manage monero-wallet-rpc instances by assigning consecutive ports from a starting port. + * + * @param startPort is the starting port to bind to + */ + public MoneroWalletRpcManager(int startPort) { + this.startPort = startPort; + } + + /** + * Start a new instance of monero-wallet-rpc. + * + * @param cmd command line parameters to start monero-wallet-rpc + * @return a client connected to the monero-wallet-rpc instance + */ + public MoneroWalletRpc startInstance(List cmd) { + + try { + + // register given port + if (cmd.indexOf("--rpc-bind-port") >= 0) { + int port = Integer.valueOf(cmd.indexOf("--rpc-bind-port") + 1); + MoneroWalletRpc walletRpc = new MoneroWalletRpc(cmd); // starts monero-wallet-rpc process + registeredPorts.put(port, walletRpc); + return walletRpc; + } + + // register assigned ports up to maximum attempts + else { + int numAttempts = 0; + while (numAttempts < NUM_ALLOWED_ATTEMPTS) { + try { + numAttempts++; + int port = registerPort(); + List cmdCopy = new ArrayList(cmd); // preserve original cmd + cmdCopy.add("--rpc-bind-port"); + cmdCopy.add("" + port); + System.out.println(cmdCopy); + MoneroWalletRpc walletRpc = new MoneroWalletRpc(cmdCopy); // start monero-wallet-rpc process + registeredPorts.put(port, walletRpc); + return walletRpc; + } catch (Exception e) { + if (numAttempts >= NUM_ALLOWED_ATTEMPTS) { + System.err.println("Unable to start monero-wallet-rpc instance after " + NUM_ALLOWED_ATTEMPTS + " attempts"); + throw e; + } + } + } + throw new MoneroError("Failed to start monero-wallet-rpc instance after " + NUM_ALLOWED_ATTEMPTS + " attempts"); // should never reach here + } + } catch (IOException e) { + throw new MoneroError(e); + } + } + + /** + * Stop an instance of monero-wallet-rpc. + * + * @param walletRpc the client connected to the monero-wallet-rpc instance to stop + */ + public void stopInstance(MoneroWalletRpc walletRpc) { + boolean found = false; + for (Map.Entry entry : registeredPorts.entrySet()) { + if (walletRpc == entry.getValue()) { + walletRpc.stop(); + found = true; + try { unregisterPort(entry.getKey()); } + catch (Exception e) { throw new MoneroError(e); } + break; + } + } + if (!found) throw new RuntimeException("MoneroWalletRpc instance not associated with port"); + } + + private int registerPort() throws IOException { + + // register next consecutive port + if (startPort != null) { + int port = startPort; + while (registeredPorts.containsKey(port)) port++; + registeredPorts.put(port, null); + return port; + } + + // register auto-assigned port + else { + ServerSocket socket = new ServerSocket(0); // use socket to get available port + int port = socket.getLocalPort(); + socket.close(); + registeredPorts.put(port, null); + return port; + } + } + + private void unregisterPort(int port) { + registeredPorts.remove(port); + } +} diff --git a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java index bd260c13..c01bdf62 100644 --- a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java +++ b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java @@ -71,6 +71,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; @@ -85,6 +86,14 @@ import static bisq.common.util.Preconditions.checkDir; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; + + +import monero.common.MoneroUtils; +import monero.daemon.model.MoneroNetworkType; +import monero.wallet.MoneroWallet; +import monero.wallet.MoneroWalletRpc; +import monero.wallet.model.MoneroWalletConfig; + /** *

Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory * and file prefix, optionally configure a few things, then use startAsync and optionally awaitRunning. The object will @@ -112,15 +121,29 @@ public class WalletConfig extends AbstractIdleService { protected static final Logger log = LoggerFactory.getLogger(WalletConfig.class); + // Monero configuration + // TODO: don't hard code configuration, inject into classes? + private static final MoneroNetworkType MONERO_NETWORK_TYPE = MoneroNetworkType.STAGENET; + private static final String MONERO_DAEMON_URI = "http://localhost:38081"; + private static final String MONERO_DAEMON_USERNAME = "superuser"; + private static final String MONERO_DAEMON_PASSWORD = "abctesting123"; + private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager(); + private static final String MONERO_WALLET_RPC_PATH = System.getProperty("user.dir") + "/monero-wallet-rpc"; // current working directory + private static final String MONERO_WALLET_RPC_USERNAME = "rpc_user"; + private static final String MONERO_WALLET_RPC_PASSWORD = "abc123"; + private static final long MONERO_WALLET_SYNC_RATE = 5000l; + protected final NetworkParameters params; protected final String filePrefix; protected volatile BlockChain vChain; protected volatile SPVBlockStore vStore; + protected volatile MoneroWallet vXmrWallet; protected volatile Wallet vBtcWallet; protected volatile Wallet vBsqWallet; protected volatile PeerGroup vPeerGroup; protected final File directory; + protected volatile File vXmrWalletFile; protected volatile File vBtcWalletFile; protected volatile File vBsqWalletFile; @@ -261,6 +284,60 @@ public class WalletConfig extends AbstractIdleService { // Meant to be overridden by subclasses } + public MoneroWallet createWallet(MoneroWalletConfig config) { + + // start monero-wallet-rpc instance + MoneroWalletRpc walletRpc = startWalletRpcInstance(); + + // create wallet + try { + walletRpc.createWallet(config); + walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE); + return walletRpc; + } catch (Exception e) { + e.printStackTrace(); + WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc); + throw e; + } + } + + public MoneroWallet openWallet(MoneroWalletConfig config) { + + // start monero-wallet-rpc instance + MoneroWalletRpc walletRpc = startWalletRpcInstance(); + + // open wallet + try { + walletRpc.openWallet(config); + walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE); + return walletRpc; + } catch (Exception e) { + e.printStackTrace(); + WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc); + throw e; + } + } + + private MoneroWalletRpc startWalletRpcInstance() { + + // check if monero-wallet-rpc exists + if (!new File(MONERO_WALLET_RPC_PATH).exists()) throw new Error("monero-wallet-rpc executable doesn't exist at path " + MONERO_WALLET_RPC_PATH + "; copy monero-wallet-rpc to the project root or set WalletConfig.java MONERO_WALLET_RPC_PATH for your system"); + + // start monero-wallet-rpc instance and return connected client + return WalletConfig.MONERO_WALLET_RPC_MANAGER.startInstance(Arrays.asList( + MONERO_WALLET_RPC_PATH, + "--" + MONERO_NETWORK_TYPE.toString().toLowerCase(), + "--daemon-address", MONERO_DAEMON_URI, + "--daemon-login", MONERO_DAEMON_USERNAME + ":" + MONERO_DAEMON_PASSWORD, + "--rpc-login", MONERO_WALLET_RPC_USERNAME + ":" + MONERO_WALLET_RPC_PASSWORD, + "--wallet-dir", directory.toString() + )); + } + + public void closeWallet(MoneroWallet walletRpc) { + WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) walletRpc); + } + @Override protected void startUp() throws Exception { // Runs in a separate thread. @@ -268,6 +345,24 @@ public class WalletConfig extends AbstractIdleService { try { File chainFile = new File(directory, filePrefix + ".spvchain"); boolean chainFileExists = chainFile.exists(); + + // XMR wallet + String xmrPrefix = "_XMR"; + vXmrWalletFile = new File(directory, filePrefix + xmrPrefix); // TODO: *.wallet? + if (MoneroUtils.walletExists(vXmrWalletFile.getPath())) { + vXmrWallet = openWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123")); + } else { + vXmrWallet = createWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123")); + } + System.out.println("Monero wallet path: " + vXmrWallet.getPath()); + System.out.println("Monero wallet address: " + vXmrWallet.getPrimaryAddress()); + System.out.println("Monero mnemonic: " + vXmrWallet.getMnemonic()); +// vXmrWallet.rescanSpent(); +// vXmrWallet.rescanBlockchain(); + vXmrWallet.sync(); + vXmrWallet.save(); + System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance()); + String btcPrefix = "_BTC"; vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet"); boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null; @@ -531,6 +626,11 @@ public class WalletConfig extends AbstractIdleService { return vBtcWallet; } + public MoneroWallet getXmrWallet() { + checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); + return vXmrWallet; + } + public Wallet bsqWallet() { checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); return vBsqWallet; diff --git a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java index fad4ac30..d01f900c 100644 --- a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java +++ b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java @@ -21,6 +21,7 @@ import bisq.core.btc.exceptions.InvalidHostException; import bisq.core.btc.exceptions.RejectedTxException; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.model.AddressEntryList; +import bisq.core.btc.model.XmrAddressEntryList; import bisq.core.btc.nodes.BtcNetworkConfig; import bisq.core.btc.nodes.BtcNodes; import bisq.core.btc.nodes.BtcNodes.BtcNode; @@ -58,8 +59,8 @@ import org.bitcoinj.wallet.Wallet; import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; -import javax.inject.Inject; -import javax.inject.Named; +import com.google.inject.Inject; +import com.google.inject.name.Named; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.Service; @@ -103,6 +104,10 @@ import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.wallet.MoneroWallet; + // Setup wallets and use WalletConfig for BitcoinJ wiring. // Other like WalletConfig we are here always on the user thread. That is one reason why we do not // merge WalletsSetup with WalletConfig to one class. @@ -120,6 +125,7 @@ public class WalletsSetup { private final RegTestHost regTestHost; private final AddressEntryList addressEntryList; + private final XmrAddressEntryList xmrAddressEntryList; private final Preferences preferences; private final Socks5ProxyProvider socks5ProxyProvider; private final Config config; @@ -148,6 +154,7 @@ public class WalletsSetup { @Inject public WalletsSetup(RegTestHost regTestHost, AddressEntryList addressEntryList, + XmrAddressEntryList xmrAddressEntryList, Preferences preferences, Socks5ProxyProvider socks5ProxyProvider, Config config, @@ -160,6 +167,7 @@ public class WalletsSetup { @Named(Config.SOCKS5_DISCOVER_MODE) String socks5DiscoverModeString) { this.regTestHost = regTestHost; this.addressEntryList = addressEntryList; + this.xmrAddressEntryList = xmrAddressEntryList; this.preferences = preferences; this.socks5ProxyProvider = socks5ProxyProvider; this.config = config; @@ -253,6 +261,7 @@ public class WalletsSetup { UserThread.execute(() -> { chainHeight.set(chain.getBestChainHeight()); addressEntryList.onWalletReady(walletConfig.btcWallet()); + xmrAddressEntryList.onWalletReady(walletConfig.getXmrWallet()); timeoutTimer.stop(); setupCompletedHandlers.forEach(Runnable::run); }); @@ -479,6 +488,10 @@ public class WalletsSetup { return walletConfig.btcWallet(); } + public MoneroWallet getXmrWallet() { + return walletConfig.getXmrWallet(); + } + @Nullable public Wallet getBsqWallet() { return walletConfig.bsqWallet(); diff --git a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java index 4244e785..87f701d1 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -28,6 +28,7 @@ import bisq.core.btc.setup.WalletConfig; import bisq.core.btc.setup.WalletsSetup; import bisq.core.locale.Res; import bisq.core.user.Preferences; +import bisq.core.util.ParsingUtils; import bisq.common.config.Config; import bisq.common.util.Tuple2; @@ -75,6 +76,13 @@ import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroDestination; +import monero.wallet.model.MoneroTxConfig; +import monero.wallet.model.MoneroTxWallet; + public class TradeWalletService { private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class); private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000); @@ -86,6 +94,8 @@ public class TradeWalletService { @Nullable private Wallet wallet; @Nullable + private MoneroWallet xmrWallet; + @Nullable private WalletConfig walletConfig; @Nullable private KeyParameter aesKey; @@ -103,6 +113,7 @@ public class TradeWalletService { walletsSetup.addSetupCompletedHandler(() -> { walletConfig = walletsSetup.getWalletConfig(); wallet = walletsSetup.getBtcWallet(); + xmrWallet = walletsSetup.getXmrWallet(); }); } @@ -125,6 +136,21 @@ public class TradeWalletService { // Trade fee /////////////////////////////////////////////////////////////////////////////////////////// + public MoneroTxWallet createXmrTradingFeeTx( + String reservedForTradeAddress, + Coin reservedFundsForOffer, + Coin makerFee, + Coin txFee, + String feeReceiver, + boolean broadcastTx) { + return xmrWallet.createTx(new MoneroTxConfig() + .setAccountIndex(0) + .setDestinations( + new MoneroDestination(feeReceiver, ParsingUtils.satoshisToXmrAtomicUnits(makerFee.value)), + new MoneroDestination(reservedForTradeAddress, ParsingUtils.satoshisToXmrAtomicUnits(reservedFundsForOffer.value))) + .setRelay(broadcastTx)); + } + /** * Create a BTC trading fee transaction for the maker or taker of an offer. The first output of the tx is for the * fee receiver. The second output is the reserve of the trade. There is an optional third output for change. @@ -1206,6 +1232,16 @@ public class TradeWalletService { // Misc /////////////////////////////////////////////////////////////////////////////////////////// + /** + * Returns the local existing wallet transaction with the given ID, or {@code null} if missing. + * + * @param txHash the transaction hash of the transaction we want to lookup + */ + public MoneroTxWallet getWalletTx(String txHash) { + checkNotNull(xmrWallet); + return xmrWallet.getTx(txHash); + } + /** * Returns the local existing wallet transaction with the given ID, or {@code null} if missing. * diff --git a/core/src/main/java/bisq/core/btc/wallet/WalletService.java b/core/src/main/java/bisq/core/btc/wallet/WalletService.java index 0b893e47..a25a6a98 100644 --- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java @@ -105,6 +105,11 @@ import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroTxWallet; + /** * Abstract base class for BTC and BSQ wallet. Provides all non-trade specific functionality. */ @@ -228,7 +233,6 @@ public abstract class WalletService { balanceListeners.remove(listener); } - /////////////////////////////////////////////////////////////////////////////////////////// // Checks /////////////////////////////////////////////////////////////////////////////////////////// @@ -802,6 +806,22 @@ public abstract class WalletService { } } + public static MoneroTxWallet maybeAddNetworkTxToWallet(byte[] serializedTransaction, MoneroWallet wallet) throws VerificationException { + throw new RuntimeException("Not implemented"); // TODO (woodser): need to serialize/deserialize tx for xmr integration? +// Transaction tx = new Transaction(wallet.getParams(), serializedTransaction); +// Transaction walletTransaction = wallet.getTransaction(tx.getHash()); +// +// if (walletTransaction == null) { +// // We need to recreate the transaction otherwise we get a null pointer... +// tx.getConfidence(Context.get()).setSource(source); +// //wallet.maybeCommitTx(tx); +// wallet.receivePending(tx, null, true); +// return tx; +// } else { +// return walletTransaction; +// } + } + public static Transaction maybeAddNetworkTxToWallet(byte[] serializedTransaction, Wallet wallet) throws VerificationException { return maybeAddTxToWallet(serializedTransaction, wallet, TransactionConfidence.Source.NETWORK); diff --git a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java new file mode 100644 index 00000000..eeb79656 --- /dev/null +++ b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java @@ -0,0 +1,460 @@ +package bisq.core.btc.wallet; + +import bisq.core.btc.exceptions.AddressEntryException; +import bisq.core.btc.listeners.XmrBalanceListener; +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.model.XmrAddressEntryList; +import bisq.core.btc.setup.WalletsSetup; +import bisq.core.util.ParsingUtils; + +import org.bitcoinj.core.AddressFormatException; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; + +import javax.inject.Inject; + +import com.google.common.util.concurrent.FutureCallback; + +import javafx.application.Platform; + +import java.io.File; + +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import lombok.Getter; + + + +import monero.common.MoneroUtils; +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroAccount; +import monero.wallet.model.MoneroOutputWallet; +import monero.wallet.model.MoneroSubaddress; +import monero.wallet.model.MoneroTransfer; +import monero.wallet.model.MoneroTxConfig; +import monero.wallet.model.MoneroTxQuery; +import monero.wallet.model.MoneroTxWallet; +import monero.wallet.model.MoneroWalletConfig; +import monero.wallet.model.MoneroWalletListener; +import monero.wallet.model.MoneroWalletListenerI; + +public class XmrWalletService { + private static final Logger log = LoggerFactory.getLogger(XmrWalletService.class); + + private WalletsSetup walletsSetup; + private final XmrAddressEntryList addressEntryList; + protected final CopyOnWriteArraySet balanceListeners = new CopyOnWriteArraySet<>(); + protected final CopyOnWriteArraySet walletListeners = new CopyOnWriteArraySet<>(); + private Map multisigWallets; + + @Getter + private MoneroWallet wallet; + + @Inject + XmrWalletService(WalletsSetup walletsSetup, + XmrAddressEntryList addressEntryList) { + this.walletsSetup = walletsSetup; + + this.addressEntryList = addressEntryList; + this.multisigWallets = new HashMap(); + + walletsSetup.addSetupCompletedHandler(() -> { + wallet = walletsSetup.getXmrWallet(); + wallet.addListener(new MoneroWalletListener() { + @Override + public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { } + + @Override + public void onNewBlock(long height) { } + + @Override + public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { + Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx + @Override public void run() { + notifyBalanceListeners(); + } + }); + } + }); + }); + } + + // TODO (woodser): move hard-coded values to config + public MoneroWallet getOrCreateMultisigWallet(String tradeId) { + String path = "xmr_multisig_trade_" + tradeId; + MoneroWallet multisigWallet = null; + if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId); + else if (MoneroUtils.walletExists(new File(walletsSetup.getWalletConfig().directory(), path).getPath())) { // TODO: use monero-wallet-rpc to determine existence? + multisigWallet = walletsSetup.getWalletConfig().openWallet(new MoneroWalletConfig() + .setPath(path) + .setPassword("abctesting123")); + } else { + multisigWallet = walletsSetup.getWalletConfig().createWallet(new MoneroWalletConfig() + .setPath(path) + .setPassword("abctesting123")); + } + multisigWallets.put(tradeId, multisigWallet); + multisigWallet.startSyncing(5000l); + return multisigWallet; + } + + public XmrAddressEntry getArbitratorAddressEntry() { + XmrAddressEntry.Context context = XmrAddressEntry.Context.ARBITRATOR; + Optional addressEntry = getAddressEntryListAsImmutableList().stream() + .filter(e -> context == e.getContext()) + .findAny(); + return getOrCreateAddressEntry(context, addressEntry); + } + + public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) { + var available = findAddressEntry(address, XmrAddressEntry.Context.AVAILABLE); + if (!available.isPresent()) + return null; + return addressEntryList.swapAvailableToAddressEntryWithOfferId(available.get(), context, offerId); + } + + public XmrAddressEntry getNewAddressEntry(String offerId, XmrAddressEntry.Context context) { + if (context == XmrAddressEntry.Context.TRADE_PAYOUT) { + XmrAddressEntry entry = new XmrAddressEntry(0, wallet.createSubaddress(0).getAddress(), context, offerId, null); + System.out.println("Adding address entry: " + entry.getAccountIndex() + ", " + entry.getAddressString()); + addressEntryList.addAddressEntry(entry); + return entry; + } else { + MoneroAccount account = wallet.createAccount(); + XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null); + addressEntryList.addAddressEntry(entry); + return entry; + } + } + + public XmrAddressEntry getOrCreateAddressEntry(String offerId, XmrAddressEntry.Context context) { + Optional addressEntry = getAddressEntryListAsImmutableList().stream() + .filter(e -> offerId.equals(e.getOfferId())) + .filter(e -> context == e.getContext()) + .findAny(); + if (addressEntry.isPresent()) { + return addressEntry.get(); + } else { + // We try to use available and not yet used entries // TODO (woodser): "available" entries is not applicable in xmr which uses account 0 for main wallet and subsequent accounts for reserved trades, refactor address association for xmr? + Optional emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream() + .filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext()) + .filter(e -> isAccountUnused(e.getAccountIndex())) + .findAny(); + if (emptyAvailableAddressEntry.isPresent()) { + return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId); + } else { + MoneroAccount account = wallet.createAccount(); + XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null); + addressEntryList.addAddressEntry(entry); + return entry; + } + } + } + + private XmrAddressEntry getOrCreateAddressEntry(XmrAddressEntry.Context context, Optional addressEntry) { + if (addressEntry.isPresent()) { + return addressEntry.get(); + } else { + if (context == XmrAddressEntry.Context.ARBITRATOR) { + MoneroSubaddress subaddress = wallet.createSubaddress(0); + XmrAddressEntry entry = new XmrAddressEntry(0, subaddress.getAddress(), context); + addressEntryList.addAddressEntry(entry); + return entry; + } else { + throw new RuntimeException("XmrWalletService.getOrCreateAddressEntry(context, addressEntry) not implemented for non-arbitrator context"); // TODO (woodser): this method used with non-arbitrator context? + } + } + } + + public Optional getAddressEntry(String offerId, XmrAddressEntry.Context context) { + return getAddressEntryListAsImmutableList().stream() + .filter(e -> offerId.equals(e.getOfferId())) + .filter(e -> context == e.getContext()) + .findAny(); + } + + public void swapTradeEntryToAvailableEntry(String offerId, XmrAddressEntry.Context context) { + Optional addressEntryOptional = getAddressEntryListAsImmutableList().stream() + .filter(e -> offerId.equals(e.getOfferId())) + .filter(e -> context == e.getContext()) + .findAny(); + addressEntryOptional.ifPresent(e -> { + log.info("swap addressEntry with address {} and offerId {} from context {} to available", + e.getAddressString(), e.getOfferId(), context); + addressEntryList.swapToAvailable(e); + saveAddressEntryList(); + }); +} + + public void resetAddressEntriesForOpenOffer(String offerId) { + log.info("resetAddressEntriesForOpenOffer offerId={}", offerId); + swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.OFFER_FUNDING); + swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.RESERVED_FOR_TRADE); + } + + public void resetAddressEntriesForPendingTrade(String offerId) { + swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.MULTI_SIG); + // We swap also TRADE_PAYOUT to be sure all is cleaned up. There might be cases where a user cannot send the funds + // to an external wallet directly in the last step of the trade, but the funds are in the Bisq wallet anyway and + // the dealing with the external wallet is pure UI thing. The user can move the funds to the wallet and then + // send out the funds to the external wallet. As this cleanup is a rare situation and most users do not use + // the feature to send out the funds we prefer that strategy (if we keep the address entry it might cause + // complications in some edge cases after a SPV resync). + swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.TRADE_PAYOUT); + } + + private Optional findAddressEntry(String address, XmrAddressEntry.Context context) { + return getAddressEntryListAsImmutableList().stream() + .filter(e -> address.equals(e.getAddressString())) + .filter(e -> context == e.getContext()) + .findAny(); + } + + public List getAvailableAddressEntries() { + return getAddressEntryListAsImmutableList().stream() + .filter(addressEntry -> XmrAddressEntry.Context.AVAILABLE == addressEntry.getContext()) + .collect(Collectors.toList()); +} + + public List getAddressEntriesForTrade() { + return getAddressEntryListAsImmutableList().stream() + .filter(addressEntry -> XmrAddressEntry.Context.MULTI_SIG == addressEntry.getContext() || + XmrAddressEntry.Context.TRADE_PAYOUT == addressEntry.getContext()) + .collect(Collectors.toList()); + } + + public List getAddressEntries(XmrAddressEntry.Context context) { + return getAddressEntryListAsImmutableList().stream() + .filter(addressEntry -> context == addressEntry.getContext()) + .collect(Collectors.toList()); + } + + public List getFundedAvailableAddressEntries() { + return getAvailableAddressEntries().stream() + .filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive()) + .collect(Collectors.toList()); + } + + public List getAddressEntryListAsImmutableList() { + return addressEntryList.getAddressEntriesAsListImmutable(); + } + + public boolean isAccountUnused(int accountIndex) { + return accountIndex != 0 && getBalanceForAccount(accountIndex).value == 0; + //return !wallet.getSubaddress(accountIndex, 0).isUsed(); // TODO: isUsed() does not include unconfirmed funds + } + + public Coin getBalanceForAccount(int accountIndex) { + + // get wallet balance + BigInteger balance = wallet.getBalance(accountIndex); + + // balance from xmr wallet does not include unconfirmed funds, so add them // TODO: support lower in stack? + for (MoneroTxWallet unconfirmedTx : wallet.getTxs(new MoneroTxQuery().setIsConfirmed(false))) { + for (MoneroTransfer transfer : unconfirmedTx.getTransfers()) { + if (transfer.getAccountIndex() == accountIndex) { + balance = transfer.isIncoming() ? balance.add(transfer.getAmount()) : balance.subtract(transfer.getAmount()); + } + } + } + + System.out.println("Returning balance for account " + accountIndex + ": " + balance.longValueExact()); + + return Coin.valueOf(balance.longValueExact()); + } + + + public Coin getAvailableConfirmedBalance() { + return wallet != null ? Coin.valueOf(wallet.getUnlockedBalance(0).longValueExact()) : Coin.ZERO; + } + + public Coin getSavingWalletBalance() { + return wallet != null ? Coin.valueOf(wallet.getBalance(0).longValueExact()) : Coin.ZERO; + } + + public Stream getAddressEntriesForAvailableBalanceStream() { + Stream availableAndPayout = Stream.concat(getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream(), getFundedAvailableAddressEntries().stream()); + Stream available = Stream.concat(availableAndPayout, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream()); + available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream()); + return available.filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive()); + } + + public void addBalanceListener(XmrBalanceListener listener) { + balanceListeners.add(listener); + } + + public void removeBalanceListener(XmrBalanceListener listener) { + balanceListeners.remove(listener); + } + + public void saveAddressEntryList() { + addressEntryList.requestPersistence(); + } + + public List getTransactions(boolean includeDead) { + return wallet.getTxs(new MoneroTxQuery().setIsFailed(includeDead ? null : false)); + } + + public void shutDown() { + System.out.println("XmrWalletService.shutDown()"); + + // collect wallets to shutdown + List openWallets = new ArrayList(); + if (wallet != null) openWallets.add(wallet); + for (String multisigWalletKey : multisigWallets.keySet()) { + openWallets.add(multisigWallets.get(multisigWalletKey)); + } + + // create shutdown threads + List threads = new ArrayList(); + for (MoneroWallet openWallet : openWallets) { + threads.add(new Thread(new Runnable() { + @Override + public void run() { + System.out.println("XmrWalletServie.shutDown() closing wallet within thread!!!"); + System.out.println("Wallet balance: " + wallet.getBalance()); + try { walletsSetup.getWalletConfig().closeWallet(openWallet); } + catch (Exception e) { e.printStackTrace(); } + } + })); + } + + // run shutdown threads in parallel + for (Thread thread : threads) thread.start(); + + // wait for all threads + System.out.println("Joining threads"); + for (Thread thread : threads) { + try { thread.join(); } + catch (InterruptedException e) { e.printStackTrace(); } + } + System.out.println("Done joining threads"); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Withdrawal Send + /////////////////////////////////////////////////////////////////////////////////////////// + + public String sendFunds(int fromAccountIndex, + String toAddress, + Coin receiverAmount, + @SuppressWarnings("SameParameterValue") XmrAddressEntry.Context context, + FutureCallback callback) throws AddressFormatException, + AddressEntryException, InsufficientMoneyException { + + try { + MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig() + .setAccountIndex(fromAccountIndex) + .setAddress(toAddress) + .setAmount(ParsingUtils.satoshisToXmrAtomicUnits(receiverAmount.value)) + .setRelay(true)); + callback.onSuccess(tx); + printTxs("sendFunds", tx); + return tx.getHash(); + } catch (Exception e) { + callback.onFailure(e); + throw e; + } + } + +// public String sendFunds(String fromAddress, String toAddress, Coin receiverAmount, Coin fee, @Nullable KeyParameter aesKey, @SuppressWarnings("SameParameterValue") AddressEntry.Context context, +// FutureCallback callback) throws AddressFormatException, AddressEntryException, InsufficientMoneyException { +// SendRequest sendRequest = getSendRequest(fromAddress, toAddress, receiverAmount, fee, aesKey, context); +// Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); +// Futures.addCallback(sendResult.broadcastComplete, callback, MoreExecutors.directExecutor()); +// +// printTx("sendFunds", sendResult.tx); +// return sendResult.tx.getTxId().toString(); +// } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Util + /////////////////////////////////////////////////////////////////////////////////////////// + + public static void printTxs(String tracePrefix, MoneroTxWallet... txs) { + StringBuilder sb = new StringBuilder(); + for (MoneroTxWallet tx : txs) sb.append('\n' + tx.toString()); + log.info("\n" + tracePrefix + ":" + sb.toString()); + } + + private void notifyBalanceListeners() { + for (XmrBalanceListener balanceListener : balanceListeners) { + Coin balance; + if (balanceListener.getAccountIndex() != null && balanceListener.getAccountIndex() != 0) { + balance = getBalanceForAccount(balanceListener.getAccountIndex()); + } else { + balance = getAvailableConfirmedBalance(); + } + balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value)); + } + } + + /** + * Wraps a MoneroWalletListener to notify the Haveno application. + */ + public class HavenoWalletListener extends MoneroWalletListener { + + private MoneroWalletListener listener; + + public HavenoWalletListener(MoneroWalletListener listener) { + this.listener = listener; + } + + @Override + public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { + Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx + @Override public void run() { + listener.onSyncProgress(height, startHeight, endHeight, percentDone, message); + } + }); + } + + @Override + public void onNewBlock(long height) { + Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx + @Override public void run() { + listener.onNewBlock(height); + } + }); + } + + @Override + public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { + Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx + @Override public void run() { + listener.onBalancesChanged(newBalance, newUnlockedBalance); + } + }); + } + + @Override + public void onOutputReceived(MoneroOutputWallet output) { + Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx + @Override public void run() { + listener.onOutputReceived(output); + } + }); + } + + @Override + public void onOutputSpent(MoneroOutputWallet output) { + Platform.runLater(new Runnable() { // jni wallet runs on separate thread which cannot update fx + @Override public void run() { + listener.onOutputSpent(output); + } + }); + } + } +} diff --git a/core/src/main/java/bisq/core/locale/CurrencyUtil.java b/core/src/main/java/bisq/core/locale/CurrencyUtil.java index 360a0b8c..f2581b45 100644 --- a/core/src/main/java/bisq/core/locale/CurrencyUtil.java +++ b/core/src/main/java/bisq/core/locale/CurrencyUtil.java @@ -57,12 +57,12 @@ import static java.lang.String.format; @Slf4j public class CurrencyUtil { public static void setup() { - setBaseCurrencyCode(Config.baseCurrencyNetwork().getCurrencyCode()); + setBaseCurrencyCode("XMR"); } private static final AssetRegistry assetRegistry = new AssetRegistry(); - private static String baseCurrencyCode = "BTC"; + private static String baseCurrencyCode = "XMR"; // Calls to isFiatCurrency and isCryptoCurrency are very frequent so we use a cache of the results. // The main improvement was already achieved with using memoize for the source maps, but diff --git a/core/src/main/java/bisq/core/locale/Res.java b/core/src/main/java/bisq/core/locale/Res.java index fcf9141c..6d102ccd 100644 --- a/core/src/main/java/bisq/core/locale/Res.java +++ b/core/src/main/java/bisq/core/locale/Res.java @@ -46,8 +46,8 @@ import org.jetbrains.annotations.NotNull; public class Res { public static void setup() { BaseCurrencyNetwork baseCurrencyNetwork = Config.baseCurrencyNetwork(); - setBaseCurrencyCode(baseCurrencyNetwork.getCurrencyCode()); - setBaseCurrencyName(baseCurrencyNetwork.getCurrencyName()); + setBaseCurrencyCode("XMR"); + setBaseCurrencyName("Monero"); } @SuppressWarnings("CanBeFinal") @@ -78,20 +78,20 @@ public class Res { private static String baseCurrencyNameLowerCase; public static void setBaseCurrencyCode(String baseCurrencyCode) { - Res.baseCurrencyCode = baseCurrencyCode; + Res.baseCurrencyCode = "XMR"; } public static void setBaseCurrencyName(String baseCurrencyName) { - Res.baseCurrencyName = baseCurrencyName; + Res.baseCurrencyName = "Monero"; baseCurrencyNameLowerCase = baseCurrencyName.toLowerCase(); } public static String getBaseCurrencyCode() { - return baseCurrencyCode; + return "XMR"; } public static String getBaseCurrencyName() { - return baseCurrencyName; + return "Monero"; } // Capitalize first character @@ -110,9 +110,9 @@ public class Res { public static String get(String key) { try { return resourceBundle.getString(key) - .replace("BTC", baseCurrencyCode) - .replace("Bitcoin", baseCurrencyName) - .replace("bitcoin", baseCurrencyNameLowerCase); + .replace("XMR", baseCurrencyCode) + .replace("Monero", baseCurrencyName) + .replace("monero", baseCurrencyNameLowerCase); } catch (MissingResourceException e) { log.warn("Missing resource for key: {}", key); e.printStackTrace(); diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index bf04e10b..6b8a3b5b 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -21,6 +21,7 @@ import bisq.core.api.CoreContext; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoFacade; import bisq.core.exceptions.TradePriceOutOfToleranceException; import bisq.core.filter.FilterManager; @@ -108,6 +109,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe private final User user; private final P2PService p2PService; private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final TradeWalletService tradeWalletService; private final BsqWalletService bsqWalletService; private final OfferBookService offerBookService; @@ -141,6 +143,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe User user, P2PService p2PService, BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, TradeWalletService tradeWalletService, BsqWalletService bsqWalletService, OfferBookService offerBookService, @@ -161,6 +164,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe this.user = user; this.p2PService = p2PService; this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.tradeWalletService = tradeWalletService; this.bsqWalletService = bsqWalletService; this.offerBookService = offerBookService; @@ -381,6 +385,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe reservedFundsForOffer, useSavingsWallet, btcWalletService, + xmrWalletService, tradeWalletService, bsqWalletService, offerBookService, @@ -642,11 +647,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe if (openOffer.getState() == OpenOffer.State.AVAILABLE) { Offer offer = openOffer.getOffer(); if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { - mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress(); - openOffer.setMediatorNodeAddress(mediatorNodeAddress); - - refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress(); - openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress); + arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress(); + openOffer.setArbitratorNodeAddress(arbitratorNodeAddress); try { // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference diff --git a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java index eaec9dcb..7b3bee5f 100644 --- a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java +++ b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java @@ -44,23 +44,14 @@ import static com.google.common.base.Preconditions.checkArgument; public class DisputeAgentSelection { public static final int LOOK_BACK_RANGE = 100; - public static T getLeastUsedMediator(TradeStatisticsManager tradeStatisticsManager, + public static T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager, DisputeAgentManager disputeAgentManager) { return getLeastUsedDisputeAgent(tradeStatisticsManager, - disputeAgentManager, - true); - } - - public static T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager, - DisputeAgentManager disputeAgentManager) { - return getLeastUsedDisputeAgent(tradeStatisticsManager, - disputeAgentManager, - false); + disputeAgentManager); } private static T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager, - DisputeAgentManager disputeAgentManager, - boolean isMediator) { + DisputeAgentManager disputeAgentManager) { // We take last 100 entries from trade statistics List list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet()); list.sort(Comparator.comparing(TradeStatistics3::getDateAsLong)); @@ -72,7 +63,7 @@ public class DisputeAgentSelection { // We stored only first 4 chars of disputeAgents onion address List lastAddressesUsedInTrades = list.stream() - .map(tradeStatistics3 -> isMediator ? tradeStatistics3.getMediator() : tradeStatistics3.getRefundAgent()) + .map(tradeStatistics3 -> tradeStatistics3.getArbitrator()) .filter(Objects::nonNull) .collect(Collectors.toList()); diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java index f6c04fa1..20bc8447 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java @@ -62,7 +62,7 @@ public class ProcessOfferAvailabilityResponse extends Task { - String value = formatter.formatCoinWithCode(newValue); - // If we get full precision the BTC postfix breaks layout so we omit it - if (value.length() > 11) - value = formatter.formatCoin(newValue); - availableBalance.set(value); + availableBalance.set(longToXmr(newValue.value)); }); balances.getReservedBalance().addListener((observable, oldValue, newValue) -> { - reservedBalance.set(formatter.formatCoinWithCode(newValue)); + reservedBalance.set(longToXmr(newValue.value)); }); balances.getLockedBalance().addListener((observable, oldValue, newValue) -> { - lockedBalance.set(formatter.formatCoinWithCode(newValue)); + lockedBalance.set(longToXmr(newValue.value)); }); } + + // TODO: truncate full precision with ellipses to not break layout? + // TODO (woodser): formatting utils in monero-java + private static String longToXmr(long amt) { + BigInteger auAmt = BigInteger.valueOf(amt); + BigInteger[] quotientAndRemainder = auAmt.divideAndRemainder(AU_PER_XMR); + double decimalRemainder = quotientAndRemainder[1].doubleValue() / AU_PER_XMR.doubleValue(); + return quotientAndRemainder[0].doubleValue() + decimalRemainder + " XMR"; + } } diff --git a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java index d30a671b..bd7796f3 100644 --- a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java @@ -43,6 +43,8 @@ import bisq.core.proto.CoreProtoResolver; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage; import bisq.core.support.dispute.mediation.mediator.Mediator; +import bisq.core.support.dispute.messages.ArbitratorPayoutTxRequest; +import bisq.core.support.dispute.messages.ArbitratorPayoutTxResponse; import bisq.core.support.dispute.messages.DisputeResultMessage; import bisq.core.support.dispute.messages.OpenNewDisputeMessage; import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage; @@ -53,14 +55,20 @@ import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.messages.InitMultisigMessage; +import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InputsForDepositTxRequest; import bisq.core.trade.messages.InputsForDepositTxResponse; +import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; +import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage; import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage; import bisq.core.trade.messages.PayoutTxPublishedMessage; import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage; import bisq.core.trade.messages.RefreshTradeStateRequest; import bisq.core.trade.messages.TraderSignedWitnessMessage; +import bisq.core.trade.messages.UpdateMultisigRequest; +import bisq.core.trade.messages.UpdateMultisigResponse; import bisq.network.p2p.AckMessage; import bisq.network.p2p.BundleOfEnvelopes; @@ -147,6 +155,18 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo // trade protocol messages case REFRESH_TRADE_STATE_REQUEST: return RefreshTradeStateRequest.fromProto(proto.getRefreshTradeStateRequest(), messageVersion); + case INIT_TRADE_REQUEST: + return InitTradeRequest.fromProto(proto.getInitTradeRequest(), this, messageVersion); + case INIT_MULTISIG_MESSAGE: + return InitMultisigMessage.fromProto(proto.getInitMultisigMessage(), this, messageVersion); + case UPDATE_MULTISIG_REQUEST: + return UpdateMultisigRequest.fromProto(proto.getUpdateMultisigRequest(), this, messageVersion); + case UPDATE_MULTISIG_RESPONSE: + return UpdateMultisigResponse.fromProto(proto.getUpdateMultisigResponse(), this, messageVersion); + case MAKER_READY_TO_FUND_MULTISIG_REQUEST: + return MakerReadyToFundMultisigRequest.fromProto(proto.getMakerReadyToFundMultisigRequest(), this, messageVersion); + case MAKER_READY_TO_FUND_MULTISIG_RESPONSE: + return MakerReadyToFundMultisigResponse.fromProto(proto.getMakerReadyToFundMultisigResponse(), this, messageVersion); case INPUTS_FOR_DEPOSIT_TX_REQUEST: return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion); case INPUTS_FOR_DEPOSIT_TX_RESPONSE: @@ -185,6 +205,10 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo return DisputeResultMessage.fromProto(proto.getDisputeResultMessage(), messageVersion); case PEER_PUBLISHED_DISPUTE_PAYOUT_TX_MESSAGE: return PeerPublishedDisputePayoutTxMessage.fromProto(proto.getPeerPublishedDisputePayoutTxMessage(), messageVersion); + case ARBITRATOR_PAYOUT_TX_REQUEST: + return ArbitratorPayoutTxRequest.fromProto(proto.getArbitratorPayoutTxRequest(), this, messageVersion); + case ARBITRATOR_PAYOUT_TX_RESPONSE: + return ArbitratorPayoutTxResponse.fromProto(proto.getArbitratorPayoutTxResponse(), this, messageVersion); case PRIVATE_NOTIFICATION_MESSAGE: return PrivateNotificationMessage.fromProto(proto.getPrivateNotificationMessage(), messageVersion); diff --git a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java index f42670d7..8f18bffc 100644 --- a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java @@ -20,7 +20,9 @@ package bisq.core.proto.persistable; import bisq.core.account.sign.SignedWitnessStore; import bisq.core.account.witness.AccountAgeWitnessStore; import bisq.core.btc.model.AddressEntryList; +import bisq.core.btc.model.XmrAddressEntryList; import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.governance.blindvote.MyBlindVoteList; import bisq.core.dao.governance.blindvote.storage.BlindVoteStore; import bisq.core.dao.governance.bond.reputation.MyReputationList; @@ -67,12 +69,15 @@ import lombok.extern.slf4j.Slf4j; @Singleton public class CorePersistenceProtoResolver extends CoreProtoResolver implements PersistenceProtoResolver { private final Provider btcWalletService; + private final Provider xmrWalletService; private final NetworkProtoResolver networkProtoResolver; @Inject public CorePersistenceProtoResolver(Provider btcWalletService, + Provider xmrWalletService, NetworkProtoResolver networkProtoResolver) { this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.networkProtoResolver = networkProtoResolver; } @@ -86,8 +91,10 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P return PeerList.fromProto(proto.getPeerList()); case ADDRESS_ENTRY_LIST: return AddressEntryList.fromProto(proto.getAddressEntryList()); + case XMR_ADDRESS_ENTRY_LIST: + return XmrAddressEntryList.fromProto(proto.getXmrAddressEntryList()); case TRADABLE_LIST: - return TradableList.fromProto(proto.getTradableList(), this, btcWalletService.get()); + return TradableList.fromProto(proto.getTradableList(), this, xmrWalletService.get()); case ARBITRATION_DISPUTE_LIST: return ArbitrationDisputeList.fromProto(proto.getArbitrationDisputeList(), this); case MEDIATION_DISPUTE_LIST: diff --git a/core/src/main/java/bisq/core/provider/mempool/MempoolService.java b/core/src/main/java/bisq/core/provider/mempool/MempoolService.java index 7894b55f..2f351828 100644 --- a/core/src/main/java/bisq/core/provider/mempool/MempoolService.java +++ b/core/src/main/java/bisq/core/provider/mempool/MempoolService.java @@ -106,8 +106,9 @@ public class MempoolService { } public void validateOfferTakerTx(Trade trade, Consumer resultHandler) { - validateOfferTakerTx(new TxValidator(daoStateService, trade.getTakerFeeTxId(), trade.getTradeAmount(), - trade.isCurrencyForTakerFeeBtc()), resultHandler); + throw new RuntimeException("MempoolService.validateOfferTakerTx needs updated for XMR"); +// validateOfferTakerTx(new TxValidator(daoStateService, trade.getTakerFeeTxId(), trade.getTradeAmount(), +// trade.isCurrencyForTakerFeeBtc()), resultHandler); } public void validateOfferTakerTx(TxValidator txValidator, Consumer resultHandler) { diff --git a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java index 0e54e552..ab2a162f 100644 --- a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java +++ b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java @@ -18,6 +18,7 @@ package bisq.core.setup; import bisq.core.btc.model.AddressEntryList; +import bisq.core.btc.model.XmrAddressEntryList; import bisq.core.dao.governance.ballot.BallotListService; import bisq.core.dao.governance.blindvote.MyBlindVoteListService; import bisq.core.dao.governance.bond.reputation.MyReputationListService; @@ -60,6 +61,7 @@ public class CorePersistedDataHost { persistedDataHosts.add(injector.getInstance(Preferences.class)); persistedDataHosts.add(injector.getInstance(User.class)); persistedDataHosts.add(injector.getInstance(AddressEntryList.class)); + persistedDataHosts.add(injector.getInstance(XmrAddressEntryList.class)); persistedDataHosts.add(injector.getInstance(OpenOfferManager.class)); persistedDataHosts.add(injector.getInstance(TradeManager.class)); persistedDataHosts.add(injector.getInstance(ClosedTradableManager.class)); diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index 98ed154f..81527f43 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -128,6 +128,9 @@ public final class Dispute implements NetworkPayload, PersistablePayload { @Nullable private String delayedPayoutTxId; + // Added for XMR integration + private boolean isOpener; + // Added at v1.4.0 @Setter @Nullable @@ -160,6 +163,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload { public Dispute(long openingDate, String tradeId, int traderId, + boolean isOpener, boolean disputeOpenerIsBuyer, boolean disputeOpenerIsMaker, PubKeyRing traderPubKeyRing, @@ -180,6 +184,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload { this.openingDate = openingDate; this.tradeId = tradeId; this.traderId = traderId; + this.isOpener = isOpener; this.disputeOpenerIsBuyer = disputeOpenerIsBuyer; this.disputeOpenerIsMaker = disputeOpenerIsMaker; this.traderPubKeyRing = traderPubKeyRing; @@ -215,6 +220,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload { protobuf.Dispute.Builder builder = protobuf.Dispute.newBuilder() .setTradeId(tradeId) .setTraderId(traderId) + .setIsOpener(isOpener) .setDisputeOpenerIsBuyer(disputeOpenerIsBuyer) .setDisputeOpenerIsMaker(disputeOpenerIsMaker) .setTraderPubKeyRing(traderPubKeyRing.toProtoMessage()) @@ -253,6 +259,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload { Dispute dispute = new Dispute(proto.getOpeningDate(), proto.getTradeId(), proto.getTraderId(), + proto.getIsOpener(), proto.getDisputeOpenerIsBuyer(), proto.getDisputeOpenerIsMaker(), PubKeyRing.fromProto(proto.getTraderPubKeyRing()), @@ -327,6 +334,9 @@ public final class Dispute implements NetworkPayload, PersistablePayload { } } + public boolean isMediationDispute() { + return !chatMessages.isEmpty() && chatMessages.get(0).getSupportType() == SupportType.MEDIATION; + } /////////////////////////////////////////////////////////////////////////////////////////// // Setters @@ -447,6 +457,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload { ",\n uid='" + uid + '\'' + ",\n state=" + disputeState + ",\n traderId=" + traderId + + ",\n isOpener=" + isOpener + ",\n disputeOpenerIsBuyer=" + disputeOpenerIsBuyer + ",\n disputeOpenerIsMaker=" + disputeOpenerIsMaker + ",\n traderPubKeyRing=" + traderPubKeyRing + diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 6abb55ff..61a15c7e 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -18,9 +18,9 @@ package bisq.core.support.dispute; import bisq.core.btc.setup.WalletsSetup; -import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoFacade; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; @@ -66,6 +66,7 @@ import javafx.collections.ObservableList; import java.security.KeyPair; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Optional; @@ -81,10 +82,14 @@ import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.wallet.MoneroWallet; + @Slf4j public abstract class DisputeManager> extends SupportManager { protected final TradeWalletService tradeWalletService; - protected final BtcWalletService btcWalletService; + protected final XmrWalletService xmrWalletService; protected final TradeManager tradeManager; protected final ClosedTradableManager closedTradableManager; protected final OpenOfferManager openOfferManager; @@ -107,7 +112,7 @@ public abstract class DisputeManager> extends Sup public DisputeManager(P2PService p2PService, TradeWalletService tradeWalletService, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, WalletsSetup walletsSetup, TradeManager tradeManager, ClosedTradableManager closedTradableManager, @@ -120,7 +125,7 @@ public abstract class DisputeManager> extends Sup super(p2PService, walletsSetup); this.tradeWalletService = tradeWalletService; - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.tradeManager = tradeManager; this.closedTradableManager = closedTradableManager; this.openOfferManager = openOfferManager; @@ -184,7 +189,7 @@ public abstract class DisputeManager> extends Sup dispute.addAndPersistChatMessage(message); requestPersistence(); } else { - log.warn("We got a chatMessage that we have already stored. UId = {} TradeId = {}", + log.warn("We got a chatMessage what we have already stored. UId = {} TradeId = {}", message.getUid(), message.getTradeId()); } }); @@ -198,7 +203,6 @@ public abstract class DisputeManager> extends Sup // We get that message at both peers. The dispute object is in context of the trader public abstract void onDisputeResultMessage(DisputeResultMessage disputeResultMessage); - @Nullable public abstract NodeAddress getAgentNodeAddress(Dispute dispute); protected abstract Trade.DisputeState getDisputeStateStartedByPeer(); @@ -276,11 +280,12 @@ public abstract class DisputeManager> extends Sup } }); - TradeDataValidation.testIfAnyDisputeTriedReplay(disputes, - disputeReplayException -> { - log.error(disputeReplayException.toString()); - validationExceptions.add(disputeReplayException); - }); + // TODO (woodser): disabled for xmr, needed? +// TradeDataValidation.testIfAnyDisputeTriedReplay(disputes, +// disputeReplayException -> { +// log.error(disputeReplayException.toString()); +// validationExceptions.add(disputeReplayException); +// }); } public boolean isTrader(Dispute dispute) { @@ -302,7 +307,7 @@ public abstract class DisputeManager> extends Sup // Message handler /////////////////////////////////////////////////////////////////////////////////////////// - // dispute agent receives that from trader who opens dispute + // arbitrator receives that from trader who opens dispute protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessage) { T disputeList = getDisputeList(); if (disputeList == null) { @@ -322,6 +327,13 @@ public abstract class DisputeManager> extends Sup PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing(); if (isAgent(dispute)) { + + // update arbitrator's multisig wallet + MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId()); + multisigWallet.importMultisigHex(Arrays.asList(openNewDisputeMessage.getUpdatedMultisigHex())); + System.out.println("Arbitrator multisig wallet updated on new dispute message, current txs:"); + System.out.println(multisigWallet.getTxs()); + if (!disputeList.contains(dispute)) { Optional storedDisputeOptional = findDispute(dispute); if (!storedDisputeOptional.isPresent()) { @@ -333,8 +345,8 @@ public abstract class DisputeManager> extends Sup dispute.getTradeId()); } } else { - errorMessage = "We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId(); - log.warn(errorMessage); + errorMessage = "We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId(); + log.warn(errorMessage); } } else { errorMessage = "Trader received openNewDisputeMessage. That must never happen."; @@ -342,22 +354,21 @@ public abstract class DisputeManager> extends Sup } // We use the ChatMessage not the openNewDisputeMessage for the ACK - ObservableList messages = dispute.getChatMessages(); + ObservableList messages = openNewDisputeMessage.getDispute().getChatMessages(); if (!messages.isEmpty()) { - ChatMessage chatMessage = messages.get(0); + ChatMessage msg = messages.get(0); PubKeyRing sendersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing(); - sendAckMessage(chatMessage, sendersPubKeyRing, errorMessage == null, errorMessage); + sendAckMessage(msg, sendersPubKeyRing, errorMessage == null, errorMessage); } addMediationResultMessage(dispute); try { TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); + //TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); // TODO (woodser): disabled for xmr, needed? TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config); TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config); } catch (TradeDataValidation.AddressException | - TradeDataValidation.DisputeReplayException | TradeDataValidation.NodeAddressException e) { log.error(e.toString()); validationExceptions.add(e); @@ -380,20 +391,7 @@ public abstract class DisputeManager> extends Sup if (!optionalTrade.isPresent()) { return; } - Trade trade = optionalTrade.get(); - try { - TradeDataValidation.validateDelayedPayoutTx(trade, - trade.getDelayedPayoutTx(), - dispute, - daoFacade, - btcWalletService); - } catch (TradeDataValidation.ValidationException e) { - // The peer sent us an invalid donation address. We do not return here as we don't want to break - // mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get - // a popup displayed to react. - log.warn("Donation address is invalid. {}", e.toString()); - } if (!isAgent(dispute)) { if (!disputeList.contains(dispute)) { @@ -435,6 +433,7 @@ public abstract class DisputeManager> extends Sup public void sendOpenNewDisputeMessage(Dispute dispute, boolean reOpen, + String updatedMultisigHex, ResultHandler resultHandler, FaultHandler faultHandler) { T disputeList = getDisputeList(); @@ -453,18 +452,16 @@ public abstract class DisputeManager> extends Sup Optional storedDisputeOptional = findDispute(dispute); if (!storedDisputeOptional.isPresent() || reOpen) { String disputeInfo = getDisputeInfo(dispute); - String disputeMessage = getDisputeIntroForDisputeCreator(disputeInfo); String sysMsg = dispute.isSupportTicket() ? Res.get("support.youOpenedTicket", disputeInfo, Version.VERSION) - : disputeMessage; + : Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION); - String message = Res.get("support.systemMsg", sysMsg); ChatMessage chatMessage = new ChatMessage( getSupportType(), dispute.getTradeId(), pubKeyRing.hashCode(), false, - message, + Res.get("support.systemMsg", sysMsg), p2PService.getAddress()); chatMessage.setSystemMessage(true); dispute.addAndPersistChatMessage(chatMessage); @@ -473,22 +470,16 @@ public abstract class DisputeManager> extends Sup } NodeAddress agentNodeAddress = getAgentNodeAddress(dispute); - if (agentNodeAddress == null) { - return; - } - OpenNewDisputeMessage openNewDisputeMessage = new OpenNewDisputeMessage(dispute, p2PService.getAddress(), UUID.randomUUID().toString(), - getSupportType()); - - log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, chatMessage.uid={}", - openNewDisputeMessage.getClass().getSimpleName(), - agentNodeAddress, - openNewDisputeMessage.getTradeId(), - openNewDisputeMessage.getUid(), + getSupportType(), + updatedMultisigHex); + log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, " + + "chatMessage.uid={}", + openNewDisputeMessage.getClass().getSimpleName(), agentNodeAddress, + openNewDisputeMessage.getTradeId(), openNewDisputeMessage.getUid(), chatMessage.getUid()); - mailboxMessageService.sendEncryptedMailboxMessage(agentNodeAddress, dispute.getAgentPubKeyRing(), openNewDisputeMessage, @@ -575,6 +566,7 @@ public abstract class DisputeManager> extends Sup Dispute dispute = new Dispute(new Date().getTime(), disputeFromOpener.getTradeId(), pubKeyRing.hashCode(), + false, !disputeFromOpener.isDisputeOpenerIsBuyer(), !disputeFromOpener.isDisputeOpenerIsMaker(), pubKeyRing, @@ -636,7 +628,6 @@ public abstract class DisputeManager> extends Sup peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress, peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(), chatMessage.getUid()); - mailboxMessageService.sendEncryptedMailboxMessage(peersNodeAddress, peersPubKeyRing, peerOpenedDisputeMessage, @@ -687,7 +678,7 @@ public abstract class DisputeManager> extends Sup requestPersistence(); } - // dispute agent send result to trader + // arbitrator send result to trader public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute dispute, String summaryText) { T disputeList = getDisputeList(); if (disputeList == null) { @@ -769,7 +760,6 @@ public abstract class DisputeManager> extends Sup requestPersistence(); } - /////////////////////////////////////////////////////////////////////////////////////////// // Utils /////////////////////////////////////////////////////////////////////////////////////////// @@ -811,7 +801,7 @@ public abstract class DisputeManager> extends Sup return findDispute(message.getTradeId(), message.getTraderId()); } - private Optional findDispute(String tradeId, int traderId) { + protected Optional findDispute(String tradeId, int traderId) { T disputeList = getDisputeList(); if (disputeList == null) { log.warn("disputes is null"); diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeResult.java b/core/src/main/java/bisq/core/support/dispute/DisputeResult.java index d046a16c..20135bae 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeResult.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeResult.java @@ -80,9 +80,6 @@ public final class DisputeResult implements NetworkPayload { @Setter @Nullable private ChatMessage chatMessage; - @Setter - @Nullable - private byte[] arbitratorSignature; private long buyerPayoutAmount; private long sellerPayoutAmount; @Setter @@ -92,6 +89,14 @@ public final class DisputeResult implements NetworkPayload { @Setter private boolean isLoserPublisher; + // added for XMR integration + @Nullable + @Setter + String arbitratorSignedPayoutTxHex; + @Nullable + @Setter + String arbitratorUpdatedMultisigHex; + public DisputeResult(String tradeId, int traderId) { this.tradeId = tradeId; this.traderId = traderId; @@ -106,7 +111,8 @@ public final class DisputeResult implements NetworkPayload { boolean screenCast, String summaryNotes, @Nullable ChatMessage chatMessage, - @Nullable byte[] arbitratorSignature, + @Nullable String arbitratorPayoutTxSigned, + @Nullable String arbitratorUpdatedMultisigHex, long buyerPayoutAmount, long sellerPayoutAmount, @Nullable byte[] arbitratorPubKey, @@ -121,7 +127,8 @@ public final class DisputeResult implements NetworkPayload { this.screenCastProperty.set(screenCast); this.summaryNotesProperty.set(summaryNotes); this.chatMessage = chatMessage; - this.arbitratorSignature = arbitratorSignature; + this.arbitratorSignedPayoutTxHex = arbitratorPayoutTxSigned; + this.arbitratorUpdatedMultisigHex = arbitratorUpdatedMultisigHex; this.buyerPayoutAmount = buyerPayoutAmount; this.sellerPayoutAmount = sellerPayoutAmount; this.arbitratorPubKey = arbitratorPubKey; @@ -144,7 +151,8 @@ public final class DisputeResult implements NetworkPayload { proto.getScreenCast(), proto.getSummaryNotes(), proto.getChatMessage() == null ? null : ChatMessage.fromPayloadProto(proto.getChatMessage()), - proto.getArbitratorSignature().toByteArray(), + ProtoUtil.stringOrNullFromProto(proto.getArbitratorSignedPayoutTxHex()), + ProtoUtil.stringOrNullFromProto(proto.getArbitratorUpdatedMultisigHex()), proto.getBuyerPayoutAmount(), proto.getSellerPayoutAmount(), proto.getArbitratorPubKey().toByteArray(), @@ -167,7 +175,8 @@ public final class DisputeResult implements NetworkPayload { .setCloseDate(closeDate) .setIsLoserPublisher(isLoserPublisher); - Optional.ofNullable(arbitratorSignature).ifPresent(arbitratorSignature -> builder.setArbitratorSignature(ByteString.copyFrom(arbitratorSignature))); + Optional.ofNullable(arbitratorSignedPayoutTxHex).ifPresent(arbitratorPayoutTxSigned -> builder.setArbitratorSignedPayoutTxHex(arbitratorPayoutTxSigned)); + Optional.ofNullable(arbitratorUpdatedMultisigHex).ifPresent(arbitratorUpdatedMultisigHex -> builder.setArbitratorUpdatedMultisigHex(arbitratorUpdatedMultisigHex)); Optional.ofNullable(arbitratorPubKey).ifPresent(arbitratorPubKey -> builder.setArbitratorPubKey(ByteString.copyFrom(arbitratorPubKey))); Optional.ofNullable(winner).ifPresent(result -> builder.setWinner(protobuf.DisputeResult.Winner.valueOf(winner.name()))); Optional.ofNullable(chatMessage).ifPresent(chatMessage -> @@ -248,7 +257,8 @@ public final class DisputeResult implements NetworkPayload { ",\n screenCastProperty=" + screenCastProperty + ",\n summaryNotesProperty=" + summaryNotesProperty + ",\n chatMessage=" + chatMessage + - ",\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) + + ",\n arbitratorPayoutTxSigned=" + arbitratorSignedPayoutTxHex + + ",\n arbitratorUpdatedMultisigHex=" + arbitratorUpdatedMultisigHex + ",\n buyerPayoutAmount=" + buyerPayoutAmount + ",\n sellerPayoutAmount=" + sellerPayoutAmount + ",\n arbitratorPubKey=" + Utilities.bytesAsHexString(arbitratorPubKey) + diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java index a2c53a29..f467bb27 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java @@ -17,14 +17,9 @@ package bisq.core.support.dispute.arbitration; -import bisq.core.btc.exceptions.TransactionVerificationException; -import bisq.core.btc.exceptions.TxBroadcastException; -import bisq.core.btc.exceptions.WalletException; import bisq.core.btc.setup.WalletsSetup; -import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; -import bisq.core.btc.wallet.TxBroadcaster; -import bisq.core.btc.wallet.WalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoFacade; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; @@ -34,7 +29,10 @@ import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeResult; +import bisq.core.support.dispute.DisputeResult.Winner; import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage; +import bisq.core.support.dispute.messages.ArbitratorPayoutTxRequest; +import bisq.core.support.dispute.messages.ArbitratorPayoutTxResponse; import bisq.core.support.dispute.messages.DisputeResultMessage; import bisq.core.support.dispute.messages.OpenNewDisputeMessage; import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage; @@ -45,10 +43,12 @@ import bisq.core.trade.Tradable; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.util.ParsingUtils; import bisq.network.p2p.AckMessageSourceType; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; +import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.Timer; @@ -58,24 +58,31 @@ import bisq.common.config.Config; import bisq.common.crypto.KeyRing; import bisq.common.crypto.PubKeyRing; -import org.bitcoinj.core.AddressFormatException; -import org.bitcoinj.core.SignatureDecodeException; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.crypto.DeterministicKey; - import com.google.inject.Inject; import com.google.inject.Singleton; +import com.google.common.base.Preconditions; + +import java.math.BigInteger; + import java.util.Arrays; import java.util.Optional; import java.util.UUID; import lombok.extern.slf4j.Slf4j; -import javax.annotation.Nullable; - import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.common.MoneroError; +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroDestination; +import monero.wallet.model.MoneroMultisigSignResult; +import monero.wallet.model.MoneroTxConfig; +import monero.wallet.model.MoneroTxSet; +import monero.wallet.model.MoneroTxWallet; + @Slf4j @Singleton public final class ArbitrationManager extends DisputeManager { @@ -87,7 +94,7 @@ public final class ArbitrationManager extends DisputeManager tradeOptional = tradeManager.getTradeById(disputeResult.getTradeId()); String tradeId = disputeResult.getTradeId(); Optional disputeOptional = findDispute(disputeResult); @@ -209,8 +215,14 @@ public final class ArbitrationManager extends DisputeManager tradeOptional = tradeManager.getTradeById(tradeId); String errorMessage = null; - boolean success = false; + boolean success = true; + boolean requestUpdatedPayoutTx = false; + MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId()); + Contract contract = dispute.getContract(); try { // We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals // There would be different transactions if both sign and publish (signers: once buyer+arb, once seller+arb) // The tx publisher is the winner or in case both get 50% the buyer, as the buyer has more inventive to publish the tx as he receives // more BTC as he has deposited - Contract contract = dispute.getContract(); - boolean isBuyer = pubKeyRing.equals(contract.getBuyerPubKeyRing()); DisputeResult.Winner publisher = disputeResult.getWinner(); @@ -252,7 +264,7 @@ public final class ArbitrationManager extends DisputeManager disputeOptional = findOwnDispute(tradeId); + if (!disputeOptional.isPresent()) { + log.warn("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId); + return; + } + Dispute dispute = disputeOptional.get(); + Contract contract = dispute.getContract(); + Trade trade = tradeManager.getTradeById(tradeId).get(); + + // submit fully signed payout tx to the network + multisigWallet.submitMultisigTxHex(txSet.getMultisigTxHex()); + + // update state + trade.setPayoutTx(txSet.getTxs().get(0)); // TODO (woodser): is trade.payoutTx() mutually exclusive from dispute payout tx? + trade.setPayoutTxId(txSet.getTxs().get(0).getHash()); + trade.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX); + dispute.setDisputePayoutTxId(txSet.getTxs().get(0).getHash()); + sendPeerPublishedPayoutTxMessage(multisigWallet.getMultisigHex(), txSet.getMultisigTxHex(), dispute, contract); + updateTradeOrOpenOfferManager(tradeId); + } /////////////////////////////////////////////////////////////////////////////////////////// // Send messages /////////////////////////////////////////////////////////////////////////////////////////// // winner (or buyer in case of 50/50) sends tx to other peer - private void sendPeerPublishedPayoutTxMessage(Transaction transaction, Dispute dispute, Contract contract) { + private void sendPeerPublishedPayoutTxMessage(String updatedMultisigHex, String payoutTxHex, Dispute dispute, Contract contract) { PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing(); NodeAddress peersNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerNodeAddress() : contract.getBuyerNodeAddress(); log.trace("sendPeerPublishedPayoutTxMessage to peerAddress {}", peersNodeAddress); - PeerPublishedDisputePayoutTxMessage message = new PeerPublishedDisputePayoutTxMessage(transaction.bitcoinSerialize(), + PeerPublishedDisputePayoutTxMessage message = new PeerPublishedDisputePayoutTxMessage(updatedMultisigHex, + payoutTxHex, dispute.getTradeId(), p2PService.getAddress(), UUID.randomUUID().toString(), @@ -427,4 +546,190 @@ public final class ArbitrationManager extends DisputeManager openOfferManager.closeOpenOffer(openOffer.getOffer())); } } + + // dispute opener's peer signs payout tx by sending updated multisig hex to arbitrator who returns updated payout tx + private void sendArbitratorPayoutTxRequest(String updatedMultisigHex, Dispute dispute, Contract contract) { + ArbitratorPayoutTxRequest request = new ArbitratorPayoutTxRequest( + dispute, + p2PService.getAddress(), + UUID.randomUUID().toString(), + SupportType.ARBITRATION, + updatedMultisigHex); + log.info("Send {} to peer {}. tradeId={}, uid={}", + request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid()); + p2PService.sendEncryptedDirectMessage(contract.getArbitratorNodeAddress(), + dispute.getAgentPubKeyRing(), + request, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at peer {}. tradeId={}, uid={}", + request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid()); + } + + @Override + public void onFault(String errorMessage) { + log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", + request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid(), errorMessage); + } + } + ); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Disputed payout tx signing + /////////////////////////////////////////////////////////////////////////////////////////// + + // TODO (woodser): where to move this common logic? + public static MoneroTxWallet arbitratorCreatesDisputedPayoutTx(Contract contract, Dispute dispute, DisputeResult disputeResult, MoneroWallet multisigWallet) { + + //System.out.println("DisputeSummaryWindow.arbitratorSignsDisputedPayoutTx()"); + //System.out.println("=== DISPUTE ==="); + //System.out.println(dispute); + //System.out.println("=== CONTRACT ==="); + //System.out.println(contract); // TODO (woodser): contract should include deposit tx hashes (pre-created then hash shared then contract signed) + //System.out.println("=== DISPUTE RESULT ==="); + //System.out.println(disputeResult); + + // gather relevant trade info + String buyerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString(); + String sellerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString(); + Preconditions.checkNotNull(buyerPayoutAddress, "buyerPayoutAddress must not be null"); + Preconditions.checkNotNull(sellerPayoutAddress, "sellerPayoutAddress must not be null"); + BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value); + BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value); + + //System.out.println("buyerPayoutAddress: " + buyerPayoutAddress); + //System.out.println("buyerPayoutAmount: " + buyerPayoutAmount); + +// Offer offer = new Offer(contract.getOfferPayload()); +// System.out.println("Buyer deposit tx fee: " + + + //System.out.println("sellerPayoutAddress: " + sellerPayoutAddress); + //System.out.println("sellerPayoutAmount: " + sellerPayoutAmount); + //System.out.println("Multisig balance: " + multisigWallet.getBalance()); + //System.out.println("Multisig unlocked balance: " + multisigWallet.getUnlockedBalance()); + //System.out.println("Multisig txs"); + //System.out.println(multisigWallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true))); + + // create transaction to get fee estimate + if (multisigWallet.isMultisigImportNeeded()) { + log.info("Arbitrator's wallet needs updated multisig hex to create payout tx which means a trader must have already broadcast the payout tx"); + return null; + } + + // TODO (woodser): include arbitration fee + //System.out.println("Creating feeEstimateTx!"); + MoneroTxWallet feeEstimateTx = multisigWallet.createTx(new MoneroTxConfig() + .setAccountIndex(0) + .addDestination(new MoneroDestination(buyerPayoutAddress, buyerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5)))) // reduce payment amount to compute fee of similar tx + .addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5)))) // TODO (woodser): support addDestination(addr, amt) without new + .setRelay(false) + ); + + System.out.println("Created fee estimate tx!"); + System.out.println(feeEstimateTx); + //BigInteger estimatedFee = feeEstimateTx.getFee(); + + // attempt to create payout tx by increasing estimated fee until successful + MoneroTxWallet payoutTx = null; + int numAttempts = 0; + while (payoutTx == null && numAttempts < 50) { + BigInteger feeEstimate = feeEstimateTx.getFee().add(feeEstimateTx.getFee().multiply(BigInteger.valueOf(numAttempts)).divide(BigInteger.valueOf(10))); // add 1/10 of fee until tx is successful + try { + numAttempts++; + payoutTx = multisigWallet.createTx(new MoneroTxConfig() + .setAccountIndex(0) + .addDestination(new MoneroDestination(buyerPayoutAddress, buyerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // split fee subtracted from each payout amount + .addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // TODO (woodser): support addDestination(addr, amt) without new + .setRelay(false)); + } catch (MoneroError e) { + e.printStackTrace(); + System.out.println("FAILED TO CREATE PAYOUT TX, ITERATING..."); + } + } + + if (payoutTx == null) throw new RuntimeException("Failed to generate dispute payout tx"); + System.out.println("DISPUTE PAYOUT TX GENERATED ON ATTEMPT " + numAttempts); + System.out.println(payoutTx); + return payoutTx; + } + + private MoneroTxSet traderSignsDisputePayoutTx(String tradeId, String payoutTxHex) { + + // gather trade info + MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId); + Optional disputeOptional = findOwnDispute(tradeId); + if (!disputeOptional.isPresent()) throw new RuntimeException("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId); + Dispute dispute = disputeOptional.get(); + Contract contract = dispute.getContract(); + DisputeResult disputeResult = dispute.getDisputeResultProperty().get(); + +// Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); +// BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getMakerDepositTxId() : trade.getTakerDepositTxId()).getIncomingAmount(); // TODO (woodser): use contract instead of trade to get deposit tx ids when contract has deposit tx ids +// BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getTakerDepositTxId() : trade.getMakerDepositTxId()).getIncomingAmount(); +// BigInteger tradeAmount = BigInteger.valueOf(contract.getTradeAmount().value).multiply(ParsingUtils.XMR_SATOSHI_MULTIPLIER); + BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value); + BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value); + System.out.println("Buyer payout amount (with multiplier): " + buyerPayoutAmount); + System.out.println("Seller payout amount (with multiplier): " + sellerPayoutAmount); + + // parse arbitrator-signed payout tx + MoneroTxSet parsedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex)); + if (parsedTxSet.getTxs() == null || parsedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad arbitrator-signed payout tx"); // TODO (woodser): nack + MoneroTxWallet arbitratorSignedPayoutTx = parsedTxSet.getTxs().get(0); + System.out.println("Parsed arbitrator-signed payout tx:\n" + arbitratorSignedPayoutTx); + + // verify payout tx has exactly 2 destinations + if (arbitratorSignedPayoutTx.getOutgoingTransfer() == null || arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations() == null || arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new RuntimeException("Buyer-signed payout tx does not have exactly two destinations"); + + // get buyer and seller destinations (order not preserved) + boolean buyerFirst = arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().get(0).getAddress().equals(contract.getBuyerPayoutAddressString()); + MoneroDestination buyerPayoutDestination = arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 0 : 1); + MoneroDestination sellerPayoutDestination = arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 1 : 0); + + // verify payout addresses + if (!buyerPayoutDestination.getAddress().equals(contract.getBuyerPayoutAddressString())) throw new RuntimeException("Buyer payout address does not match contract"); + if (!sellerPayoutDestination.getAddress().equals(contract.getSellerPayoutAddressString())) throw new RuntimeException("Seller payout address does not match contract"); + + // verify change address is multisig's primary address + if (!arbitratorSignedPayoutTx.getChangeAddress().equals(multisigWallet.getPrimaryAddress())) throw new RuntimeException("Change address is not multisig wallet's primary address"); + + // verify sum of outputs = destination amounts + change amount + if (!arbitratorSignedPayoutTx.getOutputSum().equals(buyerPayoutDestination.getAmount().add(sellerPayoutDestination.getAmount()).add(arbitratorSignedPayoutTx.getChangeAmount()))) throw new RuntimeException("Sum of outputs != destination amounts + change amount"); + + // verify buyer destination amount is payout amount - 1/2 tx costs + BigInteger txCost = arbitratorSignedPayoutTx.getFee().add(arbitratorSignedPayoutTx.getChangeAmount()); + BigInteger expectedBuyerPayout = buyerPayoutAmount.subtract(txCost.divide(BigInteger.valueOf(2))); + + System.out.println("Dispute buyer payout amount: " + buyerPayoutAmount); + System.out.println("Tx cost: " + txCost); + System.out.println("Buyer destination payout amount: " + buyerPayoutDestination.getAmount()); + + + // payout amount is dispute payout amount - 1/2 tx cost - deposit tx fee + + // TODO (woodser): VERIFY PAYOUT TX AMOUNTS WHICH CONSIDERS FEE IF LONG TRADE, EXACT AMOUNT IF SHORT TRADE + + + // if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not payout amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout); + + // verify seller destination amount is payout amount - 1/2 tx costs + // BigInteger expectedSellerPayout = sellerPayoutAmount.subtract(txCost.divide(BigInteger.valueOf(2))); + // if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new RuntimeException("Seller destination amount is not payout amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout); + + // TODO (woodser): verify fee is reasonable (e.g. within 2x of fee estimate tx) + + // update multisig wallet from arbitrator + System.out.println("Updating multisig hex from arbitrator: " + disputeResult.getArbitratorUpdatedMultisigHex()); + multisigWallet.importMultisigHex(Arrays.asList(disputeResult.getArbitratorUpdatedMultisigHex())); + + // sign arbitrator-signed payout tx + MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex); + + if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing arbitrator-signed payout tx"); + String signedMultisigTxHex = result.getSignedMultisigTxHex(); + parsedTxSet.setMultisigTxHex(signedMultisigTxHex); + return parsedTxSet; + } } diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java b/core/src/main/java/bisq/core/support/dispute/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java index 17d90090..fb940a12 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java @@ -22,9 +22,6 @@ import bisq.core.support.SupportType; import bisq.network.p2p.NodeAddress; import bisq.common.app.Version; -import bisq.common.util.Utilities; - -import com.google.protobuf.ByteString; import lombok.EqualsAndHashCode; import lombok.Value; @@ -32,16 +29,19 @@ import lombok.Value; @Value @EqualsAndHashCode(callSuper = true) public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessage { - private final byte[] transaction; + private final String updatedMultisigHex; + private final String payoutTxHex; private final String tradeId; private final NodeAddress senderNodeAddress; - public PeerPublishedDisputePayoutTxMessage(byte[] transaction, + public PeerPublishedDisputePayoutTxMessage(String updatedMultisigHex, + String payoutTxHex, String tradeId, NodeAddress senderNodeAddress, String uid, SupportType supportType) { - this(transaction, + this(updatedMultisigHex, + payoutTxHex, tradeId, senderNodeAddress, uid, @@ -54,14 +54,16 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - private PeerPublishedDisputePayoutTxMessage(byte[] transaction, + private PeerPublishedDisputePayoutTxMessage(String updatedMultisigHex, + String payoutTxHex, String tradeId, NodeAddress senderNodeAddress, String uid, int messageVersion, SupportType supportType) { super(messageVersion, uid, supportType); - this.transaction = transaction; + this.updatedMultisigHex = updatedMultisigHex; + this.payoutTxHex = payoutTxHex; this.tradeId = tradeId; this.senderNodeAddress = senderNodeAddress; } @@ -70,7 +72,8 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { return getNetworkEnvelopeBuilder() .setPeerPublishedDisputePayoutTxMessage(protobuf.PeerPublishedDisputePayoutTxMessage.newBuilder() - .setTransaction(ByteString.copyFrom(transaction)) + .setUpdatedMultisigHex(updatedMultisigHex) + .setPayoutTxHex(payoutTxHex) .setTradeId(tradeId) .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setUid(uid) @@ -80,7 +83,8 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag public static PeerPublishedDisputePayoutTxMessage fromProto(protobuf.PeerPublishedDisputePayoutTxMessage proto, int messageVersion) { - return new PeerPublishedDisputePayoutTxMessage(proto.getTransaction().toByteArray(), + return new PeerPublishedDisputePayoutTxMessage(proto.getUpdatedMultisigHex(), + proto.getPayoutTxHex(), proto.getTradeId(), NodeAddress.fromProto(proto.getSenderNodeAddress()), proto.getUid(), @@ -96,7 +100,8 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag @Override public String toString() { return "PeerPublishedDisputePayoutTxMessage{" + - "\n transaction=" + Utilities.bytesAsHexString(transaction) + + "\n updatedMultisigHex=" + updatedMultisigHex + + "\n payoutTxHex=" + payoutTxHex + ",\n tradeId='" + tradeId + '\'' + ",\n senderNodeAddress=" + senderNodeAddress + ",\n PeerPublishedDisputePayoutTxMessage.uid='" + uid + '\'' + diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java index 776e2191..f31680f9 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java @@ -18,8 +18,8 @@ package bisq.core.support.dispute.mediation; import bisq.core.btc.setup.WalletsSetup; -import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoFacade; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; @@ -77,7 +77,7 @@ public final class MediationManager extends DisputeManager @Inject public MediationManager(P2PService p2PService, TradeWalletService tradeWalletService, - BtcWalletService walletService, + XmrWalletService walletService, WalletsSetup walletsSetup, TradeManager tradeManager, ClosedTradableManager closedTradableManager, @@ -226,7 +226,7 @@ public final class MediationManager extends DisputeManager @Nullable @Override public NodeAddress getAgentNodeAddress(Dispute dispute) { - return dispute.getContract().getMediatorNodeAddress(); + return dispute.getContract().getArbitratorNodeAddress(); // TODO (woodser): mediator becomes and replaces current arbitrator? } public void onAcceptMediationResult(Trade trade, diff --git a/core/src/main/java/bisq/core/support/dispute/messages/ArbitratorPayoutTxRequest.java b/core/src/main/java/bisq/core/support/dispute/messages/ArbitratorPayoutTxRequest.java new file mode 100644 index 00000000..9d2e7eed --- /dev/null +++ b/core/src/main/java/bisq/core/support/dispute/messages/ArbitratorPayoutTxRequest.java @@ -0,0 +1,107 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.support.dispute.messages; + +import bisq.core.proto.CoreProtoResolver; +import bisq.core.support.SupportType; +import bisq.core.support.dispute.Dispute; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.app.Version; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class ArbitratorPayoutTxRequest extends DisputeMessage { + private final Dispute dispute; + private final NodeAddress senderNodeAddress; + private final String updatedMultisigHex; + + public ArbitratorPayoutTxRequest(Dispute dispute, + NodeAddress senderNodeAddress, + String uid, + SupportType supportType, + String updatedMultisigHex) { + this(dispute, + senderNodeAddress, + uid, + Version.getP2PMessageVersion(), + supportType, + updatedMultisigHex); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private ArbitratorPayoutTxRequest(Dispute dispute, + NodeAddress senderNodeAddress, + String uid, + int messageVersion, + SupportType supportType, + String updatedMultisigHex) { + super(messageVersion, uid, supportType); + this.dispute = dispute; + this.senderNodeAddress = senderNodeAddress; + this.updatedMultisigHex = updatedMultisigHex; + } + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + return getNetworkEnvelopeBuilder() + .setArbitratorPayoutTxRequest(protobuf.ArbitratorPayoutTxRequest.newBuilder() + .setUid(uid) + .setDispute(dispute.toProtoMessage()) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setType(SupportType.toProtoMessage(supportType)) + .setUpdatedMultisigHex(updatedMultisigHex)) + .build(); + } + + public static ArbitratorPayoutTxRequest fromProto(protobuf.ArbitratorPayoutTxRequest proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new ArbitratorPayoutTxRequest(Dispute.fromProto(proto.getDispute(), coreProtoResolver), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + proto.getUid(), + messageVersion, + SupportType.fromProto(proto.getType()), + proto.getUpdatedMultisigHex()); + } + + @Override + public String getTradeId() { + return dispute.getTradeId(); + } + + @Override + public String toString() { + return "ArbitratorPayoutTxRequest{" + + "\n dispute=" + dispute + + ",\n senderNodeAddress=" + senderNodeAddress + + ",\n ArbitratorPayoutTxRequest.uid='" + uid + '\'' + + ",\n messageVersion=" + messageVersion + + ",\n supportType=" + supportType + + ",\n updatedMultisigHex=" + updatedMultisigHex + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/support/dispute/messages/ArbitratorPayoutTxResponse.java b/core/src/main/java/bisq/core/support/dispute/messages/ArbitratorPayoutTxResponse.java new file mode 100644 index 00000000..88ecab26 --- /dev/null +++ b/core/src/main/java/bisq/core/support/dispute/messages/ArbitratorPayoutTxResponse.java @@ -0,0 +1,101 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.support.dispute.messages; + +import bisq.core.proto.CoreProtoResolver; +import bisq.core.support.SupportType; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.app.Version; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class ArbitratorPayoutTxResponse extends DisputeMessage { + private final String tradeId; + private final NodeAddress senderNodeAddress; + private final String arbitratorSignedPayoutTxHex; + + public ArbitratorPayoutTxResponse(String tradeId, + NodeAddress senderNodeAddress, + String uid, + SupportType supportType, + String arbitratorSignedPayoutTxHex) { + this(tradeId, + senderNodeAddress, + uid, + Version.getP2PMessageVersion(), + supportType, + arbitratorSignedPayoutTxHex); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + private ArbitratorPayoutTxResponse(String tradeId, + NodeAddress senderNodeAddress, + String uid, + int messageVersion, + SupportType supportType, + String arbitratorSignedPayoutTxHex) { + super(messageVersion, uid, supportType); + this.tradeId = tradeId; + this.senderNodeAddress = senderNodeAddress; + this.arbitratorSignedPayoutTxHex = arbitratorSignedPayoutTxHex; + } + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + return getNetworkEnvelopeBuilder() + .setArbitratorPayoutTxResponse(protobuf.ArbitratorPayoutTxResponse.newBuilder() + .setUid(uid) + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setType(SupportType.toProtoMessage(supportType)) + .setArbitratorSignedPayoutTxHex(arbitratorSignedPayoutTxHex)) + .build(); + } + + public static ArbitratorPayoutTxResponse fromProto(protobuf.ArbitratorPayoutTxResponse proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new ArbitratorPayoutTxResponse(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + proto.getUid(), + messageVersion, + SupportType.fromProto(proto.getType()), + proto.getArbitratorSignedPayoutTxHex()); + } + + @Override + public String toString() { + return "ArbitratorPayoutTxResponse{" + + "\n tradeId=" + tradeId + + ",\n senderNodeAddress=" + senderNodeAddress + + ",\n ArbitratorPayoutTxResponse.uid='" + uid + '\'' + + ",\n messageVersion=" + messageVersion + + ",\n supportType=" + supportType + + ",\n updatedMultisigHex=" + arbitratorSignedPayoutTxHex + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/support/dispute/messages/OpenNewDisputeMessage.java b/core/src/main/java/bisq/core/support/dispute/messages/OpenNewDisputeMessage.java index f60cda80..ac47e297 100644 --- a/core/src/main/java/bisq/core/support/dispute/messages/OpenNewDisputeMessage.java +++ b/core/src/main/java/bisq/core/support/dispute/messages/OpenNewDisputeMessage.java @@ -33,16 +33,19 @@ import lombok.Value; public final class OpenNewDisputeMessage extends DisputeMessage { private final Dispute dispute; private final NodeAddress senderNodeAddress; + private final String updatedMultisigHex; public OpenNewDisputeMessage(Dispute dispute, NodeAddress senderNodeAddress, String uid, - SupportType supportType) { + SupportType supportType, + String updatedMultisigHex) { this(dispute, senderNodeAddress, uid, Version.getP2PMessageVersion(), - supportType); + supportType, + updatedMultisigHex); } @@ -54,10 +57,12 @@ public final class OpenNewDisputeMessage extends DisputeMessage { NodeAddress senderNodeAddress, String uid, int messageVersion, - SupportType supportType) { + SupportType supportType, + String updatedMultisigHex) { super(messageVersion, uid, supportType); this.dispute = dispute; this.senderNodeAddress = senderNodeAddress; + this.updatedMultisigHex = updatedMultisigHex; } @Override @@ -67,7 +72,8 @@ public final class OpenNewDisputeMessage extends DisputeMessage { .setUid(uid) .setDispute(dispute.toProtoMessage()) .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) - .setType(SupportType.toProtoMessage(supportType))) + .setType(SupportType.toProtoMessage(supportType)) + .setUpdatedMultisigHex(updatedMultisigHex)) .build(); } @@ -78,7 +84,8 @@ public final class OpenNewDisputeMessage extends DisputeMessage { NodeAddress.fromProto(proto.getSenderNodeAddress()), proto.getUid(), messageVersion, - SupportType.fromProto(proto.getType())); + SupportType.fromProto(proto.getType()), + proto.getUpdatedMultisigHex()); } @Override @@ -94,6 +101,7 @@ public final class OpenNewDisputeMessage extends DisputeMessage { ",\n OpenNewDisputeMessage.uid='" + uid + '\'' + ",\n messageVersion=" + messageVersion + ",\n supportType=" + supportType + + ",\n updatedMultisigHex=" + updatedMultisigHex + "\n} " + super.toString(); } } diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java index deb1537b..527c53ab 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java @@ -18,8 +18,8 @@ package bisq.core.support.dispute.refund; import bisq.core.btc.setup.WalletsSetup; -import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoFacade; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; @@ -71,12 +71,12 @@ public final class RefundManager extends DisputeManager { @Inject public RefundManager(P2PService p2PService, TradeWalletService tradeWalletService, - BtcWalletService walletService, + XmrWalletService walletService, WalletsSetup walletsSetup, TradeManager tradeManager, ClosedTradableManager closedTradableManager, OpenOfferManager openOfferManager, - DaoFacade daoFacade, + DaoFacade daoFacade, // TODO (woodser): remove daoFacade, priceFeedService? KeyRing keyRing, RefundDisputeListService refundDisputeListService, Config config, @@ -232,6 +232,7 @@ public final class RefundManager extends DisputeManager { @Nullable @Override public NodeAddress getAgentNodeAddress(Dispute dispute) { - return dispute.getContract().getRefundAgentNodeAddress(); + throw new RuntimeException("Refund manager not used in XMR adapation"); + //return dispute.getContract().getRefundAgentNodeAddress(); } } diff --git a/core/src/main/java/bisq/core/trade/ArbitratorTrade.java b/core/src/main/java/bisq/core/trade/ArbitratorTrade.java new file mode 100644 index 00000000..13a9d6ab --- /dev/null +++ b/core/src/main/java/bisq/core/trade/ArbitratorTrade.java @@ -0,0 +1,84 @@ +package bisq.core.trade; + +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.offer.Offer; +import bisq.core.proto.CoreProtoResolver; +import bisq.core.trade.protocol.ProcessModel; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.proto.ProtoUtil; + +import org.bitcoinj.core.Coin; + +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +/** + * Trade in the context of an arbitrator. + */ +@Slf4j +public class ArbitratorTrade extends Trade { + + public ArbitratorTrade(Offer offer, + Coin tradeAmount, + Coin txFee, + Coin takerFee, + long tradePrice, + NodeAddress makerNodeAddress, + NodeAddress takerNodeAddress, + NodeAddress arbitratorNodeAddress, + XmrWalletService xmrWalletService, + ProcessModel processModel, + String uid) { + super(offer, tradeAmount, txFee, takerFee, tradePrice, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress, xmrWalletService, processModel, uid); + } + + @Override + public Coin getPayoutAmount() { + throw new RuntimeException("Arbitrator does not have a payout amount"); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.Tradable toProtoMessage() { + return protobuf.Tradable.newBuilder() + .setArbitratorTrade(protobuf.ArbitratorTrade.newBuilder() + .setTrade((protobuf.Trade) super.toProtoMessage())) + .build(); + } + + public static Tradable fromProto(protobuf.ArbitratorTrade arbitratorTradeProto, + XmrWalletService xmrWalletService, + CoreProtoResolver coreProtoResolver) { + protobuf.Trade proto = arbitratorTradeProto.getTrade(); + ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver); + String uid = ProtoUtil.stringOrNullFromProto(proto.getUid()); + if (uid == null) { + uid = UUID.randomUUID().toString(); + } + return fromProto(new ArbitratorTrade( + Offer.fromProto(proto.getOffer()), + Coin.valueOf(proto.getTradeAmountAsLong()), + Coin.valueOf(proto.getTxFeeAsLong()), + Coin.valueOf(proto.getTakerFeeAsLong()), + proto.getTradePrice(), + proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, + proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, + proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, + xmrWalletService, + processModel, + uid), + proto, + coreProtoResolver); + } + + @Override + public boolean confirmPermitted() { + throw new RuntimeException("ArbitratorTrade.confirmPermitted() not implemented"); // TODO (woodser): implement + } +} diff --git a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java index 3a0005f8..38b05400 100644 --- a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java +++ b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java @@ -17,7 +17,7 @@ package bisq.core.trade; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.Offer; import bisq.core.proto.CoreProtoResolver; import bisq.core.trade.protocol.ProcessModel; @@ -44,21 +44,19 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade { public BuyerAsMakerTrade(Offer offer, Coin txFee, Coin takeOfferFee, - boolean isCurrencyForTakerFeeBtc, + @Nullable NodeAddress takerNodeAddress, + @Nullable NodeAddress makerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, - @Nullable NodeAddress refundAgentNodeAddress, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, ProcessModel processModel, String uid) { super(offer, txFee, takeOfferFee, - isCurrencyForTakerFeeBtc, + takerNodeAddress, + makerNodeAddress, arbitratorNodeAddress, - mediatorNodeAddress, - refundAgentNodeAddress, - btcWalletService, + xmrWalletService, processModel, uid); } @@ -76,7 +74,7 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade { } public static Tradable fromProto(protobuf.BuyerAsMakerTrade buyerAsMakerTradeProto, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, CoreProtoResolver coreProtoResolver) { protobuf.Trade proto = buyerAsMakerTradeProto.getTrade(); ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver); @@ -88,17 +86,19 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade { Offer.fromProto(proto.getOffer()), Coin.valueOf(proto.getTxFeeAsLong()), Coin.valueOf(proto.getTakerFeeAsLong()), - proto.getIsCurrencyForTakerFeeBtc(), + proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, + proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, - proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null, - proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null, - btcWalletService, + xmrWalletService, processModel, uid); trade.setTradeAmountAsLong(proto.getTradeAmountAsLong()); trade.setTradePrice(proto.getTradePrice()); - trade.setTradingPeerNodeAddress(proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null); + + trade.setMakerNodeAddress(proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null); + trade.setTakerNodeAddress(proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null); + trade.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null); return fromProto(trade, proto, diff --git a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java index 2ccf0fb3..6007437e 100644 --- a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java +++ b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java @@ -17,7 +17,7 @@ package bisq.core.trade; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.Offer; import bisq.core.proto.CoreProtoResolver; import bisq.core.trade.protocol.ProcessModel; @@ -45,26 +45,22 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade { Coin tradeAmount, Coin txFee, Coin takerFee, - boolean isCurrencyForTakerFeeBtc, long tradePrice, - NodeAddress tradingPeerNodeAddress, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, - @Nullable NodeAddress refundAgentNodeAddress, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, ProcessModel processModel, String uid) { super(offer, tradeAmount, txFee, takerFee, - isCurrencyForTakerFeeBtc, tradePrice, - tradingPeerNodeAddress, + makerNodeAddress, + takerNodeAddress, arbitratorNodeAddress, - mediatorNodeAddress, - refundAgentNodeAddress, - btcWalletService, + xmrWalletService, processModel, uid); } @@ -83,7 +79,7 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade { } public static Tradable fromProto(protobuf.BuyerAsTakerTrade buyerAsTakerTradeProto, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, CoreProtoResolver coreProtoResolver) { protobuf.Trade proto = buyerAsTakerTradeProto.getTrade(); ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver); @@ -96,13 +92,11 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade { Coin.valueOf(proto.getTradeAmountAsLong()), Coin.valueOf(proto.getTxFeeAsLong()), Coin.valueOf(proto.getTakerFeeAsLong()), - proto.getIsCurrencyForTakerFeeBtc(), proto.getTradePrice(), - proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null, + proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, + proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, - proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null, - proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null, - btcWalletService, + xmrWalletService, processModel, uid), proto, diff --git a/core/src/main/java/bisq/core/trade/BuyerTrade.java b/core/src/main/java/bisq/core/trade/BuyerTrade.java index 82f38cf9..4b6313e3 100644 --- a/core/src/main/java/bisq/core/trade/BuyerTrade.java +++ b/core/src/main/java/bisq/core/trade/BuyerTrade.java @@ -17,7 +17,7 @@ package bisq.core.trade; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.Offer; import bisq.core.trade.protocol.ProcessModel; @@ -37,26 +37,22 @@ public abstract class BuyerTrade extends Trade { Coin tradeAmount, Coin txFee, Coin takerFee, - boolean isCurrencyForTakerFeeBtc, long tradePrice, - NodeAddress tradingPeerNodeAddress, + @Nullable NodeAddress takerNodeAddress, + @Nullable NodeAddress makerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, - @Nullable NodeAddress refundAgentNodeAddress, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, ProcessModel processModel, String uid) { super(offer, tradeAmount, txFee, takerFee, - isCurrencyForTakerFeeBtc, tradePrice, - tradingPeerNodeAddress, + takerNodeAddress, + makerNodeAddress, arbitratorNodeAddress, - mediatorNodeAddress, - refundAgentNodeAddress, - btcWalletService, + xmrWalletService, processModel, uid); } @@ -64,21 +60,19 @@ public abstract class BuyerTrade extends Trade { BuyerTrade(Offer offer, Coin txFee, Coin takerFee, - boolean isCurrencyForTakerFeeBtc, + @Nullable NodeAddress takerNodeAddress, + @Nullable NodeAddress makerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, - @Nullable NodeAddress refundAgentNodeAddress, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, ProcessModel processModel, String uid) { super(offer, txFee, takerFee, - isCurrencyForTakerFeeBtc, + takerNodeAddress, + makerNodeAddress, arbitratorNodeAddress, - mediatorNodeAddress, - refundAgentNodeAddress, - btcWalletService, + xmrWalletService, processModel, uid); } diff --git a/core/src/main/java/bisq/core/trade/Contract.java b/core/src/main/java/bisq/core/trade/Contract.java index 81602d22..7b145ff6 100644 --- a/core/src/main/java/bisq/core/trade/Contract.java +++ b/core/src/main/java/bisq/core/trade/Contract.java @@ -33,8 +33,6 @@ import bisq.common.proto.network.NetworkPayload; import bisq.common.util.JsonExclude; import bisq.common.util.Utilities; -import com.google.protobuf.ByteString; - import org.bitcoinj.core.Coin; import org.apache.commons.lang3.StringUtils; @@ -52,10 +50,9 @@ public final class Contract implements NetworkPayload { private final OfferPayload offerPayload; private final long tradeAmount; private final long tradePrice; - private final String takerFeeTxID; private final NodeAddress buyerNodeAddress; private final NodeAddress sellerNodeAddress; - private final NodeAddress mediatorNodeAddress; + private final NodeAddress arbitratorNodeAddress; private final boolean isBuyerMakerAndSellerTaker; private final String makerAccountId; private final String takerAccountId; @@ -67,22 +64,16 @@ public final class Contract implements NetworkPayload { private final PubKeyRing takerPubKeyRing; private final String makerPayoutAddressString; private final String takerPayoutAddressString; - @JsonExclude - private final byte[] makerMultiSigPubKey; - @JsonExclude - private final byte[] takerMultiSigPubKey; // Added in v1.2.0 private long lockTime; - private final NodeAddress refundAgentNodeAddress; public Contract(OfferPayload offerPayload, long tradeAmount, long tradePrice, - String takerFeeTxID, NodeAddress buyerNodeAddress, NodeAddress sellerNodeAddress, - NodeAddress mediatorNodeAddress, + NodeAddress arbitratorNodeAddress, boolean isBuyerMakerAndSellerTaker, String makerAccountId, String takerAccountId, @@ -92,17 +83,13 @@ public final class Contract implements NetworkPayload { PubKeyRing takerPubKeyRing, String makerPayoutAddressString, String takerPayoutAddressString, - byte[] makerMultiSigPubKey, - byte[] takerMultiSigPubKey, - long lockTime, - NodeAddress refundAgentNodeAddress) { + long lockTime) { this.offerPayload = offerPayload; this.tradeAmount = tradeAmount; this.tradePrice = tradePrice; - this.takerFeeTxID = takerFeeTxID; this.buyerNodeAddress = buyerNodeAddress; this.sellerNodeAddress = sellerNodeAddress; - this.mediatorNodeAddress = mediatorNodeAddress; + this.arbitratorNodeAddress = arbitratorNodeAddress; this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker; this.makerAccountId = makerAccountId; this.takerAccountId = takerAccountId; @@ -112,10 +99,7 @@ public final class Contract implements NetworkPayload { this.takerPubKeyRing = takerPubKeyRing; this.makerPayoutAddressString = makerPayoutAddressString; this.takerPayoutAddressString = takerPayoutAddressString; - this.makerMultiSigPubKey = makerMultiSigPubKey; - this.takerMultiSigPubKey = takerMultiSigPubKey; this.lockTime = lockTime; - this.refundAgentNodeAddress = refundAgentNodeAddress; String makerPaymentMethodId = makerPaymentAccountPayload.getPaymentMethodId(); String takerPaymentMethodId = takerPaymentAccountPayload.getPaymentMethodId(); @@ -137,10 +121,9 @@ public final class Contract implements NetworkPayload { return new Contract(OfferPayload.fromProto(proto.getOfferPayload()), proto.getTradeAmount(), proto.getTradePrice(), - proto.getTakerFeeTxId(), NodeAddress.fromProto(proto.getBuyerNodeAddress()), NodeAddress.fromProto(proto.getSellerNodeAddress()), - NodeAddress.fromProto(proto.getMediatorNodeAddress()), + NodeAddress.fromProto(proto.getArbitratorNodeAddress()), proto.getIsBuyerMakerAndSellerTaker(), proto.getMakerAccountId(), proto.getTakerAccountId(), @@ -150,10 +133,7 @@ public final class Contract implements NetworkPayload { PubKeyRing.fromProto(proto.getTakerPubKeyRing()), proto.getMakerPayoutAddressString(), proto.getTakerPayoutAddressString(), - proto.getMakerMultiSigPubKey().toByteArray(), - proto.getTakerMultiSigPubKey().toByteArray(), - proto.getLockTime(), - NodeAddress.fromProto(proto.getRefundAgentNodeAddress())); + proto.getLockTime()); } @Override @@ -162,10 +142,9 @@ public final class Contract implements NetworkPayload { .setOfferPayload(offerPayload.toProtoMessage().getOfferPayload()) .setTradeAmount(tradeAmount) .setTradePrice(tradePrice) - .setTakerFeeTxId(takerFeeTxID) .setBuyerNodeAddress(buyerNodeAddress.toProtoMessage()) .setSellerNodeAddress(sellerNodeAddress.toProtoMessage()) - .setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage()) + .setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()) .setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker) .setMakerAccountId(makerAccountId) .setTakerAccountId(takerAccountId) @@ -175,10 +154,7 @@ public final class Contract implements NetworkPayload { .setTakerPubKeyRing(takerPubKeyRing.toProtoMessage()) .setMakerPayoutAddressString(makerPayoutAddressString) .setTakerPayoutAddressString(takerPayoutAddressString) - .setMakerMultiSigPubKey(ByteString.copyFrom(makerMultiSigPubKey)) - .setTakerMultiSigPubKey(ByteString.copyFrom(takerMultiSigPubKey)) .setLockTime(lockTime) - .setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage()) .build(); } @@ -203,14 +179,6 @@ public final class Contract implements NetworkPayload { return isBuyerMakerAndSellerTaker ? takerPubKeyRing : makerPubKeyRing; } - public byte[] getBuyerMultiSigPubKey() { - return isBuyerMakerAndSellerTaker ? makerMultiSigPubKey : takerMultiSigPubKey; - } - - public byte[] getSellerMultiSigPubKey() { - return isBuyerMakerAndSellerTaker ? takerMultiSigPubKey : makerMultiSigPubKey; - } - public PaymentAccountPayload getBuyerPaymentAccountPayload() { return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayload : takerPaymentAccountPayload; } @@ -296,11 +264,9 @@ public final class Contract implements NetworkPayload { "\n offerPayload=" + offerPayload + ",\n tradeAmount=" + tradeAmount + ",\n tradePrice=" + tradePrice + - ",\n takerFeeTxID='" + takerFeeTxID + '\'' + ",\n buyerNodeAddress=" + buyerNodeAddress + ",\n sellerNodeAddress=" + sellerNodeAddress + - ",\n mediatorNodeAddress=" + mediatorNodeAddress + - ",\n refundAgentNodeAddress=" + refundAgentNodeAddress + + ",\n arbitratorNodeAddress=" + arbitratorNodeAddress + ",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker + ",\n makerAccountId='" + makerAccountId + '\'' + ",\n takerAccountId='" + takerAccountId + '\'' + @@ -310,10 +276,6 @@ public final class Contract implements NetworkPayload { ",\n takerPubKeyRing=" + takerPubKeyRing + ",\n makerPayoutAddressString='" + makerPayoutAddressString + '\'' + ",\n takerPayoutAddressString='" + takerPayoutAddressString + '\'' + - ",\n makerMultiSigPubKey=" + Utilities.bytesAsHexString(makerMultiSigPubKey) + - ",\n takerMultiSigPubKey=" + Utilities.bytesAsHexString(takerMultiSigPubKey) + - ",\n buyerMultiSigPubKey=" + Utilities.bytesAsHexString(getBuyerMultiSigPubKey()) + - ",\n sellerMultiSigPubKey=" + Utilities.bytesAsHexString(getSellerMultiSigPubKey()) + ",\n lockTime=" + lockTime + "\n}"; } diff --git a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java index ccbf66a0..e0e83c24 100644 --- a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java +++ b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java @@ -17,7 +17,7 @@ package bisq.core.trade; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.Offer; import bisq.core.proto.CoreProtoResolver; import bisq.core.trade.protocol.ProcessModel; @@ -44,21 +44,19 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade public SellerAsMakerTrade(Offer offer, Coin txFee, Coin takerFee, - boolean isCurrencyForTakerFeeBtc, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, - @Nullable NodeAddress refundAgentNodeAddress, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, ProcessModel processModel, String uid) { super(offer, txFee, takerFee, - isCurrencyForTakerFeeBtc, + makerNodeAddress, + takerNodeAddress, arbitratorNodeAddress, - mediatorNodeAddress, - refundAgentNodeAddress, - btcWalletService, + xmrWalletService, processModel, uid); } @@ -77,7 +75,7 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade } public static Tradable fromProto(protobuf.SellerAsMakerTrade sellerAsMakerTradeProto, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, CoreProtoResolver coreProtoResolver) { protobuf.Trade proto = sellerAsMakerTradeProto.getTrade(); ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver); @@ -89,17 +87,15 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade Offer.fromProto(proto.getOffer()), Coin.valueOf(proto.getTxFeeAsLong()), Coin.valueOf(proto.getTakerFeeAsLong()), - proto.getIsCurrencyForTakerFeeBtc(), + proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, + proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, - proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null, - proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null, - btcWalletService, + xmrWalletService, processModel, uid); trade.setTradeAmountAsLong(proto.getTradeAmountAsLong()); trade.setTradePrice(proto.getTradePrice()); - trade.setTradingPeerNodeAddress(proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null); return fromProto(trade, proto, diff --git a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java index 11fb6c28..85cf04e2 100644 --- a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java +++ b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java @@ -17,7 +17,7 @@ package bisq.core.trade; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.Offer; import bisq.core.proto.CoreProtoResolver; import bisq.core.trade.protocol.ProcessModel; @@ -45,26 +45,22 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade Coin tradeAmount, Coin txFee, Coin takerFee, - boolean isCurrencyForTakerFeeBtc, long tradePrice, - NodeAddress tradingPeerNodeAddress, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, - @Nullable NodeAddress refundAgentNodeAddress, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, ProcessModel processModel, String uid) { super(offer, tradeAmount, txFee, takerFee, - isCurrencyForTakerFeeBtc, tradePrice, - tradingPeerNodeAddress, + makerNodeAddress, + takerNodeAddress, arbitratorNodeAddress, - mediatorNodeAddress, - refundAgentNodeAddress, - btcWalletService, + xmrWalletService, processModel, uid); } @@ -83,7 +79,7 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade } public static Tradable fromProto(protobuf.SellerAsTakerTrade sellerAsTakerTradeProto, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, CoreProtoResolver coreProtoResolver) { protobuf.Trade proto = sellerAsTakerTradeProto.getTrade(); ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver); @@ -96,13 +92,11 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade Coin.valueOf(proto.getTradeAmountAsLong()), Coin.valueOf(proto.getTxFeeAsLong()), Coin.valueOf(proto.getTakerFeeAsLong()), - proto.getIsCurrencyForTakerFeeBtc(), proto.getTradePrice(), - proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null, + proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, + proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, - proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null, - proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null, - btcWalletService, + xmrWalletService, processModel, uid), proto, diff --git a/core/src/main/java/bisq/core/trade/SellerTrade.java b/core/src/main/java/bisq/core/trade/SellerTrade.java index a87c18dd..e54c4b95 100644 --- a/core/src/main/java/bisq/core/trade/SellerTrade.java +++ b/core/src/main/java/bisq/core/trade/SellerTrade.java @@ -17,7 +17,7 @@ package bisq.core.trade; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.locale.CurrencyUtil; import bisq.core.offer.Offer; import bisq.core.trade.protocol.ProcessModel; @@ -38,26 +38,22 @@ public abstract class SellerTrade extends Trade { Coin tradeAmount, Coin txFee, Coin takerFee, - boolean isCurrencyForTakerFeeBtc, long tradePrice, - NodeAddress tradingPeerNodeAddress, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, - @Nullable NodeAddress refundAgentNodeAddress, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, ProcessModel processModel, String uid) { super(offer, tradeAmount, txFee, takerFee, - isCurrencyForTakerFeeBtc, tradePrice, - tradingPeerNodeAddress, + makerNodeAddress, + takerNodeAddress, arbitratorNodeAddress, - mediatorNodeAddress, - refundAgentNodeAddress, - btcWalletService, + xmrWalletService, processModel, uid); } @@ -65,21 +61,19 @@ public abstract class SellerTrade extends Trade { SellerTrade(Offer offer, Coin txFee, Coin takeOfferFee, - boolean isCurrencyForTakerFeeBtc, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, - @Nullable NodeAddress refundAgentNodeAddress, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, ProcessModel processModel, String uid) { super(offer, txFee, takeOfferFee, - isCurrencyForTakerFeeBtc, + makerNodeAddress, + takerNodeAddress, arbitratorNodeAddress, - mediatorNodeAddress, - refundAgentNodeAddress, - btcWalletService, + xmrWalletService, processModel, uid); } diff --git a/core/src/main/java/bisq/core/trade/TradableList.java b/core/src/main/java/bisq/core/trade/TradableList.java index c3a66870..e39d2951 100644 --- a/core/src/main/java/bisq/core/trade/TradableList.java +++ b/core/src/main/java/bisq/core/trade/TradableList.java @@ -17,7 +17,7 @@ package bisq.core.trade; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.OpenOffer; import bisq.core.proto.CoreProtoResolver; @@ -62,20 +62,20 @@ public final class TradableList extends PersistableListAsObs public static TradableList fromProto(protobuf.TradableList proto, CoreProtoResolver coreProtoResolver, - BtcWalletService btcWalletService) { + XmrWalletService xmrWalletService) { List list = proto.getTradableList().stream() .map(tradable -> { switch (tradable.getMessageCase()) { case OPEN_OFFER: return OpenOffer.fromProto(tradable.getOpenOffer()); case BUYER_AS_MAKER_TRADE: - return BuyerAsMakerTrade.fromProto(tradable.getBuyerAsMakerTrade(), btcWalletService, coreProtoResolver); + return BuyerAsMakerTrade.fromProto(tradable.getBuyerAsMakerTrade(), xmrWalletService, coreProtoResolver); case BUYER_AS_TAKER_TRADE: - return BuyerAsTakerTrade.fromProto(tradable.getBuyerAsTakerTrade(), btcWalletService, coreProtoResolver); + return BuyerAsTakerTrade.fromProto(tradable.getBuyerAsTakerTrade(), xmrWalletService, coreProtoResolver); case SELLER_AS_MAKER_TRADE: - return SellerAsMakerTrade.fromProto(tradable.getSellerAsMakerTrade(), btcWalletService, coreProtoResolver); + return SellerAsMakerTrade.fromProto(tradable.getSellerAsMakerTrade(), xmrWalletService, coreProtoResolver); case SELLER_AS_TAKER_TRADE: - return SellerAsTakerTrade.fromProto(tradable.getSellerAsTakerTrade(), btcWalletService, coreProtoResolver); + return SellerAsTakerTrade.fromProto(tradable.getSellerAsTakerTrade(), xmrWalletService, coreProtoResolver); default: log.error("Unknown messageCase. tradable.getMessageCase() = " + tradable.getMessageCase()); throw new ProtobufferRuntimeException("Unknown messageCase. tradable.getMessageCase() = " + diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index de5c1949..8e5e9bf9 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -17,7 +17,7 @@ package bisq.core.trade; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.locale.CurrencyUtil; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; @@ -28,8 +28,10 @@ import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.mediation.MediationResultState; import bisq.core.support.dispute.refund.RefundResultState; import bisq.core.support.messages.ChatMessage; +import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.ProcessModel; import bisq.core.trade.protocol.ProcessModelServiceProvider; +import bisq.core.trade.protocol.TradeMessageListener; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.util.VolumeUtil; @@ -45,12 +47,6 @@ import com.google.protobuf.Message; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionConfidence; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; @@ -64,7 +60,9 @@ import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -78,6 +76,14 @@ import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.common.MoneroError; +import monero.daemon.MoneroDaemon; +import monero.daemon.MoneroDaemonRpc; +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroTxWallet; + /** * Holds all data which are relevant to the trade, but not those which are only needed in the trade process as shared data between tasks. Those data are * stored in the task model. @@ -109,26 +115,27 @@ public abstract class Trade implements Tradable, Model { // taker perspective TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED), // Not used anymore + // Alternatively the taker could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG + TAKER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED), // #################### Phase DEPOSIT_PUBLISHED // We changes order in trade protocol of publishing deposit tx and sending it to the peer. // Now we send it first to the peer and only if that succeeds we publish it to avoid likelihood of // failed trades. We do not want to change the order of the enum though so we keep it here as it was originally. - SELLER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED), - + TAKER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED), // DEPOSIT_TX_PUBLISHED_MSG - // seller perspective - SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED), - SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED), - SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED), - SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED), + // taker perspective + TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED), + TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED), + TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED), + TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED), - // buyer perspective - BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED), + // maker perspective + MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED), - // Alternatively the buyer could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG - BUYER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED), + // Alternatively the maker could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG + MAKER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED), // #################### Phase DEPOSIT_CONFIRMED @@ -291,8 +298,6 @@ public abstract class Trade implements Tradable, Model { @Getter private final Offer offer; @Getter - private final boolean isCurrencyForTakerFeeBtc; - @Getter private final long txFeeAsLong; @Getter private final long takerFeeAsLong; @@ -312,10 +317,6 @@ public abstract class Trade implements Tradable, Model { @Nullable @Getter @Setter - private String depositTxId; - @Nullable - @Getter - @Setter private String payoutTxId; @Getter @Setter @@ -324,8 +325,6 @@ public abstract class Trade implements Tradable, Model { private long tradePrice; @Nullable @Getter - private NodeAddress tradingPeerNodeAddress; - @Getter private State state = State.PREPARATION; @Getter private DisputeState disputeState = DisputeState.NO_DISPUTE; @@ -390,7 +389,7 @@ public abstract class Trade implements Tradable, Model { @Getter transient final private Coin takerFee; @Getter - transient final private BtcWalletService btcWalletService; + transient final private XmrWalletService xmrWalletService; transient final private ObjectProperty stateProperty = new SimpleObjectProperty<>(state); transient final private ObjectProperty statePhaseProperty = new SimpleObjectProperty<>(state.phase); @@ -399,8 +398,6 @@ public abstract class Trade implements Tradable, Model { transient final private StringProperty errorMessageProperty = new SimpleStringProperty(); // Mutable - @Nullable - transient private Transaction depositTx; @Getter transient private boolean isInitialized; @@ -409,7 +406,7 @@ public abstract class Trade implements Tradable, Model { transient private Transaction delayedPayoutTx; @Nullable - transient private Transaction payoutTx; + transient private MoneroTxWallet payoutTx; @Nullable transient private Coin tradeAmount; @@ -462,6 +459,33 @@ public abstract class Trade implements Tradable, Model { transient final private IntegerProperty assetTxProofResultUpdateProperty = new SimpleIntegerProperty(); + // Added in XMR integration + private transient List tradeMessageListeners; // notified on fully validated trade messages + @Getter + @Setter + private NodeAddress makerNodeAddress; + @Getter + @Setter + private NodeAddress takerNodeAddress; + @Getter + @Setter + private PubKeyRing makerPubKeyRing; + @Getter + @Setter + private PubKeyRing takerPubKeyRing; + @Nullable + transient private MoneroTxWallet makerDepositTx; + @Nullable + transient private MoneroTxWallet takerDepositTx; + @Nullable + @Getter + @Setter + private String makerDepositTxId; + @Nullable + @Getter + @Setter + private String takerDepositTxId; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialization /////////////////////////////////////////////////////////////////////////////////////////// @@ -470,62 +494,86 @@ public abstract class Trade implements Tradable, Model { protected Trade(Offer offer, Coin txFee, Coin takerFee, - boolean isCurrencyForTakerFeeBtc, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, - @Nullable NodeAddress refundAgentNodeAddress, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, ProcessModel processModel, String uid) { this.offer = offer; this.txFee = txFee; this.takerFee = takerFee; - this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc; + this.makerNodeAddress = makerNodeAddress; + this.takerNodeAddress = takerNodeAddress; this.arbitratorNodeAddress = arbitratorNodeAddress; - this.mediatorNodeAddress = mediatorNodeAddress; - this.refundAgentNodeAddress = refundAgentNodeAddress; - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.processModel = processModel; this.uid = uid; txFeeAsLong = txFee.value; takerFeeAsLong = takerFee.value; takeOfferDate = new Date().getTime(); + tradeMessageListeners = new ArrayList(); } + // TODO (woodser): this constructor has mediator and refund agent (to be removed), otherwise use common // taker @SuppressWarnings("NullableProblems") protected Trade(Offer offer, Coin tradeAmount, Coin txFee, Coin takerFee, - boolean isCurrencyForTakerFeeBtc, long tradePrice, - NodeAddress tradingPeerNodeAddress, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, ProcessModel processModel, String uid) { this(offer, txFee, takerFee, - isCurrencyForTakerFeeBtc, + makerNodeAddress, + takerNodeAddress, arbitratorNodeAddress, - mediatorNodeAddress, - refundAgentNodeAddress, - btcWalletService, + xmrWalletService, processModel, uid); this.tradePrice = tradePrice; - this.tradingPeerNodeAddress = tradingPeerNodeAddress; - setTradeAmount(tradeAmount); } + // arbitrator + @SuppressWarnings("NullableProblems") + protected Trade(Offer offer, + Coin tradeAmount, + Coin txFee, + Coin takerFee, + long tradePrice, + NodeAddress makerNodeAddress, + NodeAddress takerNodeAddress, + NodeAddress arbitratorNodeAddress, + XmrWalletService xmrWalletService, + ProcessModel processModel, + String uid) { + + this(offer, + txFee, + takerFee, + makerNodeAddress, + takerNodeAddress, + arbitratorNodeAddress, + xmrWalletService, + processModel, + uid); + + this.tradePrice = tradePrice; + setTradeAmount(tradeAmount); + } /////////////////////////////////////////////////////////////////////////////////////////// // PROTO BUFFER @@ -535,7 +583,6 @@ public abstract class Trade implements Tradable, Model { public Message toProtoMessage() { protobuf.Trade.Builder builder = protobuf.Trade.newBuilder() .setOffer(offer.toProtoMessage()) - .setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc) .setTxFeeAsLong(txFeeAsLong) .setTakerFeeAsLong(takerFeeAsLong) .setTakeOfferDate(takeOfferDate) @@ -552,9 +599,9 @@ public abstract class Trade implements Tradable, Model { .setUid(uid); Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId); - Optional.ofNullable(depositTxId).ifPresent(builder::setDepositTxId); + Optional.ofNullable(takerDepositTxId).ifPresent(builder::setTakerDepositTxId); + Optional.ofNullable(makerDepositTxId).ifPresent(builder::setMakerDepositTxId); Optional.ofNullable(payoutTxId).ifPresent(builder::setPayoutTxId); - Optional.ofNullable(tradingPeerNodeAddress).ifPresent(e -> builder.setTradingPeerNodeAddress(tradingPeerNodeAddress.toProtoMessage())); Optional.ofNullable(contract).ifPresent(e -> builder.setContract(contract.toProtoMessage())); Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson); Optional.ofNullable(contractHash).ifPresent(e -> builder.setContractHash(ByteString.copyFrom(contractHash))); @@ -575,7 +622,10 @@ public abstract class Trade implements Tradable, Model { Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes))); Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData)); Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.name())); - + Optional.ofNullable(makerNodeAddress).ifPresent(e -> builder.setMakerNodeAddress(makerNodeAddress.toProtoMessage())); + Optional.ofNullable(makerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage())); + Optional.ofNullable(takerNodeAddress).ifPresent(e -> builder.setTakerNodeAddress(takerNodeAddress.toProtoMessage())); + Optional.ofNullable(takerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(takerPubKeyRing.toProtoMessage())); return builder.build(); } @@ -585,7 +635,8 @@ public abstract class Trade implements Tradable, Model { trade.setDisputeState(DisputeState.fromProto(proto.getDisputeState())); trade.setTradePeriodState(TradePeriodState.fromProto(proto.getTradePeriodState())); trade.setTakerFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerFeeTxId())); - trade.setDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getDepositTxId())); + trade.setMakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getMakerDepositTxId())); + trade.setTakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerDepositTxId())); trade.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId())); trade.setContract(proto.hasContract() ? Contract.fromProto(proto.getContract(), coreProtoResolver) : null); trade.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson())); @@ -614,6 +665,10 @@ public abstract class Trade implements Tradable, Model { persistedAssetTxProofResult = null; } trade.setAssetTxProofResult(persistedAssetTxProofResult); + trade.setMakerNodeAddress(NodeAddress.fromProto(proto.getMakerNodeAddress())); + trade.setMakerPubKeyRing(proto.hasMakerPubKeyRing() ? PubKeyRing.fromProto(proto.getMakerPubKeyRing()) : null); + trade.setTakerNodeAddress(NodeAddress.fromProto(proto.getTakerNodeAddress())); + trade.setTakerPubKeyRing(proto.hasTakerPubKeyRing() ? PubKeyRing.fromProto(proto.getTakerPubKeyRing()) : null); trade.chatMessages.addAll(proto.getChatMessageList().stream() .map(ChatMessage::fromPayloadProto) @@ -647,24 +702,72 @@ public abstract class Trade implements Tradable, Model { // API /////////////////////////////////////////////////////////////////////////////////////////// - // The deserialized tx has not actual confidence data, so we need to get the fresh one from the wallet. - void updateDepositTxFromWallet() { - if (getDepositTx() != null) - applyDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getTxId())); + public void setTradingPeerNodeAddress(NodeAddress peerAddress) { + if (this instanceof MakerTrade) takerNodeAddress = peerAddress; + else if (this instanceof TakerTrade) makerNodeAddress = peerAddress; + else throw new RuntimeException("Must be maker or taker to set peer address"); } - public void applyDepositTx(Transaction tx) { - this.depositTx = tx; - depositTxId = depositTx.getTxId().toString(); - setupConfidenceListener(); + public NodeAddress getTradingPeerNodeAddress() { + if (this instanceof MakerTrade) return takerNodeAddress; + else if (this instanceof TakerTrade) return makerNodeAddress; + else if (this instanceof ArbitratorTrade) return null; + else throw new RuntimeException("Unknown trade type: " + this.getClass().getName()); + } + + public void setTradingPeerPubKeyRing(PubKeyRing peerPubKeyRing) { + if (this instanceof MakerTrade) takerPubKeyRing = peerPubKeyRing; + else if (this instanceof TakerTrade) makerPubKeyRing = peerPubKeyRing; + else throw new RuntimeException("Must be maker or taker to set peer address"); + } + + public PubKeyRing getTradingPeerPubKeyRing() { + if (this instanceof MakerTrade) return takerPubKeyRing; + else if (this instanceof TakerTrade) return makerPubKeyRing; + else if (this instanceof ArbitratorTrade) return null; + else throw new RuntimeException("Unknown trade type: " + this.getClass().getName()); + } + + // The deserialized tx has not actual confidence data, so we need to get the fresh one from the wallet. + void updateDepositTxFromWallet() { + if (getMakerDepositTx() != null && getTakerDepositTx() != null) { + System.out.println(processModel.getProvider().getXmrWalletService()); + MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(getId()); + applyDepositTxs(multisigWallet.getTx(getMakerDepositTxId()), multisigWallet.getTx(getTakerDepositTxId())); + } + } + + public void applyDepositTxs(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) { + this.makerDepositTx = makerDepositTx; + this.takerDepositTx = takerDepositTx; + makerDepositTxId = makerDepositTx.getHash(); + takerDepositTxId = takerDepositTx.getHash(); + //setupConfirmationListener(); // TODO (woodser): listening disabled here, using SetupDepositTxsListener in buyer and seller + if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) { + setConfirmedState(); // TODO (woodser): bisq "confirmed" = xmr unlocked after 10 confirmations + } } @Nullable - public Transaction getDepositTx() { - if (depositTx == null) { - depositTx = depositTxId != null ? btcWalletService.getTransaction(depositTxId) : null; - } - return depositTx; + public MoneroTxWallet getTakerDepositTx() { + try { + if (takerDepositTx == null) takerDepositTx = takerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(takerDepositTxId) : null; + return takerDepositTx; + } catch (MoneroError e) { + log.error("Wallet is missing taker deposit tx " + takerDepositTxId); + return null; + } + } + + @Nullable + public MoneroTxWallet getMakerDepositTx() { + try { + if (makerDepositTx == null) makerDepositTx = makerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(makerDepositTxId) : null; + return makerDepositTx; + } catch (MoneroError e) { + log.error("Wallet is missing maker deposit tx " + makerDepositTxId); + return null; + } } public void applyDelayedPayoutTx(Transaction delayedPayoutTx) { @@ -676,31 +779,31 @@ public abstract class Trade implements Tradable, Model { this.delayedPayoutTxBytes = delayedPayoutTxBytes; } - @Nullable - public Transaction getDelayedPayoutTx() { - return getDelayedPayoutTx(processModel.getBtcWalletService()); - } - - // If called from a not initialized trade (or a closed or failed trade) - // we need to pass the btcWalletService - @Nullable - public Transaction getDelayedPayoutTx(BtcWalletService btcWalletService) { - if (delayedPayoutTx == null) { - if (btcWalletService == null) { - log.warn("btcWalletService is null. You might call that method before the tradeManager has " + - "initialized all trades"); - return null; - } - - if (delayedPayoutTxBytes == null) { - log.warn("delayedPayoutTxBytes are null"); - return null; - } - - delayedPayoutTx = btcWalletService.getTxFromSerializedTx(delayedPayoutTxBytes); - } - return delayedPayoutTx; - } +// @Nullable +// public Transaction getDelayedPayoutTx() { +// return getDelayedPayoutTx(processModel.getBtcWalletService()); +// } +// +// // If called from a not initialized trade (or a closed or failed trade) +// // we need to pass the xmrWalletService +// @Nullable +// public Transaction getDelayedPayoutTx(XmrWalletService xmrWalletService) { +// if (delayedPayoutTx == null) { +// if (xmrWalletService == null) { +// log.warn("xmrWalletService is null. You might call that method before the tradeManager has " + +// "initialized all trades"); +// return null; +// } +// +// if (delayedPayoutTxBytes == null) { +// log.warn("delayedPayoutTxBytes are null"); +// return null; +// } +// +// delayedPayoutTx = xmrWalletService.getTxFromSerializedTx(delayedPayoutTxBytes); +// } +// return delayedPayoutTx; +// } public void addAndPersistChatMessage(ChatMessage chatMessage) { if (!chatMessages.contains(chatMessage)) { @@ -737,6 +840,26 @@ public abstract class Trade implements Tradable, Model { public abstract boolean confirmPermitted(); + + /////////////////////////////////////////////////////////////////////////////////////////// + // Listeners + /////////////////////////////////////////////////////////////////////////////////////////// + + public void addTradeMessageListener(TradeMessageListener listener) { + tradeMessageListeners.add(listener); + } + + public void removeTradeMessageListener(TradeMessageListener listener) { + if (!tradeMessageListeners.remove(listener)) throw new RuntimeException("TradeMessageListener is not registered"); + } + + // notified from TradeProtocol of verified messages + public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) { + for (TradeMessageListener listener : new ArrayList(tradeMessageListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception + listener.onVerifiedTradeMessage(message, sender); + } + } + /////////////////////////////////////////////////////////////////////////////////////////// // Setters /////////////////////////////////////////////////////////////////////////////////////////// @@ -786,13 +909,6 @@ public abstract class Trade implements Tradable, Model { tradePeriodStateProperty.set(tradePeriodState); } - public void setTradingPeerNodeAddress(NodeAddress tradingPeerNodeAddress) { - if (tradingPeerNodeAddress == null) - log.error("tradingPeerAddress=null"); - else - this.tradingPeerNodeAddress = tradingPeerNodeAddress; - } - public void setTradeAmount(Coin tradeAmount) { this.tradeAmount = tradeAmount; tradeAmountAsLong = tradeAmount.value; @@ -800,9 +916,9 @@ public abstract class Trade implements Tradable, Model { getTradeVolumeProperty().set(getTradeVolume()); } - public void setPayoutTx(Transaction payoutTx) { + public void setPayoutTx(MoneroTxWallet payoutTx) { this.payoutTx = payoutTx; - payoutTxId = payoutTx.getTxId().toString(); + payoutTxId = payoutTx.getHash(); } public void setErrorMessage(String errorMessage) { @@ -863,14 +979,19 @@ public abstract class Trade implements Tradable, Model { private long getTradeStartTime() { long now = System.currentTimeMillis(); long startTime; - Transaction depositTx = getDepositTx(); - if (depositTx != null && getTakeOfferDate() != null) { - if (depositTx.getConfidence().getDepthInBlocks() > 0) { + final MoneroTxWallet takerDepositTx = getTakerDepositTx(); + final MoneroTxWallet makerDepositTx = getMakerDepositTx(); + if (makerDepositTx != null && takerDepositTx != null && getTakeOfferDate() != null) { + if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) { final long tradeTime = getTakeOfferDate().getTime(); - // Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime() - long blockTime = depositTx.getIncludedInBestChainAt() != null - ? depositTx.getIncludedInBestChainAt().getTime() - : depositTx.getUpdateTime().getTime(); + long maxHeight = Math.max(makerDepositTx.getHeight(), takerDepositTx.getHeight()); + MoneroDaemon daemonRpc = new MoneroDaemonRpc("http://localhost:38081", "superuser", "abctesting123"); // TODO (woodser): move to common config + long blockTime = daemonRpc.getBlockByHeight(maxHeight).getTimestamp(); + +// if (depositTx.getConfidence().getDepthInBlocks() > 0) { +// final long tradeTime = getTakeOfferDate().getTime(); +// // Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime() +// long blockTime = depositTx.getIncludedInBestChainAt() != null ? depositTx.getIncludedInBestChainAt().getTime() : depositTx.getUpdateTime().getTime(); // If block date is in future (Date in Bitcoin blocks can be off by +/- 2 hours) we use our current date. // If block date is earlier than our trade date we use our trade date. if (blockTime > now) @@ -881,8 +1002,7 @@ public abstract class Trade implements Tradable, Model { log.debug("We set the start for the trade period to {}. Trade started at: {}. Block got mined at: {}", new Date(startTime), new Date(tradeTime), new Date(blockTime)); } else { - log.debug("depositTx not confirmed yet. We don't start counting remaining trade period yet. txId={}", - depositTx.getTxId().toString()); + log.debug("depositTx not confirmed yet. We don't start counting remaining trade period yet. makerTxId={}, takerTxId={}", makerDepositTx.getHash(), takerDepositTx.getHash()); startTime = now; } } else { @@ -1020,9 +1140,9 @@ public abstract class Trade implements Tradable, Model { } @Nullable - public Transaction getPayoutTx() { + public MoneroTxWallet getPayoutTx() { if (payoutTx == null) - payoutTx = payoutTxId != null ? btcWalletService.getTransaction(payoutTxId) : null; + payoutTx = payoutTxId != null ? xmrWalletService.getWallet().getTx(payoutTxId) : null; return payoutTx; } @@ -1038,8 +1158,8 @@ public abstract class Trade implements Tradable, Model { public boolean isTxChainInvalid() { return offer.getOfferFeePaymentTxId() == null || getTakerFeeTxId() == null || - getDepositTxId() == null || - getDepositTx() == null || + getMakerDepositTxId() == null || + getTakerDepositTxId() == null || getDelayedPayoutTxBytes() == null; } @@ -1076,31 +1196,31 @@ public abstract class Trade implements Tradable, Model { return tradeVolumeProperty; } - private void setupConfidenceListener() { - if (getDepositTx() != null) { - TransactionConfidence transactionConfidence = getDepositTx().getConfidence(); - if (transactionConfidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) { - setConfirmedState(); - } else { - ListenableFuture future = transactionConfidence.getDepthFuture(1); - Futures.addCallback(future, new FutureCallback<>() { - @Override - public void onSuccess(TransactionConfidence result) { - setConfirmedState(); - } - - @Override - public void onFailure(@NotNull Throwable t) { - t.printStackTrace(); - log.error(t.getMessage()); - throw new RuntimeException(t); - } - }, MoreExecutors.directExecutor()); - } - } else { - log.error("depositTx == null. That must not happen."); - } - } +// private void setupConfidenceListener() { +// if (getDepositTx() != null) { +// TransactionConfidence transactionConfidence = getDepositTx().getConfidence(); +// if (transactionConfidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) { +// setConfirmedState(); +// } else { +// ListenableFuture future = transactionConfidence.getDepthFuture(1); +// Futures.addCallback(future, new FutureCallback<>() { +// @Override +// public void onSuccess(TransactionConfidence result) { +// setConfirmedState(); +// } +// +// @Override +// public void onFailure(@NotNull Throwable t) { +// t.printStackTrace(); +// log.error(t.getMessage()); +// throw new RuntimeException(t); +// } +// }, MoreExecutors.directExecutor()); +// } +// } else { +// log.error("depositTx == null. That must not happen."); +// } +// } private void setConfirmedState() { // we only apply the state if we are not already further in the process @@ -1108,7 +1228,7 @@ public abstract class Trade implements Tradable, Model { // As setState is called here from the trade itself we cannot trigger a requestPersistence call. // But as we get setupConfidenceListener called at startup anyway there is no issue if it would not be // persisted in case the shutdown routine did not persist the trade. - setState(State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN); + setState(State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN); // TODO (woodser): for xmr this means deposit txs have unlocked after 10 confirmations } } @@ -1116,17 +1236,15 @@ public abstract class Trade implements Tradable, Model { public String toString() { return "Trade{" + "\n offer=" + offer + - ",\n isCurrencyForTakerFeeBtc=" + isCurrencyForTakerFeeBtc + ",\n txFeeAsLong=" + txFeeAsLong + ",\n takerFeeAsLong=" + takerFeeAsLong + ",\n takeOfferDate=" + takeOfferDate + ",\n processModel=" + processModel + ",\n takerFeeTxId='" + takerFeeTxId + '\'' + - ",\n depositTxId='" + depositTxId + '\'' + + ",\n takerDepositTxId='" + takerDepositTxId + '\'' + ",\n payoutTxId='" + payoutTxId + '\'' + ",\n tradeAmountAsLong=" + tradeAmountAsLong + ",\n tradePrice=" + tradePrice + - ",\n tradingPeerNodeAddress=" + tradingPeerNodeAddress + ",\n state=" + state + ",\n disputeState=" + disputeState + ",\n tradePeriodState=" + tradePeriodState + @@ -1135,9 +1253,7 @@ public abstract class Trade implements Tradable, Model { ",\n contractHash=" + Utilities.bytesAsHexString(contractHash) + ",\n takerContractSignature='" + takerContractSignature + '\'' + ",\n makerContractSignature='" + makerContractSignature + '\'' + - ",\n arbitratorNodeAddress=" + arbitratorNodeAddress + ",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) + - ",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing + ",\n mediatorNodeAddress=" + mediatorNodeAddress + ",\n mediatorPubKeyRing=" + mediatorPubKeyRing + ",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' + @@ -1148,13 +1264,13 @@ public abstract class Trade implements Tradable, Model { ",\n chatMessages=" + chatMessages + ",\n txFee=" + txFee + ",\n takerFee=" + takerFee + - ",\n btcWalletService=" + btcWalletService + + ",\n xmrWalletService=" + xmrWalletService + ",\n stateProperty=" + stateProperty + ",\n statePhaseProperty=" + statePhaseProperty + ",\n disputeStateProperty=" + disputeStateProperty + ",\n tradePeriodStateProperty=" + tradePeriodStateProperty + ",\n errorMessageProperty=" + errorMessageProperty + - ",\n depositTx=" + depositTx + + ",\n depositTx=" + takerDepositTx + ",\n delayedPayoutTx=" + delayedPayoutTx + ",\n payoutTx=" + payoutTx + ",\n tradeAmount=" + tradeAmount + @@ -1168,6 +1284,12 @@ public abstract class Trade implements Tradable, Model { ",\n refundAgentPubKeyRing=" + refundAgentPubKeyRing + ",\n refundResultState=" + refundResultState + ",\n refundResultStateProperty=" + refundResultStateProperty + + ",\n makerNodeAddress=" + makerNodeAddress + + ",\n makerPubKeyRing=" + makerPubKeyRing + + ",\n takerNodeAddress=" + takerNodeAddress + + ",\n takerPubKeyRing=" + takerPubKeyRing + + ",\n arbitratorNodeAddress=" + arbitratorNodeAddress + + ",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing + "\n}"; } } diff --git a/core/src/main/java/bisq/core/trade/TradeDataValidation.java b/core/src/main/java/bisq/core/trade/TradeDataValidation.java index 013dcdb4..829c5e2c 100644 --- a/core/src/main/java/bisq/core/trade/TradeDataValidation.java +++ b/core/src/main/java/bisq/core/trade/TradeDataValidation.java @@ -356,24 +356,25 @@ public class TradeDataValidation { } public static void validateDepositInputs(Trade trade) throws InvalidTxException { - // assumption: deposit tx always has 2 inputs, the maker and taker - if (trade == null || trade.getDepositTx() == null || trade.getDepositTx().getInputs().size() != 2) { - throw new InvalidTxException("Deposit transaction is null or has unexpected input count"); - } - Transaction depositTx = trade.getDepositTx(); - String txIdInput0 = depositTx.getInput(0).getOutpoint().getHash().toString(); - String txIdInput1 = depositTx.getInput(1).getOutpoint().getHash().toString(); - String contractMakerTxId = trade.getContract().getOfferPayload().getOfferFeePaymentTxId(); - String contractTakerTxId = trade.getContract().getTakerFeeTxID(); - boolean makerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput0) && contractTakerTxId.equalsIgnoreCase(txIdInput1); - boolean takerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput1) && contractTakerTxId.equalsIgnoreCase(txIdInput0); - if (!makerFirstMatch && !takerFirstMatch) { - String errMsg = "Maker/Taker txId in contract does not match deposit tx input"; - log.error(errMsg + - "\nContract Maker tx=" + contractMakerTxId + " Contract Taker tx=" + contractTakerTxId + - "\nDeposit Input0=" + txIdInput0 + " Deposit Input1=" + txIdInput1); - throw new InvalidTxException(errMsg); - } + throw new RuntimeException("TradeDataValidation.validateDepositInputs() needs updated for XMR"); +// // assumption: deposit tx always has 2 inputs, the maker and taker +// if (trade == null || trade.getDepositTx() == null || trade.getDepositTx().getInputs().size() != 2) { +// throw new InvalidTxException("Deposit transaction is null or has unexpected input count"); +// } +// Transaction depositTx = trade.getDepositTx(); +// String txIdInput0 = depositTx.getInput(0).getOutpoint().getHash().toString(); +// String txIdInput1 = depositTx.getInput(1).getOutpoint().getHash().toString(); +// String contractMakerTxId = trade.getContract().getOfferPayload().getOfferFeePaymentTxId(); +// String contractTakerTxId = trade.getContract().getTakerFeeTxID(); +// boolean makerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput0) && contractTakerTxId.equalsIgnoreCase(txIdInput1); +// boolean takerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput1) && contractTakerTxId.equalsIgnoreCase(txIdInput0); +// if (!makerFirstMatch && !takerFirstMatch) { +// String errMsg = "Maker/Taker txId in contract does not match deposit tx input"; +// log.error(errMsg + +// "\nContract Maker tx=" + contractMakerTxId + " Contract Taker tx=" + contractTakerTxId + +// "\nDeposit Input0=" + txIdInput0 + " Deposit Input1=" + txIdInput1); +// throw new InvalidTxException(errMsg); +// } } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 6707fde8..e3ac16e5 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -18,11 +18,12 @@ package bisq.core.trade; import bisq.core.btc.exceptions.AddressEntryException; -import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.wallet.BsqWalletService; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.locale.Res; import bisq.core.offer.Offer; +import bisq.core.offer.OfferBookService; import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; @@ -33,7 +34,14 @@ import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.handlers.TradeResultHandler; +import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.messages.InitMultisigMessage; +import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InputsForDepositTxRequest; +import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; +import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; +import bisq.core.trade.messages.UpdateMultisigRequest; +import bisq.core.trade.protocol.ArbitratorProtocol; import bisq.core.trade.protocol.MakerProtocol; import bisq.core.trade.protocol.ProcessModel; import bisq.core.trade.protocol.ProcessModelServiceProvider; @@ -65,8 +73,6 @@ import bisq.common.proto.persistable.PersistedDataHost; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; import org.bitcoinj.core.InsufficientMoneyException; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionConfidence; import javax.inject.Inject; import javax.inject.Named; @@ -107,14 +113,19 @@ import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.wallet.model.MoneroTxWallet; + public class TradeManager implements PersistedDataHost, DecryptedDirectMessageListener { private static final Logger log = LoggerFactory.getLogger(TradeManager.class); private final User user; @Getter private final KeyRing keyRing; - private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final BsqWalletService bsqWalletService; + private final OfferBookService offerBookService; private final OpenOfferManager openOfferManager; private final ClosedTradableManager closedTradableManager; private final FailedTradesManager failedTradesManager; @@ -151,8 +162,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi @Inject public TradeManager(User user, KeyRing keyRing, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, BsqWalletService bsqWalletService, + OfferBookService offerBookService, OpenOfferManager openOfferManager, ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager, @@ -170,8 +182,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi @Named(Config.ALLOW_FAULTY_DELAYED_TXS) boolean allowFaultyDelayedTxs) { this.user = user; this.keyRing = keyRing; - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.bsqWalletService = bsqWalletService; + this.offerBookService = offerBookService; this.openOfferManager = openOfferManager; this.closedTradableManager = closedTradableManager; this.failedTradesManager = failedTradesManager; @@ -222,75 +235,22 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi public void onDirectMessage(DecryptedMessageWithPubKey message, NodeAddress peer) { NetworkEnvelope networkEnvelope = message.getNetworkEnvelope(); if (networkEnvelope instanceof InputsForDepositTxRequest) { - handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); + //handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); // ignore bisq requests + } else if (networkEnvelope instanceof InitTradeRequest) { + handleInitTradeRequest((InitTradeRequest) networkEnvelope, peer); + } else if (networkEnvelope instanceof InitMultisigMessage) { + handleMultisigMessage((InitMultisigMessage) networkEnvelope, peer); + } else if (networkEnvelope instanceof MakerReadyToFundMultisigRequest) { + handleMakerReadyToFundMultisigRequest((MakerReadyToFundMultisigRequest) networkEnvelope, peer); + } else if (networkEnvelope instanceof MakerReadyToFundMultisigResponse) { + handleMakerReadyToFundMultisigResponse((MakerReadyToFundMultisigResponse) networkEnvelope, peer); + } else if (networkEnvelope instanceof DepositTxMessage) { + handleDepositTxMessage((DepositTxMessage) networkEnvelope, peer); + } else if (networkEnvelope instanceof UpdateMultisigRequest) { + handleUpdateMultisigRequest((UpdateMultisigRequest) networkEnvelope, peer); } } - // The maker received a TakeOfferRequest - private void handleTakeOfferRequest(NodeAddress peer, InputsForDepositTxRequest inputsForDepositTxRequest) { - log.info("Received inputsForDepositTxRequest from {} with tradeId {} and uid {}", - peer, inputsForDepositTxRequest.getTradeId(), inputsForDepositTxRequest.getUid()); - - try { - Validator.nonEmptyStringOf(inputsForDepositTxRequest.getTradeId()); - } catch (Throwable t) { - log.warn("Invalid inputsForDepositTxRequest " + inputsForDepositTxRequest.toString()); - return; - } - - Optional openOfferOptional = openOfferManager.getOpenOfferById(inputsForDepositTxRequest.getTradeId()); - if (!openOfferOptional.isPresent()) { - return; - } - - OpenOffer openOffer = openOfferOptional.get(); - if (openOffer.getState() != OpenOffer.State.AVAILABLE) { - return; - } - - Offer offer = openOffer.getOffer(); - openOfferManager.reserveOpenOffer(openOffer); - Trade trade; - if (offer.isBuyOffer()) { - trade = new BuyerAsMakerTrade(offer, - Coin.valueOf(inputsForDepositTxRequest.getTxFee()), - Coin.valueOf(inputsForDepositTxRequest.getTakerFee()), - inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(), - openOffer.getArbitratorNodeAddress(), - openOffer.getMediatorNodeAddress(), - openOffer.getRefundAgentNodeAddress(), - btcWalletService, - getNewProcessModel(offer), - UUID.randomUUID().toString()); - } else { - trade = new SellerAsMakerTrade(offer, - Coin.valueOf(inputsForDepositTxRequest.getTxFee()), - Coin.valueOf(inputsForDepositTxRequest.getTakerFee()), - inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(), - openOffer.getArbitratorNodeAddress(), - openOffer.getMediatorNodeAddress(), - openOffer.getRefundAgentNodeAddress(), - btcWalletService, - getNewProcessModel(offer), - UUID.randomUUID().toString()); - } - TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade); - TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol); - if (prev != null) { - log.error("We had already an entry with uid {}", trade.getUid()); - } - - tradableList.add(trade); - initTradeAndProtocol(trade, tradeProtocol); - - ((MakerProtocol) tradeProtocol).handleTakeOfferRequest(inputsForDepositTxRequest, peer, errorMessage -> { - if (takeOfferRequestErrorMessageHandler != null) - takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); - }); - - requestPersistence(); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Lifecycle @@ -311,11 +271,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi getObservableList().addListener((ListChangeListener) change -> onTradesChanged()); onTradesChanged(); - btcWalletService.getAddressEntriesForAvailableBalanceStream() + xmrWalletService.getAddressEntriesForAvailableBalanceStream() .filter(addressEntry -> addressEntry.getOfferId() != null) .forEach(addressEntry -> { log.warn("Swapping pending OFFER_FUNDING entries at startup. offerId={}", addressEntry.getOfferId()); - btcWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), AddressEntry.Context.OFFER_FUNDING); + xmrWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), XmrAddressEntry.Context.OFFER_FUNDING); }); } @@ -367,6 +327,220 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi persistenceManager.requestPersistence(); } + private void handleInitTradeRequest(InitTradeRequest initTradeRequest, NodeAddress peer) { + log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", peer, initTradeRequest.getTradeId(), initTradeRequest.getUid()); + + try { + Validator.nonEmptyStringOf(initTradeRequest.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid InitTradeRequest message " + initTradeRequest.toString()); + return; + } + + System.out.println("RECEIVED INIT REQUEST INFO"); + System.out.println("Sender peer node address: " + initTradeRequest.getSenderNodeAddress()); + System.out.println("Maker node address: " + initTradeRequest.getMakerNodeAddress()); + System.out.println("Taker node adddress: " + initTradeRequest.getTakerNodeAddress()); + System.out.println("Arbitrator node address: " + initTradeRequest.getArbitratorNodeAddress()); + + // handle request as arbitrator + boolean isArbitrator = initTradeRequest.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress()); + if (isArbitrator) { + + // get offer associated with trade + Offer offer = null; + for (Offer anOffer : offerBookService.getOffers()) { + if (anOffer.getId().equals(initTradeRequest.getTradeId())) { + offer = anOffer; + } + } + if (offer == null) throw new RuntimeException("No offer on the books with trade id: " + initTradeRequest.getTradeId()); // TODO (woodser): proper error handling + + Trade trade; + Optional tradeOptional = getTradeById(offer.getId()); + if (!tradeOptional.isPresent()) { + trade = new ArbitratorTrade(offer, + Coin.valueOf(initTradeRequest.getTradeAmount()), + Coin.valueOf(initTradeRequest.getTxFee()), + Coin.valueOf(initTradeRequest.getTradeFee()), + initTradeRequest.getTradePrice(), + initTradeRequest.getMakerNodeAddress(), + initTradeRequest.getTakerNodeAddress(), + initTradeRequest.getArbitratorNodeAddress(), + xmrWalletService, + getNewProcessModel(offer), + UUID.randomUUID().toString()); + initTradeAndProtocol(trade, getTradeProtocol(trade)); + tradableList.add(trade); + } else { + trade = tradeOptional.get(); + } + + // TODO (woodser): do this for arbitrator? +// TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade); +// TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol); +// if (prev != null) { +// log.error("We had already an entry with uid {}", trade.getUid()); +// } + + ((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler? + }); + + requestPersistence(); + } + + // handle request as maker + else { + + Optional openOfferOptional = openOfferManager.getOpenOfferById(initTradeRequest.getTradeId()); + if (!openOfferOptional.isPresent()) { + return; + } + + OpenOffer openOffer = openOfferOptional.get(); + if (openOffer.getState() != OpenOffer.State.AVAILABLE) { + return; + } + + Offer offer = openOffer.getOffer(); + openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator? + + Trade trade; + if (offer.isBuyOffer()) + trade = new BuyerAsMakerTrade(offer, + Coin.valueOf(initTradeRequest.getTxFee()), + Coin.valueOf(initTradeRequest.getTradeFee()), + initTradeRequest.getMakerNodeAddress(), + initTradeRequest.getTakerNodeAddress(), + initTradeRequest.getArbitratorNodeAddress(), + xmrWalletService, + getNewProcessModel(offer), + UUID.randomUUID().toString()); + else + trade = new SellerAsMakerTrade(offer, + Coin.valueOf(initTradeRequest.getTxFee()), + Coin.valueOf(initTradeRequest.getTradeFee()), + initTradeRequest.getMakerNodeAddress(), + initTradeRequest.getTakerNodeAddress(), + openOffer.getArbitratorNodeAddress(), + xmrWalletService, + getNewProcessModel(offer), + UUID.randomUUID().toString()); + + TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade); + TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol); + if (prev != null) { + log.error("We had already an entry with uid {}", trade.getUid()); + } + + tradableList.add(trade); + initTradeAndProtocol(trade, tradeProtocol); + + ((MakerProtocol) tradeProtocol).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + }); + + requestPersistence(); + } + } + + private void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest request, NodeAddress peer) { + log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); + + try { + Validator.nonEmptyStringOf(request.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid InitTradeRequest message " + request.toString()); + return; + } + + Optional tradeOptional = getTradeById(request.getTradeId()); + if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling + Trade trade = tradeOptional.get(); + ((MakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigRequest(request, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + }); + } + + private void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer) { + log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid()); + + try { + Validator.nonEmptyStringOf(response.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid InitTradeRequest message " + response.toString()); + return; + } + + Optional tradeOptional = getTradeById(response.getTradeId()); + if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling + Trade trade = tradeOptional.get(); + ((TakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigResponse(response, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + }); + } + + private void handleMultisigMessage(InitMultisigMessage multisigMessage, NodeAddress peer) { + log.info("Received InitMultisigMessage from {} with tradeId {} and uid {}", peer, multisigMessage.getTradeId(), multisigMessage.getUid()); + + try { + Validator.nonEmptyStringOf(multisigMessage.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid InitMultisigMessage message " + multisigMessage.toString()); + return; + } + + Optional tradeOptional = getTradeById(multisigMessage.getTradeId()); + if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + multisigMessage.getTradeId()); // TODO (woodser): error handling + Trade trade = tradeOptional.get(); + getTradeProtocol(trade).handleMultisigMessage(multisigMessage, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + }); + } + + private void handleUpdateMultisigRequest(UpdateMultisigRequest request, NodeAddress peer) { + log.info("Received UpdateMultisigRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); + + try { + Validator.nonEmptyStringOf(request.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid UpdateMultisigRequest message " + request.toString()); + return; + } + + Optional tradeOptional = getTradeById(request.getTradeId()); + if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling + Trade trade = tradeOptional.get(); + getTradeProtocol(trade).handleUpdateMultisigRequest(request, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + }); + } + + private void handleDepositTxMessage(DepositTxMessage request, NodeAddress peer) { + log.info("Received DepositTxMessage from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); + + try { + Validator.nonEmptyStringOf(request.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid InitTradeRequest message " + request.toString()); + return; + } + + Optional tradeOptional = getTradeById(request.getTradeId()); + if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling + Trade trade = tradeOptional.get(); + getTradeProtocol(trade).handleDepositTxMessage(request, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + }); + } /////////////////////////////////////////////////////////////////////////////////////////// // Take offer @@ -376,14 +550,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi boolean isTakerApiUser, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - if (btcWalletService.isUnconfirmedTransactionsLimitHit() || - bsqWalletService.isUnconfirmedTransactionsLimitHit()) { - String errorMessage = Res.get("shared.unconfirmedTransactionsLimitReached"); - errorMessageHandler.handleErrorMessage(errorMessage); - log.warn(errorMessage); - return; - } - offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser), resultHandler, errorMessageHandler); } @@ -391,7 +557,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi public void onTakeOffer(Coin amount, Coin txFee, Coin takerFee, - boolean isCurrencyForTakerFeeBtc, long tradePrice, Coin fundsNeededForTrade, Offer offer, @@ -413,13 +578,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi amount, txFee, takerFee, - isCurrencyForTakerFeeBtc, tradePrice, model.getPeerNodeAddress(), - model.getSelectedArbitrator(), - model.getSelectedMediator(), - model.getSelectedRefundAgent(), - btcWalletService, + P2PService.getMyNodeAddress(), // TODO (woodser): correct taker node address? + model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront + xmrWalletService, getNewProcessModel(offer), UUID.randomUUID().toString()); } else { @@ -427,13 +590,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi amount, txFee, takerFee, - isCurrencyForTakerFeeBtc, tradePrice, model.getPeerNodeAddress(), - model.getSelectedArbitrator(), - model.getSelectedMediator(), - model.getSelectedRefundAgent(), - btcWalletService, + P2PService.getMyNodeAddress(), + model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront + xmrWalletService, getNewProcessModel(offer), UUID.randomUUID().toString()); } @@ -483,43 +644,42 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi /////////////////////////////////////////////////////////////////////////////////////////// public void onWithdrawRequest(String toAddress, - Coin amount, - Coin fee, - KeyParameter aesKey, - Trade trade, - @Nullable String memo, - ResultHandler resultHandler, - FaultHandler faultHandler) { - String fromAddress = btcWalletService.getOrCreateAddressEntry(trade.getId(), - AddressEntry.Context.TRADE_PAYOUT).getAddressString(); - FutureCallback callback = new FutureCallback<>() { - @Override - public void onSuccess(@javax.annotation.Nullable Transaction transaction) { - if (transaction != null) { - log.debug("onWithdraw onSuccess tx ID:" + transaction.getTxId().toString()); - onTradeCompleted(trade); - trade.setState(Trade.State.WITHDRAW_COMPLETED); - getTradeProtocol(trade).onWithdrawCompleted(); - requestPersistence(); - resultHandler.handleResult(); - } - } - - @Override - public void onFailure(@NotNull Throwable t) { - t.printStackTrace(); - log.error(t.getMessage()); - faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t); - } - }; - try { - btcWalletService.sendFunds(fromAddress, toAddress, amount, fee, aesKey, - AddressEntry.Context.TRADE_PAYOUT, memo, callback); - } catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) { - e.printStackTrace(); - log.error(e.getMessage()); - faultHandler.handleFault("An exception occurred at requestWithdraw.", e); + Coin amount, + Coin fee, + KeyParameter aesKey, + Trade trade, + @Nullable String memo, + ResultHandler resultHandler, + FaultHandler faultHandler) { + int fromAccountIdx = xmrWalletService.getOrCreateAddressEntry(trade.getId(), + XmrAddressEntry.Context.TRADE_PAYOUT).getAccountIndex(); + FutureCallback callback = new FutureCallback() { + @Override + public void onSuccess(@javax.annotation.Nullable MoneroTxWallet transaction) { + if (transaction != null) { + log.debug("onWithdraw onSuccess tx ID:" + transaction.getHash()); + onTradeCompleted(trade); + trade.setState(Trade.State.WITHDRAW_COMPLETED); + getTradeProtocol(trade).onWithdrawCompleted(); + requestPersistence(); + resultHandler.handleResult(); + } } + + @Override + public void onFailure(@NotNull Throwable t) { + t.printStackTrace(); + log.error(t.getMessage()); + faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t); + } + }; + try { + xmrWalletService.sendFunds(fromAccountIdx, toAddress, amount, XmrAddressEntry.Context.TRADE_PAYOUT, callback); + } catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) { + e.printStackTrace(); + log.error(e.getMessage()); + faultHandler.handleFault("An exception occurred at requestWithdraw.", e); + } } // If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades @@ -528,7 +688,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi closedTradableManager.add(trade); // TODO The address entry should have been removed already. Check and if its the case remove that. - btcWalletService.resetAddressEntriesForPendingTrade(trade.getId()); + xmrWalletService.resetAddressEntriesForPendingTrade(trade.getId()); requestPersistence(); } @@ -543,12 +703,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi Trade trade = tradeOptional.get(); trade.setDisputeState(disputeState); onTradeCompleted(trade); - btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT); + xmrWalletService.swapTradeEntryToAvailableEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT); requestPersistence(); } } - /////////////////////////////////////////////////////////////////////////////////////////// // Trade period state /////////////////////////////////////////////////////////////////////////////////////////// @@ -616,7 +775,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi .map(Trade::getId) .collect(Collectors.toSet()); tradesIdSet.addAll(failedTradesManager.getTradesStreamWithFundsLockedIn() - .filter(trade -> trade.getDepositTx() != null) + .filter(trade -> trade.getMakerDepositTx() != null || trade.getTakerDepositTx() != null) .map(trade -> { log.warn("We found a failed trade with locked up funds. " + "That should never happen. trade ID=" + trade.getId()); @@ -625,19 +784,30 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi .collect(Collectors.toSet())); tradesIdSet.addAll(closedTradableManager.getTradesStreamWithFundsLockedIn() .map(trade -> { - Transaction depositTx = trade.getDepositTx(); - if (depositTx != null) { - TransactionConfidence confidence = btcWalletService.getConfidenceForTxId(depositTx.getTxId().toString()); - if (confidence != null && confidence.getConfidenceType() != TransactionConfidence.ConfidenceType.BUILDING) { - tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId()))); - } else { - log.warn("We found a closed trade with locked up funds. " + - "That should never happen. trade ID=" + trade.getId()); - } - } else { - tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId()))); - } - return trade.getId(); + MoneroTxWallet makerDepositTx = trade.getMakerDepositTx(); + if (makerDepositTx != null) { + if (makerDepositTx.isLocked()) { + tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId()))); // TODO (woodser): rename to closedTradeWithLockedDepositTx + } else { + log.warn("We found a closed trade with locked up funds. " + + "That should never happen. trade ID=" + trade.getId()); + } + } else { + tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId()))); + } + + MoneroTxWallet takerDepositTx = trade.getTakerDepositTx(); + if (takerDepositTx != null) { + if (!takerDepositTx.isConfirmed()) { + tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId()))); + } else { + log.warn("We found a closed trade with locked up funds. " + + "That should never happen. trade ID=" + trade.getId()); + } + } else { + tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId()))); + } + return trade.getId(); }) .collect(Collectors.toSet())); @@ -671,10 +841,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi if (entries == null) return false; - btcWalletService.recoverAddressEntry(trade.getId(), entries.first, - AddressEntry.Context.MULTI_SIG); - btcWalletService.recoverAddressEntry(trade.getId(), entries.second, - AddressEntry.Context.TRADE_PAYOUT); + xmrWalletService.recoverAddressEntry(trade.getId(), entries.first, + XmrAddressEntry.Context.MULTI_SIG); + xmrWalletService.recoverAddressEntry(trade.getId(), entries.second, + XmrAddressEntry.Context.TRADE_PAYOUT); return true; } diff --git a/core/src/main/java/bisq/core/trade/TradeUtil.java b/core/src/main/java/bisq/core/trade/TradeUtil.java index a026f6ab..a7d779e7 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtil.java +++ b/core/src/main/java/bisq/core/trade/TradeUtil.java @@ -86,38 +86,39 @@ public class TradeUtil { * @return Tuple2 tuple containing MULTI_SIG, TRADE_PAYOUT addresses for trade */ public Tuple2 getTradeAddresses(Trade trade) { - var contract = trade.getContract(); - if (contract == null) - return null; - - // Get multisig address - var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing()); - var multiSigPubKey = isMyRoleBuyer - ? contract.getBuyerMultiSigPubKey() - : contract.getSellerMultiSigPubKey(); - if (multiSigPubKey == null) - return null; - - var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey); - var multiSigAddress = btcWalletService.getAddressEntryListAsImmutableList().stream() - .filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString)) - .findAny() - .orElse(null); - if (multiSigAddress == null) - return null; - - // Get payout address - var payoutAddress = isMyRoleBuyer - ? contract.getBuyerPayoutAddressString() - : contract.getSellerPayoutAddressString(); - var payoutAddressEntry = btcWalletService.getAddressEntryListAsImmutableList().stream() - .filter(e -> Objects.equals(e.getAddressString(), payoutAddress)) - .findAny() - .orElse(null); - if (payoutAddressEntry == null) - return null; - - return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress); + throw new RuntimeException("TradeUtil.getTradeAddresses() not implemented for XMR"); +// var contract = trade.getContract(); +// if (contract == null) +// return null; +// +// // Get multisig address +// var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing()); +// var multiSigPubKey = isMyRoleBuyer +// ? contract.getBuyerMultiSigPubKey() +// : contract.getSellerMultiSigPubKey(); +// if (multiSigPubKey == null) +// return null; +// +// var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey); +// var multiSigAddress = btcWalletService.getAddressEntryListAsImmutableList().stream() +// .filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString)) +// .findAny() +// .orElse(null); +// if (multiSigAddress == null) +// return null; +// +// // Get payout address +// var payoutAddress = isMyRoleBuyer +// ? contract.getBuyerPayoutAddressString() +// : contract.getSellerPayoutAddressString(); +// var payoutAddressEntry = btcWalletService.getAddressEntryListAsImmutableList().stream() +// .filter(e -> Objects.equals(e.getAddressString(), payoutAddress)) +// .findAny() +// .orElse(null); +// if (payoutAddressEntry == null) +// return null; +// +// return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress); } public long getRemainingTradeDuration(Trade trade) { diff --git a/core/src/main/java/bisq/core/trade/TradeUtils.java b/core/src/main/java/bisq/core/trade/TradeUtils.java new file mode 100644 index 00000000..2f4da660 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/TradeUtils.java @@ -0,0 +1,81 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade; + +import bisq.core.btc.wallet.XmrWalletService; + +import bisq.common.crypto.KeyRing; +import bisq.common.util.Tuple2; + +import java.util.Objects; + +public class TradeUtils { + + // Returns if both are AVAILABLE, otherwise null + static Tuple2 getAvailableAddresses(Trade trade, XmrWalletService xmrWalletService, + KeyRing keyRing) { + var addresses = getTradeAddresses(trade, xmrWalletService, keyRing); + if (addresses == null) + return null; + + if (xmrWalletService.getAvailableAddressEntries().stream() + .noneMatch(e -> Objects.equals(e.getAddressString(), addresses.first))) + return null; + if (xmrWalletService.getAvailableAddressEntries().stream() + .noneMatch(e -> Objects.equals(e.getAddressString(), addresses.second))) + return null; + + return new Tuple2<>(addresses.first, addresses.second); + } + + // Returns addresses as strings if they're known by the wallet + public static Tuple2 getTradeAddresses(Trade trade, XmrWalletService xmrWalletService, + KeyRing keyRing) { + var contract = trade.getContract(); + if (contract == null) + return null; + + // TODO (woodser): xmr multisig does not use pub key + throw new RuntimeException("need to replace btc multisig pub key with xmr"); + + // Get multisig address +// var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing()); +// var multiSigPubKey = isMyRoleBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey(); +// if (multiSigPubKey == null) +// return null; +// var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey); +// var multiSigAddress = xmrWalletService.getAddressEntryListAsImmutableList().stream() +// .filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString)) +// .findAny() +// .orElse(null); +// if (multiSigAddress == null) +// return null; +// +// // Get payout address +// var payoutAddress = isMyRoleBuyer ? +// contract.getBuyerPayoutAddressString() : contract.getSellerPayoutAddressString(); +// var payoutAddressEntry = xmrWalletService.getAddressEntryListAsImmutableList().stream() +// .filter(e -> Objects.equals(e.getAddressString(), payoutAddress)) +// .findAny() +// .orElse(null); +// if (payoutAddressEntry == null) +// return null; +// +// return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress); + } +} diff --git a/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java b/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java index 84746a21..51277a22 100644 --- a/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java +++ b/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java @@ -17,8 +17,8 @@ package bisq.core.trade.failed; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.Offer; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.DumpDelayedPayoutTx; @@ -49,7 +49,7 @@ public class FailedTradesManager implements PersistedDataHost { private final TradableList failedTrades = new TradableList<>(); private final KeyRing keyRing; private final PriceFeedService priceFeedService; - private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final CleanupMailboxMessages cleanupMailboxMessages; private final PersistenceManager> persistenceManager; private final TradeUtil tradeUtil; @@ -60,14 +60,14 @@ public class FailedTradesManager implements PersistedDataHost { @Inject public FailedTradesManager(KeyRing keyRing, PriceFeedService priceFeedService, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, PersistenceManager> persistenceManager, TradeUtil tradeUtil, CleanupMailboxMessages cleanupMailboxMessages, DumpDelayedPayoutTx dumpDelayedPayoutTx) { this.keyRing = keyRing; this.priceFeedService = priceFeedService; - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.cleanupMailboxMessages = cleanupMailboxMessages; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; this.persistenceManager = persistenceManager; @@ -140,8 +140,8 @@ public class FailedTradesManager implements PersistedDataHost { return "Addresses not found"; } StringBuilder blockingTrades = new StringBuilder(); - for (var entry : btcWalletService.getAddressEntryListAsImmutableList()) { - if (entry.getContext() == AddressEntry.Context.AVAILABLE) + for (var entry : xmrWalletService.getAddressEntryListAsImmutableList()) { + if (entry.getContext() == XmrAddressEntry.Context.AVAILABLE) continue; if (entry.getAddressString() != null && (entry.getAddressString().equals(addresses.first) || diff --git a/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java index ea56069c..614d56a0 100644 --- a/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java @@ -21,9 +21,6 @@ import bisq.network.p2p.NodeAddress; import bisq.common.app.Version; import bisq.common.proto.ProtoUtil; -import bisq.common.util.Utilities; - -import com.google.protobuf.ByteString; import java.util.Optional; @@ -37,7 +34,7 @@ import javax.annotation.Nullable; public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMessage { private final String buyerPayoutAddress; private final NodeAddress senderNodeAddress; - private final byte[] buyerSignature; + private final String buyerPayoutTxSigned; @Nullable private final String counterCurrencyTxId; @@ -49,14 +46,14 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes public CounterCurrencyTransferStartedMessage(String tradeId, String buyerPayoutAddress, NodeAddress senderNodeAddress, - byte[] buyerSignature, + String buyerPayoutTxSigned, @Nullable String counterCurrencyTxId, @Nullable String counterCurrencyExtraData, String uid) { this(tradeId, buyerPayoutAddress, senderNodeAddress, - buyerSignature, + buyerPayoutTxSigned, counterCurrencyTxId, counterCurrencyExtraData, uid, @@ -71,7 +68,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes private CounterCurrencyTransferStartedMessage(String tradeId, String buyerPayoutAddress, NodeAddress senderNodeAddress, - byte[] buyerSignature, + String buyerPayoutTxSigned, @Nullable String counterCurrencyTxId, @Nullable String counterCurrencyExtraData, String uid, @@ -79,7 +76,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes super(messageVersion, tradeId, uid); this.buyerPayoutAddress = buyerPayoutAddress; this.senderNodeAddress = senderNodeAddress; - this.buyerSignature = buyerSignature; + this.buyerPayoutTxSigned = buyerPayoutTxSigned; this.counterCurrencyTxId = counterCurrencyTxId; this.counterCurrencyExtraData = counterCurrencyExtraData; } @@ -90,7 +87,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes builder.setTradeId(tradeId) .setBuyerPayoutAddress(buyerPayoutAddress) .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) - .setBuyerSignature(ByteString.copyFrom(buyerSignature)) + .setBuyerPayoutTxSigned(buyerPayoutTxSigned) .setUid(uid); Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId)); @@ -104,7 +101,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes return new CounterCurrencyTransferStartedMessage(proto.getTradeId(), proto.getBuyerPayoutAddress(), NodeAddress.fromProto(proto.getSenderNodeAddress()), - proto.getBuyerSignature().toByteArray(), + proto.getBuyerPayoutTxSigned(), ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyTxId()), ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData()), proto.getUid(), @@ -120,7 +117,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes ",\n counterCurrencyTxId=" + counterCurrencyTxId + ",\n counterCurrencyExtraData=" + counterCurrencyExtraData + ",\n uid='" + uid + '\'' + - ",\n buyerSignature=" + Utilities.bytesAsHexString(buyerSignature) + + ",\n buyerPayoutTxSigned=" + buyerPayoutTxSigned + "\n} " + super.toString(); } } diff --git a/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java b/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java index 631f1a9c..83b84b10 100644 --- a/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java +++ b/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java @@ -21,70 +21,66 @@ import bisq.network.p2p.DirectMessage; import bisq.network.p2p.NodeAddress; import bisq.common.app.Version; -import bisq.common.util.Utilities; +import bisq.common.proto.ProtoUtil; -import com.google.protobuf.ByteString; +import java.util.Optional; import lombok.EqualsAndHashCode; import lombok.Value; +import javax.annotation.Nullable; + // It is the last message in the take offer phase. We use MailboxMessage instead of DirectMessage to add more tolerance // in case of network issues and as the message does not trigger further protocol execution. @EqualsAndHashCode(callSuper = true) @Value public final class DepositTxMessage extends TradeMessage implements DirectMessage { private final NodeAddress senderNodeAddress; - private final byte[] depositTxWithoutWitnesses; + @Nullable + private final String tradeFeeTxId; + @Nullable + private final String depositTxId; public DepositTxMessage(String uid, String tradeId, NodeAddress senderNodeAddress, - byte[] depositTxWithoutWitnesses) { - this(Version.getP2PMessageVersion(), - uid, - tradeId, - senderNodeAddress, - depositTxWithoutWitnesses); + String tradeFeeTxId, + String depositTxId) { + super(Version.getP2PMessageVersion(), tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.tradeFeeTxId = tradeFeeTxId; + this.depositTxId = depositTxId; } /////////////////////////////////////////////////////////////////////////////////////////// // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - private DepositTxMessage(int messageVersion, - String uid, - String tradeId, - NodeAddress senderNodeAddress, - byte[] depositTxWithoutWitnesses) { - super(messageVersion, tradeId, uid); - this.senderNodeAddress = senderNodeAddress; - this.depositTxWithoutWitnesses = depositTxWithoutWitnesses; - } - @Override public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { - return getNetworkEnvelopeBuilder() - .setDepositTxMessage(protobuf.DepositTxMessage.newBuilder() - .setUid(uid) - .setTradeId(tradeId) - .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) - .setDepositTxWithoutWitnesses(ByteString.copyFrom(depositTxWithoutWitnesses))) - .build(); + protobuf.DepositTxMessage.Builder builder = protobuf.DepositTxMessage.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setUid(uid); + Optional.ofNullable(tradeFeeTxId).ifPresent(e -> builder.setTradeFeeTxId(tradeFeeTxId)); + Optional.ofNullable(depositTxId).ifPresent(e -> builder.setDepositTxId(depositTxId)); + return getNetworkEnvelopeBuilder().setDepositTxMessage(builder).build(); } public static DepositTxMessage fromProto(protobuf.DepositTxMessage proto, int messageVersion) { - return new DepositTxMessage(messageVersion, - proto.getUid(), + return new DepositTxMessage(proto.getUid(), proto.getTradeId(), NodeAddress.fromProto(proto.getSenderNodeAddress()), - proto.getDepositTxWithoutWitnesses().toByteArray()); + ProtoUtil.stringOrNullFromProto(proto.getTradeFeeTxId()), + ProtoUtil.stringOrNullFromProto(proto.getDepositTxId())); } @Override public String toString() { return "DepositTxMessage{" + "\n senderNodeAddress=" + senderNodeAddress + - ",\n depositTxWithoutWitnesses=" + Utilities.bytesAsHexString(depositTxWithoutWitnesses) + + ",\n tradeFeeTxId=" + tradeFeeTxId + + ",\n depositTxId=" + depositTxId + "\n} " + super.toString(); } } diff --git a/core/src/main/java/bisq/core/trade/messages/InitMultisigMessage.java b/core/src/main/java/bisq/core/trade/messages/InitMultisigMessage.java new file mode 100644 index 00000000..a1c3a123 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/InitMultisigMessage.java @@ -0,0 +1,106 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.proto.ProtoUtil; + +import java.util.Optional; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +import javax.annotation.Nullable; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class InitMultisigMessage extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final PubKeyRing pubKeyRing; + private final long currentDate; + @Nullable + private final String preparedMultisigHex; + @Nullable + private final String madeMultisigHex; + + public InitMultisigMessage(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + String uid, + int messageVersion, + long currentDate, + String preparedMultisigHex, + String madeMultisigHex) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + this.currentDate = currentDate; + this.preparedMultisigHex = preparedMultisigHex; + this.madeMultisigHex = madeMultisigHex; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.InitMultisigMessage.Builder builder = protobuf.InitMultisigMessage.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setUid(uid); + + Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex)); + Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex)); + + builder.setCurrentDate(currentDate); + + return getNetworkEnvelopeBuilder().setInitMultisigMessage(builder).build(); + } + + public static InitMultisigMessage fromProto(protobuf.InitMultisigMessage proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new InitMultisigMessage(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getUid(), + messageVersion, + proto.getCurrentDate(), + ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()), + ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex())); + } + + @Override + public String toString() { + return "MultisigMessage {" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n pubKeyRing=" + pubKeyRing + + ",\n currentDate=" + currentDate + + ",\n preparedMultisigHex='" + preparedMultisigHex + + ",\n madeMultisigHex='" + madeMultisigHex + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/InitTradeRequest.java b/core/src/main/java/bisq/core/trade/messages/InitTradeRequest.java new file mode 100644 index 00000000..c3bce4cc --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/InitTradeRequest.java @@ -0,0 +1,171 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.proto.ProtoUtil; +import bisq.common.util.Utilities; + +import com.google.protobuf.ByteString; + +import java.util.Optional; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +import javax.annotation.Nullable; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class InitTradeRequest extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final long tradeAmount; + private final long tradePrice; + private final long txFee; + private final long tradeFee; + private final String payoutAddressString; + private final PaymentAccountPayload paymentAccountPayload; + private final PubKeyRing pubKeyRing; + private final String accountId; + @Nullable + private final String tradeFeeTxId; + private final NodeAddress arbitratorNodeAddress; + + // added in v 0.6. can be null if we trade with an older peer + @Nullable + private final byte[] accountAgeWitnessSignatureOfOfferId; + private final long currentDate; + + // added for XMR integration + private final NodeAddress takerNodeAddress; + private final NodeAddress makerNodeAddress; + + public InitTradeRequest(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + long tradeAmount, + long tradePrice, + long txFee, + long tradeFee, + String payoutAddressString, + PaymentAccountPayload paymentAccountPayload, + String accountId, + String tradeFeeTxId, + String uid, + int messageVersion, + @Nullable byte[] accountAgeWitnessSignatureOfOfferId, + long currentDate, + NodeAddress takerNodeAddress, + NodeAddress makerNodeAddress, + NodeAddress arbitratorNodeAddress) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + this.paymentAccountPayload = paymentAccountPayload; + this.tradeAmount = tradeAmount; + this.tradePrice = tradePrice; + this.txFee = txFee; + this.tradeFee = tradeFee; + this.payoutAddressString = payoutAddressString; + this.accountId = accountId; + this.tradeFeeTxId = tradeFeeTxId; + this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId; + this.currentDate = currentDate; + this.takerNodeAddress = takerNodeAddress; + this.makerNodeAddress = makerNodeAddress; + this.arbitratorNodeAddress = arbitratorNodeAddress; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.InitTradeRequest.Builder builder = protobuf.InitTradeRequest.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setTakerNodeAddress(takerNodeAddress.toProtoMessage()) + .setMakerNodeAddress(makerNodeAddress.toProtoMessage()) + .setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()) + .setTradeAmount(tradeAmount) + .setTradePrice(tradePrice) + .setTxFee(txFee) + .setTradeFee(tradeFee) + .setPayoutAddressString(payoutAddressString) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setPaymentAccountPayload((protobuf.PaymentAccountPayload) paymentAccountPayload.toProtoMessage()) + .setAccountId(accountId) + .setUid(uid); + + Optional.ofNullable(tradeFeeTxId).ifPresent(e -> builder.setTradeFeeTxId(tradeFeeTxId)); + Optional.ofNullable(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e))); + builder.setCurrentDate(currentDate); + + return getNetworkEnvelopeBuilder().setInitTradeRequest(builder).build(); + } + + public static InitTradeRequest fromProto(protobuf.InitTradeRequest proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new InitTradeRequest(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getTradeAmount(), + proto.getTradePrice(), + proto.getTxFee(), + proto.getTradeFee(), + proto.getPayoutAddressString(), + coreProtoResolver.fromProto(proto.getPaymentAccountPayload()), + proto.getAccountId(), + ProtoUtil.stringOrNullFromProto(proto.getTradeFeeTxId()), + proto.getUid(), + messageVersion, + ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfOfferId()), + proto.getCurrentDate(), + NodeAddress.fromProto(proto.getTakerNodeAddress()), + NodeAddress.fromProto(proto.getMakerNodeAddress()), + NodeAddress.fromProto(proto.getArbitratorNodeAddress())); + } + + @Override + public String toString() { + return "InitTradeRequest{" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n tradeAmount=" + tradeAmount + + ",\n tradePrice=" + tradePrice + + ",\n txFee=" + txFee + + ",\n takerFee=" + tradeFee + + ",\n payoutAddressString='" + payoutAddressString + '\'' + + ",\n pubKeyRing=" + pubKeyRing + + ",\n paymentAccountPayload=" + paymentAccountPayload + + ",\n paymentAccountPayload='" + accountId + '\'' + + ",\n takerFeeTxId='" + tradeFeeTxId + '\'' + + ",\n arbitratorNodeAddress=" + arbitratorNodeAddress + + ",\n accountAgeWitnessSignatureOfOfferId=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfOfferId) + + ",\n currentDate=" + currentDate + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigRequest.java b/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigRequest.java new file mode 100644 index 00000000..0a2b63d1 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigRequest.java @@ -0,0 +1,79 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; + +import bisq.common.crypto.PubKeyRing; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class MakerReadyToFundMultisigRequest extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final PubKeyRing pubKeyRing; + + public MakerReadyToFundMultisigRequest(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + String uid, + int messageVersion) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.MakerReadyToFundMultisigRequest.Builder builder = protobuf.MakerReadyToFundMultisigRequest.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setUid(uid); + + return getNetworkEnvelopeBuilder().setMakerReadyToFundMultisigRequest(builder).build(); + } + + public static MakerReadyToFundMultisigRequest fromProto(protobuf.MakerReadyToFundMultisigRequest proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new MakerReadyToFundMultisigRequest(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getUid(), + messageVersion); + } + + @Override + public String toString() { + return "MakerReadyToFundMultisigRequest{" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n pubKeyRing=" + pubKeyRing + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigResponse.java b/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigResponse.java new file mode 100644 index 00000000..a4bc7c4f --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigResponse.java @@ -0,0 +1,118 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; + +import java.util.Optional; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Value; + +import javax.annotation.Nullable; + +@EqualsAndHashCode(callSuper = true) +@Value +public class MakerReadyToFundMultisigResponse extends TradeMessage implements DirectMessage { + @Getter + private final boolean isMakerReadyToFundMultisig; + @Getter + @Nullable + private final String makerContractAsJson; + @Getter + @Nullable + private final String makerContractSignature; + @Getter + @Nullable + private final String makerPayoutAddressString; + @Getter + @Nullable + private final PaymentAccountPayload makerPaymentAccountPayload; + @Getter + @Nullable + private final String makerAccountId; + @Getter + private final long currentDate; + + public MakerReadyToFundMultisigResponse(String tradeId, + boolean isMakerReadyToFundMultisig, + String uid, + int messageVersion, + String makerContractAsJson, + String makerContractSignature, + String makerPayoutAddressString, + PaymentAccountPayload makerPaymentAccountPayload, + String makerAccountId, + long currentDate) { + super(messageVersion, tradeId, uid); + this.isMakerReadyToFundMultisig = isMakerReadyToFundMultisig; + this.makerContractAsJson = makerContractAsJson; + this.makerContractSignature = makerContractSignature; + this.makerPayoutAddressString = makerPayoutAddressString; + this.makerPaymentAccountPayload = makerPaymentAccountPayload; + this.makerAccountId = makerAccountId; + this.currentDate = currentDate; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.MakerReadyToFundMultisigResponse.Builder builder = protobuf.MakerReadyToFundMultisigResponse.newBuilder() + .setTradeId(tradeId) + .setIsMakerReadyToFundMultisig(isMakerReadyToFundMultisig) + .setCurrentDate(currentDate); + + Optional.ofNullable(makerContractAsJson).ifPresent(e -> builder.setMakerContractAsJson(makerContractAsJson)); + Optional.ofNullable(makerContractSignature).ifPresent(e -> builder.setMakerContractSignature(makerContractSignature)); + Optional.ofNullable(makerPayoutAddressString).ifPresent(e -> builder.setMakerPayoutAddressString(makerPayoutAddressString)); + Optional.ofNullable(makerPaymentAccountPayload).ifPresent(e -> builder.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage())); + Optional.ofNullable(makerAccountId).ifPresent(e -> builder.setMakerAccountId(makerAccountId)); + + return getNetworkEnvelopeBuilder().setMakerReadyToFundMultisigResponse(builder).build(); + } + + public static MakerReadyToFundMultisigResponse fromProto(protobuf.MakerReadyToFundMultisigResponse proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new MakerReadyToFundMultisigResponse(proto.getTradeId(), + proto.getIsMakerReadyToFundMultisig(), + proto.getUid(), + messageVersion, + proto.getMakerContractAsJson(), + proto.getMakerContractSignature(), + proto.getMakerPayoutAddressString(), + coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()), + proto.getMakerAccountId(), + proto.getCurrentDate()); + } + + @Override + public String toString() { + return "MakerReadyToFundMultisigResponse{" + + "\n isMakerReadyToFundMultisig=" + isMakerReadyToFundMultisig + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java index 86ed851b..c61f0193 100644 --- a/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java @@ -23,9 +23,6 @@ import bisq.network.p2p.NodeAddress; import bisq.common.app.Version; import bisq.common.proto.network.NetworkEnvelope; -import bisq.common.util.Utilities; - -import com.google.protobuf.ByteString; import java.util.Optional; import java.util.UUID; @@ -40,7 +37,7 @@ import javax.annotation.Nullable; @EqualsAndHashCode(callSuper = true) @Value public final class PayoutTxPublishedMessage extends TradeMailboxMessage { - private final byte[] payoutTx; + private final String signedMultisigTxHex; private final NodeAddress senderNodeAddress; // Added in v1.4.0 @@ -48,11 +45,11 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage { private final SignedWitness signedWitness; public PayoutTxPublishedMessage(String tradeId, - byte[] payoutTx, + String signedMultisigTxHex, NodeAddress senderNodeAddress, @Nullable SignedWitness signedWitness) { this(tradeId, - payoutTx, + signedMultisigTxHex, senderNodeAddress, signedWitness, UUID.randomUUID().toString(), @@ -65,13 +62,13 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage { /////////////////////////////////////////////////////////////////////////////////////////// private PayoutTxPublishedMessage(String tradeId, - byte[] payoutTx, + String signedMultisigTxHex, NodeAddress senderNodeAddress, @Nullable SignedWitness signedWitness, String uid, int messageVersion) { super(messageVersion, tradeId, uid); - this.payoutTx = payoutTx; + this.signedMultisigTxHex = signedMultisigTxHex; this.senderNodeAddress = senderNodeAddress; this.signedWitness = signedWitness; } @@ -80,7 +77,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage { public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { protobuf.PayoutTxPublishedMessage.Builder builder = protobuf.PayoutTxPublishedMessage.newBuilder() .setTradeId(tradeId) - .setPayoutTx(ByteString.copyFrom(payoutTx)) + .setSignedMultisigTxHex(signedMultisigTxHex) .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setUid(uid); Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness())); @@ -95,7 +92,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage { SignedWitness.fromProto(protoSignedWitness) : null; return new PayoutTxPublishedMessage(proto.getTradeId(), - proto.getPayoutTx().toByteArray(), + proto.getSignedMultisigTxHex(), NodeAddress.fromProto(proto.getSenderNodeAddress()), signedWitness, proto.getUid(), @@ -105,7 +102,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage { @Override public String toString() { return "PayoutTxPublishedMessage{" + - "\n payoutTx=" + Utilities.bytesAsHexString(payoutTx) + + "\n signedMultisigTxHex=" + signedMultisigTxHex + ",\n senderNodeAddress=" + senderNodeAddress + ",\n signedWitness=" + signedWitness + "\n} " + super.toString(); diff --git a/core/src/main/java/bisq/core/trade/messages/UpdateMultisigRequest.java b/core/src/main/java/bisq/core/trade/messages/UpdateMultisigRequest.java new file mode 100644 index 00000000..f20d7c92 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/UpdateMultisigRequest.java @@ -0,0 +1,99 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.proto.ProtoUtil; + +import java.util.Optional; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +import javax.annotation.Nullable; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class UpdateMultisigRequest extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final PubKeyRing pubKeyRing; + private final long currentDate; + @Nullable + private final String updatedMultisigHex; + + public UpdateMultisigRequest(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + String uid, + int messageVersion, + long currentDate, + String updatedMultisigHex) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + this.currentDate = currentDate; + this.updatedMultisigHex = updatedMultisigHex; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.UpdateMultisigRequest.Builder builder = protobuf.UpdateMultisigRequest.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setUid(uid); + + Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex)); + + builder.setCurrentDate(currentDate); + + return getNetworkEnvelopeBuilder().setUpdateMultisigRequest(builder).build(); + } + + public static UpdateMultisigRequest fromProto(protobuf.UpdateMultisigRequest proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new UpdateMultisigRequest(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getUid(), + messageVersion, + proto.getCurrentDate(), + ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex())); + } + + @Override + public String toString() { + return "MultisigMessage {" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n pubKeyRing=" + pubKeyRing + + ",\n currentDate=" + currentDate + + ",\n updatedMultisigHex='" + updatedMultisigHex + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/UpdateMultisigResponse.java b/core/src/main/java/bisq/core/trade/messages/UpdateMultisigResponse.java new file mode 100644 index 00000000..3acfcc5c --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/UpdateMultisigResponse.java @@ -0,0 +1,99 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.proto.ProtoUtil; + +import java.util.Optional; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +import javax.annotation.Nullable; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class UpdateMultisigResponse extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final PubKeyRing pubKeyRing; + private final long currentDate; + @Nullable + private final String updatedMultisigHex; + + public UpdateMultisigResponse(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + String uid, + int messageVersion, + long currentDate, + String updatedMultisigHex) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + this.currentDate = currentDate; + this.updatedMultisigHex = updatedMultisigHex; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.UpdateMultisigResponse.Builder builder = protobuf.UpdateMultisigResponse.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setUid(uid); + + Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex)); + + builder.setCurrentDate(currentDate); + + return getNetworkEnvelopeBuilder().setUpdateMultisigResponse(builder).build(); + } + + public static UpdateMultisigResponse fromProto(protobuf.UpdateMultisigResponse proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new UpdateMultisigResponse(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getUid(), + messageVersion, + proto.getCurrentDate(), + ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex())); + } + + @Override + public String toString() { + return "MultisigMessage {" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n pubKeyRing=" + pubKeyRing + + ",\n currentDate=" + currentDate + + ",\n updatedMultisigHex='" + updatedMultisigHex + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java new file mode 100644 index 00000000..5bda7e48 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java @@ -0,0 +1,96 @@ +package bisq.core.trade.protocol; + +import bisq.core.trade.ArbitratorTrade; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.handlers.ErrorMessageHandler; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ArbitratorProtocol extends DisputeProtocol { + + private final ArbitratorTrade arbitratorTrade; + + public ArbitratorProtocol(ArbitratorTrade trade) { + super(trade); + this.arbitratorTrade = trade; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Incoming messages + /////////////////////////////////////////////////////////////////////////////////////////// + + // TODO: new implementation for MakerProtocol +// private void handle(InitTradeRequest message, NodeAddress peer) { +// expect(phase(Trade.Phase.INIT) +// .with(message) +// .from(peer)) +// .setup(tasks(ProcessInitTradeRequest.class, +// ApplyFilter.class, +// VerifyPeersAccountAgeWitness.class, +// MakerVerifyTakerFeePayment.class, +// MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig +// MakerSendsReadyToFundMultisigResponse.class) +// .withTimeout(30)) +// .executeTasks(); +// } + + public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { // TODO (woodser): update impl to use errorMessageHandler + expect(phase(Trade.Phase.INIT) + .with(message) + .from(peer)) + .setup(tasks( + //ApplyFilter.class, + ProcessInitTradeRequest.class)) + .executeTasks(); + } + + @Override + public void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) { + throw new RuntimeException("Not implemented"); + } + +// @Override +// public void handleTakeOfferRequest(InputsForDepositTxRequest message, +// NodeAddress peer, +// ErrorMessageHandler errorMessageHandler) { +// expect(phase(Trade.Phase.INIT) +// .with(message) +// .from(peer)) +// .setup(tasks( +// MakerProcessesInputsForDepositTxRequest.class, +// ApplyFilter.class, +// VerifyPeersAccountAgeWitness.class, +// getVerifyPeersFeePaymentClass(), +// MakerSetsLockTime.class, +// MakerCreateAndSignContract.class, +// BuyerAsMakerCreatesAndSignsDepositTx.class, +// BuyerSetupDepositTxListener.class, +// BuyerAsMakerSendsInputsForDepositTxResponse.class). +// using(new TradeTaskRunner(trade, +// () -> handleTaskRunnerSuccess(message), +// errorMessage -> { +// errorMessageHandler.handleErrorMessage(errorMessage); +// handleTaskRunnerFault(message, errorMessage); +// })) +// .withTimeout(30)) +// .executeTasks(); +// } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Message dispatcher + /////////////////////////////////////////////////////////////////////////////////////////// + +// @Override +// protected void onTradeMessage(TradeMessage message, NodeAddress peer) { +// if (message instanceof InitTradeRequest) { +// handleInitTradeRequest((InitTradeRequest) message, peer); +// } +// } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java index c9280d0e..f50bd8e4 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java @@ -21,24 +21,28 @@ import bisq.core.trade.BuyerAsMakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.InputsForDepositTxRequest; +import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; import bisq.core.trade.messages.PayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse; -import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener; import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx; -import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForDepositTxResponse; +import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx; import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract; -import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest; import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; -import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime; +import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest; +import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse; +import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener; +import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; +import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; @@ -63,33 +67,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol // Handle take offer request /////////////////////////////////////////////////////////////////////////////////////////// - @Override - public void handleTakeOfferRequest(InputsForDepositTxRequest message, - NodeAddress peer, - ErrorMessageHandler errorMessageHandler) { - expect(phase(Trade.Phase.INIT) - .with(message) - .from(peer)) - .setup(tasks( - MakerProcessesInputsForDepositTxRequest.class, - ApplyFilter.class, - VerifyPeersAccountAgeWitness.class, - getVerifyPeersFeePaymentClass(), - MakerSetsLockTime.class, - MakerCreateAndSignContract.class, - BuyerAsMakerCreatesAndSignsDepositTx.class, - BuyerSetupDepositTxListener.class, - BuyerAsMakerSendsInputsForDepositTxResponse.class). - using(new TradeTaskRunner(trade, - () -> handleTaskRunnerSuccess(message), - errorMessage -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(60)) - .executeTasks(); - } - + // TODO (woodser): remove or ignore any unsupported requests /////////////////////////////////////////////////////////////////////////////////////////// // Incoming messages Take offer process @@ -143,4 +121,91 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol protected Class getVerifyPeersFeePaymentClass() { return MakerVerifyTakerFeePayment.class; } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // MakerProtocol + /////////////////////////////////////////////////////////////////////////////////////////// + + // TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance + + @Override + public void handleInitTradeRequest(InitTradeRequest message, + NodeAddress peer, + ErrorMessageHandler errorMessageHandler) { + expect(phase(Trade.Phase.INIT) + .with(message) + .from(peer)) + .setup(tasks( + ProcessInitTradeRequest.class, + ApplyFilter.class, + VerifyPeersAccountAgeWitness.class, + MakerVerifyTakerFeePayment.class, + MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig + MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx + MakerSendsReadyToFundMultisigResponse.class). + using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + @Override + public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message, + NodeAddress sender, + ErrorMessageHandler errorMessageHandler) { + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + processModel.setTempTradingPeerNodeAddress(sender); + + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) + .with(message) + .from(sender)) + .setup(tasks( + MakerSendsReadyToFundMultisigResponse.class). + using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + @Override + public void handleDepositTxMessage(DepositTxMessage message, + NodeAddress sender, + ErrorMessageHandler errorMessageHandler) { + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + processModel.setTempTradingPeerNodeAddress(sender); + + // TODO (woodser): MakerProcessesTakerDepositTxMessage.java which verifies deposit amount = fee + security deposit (+ trade amount), or that deposit is exact amount + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) + .with(message) + .from(sender)) + .setup(tasks( + MakerVerifyTakerDepositTx.class, + MakerCreateAndSignContract.class, + MakerCreateAndPublishDepositTx.class, + MakerSetupDepositTxsListener.class). + using(new TradeTaskRunner(trade, + () -> handleTaskRunnerSuccess(message), + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + }))) + .executeTasks(); + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java index 48631776..6eecc420 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java @@ -23,10 +23,14 @@ import bisq.core.trade.BuyerAsTakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.messages.InitMultisigMessage; import bisq.core.trade.messages.InputsForDepositTxResponse; +import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; import bisq.core.trade.messages.PayoutTxPublishedMessage; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx; @@ -35,27 +39,45 @@ import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureRe import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener; import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs; import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage; import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx; +import bisq.core.trade.protocol.tasks.taker.FundMultisig; +import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx; import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse; +import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage; import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest; +import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages; +import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests; +import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest; +import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener; import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract; import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; +import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; +import bisq.common.Timer; +import bisq.common.UserThread; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; +import java.math.BigInteger; + import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroTxWallet; +import monero.wallet.model.MoneroWalletListener; + +// TODO (woodser): remove unused request handling @Slf4j public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol { + private ResultHandler takeOfferListener; + private Timer initDepositTimer; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -66,6 +88,8 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol Offer offer = checkNotNull(trade.getOffer()); processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); + + // TODO (woodser): setup deposit and payout listeners on construction for startup like before rebase? } @@ -73,22 +97,20 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol // Take offer /////////////////////////////////////////////////////////////////////////////////////////// + // TODO (woodser): this implementation is duplicated with SellerAsTakerProtocol @Override public void onTakeOffer() { - expect(phase(Trade.Phase.INIT) - .with(TakerEvent.TAKE_OFFER)) - .setup(tasks( - ApplyFilter.class, - getVerifyPeersFeePaymentClass(), - CreateTakerFeeTx.class, - BuyerAsTakerCreatesDepositTxInputs.class, - TakerSendInputsForDepositTxRequest.class) - .withTimeout(60)) - .run(() -> { - processModel.setTempTradingPeerNodeAddress(trade.getTradingPeerNodeAddress()); - processModel.getTradeManager().requestPersistence(); - }) - .executeTasks(); + System.out.println("onTakeOffer()"); + + expect(phase(Trade.Phase.INIT) + .with(TakerEvent.TAKE_OFFER) + .from(trade.getTradingPeerNodeAddress())) + .setup(tasks( + ApplyFilter.class, + TakerVerifyMakerFeePayment.class, + TakerSendInitTradeRequests.class) + .withTimeout(30)) + .executeTasks(); } @@ -172,4 +194,190 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol protected Class getVerifyPeersFeePaymentClass() { return TakerVerifyMakerFeePayment.class; } + + /////////////////////////////////////////////////////////////////////////////////////////// + // MakerProtocol + /////////////////////////////////////////////////////////////////////////////////////////// + + // TODO (woodser): these methods are duplicated with SellerAsTakerProtocol due to single inheritance + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Incoming message handling + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()"); + System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig()); + processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this + if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling + processModel.setTradeMessage(message); + if (message.isMakerReadyToFundMultisig()) { + createAndFundMultisig(message, takeOfferListener); + } else if (trade.getTakerFeeTxId() == null && !trade.getState().equals(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX)) { // TODO (woodser): use processModel.isTradeFeeTxInitiated() like check above to avoid timing issues with subsequent requests + reserveTrade(message, takeOfferListener); + } + } + + private void reserveTrade(MakerReadyToFundMultisigResponse message, ResultHandler handler) { + System.out.println("BuyerAsTakerProtocol.reserveTrade()"); + + // define wallet listener which initiates multisig deposit when trade fee tx unlocked + // TODO (woodser): this needs run for reserved trades when client is opened + // TODO (woodser): test initiating multisig when maker offline + MoneroWallet wallet = processModel.getProvider().getXmrWalletService().getWallet(); + MoneroWalletListener fundMultisigListener = new MoneroWalletListener() { + public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { + + // get updated offer fee tx + MoneroTxWallet feeTx = wallet.getTx(processModel.getTakeOfferFeeTxId()); + + // check if tx is unlocked + if (Boolean.FALSE.equals(feeTx.isLocked())) { + System.out.println("TRADE FEE TX IS UNLOCKED!!!"); + + // stop listening to wallet + wallet.removeListener(this); + + // periodically request multisig deposit until successful + Runnable requestMultisigDeposit = new Runnable() { + @Override + public void run() { + if (!processModel.isMultisigDepositInitiated()) sendMakerReadyToFundMultisigRequest(message, handler); + else initDepositTimer.stop(); + } + }; + UserThread.execute(requestMultisigDeposit); + initDepositTimer = UserThread.runPeriodically(requestMultisigDeposit, 60); + } + } + }; + + // run pipeline to publish trade fee tx + expect(new FluentProtocol.Condition(trade)) + .setup(tasks( + TakerCreateFeeTx.class, + TakerVerifyMakerFeePayment.class, + //TakerVerifyAndSignContract.class, // TODO (woodser): no... create taker fee tx, send to maker which creates contract, returns, then taker verifies and signs contract, then publishes taker fee tx + TakerPublishFeeTx.class) // TODO (woodser): need to notify maker/network of trade fee tx id to reserve trade? + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + if (handler != null) handler.handleResult(); // TODO (woodser): use handler to timeout initializing entire trade or remove use of handler and let gui indicate failure later? + wallet.addListener(fundMultisigListener); // listen for trade fee tx to become available then initiate multisig deposit // TODO: put in pipeline + }, + errorMessage -> { + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + private void sendMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigResponse message, ResultHandler handler) { + System.out.println("TakerProtocolBase.sendMakerReadyToFundMultisigRequest()"); + expect(new FluentProtocol.Condition(trade)) + .setup(tasks( + TakerVerifyMakerFeePayment.class, + TakerSendReadyToFundMultisigRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + private void createAndFundMultisig(MakerReadyToFundMultisigResponse message, ResultHandler handler) { + System.out.println("TakerProtocolBase.createAndFundMultisig()"); + expect(new FluentProtocol.Condition(trade)) + .setup(tasks( + TakerVerifyMakerFeePayment.class, + TakerVerifyAndSignContract.class, + TakerSendInitMultisigMessages.class) // will receive MultisigMessage in response + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + @Override + public void handleMultisigMessage(InitMultisigMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("TakerProtocolBase.handleMultisigMessage()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) + .with(message) + .from(sender)) + .setup(tasks( + ProcessInitMultisigMessage.class) + .using(new TradeTaskRunner(trade, + () -> { + System.out.println("handle multisig pipeline completed successfully!"); + handleTaskRunnerSuccess(message); + if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) { + processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time + fundMultisig(message, takeOfferListener); + } + }, + errorMessage -> { + System.out.println("error in handle multisig pipeline!!!: " + errorMessage); + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + takeOfferListener.handleResult(); + }))) + .executeTasks(); + } + + private void fundMultisig(InitMultisigMessage message, ResultHandler handler) { + System.out.println("TakerProtocolBase.fundMultisig()"); + expect(new FluentProtocol.Condition(trade)) + .setup(tasks( + FundMultisig.class). // will receive MultisigMessage in response + using(new TradeTaskRunner(trade, + () -> { + System.out.println("MULTISIG WALLET FUNDED!!!!"); + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + @Override + public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("TakerProtocolBase.handleDepositTxMessage()"); + processModel.setTradeMessage(message); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) + .with(message) + .from(sender)) + .setup(tasks( + TakerProcessesMakerDepositTxMessage.class, + TakerSetupDepositTxsListener.class). + using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java index 1fee12e2..f7fc3ceb 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java @@ -25,13 +25,12 @@ import bisq.core.trade.messages.PayoutTxPublishedMessage; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.protocol.tasks.UpdateMultisigWithTradingPeer; +import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener; import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener; -import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx; -import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesFinalDelayedPayoutTx; import bisq.network.p2p.NodeAddress; @@ -100,28 +99,28 @@ public abstract class BuyerProtocol extends DisputeProtocol { // mailbox message but the stored in mailbox case is not expected and the seller would try to send the message again // in the hope to reach the buyer directly. protected void handle(DepositTxAndDelayedPayoutTxMessage message, NodeAddress peer) { - expect(anyPhase(Trade.Phase.TAKER_FEE_PUBLISHED, Trade.Phase.DEPOSIT_PUBLISHED) - .with(message) - .from(peer) - .preCondition(trade.getDepositTx() == null || trade.getDelayedPayoutTx() == null, - () -> { - log.warn("We with a DepositTxAndDelayedPayoutTxMessage but we have already processed the deposit and " + - "delayed payout tx so we ignore the message. This can happen if the ACK message to the peer did not " + - "arrive and the peer repeats sending us the message. We send another ACK msg."); - stopTimeout(); - sendAckMessage(message, true, null); - removeMailboxMessageAfterProcessing(message); - })) - .setup(tasks(BuyerProcessDepositTxAndDelayedPayoutTxMessage.class, - BuyerVerifiesFinalDelayedPayoutTx.class) - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> handleTaskRunnerFault(message, errorMessage)))) - .run(() -> processModel.witnessDebugLog(trade)) - .executeTasks(); +// expect(anyPhase(Trade.Phase.TAKER_FEE_PUBLISHED, Trade.Phase.DEPOSIT_PUBLISHED) +// .with(message) +// .from(peer) +// .preCondition(trade.getDepositTx() == null || trade.getDelayedPayoutTx() == null, +// () -> { +// log.warn("We with a DepositTxAndDelayedPayoutTxMessage but we have already processed the deposit and " + +// "delayed payout tx so we ignore the message. This can happen if the ACK message to the peer did not " + +// "arrive and the peer repeats sending us the message. We send another ACK msg."); +// stopTimeout(); +// sendAckMessage(message, true, null); +// removeMailboxMessageAfterProcessing(message); +// })) +// .setup(tasks(BuyerProcessDepositTxAndDelayedPayoutTxMessage.class, +// BuyerVerifiesFinalDelayedPayoutTx.class) +// .using(new TradeTaskRunner(trade, +// () -> { +// stopTimeout(); +// handleTaskRunnerSuccess(message); +// }, +// errorMessage -> handleTaskRunnerFault(message, errorMessage)))) +// .run(() -> processModel.witnessDebugLog(trade)) +// .executeTasks(); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -135,7 +134,8 @@ public abstract class BuyerProtocol extends DisputeProtocol { .preCondition(trade.confirmPermitted())) .setup(tasks(ApplyFilter.class, getVerifyPeersFeePaymentClass(), - BuyerSignPayoutTx.class, + UpdateMultisigWithTradingPeer.class, + BuyerCreateAndSignPayoutTx.class, BuyerSetupPayoutTxListener.class, BuyerSendCounterCurrencyTransferStartedMessage.class) .using(new TradeTaskRunner(trade, @@ -147,10 +147,7 @@ public abstract class BuyerProtocol extends DisputeProtocol { errorMessageHandler.handleErrorMessage(errorMessage); handleTaskRunnerFault(event, errorMessage); }))) - .run(() -> { - trade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED); - processModel.getTradeManager().requestPersistence(); - }) + .run(() -> trade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED)) .executeTasks(); } @@ -159,12 +156,22 @@ public abstract class BuyerProtocol extends DisputeProtocol { /////////////////////////////////////////////////////////////////////////////////////////// protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) { + processModel.setTradeMessage(message); + processModel.setTempTradingPeerNodeAddress(peer); expect(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.PAYOUT_PUBLISHED) - .with(message) - .from(peer)) - .setup(tasks(BuyerProcessPayoutTxPublishedMessage.class)) - .executeTasks(); - + .with(message) + .from(peer)) + .setup(tasks( + getVerifyPeersFeePaymentClass(), + BuyerProcessPayoutTxPublishedMessage.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + handleTaskRunnerFault(message, errorMessage); + }))) + .executeTasks(); } diff --git a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java index 23807c96..d8e0e717 100644 --- a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java @@ -24,8 +24,6 @@ import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ProcessPeerPublishedDelayedPayoutTxMessage; -import bisq.core.trade.protocol.tasks.arbitration.PublishedDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.arbitration.SendPeerPublishedDelayedPayoutTxMessage; import bisq.core.trade.protocol.tasks.mediation.BroadcastMediatedPayoutTx; import bisq.core.trade.protocol.tasks.mediation.FinalizeMediatedPayoutTx; import bisq.core.trade.protocol.tasks.mediation.ProcessMediatedPayoutSignatureMessage; @@ -43,7 +41,7 @@ import bisq.common.handlers.ResultHandler; import lombok.extern.slf4j.Slf4j; @Slf4j -public class DisputeProtocol extends TradeProtocol { +public abstract class DisputeProtocol extends TradeProtocol { enum DisputeEvent implements FluentProtocol.Event { MEDIATION_RESULT_ACCEPTED, @@ -142,26 +140,26 @@ public class DisputeProtocol extends TradeProtocol { // Delayed payout tx /////////////////////////////////////////////////////////////////////////////////////////// - public void onPublishDelayedPayoutTx(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - DisputeEvent event = DisputeEvent.ARBITRATION_REQUESTED; - expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED, - Trade.Phase.FIAT_SENT, - Trade.Phase.FIAT_RECEIVED) - .with(event) - .preCondition(trade.getDelayedPayoutTx() != null)) - .setup(tasks(PublishedDelayedPayoutTx.class, - SendPeerPublishedDelayedPayoutTxMessage.class) - .using(new TradeTaskRunner(trade, - () -> { - resultHandler.handleResult(); - handleTaskRunnerSuccess(event); - }, - errorMessage -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(event, errorMessage); - }))) - .executeTasks(); - } +// public void onPublishDelayedPayoutTx(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { +// DisputeEvent event = DisputeEvent.ARBITRATION_REQUESTED; +// expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED, +// Trade.Phase.FIAT_SENT, +// Trade.Phase.FIAT_RECEIVED) +// .with(event) +// .preCondition(trade.getDelayedPayoutTx() != null)) +// .setup(tasks(PublishedDelayedPayoutTx.class, +// SendPeerPublishedDelayedPayoutTxMessage.class) +// .using(new TradeTaskRunner(trade, +// () -> { +// resultHandler.handleResult(); +// handleTaskRunnerSuccess(event); +// }, +// errorMessage -> { +// errorMessageHandler.handleErrorMessage(errorMessage); +// handleTaskRunnerFault(event, errorMessage); +// }))) +// .executeTasks(); +// } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java index 349e677a..c56750ca 100644 --- a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java @@ -18,14 +18,14 @@ package bisq.core.trade.protocol; -import bisq.core.trade.messages.InputsForDepositTxRequest; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; public interface MakerProtocol { - void handleTakeOfferRequest(InputsForDepositTxRequest message, - NodeAddress taker, - ErrorMessageHandler errorMessageHandler); + void handleInitTradeRequest(InitTradeRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler); + void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler); } diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java index ad0e10d6..5ea09006 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -33,7 +33,9 @@ import bisq.core.proto.CoreProtoResolver; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; +import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.MakerTrade; +import bisq.core.trade.TakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; import bisq.core.trade.messages.TradeMessage; @@ -69,6 +71,10 @@ import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; + + +import monero.wallet.model.MoneroTxWallet; + // Fields marked as transient are only used during protocol execution which are based on directMessages so we do not // persist them. @@ -84,9 +90,12 @@ public class ProcessModel implements Model, PersistablePayload { transient private ProcessModelServiceProvider provider; transient private TradeManager tradeManager; transient private Offer offer; + @Setter + transient private Trade trade; // Transient/Mutable - transient private Transaction takeOfferFeeTx; + @Getter + transient private MoneroTxWallet takeOfferFeeTx; @Setter transient private TradeMessage tradeMessage; @@ -107,12 +116,13 @@ public class ProcessModel implements Model, PersistablePayload { @Getter transient private Transaction depositTx; - - // Persistable Immutable - private final TradingPeer tradingPeer; - private final String offerId; - private final String accountId; - private final PubKeyRing pubKeyRing; + // Persistable Immutable (private setter only used by PB method) + private TradingPeer maker = new TradingPeer(); + private TradingPeer taker = new TradingPeer(); + private TradingPeer arbitrator = new TradingPeer(); + private String offerId; + private String accountId; + private PubKeyRing pubKeyRing; // Persistable Mutable @Nullable @@ -154,6 +164,36 @@ public class ProcessModel implements Model, PersistablePayload { @Setter private long sellerPayoutAmountFromMediation; + // Added for XMR integration + @Nullable + @Getter + @Setter + private String preparedMultisigHex; + @Nullable + @Getter + @Setter + private String madeMultisigHex; + @Nullable + @Getter + @Setter + private boolean multisigSetupComplete; + @Nullable + @Getter + @Setter + private boolean makerReadyToFundMultisig; + @Getter + @Setter + private boolean multisigDepositInitiated; + @Nullable + @Setter + private String makerPreparedDepositTxId; + @Nullable + @Setter + private String takerPreparedDepositTxId; + @Nullable + transient private MoneroTxWallet buyerSignedPayoutTx; + + // We want to indicate the user the state of the message delivery of the // CounterCurrencyTransferStartedMessage. As well we do an automatic re-send in case it was not ACKed yet. @@ -162,15 +202,17 @@ public class ProcessModel implements Model, PersistablePayload { private ObjectProperty paymentStartedMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED); public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing) { - this(offerId, accountId, pubKeyRing, new TradingPeer()); + this(offerId, accountId, pubKeyRing, new TradingPeer(), new TradingPeer(), new TradingPeer()); } - public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing, TradingPeer tradingPeer) { + public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing, TradingPeer arbitrator, TradingPeer maker, TradingPeer taker) { this.offerId = offerId; this.accountId = accountId; this.pubKeyRing = pubKeyRing; // If tradingPeer was null in persisted data from some error cases we set a new one to not cause nullPointers - this.tradingPeer = tradingPeer != null ? tradingPeer : new TradingPeer(); + this.arbitrator = arbitrator != null ? arbitrator : new TradingPeer(); + this.maker = maker != null ? maker : new TradingPeer(); + this.taker = taker != null ? taker : new TradingPeer(); } public void applyTransient(ProcessModelServiceProvider provider, @@ -189,7 +231,6 @@ public class ProcessModel implements Model, PersistablePayload { @Override public protobuf.ProcessModel toProtoMessage() { protobuf.ProcessModel.Builder builder = protobuf.ProcessModel.newBuilder() - .setTradingPeer((protobuf.TradingPeer) tradingPeer.toProtoMessage()) .setOfferId(offerId) .setAccountId(accountId) .setPubKeyRing(pubKeyRing.toProtoMessage()) @@ -199,24 +240,31 @@ public class ProcessModel implements Model, PersistablePayload { .setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name()) .setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation) .setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation); - + Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage())); + Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradingPeer) taker.toProtoMessage())); + Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradingPeer) arbitrator.toProtoMessage())); Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId); Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature))); - Optional.ofNullable(preparedDepositTx).ifPresent(e -> builder.setPreparedDepositTx(ByteString.copyFrom(preparedDepositTx))); - Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs( - ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class))); + Optional.ofNullable(makerPreparedDepositTxId).ifPresent(e -> builder.setMakerPreparedDepositTxId(makerPreparedDepositTxId)); + Optional.ofNullable(takerPreparedDepositTxId).ifPresent(e -> builder.setTakerPreparedDepositTxId(takerPreparedDepositTxId)); + Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class))); Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress); Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey))); Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage())); - Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e))); - + Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex)); + Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex)); + Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete)); + Optional.ofNullable(makerReadyToFundMultisig).ifPresent(e -> builder.setMakerReadyToFundMultisig(makerReadyToFundMultisig)); + Optional.ofNullable(multisigDepositInitiated).ifPresent(e -> builder.setMultisigSetupComplete(multisigDepositInitiated)); return builder.build(); } public static ProcessModel fromProto(protobuf.ProcessModel proto, CoreProtoResolver coreProtoResolver) { - TradingPeer tradingPeer = TradingPeer.fromProto(proto.getTradingPeer(), coreProtoResolver); + TradingPeer arbitrator = TradingPeer.fromProto(proto.getArbitrator(), coreProtoResolver); + TradingPeer maker = TradingPeer.fromProto(proto.getMaker(), coreProtoResolver); + TradingPeer taker = TradingPeer.fromProto(proto.getTaker(), coreProtoResolver); PubKeyRing pubKeyRing = PubKeyRing.fromProto(proto.getPubKeyRing()); - ProcessModel processModel = new ProcessModel(proto.getOfferId(), proto.getAccountId(), pubKeyRing, tradingPeer); + ProcessModel processModel = new ProcessModel(proto.getOfferId(), proto.getAccountId(), pubKeyRing, arbitrator, maker, taker); processModel.setChangeOutputValue(proto.getChangeOutputValue()); processModel.setUseSavingsWallet(proto.getUseSavingsWallet()); processModel.setFundsNeededForTradeAsLong(proto.getFundsNeededForTradeAsLong()); @@ -226,7 +274,6 @@ public class ProcessModel implements Model, PersistablePayload { // nullable processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId())); processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature())); - processModel.setPreparedDepositTx(ProtoUtil.byteArrayOrNullFromProto(proto.getPreparedDepositTx())); List rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ? null : proto.getRawTransactionInputsList().stream() .map(RawTransactionInput::fromProto).collect(Collectors.toList()); @@ -235,6 +282,13 @@ public class ProcessModel implements Model, PersistablePayload { processModel.setMyMultiSigPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getMyMultiSigPubKey())); processModel.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null); processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature())); + processModel.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex())); + processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex())); + processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete()); + processModel.setMakerReadyToFundMultisig(proto.getMakerReadyToFundMultisig()); + processModel.setMultisigDepositInitiated(proto.getMultisigDepositInitiated()); + processModel.setMakerPreparedDepositTxId(proto.getMakerPreparedDepositTxId()); + processModel.setTakerPreparedDepositTxId(proto.getTakerPreparedDepositTxId()); String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState()); MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString); @@ -252,9 +306,9 @@ public class ProcessModel implements Model, PersistablePayload { public void onComplete() { } - public void setTakeOfferFeeTx(Transaction takeOfferFeeTx) { + public void setTakeOfferFeeTx(MoneroTxWallet takeOfferFeeTx) { this.takeOfferFeeTx = takeOfferFeeTx; - takeOfferFeeTxId = takeOfferFeeTx.getTxId().toString(); + takeOfferFeeTxId = takeOfferFeeTx.getHash(); } @Nullable @@ -271,14 +325,11 @@ public class ProcessModel implements Model, PersistablePayload { return Coin.valueOf(fundsNeededForTradeAsLong); } - public Transaction resolveTakeOfferFeeTx(Trade trade) { - if (takeOfferFeeTx == null) { - if (!trade.isCurrencyForTakerFeeBtc()) - takeOfferFeeTx = getBsqWalletService().getTransaction(takeOfferFeeTxId); - else - takeOfferFeeTx = getBtcWalletService().getTransaction(takeOfferFeeTxId); - } - return takeOfferFeeTx; + public MoneroTxWallet resolveTakeOfferFeeTx(Trade trade) { + if (takeOfferFeeTx == null) { + takeOfferFeeTx = provider.getXmrWalletService().getWallet().getTx(takeOfferFeeTxId); + } + return takeOfferFeeTx; } public NodeAddress getMyNodeAddress() { @@ -299,6 +350,21 @@ public class ProcessModel implements Model, PersistablePayload { } } + public void setTradingPeer(TradingPeer peer) { + if (trade == null) throw new RuntimeException("Cannot set trading peer because trade is null"); + else if (trade instanceof MakerTrade) taker = peer; + else if (trade instanceof TakerTrade) maker = peer; + else throw new RuntimeException("Must be maker or taker to set trading peer"); + } + + public TradingPeer getTradingPeer() { + if (trade == null) throw new RuntimeException("Cannot get trading peer because trade is null"); + else if (trade instanceof MakerTrade) return taker; + else if (trade instanceof TakerTrade) return maker; + else if (trade instanceof ArbitratorTrade) return null; + else throw new RuntimeException("Unknown trade type: " + trade.getClass().getName()); + } + void setDepositTxSentAckMessage(AckMessage ackMessage) { MessageState messageState = ackMessage.isSuccess() ? MessageState.ACKNOWLEDGED : @@ -381,4 +447,13 @@ public class ProcessModel implements Model, PersistablePayload { public DaoFacade getDaoFacade() { return provider.getDaoFacade(); } + + public void setBuyerSignedPayoutTx(MoneroTxWallet buyerSignedPayoutTx) { + this.buyerSignedPayoutTx = buyerSignedPayoutTx; + } + + @Nullable + public MoneroTxWallet getBuyerSignedPayoutTx() { + return buyerSignedPayoutTx; + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java index 37600ecc..1b1fe0cc 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java @@ -21,6 +21,7 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoFacade; import bisq.core.filter.FilterManager; import bisq.core.offer.OpenOfferManager; @@ -44,6 +45,7 @@ public class ProcessModelServiceProvider { private final OpenOfferManager openOfferManager; private final P2PService p2PService; private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final BsqWalletService bsqWalletService; private final TradeWalletService tradeWalletService; private final DaoFacade daoFacade; @@ -61,6 +63,7 @@ public class ProcessModelServiceProvider { public ProcessModelServiceProvider(OpenOfferManager openOfferManager, P2PService p2PService, BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, BsqWalletService bsqWalletService, TradeWalletService tradeWalletService, DaoFacade daoFacade, @@ -73,10 +76,10 @@ public class ProcessModelServiceProvider { MediatorManager mediatorManager, RefundAgentManager refundAgentManager, KeyRing keyRing) { - this.openOfferManager = openOfferManager; this.p2PService = p2PService; this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.bsqWalletService = bsqWalletService; this.tradeWalletService = tradeWalletService; this.daoFacade = daoFacade; diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java index 0cdaad17..874a7276 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java @@ -21,25 +21,28 @@ package bisq.core.trade.protocol; import bisq.core.trade.SellerAsMakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.messages.InputsForDepositTxRequest; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; +import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx; import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract; -import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest; import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; -import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime; +import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest; +import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse; +import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener; +import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx; import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx; import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse; +import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; @@ -60,37 +63,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc } - /////////////////////////////////////////////////////////////////////////////////////////// - // Handle take offer request - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public void handleTakeOfferRequest(InputsForDepositTxRequest message, - NodeAddress peer, - ErrorMessageHandler errorMessageHandler) { - expect(phase(Trade.Phase.INIT) - .with(message) - .from(peer)) - .setup(tasks( - MakerProcessesInputsForDepositTxRequest.class, - ApplyFilter.class, - VerifyPeersAccountAgeWitness.class, - getVerifyPeersFeePaymentClass(), - MakerSetsLockTime.class, - MakerCreateAndSignContract.class, - SellerAsMakerCreatesUnsignedDepositTx.class, - SellerAsMakerSendsInputsForDepositTxResponse.class) - .using(new TradeTaskRunner(trade, - () -> handleTaskRunnerSuccess(message), - errorMessage -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(60)) - .executeTasks(); - } - - /////////////////////////////////////////////////////////////////////////////////////////// // Incoming messages Take offer process /////////////////////////////////////////////////////////////////////////////////////////// @@ -110,12 +82,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc .executeTasks(); } - // We keep the handler here in as well to make it more transparent which messages we expect - @Override - protected void handle(DelayedPayoutTxSignatureResponse message, NodeAddress peer) { - super.handle(message, peer); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Incoming message when buyer has clicked payment started button @@ -159,4 +125,88 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc protected Class getVerifyPeersFeePaymentClass() { return MakerVerifyTakerFeePayment.class; } + + /////////////////////////////////////////////////////////////////////////////////////////// + // MakerProtocol TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void handleInitTradeRequest(InitTradeRequest message, + NodeAddress peer, + ErrorMessageHandler errorMessageHandler) { + expect(phase(Trade.Phase.INIT) + .with(message) + .from(peer)) + .setup(tasks( + ProcessInitTradeRequest.class, + ApplyFilter.class, + VerifyPeersAccountAgeWitness.class, + MakerVerifyTakerFeePayment.class, + MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig + MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx + MakerSendsReadyToFundMultisigResponse.class). + using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + @Override + public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message, + NodeAddress sender, + ErrorMessageHandler errorMessageHandler) { + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + processModel.setTempTradingPeerNodeAddress(sender); + + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) + .with(message) + .from(sender)) + .setup(tasks( + MakerSendsReadyToFundMultisigResponse.class). + using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + @Override + public void handleDepositTxMessage(DepositTxMessage message, + NodeAddress sender, + ErrorMessageHandler errorMessageHandler) { + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + processModel.setTempTradingPeerNodeAddress(sender); + + // TODO (woodser): MakerProcessesTakerDepositTxMessage.java which verifies deposit amount = fee + security deposit (+ trade amount), or that deposit is exact amount + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) + .with(message) + .from(sender)) + .setup(tasks( + MakerVerifyTakerDepositTx.class, + MakerCreateAndSignContract.class, + MakerCreateAndPublishDepositTx.class, + MakerSetupDepositTxsListener.class). + using(new TradeTaskRunner(trade, + () -> handleTaskRunnerSuccess(message), + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + }))) + .executeTasks(); + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java index 7adf9026..de2ee516 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java @@ -22,35 +22,56 @@ import bisq.core.offer.Offer; import bisq.core.trade.SellerAsTakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; +import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.messages.InitMultisigMessage; import bisq.core.trade.messages.InputsForDepositTxResponse; +import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs; import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx; +import bisq.core.trade.protocol.tasks.taker.FundMultisig; +import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx; import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse; +import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage; import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest; +import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages; +import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests; +import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest; +import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener; import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract; import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; +import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; +import bisq.common.Timer; +import bisq.common.UserThread; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; +import java.math.BigInteger; + import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroTxWallet; +import monero.wallet.model.MoneroWalletListener; + +// TODO (woodser): remove unused request handling @Slf4j public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtocol { + private ResultHandler takeOfferListener; + private Timer initDepositTimer; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -69,17 +90,17 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc @Override public void onTakeOffer() { - expect(phase(Trade.Phase.INIT) - .with(TakerEvent.TAKE_OFFER) - .from(trade.getTradingPeerNodeAddress())) - .setup(tasks( - ApplyFilter.class, - getVerifyPeersFeePaymentClass(), - CreateTakerFeeTx.class, - SellerAsTakerCreatesDepositTxInputs.class, - TakerSendInputsForDepositTxRequest.class) - .withTimeout(60)) - .executeTasks(); + System.out.println("onTakeOffer()"); + + expect(phase(Trade.Phase.INIT) + .with(TakerEvent.TAKE_OFFER) + .from(trade.getTradingPeerNodeAddress())) + .setup(tasks( + ApplyFilter.class, + TakerVerifyMakerFeePayment.class, + TakerSendInitTradeRequests.class) + .withTimeout(30)) + .executeTasks(); } @@ -105,12 +126,6 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc .executeTasks(); } - // We keep the handler here in as well to make it more transparent which messages we expect - @Override - protected void handle(DelayedPayoutTxSignatureResponse message, NodeAddress peer) { - super.handle(message, peer); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Incoming message when buyer has clicked payment started button @@ -154,4 +169,189 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc protected Class getVerifyPeersFeePaymentClass() { return TakerVerifyMakerFeePayment.class; } + + /////////////////////////////////////////////////////////////////////////////////////////// + // TakerProtocol + /////////////////////////////////////////////////////////////////////////////////////////// + + // TODO (woodser): these methods are duplicated with BuyerAsTakerProtocol due to single inheritance + + /////////////////////////////////////////////////////////////////////////////////////////// + // Incoming message handling + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()"); + System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig()); + processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this + if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling + processModel.setTradeMessage(message); + if (message.isMakerReadyToFundMultisig()) { + createAndFundMultisig(message, takeOfferListener); + } else if (trade.getTakerFeeTxId() == null && !trade.getState().equals(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX)) { // TODO (woodser): use processModel.isTradeFeeTxInitiated() like check above to avoid timing issues with subsequent requests + reserveTrade(message, takeOfferListener); + } + } + + private void reserveTrade(MakerReadyToFundMultisigResponse message, ResultHandler handler) { + System.out.println("BuyerAsTakerProtocol.reserveTrade()"); + + // define wallet listener which initiates multisig deposit when trade fee tx unlocked + // TODO (woodser): this needs run for reserved trades when client is opened + // TODO (woodser): test initiating multisig when maker offline + MoneroWallet wallet = processModel.getProvider().getXmrWalletService().getWallet(); + MoneroWalletListener fundMultisigListener = new MoneroWalletListener() { + public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { + + // get updated offer fee tx + MoneroTxWallet feeTx = wallet.getTx(processModel.getTakeOfferFeeTxId()); + + // check if tx is unlocked + if (Boolean.FALSE.equals(feeTx.isLocked())) { + System.out.println("TRADE FEE TX IS UNLOCKED!!!"); + + // stop listening to wallet + wallet.removeListener(this); + + // periodically request multisig deposit until successful + Runnable requestMultisigDeposit = new Runnable() { + @Override + public void run() { + if (!processModel.isMultisigDepositInitiated()) sendMakerReadyToFundMultisigRequest(message, handler); + else initDepositTimer.stop(); + } + }; + UserThread.execute(requestMultisigDeposit); + initDepositTimer = UserThread.runPeriodically(requestMultisigDeposit, 60); + } + } + }; + + // run pipeline to publish trade fee tx + expect(new FluentProtocol.Condition(trade)) + .setup(tasks( + TakerCreateFeeTx.class, + TakerVerifyMakerFeePayment.class, + //TakerVerifyAndSignContract.class, // TODO (woodser): no... create taker fee tx, send to maker which creates contract, returns, then taker verifies and signs contract, then publishes taker fee tx + TakerPublishFeeTx.class) // TODO (woodser): need to notify maker/network of trade fee tx id to reserve trade? + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + if (handler != null) handler.handleResult(); // TODO (woodser): use handler to timeout initializing entire trade or remove use of handler and let gui indicate failure later? + wallet.addListener(fundMultisigListener); // listen for trade fee tx to become available then initiate multisig deposit // TODO: put in pipeline + }, + errorMessage -> { + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + private void sendMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigResponse message, ResultHandler handler) { + System.out.println("TakerProtocolBase.sendMakerReadyToFundMultisigRequest()"); + expect(new FluentProtocol.Condition(trade)) + .setup(tasks( + TakerVerifyMakerFeePayment.class, + TakerSendReadyToFundMultisigRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + private void createAndFundMultisig(MakerReadyToFundMultisigResponse message, ResultHandler handler) { + System.out.println("TakerProtocolBase.createAndFundMultisig()"); + expect(new FluentProtocol.Condition(trade)) + .setup(tasks( + TakerVerifyMakerFeePayment.class, + TakerVerifyAndSignContract.class, + TakerSendInitMultisigMessages.class) // will receive MultisigMessage in response + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + @Override + public void handleMultisigMessage(InitMultisigMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("TakerProtocolBase.handleMultisigMessage()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) + .with(message) + .from(sender)) + .setup(tasks( + ProcessInitMultisigMessage.class) + .using(new TradeTaskRunner(trade, + () -> { + System.out.println("handle multisig pipeline completed successfully!"); + handleTaskRunnerSuccess(message); + if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) { + processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time + fundMultisig(message, takeOfferListener); + } + }, + errorMessage -> { + System.out.println("error in handle multisig pipeline!!!: " + errorMessage); + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + takeOfferListener.handleResult(); + }))) + .executeTasks(); + } + + private void fundMultisig(InitMultisigMessage message, ResultHandler handler) { + System.out.println("TakerProtocolBase.fundMultisig()"); + expect(new FluentProtocol.Condition(trade)) + .setup(tasks( + FundMultisig.class). // will receive MultisigMessage in response + using(new TradeTaskRunner(trade, + () -> { + System.out.println("MULTISIG WALLET FUNDED!!!!"); + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + @Override + public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("TakerProtocolBase.handleDepositTxMessage()"); + processModel.setTradeMessage(message); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) + .with(message) + .from(sender)) + .setup(tasks( + TakerProcessesMakerDepositTxMessage.class, + TakerSetupDepositTxsListener.class). + using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java index 54d010eb..64b16177 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java @@ -20,19 +20,12 @@ package bisq.core.trade.protocol; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx; -import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage; -import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse; -import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx; -import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics; import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx; +import bisq.core.trade.protocol.tasks.seller.SellerSignAndPublishPayoutTx; import bisq.network.p2p.NodeAddress; @@ -67,31 +60,6 @@ public abstract class SellerProtocol extends DisputeProtocol { } - /////////////////////////////////////////////////////////////////////////////////////////// - // Incoming messages - /////////////////////////////////////////////////////////////////////////////////////////// - - protected void handle(DelayedPayoutTxSignatureResponse message, NodeAddress peer) { - expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED) - .with(message) - .from(peer)) - .setup(tasks(SellerProcessDelayedPayoutTxSignatureResponse.class, - SellerFinalizesDelayedPayoutTx.class, - SellerSendsDepositTxAndDelayedPayoutTxMessage.class, - SellerPublishesDepositTx.class, - SellerPublishesTradeStatistics.class)) - .run(() -> { - // We stop timeout here and don't start a new one as the - // SellerSendsDepositTxAndDelayedPayoutTxMessage repeats the send the message and has it's own - // timeout if it never succeeds. - stopTimeout(); - - //TODO still needed? If so move to witness domain - processModel.witnessDebugLog(trade); - }) - .executeTasks(); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Incoming message when buyer has clicked payment started button /////////////////////////////////////////////////////////////////////////////////////////// @@ -132,22 +100,18 @@ public abstract class SellerProtocol extends DisputeProtocol { .setup(tasks( ApplyFilter.class, getVerifyPeersFeePaymentClass(), - SellerSignAndFinalizePayoutTx.class, - SellerBroadcastPayoutTx.class, + SellerSignAndPublishPayoutTx.class, + // SellerSignAndFinalizePayoutTx.class, + // SellerBroadcastPayoutTx.class, SellerSendPayoutTxPublishedMessage.class) - .using(new TradeTaskRunner(trade, - () -> { - resultHandler.handleResult(); - handleTaskRunnerSuccess(event); - }, - (errorMessage) -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(event, errorMessage); - }))) - .run(() -> { - trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); - processModel.getTradeManager().requestPersistence(); - }) + .using(new TradeTaskRunner(trade, () -> { + resultHandler.handleResult(); + handleTaskRunnerSuccess(event); + }, (errorMessage) -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(event, errorMessage); + }))) + .run(() -> trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT)) .executeTasks(); } @@ -156,12 +120,7 @@ public abstract class SellerProtocol extends DisputeProtocol { protected void onTradeMessage(TradeMessage message, NodeAddress peer) { super.onTradeMessage(message, peer); - log.info("Received {} from {} with tradeId {} and uid {}", - message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid()); - - if (message instanceof DelayedPayoutTxSignatureResponse) { - handle((DelayedPayoutTxSignatureResponse) message, peer); - } else if (message instanceof CounterCurrencyTransferStartedMessage) { + if (message instanceof CounterCurrencyTransferStartedMessage) { handle((CounterCurrencyTransferStartedMessage) message, peer); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java index 249d0ac7..e506f281 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java @@ -17,10 +17,20 @@ package bisq.core.trade.protocol; +import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.handlers.ErrorMessageHandler; + public interface TakerProtocol { void onTakeOffer(); enum TakerEvent implements FluentProtocol.Event { TAKE_OFFER } -} + + // TODO (woodser): update after rebase + //åvoid takeAvailableOffer(ResultHandler handler); + void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer, ErrorMessageHandler errorMessageHandler); +} \ No newline at end of file diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeMessageListener.java b/core/src/main/java/bisq/core/trade/protocol/TradeMessageListener.java new file mode 100644 index 00000000..53134ecf --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/TradeMessageListener.java @@ -0,0 +1,12 @@ +package bisq.core.trade.protocol; + +import bisq.core.trade.messages.TradeMessage; + +import bisq.network.p2p.NodeAddress; + +/** + * Receives notifications of decrypted, verified trade messages. + */ +public class TradeMessageListener { + public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) { } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java index 2135aece..6b4bd794 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -22,7 +22,13 @@ import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; +import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.messages.InitMultisigMessage; import bisq.core.trade.messages.TradeMessage; +import bisq.core.trade.messages.UpdateMultisigRequest; +import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage; +import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest; +import bisq.core.util.Validator; import bisq.network.p2p.AckMessage; import bisq.network.p2p.AckMessageSourceType; @@ -37,6 +43,7 @@ import bisq.network.p2p.messaging.DecryptedMailboxListener; import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.crypto.PubKeyRing; +import bisq.common.handlers.ErrorMessageHandler; import bisq.common.proto.network.NetworkEnvelope; import bisq.common.taskrunner.Task; @@ -63,6 +70,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D public TradeProtocol(Trade trade) { this.trade = trade; this.processModel = trade.getProcessModel(); + this.processModel.setTrade(trade); // TODO (woodser): added to explicitly set trade circular loop, keep? } @@ -113,12 +121,17 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D return; } - if (!isPubKeyValid(decryptedMessageWithPubKey)) { + if (!isPubKeyValid(decryptedMessageWithPubKey, peer)) { return; } if (networkEnvelope instanceof TradeMessage) { onTradeMessage((TradeMessage) networkEnvelope, peer); + + // TODO (woodser): better way to register message notifications for trade? + if (((TradeMessage) networkEnvelope).getTradeId().equals(processModel.getOfferId())) { + trade.onVerifiedTradeMessage((TradeMessage) networkEnvelope, peer); + } } else if (networkEnvelope instanceof AckMessage) { onAckMessage((AckMessage) networkEnvelope, peer); } @@ -131,7 +144,18 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D @Override public void onMailboxMessageAdded(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress peer) { - handleMailboxCollection(Collections.singletonList(decryptedMessageWithPubKey)); + if (!isPubKeyValid(decryptedMessageWithPubKey, peer)) return; + handleMailboxCollectionSkipValidation(Collections.singletonList(decryptedMessageWithPubKey)); + } + + // TODO (woodser): this method only necessary because isPubKeyValid not called with sender argument, so it's validated before + private void handleMailboxCollectionSkipValidation(Collection collection) { + collection.stream() + .map(DecryptedMessageWithPubKey::getNetworkEnvelope) + .filter(this::isMyMessage) + .filter(e -> e instanceof MailboxMessage) + .map(e -> (MailboxMessage) e) + .forEach(this::handleMailboxMessage); } private void handleMailboxCollection(Collection collection) { @@ -182,6 +206,49 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer); + public void handleMultisigMessage(InitMultisigMessage message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + + TradeTaskRunner taskRunner = new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message, "handleMultisigMessage"); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + }); + taskRunner.addTasks( + ProcessInitMultisigMessage.class + ); + startTimeout(60); // TODO (woodser): what timeout to use? don't hardcode + taskRunner.run(); + } + + public abstract void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler); + + // TODO (woodser): update to use fluent for consistency + public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + + TradeTaskRunner taskRunner = new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(message, "handleUpdateMultisigRequest"); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(message, errorMessage); + }); + taskRunner.addTasks( + ProcessUpdateMultisigRequest.class + ); + startTimeout(60); // TODO (woodser): what timeout to use? don't hardcode + taskRunner.run(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // FluentProtocol @@ -244,7 +311,15 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D } protected void sendAckMessage(TradeMessage message, boolean result, @Nullable String errorMessage) { - PubKeyRing peersPubKeyRing = processModel.getTradingPeer().getPubKeyRing(); + + // If there was an error during offer verification, the tradingPeerNodeAddress of the trade might not be set yet. + // We can find the peer's node address in the processModel's tempTradingPeerNodeAddress in that case. + NodeAddress peer = trade.getTradingPeerNodeAddress() != null ? + trade.getTradingPeerNodeAddress() : + processModel.getTempTradingPeerNodeAddress(); + + // get destination pub key ring + PubKeyRing peersPubKeyRing = getPeersPubKeyRing(peer); if (peersPubKeyRing == null) { log.error("We cannot send the ACK message as peersPubKeyRing is null"); return; @@ -259,11 +334,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D tradeId, result, errorMessage); - // If there was an error during offer verification, the tradingPeerNodeAddress of the trade might not be set yet. - // We can find the peer's node address in the processModel's tempTradingPeerNodeAddress in that case. - NodeAddress peer = trade.getTradingPeerNodeAddress() != null ? - trade.getTradingPeerNodeAddress() : - processModel.getTempTradingPeerNodeAddress(); + log.info("Send AckMessage for {} to peer {}. tradeId={}, sourceUid={}", ackMessage.getSourceMsgClassName(), peer, tradeId, sourceUid); processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage( @@ -292,7 +363,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D ); } - /////////////////////////////////////////////////////////////////////////////////////////// // Timeout /////////////////////////////////////////////////////////////////////////////////////////// @@ -343,11 +413,27 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D // Validation /////////////////////////////////////////////////////////////////////////////////////////// + private PubKeyRing getPeersPubKeyRing(NodeAddress peer) { + if (peer.equals(trade.getArbitratorNodeAddress())) return trade.getArbitratorPubKeyRing(); + else if (peer.equals(trade.getMakerNodeAddress())) return trade.getMakerPubKeyRing(); + else if (peer.equals(trade.getTakerNodeAddress())) return trade.getTakerPubKeyRing(); + else { + log.error("Cannot get peer's pub key ring because peer is not maker, taker, or arbitrator"); + return null; + } + } + private boolean isPubKeyValid(DecryptedMessageWithPubKey message) { + MailboxMessage mailboxMessage = (MailboxMessage) message.getNetworkEnvelope(); + NodeAddress sender = mailboxMessage.getSenderNodeAddress(); + return isPubKeyValid(message, sender); + } + + private boolean isPubKeyValid(DecryptedMessageWithPubKey message, NodeAddress sender) { // We can only validate the peers pubKey if we have it already. If we are the taker we get it from the offer // Otherwise it depends on the state of the trade protocol if we have received the peers pubKeyRing already. - PubKeyRing peersPubKeyRing = processModel.getTradingPeer().getPubKeyRing(); - boolean isValid = true; + PubKeyRing peersPubKeyRing = getPeersPubKeyRing(sender); + boolean isValid = true; // TODO (woodser): this returns valid=true even if peer's pub key ring is null? if (peersPubKeyRing != null && !message.getSignaturePubKey().equals(peersPubKeyRing.getSignaturePubKey())) { isValid = false; @@ -356,7 +442,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D return isValid; } - /////////////////////////////////////////////////////////////////////////////////////////// // Private /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java index fe521e99..133da6d3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java @@ -17,6 +17,7 @@ package bisq.core.trade.protocol; +import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.BuyerAsMakerTrade; import bisq.core.trade.BuyerAsTakerTrade; import bisq.core.trade.SellerAsMakerTrade; @@ -33,6 +34,8 @@ public class TradeProtocolFactory { return new SellerAsMakerProtocol((SellerAsMakerTrade) trade); } else if (trade instanceof SellerAsTakerTrade) { return new SellerAsTakerProtocol((SellerAsTakerTrade) trade); + } else if (trade instanceof ArbitratorTrade) { + return new ArbitratorProtocol((ArbitratorTrade) trade); } else { throw new IllegalStateException("Trade not of expected type. Trade=" + trade); } diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeTaskRunner.java b/core/src/main/java/bisq/core/trade/protocol/TradeTaskRunner.java index fef4ff49..bf7e4fa2 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeTaskRunner.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeTaskRunner.java @@ -27,6 +27,6 @@ public class TradeTaskRunner extends TaskRunner { public TradeTaskRunner(Trade sharedModel, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { //noinspection unchecked - super(sharedModel, (Class) sharedModel.getClass().getSuperclass().getSuperclass(), resultHandler, errorMessageHandler); + super(sharedModel, (Class) Trade.class, resultHandler, errorMessageHandler); // TODO (woodser): getSuperClass().getSuperClass(), just to get to Trade.class? } } diff --git a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java index 5da74be2..e2df0557 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java @@ -88,6 +88,11 @@ public final class TradingPeer implements PersistablePayload { @Nullable private byte[] mediatedPayoutTxSignature; + // Added for XMR integration + @Nullable + private String preparedMultisigHex; + private String madeMultisigHex; + private String signedPayoutTxHex; public TradingPeer() { } @@ -110,6 +115,10 @@ public final class TradingPeer implements PersistablePayload { Optional.ofNullable(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e))); Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(ByteString.copyFrom(e))); Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e))); + Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex)); + Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex)); + Optional.ofNullable(signedPayoutTxHex).ifPresent(e -> builder.setSignedPayoutTxHex(signedPayoutTxHex)); + builder.setCurrentDate(currentDate); return builder.build(); } @@ -139,6 +148,9 @@ public final class TradingPeer implements PersistablePayload { tradingPeer.setAccountAgeWitnessSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignature())); tradingPeer.setCurrentDate(proto.getCurrentDate()); tradingPeer.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature())); + tradingPeer.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex())); + tradingPeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex())); + tradingPeer.setSignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex())); return tradingPeer; } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java index df3b8df2..5aa6ec5a 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java @@ -17,19 +17,18 @@ package bisq.core.trade.protocol.tasks; -import bisq.core.btc.exceptions.TxBroadcastException; -import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.trade.Trade; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionConfidence; - import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.wallet.model.MoneroTxWallet; + @Slf4j public abstract class BroadcastPayoutTx extends TradeTask { public BroadcastPayoutTx(TaskRunner taskHandler, Trade trade) { @@ -42,40 +41,33 @@ public abstract class BroadcastPayoutTx extends TradeTask { protected void run() { try { runInterceptHook(); - Transaction payoutTx = trade.getPayoutTx(); + if (true) throw new RuntimeException("BroadcastPayoutTx not implemented for xmr"); + MoneroTxWallet payoutTx = trade.getPayoutTx(); checkNotNull(payoutTx, "payoutTx must not be null"); - TransactionConfidence.ConfidenceType confidenceType = payoutTx.getConfidence().getConfidenceType(); - log.debug("payoutTx confidenceType:" + confidenceType); - if (confidenceType.equals(TransactionConfidence.ConfidenceType.BUILDING) || - confidenceType.equals(TransactionConfidence.ConfidenceType.PENDING)) { - log.debug("payoutTx was already published. confidenceType:" + confidenceType); + + if (payoutTx.isRelayed()) { + log.debug("payoutTx was already published"); setState(); complete(); } else { - processModel.getTradeWalletService().broadcastTx(payoutTx, - new TxBroadcaster.Callback() { - @Override - public void onSuccess(Transaction transaction) { - if (!completed) { - log.debug("BroadcastTx succeeded. Transaction:" + transaction); - setState(); - complete(); - } else { - log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); - } - } - - @Override - public void onFailure(TxBroadcastException exception) { - if (!completed) { - log.error("BroadcastTx failed. Error:" + exception.getMessage()); - failed(exception); - } else { - log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); - } - } - }); + try { + processModel.getProvider().getXmrWalletService().getWallet().relayTx(payoutTx); + if (!completed) { + log.debug("BroadcastTx succeeded. Transaction:" + payoutTx); + setState(); + complete(); + } else { + log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); + } + } catch (Exception e) { + if (!completed) { + log.error("BroadcastTx failed. Error:" + e.getMessage()); + failed(e); + } else { + log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); + } + } } } catch (Throwable t) { failed(t); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigMessage.java new file mode 100644 index 00000000..284c434e --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigMessage.java @@ -0,0 +1,228 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import bisq.core.trade.ArbitratorTrade; +import bisq.core.trade.MakerTrade; +import bisq.core.trade.TakerTrade; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.InitMultisigMessage; +import bisq.core.trade.protocol.TradingPeer; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.app.Version; +import bisq.common.crypto.PubKeyRing; +import bisq.common.taskrunner.TaskRunner; + +import java.util.Arrays; +import java.util.Date; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.util.Validator.checkTradeId; +import static com.google.common.base.Preconditions.checkNotNull; + + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroMultisigInitResult; + +@Slf4j +public class ProcessInitMultisigMessage extends TradeTask { + + private boolean ack1 = false; + private boolean ack2 = false; + private static Object lock = new Object(); + MoneroWallet multisigWallet; + + @SuppressWarnings({"unused"}) + public ProcessInitMultisigMessage(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + log.debug("current trade state " + trade.getState()); + InitMultisigMessage message = (InitMultisigMessage) processModel.getTradeMessage(); + checkNotNull(message); + checkTradeId(processModel.getOfferId(), message); + + System.out.println("PROCESS MULTISIG MESSAGE"); + System.out.println(message); +// System.out.println("PROCESS MULTISIG MESSAGE TRADE"); +// System.out.println(trade); + + // TODO (woodser): verify request including sender's signature in previous pipeline task + // TODO (woodser): run in separate thread to not block UI thread? + // TODO (woodser): validate message has expected sender in previous step + + // synchronize access to wallet + synchronized (lock) { + + // get peer multisig participant + TradingPeer multisigParticipant; + if (message.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) multisigParticipant = processModel.getMaker(); + else if (message.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) multisigParticipant = processModel.getTaker(); + else if (message.getSenderNodeAddress().equals(trade.getArbitratorNodeAddress())) multisigParticipant = processModel.getArbitrator(); + else throw new RuntimeException("Invalid sender to process init trade message: " + trade.getClass().getName()); + + // reconcile peer's established multisig hex with message + if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(message.getPreparedMultisigHex()); + else if (!multisigParticipant.getPreparedMultisigHex().equals(message.getPreparedMultisigHex())) throw new RuntimeException("Message's prepared multisig differs from previous messages, previous: " + multisigParticipant.getPreparedMultisigHex() + ", message: " + message.getPreparedMultisigHex()); + if (multisigParticipant.getMadeMultisigHex() == null) multisigParticipant.setMadeMultisigHex(message.getMadeMultisigHex()); + else if (!multisigParticipant.getMadeMultisigHex().equals(message.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages"); + + // get or create multisig wallet // TODO (woodser): ensure multisig wallet is created for first time + multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId()); + + // prepare multisig if applicable + boolean updateParticipants = false; + if (processModel.getPreparedMultisigHex() == null) { + System.out.println("Preparing multisig wallet!"); + processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig()); + updateParticipants = true; + } + + // make multisig if applicable + TradingPeer[] peers = getMultisigPeers(); + if (processModel.getMadeMultisigHex() == null && peers[0].getPreparedMultisigHex() != null && peers[1].getPreparedMultisigHex() != null) { + System.out.println("Making multisig wallet!"); + MoneroMultisigInitResult result = multisigWallet.makeMultisig(Arrays.asList(peers[0].getPreparedMultisigHex(), peers[1].getPreparedMultisigHex()), 2, "abctesting123"); // TODO (woodser): move this to config + processModel.setMadeMultisigHex(result.getMultisigHex()); + updateParticipants = true; + } + + // exchange multisig keys if applicable + if (!processModel.isMultisigSetupComplete() && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) { + System.out.println("Exchanging multisig wallet!"); + multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getMadeMultisigHex(), peers[1].getMadeMultisigHex()), "abctesting123"); // TODO (woodser): move this to config + processModel.setMultisigSetupComplete(true); + } + + // update multisig participants if new state to communicate + if (updateParticipants) { + + // get destination addresses and pub key rings // TODO: better way, use getMultisigPeers() + NodeAddress peer1Address; + PubKeyRing peer1PubKeyRing; + NodeAddress peer2Address; + PubKeyRing peer2PubKeyRing; + if (trade instanceof ArbitratorTrade) { + peer1Address = trade.getTakerNodeAddress(); + peer1PubKeyRing = trade.getTakerPubKeyRing(); + peer2Address = trade.getMakerNodeAddress(); + peer2PubKeyRing = trade.getMakerPubKeyRing(); + } else if (trade instanceof MakerTrade) { + peer1Address = trade.getTakerNodeAddress(); + peer1PubKeyRing = trade.getTakerPubKeyRing(); + peer2Address = trade.getArbitratorNodeAddress(); + peer2PubKeyRing = trade.getArbitratorPubKeyRing(); + } else { + peer1Address = trade.getMakerNodeAddress(); + peer1PubKeyRing = trade.getMakerPubKeyRing(); + peer2Address = trade.getArbitratorNodeAddress(); + peer2PubKeyRing = trade.getArbitratorPubKeyRing(); + } + + if (peer1Address == null) throw new RuntimeException("Peer1 address is null"); + if (peer1PubKeyRing == null) throw new RuntimeException("Peer1 pub key ring"); + if (peer2Address == null) throw new RuntimeException("Peer2 address is null"); + if (peer2PubKeyRing == null) throw new RuntimeException("Peer2 pub key ring"); + + // send to peer 1 + sendMultisigMessage(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived: peer={}; offerId={}; uid={}", message.getClass().getSimpleName(), peer1Address, message.getTradeId(), message.getUid()); + ack1 = true; + if (ack1 && ack2) completeAux(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), peer1Address, errorMessage); + appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + failed(); + } + }); + + // send to peer 2 + sendMultisigMessage(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived: peer={}; offerId={}; uid={}", message.getClass().getSimpleName(), peer2Address, message.getTradeId(), message.getUid()); + ack2 = true; + if (ack1 && ack2) completeAux(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), peer2Address, errorMessage); + appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + failed(); + } + }); + } else { + completeAux(); + } + } + } catch (Throwable t) { + failed(t); + } + } + + private TradingPeer[] getMultisigPeers() { + TradingPeer[] peers = new TradingPeer[2]; + if (trade instanceof TakerTrade) { + peers[0] = processModel.getArbitrator(); + peers[1] = processModel.getMaker(); + } else if (trade instanceof MakerTrade) { + peers[1] = processModel.getTaker(); + peers[0] = processModel.getArbitrator(); + } else { + peers[0] = processModel.getTaker(); + peers[1] = processModel.getMaker(); + } + return peers; + } + + private void sendMultisigMessage(NodeAddress recipient, PubKeyRing pubKeyRing, SendDirectMessageListener listener) { + + // create multisig message with current multisig hex + InitMultisigMessage message = new InitMultisigMessage( + processModel.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime(), + processModel.getPreparedMultisigHex(), + processModel.getMadeMultisigHex()); + + log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), recipient); + processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, message, listener); + } + + private void completeAux() { + multisigWallet.save(); + complete(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java new file mode 100644 index 00000000..e8f8e8cb --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java @@ -0,0 +1,133 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import bisq.core.exceptions.TradePriceOutOfToleranceException; +import bisq.core.offer.Offer; +import bisq.core.support.dispute.mediation.mediator.Mediator; +import bisq.core.trade.ArbitratorTrade; +import bisq.core.trade.MakerTrade; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.protocol.TradingPeer; +import bisq.core.user.User; + +import bisq.network.p2p.NodeAddress; + +import bisq.common.taskrunner.TaskRunner; + +import org.bitcoinj.core.Coin; + +import com.google.common.base.Charsets; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.util.Validator.checkTradeId; +import static bisq.core.util.Validator.nonEmptyStringOf; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class ProcessInitTradeRequest extends TradeTask { + @SuppressWarnings({"unused"}) + public ProcessInitTradeRequest(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + log.debug("current trade state " + trade.getState()); + InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage(); + checkNotNull(request); + checkTradeId(processModel.getOfferId(), request); + + System.out.println("PROCESS INIT TRADE REQUEST"); + System.out.println(request); + + User user = checkNotNull(processModel.getUser(), "User must not be null"); + + // handle maker trade + TradingPeer multisigParticipant; + if (trade instanceof MakerTrade) { + + NodeAddress arbitratorNodeAddress = checkNotNull(request.getArbitratorNodeAddress(), "payDepositRequest.getMediatorNodeAddress() must not be null"); + Mediator mediator = checkNotNull(user.getAcceptedMediatorByAddress(arbitratorNodeAddress), "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null"); // TODO (woodser): switch to arbitrator? + + multisigParticipant = processModel.getTaker(); + trade.setTakerNodeAddress(request.getTakerNodeAddress()); + trade.setTakerPubKeyRing(request.getPubKeyRing()); + trade.setArbitratorNodeAddress(request.getArbitratorNodeAddress()); + trade.setArbitratorPubKeyRing(mediator.getPubKeyRing()); + } + + // handle arbitrator trade + else if (trade instanceof ArbitratorTrade) { + // TODO (woodser): synchronize access to setting trade state in case of concurrent requests + if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) { + multisigParticipant = processModel.getMaker(); + if (!trade.getMakerNodeAddress().equals(request.getMakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): test when maker and taker do not agree, use proper handling + if (trade.getMakerPubKeyRing() == null) trade.setMakerPubKeyRing(request.getPubKeyRing()); + else if (!trade.getMakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling + } else if (request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) { + multisigParticipant = processModel.getTaker(); + if (!trade.getTakerNodeAddress().equals(request.getTakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling + if (trade.getTakerPubKeyRing() == null) trade.setTakerPubKeyRing(request.getPubKeyRing()); + else if (!trade.getTakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling + } else { + throw new RuntimeException("Sender is not trade's maker or taker"); + } + } else { + throw new RuntimeException("Invalid trade type to process init trade request: " + trade.getClass().getName()); + } + + multisigParticipant.setPaymentAccountPayload(checkNotNull(request.getPaymentAccountPayload())); + multisigParticipant.setPayoutAddressString(nonEmptyStringOf(request.getPayoutAddressString())); + multisigParticipant.setPubKeyRing(checkNotNull(request.getPubKeyRing())); + + multisigParticipant.setAccountId(nonEmptyStringOf(request.getAccountId())); + //trade.setTakerFeeTxId(nonEmptyStringOf(request.getTradeFeeTxId())); // TODO (woodser): no trade fee tx yet if creating multisig first + + // Taker has to sign offerId (he cannot manipulate that - so we avoid to have a challenge protocol for passing the nonce we want to get signed) + multisigParticipant.setAccountAgeWitnessNonce(trade.getId().getBytes(Charsets.UTF_8)); + multisigParticipant.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId()); + multisigParticipant.setCurrentDate(request.getCurrentDate()); + + Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); + try { + long takersTradePrice = request.getTradePrice(); + offer.checkTradePriceTolerance(takersTradePrice); + trade.setTradePrice(takersTradePrice); + } catch (TradePriceOutOfToleranceException e) { + failed(e.getMessage()); + } catch (Throwable e2) { + failed(e2); + } + + checkArgument(request.getTradeAmount() > 0); + trade.setTradeAmount(Coin.valueOf(request.getTradeAmount())); + + processModel.getTradeManager().requestPersistence(); + + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java index aab0bb5a..775079c4 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java @@ -17,19 +17,12 @@ package bisq.core.trade.protocol.tasks; -import bisq.core.btc.wallet.WalletService; import bisq.core.trade.Trade; -import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage; -import bisq.core.util.Validator; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Transaction; - import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class ProcessPeerPublishedDelayedPayoutTxMessage extends TradeTask { public ProcessPeerPublishedDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) { @@ -40,21 +33,22 @@ public class ProcessPeerPublishedDelayedPayoutTxMessage extends TradeTask { protected void run() { try { runInterceptHook(); + throw new RuntimeException("XMR adaptation does not support delayed payout tx"); - PeerPublishedDelayedPayoutTxMessage message = (PeerPublishedDelayedPayoutTxMessage) processModel.getTradeMessage(); - Validator.checkTradeId(processModel.getOfferId(), message); - checkNotNull(message); - - // update to the latest peer address of our peer if the message is correct - trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); - - // We add the tx to our wallet. - Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx()); - WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, processModel.getBtcWalletService().getWallet()); - - processModel.getTradeManager().requestPersistence(); - - complete(); +// PeerPublishedDelayedPayoutTxMessage message = (PeerPublishedDelayedPayoutTxMessage) processModel.getTradeMessage(); +// Validator.checkTradeId(processModel.getOfferId(), message); +// checkNotNull(message); +// +// // update to the latest peer address of our peer if the message is correct +// trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); +// +// // We add the tx to our wallet. +// Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx()); +// WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, processModel.getBtcWalletService().getWallet()); +// +// processModel.getTradeManager().requestPersistence(); +// +// complete(); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessUpdateMultisigRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessUpdateMultisigRequest.java new file mode 100644 index 00000000..67a15568 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessUpdateMultisigRequest.java @@ -0,0 +1,115 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import bisq.core.trade.Trade; +import bisq.core.trade.messages.UpdateMultisigRequest; +import bisq.core.trade.messages.UpdateMultisigResponse; + +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.app.Version; +import bisq.common.taskrunner.TaskRunner; + +import java.util.Arrays; +import java.util.Date; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.util.Validator.checkTradeId; +import static com.google.common.base.Preconditions.checkNotNull; + + + +import monero.wallet.MoneroWallet; + +@Slf4j +public class ProcessUpdateMultisigRequest extends TradeTask { + + @SuppressWarnings({"unused"}) + public ProcessUpdateMultisigRequest(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + log.debug("current trade state " + trade.getState()); + UpdateMultisigRequest request = (UpdateMultisigRequest) processModel.getTradeMessage(); + checkNotNull(request); + checkTradeId(processModel.getOfferId(), request); + MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId()); + + System.out.println("PROCESS UPDATE MULTISIG REQUEST"); + System.out.println(request); + + // check if multisig wallet needs updated + if (!multisigWallet.isMultisigImportNeeded()) { + log.warn("Multisig wallet does not need updated, so request is unexpected"); + failed(); // TODO (woodser): ignore instead fail + return; + } + + // get updated multisig hex + multisigWallet.sync(); + String updatedMultisigHex = multisigWallet.getMultisigHex(); + + // import the multisig hex + int numOutputsSigned = multisigWallet.importMultisigHex(Arrays.asList(request.getUpdatedMultisigHex())); + System.out.println("Num outputs signed by imported multisig hex: " + numOutputsSigned); + + // respond with updated multisig hex + UpdateMultisigResponse response = new UpdateMultisigResponse( + processModel.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime(), + updatedMultisigHex); + + System.out.println("SENDING MESSAGE!!!!!!!"); + System.out.println(response); + + log.info("Send {} with offerId {} and uid {} to peer {}", response.getClass().getSimpleName(), response.getTradeId(), response.getUid(), trade.getTradingPeerNodeAddress()); + System.out.println("GONNA BE BAD IF EITHER OF THESE ARE NULL"); + System.out.println(trade.getTradingPeerNodeAddress()); + System.out.println(trade.getTradingPeerPubKeyRing()); + processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), response, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at trading peer: offerId={}; uid={}", response.getClass().getSimpleName(), response.getTradeId(), response.getUid()); + + // save multisig wallet + multisigWallet.save(); // TODO (woodser): save on each step or after multisig wallets created? + complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), response.getUid(), trade.getArbitratorNodeAddress(), errorMessage); + appendToErrorMessage("Sending response failed: response=" + response + "\nerrorMessage=" + errorMessage); + failed(); + } + }); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java b/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java new file mode 100644 index 00000000..62b06e12 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java @@ -0,0 +1,82 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import bisq.core.offer.Offer; +import bisq.core.offer.OfferPayload; +import bisq.core.trade.Trade; +import bisq.core.trade.statistics.TradeStatistics2; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.network.NetworkNode; +import bisq.network.p2p.network.TorNetworkNode; + +import bisq.common.taskrunner.TaskRunner; + +import java.util.HashMap; +import java.util.Map; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class PublishTradeStatistics extends TradeTask { + public PublishTradeStatistics(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + checkNotNull(trade.getMakerDepositTx()); + checkNotNull(trade.getTakerDepositTx()); + + Map extraDataMap = new HashMap<>(); + if (processModel.getReferralIdService().getOptionalReferralId().isPresent()) { + extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get()); + } + + NodeAddress mediatorNodeAddress = checkNotNull(trade.getMediatorNodeAddress()); + // The first 4 chars are sufficient to identify a mediator. + // For testing with regtest/localhost we use the full address as its localhost and would result in + // same values for multiple mediators. + NetworkNode networkNode = model.getProcessModel().getP2PService().getNetworkNode(); + String address = networkNode instanceof TorNetworkNode ? + mediatorNodeAddress.getFullAddress().substring(0, 4) : + mediatorNodeAddress.getFullAddress(); + extraDataMap.put(TradeStatistics2.MEDIATOR_ADDRESS, address); + + Offer offer = checkNotNull(trade.getOffer()); + TradeStatistics2 tradeStatistics = new TradeStatistics2(offer.getOfferPayload(), + trade.getTradePrice(), + trade.getTradeAmount(), + trade.getDate(), + trade.getMakerDepositTxId(), + trade.getTakerDepositTxId(), + extraDataMap); + processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true); + + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupDepositTxsListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupDepositTxsListener.java new file mode 100644 index 00000000..6eb96385 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupDepositTxsListener.java @@ -0,0 +1,98 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.trade.Trade; +import bisq.core.trade.Trade.State; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroOutputWallet; +import monero.wallet.model.MoneroWalletListener; + +@Slf4j +public abstract class SetupDepositTxsListener extends TradeTask { + // Use instance fields to not get eaten up by the GC + private MoneroWalletListener depositTxListener; + private Boolean makerDepositLocked; // null when unknown, true while locked, false when unlocked + private Boolean takerDepositLocked; + + @SuppressWarnings({ "unused" }) + public SetupDepositTxsListener(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // fetch relevant trade info + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); + System.out.println("Maker prepared deposit tx id: " + processModel.getMakerPreparedDepositTxId()); + System.out.println("Taker prepared deposit tx id: " + processModel.getTakerPreparedDepositTxId()); + + // register listener with multisig wallet + depositTxListener = walletService.new HavenoWalletListener(new MoneroWalletListener() { // TODO (woodser): separate into own class file + @Override + public void onOutputReceived(MoneroOutputWallet output) { + + // ignore if no longer listening + if (depositTxListener == null) return; + + // TODO (woodser): remove this + if (output.getTx().isConfirmed() && (processModel.getMakerPreparedDepositTxId().equals(output.getTx().getHash()) || processModel.getTakerPreparedDepositTxId().equals(output.getTx().getHash()))) { + System.out.println("Deposit output for tx " + output.getTx().getHash() + " is confirmed at height " + output.getTx().getHeight()); + } + + // update locked state + if (output.getTx().getHash().equals(processModel.getMakerPreparedDepositTxId())) makerDepositLocked = output.getTx().isLocked(); + else if (output.getTx().getHash().equals(processModel.getTakerPreparedDepositTxId())) takerDepositLocked = output.getTx().isLocked(); + + // deposit txs seen when both locked states seen + if (makerDepositLocked != null && takerDepositLocked != null) { + trade.setState(getSeenState()); + } + + // confirm trade and update ui when both deposits unlock + if (Boolean.FALSE.equals(makerDepositLocked) && Boolean.FALSE.equals(takerDepositLocked)) { + System.out.println("MULTISIG DEPOSIT TXS UNLOCKED!!!"); + trade.applyDepositTxs(multisigWallet.getTx(processModel.getMakerPreparedDepositTxId()), multisigWallet.getTx(processModel.getTakerPreparedDepositTxId())); + multisigWallet.removeListener(depositTxListener); // remove listener when notified + depositTxListener = null; // prevent re-applying trade state in subsequent requests + } + } + }); + multisigWallet.addListener(depositTxListener); + + // complete immediately + complete(); + } catch (Throwable t) { + failed(t); + } + } + + protected abstract State getSeenState(); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java index 5d925ae4..213a904e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java @@ -18,22 +18,26 @@ package bisq.core.trade.protocol.tasks; import bisq.core.btc.listeners.AddressConfidenceListener; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.trade.Trade; import bisq.common.UserThread; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Address; -import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionConfidence; -import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; +import java.util.List; + import lombok.extern.slf4j.Slf4j; + + +import monero.wallet.model.MoneroTransferQuery; +import monero.wallet.model.MoneroTxQuery; +import monero.wallet.model.MoneroTxWallet; + @Slf4j public abstract class SetupPayoutTxListener extends TradeTask { // Use instance fields to not get eaten up by the GC @@ -51,34 +55,35 @@ public abstract class SetupPayoutTxListener extends TradeTask { protected void run() { try { runInterceptHook(); - if (!trade.isPayoutPublished()) { - BtcWalletService walletService = processModel.getBtcWalletService(); - String id = processModel.getOffer().getId(); - Address address = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddress(); - - TransactionConfidence confidence = walletService.getConfidenceForAddress(address); - if (isInNetwork(confidence)) { - applyConfidence(confidence); - } else { - confidenceListener = new AddressConfidenceListener(address) { - @Override - public void onTransactionConfidenceChanged(TransactionConfidence confidence) { - if (isInNetwork(confidence)) - applyConfidence(confidence); - } - }; - walletService.addAddressConfidenceListener(confidenceListener); - - tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> { - if (trade.isPayoutPublished()) { - processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId()); - - // hack to remove tradeStateSubscription at callback - UserThread.execute(this::unSubscribe); - } - }); - } - } + System.out.println("NEED TO IMPLEMENT PAYOUT TX LISTENER!"); // TODO (woodser): implement SetupPayoutTxListener +// if (!trade.isPayoutPublished()) { +// BtcWalletService walletService = processModel.getBtcWalletService(); +// String id = processModel.getOffer().getId(); +// Address address = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddress(); +// +// TransactionConfidence confidence = walletService.getConfidenceForAddress(address); +// if (isInNetwork(confidence)) { +// applyConfidence(confidence); +// } else { +// confidenceListener = new AddressConfidenceListener(address) { +// @Override +// public void onTransactionConfidenceChanged(TransactionConfidence confidence) { +// if (isInNetwork(confidence)) +// applyConfidence(confidence); +// } +// }; +// walletService.addAddressConfidenceListener(confidenceListener); +// +// tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> { +// if (trade.isPayoutPublished()) { +// processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId()); +// +// // hack to remove tradeStateSubscription at callback +// UserThread.execute(this::unSubscribe); +// } +// }); +// } +// } // we complete immediately, our object stays alive because the balanceListener is stored in the WalletService complete(); @@ -87,12 +92,23 @@ public abstract class SetupPayoutTxListener extends TradeTask { } } - private void applyConfidence(TransactionConfidence confidence) { + private void applyPayoutTx(int accountIdx) { if (trade.getPayoutTx() == null) { - Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash()); - trade.setPayoutTx(walletTx); - processModel.getTradeManager().requestPersistence(); - BtcWalletService.printTx("payoutTx received from network", walletTx); + + // get txs with transfers to payout subaddress + List txs = processModel.getProvider().getXmrWalletService().getWallet().getTxs(new MoneroTxQuery() + .setTransferQuery(new MoneroTransferQuery().setAccountIndex(accountIdx).setSubaddressIndex(0).setIsIncoming(true))); // TODO (woodser): hardcode account 0 as savings wallet, subaddress 0 trade accounts in config + + // resolve payout tx if multiple txs sent to payout address + MoneroTxWallet payoutTx; + if (txs.size() > 1) { + throw new RuntimeException("Need to resolve multiple payout txs"); // TODO (woodser) + } else { + payoutTx = txs.get(0); + } + + trade.setPayoutTx(payoutTx); + XmrWalletService.printTxs("payoutTx received from network", payoutTx); setState(); } else { log.info("We had the payout tx already set. tradeId={}, state={}", trade.getId(), trade.getState()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java new file mode 100644 index 00000000..a9708074 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java @@ -0,0 +1,125 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.TradeMessage; +import bisq.core.trade.messages.UpdateMultisigRequest; +import bisq.core.trade.messages.UpdateMultisigResponse; +import bisq.core.trade.protocol.TradeMessageListener; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.app.Version; +import bisq.common.taskrunner.TaskRunner; + +import java.util.Arrays; +import java.util.Date; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + + + +import monero.wallet.MoneroWallet; + +@Slf4j +public class UpdateMultisigWithTradingPeer extends TradeTask { + + private TradeMessageListener updateMultisigResponseListener; + + @SuppressWarnings({"unused"}) + public UpdateMultisigWithTradingPeer(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // fetch relevant trade info + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); + + // skip if multisig wallet does not need updated + if (!multisigWallet.isMultisigImportNeeded()) { + log.warn("Multisig wallet does not need updated, this should not happen"); + failed(); + return; + } + + // register listener to receive updated multisig response + updateMultisigResponseListener = new TradeMessageListener() { + @Override + public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) { + if (!(message instanceof UpdateMultisigResponse)) return; + + System.out.println("Received UpdateMultisigResponse!!!"); + System.out.println(message); + System.out.println(sender); + + UpdateMultisigResponse response = (UpdateMultisigResponse) message; + int numOutputsSigned = multisigWallet.importMultisigHex(Arrays.asList(response.getUpdatedMultisigHex())); + multisigWallet.sync(); + multisigWallet.save(); + System.out.println("Num outputs signed with imported multisig hex: " + numOutputsSigned); + trade.removeTradeMessageListener(updateMultisigResponseListener); + complete(); + } + }; + trade.addTradeMessageListener(updateMultisigResponseListener); + + // get updated multisig hex + multisigWallet.sync(); + String updatedMultisigHex = multisigWallet.getMultisigHex(); + + // message trading peer with updated multisig hex + UpdateMultisigRequest message = new UpdateMultisigRequest( + processModel.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime(), + updatedMultisigHex); + + System.out.println("SENDING MESSAGE!!!!!!!"); + System.out.println(message); + + // TODO (woodser): trade.getTradingPeerNodeAddress() and/or trade.getTradingPeerPubKeyRing() are null on restart of application, so cannot send payment to complete trade + log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getTradingPeerNodeAddress()); + processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), message, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at trading peer: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getArbitratorNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + failed(); + } + }); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/PublishedDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/PublishedDelayedPayoutTx.java index b35e1f53..38c718ce 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/PublishedDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/PublishedDelayedPayoutTx.java @@ -17,17 +17,11 @@ package bisq.core.trade.protocol.tasks.arbitration; -import bisq.core.btc.exceptions.TxBroadcastException; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.TxBroadcaster; -import bisq.core.btc.wallet.WalletService; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Transaction; - import lombok.extern.slf4j.Slf4j; @Slf4j @@ -38,33 +32,34 @@ public class PublishedDelayedPayoutTx extends TradeTask { @Override protected void run() { - try { - runInterceptHook(); - - Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); - BtcWalletService btcWalletService = processModel.getBtcWalletService(); - - // We have spent the funds from the deposit tx with the delayedPayoutTx - btcWalletService.resetCoinLockedInMultiSigAddressEntry(trade.getId()); - // We might receive funds on AddressEntry.Context.TRADE_PAYOUT so we don't swap that - - Transaction committedDelayedPayoutTx = WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, btcWalletService.getWallet()); - - processModel.getTradeWalletService().broadcastTx(committedDelayedPayoutTx, new TxBroadcaster.Callback() { - @Override - public void onSuccess(Transaction transaction) { - log.info("publishDelayedPayoutTx onSuccess " + transaction); - complete(); - } - - @Override - public void onFailure(TxBroadcastException exception) { - log.error("publishDelayedPayoutTx onFailure", exception); - failed(exception.toString()); - } - }); - } catch (Throwable t) { - failed(t); - } + throw new RuntimeException("PublishedDelayedPayoutTx not implemented for XMR"); +// try { +// runInterceptHook(); +// +// Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); +// BtcWalletService btcWalletService = processModel.getBtcWalletService(); +// +// // We have spent the funds from the deposit tx with the delayedPayoutTx +// btcWalletService.resetCoinLockedInMultiSigAddressEntry(trade.getId()); +// // We might receive funds on AddressEntry.Context.TRADE_PAYOUT so we don't swap that +// +// Transaction committedDelayedPayoutTx = WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, btcWalletService.getWallet()); +// +// processModel.getTradeWalletService().broadcastTx(committedDelayedPayoutTx, new TxBroadcaster.Callback() { +// @Override +// public void onSuccess(Transaction transaction) { +// log.info("publishDelayedPayoutTx onSuccess " + transaction); +// complete(); +// } +// +// @Override +// public void onFailure(TxBroadcastException exception) { +// log.error("publishDelayedPayoutTx onFailure", exception); +// failed(exception.toString()); +// } +// }); +// } catch (Throwable t) { +// failed(t); +// } } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerCreateAndSignPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerCreateAndSignPayoutTx.java new file mode 100644 index 00000000..f85f384e --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerCreateAndSignPayoutTx.java @@ -0,0 +1,245 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.buyer; + +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.offer.Offer; +import bisq.core.trade.MakerTrade; +import bisq.core.trade.Trade; +import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.util.ParsingUtils; + +import bisq.common.taskrunner.TaskRunner; + +import com.google.common.base.Preconditions; + +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.List; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + + + +import monero.common.MoneroError; +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroAccount; +import monero.wallet.model.MoneroDestination; +import monero.wallet.model.MoneroSubaddress; +import monero.wallet.model.MoneroTxConfig; +import monero.wallet.model.MoneroTxQuery; +import monero.wallet.model.MoneroTxWallet; + +@Slf4j +public class BuyerCreateAndSignPayoutTx extends TradeTask { + + @SuppressWarnings({"unused"}) + public BuyerCreateAndSignPayoutTx(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // validate state + Preconditions.checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); + Preconditions.checkNotNull(trade.getMakerDepositTx(), "trade.getMakerDepositTx() must not be null"); + Preconditions.checkNotNull(trade.getTakerDepositTx(), "trade.getTakerDepositTx() must not be null"); + Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); + + // gather relevant trade info + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); + String sellerPayoutAddress = processModel.getTradingPeer().getPayoutAddressString(); + String buyerPayoutAddress = trade instanceof MakerTrade ? trade.getContract().getMakerPayoutAddressString() : trade.getContract().getTakerPayoutAddressString(); + Preconditions.checkNotNull(sellerPayoutAddress, "sellerPayoutAddress must not be null"); + Preconditions.checkNotNull(buyerPayoutAddress, "buyerPayoutAddress must not be null"); + BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getTakerPreparedDepositTxId() : processModel.getMakerPreparedDepositTxId()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs trade.getDepositTxId() necessary or avoidable? + BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getMakerPreparedDepositTxId() : processModel.getTakerPreparedDepositTxId()).getIncomingAmount(); + BigInteger tradeAmount = ParsingUtils.satoshisToXmrAtomicUnits(trade.getTradeAmount().value); + BigInteger buyerPayoutAmount = buyerDepositAmount.add(tradeAmount); + BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount); + + System.out.println("sellerPayoutAddress: " + sellerPayoutAddress); + System.out.println("buyerPayoutAddress: " + buyerPayoutAddress); + System.out.println("Multisig balance: " + multisigWallet.getBalance()); + System.out.println("Multisig unlocked balance: " + multisigWallet.getUnlockedBalance()); + System.out.println("Multisig txs"); + System.out.println(multisigWallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true))); + + //System.out.println("Testing buyer payout amount: " + buyerPayoutAmount.multiply(BigInteger.valueOf(3)).divide(BigInteger.valueOf(5))); + //System.out.println("Testing seller payout amount: " + sellerPayoutAmount.multiply(BigInteger.valueOf(3)).divide(BigInteger.valueOf(5))); + //System.out.println("Testing payout amount: " + (buyerPayoutAmount.multiply(BigInteger.valueOf(3)).divide(BigInteger.valueOf(5))).add(sellerPayoutAmount.multiply(BigInteger.valueOf(3)).divide(BigInteger.valueOf(5)))); + + // create transaction to get fee estimate + if (multisigWallet.isMultisigImportNeeded()) throw new RuntimeException("Multisig import is still needed!!!"); + + System.out.println("Creating feeEstimateTx!"); + MoneroTxWallet feeEstimateTx = multisigWallet.createTx(new MoneroTxConfig() + .setAccountIndex(0) + .addDestination(new MoneroDestination(buyerPayoutAddress, buyerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5)))) // reduce payment amount to compute fee of similar tx + .addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5)))) // TODO (woodser): support addDestination(addr, amt) without new + .setRelay(false) + ); + + System.out.println("Created fee estimate tx!"); + System.out.println(feeEstimateTx); + //BigInteger estimatedFee = feeEstimateTx.getFee(); + + // attempt to create payout tx by increasing estimated fee until successful + MoneroTxWallet payoutTx = null; + int numAttempts = 0; + while (payoutTx == null && numAttempts < 50) { + BigInteger feeEstimate = feeEstimateTx.getFee().add(feeEstimateTx.getFee().multiply(BigInteger.valueOf(numAttempts)).divide(BigInteger.valueOf(10))); // add 1/10 of fee until tx is successful + try { + numAttempts++; + payoutTx = multisigWallet.createTx(new MoneroTxConfig() + .setAccountIndex(0) + .addDestination(new MoneroDestination(buyerPayoutAddress, buyerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // split fee subtracted from each payout amount + .addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // TODO (woodser): support addDestination(addr, amt) without new + .setRelay(false)); + } catch (MoneroError e) { + e.printStackTrace(); + System.out.println("FAILED TO CREATE PAYOUT TX, ITERATING..."); + } + } + + if (payoutTx == null) throw new RuntimeException("Failed to generate payout tx"); + System.out.println("PAYOUT TX GENERATED ON ATTEMPT " + numAttempts); + System.out.println(payoutTx); + + processModel.setBuyerSignedPayoutTx(payoutTx); + complete(); + } catch (Throwable t) { + failed(t); + } + } + + /** + * Generic parameterized pair. + * + * @author woodser + * + * @param the type of the first element + * @param the type of the second element + */ + public static class Pair { + + private F first; + private S second; + + public Pair(F first, S second) { + super(); + this.first = first; + this.second = second; + } + + public F getFirst() { + return first; + } + + public void setFirst(F first) { + this.first = first; + } + + public S getSecond() { + return second; + } + + public void setSecond(S second) { + this.second = second; + } + } + + public static void printBalances(MoneroWallet wallet) { + + // collect info about subaddresses + List>> pairs = new ArrayList>>(); + //if (wallet == null) wallet = TestUtils.getWalletJni(); + BigInteger balance = wallet.getBalance(); + BigInteger unlockedBalance = wallet.getUnlockedBalance(); + List accounts = wallet.getAccounts(true); + System.out.println("Wallet balance: " + balance); + System.out.println("Wallet unlocked balance: " + unlockedBalance); + for (MoneroAccount account : accounts) { + add(pairs, "ACCOUNT", account.getIndex()); + add(pairs, "SUBADDRESS", ""); + add(pairs, "LABEL", ""); + add(pairs, "ADDRESS", ""); + add(pairs, "BALANCE", account.getBalance()); + add(pairs, "UNLOCKED", account.getUnlockedBalance()); + for (MoneroSubaddress subaddress : account.getSubaddresses()) { + add(pairs, "ACCOUNT", account.getIndex()); + add(pairs, "SUBADDRESS", subaddress.getIndex()); + add(pairs, "LABEL", subaddress.getLabel()); + add(pairs, "ADDRESS", subaddress.getAddress()); + add(pairs, "BALANCE", subaddress.getBalance()); + add(pairs, "UNLOCKED", subaddress.getUnlockedBalance()); + } + } + + // convert info to csv + Integer length = null; + for (Pair> pair : pairs) { + if (length == null) length = pair.getSecond().size(); + } + + System.out.println(pairsToCsv(pairs)); + } + + private static void add(List>> pairs, String header, Object value) { + if (value == null) value = ""; + Pair> pair = null; + for (Pair> aPair : pairs) { + if (aPair.getFirst().equals(header)) { + pair = aPair; + break; + } + } + if (pair == null) { + List vals = new ArrayList(); + pair = new Pair>(header, vals); + pairs.add(pair); + } + pair.getSecond().add(value); + } + + private static String pairsToCsv(List>> pairs) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < pairs.size(); i++) { + sb.append(pairs.get(i).getFirst()); + if (i < pairs.size() - 1) sb.append(','); + else sb.append('\n'); + } + for (int i = 0; i < pairs.get(0).getSecond().size(); i++) { + for (int j = 0; j < pairs.size(); j++) { + sb.append(pairs.get(j).getSecond().get(i)); + if (j < pairs.size() - 1) sb.append(','); + else sb.append('\n'); + } + } + return sb.toString(); + } +} + + diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java index f57a6a78..b2365aa4 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java @@ -17,27 +17,13 @@ package bisq.core.trade.protocol.tasks.buyer; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.WalletService; import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.util.Validator; import bisq.common.taskrunner.TaskRunner; -import bisq.common.util.Utilities; - -import org.bitcoinj.core.Transaction; -import org.bitcoinj.wallet.Wallet; - -import java.util.Arrays; import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask { public BuyerProcessDepositTxAndDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) { @@ -48,40 +34,37 @@ public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask { protected void run() { try { runInterceptHook(); - var message = checkNotNull((DepositTxAndDelayedPayoutTxMessage) processModel.getTradeMessage()); - checkNotNull(message); - Validator.checkTradeId(processModel.getOfferId(), message); - - // To access tx confidence we need to add that tx into our wallet. - byte[] depositTxBytes = checkNotNull(message.getDepositTx()); - Transaction depositTx = processModel.getBtcWalletService().getTxFromSerializedTx(depositTxBytes); - // update with full tx - Wallet wallet = processModel.getBtcWalletService().getWallet(); - Transaction committedDepositTx = WalletService.maybeAddSelfTxToWallet(depositTx, wallet); - trade.applyDepositTx(committedDepositTx); - BtcWalletService.printTx("depositTx received from peer", committedDepositTx); - - // To access tx confidence we need to add that tx into our wallet. - byte[] delayedPayoutTxBytes = checkNotNull(message.getDelayedPayoutTx()); - checkArgument(Arrays.equals(delayedPayoutTxBytes, trade.getDelayedPayoutTxBytes()), - "mismatch between delayedPayoutTx received from peer and our one." + - "\n Expected: " + Utilities.bytesAsHexString(trade.getDelayedPayoutTxBytes()) + - "\n Received: " + Utilities.bytesAsHexString(delayedPayoutTxBytes)); - trade.applyDelayedPayoutTxBytes(delayedPayoutTxBytes); - - trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); - - // If we got already the confirmation we don't want to apply an earlier state - if (trade.getState().ordinal() < Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK.ordinal()) { - trade.setState(Trade.State.BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG); - } - - processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), - AddressEntry.Context.RESERVED_FOR_TRADE); - - processModel.getTradeManager().requestPersistence(); - - complete(); + throw new RuntimeException("BuyerProcessDepositTxAndDelayedPayoutTxMessage invalid in xmr base pair"); +// var message = checkNotNull((DepositTxAndDelayedPayoutTxMessage) processModel.getTradeMessage()); +// checkNotNull(message); +// Validator.checkTradeId(processModel.getOfferId(), message); +// +// // To access tx confidence we need to add that tx into our wallet. +// byte[] depositTxBytes = checkNotNull(message.getDepositTx()); +// Transaction depositTx = processModel.getBtcWalletService().getTxFromSerializedTx(depositTxBytes); +// // update with full tx +// Wallet wallet = processModel.getBtcWalletService().getWallet(); +// Transaction committedDepositTx = WalletService.maybeAddSelfTxToWallet(depositTx, wallet); +// trade.applyDepositTx(committedDepositTx); +// BtcWalletService.printTx("depositTx received from peer", committedDepositTx); +// +// // To access tx confidence we need to add that tx into our wallet. +// byte[] delayedPayoutTxBytes = checkNotNull(message.getDelayedPayoutTx()); +// trade.applyDelayedPayoutTxBytes(delayedPayoutTxBytes); +// BtcWalletService.printTx("delayedPayoutTx received from peer", +// checkNotNull(trade.getDelayedPayoutTx())); +// +// trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); +// +// // If we got already the confirmation we don't want to apply an earlier state +// if (trade.getState().ordinal() < Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK.ordinal()) { +// trade.setState(Trade.State.BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG); +// } +// +// processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), +// AddressEntry.Context.RESERVED_FOR_TRADE); +// +// complete(); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java index 5b546201..3bf5fa03 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java @@ -18,8 +18,7 @@ package bisq.core.trade.protocol.tasks.buyer; import bisq.core.account.sign.SignedWitness; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.WalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.trade.Trade; import bisq.core.trade.messages.PayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.TradeTask; @@ -27,13 +26,17 @@ import bisq.core.util.Validator; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Transaction; +import java.util.List; import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.wallet.MoneroWallet; + @Slf4j public class BuyerProcessPayoutTxPublishedMessage extends TradeTask { public BuyerProcessPayoutTxPublishedMessage(TaskRunner taskHandler, Trade trade) { @@ -48,18 +51,19 @@ public class BuyerProcessPayoutTxPublishedMessage extends TradeTask { PayoutTxPublishedMessage message = (PayoutTxPublishedMessage) processModel.getTradeMessage(); Validator.checkTradeId(processModel.getOfferId(), message); checkNotNull(message); - checkArgument(message.getPayoutTx() != null); + checkArgument(message.getSignedMultisigTxHex() != null); // update to the latest peer address of our peer if the message is correct trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); if (trade.getPayoutTx() == null) { - Transaction committedPayoutTx = WalletService.maybeAddNetworkTxToWallet(message.getPayoutTx(), processModel.getBtcWalletService().getWallet()); - trade.setPayoutTx(committedPayoutTx); - BtcWalletService.printTx("payoutTx received from peer", committedPayoutTx); - + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); + List txHashes = multisigWallet.submitMultisigTxHex(message.getSignedMultisigTxHex()); + trade.setPayoutTx(multisigWallet.getTx(txHashes.get(0))); + XmrWalletService.printTxs("payoutTx received from peer", trade.getPayoutTx()); trade.setState(Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG); - processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId()); + //processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId()); } else { log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId()); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java index b594d646..91d3a53d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java @@ -17,7 +17,8 @@ package bisq.core.trade.protocol.tasks.buyer; -import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.network.MessageState; import bisq.core.trade.Trade; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; @@ -60,8 +61,12 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM @Override protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) { if (message == null) { - AddressEntry payoutAddressEntry = processModel.getBtcWalletService().getOrCreateAddressEntry(tradeId, - AddressEntry.Context.TRADE_PAYOUT); + + // gather relevant info + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + final String id = processModel.getOfferId(); + XmrAddressEntry payoutAddressEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.TRADE_PAYOUT); + String payoutTxHex = processModel.getBuyerSignedPayoutTx().getTxSet().getMultisigTxHex(); // We do not use a real unique ID here as we want to be able to re-send the exact same message in case the // peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox @@ -72,7 +77,7 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM tradeId, payoutAddressEntry.getAddressString(), processModel.getMyNodeAddress(), - processModel.getPayoutTxSignature(), + payoutTxHex, trade.getCounterCurrencyTxId(), trade.getCounterCurrencyExtraData(), deterministicId diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java index 9c6bfa2b..8c1f23bc 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java @@ -18,33 +18,16 @@ package bisq.core.trade.protocol.tasks.buyer; import bisq.core.btc.listeners.AddressConfidenceListener; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.common.UserThread; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Address; -import org.bitcoinj.core.NetworkParameters; -import org.bitcoinj.core.Sha256Hash; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionConfidence; -import org.bitcoinj.core.TransactionInput; -import org.bitcoinj.core.TransactionOutPoint; - -import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; -import java.util.Objects; - import lombok.extern.slf4j.Slf4j; -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkArgument; - +// TODO (woodser): adapt to XMR or remove @Slf4j public class BuyerSetupDepositTxListener extends TradeTask { // Use instance fields to not get eaten up by the GC @@ -57,141 +40,142 @@ public class BuyerSetupDepositTxListener extends TradeTask { @Override protected void run() { - try { - runInterceptHook(); - - if (trade.getDepositTx() == null && processModel.getPreparedDepositTx() != null) { - BtcWalletService walletService = processModel.getBtcWalletService(); - NetworkParameters params = walletService.getParams(); - Transaction preparedDepositTx = new Transaction(params, processModel.getPreparedDepositTx()); - checkArgument(!preparedDepositTx.getOutputs().isEmpty(), "preparedDepositTx.getOutputs() must not be empty"); - Address depositTxAddress = preparedDepositTx.getOutput(0).getScriptPubKey().getToAddress(params); - - // For buyer as maker takerFeeTxId is null - @Nullable String takerFeeTxId = trade.getTakerFeeTxId(); - String makerFeeTxId = trade.getOffer().getOfferFeePaymentTxId(); - TransactionConfidence confidence = walletService.getConfidenceForAddress(depositTxAddress); - if (isConfTxDepositTx(confidence, params, depositTxAddress, takerFeeTxId, makerFeeTxId) && - isVisibleInNetwork(confidence)) { - applyConfidence(confidence); - } else { - confidenceListener = new AddressConfidenceListener(depositTxAddress) { - @Override - public void onTransactionConfidenceChanged(TransactionConfidence confidence) { - if (isConfTxDepositTx(confidence, params, depositTxAddress, - takerFeeTxId, makerFeeTxId) && isVisibleInNetwork(confidence)) { - applyConfidence(confidence); - } - } - }; - walletService.addAddressConfidenceListener(confidenceListener); - - tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> { - if (trade.isDepositPublished()) { - swapReservedForTradeEntry(); - - // hack to remove tradeStateSubscription at callback - UserThread.execute(this::unSubscribeAndRemoveListener); - } - }); - } - } - - // we complete immediately, our object stays alive because the balanceListener is stored in the WalletService - complete(); - } catch (Throwable t) { - failed(t); - } + throw new RuntimeException("BuyerSetupDepositTxListener needs updated for XMR"); +// try { +// runInterceptHook(); +// +// if (trade.getDepositTx() == null && processModel.getPreparedDepositTx() != null) { +// BtcWalletService walletService = processModel.getBtcWalletService(); +// NetworkParameters params = walletService.getParams(); +// Transaction preparedDepositTx = new Transaction(params, processModel.getPreparedDepositTx()); +// checkArgument(!preparedDepositTx.getOutputs().isEmpty(), "preparedDepositTx.getOutputs() must not be empty"); +// Address depositTxAddress = preparedDepositTx.getOutput(0).getScriptPubKey().getToAddress(params); +// +// // For buyer as maker takerFeeTxId is null +// @Nullable String takerFeeTxId = trade.getTakerFeeTxId(); +// String makerFeeTxId = trade.getOffer().getOfferFeePaymentTxId(); +// TransactionConfidence confidence = walletService.getConfidenceForAddress(depositTxAddress); +// if (isConfTxDepositTx(confidence, params, depositTxAddress, takerFeeTxId, makerFeeTxId) && +// isVisibleInNetwork(confidence)) { +// applyConfidence(confidence); +// } else { +// confidenceListener = new AddressConfidenceListener(depositTxAddress) { +// @Override +// public void onTransactionConfidenceChanged(TransactionConfidence confidence) { +// if (isConfTxDepositTx(confidence, params, depositTxAddress, +// takerFeeTxId, makerFeeTxId) && isVisibleInNetwork(confidence)) { +// applyConfidence(confidence); +// } +// } +// }; +// walletService.addAddressConfidenceListener(confidenceListener); +// +// tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> { +// if (trade.isDepositPublished()) { +// swapReservedForTradeEntry(); +// +// // hack to remove tradeStateSubscription at callback +// UserThread.execute(this::unSubscribeAndRemoveListener); +// } +// }); +// } +// } +// +// // we complete immediately, our object stays alive because the balanceListener is stored in the WalletService +// complete(); +// } catch (Throwable t) { +// failed(t); +// } } - // We check if the txIds of the inputs matches our maker fee tx and taker fee tx and if the depositTxAddress we - // use for the confidence lookup is use as an output address. - // This prevents that past txs which have the our depositTxAddress as input or output (deposit or payout txs) could - // be interpreted as our deposit tx. This happened because if a bug which caused re-use of the Multisig address - // entries and if both traders use the same key for multiple trades the depositTxAddress would be the same. - // We fix that bug as well but we also need to avoid that past already used addresses might be taken again - // (the Multisig flag got reverted to available in the address entry). - private boolean isConfTxDepositTx(@Nullable TransactionConfidence confidence, - NetworkParameters params, - Address depositTxAddress, - @Nullable String takerFeeTxId, - String makerFeeTxId) { - if (confidence == null) { - return false; - } - - Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash()); - long numInputMatches = walletTx.getInputs().stream() - .map(TransactionInput::getOutpoint) - .filter(Objects::nonNull) - .map(TransactionOutPoint::getHash) - .map(Sha256Hash::toString) - .filter(txId -> txId.equals(takerFeeTxId) || txId.equals(makerFeeTxId)) - .count(); - if (takerFeeTxId == null && numInputMatches != 1) { - log.warn("We got a transactionConfidenceTx which does not match our inputs. " + - "takerFeeTxId is null (valid if role is buyer as maker) and numInputMatches " + - "is not 1 as expected (for makerFeeTxId). " + - "numInputMatches={}, transactionConfidenceTx={}", - numInputMatches, walletTx); - return false; - } else if (takerFeeTxId != null && numInputMatches != 2) { - log.warn("We got a transactionConfidenceTx which does not match our inputs. " + - "numInputMatches is not 2 as expected (for makerFeeTxId and takerFeeTxId). " + - "numInputMatches={}, transactionConfidenceTx={}", - numInputMatches, walletTx); - return false; - } - - boolean isOutputMatching = walletTx.getOutputs().stream() - .map(transactionOutput -> transactionOutput.getScriptPubKey().getToAddress(params)) - .anyMatch(address -> address.equals(depositTxAddress)); - if (!isOutputMatching) { - log.warn("We got a transactionConfidenceTx which does not has the depositTxAddress " + - "as output (but as input). depositTxAddress={}, transactionConfidenceTx={}", - depositTxAddress, walletTx); - } - return isOutputMatching; - } - - private void applyConfidence(TransactionConfidence confidence) { - if (trade.getDepositTx() == null) { - Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash()); - trade.applyDepositTx(walletTx); - BtcWalletService.printTx("depositTx received from network", walletTx); - - // We don't want to trigger the tradeStateSubscription when setting the state, so we unsubscribe before - unSubscribeAndRemoveListener(); - trade.setState(Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK); - - processModel.getTradeManager().requestPersistence(); - } else { - unSubscribeAndRemoveListener(); - } - - swapReservedForTradeEntry(); - } - - private boolean isVisibleInNetwork(TransactionConfidence confidence) { - return confidence != null && - (confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.BUILDING) || - confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.PENDING)); - } - - private void swapReservedForTradeEntry() { - processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), - AddressEntry.Context.RESERVED_FOR_TRADE); - } - - private void unSubscribeAndRemoveListener() { - if (tradeStateSubscription != null) { - tradeStateSubscription.unsubscribe(); - tradeStateSubscription = null; - } - - if (confidenceListener != null) { - processModel.getBtcWalletService().removeAddressConfidenceListener(confidenceListener); - confidenceListener = null; - } - } +// // We check if the txIds of the inputs matches our maker fee tx and taker fee tx and if the depositTxAddress we +// // use for the confidence lookup is use as an output address. +// // This prevents that past txs which have the our depositTxAddress as input or output (deposit or payout txs) could +// // be interpreted as our deposit tx. This happened because if a bug which caused re-use of the Multisig address +// // entries and if both traders use the same key for multiple trades the depositTxAddress would be the same. +// // We fix that bug as well but we also need to avoid that past already used addresses might be taken again +// // (the Multisig flag got reverted to available in the address entry). +// private boolean isConfTxDepositTx(@Nullable TransactionConfidence confidence, +// NetworkParameters params, +// Address depositTxAddress, +// @Nullable String takerFeeTxId, +// String makerFeeTxId) { +// if (confidence == null) { +// return false; +// } +// +// Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash()); +// long numInputMatches = walletTx.getInputs().stream() +// .map(TransactionInput::getOutpoint) +// .filter(Objects::nonNull) +// .map(TransactionOutPoint::getHash) +// .map(Sha256Hash::toString) +// .filter(txId -> txId.equals(takerFeeTxId) || txId.equals(makerFeeTxId)) +// .count(); +// if (takerFeeTxId == null && numInputMatches != 1) { +// log.warn("We got a transactionConfidenceTx which does not match our inputs. " + +// "takerFeeTxId is null (valid if role is buyer as maker) and numInputMatches " + +// "is not 1 as expected (for makerFeeTxId). " + +// "numInputMatches={}, transactionConfidenceTx={}", +// numInputMatches, walletTx); +// return false; +// } else if (takerFeeTxId != null && numInputMatches != 2) { +// log.warn("We got a transactionConfidenceTx which does not match our inputs. " + +// "numInputMatches is not 2 as expected (for makerFeeTxId and takerFeeTxId). " + +// "numInputMatches={}, transactionConfidenceTx={}", +// numInputMatches, walletTx); +// return false; +// } +// +// boolean isOutputMatching = walletTx.getOutputs().stream() +// .map(transactionOutput -> transactionOutput.getScriptPubKey().getToAddress(params)) +// .anyMatch(address -> address.equals(depositTxAddress)); +// if (!isOutputMatching) { +// log.warn("We got a transactionConfidenceTx which does not has the depositTxAddress " + +// "as output (but as input). depositTxAddress={}, transactionConfidenceTx={}", +// depositTxAddress, walletTx); +// } +// return isOutputMatching; +// } +// +// private void applyConfidence(TransactionConfidence confidence) { +// if (trade.getDepositTx() == null) { +// Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash()); +// trade.applyDepositTx(walletTx); +// BtcWalletService.printTx("depositTx received from network", walletTx); +// +// // We don't want to trigger the tradeStateSubscription when setting the state, so we unsubscribe before +// unSubscribeAndRemoveListener(); +// trade.setState(Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK); +// +// processModel.getTradeManager().requestPersistence(); +// } else { +// unSubscribeAndRemoveListener(); +// } +// +// swapReservedForTradeEntry(); +// } +// +// private boolean isVisibleInNetwork(TransactionConfidence confidence) { +// return confidence != null && +// (confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.BUILDING) || +// confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.PENDING)); +// } +// +// private void swapReservedForTradeEntry() { +// processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), +// AddressEntry.Context.RESERVED_FOR_TRADE); +// } +// +// private void unSubscribeAndRemoveListener() { +// if (tradeStateSubscription != null) { +// tradeStateSubscription.unsubscribe(); +// tradeStateSubscription = null; +// } +// +// if (confidenceListener != null) { +// processModel.getBtcWalletService().removeAddressConfidenceListener(confidenceListener); +// confidenceListener = null; +// } +// } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java deleted file mode 100644 index 70dd1b80..00000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.buyer; - -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.offer.Offer; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; - -import bisq.common.taskrunner.TaskRunner; - -import org.bitcoinj.core.Coin; -import org.bitcoinj.crypto.DeterministicKey; - -import com.google.common.base.Preconditions; - -import java.util.Arrays; - -import lombok.extern.slf4j.Slf4j; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -@Slf4j -public class BuyerSignPayoutTx extends TradeTask { - - public BuyerSignPayoutTx(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - Preconditions.checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); - Preconditions.checkNotNull(trade.getDepositTx(), "trade.getDepositTx() must not be null"); - Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); - - BtcWalletService walletService = processModel.getBtcWalletService(); - String id = processModel.getOffer().getId(); - - Coin buyerPayoutAmount = offer.getBuyerSecurityDeposit().add(trade.getTradeAmount()); - Coin sellerPayoutAmount = offer.getSellerSecurityDeposit(); - - String buyerPayoutAddressString = walletService.getOrCreateAddressEntry(id, - AddressEntry.Context.TRADE_PAYOUT).getAddressString(); - final String sellerPayoutAddressString = processModel.getTradingPeer().getPayoutAddressString(); - - DeterministicKey buyerMultiSigKeyPair = walletService.getMultiSigKeyPair(id, processModel.getMyMultiSigPubKey()); - - byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey(); - checkArgument(Arrays.equals(buyerMultiSigPubKey, - walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), - "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); - - byte[] payoutTxSignature = processModel.getTradeWalletService().buyerSignsPayoutTx( - trade.getDepositTx(), - buyerPayoutAmount, - sellerPayoutAmount, - buyerPayoutAddressString, - sellerPayoutAddressString, - buyerMultiSigKeyPair, - buyerMultiSigPubKey, - sellerMultiSigPubKey); - processModel.setPayoutTxSignature(payoutTxSignature); - - processModel.getTradeManager().requestPersistence(); - - complete(); - } catch (Throwable t) { - failed(t); - } - } -} - diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java index b2a5eaf1..05efe134 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesFinalDelayedPayoutTx.java @@ -18,17 +18,12 @@ package bisq.core.trade.protocol.tasks.buyer; import bisq.core.trade.Trade; -import bisq.core.trade.TradeDataValidation; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Transaction; - import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class BuyerVerifiesFinalDelayedPayoutTx extends TradeTask { public BuyerVerifiesFinalDelayedPayoutTx(TaskRunner taskHandler, Trade trade) { @@ -40,22 +35,24 @@ public class BuyerVerifiesFinalDelayedPayoutTx extends TradeTask { try { runInterceptHook(); - Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); - checkNotNull(delayedPayoutTx, "trade.getDelayedPayoutTx() must not be null"); - // Check again tx - TradeDataValidation.validateDelayedPayoutTx(trade, - delayedPayoutTx, - processModel.getDaoFacade(), - processModel.getBtcWalletService()); + throw new RuntimeException("BuyerVerifiesFinalDelayedPayoutTx not applicable for xmr"); - // Now as we know the deposit tx we can also verify the input - Transaction depositTx = trade.getDepositTx(); - checkNotNull(depositTx, "trade.getDepositTx() must not be null"); - TradeDataValidation.validatePayoutTxInput(depositTx, delayedPayoutTx); - - complete(); - } catch (TradeDataValidation.ValidationException e) { - failed(e.getMessage()); +// Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); +// checkNotNull(delayedPayoutTx, "trade.getDelayedPayoutTx() must not be null"); +// // Check again tx +// TradeDataValidation.validateDelayedPayoutTx(trade, +// delayedPayoutTx, +// processModel.getDaoFacade(), +// processModel.getBtcWalletService()); +// +// // Now as we know the deposit tx we can also verify the input +// Transaction depositTx = trade.getDepositTx(); +// checkNotNull(depositTx, "trade.getDepositTx() must not be null"); +// TradeDataValidation.validatePayoutTxInput(depositTx, delayedPayoutTx); +// +// complete(); +// } catch (TradeDataValidation.ValidationException e) { +// failed(e.getMessage()); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java index 0277ca7b..6600a674 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java @@ -17,18 +17,13 @@ package bisq.core.trade.protocol.tasks.buyer_as_taker; -import bisq.core.btc.model.InputsAndChangeOutput; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Coin; - import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class BuyerAsTakerCreatesDepositTxInputs extends TradeTask { @@ -41,21 +36,23 @@ public class BuyerAsTakerCreatesDepositTxInputs extends TradeTask { try { runInterceptHook(); - Coin txFee = trade.getTxFee(); - Coin takerInputAmount = checkNotNull(trade.getOffer()).getBuyerSecurityDeposit() - .add(txFee) - .add(txFee); // 2 times the fee as we need it for payout tx as well - InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositTxInputs( - processModel.getTakeOfferFeeTx(), - takerInputAmount, - txFee); - processModel.setRawTransactionInputs(result.rawTransactionInputs); - processModel.setChangeOutputValue(result.changeOutputValue); - processModel.setChangeOutputAddress(result.changeOutputAddress); + throw new RuntimeException("Outputs not communicated in xmr integration"); - processModel.getTradeManager().requestPersistence(); +// processModel.getTradeManager().requestPersistence(); - complete(); +// Coin txFee = trade.getTxFee(); +// Coin takerInputAmount = checkNotNull(trade.getOffer()).getBuyerSecurityDeposit() +// .add(txFee) +// .add(txFee); // 2 times the fee as we need it for payout tx as well +// InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositTxInputs( +// processModel.getTakeOfferFeeTx(), +// takerInputAmount, +// txFee); +// processModel.setRawTransactionInputs(result.rawTransactionInputs); +// processModel.setChangeOutputValue(result.changeOutputValue); +// processModel.setChangeOutputAddress(result.changeOutputAddress); +// +// complete(); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java index ab03ece4..30a9703e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java @@ -18,16 +18,10 @@ package bisq.core.trade.protocol.tasks.buyer_as_taker; import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxMessage; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.network.p2p.NodeAddress; -import bisq.network.p2p.SendDirectMessageListener; - import bisq.common.taskrunner.TaskRunner; -import java.util.UUID; - import lombok.extern.slf4j.Slf4j; @Slf4j @@ -40,43 +34,42 @@ public class BuyerAsTakerSendsDepositTxMessage extends TradeTask { protected void run() { try { runInterceptHook(); - if (processModel.getDepositTx() != null) { - // Remove witnesses from the sent depositTx, so that the seller can still compute the final - // tx id, but cannot publish it before providing the buyer with a signed delayed payout tx. - DepositTxMessage message = new DepositTxMessage(UUID.randomUUID().toString(), - processModel.getOfferId(), - processModel.getMyNodeAddress(), - processModel.getDepositTx().bitcoinSerialize(false)); - - NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); - log.info("Send {} to peer {}. tradeId={}, uid={}", - message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); - processModel.getP2PService().sendEncryptedDirectMessage( - peersNodeAddress, - processModel.getTradingPeer().getPubKeyRing(), - message, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at peer {}. tradeId={}, uid={}", - message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); - complete(); - } - - @Override - public void onFault(String errorMessage) { - log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", - message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); - - appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - } else { - log.error("processModel.getDepositTx() = {}", processModel.getDepositTx()); - failed("DepositTx is null"); - } + throw new RuntimeException("BuyerAsTakerSendsDepositTxMessage not implemented for xmr"); +// if (processModel.getDepositTx() != null) { +// DepositTxMessage message = new DepositTxMessage(UUID.randomUUID().toString(), +// processModel.getOfferId(), +// processModel.getMyNodeAddress(), +// processModel.getDepositTx().bitcoinSerialize()); +// +// NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); +// log.info("Send {} to peer {}. tradeId={}, uid={}", +// message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); +// processModel.getP2PService().sendEncryptedDirectMessage( +// peersNodeAddress, +// processModel.getTradingPeer().getPubKeyRing(), +// message, +// new SendDirectMessageListener() { +// @Override +// public void onArrived() { +// log.info("{} arrived at peer {}. tradeId={}, uid={}", +// message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); +// complete(); +// } +// +// @Override +// public void onFault(String errorMessage) { +// log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", +// message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); +// +// appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); +// failed(); +// } +// } +// ); +// } else { +// log.error("processModel.getDepositTx() = " + processModel.getDepositTx()); +// failed("DepositTx is null"); +// } } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java index 63883ae4..26cc5456 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java @@ -17,29 +17,13 @@ package bisq.core.trade.protocol.tasks.buyer_as_taker; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.model.RawTransactionInput; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.offer.Offer; import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.common.crypto.Hash; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class BuyerAsTakerSignsDepositTx extends TradeTask { @@ -51,6 +35,7 @@ public class BuyerAsTakerSignsDepositTx extends TradeTask { protected void run() { try { runInterceptHook(); + throw new RuntimeException("BuyerAsTakerSignsDepositTx not implemented for xmr"); /* log.debug("\n\n------------------------------------------------------------\n" + "Contract as json\n" @@ -58,46 +43,38 @@ public class BuyerAsTakerSignsDepositTx extends TradeTask { + "\n------------------------------------------------------------\n");*/ - byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson())); - trade.setContractHash(contractHash); - List buyerInputs = checkNotNull(processModel.getRawTransactionInputs(), "buyerInputs must not be null"); - BtcWalletService walletService = processModel.getBtcWalletService(); - String id = processModel.getOffer().getId(); - - Optional addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG); - checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present"); - AddressEntry buyerMultiSigAddressEntry = addressEntryOptional.get(); - Coin buyerInput = Coin.valueOf(buyerInputs.stream().mapToLong(input -> input.value).sum()); - - Coin multiSigValue = buyerInput.subtract(trade.getTxFee().multiply(2)); - processModel.getBtcWalletService().setCoinLockedInMultiSigAddressEntry(buyerMultiSigAddressEntry, multiSigValue.value); - walletService.saveAddressEntryList(); - - Offer offer = trade.getOffer(); - Coin msOutputAmount = offer.getBuyerSecurityDeposit().add(offer.getSellerSecurityDeposit()).add(trade.getTxFee()) - .add(checkNotNull(trade.getTradeAmount())); - - TradingPeer tradingPeer = processModel.getTradingPeer(); - byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey(); - checkArgument(Arrays.equals(buyerMultiSigPubKey, buyerMultiSigAddressEntry.getPubKey()), - "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - - List sellerInputs = checkNotNull(tradingPeer.getRawTransactionInputs()); - byte[] sellerMultiSigPubKey = tradingPeer.getMultiSigPubKey(); - Transaction depositTx = processModel.getTradeWalletService().takerSignsDepositTx( - false, - contractHash, - processModel.getPreparedDepositTx(), - msOutputAmount, - buyerInputs, - sellerInputs, - buyerMultiSigPubKey, - sellerMultiSigPubKey); - processModel.setDepositTx(depositTx); - - processModel.getTradeManager().requestPersistence(); - - complete(); +// byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson())); +// trade.setContractHash(contractHash); +// List buyerInputs = checkNotNull(processModel.getRawTransactionInputs(), "buyerInputs must not be null"); +// BtcWalletService walletService = processModel.getBtcWalletService(); +// String id = processModel.getOffer().getId(); +// +// Optional addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG); +// checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present"); +// AddressEntry buyerMultiSigAddressEntry = addressEntryOptional.get(); +// Coin buyerInput = Coin.valueOf(buyerInputs.stream().mapToLong(input -> input.value).sum()); +// +// buyerMultiSigAddressEntry.setCoinLockedInMultiSig(buyerInput.subtract(trade.getTxFee().multiply(2))); +// walletService.saveAddressEntryList(); +// +// TradingPeer tradingPeer = processModel.getTradingPeer(); +// byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey(); +// checkArgument(Arrays.equals(buyerMultiSigPubKey, buyerMultiSigAddressEntry.getPubKey()), +// "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); +// +// List sellerInputs = checkNotNull(tradingPeer.getRawTransactionInputs()); +// byte[] sellerMultiSigPubKey = tradingPeer.getMultiSigPubKey(); +// Transaction depositTx = processModel.getTradeWalletService().takerSignsDepositTx( +// false, +// contractHash, +// processModel.getPreparedDepositTx(), +// buyerInputs, +// sellerInputs, +// buyerMultiSigPubKey, +// sellerMultiSigPubKey); +// processModel.setDepositTx(depositTx); +// +// complete(); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndPublishDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndPublishDepositTx.java new file mode 100644 index 00000000..b1ef6243 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndPublishDepositTx.java @@ -0,0 +1,186 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.maker; + +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.util.Validator; + +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.UserThread; +import bisq.common.taskrunner.TaskRunner; + +import org.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.Subscription; + +import java.math.BigInteger; + +import java.util.List; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroTxConfig; +import monero.wallet.model.MoneroTxWallet; + +@Slf4j +public class MakerCreateAndPublishDepositTx extends TradeTask { + private Subscription tradeStateSubscription; + + @SuppressWarnings({"unused"}) + public MakerCreateAndPublishDepositTx(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + System.out.println("MakerCreateAndPublishDepositTx"); + log.debug("current trade state " + trade.getState()); + DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); + Validator.checkTradeId(processModel.getOfferId(), message); + checkNotNull(message); + trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); + if (trade.getMakerDepositTxId() != null) throw new RuntimeException("Maker deposit tx already set for trade " + trade.getId() + ", this should not happen"); // TODO: ignore and nack bad requests to not show on client + + System.out.println(message); + + // decide who goes first + boolean takerGoesFirst = true; // TODO (woodser): based on rep? + + // send deposit tx after taker + if (takerGoesFirst) { + + // verify taker's deposit tx + // TODO (woodser): taker needs to prove tx to address, cannot claim tx id, verify tx id seen in pool + // TODO (woodser): need to wait for tx to be seen by multisig wallet, might not be in wallet at first + if (message.getDepositTxId() == null) throw new RuntimeException("Taker must provide deposit tx id"); + processModel.setTakerPreparedDepositTxId(message.getDepositTxId()); + + // collect parameters for transfer to multisig + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + MoneroWallet wallet = walletService.getWallet(); + MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); + String multisigAddress = multisigWallet.getPrimaryAddress(); + + // send deposit tx + XmrAddressEntry addressEntry = walletService.getAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.RESERVED_FOR_TRADE).get(); + int accountIndex = addressEntry.getAccountIndex(); + if (!wallet.getBalance(accountIndex).equals(wallet.getUnlockedBalance(accountIndex)) || wallet.getBalance(accountIndex).equals(BigInteger.valueOf(0))) { + throw new RuntimeException("Reserved trade account balance expected to be fully available"); + } + System.out.println("Sweeping unlocked balance in account " + accountIndex + ": " + wallet.getUnlockedBalance(accountIndex)); + List txs = wallet.sweepUnlocked(new MoneroTxConfig() + .setAccountIndex(accountIndex) + .setAddress(multisigAddress) + .setCanSplit(false) + .setRelay(true)); + if (txs.size() != 1) throw new RuntimeException("Sweeping reserved trade account to multisig expected to create exactly 1 transaction"); + MoneroTxWallet makerDepositTx = txs.get(0); + processModel.setMakerPreparedDepositTxId(makerDepositTx.getHash()); + //trade.setState(Trade.State.SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG); // TODO (wooder): state for MAKER_TRANSFERRED_TO_MULTISIG? + System.out.println("SUCCESSFULLY SWEPT RESERVED TRADE ACCOUNT TO MULTISIG"); + System.out.println(txs.get(0)); + + // apply published transaction which notifies ui + applyPublishedDepositTxs(makerDepositTx, multisigWallet.getTx(processModel.getTakerPreparedDepositTxId())); + + // notify trade state subscription when deposit published + tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> { + if (trade.isDepositPublished()) { + swapReservedForTradeEntry(); + UserThread.execute(this::unSubscribe); // hack to remove tradeStateSubscription at callback + } + }); + } + + // send deposit tx before taker + else { + throw new RuntimeException("Maker goes first not implemented"); + } + + // create message to notify taker of maker's deposit tx + DepositTxMessage request = new DepositTxMessage( + UUID.randomUUID().toString(), + processModel.getOffer().getId(), + processModel.getMyNodeAddress(), + null, + trade.getMakerDepositTxId()); + + // notify taker of maker's deposit tx + log.info("Send {} with offerId {} and uid {} to maker {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getTakerNodeAddress()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getTakerNodeAddress(), + trade.getTakerPubKeyRing(), + request, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at taker: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); + complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getTakerNodeAddress(), errorMessage); + appendToErrorMessage("Sending request failed: request=" + request + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + + // complete + processModel.getTradeManager().requestPersistence(); + } catch (Throwable t) { + failed(t); + } + } + + private void applyPublishedDepositTxs(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) { + if (trade.getMakerDepositTx() == null && trade.getTakerDepositTx() == null) { + trade.applyDepositTxs(makerDepositTx, takerDepositTx); + XmrWalletService.printTxs("depositTxs received from network", makerDepositTx, takerDepositTx); + trade.setState(Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK); // TODO (woodser): MAKER_PUBLISHED_DEPOSIT_TX + } else { + log.info("We got the deposit tx already set from MakerCreateAndPublishDepositTx. tradeId={}, state={}", trade.getId(), trade.getState()); + } + + swapReservedForTradeEntry(); + + // need delay as it can be called inside the listener handler before listener and tradeStateSubscription are actually set. + UserThread.execute(this::unSubscribe); + } + + private void swapReservedForTradeEntry() { + log.info("swapReservedForTradeEntry"); + processModel.getProvider().getXmrWalletService().swapTradeEntryToAvailableEntry(trade.getId(), XmrAddressEntry.Context.RESERVED_FOR_TRADE); + } + + private void unSubscribe() { + if (tradeStateSubscription != null) tradeStateSubscription.unsubscribe(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java index 4c9d05c4..3d0391ca 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java @@ -17,11 +17,12 @@ package bisq.core.trade.protocol.tasks.maker; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.trade.BuyerAsMakerTrade; import bisq.core.trade.Contract; import bisq.core.trade.Trade; +import bisq.core.trade.messages.DepositTxMessage; import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.tasks.TradeTask; @@ -46,29 +47,33 @@ public class MakerCreateAndSignContract extends TradeTask { protected void run() { try { runInterceptHook(); - String takerFeeTxId = checkNotNull(processModel.getTakeOfferFeeTxId()); + + DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); + trade.setTakerFeeTxId(message.getTradeFeeTxId()); // TODO (woodser): must verify trade fee tx. set up contract before taker deposits? + //String takerFeeTxId = checkNotNull(processModel.getTakeOfferFeeTxId()); TradingPeer taker = processModel.getTradingPeer(); boolean isBuyerMakerAndSellerTaker = trade instanceof BuyerAsMakerTrade; - NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ? - processModel.getMyNodeAddress() : processModel.getTempTradingPeerNodeAddress(); - NodeAddress sellerNodeAddress = isBuyerMakerAndSellerTaker ? - processModel.getTempTradingPeerNodeAddress() : processModel.getMyNodeAddress(); - BtcWalletService walletService = processModel.getBtcWalletService(); + NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getMyNodeAddress() : processModel.getTempTradingPeerNodeAddress(); + NodeAddress sellerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyNodeAddress(); + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); String id = processModel.getOffer().getId(); - AddressEntry makerAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); - byte[] makerMultiSigPubKey = makerAddressEntry.getPubKey(); + // get maker payout address + XmrAddressEntry makerPayoutEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.TRADE_PAYOUT); + checkNotNull(taker.getPayoutAddressString(), "taker.getPayoutAddressString()"); + +// AddressEntry makerAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); +// byte[] makerMultiSigPubKey = makerAddressEntry.getPubKey(); - AddressEntry takerAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT); Contract contract = new Contract( processModel.getOffer().getOfferPayload(), checkNotNull(trade.getTradeAmount()).value, trade.getTradePrice().getValue(), - takerFeeTxId, + //takerFeeTxId, // TODO (woodser): include taker fee tx id? buyerNodeAddress, sellerNodeAddress, - trade.getMediatorNodeAddress(), + trade.getArbitratorNodeAddress(), isBuyerMakerAndSellerTaker, processModel.getAccountId(), checkNotNull(taker.getAccountId()), @@ -76,12 +81,9 @@ public class MakerCreateAndSignContract extends TradeTask { checkNotNull(taker.getPaymentAccountPayload()), processModel.getPubKeyRing(), checkNotNull(taker.getPubKeyRing()), - takerAddressEntry.getAddressString(), + makerPayoutEntry.getAddressString(), checkNotNull(taker.getPayoutAddressString()), - makerMultiSigPubKey, - checkNotNull(taker.getMultiSigPubKey()), - trade.getLockTime(), - trade.getRefundAgentNodeAddress() + trade.getLockTime() ); String contractAsJson = Utilities.objectToJson(contract); String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); @@ -93,7 +95,6 @@ public class MakerCreateAndSignContract extends TradeTask { byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson())); trade.setContractHash(contractHash); - processModel.setMyMultiSigPubKey(makerMultiSigPubKey); processModel.getTradeManager().requestPersistence(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateFeeTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateFeeTx.java new file mode 100644 index 00000000..7d0368a7 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateFeeTx.java @@ -0,0 +1,151 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.maker; + +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.dao.exceptions.DaoDisabledException; +import bisq.core.offer.Offer; +import bisq.core.offer.placeoffer.PlaceOfferModel; + +import bisq.common.UserThread; +import bisq.common.taskrunner.Task; +import bisq.common.taskrunner.TaskRunner; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + + +import monero.wallet.model.MoneroTxWallet; + +public class MakerCreateFeeTx extends Task { + private static final Logger log = LoggerFactory.getLogger(MakerCreateFeeTx.class); + + @SuppressWarnings({"unused"}) + public MakerCreateFeeTx(TaskRunner taskHandler, PlaceOfferModel model) { + super(taskHandler, model); + } + + @Override + protected void run() { + Offer offer = model.getOffer(); + + try { + runInterceptHook(); + + String id = offer.getId(); + XmrWalletService walletService = model.getXmrWalletService(); + + String reservedForTradeAddress = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.RESERVED_FOR_TRADE).getAddressString(); + + TradeWalletService tradeWalletService = model.getTradeWalletService(); + String feeReceiver = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM"; // TODO (woodser): don't hardcode + + if (offer.isCurrencyForMakerFeeBtc()) { + try { + MoneroTxWallet tx = tradeWalletService.createXmrTradingFeeTx( + reservedForTradeAddress, + model.getReservedFundsForOffer(), + offer.getMakerFee(), + offer.getTxFee(), + feeReceiver, + true); + System.out.println("SUCCESS CREATING XMR TRADING FEE TX!"); + System.out.println(tx); + + // we delay one render frame to be sure we don't get called before the method call has + // returned (tradeFeeTx would be null in that case) + UserThread.execute(() -> { + if (!completed) { + offer.setOfferFeePaymentTxId(tx.getHash()); + model.setXmrTransaction(tx); + walletService.swapTradeEntryToAvailableEntry(id, XmrAddressEntry.Context.OFFER_FUNDING); + + model.getOffer().setState(Offer.State.OFFER_FEE_PAID); + + complete(); + } else { + log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); + } + }); + } catch (Exception e) { + System.out.println("FAILURE CREATING XMR TRADING FEE TX!"); + if (!completed) { + failed(e); + } else { + log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); + } + } + +// tradeWalletService.createBtcTradingFeeTx( +// fundingAddress, +// reservedForTradeAddress, +// changeAddress, +// model.getReservedFundsForOffer(), +// model.isUseSavingsWallet(), +// offer.getMakerFee(), +// offer.getTxFee(), +// feeReceiver, +// true, +// new TxBroadcaster.Callback() { +// @Override +// public void onSuccess(Transaction transaction) { +// // we delay one render frame to be sure we don't get called before the method call has +// // returned (tradeFeeTx would be null in that case) +// UserThread.execute(() -> { +// if (!completed) { +// offer.setOfferFeePaymentTxId(transaction.getHashAsString()); +// model.setTransaction(transaction); +// walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING); +// +// model.getOffer().setState(Offer.State.OFFER_FEE_PAID); +// +// complete(); +// } else { +// log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); +// } +// }); +// } +// +// @Override +// public void onFailure(TxBroadcastException exception) { +// if (!completed) { +// failed(exception); +// } else { +// log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); +// } +// } +// }); + } + } catch (Throwable t) { + if (t instanceof DaoDisabledException) { + offer.setErrorMessage("You cannot pay the trade fee in BSQ at the moment because the DAO features have been " + + "disabled due technical problems. Please use the BTC fee option until the issues are resolved. " + + "For more information please visit the Bisq Forum."); + } else { + offer.setErrorMessage("An error occurred.\n" + + "Error message:\n" + + t.getMessage()); + } + + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequest.java new file mode 100644 index 00000000..319bb45c --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequest.java @@ -0,0 +1,150 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.maker; + +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.user.User; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.app.Version; +import bisq.common.crypto.Sig; +import bisq.common.taskrunner.TaskRunner; + +import com.google.common.base.Charsets; + +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.util.Validator.checkTradeId; +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class MakerSendsInitTradeRequest extends TradeTask { + @SuppressWarnings({"unused"}) + public MakerSendsInitTradeRequest(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + System.out.println("MAKER SENDING INIT TRADE REQ TO ARBITRATOR"); + + // verify trade + InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage(); + checkNotNull(request); + checkTradeId(processModel.getOfferId(), request); + +// // create wallet for multisig +// // TODO (woodser): manage in common util, set path, server +// MoneroWallet multisigWallet = MoneroWallet.createWallet(new MoneroWalletConfig() +// .setPassword("abctesting123") +// .setNetworkType(MoneroNetworkType.STAGENET)); +// +// // prepare multisig +// String preparedHex = multisigWallet.prepareMultisig(); +// System.out.println("Prepared multisig hex: " + preparedHex); + + // collect fields to send taker prepared multisig response // TODO (woodser): this should happen on response from arbitrator + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + String offerId = processModel.getOffer().getId(); + String payoutAddress = walletService.getWallet().createSubaddress(0).getAddress(); // TODO (woodser): register TRADE_PAYOUT? + walletService.getWallet().save(); + checkNotNull(trade.getTradeAmount(), "TradeAmount must not be null"); +// checkNotNull(trade.getTakerFeeTxId(), "TakeOfferFeeTxId must not be null"); // TODO (woodser): no taker fee tx yet if creating multisig first + final User user = processModel.getUser(); + checkNotNull(user, "User must not be null"); + final List acceptedMediatorAddresses = user.getAcceptedMediatorAddresses(); + checkNotNull(acceptedMediatorAddresses, "acceptedMediatorAddresses must not be null"); + + // Taker has to use offerId as nonce (he cannot manipulate that - so we avoid to have a challenge protocol for passing the nonce we want to get signed) + // He cannot manipulate the offerId - so we avoid to have a challenge protocol for passing the nonce we want to get signed. + final PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), "processModel.getPaymentAccountPayload(trade) must not be null"); + byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offerId.getBytes(Charsets.UTF_8)); + + System.out.println("MAKER SENDING ARBITRTATOR SENDER NODE ADDRESS"); + System.out.println(processModel.getMyNodeAddress()); + + // create message to initialize trade + InitTradeRequest message = new InitTradeRequest( + offerId, + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + trade.getTradeAmount().value, + trade.getTradePrice().getValue(), + trade.getTxFee().getValue(), + trade.getTakerFee().getValue(), + payoutAddress, + paymentAccountPayload, + processModel.getAccountId(), + trade.getTakerFeeTxId(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + sig, + new Date().getTime(), + trade.getTakerNodeAddress(), + trade.getMakerNodeAddress(), + trade.getArbitratorNodeAddress()); + + log.info("Send {} with offerId {} and uid {} to peer {}", + message.getClass().getSimpleName(), message.getTradeId(), + message.getUid(), trade.getMediatorNodeAddress()); + + + System.out.println("MAKER TRADE INFO"); + System.out.println("Trading peer node address: " + trade.getTradingPeerNodeAddress()); + System.out.println("Maker node address: " + trade.getMakerNodeAddress()); + System.out.println("Taker node adddress: " + trade.getTakerNodeAddress()); + System.out.println("Mediator node address: " + trade.getMediatorNodeAddress()); + System.out.println("Arbitrator node address: " + trade.getArbitratorNodeAddress()); + + // send request to arbitrator + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getArbitratorNodeAddress(), + trade.getArbitratorPubKeyRing(), + message, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at arbitrator: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); + complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getTradingPeerNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsReadyToFundMultisigResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsReadyToFundMultisigResponse.java new file mode 100644 index 00000000..a361e3e7 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsReadyToFundMultisigResponse.java @@ -0,0 +1,173 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.maker; + +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.trade.BuyerAsMakerTrade; +import bisq.core.trade.Contract; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; +import bisq.core.trade.protocol.TradingPeer; +import bisq.core.trade.protocol.tasks.TradeTask; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.app.Version; +import bisq.common.crypto.Sig; +import bisq.common.taskrunner.TaskRunner; +import bisq.common.util.Utilities; + +import java.util.Date; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroTxWallet; + +@Slf4j +public class MakerSendsReadyToFundMultisigResponse extends TradeTask { + @SuppressWarnings({"unused"}) + public MakerSendsReadyToFundMultisigResponse(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + System.out.println("MAKER SENDING READY TO FUND MULTISIG RESPONSE"); + + // determine if maker is ready to fund to-be-created multisig + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + MoneroWallet wallet = walletService.getWallet(); + wallet.sync(); + MoneroTxWallet offerFeeTx = wallet.getTx(trade.getOffer().getOfferFeePaymentTxId()); + if (offerFeeTx.isFailed()) throw new RuntimeException("Offer fee tx has failed"); // TODO (woodser): proper error handling + System.out.println("Offer fee num confirmations; " + offerFeeTx.getNumConfirmations()); + System.out.println("Offer fee is locked; " + offerFeeTx.isLocked()); + boolean makerReadyToFundMultisigResponse = !offerFeeTx.isLocked(); + + String contractAsJson = null; + String contractSignature = null; + String payoutAddress = null; + + // TODO (woodser): creating and signing contract here, but should do this in own task handler + if (makerReadyToFundMultisigResponse) { + + TradingPeer taker = processModel.getTradingPeer(); + PaymentAccountPayload makerPaymentAccountPayload = processModel.getPaymentAccountPayload(trade); + checkNotNull(makerPaymentAccountPayload, "makerPaymentAccountPayload must not be null"); + PaymentAccountPayload takerPaymentAccountPayload = checkNotNull(taker.getPaymentAccountPayload()); + boolean isBuyerMakerAndSellerTaker = trade instanceof BuyerAsMakerTrade; + + NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getMyNodeAddress() : processModel.getTempTradingPeerNodeAddress(); + NodeAddress sellerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyNodeAddress(); + String id = processModel.getOffer().getId(); + + // get maker payout address + System.out.println("CREATING NEW ADDRESS ENTRY FOR TRADE ID " + id); + XmrAddressEntry makerPayoutEntry = walletService.getNewAddressEntry(id, XmrAddressEntry.Context.TRADE_PAYOUT); + checkNotNull(taker.getPayoutAddressString(), "taker.getPayoutAddressString()"); + +// checkArgument(!walletService.getAddressEntry(id, XmrAddressEntry.Context.MULTI_SIG).isPresent(), "addressEntry must not be set here."); +// XmrAddressEntry makerAddressEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.MULTI_SIG); +// byte[] makerMultiSigPubKey = makerAddressEntry.getPubKey(); + + checkNotNull(processModel.getAccountId(), "processModel.getAccountId() must not be null"); + + checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); + Contract contract = new Contract( + processModel.getOffer().getOfferPayload(), + trade.getTradeAmount().value, + trade.getTradePrice().getValue(), + buyerNodeAddress, + sellerNodeAddress, + trade.getArbitratorNodeAddress(), + isBuyerMakerAndSellerTaker, + processModel.getAccountId(), + taker.getAccountId(), + makerPaymentAccountPayload, + takerPaymentAccountPayload, + processModel.getPubKeyRing(), + taker.getPubKeyRing(), + makerPayoutEntry.getAddressString(), + taker.getPayoutAddressString(), + trade.getLockTime() + ); + contractAsJson = Utilities.objectToJson(contract); + contractSignature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); + payoutAddress = makerPayoutEntry.getAddressString(); + + trade.setContract(contract); + trade.setContractAsJson(contractAsJson); + trade.setMakerContractSignature(contractSignature); + System.out.println("Contract as json:"); + System.out.println(contractAsJson); + + processModel.getTradeManager().requestPersistence(); + } + + // create message to indicate if maker is ready to fund to-be-created multisig wallet + System.out.println("BUILDING READY RESPONSE"); + System.out.println("Payout address: " + payoutAddress); + System.out.println("Account id: " + processModel.getAccountId()); + MakerReadyToFundMultisigResponse message = new MakerReadyToFundMultisigResponse( + processModel.getOffer().getId(), + makerReadyToFundMultisigResponse, + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + contractAsJson, + contractSignature, + payoutAddress, + processModel.getPaymentAccountPayload(trade), + processModel.getAccountId(), + new Date().getTime()); + + // send message to taker + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getTakerNodeAddress(), + trade.getTakerPubKeyRing(), + message, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at taker: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); + complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getTradingPeerNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxsListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxsListener.java new file mode 100644 index 00000000..d9a63688 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxsListener.java @@ -0,0 +1,19 @@ +package bisq.core.trade.protocol.tasks.maker; + +import bisq.core.trade.Trade; +import bisq.core.trade.Trade.State; +import bisq.core.trade.protocol.tasks.SetupDepositTxsListener; + +import bisq.common.taskrunner.TaskRunner; + +public class MakerSetupDepositTxsListener extends SetupDepositTxsListener { + + public MakerSetupDepositTxsListener(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected State getSeenState() { + return Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK; + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerVerifyTakerDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerVerifyTakerDepositTx.java new file mode 100644 index 00000000..4a6b975f --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerVerifyTakerDepositTx.java @@ -0,0 +1,79 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.maker; + +import static com.google.common.base.Preconditions.checkNotNull; + +import bisq.common.taskrunner.TaskRunner; +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.util.Validator; +import common.utils.GenUtils; +import lombok.extern.slf4j.Slf4j; +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroOutputWallet; +import monero.wallet.model.MoneroTxWallet; +import monero.wallet.model.MoneroWalletListener; + +@Slf4j +public class MakerVerifyTakerDepositTx extends TradeTask { + + public MakerVerifyTakerDepositTx(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // get message + DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); + Validator.checkTradeId(processModel.getOfferId(), message); + checkNotNull(message); + trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); + if (trade.getMakerDepositTxId() != null) throw new RuntimeException("Maker deposit tx already set for trade " + trade.getId() + ", this should not happen"); // TODO: ignore and nack bad requests to not show on client + + // verify deposit tx id + if (message.getDepositTxId() == null) throw new RuntimeException("Taker must provide deposit tx id"); + + // get multisig wallet + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); + + // wait until wallet sees taker deposit tx + MoneroTxWallet takerDepositTx = null; + while (takerDepositTx == null) { + try { + takerDepositTx = multisigWallet.getTx(message.getDepositTxId()); + } catch (Exception e) { + + } + GenUtils.waitFor(1000); // TODO (woodser): better way to wait for notification, use listener + } + + // TODO (woodser): verify taker deposit tx + + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java index 875b78f7..c571b5da 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java @@ -17,27 +17,13 @@ package bisq.core.trade.protocol.tasks.mediation; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.offer.Offer; -import bisq.core.trade.Contract; import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.crypto.DeterministicKey; - -import java.util.Arrays; - import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class FinalizeMediatedPayoutTx extends TradeTask { @@ -49,70 +35,71 @@ public class FinalizeMediatedPayoutTx extends TradeTask { protected void run() { try { runInterceptHook(); + throw new RuntimeException("FinalizeMediatedPayoutTx not implemented for xmr"); - Transaction depositTx = checkNotNull(trade.getDepositTx()); - String tradeId = trade.getId(); - TradingPeer tradingPeer = processModel.getTradingPeer(); - BtcWalletService walletService = processModel.getBtcWalletService(); - Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); - Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null"); - Contract contract = checkNotNull(trade.getContract(), "contract must not be null"); - - checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); - - - byte[] mySignature = checkNotNull(processModel.getMediatedPayoutTxSignature(), - "processModel.getTxSignatureFromMediation must not be null"); - byte[] peersSignature = checkNotNull(tradingPeer.getMediatedPayoutTxSignature(), - "tradingPeer.getTxSignatureFromMediation must not be null"); - - boolean isMyRoleBuyer = contract.isMyRoleBuyer(processModel.getPubKeyRing()); - byte[] buyerSignature = isMyRoleBuyer ? mySignature : peersSignature; - byte[] sellerSignature = isMyRoleBuyer ? peersSignature : mySignature; - - Coin totalPayoutAmount = offer.getBuyerSecurityDeposit().add(tradeAmount).add(offer.getSellerSecurityDeposit()); - Coin buyerPayoutAmount = Coin.valueOf(processModel.getBuyerPayoutAmountFromMediation()); - Coin sellerPayoutAmount = Coin.valueOf(processModel.getSellerPayoutAmountFromMediation()); - checkArgument(totalPayoutAmount.equals(buyerPayoutAmount.add(sellerPayoutAmount)), - "Payout amount does not match buyerPayoutAmount=" + buyerPayoutAmount.toFriendlyString() + - "; sellerPayoutAmount=" + sellerPayoutAmount); - - String myPayoutAddressString = walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.TRADE_PAYOUT).getAddressString(); - String peersPayoutAddressString = tradingPeer.getPayoutAddressString(); - String buyerPayoutAddressString = isMyRoleBuyer ? myPayoutAddressString : peersPayoutAddressString; - String sellerPayoutAddressString = isMyRoleBuyer ? peersPayoutAddressString : myPayoutAddressString; - - byte[] myMultiSigPubKey = processModel.getMyMultiSigPubKey(); - byte[] peersMultiSigPubKey = tradingPeer.getMultiSigPubKey(); - byte[] buyerMultiSigPubKey = isMyRoleBuyer ? myMultiSigPubKey : peersMultiSigPubKey; - byte[] sellerMultiSigPubKey = isMyRoleBuyer ? peersMultiSigPubKey : myMultiSigPubKey; - - DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(tradeId, myMultiSigPubKey); - - checkArgument(Arrays.equals(myMultiSigPubKey, - walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.MULTI_SIG).getPubKey()), - "myMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + tradeId); - - Transaction transaction = processModel.getTradeWalletService().finalizeMediatedPayoutTx( - depositTx, - buyerSignature, - sellerSignature, - buyerPayoutAmount, - sellerPayoutAmount, - buyerPayoutAddressString, - sellerPayoutAddressString, - multiSigKeyPair, - buyerMultiSigPubKey, - sellerMultiSigPubKey - ); - - trade.setPayoutTx(transaction); - - processModel.getTradeManager().requestPersistence(); - - walletService.resetCoinLockedInMultiSigAddressEntry(tradeId); - - complete(); +// Transaction depositTx = checkNotNull(trade.getDepositTx()); +// String tradeId = trade.getId(); +// TradingPeer tradingPeer = processModel.getTradingPeer(); +// BtcWalletService walletService = processModel.getBtcWalletService(); +// Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); +// Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null"); +// Contract contract = checkNotNull(trade.getContract(), "contract must not be null"); +// +// checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); +// +// +// byte[] mySignature = checkNotNull(processModel.getMediatedPayoutTxSignature(), +// "processModel.getTxSignatureFromMediation must not be null"); +// byte[] peersSignature = checkNotNull(tradingPeer.getMediatedPayoutTxSignature(), +// "tradingPeer.getTxSignatureFromMediation must not be null"); +// +// boolean isMyRoleBuyer = contract.isMyRoleBuyer(processModel.getPubKeyRing()); +// byte[] buyerSignature = isMyRoleBuyer ? mySignature : peersSignature; +// byte[] sellerSignature = isMyRoleBuyer ? peersSignature : mySignature; +// +// Coin totalPayoutAmount = offer.getBuyerSecurityDeposit().add(tradeAmount).add(offer.getSellerSecurityDeposit()); +// Coin buyerPayoutAmount = Coin.valueOf(processModel.getBuyerPayoutAmountFromMediation()); +// Coin sellerPayoutAmount = Coin.valueOf(processModel.getSellerPayoutAmountFromMediation()); +// checkArgument(totalPayoutAmount.equals(buyerPayoutAmount.add(sellerPayoutAmount)), +// "Payout amount does not match buyerPayoutAmount=" + buyerPayoutAmount.toFriendlyString() + +// "; sellerPayoutAmount=" + sellerPayoutAmount); +// +// String myPayoutAddressString = walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.TRADE_PAYOUT).getAddressString(); +// String peersPayoutAddressString = tradingPeer.getPayoutAddressString(); +// String buyerPayoutAddressString = isMyRoleBuyer ? myPayoutAddressString : peersPayoutAddressString; +// String sellerPayoutAddressString = isMyRoleBuyer ? peersPayoutAddressString : myPayoutAddressString; +// +// byte[] myMultiSigPubKey = processModel.getMyMultiSigPubKey(); +// byte[] peersMultiSigPubKey = tradingPeer.getMultiSigPubKey(); +// byte[] buyerMultiSigPubKey = isMyRoleBuyer ? myMultiSigPubKey : peersMultiSigPubKey; +// byte[] sellerMultiSigPubKey = isMyRoleBuyer ? peersMultiSigPubKey : myMultiSigPubKey; +// +// DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(tradeId, myMultiSigPubKey); +// +// checkArgument(Arrays.equals(myMultiSigPubKey, +// walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.MULTI_SIG).getPubKey()), +// "myMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + tradeId); +// +// Transaction transaction = processModel.getTradeWalletService().finalizeMediatedPayoutTx( +// depositTx, +// buyerSignature, +// sellerSignature, +// buyerPayoutAmount, +// sellerPayoutAmount, +// buyerPayoutAddressString, +// sellerPayoutAddressString, +// multiSigKeyPair, +// buyerMultiSigPubKey, +// sellerMultiSigPubKey +// ); +// +// trade.setPayoutTx(transaction); +// +// processModel.getTradeManager().requestPersistence(); +// +// walletService.resetCoinLockedInMultiSigAddressEntry(tradeId); +// +// complete(); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java index cb06ed64..11fb410f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java @@ -17,25 +17,13 @@ package bisq.core.trade.protocol.tasks.mediation; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.WalletService; -import bisq.core.support.dispute.mediation.MediationResultState; import bisq.core.trade.Trade; -import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.util.Validator; -import bisq.common.UserThread; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.Utils; - import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class ProcessMediatedPayoutTxPublishedMessage extends TradeTask { public ProcessMediatedPayoutTxPublishedMessage(TaskRunner taskHandler, Trade trade) { @@ -46,39 +34,40 @@ public class ProcessMediatedPayoutTxPublishedMessage extends TradeTask { protected void run() { try { runInterceptHook(); - MediatedPayoutTxPublishedMessage message = (MediatedPayoutTxPublishedMessage) processModel.getTradeMessage(); - Validator.checkTradeId(processModel.getOfferId(), message); - checkNotNull(message); - checkArgument(message.getPayoutTx() != null); - - // update to the latest peer address of our peer if the message is correct - trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); - - if (trade.getPayoutTx() == null) { - Transaction committedMediatedPayoutTx = WalletService.maybeAddNetworkTxToWallet(message.getPayoutTx(), processModel.getBtcWalletService().getWallet()); - trade.setPayoutTx(committedMediatedPayoutTx); - log.info("MediatedPayoutTx received from peer. Txid: {}\nhex: {}", - committedMediatedPayoutTx.getTxId().toString(), Utils.HEX.encode(committedMediatedPayoutTx.bitcoinSerialize())); - - trade.setMediationResultState(MediationResultState.RECEIVED_PAYOUT_TX_PUBLISHED_MSG); - - if (trade.getPayoutTx() != null) { - // We need to delay that call as we might get executed at startup after mailbox messages are - // applied where we iterate over out pending trades. The closeDisputedTrade method would remove - // that trade from the list causing a ConcurrentModificationException. - // To avoid that we delay for one render frame. - UserThread.execute(() -> processModel.getTradeManager() - .closeDisputedTrade(trade.getId(), Trade.DisputeState.MEDIATION_CLOSED)); - } - - processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId()); - } else { - log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId()); - } - - processModel.getTradeManager().requestPersistence(); - - complete(); + throw new RuntimeException("ProcessMediatedPayoutTxPublishedMessage not implemented for xmr"); +// MediatedPayoutTxPublishedMessage message = (MediatedPayoutTxPublishedMessage) processModel.getTradeMessage(); +// Validator.checkTradeId(processModel.getOfferId(), message); +// checkNotNull(message); +// checkArgument(message.getPayoutTx() != null); +// +// // update to the latest peer address of our peer if the message is correct +// trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); +// +// if (trade.getPayoutTx() == null) { +// Transaction committedMediatedPayoutTx = WalletService.maybeAddNetworkTxToWallet(message.getPayoutTx(), processModel.getBtcWalletService().getWallet()); +// trade.setPayoutTx(committedMediatedPayoutTx); +// log.info("MediatedPayoutTx received from peer. Txid: {}\nhex: {}", +// committedMediatedPayoutTx.getTxId().toString(), Utils.HEX.encode(committedMediatedPayoutTx.bitcoinSerialize())); +// +// trade.setMediationResultState(MediationResultState.RECEIVED_PAYOUT_TX_PUBLISHED_MSG); +// +// if (trade.getPayoutTx() != null) { +// // We need to delay that call as we might get executed at startup after mailbox messages are +// // applied where we iterate over out pending trades. The closeDisputedTrade method would remove +// // that trade from the list causing a ConcurrentModificationException. +// // To avoid that we delay for one render frame. +// UserThread.execute(() -> processModel.getTradeManager() +// .closeDisputedTrade(trade.getId(), Trade.DisputeState.MEDIATION_CLOSED)); +// } +// +// processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId()); +// } else { +// log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId()); +// } +// +// processModel.getTradeManager().requestPersistence(); +// +// complete(); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SendMediatedPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SendMediatedPayoutTxPublishedMessage.java index f03baace..461b62c7 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SendMediatedPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SendMediatedPayoutTxPublishedMessage.java @@ -19,20 +19,13 @@ package bisq.core.trade.protocol.tasks.mediation; import bisq.core.support.dispute.mediation.MediationResultState; import bisq.core.trade.Trade; -import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage; import bisq.core.trade.messages.TradeMailboxMessage; import bisq.core.trade.protocol.tasks.SendMailboxMessageTask; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Transaction; - -import java.util.UUID; - import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class SendMediatedPayoutTxPublishedMessage extends SendMailboxMessageTask { @@ -42,13 +35,14 @@ public class SendMediatedPayoutTxPublishedMessage extends SendMailboxMessageTask @Override protected TradeMailboxMessage getTradeMailboxMessage(String id) { - Transaction payoutTx = checkNotNull(trade.getPayoutTx(), "trade.getPayoutTx() must not be null"); - return new MediatedPayoutTxPublishedMessage( - id, - payoutTx.bitcoinSerialize(), - processModel.getMyNodeAddress(), - UUID.randomUUID().toString() - ); + throw new RuntimeException("SendMediatedPayoutTxPublishedMessage.getMessage(id) not implemented for xmr"); +// Transaction payoutTx = checkNotNull(trade.getPayoutTx(), "trade.getPayoutTx() must not be null"); +// return new MediatedPayoutTxPublishedMessage( +// id, +// payoutTx.bitcoinSerialize(), +// processModel.getMyNodeAddress(), +// UUID.randomUUID().toString() +// ); } @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java index 6699d832..cb259a27 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java @@ -17,27 +17,13 @@ package bisq.core.trade.protocol.tasks.mediation; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.offer.Offer; -import bisq.core.trade.Contract; import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.crypto.DeterministicKey; - -import java.util.Arrays; - import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class SignMediatedPayoutTx extends TradeTask { @@ -49,59 +35,60 @@ public class SignMediatedPayoutTx extends TradeTask { protected void run() { try { runInterceptHook(); + throw new RuntimeException("SignMediatedPayoutTx not implemented for xmr"); - TradingPeer tradingPeer = processModel.getTradingPeer(); - if (processModel.getMediatedPayoutTxSignature() != null) { - log.warn("processModel.getTxSignatureFromMediation is already set"); - } - - String tradeId = trade.getId(); - BtcWalletService walletService = processModel.getBtcWalletService(); - Transaction depositTx = checkNotNull(trade.getDepositTx(), "trade.getDepositTx() must not be null"); - Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); - Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null"); - Contract contract = checkNotNull(trade.getContract(), "contract must not be null"); - - Coin totalPayoutAmount = offer.getBuyerSecurityDeposit().add(tradeAmount).add(offer.getSellerSecurityDeposit()); - Coin buyerPayoutAmount = Coin.valueOf(processModel.getBuyerPayoutAmountFromMediation()); - Coin sellerPayoutAmount = Coin.valueOf(processModel.getSellerPayoutAmountFromMediation()); - - checkArgument(totalPayoutAmount.equals(buyerPayoutAmount.add(sellerPayoutAmount)), - "Payout amount does not match buyerPayoutAmount=" + buyerPayoutAmount.toFriendlyString() + - "; sellerPayoutAmount=" + sellerPayoutAmount); - - boolean isMyRoleBuyer = contract.isMyRoleBuyer(processModel.getPubKeyRing()); - - String myPayoutAddressString = walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.TRADE_PAYOUT).getAddressString(); - String peersPayoutAddressString = tradingPeer.getPayoutAddressString(); - String buyerPayoutAddressString = isMyRoleBuyer ? myPayoutAddressString : peersPayoutAddressString; - String sellerPayoutAddressString = isMyRoleBuyer ? peersPayoutAddressString : myPayoutAddressString; - - byte[] myMultiSigPubKey = processModel.getMyMultiSigPubKey(); - byte[] peersMultiSigPubKey = tradingPeer.getMultiSigPubKey(); - byte[] buyerMultiSigPubKey = isMyRoleBuyer ? myMultiSigPubKey : peersMultiSigPubKey; - byte[] sellerMultiSigPubKey = isMyRoleBuyer ? peersMultiSigPubKey : myMultiSigPubKey; - - DeterministicKey myMultiSigKeyPair = walletService.getMultiSigKeyPair(tradeId, myMultiSigPubKey); - - checkArgument(Arrays.equals(myMultiSigPubKey, - walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.MULTI_SIG).getPubKey()), - "myMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + tradeId); - - byte[] mediatedPayoutTxSignature = processModel.getTradeWalletService().signMediatedPayoutTx( - depositTx, - buyerPayoutAmount, - sellerPayoutAmount, - buyerPayoutAddressString, - sellerPayoutAddressString, - myMultiSigKeyPair, - buyerMultiSigPubKey, - sellerMultiSigPubKey); - processModel.setMediatedPayoutTxSignature(mediatedPayoutTxSignature); - - processModel.getTradeManager().requestPersistence(); - - complete(); +// TradingPeer tradingPeer = processModel.getTradingPeer(); +// if (processModel.getMediatedPayoutTxSignature() != null) { +// log.warn("processModel.getTxSignatureFromMediation is already set"); +// } +// +// String tradeId = trade.getId(); +// BtcWalletService walletService = processModel.getBtcWalletService(); +// Transaction depositTx = checkNotNull(trade.getDepositTx(), "trade.getDepositTx() must not be null"); +// Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); +// Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null"); +// Contract contract = checkNotNull(trade.getContract(), "contract must not be null"); +// +// Coin totalPayoutAmount = offer.getBuyerSecurityDeposit().add(tradeAmount).add(offer.getSellerSecurityDeposit()); +// Coin buyerPayoutAmount = Coin.valueOf(processModel.getBuyerPayoutAmountFromMediation()); +// Coin sellerPayoutAmount = Coin.valueOf(processModel.getSellerPayoutAmountFromMediation()); +// +// checkArgument(totalPayoutAmount.equals(buyerPayoutAmount.add(sellerPayoutAmount)), +// "Payout amount does not match buyerPayoutAmount=" + buyerPayoutAmount.toFriendlyString() + +// "; sellerPayoutAmount=" + sellerPayoutAmount); +// +// boolean isMyRoleBuyer = contract.isMyRoleBuyer(processModel.getPubKeyRing()); +// +// String myPayoutAddressString = walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.TRADE_PAYOUT).getAddressString(); +// String peersPayoutAddressString = tradingPeer.getPayoutAddressString(); +// String buyerPayoutAddressString = isMyRoleBuyer ? myPayoutAddressString : peersPayoutAddressString; +// String sellerPayoutAddressString = isMyRoleBuyer ? peersPayoutAddressString : myPayoutAddressString; +// +// byte[] myMultiSigPubKey = processModel.getMyMultiSigPubKey(); +// byte[] peersMultiSigPubKey = tradingPeer.getMultiSigPubKey(); +// byte[] buyerMultiSigPubKey = isMyRoleBuyer ? myMultiSigPubKey : peersMultiSigPubKey; +// byte[] sellerMultiSigPubKey = isMyRoleBuyer ? peersMultiSigPubKey : myMultiSigPubKey; +// +// DeterministicKey myMultiSigKeyPair = walletService.getMultiSigKeyPair(tradeId, myMultiSigPubKey); +// +// checkArgument(Arrays.equals(myMultiSigPubKey, +// walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.MULTI_SIG).getPubKey()), +// "myMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + tradeId); +// +// byte[] mediatedPayoutTxSignature = processModel.getTradeWalletService().signMediatedPayoutTx( +// depositTx, +// buyerPayoutAmount, +// sellerPayoutAmount, +// buyerPayoutAddressString, +// sellerPayoutAddressString, +// myMultiSigKeyPair, +// buyerMultiSigPubKey, +// sellerMultiSigPubKey); +// processModel.setMediatedPayoutTxSignature(mediatedPayoutTxSignature); +// +// processModel.getTradeManager().requestPersistence(); +// +// complete(); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java index a466f112..17cfa77f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java @@ -43,6 +43,7 @@ public class SellerCreatesDelayedPayoutTx extends TradeTask { protected void run() { try { runInterceptHook(); + if (true) throw new RuntimeException("SellerCreatesDelayedPayoutTx not implemented for xmr"); String donationAddressString = processModel.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS); Coin minerFee = trade.getTxFee(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index 9cbba9b6..1369b41c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -43,10 +43,10 @@ public class SellerProcessCounterCurrencyTransferStartedMessage extends TradeTas Validator.checkTradeId(processModel.getOfferId(), message); checkNotNull(message); - processModel.getTradingPeer().setPayoutAddressString(Validator.nonEmptyStringOf(message.getBuyerPayoutAddress())); - processModel.getTradingPeer().setSignature(checkNotNull(message.getBuyerSignature())); + processModel.getTradingPeer().setPayoutAddressString(Validator.nonEmptyStringOf(message.getBuyerPayoutAddress())); // TODO (woodser): verify against contract + processModel.getTradingPeer().setSignedPayoutTxHex(message.getBuyerPayoutTxSigned()); - // update to the latest peer address of our peer if the message is correct + // update to the latest peer address of our peer if the message is correct // TODO (woodser): update to latest peer addresses where needed trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); String counterCurrencyTxId = message.getCounterCurrencyTxId(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java index 1afae6da..b6b0dda8 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java @@ -17,16 +17,11 @@ package bisq.core.trade.protocol.tasks.seller; -import bisq.core.btc.exceptions.TxBroadcastException; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Transaction; - import lombok.extern.slf4j.Slf4j; @Slf4j @@ -39,38 +34,39 @@ public class SellerPublishesDepositTx extends TradeTask { protected void run() { try { runInterceptHook(); + throw new RuntimeException("SellerPublishesDepositTx not implemented for xmr"); - final Transaction depositTx = processModel.getDepositTx(); - processModel.getTradeWalletService().broadcastTx(depositTx, - new TxBroadcaster.Callback() { - @Override - public void onSuccess(Transaction transaction) { - if (!completed) { - // Now as we have published the deposit tx we set it in trade - trade.applyDepositTx(depositTx); - - trade.setState(Trade.State.SELLER_PUBLISHED_DEPOSIT_TX); - - processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(processModel.getOffer().getId(), - AddressEntry.Context.RESERVED_FOR_TRADE); - - processModel.getTradeManager().requestPersistence(); - - complete(); - } else { - log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); - } - } - - @Override - public void onFailure(TxBroadcastException exception) { - if (!completed) { - failed(exception); - } else { - log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); - } - } - }); +// final Transaction depositTx = processModel.getDepositTx(); +// processModel.getTradeWalletService().broadcastTx(depositTx, +// new TxBroadcaster.Callback() { +// @Override +// public void onSuccess(Transaction transaction) { +// if (!completed) { +// // Now as we have published the deposit tx we set it in trade +// trade.applyDepositTx(depositTx); +// +// trade.setState(Trade.State.SELLER_PUBLISHED_DEPOSIT_TX); +// +// processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(processModel.getOffer().getId(), +// AddressEntry.Context.RESERVED_FOR_TRADE); +// +// processModel.getTradeManager().requestPersistence(); +// +// complete(); +// } else { +// log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); +// } +// } +// +// @Override +// public void onFailure(TxBroadcastException exception) { +// if (!completed) { +// failed(exception); +// } else { +// log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); +// } +// } +// }); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesTradeStatistics.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesTradeStatistics.java index 89b6ac5a..781e2fc4 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesTradeStatistics.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesTradeStatistics.java @@ -19,17 +19,11 @@ package bisq.core.trade.protocol.tasks.seller; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.statistics.TradeStatistics3; -import bisq.network.p2p.network.TorNetworkNode; - -import bisq.common.app.Capability; import bisq.common.taskrunner.TaskRunner; import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class SellerPublishesTradeStatistics extends TradeTask { public SellerPublishesTradeStatistics(TaskRunner taskHandler, Trade trade) { @@ -38,36 +32,37 @@ public class SellerPublishesTradeStatistics extends TradeTask { @Override protected void run() { - try { - runInterceptHook(); - - checkNotNull(trade.getDepositTx()); - - processModel.getP2PService().findPeersCapabilities(trade.getTradingPeerNodeAddress()) - .filter(capabilities -> capabilities.containsAll(Capability.TRADE_STATISTICS_3)) - .ifPresentOrElse(capabilities -> { - // Our peer has updated, so as we are the seller we will publish the trade statistics. - // The peer as buyer does not publish anymore with v.1.4.0 (where Capability.TRADE_STATISTICS_3 was added) - - String referralId = processModel.getReferralIdService().getOptionalReferralId().orElse(null); - boolean isTorNetworkNode = model.getProcessModel().getP2PService().getNetworkNode() instanceof TorNetworkNode; - TradeStatistics3 tradeStatistics = TradeStatistics3.from(trade, referralId, isTorNetworkNode); - if (tradeStatistics.isValid()) { - log.info("Publishing trade statistics"); - processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true); - } else { - log.warn("Trade statistics are invalid. We do not publish. {}", tradeStatistics); - } - - complete(); - }, - () -> { - log.info("Our peer does not has updated yet, so they will publish the trade statistics. " + - "To avoid duplicates we do not publish from our side."); - complete(); - }); - } catch (Throwable t) { - failed(t); - } + throw new RuntimeException("SellerPublishesTradeStatistics needs updated for XMR"); +// try { +// runInterceptHook(); +// +// checkNotNull(trade.getDepositTx()); +// +// processModel.getP2PService().findPeersCapabilities(trade.getTradingPeerNodeAddress()) +// .filter(capabilities -> capabilities.containsAll(Capability.TRADE_STATISTICS_3)) +// .ifPresentOrElse(capabilities -> { +// // Our peer has updated, so as we are the seller we will publish the trade statistics. +// // The peer as buyer does not publish anymore with v.1.4.0 (where Capability.TRADE_STATISTICS_3 was added) +// +// String referralId = processModel.getReferralIdService().getOptionalReferralId().orElse(null); +// boolean isTorNetworkNode = model.getProcessModel().getP2PService().getNetworkNode() instanceof TorNetworkNode; +// TradeStatistics3 tradeStatistics = TradeStatistics3.from(trade, referralId, isTorNetworkNode); +// if (tradeStatistics.isValid()) { +// log.info("Publishing trade statistics"); +// processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true); +// } else { +// log.warn("Trade statistics are invalid. We do not publish. {}", tradeStatistics); +// } +// +// complete(); +// }, +// () -> { +// log.info("Our peer does not has updated yet, so they will publish the trade statistics. " + +// "To avoid duplicates we do not publish from our side."); +// complete(); +// }); +// } catch (Throwable t) { +// failed(t); +// } } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java index be699083..87f8b1b8 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java @@ -26,8 +26,6 @@ import bisq.core.trade.protocol.tasks.SendMailboxMessageTask; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Transaction; - import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; @@ -44,7 +42,7 @@ public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask { @Override protected TradeMailboxMessage getTradeMailboxMessage(String id) { - Transaction payoutTx = checkNotNull(trade.getPayoutTx(), "trade.getPayoutTx() must not be null"); + checkNotNull(trade.getPayoutTx(), "trade.getPayoutTx() must not be null"); AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService(); if (accountAgeWitnessService.isSignWitnessTrade(trade)) { @@ -52,9 +50,13 @@ public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask { accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness); } + System.out.println("Trade.getPayoutTx(): " + trade.getPayoutTx()); + System.out.println("trade.getPayoutTx().getTxSet(): " + trade.getPayoutTx().getTxSet()); + System.out.println("trade.getPayoutTx().getTxSet().getMultisigTxHex(): " + trade.getPayoutTx().getTxSet().getMultisigTxHex()); + return new PayoutTxPublishedMessage( id, - payoutTx.bitcoinSerialize(), + trade.getPayoutTx().getTxSet().getMultisigTxHex(), processModel.getMyNodeAddress(), signedWitness ); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java deleted file mode 100644 index 2ee9d8c9..00000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.seller; - -import bisq.core.network.MessageState; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.TradeMailboxMessage; -import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.SendMailboxMessageTask; - -import bisq.common.Timer; -import bisq.common.UserThread; -import bisq.common.taskrunner.TaskRunner; - -import javafx.beans.value.ChangeListener; - -import java.util.concurrent.TimeUnit; - -import lombok.extern.slf4j.Slf4j; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * We send the buyer the deposit and delayed payout tx. We wait to receive a ACK message back and resend the message - * in case that does not happen in 4 seconds or if the message was stored in mailbox or failed. We keep repeating that - * with doubling the interval each time and until the MAX_RESEND_ATTEMPTS is reached. If never successful we fail and - * do not continue the protocol with publishing the deposit tx. That way we avoid that a deposit tx is published but the - * buyer does not have the delayed payout tx and would not be able to open arbitration. - */ -@Slf4j -public class SellerSendsDepositTxAndDelayedPayoutTxMessage extends SendMailboxMessageTask { - private static final int MAX_RESEND_ATTEMPTS = 7; - private int delayInSec = 4; - private int resendCounter = 0; - private DepositTxAndDelayedPayoutTxMessage message; - private ChangeListener listener; - private Timer timer; - - public SellerSendsDepositTxAndDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) { - if (message == null) { - // We do not use a real unique ID here as we want to be able to re-send the exact same message in case the - // peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox - // messages where only the one which gets processed by the peer would be removed we use the same uid. All - // other data stays the same when we re-send the message at any time later. - String deterministicId = tradeId + processModel.getMyNodeAddress().getFullAddress(); - message = new DepositTxAndDelayedPayoutTxMessage( - deterministicId, - processModel.getOfferId(), - processModel.getMyNodeAddress(), - checkNotNull(processModel.getDepositTx()).bitcoinSerialize(), - checkNotNull(trade.getDelayedPayoutTx()).bitcoinSerialize()); - } - return message; - } - - @Override - protected void setStateSent() { - trade.setStateIfValidTransitionTo(Trade.State.SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG); - - processModel.getTradeManager().requestPersistence(); - } - - @Override - protected void setStateArrived() { - trade.setStateIfValidTransitionTo(Trade.State.SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG); - - processModel.getTradeManager().requestPersistence(); - cleanup(); - // Complete is called in base class - } - - // We override the default behaviour for onStoredInMailbox and do not call complete - @Override - protected void onStoredInMailbox() { - setStateStoredInMailbox(); - } - - @Override - protected void setStateStoredInMailbox() { - trade.setStateIfValidTransitionTo(Trade.State.SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG); - - processModel.getTradeManager().requestPersistence(); - // The DepositTxAndDelayedPayoutTxMessage is a mailbox message as earlier we use only the deposit tx which can - // be also received from the network once published. - // Now we send the delayed payout tx as well and with that this message is mandatory for continuing the protocol. - // We do not support mailbox message handling during the take offer process as it is expected that both peers - // are online. - // For backward compatibility and extra resilience we still keep DepositTxAndDelayedPayoutTxMessage as a - // mailbox message but the stored in mailbox case is not expected and the seller would try to send the message again - // in the hope to reach the buyer directly. - if (!trade.isDepositConfirmed()) { - tryToSendAgainLater(); - } - } - - // We override the default behaviour for onFault and do not call appendToErrorMessage and failed - @Override - protected void onFault(String errorMessage, TradeMessage message) { - setStateFault(); - } - - @Override - protected void setStateFault() { - trade.setStateIfValidTransitionTo(Trade.State.SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG); - if (!trade.isDepositConfirmed()) { - tryToSendAgainLater(); - } - - processModel.getTradeManager().requestPersistence(); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - super.run(); - } catch (Throwable t) { - failed(t); - } finally { - cleanup(); - } - } - - private void cleanup() { - if (timer != null) { - timer.stop(); - } - if (listener != null) { - processModel.getPaymentStartedMessageStateProperty().removeListener(listener); - } - } - - private void tryToSendAgainLater() { - if (resendCounter >= MAX_RESEND_ATTEMPTS) { - cleanup(); - failed("We never received an ACK message when sending the msg to the peer. " + - "We fail here and do not publish the deposit tx."); - return; - } - - log.info("We send the message again to the peer after a delay of {} sec.", delayInSec); - if (timer != null) { - timer.stop(); - } - timer = UserThread.runAfter(this::run, delayInSec, TimeUnit.SECONDS); - - if (resendCounter == 0) { - // We want to register listener only once - listener = (observable, oldValue, newValue) -> onMessageStateChange(newValue); - processModel.getDepositTxMessageStateProperty().addListener(listener); - onMessageStateChange(processModel.getDepositTxMessageStateProperty().get()); - } - - delayInSec = delayInSec * 2; - resendCounter++; - } - - private void onMessageStateChange(MessageState newValue) { - // Once we receive an ACK from our msg we know the peer has received the msg and we stop. - if (newValue == MessageState.ACKNOWLEDGED) { - // We treat a ACK like SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG - trade.setStateIfValidTransitionTo(Trade.State.SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG); - - processModel.getTradeManager().requestPersistence(); - cleanup(); - complete(); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java deleted file mode 100644 index d6b0babc..00000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.seller; - -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.offer.Offer; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; - -import bisq.common.taskrunner.TaskRunner; - -import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.crypto.DeterministicKey; - -import java.util.Arrays; -import java.util.Optional; - -import lombok.extern.slf4j.Slf4j; - -import static com.google.common.base.Preconditions.checkNotNull; - -@Slf4j -public class SellerSignAndFinalizePayoutTx extends TradeTask { - - public SellerSignAndFinalizePayoutTx(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); - - Offer offer = trade.getOffer(); - TradingPeer tradingPeer = processModel.getTradingPeer(); - BtcWalletService walletService = processModel.getBtcWalletService(); - String id = processModel.getOffer().getId(); - - final byte[] buyerSignature = tradingPeer.getSignature(); - - Coin buyerPayoutAmount = checkNotNull(offer.getBuyerSecurityDeposit()).add(trade.getTradeAmount()); - Coin sellerPayoutAmount = offer.getSellerSecurityDeposit(); - - final String buyerPayoutAddressString = tradingPeer.getPayoutAddressString(); - String sellerPayoutAddressString = walletService.getOrCreateAddressEntry(id, - AddressEntry.Context.TRADE_PAYOUT).getAddressString(); - - final byte[] buyerMultiSigPubKey = tradingPeer.getMultiSigPubKey(); - byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey(); - - Optional multiSigAddressEntryOptional = walletService.getAddressEntry(id, - AddressEntry.Context.MULTI_SIG); - if (!multiSigAddressEntryOptional.isPresent() || !Arrays.equals(sellerMultiSigPubKey, - multiSigAddressEntryOptional.get().getPubKey())) { - // In some error edge cases it can be that the address entry is not marked (or was unmarked). - // We do not want to fail in that case and only report a warning. - // One case where that helped to avoid a failed payout attempt was when the taker had a power failure - // at the moment when the offer was taken. This caused first to not see step 1 in the trade process - // (all greyed out) but after the deposit tx was confirmed the trade process was on step 2 and - // everything looked ok. At the payout multiSigAddressEntryOptional was not present and payout - // could not be done. By changing the previous behaviour from fail if multiSigAddressEntryOptional - // is not present to only log a warning the payout worked. - log.warn("sellerMultiSigPubKey from AddressEntry does not match the one from the trade data. " + - "Trade id ={}, multiSigAddressEntryOptional={}", id, multiSigAddressEntryOptional); - } - - DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(id, sellerMultiSigPubKey); - - Transaction transaction = processModel.getTradeWalletService().sellerSignsAndFinalizesPayoutTx( - checkNotNull(trade.getDepositTx()), - buyerSignature, - buyerPayoutAmount, - sellerPayoutAmount, - buyerPayoutAddressString, - sellerPayoutAddressString, - multiSigKeyPair, - buyerMultiSigPubKey, - sellerMultiSigPubKey - ); - - trade.setPayoutTx(transaction); - - processModel.getTradeManager().requestPersistence(); - - walletService.resetCoinLockedInMultiSigAddressEntry(id); - - complete(); - } catch (Throwable t) { - failed(t); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndPublishPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndPublishPayoutTx.java new file mode 100644 index 00000000..e9cf2494 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndPublishPayoutTx.java @@ -0,0 +1,187 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.seller; + +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.offer.Offer; +import bisq.core.trade.Contract; +import bisq.core.trade.MakerTrade; +import bisq.core.trade.Trade; +import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.util.ParsingUtils; + +import bisq.common.taskrunner.TaskRunner; + +import java.math.BigInteger; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroDestination; +import monero.wallet.model.MoneroMultisigSignResult; +import monero.wallet.model.MoneroTxSet; +import monero.wallet.model.MoneroTxWallet; + +@Slf4j +public class SellerSignAndPublishPayoutTx extends TradeTask { + + @SuppressWarnings({"unused"}) + public SellerSignAndPublishPayoutTx(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // gather relevant trade info + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); + String buyerSignedPayoutTxHex = processModel.getTradingPeer().getSignedPayoutTxHex(); + Contract contract = trade.getContract(); + Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); + BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getMakerPreparedDepositTxId() : processModel.getTakerPreparedDepositTxId()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs trade.getDepositTxId() necessary or avoidable? + BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getTakerPreparedDepositTxId() : processModel.getMakerPreparedDepositTxId()).getIncomingAmount(); + BigInteger tradeAmount = ParsingUtils.satoshisToXmrAtomicUnits(trade.getTradeAmount().value); + + System.out.println("SELLER VERIFYING PAYOUT TX"); + System.out.println("Trade amount: " + trade.getTradeAmount()); + System.out.println("Buyer deposit amount: " + buyerDepositAmount); + System.out.println("Seller deposit amount: " + sellerDepositAmount); + + BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(offer.getBuyerSecurityDeposit().add(trade.getTradeAmount()).value); + System.out.println("Buyer payout amount (with multiplier): " + buyerPayoutAmount); + BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(offer.getSellerSecurityDeposit().value); + System.out.println("Seller payout amount (with multiplier): " + sellerPayoutAmount); + + // parse buyer-signed payout tx + MoneroTxSet parsedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(buyerSignedPayoutTxHex)); + if (parsedTxSet.getTxs().get(0).getTxSet() != parsedTxSet) System.out.println("LINKS ARE WRONG STRAIGHT FROM PARSING!!!"); + if (parsedTxSet.getTxs() == null || parsedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad buyer-signed payout tx"); // TODO (woodser): nack + MoneroTxWallet buyerSignedPayoutTx = parsedTxSet.getTxs().get(0); + System.out.println("Parsed buyer signed tx hex:\n" + buyerSignedPayoutTx); + + // verify payout tx has exactly 2 destinations + if (buyerSignedPayoutTx.getOutgoingTransfer() == null || buyerSignedPayoutTx.getOutgoingTransfer().getDestinations() == null || buyerSignedPayoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new RuntimeException("Buyer-signed payout tx does not have exactly two destinations"); + + // get buyer and seller destinations (order not preserved) + boolean buyerFirst = buyerSignedPayoutTx.getOutgoingTransfer().getDestinations().get(0).getAddress().equals(contract.getBuyerPayoutAddressString()); + MoneroDestination buyerPayoutDestination = buyerSignedPayoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 0 : 1); + MoneroDestination sellerPayoutDestination = buyerSignedPayoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 1 : 0); + + // verify payout addresses + if (!buyerPayoutDestination.getAddress().equals(contract.getBuyerPayoutAddressString())) throw new RuntimeException("Buyer payout address does not match contract"); + if (!sellerPayoutDestination.getAddress().equals(contract.getSellerPayoutAddressString())) throw new RuntimeException("Seller payout address does not match contract"); + + // verify change address is multisig's primary address + if (!buyerSignedPayoutTx.getChangeAddress().equals(multisigWallet.getPrimaryAddress())) throw new RuntimeException("Change address is not multisig wallet's primary address"); + + // verify sum of outputs = destination amounts + change amount + if (!buyerSignedPayoutTx.getOutputSum().equals(buyerPayoutDestination.getAmount().add(sellerPayoutDestination.getAmount()).add(buyerSignedPayoutTx.getChangeAmount()))) throw new RuntimeException("Sum of outputs != destination amounts + change amount"); + + // verify buyer destination amount is deposit amount + trade amount - 1/2 tx costs + BigInteger txCost = buyerSignedPayoutTx.getFee().add(buyerSignedPayoutTx.getChangeAmount()); + BigInteger expectedBuyerPayout = buyerDepositAmount.add(tradeAmount).subtract(txCost.divide(BigInteger.valueOf(2))); + if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not deposit amount + trade amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout); + + // verify seller destination amount is deposit amount - trade amount - 1/2 tx costs + BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCost.divide(BigInteger.valueOf(2))); + if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new RuntimeException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout); + + // TODO (woodser): verify fee is reasonable (e.g. within 2x of fee estimate tx) + + // sign buyer-signed payout tx + MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(buyerSignedPayoutTxHex); + if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing buyer-signed payout tx"); + String signedMultisigTxHex = result.getSignedMultisigTxHex(); + + // submit fully signed payout tx to the network + multisigWallet.submitMultisigTxHex(signedMultisigTxHex); + + // update state + parsedTxSet.setMultisigTxHex(signedMultisigTxHex); + if (parsedTxSet.getTxs().get(0).getTxSet() != parsedTxSet) System.out.println("LINKS ARE WRONG!!!"); + trade.setPayoutTx(parsedTxSet.getTxs().get(0)); + trade.setPayoutTxId(parsedTxSet.getTxs().get(0).getHash()); + trade.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX); + complete(); + +// checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); +// +// Offer offer = trade.getOffer(); +// TradingPeer tradingPeer = processModel.getTradingPeer(); +// BtcWalletService walletService = processModel.getBtcWalletService(); +// String id = processModel.getOffer().getId(); +// +// final byte[] buyerSignature = tradingPeer.getSignature(); +// +// Coin buyerPayoutAmount = checkNotNull(offer.getBuyerSecurityDeposit()).add(trade.getTradeAmount()); +// Coin sellerPayoutAmount = offer.getSellerSecurityDeposit(); +// +// final String buyerPayoutAddressString = tradingPeer.getPayoutAddressString(); +// String sellerPayoutAddressString = walletService.getOrCreateAddressEntry(id, +// AddressEntry.Context.TRADE_PAYOUT).getAddressString(); +// +// final byte[] buyerMultiSigPubKey = tradingPeer.getMultiSigPubKey(); +// byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey(); +// +// Optional multiSigAddressEntryOptional = walletService.getAddressEntry(id, +// AddressEntry.Context.MULTI_SIG); +// if (!multiSigAddressEntryOptional.isPresent() || !Arrays.equals(sellerMultiSigPubKey, +// multiSigAddressEntryOptional.get().getPubKey())) { +// // In some error edge cases it can be that the address entry is not marked (or was unmarked). +// // We do not want to fail in that case and only report a warning. +// // One case where that helped to avoid a failed payout attempt was when the taker had a power failure +// // at the moment when the offer was taken. This caused first to not see step 1 in the trade process +// // (all greyed out) but after the deposit tx was confirmed the trade process was on step 2 and +// // everything looked ok. At the payout multiSigAddressEntryOptional was not present and payout +// // could not be done. By changing the previous behaviour from fail if multiSigAddressEntryOptional +// // is not present to only log a warning the payout worked. +// log.warn("sellerMultiSigPubKey from AddressEntry does not match the one from the trade data. " + +// "Trade id ={}, multiSigAddressEntryOptional={}", id, multiSigAddressEntryOptional); +// } +// +// DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(id, sellerMultiSigPubKey); +// +// Transaction transaction = processModel.getTradeWalletService().sellerSignsAndFinalizesPayoutTx( +// checkNotNull(trade.getDepositTx()), +// buyerSignature, +// buyerPayoutAmount, +// sellerPayoutAmount, +// buyerPayoutAddressString, +// sellerPayoutAddressString, +// multiSigKeyPair, +// buyerMultiSigPubKey, +// sellerMultiSigPubKey +// ); +// +// trade.setPayoutTx(transaction); +// +// walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.MULTI_SIG); +// +// complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java index 3902668f..796944b2 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java @@ -38,6 +38,7 @@ public class SellerAsMakerFinalizesDepositTx extends TradeTask { protected void run() { try { runInterceptHook(); + if (true) throw new RuntimeException("SellerAsMakerFinalizesDepositTx not implemented for xmr"); byte[] takersRawPreparedDepositTx = checkNotNull(processModel.getTradingPeer().getPreparedDepositTx()); byte[] myRawPreparedDepositTx = checkNotNull(processModel.getPreparedDepositTx()); @@ -55,4 +56,4 @@ public class SellerAsMakerFinalizesDepositTx extends TradeTask { failed(t); } } -} +} \ No newline at end of file diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java index 0433347f..5bd25eae 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java @@ -18,16 +18,12 @@ package bisq.core.trade.protocol.tasks.seller_as_maker; import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxMessage; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.util.Validator; import bisq.common.taskrunner.TaskRunner; import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class SellerAsMakerProcessDepositTxMessage extends TradeTask { public SellerAsMakerProcessDepositTxMessage(TaskRunner taskHandler, Trade trade) { @@ -36,26 +32,27 @@ public class SellerAsMakerProcessDepositTxMessage extends TradeTask { @Override protected void run() { - try { - runInterceptHook(); - log.debug("current trade state " + trade.getState()); - DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); - Validator.checkTradeId(processModel.getOfferId(), message); - checkNotNull(message); - - processModel.getTradingPeer().setPreparedDepositTx(checkNotNull(message.getDepositTxWithoutWitnesses())); - trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); - - // When we receive that message the taker has published the taker fee, so we apply it to the trade. - // The takerFeeTx was sent in the first message. It should be part of DelayedPayoutTxSignatureRequest - // but that cannot be changed due backward compatibility issues. It is a left over from the old trade protocol. - trade.setTakerFeeTxId(processModel.getTakeOfferFeeTxId()); - - processModel.getTradeManager().requestPersistence(); - - complete(); - } catch (Throwable t) { - failed(t); - } + throw new RuntimeException("SellerAsMakerProcessDepositTxMessage needs updated for XMR"); +// try { +// runInterceptHook(); +// log.debug("current trade state " + trade.getState()); +// DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); +// Validator.checkTradeId(processModel.getOfferId(), message); +// checkNotNull(message); +// +// processModel.getTradingPeer().setPreparedDepositTx(checkNotNull(message.getDepositTxWithoutWitnesses())); +// trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); +// +// // When we receive that message the taker has published the taker fee, so we apply it to the trade. +// // The takerFeeTx was sent in the first message. It should be part of DelayedPayoutTxSignatureRequest +// // but that cannot be changed due backward compatibility issues. It is a left over from the old trade protocol. +// trade.setTakerFeeTxId(processModel.getTakeOfferFeeTxId()); +// +// processModel.getTradeManager().requestPersistence(); +// +// complete(); +// } catch (Throwable t) { +// failed(t); +// } } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java index 7aa4118e..21702c66 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java @@ -17,19 +17,13 @@ package bisq.core.trade.protocol.tasks.seller_as_taker; -import bisq.core.btc.model.InputsAndChangeOutput; -import bisq.core.offer.Offer; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Coin; - import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkNotNull; - @Slf4j public class SellerAsTakerCreatesDepositTxInputs extends TradeTask { public SellerAsTakerCreatesDepositTxInputs(TaskRunner taskHandler, Trade trade) { @@ -40,26 +34,27 @@ public class SellerAsTakerCreatesDepositTxInputs extends TradeTask { protected void run() { try { runInterceptHook(); + throw new RuntimeException("XMR integration does not communicate outputs"); - Coin tradeAmount = checkNotNull(trade.getTradeAmount()); - Offer offer = checkNotNull(trade.getOffer()); - Coin txFee = trade.getTxFee(); - Coin takerInputAmount = offer.getSellerSecurityDeposit() - .add(txFee) - .add(txFee) // We add 2 times the fee as one is for the payout tx - .add(tradeAmount); - InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositTxInputs( - processModel.getTakeOfferFeeTx(), - takerInputAmount, - txFee); - - processModel.setRawTransactionInputs(result.rawTransactionInputs); - processModel.setChangeOutputValue(result.changeOutputValue); - processModel.setChangeOutputAddress(result.changeOutputAddress); - - processModel.getTradeManager().requestPersistence(); - - complete(); +// Coin tradeAmount = checkNotNull(trade.getTradeAmount()); +// Offer offer = checkNotNull(trade.getOffer()); +// Coin txFee = trade.getTxFee(); +// Coin takerInputAmount = offer.getSellerSecurityDeposit() +// .add(txFee) +// .add(txFee) // We add 2 times the fee as one is for the payout tx +// .add(tradeAmount); +// InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositTxInputs( +// processModel.getTakeOfferFeeTx(), +// takerInputAmount, +// txFee); +// +// processModel.setRawTransactionInputs(result.rawTransactionInputs); +// processModel.setChangeOutputValue(result.changeOutputValue); +// processModel.setChangeOutputAddress(result.changeOutputAddress); +// +// processModel.getTradeManager().requestPersistence(); +// +// complete(); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java index 5e2cc1f3..fbf5c427 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java @@ -50,6 +50,7 @@ public class SellerAsTakerSignsDepositTx extends TradeTask { protected void run() { try { runInterceptHook(); + if (true) throw new RuntimeException("SellerAsTakerSignsDepositTx not implemented for xmr"); List sellerInputs = checkNotNull(processModel.getRawTransactionInputs(), "sellerInputs must not be null"); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java deleted file mode 100644 index e6468020..00000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.taker; - -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.TradeWalletService; -import bisq.core.btc.wallet.WalletService; -import bisq.core.dao.exceptions.DaoDisabledException; -import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.util.FeeReceiverSelector; - -import bisq.common.taskrunner.TaskRunner; - -import org.bitcoinj.core.Address; -import org.bitcoinj.core.Transaction; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class CreateTakerFeeTx extends TradeTask { - - public CreateTakerFeeTx(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - BtcWalletService walletService = processModel.getBtcWalletService(); - String id = processModel.getOffer().getId(); - - // We enforce here to create a MULTI_SIG and TRADE_PAYOUT address entry to avoid that the change output would be used later - // for those address entries. Because we do not commit our fee tx yet the change address would - // appear as unused and therefore selected for the outputs for the MS tx. - // That would cause incorrect display of the balance as - // the change output would be considered as not available balance (part of the locked trade amount). - walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); - walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT); - - AddressEntry fundingAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING); - AddressEntry reservedForTradeAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE); - AddressEntry changeAddressEntry = walletService.getFreshAddressEntry(); - Address fundingAddress = fundingAddressEntry.getAddress(); - Address reservedForTradeAddress = reservedForTradeAddressEntry.getAddress(); - Address changeAddress = changeAddressEntry.getAddress(); - TradeWalletService tradeWalletService = processModel.getTradeWalletService(); - Transaction transaction; - - String feeReceiver = FeeReceiverSelector.getAddress(processModel.getDaoFacade(), processModel.getFilterManager()); - - if (trade.isCurrencyForTakerFeeBtc()) { - transaction = tradeWalletService.createBtcTradingFeeTx( - fundingAddress, - reservedForTradeAddress, - changeAddress, - processModel.getFundsNeededForTrade(), - processModel.isUseSavingsWallet(), - trade.getTakerFee(), - trade.getTxFee(), - feeReceiver, - false, - null); - } else { - Transaction preparedBurnFeeTx = processModel.getBsqWalletService().getPreparedTradeFeeTx(trade.getTakerFee()); - Transaction txWithBsqFee = tradeWalletService.completeBsqTradingFeeTx(preparedBurnFeeTx, - fundingAddress, - reservedForTradeAddress, - changeAddress, - processModel.getFundsNeededForTrade(), - processModel.isUseSavingsWallet(), - trade.getTxFee()); - transaction = processModel.getBsqWalletService().signTx(txWithBsqFee); - WalletService.checkAllScriptSignaturesForTx(transaction); - } - - // We did not broadcast and commit the tx yet to avoid issues with lost trade fee in case the - // take offer attempt failed. - - // We do not set the takerFeeTxId yet to trade as it is not published. - processModel.setTakeOfferFeeTxId(transaction.getTxId().toString()); - - processModel.setTakeOfferFeeTx(transaction); - walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING); - - processModel.getTradeManager().requestPersistence(); - - complete(); - } catch (Throwable t) { - if (t instanceof DaoDisabledException) { - failed("You cannot pay the trade fee in BSQ at the moment because the DAO features have been " + - "disabled due technical problems. Please use the BTC fee option until the issues are resolved. " + - "For more information please visit the Bisq Forum."); - } else { - failed(t); - } - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/FundMultisig.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/FundMultisig.java new file mode 100644 index 00000000..6e0c3bad --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/FundMultisig.java @@ -0,0 +1,152 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.taker; + +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.util.ParsingUtils; + +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.taskrunner.TaskRunner; + +import java.math.BigInteger; + +import java.util.List; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroDestination; +import monero.wallet.model.MoneroTxConfig; +import monero.wallet.model.MoneroTxWallet; + +@Slf4j +public class FundMultisig extends TradeTask { + @SuppressWarnings({"unused"}) + public FundMultisig(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + if (trade.getMakerDepositTxId() != null) throw new RuntimeException("Maker deposit tx already set"); // TODO (woodser): proper error handling + if (trade.getTakerDepositTxId() != null) throw new RuntimeException("Taker deposit tx already set"); + + // decide who goes first + boolean takerGoesFirst = true; // TODO (woodser): decide who goes first based on rep? + + // taker and maker fund multisig + String takerDepositTxId = null; + if (takerGoesFirst) takerDepositTxId = takerFundMultisig(); + makerFundMultisig(takerDepositTxId); + } catch (Throwable t) { + failed(t); + } + } + + private String takerFundMultisig() { + + // collect parameters for transfer to multisig + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + MoneroWallet wallet = walletService.getWallet(); + MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); + String multisigAddress = multisigWallet.getPrimaryAddress(); + boolean tradeReserved = trade.getTakerFeeTxId() != null && trade.getState() == Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX; + + // if trade is reserved, fund multisig from reserved account + if (tradeReserved) { + XmrAddressEntry addressEntry = walletService.getAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.RESERVED_FOR_TRADE).get(); + int accountIndex = addressEntry.getAccountIndex(); + if (!wallet.getBalance(accountIndex).equals(wallet.getUnlockedBalance(accountIndex)) || wallet.getBalance(accountIndex).equals(BigInteger.valueOf(0))) { + throw new RuntimeException("Reserved trade account " + accountIndex + " balance expected to be fully available. Balance: " + wallet.getBalance(accountIndex) + ", Unlocked Balance: " + wallet.getUnlockedBalance(accountIndex)); + } + List txs = wallet.sweepUnlocked(new MoneroTxConfig() + .setAccountIndex(accountIndex) + .setAddress(multisigAddress) + .setCanSplit(false) + .setRelay(true)); + if (txs.size() != 1) throw new RuntimeException("Sweeping reserved trade account to multisig expected to create exactly 1 transaction"); + processModel.setTakerPreparedDepositTxId(txs.get(0).getHash()); + trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX); + System.out.println("SUCCESSFULLY SWEPT RESERVED TRADE ACCOUNT TO MULTISIG"); + System.out.println(txs.get(0)); + return txs.get(0).getHash(); + } + + // otherwise fund multisig from account 0 and pay taker fee in one transaction + else { + String tradeFeeAddress = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM"; // TODO (woodser): don't hardcode + MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig() + .setAccountIndex(0) + .setDestinations( + new MoneroDestination(tradeFeeAddress, ParsingUtils.satoshisToXmrAtomicUnits(trade.getTakerFee().value)), + new MoneroDestination(multisigAddress, ParsingUtils.satoshisToXmrAtomicUnits(processModel.getFundsNeededForTradeAsLong()))) + .setRelay(true)); + System.out.println("SUCCESSFULLY TRANSFERRED FROM ACCOUNT 0 TO MULTISIG AND PAID FEE"); + System.out.println(tx); + trade.setTakerFeeTxId(tx.getHash()); + processModel.setTakerPreparedDepositTxId(tx.getHash()); + trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX); + return tx.getHash(); + } + } + + private void makerFundMultisig(String takerDepositTxId) { + + // create message to initialize trade + DepositTxMessage request = new DepositTxMessage( + UUID.randomUUID().toString(), + processModel.getOffer().getId(), + processModel.getMyNodeAddress(), + trade.getTakerFeeTxId(), + takerDepositTxId); + + // send request to maker + // TODO (woodser): get maker deposit tx id by processing DepositTxMessage or DepositTxRequest/DepositTxResponse + log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getMakerNodeAddress(), trade.getMakerPubKeyRing()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getMakerNodeAddress(), + trade.getMakerPubKeyRing(), + request, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at arbitrator: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); + //trade.setState(Trade.State.SELLER_PUBLISHED_DEPOSIT_TX); // TODO (woodser): Trade.State.MAKER_PUBLISHED_DEPOSIT_TX + if (takerDepositTxId == null) takerFundMultisig(); // send taker funds if not already + complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getArbitratorNodeAddress(), errorMessage); + appendToErrorMessage("Sending request failed: request=" + request + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerCreateFeeTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerCreateFeeTx.java new file mode 100644 index 00000000..62a5e4ce --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerCreateFeeTx.java @@ -0,0 +1,76 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.taker; + +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.dao.exceptions.DaoDisabledException; +import bisq.core.trade.Trade; +import bisq.core.trade.protocol.tasks.TradeTask; + +import bisq.common.taskrunner.TaskRunner; + +import org.bitcoinj.core.Coin; + +import lombok.extern.slf4j.Slf4j; + + + +import monero.wallet.model.MoneroTxWallet; + +// TODO (woodser): rename this to TakerCreateFeeTx or rename TakerPublishFeeTx to TakerPublishReserveTradeTx for consistency +@Slf4j +public class TakerCreateFeeTx extends TradeTask { + @SuppressWarnings({ "unused" }) + public TakerCreateFeeTx(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + String id = processModel.getOffer().getId(); + XmrAddressEntry reservedForTradeAddressEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.RESERVED_FOR_TRADE); + TradeWalletService tradeWalletService = processModel.getTradeWalletService(); + String feeReceiver = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM"; // TODO (woodser): don't hardcode + + // pay trade fee to reserve trade + MoneroTxWallet tx = tradeWalletService.createXmrTradingFeeTx( + reservedForTradeAddressEntry.getAddressString(), + Coin.valueOf(processModel.getFundsNeededForTradeAsLong()), + trade.getTakerFee(), + trade.getTxFee(), + feeReceiver, + false); + + trade.setTakerFeeTxId(tx.getHash()); + processModel.setTakeOfferFeeTx(tx); + complete(); + } catch (Throwable t) { + if (t instanceof DaoDisabledException) { + failed("You cannot pay the trade fee in BSQ at the moment because the DAO features have been " + "disabled due technical problems. Please use the BTC fee option until the issues are resolved. " + "For more information please visit the Bisq Forum."); + } else { + failed(t); + } + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesMakerDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesMakerDepositTxMessage.java new file mode 100644 index 00000000..f3fe1154 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesMakerDepositTxMessage.java @@ -0,0 +1,54 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.taker; + +import bisq.core.trade.Trade; +import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.protocol.tasks.TradeTask; + +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.util.Validator.checkTradeId; +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class TakerProcessesMakerDepositTxMessage extends TradeTask { + @SuppressWarnings({"unused"}) + public TakerProcessesMakerDepositTxMessage(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + log.debug("current trade state " + trade.getState()); + DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); + checkTradeId(processModel.getOfferId(), message); + checkNotNull(message); + + // TODO (woodser): verify that deposit amount + tx fee = security deposit + trade fee (+ trade amount), or require exact security deposit to multisig? + processModel.setMakerPreparedDepositTxId(message.getDepositTxId()); + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerPublishFeeTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerPublishFeeTx.java index 6324f74e..dd2663b3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerPublishFeeTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerPublishFeeTx.java @@ -17,21 +17,16 @@ package bisq.core.trade.protocol.tasks.taker; -import bisq.core.btc.exceptions.TxBroadcastException; -import bisq.core.btc.wallet.BsqWalletService; -import bisq.core.btc.wallet.TradeWalletService; -import bisq.core.btc.wallet.TxBroadcaster; -import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Transaction; - import lombok.extern.slf4j.Slf4j; -import javax.annotation.Nullable; + + +import monero.wallet.model.MoneroTxWallet; @Slf4j public class TakerPublishFeeTx extends TradeTask { @@ -44,79 +39,20 @@ public class TakerPublishFeeTx extends TradeTask { try { runInterceptHook(); - TradeWalletService tradeWalletService = processModel.getTradeWalletService(); - Transaction takeOfferFeeTx = processModel.getTakeOfferFeeTx(); - - if (trade.isCurrencyForTakerFeeBtc()) { - // We committed to be sure the tx gets into the wallet even in the broadcast process it would be - // committed as well, but if user would close app before success handler returns the commit would not - // be done. - tradeWalletService.commitTx(takeOfferFeeTx); - - tradeWalletService.broadcastTx(takeOfferFeeTx, - new TxBroadcaster.Callback() { - @Override - public void onSuccess(Transaction transaction) { - TakerPublishFeeTx.this.onSuccess(transaction); - } - - @Override - public void onFailure(TxBroadcastException exception) { - TakerPublishFeeTx.this.onFailure(exception); - } - }); - } else { - BsqWalletService bsqWalletService = processModel.getBsqWalletService(); - bsqWalletService.commitTx(takeOfferFeeTx, TxType.PAY_TRADE_FEE); - // We need to create another instance, otherwise the tx would trigger an invalid state exception - // if it gets committed 2 times - tradeWalletService.commitTx(tradeWalletService.getClonedTransaction(takeOfferFeeTx)); - - // We use a short timeout as there are issues with BSQ txs. See comment in TxBroadcaster - bsqWalletService.broadcastTx(takeOfferFeeTx, - new TxBroadcaster.Callback() { - @Override - public void onSuccess(@Nullable Transaction transaction) { - TakerPublishFeeTx.this.onSuccess(transaction); - } - - @Override - public void onFailure(TxBroadcastException exception) { - TakerPublishFeeTx.this.onFailure(exception); - } - }, - 1); - } + // We committed to be sure the tx gets into the wallet even in the broadcast process it would be + // committed as well, but if user would close app before success handler returns the commit would not + // be done. + MoneroTxWallet takeOfferFeeTx = processModel.getTakeOfferFeeTx(); + processModel.getProvider().getXmrWalletService().getWallet().relayTx(takeOfferFeeTx); + System.out.println("TAKER PUBLISHED FEE TX"); + System.out.println(takeOfferFeeTx); + trade.setState(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX); + complete(); } catch (Throwable t) { + log.error(t.toString()); + t.printStackTrace(); + trade.setErrorMessage("An error occurred.\n Error message:\n" + t.getMessage()); failed(t); } } - - protected void onFailure(TxBroadcastException exception) { - if (!completed) { - log.error(exception.toString()); - exception.printStackTrace(); - trade.setErrorMessage("An error occurred.\n" + - "Error message:\n" - + exception.getMessage()); - failed(exception); - } else { - log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); - } - } - - protected void onSuccess(@org.jetbrains.annotations.Nullable Transaction transaction) { - if (!completed) { - if (transaction != null) { - trade.setTakerFeeTxId(transaction.getTxId().toString()); - trade.setState(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX); - - processModel.getTradeManager().requestPersistence(); - - complete(); - } - } else { - log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); - } - } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitMultisigMessages.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitMultisigMessages.java new file mode 100644 index 00000000..79f3d333 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitMultisigMessages.java @@ -0,0 +1,125 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.taker; + +import bisq.core.trade.Trade; +import bisq.core.trade.messages.InitMultisigMessage; +import bisq.core.trade.protocol.tasks.TradeTask; + +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.app.Version; +import bisq.common.taskrunner.TaskRunner; + +import java.util.Date; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + + + +import monero.wallet.MoneroWallet; + +@Slf4j +public class TakerSendInitMultisigMessages extends TradeTask { + + private boolean makerAck; + private boolean arbitratorAck; + + @SuppressWarnings({"unused"}) + public TakerSendInitMultisigMessages(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // create wallet for multisig + // TODO (woodser): assert that wallet does not already exist + MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId()); + + // prepare multisig + String preparedHex = multisigWallet.prepareMultisig(); + processModel.setPreparedMultisigHex(preparedHex); + System.out.println("Prepared multisig hex: " + preparedHex); + + // create message to initialize trade + InitMultisigMessage message = new InitMultisigMessage( + processModel.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime(), + preparedHex, + null); + + // send request to arbitrator + log.info("Send {} with offerId {} and uid {} to arbitrator {} with pub key ring", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getArbitratorNodeAddress(), + trade.getArbitratorPubKeyRing(), + message, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at arbitrator: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); + arbitratorAck = true; + checkComplete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getArbitratorNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + + // send request to maker + log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getMakerNodeAddress()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getMakerNodeAddress(), + trade.getMakerPubKeyRing(), + message, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at peer: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); + makerAck = true; + checkComplete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getMakerNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + } catch (Throwable t) { + failed(t); + } + } + + private void checkComplete() { + if (makerAck && arbitratorAck) complete(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitTradeRequests.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitTradeRequests.java new file mode 100644 index 00000000..1e437d4d --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitTradeRequests.java @@ -0,0 +1,161 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.taker; + +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.support.dispute.mediation.mediator.Mediator; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.user.User; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.app.Version; +import bisq.common.crypto.Sig; +import bisq.common.taskrunner.TaskRunner; + +import com.google.common.base.Charsets; + +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class TakerSendInitTradeRequests extends TradeTask { + + private boolean makerAck = false; + private boolean arbitratorAck = false; + + @SuppressWarnings({"unused"}) + public TakerSendInitTradeRequests(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // collect fields for requests + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + String offerId = processModel.getOffer().getId(); + String takerPayoutAddress = walletService.getNewAddressEntry(offerId, XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); + walletService.getWallet().save(); + checkNotNull(trade.getTradeAmount(), "TradeAmount must not be null"); + //checkNotNull(trade.getTakerFeeTxId(), "TakeOfferFeeTxId must not be null"); + final User user = processModel.getUser(); + checkNotNull(user, "User must not be null"); + + // must have mediator address // TODO (woodser): using mediator instead of arbitrator because it's initially assigned, keep or replace with arbitrator role? + final List acceptedMediatorAddresses = user.getAcceptedMediatorAddresses(); + checkNotNull(acceptedMediatorAddresses, "acceptedMediatorAddresses must not be null"); + Mediator mediator = checkNotNull(user.getAcceptedMediatorByAddress(trade.getArbitratorNodeAddress()), "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null"); // TODO (woodser): switch to arbitrator? + processModel.getArbitrator().setPubKeyRing(mediator.getPubKeyRing()); + trade.setArbitratorPubKeyRing(processModel.getArbitrator().getPubKeyRing()); + trade.setMakerPubKeyRing(processModel.getTradingPeer().getPubKeyRing()); + + // Taker has to use offerId as nonce (he cannot manipulate that - so we avoid to have a challenge protocol for passing the nonce we want to get signed) + // He cannot manipulate the offerId - so we avoid to have a challenge protocol for passing the nonce we want to get signed. + final PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), "processModel.getPaymentAccountPayload(trade) must not be null"); + byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offerId.getBytes(Charsets.UTF_8)); + + // create message to initialize trade + InitTradeRequest message = new InitTradeRequest( + offerId, + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + trade.getTradeAmount().value, + trade.getTradePrice().getValue(), + trade.getTxFee().getValue(), + trade.getTakerFee().getValue(), + takerPayoutAddress, + paymentAccountPayload, + processModel.getAccountId(), + trade.getTakerFeeTxId(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + sig, + new Date().getTime(), + trade.getTakerNodeAddress(), + trade.getMakerNodeAddress(), + trade.getArbitratorNodeAddress()); + + System.out.println("TakerSendsInitTradeRequest sending message:"); + System.out.println(message); + + // send request to arbitrator + log.info("Send {} with offerId {} and uid {} to arbitrator {} with pub key ring", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getArbitratorNodeAddress(), + trade.getArbitratorPubKeyRing(), + message, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at arbitrator: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); + arbitratorAck = true; + checkComplete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getArbitratorNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + + // send request to maker + log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getMakerNodeAddress()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getMakerNodeAddress(), + trade.getMakerPubKeyRing(), + message, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at peer: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); + makerAck = true; + checkComplete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getMakerNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + + } catch (Throwable t) { + failed(t); + } + } + + private void checkComplete() { + if (makerAck && arbitratorAck) complete(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java index 89b201b4..13b72563 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java @@ -106,7 +106,7 @@ public class TakerSendInputsForDepositTxRequest extends TradeTask { trade.getTradePrice().getValue(), trade.getTxFee().getValue(), trade.getTakerFee().getValue(), - trade.isCurrencyForTakerFeeBtc(), + true, processModel.getRawTransactionInputs(), processModel.getChangeOutputValue(), processModel.getChangeOutputAddress(), diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendReadyToFundMultisigRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendReadyToFundMultisigRequest.java new file mode 100644 index 00000000..1fc6bfbb --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendReadyToFundMultisigRequest.java @@ -0,0 +1,77 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.taker; + +import bisq.core.trade.Trade; +import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; +import bisq.core.trade.protocol.tasks.TradeTask; + +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.app.Version; +import bisq.common.taskrunner.TaskRunner; + +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class TakerSendReadyToFundMultisigRequest extends TradeTask { + @SuppressWarnings({"unused"}) + public TakerSendReadyToFundMultisigRequest(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // create message ask maker if ready + MakerReadyToFundMultisigRequest request = new MakerReadyToFundMultisigRequest( + processModel.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion()); + + // send request to maker + log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getMakerNodeAddress(), trade.getMakerPubKeyRing()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getMakerNodeAddress(), + trade.getMakerPubKeyRing(), + request, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at maker: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); + complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getMakerNodeAddress(), errorMessage); + appendToErrorMessage("Sending request failed: request=" + request + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSetupDepositTxsListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSetupDepositTxsListener.java new file mode 100644 index 00000000..64c0dedf --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSetupDepositTxsListener.java @@ -0,0 +1,19 @@ +package bisq.core.trade.protocol.tasks.taker; + +import bisq.core.trade.Trade; +import bisq.core.trade.Trade.State; +import bisq.core.trade.protocol.tasks.SetupDepositTxsListener; + +import bisq.common.taskrunner.TaskRunner; + +public class TakerSetupDepositTxsListener extends SetupDepositTxsListener { + + public TakerSetupDepositTxsListener(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected State getSeenState() { + return Trade.State.TAKER_SAW_DEPOSIT_TX_IN_NETWORK; + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java index afe14cba..11333650 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java @@ -17,12 +17,13 @@ package bisq.core.trade.protocol.tasks.taker; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.trade.Contract; import bisq.core.trade.SellerAsTakerTrade; import bisq.core.trade.Trade; +import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.tasks.TradeTask; @@ -35,11 +36,9 @@ import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; -import java.util.Arrays; - import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; +import static bisq.core.util.Validator.nonEmptyStringOf; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -53,8 +52,25 @@ public class TakerVerifyAndSignContract extends TradeTask { try { runInterceptHook(); - String takerFeeTxId = checkNotNull(processModel.getTakeOfferFeeTxId()); + + //String takerFeeTxId = checkNotNull(processModel.getTakeOfferFeeTxId())m; // TODO (woodser): take offer fee tx id removed from contract + + // collect maker info from response + // TODO (woodser): this is not right place to collect maker contract info TradingPeer maker = processModel.getTradingPeer(); + MakerReadyToFundMultisigResponse response = (MakerReadyToFundMultisigResponse) processModel.getTradeMessage(); + maker.setPaymentAccountPayload(response.getMakerPaymentAccountPayload()); + System.out.println("MAKER PAYOUT ADDRESS: " + maker.getPayoutAddressString()); + maker.setPayoutAddressString(response.getMakerPayoutAddressString()); + System.out.println("MAKER ACCOUNT ID: " + maker.getAccountId()); + maker.setPayoutAddressString(response.getMakerPayoutAddressString()); + TradingPeer tradingPeer = processModel.getTradingPeer(); + tradingPeer.setPaymentAccountPayload(checkNotNull(response.getMakerPaymentAccountPayload())); + tradingPeer.setAccountId(nonEmptyStringOf(response.getMakerAccountId())); + tradingPeer.setContractAsJson(nonEmptyStringOf(response.getMakerContractAsJson())); + tradingPeer.setContractSignature(nonEmptyStringOf(response.getMakerContractSignature())); + tradingPeer.setPayoutAddressString(nonEmptyStringOf(response.getMakerPayoutAddressString())); + tradingPeer.setCurrentDate(response.getCurrentDate()); PaymentAccountPayload makerPaymentAccountPayload = checkNotNull(maker.getPaymentAccountPayload()); PaymentAccountPayload takerPaymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade)); @@ -66,25 +82,27 @@ public class TakerVerifyAndSignContract extends TradeTask { processModel.getMyNodeAddress() : processModel.getTempTradingPeerNodeAddress(); - BtcWalletService walletService = processModel.getBtcWalletService(); + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); String id = processModel.getOffer().getId(); - AddressEntry takerPayoutAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT); + XmrAddressEntry takerPayoutAddressEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.TRADE_PAYOUT); String takerPayoutAddressString = takerPayoutAddressEntry.getAddressString(); - AddressEntry takerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); - byte[] takerMultiSigPubKey = processModel.getMyMultiSigPubKey(); - checkArgument(Arrays.equals(takerMultiSigPubKey, - takerMultiSigAddressEntry.getPubKey()), - "takerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); + + // TODO (woodser): xmr not using pub key ring for multisig address verification, needed? +// AddressEntry takerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); +// byte[] takerMultiSigPubKey = processModel.getMyMultiSigPubKey(); +// checkArgument(Arrays.equals(takerMultiSigPubKey, +// takerMultiSigAddressEntry.getPubKey()), +// "takerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); Coin tradeAmount = checkNotNull(trade.getTradeAmount()); Contract contract = new Contract( processModel.getOffer().getOfferPayload(), tradeAmount.value, trade.getTradePrice().getValue(), - takerFeeTxId, + //takerFeeTxId, buyerNodeAddress, sellerNodeAddress, - trade.getMediatorNodeAddress(), + trade.getArbitratorNodeAddress(), // TODO (woodser): updated from mediator, update and use rest of TakerVerifyAndSignContract isBuyerMakerAndSellerTaker, maker.getAccountId(), processModel.getAccountId(), @@ -94,10 +112,7 @@ public class TakerVerifyAndSignContract extends TradeTask { processModel.getPubKeyRing(), maker.getPayoutAddressString(), takerPayoutAddressString, - maker.getMultiSigPubKey(), - takerMultiSigPubKey, - trade.getLockTime(), - trade.getRefundAgentNodeAddress() + 0 ); String contractAsJson = Utilities.objectToJson(contract); diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java index 864b08f9..4fb69482 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java @@ -97,7 +97,8 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl trade.getTradePrice(), trade.getTradeAmount(), trade.getDate(), - trade.getDepositTxId(), + trade.getMakerDepositTxId(), + trade.getTakerDepositTxId(), extraDataMap); } @@ -123,7 +124,9 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl private final long tradeDate; @JsonExclude @Nullable - private final String depositTxId; + private final String makerDepositTxId; + @Nullable + private final String takerDepositTxId; // Hash get set in constructor from json of all the other data fields (with hash = null). @JsonExclude @@ -141,7 +144,8 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl Price tradePrice, Coin tradeAmount, Date tradeDate, - String depositTxId, + String makerDepositTxId, + String takerDepositTxId, Map extraDataMap) { this(offerPayload.getDirection(), offerPayload.getBaseCurrencyCode(), @@ -156,7 +160,8 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl tradePrice.getValue(), tradeAmount.value, tradeDate.getTime(), - depositTxId, + makerDepositTxId, + takerDepositTxId, null, extraDataMap); } @@ -178,7 +183,8 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl long tradePrice, long tradeAmount, long tradeDate, - @Nullable String depositTxId, + @Nullable String makerDepositTxId, + @Nullable String takerDepositTxId, @Nullable byte[] hash, @Nullable Map extraDataMap) { this.direction = direction; @@ -194,7 +200,8 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl this.tradePrice = tradePrice; this.tradeAmount = tradeAmount; this.tradeDate = tradeDate; - this.depositTxId = depositTxId; + this.makerDepositTxId = makerDepositTxId; + this.takerDepositTxId = takerDepositTxId; this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); this.hash = hash == null ? createHash() : hash; @@ -224,7 +231,8 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl .setTradeDate(tradeDate) .setHash(ByteString.copyFrom(hash)); Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); - Optional.ofNullable(depositTxId).ifPresent(builder::setDepositTxId); + Optional.ofNullable(makerDepositTxId).ifPresent(builder::setMakerDepositTxId); + Optional.ofNullable(takerDepositTxId).ifPresent(builder::setTakerDepositTxId); return builder; } @@ -252,7 +260,8 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl proto.getTradePrice(), proto.getTradeAmount(), proto.getTradeDate(), - ProtoUtil.stringOrNullFromProto(proto.getDepositTxId()), + ProtoUtil.stringOrNullFromProto(proto.getMakerDepositTxId()), + ProtoUtil.stringOrNullFromProto(proto.getTakerDepositTxId()), null, // We want to clean up the hashes with the changed hash method in v.1.2.0 so we don't use the value from the proto CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap()); } @@ -319,8 +328,9 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl // Since the trade wasn't executed it's better to filter it out to avoid it having an undue influence on the // BSQ trade stats. boolean excludedFailedTrade = offerId.equals("6E5KOI6O-3a06a037-6f03-4bfa-98c2-59f49f73466a-112"); - boolean depositTxIdValid = depositTxId == null || !depositTxId.isEmpty(); - return tradeAmount > 0 && tradePrice > 0 && !excludedFailedTrade && depositTxIdValid; + boolean makerDepositTxIdValid = makerDepositTxId == null || !makerDepositTxId.isEmpty(); + boolean takerDepositTxIdValid = takerDepositTxId == null || !takerDepositTxId.isEmpty(); + return tradeAmount > 0 && tradePrice > 0 && !excludedFailedTrade && makerDepositTxIdValid && takerDepositTxIdValid; } // TODO: Can be removed as soon as everyone uses v1.2.6+ @@ -359,7 +369,8 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl ",\n tradePrice=" + tradePrice + ",\n tradeAmount=" + tradeAmount + ",\n tradeDate=" + tradeDate + - ",\n depositTxId='" + depositTxId + '\'' + + ",\n makerDepositTxId='" + makerDepositTxId + '\'' + + ",\n takerDepositTxId='" + takerDepositTxId + '\'' + ",\n hash=" + Utilities.bytesAsHexString(hash) + ",\n extraDataMap=" + extraDataMap + "\n}"; diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics3.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics3.java index a4dd770c..bff5b416 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics3.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics3.java @@ -84,32 +84,23 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl if (referralId != null) { extraDataMap.put(OfferPayload.REFERRAL_ID, referralId); } - - NodeAddress mediatorNodeAddress = checkNotNull(trade.getMediatorNodeAddress()); - // The first 4 chars are sufficient to identify a mediator. + + NodeAddress arbitratorNodeAddress = checkNotNull(trade.getArbitratorNodeAddress()); + + // The first 4 chars are sufficient to identify an arbitrator. // For testing with regtest/localhost we use the full address as its localhost and would result in - // same values for multiple mediators. - String truncatedMediatorNodeAddress = isTorNetworkNode ? - mediatorNodeAddress.getFullAddress().substring(0, 4) : - mediatorNodeAddress.getFullAddress(); - - // RefundAgentNodeAddress can be null if converted from old version. - String truncatedRefundAgentNodeAddress = null; - NodeAddress refundAgentNodeAddress = trade.getRefundAgentNodeAddress(); - if (refundAgentNodeAddress != null) { - truncatedRefundAgentNodeAddress = isTorNetworkNode ? - refundAgentNodeAddress.getFullAddress().substring(0, 4) : - refundAgentNodeAddress.getFullAddress(); - } - + // same values for multiple arbitrators. + String truncatedArbitratorNodeAddress = isTorNetworkNode ? + arbitratorNodeAddress.getFullAddress().substring(0, 4) : + arbitratorNodeAddress.getFullAddress(); + Offer offer = checkNotNull(trade.getOffer()); return new TradeStatistics3(offer.getCurrencyCode(), trade.getTradePrice().getValue(), trade.getTradeAmountAsLong(), offer.getPaymentMethod().getId(), trade.getTakeOfferDate().getTime(), - truncatedMediatorNodeAddress, - truncatedRefundAgentNodeAddress, + truncatedArbitratorNodeAddress, extraDataMap); } @@ -169,11 +160,7 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl @Nullable @JsonExclude @Getter - private String mediator; - @Nullable - @JsonExclude - @Getter - private String refundAgent; + private String arbitrator; // todo should we add referrerId as well? get added to extra map atm but not used so far @@ -202,16 +189,14 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl long amount, String paymentMethod, long date, - String mediator, - String refundAgent, + String arbitrator, @Nullable Map extraDataMap) { this(currency, price, amount, paymentMethod, date, - mediator, - refundAgent, + arbitrator, extraDataMap, null); } @@ -222,16 +207,14 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl long amount, String paymentMethod, long date, - String mediator, - String refundAgent, + String arbitrator, @Nullable byte[] hash) { this(currency, price, amount, paymentMethod, date, - mediator, - refundAgent, + arbitrator, null, hash); } @@ -246,8 +229,7 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl long amount, String paymentMethod, long date, - @Nullable String mediator, - @Nullable String refundAgent, + @Nullable String arbitrator, @Nullable Map extraDataMap, @Nullable byte[] hash) { this.currency = currency; @@ -261,8 +243,7 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl } this.paymentMethod = tempPaymentMethod; this.date = date; - this.mediator = mediator; - this.refundAgent = refundAgent; + this.arbitrator = arbitrator; this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); this.hash = hash == null ? createHash() : hash; @@ -285,8 +266,7 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl .setPaymentMethod(paymentMethod) .setDate(date) .setHash(ByteString.copyFrom(hash)); - Optional.ofNullable(mediator).ifPresent(builder::setMediator); - Optional.ofNullable(refundAgent).ifPresent(builder::setRefundAgent); + Optional.ofNullable(arbitrator).ifPresent(builder::setArbitrator); Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); return builder; } @@ -307,8 +287,7 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl proto.getAmount(), proto.getPaymentMethod(), proto.getDate(), - ProtoUtil.stringOrNullFromProto(proto.getMediator()), - ProtoUtil.stringOrNullFromProto(proto.getRefundAgent()), + ProtoUtil.stringOrNullFromProto(proto.getArbitrator()), CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap(), proto.getHash().toByteArray()); } @@ -356,8 +335,7 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl } public void pruneOptionalData() { - mediator = null; - refundAgent = null; + arbitrator = null; } public String getPaymentMethod() { @@ -438,8 +416,7 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl ",\n amount=" + amount + ",\n paymentMethod='" + paymentMethod + '\'' + ",\n date=" + date + - ",\n mediator='" + mediator + '\'' + - ",\n refundAgent='" + refundAgent + '\'' + + ",\n arbitrator='" + arbitrator + '\'' + ",\n hash=" + Utilities.bytesAsHexString(hash) + ",\n extraDataMap=" + extraDataMap + "\n}"; diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsConverter.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsConverter.java index f360dd55..2fdf2780 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsConverter.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsConverter.java @@ -156,8 +156,7 @@ public class TradeStatisticsConverter { private static TradeStatistics3 convertToTradeStatistics3(TradeStatistics2 tradeStatistics2) { Map extraDataMap = tradeStatistics2.getExtraDataMap(); - String mediator = extraDataMap != null ? extraDataMap.get(TradeStatistics2.MEDIATOR_ADDRESS) : null; - String refundAgent = extraDataMap != null ? extraDataMap.get(TradeStatistics2.REFUND_AGENT_ADDRESS) : null; + String mediator = extraDataMap != null ? extraDataMap.get(TradeStatistics2.MEDIATOR_ADDRESS) : null; // TODO (woodser): using mediator as arbitrator long time = tradeStatistics2.getTradeDate().getTime(); // We need to avoid that we duplicate tradeStatistics2 objects in case both traders have not updated yet. // Before v1.4.0 both traders published the trade statistics. If one trader has updated he will check @@ -173,7 +172,6 @@ public class TradeStatisticsConverter { tradeStatistics2.getOfferPaymentMethod(), time, mediator, - refundAgent, hash); } } diff --git a/core/src/main/java/bisq/core/util/ParsingUtils.java b/core/src/main/java/bisq/core/util/ParsingUtils.java index b9910f5f..30743888 100644 --- a/core/src/main/java/bisq/core/util/ParsingUtils.java +++ b/core/src/main/java/bisq/core/util/ParsingUtils.java @@ -10,10 +10,30 @@ import org.bitcoinj.utils.MonetaryFormat; import org.apache.commons.lang3.StringUtils; +import java.math.BigInteger; + import lombok.extern.slf4j.Slf4j; @Slf4j public class ParsingUtils { + + /** + * Temporary multiplier to convert Coin satoshis to XMR atomic units. + * + * TODO (woodser): replace bitcoinj/Coin entirely? + */ + private static BigInteger XMR_SATOSHI_MULTIPLIER = BigInteger.valueOf(10000); // TODO (woodser): make this private and expose satoshisToXmrAtomicUnits() + + /** + * Converts Coin satoshis (the base unit throughout Bisq) to XMR atomic units. + * + * @param satoshis represents an amount in XMR atomic units scaled to a long + * @return BigInteger is the equivalent amount in XMR atomic units + */ + public static BigInteger satoshisToXmrAtomicUnits(long satoshis) { + return BigInteger.valueOf(satoshis).multiply(ParsingUtils.XMR_SATOSHI_MULTIPLIER); + } + public static Coin parseToCoin(String input, CoinFormatter coinFormatter) { return parseToCoin(input, coinFormatter.getMonetaryFormat()); } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 5d989228..d8ea38c8 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -36,14 +36,14 @@ shared.iUnderstand=I understand shared.na=N/A shared.shutDown=Shut down shared.reportBug=Report bug on GitHub -shared.buyBitcoin=Buy bitcoin -shared.sellBitcoin=Sell bitcoin +shared.buyBitcoin=Buy Monero +shared.sellBitcoin=Sell Monero shared.buyCurrency=Buy {0} shared.sellCurrency=Sell {0} -shared.buyingBTCWith=buying BTC with {0} -shared.sellingBTCFor=selling BTC for {0} -shared.buyingCurrency=buying {0} (selling BTC) -shared.sellingCurrency=selling {0} (buying BTC) +shared.buyingBTCWith=buying XMR with {0} +shared.sellingBTCFor=selling XMR for {0} +shared.buyingCurrency=buying {0} (selling XMR) +shared.sellingCurrency=selling {0} (buying XMR) shared.buy=buy shared.sell=sell shared.buying=buying @@ -93,7 +93,7 @@ shared.amountMinMax=Amount (min - max) shared.amountHelp=If an offer has a minimum and a maximum amount set, then you can trade any amount within this range. shared.remove=Remove shared.goTo=Go to {0} -shared.BTCMinMax=BTC (min - max) +shared.BTCMinMax=XMR (min - max) shared.removeOffer=Remove offer shared.dontRemoveOffer=Don't remove offer shared.editOffer=Edit offer @@ -103,17 +103,17 @@ shared.faq=Visit FAQ page shared.yesCancel=Yes, cancel shared.nextStep=Next step shared.selectTradingAccount=Select trading account -shared.fundFromSavingsWalletButton=Transfer funds from Bisq wallet +shared.fundFromSavingsWalletButton=Transfer funds from Haveno wallet shared.fundFromExternalWalletButton=Open your external wallet for funding -shared.openDefaultWalletFailed=Failed to open a Bitcoin wallet application. Are you sure you have one installed? +shared.openDefaultWalletFailed=Failed to open a Monero wallet application. Are you sure you have one installed? shared.belowInPercent=Below % from market price shared.aboveInPercent=Above % from market price shared.enterPercentageValue=Enter % value shared.OR=OR -shared.notEnoughFunds=You don''t have enough funds in your Bisq wallet for this transaction—{0} is needed but only {1} is available.\n\nPlease add funds from an external wallet, or fund your Bisq wallet at Funds > Receive Funds. +shared.notEnoughFunds=You don''t have enough funds in your Haveno wallet for this transaction—{0} is needed but only {1} is available.\n\nPlease add funds from an external wallet, or fund your Haveno wallet at Funds > Receive Funds. shared.waitingForFunds=Waiting for funds... shared.depositTransactionId=Deposit transaction ID -shared.TheBTCBuyer=The BTC buyer +shared.TheBTCBuyer=The XMR buyer shared.You=You shared.sendingConfirmation=Sending confirmation... shared.sendingConfirmationAgain=Please send confirmation again @@ -126,7 +126,7 @@ shared.notUsedYet=Not used yet shared.date=Date shared.sendFundsDetailsWithFee=Sending: {0}\nFrom address: {1}\nTo receiving address: {2}.\nRequired mining fee is: {3} ({4} satoshis/vbyte)\nTransaction vsize: {5} vKb\n\nThe recipient will receive: {6}\n\nAre you sure you want to withdraw this amount? # suppress inspection "TrailingSpacesInProperty" -shared.sendFundsDetailsDust=Bisq detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Bitcoin consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n +shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n shared.copyToClipboard=Copy to clipboard shared.language=Language shared.country=Country @@ -172,7 +172,7 @@ shared.payoutTxId=Payout transaction ID shared.contractAsJson=Contract in JSON format shared.viewContractAsJson=View contract in JSON format shared.contract.title=Contract for trade with ID: {0} -shared.paymentDetails=BTC {0} payment details +shared.paymentDetails=XMR {0} payment details shared.securityDeposit=Security deposit shared.yourSecurityDeposit=Your security deposit shared.contract=Contract @@ -182,7 +182,7 @@ shared.messageSendingFailed=Message sending failed. Error: {0} shared.unlock=Unlock shared.toReceive=to receive shared.toSpend=to spend -shared.btcAmount=BTC amount +shared.btcAmount=XMR amount shared.yourLanguage=Your languages shared.addLanguage=Add language shared.total=Total @@ -233,8 +233,8 @@ shared.enabled=Enabled #################################################################### mainView.menu.market=Market -mainView.menu.buyBtc=Buy BTC -mainView.menu.sellBtc=Sell BTC +mainView.menu.buyBtc=Buy XMR +mainView.menu.sellBtc=Sell XMR mainView.menu.portfolio=Portfolio mainView.menu.funds=Funds mainView.menu.support=Support @@ -243,9 +243,9 @@ mainView.menu.account=Account mainView.menu.dao=DAO mainView.marketPriceWithProvider.label=Market price by {0} -mainView.marketPrice.bisqInternalPrice=Price of latest Bisq trade +mainView.marketPrice.bisqInternalPrice=Price of latest Haveno trade mainView.marketPrice.tooltip.bisqInternalPrice=There is no market price from external price feed providers available.\n\ - The displayed price is the latest Bisq trade price for that currency. + The displayed price is the latest Haveno trade price for that currency. mainView.marketPrice.tooltip=Market price is provided by {0}{1}\nLast update: {2}\nProvider node URL: {3} mainView.balance.available=Available balance mainView.balance.reserved=Reserved in offers @@ -257,13 +257,13 @@ mainView.footer.usingTor=(via Tor) mainView.footer.localhostBitcoinNode=(localhost) mainView.footer.btcInfo={0} {1} mainView.footer.btcFeeRate=/ Fee rate: {0} sat/vB -mainView.footer.btcInfo.initializing=Connecting to Bitcoin network +mainView.footer.btcInfo.initializing=Connecting to Monero network mainView.footer.bsqInfo.synchronizing=/ Synchronizing DAO mainView.footer.btcInfo.synchronizingWith=Synchronizing with {0} at block: {1} / {2} mainView.footer.btcInfo.synchronizedWith=Synced with {0} at block {1} mainView.footer.btcInfo.connectingTo=Connecting to mainView.footer.btcInfo.connectionFailed=Connection failed to -mainView.footer.p2pInfo=Bitcoin network peers: {0} / Bisq network peers: {1} +mainView.footer.p2pInfo=Monero network peers: {0} / Haveno network peers: {1} mainView.footer.daoFullNode=DAO full node mainView.bootstrapState.connectionToTorNetwork=(1/4) Connecting to Tor network... @@ -273,18 +273,18 @@ mainView.bootstrapState.initialDataReceived=(4/4) Initial data received mainView.bootstrapWarning.noSeedNodesAvailable=No seed nodes available mainView.bootstrapWarning.noNodesAvailable=No seed nodes and peers available -mainView.bootstrapWarning.bootstrappingToP2PFailed=Bootstrapping to Bisq network failed +mainView.bootstrapWarning.bootstrappingToP2PFailed=Bootstrapping to Haveno network failed mainView.p2pNetworkWarnMsg.noNodesAvailable=There are no seed nodes or persisted peers available for requesting data.\nPlease check your internet connection or try to restart the application. -mainView.p2pNetworkWarnMsg.connectionToP2PFailed=Connecting to the Bisq network failed (reported error: {0}).\nPlease check your internet connection or try to restart the application. +mainView.p2pNetworkWarnMsg.connectionToP2PFailed=Connecting to the Haveno network failed (reported error: {0}).\nPlease check your internet connection or try to restart the application. -mainView.walletServiceErrorMsg.timeout=Connecting to the Bitcoin network failed because of a timeout. -mainView.walletServiceErrorMsg.connectionError=Connection to the Bitcoin network failed because of an error: {0} +mainView.walletServiceErrorMsg.timeout=Connecting to the Monero network failed because of a timeout. +mainView.walletServiceErrorMsg.connectionError=Connection to the Monero network failed because of an error: {0} mainView.walletServiceErrorMsg.rejectedTxException=A transaction was rejected from the network.\n\n{0} mainView.networkWarning.allConnectionsLost=You lost the connection to all {0} network peers.\nMaybe you lost your internet connection or your computer was in standby mode. -mainView.networkWarning.localhostBitcoinLost=You lost the connection to the localhost Bitcoin node.\nPlease restart the Bisq application to connect to other Bitcoin nodes or restart the localhost Bitcoin node. +mainView.networkWarning.localhostBitcoinLost=You lost the connection to the localhost Monero node.\nPlease restart the Haveno application to connect to other Monero nodes or restart the localhost Monero node. mainView.version.update=(Update available) @@ -309,9 +309,9 @@ market.offerBook.sell=I want to sell bitcoin # SpreadView market.spread.numberOfOffersColumn=All offers ({0}) -market.spread.numberOfBuyOffersColumn=Buy BTC ({0}) -market.spread.numberOfSellOffersColumn=Sell BTC ({0}) -market.spread.totalAmountColumn=Total BTC ({0}) +market.spread.numberOfBuyOffersColumn=Buy XMR ({0}) +market.spread.numberOfSellOffersColumn=Sell XMR ({0}) +market.spread.totalAmountColumn=Total XMR ({0}) market.spread.spreadColumn=Spread market.spread.expanded=Expanded view @@ -409,12 +409,12 @@ popup.warning.tradeLimitDueAccountAgeRestriction.buyer=The allowed trade amount offerbook.warning.wrongTradeProtocol=That offer requires a different protocol version as the one used in your version of the software.\n\nPlease check if you have the latest version installed, otherwise the user who created the offer has used an older version.\n\nUsers cannot trade with an incompatible trade protocol version. offerbook.warning.userIgnored=You have added that user's onion address to your ignore list. -offerbook.warning.offerBlocked=That offer was blocked by the Bisq developers.\nProbably there is an unhandled bug causing issues when taking that offer. -offerbook.warning.currencyBanned=The currency used in that offer was blocked by the Bisq developers.\nPlease visit the Bisq Forum for more information. -offerbook.warning.paymentMethodBanned=The payment method used in that offer was blocked by the Bisq developers.\nPlease visit the Bisq Forum for more information. -offerbook.warning.nodeBlocked=The onion address of that trader was blocked by the Bisq developers.\nProbably there is an unhandled bug causing issues when taking offers from that trader. -offerbook.warning.requireUpdateToNewVersion=Your version of Bisq is not compatible for trading anymore.\n\ - Please update to the latest Bisq version at [HYPERLINK:https://bisq.network/downloads]. +offerbook.warning.offerBlocked=That offer was blocked by the Haveno developers.\nProbably there is an unhandled bug causing issues when taking that offer. +offerbook.warning.currencyBanned=The currency used in that offer was blocked by the Haveno developers.\nPlease visit the Haveno Forum for more information. +offerbook.warning.paymentMethodBanned=The payment method used in that offer was blocked by the Haveno developers.\nPlease visit the Haveno Forum for more information. +offerbook.warning.nodeBlocked=The onion address of that trader was blocked by the Haveno developers.\nProbably there is an unhandled bug causing issues when taking offers from that trader. +offerbook.warning.requireUpdateToNewVersion=Your version of Haveno is not compatible for trading anymore.\n\ + Please update to the latest Haveno version at [HYPERLINK:https://bisq.network/downloads]. offerbook.warning.offerWasAlreadyUsedInTrade=You cannot take this offer because you already took it earlier. \ It could be that your previous take-offer attempt resulted in a failed trade. @@ -433,19 +433,19 @@ offerbook.info.roundedFiatVolume=The amount was rounded to increase the privacy # Offerbook / Create offer #################################################################### -createOffer.amount.prompt=Enter amount in BTC +createOffer.amount.prompt=Enter amount in XMR createOffer.price.prompt=Enter price createOffer.volume.prompt=Enter amount in {0} -createOffer.amountPriceBox.amountDescription=Amount of BTC to {0} +createOffer.amountPriceBox.amountDescription=Amount of XMR to {0} createOffer.amountPriceBox.buy.volumeDescription=Amount in {0} to spend createOffer.amountPriceBox.sell.volumeDescription=Amount in {0} to receive -createOffer.amountPriceBox.minAmountDescription=Minimum amount of BTC +createOffer.amountPriceBox.minAmountDescription=Minimum amount of XMR createOffer.securityDeposit.prompt=Security deposit createOffer.fundsBox.title=Fund your offer createOffer.fundsBox.offerFee=Trade fee createOffer.fundsBox.networkFee=Mining fee createOffer.fundsBox.placeOfferSpinnerInfo=Offer publishing is in progress ... -createOffer.fundsBox.paymentLabel=Bisq trade with ID {0} +createOffer.fundsBox.paymentLabel=Haveno trade with ID {0} createOffer.fundsBox.fundsStructure=({0} security deposit, {1} trade fee, {2} mining fee) createOffer.fundsBox.fundsStructure.BSQ=({0} security deposit, {1} mining fee) + {2} trade fee createOffer.success.headline=Your offer has been published @@ -478,14 +478,14 @@ createOffer.createOfferFundWalletInfo.msg=You need to deposit {0} to this offer. - Your security deposit: {2}\n\ - Trading fee: {3}\n\ - Mining fee: {4}\n\n\ - You can choose between two options when funding your trade:\n- Use your Bisq wallet (convenient, but transactions may be linkable) OR\n- Transfer from an external wallet (potentially more private)\n\nYou will see all funding options and details after closing this popup. + You can choose between two options when funding your trade:\n- Use your Haveno wallet (convenient, but transactions may be linkable) OR\n- Transfer from an external wallet (potentially more private)\n\nYou will see all funding options and details after closing this popup. # only first part "An error occurred when placing the offer:" has been used before. We added now the rest (need update in existing translations!) createOffer.amountPriceBox.error.message=An error occurred when placing the offer:\n\n{0}\n\n\ No funds have left your wallet yet.\n\ Please restart your application and check your network connection. createOffer.setAmountPrice=Set amount and price -createOffer.warnCancelOffer=You have already funded that offer.\nIf you cancel now, your funds will be moved to your local Bisq wallet and are available for withdrawal in the \"Funds/Send funds\" screen.\nAre you sure you want to cancel? +createOffer.warnCancelOffer=You have already funded that offer.\nIf you cancel now, your funds will be moved to your local Haveno wallet and are available for withdrawal in the \"Funds/Send funds\" screen.\nAre you sure you want to cancel? createOffer.timeoutAtPublishing=A timeout occurred at publishing the offer. createOffer.errorInfo=\n\nThe maker fee is already paid. In the worst case you have lost that fee.\nPlease try to restart your application and check your network connection to see if you can resolve the issue. createOffer.tooLowSecDeposit.warning=You have set the security deposit to a lower value than the recommended default value of {0}.\n\ @@ -511,22 +511,22 @@ createOffer.minSecurityDepositUsed=Min. buyer security deposit is used # Offerbook / Take offer #################################################################### -takeOffer.amount.prompt=Enter amount in BTC -takeOffer.amountPriceBox.buy.amountDescription=Amount of BTC to sell -takeOffer.amountPriceBox.sell.amountDescription=Amount of BTC to buy +takeOffer.amount.prompt=Enter amount in XMR +takeOffer.amountPriceBox.buy.amountDescription=Amount of XMR to sell +takeOffer.amountPriceBox.sell.amountDescription=Amount of XMR to buy takeOffer.amountPriceBox.priceDescription=Price per bitcoin in {0} takeOffer.amountPriceBox.amountRangeDescription=Possible amount range takeOffer.amountPriceBox.warning.invalidBtcDecimalPlaces=The amount you have entered exceeds the number of allowed decimal places.\nThe amount has been adjusted to 4 decimal places. takeOffer.validation.amountSmallerThanMinAmount=Amount cannot be smaller than minimum amount defined in the offer. takeOffer.validation.amountLargerThanOfferAmount=Input amount cannot be higher than the amount defined in the offer. -takeOffer.validation.amountLargerThanOfferAmountMinusFee=That input amount would create dust change for the BTC seller. +takeOffer.validation.amountLargerThanOfferAmountMinusFee=That input amount would create dust change for the XMR seller. takeOffer.fundsBox.title=Fund your trade takeOffer.fundsBox.isOfferAvailable=Checking if the offer is still available ... takeOffer.fundsBox.tradeAmount=Amount to sell takeOffer.fundsBox.offerFee=Trade fee takeOffer.fundsBox.networkFee=Total mining fees takeOffer.fundsBox.takeOfferSpinnerInfo=Take offer in progress ... -takeOffer.fundsBox.paymentLabel=Bisq trade with ID {0} +takeOffer.fundsBox.paymentLabel=Haveno trade with ID {0} takeOffer.fundsBox.fundsStructure=({0} security deposit, {1} trade fee, {2} mining fee) takeOffer.success.headline=You have successfully taken an offer. takeOffer.success.info=You can see the status of your trade at \"Portfolio/Open trades\". @@ -538,11 +538,11 @@ takeOffer.noPriceFeedAvailable=You cannot take that offer as it uses a percentag takeOffer.takeOfferFundWalletInfo.headline=Fund your trade # suppress inspection "TrailingSpacesInProperty" takeOffer.takeOfferFundWalletInfo.tradeAmount=- Trade amount: {0} \n -takeOffer.takeOfferFundWalletInfo.msg=You need to deposit {0} for taking this offer.\n\nThe amount is the sum of:\n{1}- Your security deposit: {2}\n- Trading fee: {3}\n- Total mining fees: {4}\n\nYou can choose between two options when funding your trade:\n- Use your Bisq wallet (convenient, but transactions may be linkable) OR\n- Transfer from an external wallet (potentially more private)\n\nYou will see all funding options and details after closing this popup. +takeOffer.takeOfferFundWalletInfo.msg=You need to deposit {0} for taking this offer.\n\nThe amount is the sum of:\n{1}- Your security deposit: {2}\n- Trading fee: {3}\n- Total mining fees: {4}\n\nYou can choose between two options when funding your trade:\n- Use your Haveno wallet (convenient, but transactions may be linkable) OR\n- Transfer from an external wallet (potentially more private)\n\nYou will see all funding options and details after closing this popup. takeOffer.alreadyPaidInFunds=If you have already paid in funds you can withdraw it in the \"Funds/Send funds\" screen. takeOffer.paymentInfo=Payment info takeOffer.setAmountPrice=Set amount -takeOffer.alreadyFunded.askCancel=You have already funded that offer.\nIf you cancel now, your funds will be moved to your local Bisq wallet and are available for withdrawal in the \"Funds/Send funds\" screen.\nAre you sure you want to cancel? +takeOffer.alreadyFunded.askCancel=You have already funded that offer.\nIf you cancel now, your funds will be moved to your local Haveno wallet and are available for withdrawal in the \"Funds/Send funds\" screen.\nAre you sure you want to cancel? takeOffer.failed.offerNotAvailable=Take offer request failed because the offer is not available anymore. Maybe another trader has taken the offer in the meantime. takeOffer.failed.offerTaken=You cannot take that offer because the offer was already taken by another trader. takeOffer.failed.offerRemoved=You cannot take that offer because the offer has been removed in the meantime. @@ -572,7 +572,7 @@ editOffer.confirmEdit=Confirm: Edit offer editOffer.publishOffer=Publishing your offer. editOffer.failed=Editing of offer failed:\n{0} editOffer.success=Your offer has been successfully edited. -editOffer.invalidDeposit=The buyer's security deposit is not within the constraints defined by the Bisq DAO and can no longer be edited. +editOffer.invalidDeposit=The buyer's security deposit is not within the constraints defined by the Haveno DAO and can no longer be edited. #################################################################### # Portfolio @@ -596,10 +596,10 @@ portfolio.pending.invalidTx=There is an issue with a missing or invalid transact portfolio.pending.unconfirmedTooLong=Security deposit transaction on trade {0} is still unconfirmed after {1} hours. \ Check the deposit transaction at a blockchain explorer; if it has been confirmed but is not being displayed \ - as confirmed in Bisq: \n\ + as confirmed in Haveno: \n\ ● Make a data backup [HYPERLINK:https://bisq.wiki/Backing_up_application_data] \n\ ● Do an SPV resync. [HYPERLINK:https://bisq.wiki/Resyncing_SPV_file]\n\n\ - Contact Bisq support [HYPERLINK:https://keybase.io/team/bisq] if you have doubts or the issue persists. + Contact Haveno support [HYPERLINK:https://keybase.io/team/bisq] if you have doubts or the issue persists. portfolio.pending.step1.waitForConf=Wait for blockchain confirmation portfolio.pending.step2_buyer.startPayment=Start payment @@ -647,34 +647,34 @@ portfolio.pending.step1.openForDispute=The deposit transaction is still not conf portfolio.pending.step2.confReached=Your trade has reached at least one blockchain confirmation.\n\n portfolio.pending.step2_buyer.refTextWarn=Important: when making the payment, leave the \"reason for payment\" field \ - empty. DO NOT put the trade ID or any other text like 'bitcoin', 'BTC', or 'Bisq'. \ + empty. DO NOT put the trade ID or any other text like 'bitcoin', 'BTC', or 'Haveno'. \ You are free to discuss via trader chat if an alternate \"reason for payment\" would be suitable to you both. # suppress inspection "TrailingSpacesInProperty" portfolio.pending.step2_buyer.fees=If your bank charges you any fees to make the transfer, you are responsible for paying those fees. # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step2_buyer.altcoin=Please transfer from your external {0} wallet\n{1} to the BTC seller.\n\n +portfolio.pending.step2_buyer.altcoin=Please transfer from your external {0} wallet\n{1} to the XMR seller.\n\n # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step2_buyer.cash=Please go to a bank and pay {0} to the BTC seller.\n\n -portfolio.pending.step2_buyer.cash.extra=IMPORTANT REQUIREMENT:\nAfter you have done the payment write on the paper receipt: NO REFUNDS.\nThen tear it in 2 parts, make a photo and send it to the BTC seller's email address. +portfolio.pending.step2_buyer.cash=Please go to a bank and pay {0} to the XMR seller.\n\n +portfolio.pending.step2_buyer.cash.extra=IMPORTANT REQUIREMENT:\nAfter you have done the payment write on the paper receipt: NO REFUNDS.\nThen tear it in 2 parts, make a photo and send it to the XMR seller's email address. # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step2_buyer.moneyGram=Please pay {0} to the BTC seller by using MoneyGram.\n\n -portfolio.pending.step2_buyer.moneyGram.extra=IMPORTANT REQUIREMENT:\nAfter you have done the payment send the Authorisation number and a photo of the receipt by email to the BTC seller.\n\ +portfolio.pending.step2_buyer.moneyGram=Please pay {0} to the XMR seller by using MoneyGram.\n\n +portfolio.pending.step2_buyer.moneyGram.extra=IMPORTANT REQUIREMENT:\nAfter you have done the payment send the Authorisation number and a photo of the receipt by email to the XMR seller.\n\ The receipt must clearly show the seller''s full name, country, state and the amount. The seller''s email is: {0}. # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step2_buyer.westernUnion=Please pay {0} to the BTC seller by using Western Union.\n\n -portfolio.pending.step2_buyer.westernUnion.extra=IMPORTANT REQUIREMENT:\nAfter you have done the payment send the MTCN (tracking number) and a photo of the receipt by email to the BTC seller.\n\ +portfolio.pending.step2_buyer.westernUnion=Please pay {0} to the XMR seller by using Western Union.\n\n +portfolio.pending.step2_buyer.westernUnion.extra=IMPORTANT REQUIREMENT:\nAfter you have done the payment send the MTCN (tracking number) and a photo of the receipt by email to the XMR seller.\n\ The receipt must clearly show the seller''s full name, city, country and the amount. The seller''s email is: {0}. # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step2_buyer.postal=Please send {0} by \"US Postal Money Order\" to the BTC seller.\n\n +portfolio.pending.step2_buyer.postal=Please send {0} by \"US Postal Money Order\" to the XMR seller.\n\n # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step2_buyer.cashByMail=Please send {0} using \"Cash by Mail\" to the BTC seller. \ +portfolio.pending.step2_buyer.cashByMail=Please send {0} using \"Cash by Mail\" to the XMR seller. \ Specific instructions are in the trade contract, or if unclear you may ask questions via trader chat. \ - See more details about Cash by Mail on the Bisq wiki [HYPERLINK:https://bisq.wiki/Cash_by_Mail].\n\n + See more details about Cash by Mail on the Haveno wiki [HYPERLINK:https://bisq.wiki/Cash_by_Mail].\n\n # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step2_buyer.pay=Please pay {0} via the specified payment method to the BTC seller. You''ll find the seller's account details on the next screen.\n\n +portfolio.pending.step2_buyer.pay=Please pay {0} via the specified payment method to the XMR seller. You''ll find the seller's account details on the next screen.\n\n # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step2_buyer.f2f=Please contact the BTC seller by the provided contact and arrange a meeting to pay {0}.\n\n +portfolio.pending.step2_buyer.f2f=Please contact the XMR seller by the provided contact and arrange a meeting to pay {0}.\n\n portfolio.pending.step2_buyer.startPaymentUsing=Start payment using {0} portfolio.pending.step2_buyer.recipientsAccountData=Recipients {0} portfolio.pending.step2_buyer.amountToTransfer=Amount to transfer @@ -685,24 +685,24 @@ portfolio.pending.step2_buyer.fillInBsqWallet=Pay from BSQ wallet portfolio.pending.step2_buyer.warn=You still have not done your {0} payment!\nPlease note that the trade has to be completed by {1}. portfolio.pending.step2_buyer.openForDispute=You have not completed your payment!\nThe max. period for the trade has elapsed.\ Please contact the mediator for assistance. -portfolio.pending.step2_buyer.paperReceipt.headline=Did you send the paper receipt to the BTC seller? +portfolio.pending.step2_buyer.paperReceipt.headline=Did you send the paper receipt to the XMR seller? portfolio.pending.step2_buyer.paperReceipt.msg=Remember:\n\ You need to write on the paper receipt: NO REFUNDS.\n\ - Then tear it in 2 parts, make a photo and send it to the BTC seller's email address. + Then tear it in 2 parts, make a photo and send it to the XMR seller's email address. portfolio.pending.step2_buyer.moneyGramMTCNInfo.headline=Send Authorisation number and receipt -portfolio.pending.step2_buyer.moneyGramMTCNInfo.msg=You need to send the Authorisation number and a photo of the receipt by email to the BTC seller.\n\ +portfolio.pending.step2_buyer.moneyGramMTCNInfo.msg=You need to send the Authorisation number and a photo of the receipt by email to the XMR seller.\n\ The receipt must clearly show the seller''s full name, country, state and the amount. The seller''s email is: {0}.\n\n\ Did you send the Authorisation number and contract to the seller? portfolio.pending.step2_buyer.westernUnionMTCNInfo.headline=Send MTCN and receipt -portfolio.pending.step2_buyer.westernUnionMTCNInfo.msg=You need to send the MTCN (tracking number) and a photo of the receipt by email to the BTC seller.\n\ +portfolio.pending.step2_buyer.westernUnionMTCNInfo.msg=You need to send the MTCN (tracking number) and a photo of the receipt by email to the XMR seller.\n\ The receipt must clearly show the seller''s full name, city, country and the amount. The seller''s email is: {0}.\n\n\ Did you send the MTCN and contract to the seller? portfolio.pending.step2_buyer.halCashInfo.headline=Send HalCash code portfolio.pending.step2_buyer.halCashInfo.msg=You need to send a text message with the HalCash code as well as the \ - trade ID ({0}) to the BTC seller.\nThe seller''s mobile nr. is {1}.\n\n\ + trade ID ({0}) to the XMR seller.\nThe seller''s mobile nr. is {1}.\n\n\ Did you send the code to the seller? portfolio.pending.step2_buyer.fasterPaymentsHolderNameInfo=Some banks might verify the receiver's name. \ - Faster Payments accounts created in old Bisq clients do not provide the receiver's name, \ + Faster Payments accounts created in old Haveno clients do not provide the receiver's name, \ so please use trade chat to obtain it (if needed). portfolio.pending.step2_buyer.confirmStart.headline=Confirm that you have started the payment portfolio.pending.step2_buyer.confirmStart.msg=Did you initiate the {0} payment to your trading partner? @@ -710,15 +710,15 @@ portfolio.pending.step2_buyer.confirmStart.yes=Yes, I have started the payment portfolio.pending.step2_buyer.confirmStart.proof.warningTitle=You have not provided proof of payment portfolio.pending.step2_buyer.confirmStart.proof.noneProvided=You have not entered the transaction ID and the transaction key.\n\n\ By not providing this data the peer cannot use the auto-confirm feature to release the BTC as soon the XMR has been received.\n\ - Beside that, Bisq requires that the sender of the XMR transaction is able to provide this information to the mediator or arbitrator in case of a dispute.\n\ - See more details on the Bisq wiki [HYPERLINK:https://bisq.wiki/Trading_Monero#Auto-confirming_trades]. + Beside that, Haveno requires that the sender of the XMR transaction is able to provide this information to the mediator or arbitrator in case of a dispute.\n\ + See more details on the Haveno wiki [HYPERLINK:https://bisq.wiki/Trading_Monero#Auto-confirming_trades]. portfolio.pending.step2_buyer.confirmStart.proof.invalidInput=Input is not a 32 byte hexadecimal value portfolio.pending.step2_buyer.confirmStart.warningButton=Ignore and continue anyway portfolio.pending.step2_seller.waitPayment.headline=Wait for payment portfolio.pending.step2_seller.f2fInfo.headline=Buyer's contact information -portfolio.pending.step2_seller.waitPayment.msg=The deposit transaction has at least one blockchain confirmation.\nYou need to wait until the BTC buyer starts the {0} payment. -portfolio.pending.step2_seller.warn=The BTC buyer still has not done the {0} payment.\nYou need to wait until they have started the payment.\nIf the trade has not been completed on {1} the arbitrator will investigate. -portfolio.pending.step2_seller.openForDispute=The BTC buyer has not started their payment!\nThe max. allowed period for the trade has elapsed.\nYou can wait longer and give the trading peer more time or contact the mediator for assistance. +portfolio.pending.step2_seller.waitPayment.msg=The deposit transaction has at least one blockchain confirmation.\nYou need to wait until the XMR buyer starts the {0} payment. +portfolio.pending.step2_seller.warn=The XMR buyer still has not done the {0} payment.\nYou need to wait until they have started the payment.\nIf the trade has not been completed on {1} the arbitrator will investigate. +portfolio.pending.step2_seller.openForDispute=The XMR buyer has not started their payment!\nThe max. allowed period for the trade has elapsed.\nYou can wait longer and give the trading peer more time or contact the mediator for assistance. disputeChat.chatWindowTitle=Dispute chat window for trade with ID ''{0}'' tradeChat.chatWindowTitle=Trader Chat window for trade with ID ''{0}'' tradeChat.openChat=Open chat window @@ -728,7 +728,7 @@ tradeChat.rules=You can communicate with your trade peer to resolve potential pr Chat rules:\n\ \t● Do not send any links (risk of malware). You can send the transaction ID and the name of a block explorer.\n\ \t● Do not send your seed words, private keys, passwords or other sensitive information!\n\ - \t● Do not encourage trading outside of Bisq (no security).\n\ + \t● Do not encourage trading outside of Haveno (no security).\n\ \t● Do not engage in any form of social engineering scam attempts.\n\ \t● If a peer is not responding and prefers to not communicate via chat, respect their decision.\n\ \t● Keep conversation scope limited to the trade. This chat is not a messenger replacement or troll-box.\n\ @@ -747,14 +747,14 @@ message.state.ACKNOWLEDGED=Peer confirmed message receipt # suppress inspection "UnusedProperty" message.state.FAILED=Sending message failed -portfolio.pending.step3_buyer.wait.headline=Wait for BTC seller's payment confirmation -portfolio.pending.step3_buyer.wait.info=Waiting for the BTC seller''s confirmation for the receipt of the {0} payment. +portfolio.pending.step3_buyer.wait.headline=Wait for XMR seller's payment confirmation +portfolio.pending.step3_buyer.wait.info=Waiting for the XMR seller''s confirmation for the receipt of the {0} payment. portfolio.pending.step3_buyer.wait.msgStateInfo.label=Payment started message status portfolio.pending.step3_buyer.warn.part1a=on the {0} blockchain portfolio.pending.step3_buyer.warn.part1b=at your payment provider (e.g. bank) -portfolio.pending.step3_buyer.warn.part2=The BTC seller still has not confirmed your payment. Please check {0} if the \ +portfolio.pending.step3_buyer.warn.part2=The XMR seller still has not confirmed your payment. Please check {0} if the \ payment sending was successful. -portfolio.pending.step3_buyer.openForDispute=The BTC seller has not confirmed your payment! The max. period for the \ +portfolio.pending.step3_buyer.openForDispute=The XMR seller has not confirmed your payment! The max. period for the \ trade has elapsed. You can wait longer and give the trading peer more time or request assistance from the mediator. # suppress inspection "TrailingSpacesInProperty" portfolio.pending.step3_seller.part=Your trading partner has confirmed that they have initiated the {0} payment.\n\n @@ -764,22 +764,22 @@ portfolio.pending.step3_seller.altcoin={0}Please check {1} if the transaction to {2}\n\ has already sufficient blockchain confirmations.\nThe payment amount has to be {3}\n\n\ You can copy & paste your {4} address from the main screen after closing that popup. -portfolio.pending.step3_seller.postal={0}Please check if you have received {1} with \"US Postal Money Order\" from the BTC buyer. +portfolio.pending.step3_seller.postal={0}Please check if you have received {1} with \"US Postal Money Order\" from the XMR buyer. # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step3_seller.cashByMail={0}Please check if you have received {1} with \"Cash by Mail\" from the BTC buyer. +portfolio.pending.step3_seller.cashByMail={0}Please check if you have received {1} with \"Cash by Mail\" from the XMR buyer. # suppress inspection "TrailingSpacesInProperty" portfolio.pending.step3_seller.bank=Your trading partner has confirmed that they have initiated the {0} payment.\n\n\ - Please go to your online banking web page and check if you have received {1} from the BTC buyer. -portfolio.pending.step3_seller.cash=Because the payment is done via Cash Deposit the BTC buyer has to write \"NO REFUND\" on the paper receipt, tear it in 2 parts and send you a photo by email.\n\n\ + Please go to your online banking web page and check if you have received {1} from the XMR buyer. +portfolio.pending.step3_seller.cash=Because the payment is done via Cash Deposit the XMR buyer has to write \"NO REFUND\" on the paper receipt, tear it in 2 parts and send you a photo by email.\n\n\ To avoid chargeback risk, only confirm if you received the email and if you are sure the paper receipt is valid.\n\ If you are not sure, {0} portfolio.pending.step3_seller.moneyGram=The buyer has to send you the Authorisation number and a photo of the receipt by email.\n\ The receipt must clearly show your full name, country, state and the amount. Please check your email if you received the Authorisation number.\n\n\ - After closing that popup you will see the BTC buyer's name and address for picking up the money from MoneyGram.\n\n\ + After closing that popup you will see the XMR buyer's name and address for picking up the money from MoneyGram.\n\n\ Only confirm receipt after you have successfully picked up the money! portfolio.pending.step3_seller.westernUnion=The buyer has to send you the MTCN (tracking number) and a photo of the receipt by email.\n\ The receipt must clearly show your full name, city, country and the amount. Please check your email if you received the MTCN.\n\n\ - After closing that popup you will see the BTC buyer's name and address for picking up the money from Western Union.\n\n\ + After closing that popup you will see the XMR buyer's name and address for picking up the money from Western Union.\n\n\ Only confirm receipt after you have successfully picked up the money! portfolio.pending.step3_seller.halCash=The buyer has to send you the HalCash code as text message. Beside that you will receive a message from HalCash with the required information to withdraw the EUR from a HalCash supporting ATM.\n\n\ After you have picked up the money from the ATM please confirm here the receipt of the payment! @@ -800,7 +800,7 @@ portfolio.pending.step3_seller.xmrTxHash=Transaction ID portfolio.pending.step3_seller.xmrTxKey=Transaction key portfolio.pending.step3_seller.buyersAccount=Buyers account data portfolio.pending.step3_seller.confirmReceipt=Confirm payment receipt -portfolio.pending.step3_seller.buyerStartedPayment=The BTC buyer has started the {0} payment.\n{1} +portfolio.pending.step3_seller.buyerStartedPayment=The XMR buyer has started the {0} payment.\n{1} portfolio.pending.step3_seller.buyerStartedPayment.altcoin=Check for blockchain confirmations at your altcoin wallet or block explorer and confirm the payment when you have sufficient blockchain confirmations. portfolio.pending.step3_seller.buyerStartedPayment.fiat=Check at your trading account (e.g. bank account) and confirm when you have received the payment. portfolio.pending.step3_seller.warn.part1a=on the {0} blockchain @@ -814,7 +814,7 @@ portfolio.pending.step3_seller.onPaymentReceived.part1=Have you received the {0} # suppress inspection "TrailingSpacesInProperty" portfolio.pending.step3_seller.onPaymentReceived.name=Please also verify that the name of the sender specified on the trade contract matches the name that appears on your bank statement:\nSender''s name, per trade contract: {0}\n\nIf the names are not exactly the same, don''t confirm payment receipt. Instead, open a dispute by pressing \"alt + o\" or \"option + o\".\n\n # suppress inspection "TrailingSpacesInProperty" -portfolio.pending.step3_seller.onPaymentReceived.note=Please note, that as soon you have confirmed the receipt, the locked trade amount will be released to the BTC buyer and the security deposit will be refunded.\n\n +portfolio.pending.step3_seller.onPaymentReceived.note=Please note, that as soon you have confirmed the receipt, the locked trade amount will be released to the XMR buyer and the security deposit will be refunded.\n\n portfolio.pending.step3_seller.onPaymentReceived.confirm.headline=Confirm that you have received the payment portfolio.pending.step3_seller.onPaymentReceived.confirm.yes=Yes, I have received the payment portfolio.pending.step3_seller.onPaymentReceived.signer=IMPORTANT: By confirming receipt of payment, you are also \ @@ -826,10 +826,10 @@ portfolio.pending.step5_buyer.tradeFee=Trade fee portfolio.pending.step5_buyer.makersMiningFee=Mining fee portfolio.pending.step5_buyer.takersMiningFee=Total mining fees portfolio.pending.step5_buyer.refunded=Refunded security deposit -portfolio.pending.step5_buyer.withdrawBTC=Withdraw your bitcoin +portfolio.pending.step5_buyer.withdrawBTC=Withdraw your bitcoin (not applicable for XMR) portfolio.pending.step5_buyer.amount=Amount to withdraw portfolio.pending.step5_buyer.withdrawToAddress=Withdraw to address -portfolio.pending.step5_buyer.moveToBisqWallet=Keep funds in Bisq wallet +portfolio.pending.step5_buyer.moveToHavenoWallet=Keep funds in Haveno wallet portfolio.pending.step5_buyer.withdrawExternal=Withdraw to external wallet portfolio.pending.step5_buyer.alreadyWithdrawn=Your funds have already been withdrawn.\nPlease check the transaction history. portfolio.pending.step5_buyer.confirmWithdrawal=Confirm withdrawal request @@ -846,8 +846,8 @@ tradeFeedbackWindow.title=Congratulations on completing your trade tradeFeedbackWindow.msg.part1=We'd love to hear back from you about your experience. It'll help us to improve the software \ and to smooth out any rough edges. If you'd like to provide feedback, please fill out this short survey \ (no registration required) at: -tradeFeedbackWindow.msg.part2=If you have any questions, or experienced any problems, please get in touch with other users and contributors via the Bisq forum at: -tradeFeedbackWindow.msg.part3=Thanks for using Bisq! +tradeFeedbackWindow.msg.part2=If you have any questions, or experienced any problems, please get in touch with other users and contributors via the Haveno forum at: +tradeFeedbackWindow.msg.part3=Thanks for using Haveno! portfolio.pending.role=My role portfolio.pending.tradeInformation=Trade information @@ -859,7 +859,7 @@ portfolio.pending.tradeNotCompleted=Trade not completed in time (until {0}) portfolio.pending.tradeProcess=Trade process portfolio.pending.openAgainDispute.msg=If you are not sure that the message to the mediator or arbitrator arrived \ (e.g. if you did not get a response after 1 day) feel free to open a dispute again with Cmd/Ctrl+o. You can also ask \ - for additional help on the Bisq forum at [HYPERLINK:https://bisq.community]. + for additional help on the Haveno forum at [HYPERLINK:https://bisq.community]. portfolio.pending.openAgainDispute.button=Open dispute again portfolio.pending.openSupportTicket.headline=Open support ticket portfolio.pending.openSupportTicket.msg=Please use this function only in emergency cases if you don't see a \ @@ -869,18 +869,18 @@ portfolio.pending.openSupportTicket.msg=Please use this function only in emergen portfolio.pending.timeLockNotOver=You have to wait until ≈{0} ({1} more blocks) before you can open an arbitration dispute. portfolio.pending.error.depositTxNull=The deposit transaction is null. You cannot open a dispute \ without a valid deposit transaction. Please go to \"Settings/Network info\" and do a SPV resync.\n\n\ - For further help please contact the Bisq support channel at the Bisq Keybase team. + For further help please contact the Haveno support channel at the Haveno Keybase team. portfolio.pending.mediationResult.error.depositTxNull=The deposit transaction is null. You can move the \ trade to failed trades. portfolio.pending.mediationResult.error.delayedPayoutTxNull=The delayed payout transaction is null. You can move the \ trade to failed trades. portfolio.pending.error.depositTxNotConfirmed=The deposit transaction is not confirmed. You can not open an arbitration dispute \ with an unconfirmed deposit transaction. Please wait until it is confirmed or go to \"Settings/Network info\" and do a SPV resync.\n\n\ - For further help please contact the Bisq support channel at the Bisq Keybase team. + For further help please contact the Haveno support channel at the Haveno Keybase team. portfolio.pending.support.headline.getHelp=Need help? portfolio.pending.support.text.getHelp=If you have any problems you can try to contact the trade peer in the trade \ - chat or ask the Bisq community at https://bisq.community. \ + chat or ask the Haveno community at https://bisq.community. \ If your issue still isn't resolved, you can request more help from a mediator. portfolio.pending.support.button.getHelp=Open Trader Chat portfolio.pending.support.headline.halfPeriodOver=Check payment @@ -939,7 +939,7 @@ portfolio.pending.failedTrade.missingDepositTx=The deposit transaction (the 2-of Feel free to move this trade to failed trades. portfolio.pending.failedTrade.buyer.existingDepositTxButMissingDelayedPayoutTx=The delayed payout transaction is missing, \ but funds have been locked in the deposit transaction.\n\n\ - Please do NOT send the fiat or altcoin payment to the BTC seller, because without the delayed payout tx, arbitration \ + Please do NOT send the fiat or altcoin payment to the XMR seller, because without the delayed payout tx, arbitration \ cannot be opened. Instead, open a mediation ticket with Cmd/Ctrl+o. \ The mediator should suggest that both peers each get back the the full amount of their security deposits \ (with seller receiving full trade amount back as well). \ @@ -958,7 +958,7 @@ portfolio.pending.failedTrade.seller.existingDepositTxButMissingDelayedPayoutTx= portfolio.pending.failedTrade.errorMsgSet=There was an error during trade protocol execution.\n\n\ Error: {0}\n\n\ It might be that this error is not critical, and the trade can be completed normally. If you are unsure, open a mediation \ - ticket to get advice from Bisq mediators. \n\n\ + ticket to get advice from Haveno mediators. \n\n\ If the error was critical and the trade cannot be completed, you might have lost your trade fee. \ Request a reimbursement for lost trade fees here: \ [HYPERLINK:https://github.com/bisq-network/support/issues] @@ -1008,11 +1008,11 @@ funds.tab.transactions=Transactions funds.deposit.unused=Unused funds.deposit.usedInTx=Used in {0} transaction(s) -funds.deposit.fundBisqWallet=Fund Bisq wallet +funds.deposit.fundHavenoWallet=Fund Haveno wallet funds.deposit.noAddresses=No deposit addresses have been generated yet funds.deposit.fundWallet=Fund your wallet funds.deposit.withdrawFromWallet=Send funds from wallet -funds.deposit.amount=Amount in BTC (optional) +funds.deposit.amount=Amount in XMR (optional) funds.deposit.generateAddress=Generate new address funds.deposit.generateAddressSegwit=Native segwit format (Bech32) funds.deposit.selectUnused=Please select an unused address from the table above rather than generating a new one. @@ -1070,21 +1070,21 @@ funds.tx.unknown=Unknown reason: {0} funds.tx.noFundsFromDispute=No refund from dispute funds.tx.receivedFunds=Received funds funds.tx.withdrawnFromWallet=Withdrawn from wallet -funds.tx.withdrawnFromBSQWallet=BTC withdrawn from BSQ wallet +funds.tx.withdrawnFromBSQWallet=XMR withdrawn from BSQ wallet funds.tx.memo=Memo funds.tx.noTxAvailable=No transactions available funds.tx.revert=Revert -funds.tx.txSent=Transaction successfully sent to a new address in the local Bisq wallet. +funds.tx.txSent=Transaction successfully sent to a new address in the local Haveno wallet. funds.tx.direction.self=Sent to yourself funds.tx.daoTxFee=Miner fee for BSQ tx funds.tx.reimbursementRequestTxFee=Reimbursement request funds.tx.compensationRequestTxFee=Compensation request funds.tx.dustAttackTx=Received dust -funds.tx.dustAttackTx.popup=This transaction is sending a very small BTC amount to your wallet and might be an attempt \ +funds.tx.dustAttackTx.popup=This transaction is sending a very small XMR amount to your wallet and might be an attempt \ from chain analysis companies to spy on your wallet.\n\n\ If you use that transaction output in a spending transaction they will learn that you are likely the owner of the \ other address as well (coin merge).\n\n\ - To protect your privacy the Bisq wallet ignores such dust outputs for spending purposes and in the balance display. \ + To protect your privacy the Haveno wallet ignores such dust outputs for spending purposes and in the balance display. \ You can set the threshold amount when an output is considered dust in the settings. #################################################################### @@ -1120,7 +1120,7 @@ support.sendingMessage=Sending Message... support.receiverNotOnline=Receiver is not online. Message is saved to their mailbox. support.sendMessageError=Sending message failed. Error: {0} support.receiverNotKnown=Receiver not known -support.wrongVersion=The offer in that dispute has been created with an older version of Bisq.\n\ +support.wrongVersion=The offer in that dispute has been created with an older version of Haveno.\n\ You cannot close that dispute with your version of the application.\n\n\ Please use an older version with protocol version {0} support.openFile=Open file to attach (max. file size: {0} kb) @@ -1139,8 +1139,8 @@ support.savedInMailbox=Message saved in receiver's mailbox support.arrived=Message arrived at receiver support.acknowledged=Message arrival confirmed by receiver support.error=Receiver could not process message. Error: {0} -support.buyerAddress=BTC buyer address -support.sellerAddress=BTC seller address +support.buyerAddress=XMR buyer address +support.sellerAddress=XMR seller address support.role=Role support.agent=Support agent support.state=State @@ -1148,26 +1148,26 @@ support.chat=Chat support.closed=Closed support.open=Open support.process=Process -support.buyerOfferer=BTC buyer/Maker -support.sellerOfferer=BTC seller/Maker -support.buyerTaker=BTC buyer/Taker -support.sellerTaker=BTC seller/Taker +support.buyerOfferer=XMR buyer/Maker +support.sellerOfferer=XMR seller/Maker +support.buyerTaker=XMR buyer/Taker +support.sellerTaker=XMR seller/Taker -support.backgroundInfo=Bisq is not a company, so it handles disputes differently.\n\n\ +support.backgroundInfo=Haveno is not a company, so it handles disputes differently.\n\n\ Traders can communicate within the application via secure chat on the open trades screen to try solving disputes on their own. \ If that is not sufficient, a mediator can step in to help. The mediator will evaluate the situation and suggest a \ payout of trade funds. If both traders accept this suggestion, the payout transaction is completed and the trade is closed. \ If one or both traders do not agree to the mediator's suggested payout, they can request arbitration.\ The arbitrator will re-evaluate the situation and, if warranted, personally pay the trader back and request reimbursement \ - for this payment from the Bisq DAO. + for this payment from the Haveno DAO. support.initialInfo=Please enter a description of your problem in the text field below. \ Add as much information as possible to speed up dispute resolution time.\n\n\ Here is a check list for information you should provide:\n\ - \t● If you are the BTC buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' \ + \t● If you are the XMR buyer: Did you make the Fiat or Altcoin transfer? If so, did you click the 'payment started' \ button in the application?\n\ - \t● If you are the BTC seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' \ + \t● If you are the XMR seller: Did you receive the Fiat or Altcoin payment? If so, did you click the 'payment received' \ button in the application?\n\ - \t● Which version of Bisq are you using?\n\ + \t● Which version of Haveno are you using?\n\ \t● Which operating system are you using?\n\ \t● If you encountered an issue with failed transactions please consider switching to a new data directory.\n\ \t Sometimes the data directory gets corrupted and leads to strange bugs. \n\ @@ -1180,12 +1180,12 @@ support.initialInfo=Please enter a description of your problem in the text field \t● You accepted the rules outlined in the dispute document in the user agreement when you first started the application.\n\n\ You can read more about the dispute process at: {2} support.systemMsg=System message: {0} -support.youOpenedTicket=You opened a request for support.\n\n{0}\n\nBisq version: {1} -support.youOpenedDispute=You opened a request for a dispute.\n\n{0}\n\nBisq version: {1} -support.youOpenedDisputeForMediation=You requested mediation.\n\n{0}\n\nBisq version: {1} -support.peerOpenedTicket=Your trading peer has requested support due to technical problems.\n\n{0}\n\nBisq version: {1} -support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}\n\nBisq version: {1} -support.peerOpenedDisputeForMediation=Your trading peer has requested mediation.\n\n{0}\n\nBisq version: {1} +support.youOpenedTicket=You opened a request for support.\n\n{0}\n\nHaveno version: {1} +support.youOpenedDispute=You opened a request for a dispute.\n\n{0}\n\nHaveno version: {1} +support.youOpenedDisputeForMediation=You requested mediation.\n\n{0}\n\nHaveno version: {1} +support.peerOpenedTicket=Your trading peer has requested support due to technical problems.\n\n{0}\n\nHaveno version: {1} +support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}\n\nHaveno version: {1} +support.peerOpenedDisputeForMediation=Your trading peer has requested mediation.\n\n{0}\n\nHaveno version: {1} support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Mediator''s node address: {0} support.warning.disputesWithInvalidDonationAddress=The delayed payout transaction has used an invalid receiver address. \ @@ -1208,8 +1208,8 @@ settings.tab.network=Network info settings.tab.about=About setting.preferences.general=General preferences -setting.preferences.explorer=Bitcoin Explorer -setting.preferences.explorer.bsq=Bisq Explorer +setting.preferences.explorer=Monero Explorer +setting.preferences.explorer.bsq=Haveno Explorer setting.preferences.deviation=Max. deviation from market price setting.preferences.bsqAverageTrimThreshold=Outlier threshold for BSQ rate setting.preferences.avoidStandbyMode=Avoid standby mode @@ -1246,18 +1246,18 @@ settings.preferences.supportLanguageWarning=In case of a dispute, please note th setting.preferences.daoOptions=DAO options setting.preferences.dao.resyncFromGenesis.label=Rebuild DAO state from genesis tx setting.preferences.dao.resyncFromResources.label=Rebuild DAO state from resources -setting.preferences.dao.resyncFromResources.popup=After an application restart the Bisq network governance data will be reloaded from \ +setting.preferences.dao.resyncFromResources.popup=After an application restart the Haveno network governance data will be reloaded from \ the seed nodes and the BSQ consensus state will be rebuilt from the latest resource files. setting.preferences.dao.resyncFromGenesis.popup=A resync from genesis transaction can take considerable time and CPU \ resources. Are you sure you want to do that? Mostly a resync from latest resource files is sufficient and much faster.\n\n\ - If you proceed, after an application restart the Bisq network governance data will be reloaded from \ + If you proceed, after an application restart the Haveno network governance data will be reloaded from \ the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction. setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdown -setting.preferences.dao.isDaoFullNode=Run Bisq as DAO full node +setting.preferences.dao.isDaoFullNode=Run Haveno as DAO full node setting.preferences.dao.rpcUser=RPC username setting.preferences.dao.rpcPw=RPC password setting.preferences.dao.blockNotifyPort=Block notify port -setting.preferences.dao.fullNodeInfo=For running Bisq as DAO full node you need to have Bitcoin Core locally running \ +setting.preferences.dao.fullNodeInfo=For running Haveno as DAO full node you need to have Monero Core locally running \ and RPC enabled. All requirements are documented in ''{0}''.\n\n\ After changing the mode you need to restart. setting.preferences.dao.fullNodeInfo.ok=Open docs page @@ -1271,28 +1271,28 @@ settings.preferences.editCustomExplorer.name=Name settings.preferences.editCustomExplorer.txUrl=Transaction URL settings.preferences.editCustomExplorer.addressUrl=Address URL -settings.net.btcHeader=Bitcoin network -settings.net.p2pHeader=Bisq network +settings.net.btcHeader=Monero network +settings.net.p2pHeader=Haveno network settings.net.onionAddressLabel=My onion address -settings.net.btcNodesLabel=Use custom Bitcoin Core nodes +settings.net.btcNodesLabel=Use custom Monero Core nodes settings.net.bitcoinPeersLabel=Connected peers -settings.net.useTorForBtcJLabel=Use Tor for Bitcoin network -settings.net.bitcoinNodesLabel=Bitcoin Core nodes to connect to -settings.net.useProvidedNodesRadio=Use provided Bitcoin Core nodes -settings.net.usePublicNodesRadio=Use public Bitcoin network -settings.net.useCustomNodesRadio=Use custom Bitcoin Core nodes -settings.net.warn.usePublicNodes=If you use the public Bitcoin network you are exposed to a severe privacy problem caused by the broken bloom filter design and implementation which is used for SPV wallets like BitcoinJ (used in Bisq). Any full node you are connected to could find out that all your wallet addresses belong to one entity.\n\n\ +settings.net.useTorForBtcJLabel=Use Tor for Monero network +settings.net.bitcoinNodesLabel=Monero Core nodes to connect to +settings.net.useProvidedNodesRadio=Use provided Monero Core nodes +settings.net.usePublicNodesRadio=Use public Monero network +settings.net.useCustomNodesRadio=Use custom Monero Core nodes +settings.net.warn.usePublicNodes=If you use the public Monero network you are exposed to a severe privacy problem caused by the broken bloom filter design and implementation which is used for SPV wallets like BitcoinJ (used in Haveno). Any full node you are connected to could find out that all your wallet addresses belong to one entity.\n\n\ Please read more about the details at [HYPERLINK:https://bisq.network/blog/privacy-in-bitsquare].\n\n\ Are you sure you want to use the public nodes? settings.net.warn.usePublicNodes.useProvided=No, use provided nodes settings.net.warn.usePublicNodes.usePublic=Yes, use public network -settings.net.warn.useCustomNodes.B2XWarning=Please be sure that your Bitcoin node is a trusted Bitcoin Core node!\n\n\ - Connecting to nodes which do not follow the Bitcoin Core consensus rules could corrupt your wallet and cause problems in the trade process.\n\n\ +settings.net.warn.useCustomNodes.B2XWarning=Please be sure that your Monero node is a trusted Monero Core node!\n\n\ + Connecting to nodes which do not follow the Monero Core consensus rules could corrupt your wallet and cause problems in the trade process.\n\n\ Users who connect to nodes that violate consensus rules are responsible for any resulting damage. \ Any resulting disputes will be decided in favor of the other peer. No technical support will be given \ to users who ignore this warning and protection mechanisms! -settings.net.warn.invalidBtcConfig=Connection to the Bitcoin network failed because your configuration is invalid.\n\nYour configuration has been reset to use the provided Bitcoin nodes instead. You will need to restart the application. -settings.net.localhostBtcNodeInfo=Background information: Bisq looks for a local Bitcoin node when starting. If it is found, Bisq will communicate with the Bitcoin network exclusively through it. +settings.net.warn.invalidBtcConfig=Connection to the Monero network failed because your configuration is invalid.\n\nYour configuration has been reset to use the provided Monero nodes instead. You will need to restart the application. +settings.net.localhostBtcNodeInfo=Background information: Haveno looks for a local Monero node when starting. If it is found, Haveno will communicate with the Monero network exclusively through it. settings.net.p2PPeersLabel=Connected peers settings.net.onionAddressColumn=Onion address settings.net.creationDateColumn=Established @@ -1314,7 +1314,7 @@ settings.net.needRestart=You need to restart the application to apply that chang settings.net.notKnownYet=Not known yet... settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec -settings.net.chainHeight=Bisq DAO chain height: {0} | Bitcoin Peers chain height: {1} +settings.net.chainHeight=Haveno DAO chain height: {0} | Monero Peers chain height: {1} settings.net.ips=[IP address:port | host name:port | onion address:port] (comma separated). Port can be omitted if default is used (8333). settings.net.seedNode=Seed node settings.net.directPeer=Peer (direct) @@ -1331,17 +1331,17 @@ After the restart it can take a while to resync with the network and you will on settings.net.reSyncSPVAfterRestart=The SPV chain file has been deleted. Please be patient. It can take a while to resync with the network. settings.net.reSyncSPVAfterRestartCompleted=The resync is now completed. Please restart the application. settings.net.reSyncSPVFailed=Could not delete SPV chain file.\nError: {0} -setting.about.aboutBisq=About Bisq -setting.about.about=Bisq is open-source software which facilitates the exchange of bitcoin with national currencies (and other cryptocurrencies) through a decentralized peer-to-peer network in a way that strongly protects user privacy. Learn more about Bisq on our project web page. -setting.about.web=Bisq web page +setting.about.aboutHaveno=About Haveno +setting.about.about=Haveno is open-source software which facilitates the exchange of bitcoin with national currencies (and other cryptocurrencies) through a decentralized peer-to-peer network in a way that strongly protects user privacy. Learn more about Haveno on our project web page. +setting.about.web=Haveno web page setting.about.code=Source code setting.about.agpl=AGPL License -setting.about.support=Support Bisq -setting.about.def=Bisq is not a company—it is a project open to the community. If you want to participate or support Bisq please follow the links below. +setting.about.support=Support Haveno +setting.about.def=Haveno is not a company—it is a project open to the community. If you want to participate or support Haveno please follow the links below. setting.about.contribute=Contribute setting.about.providers=Data providers -setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. -setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. +setting.about.apisWithFee=Haveno uses Haveno Price Indices for Fiat and Altcoin market prices, and Haveno Mempool Nodes for mining fee estimation. +setting.about.apis=Haveno uses Haveno Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=Market prices provided by setting.about.feeEstimation.label=Mining fee estimation provided by setting.about.versionDetails=Version details @@ -1355,7 +1355,7 @@ setting.about.shortcuts.ctrlOrAltOrCmd=''Ctrl + {0}'' or ''alt + {0}'' or ''cmd setting.about.shortcuts.menuNav=Navigate main menu setting.about.shortcuts.menuNav.value=To navigate the main menu press: 'Ctrl' or 'alt' or 'cmd' with a numeric key between '1-9' -setting.about.shortcuts.close=Close Bisq +setting.about.shortcuts.close=Close Haveno setting.about.shortcuts.close.value=''Ctrl + {0}'' or ''cmd + {0}'' or ''Ctrl + {1}'' or ''cmd + {1}'' setting.about.shortcuts.closePopup=Close popup or dialog window @@ -1369,7 +1369,7 @@ setting.about.shortcuts.openDispute.value=Select pending trade and click: {0} setting.about.shortcuts.walletDetails=Open wallet details window -setting.about.shortcuts.openEmergencyBtcWalletTool=Open emergency wallet tool for BTC wallet +setting.about.shortcuts.openEmergencyBtcWalletTool=Open emergency wallet tool for XMR wallet setting.about.shortcuts.openEmergencyBsqWalletTool=Open emergency wallet tool for BSQ wallet @@ -1400,13 +1400,13 @@ setting.about.shortcuts.sendPrivateNotification.value=Open peer info at avatar a setting.info.headline=New XMR auto-confirm Feature setting.info.msg=When selling BTC for XMR you can use the auto-confirm feature to verify that the correct amount of \ - XMR was sent to your wallet so that Bisq can automatically mark the trade as complete, making trades quicker for everyone.\n\n\ + XMR was sent to your wallet so that Haveno can automatically mark the trade as complete, making trades quicker for everyone.\n\n\ Auto-confirm checks the XMR transaction on at least 2 XMR explorer nodes using the private transaction key provided \ - by the XMR sender. By default, Bisq uses explorer nodes run by Bisq contributors, but we recommend running your \ + by the XMR sender. By default, Haveno uses explorer nodes run by Haveno contributors, but we recommend running your \ own XMR explorer node for maximum privacy and security.\n\n\ You can also set the maximum amount of BTC per trade to auto-confirm as well as the number of required \ confirmations here in Settings.\n\n\ - See more details (including how to set up your own explorer node) on the Bisq wiki \ + See more details (including how to set up your own explorer node) on the Haveno wiki \ [HYPERLINK:https://bisq.wiki/Trading_Monero#Auto-confirming_trades] #################################################################### # Account @@ -1415,15 +1415,15 @@ setting.info.msg=When selling BTC for XMR you can use the auto-confirm feature t account.tab.mediatorRegistration=Mediator registration account.tab.refundAgentRegistration=Refund agent registration account.tab.signing=Signing -account.info.headline=Welcome to your Bisq Account +account.info.headline=Welcome to your Haveno Account account.info.msg=Here you can add trading accounts for national currencies & altcoins and create a backup of your wallet & account data.\n\n\ -A new Bitcoin wallet was created the first time you started Bisq.\n\n\ -We strongly recommend that you write down your Bitcoin wallet seed words (see tab on the top) and consider adding a \ - password before funding. Bitcoin deposits and withdrawals are managed in the \"Funds\" section.\n\n\ +A new Monero wallet was created the first time you started Haveno.\n\n\ +We strongly recommend that you write down your Monero wallet seed words (see tab on the top) and consider adding a \ + password before funding. Monero deposits and withdrawals are managed in the \"Funds\" section.\n\n\ Privacy & security note: \ -because Bisq is a decentralized exchange, all your data is kept on your computer. There are no servers, so we have no \ +because Haveno is a decentralized exchange, all your data is kept on your computer. There are no servers, so we have no \ access to your personal info, your funds, or even your IP address. Data such as bank account numbers, \ - altcoin & Bitcoin addresses, etc are only shared with your trading partner to fulfill trades you initiate \ + altcoin & Monero addresses, etc are only shared with your trading partner to fulfill trades you initiate \ (in case of a dispute the mediator or arbitrator will see the same data as your trading peer). account.menu.paymentAccount=National currency accounts @@ -1441,10 +1441,10 @@ account.menu.walletInfo.xpub.headLine=Watch keys (xpub keys) account.menu.walletInfo.walletSelector={0} {1} wallet account.menu.walletInfo.path.headLine=HD keychain paths account.menu.walletInfo.path.info=If you import seed words into another wallet (like Electrum), you'll need to define the \ - path. This should only be done in emergency cases when you lose access to the Bisq wallet and data directory.\n\ - Keep in mind that spending funds from a non-Bisq wallet can bungle the internal Bisq data structures associated with the wallet \ + path. This should only be done in emergency cases when you lose access to the Haveno wallet and data directory.\n\ + Keep in mind that spending funds from a non-Haveno wallet can bungle the internal Haveno data structures associated with the wallet \ data, which can lead to failed trades.\n\n\ - NEVER send BSQ from a non-Bisq wallet, as it will probably lead to an invalid BSQ transaction and losing your BSQ. + NEVER send BSQ from a non-Haveno wallet, as it will probably lead to an invalid BSQ transaction and losing your BSQ. account.menu.walletInfo.openDetails=Show raw wallet details and private keys @@ -1456,9 +1456,9 @@ account.arbitratorRegistration.registration={0} registration account.arbitratorRegistration.revoke=Revoke account.arbitratorRegistration.info.msg=Please note that you need to stay available for 15 days after revoking as there might be trades which are using you as {0}. The max. allowed trade period is 8 days and the dispute process might take up to 7 days. account.arbitratorRegistration.warn.min1Language=You need to set at least 1 language.\nWe added the default language for you. -account.arbitratorRegistration.removedSuccess=You have successfully removed your registration from the Bisq network. +account.arbitratorRegistration.removedSuccess=You have successfully removed your registration from the Haveno network. account.arbitratorRegistration.removedFailed=Could not remove registration.{0} -account.arbitratorRegistration.registerSuccess=You have successfully registered to the Bisq network. +account.arbitratorRegistration.registerSuccess=You have successfully registered to the Haveno network. account.arbitratorRegistration.registerFailed=Could not complete registration.{0} account.altcoin.yourAltcoinAccounts=Your altcoin accounts @@ -1468,7 +1468,7 @@ described on the {1} web page.\nUsing wallets from centralized exchanges where ( not a {2} specialist and cannot help in such cases. account.altcoin.popup.wallet.confirm=I understand and confirm that I know which wallet I need to use. # suppress inspection "UnusedProperty" -account.altcoin.popup.upx.msg=Trading UPX on Bisq requires that you understand and fulfill \ +account.altcoin.popup.upx.msg=Trading UPX on Haveno requires that you understand and fulfill \ the following requirements:\n\n\ For sending UPX, you need to use either the official uPlexa GUI wallet or uPlexa CLI wallet with the \ store-tx-info flag enabled (default in new versions). Please be sure you can access the tx key as \ @@ -1487,7 +1487,7 @@ There is no payment ID required, just the normal public address.\n\ If you are not sure about that process visit uPlexa discord channel (https://discord.gg/vhdNSrV) \ or the uPlexa Telegram Chat (https://t.me/uplexaOfficial) to find more information. # suppress inspection "UnusedProperty" -account.altcoin.popup.arq.msg=Trading ARQ on Bisq requires that you understand and fulfill \ +account.altcoin.popup.arq.msg=Trading ARQ on Haveno requires that you understand and fulfill \ the following requirements:\n\n\ For sending ARQ, you need to use either the official ArQmA GUI wallet or ArQmA CLI wallet with the \ store-tx-info flag enabled (default in new versions). Please be sure you can access the tx key as \ @@ -1506,18 +1506,18 @@ There is no payment ID required, just the normal public address.\n\ If you are not sure about that process visit ArQmA discord channel (https://discord.gg/s9BQpJT) \ or the ArQmA forum (https://labs.arqma.com) to find more information. # suppress inspection "UnusedProperty" -account.altcoin.popup.xmr.msg=Trading XMR on Bisq requires that you understand the following requirement.\n\n\ +account.altcoin.popup.xmr.msg=Trading XMR on Haveno requires that you understand the following requirement.\n\n\ If selling XMR, you must be able to provide the following information to a mediator or arbitrator in case of a dispute:\n\ - the transaction key (Tx Key, Tx Secret Key or Tx Private Key)\n\ - the transaction ID (Tx ID or Tx Hash)\n\ - the destination address (recipient's address)\n\n\ See the wiki for details on where to find this information on popular Monero wallets [HYPERLINK:https://bisq.wiki/Trading_Monero#Proving_payments].\n\ Failure to provide the required transaction data will result in losing disputes.\n\n\ -Also note that Bisq now offers automatic confirming for XMR transactions to make trades quicker, \ +Also note that Haveno now offers automatic confirming for XMR transactions to make trades quicker, \ but you need to enable it in Settings.\n\n\ See the wiki for more information about the auto-confirm feature: [HYPERLINK:https://bisq.wiki/Trading_Monero#Auto-confirming_trades]. # suppress inspection "UnusedProperty" -account.altcoin.popup.msr.msg=Trading MSR on Bisq requires that you understand and fulfill \ +account.altcoin.popup.msr.msg=Trading MSR on Haveno requires that you understand and fulfill \ the following requirements:\n\n\ For sending MSR, you need to use either the official Masari GUI wallet, Masari CLI wallet with the \ store-tx-info flag enabled (enabled by default) or the Masari web wallet (https://wallet.getmasari.org). Please be sure you can access the tx key as \ @@ -1541,7 +1541,7 @@ mediator or arbitrator in case of a dispute.\n\n\ There is no payment ID required, just the normal public address.\n\ If you are not sure about that process, ask for help on the Official Masari Discord (https://discord.gg/sMCwMqs). # suppress inspection "UnusedProperty" -account.altcoin.popup.blur.msg=Trading BLUR on Bisq requires that you understand and fulfill \ +account.altcoin.popup.blur.msg=Trading BLUR on Haveno requires that you understand and fulfill \ the following requirements:\n\n\ To send BLUR you must use the Blur Network CLI or GUI Wallet. \n\n\ If you are using the CLI wallet, a transaction hash (tx ID) will be displayed after a transfer is sent. You must save \ @@ -1555,9 +1555,9 @@ In the event that arbitration is necessary, you must present the following to an transfer using the Blur Transaction Viewer (https://blur.cash/#tx-viewer).\n\n\ Failure to provide the required information to the mediator or arbitrator will result in losing the dispute case. In all cases of dispute, the \ BLUR sender bears 100% of the burden of responsibility in verifying transactions to an mediator or arbitrator. \n\n\ -If you do not understand these requirements, do not trade on Bisq. First, seek help at the Blur Network Discord (https://discord.gg/dMWaqVW). +If you do not understand these requirements, do not trade on Haveno. First, seek help at the Blur Network Discord (https://discord.gg/dMWaqVW). # suppress inspection "UnusedProperty" -account.altcoin.popup.solo.msg=Trading Solo on Bisq requires that you understand and fulfill \ +account.altcoin.popup.solo.msg=Trading Solo on Haveno requires that you understand and fulfill \ the following requirements:\n\n\ To send Solo you must use the Solo Network CLI Wallet. \n\n\ If you are using the CLI wallet, a transaction hash (tx ID) will be displayed after a transfer is sent. You must save \ @@ -1568,9 +1568,9 @@ In the event that arbitration is necessary, you must present the following to an transfer using the Solo Block Explorer by searching for the transaction and then using the "Prove sending" function (https://explorer.minesolo.com/).\n\n\ failure to provide the required information to the mediator or arbitrator will result in losing the dispute case. In all cases of dispute, the \ Solo sender bears 100% of the burden of responsibility in verifying transactions to an mediator or arbitrator. \n\n\ -If you do not understand these requirements, do not trade on Bisq. First, seek help at the Solo Network Discord (https://discord.minesolo.com/). +If you do not understand these requirements, do not trade on Haveno. First, seek help at the Solo Network Discord (https://discord.minesolo.com/). # suppress inspection "UnusedProperty" -account.altcoin.popup.cash2.msg=Trading CASH2 on Bisq requires that you understand and fulfill \ +account.altcoin.popup.cash2.msg=Trading CASH2 on Haveno requires that you understand and fulfill \ the following requirements:\n\n\ To send CASH2 you must use the Cash2 Wallet version 3 or higher. \n\n\ After a transaction is sent, the transaction ID will be displayed. You must save this information. \ @@ -1581,9 +1581,9 @@ In the event that arbitration is necessary, you must present the following to an transfer using the Cash2 Block Explorer (https://blocks.cash2.org).\n\n\ Failure to provide the required information to the mediator or arbitrator will result in losing the dispute case. In all cases of dispute, the \ CASH2 sender bears 100% of the burden of responsibility in verifying transactions to an mediator or arbitrator. \n\n\ -If you do not understand these requirements, do not trade on Bisq. First, seek help at the Cash2 Discord (https://discord.gg/FGfXAYN). +If you do not understand these requirements, do not trade on Haveno. First, seek help at the Cash2 Discord (https://discord.gg/FGfXAYN). # suppress inspection "UnusedProperty" -account.altcoin.popup.qwertycoin.msg=Trading Qwertycoin on Bisq requires that you understand and fulfill \ +account.altcoin.popup.qwertycoin.msg=Trading Qwertycoin on Haveno requires that you understand and fulfill \ the following requirements:\n\n\ To send QWC you must use the official QWC Wallet version 5.1.3 or higher. \n\n\ After a transaction is sent, the transaction ID will be displayed. You must save this information. \ @@ -1594,9 +1594,9 @@ In the event that arbitration is necessary, you must present the following to an transfer using the QWC Block Explorer (https://explorer.qwertycoin.org).\n\n\ Failure to provide the required information to the mediator or arbitrator will result in losing the dispute case. In all cases of dispute, the \ QWC sender bears 100% of the burden of responsibility in verifying transactions to an mediator or arbitrator. \n\n\ -If you do not understand these requirements, do not trade on Bisq. First, seek help at the QWC Discord (https://discord.gg/rUkfnpC). +If you do not understand these requirements, do not trade on Haveno. First, seek help at the QWC Discord (https://discord.gg/rUkfnpC). # suppress inspection "UnusedProperty" -account.altcoin.popup.drgl.msg=Trading Dragonglass on Bisq requires that you understand and fulfill \ +account.altcoin.popup.drgl.msg=Trading Dragonglass on Haveno requires that you understand and fulfill \ the following requirements:\n\n\ Because of the privacy Dragonglass provides, a transaction is not verifiable on the public blockchain. If required, you \ can prove your payment through the use of your TXN-Private-Key.\n\ @@ -1623,7 +1623,7 @@ the untraceable addresses, because the mediator or arbitrator would not be able account.altcoin.popup.grin.msg=GRIN requires an interactive process between the sender and receiver to create the \ transaction. Be sure to follow the instructions from the GRIN project web page to reliably send and receive GRIN \ (the receiver needs to be online or at least be online during a certain time frame). \n\n\ - Bisq supports only the Grinbox (Wallet713) wallet URL format. \n\n\ + Haveno supports only the Grinbox (Wallet713) wallet URL format. \n\n\ The GRIN sender is required to provide proof that they have sent GRIN successfully. If the wallet cannot provide that proof, a \ potential dispute will be resolved in favor of the GRIN receiver. Please be sure that you use the \ latest Grinbox software which supports the transaction proof and that you understand the process of transferring and \ @@ -1639,7 +1639,7 @@ account.altcoin.popup.beam.msg=BEAM requires an interactive process between the Be sure to use wallet software which can produce such a proof. If the wallet cannot provide the proof a potential \ dispute will be resolved in favor of the BEAM receiver. # suppress inspection "UnusedProperty" -account.altcoin.popup.pars.msg=Trading ParsiCoin on Bisq requires that you understand and fulfill \ +account.altcoin.popup.pars.msg=Trading ParsiCoin on Haveno requires that you understand and fulfill \ the following requirements:\n\n\ To send PARS you must use the official ParsiCoin Wallet version 3.0.0 or higher. \n\n\ You can Check your Transaction Hash and Transaction Key on Transactions Section on your GUI Wallet (ParsiPay) \ @@ -1649,11 +1649,11 @@ In the event that arbitration is necessary, you must present the following to an transfer using the ParsiCoin Block Explorer (http://explorer.parsicoin.net/#check_payment).\n\n\ Failure to provide the required information to the mediator or arbitrator will result in losing the dispute case. In all cases of dispute, the \ ParsiCoin sender bears 100% of the burden of responsibility in verifying transactions to an mediator or arbitrator. \n\n\ -If you do not understand these requirements, do not trade on Bisq. First, seek help at the ParsiCoin Discord (https://discord.gg/c7qmFNh). +If you do not understand these requirements, do not trade on Haveno. First, seek help at the ParsiCoin Discord (https://discord.gg/c7qmFNh). # suppress inspection "UnusedProperty" account.altcoin.popup.blk-burnt.msg=To trade burnt blackcoins, you need to know the following:\n\n\ -Burnt blackcoins are unspendable. To trade them on Bisq, output scripts need to be in the form: \ +Burnt blackcoins are unspendable. To trade them on Haveno, output scripts need to be in the form: \ OP_RETURN OP_PUSHDATA, followed by associated data bytes which, after being hex-encoded, constitute addresses. \ For example, burnt blackcoins with an address 666f6f (“foo” in UTF-8) will have the following script:\n\n\ OP_RETURN OP_PUSHDATA 666f6f\n\n\ @@ -1664,17 +1664,17 @@ burning ordinary blackcoins (with associated data equal to the destination addre In case of a dispute, the BLK seller needs to provide the transaction hash. # suppress inspection "UnusedProperty" -account.altcoin.popup.liquidbitcoin.msg=Trading L-BTC on Bisq requires that you understand the following:\n\n\ -When receiving L-BTC for a trade on Bisq, you cannot use the mobile Blockstream Green Wallet app or a \ -custodial/exchange wallet. You must only receive L-BTC into the Liquid Elements Core wallet, or another \ -L-BTC wallet which allows you to obtain the blinding key for your blinded L-BTC address.\n\n\ +account.altcoin.popup.liquidbitcoin.msg=Trading L-XMR on Haveno requires that you understand the following:\n\n\ +When receiving L-XMR for a trade on Haveno, you cannot use the mobile Blockstream Green Wallet app or a \ +custodial/exchange wallet. You must only receive L-XMR into the Liquid Elements Core wallet, or another \ +L-XMR wallet which allows you to obtain the blinding key for your blinded L-XMR address.\n\n\ In the event mediation is necessary, or if a trade dispute arises, you must disclose the blinding key for \ -your receiving L-BTC address to the Bisq mediator or refund agent so they can verify the details of \ +your receiving L-XMR address to the Haveno mediator or refund agent so they can verify the details of \ your Confidential Transaction on their own Elements Core full node.\n\n\ Failure to provide the required information to the mediator or refund agent will result in losing the \ -dispute case. In all cases of dispute, the L-BTC receiver bears 100% of the burden of responsibility in \ +dispute case. In all cases of dispute, the L-XMR receiver bears 100% of the burden of responsibility in \ providing cryptographic proof to the mediator or refund agent.\n\n\ -If you do not understand these requirements, do not trade L-BTC on Bisq. +If you do not understand these requirements, do not trade L-XMR on Haveno. account.fiat.yourFiatAccounts=Your national currency accounts @@ -1698,7 +1698,7 @@ out of your wallet, and when restoring your wallet from seed words. account.seed.backup.title=Backup your wallets seed words account.seed.info=Please write down both wallet seed words and the date! \ You can recover your wallet any time with seed words and the date.\n\ -The same seed words are used for the BTC and BSQ wallet.\n\n\ +The same seed words are used for the XMR and BSQ wallet.\n\n\ You should write down the seed words on a sheet of paper. Do not save them on your computer.\n\n\ Please note that the seed words are NOT a replacement for a backup.\n\ You need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\n\ @@ -1716,10 +1716,10 @@ account.seed.restore.info=Please make a backup before applying restore from seed It is not a way for applying a backup! Please use a backup from the application data directory for restoring a \ previous application state.\n\n\ After restoring the application will shut down automatically. After you have restarted the application it will resync \ - with the Bitcoin network. This can take a while and can consume a lot of CPU, especially if the wallet was older and \ + with the Monero network. This can take a while and can consume a lot of CPU, especially if the wallet was older and \ had many transactions. Please avoid interrupting that process, otherwise you might need to delete the SPV chain file \ again or repeat the restore process. -account.seed.restore.ok=Ok, do the restore and shut down Bisq +account.seed.restore.ok=Ok, do the restore and shut down Haveno #################################################################### @@ -1743,14 +1743,14 @@ account.notifications.trade.label=Receive trade messages account.notifications.market.label=Receive offer alerts account.notifications.price.label=Receive price alerts account.notifications.priceAlert.title=Price alerts -account.notifications.priceAlert.high.label=Notify if BTC price is above -account.notifications.priceAlert.low.label=Notify if BTC price is below +account.notifications.priceAlert.high.label=Notify if XMR price is above +account.notifications.priceAlert.low.label=Notify if XMR price is below account.notifications.priceAlert.setButton=Set price alert account.notifications.priceAlert.removeButton=Remove price alert account.notifications.trade.message.title=Trade state changed account.notifications.trade.message.msg.conf=The deposit transaction for the trade with ID {0} is confirmed. \ - Please open your Bisq application and start the payment. -account.notifications.trade.message.msg.started=The BTC buyer has started the payment for the trade with ID {0}. + Please open your Haveno application and start the payment. +account.notifications.trade.message.msg.started=The XMR buyer has started the payment for the trade with ID {0}. account.notifications.trade.message.msg.completed=The trade with ID {0} is completed. account.notifications.offer.message.title=Your offer was taken account.notifications.offer.message.msg=Your offer with ID {0} was taken @@ -1760,11 +1760,11 @@ account.notifications.dispute.message.msg=You received a dispute message for tra account.notifications.marketAlert.title=Offer alerts account.notifications.marketAlert.selectPaymentAccount=Offers matching payment account account.notifications.marketAlert.offerType.label=Offer type I am interested in -account.notifications.marketAlert.offerType.buy=Buy offers (I want to sell BTC) -account.notifications.marketAlert.offerType.sell=Sell offers (I want to buy BTC) +account.notifications.marketAlert.offerType.buy=Buy offers (I want to sell XMR) +account.notifications.marketAlert.offerType.sell=Sell offers (I want to buy XMR) account.notifications.marketAlert.trigger=Offer price distance (%) account.notifications.marketAlert.trigger.info=With a price distance set, you will only receive an alert when an offer \ - that meets (or exceeds) your requirements is published. Example: you want to sell BTC, but you will only sell at \ + that meets (or exceeds) your requirements is published. Example: you want to sell XMR, but you will only sell at \ a 2% premium to the current market price. Setting this field to 2% will ensure you only receive alerts for offers \ with prices that are 2% (or more) above the current market price. account.notifications.marketAlert.trigger.prompt=Percentage distance from market price (e.g. 2.50%, -0.50%, etc) @@ -1778,12 +1778,12 @@ account.notifications.marketAlert.message.title=Offer alert account.notifications.marketAlert.message.msg.below=below account.notifications.marketAlert.message.msg.above=above account.notifications.marketAlert.message.msg=A new ''{0} {1}'' offer with price {2} ({3} {4} market price) and \ - payment method ''{5}'' was published to the Bisq offerbook.\n\ + payment method ''{5}'' was published to the Haveno offerbook.\n\ Offer ID: {6}. account.notifications.priceAlert.message.title=Price alert for {0} account.notifications.priceAlert.message.msg=Your price alert got triggered. The current {0} price is {1} {2} account.notifications.noWebCamFound.warning=No webcam found.\n\n\ - Please use the email option to send the token and encryption key from your mobile phone to the Bisq application. + Please use the email option to send the token and encryption key from your mobile phone to the Haveno application. account.notifications.priceAlert.warning.highPriceTooLow=The higher price must be larger than the lower price. account.notifications.priceAlert.warning.lowerPriceTooHigh=The lower price must be lower than the higher price. @@ -1809,7 +1809,7 @@ dao.unconfirmedChangeBalance=Balance of all unconfirmed change outputs dao.unverifiedBsqBalance=Balance of all unverified transactions (awaiting block confirmation) dao.lockedForVoteBalance=Used for voting dao.lockedInBonds=Locked in bonds -dao.availableNonBsqBalance=Available non-BSQ balance (BTC) +dao.availableNonBsqBalance=Available non-BSQ balance (XMR) dao.reputationBalance=Merit Value (not spendable) dao.tx.published.success=Your transaction has been successfully published. @@ -1865,14 +1865,14 @@ dao.param.MIN_MAKER_FEE_BSQ=Min. BSQ maker fee # suppress inspection "UnusedProperty" dao.param.MIN_TAKER_FEE_BSQ=Min. BSQ taker fee # suppress inspection "UnusedProperty" -dao.param.DEFAULT_MAKER_FEE_BTC=BTC maker fee +dao.param.DEFAULT_MAKER_FEE_BTC=XMR maker fee # suppress inspection "UnusedProperty" -dao.param.DEFAULT_TAKER_FEE_BTC=BTC taker fee +dao.param.DEFAULT_TAKER_FEE_BTC=XMR taker fee # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" -dao.param.MIN_MAKER_FEE_BTC=Min. BTC maker fee +dao.param.MIN_MAKER_FEE_BTC=Min. XMR maker fee # suppress inspection "UnusedProperty" -dao.param.MIN_TAKER_FEE_BTC=Min. BTC taker fee +dao.param.MIN_TAKER_FEE_BTC=Min. XMR taker fee # suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty" @@ -1920,7 +1920,7 @@ dao.param.THRESHOLD_CONFISCATION=Required threshold in % for a confiscation requ dao.param.THRESHOLD_ROLE=Required threshold in % for bonded role requests # suppress inspection "UnusedProperty" -dao.param.RECIPIENT_BTC_ADDRESS=Recipient BTC address +dao.param.RECIPIENT_BTC_ADDRESS=Recipient XMR address # suppress inspection "UnusedProperty" dao.param.ASSET_LISTING_FEE_PER_DAY=Asset listing fee per day @@ -1930,10 +1930,10 @@ dao.param.ASSET_MIN_VOLUME=Min. trade volume for assets # suppress inspection "UnusedProperty" dao.param.LOCK_TIME_TRADE_PAYOUT=Lock time for alternative trade payout tx # suppress inspection "UnusedProperty" -dao.param.ARBITRATOR_FEE=Arbitrator fee in BTC +dao.param.ARBITRATOR_FEE=Arbitrator fee in XMR # suppress inspection "UnusedProperty" -dao.param.MAX_TRADE_LIMIT=Max. trade limit in BTC +dao.param.MAX_TRADE_LIMIT=Max. trade limit in XMR # suppress inspection "UnusedProperty" dao.param.BONDED_ROLE_FACTOR=Bonded role unit factor in BSQ @@ -1945,7 +1945,7 @@ dao.param.currentAndPastValue=Current value: {0} (Value when proposal was made: dao.param.blocks={0} blocks dao.results.invalidVotes=We had invalid votes in that voting cycle. That can happen if a vote was \ - not distributed well in the Bisq network.\n{0} + not distributed well in the Haveno network.\n{0} # suppress inspection "UnusedProperty" dao.phase.PHASE_UNDEFINED=Undefined @@ -2057,7 +2057,7 @@ dao.bond.bondedRoleType.ROCKET_CHAT_ADMIN=Keybase admin # suppress inspection "UnusedProperty" dao.bond.bondedRoleType.YOUTUBE_ADMIN=YouTube admin # suppress inspection "UnusedProperty" -dao.bond.bondedRoleType.BISQ_MAINTAINER=Bisq maintainer +dao.bond.bondedRoleType.BISQ_MAINTAINER=Haveno maintainer # suppress inspection "UnusedProperty" dao.bond.bondedRoleType.BITCOINJ_MAINTAINER=BitcoinJ-fork maintainer # suppress inspection "UnusedProperty" @@ -2071,7 +2071,7 @@ dao.bond.bondedRoleType.SEED_NODE_OPERATOR=Seed node operator # suppress inspection "UnusedProperty" dao.bond.bondedRoleType.DATA_RELAY_NODE_OPERATOR=Price node operator # suppress inspection "UnusedProperty" -dao.bond.bondedRoleType.BTC_NODE_OPERATOR=Bitcoin node operator +dao.bond.bondedRoleType.BTC_NODE_OPERATOR=Monero node operator # suppress inspection "UnusedProperty" dao.bond.bondedRoleType.MARKETS_OPERATOR=Markets operator # suppress inspection "UnusedProperty" @@ -2087,7 +2087,7 @@ dao.bond.bondedRoleType.MEDIATOR=Mediator # suppress inspection "UnusedProperty" dao.bond.bondedRoleType.ARBITRATOR=Arbitrator # suppress inspection "UnusedProperty" -dao.bond.bondedRoleType.BTC_DONATION_ADDRESS_OWNER=BTC donation address owner +dao.bond.bondedRoleType.BTC_DONATION_ADDRESS_OWNER=XMR donation address owner dao.burnBsq.assetFee=Asset listing dao.burnBsq.menuItem.assetFee=Asset listing fee @@ -2268,7 +2268,7 @@ dao.blindVote=blind vote dao.blindVote.startPublishing=Publishing blind vote transaction... dao.blindVote.success=Your blind vote transaction has been successfully published.\n\nPlease note, that you have to be \ - online in the vote reveal phase so that your Bisq application can publish the vote reveal transaction. \ + online in the vote reveal phase so that your Haveno application can publish the vote reveal transaction. \ Without the vote reveal transaction your vote would be invalid! dao.wallet.menuItem.send=Send @@ -2281,16 +2281,16 @@ dao.wallet.receive.fundYourWallet=Your BSQ receive address dao.wallet.receive.bsqAddress=BSQ wallet address (Fresh unused address) dao.wallet.send.sendFunds=Send funds -dao.wallet.send.sendBtcFunds=Send non-BSQ funds (BTC) +dao.wallet.send.sendBtcFunds=Send non-BSQ funds (XMR) dao.wallet.send.amount=Amount in BSQ -dao.wallet.send.btcAmount=Amount in BTC (non-BSQ funds) +dao.wallet.send.btcAmount=Amount in XMR (non-BSQ funds) dao.wallet.send.setAmount=Set amount to withdraw (min. amount is {0}) dao.wallet.send.receiverAddress=Receiver's BSQ address -dao.wallet.send.receiverBtcAddress=Receiver's BTC address +dao.wallet.send.receiverBtcAddress=Receiver's XMR address dao.wallet.send.setDestinationAddress=Fill in your destination address dao.wallet.send.send=Send BSQ funds dao.wallet.send.inputControl=Select inputs -dao.wallet.send.sendBtc=Send BTC funds +dao.wallet.send.sendBtc=Send XMR funds dao.wallet.send.sendFunds.headline=Confirm withdrawal request dao.wallet.send.sendFunds.details=Sending: {0}\nTo receiving address: {1}.\nRequired mining fee is: {2} ({3} satoshis/vbyte)\nTransaction vsize: {4} vKb\n\nThe recipient will receive: {5}\n\nAre you sure you want to withdraw that amount? dao.wallet.chainHeightSynced=Latest verified block: {0} @@ -2336,7 +2336,7 @@ dao.tx.type.enum.PROOF_OF_BURN=Proof of burn # suppress inspection "UnusedProperty" dao.tx.type.enum.IRREGULAR=Irregular -dao.tx.withdrawnFromWallet=BTC withdrawn from wallet +dao.tx.withdrawnFromWallet=XMR withdrawn from wallet dao.tx.issuanceFromCompReq=Compensation request/issuance dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\n\ Issuance date: {0} @@ -2352,12 +2352,12 @@ dao.proposal.create.missingBsqFundsForBond=You don''t have sufficient BSQ funds publish this proposal, but you''ll need the full BSQ amount required for this role if it gets accepted. \n\ Missing: {0} -dao.proposal.create.missingMinerFeeFunds=You don''t have sufficient BTC funds for creating the proposal transaction. \ - All BSQ transactions require a miner fee in BTC.\n\ +dao.proposal.create.missingMinerFeeFunds=You don''t have sufficient XMR funds for creating the proposal transaction. \ + All BSQ transactions require a miner fee in XMR.\n\ Missing: {0} -dao.proposal.create.missingIssuanceFunds=You don''t have sufficient BTC funds for creating the proposal transaction. \ - All BSQ transactions require a miner fee in BTC, and issuance transactions also require BTC for the requested BSQ \ +dao.proposal.create.missingIssuanceFunds=You don''t have sufficient XMR funds for creating the proposal transaction. \ + All BSQ transactions require a miner fee in XMR, and issuance transactions also require XMR for the requested BSQ \ amount ({0} Satoshis/BSQ).\n\ Missing: {1} @@ -2368,32 +2368,32 @@ dao.feeTx.confirm.details={0} fee: {1}\n\ Are you sure you want to publish the {5} transaction? dao.feeTx.issuanceProposal.confirm.details={0} fee: {1}\n\ - BTC needed for BSQ issuance: {2} ({3} Satoshis/BSQ)\n\ + XMR needed for BSQ issuance: {2} ({3} Satoshis/BSQ)\n\ Mining fee: {4} ({5} Satoshis/vbyte)\n\ Transaction vsize: {6} vKb\n\n\ If your request is approved, you will receive the amount you requested net of the 2 BSQ proposal fee.\n\n\ Are you sure you want to publish the {7} transaction? dao.news.bisqDAO.title=THE BISQ DAO -dao.news.bisqDAO.description=Just as the Bisq exchange is decentralized and censorship-resistant, so is its \ - governance model - and the Bisq DAO and BSQ token are the tools that make it possible. -dao.news.bisqDAO.readMoreLink=Learn More About the Bisq DAO +dao.news.bisqDAO.description=Just as the Haveno exchange is decentralized and censorship-resistant, so is its \ + governance model - and the Haveno DAO and BSQ token are the tools that make it possible. +dao.news.bisqDAO.readMoreLink=Learn More About the Haveno DAO dao.news.pastContribution.title=MADE PAST CONTRIBUTIONS? REQUEST BSQ -dao.news.pastContribution.description=If you have contributed to Bisq please use the BSQ address below and make a \ +dao.news.pastContribution.description=If you have contributed to Haveno please use the BSQ address below and make a \ request for taking part of the BSQ genesis distribution. dao.news.pastContribution.yourAddress=Your BSQ Wallet Address dao.news.pastContribution.requestNow=Request now dao.news.DAOOnTestnet.title=RUN THE BISQ DAO ON OUR TESTNET -dao.news.DAOOnTestnet.description=The mainnet Bisq DAO is not launched yet but you can learn about the Bisq DAO \ +dao.news.DAOOnTestnet.description=The mainnet Haveno DAO is not launched yet but you can learn about the Haveno DAO \ by running it on our testnet. dao.news.DAOOnTestnet.firstSection.title=1. Switch to DAO Testnet Mode dao.news.DAOOnTestnet.firstSection.content=Switch to DAO Testnet from the Settings screen. dao.news.DAOOnTestnet.secondSection.title=2. Acquire Some BSQ -dao.news.DAOOnTestnet.secondSection.content=Request BSQ on Slack or Buy BSQ on Bisq. +dao.news.DAOOnTestnet.secondSection.content=Request BSQ on Slack or Buy BSQ on Haveno. dao.news.DAOOnTestnet.thirdSection.title=3. Participate in a Voting Cycle -dao.news.DAOOnTestnet.thirdSection.content=Making proposals and voting on proposals to change various aspects of Bisq. +dao.news.DAOOnTestnet.thirdSection.content=Making proposals and voting on proposals to change various aspects of Haveno. dao.news.DAOOnTestnet.fourthSection.title=4. Explore a BSQ Block Explorer dao.news.DAOOnTestnet.fourthSection.content=Since BSQ is just bitcoin, you can see BSQ transactions on our bitcoin block explorer. dao.news.DAOOnTestnet.readMoreLink=Read the full documentation @@ -2512,10 +2512,10 @@ inputControlWindow.balanceLabel=Available balance contractWindow.title=Dispute details contractWindow.dates=Offer date / Trade date -contractWindow.btcAddresses=Bitcoin address BTC buyer / BTC seller -contractWindow.onions=Network address BTC buyer / BTC seller -contractWindow.accountAge=Account age BTC buyer / BTC seller -contractWindow.numDisputes=No. of disputes BTC buyer / BTC seller +contractWindow.btcAddresses=Monero address XMR buyer / XMR seller +contractWindow.onions=Network address XMR buyer / XMR seller +contractWindow.accountAge=Account age XMR buyer / XMR seller +contractWindow.numDisputes=No. of disputes XMR buyer / XMR seller contractWindow.contractHash=Contract hash displayAlertMessageWindow.headline=Important information! @@ -2529,7 +2529,7 @@ displayUpdateDownloadWindow.status.verifying=Verifying signature... displayUpdateDownloadWindow.button.label=Download installer and verify signature displayUpdateDownloadWindow.button.downloadLater=Download later displayUpdateDownloadWindow.button.ignoreDownload=Ignore this version -displayUpdateDownloadWindow.headline=A new Bisq update is available! +displayUpdateDownloadWindow.headline=A new Haveno update is available! displayUpdateDownloadWindow.download.failed.headline=Download failed displayUpdateDownloadWindow.download.failed=Download failed.\n\ Please download and verify manually at [HYPERLINK:https://bisq.network/downloads] @@ -2594,8 +2594,8 @@ disputeSummaryWindow.close.msg=Ticket closed on {0}\n\ Trade ID: {3}\n\ Currency: {4}\n\ Trade amount: {5}\n\ - Payout amount for BTC buyer: {6}\n\ - Payout amount for BTC seller: {7}\n\n\ + Payout amount for XMR buyer: {6}\n\ + Payout amount for XMR seller: {7}\n\n\ Reason for dispute: {8}\n\n\ Summary notes:\n{9}\n @@ -2626,7 +2626,7 @@ emptyWalletWindow.info=Please use that only in emergency case if you cannot acce Please note that all open offers will be closed automatically when using this tool.\n\n\ Before you use this tool, please backup your data directory. \ You can do this at \"Account/Backup\".\n\n\ -Please report us your problem and file a bug report on GitHub or at the Bisq forum so that we can investigate what was causing the problem. +Please report us your problem and file a bug report on GitHub or at the Haveno forum so that we can investigate what was causing the problem. emptyWalletWindow.balance=Your available wallet balance emptyWalletWindow.bsq.btcBalance=Balance of non-BSQ Satoshis @@ -2652,8 +2652,8 @@ filterWindow.mediators=Filtered mediators (comma sep. onion addresses) filterWindow.refundAgents=Filtered refund agents (comma sep. onion addresses) filterWindow.seedNode=Filtered seed nodes (comma sep. onion addresses) filterWindow.priceRelayNode=Filtered price relay nodes (comma sep. onion addresses) -filterWindow.btcNode=Filtered Bitcoin nodes (comma sep. addresses + port) -filterWindow.preventPublicBtcNetwork=Prevent usage of public Bitcoin network +filterWindow.btcNode=Filtered Monero nodes (comma sep. addresses + port) +filterWindow.preventPublicBtcNetwork=Prevent usage of public Monero network filterWindow.disableDao=Disable DAO filterWindow.disableAutoConf=Disable auto-confirm filterWindow.autoConfExplorers=Filtered auto-confirm explorers (comma sep. addresses) @@ -2665,7 +2665,7 @@ filterWindow.btcFeeReceiverAddresses=BTC fee receiver addresses filterWindow.disableApi=Disable API filterWindow.disableMempoolValidation=Disable Mempool Validation -offerDetailsWindow.minBtcAmount=Min. BTC amount +offerDetailsWindow.minBtcAmount=Min. XMR amount offerDetailsWindow.min=(min. {0}) offerDetailsWindow.distance=(distance from market price: {0}) offerDetailsWindow.myTradingAccount=My trading account @@ -2680,7 +2680,7 @@ offerDetailsWindow.creationDate=Creation date offerDetailsWindow.makersOnion=Maker's onion address qRCodeWindow.headline=QR Code -qRCodeWindow.msg=Please use this QR code for funding your Bisq wallet from your external wallet. +qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet. qRCodeWindow.request=Payment request:\n{0} selectDepositTxWindow.headline=Select deposit transaction for dispute @@ -2779,9 +2779,9 @@ torNetworkSettingWindow.bridges.info=If Tor is blocked by your internet provider bridges and pluggable transports. feeOptionWindow.headline=Choose currency for trade fee payment -feeOptionWindow.info=You can choose to pay the trade fee in BSQ or in BTC. If you choose BSQ you appreciate the discounted trade fee. +feeOptionWindow.info=You can choose to pay the trade fee in BSQ or in XMR. If you choose BSQ you appreciate the discounted trade fee. feeOptionWindow.optionsLabel=Choose currency for trade fee payment -feeOptionWindow.useBTC=Use BTC +feeOptionWindow.useBTC=Use XMR feeOptionWindow.fee={0} (≈ {1}) feeOptionWindow.btcFeeWithFiatAndPercentage={0} (≈ {1} / {2}) feeOptionWindow.btcFeeWithPercentage={0} ({1}) @@ -2821,24 +2821,24 @@ error.closedTradeWithNoDepositTx=The deposit transaction of the closed trade wit popup.warning.walletNotInitialized=The wallet is not initialized yet popup.warning.osxKeyLoggerWarning=Due to stricter security measures in macOS 10.14 and above, launching a Java application \ - (Bisq uses Java) causes a popup warning in macOS ('Bisq would like to receive keystrokes from any application').\n\n\ + (Haveno uses Java) causes a popup warning in macOS ('Haveno would like to receive keystrokes from any application').\n\n\ To avoid that issue please open your 'macOS Settings' and go to 'Security & Privacy' -> 'Privacy' -> \ - 'Input Monitoring' and Remove 'Bisq' from the list on the right side.\n\n\ - Bisq will upgrade to a newer Java version to avoid that issue as soon the technical limitations \ + 'Input Monitoring' and Remove 'Haveno' from the list on the right side.\n\n\ + Haveno will upgrade to a newer Java version to avoid that issue as soon the technical limitations \ (Java packager for the required Java version is not shipped yet) are resolved. -popup.warning.wrongVersion=You probably have the wrong Bisq version for this computer.\n\ +popup.warning.wrongVersion=You probably have the wrong Haveno version for this computer.\n\ Your computer''s architecture is: {0}.\n\ -The Bisq binary you installed is: {1}.\n\ +The Haveno binary you installed is: {1}.\n\ Please shut down and re-install the correct version ({2}). popup.warning.incompatibleDB=We detected incompatible data base files!\n\n\ Those database file(s) are not compatible with our current code base:\n{0}\n\n\ We made a backup of the corrupted file(s) and applied the default values to a new database version.\n\n\ The backup is located at:\n\ {1}/db/backup_of_corrupted_data.\n\n\ -Please check if you have the latest version of Bisq installed.\n\ +Please check if you have the latest version of Haveno installed.\n\ You can download it at: [HYPERLINK:https://bisq.network/downloads].\n\n\ Please restart the application. -popup.warning.startupFailed.twoInstances=Bisq is already running. You cannot run two instances of Bisq. +popup.warning.startupFailed.twoInstances=Haveno is already running. You cannot run two instances of Haveno. popup.warning.tradePeriod.halfReached=Your trade with ID {0} has reached the half of the max. allowed trading period and is still not completed.\n\nThe trade period ends on {1}\n\nPlease check your trade state at \"Portfolio/Open trades\" for further information. popup.warning.tradePeriod.ended=Your trade with ID {0} has reached the max. allowed trading period and is not completed.\n\n\ The trade period ended on {1}\n\n\ @@ -2848,29 +2848,29 @@ popup.warning.noTradingAccountSetup.msg=You need to setup a national currency or popup.warning.noArbitratorsAvailable=There are no arbitrators available. popup.warning.noMediatorsAvailable=There are no mediators available. popup.warning.notFullyConnected=You need to wait until you are fully connected to the network.\nThat might take up to about 2 minutes at startup. -popup.warning.notSufficientConnectionsToBtcNetwork=You need to wait until you have at least {0} connections to the Bitcoin network. -popup.warning.downloadNotComplete=You need to wait until the download of missing Bitcoin blocks is complete. -popup.warning.chainNotSynced=The Bisq wallet blockchain height is not synced correctly. If you recently started the application, please wait until one Bitcoin block has been published.\n\n\ +popup.warning.notSufficientConnectionsToBtcNetwork=You need to wait until you have at least {0} connections to the Monero network. +popup.warning.downloadNotComplete=You need to wait until the download of missing Monero blocks is complete. +popup.warning.chainNotSynced=The Haveno wallet blockchain height is not synced correctly. If you recently started the application, please wait until one Monero block has been published.\n\n\ You can check the blockchain height in Settings/Network Info. If more than one block passes and this problem persists it may be stalled, in which case you should do an SPV resync. [HYPERLINK:https://bisq.wiki/Resyncing_SPV_file] popup.warning.removeOffer=Are you sure you want to remove that offer?\nThe maker fee of {0} will be lost if you remove that offer. popup.warning.tooLargePercentageValue=You cannot set a percentage of 100% or larger. popup.warning.examplePercentageValue=Please enter a percentage number like \"5.4\" for 5.4% popup.warning.noPriceFeedAvailable=There is no price feed available for that currency. You cannot use a percent based price.\nPlease select the fixed price. popup.warning.sendMsgFailed=Sending message to your trading partner failed.\nPlease try again and if it continue to fail report a bug. -popup.warning.insufficientBtcFundsForBsqTx=You don''t have sufficient BTC funds for paying the miner fee for that transaction.\n\ -Please fund your BTC wallet.\nMissing funds: {0} +popup.warning.insufficientBtcFundsForBsqTx=You don''t have sufficient XMR funds for paying the miner fee for that transaction.\n\ +Please fund your XMR wallet.\nMissing funds: {0} popup.warning.bsqChangeBelowDustException=This transaction creates a BSQ change output which is below dust \ - limit (5.46 BSQ) and would be rejected by the Bitcoin network.\n\n\ + limit (5.46 BSQ) and would be rejected by the Monero network.\n\n\ You need to either send a higher amount to avoid the change output (e.g. by adding the dust amount to your \ sending amount) or add more BSQ funds to your wallet so you avoid to generate a dust output.\n\n\ The dust output is {0}. popup.warning.btcChangeBelowDustException=This transaction creates a change output which is below dust \ - limit (546 Satoshi) and would be rejected by the Bitcoin network.\n\n\ + limit (546 Satoshi) and would be rejected by the Monero network.\n\n\ You need to add the dust amount to your sending amount to avoid to generate a dust output.\n\n\ The dust output is {0}. popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last \ - 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\n\ + 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Monero protocol.\n\n\ You can either buy more BSQ or pay trade fees with BTC.\n\n\ Missing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=Your BSQ wallet does not have sufficient funds for paying the trade fee in BSQ. @@ -2890,64 +2890,64 @@ popup.warning.nodeBanned=One of the {0} nodes got banned. # suppress inspection "UnusedProperty" popup.warning.priceRelay=price relay popup.warning.seed=seed -popup.warning.mandatoryUpdate.trading=Please update to the latest Bisq version. \ +popup.warning.mandatoryUpdate.trading=Please update to the latest Haveno version. \ A mandatory update was released which disables trading for old versions. \ - Please check out the Bisq Forum for more information. -popup.warning.mandatoryUpdate.dao=Please update to the latest Bisq version. \ - A mandatory update was released which disables the Bisq DAO and BSQ for old versions. \ - Please check out the Bisq Forum for more information. -popup.warning.disable.dao=The Bisq DAO and BSQ are temporary disabled. \ - Please check out the Bisq Forum for more information. -popup.warning.noFilter=We did not receive a filter object from the seed nodes. This is a not expected situation. Please inform the Bisq developers. + Please check out the Haveno Forum for more information. +popup.warning.mandatoryUpdate.dao=Please update to the latest Haveno version. \ + A mandatory update was released which disables the Haveno DAO and BSQ for old versions. \ + Please check out the Haveno Forum for more information. +popup.warning.disable.dao=The Haveno DAO and BSQ are temporary disabled. \ + Please check out the Haveno Forum for more information. +popup.warning.noFilter=We did not receive a filter object from the seed nodes. This is a not expected situation. Please inform the Haveno developers. popup.warning.burnBTC=This transaction is not possible, as the mining fees of {0} would exceed the amount to transfer of {1}. \ - Please wait until the mining fees are low again or until you''ve accumulated more BTC to transfer. + Please wait until the mining fees are low again or until you''ve accumulated more XMR to transfer. -popup.warning.openOffer.makerFeeTxRejected=The maker fee transaction for offer with ID {0} was rejected by the Bitcoin network.\n\ +popup.warning.openOffer.makerFeeTxRejected=The maker fee transaction for offer with ID {0} was rejected by the Monero network.\n\ Transaction ID={1}.\n\ The offer has been removed to avoid further problems.\n\ Please go to \"Settings/Network info\" and do a SPV resync.\n\ - For further help please contact the Bisq support channel at the Bisq Keybase team. + For further help please contact the Haveno support channel at the Haveno Keybase team. popup.warning.trade.txRejected.tradeFee=trade fee popup.warning.trade.txRejected.deposit=deposit -popup.warning.trade.txRejected=The {0} transaction for trade with ID {1} was rejected by the Bitcoin network.\n\ +popup.warning.trade.txRejected=The {0} transaction for trade with ID {1} was rejected by the Monero network.\n\ Transaction ID={2}\n\ The trade has been moved to failed trades.\n\ Please go to \"Settings/Network info\" and do a SPV resync.\n\ - For further help please contact the Bisq support channel at the Bisq Keybase team. + For further help please contact the Haveno support channel at the Haveno Keybase team. popup.warning.openOfferWithInvalidMakerFeeTx=The maker fee transaction for offer with ID {0} is invalid.\n\ Transaction ID={1}.\n\ Please go to \"Settings/Network info\" and do a SPV resync.\n\ - For further help please contact the Bisq support channel at the Bisq Keybase team. + For further help please contact the Haveno support channel at the Haveno Keybase team. popup.info.securityDepositInfo=To ensure both traders follow the trade protocol, both traders need to pay a security \ deposit.\n\nThis deposit is kept in your trade wallet until your trade has been successfully completed, and then it's \ -refunded to you.\n\nPlease note: if you're creating a new offer, Bisq needs to be running for another trader to take \ -it. To keep your offers online, keep Bisq running and make sure this computer remains online too (i.e., make sure it \ +refunded to you.\n\nPlease note: if you're creating a new offer, Haveno needs to be running for another trader to take \ +it. To keep your offers online, keep Haveno running and make sure this computer remains online too (i.e., make sure it \ doesn't switch to standby mode...monitor standby is fine). popup.info.cashDepositInfo=Please be sure that you have a bank branch in your area to be able to make the cash deposit.\n\ The bank ID (BIC/SWIFT) of the seller''s bank is: {0}. popup.info.cashDepositInfo.confirm=I confirm that I can make the deposit -popup.info.shutDownWithOpenOffers=Bisq is being shut down, but there are open offers. \n\n\ - These offers won't be available on the P2P network while Bisq is shut down, but \ - they will be re-published to the P2P network the next time you start Bisq.\n\n\ - To keep your offers online, keep Bisq running and make sure this computer remains online too \ +popup.info.shutDownWithOpenOffers=Haveno is being shut down, but there are open offers. \n\n\ + These offers won't be available on the P2P network while Haveno is shut down, but \ + they will be re-published to the P2P network the next time you start Haveno.\n\n\ + To keep your offers online, keep Haveno running and make sure this computer remains online too \ (i.e., make sure it doesn't go into standby mode...monitor standby is not a problem). -popup.info.qubesOSSetupInfo=It appears you are running Bisq on Qubes OS. \n\n\ - Please make sure your Bisq qube is setup according to our Setup Guide at [HYPERLINK:https://bisq.wiki/Running_Bisq_on_Qubes]. -popup.warn.downGradePrevention=Downgrade from version {0} to version {1} is not supported. Please use the latest Bisq version. +popup.info.qubesOSSetupInfo=It appears you are running Haveno on Qubes OS. \n\n\ + Please make sure your Haveno qube is setup according to our Setup Guide at [HYPERLINK:https://bisq.wiki/Running_Bisq_on_Qubes]. +popup.warn.downGradePrevention=Downgrade from version {0} to version {1} is not supported. Please use the latest Haveno version. popup.warn.daoRequiresRestart=There was a problem with synchronizing the DAO state. You have to restart the application to fix the issue. popup.privateNotification.headline=Important private notification! popup.securityRecommendation.headline=Important security recommendation -popup.securityRecommendation.msg=We would like to remind you to consider using password protection for your wallet if you have not already enabled that.\n\nIt is also highly recommended to write down the wallet seed words. Those seed words are like a master password for recovering your Bitcoin wallet.\nAt the \"Wallet Seed\" section you find more information.\n\nAdditionally you should backup the complete application data folder at the \"Backup\" section. +popup.securityRecommendation.msg=We would like to remind you to consider using password protection for your wallet if you have not already enabled that.\n\nIt is also highly recommended to write down the wallet seed words. Those seed words are like a master password for recovering your Monero wallet.\nAt the \"Wallet Seed\" section you find more information.\n\nAdditionally you should backup the complete application data folder at the \"Backup\" section. -popup.bitcoinLocalhostNode.msg=Bisq detected a Bitcoin Core node running on this machine (at localhost).\n\n\ +popup.bitcoinLocalhostNode.msg=Haveno detected a Monero node running on this machine (at localhost).\n\n\ Please ensure:\n\ - - the node is fully synced before starting Bisq\n\ + - the node is fully synced before starting Haveno\n\ - pruning is disabled ('prune=0' in bitcoin.conf)\n\ - bloom filters are enabled ('peerbloomfilters=1' in bitcoin.conf) @@ -3006,9 +3006,9 @@ popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign notification.trade.headline=Notification for trade with ID {0} notification.ticket.headline=Support ticket for trade with ID {0} notification.trade.completed=The trade is now completed and you can withdraw your funds. -notification.trade.accepted=Your offer has been accepted by a BTC {0}. +notification.trade.accepted=Your offer has been accepted by a XMR {0}. notification.trade.confirmed=Your trade has at least one blockchain confirmation.\nYou can start the payment now. -notification.trade.paymentStarted=The BTC buyer has started the payment. +notification.trade.paymentStarted=The XMR buyer has started the payment. notification.trade.selectTrade=Select trade notification.trade.peerOpenedDispute=Your trading peer has opened a {0}. notification.trade.disputeClosed=The {0} has been closed. @@ -3016,7 +3016,7 @@ notification.walletUpdate.headline=Trading wallet update notification.walletUpdate.msg=Your trading wallet is sufficiently funded.\nAmount: {0} notification.takeOffer.walletUpdate.msg=Your trading wallet was already sufficiently funded from an earlier take offer attempt.\nAmount: {0} notification.tradeCompleted.headline=Trade completed -notification.tradeCompleted.msg=You can withdraw your funds now to your external Bitcoin wallet or transfer it to the Bisq wallet. +notification.tradeCompleted.msg=You can withdraw your funds now to your external Monero wallet or transfer it to the Haveno wallet. #################################################################### @@ -3025,9 +3025,9 @@ notification.tradeCompleted.msg=You can withdraw your funds now to your external systemTray.show=Show application window systemTray.hide=Hide application window -systemTray.info=Info about Bisq +systemTray.info=Info about Haveno systemTray.exit=Exit -systemTray.tooltip=Bisq: A decentralized bitcoin exchange network +systemTray.tooltip=Haveno: A decentralized bitcoin exchange network #################################################################### @@ -3097,10 +3097,10 @@ peerInfo.age.noRisk=Payment account age peerInfo.age.chargeBackRisk=Time since signing peerInfo.unknownAge=Age not known -addressTextField.openWallet=Open your default Bitcoin wallet +addressTextField.openWallet=Open your default Monero wallet addressTextField.copyToClipboard=Copy address to clipboard addressTextField.addressCopiedToClipboard=Address has been copied to clipboard -addressTextField.openWallet.failed=Opening a default Bitcoin wallet application has failed. Perhaps you don't have one installed? +addressTextField.openWallet.failed=Opening a default Monero wallet application has failed. Perhaps you don't have one installed? peerInfoIcon.tooltip={0}\nTag: {1} @@ -3149,17 +3149,17 @@ formatter.asTaker={0} {1} as taker # we use enum values here # dynamic values are not recognized by IntelliJ # suppress inspection "UnusedProperty" -BTC_MAINNET=Bitcoin Mainnet +BTC_MAINNET=Monero Mainnet # suppress inspection "UnusedProperty" -BTC_TESTNET=Bitcoin Testnet +BTC_TESTNET=Monero Testnet # suppress inspection "UnusedProperty" -BTC_REGTEST=Bitcoin Regtest +BTC_REGTEST=Monero Regtest # suppress inspection "UnusedProperty" -BTC_DAO_TESTNET=Bitcoin DAO Testnet (deprecated) +BTC_DAO_TESTNET=Monero DAO Testnet (deprecated) # suppress inspection "UnusedProperty" -BTC_DAO_BETANET=Bisq DAO Betanet (Bitcoin Mainnet) +BTC_DAO_BETANET=Haveno DAO Betanet (Monero Mainnet) # suppress inspection "UnusedProperty" -BTC_DAO_REGTEST=Bitcoin DAO Regtest +BTC_DAO_REGTEST=Monero DAO Regtest time.year=Year time.month=Month @@ -3199,7 +3199,7 @@ seed.date=Wallet date seed.restore.title=Restore wallets from seed words seed.restore=Restore wallets seed.creationDate=Creation date -seed.warn.walletNotEmpty.msg=Your Bitcoin wallet is not empty.\n\n\ +seed.warn.walletNotEmpty.msg=Your Monero wallet is not empty.\n\n\ You must empty this wallet before attempting to restore an older one, as mixing wallets \ together can lead to invalidated backups.\n\n\ Please finalize your trades, close all your open offers and go to the Funds section to withdraw your bitcoin.\n\ @@ -3309,44 +3309,44 @@ payment.makeOfferToUnsignedAccount.warning=With the recent rise in BTC price, be It is highly recommended to either:\n\ - make offers >0.01 BTC, so you only deal with signed/trusted buyers\n\ - keep any offers to sell <0.01 BTC to around ~100 USD in value, as this value has (historically) discouraged scammers\n\n\ -Bisq developers are working on better ways to secure the payment account model for such smaller trades. \ +Haveno developers are working on better ways to secure the payment account model for such smaller trades. \ Join the discussion here: [HYPERLINK:https://github.com/bisq-network/bisq/discussions/5339]. payment.takeOfferFromUnsignedAccount.warning=With the recent rise in BTC price, beware that selling 0.01 BTC or less incurs higher risk than before.\n\n\ It is highly recommended to either:\n\ - take offers from signed buyers only\n\ - keep trades with unsigned/untrusted buyers to around ~100 USD in value, as this value has (historically) discouraged scammers\n\n\ -Bisq developers are working on better ways to secure the payment account model for such smaller trades. \ +Haveno developers are working on better ways to secure the payment account model for such smaller trades. \ Join the discussion here: [HYPERLINK:https://github.com/bisq-network/bisq/discussions/5339]. payment.clearXchange.info=Zelle is a money transfer service that works best *through* another bank.\n\n\ 1. Check this page to see if (and how) your bank works with Zelle: [HYPERLINK:https://www.zellepay.com/get-started]\n\n\ 2. Take special note of your transfer limits—sending limits vary by bank, and banks often specify separate daily, weekly, and monthly limits.\n\n\ 3. If your bank does not work with Zelle, you can still use it through the Zelle mobile app, but your transfer limits will be much lower.\n\n\ - 4. The name specified on your Bisq account MUST match the name on your Zelle/bank account. \n\n\ + 4. The name specified on your Haveno account MUST match the name on your Zelle/bank account. \n\n\ If you cannot complete a Zelle transaction as specified in your trade contract, you may lose some (or all) of your security deposit.\n\n\ Because of Zelle''s somewhat higher chargeback risk, sellers are advised to contact unsigned buyers through email or SMS to verify that the buyer \ - really owns the Zelle account specified in Bisq. + really owns the Zelle account specified in Haveno. payment.fasterPayments.newRequirements.info=Some banks have started verifying the receiver''s full name for Faster \ Payments transfers. Your current Faster Payments account does not specify a full name.\n\n\ - Please consider recreating your Faster Payments account in Bisq to provide future {0} buyers with a full name.\n\n\ + Please consider recreating your Faster Payments account in Haveno to provide future {0} buyers with a full name.\n\n\ When you recreate the account, make sure to copy the precise sort code, account number and account age verification \ salt values from your old account to your new account. This will ensure your existing account''s age and signing \ status are preserved. -payment.moneyGram.info=When using MoneyGram the BTC buyer has to send the Authorisation number and a photo of the receipt by email to the BTC seller. \ +payment.moneyGram.info=When using MoneyGram the XMR buyer has to send the Authorisation number and a photo of the receipt by email to the XMR seller. \ The receipt must clearly show the seller's full name, country, state and the amount. The seller's email will be displayed to the buyer during the trade process. -payment.westernUnion.info=When using Western Union the BTC buyer has to send the MTCN (tracking number) and a photo of the receipt by email to the BTC seller. \ +payment.westernUnion.info=When using Western Union the XMR buyer has to send the MTCN (tracking number) and a photo of the receipt by email to the XMR seller. \ The receipt must clearly show the seller's full name, city, country and the amount. The seller's email will be displayed to the buyer during the trade process. -payment.halCash.info=When using HalCash the BTC buyer needs to send the BTC seller the HalCash code via a text message from their mobile phone.\n\n\ +payment.halCash.info=When using HalCash the XMR buyer needs to send the XMR seller the HalCash code via a text message from their mobile phone.\n\n\ Please make sure to not exceed the maximum amount your bank allows you to send with HalCash. \ The min. amount per withdrawal is 10 EUR and the max. amount is 600 EUR. For repeated withdrawals it is \ 3000 EUR per receiver per day and 6000 EUR per receiver per month. Please cross check those limits with your \ bank to be sure they use the same limits as stated here.\n\n\ The withdrawal amount must be a multiple of 10 EUR as you cannot withdraw other amounts from an ATM. The \ - UI in the create-offer and take-offer screen will adjust the BTC amount so that the EUR amount is correct. You cannot use market \ + UI in the create-offer and take-offer screen will adjust the XMR amount so that the EUR amount is correct. You cannot use market \ based price as the EUR amount would be changing with changing prices.\n\n\ - In case of a dispute the BTC buyer needs to provide the proof that they sent the EUR. + In case of a dispute the XMR buyer needs to provide the proof that they sent the EUR. # suppress inspection "UnusedMessageFormatParameter" payment.limits.info=Please be aware that all bank transfers carry a certain amount of chargeback risk. To mitigate this risk, \ - Bisq sets per-trade limits based on the estimated level of chargeback risk for the payment method used.\n\ + Haveno sets per-trade limits based on the estimated level of chargeback risk for the payment method used.\n\ \n\ For this payment method, your per-trade limit for buying and selling is {2}.\n\ \n\ @@ -3354,7 +3354,7 @@ payment.limits.info=Please be aware that all bank transfers carry a certain amou \n\ See more details on the wiki [HYPERLINK:https://bisq.wiki/Account_limits]. # suppress inspection "UnusedProperty" -payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade limits for this payment account type based \ +payment.limits.info.withSigning=To limit chargeback risk, Haveno sets per-trade limits for this payment account type based \ on the following 2 factors:\n\n\ 1. General chargeback risk for the payment method\n\ 2. Account signing status\n\ @@ -3389,22 +3389,22 @@ payment.account.amazonGiftCard.addCountryInfo={0}\n\ This will not affect your account age status. payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account -payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\ +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Haveno requires that you understand the following:\n\ \n\ -- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n\ -- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\ +- XMR buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n\ +- XMR buyers must send the USPMO to the XMR seller with Delivery Confirmation.\n\ \n\ -In the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\n\ +In the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Haveno mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\n\ Failure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\n\ In all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\n\ -If you do not understand these requirements, do not trade using USPMO on Bisq. +If you do not understand these requirements, do not trade using USPMO on Haveno. -payment.cashByMail.info=Trading using cash-by-mail (CBM) on Bisq requires that you understand the following:\n\ +payment.cashByMail.info=Trading using cash-by-mail (CBM) on Haveno requires that you understand the following:\n\ \n\ - ● BTC buyer should package cash in a tamper-evident cash bag.\n\ - ● BTC buyer should film or take high-resolution photos of the cash packaging process with the address & tracking number already affixed to packaging.\n\ - ● BTC buyer should send the cash package to the BTC seller with Delivery Confirmation and appropriate Insurance.\n\ - ● BTC seller should film the opening of the package, making sure that the tracking number provided by the sender is visible in the video.\n\ + ● XMR buyer should package cash in a tamper-evident cash bag.\n\ + ● XMR buyer should film or take high-resolution photos of the cash packaging process with the address & tracking number already affixed to packaging.\n\ + ● XMR buyer should send the cash package to the XMR seller with Delivery Confirmation and appropriate Insurance.\n\ + ● XMR seller should film the opening of the package, making sure that the tracking number provided by the sender is visible in the video.\n\ ● Offer maker must state any special terms or conditions in the 'Additional Information' field of the payment account.\n\ ● Offer taker agrees to the offer maker's terms and conditions by taking the offer.\n\ \n\ @@ -3413,14 +3413,14 @@ payment.cashByMail.info=Trading using cash-by-mail (CBM) on Bisq requires that y ● CBM trades have less verifiable actions than other fiat trades. This makes handling dispute much harder.\n\ ● Try to resolve disputes directly with your peer using trader chat. This is your most promising route to solving any CBM dispute.\n\ ● Mediators can consider your case and make a suggestion, but they are NOT guaranteed to help.\n\ - ● If a mediator is engaged, and if either peer rejects the mediator's suggestion, both peers' funds will be sent to a Bisq 'donation' address [HYPERLINK:https://bisq.wiki/Arbitration#Time-Locked_Payout_Transaction], and the trade will effectively be completed.\n\ + ● If a mediator is engaged, and if either peer rejects the mediator's suggestion, both peers' funds will be sent to a Haveno 'donation' address [HYPERLINK:https://bisq.wiki/Arbitration#Time-Locked_Payout_Transaction], and the trade will effectively be completed.\n\ ● If a trader rejects a mediation suggestion and opens arbitration, it could lead to a loss of both the trading and the deposit funds.\n\ ● Arbitrators will make a decision based on the evidence provided to them. Therefore, please follow and document the above processes to have evidence in case of dispute. For Cash by Mail trades the Arbitrators decision is final.\n\ - ● Reimbursement requests any lost funds resulting from Cash By Mail trades to the Bisq DAO will NOT be considered.\n\ + ● Reimbursement requests any lost funds resulting from Cash By Mail trades to the Haveno DAO will NOT be considered.\n\ \n\ To be sure you fully understand the requirements of cash-by-mail trades, please see: [HYPERLINK:https://bisq.wiki/Cash_by_Mail]\n\ \n\ - If you do not understand these requirements, do not trade using CBM on Bisq. + If you do not understand these requirements, do not trade using CBM on Haveno. payment.cashByMail.contact=Contact info payment.cashByMail.contact.prompt=Name or nym envelope should be addressed to @@ -3445,7 +3445,7 @@ payment.f2f.info='Face to Face' trades have different rules and come with differ ● If a maker has special 'terms and conditions' they must state those in the 'Additional information' text field in the account.\n\ ● By taking an offer the taker agrees to the maker's stated 'terms and conditions'.\n\ ● In case of a dispute the mediator or arbitrator cannot be of much assistance as it is usually difficult to get tamper-proof evidence \ - of what happened at the meeting. In such cases the BTC funds might get locked indefinitely or until the trading peers come to \ + of what happened at the meeting. In such cases the XMR funds might get locked indefinitely or until the trading peers come to \ an agreement.\n\n\ To be sure you fully understand the differences with 'Face to Face' trades please read the instructions and \ recommendations at: [HYPERLINK:https://docs.bisq.network/trading-rules.html#f2f-trading] @@ -3462,8 +3462,8 @@ payment.payid=PayID linked to financial institution. Like email address or mobil payment.payid.info=A PayID like a phone number, email address or an Australian Business Number (ABN), that you can securely link to your \ bank, credit union or building society account. You need to have already created a PayID with your Australian financial institution. \ Both sending and receiving financial institutions must support PayID. For more information please check [HYPERLINK:https://payid.com.au/faqs/] -payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the BTC seller via your Amazon account. \n\n\ - Bisq will show the BTC seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift \ +payment.amazonGiftCard.info=To pay with Amazon eGift Card, you will need to send an Amazon eGift Card to the XMR seller via your Amazon account. \n\n\ + Haveno will show the XMR seller''s email address or phone number where the gift card should be sent, and you must include the trade ID in the gift \ card''s message field. Please see the wiki [HYPERLINK:https://bisq.wiki/Amazon_eGift_card] for further details and best practices. \n\n\ Three important notes:\n\ - try to send gift cards with amounts of 100 USD or smaller, as Amazon is known to flag larger gift cards as fraudulent\n\ @@ -3663,7 +3663,7 @@ validation.bic.letters=Bank and Country code must be letters validation.bic.invalidLocationCode=BIC contains invalid location code validation.bic.invalidBranchCode=BIC contains invalid branch code validation.bic.sepaRevolutBic=Revolut Sepa accounts are not supported. -validation.btc.invalidFormat=Invalid format for a Bitcoin address. +validation.btc.invalidFormat=Invalid format for a Monero address. validation.bsq.invalidFormat=Invalid format for a BSQ address. validation.email.invalidAddress=Invalid address validation.iban.invalidCountryCode=Country code invalid diff --git a/core/src/main/resources/i18n/displayStrings_cs.properties b/core/src/main/resources/i18n/displayStrings_cs.properties index a7d8c9a4..35785f47 100644 --- a/core/src/main/resources/i18n/displayStrings_cs.properties +++ b/core/src/main/resources/i18n/displayStrings_cs.properties @@ -840,7 +840,7 @@ funds.tab.transactions=Transakce funds.deposit.unused=Nepoužito funds.deposit.usedInTx=Používá se v {0} transakcích -funds.deposit.fundBisqWallet=Financovat Bisq peněženku +funds.deposit.fundHavenoWallet=Financovat Bisq peněženku funds.deposit.noAddresses=Dosud nebyly vygenerovány žádné adresy pro vklad funds.deposit.fundWallet=Financujte svou peněženku funds.deposit.withdrawFromWallet=Pošlete peníze z peněženky diff --git a/core/src/main/resources/i18n/displayStrings_de.properties b/core/src/main/resources/i18n/displayStrings_de.properties index 717d5402..6c32bcb7 100644 --- a/core/src/main/resources/i18n/displayStrings_de.properties +++ b/core/src/main/resources/i18n/displayStrings_de.properties @@ -840,7 +840,7 @@ funds.tab.transactions=Transaktionen funds.deposit.unused=Ungenutzt funds.deposit.usedInTx=In {0} Transaktion(en) genutzt -funds.deposit.fundBisqWallet=Bisq-Wallet finanzieren +funds.deposit.fundHavenoWallet=Bisq-Wallet finanzieren funds.deposit.noAddresses=Es wurden noch keine Kautionsadressen generiert funds.deposit.fundWallet=Ihre Wallet finanzieren funds.deposit.withdrawFromWallet=Gelder aus Wallet übertragen diff --git a/core/src/main/resources/i18n/displayStrings_es.properties b/core/src/main/resources/i18n/displayStrings_es.properties index 110870e9..b27dd7dd 100644 --- a/core/src/main/resources/i18n/displayStrings_es.properties +++ b/core/src/main/resources/i18n/displayStrings_es.properties @@ -840,7 +840,7 @@ funds.tab.transactions=Transacciones funds.deposit.unused=Sin usar funds.deposit.usedInTx=Usadas en {0} transacciones -funds.deposit.fundBisqWallet=Fondear monedero Bisq +funds.deposit.fundHavenoWallet=Fondear monedero Bisq funds.deposit.noAddresses=Aún no se ha generado la dirección de depósito funds.deposit.fundWallet=Dotar de fondos su monedero funds.deposit.withdrawFromWallet=Enviar fondos desde monedero diff --git a/core/src/main/resources/i18n/displayStrings_fa.properties b/core/src/main/resources/i18n/displayStrings_fa.properties index 6f217862..2c317ac2 100644 --- a/core/src/main/resources/i18n/displayStrings_fa.properties +++ b/core/src/main/resources/i18n/displayStrings_fa.properties @@ -840,7 +840,7 @@ funds.tab.transactions=تراکنش‌ها funds.deposit.unused=استفاده نشده funds.deposit.usedInTx=مورد استفاده در تراکنش (های) {0} -funds.deposit.fundBisqWallet=تأمین مالی کیف پول Bisq  +funds.deposit.fundHavenoWallet=تأمین مالی کیف پول Bisq  funds.deposit.noAddresses=آدرس‌هایی برای سپرده ایجاد نشده است funds.deposit.fundWallet=تأمین مالی کیف پول شما funds.deposit.withdrawFromWallet=ارسال وجه از کیف‌پول diff --git a/core/src/main/resources/i18n/displayStrings_fr.properties b/core/src/main/resources/i18n/displayStrings_fr.properties index 49c762a3..dac91f20 100644 --- a/core/src/main/resources/i18n/displayStrings_fr.properties +++ b/core/src/main/resources/i18n/displayStrings_fr.properties @@ -840,7 +840,7 @@ funds.tab.transactions=Transactions funds.deposit.unused=Inutilisé funds.deposit.usedInTx=Utilisé dans {0} transaction(s) -funds.deposit.fundBisqWallet=Alimenter le portefeuille Bisq +funds.deposit.fundHavenoWallet=Alimenter le portefeuille Bisq funds.deposit.noAddresses=Aucune adresse de dépôt n'a encore été générée funds.deposit.fundWallet=Alimenter votre portefeuille funds.deposit.withdrawFromWallet=Transférer des fonds depuis le portefeuille diff --git a/core/src/main/resources/i18n/displayStrings_it.properties b/core/src/main/resources/i18n/displayStrings_it.properties index bcf28639..e38c5f74 100644 --- a/core/src/main/resources/i18n/displayStrings_it.properties +++ b/core/src/main/resources/i18n/displayStrings_it.properties @@ -840,7 +840,7 @@ funds.tab.transactions=Transazioni funds.deposit.unused=Non usato funds.deposit.usedInTx=Utilizzato in {0} transazioni -funds.deposit.fundBisqWallet=Finanzia portafoglio Bisq +funds.deposit.fundHavenoWallet=Finanzia portafoglio Bisq funds.deposit.noAddresses=Non sono stati ancora generati indirizzi di deposito funds.deposit.fundWallet=Finanzia il tuo portafoglio funds.deposit.withdrawFromWallet=Invia fondi dal portafoglio diff --git a/core/src/main/resources/i18n/displayStrings_ja.properties b/core/src/main/resources/i18n/displayStrings_ja.properties index ade6e596..150e2817 100644 --- a/core/src/main/resources/i18n/displayStrings_ja.properties +++ b/core/src/main/resources/i18n/displayStrings_ja.properties @@ -840,7 +840,7 @@ funds.tab.transactions=トランザクション funds.deposit.unused=未使用 funds.deposit.usedInTx={0}トランザクションで使われています -funds.deposit.fundBisqWallet=Bisqウォレットに入金 +funds.deposit.fundHavenoWallet=Bisqウォレットに入金 funds.deposit.noAddresses=デポジットアドレスはまだ生成されていません funds.deposit.fundWallet=あなたのウォレットに入金 funds.deposit.withdrawFromWallet=ウォレットから資金を送金 diff --git a/core/src/main/resources/i18n/displayStrings_pt-br.properties b/core/src/main/resources/i18n/displayStrings_pt-br.properties index dc7b6d66..0d235994 100644 --- a/core/src/main/resources/i18n/displayStrings_pt-br.properties +++ b/core/src/main/resources/i18n/displayStrings_pt-br.properties @@ -840,7 +840,7 @@ funds.tab.transactions=Transações funds.deposit.unused=Não utilizado funds.deposit.usedInTx=Utilizado em {0} transação(ões) -funds.deposit.fundBisqWallet=Financiar carteira Bisq +funds.deposit.fundHavenoWallet=Financiar carteira Bisq funds.deposit.noAddresses=Nenhum endereço de depósito foi gerado ainda funds.deposit.fundWallet=Financiar sua carteira funds.deposit.withdrawFromWallet=Enviar fundos da carteira diff --git a/core/src/main/resources/i18n/displayStrings_pt.properties b/core/src/main/resources/i18n/displayStrings_pt.properties index 186f0fdf..a997b208 100644 --- a/core/src/main/resources/i18n/displayStrings_pt.properties +++ b/core/src/main/resources/i18n/displayStrings_pt.properties @@ -840,7 +840,7 @@ funds.tab.transactions=Transações funds.deposit.unused=Não utilizado funds.deposit.usedInTx=Utilizado em {0} transação(s) -funds.deposit.fundBisqWallet=Financiar carteira Bisq +funds.deposit.fundHavenoWallet=Financiar carteira Bisq funds.deposit.noAddresses=Ainda não foi gerado um endereço de depósito funds.deposit.fundWallet=Financiar sua carteira funds.deposit.withdrawFromWallet=Enviar fundos da carteira diff --git a/core/src/main/resources/i18n/displayStrings_ru.properties b/core/src/main/resources/i18n/displayStrings_ru.properties index 1b0df870..20e0c55c 100644 --- a/core/src/main/resources/i18n/displayStrings_ru.properties +++ b/core/src/main/resources/i18n/displayStrings_ru.properties @@ -840,7 +840,7 @@ funds.tab.transactions=Транзакции funds.deposit.unused=Не использован funds.deposit.usedInTx=Использован в {0} транзакциях -funds.deposit.fundBisqWallet=Пополнить кошелёк Bisq +funds.deposit.fundHavenoWallet=Пополнить кошелёк Bisq funds.deposit.noAddresses=Адреса для перевода средств ещё не созданы funds.deposit.fundWallet=Пополнить кошелёк funds.deposit.withdrawFromWallet=Отправить средства из кошелька diff --git a/core/src/main/resources/i18n/displayStrings_th.properties b/core/src/main/resources/i18n/displayStrings_th.properties index 3742a526..56c62c40 100644 --- a/core/src/main/resources/i18n/displayStrings_th.properties +++ b/core/src/main/resources/i18n/displayStrings_th.properties @@ -840,7 +840,7 @@ funds.tab.transactions=การทำธุรกรรม funds.deposit.unused=ไม่ได้ใช้ funds.deposit.usedInTx=ใช้ใน {0} ธุรกรรม(ต่าง ๆ ) -funds.deposit.fundBisqWallet=เติมเงิน Bisq wallet +funds.deposit.fundHavenoWallet=เติมเงิน Bisq wallet funds.deposit.noAddresses=ยังไม่มีการสร้างที่อยู่ของเงินฝาก funds.deposit.fundWallet=เติมเงินใน wallet ของคุณ funds.deposit.withdrawFromWallet=ส่งเงินทุนจากกระเป๋าสตางค์ของคุณ diff --git a/core/src/main/resources/i18n/displayStrings_vi.properties b/core/src/main/resources/i18n/displayStrings_vi.properties index 85d6be85..cbd22977 100644 --- a/core/src/main/resources/i18n/displayStrings_vi.properties +++ b/core/src/main/resources/i18n/displayStrings_vi.properties @@ -840,7 +840,7 @@ funds.tab.transactions=Giao dịch funds.deposit.unused=Không sử dụng funds.deposit.usedInTx=Sử dụng trong {0} giao dịch -funds.deposit.fundBisqWallet=Nộp tiền ví Bisq +funds.deposit.fundHavenoWallet=Nộp tiền ví Bisq funds.deposit.noAddresses=Chưa có địa chỉ nộp tiền được tạo funds.deposit.fundWallet=Nộp tiền cho ví của bạn funds.deposit.withdrawFromWallet=Chuyển tiền từ ví diff --git a/core/src/main/resources/i18n/displayStrings_zh-hans.properties b/core/src/main/resources/i18n/displayStrings_zh-hans.properties index 26ea5d9a..d3fb4589 100644 --- a/core/src/main/resources/i18n/displayStrings_zh-hans.properties +++ b/core/src/main/resources/i18n/displayStrings_zh-hans.properties @@ -840,7 +840,7 @@ funds.tab.transactions=交易记录 funds.deposit.unused=尚未使用 funds.deposit.usedInTx=用在 {0} 交易 -funds.deposit.fundBisqWallet=充值 Bisq 钱包 +funds.deposit.fundHavenoWallet=充值 Bisq 钱包 funds.deposit.noAddresses=尚未生成存款地址 funds.deposit.fundWallet=充值您的钱包 funds.deposit.withdrawFromWallet=从钱包转出资金 diff --git a/core/src/main/resources/i18n/displayStrings_zh-hant.properties b/core/src/main/resources/i18n/displayStrings_zh-hant.properties index c8e89d15..adfa8b13 100644 --- a/core/src/main/resources/i18n/displayStrings_zh-hant.properties +++ b/core/src/main/resources/i18n/displayStrings_zh-hant.properties @@ -840,7 +840,7 @@ funds.tab.transactions=交易記錄 funds.deposit.unused=尚未使用 funds.deposit.usedInTx=用在 {0} 交易 -funds.deposit.fundBisqWallet=充值 Bisq 錢包 +funds.deposit.fundHavenoWallet=充值 Bisq 錢包 funds.deposit.noAddresses=尚未生成存款地址 funds.deposit.fundWallet=充值您的錢包 funds.deposit.withdrawFromWallet=從錢包轉出資金 diff --git a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java index cf47c712..7bb019cb 100644 --- a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java +++ b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java @@ -182,6 +182,7 @@ public class AccountAgeWitnessServiceTest { 0, true, true, + true, buyerPubKeyRing, now - 1, now - 1, @@ -209,6 +210,7 @@ public class AccountAgeWitnessServiceTest { "summary", null, null, + null, 100000, 0, null, diff --git a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java index 63b4e406..afeb246c 100644 --- a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java +++ b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java @@ -57,6 +57,7 @@ public class OpenOfferManagerTest { null, null, null, + null, offerBookService, null, null, @@ -104,6 +105,7 @@ public class OpenOfferManagerTest { null, null, null, + null, offerBookService, null, null, @@ -144,6 +146,7 @@ public class OpenOfferManagerTest { null, null, null, + null, offerBookService, null, null, diff --git a/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java b/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java index d2577194..7b458472 100644 --- a/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java +++ b/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java @@ -36,7 +36,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class BisqAppMain extends BisqExecutable { - public static final String DEFAULT_APP_NAME = "Bisq"; + public static final String DEFAULT_APP_NAME = "Haveno"; private BisqApp application; diff --git a/desktop/src/main/java/bisq/desktop/main/MainView.java b/desktop/src/main/java/bisq/desktop/main/MainView.java index 854e8af9..24d17ed0 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainView.java +++ b/desktop/src/main/java/bisq/desktop/main/MainView.java @@ -28,8 +28,6 @@ import bisq.desktop.components.AutoTooltipLabel; import bisq.desktop.components.AutoTooltipToggleButton; import bisq.desktop.components.BusyAnimation; import bisq.desktop.main.account.AccountView; -import bisq.desktop.main.dao.DaoView; -import bisq.desktop.main.funds.FundsView; import bisq.desktop.main.market.MarketView; import bisq.desktop.main.market.offerbook.OfferBookChartView; import bisq.desktop.main.offer.BuyOfferView; @@ -180,12 +178,12 @@ public class MainView extends InitializableView ToggleButton buyButton = new NavButton(BuyOfferView.class, Res.get("mainView.menu.buyBtc").toUpperCase()); ToggleButton sellButton = new NavButton(SellOfferView.class, Res.get("mainView.menu.sellBtc").toUpperCase()); ToggleButton portfolioButton = new NavButton(PortfolioView.class, Res.get("mainView.menu.portfolio").toUpperCase()); - ToggleButton fundsButton = new NavButton(FundsView.class, Res.get("mainView.menu.funds").toUpperCase()); +// ToggleButton fundsButton = new NavButton(FundsView.class, Res.get("mainView.menu.funds").toUpperCase()); ToggleButton supportButton = new NavButton(SupportView.class, Res.get("mainView.menu.support")); ToggleButton settingsButton = new NavButton(SettingsView.class, Res.get("mainView.menu.settings")); ToggleButton accountButton = new NavButton(AccountView.class, Res.get("mainView.menu.account")); - ToggleButton daoButton = new NavButton(DaoView.class, Res.get("mainView.menu.dao")); +// ToggleButton daoButton = new NavButton(DaoView.class, Res.get("mainView.menu.dao")); JFXBadge portfolioButtonWithBadge = new JFXBadge(portfolioButton); JFXBadge supportButtonWithBadge = new JFXBadge(supportButton); @@ -208,17 +206,17 @@ public class MainView extends InitializableView sellButton.fire(); } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT4, keyEvent)) { portfolioButton.fire(); - } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT5, keyEvent)) { - fundsButton.fire(); +// } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT5, keyEvent)) { +// fundsButton.fire(); } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT6, keyEvent)) { supportButton.fire(); } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT7, keyEvent)) { settingsButton.fire(); } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT8, keyEvent)) { accountButton.fire(); - } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT9, keyEvent)) { - if (daoButton.isVisible()) - daoButton.fire(); +// } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT9, keyEvent)) { +// if (daoButton.isVisible()) +// daoButton.fire(); } }); } @@ -315,14 +313,14 @@ public class MainView extends InitializableView }); HBox primaryNav = new HBox(marketButton, getNavigationSeparator(), buyButton, getNavigationSeparator(), - sellButton, getNavigationSeparator(), portfolioButtonWithBadge, getNavigationSeparator(), fundsButton); + sellButton, getNavigationSeparator(), portfolioButtonWithBadge, getNavigationSeparator()); primaryNav.setAlignment(Pos.CENTER_LEFT); primaryNav.getStyleClass().add("nav-primary"); HBox.setHgrow(primaryNav, Priority.SOMETIMES); HBox secondaryNav = new HBox(supportButtonWithBadge, getNavigationSpacer(), settingsButtonWithBadge, - getNavigationSpacer(), accountButton, getNavigationSpacer(), daoButton); + getNavigationSpacer(), accountButton, getNavigationSpacer()); secondaryNav.getStyleClass().add("nav-secondary"); HBox.setHgrow(secondaryNav, Priority.SOMETIMES); @@ -364,7 +362,7 @@ public class MainView extends InitializableView baseApplicationContainer.setBottom(createFooter()); setupBadge(portfolioButtonWithBadge, model.getNumPendingTrades(), model.getShowPendingTradesNotification()); - setupBadge(supportButtonWithBadge, model.getNumOpenSupportTickets(), model.getShowOpenSupportTicketsNotification()); +// setupBadge(supportButtonWithBadge, model.getNumOpenSupportTickets(), model.getShowOpenSupportTicketsNotification()); setupBadge(settingsButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowSettingsUpdatesNotification()); navigation.addListener((viewPath, data) -> { diff --git a/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java b/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java index 764d073a..ae051a24 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java @@ -38,8 +38,8 @@ import bisq.core.dao.governance.bond.BondState; import bisq.core.dao.governance.bond.reputation.MyBondedReputation; import bisq.core.locale.Res; import bisq.core.user.Preferences; -import bisq.core.util.coin.BsqFormatter; import bisq.core.util.ParsingUtils; +import bisq.core.util.coin.BsqFormatter; import bisq.core.util.validation.HexStringValidator; import bisq.core.util.validation.IntegerValidator; diff --git a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java index 89d0acbf..d85ab457 100644 --- a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java +++ b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java @@ -24,18 +24,17 @@ import bisq.desktop.components.TitledGroupBg; import bisq.core.offer.availability.tasks.ProcessOfferAvailabilityResponse; import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest; import bisq.core.offer.placeoffer.tasks.AddToOfferBook; -import bisq.core.offer.placeoffer.tasks.CreateMakerFeeTx; import bisq.core.offer.placeoffer.tasks.ValidateOffer; import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.SetupDepositTxsListener; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; +import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse; -import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener; import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener; -import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesFinalDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; @@ -45,11 +44,12 @@ import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositT import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage; import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx; import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract; +import bisq.core.trade.protocol.tasks.maker.MakerCreateFeeTx; import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest; import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime; +import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; -import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage; @@ -58,19 +58,19 @@ import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx; import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics; import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx; +import bisq.core.trade.protocol.tasks.seller.SellerSignAndPublishPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx; import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx; -import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage; import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse; import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs; import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx; +import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx; import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse; +import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage; import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest; +import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener; import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract; import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; @@ -118,7 +118,7 @@ public class DebugView extends InitializableView { addGroup("PlaceOfferProtocol", FXCollections.observableArrayList(Arrays.asList( ValidateOffer.class, - CreateMakerFeeTx.class, + MakerCreateFeeTx.class, AddToOfferBook.class) )); @@ -127,7 +127,7 @@ public class DebugView extends InitializableView { FXCollections.observableArrayList(Arrays.asList( ApplyFilter.class, TakerVerifyMakerFeePayment.class, - CreateTakerFeeTx.class, + TakerCreateFeeTx.class, // TODO (woodser): rename to TakerCreateFeeTx SellerAsTakerCreatesDepositTxInputs.class, TakerSendInputsForDepositTxRequest.class, @@ -143,7 +143,8 @@ public class DebugView extends InitializableView { SellerProcessDelayedPayoutTxSignatureResponse.class, SellerSignsDelayedPayoutTx.class, SellerFinalizesDelayedPayoutTx.class, - SellerSendsDepositTxAndDelayedPayoutTxMessage.class, + //SellerSendsDepositTxAndDelayedPayoutTxMessage.class, + TakerProcessesMakerDepositTxMessage.class, SellerPublishesDepositTx.class, SellerPublishesTradeStatistics.class, @@ -153,8 +154,8 @@ public class DebugView extends InitializableView { ApplyFilter.class, TakerVerifyMakerFeePayment.class, - SellerSignAndFinalizePayoutTx.class, - SellerBroadcastPayoutTx.class, + SellerSignAndPublishPayoutTx.class, + //SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view? SellerSendPayoutTxPublishedMessage.class ) @@ -168,7 +169,7 @@ public class DebugView extends InitializableView { MakerSetsLockTime.class, MakerCreateAndSignContract.class, BuyerAsMakerCreatesAndSignsDepositTx.class, - BuyerSetupDepositTxListener.class, + MakerSetupDepositTxsListener.class, BuyerAsMakerSendsInputsForDepositTxResponse.class, BuyerProcessDelayedPayoutTxSignatureRequest.class, @@ -182,7 +183,7 @@ public class DebugView extends InitializableView { ApplyFilter.class, MakerVerifyTakerFeePayment.class, - BuyerSignPayoutTx.class, + BuyerCreateAndSignPayoutTx.class, BuyerSetupPayoutTxListener.class, BuyerSendCounterCurrencyTransferStartedMessage.class, @@ -195,7 +196,7 @@ public class DebugView extends InitializableView { FXCollections.observableArrayList(Arrays.asList( ApplyFilter.class, TakerVerifyMakerFeePayment.class, - CreateTakerFeeTx.class, + TakerCreateFeeTx.class, BuyerAsTakerCreatesDepositTxInputs.class, TakerSendInputsForDepositTxRequest.class, @@ -205,7 +206,7 @@ public class DebugView extends InitializableView { TakerVerifyAndSignContract.class, TakerPublishFeeTx.class, BuyerAsTakerSignsDepositTx.class, - BuyerSetupDepositTxListener.class, + TakerSetupDepositTxsListener.class, BuyerAsTakerSendsDepositTxMessage.class, BuyerProcessDelayedPayoutTxSignatureRequest.class, @@ -218,7 +219,7 @@ public class DebugView extends InitializableView { ApplyFilter.class, TakerVerifyMakerFeePayment.class, - BuyerSignPayoutTx.class, + BuyerCreateAndSignPayoutTx.class, BuyerSetupPayoutTxListener.class, BuyerSendCounterCurrencyTransferStartedMessage.class, @@ -234,8 +235,9 @@ public class DebugView extends InitializableView { MakerCreateAndSignContract.class, SellerAsMakerCreatesUnsignedDepositTx.class, SellerAsMakerSendsInputsForDepositTxResponse.class, + SetupDepositTxsListener.class, - SellerAsMakerProcessDepositTxMessage.class, + //SellerAsMakerProcessDepositTxMessage.class, MakerRemovesOpenOffer.class, SellerAsMakerFinalizesDepositTx.class, SellerCreatesDelayedPayoutTx.class, @@ -244,7 +246,7 @@ public class DebugView extends InitializableView { SellerProcessDelayedPayoutTxSignatureResponse.class, SellerSignsDelayedPayoutTx.class, SellerFinalizesDelayedPayoutTx.class, - SellerSendsDepositTxAndDelayedPayoutTxMessage.class, + //SellerSendsDepositTxAndDelayedPayoutTxMessage.class, SellerPublishesDepositTx.class, SellerPublishesTradeStatistics.class, @@ -254,8 +256,8 @@ public class DebugView extends InitializableView { ApplyFilter.class, MakerVerifyTakerFeePayment.class, - SellerSignAndFinalizePayoutTx.class, - SellerBroadcastPayoutTx.class, + SellerSignAndPublishPayoutTx.class, + //SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view? SellerSendPayoutTxPublishedMessage.class ) )); diff --git a/desktop/src/main/java/bisq/desktop/main/funds/deposit/DepositView.java b/desktop/src/main/java/bisq/desktop/main/funds/deposit/DepositView.java index cc2c4fed..08933403 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/deposit/DepositView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/deposit/DepositView.java @@ -136,7 +136,7 @@ public class DepositView extends ActivatableView { @Override public void initialize() { - paymentLabelString = Res.get("funds.deposit.fundBisqWallet"); + paymentLabelString = Res.get("funds.deposit.fundHavenoWallet"); addressColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.address"))); balanceColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.balanceWithCur", Res.getBaseCurrencyCode()))); confirmationsColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.confirmations"))); diff --git a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java index a2d80382..968daed4 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java @@ -17,20 +17,17 @@ package bisq.desktop.main.funds.locked; -import bisq.desktop.components.AutoTooltipLabel; import bisq.desktop.util.DisplayUtils; import bisq.core.btc.listeners.BalanceListener; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.WalletService; import bisq.core.locale.Res; import bisq.core.trade.Trade; import bisq.core.util.coin.CoinFormatter; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; import javafx.scene.control.Label; @@ -67,22 +64,24 @@ class LockedListItem { this.btcWalletService = btcWalletService; this.formatter = formatter; - if (trade.getDepositTx() != null && !trade.getDepositTx().getOutputs().isEmpty()) { - address = WalletService.getAddressFromOutput(trade.getDepositTx().getOutput(0)); - addressString = address != null ? address.toString() : ""; - } else { - address = null; - addressString = ""; - } - balanceLabel = new AutoTooltipLabel(); - balanceListener = new BalanceListener(address) { - @Override - public void onBalanceChanged(Coin balance, Transaction tx) { - updateBalance(); - } - }; - btcWalletService.addBalanceListener(balanceListener); - updateBalance(); + throw new RuntimeException("Cannot listen to multisig deposits in xmr without exchanging multisig info"); + +// if (trade.getDepositTx() != null && !trade.getDepositTx().getOutputs().isEmpty()) { +// address = WalletService.getAddressFromOutput(trade.getDepositTx().getOutput(0)); +// addressString = address != null ? address.toString() : ""; +// } else { +// address = null; +// addressString = ""; +// } +// balanceLabel = new AutoTooltipLabel(); +// balanceListener = new BalanceListener(address) { +// @Override +// public void onBalanceChanged(Coin balance, Transaction tx) { +// updateBalance(); +// } +// }; +// btcWalletService.addBalanceListener(balanceListener); +// updateBalance(); } LockedListItem() { diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactions.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactions.java index 06062995..a1005d6c 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactions.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactions.java @@ -17,25 +17,27 @@ package bisq.desktop.main.funds.transactions; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.trade.Tradable; -import org.bitcoinj.core.Transaction; - import java.util.List; import java.util.Set; import java.util.stream.Collectors; + + +import monero.wallet.model.MoneroTxWallet; + class DisplayedTransactions extends ObservableListDecorator { - private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final TradableRepository tradableRepository; private final TransactionListItemFactory transactionListItemFactory; private final TransactionAwareTradableFactory transactionAwareTradableFactory; - DisplayedTransactions(BtcWalletService btcWalletService, TradableRepository tradableRepository, + DisplayedTransactions(XmrWalletService xmrWalletService, TradableRepository tradableRepository, TransactionListItemFactory transactionListItemFactory, TransactionAwareTradableFactory transactionAwareTradableFactory) { - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.tradableRepository = tradableRepository; this.transactionListItemFactory = transactionListItemFactory; this.transactionAwareTradableFactory = transactionAwareTradableFactory; @@ -49,13 +51,13 @@ class DisplayedTransactions extends ObservableListDecorator getTransactionListItems() { - Set transactions = btcWalletService.getTransactions(false); + List transactions = xmrWalletService.getTransactions(false); return transactions.stream() .map(this::convertTransactionToListItem) .collect(Collectors.toList()); } - private TransactionsListItem convertTransactionToListItem(Transaction transaction) { + private TransactionsListItem convertTransactionToListItem(MoneroTxWallet transaction) { Set tradables = tradableRepository.getAll(); TransactionAwareTradable maybeTradable = tradables.stream() diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsFactory.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsFactory.java index 049b8efe..5abd6b80 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsFactory.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsFactory.java @@ -17,31 +17,31 @@ package bisq.desktop.main.funds.transactions; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import javax.inject.Inject; import javax.inject.Singleton; @Singleton public class DisplayedTransactionsFactory { - private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final TradableRepository tradableRepository; private final TransactionListItemFactory transactionListItemFactory; private final TransactionAwareTradableFactory transactionAwareTradableFactory; @Inject - DisplayedTransactionsFactory(BtcWalletService btcWalletService, + DisplayedTransactionsFactory(XmrWalletService xmrWalletService, TradableRepository tradableRepository, TransactionListItemFactory transactionListItemFactory, TransactionAwareTradableFactory transactionAwareTradableFactory) { - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.tradableRepository = tradableRepository; this.transactionListItemFactory = transactionListItemFactory; this.transactionAwareTradableFactory = transactionAwareTradableFactory; } DisplayedTransactions create() { - return new DisplayedTransactions(btcWalletService, tradableRepository, transactionListItemFactory, + return new DisplayedTransactions(xmrWalletService, tradableRepository, transactionListItemFactory, transactionAwareTradableFactory); } } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DummyTransactionAwareTradable.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DummyTransactionAwareTradable.java index 74b24037..dbc7ae31 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DummyTransactionAwareTradable.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DummyTransactionAwareTradable.java @@ -19,7 +19,9 @@ package bisq.desktop.main.funds.transactions; import bisq.core.trade.Tradable; -import org.bitcoinj.core.Transaction; + + +import monero.wallet.model.MoneroTxWallet; class DummyTransactionAwareTradable implements TransactionAwareTradable { private final Tradable delegate; @@ -29,7 +31,7 @@ class DummyTransactionAwareTradable implements TransactionAwareTradable { } @Override - public boolean isRelatedToTransaction(Transaction transaction) { + public boolean isRelatedToTransaction(MoneroTxWallet transaction) { return false; } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareOpenOffer.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareOpenOffer.java index fd59a7dc..88173d02 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareOpenOffer.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareOpenOffer.java @@ -21,7 +21,9 @@ import bisq.core.offer.Offer; import bisq.core.offer.OpenOffer; import bisq.core.trade.Tradable; -import org.bitcoinj.core.Transaction; + + +import monero.wallet.model.MoneroTxWallet; class TransactionAwareOpenOffer implements TransactionAwareTradable { private final OpenOffer delegate; @@ -30,11 +32,11 @@ class TransactionAwareOpenOffer implements TransactionAwareTradable { this.delegate = delegate; } - public boolean isRelatedToTransaction(Transaction transaction) { + public boolean isRelatedToTransaction(MoneroTxWallet transaction) { Offer offer = delegate.getOffer(); String paymentTxId = offer.getOfferFeePaymentTxId(); - String txId = transaction.getTxId().toString(); + String txId = transaction.getHash(); return paymentTxId.equals(txId); } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradable.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradable.java index 199e0159..543e7d41 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradable.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradable.java @@ -19,10 +19,12 @@ package bisq.desktop.main.funds.transactions; import bisq.core.trade.Tradable; -import org.bitcoinj.core.Transaction; + + +import monero.wallet.model.MoneroTxWallet; interface TransactionAwareTradable { - boolean isRelatedToTransaction(Transaction transaction); + boolean isRelatedToTransaction(MoneroTxWallet transaction); Tradable asTradable(); } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java index 86b7699e..505682e4 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java @@ -17,7 +17,7 @@ package bisq.desktop.main.funds.transactions; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.OpenOffer; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.refund.RefundManager; @@ -34,17 +34,17 @@ import javax.inject.Singleton; public class TransactionAwareTradableFactory { private final ArbitrationManager arbitrationManager; private final RefundManager refundManager; - private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final PubKeyRing pubKeyRing; @Inject TransactionAwareTradableFactory(ArbitrationManager arbitrationManager, RefundManager refundManager, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, PubKeyRing pubKeyRing) { this.arbitrationManager = arbitrationManager; this.refundManager = refundManager; - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.pubKeyRing = pubKeyRing; } @@ -55,7 +55,7 @@ public class TransactionAwareTradableFactory { return new TransactionAwareTrade((Trade) delegate, arbitrationManager, refundManager, - btcWalletService, + xmrWalletService, pubKeyRing); } else { return new DummyTransactionAwareTradable(delegate); diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java index 9d0ab09e..72e61133 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java @@ -17,79 +17,81 @@ package bisq.desktop.main.funds.transactions; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.Offer; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.refund.RefundManager; -import bisq.core.trade.Contract; import bisq.core.trade.Tradable; import bisq.core.trade.Trade; import bisq.common.crypto.PubKeyRing; -import org.bitcoinj.core.Address; -import org.bitcoinj.core.Sha256Hash; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionOutput; - import javafx.collections.ObservableList; import java.util.Optional; import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.wallet.model.MoneroTxWallet; + @Slf4j class TransactionAwareTrade implements TransactionAwareTradable { private final Trade trade; private final ArbitrationManager arbitrationManager; private final RefundManager refundManager; - private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final PubKeyRing pubKeyRing; TransactionAwareTrade(Trade trade, ArbitrationManager arbitrationManager, RefundManager refundManager, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, PubKeyRing pubKeyRing) { this.trade = trade; this.arbitrationManager = arbitrationManager; this.refundManager = refundManager; - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.pubKeyRing = pubKeyRing; } @Override - public boolean isRelatedToTransaction(Transaction transaction) { - Sha256Hash hash = transaction.getTxId(); - String txId = hash.toString(); + public boolean isRelatedToTransaction(MoneroTxWallet transaction) { + String txId = transaction.getHash(); boolean isTakerOfferFeeTx = txId.equals(trade.getTakerFeeTxId()); boolean isOfferFeeTx = isOfferFeeTx(txId); - boolean isDepositTx = isDepositTx(hash); - boolean isPayoutTx = isPayoutTx(hash); + boolean isMakerDepositTx = isMakerDepositTx(txId); + boolean isTakerDepositTx = isTakerDepositTx(txId); + boolean isPayoutTx = isPayoutTx(txId); boolean isDisputedPayoutTx = isDisputedPayoutTx(txId); - boolean isDelayedPayoutTx = transaction.getLockTime() != 0 && isDelayedPayoutTx(txId); - boolean isRefundPayoutTx = isRefundPayoutTx(txId); - return isTakerOfferFeeTx || isOfferFeeTx || isDepositTx || isPayoutTx || - isDisputedPayoutTx || isDelayedPayoutTx || isRefundPayoutTx; + return isTakerOfferFeeTx || isOfferFeeTx || isMakerDepositTx || isTakerDepositTx || + isPayoutTx || isDisputedPayoutTx; } - private boolean isPayoutTx(Sha256Hash txId) { - return Optional.ofNullable(trade.getPayoutTx()) - .map(Transaction::getTxId) - .map(hash -> hash.equals(txId)) - .orElse(false); + private boolean isPayoutTx(String txId) { + return Optional.ofNullable(trade.getPayoutTx()) + .map(MoneroTxWallet::getHash) + .map(hash -> hash.equals(txId)) + .orElse(false); } - private boolean isDepositTx(Sha256Hash txId) { - return Optional.ofNullable(trade.getDepositTx()) - .map(Transaction::getTxId) - .map(hash -> hash.equals(txId)) - .orElse(false); + private boolean isMakerDepositTx(String txId) { + return Optional.ofNullable(trade.getMakerDepositTx()) + .map(MoneroTxWallet::getHash) + .map(hash -> hash.equals(txId)) + .orElse(false); + } + + private boolean isTakerDepositTx(String txId) { + return Optional.ofNullable(trade.getTakerDepositTx()) + .map(MoneroTxWallet::getHash) + .map(hash -> hash.equals(txId)) + .orElse(false); } private boolean isOfferFeeTx(String txId) { @@ -117,59 +119,59 @@ class TransactionAwareTrade implements TransactionAwareTradable { }); } - boolean isDelayedPayoutTx(String txId) { - Transaction transaction = btcWalletService.getTransaction(txId); - if (transaction == null) - return false; - - if (transaction.getLockTime() == 0) - return false; - - if (transaction.getInputs() == null) - return false; - - return transaction.getInputs().stream() - .anyMatch(input -> { - TransactionOutput connectedOutput = input.getConnectedOutput(); - if (connectedOutput == null) { - return false; - } - Transaction parentTransaction = connectedOutput.getParentTransaction(); - if (parentTransaction == null) { - return false; - } - return isDepositTx(parentTransaction.getTxId()); - }); - } - - private boolean isRefundPayoutTx(String txId) { - String tradeId = trade.getId(); - ObservableList disputes = refundManager.getDisputesAsObservableList(); - - boolean isAnyDisputeRelatedToThis = refundManager.getDisputedTradeIds().contains(tradeId); - - if (isAnyDisputeRelatedToThis) { - Transaction tx = btcWalletService.getTransaction(txId); - if (tx != null) { - for (TransactionOutput txo : tx.getOutputs()) { - if (btcWalletService.isTransactionOutputMine(txo)) { - try { - Address receiverAddress = txo.getScriptPubKey().getToAddress(btcWalletService.getParams()); - Contract contract = checkNotNull(trade.getContract()); - String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ? - contract.getBuyerPayoutAddressString() : - contract.getSellerPayoutAddressString(); - if (receiverAddress != null && myPayoutAddressString.equals(receiverAddress.toString())) { - return true; - } - } catch (RuntimeException ignore) { - } - } - } - } - } - return false; - } +// boolean isDelayedPayoutTx(String txId) { +// Transaction transaction = btcWalletService.getTransaction(txId); +// if (transaction == null) +// return false; +// +// if (transaction.getLockTime() == 0) +// return false; +// +// if (transaction.getInputs() == null) +// return false; +// +// return transaction.getInputs().stream() +// .anyMatch(input -> { +// TransactionOutput connectedOutput = input.getConnectedOutput(); +// if (connectedOutput == null) { +// return false; +// } +// Transaction parentTransaction = connectedOutput.getParentTransaction(); +// if (parentTransaction == null) { +// return false; +// } +// return isDepositTx(parentTransaction.getTxId()); +// }); +// } +// +// private boolean isRefundPayoutTx(String txId) { +// String tradeId = trade.getId(); +// ObservableList disputes = refundManager.getDisputesAsObservableList(); +// +// boolean isAnyDisputeRelatedToThis = refundManager.getDisputedTradeIds().contains(tradeId); +// +// if (isAnyDisputeRelatedToThis) { +// Transaction tx = btcWalletService.getTransaction(txId); +// if (tx != null) { +// for (TransactionOutput txo : tx.getOutputs()) { +// if (btcWalletService.isTransactionOutputMine(txo)) { +// try { +// Address receiverAddress = txo.getScriptPubKey().getToAddress(btcWalletService.getParams()); +// Contract contract = checkNotNull(trade.getContract()); +// String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ? +// contract.getBuyerPayoutAddressString() : +// contract.getSellerPayoutAddressString(); +// if (receiverAddress != null && myPayoutAddressString.equals(receiverAddress.toString())) { +// return true; +// } +// } catch (RuntimeException ignore) { +// } +// } +// } +// } +// } +// return false; +// } @Override public Tradable asTradable() { diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionListItemFactory.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionListItemFactory.java index 55234b48..a648ab04 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionListItemFactory.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionListItemFactory.java @@ -18,14 +18,12 @@ package bisq.desktop.main.funds.transactions; import bisq.core.btc.wallet.BsqWalletService; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoFacade; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; -import org.bitcoinj.core.Transaction; - import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; @@ -33,30 +31,34 @@ import javax.inject.Singleton; import javax.annotation.Nullable; + +import monero.wallet.model.MoneroTxWallet; + + @Singleton public class TransactionListItemFactory { - private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final BsqWalletService bsqWalletService; private final DaoFacade daoFacade; private final CoinFormatter formatter; private final Preferences preferences; @Inject - TransactionListItemFactory(BtcWalletService btcWalletService, + TransactionListItemFactory(XmrWalletService xmrWalletService, BsqWalletService bsqWalletService, DaoFacade daoFacade, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, Preferences preferences) { - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.bsqWalletService = bsqWalletService; this.daoFacade = daoFacade; this.formatter = formatter; this.preferences = preferences; } - TransactionsListItem create(Transaction transaction, @Nullable TransactionAwareTradable tradable) { + TransactionsListItem create(MoneroTxWallet transaction, @Nullable TransactionAwareTradable tradable) { return new TransactionsListItem(transaction, - btcWalletService, + xmrWalletService, bsqWalletService, tradable, daoFacade, diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java index f977c80f..009a20ac 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java @@ -18,34 +18,21 @@ package bisq.desktop.main.funds.transactions; import bisq.desktop.components.indicator.TxConfidenceIndicator; -import bisq.desktop.util.DisplayUtils; -import bisq.desktop.util.GUIUtil; import bisq.core.btc.listeners.TxConfidenceListener; import bisq.core.btc.wallet.BsqWalletService; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.WalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoFacade; -import bisq.core.dao.state.model.blockchain.TxType; -import bisq.core.locale.Res; -import bisq.core.offer.Offer; -import bisq.core.offer.OpenOffer; import bisq.core.trade.Tradable; -import bisq.core.trade.Trade; import bisq.core.util.coin.CoinFormatter; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Sha256Hash; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionConfidence; -import org.bitcoinj.core.TransactionOutput; import com.google.common.base.Suppliers; import javafx.scene.control.Tooltip; import java.util.Date; -import java.util.Optional; import java.util.function.Supplier; import lombok.Getter; @@ -53,9 +40,13 @@ import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; + + +import monero.wallet.model.MoneroTxWallet; + @Slf4j class TransactionsListItem { - private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final CoinFormatter formatter; private String dateString; private final Date date; @@ -88,218 +79,220 @@ class TransactionsListItem { // used at exportCSV TransactionsListItem() { date = null; - btcWalletService = null; + xmrWalletService = null; txId = null; formatter = null; isDustAttackTx = false; lazyFieldsSupplier = null; } - TransactionsListItem(Transaction transaction, - BtcWalletService btcWalletService, + TransactionsListItem(MoneroTxWallet transaction, + XmrWalletService xmrWalletService, BsqWalletService bsqWalletService, TransactionAwareTradable transactionAwareTradable, DaoFacade daoFacade, CoinFormatter formatter, long ignoreDustThreshold) { - this.btcWalletService = btcWalletService; - this.formatter = formatter; - this.memo = transaction.getMemo(); - - txId = transaction.getTxId().toString(); - - Optional optionalTradable = Optional.ofNullable(transactionAwareTradable) - .map(TransactionAwareTradable::asTradable); - - Coin valueSentToMe = btcWalletService.getValueSentToMeForTransaction(transaction); - Coin valueSentFromMe = btcWalletService.getValueSentFromMeForTransaction(transaction); - - // TODO check and refactor - boolean txFeeForBsqPayment = false; - boolean withdrawalFromBSQWallet = false; - if (valueSentToMe.isZero()) { - amountAsCoin = valueSentFromMe.multiply(-1); - for (TransactionOutput output : transaction.getOutputs()) { - if (!btcWalletService.isTransactionOutputMine(output)) { - received = false; - if (WalletService.isOutputScriptConvertibleToAddress(output)) { - addressString = WalletService.getAddressStringFromOutput(output); - if (bsqWalletService.isTransactionOutputMine(output)) { - txFeeForBsqPayment = true; - } else { - direction = Res.get("funds.tx.direction.sentTo"); - } - break; - } - } - } - } else if (valueSentFromMe.isZero()) { - amountAsCoin = valueSentToMe; - direction = Res.get("funds.tx.direction.receivedWith"); - received = true; - for (TransactionOutput output : transaction.getOutputs()) { - if (btcWalletService.isTransactionOutputMine(output) && - WalletService.isOutputScriptConvertibleToAddress(output)) { - addressString = WalletService.getAddressStringFromOutput(output); - break; - } - } - } else { - amountAsCoin = valueSentToMe.subtract(valueSentFromMe); - boolean outgoing = false; - for (TransactionOutput output : transaction.getOutputs()) { - if (!btcWalletService.isTransactionOutputMine(output)) { - if (WalletService.isOutputScriptConvertibleToAddress(output)) { - addressString = WalletService.getAddressStringFromOutput(output); - if (bsqWalletService.isTransactionOutputMine(output)) { - outgoing = false; - txFeeForBsqPayment = true; - - Optional txTypeOptional = daoFacade.getOptionalTxType(txId); - if (txTypeOptional.isPresent()) { - if (txTypeOptional.get().equals(TxType.COMPENSATION_REQUEST)) - details = Res.get("funds.tx.compensationRequestTxFee"); - else if (txTypeOptional.get().equals(TxType.REIMBURSEMENT_REQUEST)) - details = Res.get("funds.tx.reimbursementRequestTxFee"); - else - details = Res.get("funds.tx.daoTxFee"); - } - } else { - outgoing = true; - } - break; - } - } else { - addressString = WalletService.getAddressStringFromOutput(output); - outgoing = (valueSentToMe.getValue() < valueSentFromMe.getValue()); - if (!outgoing) { - direction = Res.get("funds.tx.direction.receivedWith"); - received = true; - withdrawalFromBSQWallet = true; - } - } - } - - if (outgoing) { - direction = Res.get("funds.tx.direction.sentTo"); - received = false; - } - } - - if (txFeeForBsqPayment) { - // direction = Res.get("funds.tx.txFeePaymentForBsqTx"); - direction = Res.get("funds.tx.direction.sentTo"); - //addressString = ""; - } - - if (optionalTradable.isPresent()) { - tradable = optionalTradable.get(); - detailsAvailable = true; - String tradeId = tradable.getShortId(); - if (tradable instanceof OpenOffer) { - details = Res.get("funds.tx.createOfferFee", tradeId); - } else if (tradable instanceof Trade) { - Trade trade = (Trade) tradable; - TransactionAwareTrade transactionAwareTrade = (TransactionAwareTrade) transactionAwareTradable; - if (trade.getTakerFeeTxId() != null && trade.getTakerFeeTxId().equals(txId)) { - details = Res.get("funds.tx.takeOfferFee", tradeId); - } else { - Offer offer = trade.getOffer(); - String offerFeePaymentTxID = offer.getOfferFeePaymentTxId(); - if (offerFeePaymentTxID != null && offerFeePaymentTxID.equals(txId)) { - details = Res.get("funds.tx.createOfferFee", tradeId); - } else if (trade.getDepositTx() != null && - trade.getDepositTx().getTxId().equals(Sha256Hash.wrap(txId))) { - details = Res.get("funds.tx.multiSigDeposit", tradeId); - } else if (trade.getPayoutTx() != null && - trade.getPayoutTx().getTxId().equals(Sha256Hash.wrap(txId))) { - details = Res.get("funds.tx.multiSigPayout", tradeId); - - if (amountAsCoin.isZero()) { - initialTxConfidenceVisibility = false; - } - } else { - Trade.DisputeState disputeState = trade.getDisputeState(); - if (disputeState == Trade.DisputeState.DISPUTE_CLOSED) { - if (valueSentToMe.isPositive()) { - details = Res.get("funds.tx.disputePayout", tradeId); - } else { - details = Res.get("funds.tx.disputeLost", tradeId); - initialTxConfidenceVisibility = false; - } - } else if (disputeState == Trade.DisputeState.REFUND_REQUEST_CLOSED || - disputeState == Trade.DisputeState.REFUND_REQUESTED || - disputeState == Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER) { - if (valueSentToMe.isPositive()) { - details = Res.get("funds.tx.refund", tradeId); - } else { - // We have spent the deposit tx outputs to the Bisq donation address to enable - // the refund process (refund agent -> reimbursement). As the funds have left our wallet - // already when funding the deposit tx we show 0 BTC as amount. - // Confirmation is not known from the BitcoinJ side (not 100% clear why) as no funds - // left our wallet nor we received funds. So we set indicator invisible. - amountAsCoin = Coin.ZERO; - details = Res.get("funds.tx.collateralForRefund", tradeId); - initialTxConfidenceVisibility = false; - } - } else { - if (transactionAwareTrade.isDelayedPayoutTx(txId)) { - details = Res.get("funds.tx.timeLockedPayoutTx", tradeId); - initialTxConfidenceVisibility = false; - } else { - details = Res.get("funds.tx.unknown", tradeId); - } - } - } - } - } - } else { - if (amountAsCoin.isZero()) { - details = Res.get("funds.tx.noFundsFromDispute"); - initialTxConfidenceVisibility = false; - } else if (withdrawalFromBSQWallet) { - details = Res.get("funds.tx.withdrawnFromBSQWallet"); - } else if (!txFeeForBsqPayment) { - details = received ? Res.get("funds.tx.receivedFunds") : Res.get("funds.tx.withdrawnFromWallet"); - } else if (details.isEmpty()) { - details = Res.get("funds.tx.txFeePaymentForBsqTx"); - } - } - // Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime() - date = transaction.getIncludedInBestChainAt() != null ? transaction.getIncludedInBestChainAt() : transaction.getUpdateTime(); - dateString = DisplayUtils.formatDateTime(date); - - isDustAttackTx = received && valueSentToMe.value < ignoreDustThreshold; - if (isDustAttackTx) { - details = Res.get("funds.tx.dustAttackTx"); - } - - // confidence - lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{ - txConfidenceIndicator = new TxConfidenceIndicator(); - txConfidenceIndicator.setId("funds-confidence"); - tooltip = new Tooltip(Res.get("shared.notUsedYet")); - txConfidenceIndicator.setProgress(0); - txConfidenceIndicator.setTooltip(tooltip); - txConfidenceIndicator.setVisible(initialTxConfidenceVisibility); - - TransactionConfidence confidence = transaction.getConfidence(); - GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator); - confirmations = confidence.getDepthInBlocks(); - }}); - - txConfidenceListener = new TxConfidenceListener(txId) { - @Override - public void onTransactionConfidenceChanged(TransactionConfidence confidence) { - GUIUtil.updateConfidence(confidence, lazy().tooltip, lazy().txConfidenceIndicator); - confirmations = confidence.getDepthInBlocks(); - } - }; - btcWalletService.addTxConfidenceListener(txConfidenceListener); + throw new RuntimeException("TransactionsListItem needs updated to use XMR wallet"); +// this.btcWalletService = btcWalletService; +// this.formatter = formatter; +// this.memo = transaction.getMemo(); +// +// txId = transaction.getTxId().toString(); +// +// Optional optionalTradable = Optional.ofNullable(transactionAwareTradable) +// .map(TransactionAwareTradable::asTradable); +// +// Coin valueSentToMe = btcWalletService.getValueSentToMeForTransaction(transaction); +// Coin valueSentFromMe = btcWalletService.getValueSentFromMeForTransaction(transaction); +// +// // TODO check and refactor +// boolean txFeeForBsqPayment = false; +// boolean withdrawalFromBSQWallet = false; +// if (valueSentToMe.isZero()) { +// amountAsCoin = valueSentFromMe.multiply(-1); +// for (TransactionOutput output : transaction.getOutputs()) { +// if (!btcWalletService.isTransactionOutputMine(output)) { +// received = false; +// if (WalletService.isOutputScriptConvertibleToAddress(output)) { +// addressString = WalletService.getAddressStringFromOutput(output); +// if (bsqWalletService.isTransactionOutputMine(output)) { +// txFeeForBsqPayment = true; +// } else { +// direction = Res.get("funds.tx.direction.sentTo"); +// } +// break; +// } +// } +// } +// } else if (valueSentFromMe.isZero()) { +// amountAsCoin = valueSentToMe; +// direction = Res.get("funds.tx.direction.receivedWith"); +// received = true; +// for (TransactionOutput output : transaction.getOutputs()) { +// if (btcWalletService.isTransactionOutputMine(output) && +// WalletService.isOutputScriptConvertibleToAddress(output)) { +// addressString = WalletService.getAddressStringFromOutput(output); +// break; +// } +// } +// } else { +// amountAsCoin = valueSentToMe.subtract(valueSentFromMe); +// boolean outgoing = false; +// for (TransactionOutput output : transaction.getOutputs()) { +// if (!btcWalletService.isTransactionOutputMine(output)) { +// if (WalletService.isOutputScriptConvertibleToAddress(output)) { +// addressString = WalletService.getAddressStringFromOutput(output); +// if (bsqWalletService.isTransactionOutputMine(output)) { +// outgoing = false; +// txFeeForBsqPayment = true; +// +// Optional txTypeOptional = daoFacade.getOptionalTxType(txId); +// if (txTypeOptional.isPresent()) { +// if (txTypeOptional.get().equals(TxType.COMPENSATION_REQUEST)) +// details = Res.get("funds.tx.compensationRequestTxFee"); +// else if (txTypeOptional.get().equals(TxType.REIMBURSEMENT_REQUEST)) +// details = Res.get("funds.tx.reimbursementRequestTxFee"); +// else +// details = Res.get("funds.tx.daoTxFee"); +// } +// } else { +// outgoing = true; +// } +// break; +// } +// } else { +// addressString = WalletService.getAddressStringFromOutput(output); +// outgoing = (valueSentToMe.getValue() < valueSentFromMe.getValue()); +// if (!outgoing) { +// direction = Res.get("funds.tx.direction.receivedWith"); +// received = true; +// withdrawalFromBSQWallet = true; +// } +// } +// } +// +// if (outgoing) { +// direction = Res.get("funds.tx.direction.sentTo"); +// received = false; +// } +// } +// +// if (txFeeForBsqPayment) { +// // direction = Res.get("funds.tx.txFeePaymentForBsqTx"); +// direction = Res.get("funds.tx.direction.sentTo"); +// //addressString = ""; +// } +// +// if (optionalTradable.isPresent()) { +// tradable = optionalTradable.get(); +// detailsAvailable = true; +// String tradeId = tradable.getShortId(); +// if (tradable instanceof OpenOffer) { +// details = Res.get("funds.tx.createOfferFee", tradeId); +// } else if (tradable instanceof Trade) { +// Trade trade = (Trade) tradable; +// TransactionAwareTrade transactionAwareTrade = (TransactionAwareTrade) transactionAwareTradable; +// if (trade.getTakerFeeTxId() != null && trade.getTakerFeeTxId().equals(txId)) { +// details = Res.get("funds.tx.takeOfferFee", tradeId); +// } else { +// Offer offer = trade.getOffer(); +// String offerFeePaymentTxID = offer.getOfferFeePaymentTxId(); +// if (offerFeePaymentTxID != null && offerFeePaymentTxID.equals(txId)) { +// details = Res.get("funds.tx.createOfferFee", tradeId); +// } else if (trade.getDepositTx() != null && +// trade.getDepositTx().getTxId().equals(Sha256Hash.wrap(txId))) { +// details = Res.get("funds.tx.multiSigDeposit", tradeId); +// } else if (trade.getPayoutTx() != null && +// trade.getPayoutTx().getTxId().equals(Sha256Hash.wrap(txId))) { +// details = Res.get("funds.tx.multiSigPayout", tradeId); +// +// if (amountAsCoin.isZero()) { +// initialTxConfidenceVisibility = false; +// } +// } else { +// Trade.DisputeState disputeState = trade.getDisputeState(); +// if (disputeState == Trade.DisputeState.DISPUTE_CLOSED) { +// if (valueSentToMe.isPositive()) { +// details = Res.get("funds.tx.disputePayout", tradeId); +// } else { +// details = Res.get("funds.tx.disputeLost", tradeId); +// initialTxConfidenceVisibility = false; +// } +// } else if (disputeState == Trade.DisputeState.REFUND_REQUEST_CLOSED || +// disputeState == Trade.DisputeState.REFUND_REQUESTED || +// disputeState == Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER) { +// if (valueSentToMe.isPositive()) { +// details = Res.get("funds.tx.refund", tradeId); +// } else { +// // We have spent the deposit tx outputs to the Bisq donation address to enable +// // the refund process (refund agent -> reimbursement). As the funds have left our wallet +// // already when funding the deposit tx we show 0 BTC as amount. +// // Confirmation is not known from the BitcoinJ side (not 100% clear why) as no funds +// // left our wallet nor we received funds. So we set indicator invisible. +// amountAsCoin = Coin.ZERO; +// details = Res.get("funds.tx.collateralForRefund", tradeId); +// initialTxConfidenceVisibility = false; +// } +// } else { +// if (transactionAwareTrade.isDelayedPayoutTx(txId)) { +// details = Res.get("funds.tx.timeLockedPayoutTx", tradeId); +// initialTxConfidenceVisibility = false; +// } else { +// details = Res.get("funds.tx.unknown", tradeId); +// } +// } +// } +// } +// } +// } else { +// if (amountAsCoin.isZero()) { +// details = Res.get("funds.tx.noFundsFromDispute"); +// initialTxConfidenceVisibility = false; +// } else if (withdrawalFromBSQWallet) { +// details = Res.get("funds.tx.withdrawnFromBSQWallet"); +// } else if (!txFeeForBsqPayment) { +// details = received ? Res.get("funds.tx.receivedFunds") : Res.get("funds.tx.withdrawnFromWallet"); +// } else if (details.isEmpty()) { +// details = Res.get("funds.tx.txFeePaymentForBsqTx"); +// } +// } +// // Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime() +// date = transaction.getIncludedInBestChainAt() != null ? transaction.getIncludedInBestChainAt() : transaction.getUpdateTime(); +// dateString = DisplayUtils.formatDateTime(date); +// +// isDustAttackTx = received && valueSentToMe.value < ignoreDustThreshold; +// if (isDustAttackTx) { +// details = Res.get("funds.tx.dustAttackTx"); +// } +// +// // confidence +// lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{ +// txConfidenceIndicator = new TxConfidenceIndicator(); +// txConfidenceIndicator.setId("funds-confidence"); +// tooltip = new Tooltip(Res.get("shared.notUsedYet")); +// txConfidenceIndicator.setProgress(0); +// txConfidenceIndicator.setTooltip(tooltip); +// txConfidenceIndicator.setVisible(initialTxConfidenceVisibility); +// +// TransactionConfidence confidence = transaction.getConfidence(); +// GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator); +// confirmations = confidence.getDepthInBlocks(); +// }}); +// +// txConfidenceListener = new TxConfidenceListener(txId) { +// @Override +// public void onTransactionConfidenceChanged(TransactionConfidence confidence) { +// GUIUtil.updateConfidence(confidence, lazy().tooltip, lazy().txConfidenceIndicator); +// confirmations = confidence.getDepthInBlocks(); +// } +// }; +// btcWalletService.addTxConfidenceListener(txConfidenceListener); } public void cleanup() { - btcWalletService.removeTxConfidenceListener(txConfidenceListener); + // TODO (woodser): remove wallet listener + //xmrWalletService.removeTxConfidenceListener(txConfidenceListener); } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalListItem.java b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalListItem.java index 0aebe8e2..7d5eff00 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalListItem.java @@ -19,26 +19,26 @@ package bisq.desktop.main.funds.withdrawal; import bisq.desktop.components.AutoTooltipLabel; -import bisq.core.btc.listeners.BalanceListener; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.listeners.XmrBalanceListener; +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.locale.Res; import bisq.core.util.coin.CoinFormatter; -import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; import javafx.scene.control.Label; +import java.math.BigInteger; + import lombok.Getter; import lombok.Setter; class WithdrawalListItem { - private final BalanceListener balanceListener; + private final XmrBalanceListener balanceListener; private final Label balanceLabel; - private final AddressEntry addressEntry; - private final BtcWalletService walletService; + private final XmrAddressEntry addressEntry; + private final XmrWalletService walletService; private final CoinFormatter formatter; private Coin balance; private final String addressString; @@ -46,7 +46,7 @@ class WithdrawalListItem { @Getter private boolean isSelected; - public WithdrawalListItem(AddressEntry addressEntry, BtcWalletService walletService, + public WithdrawalListItem(XmrAddressEntry addressEntry, XmrWalletService walletService, CoinFormatter formatter) { this.addressEntry = addressEntry; this.walletService = walletService; @@ -55,9 +55,9 @@ class WithdrawalListItem { // balance balanceLabel = new AutoTooltipLabel(); - balanceListener = new BalanceListener(getAddress()) { + balanceListener = new XmrBalanceListener(addressEntry.getAccountIndex()) { @Override - public void onBalanceChanged(Coin balance, Transaction tx) { + public void onBalanceChanged(BigInteger balance) { updateBalance(); } }; @@ -71,7 +71,7 @@ class WithdrawalListItem { } private void updateBalance() { - balance = walletService.getBalanceForAddress(addressEntry.getAddress()); + balance = walletService.getBalanceForAccount(addressEntry.getAccountIndex()); if (balance != null) balanceLabel.setText(formatter.formatCoin(this.balance)); } @@ -81,7 +81,7 @@ class WithdrawalListItem { return Res.getWithCol("shared.offerId") + " " + addressEntry.getShortOfferId(); else if (addressEntry.isTrade()) return Res.getWithCol("shared.tradeId") + " " + addressEntry.getShortOfferId(); - else if (addressEntry.getContext() == AddressEntry.Context.ARBITRATOR) + else if (addressEntry.getContext() == XmrAddressEntry.Context.ARBITRATOR) return Res.get("funds.withdrawal.arbitrationFee"); else return "-"; @@ -102,11 +102,7 @@ class WithdrawalListItem { return addressEntry.hashCode(); } - private Address getAddress() { - return addressEntry.getAddress(); - } - - public AddressEntry getAddressEntry() { + public XmrAddressEntry getAddressEntry() { return addressEntry; } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java index b1920085..ae0102e1 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java @@ -26,23 +26,16 @@ import bisq.desktop.components.HyperlinkWithIcon; import bisq.desktop.components.InputTextField; import bisq.desktop.components.TitledGroupBg; import bisq.desktop.main.overlays.popups.Popup; -import bisq.desktop.main.overlays.windows.TxDetails; import bisq.desktop.main.overlays.windows.WalletPasswordWindow; import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; -import bisq.core.btc.exceptions.AddressEntryException; -import bisq.core.btc.exceptions.InsufficientFundsException; -import bisq.core.btc.listeners.BalanceListener; -import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.listeners.XmrBalanceListener; import bisq.core.btc.setup.WalletsSetup; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.Restrictions; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.locale.Res; import bisq.core.provider.fee.FeeService; -import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; -import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; @@ -51,15 +44,11 @@ import bisq.core.util.validation.BtcAddressValidator; import bisq.network.p2p.P2PService; -import bisq.common.UserThread; import bisq.common.util.Tuple3; import bisq.common.util.Tuple4; -import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.Transaction; -import org.bitcoinj.wallet.Wallet; import javax.inject.Inject; import javax.inject.Named; @@ -101,18 +90,14 @@ import javafx.util.Callback; import org.bouncycastle.crypto.params.KeyParameter; -import java.util.ArrayList; +import java.math.BigInteger; + import java.util.Comparator; import java.util.HashSet; -import java.util.List; import java.util.Set; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import org.jetbrains.annotations.NotNull; - import static bisq.desktop.util.FormBuilder.*; -import static com.google.common.base.Preconditions.checkNotNull; @FxmlView public class WithdrawalView extends ActivatableView { @@ -128,7 +113,7 @@ public class WithdrawalView extends ActivatableView { private Label amountLabel; private TextField amountTextField, withdrawFromTextField, withdrawToTextField, withdrawMemoTextField, transactionFeeInputTextField; - private final BtcWalletService btcWalletService; + private final XmrWalletService xmrWalletService; private final TradeManager tradeManager; private final P2PService p2PService; private final WalletsSetup walletsSetup; @@ -139,7 +124,7 @@ public class WithdrawalView extends ActivatableView { private final ObservableList observableList = FXCollections.observableArrayList(); private final SortedList sortedList = new SortedList<>(observableList); private final Set selectedItems = new HashSet<>(); - private BalanceListener balanceListener; + private XmrBalanceListener balanceListener; private Set fromAddresses = new HashSet<>(); private Coin totalAvailableAmountOfSelectedItems = Coin.ZERO; private Coin amountAsCoin = Coin.ZERO; @@ -160,7 +145,7 @@ public class WithdrawalView extends ActivatableView { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private WithdrawalView(BtcWalletService btcWalletService, + private WithdrawalView(XmrWalletService xmrWalletService, TradeManager tradeManager, P2PService p2PService, WalletsSetup walletsSetup, @@ -169,7 +154,8 @@ public class WithdrawalView extends ActivatableView { BtcAddressValidator btcAddressValidator, WalletPasswordWindow walletPasswordWindow, FeeService feeService) { - this.btcWalletService = btcWalletService; +// throw new RuntimeException("WithdrawalView needs updated to use XMR wallet"); + this.xmrWalletService = xmrWalletService; this.tradeManager = tradeManager; this.p2PService = p2PService; this.walletsSetup = walletsSetup; @@ -295,9 +281,9 @@ public class WithdrawalView extends ActivatableView { balanceColumn.setSortType(TableColumn.SortType.DESCENDING); tableView.getSortOrder().add(balanceColumn); - balanceListener = new BalanceListener() { + balanceListener = new XmrBalanceListener() { @Override - public void onBalanceChanged(Coin balance, Transaction tx) { + public void onBalanceChanged(BigInteger balance) { updateList(); } }; @@ -346,7 +332,7 @@ public class WithdrawalView extends ActivatableView { amountTextField.textProperty().addListener(amountListener); amountTextField.focusedProperty().addListener(amountFocusListener); - btcWalletService.addBalanceListener(balanceListener); + xmrWalletService.addBalanceListener(balanceListener); feeToggleGroup.selectedToggleProperty().addListener(feeToggleGroupListener); inputsToggleGroup.selectedToggleProperty().addListener(inputsToggleGroupListener); @@ -371,7 +357,7 @@ public class WithdrawalView extends ActivatableView { protected void deactivate() { sortedList.comparatorProperty().unbind(); observableList.forEach(WithdrawalListItem::cleanup); - btcWalletService.removeBalanceListener(balanceListener); + xmrWalletService.removeBalanceListener(balanceListener); amountTextField.textProperty().removeListener(amountListener); amountTextField.focusedProperty().removeListener(amountFocusListener); feeToggleGroup.selectedToggleProperty().removeListener(feeToggleGroupListener); @@ -388,107 +374,108 @@ public class WithdrawalView extends ActivatableView { /////////////////////////////////////////////////////////////////////////////////////////// private void onWithdraw() { - if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) { - try { - final String withdrawToAddress = withdrawToTextField.getText(); - final Coin sendersAmount; - - // We do not know sendersAmount if senderPaysFee is true. We repeat fee calculation after first attempt if senderPaysFee is true. - Transaction feeEstimationTransaction = btcWalletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, amountAsCoin); - if (feeExcluded && feeEstimationTransaction != null) { - feeEstimationTransaction = btcWalletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, amountAsCoin.add(feeEstimationTransaction.getFee())); - } - checkNotNull(feeEstimationTransaction, "feeEstimationTransaction must not be null"); - - Coin dust = btcWalletService.getDust(feeEstimationTransaction); - Coin fee = feeEstimationTransaction.getFee().add(dust); - Coin receiverAmount; - // amountAsCoin is what the user typed into the withdrawal field. - // this can be interpreted as either the senders amount or receivers amount depending - // on a radio button "fee excluded / fee included". - // therefore we calculate the actual sendersAmount and receiverAmount as follows: - if (feeExcluded) { - receiverAmount = amountAsCoin; - sendersAmount = receiverAmount.add(fee); - } else { - sendersAmount = amountAsCoin.add(dust); // sendersAmount bumped up to UTXO size when dust is in play - receiverAmount = sendersAmount.subtract(fee); - } - if (dust.isPositive()) { - log.info("Dust output ({} satoshi) was detected, the dust amount has been added to the fee (was {}, now {})", - dust.value, - feeEstimationTransaction.getFee(), - fee.value); - } - - if (areInputsValid(sendersAmount)) { - int txVsize = feeEstimationTransaction.getVsize(); - log.info("Fee for tx with size {}: {} " + Res.getBaseCurrencyCode() + "", txVsize, fee.toPlainString()); - - if (receiverAmount.isPositive()) { - double vkb = txVsize / 1000d; - - String messageText = Res.get("shared.sendFundsDetailsWithFee", - formatter.formatCoinWithCode(sendersAmount), - withdrawFromTextField.getText(), - withdrawToAddress, - formatter.formatCoinWithCode(fee), - Double.parseDouble(transactionFeeInputTextField.getText()), - vkb, - formatter.formatCoinWithCode(receiverAmount)); - if (dust.isPositive()) { - messageText = Res.get("shared.sendFundsDetailsDust", - dust.value, dust.value > 1 ? "s" : "") - + messageText; - } - - new Popup().headLine(Res.get("funds.withdrawal.confirmWithdrawalRequest")) - .confirmation(messageText) - .actionButtonText(Res.get("shared.yes")) - .onAction(() -> doWithdraw(sendersAmount, fee, new FutureCallback<>() { - @Override - public void onSuccess(@javax.annotation.Nullable Transaction transaction) { - if (transaction != null) { - String key = "showTransactionSent"; - if (DontShowAgainLookup.showAgain(key)) { - new TxDetails(transaction.getTxId().toString(), withdrawToAddress, formatter.formatCoinWithCode(sendersAmount)) - .dontShowAgainId(key) - .show(); - } - log.debug("onWithdraw onSuccess tx ID:{}", transaction.getTxId().toString()); - } else { - log.error("onWithdraw transaction is null"); - } - - List trades = new ArrayList<>(tradeManager.getObservableList()); - trades.stream() - .filter(Trade::isPayoutPublished) - .forEach(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT) - .ifPresent(addressEntry -> { - if (btcWalletService.getBalanceForAddress(addressEntry.getAddress()).isZero()) - tradeManager.onTradeCompleted(trade); - })); - } - - @Override - public void onFailure(@NotNull Throwable t) { - log.error("onWithdraw onFailure"); - } - })) - .closeButtonText(Res.get("shared.cancel")) - .show(); - } else { - new Popup().warning(Res.get("portfolio.pending.step5_buyer.amountTooLow")).show(); - } - } - } catch (InsufficientFundsException e) { - new Popup().warning(Res.get("funds.withdrawal.warn.amountExceeds") + "\n\nError message:\n" + e.getMessage()).show(); - } catch (Throwable e) { - e.printStackTrace(); - log.error(e.toString()); - new Popup().warning(e.toString()).show(); - } - } + throw new RuntimeException("WithdrawalView.onWithdraw() not updated to XMR"); +// if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) { +// try { +// final String withdrawToAddress = withdrawToTextField.getText(); +// final Coin sendersAmount; +// +// // We do not know sendersAmount if senderPaysFee is true. We repeat fee calculation after first attempt if senderPaysFee is true. +// Transaction feeEstimationTransaction = btcWalletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, amountAsCoin); +// if (feeExcluded && feeEstimationTransaction != null) { +// feeEstimationTransaction = btcWalletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, amountAsCoin.add(feeEstimationTransaction.getFee())); +// } +// checkNotNull(feeEstimationTransaction, "feeEstimationTransaction must not be null"); +// +// Coin dust = btcWalletService.getDust(feeEstimationTransaction); +// Coin fee = feeEstimationTransaction.getFee().add(dust); +// Coin receiverAmount; +// // amountAsCoin is what the user typed into the withdrawal field. +// // this can be interpreted as either the senders amount or receivers amount depending +// // on a radio button "fee excluded / fee included". +// // therefore we calculate the actual sendersAmount and receiverAmount as follows: +// if (feeExcluded) { +// receiverAmount = amountAsCoin; +// sendersAmount = receiverAmount.add(fee); +// } else { +// sendersAmount = amountAsCoin.add(dust); // sendersAmount bumped up to UTXO size when dust is in play +// receiverAmount = sendersAmount.subtract(fee); +// } +// if (dust.isPositive()) { +// log.info("Dust output ({} satoshi) was detected, the dust amount has been added to the fee (was {}, now {})", +// dust.value, +// feeEstimationTransaction.getFee(), +// fee.value); +// } +// +// if (areInputsValid(sendersAmount)) { +// int txVsize = feeEstimationTransaction.getVsize(); +// log.info("Fee for tx with size {}: {} " + Res.getBaseCurrencyCode() + "", txVsize, fee.toPlainString()); +// +// if (receiverAmount.isPositive()) { +// double vkb = txVsize / 1000d; +// +// String messageText = Res.get("shared.sendFundsDetailsWithFee", +// formatter.formatCoinWithCode(sendersAmount), +// withdrawFromTextField.getText(), +// withdrawToAddress, +// formatter.formatCoinWithCode(fee), +// Double.parseDouble(transactionFeeInputTextField.getText()), +// vkb, +// formatter.formatCoinWithCode(receiverAmount)); +// if (dust.isPositive()) { +// messageText = Res.get("shared.sendFundsDetailsDust", +// dust.value, dust.value > 1 ? "s" : "") +// + messageText; +// } +// +// new Popup().headLine(Res.get("funds.withdrawal.confirmWithdrawalRequest")) +// .confirmation(messageText) +// .actionButtonText(Res.get("shared.yes")) +// .onAction(() -> doWithdraw(sendersAmount, fee, new FutureCallback<>() { +// @Override +// public void onSuccess(@javax.annotation.Nullable Transaction transaction) { +// if (transaction != null) { +// String key = "showTransactionSent"; +// if (DontShowAgainLookup.showAgain(key)) { +// new TxDetails(transaction.getTxId().toString(), withdrawToAddress, formatter.formatCoinWithCode(sendersAmount)) +// .dontShowAgainId(key) +// .show(); +// } +// log.debug("onWithdraw onSuccess tx ID:{}", transaction.getTxId().toString()); +// } else { +// log.error("onWithdraw transaction is null"); +// } +// +// List trades = new ArrayList<>(tradeManager.getObservableList()); +// trades.stream() +// .filter(Trade::isPayoutPublished) +// .forEach(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT) +// .ifPresent(addressEntry -> { +// if (btcWalletService.getBalanceForAddress(addressEntry.getAddress()).isZero()) +// tradeManager.onTradeCompleted(trade); +// })); +// } +// +// @Override +// public void onFailure(@NotNull Throwable t) { +// log.error("onWithdraw onFailure"); +// } +// })) +// .closeButtonText(Res.get("shared.cancel")) +// .show(); +// } else { +// new Popup().warning(Res.get("portfolio.pending.step5_buyer.amountTooLow")).show(); +// } +// } +// } catch (InsufficientFundsException e) { +// new Popup().warning(Res.get("funds.withdrawal.warn.amountExceeds") + "\n\nError message:\n" + e.getMessage()).show(); +// } catch (Throwable e) { +// e.printStackTrace(); +// log.error(e.toString()); +// new Popup().warning(e.toString()).show(); +// } +// } } private void selectForWithdrawal(WithdrawalListItem item) { @@ -546,55 +533,58 @@ public class WithdrawalView extends ActivatableView { /////////////////////////////////////////////////////////////////////////////////////////// private void updateList() { + //throw new RuntimeException("WithdrawalView.updateList() needs updated to use XMR"); observableList.forEach(WithdrawalListItem::cleanup); - observableList.setAll(btcWalletService.getAddressEntriesForAvailableBalanceStream() - .map(addressEntry -> new WithdrawalListItem(addressEntry, btcWalletService, formatter)) + observableList.setAll(xmrWalletService.getAddressEntriesForAvailableBalanceStream() + .map(addressEntry -> new WithdrawalListItem(addressEntry, xmrWalletService, formatter)) .collect(Collectors.toList())); updateInputSelection(); } private void doWithdraw(Coin amount, Coin fee, FutureCallback callback) { - if (btcWalletService.isEncrypted()) { - UserThread.runAfter(() -> walletPasswordWindow.onAesKey(aesKey -> - sendFunds(amount, fee, aesKey, callback)) - .show(), 300, TimeUnit.MILLISECONDS); - } else { - sendFunds(amount, fee, null, callback); - } + throw new RuntimeException("WithdrawalView.doWithdraw() not updated to XMR"); +// if (xmrWalletService.isEncrypted()) { +// UserThread.runAfter(() -> walletPasswordWindow.onAesKey(aesKey -> +// sendFunds(amount, fee, aesKey, callback)) +// .show(), 300, TimeUnit.MILLISECONDS); +// } else { +// sendFunds(amount, fee, null, callback); +// } } private void sendFunds(Coin amount, Coin fee, KeyParameter aesKey, FutureCallback callback) { - try { - String memo = withdrawMemoTextField.getText(); - if (memo.isEmpty()) { - memo = null; - } - Transaction transaction = btcWalletService.sendFundsForMultipleAddresses(fromAddresses, - withdrawToTextField.getText(), - amount, - fee, - null, - aesKey, - memo, - callback); - - reset(); - updateList(); - } catch (AddressFormatException e) { - new Popup().warning(Res.get("validation.btc.invalidAddress")).show(); - } catch (Wallet.DustySendRequested e) { - new Popup().warning(Res.get("validation.amountBelowDust", - formatter.formatCoinWithCode(Restrictions.getMinNonDustOutput()))).show(); - } catch (AddressEntryException e) { - new Popup().error(e.getMessage()).show(); - } catch (InsufficientMoneyException e) { - log.warn(e.getMessage()); - new Popup().warning(Res.get("funds.withdrawal.notEnoughFunds") + "\n\nError message:\n" + e.getMessage()).show(); - } catch (Throwable e) { - log.warn(e.toString()); - new Popup().warning(e.toString()).show(); - } + throw new RuntimeException("WithdrawalView.sendFunds() not updated to XMR"); +// try { +// String memo = withdrawMemoTextField.getText(); +// if (memo.isEmpty()) { +// memo = null; +// } +// Transaction transaction = btcWalletService.sendFundsForMultipleAddresses(fromAddresses, +// withdrawToTextField.getText(), +// amount, +// fee, +// null, +// aesKey, +// memo, +// callback); +// +// reset(); +// updateList(); +// } catch (AddressFormatException e) { +// new Popup().warning(Res.get("validation.btc.invalidAddress")).show(); +// } catch (Wallet.DustySendRequested e) { +// new Popup().warning(Res.get("validation.amountBelowDust", +// formatter.formatCoinWithCode(Restrictions.getMinNonDustOutput()))).show(); +// } catch (AddressEntryException e) { +// new Popup().error(e.getMessage()).show(); +// } catch (InsufficientMoneyException e) { +// log.warn(e.getMessage()); +// new Popup().warning(Res.get("funds.withdrawal.notEnoughFunds") + "\n\nError message:\n" + e.getMessage()).show(); +// } catch (Throwable e) { +// log.warn(e.toString()); +// new Popup().warning(e.toString()).show(); +// } } private void reset() { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 39a8f81f..2fc9f1ea 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -23,12 +23,12 @@ import bisq.desktop.util.GUIUtil; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.TxFeeEstimationService; -import bisq.core.btc.listeners.BalanceListener; import bisq.core.btc.listeners.BsqBalanceListener; -import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.listeners.XmrBalanceListener; +import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.wallet.BsqWalletService; -import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; @@ -58,7 +58,6 @@ import bisq.common.util.Tuple2; import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; import com.google.inject.Inject; @@ -81,6 +80,8 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.SetChangeListener; +import java.math.BigInteger; + import java.util.Comparator; import java.util.Date; import java.util.HashSet; @@ -98,6 +99,7 @@ import static java.util.Comparator.comparing; public abstract class MutableOfferDataModel extends OfferDataModel implements BsqBalanceListener { private final CreateOfferService createOfferService; protected final OpenOfferManager openOfferManager; + private final XmrWalletService xmrWalletService; private final BsqWalletService bsqWalletService; private final Preferences preferences; protected final User user; @@ -109,7 +111,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs private final CoinFormatter btcFormatter; private final Navigation navigation; private final String offerId; - private final BalanceListener btcBalanceListener; + private final XmrBalanceListener xmrBalanceListener; private final SetChangeListener paymentAccountsChangeListener; protected OfferPayload.Direction direction; @@ -152,7 +154,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs public MutableOfferDataModel(CreateOfferService createOfferService, OpenOfferManager openOfferManager, OfferUtil offerUtil, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, BsqWalletService bsqWalletService, Preferences preferences, User user, @@ -163,8 +165,9 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, TradeStatisticsManager tradeStatisticsManager, Navigation navigation) { - super(btcWalletService, offerUtil); + super(xmrWalletService, offerUtil); + this.xmrWalletService = xmrWalletService; this.createOfferService = createOfferService; this.openOfferManager = openOfferManager; this.bsqWalletService = bsqWalletService; @@ -180,14 +183,14 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs offerId = createOfferService.getRandomOfferId(); shortOfferId = Utilities.getShortId(offerId); - addressEntry = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING); + addressEntry = xmrWalletService.getOrCreateAddressEntry(offerId, XmrAddressEntry.Context.OFFER_FUNDING); useMarketBasedPrice.set(preferences.isUsePercentageBasedPrice()); buyerSecurityDeposit.set(Restrictions.getMinBuyerSecurityDepositAsPercent()); - btcBalanceListener = new BalanceListener(getAddressEntry().getAddress()) { + xmrBalanceListener = new XmrBalanceListener(getAddressEntry().getAccountIndex()) { @Override - public void onBalanceChanged(Coin balance, Transaction tx) { + public void onBalanceChanged(BigInteger balance) { updateBalance(); } }; @@ -211,13 +214,13 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs } private void addListeners() { - btcWalletService.addBalanceListener(btcBalanceListener); + xmrWalletService.addBalanceListener(xmrBalanceListener); bsqWalletService.addBsqBalanceListener(this); user.getPaymentAccountsAsObservable().addListener(paymentAccountsChangeListener); } private void removeListeners() { - btcWalletService.removeBalanceListener(btcBalanceListener); + xmrWalletService.removeBalanceListener(xmrBalanceListener); bsqWalletService.removeBsqBalanceListener(this); user.getPaymentAccountsAsObservable().removeListener(paymentAccountsChangeListener); } @@ -482,7 +485,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs return direction == OfferPayload.Direction.BUY; } - AddressEntry getAddressEntry() { + XmrAddressEntry getAddressEntry() { return addressEntry; } @@ -618,7 +621,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs } void swapTradeToSavings() { - btcWalletService.resetAddressEntriesForOpenOffer(offerId); + xmrWalletService.resetAddressEntriesForOpenOffer(offerId); } private void fillPaymentAccounts() { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java index 4e9697a5..b9783347 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java @@ -1282,8 +1282,9 @@ public abstract class MutableOfferView> exten @NotNull private String getBitcoinURI() { - return GUIUtil.getBitcoinURI(addressTextField.getAddress(), model.getDataModel().getMissingCoin().get(), - model.getPaymentLabel()); + return "TODO"; // TODO (woodser): wallet.createPaymentUri(); +// return GUIUtil.getBitcoinURI(addressTextField.getAddress(), model.getDataModel().getMissingCoin().get(), +// model.getPaymentLabel()); } private void addAmountPriceFields() { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java index 14e26e14..e799bdef 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java @@ -19,8 +19,8 @@ package bisq.desktop.main.offer; import bisq.desktop.common.model.ActivatableDataModel; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.OfferUtil; import org.bitcoinj.core.Coin; @@ -41,7 +41,7 @@ import static bisq.core.util.coin.CoinUtil.minCoin; * needed in that UI element. */ public abstract class OfferDataModel extends ActivatableDataModel { - protected final BtcWalletService btcWalletService; + protected final XmrWalletService xmrWalletService; protected final OfferUtil offerUtil; @Getter @@ -56,18 +56,18 @@ public abstract class OfferDataModel extends ActivatableDataModel { protected final BooleanProperty showWalletFundedNotification = new SimpleBooleanProperty(); @Getter protected Coin totalAvailableBalance; - protected AddressEntry addressEntry; + protected XmrAddressEntry addressEntry; protected boolean useSavingsWallet; - public OfferDataModel(BtcWalletService btcWalletService, OfferUtil offerUtil) { - this.btcWalletService = btcWalletService; + public OfferDataModel(XmrWalletService xmrWalletService, OfferUtil offerUtil) { + this.xmrWalletService = xmrWalletService; this.offerUtil = offerUtil; } protected void updateBalance() { - Coin tradeWalletBalance = btcWalletService.getBalanceForAddress(addressEntry.getAddress()); + Coin tradeWalletBalance = xmrWalletService.getBalanceForAccount(addressEntry.getAccountIndex()); if (useSavingsWallet) { - Coin savingWalletBalance = btcWalletService.getSavingWalletBalance(); + Coin savingWalletBalance = xmrWalletService.getSavingWalletBalance(); totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance); if (totalToPayAsCoin.get() != null) { balance.set(minCoin(totalToPayAsCoin.get(), totalAvailableBalance)); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java index 0bec3dcf..4b99dce9 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java @@ -26,7 +26,7 @@ import bisq.desktop.main.offer.MutableOfferDataModel; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.wallet.BsqWalletService; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.CreateOfferService; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOfferManager; @@ -55,7 +55,7 @@ class CreateOfferDataModel extends MutableOfferDataModel { public CreateOfferDataModel(CreateOfferService createOfferService, OpenOfferManager openOfferManager, OfferUtil offerUtil, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, BsqWalletService bsqWalletService, Preferences preferences, User user, @@ -69,7 +69,7 @@ class CreateOfferDataModel extends MutableOfferDataModel { super(createOfferService, openOfferManager, offerUtil, - btcWalletService, + xmrWalletService, bsqWalletService, preferences, user, diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java index 1e01c8a8..51f435cc 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -25,11 +25,11 @@ import bisq.desktop.util.GUIUtil; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.TxFeeEstimationService; -import bisq.core.btc.listeners.BalanceListener; -import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.listeners.XmrBalanceListener; +import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.wallet.BsqWalletService; -import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.filter.FilterManager; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; @@ -56,8 +56,6 @@ import bisq.network.p2p.P2PService; import bisq.common.util.Tuple2; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.wallet.Wallet; import com.google.inject.Inject; @@ -69,6 +67,8 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ObservableList; +import java.math.BigInteger; + import java.util.Set; import lombok.Getter; @@ -111,7 +111,7 @@ class TakeOfferDataModel extends OfferDataModel { private final ObjectProperty amount = new SimpleObjectProperty<>(); final ObjectProperty volume = new SimpleObjectProperty<>(); - private BalanceListener balanceListener; + private XmrBalanceListener balanceListener; private PaymentAccount paymentAccount; private boolean isTabSelected; Price tradePrice; @@ -134,7 +134,7 @@ class TakeOfferDataModel extends OfferDataModel { TakeOfferDataModel(TradeManager tradeManager, OfferBook offerBook, OfferUtil offerUtil, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, BsqWalletService bsqWalletService, User user, FeeService feeService, MempoolService mempoolService, @@ -146,7 +146,7 @@ class TakeOfferDataModel extends OfferDataModel { Navigation navigation, P2PService p2PService ) { - super(btcWalletService, offerUtil); + super(xmrWalletService, offerUtil); this.tradeManager = tradeManager; this.offerBook = offerBook; @@ -207,7 +207,7 @@ class TakeOfferDataModel extends OfferDataModel { void initWithData(Offer offer) { this.offer = offer; tradePrice = offer.getPrice(); - addressEntry = btcWalletService.getOrCreateAddressEntry(offer.getId(), AddressEntry.Context.OFFER_FUNDING); + addressEntry = xmrWalletService.getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); checkNotNull(addressEntry, "addressEntry must not be null"); ObservableList possiblePaymentAccounts = getPossiblePaymentAccounts(); @@ -265,11 +265,40 @@ class TakeOfferDataModel extends OfferDataModel { calculateVolume(); calculateTotalToPay(); - balanceListener = new BalanceListener(addressEntry.getAddress()) { + balanceListener = new XmrBalanceListener(addressEntry.getAccountIndex()) { @Override - public void onBalanceChanged(Coin balance, Transaction tx) { - updateBalance(); + public void onBalanceChanged(BigInteger balance) { + updateBalance(); } + +// public void onBalanceChanged(Coin balance, Transaction tx) { +// updateBalance(); +// +// /*if (isMainNet.get()) { +// SettableFuture future = blockchainService.requestFee(tx.getHashAsString()); +// Futures.addCallback(future, new FutureCallback() { +// public void onSuccess(Coin fee) { +// UserThread.execute(() -> setFeeFromFundingTx(fee)); +// } +// +// public void onFailure(@NotNull Throwable throwable) { +// UserThread.execute(() -> new Popup<>() +// .warning("We did not get a response for the request of the mining fee used " + +// "in the funding transaction.\n\n" + +// "Are you sure you used a sufficiently high fee of at least " + +// formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + "?") +// .actionButtonText("Yes, I used a sufficiently high fee.") +// .onAction(() -> setFeeFromFundingTx(FeePolicy.getMinRequiredFeeForFundingTx())) +// .closeButtonText("No. Let's cancel that payment.") +// .onClose(() -> setFeeFromFundingTx(Coin.NEGATIVE_SATOSHI)) +// .show()); +// } +// }); +// } else { +// setFeeFromFundingTx(FeePolicy.getMinRequiredFeeForFundingTx()); +// isFeeFromFundingTxSufficient.set(feeFromFundingTx.compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0); +// }*/ +// } }; offer.resetState(); @@ -301,7 +330,7 @@ class TakeOfferDataModel extends OfferDataModel { offerBook.removeOffer(checkNotNull(offer), tradeManager); } - btcWalletService.resetAddressEntriesForOpenOffer(offer.getId()); + //xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId()); // TODO (woodser): this removes address entries for reserved trades before completion. how doesn't this delete the multisig address entry in bisq before completion? } @@ -335,7 +364,6 @@ class TakeOfferDataModel extends OfferDataModel { tradeManager.onTakeOffer(amount.get(), txFeeFromFeeService, getTakerFee(), - isCurrencyForTakerFeeBtc(), tradePrice.getValue(), fundsNeededForTrade, offer, @@ -363,7 +391,7 @@ class TakeOfferDataModel extends OfferDataModel { // So that would require more thoughts how to deal with all those cases. public void estimateTxVsize() { int txVsize = 0; - if (btcWalletService.getBalance(Wallet.BalanceType.AVAILABLE).isPositive()) { + if (xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(new BigInteger("0")) > 0) { Coin fundsNeededForTrade = getFundsNeededForTrade(); if (isBuyOffer()) fundsNeededForTrade = fundsNeededForTrade.add(amount.get()); @@ -469,11 +497,11 @@ class TakeOfferDataModel extends OfferDataModel { /////////////////////////////////////////////////////////////////////////////////////////// private void addListeners() { - btcWalletService.addBalanceListener(balanceListener); + xmrWalletService.addBalanceListener(balanceListener); } private void removeListeners() { - btcWalletService.removeBalanceListener(balanceListener); + xmrWalletService.removeBalanceListener(balanceListener); } @@ -554,7 +582,7 @@ class TakeOfferDataModel extends OfferDataModel { public void swapTradeToSavings() { log.debug("swapTradeToSavings, offerId={}", offer.getId()); - btcWalletService.resetAddressEntriesForOpenOffer(offer.getId()); + xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId()); } // We use the sum of the vsize of the trade fee and the deposit tx to get an average. @@ -648,7 +676,7 @@ class TakeOfferDataModel extends OfferDataModel { return txFeeFromFeeService; //feeService.getTxFee(169); } - public AddressEntry getAddressEntry() { + public XmrAddressEntry getAddressEntry() { return addressEntry; } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferView.java index 5f6bed73..09cd4885 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferView.java @@ -1086,9 +1086,10 @@ public class TakeOfferView extends ActivatableViewAndModel im showTransactionPublishedScreen.set(false); dataModel.onTakeOffer(trade -> { this.trade = trade; + takeOfferCompleted.set(true); trade.stateProperty().addListener(tradeStateListener); applyTradeState(); trade.errorMessageProperty().addListener(tradeErrorListener); applyTradeErrorMessage(trade.getErrorMessage()); - takeOfferCompleted.set(true); }); updateButtonDisableState(); @@ -448,13 +448,16 @@ class TakeOfferViewModel extends ActivatableWithDataModel im } private void applyTradeState() { - if (trade.isDepositPublished()) { - if (trade.getDepositTx() != null) { + if (trade.isTakerFeePublished()) { + if (trade.getTakerFeeTxId() != null) { if (takeOfferSucceededHandler != null) takeOfferSucceededHandler.run(); showTransactionPublishedScreen.set(true); updateSpinnerInfo(); + } else { + final String msg = "trade.getTakerFeeTxId() must not be null."; + DevEnv.logErrorAndThrowIfDevMode(msg); } } } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java index 4f025afe..d2864042 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java @@ -33,7 +33,6 @@ import bisq.core.payment.payload.PaymentMethod; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; -import bisq.core.support.dispute.agent.DisputeAgentLookupMap; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; @@ -201,15 +200,17 @@ public class ContractWindow extends Overlay { title = Res.get("shared.selectedArbitrator"); break; case MEDIATION: - agentKeyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(contract.getMediatorNodeAddress().getFullAddress()); - title = Res.get("shared.selectedMediator"); - break; + throw new RuntimeException("Mediation type not adapted to XMR"); +// agentKeyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(contract.getMediatorNodeAddress().getFullAddress()); +// title = Res.get("shared.selectedMediator"); +// break; case TRADE: break; case REFUND: - agentKeyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(contract.getRefundAgentNodeAddress().getFullAddress()); - title = Res.get("shared.selectedRefundAgent"); - break; + throw new RuntimeException("Refund type not adapted to XMR"); + //agentKeyBaseUserName = DisputeAgentLookupMap.getKeyBaseUserName(contract.getRefundAgentNodeAddress().getFullAddress()); + //title = Res.get("shared.selectedRefundAgent"); + //break; } } @@ -247,7 +248,7 @@ public class ContractWindow extends Overlay { } addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.makerFeeTxId"), offer.getOfferFeePaymentTxId()); - addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.takerFeeTxId"), contract.getTakerFeeTxID()); + addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.takerFeeTxId"), "TAKER FEE TX ID NOT PART OF CONTRACT"); // TODO (woodser): should taker fee tx id be part of contract? if (dispute.getDepositTxSerialized() != null) addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.depositTransactionId"), dispute.getDepositTxId()); @@ -273,8 +274,6 @@ public class ContractWindow extends Overlay { viewContractButton.setOnAction(e -> { TextArea textArea = new BisqTextArea(); String contractAsJson = dispute.getContractAsJson(); - contractAsJson += "\n\nBuyerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getBuyerMultiSigPubKey()); - contractAsJson += "\nSellerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getSellerMultiSigPubKey()); textArea.setText(contractAsJson); textArea.setPrefHeight(50); textArea.setEditable(false); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index a6ac3ce0..5400781d 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -23,35 +23,25 @@ import bisq.desktop.components.BisqTextArea; import bisq.desktop.components.InputTextField; import bisq.desktop.main.overlays.Overlay; import bisq.desktop.main.overlays.popups.Popup; -import bisq.desktop.main.support.dispute.DisputeSummaryVerification; import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.Layout; -import bisq.core.btc.TxFeeEstimationService; -import bisq.core.btc.exceptions.TransactionVerificationException; -import bisq.core.btc.exceptions.TxBroadcastException; -import bisq.core.btc.exceptions.WalletException; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.Restrictions; +import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.wallet.TradeWalletService; -import bisq.core.btc.wallet.TxBroadcaster; -import bisq.core.dao.DaoFacade; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.locale.Res; import bisq.core.offer.Offer; -import bisq.core.provider.mempool.MempoolService; -import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeResult; +import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.trade.Contract; -import bisq.core.trade.TradeDataValidation; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; import bisq.core.util.coin.CoinFormatter; -import bisq.core.util.coin.CoinUtil; import bisq.common.UserThread; import bisq.common.app.DevEnv; @@ -59,12 +49,11 @@ import bisq.common.handlers.ResultHandler; import bisq.common.util.Tuple2; import bisq.common.util.Tuple3; +import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.InsufficientMoneyException; -import org.bitcoinj.core.Transaction; -import javax.inject.Inject; -import javax.inject.Named; +import com.google.inject.Inject; +import com.google.inject.name.Named; import javafx.scene.Scene; import javafx.scene.control.Button; @@ -85,28 +74,30 @@ import javafx.geometry.Insets; import javafx.beans.binding.Bindings; import javafx.beans.value.ChangeListener; -import java.time.Instant; - import java.util.Date; -import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; -import static bisq.desktop.util.FormBuilder.*; +import static bisq.desktop.util.FormBuilder.add2ButtonsWithBox; +import static bisq.desktop.util.FormBuilder.addConfirmationLabelLabel; +import static bisq.desktop.util.FormBuilder.addTitledGroupBg; +import static bisq.desktop.util.FormBuilder.addTopLabelWithVBox; import static com.google.common.base.Preconditions.checkNotNull; + + +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroTxWallet; + @Slf4j public class DisputeSummaryWindow extends Overlay { private final CoinFormatter formatter; + private final ArbitrationManager arbitrationManager; private final MediationManager mediationManager; - private final RefundManager refundManager; - private final TradeWalletService tradeWalletService; - private final BtcWalletService btcWalletService; - private final TxFeeEstimationService txFeeEstimationService; - private final MempoolService mempoolService; - private final DaoFacade daoFacade; + private final XmrWalletService walletService; + private final TradeWalletService tradeWalletService; // TODO (woodser): remove for xmr or adapt to get/create multisig wallets for tx creation utils private Dispute dispute; private Optional finalizeDisputeHandlerOptional = Optional.empty(); private ToggleGroup tradeAmountToggleGroup, reasonToggleGroup; @@ -122,13 +113,12 @@ public class DisputeSummaryWindow extends Overlay { // Dispute object of other trade peer. The dispute field is the one from which we opened the close dispute window. private Optional peersDisputeOptional; private String role; - private Label delayedPayoutTxStatus; private TextArea summaryNotesTextArea; private ChangeListener customRadioButtonSelectedListener; private ChangeListener reasonToggleSelectionListener; private InputTextField buyerPayoutAmountInputTextField, sellerPayoutAmountInputTextField; - private ChangeListener buyerPayoutAmountListener, sellerPayoutAmountListener; + private ChangeListener buyerPayoutAmountListener, sellerPayoutAmountListener; private CheckBox isLoserPublisherCheckBox; private ChangeListener tradeAmountToggleGroupListener; @@ -139,22 +129,16 @@ public class DisputeSummaryWindow extends Overlay { @Inject public DisputeSummaryWindow(@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, + ArbitrationManager arbitrationManager, MediationManager mediationManager, - RefundManager refundManager, - TradeWalletService tradeWalletService, - BtcWalletService btcWalletService, - TxFeeEstimationService txFeeEstimationService, - MempoolService mempoolService, - DaoFacade daoFacade) { + XmrWalletService walletService, + TradeWalletService tradeWalletService) { this.formatter = formatter; + this.arbitrationManager = arbitrationManager; this.mediationManager = mediationManager; - this.refundManager = refundManager; + this.walletService = walletService; this.tradeWalletService = tradeWalletService; - this.btcWalletService = btcWalletService; - this.txFeeEstimationService = txFeeEstimationService; - this.mempoolService = mempoolService; - this.daoFacade = daoFacade; type = Type.Confirmation; } @@ -166,7 +150,6 @@ public class DisputeSummaryWindow extends Overlay { width = 1150; createGridPane(); addContent(); - checkDelayedPayoutTransaction(); display(); if (DevEnv.isDevMode()) { @@ -176,6 +159,12 @@ public class DisputeSummaryWindow extends Overlay { } } + public DisputeSummaryWindow onFinalizeDispute(Runnable finalizeDisputeHandler) { + this.finalizeDisputeHandlerOptional = Optional.of(finalizeDisputeHandler); + return this; + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Protected /////////////////////////////////////////////////////////////////////////////////////////// @@ -288,14 +277,24 @@ public class DisputeSummaryWindow extends Overlay { addConfirmationLabelLabel(gridPane, rowIndex, Res.get("shared.tradeId"), dispute.getShortTradeId(), Layout.TWICE_FIRST_ROW_DISTANCE); addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.openDate"), DisplayUtils.formatDateTime(dispute.getOpeningDate())); - role = dispute.getRoleString(); + if (dispute.isDisputeOpenerIsMaker()) { + if (dispute.isDisputeOpenerIsBuyer()) + role = Res.get("support.buyerOfferer"); + else + role = Res.get("support.sellerOfferer"); + } else { + if (dispute.isDisputeOpenerIsBuyer()) + role = Res.get("support.buyerTaker"); + else + role = Res.get("support.sellerTaker"); + } addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.role"), role); addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradeAmount"), formatter.formatCoinWithCode(contract.getTradeAmount())); addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradePrice"), - FormattingUtils.formatPrice(contract.getTradePrice())); + FormattingUtils.formatPrice(contract.getTradePrice())); addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradeVolume"), - DisplayUtils.formatVolumeWithCode(contract.getTradeVolume())); + DisplayUtils.formatVolumeWithCode(contract.getTradeVolume())); String securityDeposit = Res.getWithColAndCap("shared.buyer") + " " + formatter.formatCoinWithCode(contract.getOfferPayload().getBuyerSecurityDeposit()) + @@ -304,26 +303,6 @@ public class DisputeSummaryWindow extends Overlay { " " + formatter.formatCoinWithCode(contract.getOfferPayload().getSellerSecurityDeposit()); addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.securityDeposit"), securityDeposit); - - boolean isMediationDispute = getDisputeManager(dispute) instanceof MediationManager; - if (isMediationDispute) { - if (dispute.getTradePeriodEnd().getTime() > 0) { - String status = DisplayUtils.formatDateTime(dispute.getTradePeriodEnd()); - Label tradePeriodEnd = addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.tradePeriodEnd"), status).second; - if (dispute.getTradePeriodEnd().toInstant().isAfter(Instant.now())) { - tradePeriodEnd.getStyleClass().add("alert"); // highlight field when the trade period is still active - } - } - if (dispute.getExtraDataMap() != null && dispute.getExtraDataMap().size() > 0) { - String extraDataSummary = ""; - for (Map.Entry entry : dispute.getExtraDataMap().entrySet()) { - extraDataSummary += "[" + entry.getKey() + ":" + entry.getValue() + "] "; - } - addConfirmationLabelLabelWithCopyIcon(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.extraInfo"), extraDataSummary); - } - } else { - delayedPayoutTxStatus = addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.delayedPayoutStatus"), "Checking...").second; - } } private void addTradeAmountPayoutControls() { @@ -355,16 +334,16 @@ public class DisputeSummaryWindow extends Overlay { tradeAmountToggleGroupListener = (observable, oldValue, newValue) -> applyPayoutAmounts(newValue); tradeAmountToggleGroup.selectedToggleProperty().addListener(tradeAmountToggleGroupListener); - buyerPayoutAmountListener = (observable, oldValue, newValue) -> applyCustomAmounts(buyerPayoutAmountInputTextField, oldValue, newValue); - sellerPayoutAmountListener = (observable, oldValue, newValue) -> applyCustomAmounts(sellerPayoutAmountInputTextField, oldValue, newValue); + buyerPayoutAmountListener = (observable1, oldValue1, newValue1) -> applyCustomAmounts(buyerPayoutAmountInputTextField); + sellerPayoutAmountListener = (observable1, oldValue1, newValue1) -> applyCustomAmounts(sellerPayoutAmountInputTextField); customRadioButtonSelectedListener = (observable, oldValue, newValue) -> { buyerPayoutAmountInputTextField.setEditable(newValue); sellerPayoutAmountInputTextField.setEditable(newValue); if (newValue) { - buyerPayoutAmountInputTextField.focusedProperty().addListener(buyerPayoutAmountListener); - sellerPayoutAmountInputTextField.focusedProperty().addListener(sellerPayoutAmountListener); + buyerPayoutAmountInputTextField.textProperty().addListener(buyerPayoutAmountListener); + sellerPayoutAmountInputTextField.textProperty().addListener(sellerPayoutAmountListener); } else { removePayoutAmountListeners(); } @@ -374,10 +353,10 @@ public class DisputeSummaryWindow extends Overlay { private void removePayoutAmountListeners() { if (buyerPayoutAmountInputTextField != null && buyerPayoutAmountListener != null) - buyerPayoutAmountInputTextField.focusedProperty().removeListener(buyerPayoutAmountListener); + buyerPayoutAmountInputTextField.textProperty().removeListener(buyerPayoutAmountListener); if (sellerPayoutAmountInputTextField != null && sellerPayoutAmountListener != null) - sellerPayoutAmountInputTextField.focusedProperty().removeListener(sellerPayoutAmountListener); + sellerPayoutAmountInputTextField.textProperty().removeListener(sellerPayoutAmountListener); } @@ -405,66 +384,36 @@ public class DisputeSummaryWindow extends Overlay { } } - private void applyCustomAmounts(InputTextField inputTextField, boolean oldFocusValue, boolean newFocusValue) { - // We only apply adjustments at focus out, otherwise we cannot enter certain values if we update at each - // keystroke. - if (!oldFocusValue || newFocusValue) { - return; - } + private void applyCustomAmounts(InputTextField inputTextField) { +// // We only apply adjustments at focus out, otherwise we cannot enter certain values if we update at each +// // keystroke. +// if (!oldFocusValue || newFocusValue) { +// return; +// } Contract contract = dispute.getContract(); - boolean isMediationDispute = getDisputeManager(dispute) instanceof MediationManager; - // At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the - // mediated payout. For Refund agent cases we do not have that restriction. - Coin minRefundAtDispute = isMediationDispute ? Restrictions.getMinRefundAtMediatedDispute() : Coin.ZERO; - Offer offer = new Offer(contract.getOfferPayload()); - Coin totalAvailable = contract.getTradeAmount() + Coin available = contract.getTradeAmount() .add(offer.getBuyerSecurityDeposit()) .add(offer.getSellerSecurityDeposit()); - Coin availableForPayout = totalAvailable.subtract(minRefundAtDispute); - Coin enteredAmount = ParsingUtils.parseToCoin(inputTextField.getText(), formatter); - if (enteredAmount.compareTo(minRefundAtDispute) < 0) { - enteredAmount = minRefundAtDispute; - inputTextField.setText(formatter.formatCoin(enteredAmount)); + if (enteredAmount.compareTo(available) > 0) { + enteredAmount = available; + Coin finalEnteredAmount = enteredAmount; + inputTextField.setText(formatter.formatCoin(finalEnteredAmount)); } - if (enteredAmount.isPositive() && !Restrictions.isAboveDust(enteredAmount)) { - enteredAmount = Restrictions.getMinNonDustOutput(); - inputTextField.setText(formatter.formatCoin(enteredAmount)); - } - if (enteredAmount.compareTo(availableForPayout) > 0) { - enteredAmount = availableForPayout; - inputTextField.setText(formatter.formatCoin(enteredAmount)); - } - Coin counterPartAsCoin = totalAvailable.subtract(enteredAmount); + Coin counterPartAsCoin = available.subtract(enteredAmount); String formattedCounterPartAmount = formatter.formatCoin(counterPartAsCoin); Coin buyerAmount; Coin sellerAmount; if (inputTextField == buyerPayoutAmountInputTextField) { buyerAmount = enteredAmount; sellerAmount = counterPartAsCoin; - Coin sellerAmountFromField = ParsingUtils.parseToCoin(sellerPayoutAmountInputTextField.getText(), formatter); - Coin totalAmountFromFields = enteredAmount.add(sellerAmountFromField); - // RefundAgent can enter less then available - if (isMediationDispute || - totalAmountFromFields.compareTo(totalAvailable) > 0) { - sellerPayoutAmountInputTextField.setText(formattedCounterPartAmount); - } else { - sellerAmount = sellerAmountFromField; - } + sellerPayoutAmountInputTextField.setText(formattedCounterPartAmount); } else { sellerAmount = enteredAmount; buyerAmount = counterPartAsCoin; - Coin buyerAmountFromField = ParsingUtils.parseToCoin(buyerPayoutAmountInputTextField.getText(), formatter); - Coin totalAmountFromFields = enteredAmount.add(buyerAmountFromField); - // RefundAgent can enter less then available - if (isMediationDispute || - totalAmountFromFields.compareTo(totalAvailable) > 0) { - buyerPayoutAmountInputTextField.setText(formattedCounterPartAmount); - } else { - buyerAmount = buyerAmountFromField; - } + buyerPayoutAmountInputTextField.setText(formattedCounterPartAmount); } disputeResult.setBuyerPayoutAmount(buyerAmount); @@ -632,100 +581,138 @@ public class DisputeSummaryWindow extends Overlay { } private void addButtons(Contract contract) { - Tuple3 tuple = add2ButtonsWithBox(gridPane, ++rowIndex, - Res.get("disputeSummaryWindow.close.button"), - Res.get("shared.cancel"), 15, true); - Button closeTicketButton = tuple.first; - closeTicketButton.disableProperty().bind(Bindings.createBooleanBinding( - () -> tradeAmountToggleGroup.getSelectedToggle() == null - || summaryNotesTextArea.getText() == null - || summaryNotesTextArea.getText().length() == 0 - || !isPayoutAmountValid(), - tradeAmountToggleGroup.selectedToggleProperty(), - summaryNotesTextArea.textProperty(), - buyerPayoutAmountInputTextField.textProperty(), - sellerPayoutAmountInputTextField.textProperty())); + Tuple3 tuple = add2ButtonsWithBox(gridPane, ++rowIndex, + Res.get("disputeSummaryWindow.close.button"), + Res.get("shared.cancel"), 15, true); + Button closeTicketButton = tuple.first; + closeTicketButton.disableProperty().bind(Bindings.createBooleanBinding( + () -> tradeAmountToggleGroup.getSelectedToggle() == null + || summaryNotesTextArea.getText() == null + || summaryNotesTextArea.getText().length() == 0 + || !isPayoutAmountValid(), + tradeAmountToggleGroup.selectedToggleProperty(), + summaryNotesTextArea.textProperty(), + buyerPayoutAmountInputTextField.textProperty(), + sellerPayoutAmountInputTextField.textProperty())); - Button cancelButton = tuple.second; + Button cancelButton = tuple.second; - closeTicketButton.setOnAction(e -> { - if (dispute.getDepositTxSerialized() == null) { - log.warn("dispute.getDepositTxSerialized is null"); + closeTicketButton.setOnAction(e -> { + + // TODO (woodser): create disputed payout tx after showing payout tx confirmation, within doCloseIfValid() (see upstream/master) + if (!dispute.isMediationDispute()) { + try { + System.out.println(disputeResult); + XmrAddressEntry arbitratorAddressEntry = walletService.getArbitratorAddressEntry(); + MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(dispute.getTradeId()); + System.out.println("Arbitrator payout address entry: " + arbitratorAddressEntry.getAddressString()); + //dispute.getContract().getArbitratorPubKeyRing(); // TODO: support arbitrator pub key ring in contract? + //disputeResult.setArbitratorPubKey(arbitratorAddressEntry.getPubKey()); + + // TODO (woodser): don't send signed tx if opener is not co-signer? + // // determine if opener is co-signer + // boolean openerIsWinner = (contract.getBuyerPubKeyRing().equals(dispute.getTraderPubKeyRing()) && disputeResult.getWinner() == Winner.BUYER) || (contract.getSellerPubKeyRing().equals(dispute.getTraderPubKeyRing()) && disputeResult.getWinner() == Winner.SELLER); + // boolean openerIsCosigner = openerIsWinner || disputeResult.isLoserPublisher(); + // if (!openerIsCosigner) throw new RuntimeException("Need to query non-opener for updated multisig hex before creating tx"); + + // arbitrator creates and signs dispute payout tx if dispute is in context of opener, otherwise opener's peer must request payout tx by providing updated multisig hex + boolean isOpener = dispute.isOpener(); + System.out.println("Is dispute opener: " + isOpener); + if (isOpener) { + MoneroTxWallet arbitratorPayoutTx = ArbitrationManager.arbitratorCreatesDisputedPayoutTx(contract, dispute, disputeResult, multisigWallet); + System.out.println("Created arbitrator-signed payout tx: " + arbitratorPayoutTx); + if (arbitratorPayoutTx != null) disputeResult.setArbitratorSignedPayoutTxHex(arbitratorPayoutTx.getTxSet().getMultisigTxHex()); + } + + // send arbitrator's updated multisig hex with dispute result + disputeResult.setArbitratorUpdatedMultisigHex(multisigWallet.getMultisigHex()); + } catch (AddressFormatException e2) { + log.error("Error at close dispute", e2); return; } + } - if (dispute.getSupportType() == SupportType.REFUND && - peersDisputeOptional.isPresent() && - !peersDisputeOptional.get().isClosed()) { - showPayoutTxConfirmation(contract, - disputeResult, - () -> doCloseIfValid(closeTicketButton)); - } else { - doCloseIfValid(closeTicketButton); - } - }); +// // TODO (woodser): handle with showPayoutTxConfirmation() / doCloseIfValid() in order to have confirmation window (see upstream/master) + doClose(closeTicketButton); - cancelButton.setOnAction(e -> { - dispute.setDisputeResult(disputeResult); - checkNotNull(getDisputeManager(dispute)).requestPersistence(); - hide(); - }); +// if (dispute.getDepositTxSerialized() == null) { +// log.warn("dispute.getDepositTxSerialized is null"); +// return; +// } +// +// if (dispute.getSupportType() == SupportType.REFUND && +// peersDisputeOptional.isPresent() && +// !peersDisputeOptional.get().isClosed()) { +// showPayoutTxConfirmation(contract, +// disputeResult, +// () -> doCloseIfValid(closeTicketButton)); +// } else { +// doCloseIfValid(closeTicketButton); +// } + }); + + cancelButton.setOnAction(e -> { + dispute.setDisputeResult(disputeResult); + checkNotNull(getDisputeManager(dispute)).requestPersistence(); + hide(); + }); } private void showPayoutTxConfirmation(Contract contract, DisputeResult disputeResult, ResultHandler resultHandler) { - Coin buyerPayoutAmount = disputeResult.getBuyerPayoutAmount(); - String buyerPayoutAddressString = contract.getBuyerPayoutAddressString(); - Coin sellerPayoutAmount = disputeResult.getSellerPayoutAmount(); - String sellerPayoutAddressString = contract.getSellerPayoutAddressString(); - Coin outputAmount = buyerPayoutAmount.add(sellerPayoutAmount); - Tuple2 feeTuple = txFeeEstimationService.getEstimatedFeeAndTxVsize(outputAmount, btcWalletService); - Coin fee = feeTuple.first; - Integer txVsize = feeTuple.second; - double feePerVbyte = CoinUtil.getFeePerVbyte(fee, txVsize); - double vkb = txVsize / 1000d; - Coin inputAmount = outputAmount.add(fee); - String buyerDetails = ""; - if (buyerPayoutAmount.isPositive()) { - buyerDetails = Res.get("disputeSummaryWindow.close.txDetails.buyer", - formatter.formatCoinWithCode(buyerPayoutAmount), - buyerPayoutAddressString); - } - String sellerDetails = ""; - if (sellerPayoutAmount.isPositive()) { - sellerDetails = Res.get("disputeSummaryWindow.close.txDetails.seller", - formatter.formatCoinWithCode(sellerPayoutAmount), - sellerPayoutAddressString); - } - if (outputAmount.isPositive()) { - new Popup().width(900) - .headLine(Res.get("disputeSummaryWindow.close.txDetails.headline")) - .confirmation(Res.get("disputeSummaryWindow.close.txDetails", - formatter.formatCoinWithCode(inputAmount), - buyerDetails, - sellerDetails, - formatter.formatCoinWithCode(fee), - feePerVbyte, - vkb)) - .actionButtonText(Res.get("shared.yes")) - .onAction(() -> { - doPayout(buyerPayoutAmount, - sellerPayoutAmount, - fee, - buyerPayoutAddressString, - sellerPayoutAddressString, - resultHandler); - }) - .closeButtonText(Res.get("shared.cancel")) - .show(); - } else { - // No payout will be made - new Popup().headLine(Res.get("disputeSummaryWindow.close.noPayout.headline")) - .confirmation(Res.get("disputeSummaryWindow.close.noPayout.text")) - .actionButtonText(Res.get("shared.yes")) - .onAction(resultHandler::handleResult) - .closeButtonText(Res.get("shared.cancel")) - .show(); - } + throw new RuntimeException("DisputeSummaryWindow.showPayoutTxConfimration() needs updated for XMR"); +// Coin buyerPayoutAmount = disputeResult.getBuyerPayoutAmount(); +// String buyerPayoutAddressString = contract.getBuyerPayoutAddressString(); +// Coin sellerPayoutAmount = disputeResult.getSellerPayoutAmount(); +// String sellerPayoutAddressString = contract.getSellerPayoutAddressString(); +// Coin outputAmount = buyerPayoutAmount.add(sellerPayoutAmount); +// Tuple2 feeTuple = txFeeEstimationService.getEstimatedFeeAndTxSize(outputAmount, feeService, btcWalletService); +// Coin fee = feeTuple.first; +// Integer txSize = feeTuple.second; +// double feePerByte = CoinUtil.getFeePerByte(fee, txSize); +// double kb = txSize / 1000d; +// Coin inputAmount = outputAmount.add(fee); +// String buyerDetails = ""; +// if (buyerPayoutAmount.isPositive()) { +// buyerDetails = Res.get("disputeSummaryWindow.close.txDetails.buyer", +// formatter.formatCoinWithCode(buyerPayoutAmount), +// buyerPayoutAddressString); +// } +// String sellerDetails = ""; +// if (sellerPayoutAmount.isPositive()) { +// sellerDetails = Res.get("disputeSummaryWindow.close.txDetails.seller", +// formatter.formatCoinWithCode(sellerPayoutAmount), +// sellerPayoutAddressString); +// } +// if (outputAmount.isPositive()) { +// new Popup().width(900) +// .headLine(Res.get("disputeSummaryWindow.close.txDetails.headline")) +// .confirmation(Res.get("disputeSummaryWindow.close.txDetails", +// formatter.formatCoinWithCode(inputAmount), +// buyerDetails, +// sellerDetails, +// formatter.formatCoinWithCode(fee), +// feePerByte, +// kb)) +// .actionButtonText(Res.get("shared.yes")) +// .onAction(() -> { +// doPayout(buyerPayoutAmount, +// sellerPayoutAmount, +// fee, +// buyerPayoutAddressString, +// sellerPayoutAddressString, +// resultHandler); +// }) +// .closeButtonText(Res.get("shared.cancel")) +// .show(); +// } else { +// // No payout will be made +// new Popup().headLine(Res.get("disputeSummaryWindow.close.noPayout.headline")) +// .confirmation(Res.get("disputeSummaryWindow.close.noPayout.text")) +// .actionButtonText(Res.get("shared.yes")) +// .onAction(resultHandler::handleResult) +// .closeButtonText(Res.get("shared.cancel")) +// .show(); +// } } private void doPayout(Coin buyerPayoutAmount, @@ -734,82 +721,82 @@ public class DisputeSummaryWindow extends Overlay { String buyerPayoutAddressString, String sellerPayoutAddressString, ResultHandler resultHandler) { - try { - Transaction tx = btcWalletService.createRefundPayoutTx(buyerPayoutAmount, - sellerPayoutAmount, - fee, - buyerPayoutAddressString, - sellerPayoutAddressString); - tradeWalletService.broadcastTx(tx, new TxBroadcaster.Callback() { - @Override - public void onSuccess(Transaction transaction) { - resultHandler.handleResult(); - } - - @Override - public void onFailure(TxBroadcastException exception) { - log.error("TxBroadcastException at doPayout", exception); - new Popup().error(exception.toString()).show(); - } - }); - } catch (InsufficientMoneyException | WalletException | TransactionVerificationException e) { - log.error("Exception at doPayout", e); - new Popup().error(e.toString()).show(); - } + throw new RuntimeException("DisputeSummaryWindow.doPayout() needs updated for XMR"); +// try { +// Transaction tx = btcWalletService.createRefundPayoutTx(buyerPayoutAmount, +// sellerPayoutAmount, +// fee, +// buyerPayoutAddressString, +// sellerPayoutAddressString); +// tradeWalletService.broadcastTx(tx, new TxBroadcaster.Callback() { +// @Override +// public void onSuccess(Transaction transaction) { +// resultHandler.handleResult(); +// } +// +// @Override +// public void onFailure(TxBroadcastException exception) { +// log.error("TxBroadcastException at doPayout", exception); +// new Popup().error(exception.toString()).show(); +// } +// }); +// } catch (InsufficientMoneyException | WalletException | TransactionVerificationException e) { +// log.error("Exception at doPayout", e); +// new Popup().error(e.toString()).show(); +// } } private void doCloseIfValid(Button closeTicketButton) { - var disputeManager = checkNotNull(getDisputeManager(dispute)); - try { - TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); - TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeManager.getDisputesAsObservableList()); - doClose(closeTicketButton); - } catch (TradeDataValidation.AddressException exception) { - String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); - String tradeId = dispute.getTradeId(); - - // For mediators we do not enforce that the case cannot be closed to stay flexible, - // but for refund agents we do. - if (disputeManager instanceof MediationManager) { - new Popup().width(900) - .warning(Res.get("support.warning.disputesWithInvalidDonationAddress", - addressAsString, - daoFacade.getAllDonationAddresses(), - tradeId, - Res.get("support.warning.disputesWithInvalidDonationAddress.mediator"))) - .onAction(() -> { - doClose(closeTicketButton); - }) - .actionButtonText(Res.get("shared.yes")) - .closeButtonText(Res.get("shared.no")) - .show(); - } else { - new Popup().width(900) - .warning(Res.get("support.warning.disputesWithInvalidDonationAddress", - addressAsString, - daoFacade.getAllDonationAddresses(), - tradeId, - Res.get("support.warning.disputesWithInvalidDonationAddress.refundAgent"))) - .show(); - } - } catch (TradeDataValidation.DisputeReplayException exception) { - if (disputeManager instanceof MediationManager) { - log.error("Closing of ticket failed as mediator", exception); - new Popup().width(900) - .warning(exception.getMessage()) - .onAction(() -> { - doClose(closeTicketButton); - }) - .actionButtonText(Res.get("shared.yes")) - .closeButtonText(Res.get("shared.no")) - .show(); - } else { - log.error("Closing of ticket failed", exception); - new Popup().width(900) - .warning(exception.getMessage()) - .show(); - } - } + throw new RuntimeException("DisputeSummaryWindow.doCloseIfValid() needs updated for XMR"); +// var disputeManager = checkNotNull(getDisputeManager(dispute)); +// try { +// TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); +// TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeManager.getDisputesAsObservableList()); +// doClose(closeTicketButton); +// } catch (TradeDataValidation.AddressException exception) { +// String addressAsString = dispute.getDonationAddressOfDelayedPayoutTx(); +// String tradeId = dispute.getTradeId(); +// +// // For mediators we do not enforce that the case cannot be closed to stay flexible, +// // but for refund agents we do. +// if (disputeManager instanceof MediationManager) { +// new Popup().width(900) +// .warning(Res.get("support.warning.disputesWithInvalidDonationAddress", +// addressAsString, +// daoFacade.getAllDonationAddresses(), +// tradeId, +// Res.get("support.warning.disputesWithInvalidDonationAddress.mediator"))) +// .onAction(() -> { +// doClose(closeTicketButton); +// }) +// .actionButtonText(Res.get("shared.yes")) +// .closeButtonText(Res.get("shared.no")) +// .show(); +// } else { +// new Popup().width(900) +// .warning(Res.get("support.warning.disputesWithInvalidDonationAddress", +// addressAsString, +// daoFacade.getAllDonationAddresses(), +// tradeId, +// Res.get("support.warning.disputesWithInvalidDonationAddress.refundAgent"))) +// .show(); +// } +// } catch (TradeDataValidation.DisputeReplayException exception) { +// if (disputeManager instanceof MediationManager) { +// new Popup().width(900) +// .warning(exception.getMessage()) +// .onAction(() -> { +// doClose(closeTicketButton); +// }) +// .actionButtonText(Res.get("shared.yes")) +// .closeButtonText(Res.get("shared.no")) +// .show(); +// } else { +// new Popup().width(900) +// .warning(exception.getMessage()) +// .show(); +// } +// } } private void doClose(Button closeTicketButton) { @@ -818,43 +805,58 @@ public class DisputeSummaryWindow extends Overlay { return; } - boolean isRefundAgent = disputeManager instanceof RefundManager; +// boolean isRefundAgent = disputeManager instanceof RefundManager; disputeResult.setLoserPublisher(isLoserPublisherCheckBox.isSelected()); disputeResult.setCloseDate(new Date()); dispute.setDisputeResult(disputeResult); dispute.setIsClosed(); - DisputeResult.Reason reason = disputeResult.getReason(); +// DisputeResult.Reason reason = disputeResult.getReason(); summaryNotesTextArea.textProperty().unbindBidirectional(disputeResult.summaryNotesProperty()); - String role = isRefundAgent ? Res.get("shared.refundAgent") : Res.get("shared.mediator"); - String agentNodeAddress = checkNotNull(disputeManager.getAgentNodeAddress(dispute)).getFullAddress(); - Contract contract = dispute.getContract(); - String currencyCode = contract.getOfferPayload().getCurrencyCode(); - String amount = formatter.formatCoinWithCode(contract.getTradeAmount()); - String textToSign = Res.get("disputeSummaryWindow.close.msg", - DisplayUtils.formatDateTime(disputeResult.getCloseDate()), - role, - agentNodeAddress, - dispute.getShortTradeId(), - currencyCode, - amount, - formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()), - formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()), - Res.get("disputeSummaryWindow.reason." + reason.name()), - disputeResult.summaryNotesProperty().get() - ); - if (reason == DisputeResult.Reason.OPTION_TRADE && - dispute.getChatMessages().size() > 1 && - dispute.getChatMessages().get(1).isSystemMessage()) { - textToSign += "\n" + dispute.getChatMessages().get(1).getMessage() + "\n"; - } + // TODO (woodser): not used for xmr? calls setArbitratorSignature() - String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign); +// String role = isRefundAgent ? Res.get("shared.refundAgent") : Res.get("shared.mediator"); +// String agentNodeAddress = checkNotNull(disputeManager.getAgentNodeAddress(dispute)).getFullAddress(); +// Contract contract = dispute.getContract(); +// String currencyCode = contract.getOfferPayload().getCurrencyCode(); +// String amount = formatter.formatCoinWithCode(contract.getTradeAmount()); +// +// +// String textToSign = Res.get("disputeSummaryWindow.close.msg", +// DisplayUtils.formatDateTime(disputeResult.getCloseDate()), +// role, +// agentNodeAddress, +// dispute.getShortTradeId(), +// currencyCode, +// amount, +// formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()), +// formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()), +// Res.get("disputeSummaryWindow.reason." + reason.name()), +// disputeResult.summaryNotesProperty().get() +// ); +// +// if (reason == DisputeResult.Reason.OPTION_TRADE && +// dispute.getChatMessages().size() > 1 && +// dispute.getChatMessages().get(1).isSystemMessage()) { +// textToSign += "\n" + dispute.getChatMessages().get(1).getMessage() + "\n"; +// } +// +// String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign); +// +// if (isRefundAgent) { +// summaryText += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration"); +// } else { +// summaryText += Res.get("disputeSummaryWindow.close.nextStepsForMediation"); +// } - if (isRefundAgent) { - summaryText += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration"); - } else { + String summaryText = Res.get("disputeSummaryWindow.close.msg", + DisplayUtils.formatDateTime(disputeResult.getCloseDate()), + formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()), + formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()), + disputeResult.summaryNotesProperty().get()); + + if (dispute.isMediationDispute()) { summaryText += Res.get("disputeSummaryWindow.close.nextStepsForMediation"); } @@ -877,19 +879,7 @@ public class DisputeSummaryWindow extends Overlay { } private DisputeManager> getDisputeManager(Dispute dispute) { - if (dispute.getSupportType() != null) { - switch (dispute.getSupportType()) { - case ARBITRATION: - return null; - case MEDIATION: - return mediationManager; - case TRADE: - break; - case REFUND: - return refundManager; - } - } - return null; + return dispute.isMediationDispute() ? mediationManager : arbitrationManager; } @@ -910,31 +900,25 @@ public class DisputeSummaryWindow extends Overlay { Coin buyerSecurityDeposit = offer.getBuyerSecurityDeposit(); Coin sellerSecurityDeposit = offer.getSellerSecurityDeposit(); Coin tradeAmount = contract.getTradeAmount(); - - boolean isMediationDispute = getDisputeManager(dispute) instanceof MediationManager; - // At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the - // mediated payout. For Refund agent cases we do not have that restriction. - Coin minRefundAtDispute = isMediationDispute ? Restrictions.getMinRefundAtMediatedDispute() : Coin.ZERO; - Coin maxPayoutAmount = tradeAmount - .add(buyerSecurityDeposit) - .add(sellerSecurityDeposit) - .subtract(minRefundAtDispute); - if (selectedTradeAmountToggle == buyerGetsTradeAmountRadioButton) { disputeResult.setBuyerPayoutAmount(tradeAmount.add(buyerSecurityDeposit)); disputeResult.setSellerPayoutAmount(sellerSecurityDeposit); disputeResult.setWinner(DisputeResult.Winner.BUYER); } else if (selectedTradeAmountToggle == buyerGetsAllRadioButton) { - disputeResult.setBuyerPayoutAmount(maxPayoutAmount); - disputeResult.setSellerPayoutAmount(minRefundAtDispute); + disputeResult.setBuyerPayoutAmount(tradeAmount + .add(buyerSecurityDeposit) + .add(sellerSecurityDeposit)); // TODO (woodser): apply min payout to incentivize loser (see post v1.1.7) + disputeResult.setSellerPayoutAmount(Coin.ZERO); disputeResult.setWinner(DisputeResult.Winner.BUYER); } else if (selectedTradeAmountToggle == sellerGetsTradeAmountRadioButton) { disputeResult.setBuyerPayoutAmount(buyerSecurityDeposit); disputeResult.setSellerPayoutAmount(tradeAmount.add(sellerSecurityDeposit)); disputeResult.setWinner(DisputeResult.Winner.SELLER); } else if (selectedTradeAmountToggle == sellerGetsAllRadioButton) { - disputeResult.setBuyerPayoutAmount(minRefundAtDispute); - disputeResult.setSellerPayoutAmount(maxPayoutAmount); + disputeResult.setBuyerPayoutAmount(Coin.ZERO); + disputeResult.setSellerPayoutAmount(tradeAmount + .add(sellerSecurityDeposit) + .add(buyerSecurityDeposit)); disputeResult.setWinner(DisputeResult.Winner.SELLER); } @@ -955,50 +939,20 @@ public class DisputeSummaryWindow extends Overlay { buyerPayoutAmountInputTextField.setText(formatter.formatCoin(buyerPayoutAmount)); sellerPayoutAmountInputTextField.setText(formatter.formatCoin(sellerPayoutAmount)); - boolean isMediationDispute = getDisputeManager(dispute) instanceof MediationManager; - // At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the - // mediated payout. For Refund agent cases we do not have that restriction. - Coin minRefundAtDispute = isMediationDispute ? Restrictions.getMinRefundAtMediatedDispute() : Coin.ZERO; - Coin maxPayoutAmount = tradeAmount - .add(buyerSecurityDeposit) - .add(sellerSecurityDeposit) - .subtract(minRefundAtDispute); - if (buyerPayoutAmount.equals(tradeAmount.add(buyerSecurityDeposit)) && sellerPayoutAmount.equals(sellerSecurityDeposit)) { buyerGetsTradeAmountRadioButton.setSelected(true); - } else if (buyerPayoutAmount.equals(maxPayoutAmount) && - sellerPayoutAmount.equals(minRefundAtDispute)) { + } else if (buyerPayoutAmount.equals(tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit)) && + sellerPayoutAmount.equals(Coin.ZERO)) { // TODO (woodser): apply min payout to incentivize loser (see post v1.1.7) buyerGetsAllRadioButton.setSelected(true); } else if (sellerPayoutAmount.equals(tradeAmount.add(sellerSecurityDeposit)) && buyerPayoutAmount.equals(buyerSecurityDeposit)) { sellerGetsTradeAmountRadioButton.setSelected(true); - } else if (sellerPayoutAmount.equals(maxPayoutAmount) - && buyerPayoutAmount.equals(minRefundAtDispute)) { + } else if (sellerPayoutAmount.equals(tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit)) + && buyerPayoutAmount.equals(Coin.ZERO)) { sellerGetsAllRadioButton.setSelected(true); } else { customRadioButton.setSelected(true); } } - - private void checkDelayedPayoutTransaction() { - if (dispute.getDelayedPayoutTxId() == null) - return; - mempoolService.checkTxIsConfirmed(dispute.getDelayedPayoutTxId(), (validator -> { - long confirms = validator.parseJsonValidateTx(); - log.info("Mempool check confirmation status of DelayedPayoutTxId returned: [{}]", confirms); - displayPayoutStatus(confirms); - })); - } - - private void displayPayoutStatus(long nConfirmStatus) { - if (delayedPayoutTxStatus != null) { - String status = Res.get("confidence.unknown"); - if (nConfirmStatus == 0) - status = Res.get("confidence.seen", 1); - else if (nConfirmStatus > 0) - status = Res.get("confidence.confirmed", nConfirmStatus); - delayedPayoutTxStatus.setText(status); - } - } } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ManualPayoutTxWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ManualPayoutTxWindow.java index 6686cf81..b25313ae 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ManualPayoutTxWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ManualPayoutTxWindow.java @@ -711,24 +711,25 @@ public class ManualPayoutTxWindow extends Overlay { } private void importFromMediationTicket(String tradeId) { - clearInputFields(); - Optional optionalDispute = mediationManager.findDispute(tradeId); - if (optionalDispute.isPresent()) { - Dispute dispute = optionalDispute.get(); - depositTxHex.setText(dispute.getDepositTxId()); - if (dispute.disputeResultProperty().get() != null) { - buyerPayoutAmount.setText(dispute.disputeResultProperty().get().getBuyerPayoutAmount().toPlainString()); - sellerPayoutAmount.setText(dispute.disputeResultProperty().get().getSellerPayoutAmount().toPlainString()); - } - buyerAddressString.setText(dispute.getContract().getBuyerPayoutAddressString()); - sellerAddressString.setText(dispute.getContract().getSellerPayoutAddressString()); - buyerPubKeyAsHex.setText(Utils.HEX.encode(dispute.getContract().getBuyerMultiSigPubKey())); - sellerPubKeyAsHex.setText(Utils.HEX.encode(dispute.getContract().getSellerMultiSigPubKey())); - // switch back to the inputs pane - hideAllPanes(); - inputsGridPane.setVisible(true); - UserThread.execute(() -> new Popup().warning("Ticket imported. You still need to enter the multisig amount and specify if it is a legacy Tx").show()); - } + throw new RuntimeException("ManualPayoutTxWindow.importFromMediationTicket() not adapted to XMR"); +// clearInputFields(); +// Optional optionalDispute = mediationManager.findDispute(tradeId); +// if (optionalDispute.isPresent()) { +// Dispute dispute = optionalDispute.get(); +// depositTxHex.setText(dispute.getDepositTxId()); +// if (dispute.disputeResultProperty().get() != null) { +// buyerPayoutAmount.setText(dispute.disputeResultProperty().get().getBuyerPayoutAmount().toPlainString()); +// sellerPayoutAmount.setText(dispute.disputeResultProperty().get().getSellerPayoutAmount().toPlainString()); +// } +// buyerAddressString.setText(dispute.getContract().getBuyerPayoutAddressString()); +// sellerAddressString.setText(dispute.getContract().getSellerPayoutAddressString()); +// buyerPubKeyAsHex.setText(Utils.HEX.encode(dispute.getContract().getBuyerMultiSigPubKey())); +// sellerPubKeyAsHex.setText(Utils.HEX.encode(dispute.getContract().getSellerMultiSigPubKey())); +// // switch back to the inputs pane +// hideAllPanes(); +// inputsGridPane.setVisible(true); +// UserThread.execute(() -> new Popup().warning("Ticket imported. You still need to enter the multisig amount and specify if it is a legacy Tx").show()); +// } } private String generateSignature() { diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index be2d1d34..fb6a5f68 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -46,9 +46,6 @@ import bisq.network.p2p.NodeAddress; import bisq.common.UserThread; import bisq.common.util.Tuple3; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.Utils; - import javax.inject.Inject; import javax.inject.Named; @@ -199,6 +196,12 @@ public class TradeDetailsWindow extends Overlay { trade.getAssetTxProofResult() != null && trade.getAssetTxProofResult() != AssetTxProofResult.UNDEFINED; + if (trade.getTakerFeeTxId() != null) + rows++; + if (trade.getMakerDepositTx() != null) + rows++; + if (trade.getTakerDepositTx() != null) + rows++; if (trade.getPayoutTx() != null) rows++; boolean showDisputedTx = arbitrationManager.findOwnDispute(trade.getId()).isPresent() && @@ -287,25 +290,16 @@ public class TradeDetailsWindow extends Overlay { addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.makerFeeTxId"), offer.getOfferFeePaymentTxId()); addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.takerFeeTxId"), trade.getTakerFeeTxId()); - String depositTxId = trade.getDepositTxId(); - Transaction depositTx = trade.getDepositTx(); - String depositTxIdFromTx = depositTx != null ? depositTx.getTxId().toString() : null; - TxIdTextField depositTxIdTextField = addLabelTxIdTextField(gridPane, ++rowIndex, - Res.get("shared.depositTransactionId"), depositTxId).second; - if (depositTxId == null || !depositTxId.equals(depositTxIdFromTx)) { - depositTxIdTextField.getTextField().setId("address-text-field-error"); - log.error("trade.getDepositTxId() and trade.getDepositTx().getTxId().toString() are not the same. " + - "trade.getDepositTxId()={}, trade.getDepositTx().getTxId().toString()={}, depositTx={}", - depositTxId, depositTxIdFromTx, depositTx); - } - - Transaction delayedPayoutTx = trade.getDelayedPayoutTx(btcWalletService); - String delayedPayoutTxString = delayedPayoutTx != null ? delayedPayoutTx.getTxId().toString() : null; - addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.delayedPayoutTxId"), delayedPayoutTxString); + if (trade.getMakerDepositTx() != null) + addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.depositTransactionId"), // TODO (woodser): separate UI labels for deposit tx ids + trade.getMakerDepositTx().getHash()); + if (trade.getTakerDepositTx() != null) + addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.depositTransactionId"), // TODO (woodser): separate UI labels for deposit tx ids + trade.getTakerDepositTx().getHash()); if (trade.getPayoutTx() != null) addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.payoutTxId"), - trade.getPayoutTx().getTxId().toString()); + trade.getPayoutTx().getHash()); if (showDisputedTx) addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.disputedPayoutTxId"), arbitrationManager.findOwnDispute(trade.getId()).get().getDisputePayoutTxId()); @@ -347,21 +341,18 @@ public class TradeDetailsWindow extends Overlay { textArea.setText(trade.getContractAsJson()); String data = "Contract as json:\n"; data += trade.getContractAsJson(); - data += "\n\nOther detail data:"; - data += "\n\nBuyerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getBuyerMultiSigPubKey()); - data += "\nSellerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getSellerMultiSigPubKey()); if (CurrencyUtil.isFiatCurrency(offer.getCurrencyCode())) { data += "\n\nBuyersAccountAge: " + buyersAccountAge; data += "\nSellersAccountAge: " + sellersAccountAge; } - if (depositTx != null) { - String depositTxAsHex = Utils.HEX.encode(depositTx.bitcoinSerialize(true)); - data += "\n\nRaw deposit transaction as hex:\n" + depositTxAsHex; - } + // TODO (woodser): include maker and taker deposit tx hex in contract? +// if (depositTx != null) { +// String depositTxAsHex = Utils.HEX.encode(depositTx.bitcoinSerialize(true)); +// data += "\n\nRaw deposit transaction as hex:\n" + depositTxAsHex; +// } - data += "\n\nSelected mediator: " + DisputeAgentLookupMap.getKeyBaseUserName(contract.getMediatorNodeAddress().getFullAddress()); - data += "\nSelected arbitrator (refund agent): " + DisputeAgentLookupMap.getKeyBaseUserName(contract.getRefundAgentNodeAddress().getFullAddress()); + data += "\n\nSelected arbitrator: " + DisputeAgentLookupMap.getKeyBaseUserName(contract.getArbitratorNodeAddress().getFullAddress()); textArea.setText(data); textArea.setPrefHeight(50); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java index bb7e4266..f4fec230 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java @@ -433,7 +433,10 @@ public class ClosedTradesView extends ActivatableViewAndModel selectedItemProperty = new SimpleObjectProperty<>(); - public final StringProperty txId = new SimpleStringProperty(); + public final StringProperty makerTxId = new SimpleStringProperty(); + public final StringProperty takerTxId = new SimpleStringProperty(); @Getter private final TraderChatManager traderChatManager; @@ -132,31 +128,29 @@ public class PendingTradesDataModel extends ActivatableDataModel { @Inject public PendingTradesDataModel(TradeManager tradeManager, - BtcWalletService btcWalletService, + XmrWalletService xmrWalletService, PubKeyRing pubKeyRing, + ArbitrationManager arbitrationManager, MediationManager mediationManager, - RefundManager refundManager, TraderChatManager traderChatManager, Preferences preferences, P2PService p2PService, WalletsSetup walletsSetup, AccountAgeWitnessService accountAgeWitnessService, - DaoFacade daoFacade, Navigation navigation, WalletPasswordWindow walletPasswordWindow, NotificationCenter notificationCenter, OfferUtil offerUtil) { this.tradeManager = tradeManager; - this.btcWalletService = btcWalletService; + this.xmrWalletService = xmrWalletService; this.pubKeyRing = pubKeyRing; + this.arbitrationManager = arbitrationManager; this.mediationManager = mediationManager; - this.refundManager = refundManager; this.traderChatManager = traderChatManager; this.preferences = preferences; this.p2PService = p2PService; this.walletsSetup = walletsSetup; this.accountAgeWitnessService = accountAgeWitnessService; - this.daoFacade = daoFacade; this.navigation = navigation; this.walletPasswordWindow = walletPasswordWindow; this.notificationCenter = notificationCenter; @@ -290,10 +284,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { return Coin.ZERO; } } else { - if (trade.isCurrencyForTakerFeeBtc()) - return trade.getTakerFee(); - else - return Coin.ZERO; // getTradeFeeAsBsq is used for BSQ + return trade.getTakerFee(); } } else { log.error("Trade is null at getTotalFees"); @@ -316,10 +307,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { return Coin.ZERO; } } else { - if (trade.isCurrencyForTakerFeeBtc()) - return trade.getTxFee().multiply(3); - else - return trade.getTxFee().multiply(3).subtract(trade.getTakerFee()); // BSQ will be used as part of the miner fee + return trade.getTxFee().multiply(3); } } else { log.error("Trade is null at getTotalFees"); @@ -343,10 +331,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { return Coin.ZERO; } } else { - if (trade.isCurrencyForTakerFeeBtc()) - return Coin.ZERO; // getTradeFeeInBTC is used for BTC - else - return trade.getTakerFee(); + return Coin.ZERO; // getTradeFeeInBTC is used for BTC } } else { log.error("Trade is null at getTotalFees"); @@ -414,15 +399,18 @@ public class PendingTradesDataModel extends ActivatableDataModel { return; } - Transaction depositTx = selectedTrade.getDepositTx(); + MoneroTxWallet makerDepositTx = selectedTrade.getMakerDepositTx(); + MoneroTxWallet takerDepositTx = selectedTrade.getTakerDepositTx(); String tradeId = selectedTrade.getId(); tradeStateChangeListener = (observable, oldValue, newValue) -> { - if (depositTx != null) { - txId.set(depositTx.getTxId().toString()); + if (makerDepositTx != null && takerDepositTx != null) { // TODO (woodser): this treats separate deposit ids as one unit, being both available or unavailable + makerTxId.set(makerDepositTx.getHash()); + takerTxId.set(takerDepositTx.getHash()); notificationCenter.setSelectedTradeId(tradeId); selectedTrade.stateProperty().removeListener(tradeStateChangeListener); } else { - txId.set(""); + makerTxId.set(""); + takerTxId.set(""); } }; selectedTrade.stateProperty().addListener(tradeStateChangeListener); @@ -434,15 +422,18 @@ public class PendingTradesDataModel extends ActivatableDataModel { } isMaker = tradeManager.isMyOffer(offer); - if (depositTx != null) { - txId.set(depositTx.getTxId().toString()); + if (makerDepositTx != null && takerDepositTx != null) { + makerTxId.set(makerDepositTx.getHash()); + takerTxId.set(takerDepositTx.getHash()); } else { - txId.set(""); + makerTxId.set(""); + takerTxId.set(""); } notificationCenter.setSelectedTradeId(tradeId); } else { selectedTrade = null; - txId.set(""); + makerTxId.set(""); + takerTxId.set(""); notificationCenter.setSelectedTradeId(null); } selectedItemProperty.set(item); @@ -455,72 +446,96 @@ public class PendingTradesDataModel extends ActivatableDataModel { return; } - doOpenDispute(isSupportTicket, trade.getDepositTx()); + doOpenDispute(isSupportTicket, trade); } - private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { - // We do not support opening a dispute if the deposit tx is null. Traders have to use the support channel at keybase - // in such cases. The mediators or arbitrators could not help anyway with a payout in such cases. - if (depositTx == null) { - log.error("Deposit tx must not be null"); - new Popup().instruction(Res.get("portfolio.pending.error.depositTxNull")).show(); - return; - } - String depositTxId = depositTx.getTxId().toString(); + private void doOpenDispute(boolean isSupportTicket, Trade trade) { + if (trade == null) { + log.warn("trade is null at doOpenDispute"); + return; + } - Trade trade = getTrade(); - if (trade == null) { - log.warn("trade is null at doOpenDispute"); - return; + // We do not support opening a dispute if the deposit tx is null. Traders have to use the support channel at keybase + // in such cases. The mediators or arbitrators could not help anyway with a payout in such cases. + String depositTxId = null; + if (isMaker) { + if (trade.getMakerDepositTxId() == null) { + log.error("Deposit tx must not be null"); + new Popup().instruction(Res.get("portfolio.pending.error.depositTxNull")).show(); + return; } - - Offer offer = trade.getOffer(); - if (offer == null) { - log.warn("offer is null at doOpenDispute"); - return; + depositTxId = trade.getMakerDepositTxId(); + } else { + if (trade.getTakerDepositTxId() == null) { + log.error("Deposit tx must not be null"); + new Popup().instruction(Res.get("portfolio.pending.error.depositTxNull")).show(); + return; } + depositTxId = trade.getTakerDepositTxId(); + } - if (!GUIUtil.isBootstrappedOrShowPopup(p2PService)) { - return; - } + Offer offer = trade.getOffer(); + if (offer == null) { + log.warn("offer is null at doOpenDispute"); + return; + } - byte[] payoutTxSerialized = null; - String payoutTxHashAsString = null; - Transaction payoutTx = trade.getPayoutTx(); - if (payoutTx != null) { - payoutTxSerialized = payoutTx.bitcoinSerialize(); - payoutTxHashAsString = payoutTx.getTxId().toString(); - } - Trade.DisputeState disputeState = trade.getDisputeState(); - DisputeManager> disputeManager; - boolean useMediation; - boolean useRefundAgent; - // In case we re-open a dispute we allow Trade.DisputeState.MEDIATION_REQUESTED - useMediation = disputeState == Trade.DisputeState.NO_DISPUTE || disputeState == Trade.DisputeState.MEDIATION_REQUESTED; - // In case we re-open a dispute we allow Trade.DisputeState.REFUND_REQUESTED - useRefundAgent = disputeState == Trade.DisputeState.MEDIATION_CLOSED || disputeState == Trade.DisputeState.REFUND_REQUESTED; + if (!GUIUtil.isBootstrappedOrShowPopup(p2PService)) { + return; + } - AtomicReference donationAddressString = new AtomicReference<>(""); - Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); - try { - TradeDataValidation.validateDelayedPayoutTx(trade, - delayedPayoutTx, - daoFacade, - btcWalletService, - donationAddressString::set); - } catch (TradeDataValidation.ValidationException e) { - // The peer sent us an invalid donation address. We do not return here as we don't want to break - // mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get - // a popup displayed to react. - log.error("DelayedPayoutTxValidation failed. {}", e.toString()); + byte[] payoutTxSerialized = null; + String payoutTxHashAsString = null; + MoneroTxWallet payoutTx = trade.getPayoutTx(); + MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(trade.getId()); + String updatedMultisigHex = multisigWallet.getMultisigHex(); + if (payoutTx != null) { +// payoutTxSerialized = payoutTx.bitcoinSerialize(); // TODO (woodser): no need to pass serialized txs for xmr +// payoutTxHashAsString = payoutTx.getHashAsString(); + } + Trade.DisputeState disputeState = trade.getDisputeState(); + DisputeManager> disputeManager; + boolean useMediation; + boolean useArbitration; + // If mediation is not activated we use arbitration + if (false) { // TODO (woodser): use mediation for xmr? if (MediationManager.isMediationActivated()) { + // In case we re-open a dispute we allow Trade.DisputeState.MEDIATION_REQUESTED or + useMediation = disputeState == Trade.DisputeState.NO_DISPUTE || disputeState == Trade.DisputeState.MEDIATION_REQUESTED; + // in case of arbitration disputeState == Trade.DisputeState.ARBITRATION_REQUESTED + useArbitration = disputeState == Trade.DisputeState.MEDIATION_CLOSED || disputeState == Trade.DisputeState.DISPUTE_REQUESTED; + } else { + useMediation = false; + useArbitration = true; + } - if (useRefundAgent) { - // We don't allow to continue and publish payout tx and open refund agent case. - // In case it was caused by some bug we want to prevent a wrong payout. In case its a scam attempt we - // want to protect the refund agent. - return; - } - } +// if (useMediation) { +// // If no dispute state set we start with mediation +// disputeManager = mediationManager; +// PubKeyRing mediatorPubKeyRing = trade.getMediatorPubKeyRing(); +// checkNotNull(mediatorPubKeyRing, "mediatorPubKeyRing must not be null"); +// byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); // TODO (woodser): no serialized txs in xmr +// String depositTxHashAsString = null; // depositTx.getHashAsString(); // TODO (woodser): two deposit txs for dispute +// Dispute dispute = new Dispute(new Date().getTime(), +// trade.getId(), +// pubKeyRing.hashCode(), // traderId +// true, +// (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker, +// isMaker, +// pubKeyRing, +// trade.getDate().getTime(), +// trade.getMaxTradePeriodDate().getTime(), +// trade.getContract(), +// trade.getContractHash(), +// depositTxSerialized, +// payoutTxSerialized, +// depositTxHashAsString, +// payoutTxHashAsString, +// trade.getContractAsJson(), +// trade.getMakerContractSignature(), +// trade.getTakerContractSignature(), +// mediatorPubKeyRing, +// isSupportTicket, +// SupportType.MEDIATION); ResultHandler resultHandler; if (useMediation) { @@ -529,10 +544,11 @@ public class PendingTradesDataModel extends ActivatableDataModel { disputeManager = mediationManager; PubKeyRing mediatorPubKeyRing = trade.getMediatorPubKeyRing(); checkNotNull(mediatorPubKeyRing, "mediatorPubKeyRing must not be null"); - byte[] depositTxSerialized = depositTx.bitcoinSerialize(); + byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); // TODO (woodser): no serialized txs in xmr Dispute dispute = new Dispute(new Date().getTime(), trade.getId(), pubKeyRing.hashCode(), // traderId + true, (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker, isMaker, pubKeyRing, @@ -553,98 +569,61 @@ public class PendingTradesDataModel extends ActivatableDataModel { dispute.setExtraData("counterCurrencyTxId", trade.getCounterCurrencyTxId()); dispute.setExtraData("counterCurrencyExtraData", trade.getCounterCurrencyExtraData()); - dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get()); - if (delayedPayoutTx != null) { - dispute.setDelayedPayoutTxId(delayedPayoutTx.getTxId().toString()); - } - trade.setDisputeState(Trade.DisputeState.MEDIATION_REQUESTED); - sendOpenDisputeMessage(disputeManager, resultHandler, dispute); + sendOpenNewDisputeMessage(dispute, false, disputeManager, updatedMultisigHex); tradeManager.requestPersistence(); - } else if (useRefundAgent) { - resultHandler = () -> navigation.navigateTo(MainView.class, SupportView.class, RefundClientView.class); + } else if (useArbitration) { + // Only if we have completed mediation we allow arbitration + disputeManager = arbitrationManager; + PubKeyRing arbitratorPubKeyRing = trade.getArbitratorPubKeyRing(); + checkNotNull(arbitratorPubKeyRing, "arbitratorPubKeyRing must not be null"); + byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); TODO (woodser) + String depositTxHashAsString = null; // depositTx.getHashAsString(); TODO (woodser) + Dispute dispute = new Dispute(new Date().getTime(), + trade.getId(), + pubKeyRing.hashCode(), // traderId, + true, + (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker, + isMaker, + pubKeyRing, + trade.getDate().getTime(), + trade.getMaxTradePeriodDate().getTime(), + trade.getContract(), + trade.getContractHash(), + depositTxSerialized, + payoutTxSerialized, + depositTxHashAsString, + payoutTxHashAsString, + trade.getContractAsJson(), + trade.getMakerContractSignature(), + trade.getTakerContractSignature(), + arbitratorPubKeyRing, + isSupportTicket, + SupportType.ARBITRATION); - if (delayedPayoutTx == null) { - log.error("Delayed payout tx is missing"); - return; - } - - // We only require for refund agent a confirmed deposit tx. For mediation we tolerate a unconfirmed tx as - // no harm can be done to the mediator (refund agent who would accept a invalid deposit tx might reimburse - // the traders but the funds never have been spent). - TransactionConfidence confidenceForTxId = btcWalletService.getConfidenceForTxId(depositTxId); - if (confidenceForTxId == null || confidenceForTxId.getConfidenceType() != TransactionConfidence.ConfidenceType.BUILDING) { - log.error("Confidence for deposit tx must be BUILDING, confidenceForTxId={}", confidenceForTxId); - new Popup().instruction(Res.get("portfolio.pending.error.depositTxNotConfirmed")).show(); - return; - } - - long lockTime = delayedPayoutTx.getLockTime(); - int bestChainHeight = btcWalletService.getBestChainHeight(); - long remaining = lockTime - bestChainHeight; - if (remaining > 0) { - new Popup().instruction(Res.get("portfolio.pending.timeLockNotOver", - FormattingUtils.getDateFromBlockHeight(remaining), remaining)) - .show(); - return; - } - - disputeManager = refundManager; - PubKeyRing refundAgentPubKeyRing = trade.getRefundAgentPubKeyRing(); - checkNotNull(refundAgentPubKeyRing, "refundAgentPubKeyRing must not be null"); - byte[] depositTxSerialized = depositTx.bitcoinSerialize(); - String depositTxHashAsString = depositTx.getTxId().toString(); - Dispute dispute = new Dispute(new Date().getTime(), - trade.getId(), - pubKeyRing.hashCode(), // traderId - (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker, - isMaker, - pubKeyRing, - trade.getDate().getTime(), - trade.getMaxTradePeriodDate().getTime(), - trade.getContract(), - trade.getContractHash(), - depositTxSerialized, - payoutTxSerialized, - depositTxHashAsString, - payoutTxHashAsString, - trade.getContractAsJson(), - trade.getMakerContractSignature(), - trade.getTakerContractSignature(), - refundAgentPubKeyRing, - isSupportTicket, - SupportType.REFUND); - dispute.setExtraData("counterCurrencyTxId", trade.getCounterCurrencyTxId()); - dispute.setExtraData("counterCurrencyExtraData", trade.getCounterCurrencyExtraData()); - - String tradeId = dispute.getTradeId(); - mediationManager.findDispute(tradeId) - .ifPresent(mediatorsDispute -> { - DisputeResult mediatorsDisputeResult = mediatorsDispute.getDisputeResultProperty().get(); - ChatMessage mediatorsResultMessage = mediatorsDisputeResult.getChatMessage(); - if (mediatorsResultMessage != null) { - String mediatorAddress = Res.get("support.mediatorsAddress", - mediatorsDispute.getContract().getRefundAgentNodeAddress().getFullAddress()); - String message = mediatorAddress + "\n\n" + mediatorsResultMessage.getMessage(); - dispute.setMediatorsDisputeResult(message); - } - }); - - dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get()); - dispute.setDelayedPayoutTxId(delayedPayoutTx.getTxId().toString()); - trade.setDisputeState(Trade.DisputeState.REFUND_REQUESTED); - - ((DisputeProtocol) tradeManager.getTradeProtocol(trade)).onPublishDelayedPayoutTx(() -> { - log.info("DelayedPayoutTx published and message sent to peer"); - sendOpenDisputeMessage(disputeManager, resultHandler, dispute); - }, - errorMessage -> new Popup().error(errorMessage).show()); + trade.setDisputeState(Trade.DisputeState.DISPUTE_REQUESTED); + sendOpenNewDisputeMessage(dispute, false, disputeManager, updatedMultisigHex); } else { log.warn("Invalid dispute state {}", disputeState.name()); } tradeManager.requestPersistence(); } + private void sendOpenNewDisputeMessage(Dispute dispute, boolean reOpen, DisputeManager> disputeManager, String senderMultisigHex) { + disputeManager.sendOpenNewDisputeMessage(dispute, reOpen, senderMultisigHex, + () -> navigation.navigateTo(MainView.class, SupportView.class, ArbitrationClientView.class), (errorMessage, throwable) -> { + if ((throwable instanceof DisputeAlreadyOpenException)) { + errorMessage += "\n\n" + Res.get("portfolio.pending.openAgainDispute.msg"); + new Popup().warning(errorMessage) + .actionButtonText(Res.get("portfolio.pending.openAgainDispute.button")) + .onAction(() -> sendOpenNewDisputeMessage(dispute, true, disputeManager, senderMultisigHex)) + .closeButtonText(Res.get("shared.cancel")).show(); + } else { + new Popup().warning(errorMessage).show(); + } + }); + } + public boolean isReadyForTxBroadcast() { return GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup); } @@ -660,28 +639,5 @@ public class PendingTradesDataModel extends ActivatableDataModel { public boolean isSignWitnessTrade() { return accountAgeWitnessService.isSignWitnessTrade(selectedTrade); } - - private void sendOpenDisputeMessage(DisputeManager> disputeManager, - ResultHandler resultHandler, - Dispute dispute) { - disputeManager.sendOpenNewDisputeMessage(dispute, - false, - resultHandler, - (errorMessage, throwable) -> { - if ((throwable instanceof DisputeAlreadyOpenException)) { - errorMessage += "\n\n" + Res.get("portfolio.pending.openAgainDispute.msg"); - new Popup().warning(errorMessage) - .actionButtonText(Res.get("portfolio.pending.openAgainDispute.button")) - .onAction(() -> disputeManager.sendOpenNewDisputeMessage(dispute, - true, - resultHandler, - (e, t) -> log.error(e))) - .closeButtonText(Res.get("shared.cancel")) - .show(); - } else { - new Popup().warning(errorMessage).show(); - } - }); - } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java index 1c2c219e..5dabe5cb 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java @@ -408,14 +408,12 @@ public class PendingTradesView extends ActivatableViewAndModel { - mempoolStatus.setValue(txValidator.isFail() ? 0 : 1); - if (txValidator.isFail()) { - String errorMessage = "Validation of Taker Tx returned: " + txValidator.toString(); - log.warn(errorMessage); - // prompt user to open mediation - if (trade.getDisputeState() == Trade.DisputeState.NO_DISPUTE) { - UserThread.runAfter(() -> { - Popup popup = new Popup(); - popup.headLine(Res.get("portfolio.pending.openSupportTicket.headline")) - .message(Res.get("portfolio.pending.invalidTx", errorMessage)) - .actionButtonText(Res.get("portfolio.pending.openSupportTicket.headline")) - .onAction(dataModel::onOpenSupportTicket) - .closeButtonText(Res.get("shared.cancel")) - .onClose(popup::hide) - .show(); - }, 100, TimeUnit.MILLISECONDS); - } - } - })); + log.warn("PendingTradesViewModel.checkTakerFeeTx() needs adapted to XMR"); + return; // TODO (woodser): PendingTradesViewModel.checkTakerFeeTx() needs adapted to XMR, use common TradeDataValidation utility +// mempoolStatus.setValue(-1); +// mempoolService.validateOfferTakerTx(trade, (txValidator -> { +// mempoolStatus.setValue(txValidator.isFail() ? 0 : 1); +// if (txValidator.isFail()) { +// String errorMessage = "Validation of Taker Tx returned: " + txValidator.toString(); +// log.warn(errorMessage); +// // prompt user to open mediation +// if (trade.getDisputeState() == Trade.DisputeState.NO_DISPUTE) { +// UserThread.runAfter(() -> { +// Popup popup = new Popup(); +// popup.headLine(Res.get("portfolio.pending.openSupportTicket.headline")) +// .message(Res.get("portfolio.pending.invalidTx", errorMessage)) +// .actionButtonText(Res.get("portfolio.pending.openSupportTicket.headline")) +// .onAction(dataModel::onOpenSupportTicket) +// .closeButtonText(Res.get("shared.cancel")) +// .onClose(popup::hide) +// .show(); +// }, 100, TimeUnit.MILLISECONDS); +// } +// } +// })); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -329,7 +327,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel errorMessageListener; protected Label infoLabel; private Popup acceptMediationResultPopup; private BootstrapListener bootstrapListener; private TradeSubView.ChatCallback chatCallback; - private final NewBestBlockListener newBestBlockListener; private ChangeListener pendingTradesInitializedListener; @@ -170,21 +169,32 @@ public abstract class TradeStepView extends AnchorPane { } }; - newBestBlockListener = block -> { - checkIfLockTimeIsOver(); - }; +// newBestBlockListener = block -> { +// checkIfLockTimeIsOver(); +// }; } public void activate() { - if (txIdTextField != null) { - if (txIdSubscription != null) - txIdSubscription.unsubscribe(); + if (makerTxIdTextField != null) { + if (makerTxIdSubscription != null) + makerTxIdSubscription.unsubscribe(); - txIdSubscription = EasyBind.subscribe(model.dataModel.txId, id -> { + makerTxIdSubscription = EasyBind.subscribe(model.dataModel.makerTxId, id -> { if (!id.isEmpty()) - txIdTextField.setup(id); + makerTxIdTextField.setup(id); else - txIdTextField.cleanup(); + makerTxIdTextField.cleanup(); + }); + } + if (takerTxIdTextField != null) { + if (takerTxIdSubscription != null) + takerTxIdSubscription.unsubscribe(); + + takerTxIdSubscription = EasyBind.subscribe(model.dataModel.takerTxId, id -> { + if (!id.isEmpty()) + takerTxIdTextField.setup(id); + else + takerTxIdTextField.cleanup(); }); } trade.errorMessageProperty().addListener(errorMessageListener); @@ -241,8 +251,8 @@ public abstract class TradeStepView extends AnchorPane { } protected void onPendingTradesInitialized() { - model.dataModel.btcWalletService.addNewBestBlockListener(newBestBlockListener); - checkIfLockTimeIsOver(); +// model.dataModel.xmrWalletService.addNewBestBlockListener(newBestBlockListener); // TODO (woodser): different listener? +// checkIfLockTimeIsOver(); } private void registerSubscriptions() { @@ -274,38 +284,38 @@ public abstract class TradeStepView extends AnchorPane { } public void deactivate() { - if (txIdSubscription != null) - txIdSubscription.unsubscribe(); + if (makerTxIdSubscription != null) + makerTxIdSubscription.unsubscribe(); + if (takerTxIdSubscription != null) + takerTxIdSubscription.unsubscribe(); - if (txIdTextField != null) - txIdTextField.cleanup(); + if (makerTxIdTextField != null) + makerTxIdTextField.cleanup(); + if (takerTxIdTextField != null) + takerTxIdTextField.cleanup(); - if (errorMessageListener != null) - trade.errorMessageProperty().removeListener(errorMessageListener); + if (errorMessageListener != null) + trade.errorMessageProperty().removeListener(errorMessageListener); - if (disputeStateSubscription != null) - disputeStateSubscription.unsubscribe(); + if (disputeStateSubscription != null) + disputeStateSubscription.unsubscribe(); - if (mediationResultStateSubscription != null) - mediationResultStateSubscription.unsubscribe(); + if (mediationResultStateSubscription != null) + mediationResultStateSubscription.unsubscribe(); - if (tradePeriodStateSubscription != null) - tradePeriodStateSubscription.unsubscribe(); + if (tradePeriodStateSubscription != null) + tradePeriodStateSubscription.unsubscribe(); - if (clockListener != null) - model.clockWatcher.removeListener(clockListener); + if (clockListener != null) + model.clockWatcher.removeListener(clockListener); - if (tradeStepInfo != null) - tradeStepInfo.setOnAction(null); + if (tradeStepInfo != null) + tradeStepInfo.setOnAction(null); - if (newBestBlockListener != null) { - model.dataModel.btcWalletService.removeNewBestBlockListener(newBestBlockListener); - } - - if (acceptMediationResultPopup != null) { - acceptMediationResultPopup.hide(); - acceptMediationResultPopup = null; - } + if (acceptMediationResultPopup != null) { + acceptMediationResultPopup.hide(); + acceptMediationResultPopup = null; + } } /////////////////////////////////////////////////////////////////////////////////////////// @@ -322,19 +332,35 @@ public abstract class TradeStepView extends AnchorPane { Res.get("portfolio.pending.tradeInformation")); GridPane.setColumnSpan(tradeInfoTitledGroupBg, 2); - final Tuple3 labelTxIdTextFieldVBoxTuple3 = + // maker + final Tuple3 labelMakerTxIdTextFieldVBoxTuple3 = + addTopLabelTxIdTextField(gridPane, gridRow, + Res.get("shared.depositTransactionId"), // TODO (woodser): need separate labels for maker and taker deposit tx ids + Layout.COMPACT_FIRST_ROW_DISTANCE); + + GridPane.setColumnSpan(labelMakerTxIdTextFieldVBoxTuple3.third, 2); + makerTxIdTextField = labelMakerTxIdTextFieldVBoxTuple3.second; + + String makerId = model.dataModel.makerTxId.get(); + if (!makerId.isEmpty()) + makerTxIdTextField.setup(makerId); + else + makerTxIdTextField.cleanup(); + + // taker + final Tuple3 labelTakerTxIdTextFieldVBoxTuple3 = addTopLabelTxIdTextField(gridPane, gridRow, Res.get("shared.depositTransactionId"), Layout.COMPACT_FIRST_ROW_DISTANCE); - GridPane.setColumnSpan(labelTxIdTextFieldVBoxTuple3.third, 2); - txIdTextField = labelTxIdTextFieldVBoxTuple3.second; + GridPane.setColumnSpan(labelTakerTxIdTextFieldVBoxTuple3.third, 2); + takerTxIdTextField = labelTakerTxIdTextFieldVBoxTuple3.second; - String id = model.dataModel.txId.get(); - if (!id.isEmpty()) - txIdTextField.setup(id); + String takerId = model.dataModel.takerTxId.get(); + if (!takerId.isEmpty()) + takerTxIdTextField.setup(takerId); else - txIdTextField.cleanup(); + takerTxIdTextField.cleanup(); if (model.dataModel.getTrade() != null) { checkNotNull(model.dataModel.getTrade().getOffer(), "Offer must not be null in TradeStepView"); @@ -439,6 +465,33 @@ public abstract class TradeStepView extends AnchorPane { switch (disputeState) { case NO_DISPUTE: break; + case DISPUTE_REQUESTED: + if (tradeStepInfo != null) { + tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText); + } + applyOnDisputeOpened(); + + ownDispute = model.dataModel.arbitrationManager.findOwnDispute(trade.getId()); + ownDispute.ifPresent(dispute -> { + if (tradeStepInfo != null) + tradeStepInfo.setState(TradeStepInfo.State.IN_ARBITRATION_SELF_REQUESTED); + }); + + break; + case DISPUTE_STARTED_BY_PEER: + if (tradeStepInfo != null) { + tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText); + } + applyOnDisputeOpened(); + + ownDispute = model.dataModel.arbitrationManager.findOwnDispute(trade.getId()); + ownDispute.ifPresent(dispute -> { + if (tradeStepInfo != null) + tradeStepInfo.setState(TradeStepInfo.State.IN_ARBITRATION_PEER_REQUESTED); + }); + break; + case DISPUTE_CLOSED: + break; case MEDIATION_REQUESTED: if (tradeStepInfo != null) { tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText); @@ -465,53 +518,55 @@ public abstract class TradeStepView extends AnchorPane { }); break; case MEDIATION_CLOSED: - if (tradeStepInfo != null) { - tradeStepInfo.setOnAction(e -> { - updateMediationResultState(false); - }); - } + if (tradeStepInfo != null) { + tradeStepInfo.setOnAction(e -> { + updateMediationResultState(false); + }); + } - if (tradeStepInfo != null) { - tradeStepInfo.setState(TradeStepInfo.State.MEDIATION_RESULT); - } + if (tradeStepInfo != null) { + tradeStepInfo.setState(TradeStepInfo.State.MEDIATION_RESULT); + } - updateMediationResultState(true); - break; + updateMediationResultState(true); + break; case REFUND_REQUESTED: - if (tradeStepInfo != null) { - tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText); - } - applyOnDisputeOpened(); - - ownDispute = model.dataModel.refundManager.findOwnDispute(trade.getId()); - ownDispute.ifPresent(dispute -> { - if (tradeStepInfo != null) - tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_SELF_REQUESTED); - }); - - if (acceptMediationResultPopup != null) { - acceptMediationResultPopup.hide(); - acceptMediationResultPopup = null; - } - - break; + throw new RuntimeException("Unhandled case: " + Trade.DisputeState.REFUND_REQUESTED); +// if (tradeStepInfo != null) { +// tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText); +// } +// applyOnDisputeOpened(); +// +// ownDispute = model.dataModel.refundManager.findOwnDispute(trade.getId()); +// ownDispute.ifPresent(dispute -> { +// if (tradeStepInfo != null) +// tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_SELF_REQUESTED); +// }); +// +// if (acceptMediationResultPopup != null) { +// acceptMediationResultPopup.hide(); +// acceptMediationResultPopup = null; +// } +// +// break; case REFUND_REQUEST_STARTED_BY_PEER: - if (tradeStepInfo != null) { - tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText); - } - applyOnDisputeOpened(); - - ownDispute = model.dataModel.refundManager.findOwnDispute(trade.getId()); - ownDispute.ifPresent(dispute -> { - if (tradeStepInfo != null) - tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_PEER_REQUESTED); - }); - - if (acceptMediationResultPopup != null) { - acceptMediationResultPopup.hide(); - acceptMediationResultPopup = null; - } - break; + throw new RuntimeException("Unhandled case: " + Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER); +// if (tradeStepInfo != null) { +// tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText); +// } +// applyOnDisputeOpened(); +// +// ownDispute = model.dataModel.refundManager.findOwnDispute(trade.getId()); +// ownDispute.ifPresent(dispute -> { +// if (tradeStepInfo != null) +// tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_PEER_REQUESTED); +// }); +// +// if (acceptMediationResultPopup != null) { +// acceptMediationResultPopup.hide(); +// acceptMediationResultPopup = null; +// } +// break; case REFUND_REQUEST_CLOSED: break; default: @@ -587,16 +642,20 @@ public abstract class TradeStepView extends AnchorPane { return; } - if (trade.getDepositTx() == null) { - log.error("trade.getDepositTx() was null at openMediationResultPopup. " + - "We add the trade to failed trades. TradeId={}", trade.getId()); - new Popup().warning(Res.get("portfolio.pending.mediationResult.error.depositTxNull")).show(); - return; - } else if (trade.getDelayedPayoutTx() == null) { - log.error("trade.getDelayedPayoutTx() was null at openMediationResultPopup. " + - "We add the trade to failed trades. TradeId={}", trade.getId()); - new Popup().warning(Res.get("portfolio.pending.mediationResult.error.delayedPayoutTxNull")).show(); - return; + if (trade instanceof MakerTrade && trade.getMakerDepositTx() == null) { + log.error("trade.getMakerDepositTx() was null at openMediationResultPopup. " + + "We add the trade to failed trades. TradeId={}", trade.getId()); + //model.dataModel.addTradeToFailedTrades(); // TODO (woodser): new way to move trade to failed trades? + model.dataModel.onMoveInvalidTradeToFailedTrades(trade);; + new Popup().warning(Res.get("portfolio.pending.mediationResult.error.depositTxNull")).show(); // TODO (woodser): separate error messages for maker/taker + return; + } else if (trade instanceof TakerTrade && trade.getTakerDepositTx() == null) { + log.error("trade.getTakerDepositTx() was null at openMediationResultPopup. " + + "We add the trade to failed trades. TradeId={}", trade.getId()); + //model.dataModel.addTradeToFailedTrades(); + model.dataModel.onMoveInvalidTradeToFailedTrades(trade);; + new Popup().warning(Res.get("portfolio.pending.mediationResult.error.depositTxNull")).show(); + return; } DisputeResult disputeResult = optionalDispute.get().getDisputeResultProperty().get(); @@ -607,10 +666,6 @@ public abstract class TradeStepView extends AnchorPane { String myPayoutAmount = isMyRoleBuyer ? buyerPayoutAmount : sellerPayoutAmount; String peersPayoutAmount = isMyRoleBuyer ? sellerPayoutAmount : buyerPayoutAmount; - long lockTime = trade.getDelayedPayoutTx().getLockTime(); - int bestChainHeight = model.dataModel.btcWalletService.getBestChainHeight(); - long remaining = lockTime - bestChainHeight; - String actionButtonText = hasSelfAccepted() ? Res.get("portfolio.pending.mediationResult.popup.alreadyAccepted") : Res.get("shared.accept"); @@ -627,15 +682,15 @@ public abstract class TradeStepView extends AnchorPane { case SIG_MSG_IN_MAILBOX: case SIG_MSG_SEND_FAILED: message = Res.get("portfolio.pending.mediationResult.popup.selfAccepted.lockTimeOver", - FormattingUtils.getDateFromBlockHeight(remaining), - lockTime); + "N/A", // TODO (woodser): no timelocked tx in xmr, so part of popup message is n/a + -1); break; default: message = Res.get("portfolio.pending.mediationResult.popup.info", myPayoutAmount, peersPayoutAmount, - FormattingUtils.getDateFromBlockHeight(remaining), - lockTime); + "N/A", // TODO (woodser): no timelocked tx in xmr, so part of popup message is n/a + -1); break; } @@ -720,19 +775,19 @@ public abstract class TradeStepView extends AnchorPane { } } - private void checkIfLockTimeIsOver() { - if (trade.getDisputeState() == Trade.DisputeState.MEDIATION_CLOSED) { - Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); - if (delayedPayoutTx != null) { - long lockTime = delayedPayoutTx.getLockTime(); - int bestChainHeight = model.dataModel.btcWalletService.getBestChainHeight(); - long remaining = lockTime - bestChainHeight; - if (remaining <= 0) { - openMediationResultPopup(Res.get("portfolio.pending.mediationResult.popup.headline", trade.getShortId())); - } - } - } - } +// private void checkIfLockTimeIsOver() { +// if (trade.getDisputeState() == Trade.DisputeState.MEDIATION_CLOSED) { +// Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); +// if (delayedPayoutTx != null) { +// long lockTime = delayedPayoutTx.getLockTime(); +// int bestChainHeight = model.dataModel.btcWalletService.getBestChainHeight(); +// long remaining = lockTime - bestChainHeight; +// if (remaining <= 0) { +// openMediationResultPopup(Res.get("portfolio.pending.mediationResult.popup.headline", trade.getShortId())); +// } +// } +// } +// } protected void checkForTimeout() { long unconfirmedHours = Duration.between(trade.getTakeOfferDate().toInstant(), Instant.now()).toHours(); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java index 809b9a5c..12f315fc 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep1View.java @@ -17,12 +17,10 @@ package bisq.desktop.main.portfolio.pendingtrades.steps.buyer; -import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.core.locale.Res; -import bisq.core.trade.TradeDataValidation; public class BuyerStep1View extends TradeStepView { @@ -37,8 +35,8 @@ public class BuyerStep1View extends TradeStepView { @Override protected void onPendingTradesInitialized() { super.onPendingTradesInitialized(); - validatePayoutTx(); - validateDepositInputs(); + //validatePayoutTx(); // TODO (woodser): no payout tx in xmr integration, do something else? + //validateDepositInputs(); checkForTimeout(); } @@ -80,32 +78,32 @@ public class BuyerStep1View extends TradeStepView { // Private /////////////////////////////////////////////////////////////////////////////////////////// - private void validatePayoutTx() { - try { - TradeDataValidation.validateDelayedPayoutTx(trade, - trade.getDelayedPayoutTx(), - model.dataModel.daoFacade, - model.dataModel.btcWalletService); - } catch (TradeDataValidation.MissingTxException ignore) { - // We don't react on those errors as a failed trade might get listed initially but getting removed from the - // trade manager after initPendingTrades which happens after activate might be called. - } catch (TradeDataValidation.ValidationException e) { - if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { - new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show(); - } - } - } - - // Verify that deposit tx inputs are matching the trade fee txs outputs. - private void validateDepositInputs() { - try { - TradeDataValidation.validateDepositInputs(trade); - } catch (TradeDataValidation.ValidationException e) { - if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { - new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show(); - } - } - } +// private void validatePayoutTx() { +// try { +// TradeDataValidation.validateDelayedPayoutTx(trade, +// trade.getDelayedPayoutTx(), +// model.dataModel.daoFacade, +// model.dataModel.btcWalletService); +// } catch (TradeDataValidation.MissingTxException ignore) { +// // We don't react on those errors as a failed trade might get listed initially but getting removed from the +// // trade manager after initPendingTrades which happens after activate might be called. +// } catch (TradeDataValidation.ValidationException e) { +// if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { +// new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show(); +// } +// } +// } +// +// // Verify that deposit tx inputs are matching the trade fee txs outputs. +// private void validateDepositInputs() { +// try { +// TradeDataValidation.validateDepositInputs(trade); +// } catch (TradeDataValidation.ValidationException e) { +// if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { +// new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show(); +// } +// } +// } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index fd6e456b..1d0dc90b 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -81,7 +81,6 @@ import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.USPostalMoneyOrderAccountPayload; import bisq.core.payment.payload.WesternUnionAccountPayload; import bisq.core.trade.Trade; -import bisq.core.trade.TradeDataValidation; import bisq.core.user.DontShowAgainLookup; import bisq.common.Timer; @@ -202,7 +201,7 @@ public class BuyerStep2View extends TradeStepView { @Override protected void onPendingTradesInitialized() { super.onPendingTradesInitialized(); - validatePayoutTx(); + //validatePayoutTx(); // TODO (woodser): no payout tx in xmr integration, do something else? model.checkTakerFeeTx(trade); } @@ -608,19 +607,19 @@ public class BuyerStep2View extends TradeStepView { } } - private void validatePayoutTx() { - try { - TradeDataValidation.validateDelayedPayoutTx(trade, - trade.getDelayedPayoutTx(), - model.dataModel.daoFacade, - model.dataModel.btcWalletService); - } catch (TradeDataValidation.MissingTxException ignore) { - // We don't react on those errors as a failed trade might get listed initially but getting removed from the - // trade manager after initPendingTrades which happens after activate might be called. - } catch (TradeDataValidation.ValidationException e) { - if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { - new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show(); - } - } - } +// private void validatePayoutTx() { +// try { +// TradeDataValidation.validateDelayedPayoutTx(trade, +// trade.getDelayedPayoutTx(), +// model.dataModel.daoFacade, +// model.dataModel.btcWalletService); +// } catch (TradeDataValidation.MissingTxException ignore) { +// // We don't react on those errors as a failed trade might get listed initially but getting removed from the +// // trade manager after initPendingTrades which happens after activate might be called. +// } catch (TradeDataValidation.ValidationException e) { +// if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { +// new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show(); +// } +// } +// } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index b6dbf081..82ed8415 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -31,26 +31,17 @@ import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.desktop.util.Layout; -import bisq.core.btc.exceptions.AddressEntryException; -import bisq.core.btc.exceptions.InsufficientFundsException; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.Restrictions; +import bisq.core.btc.model.XmrAddressEntry; import bisq.core.locale.Res; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.DontShowAgainLookup; -import bisq.core.util.coin.CoinFormatter; -import bisq.core.util.coin.CoinUtil; -import bisq.core.util.validation.BtcAddressValidator; import bisq.common.UserThread; import bisq.common.app.DevEnv; import bisq.common.handlers.FaultHandler; import bisq.common.handlers.ResultHandler; -import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; import com.jfoenix.controls.JFXBadge; @@ -144,7 +135,7 @@ public class BuyerStep4View extends TradeStepView { HBox hBox = new HBox(); hBox.setSpacing(10); - useSavingsWalletButton = new AutoTooltipButton(Res.get("portfolio.pending.step5_buyer.moveToBisqWallet")); + useSavingsWalletButton = new AutoTooltipButton(Res.get("portfolio.pending.step5_buyer.moveToHavenoWallet")); useSavingsWalletButton.setDefaultButton(true); useSavingsWalletButton.getStyleClass().add("action-button"); Label label = new AutoTooltipLabel(Res.get("shared.OR")); @@ -194,63 +185,64 @@ public class BuyerStep4View extends TradeStepView { } private void reviewWithdrawal() { - Coin amount = trade.getPayoutAmount(); - BtcWalletService walletService = model.dataModel.btcWalletService; - - AddressEntry fromAddressesEntry = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT); - String fromAddresses = fromAddressesEntry.getAddressString(); - String toAddresses = withdrawAddressTextField.getText(); - if (new BtcAddressValidator().validate(toAddresses).isValid) { - Coin balance = walletService.getBalanceForAddress(fromAddressesEntry.getAddress()); - try { - Transaction feeEstimationTransaction = walletService.getFeeEstimationTransaction(fromAddresses, toAddresses, amount, AddressEntry.Context.TRADE_PAYOUT); - Coin fee = feeEstimationTransaction.getFee(); - Coin receiverAmount = amount.subtract(fee); - if (balance.isZero()) { - new Popup().warning(Res.get("portfolio.pending.step5_buyer.alreadyWithdrawn")).show(); - model.dataModel.tradeManager.onTradeCompleted(trade); - } else { - if (toAddresses.isEmpty()) { - validateWithdrawAddress(); - } else if (Restrictions.isAboveDust(receiverAmount)) { - CoinFormatter formatter = model.btcFormatter; - int txVsize = feeEstimationTransaction.getVsize(); - double feePerVbyte = CoinUtil.getFeePerVbyte(fee, txVsize); - double vkb = txVsize / 1000d; - String recAmount = formatter.formatCoinWithCode(receiverAmount); - new Popup().headLine(Res.get("portfolio.pending.step5_buyer.confirmWithdrawal")) - .confirmation(Res.get("shared.sendFundsDetailsWithFee", - formatter.formatCoinWithCode(amount), - fromAddresses, - toAddresses, - formatter.formatCoinWithCode(fee), - feePerVbyte, - vkb, - recAmount)) - .actionButtonText(Res.get("shared.yes")) - .onAction(() -> doWithdrawal(amount, fee)) - .closeButtonText(Res.get("shared.cancel")) - .onClose(() -> { - useSavingsWalletButton.setDisable(false); - withdrawToExternalWalletButton.setDisable(false); - }) - .show(); - } else { - new Popup().warning(Res.get("portfolio.pending.step5_buyer.amountTooLow")).show(); - } - } - } catch (AddressFormatException e) { - validateWithdrawAddress(); - } catch (AddressEntryException e) { - log.error(e.getMessage()); - } catch (InsufficientFundsException e) { - log.error(e.getMessage()); - e.printStackTrace(); - new Popup().warning(e.getMessage()).show(); - } - } else { - new Popup().warning(Res.get("validation.btc.invalidAddress")).show(); - } + throw new RuntimeException("BuyerStep4View.reviewWithdrawal() not yet updated for XMR"); +// Coin amount = trade.getPayoutAmount(); +// BtcWalletService walletService = model.dataModel.btcWalletService; +// +// AddressEntry fromAddressesEntry = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT); +// String fromAddresses = fromAddressesEntry.getAddressString(); +// String toAddresses = withdrawAddressTextField.getText(); +// if (new BtcAddressValidator().validate(toAddresses).isValid) { +// Coin balance = walletService.getBalanceForAddress(fromAddressesEntry.getAddress()); +// try { +// Transaction feeEstimationTransaction = walletService.getFeeEstimationTransaction(fromAddresses, toAddresses, amount, AddressEntry.Context.TRADE_PAYOUT); +// Coin fee = feeEstimationTransaction.getFee(); +// Coin receiverAmount = amount.subtract(fee); +// if (balance.isZero()) { +// new Popup().warning(Res.get("portfolio.pending.step5_buyer.alreadyWithdrawn")).show(); +// model.dataModel.tradeManager.onTradeCompleted(trade); +// } else { +// if (toAddresses.isEmpty()) { +// validateWithdrawAddress(); +// } else if (Restrictions.isAboveDust(receiverAmount)) { +// CoinFormatter formatter = model.btcFormatter; +// int txVsize = feeEstimationTransaction.getVsize(); +// double feePerVbyte = CoinUtil.getFeePerVbyte(fee, txVsize); +// double vkb = txVsize / 1000d; +// String recAmount = formatter.formatCoinWithCode(receiverAmount); +// new Popup().headLine(Res.get("portfolio.pending.step5_buyer.confirmWithdrawal")) +// .confirmation(Res.get("shared.sendFundsDetailsWithFee", +// formatter.formatCoinWithCode(amount), +// fromAddresses, +// toAddresses, +// formatter.formatCoinWithCode(fee), +// feePerVbyte, +// vkb, +// recAmount)) +// .actionButtonText(Res.get("shared.yes")) +// .onAction(() -> doWithdrawal(amount, fee)) +// .closeButtonText(Res.get("shared.cancel")) +// .onClose(() -> { +// useSavingsWalletButton.setDisable(false); +// withdrawToExternalWalletButton.setDisable(false); +// }) +// .show(); +// } else { +// new Popup().warning(Res.get("portfolio.pending.step5_buyer.amountTooLow")).show(); +// } +// } +// } catch (AddressFormatException e) { +// validateWithdrawAddress(); +// } catch (AddressEntryException e) { +// log.error(e.getMessage()); +// } catch (InsufficientFundsException e) { +// log.error(e.getMessage()); +// e.printStackTrace(); +// new Popup().warning(e.getMessage()).show(); +// } +// } else { +// new Popup().warning(Res.get("validation.btc.invalidAddress")).show(); +// } } private void doWithdrawal(Coin amount, Coin fee) { @@ -264,12 +256,13 @@ public class BuyerStep4View extends TradeStepView { else new Popup().error(errorMessage).show(); }; - if (model.dataModel.btcWalletService.isEncrypted()) { - UserThread.runAfter(() -> model.dataModel.walletPasswordWindow.onAesKey(aesKey -> - doWithdrawRequest(toAddress, amount, fee, aesKey, resultHandler, faultHandler)) - .show(), 300, TimeUnit.MILLISECONDS); - } else - doWithdrawRequest(toAddress, amount, fee, null, resultHandler, faultHandler); + if (true) throw new RuntimeException("BuyerStep4View.doWithdrawal() not yet updated for XMR"); +// if (model.dataModel.btcWalletService.isEncrypted()) { +// UserThread.runAfter(() -> model.dataModel.walletPasswordWindow.onAesKey(aesKey -> +// doWithdrawRequest(toAddress, amount, fee, aesKey, resultHandler, faultHandler)) +// .show(), 300, TimeUnit.MILLISECONDS); +// } else +// doWithdrawRequest(toAddress, amount, fee, null, resultHandler, faultHandler); } private void doWithdrawRequest(String toAddress, @@ -296,7 +289,7 @@ public class BuyerStep4View extends TradeStepView { private void handleTradeCompleted() { useSavingsWalletButton.setDisable(true); withdrawToExternalWalletButton.setDisable(true); - model.dataModel.btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT); + model.dataModel.xmrWalletService.swapTradeEntryToAvailableEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT); openTradeFeedbackWindow(); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep1View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep1View.java index eb712934..50cf9b17 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep1View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep1View.java @@ -17,13 +17,14 @@ package bisq.desktop.main.portfolio.pendingtrades.steps.seller; -import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.core.locale.Res; -import bisq.core.trade.TradeDataValidation; +import lombok.extern.slf4j.Slf4j; + +@Slf4j public class SellerStep1View extends TradeStepView { /////////////////////////////////////////////////////////////////////////////////////////// @@ -37,7 +38,8 @@ public class SellerStep1View extends TradeStepView { @Override protected void onPendingTradesInitialized() { super.onPendingTradesInitialized(); - validateDepositInputs(); + //validateDepositInputs(); + log.warn("Need to validate fee and/or deposit txs in SellerStep1View for XMR?"); // TODO (woodser): need to validate fee and/or deposit txs in SellerStep1View? checkForTimeout(); } @@ -77,16 +79,16 @@ public class SellerStep1View extends TradeStepView { // Private /////////////////////////////////////////////////////////////////////////////////////////// - // Verify that deposit tx inputs are matching the trade fee txs outputs. - private void validateDepositInputs() { - try { - TradeDataValidation.validateDepositInputs(trade); - } catch (TradeDataValidation.ValidationException e) { - if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { - new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show(); - } - } - } +// // Verify that deposit tx inputs are matching the trade fee txs outputs. +// private void validateDepositInputs() { +// try { +// TradeDataValidation.validateDepositInputs(trade); +// } catch (TradeDataValidation.ValidationException e) { +// if (!model.dataModel.tradeManager.isAllowFaultyDelayedTxs()) { +// new Popup().warning(Res.get("portfolio.pending.invalidTx", e.getMessage())).show(); +// } +// } +// } } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java index 8ddec2de..9551b62a 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java @@ -33,7 +33,6 @@ import bisq.common.crypto.Hash; import bisq.common.crypto.Sig; import bisq.common.util.Utilities; -import java.security.KeyPair; import java.security.PublicKey; import lombok.extern.slf4j.Slf4j; @@ -49,23 +48,24 @@ public class DisputeSummaryVerification { public static String signAndApply(DisputeManager> disputeManager, DisputeResult disputeResult, String textToSign) { + throw new RuntimeException("DisputeSummaryVerification.signAndApply() not implemented"); - byte[] hash = Hash.getSha256Hash(textToSign); - KeyPair signatureKeyPair = disputeManager.getSignatureKeyPair(); - String sigAsHex; - try { - byte[] signature = Sig.sign(signatureKeyPair.getPrivate(), hash); - sigAsHex = Utilities.encodeToHex(signature); - disputeResult.setArbitratorSignature(signature); - } catch (CryptoException e) { - sigAsHex = "Signing failed"; - } - - return Res.get("disputeSummaryWindow.close.msgWithSig", - textToSign, - SEPARATOR1, - sigAsHex, - SEPARATOR2); +// byte[] hash = Hash.getSha256Hash(textToSign); +// KeyPair signatureKeyPair = disputeManager.getSignatureKeyPair(); +// String sigAsHex; +// try { +// byte[] signature = Sig.sign(signatureKeyPair.getPrivate(), hash); +// sigAsHex = Utilities.encodeToHex(signature); +// disputeResult.setArbitratorSignature(signature); +// } catch (CryptoException e) { +// sigAsHex = "Signing failed"; +// } +// +// return Res.get("disputeSummaryWindow.close.msgWithSig", +// textToSign, +// SEPARATOR1, +// sigAsHex, +// SEPARATOR2); } public static String verifySignature(String input, diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java index 08b16973..77bd4b2d 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/arbitration/ArbitratorView.java @@ -18,7 +18,6 @@ package bisq.desktop.main.support.dispute.agent.arbitration; import bisq.desktop.common.view.FxmlView; -import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.ContractWindow; import bisq.desktop.main.overlays.windows.DisputeSummaryWindow; import bisq.desktop.main.overlays.windows.TradeDetailsWindow; @@ -27,7 +26,6 @@ import bisq.desktop.main.support.dispute.agent.DisputeAgentView; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.alert.PrivateNotificationManager; import bisq.core.dao.DaoFacade; -import bisq.core.locale.Res; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeSession; @@ -96,11 +94,11 @@ public class ArbitratorView extends DisputeAgentView { // Only cases with protocolVersion 1 are candidates for legacy arbitration. // This code path is not tested and it is not assumed that it is still be used as old arbitrators would use // their old Bisq version if still cases are pending. - if (protocolVersion == 1) { +// if (protocolVersion == 1) { chatPopup.closeChat(); disputeSummaryWindow.show(dispute); - } else { - new Popup().warning(Res.get("support.wrongVersion", protocolVersion)).show(); - } +// } else { +// new Popup().warning(Res.get("support.wrongVersion", protocolVersion)).show(); +// } } } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java index 7f55c517..d771067e 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/mediation/MediationClientView.java @@ -113,7 +113,8 @@ public class MediationClientView extends DisputeClientView { @Override protected NodeAddress getAgentNodeAddress(Contract contract) { - return contract.getMediatorNodeAddress(); + throw new RuntimeException("MediationClientView.getAgentNodeAddress() not implementd for XMR"); + //return contract.getMediatorNodeAddress(); } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java index b622f25b..6aa41bd1 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/refund/RefundClientView.java @@ -81,7 +81,8 @@ public class RefundClientView extends DisputeClientView { @Override protected NodeAddress getAgentNodeAddress(Contract contract) { - return contract.getRefundAgentNodeAddress(); + throw new RuntimeException("RefundClientView.getAgentNodeAddress() not implementd for XMR"); + //return contract.getRefundAgentNodeAddress(); } @Override diff --git a/desktop/src/test/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsTest.java b/desktop/src/test/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsTest.java index 09c84516..7948fc76 100644 --- a/desktop/src/test/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsTest.java +++ b/desktop/src/test/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsTest.java @@ -17,28 +17,35 @@ package bisq.desktop.main.funds.transactions; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; -import org.bitcoinj.core.Transaction; - -import com.google.common.collect.Sets; +import com.google.common.collect.Lists; import javafx.collections.FXCollections; import java.util.Collections; -import java.util.Set; +import java.util.List; import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + + +import monero.wallet.model.MoneroTxWallet; public class DisplayedTransactionsTest { @Test public void testUpdate() { - Set transactions = Sets.newHashSet(mock(Transaction.class), mock(Transaction.class)); + List transactions = Lists.newArrayList(mock(MoneroTxWallet.class), mock(MoneroTxWallet.class)); - BtcWalletService walletService = mock(BtcWalletService.class); + XmrWalletService walletService = mock(XmrWalletService.class); when(walletService.getTransactions(false)).thenReturn(transactions); TransactionListItemFactory transactionListItemFactory = mock(TransactionListItemFactory.class, @@ -58,9 +65,9 @@ public class DisplayedTransactionsTest { @Test public void testUpdateWhenRepositoryIsEmpty() { - BtcWalletService walletService = mock(BtcWalletService.class); + XmrWalletService walletService = mock(XmrWalletService.class); when(walletService.getTransactions(false)) - .thenReturn(Collections.singleton(mock(Transaction.class))); + .thenReturn(Collections.singletonList(mock(MoneroTxWallet.class))); TradableRepository tradableRepository = mock(TradableRepository.class); when(tradableRepository.getAll()).thenReturn(FXCollections.emptyObservableSet()); diff --git a/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactoryTest.java b/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactoryTest.java index 86a62b28..01267790 100644 --- a/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactoryTest.java +++ b/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactoryTest.java @@ -22,13 +22,15 @@ import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.trade.Tradable; import bisq.core.trade.Trade; -import org.bitcoinj.core.Transaction; - import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; + + +import monero.wallet.model.MoneroTxWallet; + public class TransactionAwareTradableFactoryTest { @Test public void testCreateWhenNotOpenOfferOrTrade() { @@ -43,6 +45,6 @@ public class TransactionAwareTradableFactoryTest { TransactionAwareTradable tradable = factory.create(delegate); - assertFalse(tradable.isRelatedToTransaction(mock(Transaction.class))); + assertFalse(tradable.isRelatedToTransaction(mock(MoneroTxWallet.class))); } } diff --git a/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java b/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java index 4552c9bc..ab3e3d2b 100644 --- a/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java +++ b/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java @@ -17,14 +17,13 @@ package bisq.desktop.main.funds.transactions; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.trade.Trade; import org.bitcoinj.core.Sha256Hash; -import org.bitcoinj.core.Transaction; import javafx.collections.FXCollections; @@ -38,26 +37,30 @@ import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; + + +import monero.wallet.model.MoneroTxWallet; + public class TransactionAwareTradeTest { private static final Sha256Hash XID = Sha256Hash.wrap("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); - private Transaction transaction; + private MoneroTxWallet transaction; private ArbitrationManager arbitrationManager; private Trade delegate; private TransactionAwareTradable trade; private RefundManager refundManager; - private BtcWalletService btcWalletService; + private XmrWalletService xmrWalletService; @Before public void setUp() { - this.transaction = mock(Transaction.class); - when(transaction.getTxId()).thenReturn(XID); + this.transaction = mock(MoneroTxWallet.class); + when(transaction.getHash()).thenReturn(XID.toString()); delegate = mock(Trade.class, RETURNS_DEEP_STUBS); arbitrationManager = mock(ArbitrationManager.class, RETURNS_DEEP_STUBS); refundManager = mock(RefundManager.class, RETURNS_DEEP_STUBS); - btcWalletService = mock(BtcWalletService.class, RETURNS_DEEP_STUBS); - trade = new TransactionAwareTrade(delegate, arbitrationManager, refundManager, btcWalletService, null); + xmrWalletService = mock(XmrWalletService.class, RETURNS_DEEP_STUBS); + trade = new TransactionAwareTrade(delegate, arbitrationManager, refundManager, xmrWalletService, null); } @Test @@ -68,13 +71,19 @@ public class TransactionAwareTradeTest { @Test public void testIsRelatedToTransactionWhenPayoutTx() { - when(delegate.getPayoutTx().getTxId()).thenReturn(XID); + when(delegate.getPayoutTx().getHash()).thenReturn(XID.toString()); assertTrue(trade.isRelatedToTransaction(transaction)); } @Test - public void testIsRelatedToTransactionWhenDepositTx() { - when(delegate.getDepositTx().getTxId()).thenReturn(XID); + public void testIsRelatedToTransactionWhenMakerDepositTx() { + when(delegate.getMakerDepositTx().getHash()).thenReturn(XID.toString()); + assertTrue(trade.isRelatedToTransaction(transaction)); + } + + @Test + public void testIsRelatedToTransactionWhenTakerDepositTx() { + when(delegate.getTakerDepositTx().getHash()).thenReturn(XID.toString()); assertTrue(trade.isRelatedToTransaction(transaction)); } diff --git a/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java index 470c86b8..477ad29d 100644 --- a/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java @@ -137,7 +137,6 @@ public class TradesChartsViewModelTest { now.getTime(), null, null, - null, null)); set.add(new TradeStatistics3(offer.getCurrencyCode(), Price.parse("EUR", "500").getValue(), @@ -146,7 +145,6 @@ public class TradesChartsViewModelTest { now.getTime() + 100, null, null, - null, null)); set.add(new TradeStatistics3(offer.getCurrencyCode(), Price.parse("EUR", "600").getValue(), @@ -155,7 +153,6 @@ public class TradesChartsViewModelTest { now.getTime() + 200, null, null, - null, null)); set.add(new TradeStatistics3(offer.getCurrencyCode(), Price.parse("EUR", "580").getValue(), @@ -164,7 +161,6 @@ public class TradesChartsViewModelTest { now.getTime() + 300, null, null, - null, null)); CandleData candleData = model.getCandleData(model.roundToTick(now, TradesChartsViewModel.TickUnit.DAY).getTime(), set, 0); @@ -227,7 +223,6 @@ public class TradesChartsViewModelTest { t.date.getTime(), null, null, - null, null)) ); ObservableSet tradeStats = FXCollections.observableSet(set); diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java index 691da767..4129049b 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java @@ -1,7 +1,7 @@ package bisq.desktop.main.offer.createoffer; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.FiatCurrency; import bisq.core.locale.GlobalSettings; @@ -47,8 +47,8 @@ public class CreateOfferDataModelTest { GlobalSettings.setDefaultTradeCurrency(btc); Res.setup(); - AddressEntry addressEntry = mock(AddressEntry.class); - BtcWalletService btcWalletService = mock(BtcWalletService.class); + XmrAddressEntry addressEntry = mock(XmrAddressEntry.class); + XmrWalletService btcWalletService = mock(XmrWalletService.class); PriceFeedService priceFeedService = mock(PriceFeedService.class); FeeService feeService = mock(FeeService.class); CreateOfferService createOfferService = mock(CreateOfferService.class); diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java index f82c5636..14b9c924 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java @@ -23,14 +23,15 @@ import bisq.desktop.util.validation.FiatPriceValidator; import bisq.desktop.util.validation.SecurityDepositValidator; import bisq.core.account.witness.AccountAgeWitnessService; -import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.wallet.BsqWalletService; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.locale.Country; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.GlobalSettings; import bisq.core.locale.Res; import bisq.core.offer.CreateOfferService; +import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferUtil; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; @@ -60,7 +61,6 @@ import java.util.UUID; import org.junit.Before; import org.junit.Test; -import static bisq.core.offer.OfferPayload.Direction; import static bisq.desktop.maker.PreferenceMakers.empty; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -87,8 +87,8 @@ public class CreateOfferViewModelTest { final FiatPriceValidator fiatPriceValidator = new FiatPriceValidator(); FeeService feeService = mock(FeeService.class); - AddressEntry addressEntry = mock(AddressEntry.class); - BtcWalletService btcWalletService = mock(BtcWalletService.class); + XmrAddressEntry addressEntry = mock(XmrAddressEntry.class); + XmrWalletService xmrWalletService = mock(XmrWalletService.class); PriceFeedService priceFeedService = mock(PriceFeedService.class); User user = mock(User.class); PaymentAccount paymentAccount = mock(PaymentAccount.class); @@ -101,8 +101,8 @@ public class CreateOfferViewModelTest { OfferUtil offerUtil = mock(OfferUtil.class); var tradeStats = mock(TradeStatisticsManager.class); - when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); - when(btcWalletService.getBalanceForAddress(any())).thenReturn(Coin.valueOf(1000L)); + when(xmrWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); + when(xmrWalletService.getBalanceForAccount(any(Integer.class))).thenReturn(Coin.valueOf(1000L)); when(priceFeedService.updateCounterProperty()).thenReturn(new SimpleIntegerProperty()); when(priceFeedService.getMarketPrice(anyString())).thenReturn( new MarketPrice("USD", @@ -122,20 +122,20 @@ public class CreateOfferViewModelTest { when(tradeStats.getObservableTradeStatisticsSet()).thenReturn(FXCollections.observableSet()); CreateOfferDataModel dataModel = new CreateOfferDataModel(createOfferService, - null, - offerUtil, - btcWalletService, - bsqWalletService, - empty, - user, - null, - priceFeedService, - accountAgeWitnessService, - feeService, - coinFormatter, - tradeStats, - null); - dataModel.initWithData(Direction.BUY, new CryptoCurrency("BTC", "bitcoin")); + null, + offerUtil, + xmrWalletService, + bsqWalletService, + empty, + user, + null, + priceFeedService, + accountAgeWitnessService, + feeService, + coinFormatter, + tradeStats, + null); + dataModel.initWithData(OfferPayload.Direction.BUY, new CryptoCurrency("BTC", "bitcoin")); dataModel.activate(); model = new CreateOfferViewModel(dataModel, diff --git a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java index d0b4b01c..a4cf7c73 100644 --- a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java @@ -3,9 +3,9 @@ package bisq.desktop.main.portfolio.editoffer; import bisq.desktop.util.validation.SecurityDepositValidator; import bisq.core.account.witness.AccountAgeWitnessService; -import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.wallet.BsqWalletService; -import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.locale.Country; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.GlobalSettings; @@ -66,8 +66,8 @@ public class EditOfferDataModelTest { Res.setup(); FeeService feeService = mock(FeeService.class); - AddressEntry addressEntry = mock(AddressEntry.class); - BtcWalletService btcWalletService = mock(BtcWalletService.class); + XmrAddressEntry addressEntry = mock(XmrAddressEntry.class); + XmrWalletService xmrWalletService = mock(XmrWalletService.class); PriceFeedService priceFeedService = mock(PriceFeedService.class); user = mock(User.class); PaymentAccount paymentAccount = mock(PaymentAccount.class); @@ -79,8 +79,8 @@ public class EditOfferDataModelTest { CreateOfferService createOfferService = mock(CreateOfferService.class); OfferUtil offerUtil = mock(OfferUtil.class); - when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); - when(btcWalletService.getBalanceForAddress(any())).thenReturn(Coin.valueOf(1000L)); + when(xmrWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); + when(xmrWalletService.getBalanceForAccount(any(Integer.class))).thenReturn(Coin.valueOf(1000L)); when(priceFeedService.updateCounterProperty()).thenReturn(new SimpleIntegerProperty()); when(priceFeedService.getMarketPrice(anyString())).thenReturn( new MarketPrice("USD", @@ -98,20 +98,20 @@ public class EditOfferDataModelTest { when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString()); model = new EditOfferDataModel(createOfferService, - null, - offerUtil, - btcWalletService, - bsqWalletService, - empty, - user, - null, - priceFeedService, - accountAgeWitnessService, - feeService, - null, - null, - mock(TradeStatisticsManager.class), - null); + null, + offerUtil, + xmrWalletService, + bsqWalletService, + empty, + user, + null, + priceFeedService, + accountAgeWitnessService, + feeService, + null, + null, + mock(TradeStatisticsManager.class), + null); } @Test diff --git a/desktop/src/test/java/bisq/desktop/util/ImmutableCoinFormatterTest.java b/desktop/src/test/java/bisq/desktop/util/ImmutableCoinFormatterTest.java index afd17442..1d91e0ba 100644 --- a/desktop/src/test/java/bisq/desktop/util/ImmutableCoinFormatterTest.java +++ b/desktop/src/test/java/bisq/desktop/util/ImmutableCoinFormatterTest.java @@ -18,8 +18,8 @@ package bisq.desktop.util; import bisq.core.locale.Res; -import bisq.core.util.coin.ImmutableCoinFormatter; import bisq.core.util.coin.CoinFormatter; +import bisq.core.util.coin.ImmutableCoinFormatter; import bisq.common.config.Config; @@ -36,8 +36,6 @@ import static com.natpryce.makeiteasy.MakeItEasy.with; import static org.bitcoinj.core.CoinMaker.oneBitcoin; import static org.bitcoinj.core.CoinMaker.satoshis; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; public class ImmutableCoinFormatterTest { diff --git a/gradle/witness/gradle-witness.gradle b/gradle/witness/gradle-witness.gradle index ea6d6ab8..607829e5 100644 --- a/gradle/witness/gradle-witness.gradle +++ b/gradle/witness/gradle-witness.gradle @@ -18,9 +18,10 @@ dependencyVerification { 'aopalliance:aopalliance:0addec670fedcd3f113c5c8091d783280d23f75e3acb841b61a9cdb079376a08', 'ch.qos.logback:logback-classic:86a0268c3c96888d4e49d8a754b5b2173286aee100559e803efcbb0df676c66e', 'ch.qos.logback:logback-core:58738067842476feeae5768e832cd36a0e40ce41576ba5739c3632d376bd8c86', - 'com.fasterxml.jackson.core:jackson-annotations:203cefdfa6c81e6aa84e11f292f29ca97344a3c3bc0293abea065cd837592873', - 'com.fasterxml.jackson.core:jackson-core:cc899cb6eae0c80b87d590eea86528797369cc4feb7b79463207d6bb18f0c257', - 'com.fasterxml.jackson.core:jackson-databind:f2ca3c28ebded59c98447d51afe945323df961540af66a063c015597af936aa0', + 'com.fasterxml.jackson.core:jackson-annotations:05da0a25bb44a217880a299a1a1e0a301d194b5656a9a07776b77a88f326e7e9', + 'com.fasterxml.jackson.core:jackson-core:baef34fbce041d54f3af3ff4fc917ed8b43ed2a6fa30e0a6abfd9a2b2c3f71e0', + 'com.fasterxml.jackson.core:jackson-databind:94d973062c2fda3dff2c9a85eafce57204821cce9085a99377693dbc9fb8da23', + 'com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2b95bada85960212a9b138da954e7b8d278816fa92efa25437e5cfeb5a235dd0', 'com.github.JesusMcCloud:jtorctl:389d61b1b5a85eb2f23c582c3913ede49f80c9f2b553e4762382c836270e57e5', 'com.github.bisq-network.netlayer:tor.external:e1d6b8fe73891207701c6b14317be789fd4acd25f7b499425d2471598d9a22ac', 'com.github.bisq-network.netlayer:tor.native:aa3edf9c27071fdc2b7d55b00dbc7c6cd5dc9aa9f87aafa4be0805f818a466be', diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java b/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java index 48c60897..e4d95b81 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java @@ -128,8 +128,7 @@ public class P2PNetworkLoad extends Metric implements MessageListener, SetupList CorruptedStorageFileHandler corruptedStorageFileHandler = new CorruptedStorageFileHandler(); int maxConnections = Integer.parseInt(configuration.getProperty(MAX_CONNECTIONS, "12")); NetworkProtoResolver networkProtoResolver = new CoreNetworkProtoResolver(Clock.systemDefaultZone()); - CorePersistenceProtoResolver persistenceProtoResolver = new CorePersistenceProtoResolver(null, - networkProtoResolver); + CorePersistenceProtoResolver persistenceProtoResolver = new CorePersistenceProtoResolver(null, null, networkProtoResolver); DefaultSeedNodeRepository seedNodeRepository = new DefaultSeedNodeRepository(config); PeerManager peerManager = new PeerManager(networkNode, seedNodeRepository, new ClockWatcher(), new PersistenceManager<>(torHiddenServiceDir, persistenceProtoResolver, corruptedStorageFileHandler), maxConnections); diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshotBase.java b/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshotBase.java index 9550b3da..fbe54fa4 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshotBase.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshotBase.java @@ -115,7 +115,7 @@ public abstract class P2PSeedNodeSnapshotBase extends Metric implements MessageL File dir = new File(configuration.getProperty(DATABASE_DIR)); String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString(); try { - CorePersistenceProtoResolver persistenceProtoResolver = new CorePersistenceProtoResolver(null, null); + CorePersistenceProtoResolver persistenceProtoResolver = new CorePersistenceProtoResolver(null, null, null); //TODO will not work with historical data... should be refactored to re-use code for reading resource files TradeStatistics3Store tradeStatistics3Store = new TradeStatistics3Store(); diff --git a/p2p/src/main/java/bisq/network/p2p/network/Connection.java b/p2p/src/main/java/bisq/network/p2p/network/Connection.java index bedef3ed..fba0496a 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/Connection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/Connection.java @@ -778,7 +778,7 @@ public class Connection implements HasCapabilities, Runnable, MessageListener { if (proto == null) { if (protoInputStream.read() == -1) { - log.warn("proto is null because protoInputStream.read()=-1 (EOF). That is expected if client got stopped without proper shutdown."); + log.warn("proto is null because protoInputStream.read()=-1 (EOF). That is expected if client got stopped without proper shutdown."); // TODO (woodser): why is this warning printing on shutdown? } else { log.warn("proto is null. protoInputStream.read()=" + protoInputStream.read()); } diff --git a/p2p/src/main/resources/AccountAgeWitnessStore_XMR_MAINNET_placeholder b/p2p/src/main/resources/AccountAgeWitnessStore_XMR_MAINNET_placeholder new file mode 100644 index 00000000..33fca68e --- /dev/null +++ b/p2p/src/main/resources/AccountAgeWitnessStore_XMR_MAINNET_placeholder @@ -0,0 +1 @@ +TODO (woodser): this is a placeholder for git lfs files; replace with stored Haveno network data \ No newline at end of file diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index b8d50bfe..e387aa90 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -355,11 +355,10 @@ message TradeInfo { string shortId = 3; uint64 date = 4; string role = 5; - bool isCurrencyForTakerFeeBtc = 6; uint64 txFeeAsLong = 7; uint64 takerFeeAsLong = 8; string takerFeeTxId = 9; - string depositTxId = 10; + reserved 10; // was depositTxId string payoutTxId = 11; uint64 tradeAmountAsLong = 12; uint64 tradePrice = 13; @@ -375,13 +374,16 @@ message TradeInfo { bool isWithdrawn = 23; string contractAsJson = 24; ContractInfo contract = 25; + + string makerDepositTxId = 100; + string takerDepositTxId = 101; } message ContractInfo { string buyerNodeAddress = 1; string sellerNodeAddress = 2; - string mediatorNodeAddress = 3; - string refundAgentNodeAddress = 4; + reserved 3; // was mediatorNodeAddress + reserved 4; // was refundAgendNodeAddress bool isBuyerMakerAndSellerTaker = 5; string makerAccountId = 6; string takerAccountId = 7; @@ -390,6 +392,8 @@ message ContractInfo { string makerPayoutAddressString = 10; string takerPayoutAddressString = 11; uint64 lockTime = 12; + + string arbitratorNodeAddress = 100; } message PaymentAccountPayloadInfo { diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index dfef1f67..8d0aec26 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -81,6 +81,15 @@ message NetworkEnvelope { GetInventoryRequest get_inventory_request = 52; GetInventoryResponse get_inventory_response = 53; + + InitTradeRequest init_trade_request = 1001; + MakerReadyToFundMultisigRequest maker_ready_to_fund_multisig_request = 1002; + MakerReadyToFundMultisigResponse maker_ready_to_fund_multisig_response = 1003; + InitMultisigMessage init_multisig_message = 1004; + UpdateMultisigRequest update_multisig_request = 1005; + UpdateMultisigResponse update_multisig_response = 1006; + ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1007; + ArbitratorPayoutTxResponse arbitrator_payout_tx_response = 1008; } } @@ -267,6 +276,73 @@ message InputsForDepositTxResponse { int64 lock_time = 14; } +message InitTradeRequest { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + int64 trade_amount = 4; + int64 trade_price = 5; + int64 tx_fee = 6; + int64 trade_fee = 7; + string payout_address_string = 8; + PaymentAccountPayload payment_account_payload = 9; + string account_id = 10; + string trade_fee_tx_id = 11; + string uid = 12; + bytes account_age_witness_signature_of_offer_id = 13; + int64 current_date = 14; + NodeAddress maker_node_address = 15; + NodeAddress taker_node_address = 16; + NodeAddress arbitrator_node_address = 17; +} + +message MakerReadyToFundMultisigRequest { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string uid = 4; +} + +message MakerReadyToFundMultisigResponse { + string trade_id = 1; + string uid = 2; + bool is_maker_ready_to_fund_multisig = 3; + string maker_contract_as_json = 4; + string maker_contract_signature = 5; + string maker_payout_address_string = 6; + PaymentAccountPayload maker_payment_account_payload = 7; + string maker_account_id = 8; + int64 current_date = 9; +} + +message InitMultisigMessage { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string uid = 4; + int64 current_date = 5; + string prepared_multisig_hex = 6; + string made_multisig_hex = 7; +} + +message UpdateMultisigRequest { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string uid = 4; + int64 current_date = 5; + string updated_multisig_hex = 6; +} + +message UpdateMultisigResponse { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string uid = 4; + int64 current_date = 5; + string updated_multisig_hex = 6; +} + message DelayedPayoutTxSignatureRequest { string uid = 1; string trade_id = 2; @@ -296,6 +372,10 @@ message DepositTxMessage { string trade_id = 2; NodeAddress sender_node_address = 3; bytes deposit_tx_without_witnesses = 4; + PubKeyRing pub_key_ring = 100; + reserved 5; // WAS: bytes deposit_tx = 101; + string trade_fee_tx_id = 102; + string deposit_tx_id = 103; } message PeerPublishedDelayedPayoutTxMessage { @@ -308,7 +388,7 @@ message CounterCurrencyTransferStartedMessage { string trade_id = 1; string buyer_payout_address = 2; NodeAddress sender_node_address = 3; - bytes buyer_signature = 4; + string buyer_payout_tx_signed = 4; string counter_currency_tx_id = 5; string uid = 6; string counter_currency_extra_data = 7; @@ -322,9 +402,25 @@ message FinalizePayoutTxRequest { string uid = 5; } +message ArbitratorPayoutTxRequest { + Dispute dispute = 1; // TODO (woodser): replace with trade id + NodeAddress sender_node_address = 2; + string uid = 3; + SupportType type = 4; + string updated_multisig_hex = 5; +} + +message ArbitratorPayoutTxResponse { + string trade_id = 1; + NodeAddress sender_node_address = 2; + string uid = 3; + SupportType type = 4; + string arbitrator_signed_payout_tx_hex = 5; +} + message PayoutTxPublishedMessage { string trade_id = 1; - bytes payout_tx = 2; + string signed_multisig_tx_hex = 2; NodeAddress sender_node_address = 3; string uid = 4; SignedWitness signed_witness = 5; // Added in v1.4.0 @@ -373,6 +469,7 @@ message OpenNewDisputeMessage { NodeAddress sender_node_address = 2; string uid = 3; SupportType type = 4; + string updated_multisig_hex = 5; } message PeerOpenedDisputeMessage { @@ -410,10 +507,12 @@ message DisputeResultMessage { message PeerPublishedDisputePayoutTxMessage { string uid = 1; - bytes transaction = 2; + reserved 2; // was bytes transaction = 2; string trade_id = 3; NodeAddress sender_node_address = 4; SupportType type = 5; + string updated_multisig_hex = 6; + string payout_tx_hex = 7; } message PrivateNotificationMessage { @@ -710,6 +809,8 @@ message TradeStatistics2 { string deposit_tx_id = 14 [deprecated = true]; bytes hash = 15 [deprecated = true]; map extra_data = 16 [deprecated = true]; + string maker_deposit_tx_id = 100; + string taker_deposit_tx_id = 101; } message TradeStatistics3 { @@ -718,10 +819,14 @@ message TradeStatistics3 { int64 amount = 3; string payment_method = 4; int64 date = 5; - string mediator = 6; - string refund_agent = 7; + reserved 6; // was string mediator = 6; + reserved 7; // was string refund_agent = 7; bytes hash = 8; map extra_data = 9; + + string arbitrator = 100; + string maker_deposit_tx_id = 101; + string taker_deposit_tx_id = 102; } message MailboxStoragePayload { @@ -842,6 +947,7 @@ message Dispute { State state = 28; int64 trade_period_end = 29; map extra_data = 30; + bool is_opener = 100; } message Attachment { @@ -881,12 +987,14 @@ message DisputeResult { bool screen_cast = 7; string summary_notes = 8; ChatMessage chat_message = 9; - bytes arbitrator_signature = 10; + reserved 10; // was bytes arbitrator_signature = 10; int64 buyer_payout_amount = 11; int64 seller_payout_amount = 12; bytes arbitrator_pub_key = 13; int64 close_date = 14; bool is_loser_publisher = 15; + string arbitrator_signed_payout_tx_hex = 16; + string arbitrator_updated_multisig_hex = 17; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -897,7 +1005,7 @@ message Contract { OfferPayload offer_payload = 1; int64 trade_amount = 2; int64 trade_price = 3; - string taker_fee_tx_id = 4; + reserved 4; // WAS: taker_fee_tx_id reserved 5; // WAS: arbitrator_node_address bool is_buyer_maker_and_seller_taker = 6; string maker_account_id = 7; @@ -910,11 +1018,8 @@ message Contract { NodeAddress seller_node_address = 14; string maker_payout_address_string = 15; string taker_payout_address_string = 16; - bytes maker_multi_sig_pub_key = 17; - bytes taker_multi_sig_pub_key = 18; - NodeAddress mediator_node_address = 19; - int64 lock_time = 20; - NodeAddress refund_agent_node_address = 21; + NodeAddress arbitrator_node_address = 17; + int64 lock_time = 18; } message RawTransactionInput { @@ -1246,6 +1351,8 @@ message PersistableEnvelope { MailboxMessageList mailbox_message_list = 32; IgnoredMailboxMap ignored_mailbox_map = 33; RemovedPayloadsMap removed_payloads_map = 34; + + XmrAddressEntryList xmr_address_entry_list = 100; } } @@ -1322,6 +1429,28 @@ message AddressEntry { bool segwit = 12; } +message XmrAddressEntryList { + repeated XmrAddressEntry xmr_address_entry = 1; +} + +message XmrAddressEntry { + enum Context { + PB_ERROR = 0; + ARBITRATOR = 1; + AVAILABLE = 2; + OFFER_FUNDING = 3; + RESERVED_FOR_TRADE = 4; + MULTI_SIG = 5; + TRADE_PAYOUT = 6; + } + + int32 account_index = 7; + string address_string = 8; + string offer_id = 9; + Context context = 10; + int64 coin_locked_in_multi_sig = 11; +} + message NavigationPath { repeated string path = 1; } @@ -1377,6 +1506,7 @@ message Tradable { BuyerAsTakerTrade buyer_as_taker_trade = 3; SellerAsMakerTrade seller_as_maker_trade = 4; SellerAsTakerTrade seller_as_taker_trade = 5; + ArbitratorTrade arbitrator_trade = 6; } } @@ -1390,29 +1520,30 @@ message Trade { MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST = 5; MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 6; TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST = 7; - SELLER_PUBLISHED_DEPOSIT_TX = 8; - SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG = 9; - SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG = 10; - SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG = 11; - SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG = 12; - BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 13; - BUYER_SAW_DEPOSIT_TX_IN_NETWORK = 14; - DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN = 15; - BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED = 16; - BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG = 17; - BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG = 18; - BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG = 19; - BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG = 20; - SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG = 21; - SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT = 22; - SELLER_PUBLISHED_PAYOUT_TX = 23; - SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG = 24; - SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG = 25; - SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG = 26; - SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG = 27; - BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG = 28; - BUYER_SAW_PAYOUT_TX_IN_NETWORK = 29; - WITHDRAW_COMPLETED = 30; + TAKER_PUBLISHED_DEPOSIT_TX = 8; + TAKER_SAW_DEPOSIT_TX_IN_NETWORK = 9; + TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG = 10; + TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG = 11; + TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG = 12; + TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG = 13; + MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 14; + MAKER_SAW_DEPOSIT_TX_IN_NETWORK = 15; + DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN = 16; + BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED = 17; + BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG = 18; + BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG = 19; + BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG = 20; + BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG = 21; + SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG = 22; + SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT = 23; + SELLER_PUBLISHED_PAYOUT_TX = 24; + SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG = 25; + SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG = 26; + SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG = 27; + SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG = 28; + BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG = 29; + BUYER_SAW_PAYOUT_TX_IN_NETWORK = 30; + WITHDRAW_COMPLETED = 31; } enum Phase { @@ -1451,7 +1582,7 @@ message Trade { Offer offer = 1; ProcessModel process_model = 2; string taker_fee_tx_id = 3; - string deposit_tx_id = 4; + reserved 4; string payout_tx_id = 5; int64 trade_amount_as_long = 6; int64 tx_fee_as_long = 7; @@ -1459,7 +1590,6 @@ message Trade { int64 take_offer_date = 9; bool is_currency_for_taker_fee_btc = 10; int64 trade_price = 11; - NodeAddress trading_peer_node_address = 12; State state = 13; DisputeState dispute_state = 14; TradePeriodState trade_period_state = 15; @@ -1487,6 +1617,13 @@ message Trade { string counter_currency_extra_data = 37; string asset_tx_proof_result = 38; // name of AssetTxProofResult enum string uid = 39; + + NodeAddress taker_node_address = 100; + PubKeyRing taker_pub_key_ring = 101; + string taker_deposit_tx_id = 102; + NodeAddress maker_node_address = 103; + PubKeyRing maker_pub_key_ring = 104; + string maker_deposit_tx_id = 105; } message BuyerAsMakerTrade { @@ -1505,8 +1642,12 @@ message SellerAsTakerTrade { Trade trade = 1; } +message ArbitratorTrade { + Trade trade = 1; +} + message ProcessModel { - TradingPeer trading_peer = 1; + reserved 1; // Not used anymore string offer_id = 2; string account_id = 3; PubKeyRing pub_key_ring = 4; @@ -1514,18 +1655,30 @@ message ProcessModel { bytes payout_tx_signature = 6; reserved 7; // Not used anymore reserved 8; // Not used anymore - bytes prepared_deposit_tx = 9; + reserved 9; // Not used anymore repeated RawTransactionInput raw_transaction_inputs = 10; int64 change_output_value = 11; string change_output_address = 12; bool use_savings_wallet = 13; int64 funds_needed_for_trade_as_long = 14; bytes my_multi_sig_pub_key = 15; - NodeAddress temp_trading_peer_node_address = 16; + reserved 16; // Not used anymore string payment_started_message_state = 17; bytes mediated_payout_tx_signature = 18; int64 buyer_payout_amount_from_mediation = 19; int64 seller_payout_amount_from_mediation = 20; + + TradingPeer maker = 1001; + TradingPeer taker = 1002; + TradingPeer arbitrator = 1003; + NodeAddress temp_trading_peer_node_address = 1004; + string prepared_multisig_hex = 1005; + string made_multisig_hex = 1006; + bool multisig_setup_complete = 1007; + bool maker_ready_to_fund_multisig = 1008; + bool multisig_deposit_initiated = 1009; + string taker_prepared_deposit_tx_id = 1010; + string maker_prepared_deposit_tx_id = 1011; } message TradingPeer { @@ -1534,7 +1687,7 @@ message TradingPeer { string payout_address_string = 3; string contract_as_json = 4; string contract_signature = 5; - bytes signature = 6; + bytes signature = 6; // TODO (woodser): remove unused fields? this was buyer-signed payout tx as bytes PubKeyRing pub_key_ring = 7; bytes multi_sig_pub_key = 8; repeated RawTransactionInput raw_transaction_inputs = 9; @@ -1544,6 +1697,10 @@ message TradingPeer { bytes account_age_witness_signature = 13; int64 current_date = 14; bytes mediated_payout_tx_signature = 15; + + string prepared_multisig_hex = 1001; + string made_multisig_hex = 1002; + string signed_payout_tx_hex = 1003; } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/settings.gradle b/settings.gradle index 1827ef9c..fe976bf0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -14,4 +14,4 @@ include 'statsnode' include 'inventory' include 'apitest' -rootProject.name = 'bisq' +rootProject.name = 'haveno' diff --git a/todo.txt b/todo.txt new file mode 100644 index 00000000..ccdfc038 --- /dev/null +++ b/todo.txt @@ -0,0 +1,30 @@ +configurable daemon for poc +trade removed from books immediately or when taker pays fee? see ProcessOfferAvailabilityResponse +one TraderProtocol which supports maker, taker, buyer, seller methods to prevent duplicate implementations +script to launch local network +use "deposit tx" terminology instead of "fund multisig" +use rpc instead of jni bindings? requires notifications in monero-java +resume listeners on startup (BisqSetup.start() - init multisig?) +finalize protocol (when to remove trade from books, tx ids, no arbitrator till ms?) +deploy seed +balances: total, locked, reserved? +verify payout txs in dispute +user can review/accept/reject arbitrator payout decision +arbitrator fee? +proper error handling +peer verify fee tx +multisig wallets replaced if exist and trade restarted +multisig wallets named with trade id + trade peer id for concurrent acceptance? +use getNewAddressEntry() to register payout destination +use BigIntegers instead of satoshi conversion? +remove bitcoinj dependency? +manage all funds in account 0 with reserved subaddresses or move failed trade account funds to account 0 (incurs miner fee + 10 block wait time) +refactor State/Phase for CleanupTradeableOnFault +temp node address vs trusting/persisitng into trade model +ui designs +flatten ProcessModel into Trade / other model refactors? +might occasionally need to reimburse taker fee if maker does not enter into multisig +test disconnection from daemon at various stages + +BEFORE PRODUCTION +build status like bisq: https://travis-ci.org/bisq-network/bisq \ No newline at end of file