diff --git a/.gitattributes b/.gitattributes
index 923d54457a..54522ef5ec 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 7f69fae833..0000000000
--- 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 3c462f9a6d..0000000000
--- 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 16e5fa4216..0000000000
--- 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 0000000000..c073c5ccb1
--- /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 f3d52c3073..d5dc205578 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 c5ee421e25..ddc64a6f9e 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 c7e0a43c23..0000000000
--- 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 dba13ed2dd..93a5d000f0 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 b76c4d0ef9..addf52d17c 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 fc365931d5..5b327ccb29 100644
--- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBSQOfferTest.java
@@ -103,7 +103,6 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
- assertFalse(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
@@ -115,9 +114,10 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositConfirmed()) {
- log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
+ log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
- trade.getDepositTxId(),
+ trade.getMakerDepositTxId(),
+ trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;
diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java
index 93d9b1b9c8..0f0091fc9f 100644
--- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java
@@ -86,7 +86,6 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
- assertFalse(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
@@ -100,9 +99,10 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositConfirmed()) {
- log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
+ log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
- trade.getDepositTxId(),
+ trade.getMakerDepositTxId(),
+ trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;
diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java
index 786601e6fa..1ffa318afb 100644
--- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBSQOfferTest.java
@@ -104,7 +104,6 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
- assertTrue(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
@@ -116,9 +115,10 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositConfirmed()) {
- log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
+ log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
- trade.getDepositTxId(),
+ trade.getMakerDepositTxId(),
+ trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;
diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java
index ece3432123..1e5b95448b 100644
--- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java
+++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java
@@ -90,7 +90,6 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
assertNotNull(trade);
assertEquals(offerId, trade.getTradeId());
- assertTrue(trade.getIsCurrencyForTakerFeeBtc());
// Cache the trade id for the other tests.
tradeId = trade.getTradeId();
@@ -104,9 +103,10 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
trade = bobClient.getTrade(trade.getTradeId());
if (!trade.getIsDepositConfirmed()) {
- log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
+ log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
trade.getShortId(),
- trade.getDepositTxId(),
+ trade.getMakerDepositTxId(),
+ trade.getTakerDepositTxId(),
i);
genBtcBlocksThenWait(1, 4000);
continue;
diff --git a/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/BotProtocol.java b/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/BotProtocol.java
index 51d59e7537..1435d61d4b 100644
--- a/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/BotProtocol.java
+++ b/apitest/src/test/java/bisq/apitest/scenario/bot/protocol/BotProtocol.java
@@ -304,7 +304,7 @@ public abstract class BotProtocol {
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex));
}
} // end while
- throw new IllegalStateException(stoppedWaitingForDepositFeeTxMsg(this.getBotClient().getTrade(tradeId).getDepositTxId()));
+ throw new IllegalStateException(stoppedWaitingForDepositFeeTxMsg(this.getBotClient().getTrade(tradeId).getTakerDepositTxId()));
} catch (ManualBotShutdownException ex) {
throw ex; // not an error, tells bot to shutdown
} catch (Exception ex) {
@@ -314,10 +314,10 @@ public abstract class BotProtocol {
private final Predicate isDepositFeeTxStepComplete = (trade) -> {
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) && trade.getIsDepositPublished()) {
- log.info("Taker deposit fee tx {} has been published.", trade.getDepositTxId());
+ log.info("Taker deposit fee tx {} has been published.", trade.getTakerDepositTxId());
return true;
} else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED) && trade.getIsDepositConfirmed()) {
- log.info("Taker deposit fee tx {} has been confirmed.", trade.getDepositTxId());
+ log.info("Taker deposit fee tx {} has been confirmed.", trade.getTakerDepositTxId());
return true;
} else {
return false;
diff --git a/assets/src/test/java/bisq/asset/coins/IridiumTest.java b/assets/src/test/java/bisq/asset/coins/IridiumTest.java
index c9572aaaac..aedfd58998 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 1556a6aa31..049bc350aa 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 4abf20276e..c3070a9826 100644
--- a/cli/src/main/java/bisq/cli/CurrencyFormat.java
+++ b/cli/src/main/java/bisq/cli/CurrencyFormat.java
@@ -25,6 +25,7 @@ import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.Locale;
@@ -32,6 +33,10 @@ import static java.lang.String.format;
import static java.math.RoundingMode.HALF_UP;
import static java.math.RoundingMode.UNNECESSARY;
+
+
+import monero.common.MoneroUtils;
+
@VisibleForTesting
public class CurrencyFormat {
@@ -57,6 +62,10 @@ public class CurrencyFormat {
return BSQ_FORMAT.format(BigDecimal.valueOf(sats).divide(BSQ_SATOSHI_DIVISOR));
}
+ public static String formatXmr(BigInteger amount) {
+ return "" + MoneroUtils.atomicUnitsToXmr(amount);
+ }
+
public static String formatBsqAmount(long bsqSats) {
// BSQ sats = trade.getOffer().getVolume()
NUMBER_FORMAT.setMinimumFractionDigits(2);
diff --git a/cli/src/main/java/bisq/cli/TradeFormat.java b/cli/src/main/java/bisq/cli/TradeFormat.java
index dbf8dbf4b8..ded61548c3 100644
--- a/cli/src/main/java/bisq/cli/TradeFormat.java
+++ b/cli/src/main/java/bisq/cli/TradeFormat.java
@@ -17,6 +17,8 @@
package bisq.cli;
+import bisq.core.util.ParsingUtils;
+
import bisq.proto.grpc.ContractInfo;
import bisq.proto.grpc.TradeInfo;
@@ -133,7 +135,7 @@ public class TradeFormat {
bsqReceiveAddress.apply(tradeInfo, showBsqBuyerAddress));
}
- private static final Function priceHeader = (t) ->
+ private static final Function priceHeader = (t) -> // TODO (woodser): update these to XMR
t.getOffer().getBaseCurrencyCode().equals("BTC")
? COL_HEADER_PRICE
: COL_HEADER_PRICE_OF_ALTCOIN;
@@ -144,11 +146,7 @@ public class TradeFormat {
: t.getOffer().getBaseCurrencyCode();
private static final BiFunction makerTakerFeeHeaderCurrencyCode = (t, isTaker) -> {
- if (isTaker) {
- return t.getIsCurrencyForTakerFeeBtc() ? "BTC" : "BSQ";
- } else {
- return t.getOffer().getIsCurrencyForMakerFeeBtc() ? "BTC" : "BSQ";
- }
+ return "XMR";
};
private static final Function paymentStatusHeaderCurrencyCode = (t) ->
@@ -163,7 +161,7 @@ public class TradeFormat {
private static final Function amountFormat = (t) ->
t.getOffer().getBaseCurrencyCode().equals("BTC")
- ? formatSatoshis(t.getTradeAmountAsLong())
+ ? formatSatoshis(t.getTradeAmountAsLong()) // TODO (woodser): delete formatSatoshis(), formatBsq() and change base currency code to XMR
: formatCryptoCurrencyOfferVolume(t.getOffer().getVolume());
private static final BiFunction makerTakerMinerTxFeeFormat = (t, isTaker) -> {
@@ -175,15 +173,7 @@ public class TradeFormat {
};
private static final BiFunction makerTakerFeeFormat = (t, isTaker) -> {
- if (isTaker) {
- return t.getIsCurrencyForTakerFeeBtc()
- ? formatSatoshis(t.getTakerFeeAsLong())
- : formatBsq(t.getTakerFeeAsLong());
- } else {
- return t.getOffer().getIsCurrencyForMakerFeeBtc()
- ? formatSatoshis(t.getOffer().getMakerFee())
- : formatBsq(t.getOffer().getMakerFee());
- }
+ return formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTakerFeeAsLong()));
};
private static final Function tradeCostFormat = (t) ->
diff --git a/common/src/main/java/bisq/common/taskrunner/Task.java b/common/src/main/java/bisq/common/taskrunner/Task.java
index e6fce41550..79bff399ee 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 b634f01a8d..69c123bd6a 100644
--- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java
+++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java
@@ -33,6 +33,7 @@ import bisq.core.payment.payload.PaymentMethod;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.arbitration.TraderDataItem;
+import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Contract;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.TradingPeer;
@@ -300,6 +301,7 @@ public class AccountAgeWitnessService {
}
private Optional findTradePeerWitness(Trade trade) {
+ if (trade instanceof ArbitratorTrade) return Optional.empty(); // TODO (woodser): arbitrator trade has two peers
TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
return (tradingPeer == null ||
tradingPeer.getPaymentAccountPayload() == null ||
@@ -842,6 +844,7 @@ public class AccountAgeWitnessService {
}
public SignState getSignState(Trade trade) {
+ if (trade instanceof ArbitratorTrade) return SignState.UNSIGNED; // TODO (woodser): arbitrator has two peers
return findTradePeerWitness(trade)
.map(this::getSignState)
.orElse(SignState.UNSIGNED);
diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java
index 2f5683108a..21942a60ff 100644
--- a/core/src/main/java/bisq/core/api/CoreTradesService.java
+++ b/core/src/main/java/bisq/core/api/CoreTradesService.java
@@ -109,7 +109,6 @@ class CoreTradesService {
tradeManager.onTakeOffer(offer.getAmount(),
takeOfferModel.getTxFeeFromFeeService(),
takeOfferModel.getTakerFee(),
- takeOfferModel.isCurrencyForTakerFeeBtc(),
offer.getPrice().getValue(),
takeOfferModel.getFundsNeededForTrade(),
offer,
diff --git a/core/src/main/java/bisq/core/api/CoreWalletsService.java b/core/src/main/java/bisq/core/api/CoreWalletsService.java
index b9ea896ef7..69b113e3c3 100644
--- a/core/src/main/java/bisq/core/api/CoreWalletsService.java
+++ b/core/src/main/java/bisq/core/api/CoreWalletsService.java
@@ -84,7 +84,6 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
-import static bisq.common.config.BaseCurrencyNetwork.BTC_DAO_REGTEST;
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
import static bisq.core.util.ParsingUtils.parseToCoin;
import static java.lang.String.format;
diff --git a/core/src/main/java/bisq/core/api/model/ContractInfo.java b/core/src/main/java/bisq/core/api/model/ContractInfo.java
index 404335c9c7..c741cec5bd 100644
--- a/core/src/main/java/bisq/core/api/model/ContractInfo.java
+++ b/core/src/main/java/bisq/core/api/model/ContractInfo.java
@@ -34,8 +34,7 @@ public class ContractInfo implements Payload {
private final String buyerNodeAddress;
private final String sellerNodeAddress;
- private final String mediatorNodeAddress;
- private final String refundAgentNodeAddress;
+ private final String arbitratorNodeAddress;
private final boolean isBuyerMakerAndSellerTaker;
private final String makerAccountId;
private final String takerAccountId;
@@ -47,8 +46,7 @@ public class ContractInfo implements Payload {
public ContractInfo(String buyerNodeAddress,
String sellerNodeAddress,
- String mediatorNodeAddress,
- String refundAgentNodeAddress,
+ String arbitratorNodeAddress,
boolean isBuyerMakerAndSellerTaker,
String makerAccountId,
String takerAccountId,
@@ -59,8 +57,7 @@ public class ContractInfo implements Payload {
long lockTime) {
this.buyerNodeAddress = buyerNodeAddress;
this.sellerNodeAddress = sellerNodeAddress;
- this.mediatorNodeAddress = mediatorNodeAddress;
- this.refundAgentNodeAddress = refundAgentNodeAddress;
+ this.arbitratorNodeAddress = arbitratorNodeAddress;
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
this.makerAccountId = makerAccountId;
this.takerAccountId = takerAccountId;
@@ -75,7 +72,6 @@ public class ContractInfo implements Payload {
// For transmitting TradeInfo messages when no contract is available.
public static Supplier emptyContract = () ->
new ContractInfo("",
- "",
"",
"",
false,
@@ -94,8 +90,7 @@ public class ContractInfo implements Payload {
public static ContractInfo fromProto(bisq.proto.grpc.ContractInfo proto) {
return new ContractInfo(proto.getBuyerNodeAddress(),
proto.getSellerNodeAddress(),
- proto.getMediatorNodeAddress(),
- proto.getRefundAgentNodeAddress(),
+ proto.getArbitratorNodeAddress(),
proto.getIsBuyerMakerAndSellerTaker(),
proto.getMakerAccountId(),
proto.getTakerAccountId(),
@@ -111,8 +106,7 @@ public class ContractInfo implements Payload {
return bisq.proto.grpc.ContractInfo.newBuilder()
.setBuyerNodeAddress(buyerNodeAddress)
.setSellerNodeAddress(sellerNodeAddress)
- .setMediatorNodeAddress(mediatorNodeAddress)
- .setRefundAgentNodeAddress(refundAgentNodeAddress)
+ .setArbitratorNodeAddress(arbitratorNodeAddress)
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
.setMakerAccountId(makerAccountId)
.setTakerAccountId(takerAccountId)
diff --git a/core/src/main/java/bisq/core/api/model/TradeInfo.java b/core/src/main/java/bisq/core/api/model/TradeInfo.java
index 078e5ee4d9..8ba70a02e2 100644
--- a/core/src/main/java/bisq/core/api/model/TradeInfo.java
+++ b/core/src/main/java/bisq/core/api/model/TradeInfo.java
@@ -47,7 +47,8 @@ public class TradeInfo implements Payload {
private final long txFeeAsLong;
private final long takerFeeAsLong;
private final String takerFeeTxId;
- private final String depositTxId;
+ private final String makerDepositTxId;
+ private final String takerDepositTxId;
private final String payoutTxId;
private final long tradeAmountAsLong;
private final long tradePrice;
@@ -74,7 +75,8 @@ public class TradeInfo implements Payload {
this.txFeeAsLong = builder.txFeeAsLong;
this.takerFeeAsLong = builder.takerFeeAsLong;
this.takerFeeTxId = builder.takerFeeTxId;
- this.depositTxId = builder.depositTxId;
+ this.makerDepositTxId = builder.makerDepositTxId;
+ this.takerDepositTxId = builder.takerDepositTxId;
this.payoutTxId = builder.payoutTxId;
this.tradeAmountAsLong = builder.tradeAmountAsLong;
this.tradePrice = builder.tradePrice;
@@ -102,8 +104,7 @@ public class TradeInfo implements Payload {
Contract contract = trade.getContract();
contractInfo = new ContractInfo(contract.getBuyerPayoutAddressString(),
contract.getSellerPayoutAddressString(),
- contract.getMediatorNodeAddress().getFullAddress(),
- contract.getRefundAgentNodeAddress().getFullAddress(),
+ contract.getArbitratorNodeAddress().getFullAddress(),
contract.isBuyerMakerAndSellerTaker(),
contract.getMakerAccountId(),
contract.getTakerAccountId(),
@@ -122,12 +123,12 @@ public class TradeInfo implements Payload {
.withShortId(trade.getShortId())
.withDate(trade.getDate().getTime())
.withRole(role == null ? "" : role)
- .withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc())
.withTxFeeAsLong(trade.getTxFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeTxId(trade.getTakerFeeTxId())
- .withDepositTxId(trade.getDepositTxId())
+ .withMakerDepositTxId(trade.getMakerDepositTxId())
+ .withTakerDepositTxId(trade.getTakerDepositTxId())
.withPayoutTxId(trade.getPayoutTxId())
.withTradeAmountAsLong(trade.getTradeAmountAsLong())
.withTradePrice(trade.getTradePrice().getValue())
@@ -159,11 +160,11 @@ public class TradeInfo implements Payload {
.setShortId(shortId)
.setDate(date)
.setRole(role)
- .setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc)
.setTxFeeAsLong(txFeeAsLong)
.setTakerFeeAsLong(takerFeeAsLong)
.setTakerFeeTxId(takerFeeTxId == null ? "" : takerFeeTxId)
- .setDepositTxId(depositTxId == null ? "" : depositTxId)
+ .setMakerDepositTxId(makerDepositTxId == null ? "" : makerDepositTxId)
+ .setTakerDepositTxId(takerDepositTxId == null ? "" : takerDepositTxId)
.setPayoutTxId(payoutTxId == null ? "" : payoutTxId)
.setTradeAmountAsLong(tradeAmountAsLong)
.setTradePrice(tradePrice)
@@ -189,11 +190,11 @@ public class TradeInfo implements Payload {
.withShortId(proto.getShortId())
.withDate(proto.getDate())
.withRole(proto.getRole())
- .withIsCurrencyForTakerFeeBtc(proto.getIsCurrencyForTakerFeeBtc())
.withTxFeeAsLong(proto.getTxFeeAsLong())
.withTakerFeeAsLong(proto.getTakerFeeAsLong())
.withTakerFeeTxId(proto.getTakerFeeTxId())
- .withDepositTxId(proto.getDepositTxId())
+ .withMakerDepositTxId(proto.getMakerDepositTxId())
+ .withTakerDepositTxId(proto.getTakerDepositTxId())
.withPayoutTxId(proto.getPayoutTxId())
.withTradeAmountAsLong(proto.getTradeAmountAsLong())
.withTradePrice(proto.getTradePrice())
@@ -228,7 +229,8 @@ public class TradeInfo implements Payload {
private long txFeeAsLong;
private long takerFeeAsLong;
private String takerFeeTxId;
- private String depositTxId;
+ private String makerDepositTxId;
+ private String takerDepositTxId;
private String payoutTxId;
private long tradeAmountAsLong;
private long tradePrice;
@@ -290,8 +292,13 @@ public class TradeInfo implements Payload {
return this;
}
- public TradeInfoBuilder withDepositTxId(String depositTxId) {
- this.depositTxId = depositTxId;
+ public TradeInfoBuilder withMakerDepositTxId(String makerDepositTxId) {
+ this.makerDepositTxId = makerDepositTxId;
+ return this;
+ }
+
+ public TradeInfoBuilder withTakerDepositTxId(String takerDepositTxId) {
+ this.takerDepositTxId = takerDepositTxId;
return this;
}
@@ -386,7 +393,8 @@ public class TradeInfo implements Payload {
", txFeeAsLong='" + txFeeAsLong + '\'' + "\n" +
", takerFeeAsLong='" + takerFeeAsLong + '\'' + "\n" +
", takerFeeTxId='" + takerFeeTxId + '\'' + "\n" +
- ", depositTxId='" + depositTxId + '\'' + "\n" +
+ ", makerDepositTxId='" + makerDepositTxId + '\'' + "\n" +
+ ", takerDepositTxId='" + takerDepositTxId + '\'' + "\n" +
", payoutTxId='" + payoutTxId + '\'' + "\n" +
", tradeAmountAsLong='" + tradeAmountAsLong + '\'' + "\n" +
", tradePrice='" + tradePrice + '\'' + "\n" +
diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java
index 980875cd0a..355483cee0 100644
--- a/core/src/main/java/bisq/core/app/BisqExecutable.java
+++ b/core/src/main/java/bisq/core/app/BisqExecutable.java
@@ -20,6 +20,7 @@ package bisq.core.app;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoSetup;
import bisq.core.dao.node.full.RpcService;
import bisq.core.offer.OpenOfferManager;
@@ -235,6 +236,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
injector.getInstance(RpcService.class).shutDown();
injector.getInstance(DaoSetup.class).shutDown();
injector.getInstance(AvoidStandbyModeService.class).shutDown();
+ injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc?
log.info("OpenOfferManager shutdown started");
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
log.info("OpenOfferManager shutdown completed");
diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java
index de6db2c2ba..ee212e4fb2 100644
--- a/core/src/main/java/bisq/core/app/BisqSetup.java
+++ b/core/src/main/java/bisq/core/app/BisqSetup.java
@@ -28,6 +28,7 @@ import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletsManager;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
import bisq.core.dao.governance.voteresult.VoteResultException;
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
@@ -126,6 +127,7 @@ public class BisqSetup {
private final WalletsManager walletsManager;
private final WalletsSetup walletsSetup;
private final BtcWalletService btcWalletService;
+ private final XmrWalletService xmrWalletService;
private final P2PService p2PService;
private final SignedWitnessStorageService signedWitnessStorageService;
private final TradeManager tradeManager;
@@ -210,6 +212,7 @@ public class BisqSetup {
WalletAppSetup walletAppSetup,
WalletsManager walletsManager,
WalletsSetup walletsSetup,
+ XmrWalletService xmrWalletService,
BtcWalletService btcWalletService,
P2PService p2PService,
SignedWitnessStorageService signedWitnessStorageService,
@@ -231,6 +234,7 @@ public class BisqSetup {
this.walletAppSetup = walletAppSetup;
this.walletsManager = walletsManager;
this.walletsSetup = walletsSetup;
+ this.xmrWalletService = xmrWalletService;
this.btcWalletService = btcWalletService;
this.p2PService = p2PService;
this.signedWitnessStorageService = signedWitnessStorageService;
@@ -514,17 +518,18 @@ public class BisqSetup {
// We check if we have open offers with no confidence object at the maker fee tx. That can happen if the
// miner fee was too low and the transaction got removed from mempool and got out from our wallet after a
// resync.
- openOfferManager.getObservableList().forEach(e -> {
- String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId();
- if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) {
- String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx",
- e.getOffer().getShortId(), offerFeePaymentTxId);
- log.warn(message);
- if (lockedUpFundsHandler != null) {
- lockedUpFundsHandler.accept(message);
- }
- }
- });
+ // TODO (woodser): check for invalid maker fee txs with xmr?
+// openOfferManager.getObservableList().forEach(e -> {
+// String offerFeePaymentTxId = e.getOffer().getOfferFeePaymentTxId();
+// if (btcWalletService.getConfidenceForTxId(offerFeePaymentTxId) == null) { // TODO (woodser): needed for xmr base?
+// String message = Res.get("popup.warning.openOfferWithInvalidMakerFeeTx",
+// e.getOffer().getShortId(), offerFeePaymentTxId);
+// log.warn(message);
+// if (lockedUpFundsHandler != null) {
+// lockedUpFundsHandler.accept(message);
+// }
+// }
+// });
}
@Nullable
diff --git a/core/src/main/java/bisq/core/app/WalletAppSetup.java b/core/src/main/java/bisq/core/app/WalletAppSetup.java
index b54e6706ad..a5cb49acd4 100644
--- a/core/src/main/java/bisq/core/app/WalletAppSetup.java
+++ b/core/src/main/java/bisq/core/app/WalletAppSetup.java
@@ -249,9 +249,12 @@ public class WalletAppSetup {
.filter(trade -> trade.getOffer() != null)
.forEach(trade -> {
String details = null;
- if (txId.equals(trade.getDepositTxId())) {
- details = Res.get("popup.warning.trade.txRejected.deposit");
+ if (txId.equals(trade.getMakerDepositTxId())) {
+ details = Res.get("popup.warning.trade.txRejected.deposit"); // TODO (woodser): txRejected.maker_deposit, txRejected.taker_deposit
}
+ if (txId.equals(trade.getTakerDepositTxId())) {
+ details = Res.get("popup.warning.trade.txRejected.deposit");
+ }
if (txId.equals(trade.getOffer().getOfferFeePaymentTxId()) || txId.equals(trade.getTakerFeeTxId())) {
details = Res.get("popup.warning.trade.txRejected.tradeFee");
}
diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java
index ccca2a6a0c..9ad33d9b2f 100644
--- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java
+++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java
@@ -21,6 +21,7 @@ import bisq.core.app.BisqExecutable;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoSetup;
import bisq.core.dao.node.full.RpcService;
import bisq.core.offer.OpenOfferManager;
@@ -101,6 +102,7 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
});
});
injector.getInstance(WalletsSetup.class).shutDown();
+ injector.getInstance(XmrWalletService.class).shutDown(); // TODO (woodser): this is not actually called, perhaps because WalletsSetup.class completes too quick so its listener calls System.exit(0)
injector.getInstance(BtcWalletService.class).shutDown();
injector.getInstance(BsqWalletService.class).shutDown();
}));
diff --git a/core/src/main/java/bisq/core/btc/Balances.java b/core/src/main/java/bisq/core/btc/Balances.java
index 5d3ddd4e43..bb38c1ef19 100644
--- a/core/src/main/java/bisq/core/btc/Balances.java
+++ b/core/src/main/java/bisq/core/btc/Balances.java
@@ -17,9 +17,7 @@
package bisq.core.btc;
-import bisq.core.btc.listeners.BalanceListener;
-import bisq.core.btc.model.AddressEntry;
-import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.dispute.Dispute;
@@ -32,7 +30,6 @@ import bisq.core.trade.failed.FailedTradesManager;
import bisq.common.UserThread;
import org.bitcoinj.core.Coin;
-import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
@@ -41,16 +38,23 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
-import java.util.Objects;
-import java.util.stream.Stream;
+import java.math.BigInteger;
+
+import java.util.List;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
+
+
+import monero.wallet.model.MoneroAccount;
+import monero.wallet.model.MoneroOutputWallet;
+import monero.wallet.model.MoneroWalletListener;
+
@Slf4j
public class Balances {
private final TradeManager tradeManager;
- private final BtcWalletService btcWalletService;
+ private final XmrWalletService xmrWalletService;
private final OpenOfferManager openOfferManager;
private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager;
@@ -65,13 +69,13 @@ public class Balances {
@Inject
public Balances(TradeManager tradeManager,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
OpenOfferManager openOfferManager,
ClosedTradableManager closedTradableManager,
FailedTradesManager failedTradesManager,
RefundManager refundManager) {
this.tradeManager = tradeManager;
- this.btcWalletService = btcWalletService;
+ this.xmrWalletService = xmrWalletService;
this.openOfferManager = openOfferManager;
this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager;
@@ -82,13 +86,11 @@ public class Balances {
openOfferManager.getObservableList().addListener((ListChangeListener) c -> updateBalance());
tradeManager.getObservableList().addListener((ListChangeListener) change -> updateBalance());
refundManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> updateBalance());
- btcWalletService.addBalanceListener(new BalanceListener() {
- @Override
- public void onBalanceChanged(Coin balance, Transaction tx) {
- updateBalance();
- }
+ xmrWalletService.getWallet().addListener(new MoneroWalletListener() {
+ @Override public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { updateBalance(); }
+ @Override public void onOutputReceived(MoneroOutputWallet output) { updateBalance(); }
+ @Override public void onOutputSpent(MoneroOutputWallet output) { updateBalance(); }
});
-
updateBalance();
}
@@ -101,31 +103,24 @@ public class Balances {
});
}
+ // TODO (woodser): reserved balance = reserved for trade, locked balance = locked in multisig
+
private void updateAvailableBalance() {
- long sum = btcWalletService.getAddressEntriesForAvailableBalanceStream()
- .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).value)
- .sum();
- availableBalance.set(Coin.valueOf(sum));
+ availableBalance.set(Coin.valueOf(xmrWalletService.getWallet().getUnlockedBalance(0).longValue()));
}
private void updateReservedBalance() {
- long sum = openOfferManager.getObservableList().stream()
- .map(openOffer -> btcWalletService.getAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE)
- .orElse(null))
- .filter(Objects::nonNull)
- .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).value)
- .sum();
- reservedBalance.set(Coin.valueOf(sum));
+ BigInteger sum = new BigInteger("0");
+ List accounts = xmrWalletService.getWallet().getAccounts();
+ for (MoneroAccount account : accounts) {
+ if (account.getIndex() != 0) sum = sum.add(account.getBalance());
+ }
+ reservedBalance.set(Coin.valueOf(sum.longValue()));
}
-
+
private void updateLockedBalance() {
- Stream lockedTrades = Stream.concat(closedTradableManager.getTradesStreamWithFundsLockedIn(), failedTradesManager.getTradesStreamWithFundsLockedIn());
- lockedTrades = Stream.concat(lockedTrades, tradeManager.getTradesStreamWithFundsLockedIn());
- long sum = lockedTrades.map(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG)
- .orElse(null))
- .filter(Objects::nonNull)
- .mapToLong(AddressEntry::getCoinLockedInMultiSig)
- .sum();
- lockedBalance.set(Coin.valueOf(sum));
+ BigInteger balance = xmrWalletService.getWallet().getBalance(0);
+ BigInteger unlockedBalance = xmrWalletService.getWallet().getUnlockedBalance(0);
+ lockedBalance.set(Coin.valueOf(balance.subtract(unlockedBalance).longValue()));
}
}
diff --git a/core/src/main/java/bisq/core/btc/BitcoinModule.java b/core/src/main/java/bisq/core/btc/BitcoinModule.java
index 3f733fb570..18e4b891bf 100644
--- a/core/src/main/java/bisq/core/btc/BitcoinModule.java
+++ b/core/src/main/java/bisq/core/btc/BitcoinModule.java
@@ -18,6 +18,7 @@
package bisq.core.btc;
import bisq.core.btc.model.AddressEntryList;
+import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.btc.nodes.BtcNodes;
import bisq.core.btc.setup.RegTestHost;
import bisq.core.btc.setup.WalletsSetup;
@@ -26,6 +27,7 @@ import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.NonBsqCoinSelector;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.provider.ProvidersRepository;
import bisq.core.provider.fee.FeeProvider;
import bisq.core.provider.fee.FeeService;
@@ -85,7 +87,9 @@ public class BitcoinModule extends AppModule {
bind(new TypeLiteral>(){}).annotatedWith(named(PROVIDERS)).toInstance(config.providers);
bind(AddressEntryList.class).in(Singleton.class);
+ bind(XmrAddressEntryList.class).in(Singleton.class);
bind(WalletsSetup.class).in(Singleton.class);
+ bind(XmrWalletService.class).in(Singleton.class);
bind(BtcWalletService.class).in(Singleton.class);
bind(BsqWalletService.class).in(Singleton.class);
bind(TradeWalletService.class).in(Singleton.class);
diff --git a/core/src/main/java/bisq/core/btc/listeners/XmrBalanceListener.java b/core/src/main/java/bisq/core/btc/listeners/XmrBalanceListener.java
new file mode 100644
index 0000000000..2c8247453d
--- /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 0000000000..aaba5fec67
--- /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 0000000000..59c0212f41
--- /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 0000000000..2bb0b55982
--- /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 bd260c13d8..c01bdf62f6 100644
--- a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java
+++ b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java
@@ -71,6 +71,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
@@ -85,6 +86,14 @@ import static bisq.common.util.Preconditions.checkDir;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+
+
+import monero.common.MoneroUtils;
+import monero.daemon.model.MoneroNetworkType;
+import monero.wallet.MoneroWallet;
+import monero.wallet.MoneroWalletRpc;
+import monero.wallet.model.MoneroWalletConfig;
+
/**
*
Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory
* and file prefix, optionally configure a few things, then use startAsync and optionally awaitRunning. The object will
@@ -112,15 +121,29 @@ public class WalletConfig extends AbstractIdleService {
protected static final Logger log = LoggerFactory.getLogger(WalletConfig.class);
+ // Monero configuration
+ // TODO: don't hard code configuration, inject into classes?
+ private static final MoneroNetworkType MONERO_NETWORK_TYPE = MoneroNetworkType.STAGENET;
+ private static final String MONERO_DAEMON_URI = "http://localhost:38081";
+ private static final String MONERO_DAEMON_USERNAME = "superuser";
+ private static final String MONERO_DAEMON_PASSWORD = "abctesting123";
+ private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager();
+ private static final String MONERO_WALLET_RPC_PATH = System.getProperty("user.dir") + "/monero-wallet-rpc"; // current working directory
+ private static final String MONERO_WALLET_RPC_USERNAME = "rpc_user";
+ private static final String MONERO_WALLET_RPC_PASSWORD = "abc123";
+ private static final long MONERO_WALLET_SYNC_RATE = 5000l;
+
protected final NetworkParameters params;
protected final String filePrefix;
protected volatile BlockChain vChain;
protected volatile SPVBlockStore vStore;
+ protected volatile MoneroWallet vXmrWallet;
protected volatile Wallet vBtcWallet;
protected volatile Wallet vBsqWallet;
protected volatile PeerGroup vPeerGroup;
protected final File directory;
+ protected volatile File vXmrWalletFile;
protected volatile File vBtcWalletFile;
protected volatile File vBsqWalletFile;
@@ -261,6 +284,60 @@ public class WalletConfig extends AbstractIdleService {
// Meant to be overridden by subclasses
}
+ public MoneroWallet createWallet(MoneroWalletConfig config) {
+
+ // start monero-wallet-rpc instance
+ MoneroWalletRpc walletRpc = startWalletRpcInstance();
+
+ // create wallet
+ try {
+ walletRpc.createWallet(config);
+ walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
+ return walletRpc;
+ } catch (Exception e) {
+ e.printStackTrace();
+ WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc);
+ throw e;
+ }
+ }
+
+ public MoneroWallet openWallet(MoneroWalletConfig config) {
+
+ // start monero-wallet-rpc instance
+ MoneroWalletRpc walletRpc = startWalletRpcInstance();
+
+ // open wallet
+ try {
+ walletRpc.openWallet(config);
+ walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
+ return walletRpc;
+ } catch (Exception e) {
+ e.printStackTrace();
+ WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc);
+ throw e;
+ }
+ }
+
+ private MoneroWalletRpc startWalletRpcInstance() {
+
+ // check if monero-wallet-rpc exists
+ if (!new File(MONERO_WALLET_RPC_PATH).exists()) throw new Error("monero-wallet-rpc executable doesn't exist at path " + MONERO_WALLET_RPC_PATH + "; copy monero-wallet-rpc to the project root or set WalletConfig.java MONERO_WALLET_RPC_PATH for your system");
+
+ // start monero-wallet-rpc instance and return connected client
+ return WalletConfig.MONERO_WALLET_RPC_MANAGER.startInstance(Arrays.asList(
+ MONERO_WALLET_RPC_PATH,
+ "--" + MONERO_NETWORK_TYPE.toString().toLowerCase(),
+ "--daemon-address", MONERO_DAEMON_URI,
+ "--daemon-login", MONERO_DAEMON_USERNAME + ":" + MONERO_DAEMON_PASSWORD,
+ "--rpc-login", MONERO_WALLET_RPC_USERNAME + ":" + MONERO_WALLET_RPC_PASSWORD,
+ "--wallet-dir", directory.toString()
+ ));
+ }
+
+ public void closeWallet(MoneroWallet walletRpc) {
+ WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) walletRpc);
+ }
+
@Override
protected void startUp() throws Exception {
// Runs in a separate thread.
@@ -268,6 +345,24 @@ public class WalletConfig extends AbstractIdleService {
try {
File chainFile = new File(directory, filePrefix + ".spvchain");
boolean chainFileExists = chainFile.exists();
+
+ // XMR wallet
+ String xmrPrefix = "_XMR";
+ vXmrWalletFile = new File(directory, filePrefix + xmrPrefix); // TODO: *.wallet?
+ if (MoneroUtils.walletExists(vXmrWalletFile.getPath())) {
+ vXmrWallet = openWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123"));
+ } else {
+ vXmrWallet = createWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123"));
+ }
+ System.out.println("Monero wallet path: " + vXmrWallet.getPath());
+ System.out.println("Monero wallet address: " + vXmrWallet.getPrimaryAddress());
+ System.out.println("Monero mnemonic: " + vXmrWallet.getMnemonic());
+// vXmrWallet.rescanSpent();
+// vXmrWallet.rescanBlockchain();
+ vXmrWallet.sync();
+ vXmrWallet.save();
+ System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance());
+
String btcPrefix = "_BTC";
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
@@ -531,6 +626,11 @@ public class WalletConfig extends AbstractIdleService {
return vBtcWallet;
}
+ public MoneroWallet getXmrWallet() {
+ checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
+ return vXmrWallet;
+ }
+
public Wallet bsqWallet() {
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
return vBsqWallet;
diff --git a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java
index fad4ac3051..d01f900ce8 100644
--- a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java
+++ b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java
@@ -21,6 +21,7 @@ import bisq.core.btc.exceptions.InvalidHostException;
import bisq.core.btc.exceptions.RejectedTxException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.AddressEntryList;
+import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.btc.nodes.BtcNetworkConfig;
import bisq.core.btc.nodes.BtcNodes;
import bisq.core.btc.nodes.BtcNodes.BtcNode;
@@ -58,8 +59,8 @@ import org.bitcoinj.wallet.Wallet;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
-import javax.inject.Inject;
-import javax.inject.Named;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.Service;
@@ -103,6 +104,10 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
+
+
+import monero.wallet.MoneroWallet;
+
// Setup wallets and use WalletConfig for BitcoinJ wiring.
// Other like WalletConfig we are here always on the user thread. That is one reason why we do not
// merge WalletsSetup with WalletConfig to one class.
@@ -120,6 +125,7 @@ public class WalletsSetup {
private final RegTestHost regTestHost;
private final AddressEntryList addressEntryList;
+ private final XmrAddressEntryList xmrAddressEntryList;
private final Preferences preferences;
private final Socks5ProxyProvider socks5ProxyProvider;
private final Config config;
@@ -148,6 +154,7 @@ public class WalletsSetup {
@Inject
public WalletsSetup(RegTestHost regTestHost,
AddressEntryList addressEntryList,
+ XmrAddressEntryList xmrAddressEntryList,
Preferences preferences,
Socks5ProxyProvider socks5ProxyProvider,
Config config,
@@ -160,6 +167,7 @@ public class WalletsSetup {
@Named(Config.SOCKS5_DISCOVER_MODE) String socks5DiscoverModeString) {
this.regTestHost = regTestHost;
this.addressEntryList = addressEntryList;
+ this.xmrAddressEntryList = xmrAddressEntryList;
this.preferences = preferences;
this.socks5ProxyProvider = socks5ProxyProvider;
this.config = config;
@@ -253,6 +261,7 @@ public class WalletsSetup {
UserThread.execute(() -> {
chainHeight.set(chain.getBestChainHeight());
addressEntryList.onWalletReady(walletConfig.btcWallet());
+ xmrAddressEntryList.onWalletReady(walletConfig.getXmrWallet());
timeoutTimer.stop();
setupCompletedHandlers.forEach(Runnable::run);
});
@@ -479,6 +488,10 @@ public class WalletsSetup {
return walletConfig.btcWallet();
}
+ public MoneroWallet getXmrWallet() {
+ return walletConfig.getXmrWallet();
+ }
+
@Nullable
public Wallet getBsqWallet() {
return walletConfig.bsqWallet();
diff --git a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
index 4244e78534..87f701d114 100644
--- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
@@ -28,6 +28,7 @@ import bisq.core.btc.setup.WalletConfig;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
+import bisq.core.util.ParsingUtils;
import bisq.common.config.Config;
import bisq.common.util.Tuple2;
@@ -75,6 +76,13 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+
+
+import monero.wallet.MoneroWallet;
+import monero.wallet.model.MoneroDestination;
+import monero.wallet.model.MoneroTxConfig;
+import monero.wallet.model.MoneroTxWallet;
+
public class TradeWalletService {
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000);
@@ -86,6 +94,8 @@ public class TradeWalletService {
@Nullable
private Wallet wallet;
@Nullable
+ private MoneroWallet xmrWallet;
+ @Nullable
private WalletConfig walletConfig;
@Nullable
private KeyParameter aesKey;
@@ -103,6 +113,7 @@ public class TradeWalletService {
walletsSetup.addSetupCompletedHandler(() -> {
walletConfig = walletsSetup.getWalletConfig();
wallet = walletsSetup.getBtcWallet();
+ xmrWallet = walletsSetup.getXmrWallet();
});
}
@@ -125,6 +136,21 @@ public class TradeWalletService {
// Trade fee
///////////////////////////////////////////////////////////////////////////////////////////
+ public MoneroTxWallet createXmrTradingFeeTx(
+ String reservedForTradeAddress,
+ Coin reservedFundsForOffer,
+ Coin makerFee,
+ Coin txFee,
+ String feeReceiver,
+ boolean broadcastTx) {
+ return xmrWallet.createTx(new MoneroTxConfig()
+ .setAccountIndex(0)
+ .setDestinations(
+ new MoneroDestination(feeReceiver, ParsingUtils.satoshisToXmrAtomicUnits(makerFee.value)),
+ new MoneroDestination(reservedForTradeAddress, ParsingUtils.satoshisToXmrAtomicUnits(reservedFundsForOffer.value)))
+ .setRelay(broadcastTx));
+ }
+
/**
* Create a BTC trading fee transaction for the maker or taker of an offer. The first output of the tx is for the
* fee receiver. The second output is the reserve of the trade. There is an optional third output for change.
@@ -1206,6 +1232,16 @@ public class TradeWalletService {
// Misc
///////////////////////////////////////////////////////////////////////////////////////////
+ /**
+ * Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
+ *
+ * @param txHash the transaction hash of the transaction we want to lookup
+ */
+ public MoneroTxWallet getWalletTx(String txHash) {
+ checkNotNull(xmrWallet);
+ return xmrWallet.getTx(txHash);
+ }
+
/**
* Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
*
diff --git a/core/src/main/java/bisq/core/btc/wallet/WalletService.java b/core/src/main/java/bisq/core/btc/wallet/WalletService.java
index 0b893e470a..a25a6a98f0 100644
--- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java
@@ -105,6 +105,11 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+
+
+import monero.wallet.MoneroWallet;
+import monero.wallet.model.MoneroTxWallet;
+
/**
* Abstract base class for BTC and BSQ wallet. Provides all non-trade specific functionality.
*/
@@ -228,7 +233,6 @@ public abstract class WalletService {
balanceListeners.remove(listener);
}
-
///////////////////////////////////////////////////////////////////////////////////////////
// Checks
///////////////////////////////////////////////////////////////////////////////////////////
@@ -802,6 +806,22 @@ public abstract class WalletService {
}
}
+ public static MoneroTxWallet maybeAddNetworkTxToWallet(byte[] serializedTransaction, MoneroWallet wallet) throws VerificationException {
+ throw new RuntimeException("Not implemented"); // TODO (woodser): need to serialize/deserialize tx for xmr integration?
+// Transaction tx = new Transaction(wallet.getParams(), serializedTransaction);
+// Transaction walletTransaction = wallet.getTransaction(tx.getHash());
+//
+// if (walletTransaction == null) {
+// // We need to recreate the transaction otherwise we get a null pointer...
+// tx.getConfidence(Context.get()).setSource(source);
+// //wallet.maybeCommitTx(tx);
+// wallet.receivePending(tx, null, true);
+// return tx;
+// } else {
+// return walletTransaction;
+// }
+ }
+
public static Transaction maybeAddNetworkTxToWallet(byte[] serializedTransaction,
Wallet wallet) throws VerificationException {
return maybeAddTxToWallet(serializedTransaction, wallet, TransactionConfidence.Source.NETWORK);
diff --git a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java
new file mode 100644
index 0000000000..eeb7965627
--- /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 360a0b8c34..f2581b45aa 100644
--- a/core/src/main/java/bisq/core/locale/CurrencyUtil.java
+++ b/core/src/main/java/bisq/core/locale/CurrencyUtil.java
@@ -57,12 +57,12 @@ import static java.lang.String.format;
@Slf4j
public class CurrencyUtil {
public static void setup() {
- setBaseCurrencyCode(Config.baseCurrencyNetwork().getCurrencyCode());
+ setBaseCurrencyCode("XMR");
}
private static final AssetRegistry assetRegistry = new AssetRegistry();
- private static String baseCurrencyCode = "BTC";
+ private static String baseCurrencyCode = "XMR";
// Calls to isFiatCurrency and isCryptoCurrency are very frequent so we use a cache of the results.
// The main improvement was already achieved with using memoize for the source maps, but
diff --git a/core/src/main/java/bisq/core/locale/Res.java b/core/src/main/java/bisq/core/locale/Res.java
index fcf9141c91..6d102ccdd2 100644
--- a/core/src/main/java/bisq/core/locale/Res.java
+++ b/core/src/main/java/bisq/core/locale/Res.java
@@ -46,8 +46,8 @@ import org.jetbrains.annotations.NotNull;
public class Res {
public static void setup() {
BaseCurrencyNetwork baseCurrencyNetwork = Config.baseCurrencyNetwork();
- setBaseCurrencyCode(baseCurrencyNetwork.getCurrencyCode());
- setBaseCurrencyName(baseCurrencyNetwork.getCurrencyName());
+ setBaseCurrencyCode("XMR");
+ setBaseCurrencyName("Monero");
}
@SuppressWarnings("CanBeFinal")
@@ -78,20 +78,20 @@ public class Res {
private static String baseCurrencyNameLowerCase;
public static void setBaseCurrencyCode(String baseCurrencyCode) {
- Res.baseCurrencyCode = baseCurrencyCode;
+ Res.baseCurrencyCode = "XMR";
}
public static void setBaseCurrencyName(String baseCurrencyName) {
- Res.baseCurrencyName = baseCurrencyName;
+ Res.baseCurrencyName = "Monero";
baseCurrencyNameLowerCase = baseCurrencyName.toLowerCase();
}
public static String getBaseCurrencyCode() {
- return baseCurrencyCode;
+ return "XMR";
}
public static String getBaseCurrencyName() {
- return baseCurrencyName;
+ return "Monero";
}
// Capitalize first character
@@ -110,9 +110,9 @@ public class Res {
public static String get(String key) {
try {
return resourceBundle.getString(key)
- .replace("BTC", baseCurrencyCode)
- .replace("Bitcoin", baseCurrencyName)
- .replace("bitcoin", baseCurrencyNameLowerCase);
+ .replace("XMR", baseCurrencyCode)
+ .replace("Monero", baseCurrencyName)
+ .replace("monero", baseCurrencyNameLowerCase);
} catch (MissingResourceException e) {
log.warn("Missing resource for key: {}", key);
e.printStackTrace();
diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java
index bf04e10b19..6b8a3b5b73 100644
--- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java
+++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java
@@ -21,6 +21,7 @@ import bisq.core.api.CoreContext;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.exceptions.TradePriceOutOfToleranceException;
import bisq.core.filter.FilterManager;
@@ -108,6 +109,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final User user;
private final P2PService p2PService;
private final BtcWalletService btcWalletService;
+ private final XmrWalletService xmrWalletService;
private final TradeWalletService tradeWalletService;
private final BsqWalletService bsqWalletService;
private final OfferBookService offerBookService;
@@ -141,6 +143,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
User user,
P2PService p2PService,
BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
TradeWalletService tradeWalletService,
BsqWalletService bsqWalletService,
OfferBookService offerBookService,
@@ -161,6 +164,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
this.user = user;
this.p2PService = p2PService;
this.btcWalletService = btcWalletService;
+ this.xmrWalletService = xmrWalletService;
this.tradeWalletService = tradeWalletService;
this.bsqWalletService = bsqWalletService;
this.offerBookService = offerBookService;
@@ -381,6 +385,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
reservedFundsForOffer,
useSavingsWallet,
btcWalletService,
+ xmrWalletService,
tradeWalletService,
bsqWalletService,
offerBookService,
@@ -642,11 +647,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
Offer offer = openOffer.getOffer();
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
- mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress();
- openOffer.setMediatorNodeAddress(mediatorNodeAddress);
-
- refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress();
- openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress);
+ arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress();
+ openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
try {
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
diff --git a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java
index eaec9dcbe9..7b3bee5fc5 100644
--- a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java
+++ b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java
@@ -44,23 +44,14 @@ import static com.google.common.base.Preconditions.checkArgument;
public class DisputeAgentSelection {
public static final int LOOK_BACK_RANGE = 100;
- public static T getLeastUsedMediator(TradeStatisticsManager tradeStatisticsManager,
+ public static T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager disputeAgentManager) {
return getLeastUsedDisputeAgent(tradeStatisticsManager,
- disputeAgentManager,
- true);
- }
-
- public static T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager,
- DisputeAgentManager disputeAgentManager) {
- return getLeastUsedDisputeAgent(tradeStatisticsManager,
- disputeAgentManager,
- false);
+ disputeAgentManager);
}
private static T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
- DisputeAgentManager disputeAgentManager,
- boolean isMediator) {
+ DisputeAgentManager disputeAgentManager) {
// We take last 100 entries from trade statistics
List list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
list.sort(Comparator.comparing(TradeStatistics3::getDateAsLong));
@@ -72,7 +63,7 @@ public class DisputeAgentSelection {
// We stored only first 4 chars of disputeAgents onion address
List lastAddressesUsedInTrades = list.stream()
- .map(tradeStatistics3 -> isMediator ? tradeStatistics3.getMediator() : tradeStatistics3.getRefundAgent())
+ .map(tradeStatistics3 -> tradeStatistics3.getArbitrator())
.filter(Objects::nonNull)
.collect(Collectors.toList());
diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java
index f6c04fa198..20bc844792 100644
--- a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java
+++ b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java
@@ -62,7 +62,7 @@ public class ProcessOfferAvailabilityResponse extends Task {
- String value = formatter.formatCoinWithCode(newValue);
- // If we get full precision the BTC postfix breaks layout so we omit it
- if (value.length() > 11)
- value = formatter.formatCoin(newValue);
- availableBalance.set(value);
+ availableBalance.set(longToXmr(newValue.value));
});
balances.getReservedBalance().addListener((observable, oldValue, newValue) -> {
- reservedBalance.set(formatter.formatCoinWithCode(newValue));
+ reservedBalance.set(longToXmr(newValue.value));
});
balances.getLockedBalance().addListener((observable, oldValue, newValue) -> {
- lockedBalance.set(formatter.formatCoinWithCode(newValue));
+ lockedBalance.set(longToXmr(newValue.value));
});
}
+
+ // TODO: truncate full precision with ellipses to not break layout?
+ // TODO (woodser): formatting utils in monero-java
+ private static String longToXmr(long amt) {
+ BigInteger auAmt = BigInteger.valueOf(amt);
+ BigInteger[] quotientAndRemainder = auAmt.divideAndRemainder(AU_PER_XMR);
+ double decimalRemainder = quotientAndRemainder[1].doubleValue() / AU_PER_XMR.doubleValue();
+ return quotientAndRemainder[0].doubleValue() + decimalRemainder + " XMR";
+ }
}
diff --git a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
index d30a671bc8..bd7796f3ac 100644
--- a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
+++ b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
@@ -43,6 +43,8 @@ import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
import bisq.core.support.dispute.mediation.mediator.Mediator;
+import bisq.core.support.dispute.messages.ArbitratorPayoutTxRequest;
+import bisq.core.support.dispute.messages.ArbitratorPayoutTxResponse;
import bisq.core.support.dispute.messages.DisputeResultMessage;
import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
@@ -53,14 +55,20 @@ import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.messages.InitMultisigMessage;
+import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.InputsForDepositTxResponse;
+import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
+import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.RefreshTradeStateRequest;
import bisq.core.trade.messages.TraderSignedWitnessMessage;
+import bisq.core.trade.messages.UpdateMultisigRequest;
+import bisq.core.trade.messages.UpdateMultisigResponse;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.BundleOfEnvelopes;
@@ -147,6 +155,18 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
// trade protocol messages
case REFRESH_TRADE_STATE_REQUEST:
return RefreshTradeStateRequest.fromProto(proto.getRefreshTradeStateRequest(), messageVersion);
+ case INIT_TRADE_REQUEST:
+ return InitTradeRequest.fromProto(proto.getInitTradeRequest(), this, messageVersion);
+ case INIT_MULTISIG_MESSAGE:
+ return InitMultisigMessage.fromProto(proto.getInitMultisigMessage(), this, messageVersion);
+ case UPDATE_MULTISIG_REQUEST:
+ return UpdateMultisigRequest.fromProto(proto.getUpdateMultisigRequest(), this, messageVersion);
+ case UPDATE_MULTISIG_RESPONSE:
+ return UpdateMultisigResponse.fromProto(proto.getUpdateMultisigResponse(), this, messageVersion);
+ case MAKER_READY_TO_FUND_MULTISIG_REQUEST:
+ return MakerReadyToFundMultisigRequest.fromProto(proto.getMakerReadyToFundMultisigRequest(), this, messageVersion);
+ case MAKER_READY_TO_FUND_MULTISIG_RESPONSE:
+ return MakerReadyToFundMultisigResponse.fromProto(proto.getMakerReadyToFundMultisigResponse(), this, messageVersion);
case INPUTS_FOR_DEPOSIT_TX_REQUEST:
return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion);
case INPUTS_FOR_DEPOSIT_TX_RESPONSE:
@@ -185,6 +205,10 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
return DisputeResultMessage.fromProto(proto.getDisputeResultMessage(), messageVersion);
case PEER_PUBLISHED_DISPUTE_PAYOUT_TX_MESSAGE:
return PeerPublishedDisputePayoutTxMessage.fromProto(proto.getPeerPublishedDisputePayoutTxMessage(), messageVersion);
+ case ARBITRATOR_PAYOUT_TX_REQUEST:
+ return ArbitratorPayoutTxRequest.fromProto(proto.getArbitratorPayoutTxRequest(), this, messageVersion);
+ case ARBITRATOR_PAYOUT_TX_RESPONSE:
+ return ArbitratorPayoutTxResponse.fromProto(proto.getArbitratorPayoutTxResponse(), this, messageVersion);
case PRIVATE_NOTIFICATION_MESSAGE:
return PrivateNotificationMessage.fromProto(proto.getPrivateNotificationMessage(), messageVersion);
diff --git a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java
index f42670d76d..8f18bffc81 100644
--- a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java
+++ b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java
@@ -20,7 +20,9 @@ package bisq.core.proto.persistable;
import bisq.core.account.sign.SignedWitnessStore;
import bisq.core.account.witness.AccountAgeWitnessStore;
import bisq.core.btc.model.AddressEntryList;
+import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.governance.blindvote.MyBlindVoteList;
import bisq.core.dao.governance.blindvote.storage.BlindVoteStore;
import bisq.core.dao.governance.bond.reputation.MyReputationList;
@@ -67,12 +69,15 @@ import lombok.extern.slf4j.Slf4j;
@Singleton
public class CorePersistenceProtoResolver extends CoreProtoResolver implements PersistenceProtoResolver {
private final Provider btcWalletService;
+ private final Provider xmrWalletService;
private final NetworkProtoResolver networkProtoResolver;
@Inject
public CorePersistenceProtoResolver(Provider btcWalletService,
+ Provider xmrWalletService,
NetworkProtoResolver networkProtoResolver) {
this.btcWalletService = btcWalletService;
+ this.xmrWalletService = xmrWalletService;
this.networkProtoResolver = networkProtoResolver;
}
@@ -86,8 +91,10 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
return PeerList.fromProto(proto.getPeerList());
case ADDRESS_ENTRY_LIST:
return AddressEntryList.fromProto(proto.getAddressEntryList());
+ case XMR_ADDRESS_ENTRY_LIST:
+ return XmrAddressEntryList.fromProto(proto.getXmrAddressEntryList());
case TRADABLE_LIST:
- return TradableList.fromProto(proto.getTradableList(), this, btcWalletService.get());
+ return TradableList.fromProto(proto.getTradableList(), this, xmrWalletService.get());
case ARBITRATION_DISPUTE_LIST:
return ArbitrationDisputeList.fromProto(proto.getArbitrationDisputeList(), this);
case MEDIATION_DISPUTE_LIST:
diff --git a/core/src/main/java/bisq/core/provider/mempool/MempoolService.java b/core/src/main/java/bisq/core/provider/mempool/MempoolService.java
index 7894b55f08..2f35182894 100644
--- a/core/src/main/java/bisq/core/provider/mempool/MempoolService.java
+++ b/core/src/main/java/bisq/core/provider/mempool/MempoolService.java
@@ -106,8 +106,9 @@ public class MempoolService {
}
public void validateOfferTakerTx(Trade trade, Consumer resultHandler) {
- validateOfferTakerTx(new TxValidator(daoStateService, trade.getTakerFeeTxId(), trade.getTradeAmount(),
- trade.isCurrencyForTakerFeeBtc()), resultHandler);
+ throw new RuntimeException("MempoolService.validateOfferTakerTx needs updated for XMR");
+// validateOfferTakerTx(new TxValidator(daoStateService, trade.getTakerFeeTxId(), trade.getTradeAmount(),
+// trade.isCurrencyForTakerFeeBtc()), resultHandler);
}
public void validateOfferTakerTx(TxValidator txValidator, Consumer resultHandler) {
diff --git a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java
index 0e54e5527c..ab2a162f06 100644
--- a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java
+++ b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java
@@ -18,6 +18,7 @@
package bisq.core.setup;
import bisq.core.btc.model.AddressEntryList;
+import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.dao.governance.ballot.BallotListService;
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
import bisq.core.dao.governance.bond.reputation.MyReputationListService;
@@ -60,6 +61,7 @@ public class CorePersistedDataHost {
persistedDataHosts.add(injector.getInstance(Preferences.class));
persistedDataHosts.add(injector.getInstance(User.class));
persistedDataHosts.add(injector.getInstance(AddressEntryList.class));
+ persistedDataHosts.add(injector.getInstance(XmrAddressEntryList.class));
persistedDataHosts.add(injector.getInstance(OpenOfferManager.class));
persistedDataHosts.add(injector.getInstance(TradeManager.class));
persistedDataHosts.add(injector.getInstance(ClosedTradableManager.class));
diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java
index 98ed154fbe..81527f431a 100644
--- a/core/src/main/java/bisq/core/support/dispute/Dispute.java
+++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java
@@ -128,6 +128,9 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
@Nullable
private String delayedPayoutTxId;
+ // Added for XMR integration
+ private boolean isOpener;
+
// Added at v1.4.0
@Setter
@Nullable
@@ -160,6 +163,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
public Dispute(long openingDate,
String tradeId,
int traderId,
+ boolean isOpener,
boolean disputeOpenerIsBuyer,
boolean disputeOpenerIsMaker,
PubKeyRing traderPubKeyRing,
@@ -180,6 +184,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
this.openingDate = openingDate;
this.tradeId = tradeId;
this.traderId = traderId;
+ this.isOpener = isOpener;
this.disputeOpenerIsBuyer = disputeOpenerIsBuyer;
this.disputeOpenerIsMaker = disputeOpenerIsMaker;
this.traderPubKeyRing = traderPubKeyRing;
@@ -215,6 +220,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
protobuf.Dispute.Builder builder = protobuf.Dispute.newBuilder()
.setTradeId(tradeId)
.setTraderId(traderId)
+ .setIsOpener(isOpener)
.setDisputeOpenerIsBuyer(disputeOpenerIsBuyer)
.setDisputeOpenerIsMaker(disputeOpenerIsMaker)
.setTraderPubKeyRing(traderPubKeyRing.toProtoMessage())
@@ -253,6 +259,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
Dispute dispute = new Dispute(proto.getOpeningDate(),
proto.getTradeId(),
proto.getTraderId(),
+ proto.getIsOpener(),
proto.getDisputeOpenerIsBuyer(),
proto.getDisputeOpenerIsMaker(),
PubKeyRing.fromProto(proto.getTraderPubKeyRing()),
@@ -327,6 +334,9 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
}
}
+ public boolean isMediationDispute() {
+ return !chatMessages.isEmpty() && chatMessages.get(0).getSupportType() == SupportType.MEDIATION;
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
@@ -447,6 +457,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
",\n uid='" + uid + '\'' +
",\n state=" + disputeState +
",\n traderId=" + traderId +
+ ",\n isOpener=" + isOpener +
",\n disputeOpenerIsBuyer=" + disputeOpenerIsBuyer +
",\n disputeOpenerIsMaker=" + disputeOpenerIsMaker +
",\n traderPubKeyRing=" + traderPubKeyRing +
diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java
index 6abb55ffcb..61a15c7ea0 100644
--- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java
@@ -18,9 +18,9 @@
package bisq.core.support.dispute;
import bisq.core.btc.setup.WalletsSetup;
-import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
@@ -66,6 +66,7 @@ import javafx.collections.ObservableList;
import java.security.KeyPair;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@@ -81,10 +82,14 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
+
+
+import monero.wallet.MoneroWallet;
+
@Slf4j
public abstract class DisputeManager> extends SupportManager {
protected final TradeWalletService tradeWalletService;
- protected final BtcWalletService btcWalletService;
+ protected final XmrWalletService xmrWalletService;
protected final TradeManager tradeManager;
protected final ClosedTradableManager closedTradableManager;
protected final OpenOfferManager openOfferManager;
@@ -107,7 +112,7 @@ public abstract class DisputeManager> extends Sup
public DisputeManager(P2PService p2PService,
TradeWalletService tradeWalletService,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
WalletsSetup walletsSetup,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
@@ -120,7 +125,7 @@ public abstract class DisputeManager> extends Sup
super(p2PService, walletsSetup);
this.tradeWalletService = tradeWalletService;
- this.btcWalletService = btcWalletService;
+ this.xmrWalletService = xmrWalletService;
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
this.openOfferManager = openOfferManager;
@@ -184,7 +189,7 @@ public abstract class DisputeManager> extends Sup
dispute.addAndPersistChatMessage(message);
requestPersistence();
} else {
- log.warn("We got a chatMessage that we have already stored. UId = {} TradeId = {}",
+ log.warn("We got a chatMessage what we have already stored. UId = {} TradeId = {}",
message.getUid(), message.getTradeId());
}
});
@@ -198,7 +203,6 @@ public abstract class DisputeManager> extends Sup
// We get that message at both peers. The dispute object is in context of the trader
public abstract void onDisputeResultMessage(DisputeResultMessage disputeResultMessage);
- @Nullable
public abstract NodeAddress getAgentNodeAddress(Dispute dispute);
protected abstract Trade.DisputeState getDisputeStateStartedByPeer();
@@ -276,11 +280,12 @@ public abstract class DisputeManager> extends Sup
}
});
- TradeDataValidation.testIfAnyDisputeTriedReplay(disputes,
- disputeReplayException -> {
- log.error(disputeReplayException.toString());
- validationExceptions.add(disputeReplayException);
- });
+ // TODO (woodser): disabled for xmr, needed?
+// TradeDataValidation.testIfAnyDisputeTriedReplay(disputes,
+// disputeReplayException -> {
+// log.error(disputeReplayException.toString());
+// validationExceptions.add(disputeReplayException);
+// });
}
public boolean isTrader(Dispute dispute) {
@@ -302,7 +307,7 @@ public abstract class DisputeManager> extends Sup
// Message handler
///////////////////////////////////////////////////////////////////////////////////////////
- // dispute agent receives that from trader who opens dispute
+ // arbitrator receives that from trader who opens dispute
protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessage) {
T disputeList = getDisputeList();
if (disputeList == null) {
@@ -322,6 +327,13 @@ public abstract class DisputeManager> extends Sup
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
if (isAgent(dispute)) {
+
+ // update arbitrator's multisig wallet
+ MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
+ multisigWallet.importMultisigHex(Arrays.asList(openNewDisputeMessage.getUpdatedMultisigHex()));
+ System.out.println("Arbitrator multisig wallet updated on new dispute message, current txs:");
+ System.out.println(multisigWallet.getTxs());
+
if (!disputeList.contains(dispute)) {
Optional storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent()) {
@@ -333,8 +345,8 @@ public abstract class DisputeManager> extends Sup
dispute.getTradeId());
}
} else {
- errorMessage = "We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId();
- log.warn(errorMessage);
+ errorMessage = "We got a dispute msg what we have already stored. TradeId = " + dispute.getTradeId();
+ log.warn(errorMessage);
}
} else {
errorMessage = "Trader received openNewDisputeMessage. That must never happen.";
@@ -342,22 +354,21 @@ public abstract class DisputeManager> extends Sup
}
// We use the ChatMessage not the openNewDisputeMessage for the ACK
- ObservableList messages = dispute.getChatMessages();
+ ObservableList messages = openNewDisputeMessage.getDispute().getChatMessages();
if (!messages.isEmpty()) {
- ChatMessage chatMessage = messages.get(0);
+ ChatMessage msg = messages.get(0);
PubKeyRing sendersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
- sendAckMessage(chatMessage, sendersPubKeyRing, errorMessage == null, errorMessage);
+ sendAckMessage(msg, sendersPubKeyRing, errorMessage == null, errorMessage);
}
addMediationResultMessage(dispute);
try {
TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
- TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList());
+ //TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); // TODO (woodser): disabled for xmr, needed?
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config);
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config);
} catch (TradeDataValidation.AddressException |
- TradeDataValidation.DisputeReplayException |
TradeDataValidation.NodeAddressException e) {
log.error(e.toString());
validationExceptions.add(e);
@@ -380,20 +391,7 @@ public abstract class DisputeManager> extends Sup
if (!optionalTrade.isPresent()) {
return;
}
-
Trade trade = optionalTrade.get();
- try {
- TradeDataValidation.validateDelayedPayoutTx(trade,
- trade.getDelayedPayoutTx(),
- dispute,
- daoFacade,
- btcWalletService);
- } catch (TradeDataValidation.ValidationException e) {
- // The peer sent us an invalid donation address. We do not return here as we don't want to break
- // mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get
- // a popup displayed to react.
- log.warn("Donation address is invalid. {}", e.toString());
- }
if (!isAgent(dispute)) {
if (!disputeList.contains(dispute)) {
@@ -435,6 +433,7 @@ public abstract class DisputeManager> extends Sup
public void sendOpenNewDisputeMessage(Dispute dispute,
boolean reOpen,
+ String updatedMultisigHex,
ResultHandler resultHandler,
FaultHandler faultHandler) {
T disputeList = getDisputeList();
@@ -453,18 +452,16 @@ public abstract class DisputeManager> extends Sup
Optional storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent() || reOpen) {
String disputeInfo = getDisputeInfo(dispute);
- String disputeMessage = getDisputeIntroForDisputeCreator(disputeInfo);
String sysMsg = dispute.isSupportTicket() ?
Res.get("support.youOpenedTicket", disputeInfo, Version.VERSION)
- : disputeMessage;
+ : Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
- String message = Res.get("support.systemMsg", sysMsg);
ChatMessage chatMessage = new ChatMessage(
getSupportType(),
dispute.getTradeId(),
pubKeyRing.hashCode(),
false,
- message,
+ Res.get("support.systemMsg", sysMsg),
p2PService.getAddress());
chatMessage.setSystemMessage(true);
dispute.addAndPersistChatMessage(chatMessage);
@@ -473,22 +470,16 @@ public abstract class DisputeManager> extends Sup
}
NodeAddress agentNodeAddress = getAgentNodeAddress(dispute);
- if (agentNodeAddress == null) {
- return;
- }
-
OpenNewDisputeMessage openNewDisputeMessage = new OpenNewDisputeMessage(dispute,
p2PService.getAddress(),
UUID.randomUUID().toString(),
- getSupportType());
-
- log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, chatMessage.uid={}",
- openNewDisputeMessage.getClass().getSimpleName(),
- agentNodeAddress,
- openNewDisputeMessage.getTradeId(),
- openNewDisputeMessage.getUid(),
+ getSupportType(),
+ updatedMultisigHex);
+ log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, " +
+ "chatMessage.uid={}",
+ openNewDisputeMessage.getClass().getSimpleName(), agentNodeAddress,
+ openNewDisputeMessage.getTradeId(), openNewDisputeMessage.getUid(),
chatMessage.getUid());
-
mailboxMessageService.sendEncryptedMailboxMessage(agentNodeAddress,
dispute.getAgentPubKeyRing(),
openNewDisputeMessage,
@@ -575,6 +566,7 @@ public abstract class DisputeManager> extends Sup
Dispute dispute = new Dispute(new Date().getTime(),
disputeFromOpener.getTradeId(),
pubKeyRing.hashCode(),
+ false,
!disputeFromOpener.isDisputeOpenerIsBuyer(),
!disputeFromOpener.isDisputeOpenerIsMaker(),
pubKeyRing,
@@ -636,7 +628,6 @@ public abstract class DisputeManager> extends Sup
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
chatMessage.getUid());
-
mailboxMessageService.sendEncryptedMailboxMessage(peersNodeAddress,
peersPubKeyRing,
peerOpenedDisputeMessage,
@@ -687,7 +678,7 @@ public abstract class DisputeManager> extends Sup
requestPersistence();
}
- // dispute agent send result to trader
+ // arbitrator send result to trader
public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute dispute, String summaryText) {
T disputeList = getDisputeList();
if (disputeList == null) {
@@ -769,7 +760,6 @@ public abstract class DisputeManager> extends Sup
requestPersistence();
}
-
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
@@ -811,7 +801,7 @@ public abstract class DisputeManager> extends Sup
return findDispute(message.getTradeId(), message.getTraderId());
}
- private Optional findDispute(String tradeId, int traderId) {
+ protected Optional findDispute(String tradeId, int traderId) {
T disputeList = getDisputeList();
if (disputeList == null) {
log.warn("disputes is null");
diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeResult.java b/core/src/main/java/bisq/core/support/dispute/DisputeResult.java
index d046a16c98..20135baed1 100644
--- a/core/src/main/java/bisq/core/support/dispute/DisputeResult.java
+++ b/core/src/main/java/bisq/core/support/dispute/DisputeResult.java
@@ -80,9 +80,6 @@ public final class DisputeResult implements NetworkPayload {
@Setter
@Nullable
private ChatMessage chatMessage;
- @Setter
- @Nullable
- private byte[] arbitratorSignature;
private long buyerPayoutAmount;
private long sellerPayoutAmount;
@Setter
@@ -92,6 +89,14 @@ public final class DisputeResult implements NetworkPayload {
@Setter
private boolean isLoserPublisher;
+ // added for XMR integration
+ @Nullable
+ @Setter
+ String arbitratorSignedPayoutTxHex;
+ @Nullable
+ @Setter
+ String arbitratorUpdatedMultisigHex;
+
public DisputeResult(String tradeId, int traderId) {
this.tradeId = tradeId;
this.traderId = traderId;
@@ -106,7 +111,8 @@ public final class DisputeResult implements NetworkPayload {
boolean screenCast,
String summaryNotes,
@Nullable ChatMessage chatMessage,
- @Nullable byte[] arbitratorSignature,
+ @Nullable String arbitratorPayoutTxSigned,
+ @Nullable String arbitratorUpdatedMultisigHex,
long buyerPayoutAmount,
long sellerPayoutAmount,
@Nullable byte[] arbitratorPubKey,
@@ -121,7 +127,8 @@ public final class DisputeResult implements NetworkPayload {
this.screenCastProperty.set(screenCast);
this.summaryNotesProperty.set(summaryNotes);
this.chatMessage = chatMessage;
- this.arbitratorSignature = arbitratorSignature;
+ this.arbitratorSignedPayoutTxHex = arbitratorPayoutTxSigned;
+ this.arbitratorUpdatedMultisigHex = arbitratorUpdatedMultisigHex;
this.buyerPayoutAmount = buyerPayoutAmount;
this.sellerPayoutAmount = sellerPayoutAmount;
this.arbitratorPubKey = arbitratorPubKey;
@@ -144,7 +151,8 @@ public final class DisputeResult implements NetworkPayload {
proto.getScreenCast(),
proto.getSummaryNotes(),
proto.getChatMessage() == null ? null : ChatMessage.fromPayloadProto(proto.getChatMessage()),
- proto.getArbitratorSignature().toByteArray(),
+ ProtoUtil.stringOrNullFromProto(proto.getArbitratorSignedPayoutTxHex()),
+ ProtoUtil.stringOrNullFromProto(proto.getArbitratorUpdatedMultisigHex()),
proto.getBuyerPayoutAmount(),
proto.getSellerPayoutAmount(),
proto.getArbitratorPubKey().toByteArray(),
@@ -167,7 +175,8 @@ public final class DisputeResult implements NetworkPayload {
.setCloseDate(closeDate)
.setIsLoserPublisher(isLoserPublisher);
- Optional.ofNullable(arbitratorSignature).ifPresent(arbitratorSignature -> builder.setArbitratorSignature(ByteString.copyFrom(arbitratorSignature)));
+ Optional.ofNullable(arbitratorSignedPayoutTxHex).ifPresent(arbitratorPayoutTxSigned -> builder.setArbitratorSignedPayoutTxHex(arbitratorPayoutTxSigned));
+ Optional.ofNullable(arbitratorUpdatedMultisigHex).ifPresent(arbitratorUpdatedMultisigHex -> builder.setArbitratorUpdatedMultisigHex(arbitratorUpdatedMultisigHex));
Optional.ofNullable(arbitratorPubKey).ifPresent(arbitratorPubKey -> builder.setArbitratorPubKey(ByteString.copyFrom(arbitratorPubKey)));
Optional.ofNullable(winner).ifPresent(result -> builder.setWinner(protobuf.DisputeResult.Winner.valueOf(winner.name())));
Optional.ofNullable(chatMessage).ifPresent(chatMessage ->
@@ -248,7 +257,8 @@ public final class DisputeResult implements NetworkPayload {
",\n screenCastProperty=" + screenCastProperty +
",\n summaryNotesProperty=" + summaryNotesProperty +
",\n chatMessage=" + chatMessage +
- ",\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) +
+ ",\n arbitratorPayoutTxSigned=" + arbitratorSignedPayoutTxHex +
+ ",\n arbitratorUpdatedMultisigHex=" + arbitratorUpdatedMultisigHex +
",\n buyerPayoutAmount=" + buyerPayoutAmount +
",\n sellerPayoutAmount=" + sellerPayoutAmount +
",\n arbitratorPubKey=" + Utilities.bytesAsHexString(arbitratorPubKey) +
diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
index a2c53a29f5..f467bb27d4 100644
--- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
@@ -17,14 +17,9 @@
package bisq.core.support.dispute.arbitration;
-import bisq.core.btc.exceptions.TransactionVerificationException;
-import bisq.core.btc.exceptions.TxBroadcastException;
-import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.setup.WalletsSetup;
-import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
-import bisq.core.btc.wallet.TxBroadcaster;
-import bisq.core.btc.wallet.WalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
@@ -34,7 +29,10 @@ import bisq.core.support.SupportType;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeManager;
import bisq.core.support.dispute.DisputeResult;
+import bisq.core.support.dispute.DisputeResult.Winner;
import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
+import bisq.core.support.dispute.messages.ArbitratorPayoutTxRequest;
+import bisq.core.support.dispute.messages.ArbitratorPayoutTxResponse;
import bisq.core.support.dispute.messages.DisputeResultMessage;
import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
@@ -45,10 +43,12 @@ import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
+import bisq.core.util.ParsingUtils;
import bisq.network.p2p.AckMessageSourceType;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService;
+import bisq.network.p2p.SendDirectMessageListener;
import bisq.network.p2p.SendMailboxMessageListener;
import bisq.common.Timer;
@@ -58,24 +58,31 @@ import bisq.common.config.Config;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;
-import org.bitcoinj.core.AddressFormatException;
-import org.bitcoinj.core.SignatureDecodeException;
-import org.bitcoinj.core.Transaction;
-import org.bitcoinj.crypto.DeterministicKey;
-
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import com.google.common.base.Preconditions;
+
+import java.math.BigInteger;
+
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
-import javax.annotation.Nullable;
-
import static com.google.common.base.Preconditions.checkNotNull;
+
+
+import monero.common.MoneroError;
+import monero.wallet.MoneroWallet;
+import monero.wallet.model.MoneroDestination;
+import monero.wallet.model.MoneroMultisigSignResult;
+import monero.wallet.model.MoneroTxConfig;
+import monero.wallet.model.MoneroTxSet;
+import monero.wallet.model.MoneroTxWallet;
+
@Slf4j
@Singleton
public final class ArbitrationManager extends DisputeManager {
@@ -87,7 +94,7 @@ public final class ArbitrationManager extends DisputeManager tradeOptional = tradeManager.getTradeById(disputeResult.getTradeId());
String tradeId = disputeResult.getTradeId();
Optional disputeOptional = findDispute(disputeResult);
@@ -209,8 +215,14 @@ public final class ArbitrationManager extends DisputeManager tradeOptional = tradeManager.getTradeById(tradeId);
String errorMessage = null;
- boolean success = false;
+ boolean success = true;
+ boolean requestUpdatedPayoutTx = false;
+ MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
+ Contract contract = dispute.getContract();
try {
// We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals
// There would be different transactions if both sign and publish (signers: once buyer+arb, once seller+arb)
// The tx publisher is the winner or in case both get 50% the buyer, as the buyer has more inventive to publish the tx as he receives
// more BTC as he has deposited
- Contract contract = dispute.getContract();
-
boolean isBuyer = pubKeyRing.equals(contract.getBuyerPubKeyRing());
DisputeResult.Winner publisher = disputeResult.getWinner();
@@ -252,7 +264,7 @@ public final class ArbitrationManager extends DisputeManager disputeOptional = findOwnDispute(tradeId);
+ if (!disputeOptional.isPresent()) {
+ log.warn("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId);
+ return;
+ }
+ Dispute dispute = disputeOptional.get();
+ Contract contract = dispute.getContract();
+ Trade trade = tradeManager.getTradeById(tradeId).get();
+
+ // submit fully signed payout tx to the network
+ multisigWallet.submitMultisigTxHex(txSet.getMultisigTxHex());
+
+ // update state
+ trade.setPayoutTx(txSet.getTxs().get(0)); // TODO (woodser): is trade.payoutTx() mutually exclusive from dispute payout tx?
+ trade.setPayoutTxId(txSet.getTxs().get(0).getHash());
+ trade.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
+ dispute.setDisputePayoutTxId(txSet.getTxs().get(0).getHash());
+ sendPeerPublishedPayoutTxMessage(multisigWallet.getMultisigHex(), txSet.getMultisigTxHex(), dispute, contract);
+ updateTradeOrOpenOfferManager(tradeId);
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// Send messages
///////////////////////////////////////////////////////////////////////////////////////////
// winner (or buyer in case of 50/50) sends tx to other peer
- private void sendPeerPublishedPayoutTxMessage(Transaction transaction, Dispute dispute, Contract contract) {
+ private void sendPeerPublishedPayoutTxMessage(String updatedMultisigHex, String payoutTxHex, Dispute dispute, Contract contract) {
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
NodeAddress peersNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerNodeAddress() : contract.getBuyerNodeAddress();
log.trace("sendPeerPublishedPayoutTxMessage to peerAddress {}", peersNodeAddress);
- PeerPublishedDisputePayoutTxMessage message = new PeerPublishedDisputePayoutTxMessage(transaction.bitcoinSerialize(),
+ PeerPublishedDisputePayoutTxMessage message = new PeerPublishedDisputePayoutTxMessage(updatedMultisigHex,
+ payoutTxHex,
dispute.getTradeId(),
p2PService.getAddress(),
UUID.randomUUID().toString(),
@@ -427,4 +546,190 @@ public final class ArbitrationManager extends DisputeManager openOfferManager.closeOpenOffer(openOffer.getOffer()));
}
}
+
+ // dispute opener's peer signs payout tx by sending updated multisig hex to arbitrator who returns updated payout tx
+ private void sendArbitratorPayoutTxRequest(String updatedMultisigHex, Dispute dispute, Contract contract) {
+ ArbitratorPayoutTxRequest request = new ArbitratorPayoutTxRequest(
+ dispute,
+ p2PService.getAddress(),
+ UUID.randomUUID().toString(),
+ SupportType.ARBITRATION,
+ updatedMultisigHex);
+ log.info("Send {} to peer {}. tradeId={}, uid={}",
+ request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid());
+ p2PService.sendEncryptedDirectMessage(contract.getArbitratorNodeAddress(),
+ dispute.getAgentPubKeyRing(),
+ request,
+ new SendDirectMessageListener() {
+ @Override
+ public void onArrived() {
+ log.info("{} arrived at peer {}. tradeId={}, uid={}",
+ request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid());
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
+ request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid(), errorMessage);
+ }
+ }
+ );
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Disputed payout tx signing
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ // TODO (woodser): where to move this common logic?
+ public static MoneroTxWallet arbitratorCreatesDisputedPayoutTx(Contract contract, Dispute dispute, DisputeResult disputeResult, MoneroWallet multisigWallet) {
+
+ //System.out.println("DisputeSummaryWindow.arbitratorSignsDisputedPayoutTx()");
+ //System.out.println("=== DISPUTE ===");
+ //System.out.println(dispute);
+ //System.out.println("=== CONTRACT ===");
+ //System.out.println(contract); // TODO (woodser): contract should include deposit tx hashes (pre-created then hash shared then contract signed)
+ //System.out.println("=== DISPUTE RESULT ===");
+ //System.out.println(disputeResult);
+
+ // gather relevant trade info
+ String buyerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString();
+ String sellerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString();
+ Preconditions.checkNotNull(buyerPayoutAddress, "buyerPayoutAddress must not be null");
+ Preconditions.checkNotNull(sellerPayoutAddress, "sellerPayoutAddress must not be null");
+ BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
+ BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
+
+ //System.out.println("buyerPayoutAddress: " + buyerPayoutAddress);
+ //System.out.println("buyerPayoutAmount: " + buyerPayoutAmount);
+
+// Offer offer = new Offer(contract.getOfferPayload());
+// System.out.println("Buyer deposit tx fee: " +
+
+ //System.out.println("sellerPayoutAddress: " + sellerPayoutAddress);
+ //System.out.println("sellerPayoutAmount: " + sellerPayoutAmount);
+ //System.out.println("Multisig balance: " + multisigWallet.getBalance());
+ //System.out.println("Multisig unlocked balance: " + multisigWallet.getUnlockedBalance());
+ //System.out.println("Multisig txs");
+ //System.out.println(multisigWallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true)));
+
+ // create transaction to get fee estimate
+ if (multisigWallet.isMultisigImportNeeded()) {
+ log.info("Arbitrator's wallet needs updated multisig hex to create payout tx which means a trader must have already broadcast the payout tx");
+ return null;
+ }
+
+ // TODO (woodser): include arbitration fee
+ //System.out.println("Creating feeEstimateTx!");
+ MoneroTxWallet feeEstimateTx = multisigWallet.createTx(new MoneroTxConfig()
+ .setAccountIndex(0)
+ .addDestination(new MoneroDestination(buyerPayoutAddress, buyerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5)))) // reduce payment amount to compute fee of similar tx
+ .addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5)))) // TODO (woodser): support addDestination(addr, amt) without new
+ .setRelay(false)
+ );
+
+ System.out.println("Created fee estimate tx!");
+ System.out.println(feeEstimateTx);
+ //BigInteger estimatedFee = feeEstimateTx.getFee();
+
+ // attempt to create payout tx by increasing estimated fee until successful
+ MoneroTxWallet payoutTx = null;
+ int numAttempts = 0;
+ while (payoutTx == null && numAttempts < 50) {
+ BigInteger feeEstimate = feeEstimateTx.getFee().add(feeEstimateTx.getFee().multiply(BigInteger.valueOf(numAttempts)).divide(BigInteger.valueOf(10))); // add 1/10 of fee until tx is successful
+ try {
+ numAttempts++;
+ payoutTx = multisigWallet.createTx(new MoneroTxConfig()
+ .setAccountIndex(0)
+ .addDestination(new MoneroDestination(buyerPayoutAddress, buyerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // split fee subtracted from each payout amount
+ .addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // TODO (woodser): support addDestination(addr, amt) without new
+ .setRelay(false));
+ } catch (MoneroError e) {
+ e.printStackTrace();
+ System.out.println("FAILED TO CREATE PAYOUT TX, ITERATING...");
+ }
+ }
+
+ if (payoutTx == null) throw new RuntimeException("Failed to generate dispute payout tx");
+ System.out.println("DISPUTE PAYOUT TX GENERATED ON ATTEMPT " + numAttempts);
+ System.out.println(payoutTx);
+ return payoutTx;
+ }
+
+ private MoneroTxSet traderSignsDisputePayoutTx(String tradeId, String payoutTxHex) {
+
+ // gather trade info
+ MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
+ Optional disputeOptional = findOwnDispute(tradeId);
+ if (!disputeOptional.isPresent()) throw new RuntimeException("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId);
+ Dispute dispute = disputeOptional.get();
+ Contract contract = dispute.getContract();
+ DisputeResult disputeResult = dispute.getDisputeResultProperty().get();
+
+// Offer offer = checkNotNull(trade.getOffer(), "offer must not be null");
+// BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getMakerDepositTxId() : trade.getTakerDepositTxId()).getIncomingAmount(); // TODO (woodser): use contract instead of trade to get deposit tx ids when contract has deposit tx ids
+// BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getTakerDepositTxId() : trade.getMakerDepositTxId()).getIncomingAmount();
+// BigInteger tradeAmount = BigInteger.valueOf(contract.getTradeAmount().value).multiply(ParsingUtils.XMR_SATOSHI_MULTIPLIER);
+ BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
+ BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
+ System.out.println("Buyer payout amount (with multiplier): " + buyerPayoutAmount);
+ System.out.println("Seller payout amount (with multiplier): " + sellerPayoutAmount);
+
+ // parse arbitrator-signed payout tx
+ MoneroTxSet parsedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
+ if (parsedTxSet.getTxs() == null || parsedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad arbitrator-signed payout tx"); // TODO (woodser): nack
+ MoneroTxWallet arbitratorSignedPayoutTx = parsedTxSet.getTxs().get(0);
+ System.out.println("Parsed arbitrator-signed payout tx:\n" + arbitratorSignedPayoutTx);
+
+ // verify payout tx has exactly 2 destinations
+ if (arbitratorSignedPayoutTx.getOutgoingTransfer() == null || arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations() == null || arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new RuntimeException("Buyer-signed payout tx does not have exactly two destinations");
+
+ // get buyer and seller destinations (order not preserved)
+ boolean buyerFirst = arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().get(0).getAddress().equals(contract.getBuyerPayoutAddressString());
+ MoneroDestination buyerPayoutDestination = arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 0 : 1);
+ MoneroDestination sellerPayoutDestination = arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 1 : 0);
+
+ // verify payout addresses
+ if (!buyerPayoutDestination.getAddress().equals(contract.getBuyerPayoutAddressString())) throw new RuntimeException("Buyer payout address does not match contract");
+ if (!sellerPayoutDestination.getAddress().equals(contract.getSellerPayoutAddressString())) throw new RuntimeException("Seller payout address does not match contract");
+
+ // verify change address is multisig's primary address
+ if (!arbitratorSignedPayoutTx.getChangeAddress().equals(multisigWallet.getPrimaryAddress())) throw new RuntimeException("Change address is not multisig wallet's primary address");
+
+ // verify sum of outputs = destination amounts + change amount
+ if (!arbitratorSignedPayoutTx.getOutputSum().equals(buyerPayoutDestination.getAmount().add(sellerPayoutDestination.getAmount()).add(arbitratorSignedPayoutTx.getChangeAmount()))) throw new RuntimeException("Sum of outputs != destination amounts + change amount");
+
+ // verify buyer destination amount is payout amount - 1/2 tx costs
+ BigInteger txCost = arbitratorSignedPayoutTx.getFee().add(arbitratorSignedPayoutTx.getChangeAmount());
+ BigInteger expectedBuyerPayout = buyerPayoutAmount.subtract(txCost.divide(BigInteger.valueOf(2)));
+
+ System.out.println("Dispute buyer payout amount: " + buyerPayoutAmount);
+ System.out.println("Tx cost: " + txCost);
+ System.out.println("Buyer destination payout amount: " + buyerPayoutDestination.getAmount());
+
+
+ // payout amount is dispute payout amount - 1/2 tx cost - deposit tx fee
+
+ // TODO (woodser): VERIFY PAYOUT TX AMOUNTS WHICH CONSIDERS FEE IF LONG TRADE, EXACT AMOUNT IF SHORT TRADE
+
+
+ // if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not payout amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout);
+
+ // verify seller destination amount is payout amount - 1/2 tx costs
+ // BigInteger expectedSellerPayout = sellerPayoutAmount.subtract(txCost.divide(BigInteger.valueOf(2)));
+ // if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new RuntimeException("Seller destination amount is not payout amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout);
+
+ // TODO (woodser): verify fee is reasonable (e.g. within 2x of fee estimate tx)
+
+ // update multisig wallet from arbitrator
+ System.out.println("Updating multisig hex from arbitrator: " + disputeResult.getArbitratorUpdatedMultisigHex());
+ multisigWallet.importMultisigHex(Arrays.asList(disputeResult.getArbitratorUpdatedMultisigHex()));
+
+ // sign arbitrator-signed payout tx
+ MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex);
+
+ if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing arbitrator-signed payout tx");
+ String signedMultisigTxHex = result.getSignedMultisigTxHex();
+ parsedTxSet.setMultisigTxHex(signedMultisigTxHex);
+ return parsedTxSet;
+ }
}
diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java b/core/src/main/java/bisq/core/support/dispute/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java
index 17d9009044..fb940a12a7 100644
--- a/core/src/main/java/bisq/core/support/dispute/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java
+++ b/core/src/main/java/bisq/core/support/dispute/arbitration/messages/PeerPublishedDisputePayoutTxMessage.java
@@ -22,9 +22,6 @@ import bisq.core.support.SupportType;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
-import bisq.common.util.Utilities;
-
-import com.google.protobuf.ByteString;
import lombok.EqualsAndHashCode;
import lombok.Value;
@@ -32,16 +29,19 @@ import lombok.Value;
@Value
@EqualsAndHashCode(callSuper = true)
public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessage {
- private final byte[] transaction;
+ private final String updatedMultisigHex;
+ private final String payoutTxHex;
private final String tradeId;
private final NodeAddress senderNodeAddress;
- public PeerPublishedDisputePayoutTxMessage(byte[] transaction,
+ public PeerPublishedDisputePayoutTxMessage(String updatedMultisigHex,
+ String payoutTxHex,
String tradeId,
NodeAddress senderNodeAddress,
String uid,
SupportType supportType) {
- this(transaction,
+ this(updatedMultisigHex,
+ payoutTxHex,
tradeId,
senderNodeAddress,
uid,
@@ -54,14 +54,16 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
- private PeerPublishedDisputePayoutTxMessage(byte[] transaction,
+ private PeerPublishedDisputePayoutTxMessage(String updatedMultisigHex,
+ String payoutTxHex,
String tradeId,
NodeAddress senderNodeAddress,
String uid,
int messageVersion,
SupportType supportType) {
super(messageVersion, uid, supportType);
- this.transaction = transaction;
+ this.updatedMultisigHex = updatedMultisigHex;
+ this.payoutTxHex = payoutTxHex;
this.tradeId = tradeId;
this.senderNodeAddress = senderNodeAddress;
}
@@ -70,7 +72,8 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
return getNetworkEnvelopeBuilder()
.setPeerPublishedDisputePayoutTxMessage(protobuf.PeerPublishedDisputePayoutTxMessage.newBuilder()
- .setTransaction(ByteString.copyFrom(transaction))
+ .setUpdatedMultisigHex(updatedMultisigHex)
+ .setPayoutTxHex(payoutTxHex)
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setUid(uid)
@@ -80,7 +83,8 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag
public static PeerPublishedDisputePayoutTxMessage fromProto(protobuf.PeerPublishedDisputePayoutTxMessage proto,
int messageVersion) {
- return new PeerPublishedDisputePayoutTxMessage(proto.getTransaction().toByteArray(),
+ return new PeerPublishedDisputePayoutTxMessage(proto.getUpdatedMultisigHex(),
+ proto.getPayoutTxHex(),
proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getUid(),
@@ -96,7 +100,8 @@ public final class PeerPublishedDisputePayoutTxMessage extends ArbitrationMessag
@Override
public String toString() {
return "PeerPublishedDisputePayoutTxMessage{" +
- "\n transaction=" + Utilities.bytesAsHexString(transaction) +
+ "\n updatedMultisigHex=" + updatedMultisigHex +
+ "\n payoutTxHex=" + payoutTxHex +
",\n tradeId='" + tradeId + '\'' +
",\n senderNodeAddress=" + senderNodeAddress +
",\n PeerPublishedDisputePayoutTxMessage.uid='" + uid + '\'' +
diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java
index 776e2191ed..f31680f981 100644
--- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java
@@ -18,8 +18,8 @@
package bisq.core.support.dispute.mediation;
import bisq.core.btc.setup.WalletsSetup;
-import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
@@ -77,7 +77,7 @@ public final class MediationManager extends DisputeManager
@Inject
public MediationManager(P2PService p2PService,
TradeWalletService tradeWalletService,
- BtcWalletService walletService,
+ XmrWalletService walletService,
WalletsSetup walletsSetup,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
@@ -226,7 +226,7 @@ public final class MediationManager extends DisputeManager
@Nullable
@Override
public NodeAddress getAgentNodeAddress(Dispute dispute) {
- return dispute.getContract().getMediatorNodeAddress();
+ return dispute.getContract().getArbitratorNodeAddress(); // TODO (woodser): mediator becomes and replaces current arbitrator?
}
public void onAcceptMediationResult(Trade trade,
diff --git a/core/src/main/java/bisq/core/support/dispute/messages/ArbitratorPayoutTxRequest.java b/core/src/main/java/bisq/core/support/dispute/messages/ArbitratorPayoutTxRequest.java
new file mode 100644
index 0000000000..9d2e7eed0a
--- /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 0000000000..88ecab2624
--- /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 f60cda8056..ac47e2974b 100644
--- a/core/src/main/java/bisq/core/support/dispute/messages/OpenNewDisputeMessage.java
+++ b/core/src/main/java/bisq/core/support/dispute/messages/OpenNewDisputeMessage.java
@@ -33,16 +33,19 @@ import lombok.Value;
public final class OpenNewDisputeMessage extends DisputeMessage {
private final Dispute dispute;
private final NodeAddress senderNodeAddress;
+ private final String updatedMultisigHex;
public OpenNewDisputeMessage(Dispute dispute,
NodeAddress senderNodeAddress,
String uid,
- SupportType supportType) {
+ SupportType supportType,
+ String updatedMultisigHex) {
this(dispute,
senderNodeAddress,
uid,
Version.getP2PMessageVersion(),
- supportType);
+ supportType,
+ updatedMultisigHex);
}
@@ -54,10 +57,12 @@ public final class OpenNewDisputeMessage extends DisputeMessage {
NodeAddress senderNodeAddress,
String uid,
int messageVersion,
- SupportType supportType) {
+ SupportType supportType,
+ String updatedMultisigHex) {
super(messageVersion, uid, supportType);
this.dispute = dispute;
this.senderNodeAddress = senderNodeAddress;
+ this.updatedMultisigHex = updatedMultisigHex;
}
@Override
@@ -67,7 +72,8 @@ public final class OpenNewDisputeMessage extends DisputeMessage {
.setUid(uid)
.setDispute(dispute.toProtoMessage())
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
- .setType(SupportType.toProtoMessage(supportType)))
+ .setType(SupportType.toProtoMessage(supportType))
+ .setUpdatedMultisigHex(updatedMultisigHex))
.build();
}
@@ -78,7 +84,8 @@ public final class OpenNewDisputeMessage extends DisputeMessage {
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getUid(),
messageVersion,
- SupportType.fromProto(proto.getType()));
+ SupportType.fromProto(proto.getType()),
+ proto.getUpdatedMultisigHex());
}
@Override
@@ -94,6 +101,7 @@ public final class OpenNewDisputeMessage extends DisputeMessage {
",\n OpenNewDisputeMessage.uid='" + uid + '\'' +
",\n messageVersion=" + messageVersion +
",\n supportType=" + supportType +
+ ",\n updatedMultisigHex=" + updatedMultisigHex +
"\n} " + super.toString();
}
}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java
index deb1537b62..527c53ab01 100644
--- a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java
@@ -18,8 +18,8 @@
package bisq.core.support.dispute.refund;
import bisq.core.btc.setup.WalletsSetup;
-import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
@@ -71,12 +71,12 @@ public final class RefundManager extends DisputeManager {
@Inject
public RefundManager(P2PService p2PService,
TradeWalletService tradeWalletService,
- BtcWalletService walletService,
+ XmrWalletService walletService,
WalletsSetup walletsSetup,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager,
- DaoFacade daoFacade,
+ DaoFacade daoFacade, // TODO (woodser): remove daoFacade, priceFeedService?
KeyRing keyRing,
RefundDisputeListService refundDisputeListService,
Config config,
@@ -232,6 +232,7 @@ public final class RefundManager extends DisputeManager {
@Nullable
@Override
public NodeAddress getAgentNodeAddress(Dispute dispute) {
- return dispute.getContract().getRefundAgentNodeAddress();
+ throw new RuntimeException("Refund manager not used in XMR adapation");
+ //return dispute.getContract().getRefundAgentNodeAddress();
}
}
diff --git a/core/src/main/java/bisq/core/trade/ArbitratorTrade.java b/core/src/main/java/bisq/core/trade/ArbitratorTrade.java
new file mode 100644
index 0000000000..13a9d6abd7
--- /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 3a0005f824..38b0540053 100644
--- a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java
@@ -17,7 +17,7 @@
package bisq.core.trade;
-import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.protocol.ProcessModel;
@@ -44,21 +44,19 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
public BuyerAsMakerTrade(Offer offer,
Coin txFee,
Coin takeOfferFee,
- boolean isCurrencyForTakerFeeBtc,
+ @Nullable NodeAddress takerNodeAddress,
+ @Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
- @Nullable NodeAddress mediatorNodeAddress,
- @Nullable NodeAddress refundAgentNodeAddress,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
txFee,
takeOfferFee,
- isCurrencyForTakerFeeBtc,
+ takerNodeAddress,
+ makerNodeAddress,
arbitratorNodeAddress,
- mediatorNodeAddress,
- refundAgentNodeAddress,
- btcWalletService,
+ xmrWalletService,
processModel,
uid);
}
@@ -76,7 +74,7 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
}
public static Tradable fromProto(protobuf.BuyerAsMakerTrade buyerAsMakerTradeProto,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
CoreProtoResolver coreProtoResolver) {
protobuf.Trade proto = buyerAsMakerTradeProto.getTrade();
ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver);
@@ -88,17 +86,19 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
- proto.getIsCurrencyForTakerFeeBtc(),
+ proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
+ proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
- proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
- proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
- btcWalletService,
+ xmrWalletService,
processModel,
uid);
trade.setTradeAmountAsLong(proto.getTradeAmountAsLong());
trade.setTradePrice(proto.getTradePrice());
- trade.setTradingPeerNodeAddress(proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null);
+
+ trade.setMakerNodeAddress(proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null);
+ trade.setTakerNodeAddress(proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null);
+ trade.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
return fromProto(trade,
proto,
diff --git a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java
index 2ccf0fb3cb..6007437e9f 100644
--- a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java
@@ -17,7 +17,7 @@
package bisq.core.trade;
-import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.protocol.ProcessModel;
@@ -45,26 +45,22 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
Coin tradeAmount,
Coin txFee,
Coin takerFee,
- boolean isCurrencyForTakerFeeBtc,
long tradePrice,
- NodeAddress tradingPeerNodeAddress,
+ @Nullable NodeAddress makerNodeAddress,
+ @Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
- @Nullable NodeAddress mediatorNodeAddress,
- @Nullable NodeAddress refundAgentNodeAddress,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
tradeAmount,
txFee,
takerFee,
- isCurrencyForTakerFeeBtc,
tradePrice,
- tradingPeerNodeAddress,
+ makerNodeAddress,
+ takerNodeAddress,
arbitratorNodeAddress,
- mediatorNodeAddress,
- refundAgentNodeAddress,
- btcWalletService,
+ xmrWalletService,
processModel,
uid);
}
@@ -83,7 +79,7 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
}
public static Tradable fromProto(protobuf.BuyerAsTakerTrade buyerAsTakerTradeProto,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
CoreProtoResolver coreProtoResolver) {
protobuf.Trade proto = buyerAsTakerTradeProto.getTrade();
ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver);
@@ -96,13 +92,11 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
Coin.valueOf(proto.getTradeAmountAsLong()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
- proto.getIsCurrencyForTakerFeeBtc(),
proto.getTradePrice(),
- proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
+ proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
+ proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
- proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
- proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
- btcWalletService,
+ xmrWalletService,
processModel,
uid),
proto,
diff --git a/core/src/main/java/bisq/core/trade/BuyerTrade.java b/core/src/main/java/bisq/core/trade/BuyerTrade.java
index 82f38cf9c1..4b6313e32a 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 81602d226d..7b145ff6b6 100644
--- a/core/src/main/java/bisq/core/trade/Contract.java
+++ b/core/src/main/java/bisq/core/trade/Contract.java
@@ -33,8 +33,6 @@ import bisq.common.proto.network.NetworkPayload;
import bisq.common.util.JsonExclude;
import bisq.common.util.Utilities;
-import com.google.protobuf.ByteString;
-
import org.bitcoinj.core.Coin;
import org.apache.commons.lang3.StringUtils;
@@ -52,10 +50,9 @@ public final class Contract implements NetworkPayload {
private final OfferPayload offerPayload;
private final long tradeAmount;
private final long tradePrice;
- private final String takerFeeTxID;
private final NodeAddress buyerNodeAddress;
private final NodeAddress sellerNodeAddress;
- private final NodeAddress mediatorNodeAddress;
+ private final NodeAddress arbitratorNodeAddress;
private final boolean isBuyerMakerAndSellerTaker;
private final String makerAccountId;
private final String takerAccountId;
@@ -67,22 +64,16 @@ public final class Contract implements NetworkPayload {
private final PubKeyRing takerPubKeyRing;
private final String makerPayoutAddressString;
private final String takerPayoutAddressString;
- @JsonExclude
- private final byte[] makerMultiSigPubKey;
- @JsonExclude
- private final byte[] takerMultiSigPubKey;
// Added in v1.2.0
private long lockTime;
- private final NodeAddress refundAgentNodeAddress;
public Contract(OfferPayload offerPayload,
long tradeAmount,
long tradePrice,
- String takerFeeTxID,
NodeAddress buyerNodeAddress,
NodeAddress sellerNodeAddress,
- NodeAddress mediatorNodeAddress,
+ NodeAddress arbitratorNodeAddress,
boolean isBuyerMakerAndSellerTaker,
String makerAccountId,
String takerAccountId,
@@ -92,17 +83,13 @@ public final class Contract implements NetworkPayload {
PubKeyRing takerPubKeyRing,
String makerPayoutAddressString,
String takerPayoutAddressString,
- byte[] makerMultiSigPubKey,
- byte[] takerMultiSigPubKey,
- long lockTime,
- NodeAddress refundAgentNodeAddress) {
+ long lockTime) {
this.offerPayload = offerPayload;
this.tradeAmount = tradeAmount;
this.tradePrice = tradePrice;
- this.takerFeeTxID = takerFeeTxID;
this.buyerNodeAddress = buyerNodeAddress;
this.sellerNodeAddress = sellerNodeAddress;
- this.mediatorNodeAddress = mediatorNodeAddress;
+ this.arbitratorNodeAddress = arbitratorNodeAddress;
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
this.makerAccountId = makerAccountId;
this.takerAccountId = takerAccountId;
@@ -112,10 +99,7 @@ public final class Contract implements NetworkPayload {
this.takerPubKeyRing = takerPubKeyRing;
this.makerPayoutAddressString = makerPayoutAddressString;
this.takerPayoutAddressString = takerPayoutAddressString;
- this.makerMultiSigPubKey = makerMultiSigPubKey;
- this.takerMultiSigPubKey = takerMultiSigPubKey;
this.lockTime = lockTime;
- this.refundAgentNodeAddress = refundAgentNodeAddress;
String makerPaymentMethodId = makerPaymentAccountPayload.getPaymentMethodId();
String takerPaymentMethodId = takerPaymentAccountPayload.getPaymentMethodId();
@@ -137,10 +121,9 @@ public final class Contract implements NetworkPayload {
return new Contract(OfferPayload.fromProto(proto.getOfferPayload()),
proto.getTradeAmount(),
proto.getTradePrice(),
- proto.getTakerFeeTxId(),
NodeAddress.fromProto(proto.getBuyerNodeAddress()),
NodeAddress.fromProto(proto.getSellerNodeAddress()),
- NodeAddress.fromProto(proto.getMediatorNodeAddress()),
+ NodeAddress.fromProto(proto.getArbitratorNodeAddress()),
proto.getIsBuyerMakerAndSellerTaker(),
proto.getMakerAccountId(),
proto.getTakerAccountId(),
@@ -150,10 +133,7 @@ public final class Contract implements NetworkPayload {
PubKeyRing.fromProto(proto.getTakerPubKeyRing()),
proto.getMakerPayoutAddressString(),
proto.getTakerPayoutAddressString(),
- proto.getMakerMultiSigPubKey().toByteArray(),
- proto.getTakerMultiSigPubKey().toByteArray(),
- proto.getLockTime(),
- NodeAddress.fromProto(proto.getRefundAgentNodeAddress()));
+ proto.getLockTime());
}
@Override
@@ -162,10 +142,9 @@ public final class Contract implements NetworkPayload {
.setOfferPayload(offerPayload.toProtoMessage().getOfferPayload())
.setTradeAmount(tradeAmount)
.setTradePrice(tradePrice)
- .setTakerFeeTxId(takerFeeTxID)
.setBuyerNodeAddress(buyerNodeAddress.toProtoMessage())
.setSellerNodeAddress(sellerNodeAddress.toProtoMessage())
- .setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage())
+ .setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
.setMakerAccountId(makerAccountId)
.setTakerAccountId(takerAccountId)
@@ -175,10 +154,7 @@ public final class Contract implements NetworkPayload {
.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage())
.setMakerPayoutAddressString(makerPayoutAddressString)
.setTakerPayoutAddressString(takerPayoutAddressString)
- .setMakerMultiSigPubKey(ByteString.copyFrom(makerMultiSigPubKey))
- .setTakerMultiSigPubKey(ByteString.copyFrom(takerMultiSigPubKey))
.setLockTime(lockTime)
- .setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage())
.build();
}
@@ -203,14 +179,6 @@ public final class Contract implements NetworkPayload {
return isBuyerMakerAndSellerTaker ? takerPubKeyRing : makerPubKeyRing;
}
- public byte[] getBuyerMultiSigPubKey() {
- return isBuyerMakerAndSellerTaker ? makerMultiSigPubKey : takerMultiSigPubKey;
- }
-
- public byte[] getSellerMultiSigPubKey() {
- return isBuyerMakerAndSellerTaker ? takerMultiSigPubKey : makerMultiSigPubKey;
- }
-
public PaymentAccountPayload getBuyerPaymentAccountPayload() {
return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayload : takerPaymentAccountPayload;
}
@@ -296,11 +264,9 @@ public final class Contract implements NetworkPayload {
"\n offerPayload=" + offerPayload +
",\n tradeAmount=" + tradeAmount +
",\n tradePrice=" + tradePrice +
- ",\n takerFeeTxID='" + takerFeeTxID + '\'' +
",\n buyerNodeAddress=" + buyerNodeAddress +
",\n sellerNodeAddress=" + sellerNodeAddress +
- ",\n mediatorNodeAddress=" + mediatorNodeAddress +
- ",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
+ ",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker +
",\n makerAccountId='" + makerAccountId + '\'' +
",\n takerAccountId='" + takerAccountId + '\'' +
@@ -310,10 +276,6 @@ public final class Contract implements NetworkPayload {
",\n takerPubKeyRing=" + takerPubKeyRing +
",\n makerPayoutAddressString='" + makerPayoutAddressString + '\'' +
",\n takerPayoutAddressString='" + takerPayoutAddressString + '\'' +
- ",\n makerMultiSigPubKey=" + Utilities.bytesAsHexString(makerMultiSigPubKey) +
- ",\n takerMultiSigPubKey=" + Utilities.bytesAsHexString(takerMultiSigPubKey) +
- ",\n buyerMultiSigPubKey=" + Utilities.bytesAsHexString(getBuyerMultiSigPubKey()) +
- ",\n sellerMultiSigPubKey=" + Utilities.bytesAsHexString(getSellerMultiSigPubKey()) +
",\n lockTime=" + lockTime +
"\n}";
}
diff --git a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java
index ccbf66a0f6..e0e83c24be 100644
--- a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java
@@ -17,7 +17,7 @@
package bisq.core.trade;
-import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.protocol.ProcessModel;
@@ -44,21 +44,19 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
public SellerAsMakerTrade(Offer offer,
Coin txFee,
Coin takerFee,
- boolean isCurrencyForTakerFeeBtc,
+ @Nullable NodeAddress makerNodeAddress,
+ @Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
- @Nullable NodeAddress mediatorNodeAddress,
- @Nullable NodeAddress refundAgentNodeAddress,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
txFee,
takerFee,
- isCurrencyForTakerFeeBtc,
+ makerNodeAddress,
+ takerNodeAddress,
arbitratorNodeAddress,
- mediatorNodeAddress,
- refundAgentNodeAddress,
- btcWalletService,
+ xmrWalletService,
processModel,
uid);
}
@@ -77,7 +75,7 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
}
public static Tradable fromProto(protobuf.SellerAsMakerTrade sellerAsMakerTradeProto,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
CoreProtoResolver coreProtoResolver) {
protobuf.Trade proto = sellerAsMakerTradeProto.getTrade();
ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver);
@@ -89,17 +87,15 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
- proto.getIsCurrencyForTakerFeeBtc(),
+ proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
+ proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
- proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
- proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
- btcWalletService,
+ xmrWalletService,
processModel,
uid);
trade.setTradeAmountAsLong(proto.getTradeAmountAsLong());
trade.setTradePrice(proto.getTradePrice());
- trade.setTradingPeerNodeAddress(proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null);
return fromProto(trade,
proto,
diff --git a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java
index 11fb6c281d..85cf04e2d1 100644
--- a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java
@@ -17,7 +17,7 @@
package bisq.core.trade;
-import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.protocol.ProcessModel;
@@ -45,26 +45,22 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
Coin tradeAmount,
Coin txFee,
Coin takerFee,
- boolean isCurrencyForTakerFeeBtc,
long tradePrice,
- NodeAddress tradingPeerNodeAddress,
+ @Nullable NodeAddress makerNodeAddress,
+ @Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
- @Nullable NodeAddress mediatorNodeAddress,
- @Nullable NodeAddress refundAgentNodeAddress,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
tradeAmount,
txFee,
takerFee,
- isCurrencyForTakerFeeBtc,
tradePrice,
- tradingPeerNodeAddress,
+ makerNodeAddress,
+ takerNodeAddress,
arbitratorNodeAddress,
- mediatorNodeAddress,
- refundAgentNodeAddress,
- btcWalletService,
+ xmrWalletService,
processModel,
uid);
}
@@ -83,7 +79,7 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
}
public static Tradable fromProto(protobuf.SellerAsTakerTrade sellerAsTakerTradeProto,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
CoreProtoResolver coreProtoResolver) {
protobuf.Trade proto = sellerAsTakerTradeProto.getTrade();
ProcessModel processModel = ProcessModel.fromProto(proto.getProcessModel(), coreProtoResolver);
@@ -96,13 +92,11 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
Coin.valueOf(proto.getTradeAmountAsLong()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
- proto.getIsCurrencyForTakerFeeBtc(),
proto.getTradePrice(),
- proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
+ proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
+ proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
- proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
- proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
- btcWalletService,
+ xmrWalletService,
processModel,
uid),
proto,
diff --git a/core/src/main/java/bisq/core/trade/SellerTrade.java b/core/src/main/java/bisq/core/trade/SellerTrade.java
index a87c18ddee..e54c4b952b 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 c3a668708a..e39d29513f 100644
--- a/core/src/main/java/bisq/core/trade/TradableList.java
+++ b/core/src/main/java/bisq/core/trade/TradableList.java
@@ -17,7 +17,7 @@
package bisq.core.trade;
-import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.OpenOffer;
import bisq.core.proto.CoreProtoResolver;
@@ -62,20 +62,20 @@ public final class TradableList extends PersistableListAsObs
public static TradableList fromProto(protobuf.TradableList proto,
CoreProtoResolver coreProtoResolver,
- BtcWalletService btcWalletService) {
+ XmrWalletService xmrWalletService) {
List list = proto.getTradableList().stream()
.map(tradable -> {
switch (tradable.getMessageCase()) {
case OPEN_OFFER:
return OpenOffer.fromProto(tradable.getOpenOffer());
case BUYER_AS_MAKER_TRADE:
- return BuyerAsMakerTrade.fromProto(tradable.getBuyerAsMakerTrade(), btcWalletService, coreProtoResolver);
+ return BuyerAsMakerTrade.fromProto(tradable.getBuyerAsMakerTrade(), xmrWalletService, coreProtoResolver);
case BUYER_AS_TAKER_TRADE:
- return BuyerAsTakerTrade.fromProto(tradable.getBuyerAsTakerTrade(), btcWalletService, coreProtoResolver);
+ return BuyerAsTakerTrade.fromProto(tradable.getBuyerAsTakerTrade(), xmrWalletService, coreProtoResolver);
case SELLER_AS_MAKER_TRADE:
- return SellerAsMakerTrade.fromProto(tradable.getSellerAsMakerTrade(), btcWalletService, coreProtoResolver);
+ return SellerAsMakerTrade.fromProto(tradable.getSellerAsMakerTrade(), xmrWalletService, coreProtoResolver);
case SELLER_AS_TAKER_TRADE:
- return SellerAsTakerTrade.fromProto(tradable.getSellerAsTakerTrade(), btcWalletService, coreProtoResolver);
+ return SellerAsTakerTrade.fromProto(tradable.getSellerAsTakerTrade(), xmrWalletService, coreProtoResolver);
default:
log.error("Unknown messageCase. tradable.getMessageCase() = " + tradable.getMessageCase());
throw new ProtobufferRuntimeException("Unknown messageCase. tradable.getMessageCase() = " +
diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java
index de5c1949bb..8e5e9bf963 100644
--- a/core/src/main/java/bisq/core/trade/Trade.java
+++ b/core/src/main/java/bisq/core/trade/Trade.java
@@ -17,7 +17,7 @@
package bisq.core.trade;
-import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.CurrencyUtil;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
@@ -28,8 +28,10 @@ import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.support.dispute.refund.RefundResultState;
import bisq.core.support.messages.ChatMessage;
+import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.ProcessModel;
import bisq.core.trade.protocol.ProcessModelServiceProvider;
+import bisq.core.trade.protocol.TradeMessageListener;
import bisq.core.trade.txproof.AssetTxProofResult;
import bisq.core.util.VolumeUtil;
@@ -45,12 +47,6 @@ import com.google.protobuf.Message;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
-import org.bitcoinj.core.TransactionConfidence;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.MoreExecutors;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
@@ -64,7 +60,9 @@ import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -78,6 +76,14 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
+
+
+import monero.common.MoneroError;
+import monero.daemon.MoneroDaemon;
+import monero.daemon.MoneroDaemonRpc;
+import monero.wallet.MoneroWallet;
+import monero.wallet.model.MoneroTxWallet;
+
/**
* Holds all data which are relevant to the trade, but not those which are only needed in the trade process as shared data between tasks. Those data are
* stored in the task model.
@@ -109,26 +115,27 @@ public abstract class Trade implements Tradable, Model {
// taker perspective
TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED), // Not used anymore
+ // Alternatively the taker could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
+ TAKER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED),
// #################### Phase DEPOSIT_PUBLISHED
// We changes order in trade protocol of publishing deposit tx and sending it to the peer.
// Now we send it first to the peer and only if that succeeds we publish it to avoid likelihood of
// failed trades. We do not want to change the order of the enum though so we keep it here as it was originally.
- SELLER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED),
-
+ TAKER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED),
// DEPOSIT_TX_PUBLISHED_MSG
- // seller perspective
- SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ // taker perspective
+ TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- // buyer perspective
- BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ // maker perspective
+ MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- // Alternatively the buyer could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
- BUYER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED),
+ // Alternatively the maker could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
+ MAKER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED),
// #################### Phase DEPOSIT_CONFIRMED
@@ -291,8 +298,6 @@ public abstract class Trade implements Tradable, Model {
@Getter
private final Offer offer;
@Getter
- private final boolean isCurrencyForTakerFeeBtc;
- @Getter
private final long txFeeAsLong;
@Getter
private final long takerFeeAsLong;
@@ -312,10 +317,6 @@ public abstract class Trade implements Tradable, Model {
@Nullable
@Getter
@Setter
- private String depositTxId;
- @Nullable
- @Getter
- @Setter
private String payoutTxId;
@Getter
@Setter
@@ -324,8 +325,6 @@ public abstract class Trade implements Tradable, Model {
private long tradePrice;
@Nullable
@Getter
- private NodeAddress tradingPeerNodeAddress;
- @Getter
private State state = State.PREPARATION;
@Getter
private DisputeState disputeState = DisputeState.NO_DISPUTE;
@@ -390,7 +389,7 @@ public abstract class Trade implements Tradable, Model {
@Getter
transient final private Coin takerFee;
@Getter
- transient final private BtcWalletService btcWalletService;
+ transient final private XmrWalletService xmrWalletService;
transient final private ObjectProperty stateProperty = new SimpleObjectProperty<>(state);
transient final private ObjectProperty statePhaseProperty = new SimpleObjectProperty<>(state.phase);
@@ -399,8 +398,6 @@ public abstract class Trade implements Tradable, Model {
transient final private StringProperty errorMessageProperty = new SimpleStringProperty();
// Mutable
- @Nullable
- transient private Transaction depositTx;
@Getter
transient private boolean isInitialized;
@@ -409,7 +406,7 @@ public abstract class Trade implements Tradable, Model {
transient private Transaction delayedPayoutTx;
@Nullable
- transient private Transaction payoutTx;
+ transient private MoneroTxWallet payoutTx;
@Nullable
transient private Coin tradeAmount;
@@ -462,6 +459,33 @@ public abstract class Trade implements Tradable, Model {
transient final private IntegerProperty assetTxProofResultUpdateProperty = new SimpleIntegerProperty();
+ // Added in XMR integration
+ private transient List tradeMessageListeners; // notified on fully validated trade messages
+ @Getter
+ @Setter
+ private NodeAddress makerNodeAddress;
+ @Getter
+ @Setter
+ private NodeAddress takerNodeAddress;
+ @Getter
+ @Setter
+ private PubKeyRing makerPubKeyRing;
+ @Getter
+ @Setter
+ private PubKeyRing takerPubKeyRing;
+ @Nullable
+ transient private MoneroTxWallet makerDepositTx;
+ @Nullable
+ transient private MoneroTxWallet takerDepositTx;
+ @Nullable
+ @Getter
+ @Setter
+ private String makerDepositTxId;
+ @Nullable
+ @Getter
+ @Setter
+ private String takerDepositTxId;
+
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialization
///////////////////////////////////////////////////////////////////////////////////////////
@@ -470,62 +494,86 @@ public abstract class Trade implements Tradable, Model {
protected Trade(Offer offer,
Coin txFee,
Coin takerFee,
- boolean isCurrencyForTakerFeeBtc,
+ @Nullable NodeAddress makerNodeAddress,
+ @Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
- @Nullable NodeAddress mediatorNodeAddress,
- @Nullable NodeAddress refundAgentNodeAddress,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
this.offer = offer;
this.txFee = txFee;
this.takerFee = takerFee;
- this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc;
+ this.makerNodeAddress = makerNodeAddress;
+ this.takerNodeAddress = takerNodeAddress;
this.arbitratorNodeAddress = arbitratorNodeAddress;
- this.mediatorNodeAddress = mediatorNodeAddress;
- this.refundAgentNodeAddress = refundAgentNodeAddress;
- this.btcWalletService = btcWalletService;
+ this.xmrWalletService = xmrWalletService;
this.processModel = processModel;
this.uid = uid;
txFeeAsLong = txFee.value;
takerFeeAsLong = takerFee.value;
takeOfferDate = new Date().getTime();
+ tradeMessageListeners = new ArrayList();
}
+ // TODO (woodser): this constructor has mediator and refund agent (to be removed), otherwise use common
// taker
@SuppressWarnings("NullableProblems")
protected Trade(Offer offer,
Coin tradeAmount,
Coin txFee,
Coin takerFee,
- boolean isCurrencyForTakerFeeBtc,
long tradePrice,
- NodeAddress tradingPeerNodeAddress,
+ @Nullable NodeAddress makerNodeAddress,
+ @Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
this(offer,
txFee,
takerFee,
- isCurrencyForTakerFeeBtc,
+ makerNodeAddress,
+ takerNodeAddress,
arbitratorNodeAddress,
- mediatorNodeAddress,
- refundAgentNodeAddress,
- btcWalletService,
+ xmrWalletService,
processModel,
uid);
this.tradePrice = tradePrice;
- this.tradingPeerNodeAddress = tradingPeerNodeAddress;
-
setTradeAmount(tradeAmount);
}
+ // arbitrator
+ @SuppressWarnings("NullableProblems")
+ protected Trade(Offer offer,
+ Coin tradeAmount,
+ Coin txFee,
+ Coin takerFee,
+ long tradePrice,
+ NodeAddress makerNodeAddress,
+ NodeAddress takerNodeAddress,
+ NodeAddress arbitratorNodeAddress,
+ XmrWalletService xmrWalletService,
+ ProcessModel processModel,
+ String uid) {
+
+ this(offer,
+ txFee,
+ takerFee,
+ makerNodeAddress,
+ takerNodeAddress,
+ arbitratorNodeAddress,
+ xmrWalletService,
+ processModel,
+ uid);
+
+ this.tradePrice = tradePrice;
+ setTradeAmount(tradeAmount);
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
@@ -535,7 +583,6 @@ public abstract class Trade implements Tradable, Model {
public Message toProtoMessage() {
protobuf.Trade.Builder builder = protobuf.Trade.newBuilder()
.setOffer(offer.toProtoMessage())
- .setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc)
.setTxFeeAsLong(txFeeAsLong)
.setTakerFeeAsLong(takerFeeAsLong)
.setTakeOfferDate(takeOfferDate)
@@ -552,9 +599,9 @@ public abstract class Trade implements Tradable, Model {
.setUid(uid);
Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId);
- Optional.ofNullable(depositTxId).ifPresent(builder::setDepositTxId);
+ Optional.ofNullable(takerDepositTxId).ifPresent(builder::setTakerDepositTxId);
+ Optional.ofNullable(makerDepositTxId).ifPresent(builder::setMakerDepositTxId);
Optional.ofNullable(payoutTxId).ifPresent(builder::setPayoutTxId);
- Optional.ofNullable(tradingPeerNodeAddress).ifPresent(e -> builder.setTradingPeerNodeAddress(tradingPeerNodeAddress.toProtoMessage()));
Optional.ofNullable(contract).ifPresent(e -> builder.setContract(contract.toProtoMessage()));
Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson);
Optional.ofNullable(contractHash).ifPresent(e -> builder.setContractHash(ByteString.copyFrom(contractHash)));
@@ -575,7 +622,10 @@ public abstract class Trade implements Tradable, Model {
Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes)));
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.name()));
-
+ Optional.ofNullable(makerNodeAddress).ifPresent(e -> builder.setMakerNodeAddress(makerNodeAddress.toProtoMessage()));
+ Optional.ofNullable(makerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage()));
+ Optional.ofNullable(takerNodeAddress).ifPresent(e -> builder.setTakerNodeAddress(takerNodeAddress.toProtoMessage()));
+ Optional.ofNullable(takerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(takerPubKeyRing.toProtoMessage()));
return builder.build();
}
@@ -585,7 +635,8 @@ public abstract class Trade implements Tradable, Model {
trade.setDisputeState(DisputeState.fromProto(proto.getDisputeState()));
trade.setTradePeriodState(TradePeriodState.fromProto(proto.getTradePeriodState()));
trade.setTakerFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerFeeTxId()));
- trade.setDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getDepositTxId()));
+ trade.setMakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getMakerDepositTxId()));
+ trade.setTakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerDepositTxId()));
trade.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId()));
trade.setContract(proto.hasContract() ? Contract.fromProto(proto.getContract(), coreProtoResolver) : null);
trade.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
@@ -614,6 +665,10 @@ public abstract class Trade implements Tradable, Model {
persistedAssetTxProofResult = null;
}
trade.setAssetTxProofResult(persistedAssetTxProofResult);
+ trade.setMakerNodeAddress(NodeAddress.fromProto(proto.getMakerNodeAddress()));
+ trade.setMakerPubKeyRing(proto.hasMakerPubKeyRing() ? PubKeyRing.fromProto(proto.getMakerPubKeyRing()) : null);
+ trade.setTakerNodeAddress(NodeAddress.fromProto(proto.getTakerNodeAddress()));
+ trade.setTakerPubKeyRing(proto.hasTakerPubKeyRing() ? PubKeyRing.fromProto(proto.getTakerPubKeyRing()) : null);
trade.chatMessages.addAll(proto.getChatMessageList().stream()
.map(ChatMessage::fromPayloadProto)
@@ -647,24 +702,72 @@ public abstract class Trade implements Tradable, Model {
// API
///////////////////////////////////////////////////////////////////////////////////////////
- // The deserialized tx has not actual confidence data, so we need to get the fresh one from the wallet.
- void updateDepositTxFromWallet() {
- if (getDepositTx() != null)
- applyDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getTxId()));
+ public void setTradingPeerNodeAddress(NodeAddress peerAddress) {
+ if (this instanceof MakerTrade) takerNodeAddress = peerAddress;
+ else if (this instanceof TakerTrade) makerNodeAddress = peerAddress;
+ else throw new RuntimeException("Must be maker or taker to set peer address");
}
- public void applyDepositTx(Transaction tx) {
- this.depositTx = tx;
- depositTxId = depositTx.getTxId().toString();
- setupConfidenceListener();
+ public NodeAddress getTradingPeerNodeAddress() {
+ if (this instanceof MakerTrade) return takerNodeAddress;
+ else if (this instanceof TakerTrade) return makerNodeAddress;
+ else if (this instanceof ArbitratorTrade) return null;
+ else throw new RuntimeException("Unknown trade type: " + this.getClass().getName());
+ }
+
+ public void setTradingPeerPubKeyRing(PubKeyRing peerPubKeyRing) {
+ if (this instanceof MakerTrade) takerPubKeyRing = peerPubKeyRing;
+ else if (this instanceof TakerTrade) makerPubKeyRing = peerPubKeyRing;
+ else throw new RuntimeException("Must be maker or taker to set peer address");
+ }
+
+ public PubKeyRing getTradingPeerPubKeyRing() {
+ if (this instanceof MakerTrade) return takerPubKeyRing;
+ else if (this instanceof TakerTrade) return makerPubKeyRing;
+ else if (this instanceof ArbitratorTrade) return null;
+ else throw new RuntimeException("Unknown trade type: " + this.getClass().getName());
+ }
+
+ // The deserialized tx has not actual confidence data, so we need to get the fresh one from the wallet.
+ void updateDepositTxFromWallet() {
+ if (getMakerDepositTx() != null && getTakerDepositTx() != null) {
+ System.out.println(processModel.getProvider().getXmrWalletService());
+ MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(getId());
+ applyDepositTxs(multisigWallet.getTx(getMakerDepositTxId()), multisigWallet.getTx(getTakerDepositTxId()));
+ }
+ }
+
+ public void applyDepositTxs(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) {
+ this.makerDepositTx = makerDepositTx;
+ this.takerDepositTx = takerDepositTx;
+ makerDepositTxId = makerDepositTx.getHash();
+ takerDepositTxId = takerDepositTx.getHash();
+ //setupConfirmationListener(); // TODO (woodser): listening disabled here, using SetupDepositTxsListener in buyer and seller
+ if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
+ setConfirmedState(); // TODO (woodser): bisq "confirmed" = xmr unlocked after 10 confirmations
+ }
}
@Nullable
- public Transaction getDepositTx() {
- if (depositTx == null) {
- depositTx = depositTxId != null ? btcWalletService.getTransaction(depositTxId) : null;
- }
- return depositTx;
+ public MoneroTxWallet getTakerDepositTx() {
+ try {
+ if (takerDepositTx == null) takerDepositTx = takerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(takerDepositTxId) : null;
+ return takerDepositTx;
+ } catch (MoneroError e) {
+ log.error("Wallet is missing taker deposit tx " + takerDepositTxId);
+ return null;
+ }
+ }
+
+ @Nullable
+ public MoneroTxWallet getMakerDepositTx() {
+ try {
+ if (makerDepositTx == null) makerDepositTx = makerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(makerDepositTxId) : null;
+ return makerDepositTx;
+ } catch (MoneroError e) {
+ log.error("Wallet is missing maker deposit tx " + makerDepositTxId);
+ return null;
+ }
}
public void applyDelayedPayoutTx(Transaction delayedPayoutTx) {
@@ -676,31 +779,31 @@ public abstract class Trade implements Tradable, Model {
this.delayedPayoutTxBytes = delayedPayoutTxBytes;
}
- @Nullable
- public Transaction getDelayedPayoutTx() {
- return getDelayedPayoutTx(processModel.getBtcWalletService());
- }
-
- // If called from a not initialized trade (or a closed or failed trade)
- // we need to pass the btcWalletService
- @Nullable
- public Transaction getDelayedPayoutTx(BtcWalletService btcWalletService) {
- if (delayedPayoutTx == null) {
- if (btcWalletService == null) {
- log.warn("btcWalletService is null. You might call that method before the tradeManager has " +
- "initialized all trades");
- return null;
- }
-
- if (delayedPayoutTxBytes == null) {
- log.warn("delayedPayoutTxBytes are null");
- return null;
- }
-
- delayedPayoutTx = btcWalletService.getTxFromSerializedTx(delayedPayoutTxBytes);
- }
- return delayedPayoutTx;
- }
+// @Nullable
+// public Transaction getDelayedPayoutTx() {
+// return getDelayedPayoutTx(processModel.getBtcWalletService());
+// }
+//
+// // If called from a not initialized trade (or a closed or failed trade)
+// // we need to pass the xmrWalletService
+// @Nullable
+// public Transaction getDelayedPayoutTx(XmrWalletService xmrWalletService) {
+// if (delayedPayoutTx == null) {
+// if (xmrWalletService == null) {
+// log.warn("xmrWalletService is null. You might call that method before the tradeManager has " +
+// "initialized all trades");
+// return null;
+// }
+//
+// if (delayedPayoutTxBytes == null) {
+// log.warn("delayedPayoutTxBytes are null");
+// return null;
+// }
+//
+// delayedPayoutTx = xmrWalletService.getTxFromSerializedTx(delayedPayoutTxBytes);
+// }
+// return delayedPayoutTx;
+// }
public void addAndPersistChatMessage(ChatMessage chatMessage) {
if (!chatMessages.contains(chatMessage)) {
@@ -737,6 +840,26 @@ public abstract class Trade implements Tradable, Model {
public abstract boolean confirmPermitted();
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Listeners
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public void addTradeMessageListener(TradeMessageListener listener) {
+ tradeMessageListeners.add(listener);
+ }
+
+ public void removeTradeMessageListener(TradeMessageListener listener) {
+ if (!tradeMessageListeners.remove(listener)) throw new RuntimeException("TradeMessageListener is not registered");
+ }
+
+ // notified from TradeProtocol of verified messages
+ public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) {
+ for (TradeMessageListener listener : new ArrayList(tradeMessageListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception
+ listener.onVerifiedTradeMessage(message, sender);
+ }
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
@@ -786,13 +909,6 @@ public abstract class Trade implements Tradable, Model {
tradePeriodStateProperty.set(tradePeriodState);
}
- public void setTradingPeerNodeAddress(NodeAddress tradingPeerNodeAddress) {
- if (tradingPeerNodeAddress == null)
- log.error("tradingPeerAddress=null");
- else
- this.tradingPeerNodeAddress = tradingPeerNodeAddress;
- }
-
public void setTradeAmount(Coin tradeAmount) {
this.tradeAmount = tradeAmount;
tradeAmountAsLong = tradeAmount.value;
@@ -800,9 +916,9 @@ public abstract class Trade implements Tradable, Model {
getTradeVolumeProperty().set(getTradeVolume());
}
- public void setPayoutTx(Transaction payoutTx) {
+ public void setPayoutTx(MoneroTxWallet payoutTx) {
this.payoutTx = payoutTx;
- payoutTxId = payoutTx.getTxId().toString();
+ payoutTxId = payoutTx.getHash();
}
public void setErrorMessage(String errorMessage) {
@@ -863,14 +979,19 @@ public abstract class Trade implements Tradable, Model {
private long getTradeStartTime() {
long now = System.currentTimeMillis();
long startTime;
- Transaction depositTx = getDepositTx();
- if (depositTx != null && getTakeOfferDate() != null) {
- if (depositTx.getConfidence().getDepthInBlocks() > 0) {
+ final MoneroTxWallet takerDepositTx = getTakerDepositTx();
+ final MoneroTxWallet makerDepositTx = getMakerDepositTx();
+ if (makerDepositTx != null && takerDepositTx != null && getTakeOfferDate() != null) {
+ if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
final long tradeTime = getTakeOfferDate().getTime();
- // Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime()
- long blockTime = depositTx.getIncludedInBestChainAt() != null
- ? depositTx.getIncludedInBestChainAt().getTime()
- : depositTx.getUpdateTime().getTime();
+ long maxHeight = Math.max(makerDepositTx.getHeight(), takerDepositTx.getHeight());
+ MoneroDaemon daemonRpc = new MoneroDaemonRpc("http://localhost:38081", "superuser", "abctesting123"); // TODO (woodser): move to common config
+ long blockTime = daemonRpc.getBlockByHeight(maxHeight).getTimestamp();
+
+// if (depositTx.getConfidence().getDepthInBlocks() > 0) {
+// final long tradeTime = getTakeOfferDate().getTime();
+// // Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime()
+// long blockTime = depositTx.getIncludedInBestChainAt() != null ? depositTx.getIncludedInBestChainAt().getTime() : depositTx.getUpdateTime().getTime();
// If block date is in future (Date in Bitcoin blocks can be off by +/- 2 hours) we use our current date.
// If block date is earlier than our trade date we use our trade date.
if (blockTime > now)
@@ -881,8 +1002,7 @@ public abstract class Trade implements Tradable, Model {
log.debug("We set the start for the trade period to {}. Trade started at: {}. Block got mined at: {}",
new Date(startTime), new Date(tradeTime), new Date(blockTime));
} else {
- log.debug("depositTx not confirmed yet. We don't start counting remaining trade period yet. txId={}",
- depositTx.getTxId().toString());
+ log.debug("depositTx not confirmed yet. We don't start counting remaining trade period yet. makerTxId={}, takerTxId={}", makerDepositTx.getHash(), takerDepositTx.getHash());
startTime = now;
}
} else {
@@ -1020,9 +1140,9 @@ public abstract class Trade implements Tradable, Model {
}
@Nullable
- public Transaction getPayoutTx() {
+ public MoneroTxWallet getPayoutTx() {
if (payoutTx == null)
- payoutTx = payoutTxId != null ? btcWalletService.getTransaction(payoutTxId) : null;
+ payoutTx = payoutTxId != null ? xmrWalletService.getWallet().getTx(payoutTxId) : null;
return payoutTx;
}
@@ -1038,8 +1158,8 @@ public abstract class Trade implements Tradable, Model {
public boolean isTxChainInvalid() {
return offer.getOfferFeePaymentTxId() == null ||
getTakerFeeTxId() == null ||
- getDepositTxId() == null ||
- getDepositTx() == null ||
+ getMakerDepositTxId() == null ||
+ getTakerDepositTxId() == null ||
getDelayedPayoutTxBytes() == null;
}
@@ -1076,31 +1196,31 @@ public abstract class Trade implements Tradable, Model {
return tradeVolumeProperty;
}
- private void setupConfidenceListener() {
- if (getDepositTx() != null) {
- TransactionConfidence transactionConfidence = getDepositTx().getConfidence();
- if (transactionConfidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
- setConfirmedState();
- } else {
- ListenableFuture future = transactionConfidence.getDepthFuture(1);
- Futures.addCallback(future, new FutureCallback<>() {
- @Override
- public void onSuccess(TransactionConfidence result) {
- setConfirmedState();
- }
-
- @Override
- public void onFailure(@NotNull Throwable t) {
- t.printStackTrace();
- log.error(t.getMessage());
- throw new RuntimeException(t);
- }
- }, MoreExecutors.directExecutor());
- }
- } else {
- log.error("depositTx == null. That must not happen.");
- }
- }
+// private void setupConfidenceListener() {
+// if (getDepositTx() != null) {
+// TransactionConfidence transactionConfidence = getDepositTx().getConfidence();
+// if (transactionConfidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
+// setConfirmedState();
+// } else {
+// ListenableFuture future = transactionConfidence.getDepthFuture(1);
+// Futures.addCallback(future, new FutureCallback<>() {
+// @Override
+// public void onSuccess(TransactionConfidence result) {
+// setConfirmedState();
+// }
+//
+// @Override
+// public void onFailure(@NotNull Throwable t) {
+// t.printStackTrace();
+// log.error(t.getMessage());
+// throw new RuntimeException(t);
+// }
+// }, MoreExecutors.directExecutor());
+// }
+// } else {
+// log.error("depositTx == null. That must not happen.");
+// }
+// }
private void setConfirmedState() {
// we only apply the state if we are not already further in the process
@@ -1108,7 +1228,7 @@ public abstract class Trade implements Tradable, Model {
// As setState is called here from the trade itself we cannot trigger a requestPersistence call.
// But as we get setupConfidenceListener called at startup anyway there is no issue if it would not be
// persisted in case the shutdown routine did not persist the trade.
- setState(State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN);
+ setState(State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN); // TODO (woodser): for xmr this means deposit txs have unlocked after 10 confirmations
}
}
@@ -1116,17 +1236,15 @@ public abstract class Trade implements Tradable, Model {
public String toString() {
return "Trade{" +
"\n offer=" + offer +
- ",\n isCurrencyForTakerFeeBtc=" + isCurrencyForTakerFeeBtc +
",\n txFeeAsLong=" + txFeeAsLong +
",\n takerFeeAsLong=" + takerFeeAsLong +
",\n takeOfferDate=" + takeOfferDate +
",\n processModel=" + processModel +
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
- ",\n depositTxId='" + depositTxId + '\'' +
+ ",\n takerDepositTxId='" + takerDepositTxId + '\'' +
",\n payoutTxId='" + payoutTxId + '\'' +
",\n tradeAmountAsLong=" + tradeAmountAsLong +
",\n tradePrice=" + tradePrice +
- ",\n tradingPeerNodeAddress=" + tradingPeerNodeAddress +
",\n state=" + state +
",\n disputeState=" + disputeState +
",\n tradePeriodState=" + tradePeriodState +
@@ -1135,9 +1253,7 @@ public abstract class Trade implements Tradable, Model {
",\n contractHash=" + Utilities.bytesAsHexString(contractHash) +
",\n takerContractSignature='" + takerContractSignature + '\'' +
",\n makerContractSignature='" + makerContractSignature + '\'' +
- ",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) +
- ",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' +
@@ -1148,13 +1264,13 @@ public abstract class Trade implements Tradable, Model {
",\n chatMessages=" + chatMessages +
",\n txFee=" + txFee +
",\n takerFee=" + takerFee +
- ",\n btcWalletService=" + btcWalletService +
+ ",\n xmrWalletService=" + xmrWalletService +
",\n stateProperty=" + stateProperty +
",\n statePhaseProperty=" + statePhaseProperty +
",\n disputeStateProperty=" + disputeStateProperty +
",\n tradePeriodStateProperty=" + tradePeriodStateProperty +
",\n errorMessageProperty=" + errorMessageProperty +
- ",\n depositTx=" + depositTx +
+ ",\n depositTx=" + takerDepositTx +
",\n delayedPayoutTx=" + delayedPayoutTx +
",\n payoutTx=" + payoutTx +
",\n tradeAmount=" + tradeAmount +
@@ -1168,6 +1284,12 @@ public abstract class Trade implements Tradable, Model {
",\n refundAgentPubKeyRing=" + refundAgentPubKeyRing +
",\n refundResultState=" + refundResultState +
",\n refundResultStateProperty=" + refundResultStateProperty +
+ ",\n makerNodeAddress=" + makerNodeAddress +
+ ",\n makerPubKeyRing=" + makerPubKeyRing +
+ ",\n takerNodeAddress=" + takerNodeAddress +
+ ",\n takerPubKeyRing=" + takerPubKeyRing +
+ ",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
+ ",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing +
"\n}";
}
}
diff --git a/core/src/main/java/bisq/core/trade/TradeDataValidation.java b/core/src/main/java/bisq/core/trade/TradeDataValidation.java
index 013dcdb42a..829c5e2c01 100644
--- a/core/src/main/java/bisq/core/trade/TradeDataValidation.java
+++ b/core/src/main/java/bisq/core/trade/TradeDataValidation.java
@@ -356,24 +356,25 @@ public class TradeDataValidation {
}
public static void validateDepositInputs(Trade trade) throws InvalidTxException {
- // assumption: deposit tx always has 2 inputs, the maker and taker
- if (trade == null || trade.getDepositTx() == null || trade.getDepositTx().getInputs().size() != 2) {
- throw new InvalidTxException("Deposit transaction is null or has unexpected input count");
- }
- Transaction depositTx = trade.getDepositTx();
- String txIdInput0 = depositTx.getInput(0).getOutpoint().getHash().toString();
- String txIdInput1 = depositTx.getInput(1).getOutpoint().getHash().toString();
- String contractMakerTxId = trade.getContract().getOfferPayload().getOfferFeePaymentTxId();
- String contractTakerTxId = trade.getContract().getTakerFeeTxID();
- boolean makerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput0) && contractTakerTxId.equalsIgnoreCase(txIdInput1);
- boolean takerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput1) && contractTakerTxId.equalsIgnoreCase(txIdInput0);
- if (!makerFirstMatch && !takerFirstMatch) {
- String errMsg = "Maker/Taker txId in contract does not match deposit tx input";
- log.error(errMsg +
- "\nContract Maker tx=" + contractMakerTxId + " Contract Taker tx=" + contractTakerTxId +
- "\nDeposit Input0=" + txIdInput0 + " Deposit Input1=" + txIdInput1);
- throw new InvalidTxException(errMsg);
- }
+ throw new RuntimeException("TradeDataValidation.validateDepositInputs() needs updated for XMR");
+// // assumption: deposit tx always has 2 inputs, the maker and taker
+// if (trade == null || trade.getDepositTx() == null || trade.getDepositTx().getInputs().size() != 2) {
+// throw new InvalidTxException("Deposit transaction is null or has unexpected input count");
+// }
+// Transaction depositTx = trade.getDepositTx();
+// String txIdInput0 = depositTx.getInput(0).getOutpoint().getHash().toString();
+// String txIdInput1 = depositTx.getInput(1).getOutpoint().getHash().toString();
+// String contractMakerTxId = trade.getContract().getOfferPayload().getOfferFeePaymentTxId();
+// String contractTakerTxId = trade.getContract().getTakerFeeTxID();
+// boolean makerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput0) && contractTakerTxId.equalsIgnoreCase(txIdInput1);
+// boolean takerFirstMatch = contractMakerTxId.equalsIgnoreCase(txIdInput1) && contractTakerTxId.equalsIgnoreCase(txIdInput0);
+// if (!makerFirstMatch && !takerFirstMatch) {
+// String errMsg = "Maker/Taker txId in contract does not match deposit tx input";
+// log.error(errMsg +
+// "\nContract Maker tx=" + contractMakerTxId + " Contract Taker tx=" + contractTakerTxId +
+// "\nDeposit Input0=" + txIdInput0 + " Deposit Input1=" + txIdInput1);
+// throw new InvalidTxException(errMsg);
+// }
}
diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java
index 6707fde8ea..e3ac16e511 100644
--- a/core/src/main/java/bisq/core/trade/TradeManager.java
+++ b/core/src/main/java/bisq/core/trade/TradeManager.java
@@ -18,11 +18,12 @@
package bisq.core.trade;
import bisq.core.btc.exceptions.AddressEntryException;
-import bisq.core.btc.model.AddressEntry;
+import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.BsqWalletService;
-import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
+import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
@@ -33,7 +34,14 @@ import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.handlers.TradeResultHandler;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.messages.InitMultisigMessage;
+import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.InputsForDepositTxRequest;
+import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
+import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
+import bisq.core.trade.messages.UpdateMultisigRequest;
+import bisq.core.trade.protocol.ArbitratorProtocol;
import bisq.core.trade.protocol.MakerProtocol;
import bisq.core.trade.protocol.ProcessModel;
import bisq.core.trade.protocol.ProcessModelServiceProvider;
@@ -65,8 +73,6 @@ import bisq.common.proto.persistable.PersistedDataHost;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
-import org.bitcoinj.core.Transaction;
-import org.bitcoinj.core.TransactionConfidence;
import javax.inject.Inject;
import javax.inject.Named;
@@ -107,14 +113,19 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+
+
+import monero.wallet.model.MoneroTxWallet;
+
public class TradeManager implements PersistedDataHost, DecryptedDirectMessageListener {
private static final Logger log = LoggerFactory.getLogger(TradeManager.class);
private final User user;
@Getter
private final KeyRing keyRing;
- private final BtcWalletService btcWalletService;
+ private final XmrWalletService xmrWalletService;
private final BsqWalletService bsqWalletService;
+ private final OfferBookService offerBookService;
private final OpenOfferManager openOfferManager;
private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager;
@@ -151,8 +162,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
@Inject
public TradeManager(User user,
KeyRing keyRing,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
BsqWalletService bsqWalletService,
+ OfferBookService offerBookService,
OpenOfferManager openOfferManager,
ClosedTradableManager closedTradableManager,
FailedTradesManager failedTradesManager,
@@ -170,8 +182,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
@Named(Config.ALLOW_FAULTY_DELAYED_TXS) boolean allowFaultyDelayedTxs) {
this.user = user;
this.keyRing = keyRing;
- this.btcWalletService = btcWalletService;
+ this.xmrWalletService = xmrWalletService;
this.bsqWalletService = bsqWalletService;
+ this.offerBookService = offerBookService;
this.openOfferManager = openOfferManager;
this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager;
@@ -222,75 +235,22 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
public void onDirectMessage(DecryptedMessageWithPubKey message, NodeAddress peer) {
NetworkEnvelope networkEnvelope = message.getNetworkEnvelope();
if (networkEnvelope instanceof InputsForDepositTxRequest) {
- handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope);
+ //handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); // ignore bisq requests
+ } else if (networkEnvelope instanceof InitTradeRequest) {
+ handleInitTradeRequest((InitTradeRequest) networkEnvelope, peer);
+ } else if (networkEnvelope instanceof InitMultisigMessage) {
+ handleMultisigMessage((InitMultisigMessage) networkEnvelope, peer);
+ } else if (networkEnvelope instanceof MakerReadyToFundMultisigRequest) {
+ handleMakerReadyToFundMultisigRequest((MakerReadyToFundMultisigRequest) networkEnvelope, peer);
+ } else if (networkEnvelope instanceof MakerReadyToFundMultisigResponse) {
+ handleMakerReadyToFundMultisigResponse((MakerReadyToFundMultisigResponse) networkEnvelope, peer);
+ } else if (networkEnvelope instanceof DepositTxMessage) {
+ handleDepositTxMessage((DepositTxMessage) networkEnvelope, peer);
+ } else if (networkEnvelope instanceof UpdateMultisigRequest) {
+ handleUpdateMultisigRequest((UpdateMultisigRequest) networkEnvelope, peer);
}
}
- // The maker received a TakeOfferRequest
- private void handleTakeOfferRequest(NodeAddress peer, InputsForDepositTxRequest inputsForDepositTxRequest) {
- log.info("Received inputsForDepositTxRequest from {} with tradeId {} and uid {}",
- peer, inputsForDepositTxRequest.getTradeId(), inputsForDepositTxRequest.getUid());
-
- try {
- Validator.nonEmptyStringOf(inputsForDepositTxRequest.getTradeId());
- } catch (Throwable t) {
- log.warn("Invalid inputsForDepositTxRequest " + inputsForDepositTxRequest.toString());
- return;
- }
-
- Optional openOfferOptional = openOfferManager.getOpenOfferById(inputsForDepositTxRequest.getTradeId());
- if (!openOfferOptional.isPresent()) {
- return;
- }
-
- OpenOffer openOffer = openOfferOptional.get();
- if (openOffer.getState() != OpenOffer.State.AVAILABLE) {
- return;
- }
-
- Offer offer = openOffer.getOffer();
- openOfferManager.reserveOpenOffer(openOffer);
- Trade trade;
- if (offer.isBuyOffer()) {
- trade = new BuyerAsMakerTrade(offer,
- Coin.valueOf(inputsForDepositTxRequest.getTxFee()),
- Coin.valueOf(inputsForDepositTxRequest.getTakerFee()),
- inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(),
- openOffer.getArbitratorNodeAddress(),
- openOffer.getMediatorNodeAddress(),
- openOffer.getRefundAgentNodeAddress(),
- btcWalletService,
- getNewProcessModel(offer),
- UUID.randomUUID().toString());
- } else {
- trade = new SellerAsMakerTrade(offer,
- Coin.valueOf(inputsForDepositTxRequest.getTxFee()),
- Coin.valueOf(inputsForDepositTxRequest.getTakerFee()),
- inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(),
- openOffer.getArbitratorNodeAddress(),
- openOffer.getMediatorNodeAddress(),
- openOffer.getRefundAgentNodeAddress(),
- btcWalletService,
- getNewProcessModel(offer),
- UUID.randomUUID().toString());
- }
- TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
- TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
- if (prev != null) {
- log.error("We had already an entry with uid {}", trade.getUid());
- }
-
- tradableList.add(trade);
- initTradeAndProtocol(trade, tradeProtocol);
-
- ((MakerProtocol) tradeProtocol).handleTakeOfferRequest(inputsForDepositTxRequest, peer, errorMessage -> {
- if (takeOfferRequestErrorMessageHandler != null)
- takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
- });
-
- requestPersistence();
- }
-
///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle
@@ -311,11 +271,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
getObservableList().addListener((ListChangeListener) change -> onTradesChanged());
onTradesChanged();
- btcWalletService.getAddressEntriesForAvailableBalanceStream()
+ xmrWalletService.getAddressEntriesForAvailableBalanceStream()
.filter(addressEntry -> addressEntry.getOfferId() != null)
.forEach(addressEntry -> {
log.warn("Swapping pending OFFER_FUNDING entries at startup. offerId={}", addressEntry.getOfferId());
- btcWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), AddressEntry.Context.OFFER_FUNDING);
+ xmrWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), XmrAddressEntry.Context.OFFER_FUNDING);
});
}
@@ -367,6 +327,220 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
persistenceManager.requestPersistence();
}
+ private void handleInitTradeRequest(InitTradeRequest initTradeRequest, NodeAddress peer) {
+ log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", peer, initTradeRequest.getTradeId(), initTradeRequest.getUid());
+
+ try {
+ Validator.nonEmptyStringOf(initTradeRequest.getTradeId());
+ } catch (Throwable t) {
+ log.warn("Invalid InitTradeRequest message " + initTradeRequest.toString());
+ return;
+ }
+
+ System.out.println("RECEIVED INIT REQUEST INFO");
+ System.out.println("Sender peer node address: " + initTradeRequest.getSenderNodeAddress());
+ System.out.println("Maker node address: " + initTradeRequest.getMakerNodeAddress());
+ System.out.println("Taker node adddress: " + initTradeRequest.getTakerNodeAddress());
+ System.out.println("Arbitrator node address: " + initTradeRequest.getArbitratorNodeAddress());
+
+ // handle request as arbitrator
+ boolean isArbitrator = initTradeRequest.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress());
+ if (isArbitrator) {
+
+ // get offer associated with trade
+ Offer offer = null;
+ for (Offer anOffer : offerBookService.getOffers()) {
+ if (anOffer.getId().equals(initTradeRequest.getTradeId())) {
+ offer = anOffer;
+ }
+ }
+ if (offer == null) throw new RuntimeException("No offer on the books with trade id: " + initTradeRequest.getTradeId()); // TODO (woodser): proper error handling
+
+ Trade trade;
+ Optional tradeOptional = getTradeById(offer.getId());
+ if (!tradeOptional.isPresent()) {
+ trade = new ArbitratorTrade(offer,
+ Coin.valueOf(initTradeRequest.getTradeAmount()),
+ Coin.valueOf(initTradeRequest.getTxFee()),
+ Coin.valueOf(initTradeRequest.getTradeFee()),
+ initTradeRequest.getTradePrice(),
+ initTradeRequest.getMakerNodeAddress(),
+ initTradeRequest.getTakerNodeAddress(),
+ initTradeRequest.getArbitratorNodeAddress(),
+ xmrWalletService,
+ getNewProcessModel(offer),
+ UUID.randomUUID().toString());
+ initTradeAndProtocol(trade, getTradeProtocol(trade));
+ tradableList.add(trade);
+ } else {
+ trade = tradeOptional.get();
+ }
+
+ // TODO (woodser): do this for arbitrator?
+// TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
+// TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
+// if (prev != null) {
+// log.error("We had already an entry with uid {}", trade.getUid());
+// }
+
+ ((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> {
+ if (takeOfferRequestErrorMessageHandler != null)
+ takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler?
+ });
+
+ requestPersistence();
+ }
+
+ // handle request as maker
+ else {
+
+ Optional openOfferOptional = openOfferManager.getOpenOfferById(initTradeRequest.getTradeId());
+ if (!openOfferOptional.isPresent()) {
+ return;
+ }
+
+ OpenOffer openOffer = openOfferOptional.get();
+ if (openOffer.getState() != OpenOffer.State.AVAILABLE) {
+ return;
+ }
+
+ Offer offer = openOffer.getOffer();
+ openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator?
+
+ Trade trade;
+ if (offer.isBuyOffer())
+ trade = new BuyerAsMakerTrade(offer,
+ Coin.valueOf(initTradeRequest.getTxFee()),
+ Coin.valueOf(initTradeRequest.getTradeFee()),
+ initTradeRequest.getMakerNodeAddress(),
+ initTradeRequest.getTakerNodeAddress(),
+ initTradeRequest.getArbitratorNodeAddress(),
+ xmrWalletService,
+ getNewProcessModel(offer),
+ UUID.randomUUID().toString());
+ else
+ trade = new SellerAsMakerTrade(offer,
+ Coin.valueOf(initTradeRequest.getTxFee()),
+ Coin.valueOf(initTradeRequest.getTradeFee()),
+ initTradeRequest.getMakerNodeAddress(),
+ initTradeRequest.getTakerNodeAddress(),
+ openOffer.getArbitratorNodeAddress(),
+ xmrWalletService,
+ getNewProcessModel(offer),
+ UUID.randomUUID().toString());
+
+ TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
+ TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
+ if (prev != null) {
+ log.error("We had already an entry with uid {}", trade.getUid());
+ }
+
+ tradableList.add(trade);
+ initTradeAndProtocol(trade, tradeProtocol);
+
+ ((MakerProtocol) tradeProtocol).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> {
+ if (takeOfferRequestErrorMessageHandler != null)
+ takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
+ });
+
+ requestPersistence();
+ }
+ }
+
+ private void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest request, NodeAddress peer) {
+ log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
+
+ try {
+ Validator.nonEmptyStringOf(request.getTradeId());
+ } catch (Throwable t) {
+ log.warn("Invalid InitTradeRequest message " + request.toString());
+ return;
+ }
+
+ Optional tradeOptional = getTradeById(request.getTradeId());
+ if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
+ Trade trade = tradeOptional.get();
+ ((MakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigRequest(request, peer, errorMessage -> {
+ if (takeOfferRequestErrorMessageHandler != null)
+ takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
+ });
+ }
+
+ private void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer) {
+ log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid());
+
+ try {
+ Validator.nonEmptyStringOf(response.getTradeId());
+ } catch (Throwable t) {
+ log.warn("Invalid InitTradeRequest message " + response.toString());
+ return;
+ }
+
+ Optional tradeOptional = getTradeById(response.getTradeId());
+ if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling
+ Trade trade = tradeOptional.get();
+ ((TakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigResponse(response, peer, errorMessage -> {
+ if (takeOfferRequestErrorMessageHandler != null)
+ takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
+ });
+ }
+
+ private void handleMultisigMessage(InitMultisigMessage multisigMessage, NodeAddress peer) {
+ log.info("Received InitMultisigMessage from {} with tradeId {} and uid {}", peer, multisigMessage.getTradeId(), multisigMessage.getUid());
+
+ try {
+ Validator.nonEmptyStringOf(multisigMessage.getTradeId());
+ } catch (Throwable t) {
+ log.warn("Invalid InitMultisigMessage message " + multisigMessage.toString());
+ return;
+ }
+
+ Optional tradeOptional = getTradeById(multisigMessage.getTradeId());
+ if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + multisigMessage.getTradeId()); // TODO (woodser): error handling
+ Trade trade = tradeOptional.get();
+ getTradeProtocol(trade).handleMultisigMessage(multisigMessage, peer, errorMessage -> {
+ if (takeOfferRequestErrorMessageHandler != null)
+ takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
+ });
+ }
+
+ private void handleUpdateMultisigRequest(UpdateMultisigRequest request, NodeAddress peer) {
+ log.info("Received UpdateMultisigRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
+
+ try {
+ Validator.nonEmptyStringOf(request.getTradeId());
+ } catch (Throwable t) {
+ log.warn("Invalid UpdateMultisigRequest message " + request.toString());
+ return;
+ }
+
+ Optional tradeOptional = getTradeById(request.getTradeId());
+ if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
+ Trade trade = tradeOptional.get();
+ getTradeProtocol(trade).handleUpdateMultisigRequest(request, peer, errorMessage -> {
+ if (takeOfferRequestErrorMessageHandler != null)
+ takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
+ });
+ }
+
+ private void handleDepositTxMessage(DepositTxMessage request, NodeAddress peer) {
+ log.info("Received DepositTxMessage from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
+
+ try {
+ Validator.nonEmptyStringOf(request.getTradeId());
+ } catch (Throwable t) {
+ log.warn("Invalid InitTradeRequest message " + request.toString());
+ return;
+ }
+
+ Optional tradeOptional = getTradeById(request.getTradeId());
+ if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
+ Trade trade = tradeOptional.get();
+ getTradeProtocol(trade).handleDepositTxMessage(request, peer, errorMessage -> {
+ if (takeOfferRequestErrorMessageHandler != null)
+ takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
+ });
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// Take offer
@@ -376,14 +550,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
boolean isTakerApiUser,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
- if (btcWalletService.isUnconfirmedTransactionsLimitHit() ||
- bsqWalletService.isUnconfirmedTransactionsLimitHit()) {
- String errorMessage = Res.get("shared.unconfirmedTransactionsLimitReached");
- errorMessageHandler.handleErrorMessage(errorMessage);
- log.warn(errorMessage);
- return;
- }
-
offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser), resultHandler, errorMessageHandler);
}
@@ -391,7 +557,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
public void onTakeOffer(Coin amount,
Coin txFee,
Coin takerFee,
- boolean isCurrencyForTakerFeeBtc,
long tradePrice,
Coin fundsNeededForTrade,
Offer offer,
@@ -413,13 +578,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
amount,
txFee,
takerFee,
- isCurrencyForTakerFeeBtc,
tradePrice,
model.getPeerNodeAddress(),
- model.getSelectedArbitrator(),
- model.getSelectedMediator(),
- model.getSelectedRefundAgent(),
- btcWalletService,
+ P2PService.getMyNodeAddress(), // TODO (woodser): correct taker node address?
+ model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront
+ xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
} else {
@@ -427,13 +590,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
amount,
txFee,
takerFee,
- isCurrencyForTakerFeeBtc,
tradePrice,
model.getPeerNodeAddress(),
- model.getSelectedArbitrator(),
- model.getSelectedMediator(),
- model.getSelectedRefundAgent(),
- btcWalletService,
+ P2PService.getMyNodeAddress(),
+ model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront
+ xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
}
@@ -483,43 +644,42 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
///////////////////////////////////////////////////////////////////////////////////////////
public void onWithdrawRequest(String toAddress,
- Coin amount,
- Coin fee,
- KeyParameter aesKey,
- Trade trade,
- @Nullable String memo,
- ResultHandler resultHandler,
- FaultHandler faultHandler) {
- String fromAddress = btcWalletService.getOrCreateAddressEntry(trade.getId(),
- AddressEntry.Context.TRADE_PAYOUT).getAddressString();
- FutureCallback callback = new FutureCallback<>() {
- @Override
- public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
- if (transaction != null) {
- log.debug("onWithdraw onSuccess tx ID:" + transaction.getTxId().toString());
- onTradeCompleted(trade);
- trade.setState(Trade.State.WITHDRAW_COMPLETED);
- getTradeProtocol(trade).onWithdrawCompleted();
- requestPersistence();
- resultHandler.handleResult();
- }
- }
-
- @Override
- public void onFailure(@NotNull Throwable t) {
- t.printStackTrace();
- log.error(t.getMessage());
- faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t);
- }
- };
- try {
- btcWalletService.sendFunds(fromAddress, toAddress, amount, fee, aesKey,
- AddressEntry.Context.TRADE_PAYOUT, memo, callback);
- } catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) {
- e.printStackTrace();
- log.error(e.getMessage());
- faultHandler.handleFault("An exception occurred at requestWithdraw.", e);
+ Coin amount,
+ Coin fee,
+ KeyParameter aesKey,
+ Trade trade,
+ @Nullable String memo,
+ ResultHandler resultHandler,
+ FaultHandler faultHandler) {
+ int fromAccountIdx = xmrWalletService.getOrCreateAddressEntry(trade.getId(),
+ XmrAddressEntry.Context.TRADE_PAYOUT).getAccountIndex();
+ FutureCallback callback = new FutureCallback() {
+ @Override
+ public void onSuccess(@javax.annotation.Nullable MoneroTxWallet transaction) {
+ if (transaction != null) {
+ log.debug("onWithdraw onSuccess tx ID:" + transaction.getHash());
+ onTradeCompleted(trade);
+ trade.setState(Trade.State.WITHDRAW_COMPLETED);
+ getTradeProtocol(trade).onWithdrawCompleted();
+ requestPersistence();
+ resultHandler.handleResult();
+ }
}
+
+ @Override
+ public void onFailure(@NotNull Throwable t) {
+ t.printStackTrace();
+ log.error(t.getMessage());
+ faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t);
+ }
+ };
+ try {
+ xmrWalletService.sendFunds(fromAccountIdx, toAddress, amount, XmrAddressEntry.Context.TRADE_PAYOUT, callback);
+ } catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) {
+ e.printStackTrace();
+ log.error(e.getMessage());
+ faultHandler.handleFault("An exception occurred at requestWithdraw.", e);
+ }
}
// If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades
@@ -528,7 +688,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
closedTradableManager.add(trade);
// TODO The address entry should have been removed already. Check and if its the case remove that.
- btcWalletService.resetAddressEntriesForPendingTrade(trade.getId());
+ xmrWalletService.resetAddressEntriesForPendingTrade(trade.getId());
requestPersistence();
}
@@ -543,12 +703,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
Trade trade = tradeOptional.get();
trade.setDisputeState(disputeState);
onTradeCompleted(trade);
- btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT);
+ xmrWalletService.swapTradeEntryToAvailableEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT);
requestPersistence();
}
}
-
///////////////////////////////////////////////////////////////////////////////////////////
// Trade period state
///////////////////////////////////////////////////////////////////////////////////////////
@@ -616,7 +775,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
.map(Trade::getId)
.collect(Collectors.toSet());
tradesIdSet.addAll(failedTradesManager.getTradesStreamWithFundsLockedIn()
- .filter(trade -> trade.getDepositTx() != null)
+ .filter(trade -> trade.getMakerDepositTx() != null || trade.getTakerDepositTx() != null)
.map(trade -> {
log.warn("We found a failed trade with locked up funds. " +
"That should never happen. trade ID=" + trade.getId());
@@ -625,19 +784,30 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
.collect(Collectors.toSet()));
tradesIdSet.addAll(closedTradableManager.getTradesStreamWithFundsLockedIn()
.map(trade -> {
- Transaction depositTx = trade.getDepositTx();
- if (depositTx != null) {
- TransactionConfidence confidence = btcWalletService.getConfidenceForTxId(depositTx.getTxId().toString());
- if (confidence != null && confidence.getConfidenceType() != TransactionConfidence.ConfidenceType.BUILDING) {
- tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId())));
- } else {
- log.warn("We found a closed trade with locked up funds. " +
- "That should never happen. trade ID=" + trade.getId());
- }
- } else {
- tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
- }
- return trade.getId();
+ MoneroTxWallet makerDepositTx = trade.getMakerDepositTx();
+ if (makerDepositTx != null) {
+ if (makerDepositTx.isLocked()) {
+ tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId()))); // TODO (woodser): rename to closedTradeWithLockedDepositTx
+ } else {
+ log.warn("We found a closed trade with locked up funds. " +
+ "That should never happen. trade ID=" + trade.getId());
+ }
+ } else {
+ tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
+ }
+
+ MoneroTxWallet takerDepositTx = trade.getTakerDepositTx();
+ if (takerDepositTx != null) {
+ if (!takerDepositTx.isConfirmed()) {
+ tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId())));
+ } else {
+ log.warn("We found a closed trade with locked up funds. " +
+ "That should never happen. trade ID=" + trade.getId());
+ }
+ } else {
+ tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
+ }
+ return trade.getId();
})
.collect(Collectors.toSet()));
@@ -671,10 +841,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
if (entries == null)
return false;
- btcWalletService.recoverAddressEntry(trade.getId(), entries.first,
- AddressEntry.Context.MULTI_SIG);
- btcWalletService.recoverAddressEntry(trade.getId(), entries.second,
- AddressEntry.Context.TRADE_PAYOUT);
+ xmrWalletService.recoverAddressEntry(trade.getId(), entries.first,
+ XmrAddressEntry.Context.MULTI_SIG);
+ xmrWalletService.recoverAddressEntry(trade.getId(), entries.second,
+ XmrAddressEntry.Context.TRADE_PAYOUT);
return true;
}
diff --git a/core/src/main/java/bisq/core/trade/TradeUtil.java b/core/src/main/java/bisq/core/trade/TradeUtil.java
index a026f6ab98..a7d779e770 100644
--- a/core/src/main/java/bisq/core/trade/TradeUtil.java
+++ b/core/src/main/java/bisq/core/trade/TradeUtil.java
@@ -86,38 +86,39 @@ public class TradeUtil {
* @return Tuple2 tuple containing MULTI_SIG, TRADE_PAYOUT addresses for trade
*/
public Tuple2 getTradeAddresses(Trade trade) {
- var contract = trade.getContract();
- if (contract == null)
- return null;
-
- // Get multisig address
- var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing());
- var multiSigPubKey = isMyRoleBuyer
- ? contract.getBuyerMultiSigPubKey()
- : contract.getSellerMultiSigPubKey();
- if (multiSigPubKey == null)
- return null;
-
- var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey);
- var multiSigAddress = btcWalletService.getAddressEntryListAsImmutableList().stream()
- .filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString))
- .findAny()
- .orElse(null);
- if (multiSigAddress == null)
- return null;
-
- // Get payout address
- var payoutAddress = isMyRoleBuyer
- ? contract.getBuyerPayoutAddressString()
- : contract.getSellerPayoutAddressString();
- var payoutAddressEntry = btcWalletService.getAddressEntryListAsImmutableList().stream()
- .filter(e -> Objects.equals(e.getAddressString(), payoutAddress))
- .findAny()
- .orElse(null);
- if (payoutAddressEntry == null)
- return null;
-
- return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress);
+ throw new RuntimeException("TradeUtil.getTradeAddresses() not implemented for XMR");
+// var contract = trade.getContract();
+// if (contract == null)
+// return null;
+//
+// // Get multisig address
+// var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing());
+// var multiSigPubKey = isMyRoleBuyer
+// ? contract.getBuyerMultiSigPubKey()
+// : contract.getSellerMultiSigPubKey();
+// if (multiSigPubKey == null)
+// return null;
+//
+// var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey);
+// var multiSigAddress = btcWalletService.getAddressEntryListAsImmutableList().stream()
+// .filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString))
+// .findAny()
+// .orElse(null);
+// if (multiSigAddress == null)
+// return null;
+//
+// // Get payout address
+// var payoutAddress = isMyRoleBuyer
+// ? contract.getBuyerPayoutAddressString()
+// : contract.getSellerPayoutAddressString();
+// var payoutAddressEntry = btcWalletService.getAddressEntryListAsImmutableList().stream()
+// .filter(e -> Objects.equals(e.getAddressString(), payoutAddress))
+// .findAny()
+// .orElse(null);
+// if (payoutAddressEntry == null)
+// return null;
+//
+// return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress);
}
public long getRemainingTradeDuration(Trade trade) {
diff --git a/core/src/main/java/bisq/core/trade/TradeUtils.java b/core/src/main/java/bisq/core/trade/TradeUtils.java
new file mode 100644
index 0000000000..2f4da66098
--- /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 84746a2128..51277a221e 100644
--- a/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java
+++ b/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java
@@ -17,8 +17,8 @@
package bisq.core.trade.failed;
-import bisq.core.btc.model.AddressEntry;
-import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.model.XmrAddressEntry;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.DumpDelayedPayoutTx;
@@ -49,7 +49,7 @@ public class FailedTradesManager implements PersistedDataHost {
private final TradableList failedTrades = new TradableList<>();
private final KeyRing keyRing;
private final PriceFeedService priceFeedService;
- private final BtcWalletService btcWalletService;
+ private final XmrWalletService xmrWalletService;
private final CleanupMailboxMessages cleanupMailboxMessages;
private final PersistenceManager> persistenceManager;
private final TradeUtil tradeUtil;
@@ -60,14 +60,14 @@ public class FailedTradesManager implements PersistedDataHost {
@Inject
public FailedTradesManager(KeyRing keyRing,
PriceFeedService priceFeedService,
- BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
PersistenceManager> persistenceManager,
TradeUtil tradeUtil,
CleanupMailboxMessages cleanupMailboxMessages,
DumpDelayedPayoutTx dumpDelayedPayoutTx) {
this.keyRing = keyRing;
this.priceFeedService = priceFeedService;
- this.btcWalletService = btcWalletService;
+ this.xmrWalletService = xmrWalletService;
this.cleanupMailboxMessages = cleanupMailboxMessages;
this.dumpDelayedPayoutTx = dumpDelayedPayoutTx;
this.persistenceManager = persistenceManager;
@@ -140,8 +140,8 @@ public class FailedTradesManager implements PersistedDataHost {
return "Addresses not found";
}
StringBuilder blockingTrades = new StringBuilder();
- for (var entry : btcWalletService.getAddressEntryListAsImmutableList()) {
- if (entry.getContext() == AddressEntry.Context.AVAILABLE)
+ for (var entry : xmrWalletService.getAddressEntryListAsImmutableList()) {
+ if (entry.getContext() == XmrAddressEntry.Context.AVAILABLE)
continue;
if (entry.getAddressString() != null &&
(entry.getAddressString().equals(addresses.first) ||
diff --git a/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java
index ea56069cfc..614d56a0eb 100644
--- a/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java
+++ b/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java
@@ -21,9 +21,6 @@ import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import bisq.common.proto.ProtoUtil;
-import bisq.common.util.Utilities;
-
-import com.google.protobuf.ByteString;
import java.util.Optional;
@@ -37,7 +34,7 @@ import javax.annotation.Nullable;
public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMessage {
private final String buyerPayoutAddress;
private final NodeAddress senderNodeAddress;
- private final byte[] buyerSignature;
+ private final String buyerPayoutTxSigned;
@Nullable
private final String counterCurrencyTxId;
@@ -49,14 +46,14 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
public CounterCurrencyTransferStartedMessage(String tradeId,
String buyerPayoutAddress,
NodeAddress senderNodeAddress,
- byte[] buyerSignature,
+ String buyerPayoutTxSigned,
@Nullable String counterCurrencyTxId,
@Nullable String counterCurrencyExtraData,
String uid) {
this(tradeId,
buyerPayoutAddress,
senderNodeAddress,
- buyerSignature,
+ buyerPayoutTxSigned,
counterCurrencyTxId,
counterCurrencyExtraData,
uid,
@@ -71,7 +68,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
private CounterCurrencyTransferStartedMessage(String tradeId,
String buyerPayoutAddress,
NodeAddress senderNodeAddress,
- byte[] buyerSignature,
+ String buyerPayoutTxSigned,
@Nullable String counterCurrencyTxId,
@Nullable String counterCurrencyExtraData,
String uid,
@@ -79,7 +76,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
super(messageVersion, tradeId, uid);
this.buyerPayoutAddress = buyerPayoutAddress;
this.senderNodeAddress = senderNodeAddress;
- this.buyerSignature = buyerSignature;
+ this.buyerPayoutTxSigned = buyerPayoutTxSigned;
this.counterCurrencyTxId = counterCurrencyTxId;
this.counterCurrencyExtraData = counterCurrencyExtraData;
}
@@ -90,7 +87,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
builder.setTradeId(tradeId)
.setBuyerPayoutAddress(buyerPayoutAddress)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
- .setBuyerSignature(ByteString.copyFrom(buyerSignature))
+ .setBuyerPayoutTxSigned(buyerPayoutTxSigned)
.setUid(uid);
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
@@ -104,7 +101,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
return new CounterCurrencyTransferStartedMessage(proto.getTradeId(),
proto.getBuyerPayoutAddress(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
- proto.getBuyerSignature().toByteArray(),
+ proto.getBuyerPayoutTxSigned(),
ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyTxId()),
ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData()),
proto.getUid(),
@@ -120,7 +117,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
",\n counterCurrencyTxId=" + counterCurrencyTxId +
",\n counterCurrencyExtraData=" + counterCurrencyExtraData +
",\n uid='" + uid + '\'' +
- ",\n buyerSignature=" + Utilities.bytesAsHexString(buyerSignature) +
+ ",\n buyerPayoutTxSigned=" + buyerPayoutTxSigned +
"\n} " + super.toString();
}
}
diff --git a/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java b/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java
index 631f1a9ca2..83b84b1047 100644
--- a/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java
+++ b/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java
@@ -21,70 +21,66 @@ import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
-import bisq.common.util.Utilities;
+import bisq.common.proto.ProtoUtil;
-import com.google.protobuf.ByteString;
+import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Value;
+import javax.annotation.Nullable;
+
// It is the last message in the take offer phase. We use MailboxMessage instead of DirectMessage to add more tolerance
// in case of network issues and as the message does not trigger further protocol execution.
@EqualsAndHashCode(callSuper = true)
@Value
public final class DepositTxMessage extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
- private final byte[] depositTxWithoutWitnesses;
+ @Nullable
+ private final String tradeFeeTxId;
+ @Nullable
+ private final String depositTxId;
public DepositTxMessage(String uid,
String tradeId,
NodeAddress senderNodeAddress,
- byte[] depositTxWithoutWitnesses) {
- this(Version.getP2PMessageVersion(),
- uid,
- tradeId,
- senderNodeAddress,
- depositTxWithoutWitnesses);
+ String tradeFeeTxId,
+ String depositTxId) {
+ super(Version.getP2PMessageVersion(), tradeId, uid);
+ this.senderNodeAddress = senderNodeAddress;
+ this.tradeFeeTxId = tradeFeeTxId;
+ this.depositTxId = depositTxId;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
- private DepositTxMessage(int messageVersion,
- String uid,
- String tradeId,
- NodeAddress senderNodeAddress,
- byte[] depositTxWithoutWitnesses) {
- super(messageVersion, tradeId, uid);
- this.senderNodeAddress = senderNodeAddress;
- this.depositTxWithoutWitnesses = depositTxWithoutWitnesses;
- }
-
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
- return getNetworkEnvelopeBuilder()
- .setDepositTxMessage(protobuf.DepositTxMessage.newBuilder()
- .setUid(uid)
- .setTradeId(tradeId)
- .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
- .setDepositTxWithoutWitnesses(ByteString.copyFrom(depositTxWithoutWitnesses)))
- .build();
+ protobuf.DepositTxMessage.Builder builder = protobuf.DepositTxMessage.newBuilder()
+ .setTradeId(tradeId)
+ .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
+ .setUid(uid);
+ Optional.ofNullable(tradeFeeTxId).ifPresent(e -> builder.setTradeFeeTxId(tradeFeeTxId));
+ Optional.ofNullable(depositTxId).ifPresent(e -> builder.setDepositTxId(depositTxId));
+ return getNetworkEnvelopeBuilder().setDepositTxMessage(builder).build();
}
public static DepositTxMessage fromProto(protobuf.DepositTxMessage proto, int messageVersion) {
- return new DepositTxMessage(messageVersion,
- proto.getUid(),
+ return new DepositTxMessage(proto.getUid(),
proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
- proto.getDepositTxWithoutWitnesses().toByteArray());
+ ProtoUtil.stringOrNullFromProto(proto.getTradeFeeTxId()),
+ ProtoUtil.stringOrNullFromProto(proto.getDepositTxId()));
}
@Override
public String toString() {
return "DepositTxMessage{" +
"\n senderNodeAddress=" + senderNodeAddress +
- ",\n depositTxWithoutWitnesses=" + Utilities.bytesAsHexString(depositTxWithoutWitnesses) +
+ ",\n tradeFeeTxId=" + tradeFeeTxId +
+ ",\n depositTxId=" + depositTxId +
"\n} " + super.toString();
}
}
diff --git a/core/src/main/java/bisq/core/trade/messages/InitMultisigMessage.java b/core/src/main/java/bisq/core/trade/messages/InitMultisigMessage.java
new file mode 100644
index 0000000000..a1c3a12300
--- /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 0000000000..c3bce4cc77
--- /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 0000000000..0a2b63d1f2
--- /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 0000000000..a4bc7c4fd5
--- /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 86ed851ba8..c61f0193ef 100644
--- a/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/messages/PayoutTxPublishedMessage.java
@@ -23,9 +23,6 @@ import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import bisq.common.proto.network.NetworkEnvelope;
-import bisq.common.util.Utilities;
-
-import com.google.protobuf.ByteString;
import java.util.Optional;
import java.util.UUID;
@@ -40,7 +37,7 @@ import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true)
@Value
public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
- private final byte[] payoutTx;
+ private final String signedMultisigTxHex;
private final NodeAddress senderNodeAddress;
// Added in v1.4.0
@@ -48,11 +45,11 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
private final SignedWitness signedWitness;
public PayoutTxPublishedMessage(String tradeId,
- byte[] payoutTx,
+ String signedMultisigTxHex,
NodeAddress senderNodeAddress,
@Nullable SignedWitness signedWitness) {
this(tradeId,
- payoutTx,
+ signedMultisigTxHex,
senderNodeAddress,
signedWitness,
UUID.randomUUID().toString(),
@@ -65,13 +62,13 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
///////////////////////////////////////////////////////////////////////////////////////////
private PayoutTxPublishedMessage(String tradeId,
- byte[] payoutTx,
+ String signedMultisigTxHex,
NodeAddress senderNodeAddress,
@Nullable SignedWitness signedWitness,
String uid,
int messageVersion) {
super(messageVersion, tradeId, uid);
- this.payoutTx = payoutTx;
+ this.signedMultisigTxHex = signedMultisigTxHex;
this.senderNodeAddress = senderNodeAddress;
this.signedWitness = signedWitness;
}
@@ -80,7 +77,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.PayoutTxPublishedMessage.Builder builder = protobuf.PayoutTxPublishedMessage.newBuilder()
.setTradeId(tradeId)
- .setPayoutTx(ByteString.copyFrom(payoutTx))
+ .setSignedMultisigTxHex(signedMultisigTxHex)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setUid(uid);
Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness()));
@@ -95,7 +92,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
SignedWitness.fromProto(protoSignedWitness) :
null;
return new PayoutTxPublishedMessage(proto.getTradeId(),
- proto.getPayoutTx().toByteArray(),
+ proto.getSignedMultisigTxHex(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
signedWitness,
proto.getUid(),
@@ -105,7 +102,7 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
@Override
public String toString() {
return "PayoutTxPublishedMessage{" +
- "\n payoutTx=" + Utilities.bytesAsHexString(payoutTx) +
+ "\n signedMultisigTxHex=" + signedMultisigTxHex +
",\n senderNodeAddress=" + senderNodeAddress +
",\n signedWitness=" + signedWitness +
"\n} " + super.toString();
diff --git a/core/src/main/java/bisq/core/trade/messages/UpdateMultisigRequest.java b/core/src/main/java/bisq/core/trade/messages/UpdateMultisigRequest.java
new file mode 100644
index 0000000000..f20d7c9221
--- /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 0000000000..3acfcc5c69
--- /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 0000000000..5bda7e48dc
--- /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 c9280d0e20..f50bd8e40e 100644
--- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java
@@ -21,24 +21,28 @@ import bisq.core.trade.BuyerAsMakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
-import bisq.core.trade.messages.InputsForDepositTxRequest;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.messages.InitTradeRequest;
+import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
+import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
-import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
-import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx;
-import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForDepositTxResponse;
+import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
-import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
+import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest;
+import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse;
+import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener;
+import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
+import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
@@ -63,33 +67,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
// Handle take offer request
///////////////////////////////////////////////////////////////////////////////////////////
- @Override
- public void handleTakeOfferRequest(InputsForDepositTxRequest message,
- NodeAddress peer,
- ErrorMessageHandler errorMessageHandler) {
- expect(phase(Trade.Phase.INIT)
- .with(message)
- .from(peer))
- .setup(tasks(
- MakerProcessesInputsForDepositTxRequest.class,
- ApplyFilter.class,
- VerifyPeersAccountAgeWitness.class,
- getVerifyPeersFeePaymentClass(),
- MakerSetsLockTime.class,
- MakerCreateAndSignContract.class,
- BuyerAsMakerCreatesAndSignsDepositTx.class,
- BuyerSetupDepositTxListener.class,
- BuyerAsMakerSendsInputsForDepositTxResponse.class).
- using(new TradeTaskRunner(trade,
- () -> handleTaskRunnerSuccess(message),
- errorMessage -> {
- errorMessageHandler.handleErrorMessage(errorMessage);
- handleTaskRunnerFault(message, errorMessage);
- }))
- .withTimeout(60))
- .executeTasks();
- }
-
+ // TODO (woodser): remove or ignore any unsupported requests
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages Take offer process
@@ -143,4 +121,91 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
protected Class 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 48631776af..6eecc420ff 100644
--- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java
@@ -23,10 +23,14 @@ import bisq.core.trade.BuyerAsTakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.InputsForDepositTxResponse;
+import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
+import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
@@ -35,27 +39,45 @@ import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureRe
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
-import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx;
-import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
+import bisq.core.trade.protocol.tasks.taker.FundMultisig;
+import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
+import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages;
+import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests;
+import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
+import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
+import bisq.common.Timer;
+import bisq.common.UserThread;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
+import java.math.BigInteger;
+
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
+
+
+import monero.wallet.MoneroWallet;
+import monero.wallet.model.MoneroTxWallet;
+import monero.wallet.model.MoneroWalletListener;
+
+// TODO (woodser): remove unused request handling
@Slf4j
public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol {
+ private ResultHandler takeOfferListener;
+ private Timer initDepositTimer;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@@ -66,6 +88,8 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
Offer offer = checkNotNull(trade.getOffer());
processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
+
+ // TODO (woodser): setup deposit and payout listeners on construction for startup like before rebase?
}
@@ -73,22 +97,20 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
// Take offer
///////////////////////////////////////////////////////////////////////////////////////////
+ // TODO (woodser): this implementation is duplicated with SellerAsTakerProtocol
@Override
public void onTakeOffer() {
- expect(phase(Trade.Phase.INIT)
- .with(TakerEvent.TAKE_OFFER))
- .setup(tasks(
- ApplyFilter.class,
- getVerifyPeersFeePaymentClass(),
- CreateTakerFeeTx.class,
- BuyerAsTakerCreatesDepositTxInputs.class,
- TakerSendInputsForDepositTxRequest.class)
- .withTimeout(60))
- .run(() -> {
- processModel.setTempTradingPeerNodeAddress(trade.getTradingPeerNodeAddress());
- processModel.getTradeManager().requestPersistence();
- })
- .executeTasks();
+ System.out.println("onTakeOffer()");
+
+ expect(phase(Trade.Phase.INIT)
+ .with(TakerEvent.TAKE_OFFER)
+ .from(trade.getTradingPeerNodeAddress()))
+ .setup(tasks(
+ ApplyFilter.class,
+ TakerVerifyMakerFeePayment.class,
+ TakerSendInitTradeRequests.class)
+ .withTimeout(30))
+ .executeTasks();
}
@@ -172,4 +194,190 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
protected Class 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 1fee12e22b..f7fc3ceb3b 100644
--- a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java
@@ -25,13 +25,12 @@ import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.TradeTask;
-import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
+import bisq.core.trade.protocol.tasks.UpdateMultisigWithTradingPeer;
+import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
-import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx;
-import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesFinalDelayedPayoutTx;
import bisq.network.p2p.NodeAddress;
@@ -100,28 +99,28 @@ public abstract class BuyerProtocol extends DisputeProtocol {
// mailbox message but the stored in mailbox case is not expected and the seller would try to send the message again
// in the hope to reach the buyer directly.
protected void handle(DepositTxAndDelayedPayoutTxMessage message, NodeAddress peer) {
- expect(anyPhase(Trade.Phase.TAKER_FEE_PUBLISHED, Trade.Phase.DEPOSIT_PUBLISHED)
- .with(message)
- .from(peer)
- .preCondition(trade.getDepositTx() == null || trade.getDelayedPayoutTx() == null,
- () -> {
- log.warn("We with a DepositTxAndDelayedPayoutTxMessage but we have already processed the deposit and " +
- "delayed payout tx so we ignore the message. This can happen if the ACK message to the peer did not " +
- "arrive and the peer repeats sending us the message. We send another ACK msg.");
- stopTimeout();
- sendAckMessage(message, true, null);
- removeMailboxMessageAfterProcessing(message);
- }))
- .setup(tasks(BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
- BuyerVerifiesFinalDelayedPayoutTx.class)
- .using(new TradeTaskRunner(trade,
- () -> {
- stopTimeout();
- handleTaskRunnerSuccess(message);
- },
- errorMessage -> handleTaskRunnerFault(message, errorMessage))))
- .run(() -> processModel.witnessDebugLog(trade))
- .executeTasks();
+// expect(anyPhase(Trade.Phase.TAKER_FEE_PUBLISHED, Trade.Phase.DEPOSIT_PUBLISHED)
+// .with(message)
+// .from(peer)
+// .preCondition(trade.getDepositTx() == null || trade.getDelayedPayoutTx() == null,
+// () -> {
+// log.warn("We with a DepositTxAndDelayedPayoutTxMessage but we have already processed the deposit and " +
+// "delayed payout tx so we ignore the message. This can happen if the ACK message to the peer did not " +
+// "arrive and the peer repeats sending us the message. We send another ACK msg.");
+// stopTimeout();
+// sendAckMessage(message, true, null);
+// removeMailboxMessageAfterProcessing(message);
+// }))
+// .setup(tasks(BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
+// BuyerVerifiesFinalDelayedPayoutTx.class)
+// .using(new TradeTaskRunner(trade,
+// () -> {
+// stopTimeout();
+// handleTaskRunnerSuccess(message);
+// },
+// errorMessage -> handleTaskRunnerFault(message, errorMessage))))
+// .run(() -> processModel.witnessDebugLog(trade))
+// .executeTasks();
}
///////////////////////////////////////////////////////////////////////////////////////////
@@ -135,7 +134,8 @@ public abstract class BuyerProtocol extends DisputeProtocol {
.preCondition(trade.confirmPermitted()))
.setup(tasks(ApplyFilter.class,
getVerifyPeersFeePaymentClass(),
- BuyerSignPayoutTx.class,
+ UpdateMultisigWithTradingPeer.class,
+ BuyerCreateAndSignPayoutTx.class,
BuyerSetupPayoutTxListener.class,
BuyerSendCounterCurrencyTransferStartedMessage.class)
.using(new TradeTaskRunner(trade,
@@ -147,10 +147,7 @@ public abstract class BuyerProtocol extends DisputeProtocol {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(event, errorMessage);
})))
- .run(() -> {
- trade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED);
- processModel.getTradeManager().requestPersistence();
- })
+ .run(() -> trade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED))
.executeTasks();
}
@@ -159,12 +156,22 @@ public abstract class BuyerProtocol extends DisputeProtocol {
///////////////////////////////////////////////////////////////////////////////////////////
protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) {
+ processModel.setTradeMessage(message);
+ processModel.setTempTradingPeerNodeAddress(peer);
expect(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.PAYOUT_PUBLISHED)
- .with(message)
- .from(peer))
- .setup(tasks(BuyerProcessPayoutTxPublishedMessage.class))
- .executeTasks();
-
+ .with(message)
+ .from(peer))
+ .setup(tasks(
+ getVerifyPeersFeePaymentClass(),
+ BuyerProcessPayoutTxPublishedMessage.class)
+ .using(new TradeTaskRunner(trade,
+ () -> {
+ handleTaskRunnerSuccess(message);
+ },
+ errorMessage -> {
+ handleTaskRunnerFault(message, errorMessage);
+ })))
+ .executeTasks();
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java
index 23807c96f5..d8e0e71721 100644
--- a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java
@@ -24,8 +24,6 @@ import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessPeerPublishedDelayedPayoutTxMessage;
-import bisq.core.trade.protocol.tasks.arbitration.PublishedDelayedPayoutTx;
-import bisq.core.trade.protocol.tasks.arbitration.SendPeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.mediation.BroadcastMediatedPayoutTx;
import bisq.core.trade.protocol.tasks.mediation.FinalizeMediatedPayoutTx;
import bisq.core.trade.protocol.tasks.mediation.ProcessMediatedPayoutSignatureMessage;
@@ -43,7 +41,7 @@ import bisq.common.handlers.ResultHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
-public class DisputeProtocol extends TradeProtocol {
+public abstract class DisputeProtocol extends TradeProtocol {
enum DisputeEvent implements FluentProtocol.Event {
MEDIATION_RESULT_ACCEPTED,
@@ -142,26 +140,26 @@ public class DisputeProtocol extends TradeProtocol {
// Delayed payout tx
///////////////////////////////////////////////////////////////////////////////////////////
- public void onPublishDelayedPayoutTx(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
- DisputeEvent event = DisputeEvent.ARBITRATION_REQUESTED;
- expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED,
- Trade.Phase.FIAT_SENT,
- Trade.Phase.FIAT_RECEIVED)
- .with(event)
- .preCondition(trade.getDelayedPayoutTx() != null))
- .setup(tasks(PublishedDelayedPayoutTx.class,
- SendPeerPublishedDelayedPayoutTxMessage.class)
- .using(new TradeTaskRunner(trade,
- () -> {
- resultHandler.handleResult();
- handleTaskRunnerSuccess(event);
- },
- errorMessage -> {
- errorMessageHandler.handleErrorMessage(errorMessage);
- handleTaskRunnerFault(event, errorMessage);
- })))
- .executeTasks();
- }
+// public void onPublishDelayedPayoutTx(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
+// DisputeEvent event = DisputeEvent.ARBITRATION_REQUESTED;
+// expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED,
+// Trade.Phase.FIAT_SENT,
+// Trade.Phase.FIAT_RECEIVED)
+// .with(event)
+// .preCondition(trade.getDelayedPayoutTx() != null))
+// .setup(tasks(PublishedDelayedPayoutTx.class,
+// SendPeerPublishedDelayedPayoutTxMessage.class)
+// .using(new TradeTaskRunner(trade,
+// () -> {
+// resultHandler.handleResult();
+// handleTaskRunnerSuccess(event);
+// },
+// errorMessage -> {
+// errorMessageHandler.handleErrorMessage(errorMessage);
+// handleTaskRunnerFault(event, errorMessage);
+// })))
+// .executeTasks();
+// }
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java
index 349e677a70..c56750ca17 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 ad0e10d671..5ea0900694 100644
--- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java
+++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java
@@ -33,7 +33,9 @@ import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
+import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.MakerTrade;
+import bisq.core.trade.TakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.messages.TradeMessage;
@@ -69,6 +71,10 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
+
+
+import monero.wallet.model.MoneroTxWallet;
+
// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
// persist them.
@@ -84,9 +90,12 @@ public class ProcessModel implements Model, PersistablePayload {
transient private ProcessModelServiceProvider provider;
transient private TradeManager tradeManager;
transient private Offer offer;
+ @Setter
+ transient private Trade trade;
// Transient/Mutable
- transient private Transaction takeOfferFeeTx;
+ @Getter
+ transient private MoneroTxWallet takeOfferFeeTx;
@Setter
transient private TradeMessage tradeMessage;
@@ -107,12 +116,13 @@ public class ProcessModel implements Model, PersistablePayload {
@Getter
transient private Transaction depositTx;
-
- // Persistable Immutable
- private final TradingPeer tradingPeer;
- private final String offerId;
- private final String accountId;
- private final PubKeyRing pubKeyRing;
+ // Persistable Immutable (private setter only used by PB method)
+ private TradingPeer maker = new TradingPeer();
+ private TradingPeer taker = new TradingPeer();
+ private TradingPeer arbitrator = new TradingPeer();
+ private String offerId;
+ private String accountId;
+ private PubKeyRing pubKeyRing;
// Persistable Mutable
@Nullable
@@ -154,6 +164,36 @@ public class ProcessModel implements Model, PersistablePayload {
@Setter
private long sellerPayoutAmountFromMediation;
+ // Added for XMR integration
+ @Nullable
+ @Getter
+ @Setter
+ private String preparedMultisigHex;
+ @Nullable
+ @Getter
+ @Setter
+ private String madeMultisigHex;
+ @Nullable
+ @Getter
+ @Setter
+ private boolean multisigSetupComplete;
+ @Nullable
+ @Getter
+ @Setter
+ private boolean makerReadyToFundMultisig;
+ @Getter
+ @Setter
+ private boolean multisigDepositInitiated;
+ @Nullable
+ @Setter
+ private String makerPreparedDepositTxId;
+ @Nullable
+ @Setter
+ private String takerPreparedDepositTxId;
+ @Nullable
+ transient private MoneroTxWallet buyerSignedPayoutTx;
+
+
// We want to indicate the user the state of the message delivery of the
// CounterCurrencyTransferStartedMessage. As well we do an automatic re-send in case it was not ACKed yet.
@@ -162,15 +202,17 @@ public class ProcessModel implements Model, PersistablePayload {
private ObjectProperty paymentStartedMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing) {
- this(offerId, accountId, pubKeyRing, new TradingPeer());
+ this(offerId, accountId, pubKeyRing, new TradingPeer(), new TradingPeer(), new TradingPeer());
}
- public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing, TradingPeer tradingPeer) {
+ public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing, TradingPeer arbitrator, TradingPeer maker, TradingPeer taker) {
this.offerId = offerId;
this.accountId = accountId;
this.pubKeyRing = pubKeyRing;
// If tradingPeer was null in persisted data from some error cases we set a new one to not cause nullPointers
- this.tradingPeer = tradingPeer != null ? tradingPeer : new TradingPeer();
+ this.arbitrator = arbitrator != null ? arbitrator : new TradingPeer();
+ this.maker = maker != null ? maker : new TradingPeer();
+ this.taker = taker != null ? taker : new TradingPeer();
}
public void applyTransient(ProcessModelServiceProvider provider,
@@ -189,7 +231,6 @@ public class ProcessModel implements Model, PersistablePayload {
@Override
public protobuf.ProcessModel toProtoMessage() {
protobuf.ProcessModel.Builder builder = protobuf.ProcessModel.newBuilder()
- .setTradingPeer((protobuf.TradingPeer) tradingPeer.toProtoMessage())
.setOfferId(offerId)
.setAccountId(accountId)
.setPubKeyRing(pubKeyRing.toProtoMessage())
@@ -199,24 +240,31 @@ public class ProcessModel implements Model, PersistablePayload {
.setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name())
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation);
-
+ Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage()));
+ Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradingPeer) taker.toProtoMessage()));
+ Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradingPeer) arbitrator.toProtoMessage()));
Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId);
Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature)));
- Optional.ofNullable(preparedDepositTx).ifPresent(e -> builder.setPreparedDepositTx(ByteString.copyFrom(preparedDepositTx)));
- Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(
- ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class)));
+ Optional.ofNullable(makerPreparedDepositTxId).ifPresent(e -> builder.setMakerPreparedDepositTxId(makerPreparedDepositTxId));
+ Optional.ofNullable(takerPreparedDepositTxId).ifPresent(e -> builder.setTakerPreparedDepositTxId(takerPreparedDepositTxId));
+ Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class)));
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey)));
Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage()));
- Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e)));
-
+ Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
+ Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
+ Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete));
+ Optional.ofNullable(makerReadyToFundMultisig).ifPresent(e -> builder.setMakerReadyToFundMultisig(makerReadyToFundMultisig));
+ Optional.ofNullable(multisigDepositInitiated).ifPresent(e -> builder.setMultisigSetupComplete(multisigDepositInitiated));
return builder.build();
}
public static ProcessModel fromProto(protobuf.ProcessModel proto, CoreProtoResolver coreProtoResolver) {
- TradingPeer tradingPeer = TradingPeer.fromProto(proto.getTradingPeer(), coreProtoResolver);
+ TradingPeer arbitrator = TradingPeer.fromProto(proto.getArbitrator(), coreProtoResolver);
+ TradingPeer maker = TradingPeer.fromProto(proto.getMaker(), coreProtoResolver);
+ TradingPeer taker = TradingPeer.fromProto(proto.getTaker(), coreProtoResolver);
PubKeyRing pubKeyRing = PubKeyRing.fromProto(proto.getPubKeyRing());
- ProcessModel processModel = new ProcessModel(proto.getOfferId(), proto.getAccountId(), pubKeyRing, tradingPeer);
+ ProcessModel processModel = new ProcessModel(proto.getOfferId(), proto.getAccountId(), pubKeyRing, arbitrator, maker, taker);
processModel.setChangeOutputValue(proto.getChangeOutputValue());
processModel.setUseSavingsWallet(proto.getUseSavingsWallet());
processModel.setFundsNeededForTradeAsLong(proto.getFundsNeededForTradeAsLong());
@@ -226,7 +274,6 @@ public class ProcessModel implements Model, PersistablePayload {
// nullable
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
- processModel.setPreparedDepositTx(ProtoUtil.byteArrayOrNullFromProto(proto.getPreparedDepositTx()));
List rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ?
null : proto.getRawTransactionInputsList().stream()
.map(RawTransactionInput::fromProto).collect(Collectors.toList());
@@ -235,6 +282,13 @@ public class ProcessModel implements Model, PersistablePayload {
processModel.setMyMultiSigPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getMyMultiSigPubKey()));
processModel.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null);
processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
+ processModel.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
+ processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
+ processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete());
+ processModel.setMakerReadyToFundMultisig(proto.getMakerReadyToFundMultisig());
+ processModel.setMultisigDepositInitiated(proto.getMultisigDepositInitiated());
+ processModel.setMakerPreparedDepositTxId(proto.getMakerPreparedDepositTxId());
+ processModel.setTakerPreparedDepositTxId(proto.getTakerPreparedDepositTxId());
String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState());
MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString);
@@ -252,9 +306,9 @@ public class ProcessModel implements Model, PersistablePayload {
public void onComplete() {
}
- public void setTakeOfferFeeTx(Transaction takeOfferFeeTx) {
+ public void setTakeOfferFeeTx(MoneroTxWallet takeOfferFeeTx) {
this.takeOfferFeeTx = takeOfferFeeTx;
- takeOfferFeeTxId = takeOfferFeeTx.getTxId().toString();
+ takeOfferFeeTxId = takeOfferFeeTx.getHash();
}
@Nullable
@@ -271,14 +325,11 @@ public class ProcessModel implements Model, PersistablePayload {
return Coin.valueOf(fundsNeededForTradeAsLong);
}
- public Transaction resolveTakeOfferFeeTx(Trade trade) {
- if (takeOfferFeeTx == null) {
- if (!trade.isCurrencyForTakerFeeBtc())
- takeOfferFeeTx = getBsqWalletService().getTransaction(takeOfferFeeTxId);
- else
- takeOfferFeeTx = getBtcWalletService().getTransaction(takeOfferFeeTxId);
- }
- return takeOfferFeeTx;
+ public MoneroTxWallet resolveTakeOfferFeeTx(Trade trade) {
+ if (takeOfferFeeTx == null) {
+ takeOfferFeeTx = provider.getXmrWalletService().getWallet().getTx(takeOfferFeeTxId);
+ }
+ return takeOfferFeeTx;
}
public NodeAddress getMyNodeAddress() {
@@ -299,6 +350,21 @@ public class ProcessModel implements Model, PersistablePayload {
}
}
+ public void setTradingPeer(TradingPeer peer) {
+ if (trade == null) throw new RuntimeException("Cannot set trading peer because trade is null");
+ else if (trade instanceof MakerTrade) taker = peer;
+ else if (trade instanceof TakerTrade) maker = peer;
+ else throw new RuntimeException("Must be maker or taker to set trading peer");
+ }
+
+ public TradingPeer getTradingPeer() {
+ if (trade == null) throw new RuntimeException("Cannot get trading peer because trade is null");
+ else if (trade instanceof MakerTrade) return taker;
+ else if (trade instanceof TakerTrade) return maker;
+ else if (trade instanceof ArbitratorTrade) return null;
+ else throw new RuntimeException("Unknown trade type: " + trade.getClass().getName());
+ }
+
void setDepositTxSentAckMessage(AckMessage ackMessage) {
MessageState messageState = ackMessage.isSuccess() ?
MessageState.ACKNOWLEDGED :
@@ -381,4 +447,13 @@ public class ProcessModel implements Model, PersistablePayload {
public DaoFacade getDaoFacade() {
return provider.getDaoFacade();
}
+
+ public void setBuyerSignedPayoutTx(MoneroTxWallet buyerSignedPayoutTx) {
+ this.buyerSignedPayoutTx = buyerSignedPayoutTx;
+ }
+
+ @Nullable
+ public MoneroTxWallet getBuyerSignedPayoutTx() {
+ return buyerSignedPayoutTx;
+ }
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java
index 37600eccaf..1b1fe0cc2b 100644
--- a/core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java
+++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModelServiceProvider.java
@@ -21,6 +21,7 @@ import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.offer.OpenOfferManager;
@@ -44,6 +45,7 @@ public class ProcessModelServiceProvider {
private final OpenOfferManager openOfferManager;
private final P2PService p2PService;
private final BtcWalletService btcWalletService;
+ private final XmrWalletService xmrWalletService;
private final BsqWalletService bsqWalletService;
private final TradeWalletService tradeWalletService;
private final DaoFacade daoFacade;
@@ -61,6 +63,7 @@ public class ProcessModelServiceProvider {
public ProcessModelServiceProvider(OpenOfferManager openOfferManager,
P2PService p2PService,
BtcWalletService btcWalletService,
+ XmrWalletService xmrWalletService,
BsqWalletService bsqWalletService,
TradeWalletService tradeWalletService,
DaoFacade daoFacade,
@@ -73,10 +76,10 @@ public class ProcessModelServiceProvider {
MediatorManager mediatorManager,
RefundAgentManager refundAgentManager,
KeyRing keyRing) {
-
this.openOfferManager = openOfferManager;
this.p2PService = p2PService;
this.btcWalletService = btcWalletService;
+ this.xmrWalletService = xmrWalletService;
this.bsqWalletService = bsqWalletService;
this.tradeWalletService = tradeWalletService;
this.daoFacade = daoFacade;
diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java
index 0cdaad17c0..874a7276fe 100644
--- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java
@@ -21,25 +21,28 @@ package bisq.core.trade.protocol;
import bisq.core.trade.SellerAsMakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
-import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.messages.DepositTxMessage;
-import bisq.core.trade.messages.InputsForDepositTxRequest;
+import bisq.core.trade.messages.InitTradeRequest;
+import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
+import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
+import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
-import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
+import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest;
+import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse;
+import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener;
+import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
-import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
-import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse;
+import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
@@ -60,37 +63,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
}
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Handle take offer request
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- @Override
- public void handleTakeOfferRequest(InputsForDepositTxRequest message,
- NodeAddress peer,
- ErrorMessageHandler errorMessageHandler) {
- expect(phase(Trade.Phase.INIT)
- .with(message)
- .from(peer))
- .setup(tasks(
- MakerProcessesInputsForDepositTxRequest.class,
- ApplyFilter.class,
- VerifyPeersAccountAgeWitness.class,
- getVerifyPeersFeePaymentClass(),
- MakerSetsLockTime.class,
- MakerCreateAndSignContract.class,
- SellerAsMakerCreatesUnsignedDepositTx.class,
- SellerAsMakerSendsInputsForDepositTxResponse.class)
- .using(new TradeTaskRunner(trade,
- () -> handleTaskRunnerSuccess(message),
- errorMessage -> {
- errorMessageHandler.handleErrorMessage(errorMessage);
- handleTaskRunnerFault(message, errorMessage);
- }))
- .withTimeout(60))
- .executeTasks();
- }
-
-
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages Take offer process
///////////////////////////////////////////////////////////////////////////////////////////
@@ -110,12 +82,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
.executeTasks();
}
- // We keep the handler here in as well to make it more transparent which messages we expect
- @Override
- protected void handle(DelayedPayoutTxSignatureResponse message, NodeAddress peer) {
- super.handle(message, peer);
- }
-
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message when buyer has clicked payment started button
@@ -159,4 +125,88 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
protected Class 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 7adf9026c5..de2ee5162c 100644
--- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java
@@ -22,35 +22,56 @@ import bisq.core.offer.Offer;
import bisq.core.trade.SellerAsTakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
-import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.InputsForDepositTxResponse;
+import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
+import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
-import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs;
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
-import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
+import bisq.core.trade.protocol.tasks.taker.FundMultisig;
+import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
+import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages;
+import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests;
+import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
+import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
+import bisq.common.Timer;
+import bisq.common.UserThread;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
+import java.math.BigInteger;
+
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
+
+
+import monero.wallet.MoneroWallet;
+import monero.wallet.model.MoneroTxWallet;
+import monero.wallet.model.MoneroWalletListener;
+
+// TODO (woodser): remove unused request handling
@Slf4j
public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtocol {
+ private ResultHandler takeOfferListener;
+ private Timer initDepositTimer;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@@ -69,17 +90,17 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
@Override
public void onTakeOffer() {
- expect(phase(Trade.Phase.INIT)
- .with(TakerEvent.TAKE_OFFER)
- .from(trade.getTradingPeerNodeAddress()))
- .setup(tasks(
- ApplyFilter.class,
- getVerifyPeersFeePaymentClass(),
- CreateTakerFeeTx.class,
- SellerAsTakerCreatesDepositTxInputs.class,
- TakerSendInputsForDepositTxRequest.class)
- .withTimeout(60))
- .executeTasks();
+ System.out.println("onTakeOffer()");
+
+ expect(phase(Trade.Phase.INIT)
+ .with(TakerEvent.TAKE_OFFER)
+ .from(trade.getTradingPeerNodeAddress()))
+ .setup(tasks(
+ ApplyFilter.class,
+ TakerVerifyMakerFeePayment.class,
+ TakerSendInitTradeRequests.class)
+ .withTimeout(30))
+ .executeTasks();
}
@@ -105,12 +126,6 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
.executeTasks();
}
- // We keep the handler here in as well to make it more transparent which messages we expect
- @Override
- protected void handle(DelayedPayoutTxSignatureResponse message, NodeAddress peer) {
- super.handle(message, peer);
- }
-
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message when buyer has clicked payment started button
@@ -154,4 +169,189 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
protected Class 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 54d010eb7e..64b16177a1 100644
--- a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java
@@ -20,19 +20,12 @@ package bisq.core.trade.protocol;
import bisq.core.trade.SellerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
-import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.TradeTask;
-import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
-import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
-import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
-import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
-import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics;
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
-import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerSignAndPublishPayoutTx;
import bisq.network.p2p.NodeAddress;
@@ -67,31 +60,6 @@ public abstract class SellerProtocol extends DisputeProtocol {
}
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Incoming messages
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- protected void handle(DelayedPayoutTxSignatureResponse message, NodeAddress peer) {
- expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
- .with(message)
- .from(peer))
- .setup(tasks(SellerProcessDelayedPayoutTxSignatureResponse.class,
- SellerFinalizesDelayedPayoutTx.class,
- SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
- SellerPublishesDepositTx.class,
- SellerPublishesTradeStatistics.class))
- .run(() -> {
- // We stop timeout here and don't start a new one as the
- // SellerSendsDepositTxAndDelayedPayoutTxMessage repeats the send the message and has it's own
- // timeout if it never succeeds.
- stopTimeout();
-
- //TODO still needed? If so move to witness domain
- processModel.witnessDebugLog(trade);
- })
- .executeTasks();
- }
-
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message when buyer has clicked payment started button
///////////////////////////////////////////////////////////////////////////////////////////
@@ -132,22 +100,18 @@ public abstract class SellerProtocol extends DisputeProtocol {
.setup(tasks(
ApplyFilter.class,
getVerifyPeersFeePaymentClass(),
- SellerSignAndFinalizePayoutTx.class,
- SellerBroadcastPayoutTx.class,
+ SellerSignAndPublishPayoutTx.class,
+ // SellerSignAndFinalizePayoutTx.class,
+ // SellerBroadcastPayoutTx.class,
SellerSendPayoutTxPublishedMessage.class)
- .using(new TradeTaskRunner(trade,
- () -> {
- resultHandler.handleResult();
- handleTaskRunnerSuccess(event);
- },
- (errorMessage) -> {
- errorMessageHandler.handleErrorMessage(errorMessage);
- handleTaskRunnerFault(event, errorMessage);
- })))
- .run(() -> {
- trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT);
- processModel.getTradeManager().requestPersistence();
- })
+ .using(new TradeTaskRunner(trade, () -> {
+ resultHandler.handleResult();
+ handleTaskRunnerSuccess(event);
+ }, (errorMessage) -> {
+ errorMessageHandler.handleErrorMessage(errorMessage);
+ handleTaskRunnerFault(event, errorMessage);
+ })))
+ .run(() -> trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT))
.executeTasks();
}
@@ -156,12 +120,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer);
- log.info("Received {} from {} with tradeId {} and uid {}",
- message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid());
-
- if (message instanceof DelayedPayoutTxSignatureResponse) {
- handle((DelayedPayoutTxSignatureResponse) message, peer);
- } else if (message instanceof CounterCurrencyTransferStartedMessage) {
+ if (message instanceof CounterCurrencyTransferStartedMessage) {
handle((CounterCurrencyTransferStartedMessage) message, peer);
}
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java
index 249d0ac73b..e506f281cb 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 0000000000..53134ecf9a
--- /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 2135aecec8..6b4bd79465 100644
--- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
@@ -22,7 +22,13 @@ import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.TradeMessage;
+import bisq.core.trade.messages.UpdateMultisigRequest;
+import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
+import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest;
+import bisq.core.util.Validator;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.AckMessageSourceType;
@@ -37,6 +43,7 @@ import bisq.network.p2p.messaging.DecryptedMailboxListener;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.crypto.PubKeyRing;
+import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.proto.network.NetworkEnvelope;
import bisq.common.taskrunner.Task;
@@ -63,6 +70,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
public TradeProtocol(Trade trade) {
this.trade = trade;
this.processModel = trade.getProcessModel();
+ this.processModel.setTrade(trade); // TODO (woodser): added to explicitly set trade circular loop, keep?
}
@@ -113,12 +121,17 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
return;
}
- if (!isPubKeyValid(decryptedMessageWithPubKey)) {
+ if (!isPubKeyValid(decryptedMessageWithPubKey, peer)) {
return;
}
if (networkEnvelope instanceof TradeMessage) {
onTradeMessage((TradeMessage) networkEnvelope, peer);
+
+ // TODO (woodser): better way to register message notifications for trade?
+ if (((TradeMessage) networkEnvelope).getTradeId().equals(processModel.getOfferId())) {
+ trade.onVerifiedTradeMessage((TradeMessage) networkEnvelope, peer);
+ }
} else if (networkEnvelope instanceof AckMessage) {
onAckMessage((AckMessage) networkEnvelope, peer);
}
@@ -131,7 +144,18 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
@Override
public void onMailboxMessageAdded(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress peer) {
- handleMailboxCollection(Collections.singletonList(decryptedMessageWithPubKey));
+ if (!isPubKeyValid(decryptedMessageWithPubKey, peer)) return;
+ handleMailboxCollectionSkipValidation(Collections.singletonList(decryptedMessageWithPubKey));
+ }
+
+ // TODO (woodser): this method only necessary because isPubKeyValid not called with sender argument, so it's validated before
+ private void handleMailboxCollectionSkipValidation(Collection collection) {
+ collection.stream()
+ .map(DecryptedMessageWithPubKey::getNetworkEnvelope)
+ .filter(this::isMyMessage)
+ .filter(e -> e instanceof MailboxMessage)
+ .map(e -> (MailboxMessage) e)
+ .forEach(this::handleMailboxMessage);
}
private void handleMailboxCollection(Collection collection) {
@@ -182,6 +206,49 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer);
+ public void handleMultisigMessage(InitMultisigMessage message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
+ Validator.checkTradeId(processModel.getOfferId(), message);
+ processModel.setTradeMessage(message);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
+ () -> {
+ stopTimeout();
+ handleTaskRunnerSuccess(message, "handleMultisigMessage");
+ },
+ errorMessage -> {
+ errorMessageHandler.handleErrorMessage(errorMessage);
+ handleTaskRunnerFault(message, errorMessage);
+ });
+ taskRunner.addTasks(
+ ProcessInitMultisigMessage.class
+ );
+ startTimeout(60); // TODO (woodser): what timeout to use? don't hardcode
+ taskRunner.run();
+ }
+
+ public abstract void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
+
+ // TODO (woodser): update to use fluent for consistency
+ public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
+ Validator.checkTradeId(processModel.getOfferId(), message);
+ processModel.setTradeMessage(message);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
+ () -> {
+ stopTimeout();
+ handleTaskRunnerSuccess(message, "handleUpdateMultisigRequest");
+ },
+ errorMessage -> {
+ errorMessageHandler.handleErrorMessage(errorMessage);
+ handleTaskRunnerFault(message, errorMessage);
+ });
+ taskRunner.addTasks(
+ ProcessUpdateMultisigRequest.class
+ );
+ startTimeout(60); // TODO (woodser): what timeout to use? don't hardcode
+ taskRunner.run();
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// FluentProtocol
@@ -244,7 +311,15 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
}
protected void sendAckMessage(TradeMessage message, boolean result, @Nullable String errorMessage) {
- PubKeyRing peersPubKeyRing = processModel.getTradingPeer().getPubKeyRing();
+
+ // If there was an error during offer verification, the tradingPeerNodeAddress of the trade might not be set yet.
+ // We can find the peer's node address in the processModel's tempTradingPeerNodeAddress in that case.
+ NodeAddress peer = trade.getTradingPeerNodeAddress() != null ?
+ trade.getTradingPeerNodeAddress() :
+ processModel.getTempTradingPeerNodeAddress();
+
+ // get destination pub key ring
+ PubKeyRing peersPubKeyRing = getPeersPubKeyRing(peer);
if (peersPubKeyRing == null) {
log.error("We cannot send the ACK message as peersPubKeyRing is null");
return;
@@ -259,11 +334,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
tradeId,
result,
errorMessage);
- // If there was an error during offer verification, the tradingPeerNodeAddress of the trade might not be set yet.
- // We can find the peer's node address in the processModel's tempTradingPeerNodeAddress in that case.
- NodeAddress peer = trade.getTradingPeerNodeAddress() != null ?
- trade.getTradingPeerNodeAddress() :
- processModel.getTempTradingPeerNodeAddress();
+
log.info("Send AckMessage for {} to peer {}. tradeId={}, sourceUid={}",
ackMessage.getSourceMsgClassName(), peer, tradeId, sourceUid);
processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage(
@@ -292,7 +363,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
);
}
-
///////////////////////////////////////////////////////////////////////////////////////////
// Timeout
///////////////////////////////////////////////////////////////////////////////////////////
@@ -343,11 +413,27 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
// Validation
///////////////////////////////////////////////////////////////////////////////////////////
+ private PubKeyRing getPeersPubKeyRing(NodeAddress peer) {
+ if (peer.equals(trade.getArbitratorNodeAddress())) return trade.getArbitratorPubKeyRing();
+ else if (peer.equals(trade.getMakerNodeAddress())) return trade.getMakerPubKeyRing();
+ else if (peer.equals(trade.getTakerNodeAddress())) return trade.getTakerPubKeyRing();
+ else {
+ log.error("Cannot get peer's pub key ring because peer is not maker, taker, or arbitrator");
+ return null;
+ }
+ }
+
private boolean isPubKeyValid(DecryptedMessageWithPubKey message) {
+ MailboxMessage mailboxMessage = (MailboxMessage) message.getNetworkEnvelope();
+ NodeAddress sender = mailboxMessage.getSenderNodeAddress();
+ return isPubKeyValid(message, sender);
+ }
+
+ private boolean isPubKeyValid(DecryptedMessageWithPubKey message, NodeAddress sender) {
// We can only validate the peers pubKey if we have it already. If we are the taker we get it from the offer
// Otherwise it depends on the state of the trade protocol if we have received the peers pubKeyRing already.
- PubKeyRing peersPubKeyRing = processModel.getTradingPeer().getPubKeyRing();
- boolean isValid = true;
+ PubKeyRing peersPubKeyRing = getPeersPubKeyRing(sender);
+ boolean isValid = true; // TODO (woodser): this returns valid=true even if peer's pub key ring is null?
if (peersPubKeyRing != null &&
!message.getSignaturePubKey().equals(peersPubKeyRing.getSignaturePubKey())) {
isValid = false;
@@ -356,7 +442,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
return isValid;
}
-
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java
index fe521e9918..133da6d326 100644
--- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java
+++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocolFactory.java
@@ -17,6 +17,7 @@
package bisq.core.trade.protocol;
+import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.BuyerAsMakerTrade;
import bisq.core.trade.BuyerAsTakerTrade;
import bisq.core.trade.SellerAsMakerTrade;
@@ -33,6 +34,8 @@ public class TradeProtocolFactory {
return new SellerAsMakerProtocol((SellerAsMakerTrade) trade);
} else if (trade instanceof SellerAsTakerTrade) {
return new SellerAsTakerProtocol((SellerAsTakerTrade) trade);
+ } else if (trade instanceof ArbitratorTrade) {
+ return new ArbitratorProtocol((ArbitratorTrade) trade);
} else {
throw new IllegalStateException("Trade not of expected type. Trade=" + trade);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeTaskRunner.java b/core/src/main/java/bisq/core/trade/protocol/TradeTaskRunner.java
index fef4ff490d..bf7e4fa28c 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 5da74be208..e2df05570b 100644
--- a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
+++ b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
@@ -88,6 +88,11 @@ public final class TradingPeer implements PersistablePayload {
@Nullable
private byte[] mediatedPayoutTxSignature;
+ // Added for XMR integration
+ @Nullable
+ private String preparedMultisigHex;
+ private String madeMultisigHex;
+ private String signedPayoutTxHex;
public TradingPeer() {
}
@@ -110,6 +115,10 @@ public final class TradingPeer implements PersistablePayload {
Optional.ofNullable(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e)));
Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(ByteString.copyFrom(e)));
Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e)));
+ Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
+ Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
+ Optional.ofNullable(signedPayoutTxHex).ifPresent(e -> builder.setSignedPayoutTxHex(signedPayoutTxHex));
+
builder.setCurrentDate(currentDate);
return builder.build();
}
@@ -139,6 +148,9 @@ public final class TradingPeer implements PersistablePayload {
tradingPeer.setAccountAgeWitnessSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignature()));
tradingPeer.setCurrentDate(proto.getCurrentDate());
tradingPeer.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
+ tradingPeer.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
+ tradingPeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
+ tradingPeer.setSignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex()));
return tradingPeer;
}
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java
index df3b8df2c6..5aa6ec5a43 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BroadcastPayoutTx.java
@@ -17,19 +17,18 @@
package bisq.core.trade.protocol.tasks;
-import bisq.core.btc.exceptions.TxBroadcastException;
-import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.trade.Trade;
import bisq.common.taskrunner.TaskRunner;
-import org.bitcoinj.core.Transaction;
-import org.bitcoinj.core.TransactionConfidence;
-
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
+
+
+import monero.wallet.model.MoneroTxWallet;
+
@Slf4j
public abstract class BroadcastPayoutTx extends TradeTask {
public BroadcastPayoutTx(TaskRunner taskHandler, Trade trade) {
@@ -42,40 +41,33 @@ public abstract class BroadcastPayoutTx extends TradeTask {
protected void run() {
try {
runInterceptHook();
- Transaction payoutTx = trade.getPayoutTx();
+ if (true) throw new RuntimeException("BroadcastPayoutTx not implemented for xmr");
+ MoneroTxWallet payoutTx = trade.getPayoutTx();
checkNotNull(payoutTx, "payoutTx must not be null");
- TransactionConfidence.ConfidenceType confidenceType = payoutTx.getConfidence().getConfidenceType();
- log.debug("payoutTx confidenceType:" + confidenceType);
- if (confidenceType.equals(TransactionConfidence.ConfidenceType.BUILDING) ||
- confidenceType.equals(TransactionConfidence.ConfidenceType.PENDING)) {
- log.debug("payoutTx was already published. confidenceType:" + confidenceType);
+
+ if (payoutTx.isRelayed()) {
+ log.debug("payoutTx was already published");
setState();
complete();
} else {
- processModel.getTradeWalletService().broadcastTx(payoutTx,
- new TxBroadcaster.Callback() {
- @Override
- public void onSuccess(Transaction transaction) {
- if (!completed) {
- log.debug("BroadcastTx succeeded. Transaction:" + transaction);
- setState();
- complete();
- } else {
- log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
- }
- }
-
- @Override
- public void onFailure(TxBroadcastException exception) {
- if (!completed) {
- log.error("BroadcastTx failed. Error:" + exception.getMessage());
- failed(exception);
- } else {
- log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
- }
- }
- });
+ try {
+ processModel.getProvider().getXmrWalletService().getWallet().relayTx(payoutTx);
+ if (!completed) {
+ log.debug("BroadcastTx succeeded. Transaction:" + payoutTx);
+ setState();
+ complete();
+ } else {
+ log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
+ }
+ } catch (Exception e) {
+ if (!completed) {
+ log.error("BroadcastTx failed. Error:" + e.getMessage());
+ failed(e);
+ } else {
+ log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
+ }
+ }
}
} catch (Throwable t) {
failed(t);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigMessage.java
new file mode 100644
index 0000000000..284c434e9a
--- /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 0000000000..e8f8e8cbd2
--- /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 aab0bb5a3b..775079c419 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java
@@ -17,19 +17,12 @@
package bisq.core.trade.protocol.tasks;
-import bisq.core.btc.wallet.WalletService;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
-import bisq.core.util.Validator;
import bisq.common.taskrunner.TaskRunner;
-import org.bitcoinj.core.Transaction;
-
import lombok.extern.slf4j.Slf4j;
-import static com.google.common.base.Preconditions.checkNotNull;
-
@Slf4j
public class ProcessPeerPublishedDelayedPayoutTxMessage extends TradeTask {
public ProcessPeerPublishedDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) {
@@ -40,21 +33,22 @@ public class ProcessPeerPublishedDelayedPayoutTxMessage extends TradeTask {
protected void run() {
try {
runInterceptHook();
+ throw new RuntimeException("XMR adaptation does not support delayed payout tx");
- PeerPublishedDelayedPayoutTxMessage message = (PeerPublishedDelayedPayoutTxMessage) processModel.getTradeMessage();
- Validator.checkTradeId(processModel.getOfferId(), message);
- checkNotNull(message);
-
- // update to the latest peer address of our peer if the message is correct
- trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
-
- // We add the tx to our wallet.
- Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
- WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, processModel.getBtcWalletService().getWallet());
-
- processModel.getTradeManager().requestPersistence();
-
- complete();
+// PeerPublishedDelayedPayoutTxMessage message = (PeerPublishedDelayedPayoutTxMessage) processModel.getTradeMessage();
+// Validator.checkTradeId(processModel.getOfferId(), message);
+// checkNotNull(message);
+//
+// // update to the latest peer address of our peer if the message is correct
+// trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+//
+// // We add the tx to our wallet.
+// Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
+// WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, processModel.getBtcWalletService().getWallet());
+//
+// processModel.getTradeManager().requestPersistence();
+//
+// complete();
} catch (Throwable t) {
failed(t);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessUpdateMultisigRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessUpdateMultisigRequest.java
new file mode 100644
index 0000000000..67a15568a1
--- /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 0000000000..62b06e122a
--- /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 0000000000..6eb96385a7
--- /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 5d925ae41c..213a904e6a 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
@@ -18,22 +18,26 @@
package bisq.core.trade.protocol.tasks;
import bisq.core.btc.listeners.AddressConfidenceListener;
-import bisq.core.btc.model.AddressEntry;
-import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.trade.Trade;
import bisq.common.UserThread;
import bisq.common.taskrunner.TaskRunner;
-import org.bitcoinj.core.Address;
-import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
-import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
+import java.util.List;
+
import lombok.extern.slf4j.Slf4j;
+
+
+import monero.wallet.model.MoneroTransferQuery;
+import monero.wallet.model.MoneroTxQuery;
+import monero.wallet.model.MoneroTxWallet;
+
@Slf4j
public abstract class SetupPayoutTxListener extends TradeTask {
// Use instance fields to not get eaten up by the GC
@@ -51,34 +55,35 @@ public abstract class SetupPayoutTxListener extends TradeTask {
protected void run() {
try {
runInterceptHook();
- if (!trade.isPayoutPublished()) {
- BtcWalletService walletService = processModel.getBtcWalletService();
- String id = processModel.getOffer().getId();
- Address address = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddress();
-
- TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
- if (isInNetwork(confidence)) {
- applyConfidence(confidence);
- } else {
- confidenceListener = new AddressConfidenceListener(address) {
- @Override
- public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
- if (isInNetwork(confidence))
- applyConfidence(confidence);
- }
- };
- walletService.addAddressConfidenceListener(confidenceListener);
-
- tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
- if (trade.isPayoutPublished()) {
- processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId());
-
- // hack to remove tradeStateSubscription at callback
- UserThread.execute(this::unSubscribe);
- }
- });
- }
- }
+ System.out.println("NEED TO IMPLEMENT PAYOUT TX LISTENER!"); // TODO (woodser): implement SetupPayoutTxListener
+// if (!trade.isPayoutPublished()) {
+// BtcWalletService walletService = processModel.getBtcWalletService();
+// String id = processModel.getOffer().getId();
+// Address address = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddress();
+//
+// TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
+// if (isInNetwork(confidence)) {
+// applyConfidence(confidence);
+// } else {
+// confidenceListener = new AddressConfidenceListener(address) {
+// @Override
+// public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
+// if (isInNetwork(confidence))
+// applyConfidence(confidence);
+// }
+// };
+// walletService.addAddressConfidenceListener(confidenceListener);
+//
+// tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
+// if (trade.isPayoutPublished()) {
+// processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId());
+//
+// // hack to remove tradeStateSubscription at callback
+// UserThread.execute(this::unSubscribe);
+// }
+// });
+// }
+// }
// we complete immediately, our object stays alive because the balanceListener is stored in the WalletService
complete();
@@ -87,12 +92,23 @@ public abstract class SetupPayoutTxListener extends TradeTask {
}
}
- private void applyConfidence(TransactionConfidence confidence) {
+ private void applyPayoutTx(int accountIdx) {
if (trade.getPayoutTx() == null) {
- Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash());
- trade.setPayoutTx(walletTx);
- processModel.getTradeManager().requestPersistence();
- BtcWalletService.printTx("payoutTx received from network", walletTx);
+
+ // get txs with transfers to payout subaddress
+ List txs = processModel.getProvider().getXmrWalletService().getWallet().getTxs(new MoneroTxQuery()
+ .setTransferQuery(new MoneroTransferQuery().setAccountIndex(accountIdx).setSubaddressIndex(0).setIsIncoming(true))); // TODO (woodser): hardcode account 0 as savings wallet, subaddress 0 trade accounts in config
+
+ // resolve payout tx if multiple txs sent to payout address
+ MoneroTxWallet payoutTx;
+ if (txs.size() > 1) {
+ throw new RuntimeException("Need to resolve multiple payout txs"); // TODO (woodser)
+ } else {
+ payoutTx = txs.get(0);
+ }
+
+ trade.setPayoutTx(payoutTx);
+ XmrWalletService.printTxs("payoutTx received from network", payoutTx);
setState();
} else {
log.info("We had the payout tx already set. tradeId={}, state={}", trade.getId(), trade.getState());
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java
new file mode 100644
index 0000000000..a970807410
--- /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 b35e1f53a6..38c718ce31 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/PublishedDelayedPayoutTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/arbitration/PublishedDelayedPayoutTx.java
@@ -17,17 +17,11 @@
package bisq.core.trade.protocol.tasks.arbitration;
-import bisq.core.btc.exceptions.TxBroadcastException;
-import bisq.core.btc.wallet.BtcWalletService;
-import bisq.core.btc.wallet.TxBroadcaster;
-import bisq.core.btc.wallet.WalletService;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.common.taskrunner.TaskRunner;
-import org.bitcoinj.core.Transaction;
-
import lombok.extern.slf4j.Slf4j;
@Slf4j
@@ -38,33 +32,34 @@ public class PublishedDelayedPayoutTx extends TradeTask {
@Override
protected void run() {
- try {
- runInterceptHook();
-
- Transaction delayedPayoutTx = trade.getDelayedPayoutTx();
- BtcWalletService btcWalletService = processModel.getBtcWalletService();
-
- // We have spent the funds from the deposit tx with the delayedPayoutTx
- btcWalletService.resetCoinLockedInMultiSigAddressEntry(trade.getId());
- // We might receive funds on AddressEntry.Context.TRADE_PAYOUT so we don't swap that
-
- Transaction committedDelayedPayoutTx = WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, btcWalletService.getWallet());
-
- processModel.getTradeWalletService().broadcastTx(committedDelayedPayoutTx, new TxBroadcaster.Callback() {
- @Override
- public void onSuccess(Transaction transaction) {
- log.info("publishDelayedPayoutTx onSuccess " + transaction);
- complete();
- }
-
- @Override
- public void onFailure(TxBroadcastException exception) {
- log.error("publishDelayedPayoutTx onFailure", exception);
- failed(exception.toString());
- }
- });
- } catch (Throwable t) {
- failed(t);
- }
+ throw new RuntimeException("PublishedDelayedPayoutTx not implemented for XMR");
+// try {
+// runInterceptHook();
+//
+// Transaction delayedPayoutTx = trade.getDelayedPayoutTx();
+// BtcWalletService btcWalletService = processModel.getBtcWalletService();
+//
+// // We have spent the funds from the deposit tx with the delayedPayoutTx
+// btcWalletService.resetCoinLockedInMultiSigAddressEntry(trade.getId());
+// // We might receive funds on AddressEntry.Context.TRADE_PAYOUT so we don't swap that
+//
+// Transaction committedDelayedPayoutTx = WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, btcWalletService.getWallet());
+//
+// processModel.getTradeWalletService().broadcastTx(committedDelayedPayoutTx, new TxBroadcaster.Callback() {
+// @Override
+// public void onSuccess(Transaction transaction) {
+// log.info("publishDelayedPayoutTx onSuccess " + transaction);
+// complete();
+// }
+//
+// @Override
+// public void onFailure(TxBroadcastException exception) {
+// log.error("publishDelayedPayoutTx onFailure", exception);
+// failed(exception.toString());
+// }
+// });
+// } catch (Throwable t) {
+// failed(t);
+// }
}
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerCreateAndSignPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerCreateAndSignPayoutTx.java
new file mode 100644
index 0000000000..f85f384e92
--- /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