diff --git a/.gitattributes b/.gitattributes
index 923d54457a1..54522ef5ec0 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 7f69fae8338..00000000000
--- 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 3c462f9a6d7..00000000000
--- 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 16e5fa42169..00000000000
--- 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 00000000000..c073c5ccb11
--- /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 f3d52c3073d..d5dc2055786 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 c5ee421e25c..ddc64a6f9e1 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 c7e0a43c231..00000000000
--- 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 dba13ed2ddf..93a5d000f0d 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 b76c4d0ef98..addf52d17c4 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,124 @@
-# Bisq
+
+
+
-[![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.
+
+
+
+
+
+
+## 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 fc365931d5f..5b327ccb290 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 void testTakeAlicesSellBTCForBSQOffer(final TestInfo testInfo) {
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 void testTakeAlicesSellBTCForBSQOffer(final TestInfo testInfo) {
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 93d9b1b9c8b..0f0091fc9f9 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 void testTakeAlicesBuyOffer(final TestInfo testInfo) {
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 void testTakeAlicesBuyOffer(final TestInfo testInfo) {
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 786601e6fa1..1ffa318afbf 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 void testTakeAlicesBuyBTCForBSQOffer(final TestInfo testInfo) {
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 void testTakeAlicesBuyBTCForBSQOffer(final TestInfo testInfo) {
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 ece3432123b..1e5b95448b8 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 void testTakeAlicesSellOffer(final TestInfo testInfo) {
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 void testTakeAlicesSellOffer(final TestInfo testInfo) {
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 51d59e7537d..1435d61d4b3 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 @@ private void waitForTakerDepositFee(String tradeId, ProtocolStep depositTxProtoc
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 @@ private void waitForTakerDepositFee(String tradeId, ProtocolStep depositTxProtoc
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 c9572aaaace..aedfd58998e 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 1556a6aa318..049bc350aab 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 4abf20276ee..c3070a9826d 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.NumberFormat;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.Locale;
@@ -32,6 +33,10 @@
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 static String formatBsq(long sats) {
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 dbf8dbf4b86..ded61548c3a 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 @@ private static String formatTradeData(String format,
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 @@ private static String formatTradeData(String format,
: 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 @@ private static String formatTradeData(String format,
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 @@ private static String formatTradeData(String format,
};
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 e6fce415507..79bff399ee2 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 extends Task> 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 b634f01a8d5..69c123bd6ad 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.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 Optional findWitness(Offer offer) {
}
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 SignState getSignState(Offer offer) {
}
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 2f5683108a9..21942a60ff4 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 @@ void takeOffer(Offer offer,
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 b9ea896ef71..69b113e3c35 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 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 404335c9c7f..c741cec5bde 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 ContractInfo(String buyerNodeAddress,
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 ContractInfo(String buyerNodeAddress,
// For transmitting TradeInfo messages when no contract is available.
public static Supplier emptyContract = () ->
new ContractInfo("",
- "",
"",
"",
false,
@@ -94,8 +90,7 @@ public ContractInfo(String buyerNodeAddress,
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 bisq.proto.grpc.ContractInfo toProtoMessage() {
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 078e5ee4d9c..8ba70a02e20 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 TradeInfo(TradeInfoBuilder builder) {
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 static TradeInfo toTradeInfo(Trade trade, String role) {
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 static TradeInfo toTradeInfo(Trade trade, String role) {
.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 bisq.proto.grpc.TradeInfo toProtoMessage() {
.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 static TradeInfo fromProto(bisq.proto.grpc.TradeInfo proto) {
.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 static class TradeInfoBuilder {
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 TradeInfoBuilder withTakerFeeTxId(String takerFeeTxId) {
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 String toString() {
", 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 980875cd0aa..355483cee0d 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 @@
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 void gracefulShutDown(ResultHandler resultHandler) {
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 de6db2c2ba9..ee212e4fb2c 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.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 @@ default void onRequestWalletPassword() {
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 BisqSetup(DomainInitialisation domainInitialisation,
WalletAppSetup walletAppSetup,
WalletsManager walletsManager,
WalletsSetup walletsSetup,
+ XmrWalletService xmrWalletService,
BtcWalletService btcWalletService,
P2PService p2PService,
SignedWitnessStorageService signedWitnessStorageService,
@@ -231,6 +234,7 @@ public BisqSetup(DomainInitialisation domainInitialisation,
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 @@ private void checkForInvalidMakerFeeTxs() {
// 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 b54e6706ada..a5cb49acd49 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 @@ void setRejectedTxErrorMessageHandler(Consumer rejectedTxErrorMessageHan
.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 ccca2a6a0c6..9ad33d9b2fe 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.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 void gracefulShutDown(ResultHandler resultHandler) {
});
});
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 5d3ddd4e437..bb38c1ef197 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.common.UserThread;
import org.bitcoinj.core.Coin;
-import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
@@ -41,16 +38,23 @@
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 void onAllServicesInitialized() {
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 @@ private void updateBalance() {
});
}
+ // 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 3f733fb570a..18e4b891bf6 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.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 @@ protected void configure() {
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 00000000000..2c8247453de
--- /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 00000000000..aaba5fec67e
--- /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 00000000000..59c0212f415
--- /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 00000000000..2bb0b559825
--- /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 bd260c13d8b..c01bdf62f6e 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.IOException;
import java.io.InputStream;
+import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
@@ -85,6 +86,14 @@
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 @@ protected void onSetupCompleted() {
// 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 @@ protected void startUp() throws Exception {
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 Wallet btcWallet() {
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 fad4ac30510..d01f900ce81 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.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 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 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 WalletsSetup(RegTestHost regTestHost,
@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 @@ protected void onSetupCompleted() {
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 Wallet getBtcWallet() {
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 4244e78534e..87f701d114b 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.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 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 TradeWalletService(WalletsSetup walletsSetup, Preferences preferences) {
walletsSetup.addSetupCompletedHandler(() -> {
walletConfig = walletsSetup.getWalletConfig();
wallet = walletsSetup.getBtcWallet();
+ xmrWallet = walletsSetup.getXmrWallet();
});
}
@@ -125,6 +136,21 @@ public KeyParameter getAesKey() {
// 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 void broadcastTx(Transaction tx, TxBroadcaster.Callback callback, int tim
// 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 0b893e470a8..a25a6a98f04 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 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 void removeBalanceListener(BalanceListener listener) {
balanceListeners.remove(listener);
}
-
///////////////////////////////////////////////////////////////////////////////////////////
// Checks
///////////////////////////////////////////////////////////////////////////////////////////
@@ -802,6 +806,22 @@ public static Transaction maybeAddTxToWallet(byte[] serializedTransaction,
}
}
+ 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 00000000000..eeb79656271
--- /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 360a0b8c346..f2581b45aaf 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 @@
@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 fcf9141c918..6d102ccdd2c 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 @@
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 static ResourceBundle getResourceBundle() {
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 static String get(String key, Object... arguments) {
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 bf04e10b19e..6b8a3b5b734 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.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 OpenOfferManager(CoreContext coreContext,
User user,
P2PService p2PService,
BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
TradeWalletService tradeWalletService,
BsqWalletService bsqWalletService,
OfferBookService offerBookService,
@@ -161,6 +164,7 @@ public OpenOfferManager(CoreContext coreContext,
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 void placeOffer(Offer offer,
reservedFundsForOffer,
useSavingsWallet,
btcWalletService,
+ xmrWalletService,
tradeWalletService,
bsqWalletService,
offerBookService,
@@ -642,11 +647,8 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No
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 eaec9dcbe96..7b3bee5fc5f 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 @@
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 @@ private static T getLeastUsedDisputeAgent(TradeStatisti
// 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 f6c04fa1982..20bc8447926 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 @@ protected void run() {
NodeAddress mediator = offerAvailabilityResponse.getMediator();
if (mediator == null) {
// We do not get a mediator from old clients so we need to handle the null case.
- mediator = DisputeAgentSelection.getLeastUsedMediator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress();
+ mediator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress();
}
model.setSelectedMediator(mediator);
diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java
index 0c54d733ed0..f2683978c19 100644
--- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java
+++ b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java
@@ -20,6 +20,7 @@
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.Offer;
@@ -37,6 +38,10 @@
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
+
+
+import monero.wallet.model.MoneroTxWallet;
+
@Slf4j
@Getter
public class PlaceOfferModel implements Model {
@@ -45,6 +50,7 @@ public class PlaceOfferModel implements Model {
private final Coin reservedFundsForOffer;
private final boolean useSavingsWallet;
private final BtcWalletService walletService;
+ private final XmrWalletService xmrWalletService;
private final TradeWalletService tradeWalletService;
private final BsqWalletService bsqWalletService;
private final OfferBookService offerBookService;
@@ -60,11 +66,14 @@ public class PlaceOfferModel implements Model {
private boolean offerAddedToOfferBook;
@Setter
private Transaction transaction;
+ @Setter
+ private MoneroTxWallet xmrTransaction;
public PlaceOfferModel(Offer offer,
Coin reservedFundsForOffer,
boolean useSavingsWallet,
BtcWalletService walletService,
+ XmrWalletService xmrWalletService,
TradeWalletService tradeWalletService,
BsqWalletService bsqWalletService,
OfferBookService offerBookService,
@@ -77,6 +86,7 @@ public PlaceOfferModel(Offer offer,
this.reservedFundsForOffer = reservedFundsForOffer;
this.useSavingsWallet = useSavingsWallet;
this.walletService = walletService;
+ this.xmrWalletService = xmrWalletService;
this.tradeWalletService = tradeWalletService;
this.bsqWalletService = bsqWalletService;
this.offerBookService = offerBookService;
diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java
index cdf394435fc..cf1437bfd99 100644
--- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java
+++ b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java
@@ -19,9 +19,9 @@
import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
import bisq.core.offer.placeoffer.tasks.CheckNumberOfUnconfirmedTransactions;
-import bisq.core.offer.placeoffer.tasks.CreateMakerFeeTx;
import bisq.core.offer.placeoffer.tasks.ValidateOffer;
import bisq.core.trade.handlers.TransactionResultHandler;
+import bisq.core.trade.protocol.tasks.maker.MakerCreateFeeTx;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.taskrunner.TaskRunner;
@@ -79,7 +79,7 @@ public void placeOffer() {
taskRunner.addTasks(
ValidateOffer.class,
CheckNumberOfUnconfirmedTransactions.class,
- CreateMakerFeeTx.class,
+ MakerCreateFeeTx.class,
AddToOfferBook.class
);
diff --git a/core/src/main/java/bisq/core/payment/JapanBankAccount.java b/core/src/main/java/bisq/core/payment/JapanBankAccount.java
index b2d9fede114..8aa952d074d 100644
--- a/core/src/main/java/bisq/core/payment/JapanBankAccount.java
+++ b/core/src/main/java/bisq/core/payment/JapanBankAccount.java
@@ -17,19 +17,10 @@
package bisq.core.payment;
+import bisq.core.locale.FiatCurrency;
import bisq.core.payment.payload.JapanBankAccountPayload;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
-import bisq.core.payment.payload.JapanBankAccountPayload;
-
-import org.jetbrains.annotations.NotNull;
-
-import lombok.Getter;
-import lombok.Setter;
-
-import bisq.core.locale.Country;
-import bisq.core.locale.FiatCurrency;
-import bisq.core.payment.payload.JapanBankAccountPayload;
public final class JapanBankAccount extends PaymentAccount
{
diff --git a/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java b/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java
index 5498b7da177..333b630665d 100644
--- a/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java
+++ b/core/src/main/java/bisq/core/payment/payload/PaymentMethod.java
@@ -50,7 +50,8 @@ public final class PaymentMethod implements PersistablePayload, Comparable {
- 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 d30a671bc8f..bd7796f3ace 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.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.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 NetworkEnvelope fromProto(protobuf.NetworkEnvelope proto) throws Protobuf
// 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 NetworkEnvelope fromProto(protobuf.NetworkEnvelope proto) throws Protobuf
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 f42670d76d4..8f18bffc81a 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 @@
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 @@
@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 PersistableEnvelope fromProto(protobuf.PersistableEnvelope proto) {
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 7894b55f089..2f351828945 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 void validateOfferMakerTx(TxValidator txValidator, Consumer
}
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 0e54e5527ca..ab2a162f065 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 static List getPersistedDataHosts(Injector injector) {
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 98ed154fbee..81527f431a9 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 static protobuf.Dispute.State toProtoMessage(Dispute.State state) {
@Nullable
private String delayedPayoutTxId;
+ // Added for XMR integration
+ private boolean isOpener;
+
// Added at v1.4.0
@Setter
@Nullable
@@ -160,6 +163,7 @@ public static protobuf.Dispute.State toProtoMessage(Dispute.State state) {
public Dispute(long openingDate,
String tradeId,
int traderId,
+ boolean isOpener,
boolean disputeOpenerIsBuyer,
boolean disputeOpenerIsMaker,
PubKeyRing traderPubKeyRing,
@@ -180,6 +184,7 @@ public Dispute(long openingDate,
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 protobuf.Dispute toProtoMessage() {
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 static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr
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 void addAndPersistChatMessage(ChatMessage chatMessage) {
}
}
+ public boolean isMediationDispute() {
+ return !chatMessages.isEmpty() && chatMessages.get(0).getSupportType() == SupportType.MEDIATION;
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
@@ -447,6 +457,7 @@ public String toString() {
",\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 6abb55ffcbc..61a15c7ea03 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 java.security.KeyPair;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@@ -81,10 +82,14 @@
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 DisputeManager(P2PService p2PService,
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 void addAndPersistChatMessage(ChatMessage message) {
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 void addAndPersistChatMessage(ChatMessage message) {
// 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 void onUpdatedDataReceived() {
}
});
- 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 Optional findOwnDispute(String tradeId) {
// 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 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa
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 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa
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 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa
}
// 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 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis
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 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis
public void sendOpenNewDisputeMessage(Dispute dispute,
boolean reOpen,
+ String updatedMultisigHex,
ResultHandler resultHandler,
FaultHandler faultHandler) {
T disputeList = getDisputeList();
@@ -453,18 +452,16 @@ public void sendOpenNewDisputeMessage(Dispute dispute,
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 void sendOpenNewDisputeMessage(Dispute dispute,
}
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 @@ private void doSendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
Dispute dispute = new Dispute(new Date().getTime(),
disputeFromOpener.getTradeId(),
pubKeyRing.hashCode(),
+ false,
!disputeFromOpener.isDisputeOpenerIsBuyer(),
!disputeFromOpener.isDisputeOpenerIsMaker(),
pubKeyRing,
@@ -636,7 +628,6 @@ private void doSendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
chatMessage.getUid());
-
mailboxMessageService.sendEncryptedMailboxMessage(peersNodeAddress,
peersPubKeyRing,
peerOpenedDisputeMessage,
@@ -687,7 +678,7 @@ public void onFault(String errorMessage) {
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 void onFault(String errorMessage) {
requestPersistence();
}
-
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
@@ -811,7 +801,7 @@ private Optional findDispute(ChatMessage message) {
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 d046a16c982..20135baed17 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 enum Reason {
@Setter
@Nullable
private ChatMessage chatMessage;
- @Setter
- @Nullable
- private byte[] arbitratorSignature;
private long buyerPayoutAmount;
private long sellerPayoutAmount;
@Setter
@@ -92,6 +89,14 @@ public enum Reason {
@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 DisputeResult(String tradeId,
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 DisputeResult(String tradeId,
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 static DisputeResult fromProto(protobuf.DisputeResult proto) {
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 protobuf.DisputeResult toProtoMessage() {
.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 String toString() {
",\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 a2c53a29f52..f467bb27d4a 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.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.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.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 void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
}
return;
}
-
Dispute dispute = disputeOptional.get();
+
+ // verify that arbitrator does not get DisputeResultMessage
+ if (pubKeyRing.equals(dispute.getAgentPubKeyRing())) {
+ log.error("Arbitrator received disputeResultMessage. That must never happen.");
+ return;
+ }
+
cleanupRetryMap(uid);
if (!dispute.getChatMessages().contains(chatMessage)) {
dispute.addAndPersistChatMessage(chatMessage);
@@ -225,16 +237,16 @@ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
}
dispute.setDisputeResult(disputeResult);
- Optional 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 @@ else if (publisher == DisputeResult.Winner.SELLER)
if ((isBuyer && publisher == DisputeResult.Winner.BUYER)
|| (!isBuyer && publisher == DisputeResult.Winner.SELLER)) {
- Transaction payoutTx = null;
+ MoneroTxWallet payoutTx = null;
if (tradeOptional.isPresent()) {
payoutTx = tradeOptional.get().getPayoutTx();
} else {
@@ -262,51 +274,29 @@ else if (publisher == DisputeResult.Winner.SELLER)
}
}
+
if (payoutTx == null) {
- if (dispute.getDepositTxSerialized() != null) {
- byte[] multiSigPubKey = isBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
- DeterministicKey multiSigKeyPair = btcWalletService.getMultiSigKeyPair(tradeId, multiSigPubKey);
- Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(
- dispute.getDepositTxSerialized(),
- disputeResult.getArbitratorSignature(),
- disputeResult.getBuyerPayoutAmount(),
- disputeResult.getSellerPayoutAmount(),
- contract.getBuyerPayoutAddressString(),
- contract.getSellerPayoutAddressString(),
- multiSigKeyPair,
- contract.getBuyerMultiSigPubKey(),
- contract.getSellerMultiSigPubKey(),
- disputeResult.getArbitratorPubKey()
- );
- Transaction committedDisputedPayoutTx = WalletService.maybeAddSelfTxToWallet(signedDisputedPayoutTx, btcWalletService.getWallet());
- tradeWalletService.broadcastTx(committedDisputedPayoutTx, new TxBroadcaster.Callback() {
- @Override
- public void onSuccess(Transaction transaction) {
- // after successful publish we send peer the tx
- dispute.setDisputePayoutTxId(transaction.getTxId().toString());
- sendPeerPublishedPayoutTxMessage(transaction, dispute, contract);
- updateTradeOrOpenOfferManager(tradeId);
- }
-
- @Override
- public void onFailure(TxBroadcastException exception) {
- log.error(exception.getMessage());
- }
- }, 15);
-
- success = true;
- } else {
- errorMessage = "DepositTx is null. TradeId = " + tradeId;
+
+ // gather relevant info
+ String arbitratorSignedPayoutTxHex = disputeResult.getArbitratorSignedPayoutTxHex();
+
+ if (arbitratorSignedPayoutTxHex != null) {
+ if (!tradeOptional.isPresent()) throw new RuntimeException("Trade must not be null when trader signs arbitrator's payout tx");
+
+ try {
+ MoneroTxSet txSet = traderSignsDisputePayoutTx(tradeId, arbitratorSignedPayoutTxHex);
+ onTraderSignedDisputePayoutTx(tradeId, txSet);
+ } catch (Exception e) {
+ errorMessage = "Failed to sign dispute payout tx from arbitrator: " + e.getMessage() + ". TradeId = " + tradeId;
log.warn(errorMessage);
success = false;
- }
+ }
+ } else {
+ requestUpdatedPayoutTx = true;
+ }
} else {
log.warn("We already got a payout tx. That might be the case if the other peer did not get the " +
"payout tx and opened a dispute. TradeId = " + tradeId);
- dispute.setDisputePayoutTxId(payoutTx.getTxId().toString());
- sendPeerPublishedPayoutTxMessage(payoutTx, dispute, contract);
-
- success = true;
}
} else {
log.trace("We don't publish the tx as we are not the winning party.");
@@ -314,28 +304,37 @@ public void onFailure(TxBroadcastException exception) {
if (dispute.disputeResultProperty().get() != null && dispute.isClosed()) {
updateTradeOrOpenOfferManager(tradeId);
}
-
- success = true;
}
- } catch (TransactionVerificationException e) {
+ }
+// catch (TransactionVerificationException e) {
+// errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString();
+// log.error(errorMessage, e);
+// success = false;
+//
+// // We prefer to close the dispute in that case. If there was no deposit tx and a random tx was used
+// // we get a TransactionVerificationException. No reason to keep that dispute open...
+// updateTradeOrOpenOfferManager(tradeId);
+//
+// throw new RuntimeException(errorMessage);
+// }
+// catch (AddressFormatException | WalletException e) {
+ catch (Exception e) {
errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString();
log.error(errorMessage, e);
success = false;
// We prefer to close the dispute in that case. If there was no deposit tx and a random tx was used
// we get a TransactionVerificationException. No reason to keep that dispute open...
- updateTradeOrOpenOfferManager(tradeId);
+ updateTradeOrOpenOfferManager(tradeId); // TODO (woodser): only close in case of verification exception?
- throw new RuntimeException(errorMessage);
- } catch (AddressFormatException | WalletException | SignatureDecodeException e) {
- errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString();
- log.error(errorMessage, e);
- success = false;
throw new RuntimeException(errorMessage);
} finally {
// We use the chatMessage as we only persist those not the disputeResultMessage.
// If we would use the disputeResultMessage we could not lookup for the msg when we receive the AckMessage.
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), success, errorMessage);
+
+ // If dispute opener's peer is co-signer, send updated multisig hex to arbitrator to receive updated payout tx
+ if (requestUpdatedPayoutTx) sendArbitratorPayoutTxRequest(multisigWallet.getMultisigHex(), dispute, contract);
}
requestPersistence();
@@ -366,27 +365,147 @@ private void onDisputedPayoutTxMessage(PeerPublishedDisputePayoutTxMessage peerP
cleanupRetryMap(uid);
- Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
+ // update multisig wallet
+ MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
+ multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
+
+ // parse payout tx
+ MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
+
+// System.out.println("LOSER'S VIEW OF MULTISIG WALLET (SHOULD INCLUDE PAYOUT TX):\n" + multisigWallet.getTxs());
+// if (multisigWallet.getTxs().size() != 3) throw new RuntimeException("Loser's multisig wallet does not include record of payout tx");
+// Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
- dispute.setDisputePayoutTxId(committedDisputePayoutTx.getTxId().toString());
- BtcWalletService.printTx("Disputed payoutTx received from peer", committedDisputePayoutTx);
+ dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
+ XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
requestPersistence();
}
+ // Arbitrator receives updated multisig hex from dispute opener's peer (if co-signer) and returns updated payout tx to be signed and published
+ private void onArbitratorPayoutTxRequest(ArbitratorPayoutTxRequest request) {
+ String tradeId = request.getTradeId();
+ Dispute dispute = findDispute(request.getDispute().getTradeId(), request.getDispute().getTraderId()).get();
+ DisputeResult disputeResult = dispute.getDisputeResultProperty().get();
+ Contract contract = dispute.getContract();
+
+ // verify sender is co-signer and receiver is arbitrator
+ System.out.println("Any of these null???"); // TODO (woodser): NPE if dispute opener's peer-as-cosigner's ticket is closed first
+ System.out.println(disputeResult);
+ System.out.println(disputeResult.getWinner());
+ System.out.println(contract.getBuyerNodeAddress());
+ System.out.println(contract.getSellerNodeAddress());
+ boolean senderIsWinner = (disputeResult.getWinner() == Winner.BUYER && contract.getBuyerNodeAddress().equals(request.getSenderNodeAddress())) || (disputeResult.getWinner() == Winner.SELLER && contract.getSellerNodeAddress().equals(request.getSenderNodeAddress()));
+ boolean senderIsCosigner = senderIsWinner || disputeResult.isLoserPublisher();
+ boolean receiverIsArbitrator = pubKeyRing.equals(dispute.getAgentPubKeyRing());
+
+ System.out.println("TESTING PUB KEY RINGS");
+ System.out.println(pubKeyRing);
+ System.out.println(dispute.getAgentPubKeyRing());
+ System.out.println("Receiver is arbitrator: " + receiverIsArbitrator);
+
+ if (!senderIsCosigner) {
+ log.warn("Received ArbitratorPayoutTxRequest but sender is not co-signer for trade id " + tradeId);
+ return;
+ }
+ if (!receiverIsArbitrator) {
+ log.warn("Received ArbitratorPayoutTxRequest but receiver is not arbitrator for trade id " + tradeId);
+ return;
+ }
+
+ // update arbitrator's multisig wallet with co-signer's multisig hex
+ MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
+ try {
+ multisigWallet.importMultisigHex(Arrays.asList(request.getUpdatedMultisigHex()));
+ } catch (Exception e) {
+ log.warn("Failed to import multisig hex from payout co-signer for trade id " + tradeId);
+ return;
+ }
+
+ // create updated payout tx
+ MoneroTxWallet payoutTx = arbitratorCreatesDisputedPayoutTx(contract, dispute, disputeResult, multisigWallet);
+ System.out.println("Arbitrator created updated payout tx for co-signer!!!");
+ System.out.println(payoutTx);
+
+ // send updated payout tx to sender
+ PubKeyRing senderPubKeyRing = contract.getBuyerNodeAddress().equals(request.getSenderNodeAddress()) ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
+ ArbitratorPayoutTxResponse response = new ArbitratorPayoutTxResponse(
+ tradeId,
+ p2PService.getAddress(),
+ UUID.randomUUID().toString(),
+ SupportType.ARBITRATION,
+ payoutTx.getTxSet().getMultisigTxHex());
+ log.info("Send {} to peer {}. tradeId={}, uid={}", response.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), response.getUid());
+ p2PService.sendEncryptedDirectMessage(request.getSenderNodeAddress(),
+ senderPubKeyRing,
+ response,
+ new SendDirectMessageListener() {
+ @Override
+ public void onArrived() {
+ log.info("{} arrived at peer {}. tradeId={}, uid={}",
+ response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid());
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
+ response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid(), errorMessage);
+ }
+ }
+ );
+ }
+
+ // Dispute opener's peer receives updated payout tx after providing updated multisig hex (if co-signer)
+ private void onArbitratorPayoutTxResponse(ArbitratorPayoutTxResponse response) {
+
+ // gather and verify trade info // TODO (woodser): verify response is from arbitrator, etc
+ String tradeId = response.getTradeId();
+
+ // verify and sign dispute payout tx
+ MoneroTxSet signedPayoutTx = traderSignsDisputePayoutTx(tradeId, response.getArbitratorSignedPayoutTxHex());
+
+ // process fully signed payout tx (publish, notify peer, etc)
+ onTraderSignedDisputePayoutTx(tradeId, signedPayoutTx);
+ }
+
+ private void onTraderSignedDisputePayoutTx(String tradeId, MoneroTxSet txSet) {
+
+ // gather trade info
+ MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
+ Optional 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 @@ private void updateTradeOrOpenOfferManager(String tradeId) {
openOfferOptional.ifPresent(openOffer -> 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 17d90090442..fb940a12a74 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.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 @@
@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 PeerPublishedDisputePayoutTxMessage(byte[] transaction,
// 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 @@ private PeerPublishedDisputePayoutTxMessage(byte[] transaction,
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 protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
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 String getTradeId() {
@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 776e2191ed3..f31680f9818 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 void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
@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 00000000000..9d2e7eed0a2
--- /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 00000000000..88ecab2624f
--- /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 f60cda80565..ac47e2974bb 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 @@
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 @@ private OpenNewDisputeMessage(Dispute dispute,
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 protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
.setUid(uid)
.setDispute(dispute.toProtoMessage())
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
- .setType(SupportType.toProtoMessage(supportType)))
+ .setType(SupportType.toProtoMessage(supportType))
+ .setUpdatedMultisigHex(updatedMultisigHex))
.build();
}
@@ -78,7 +84,8 @@ public static OpenNewDisputeMessage fromProto(protobuf.OpenNewDisputeMessage pro
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getUid(),
messageVersion,
- SupportType.fromProto(proto.getType()));
+ SupportType.fromProto(proto.getType()),
+ proto.getUpdatedMultisigHex());
}
@Override
@@ -94,6 +101,7 @@ public String toString() {
",\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 deb1537b624..527c53ab017 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 void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
@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 00000000000..13a9d6abd7f
--- /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 3a0005f8247..38b0540053f 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 protobuf.Tradable toProtoMessage() {
}
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 static Tradable fromProto(protobuf.BuyerAsMakerTrade buyerAsMakerTradePro
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 2ccf0fb3cb9..6007437e9ff 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 BuyerAsTakerTrade(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) {
super(offer,
tradeAmount,
txFee,
takerFee,
- isCurrencyForTakerFeeBtc,
tradePrice,
- tradingPeerNodeAddress,
+ makerNodeAddress,
+ takerNodeAddress,
arbitratorNodeAddress,
- mediatorNodeAddress,
- refundAgentNodeAddress,
- btcWalletService,
+ xmrWalletService,
processModel,
uid);
}
@@ -83,7 +79,7 @@ public protobuf.Tradable toProtoMessage() {
}
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 static Tradable fromProto(protobuf.BuyerAsTakerTrade buyerAsTakerTradePro
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 82f38cf9c16..4b6313e32a0 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 81602d226d0..7b145ff6b66 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.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 Contract(OfferPayload offerPayload,
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 Contract(OfferPayload offerPayload,
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 static Contract fromProto(protobuf.Contract proto, CoreProtoResolver core
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 static Contract fromProto(protobuf.Contract proto, CoreProtoResolver core
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 protobuf.Contract toProtoMessage() {
.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 protobuf.Contract toProtoMessage() {
.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 PubKeyRing getSellerPubKeyRing() {
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 String toString() {
"\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 String toString() {
",\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 ccbf66a0f6c..e0e83c24be1 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 protobuf.Tradable toProtoMessage() {
}
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 static Tradable fromProto(protobuf.SellerAsMakerTrade sellerAsMakerTradeP
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 11fb6c281d0..85cf04e2d18 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 SellerAsTakerTrade(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) {
super(offer,
tradeAmount,
txFee,
takerFee,
- isCurrencyForTakerFeeBtc,
tradePrice,
- tradingPeerNodeAddress,
+ makerNodeAddress,
+ takerNodeAddress,
arbitratorNodeAddress,
- mediatorNodeAddress,
- refundAgentNodeAddress,
- btcWalletService,
+ xmrWalletService,
processModel,
uid);
}
@@ -83,7 +79,7 @@ public protobuf.Tradable toProtoMessage() {
}
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 static Tradable fromProto(protobuf.SellerAsTakerTrade sellerAsTakerTradeP
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 a87c18ddee9..e54c4b952b8 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 c3a668708ad..e39d29513f3 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 Message toProtoMessage() {
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 de5c1949bb7..8e5e9bf963b 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.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 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.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 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 enum State {
// 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 static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
@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 static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
@Nullable
@Getter
@Setter
- private String depositTxId;
- @Nullable
- @Getter
- @Setter
private String payoutTxId;
@Getter
@Setter
@@ -324,8 +325,6 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
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 static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
@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 static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
transient final private StringProperty errorMessageProperty = new SimpleStringProperty();
// Mutable
- @Nullable
- transient private Transaction depositTx;
@Getter
transient private boolean isInitialized;
@@ -409,7 +406,7 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
transient private Transaction delayedPayoutTx;
@Nullable
- transient private Transaction payoutTx;
+ transient private MoneroTxWallet payoutTx;
@Nullable
transient private Coin tradeAmount;
@@ -462,6 +459,33 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
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 static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
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 @@ protected Trade(Offer offer,
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 Message toProtoMessage() {
.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 Message toProtoMessage() {
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 static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv
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 static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv
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 void initialize(ProcessModelServiceProvider serviceProvider) {
// API
///////////////////////////////////////////////////////////////////////////////////////////
+ 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 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 (getDepositTx() != null)
- applyDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getTxId()));
+ 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 applyDepositTx(Transaction tx) {
- this.depositTx = tx;
- depositTxId = depositTx.getTxId().toString();
- setupConfidenceListener();
+ 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 void applyDelayedPayoutTxBytes(byte[] delayedPayoutTxBytes) {
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 void onComplete() {
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 void setTradePeriodState(TradePeriodState tradePeriodState) {
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 void setTradeAmount(Coin tradeAmount) {
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 @@ private long getMaxTradePeriod() {
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 @@ private long getTradeStartTime() {
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 Coin getTradeAmount() {
}
@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 String getErrorMessage() {
public boolean isTxChainInvalid() {
return offer.getOfferFeePaymentTxId() == null ||
getTakerFeeTxId() == null ||
- getDepositTxId() == null ||
- getDepositTx() == null ||
+ getMakerDepositTxId() == null ||
+ getTakerDepositTxId() == null ||
getDelayedPayoutTxBytes() == null;
}
@@ -1076,31 +1196,31 @@ private ObjectProperty getTradeVolumeProperty() {
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 @@ private void setConfirmedState() {
// 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 @@ private void setConfirmedState() {
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 String toString() {
",\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 String toString() {
",\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 String toString() {
",\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 013dcdb42ab..829c5e2c018 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 static void validatePayoutTxInput(Transaction depositTx,
}
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 6707fde8ea7..e3ac16e5114 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.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 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 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 TradeManager(User user,
@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 void readPersisted(Runnable completeHandler) {
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 void onUpdatedDataReceived() {
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 void requestPersistence() {
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 void checkOfferAvailability(Offer offer,
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 void checkOfferAvailability(Offer offer,
public void onTakeOffer(Coin amount,
Coin txFee,
Coin takerFee,
- boolean isCurrencyForTakerFeeBtc,
long tradePrice,
Coin fundsNeededForTrade,
Offer offer,
@@ -413,13 +578,11 @@ public void onTakeOffer(Coin amount,
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 void onTakeOffer(Coin amount,
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 @@ private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean is
///////////////////////////////////////////////////////////////////////////////////////////
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();
- }
- }
+ 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 {
- 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);
+ @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 void onTradeCompleted(Trade trade) {
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 void closeDisputedTrade(String tradeId, Trade.DisputeState disputeState)
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 Set getSetOfFailedOrClosedTradeIdsFromLockedInFunds() throws Trad
.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 Set getSetOfFailedOrClosedTradeIdsFromLockedInFunds() throws Trad
.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 @@ private boolean recoverAddresses(Trade trade) {
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 a026f6ab982..a7d779e770c 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 Tuple2 getAvailableAddresses(Trade trade) {
* @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 00000000000..2f4da66098c
--- /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 84746a2128c..51277a221e2 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 String checkUnFail(Trade trade) {
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 ea56069cfc7..614d56a0ebe 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.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 @@
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 CounterCurrencyTransferStartedMessage(String tradeId,
private CounterCurrencyTransferStartedMessage(String tradeId,
String buyerPayoutAddress,
NodeAddress senderNodeAddress,
- byte[] buyerSignature,
+ String buyerPayoutTxSigned,
@Nullable String counterCurrencyTxId,
@Nullable String counterCurrencyExtraData,
String uid,
@@ -79,7 +76,7 @@ private CounterCurrencyTransferStartedMessage(String tradeId,
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 protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
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 static CounterCurrencyTransferStartedMessage fromProto(protobuf.CounterCu
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 String toString() {
",\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 631f1a9ca2d..83b84b10472 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.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 00000000000..a1c3a12300d
--- /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 00000000000..c3bce4cc772
--- /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 00000000000..0a2b63d1f2a
--- /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 00000000000..a4bc7c4fd52
--- /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 86ed851ba8e..c61f0193ef7 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.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 @@
@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 PayoutTxPublishedMessage(String tradeId,
///////////////////////////////////////////////////////////////////////////////////////////
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 @@ private PayoutTxPublishedMessage(String tradeId,
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 static NetworkEnvelope fromProto(protobuf.PayoutTxPublishedMessage proto,
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 static NetworkEnvelope fromProto(protobuf.PayoutTxPublishedMessage proto,
@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 00000000000..f20d7c9221e
--- /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 00000000000..3acfcc5c697
--- /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 00000000000..5bda7e48dc7
--- /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 c9280d0e206..f50bd8e40ee 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.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 BuyerAsMakerProtocol(BuyerAsMakerTrade trade) {
// 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 @@ protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) {
protected Class extends TradeTask> 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 48631776afb..6eecc420ff1 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.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.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 BuyerAsTakerProtocol(BuyerAsTakerTrade trade) {
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 BuyerAsTakerProtocol(BuyerAsTakerTrade trade) {
// 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 @@ protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
protected Class extends TradeTask> 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 1fee12e22be..f7fc3ceb3b6 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.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 void onMailboxMessage(TradeMessage message, NodeAddress peer) {
// 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 void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler er
.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 void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler er
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 void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler er
///////////////////////////////////////////////////////////////////////////////////////////
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 23807c96f56..d8e0e717215 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.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 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 @@ protected void handle(MediatedPayoutTxPublishedMessage message, NodeAddress peer
// 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 349e677a702..c56750ca177 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 ad0e10d671b..5ea09006947 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.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 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 void applyTransient(ProcessModelServiceProvider provider,
@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 protobuf.ProcessModel toProtoMessage() {
.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 static ProcessModel fromProto(protobuf.ProcessModel proto, CoreProtoResol
// 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 static ProcessModel fromProto(protobuf.ProcessModel proto, CoreProtoResol
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 static ProcessModel fromProto(protobuf.ProcessModel proto, CoreProtoResol
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 Coin getFundsNeededForTrade() {
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 void setPaymentStartedMessageState(MessageState paymentStartedMessageStat
}
}
+ 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 KeyRing getKeyRing() {
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 37600eccaf3..1b1fe0cc2b9 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.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 ProcessModelServiceProvider(OpenOfferManager openOfferManager,
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 0cdaad17c08..874a7276fe8 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 @@
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 SellerAsMakerProtocol(SellerAsMakerTrade trade) {
}
- ///////////////////////////////////////////////////////////////////////////////////////////
- // 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 @@ protected void handle(DepositTxMessage message, NodeAddress peer) {
.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 @@ protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
protected Class extends TradeTask> 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 7adf9026c5c..de2ee5162cb 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.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 SellerAsTakerProtocol(SellerAsTakerTrade trade) {
@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 @@ private void handle(InputsForDepositTxResponse message, NodeAddress peer) {
.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 @@ protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
protected Class extends TradeTask> 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 54d010eb7ee..64b16177a1f 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 @@
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 void onMailboxMessage(TradeMessage message, NodeAddress peerNodeAddress)
}
- ///////////////////////////////////////////////////////////////////////////////////////////
- // 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 void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler e
.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 void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler e
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 249d0ac73bd..e506f281cbe 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 00000000000..53134ecf9a9
--- /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 2135aecec8a..6b4bd794651 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.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.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 void onDirectMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKe
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 void onDirectMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKe
@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 void removeMailboxMessageAfterProcessing(TradeMessage tradeMessage) {
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 @@ private void onAckMessage(AckMessage ackMessage, NodeAddress peer) {
}
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 @@ protected void sendAckMessage(TradeMessage message, boolean result, @Nullable St
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 void onFault(String errorMessage) {
);
}
-
///////////////////////////////////////////////////////////////////////////////////////////
// Timeout
///////////////////////////////////////////////////////////////////////////////////////////
@@ -343,11 +413,27 @@ protected void handleTaskRunnerFault(FluentProtocol.Event event, String errorMes
// 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 @@ private boolean isPubKeyValid(DecryptedMessageWithPubKey message) {
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 fe521e99186..133da6d326e 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 static TradeProtocol getNewTradeProtocol(Trade trade) {
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 fef4ff490d6..bf7e4fa28c9 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 5da74be2083..e2df05570b5 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 Message toProtoMessage() {
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 static TradingPeer fromProto(protobuf.TradingPeer proto, CoreProtoResolve
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 df3b8df2c63..5aa6ec5a439 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 BroadcastPayoutTx(TaskRunner taskHandler, Trade trade) {
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 00000000000..284c434e9a7
--- /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 00000000000..e8f8e8cbd20
--- /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 aab0bb5a3b9..775079c419e 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 ProcessPeerPublishedDelayedPayoutTxMessage(TaskRunner taskHandler,
protected void run() {
try {
runInterceptHook();
-
- 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();
+ 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();
} 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 00000000000..67a15568a12
--- /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 00000000000..62b06e122ae
--- /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 00000000000..6eb96385a79
--- /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 5d925ae41c2..213a904e6ae 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 SetupPayoutTxListener(TaskRunner taskHandler, Trade trade) {
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 void onTransactionConfidenceChanged(TransactionConfidence confidence) {
}
}
- 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 00000000000..a970807410a
--- /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 b35e1f53a62..38c718ce314 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 PublishedDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
@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 00000000000..f85f384e92a
--- /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