mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-10-01 01:35:48 -04:00
Haveno
This commit is contained in:
parent
8a38081c04
commit
a22edd60f8
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -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
|
||||
|
18
.github/ISSUE_TEMPLATE/new_asset.md
vendored
18
.github/ISSUE_TEMPLATE/new_asset.md
vendored
@ -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)_
|
15
.github/boring-cyborg.yml
vendored
15
.github/boring-cyborg.yml
vendored
@ -1,15 +0,0 @@
|
||||
labelPRBasedOnFilePath:
|
||||
in:altcoins:
|
||||
- assets/**/*
|
||||
|
||||
is:no-priority:
|
||||
- assets/**/*
|
||||
|
||||
firstPRWelcomeComment: >
|
||||
**Thanks for opening this pull request!**<br/><br/>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).<br/><br/>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!**<br/><br/>Be sure to follow the issue template. Your issue will be reviewed by a maintainer and labeled for further action.
|
35
.github/stale.yml
vendored
35
.github/stale.yml
vendored
@ -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.
|
||||
|
35
.github/workflows/build.yml
vendored
Normal file
35
.github/workflows/build.yml
vendored
Normal file
@ -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
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -35,3 +35,4 @@ deploy
|
||||
.java-version
|
||||
.localnet
|
||||
/apitest/src/main/resources/dao-setup*
|
||||
/monero-wallet-rpc
|
||||
|
12
CODEOWNERS
12
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
|
||||
|
101
CONTRIBUTING.md
101
CONTRIBUTING.md
@ -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)
|
||||
|
1
LICENSE
1
LICENSE
@ -2,6 +2,7 @@
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2020 Haveno Dex
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
122
README.md
122
README.md
@ -1,20 +1,124 @@
|
||||
# Bisq
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/haveno-dex/haveno-meta/721e52919b28b44d12b6e1e5dac57265f1c05cda/logo/haveno_logo_landscape.svg" alt="Haveno logo">
|
||||
</div>
|
||||
|
||||
[![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<br>
|
||||
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.
|
||||
|
||||
<a href="https://getmonero.org"><img src="/media/sponsors/monero-community.png" title="Monero community" alt="Monero community logo" width="100px"></a>
|
||||
<a href="https://samouraiwallet.com/"><img src="/media/sponsors/samourai.png" title="Samourai wallet" alt="Samourai wallet logo" width="100px"></a>
|
||||
<a href="https://cakewallet.com/"><img src="/media/sponsors/cake-logo-blue.jpg" title="Cake wallet" alt="Cake wallet logo" width="100px"></a>
|
||||
<a href="https://twitter.com/DonYakka"><img src="/media/sponsors/donyakka.jpg" title="Don Yakka" alt="Don Yakka logo" width="100px"></a>
|
||||
|
||||
## 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.
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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<TradeInfo> 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;
|
||||
|
@ -16,7 +16,9 @@
|
||||
*/
|
||||
|
||||
package bisq.asset.coins;
|
||||
|
||||
import bisq.asset.AbstractAssetTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class IridiumTest extends AbstractAssetTest {
|
||||
|
14
build.gradle
14
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"
|
||||
|
@ -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);
|
||||
|
@ -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<TradeInfo, String> priceHeader = (t) ->
|
||||
private static final Function<TradeInfo, String> 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<TradeInfo, Boolean, String> makerTakerFeeHeaderCurrencyCode = (t, isTaker) -> {
|
||||
if (isTaker) {
|
||||
return t.getIsCurrencyForTakerFeeBtc() ? "BTC" : "BSQ";
|
||||
} else {
|
||||
return t.getOffer().getIsCurrencyForMakerFeeBtc() ? "BTC" : "BSQ";
|
||||
}
|
||||
return "XMR";
|
||||
};
|
||||
|
||||
private static final Function<TradeInfo, String> paymentStatusHeaderCurrencyCode = (t) ->
|
||||
@ -163,7 +161,7 @@ public class TradeFormat {
|
||||
|
||||
private static final Function<TradeInfo, String> 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<TradeInfo, Boolean, String> makerTakerMinerTxFeeFormat = (t, isTaker) -> {
|
||||
@ -175,15 +173,7 @@ public class TradeFormat {
|
||||
};
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> 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<TradeInfo, String> tradeCostFormat = (t) ->
|
||||
|
@ -25,7 +25,7 @@ public abstract class Task<T extends Model> {
|
||||
|
||||
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;
|
||||
|
@ -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<AccountAgeWitness> 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);
|
||||
|
@ -109,7 +109,6 @@ class CoreTradesService {
|
||||
tradeManager.onTakeOffer(offer.getAmount(),
|
||||
takeOfferModel.getTxFeeFromFeeService(),
|
||||
takeOfferModel.getTakerFee(),
|
||||
takeOfferModel.isCurrencyForTakerFeeBtc(),
|
||||
offer.getPrice().getValue(),
|
||||
takeOfferModel.getFundsNeededForTrade(),
|
||||
offer,
|
||||
|
@ -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;
|
||||
|
@ -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<ContractInfo> 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)
|
||||
|
@ -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" +
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -249,7 +249,10 @@ public class WalletAppSetup {
|
||||
.filter(trade -> trade.getOffer() != null)
|
||||
.forEach(trade -> {
|
||||
String details = null;
|
||||
if (txId.equals(trade.getDepositTxId())) {
|
||||
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())) {
|
||||
|
@ -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();
|
||||
}));
|
||||
|
@ -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<OpenOffer>) c -> updateBalance());
|
||||
tradeManager.getObservableList().addListener((ListChangeListener<Trade>) change -> updateBalance());
|
||||
refundManager.getDisputesAsObservableList().addListener((ListChangeListener<Dispute>) 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<MoneroAccount> 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<Trade> 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()));
|
||||
}
|
||||
}
|
||||
|
@ -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<List<String>>(){}).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);
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
150
core/src/main/java/bisq/core/btc/model/XmrAddressEntry.java
Normal file
150
core/src/main/java/bisq/core/btc/model/XmrAddressEntry.java
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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() +
|
||||
'}';
|
||||
}
|
||||
}
|
235
core/src/main/java/bisq/core/btc/model/XmrAddressEntryList.java
Normal file
235
core/src/main/java/bisq/core/btc/model/XmrAddressEntryList.java
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<XmrAddressEntryList> persistenceManager;
|
||||
transient private MoneroWallet wallet;
|
||||
private final Set<XmrAddressEntry> entrySet = new CopyOnWriteArraySet<>();
|
||||
|
||||
@Inject
|
||||
public XmrAddressEntryList(PersistenceManager<XmrAddressEntryList> 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<XmrAddressEntry> entrySet) {
|
||||
this.entrySet.addAll(entrySet);
|
||||
}
|
||||
|
||||
public static XmrAddressEntryList fromProto(protobuf.XmrAddressEntryList proto) {
|
||||
Set<XmrAddressEntry> entrySet = proto.getXmrAddressEntryList().stream()
|
||||
.map(XmrAddressEntry::fromProto)
|
||||
.collect(Collectors.toSet());
|
||||
return new XmrAddressEntryList(entrySet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
Set<protobuf.XmrAddressEntry> 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<XmrAddressEntry> 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<XmrAddressEntry> 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}";
|
||||
}
|
||||
}
|
@ -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<Integer, MoneroWalletRpc> registeredPorts = new HashMap<Integer, MoneroWalletRpc>();
|
||||
|
||||
/**
|
||||
* 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<String> 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<String> cmdCopy = new ArrayList<String>(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<Integer, MoneroWalletRpc> 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>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;
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
|
460
core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java
Normal file
460
core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java
Normal file
@ -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<XmrBalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
|
||||
protected final CopyOnWriteArraySet<MoneroWalletListenerI> walletListeners = new CopyOnWriteArraySet<>();
|
||||
private Map<String, MoneroWallet> multisigWallets;
|
||||
|
||||
@Getter
|
||||
private MoneroWallet wallet;
|
||||
|
||||
@Inject
|
||||
XmrWalletService(WalletsSetup walletsSetup,
|
||||
XmrAddressEntryList addressEntryList) {
|
||||
this.walletsSetup = walletsSetup;
|
||||
|
||||
this.addressEntryList = addressEntryList;
|
||||
this.multisigWallets = new HashMap<String, MoneroWallet>();
|
||||
|
||||
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<XmrAddressEntry> 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<XmrAddressEntry> 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<XmrAddressEntry> 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<XmrAddressEntry> 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<XmrAddressEntry> 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<XmrAddressEntry> 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<XmrAddressEntry> findAddressEntry(String address, XmrAddressEntry.Context context) {
|
||||
return getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> address.equals(e.getAddressString()))
|
||||
.filter(e -> context == e.getContext())
|
||||
.findAny();
|
||||
}
|
||||
|
||||
public List<XmrAddressEntry> getAvailableAddressEntries() {
|
||||
return getAddressEntryListAsImmutableList().stream()
|
||||
.filter(addressEntry -> XmrAddressEntry.Context.AVAILABLE == addressEntry.getContext())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<XmrAddressEntry> getAddressEntriesForTrade() {
|
||||
return getAddressEntryListAsImmutableList().stream()
|
||||
.filter(addressEntry -> XmrAddressEntry.Context.MULTI_SIG == addressEntry.getContext() ||
|
||||
XmrAddressEntry.Context.TRADE_PAYOUT == addressEntry.getContext())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<XmrAddressEntry> getAddressEntries(XmrAddressEntry.Context context) {
|
||||
return getAddressEntryListAsImmutableList().stream()
|
||||
.filter(addressEntry -> context == addressEntry.getContext())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<XmrAddressEntry> getFundedAvailableAddressEntries() {
|
||||
return getAvailableAddressEntries().stream()
|
||||
.filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<XmrAddressEntry> 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<XmrAddressEntry> getAddressEntriesForAvailableBalanceStream() {
|
||||
Stream<XmrAddressEntry> availableAndPayout = Stream.concat(getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream(), getFundedAvailableAddressEntries().stream());
|
||||
Stream<XmrAddressEntry> 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<MoneroTxWallet> 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<MoneroWallet> openWallets = new ArrayList<MoneroWallet>();
|
||||
if (wallet != null) openWallets.add(wallet);
|
||||
for (String multisigWalletKey : multisigWallets.keySet()) {
|
||||
openWallets.add(multisigWallets.get(multisigWalletKey));
|
||||
}
|
||||
|
||||
// create shutdown threads
|
||||
List<Thread> threads = new ArrayList<Thread>();
|
||||
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<MoneroTxWallet> 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<Transaction> 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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 extends DisputeAgent> T getLeastUsedMediator(TradeStatisticsManager tradeStatisticsManager,
|
||||
public static <T extends DisputeAgent> T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager) {
|
||||
return getLeastUsedDisputeAgent(tradeStatisticsManager,
|
||||
disputeAgentManager,
|
||||
true);
|
||||
}
|
||||
|
||||
public static <T extends DisputeAgent> T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager) {
|
||||
return getLeastUsedDisputeAgent(tradeStatisticsManager,
|
||||
disputeAgentManager,
|
||||
false);
|
||||
disputeAgentManager);
|
||||
}
|
||||
|
||||
private static <T extends DisputeAgent> T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager,
|
||||
boolean isMediator) {
|
||||
DisputeAgentManager<T> disputeAgentManager) {
|
||||
// We take last 100 entries from trade statistics
|
||||
List<TradeStatistics3> 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<String> lastAddressesUsedInTrades = list.stream()
|
||||
.map(tradeStatistics3 -> isMediator ? tradeStatistics3.getMediator() : tradeStatistics3.getRefundAgent())
|
||||
.map(tradeStatistics3 -> tradeStatistics3.getArbitrator())
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
@ -62,7 +62,7 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
|
||||
NodeAddress mediator = offerAvailabilityResponse.getMediator();
|
||||
if (mediator == null) {
|
||||
// We do not get a mediator from old clients so we need to handle the null case.
|
||||
mediator = DisputeAgentSelection.getLeastUsedMediator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress();
|
||||
mediator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress();
|
||||
}
|
||||
model.setSelectedMediator(mediator);
|
||||
|
||||
|
@ -20,6 +20,7 @@ package bisq.core.offer.placeoffer;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.offer.Offer;
|
||||
@ -37,6 +38,10 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
public class PlaceOfferModel implements Model {
|
||||
@ -45,6 +50,7 @@ public class PlaceOfferModel implements Model {
|
||||
private final Coin reservedFundsForOffer;
|
||||
private final boolean useSavingsWallet;
|
||||
private final BtcWalletService walletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final TradeWalletService tradeWalletService;
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final OfferBookService offerBookService;
|
||||
@ -60,11 +66,14 @@ public class PlaceOfferModel implements Model {
|
||||
private boolean offerAddedToOfferBook;
|
||||
@Setter
|
||||
private Transaction transaction;
|
||||
@Setter
|
||||
private MoneroTxWallet xmrTransaction;
|
||||
|
||||
public PlaceOfferModel(Offer offer,
|
||||
Coin reservedFundsForOffer,
|
||||
boolean useSavingsWallet,
|
||||
BtcWalletService walletService,
|
||||
XmrWalletService xmrWalletService,
|
||||
TradeWalletService tradeWalletService,
|
||||
BsqWalletService bsqWalletService,
|
||||
OfferBookService offerBookService,
|
||||
@ -77,6 +86,7 @@ public class PlaceOfferModel implements Model {
|
||||
this.reservedFundsForOffer = reservedFundsForOffer;
|
||||
this.useSavingsWallet = useSavingsWallet;
|
||||
this.walletService = walletService;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.offerBookService = offerBookService;
|
||||
|
@ -19,9 +19,9 @@ package bisq.core.offer.placeoffer;
|
||||
|
||||
import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
|
||||
import bisq.core.offer.placeoffer.tasks.CheckNumberOfUnconfirmedTransactions;
|
||||
import bisq.core.offer.placeoffer.tasks.CreateMakerFeeTx;
|
||||
import bisq.core.offer.placeoffer.tasks.ValidateOffer;
|
||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerCreateFeeTx;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
@ -79,7 +79,7 @@ public class PlaceOfferProtocol {
|
||||
taskRunner.addTasks(
|
||||
ValidateOffer.class,
|
||||
CheckNumberOfUnconfirmedTransactions.class,
|
||||
CreateMakerFeeTx.class,
|
||||
MakerCreateFeeTx.class,
|
||||
AddToOfferBook.class
|
||||
);
|
||||
|
||||
|
@ -17,19 +17,10 @@
|
||||
|
||||
package bisq.core.payment;
|
||||
|
||||
import bisq.core.locale.FiatCurrency;
|
||||
import bisq.core.payment.payload.JapanBankAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.payment.payload.JapanBankAccountPayload;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import bisq.core.locale.Country;
|
||||
import bisq.core.locale.FiatCurrency;
|
||||
import bisq.core.payment.payload.JapanBankAccountPayload;
|
||||
|
||||
public final class JapanBankAccount extends PaymentAccount
|
||||
{
|
||||
|
@ -50,7 +50,8 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// time in blocks (average 10 min for one block confirmation
|
||||
private static final long DAY = TimeUnit.HOURS.toMillis(24);
|
||||
//private static final long DAY = TimeUnit.HOURS.toMillis(24);
|
||||
private static final long DAY = TimeUnit.MINUTES.toMillis(1); // TODO (woodser): changed to 1 minute for development
|
||||
|
||||
// Default trade limits.
|
||||
// We initialize very early before reading persisted data. We will apply later the limit from
|
||||
|
@ -18,20 +18,21 @@
|
||||
package bisq.core.presentation;
|
||||
|
||||
import bisq.core.btc.Balances;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class BalancePresentation {
|
||||
private static final BigInteger AU_PER_XMR = new BigInteger("1000000000000");
|
||||
|
||||
@Getter
|
||||
private final StringProperty availableBalance = new SimpleStringProperty();
|
||||
@Getter
|
||||
@ -40,20 +41,25 @@ public class BalancePresentation {
|
||||
private final StringProperty lockedBalance = new SimpleStringProperty();
|
||||
|
||||
@Inject
|
||||
public BalancePresentation(Balances balances, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter) {
|
||||
public BalancePresentation(Balances balances) {
|
||||
balances.getAvailableBalance().addListener((observable, oldValue, newValue) -> {
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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> btcWalletService;
|
||||
private final Provider<XmrWalletService> xmrWalletService;
|
||||
private final NetworkProtoResolver networkProtoResolver;
|
||||
|
||||
@Inject
|
||||
public CorePersistenceProtoResolver(Provider<BtcWalletService> btcWalletService,
|
||||
Provider<XmrWalletService> 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:
|
||||
|
@ -106,8 +106,9 @@ public class MempoolService {
|
||||
}
|
||||
|
||||
public void validateOfferTakerTx(Trade trade, Consumer<TxValidator> 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<TxValidator> resultHandler) {
|
||||
|
@ -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));
|
||||
|
@ -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 +
|
||||
|
@ -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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<Dispute> storedDisputeOptional = findDispute(dispute);
|
||||
if (!storedDisputeOptional.isPresent()) {
|
||||
@ -342,22 +354,21 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
}
|
||||
|
||||
// We use the ChatMessage not the openNewDisputeMessage for the ACK
|
||||
ObservableList<ChatMessage> messages = dispute.getChatMessages();
|
||||
ObservableList<ChatMessage> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> extends Sup
|
||||
Optional<Dispute> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> extends Sup
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -811,7 +801,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
return findDispute(message.getTradeId(), message.getTraderId());
|
||||
}
|
||||
|
||||
private Optional<Dispute> findDispute(String tradeId, int traderId) {
|
||||
protected Optional<Dispute> findDispute(String tradeId, int traderId) {
|
||||
T disputeList = getDisputeList();
|
||||
if (disputeList == null) {
|
||||
log.warn("disputes is null");
|
||||
|
@ -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) +
|
||||
|
@ -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<ArbitrationDisputeList> {
|
||||
@ -87,7 +94,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
@Inject
|
||||
public ArbitrationManager(P2PService p2PService,
|
||||
TradeWalletService tradeWalletService,
|
||||
BtcWalletService walletService,
|
||||
XmrWalletService walletService,
|
||||
WalletsSetup walletsSetup,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
@ -127,16 +134,19 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
onDisputeResultMessage((DisputeResultMessage) message);
|
||||
} else if (message instanceof PeerPublishedDisputePayoutTxMessage) {
|
||||
onDisputedPayoutTxMessage((PeerPublishedDisputePayoutTxMessage) message);
|
||||
} else if (message instanceof ArbitratorPayoutTxRequest) {
|
||||
onArbitratorPayoutTxRequest((ArbitratorPayoutTxRequest) message);
|
||||
} else if (message instanceof ArbitratorPayoutTxResponse) {
|
||||
onArbitratorPayoutTxResponse((ArbitratorPayoutTxResponse) message);
|
||||
} else {
|
||||
log.warn("Unsupported message at dispatchMessage. message={}", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public NodeAddress getAgentNodeAddress(Dispute dispute) {
|
||||
return null;
|
||||
return dispute.getContract().getArbitratorNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -186,11 +196,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
DisputeResult disputeResult = disputeResultMessage.getDisputeResult();
|
||||
ChatMessage chatMessage = disputeResult.getChatMessage();
|
||||
checkNotNull(chatMessage, "chatMessage must not be null");
|
||||
if (Arrays.equals(disputeResult.getArbitratorPubKey(),
|
||||
btcWalletService.getArbitratorAddressEntry().getPubKey())) {
|
||||
log.error("Arbitrator received disputeResultMessage. That must never happen.");
|
||||
return;
|
||||
}
|
||||
Optional<Trade> tradeOptional = tradeManager.getTradeById(disputeResult.getTradeId());
|
||||
|
||||
String tradeId = disputeResult.getTradeId();
|
||||
Optional<Dispute> disputeOptional = findDispute(disputeResult);
|
||||
@ -209,8 +215,14 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Dispute dispute = disputeOptional.get();
|
||||
|
||||
// verify that arbitrator does not get DisputeResultMessage
|
||||
if (pubKeyRing.equals(dispute.getAgentPubKeyRing())) {
|
||||
log.error("Arbitrator received disputeResultMessage. That must never happen.");
|
||||
return;
|
||||
}
|
||||
|
||||
cleanupRetryMap(uid);
|
||||
if (!dispute.getChatMessages().contains(chatMessage)) {
|
||||
dispute.addAndPersistChatMessage(chatMessage);
|
||||
@ -225,16 +237,16 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
}
|
||||
|
||||
dispute.setDisputeResult(disputeResult);
|
||||
Optional<Trade> 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<ArbitrationDisputeL
|
||||
if ((isBuyer && publisher == DisputeResult.Winner.BUYER)
|
||||
|| (!isBuyer && publisher == DisputeResult.Winner.SELLER)) {
|
||||
|
||||
Transaction payoutTx = null;
|
||||
MoneroTxWallet payoutTx = null;
|
||||
if (tradeOptional.isPresent()) {
|
||||
payoutTx = tradeOptional.get().getPayoutTx();
|
||||
} else {
|
||||
@ -262,51 +274,29 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (payoutTx == null) {
|
||||
if (dispute.getDepositTxSerialized() != null) {
|
||||
byte[] multiSigPubKey = isBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
|
||||
DeterministicKey multiSigKeyPair = btcWalletService.getMultiSigKeyPair(tradeId, multiSigPubKey);
|
||||
Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(
|
||||
dispute.getDepositTxSerialized(),
|
||||
disputeResult.getArbitratorSignature(),
|
||||
disputeResult.getBuyerPayoutAmount(),
|
||||
disputeResult.getSellerPayoutAmount(),
|
||||
contract.getBuyerPayoutAddressString(),
|
||||
contract.getSellerPayoutAddressString(),
|
||||
multiSigKeyPair,
|
||||
contract.getBuyerMultiSigPubKey(),
|
||||
contract.getSellerMultiSigPubKey(),
|
||||
disputeResult.getArbitratorPubKey()
|
||||
);
|
||||
Transaction committedDisputedPayoutTx = WalletService.maybeAddSelfTxToWallet(signedDisputedPayoutTx, btcWalletService.getWallet());
|
||||
tradeWalletService.broadcastTx(committedDisputedPayoutTx, new TxBroadcaster.Callback() {
|
||||
@Override
|
||||
public void onSuccess(Transaction transaction) {
|
||||
// after successful publish we send peer the tx
|
||||
dispute.setDisputePayoutTxId(transaction.getTxId().toString());
|
||||
sendPeerPublishedPayoutTxMessage(transaction, dispute, contract);
|
||||
updateTradeOrOpenOfferManager(tradeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(TxBroadcastException exception) {
|
||||
log.error(exception.getMessage());
|
||||
}
|
||||
}, 15);
|
||||
// gather relevant info
|
||||
String arbitratorSignedPayoutTxHex = disputeResult.getArbitratorSignedPayoutTxHex();
|
||||
|
||||
success = true;
|
||||
} else {
|
||||
errorMessage = "DepositTx is null. TradeId = " + tradeId;
|
||||
if (arbitratorSignedPayoutTxHex != null) {
|
||||
if (!tradeOptional.isPresent()) throw new RuntimeException("Trade must not be null when trader signs arbitrator's payout tx");
|
||||
|
||||
try {
|
||||
MoneroTxSet txSet = traderSignsDisputePayoutTx(tradeId, arbitratorSignedPayoutTxHex);
|
||||
onTraderSignedDisputePayoutTx(tradeId, txSet);
|
||||
} catch (Exception e) {
|
||||
errorMessage = "Failed to sign dispute payout tx from arbitrator: " + e.getMessage() + ". TradeId = " + tradeId;
|
||||
log.warn(errorMessage);
|
||||
success = false;
|
||||
}
|
||||
} else {
|
||||
requestUpdatedPayoutTx = true;
|
||||
}
|
||||
} else {
|
||||
log.warn("We already got a payout tx. That might be the case if the other peer did not get the " +
|
||||
"payout tx and opened a dispute. TradeId = " + tradeId);
|
||||
dispute.setDisputePayoutTxId(payoutTx.getTxId().toString());
|
||||
sendPeerPublishedPayoutTxMessage(payoutTx, dispute, contract);
|
||||
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
log.trace("We don't publish the tx as we are not the winning party.");
|
||||
@ -314,28 +304,37 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
if (dispute.disputeResultProperty().get() != null && dispute.isClosed()) {
|
||||
updateTradeOrOpenOfferManager(tradeId);
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
} catch (TransactionVerificationException e) {
|
||||
}
|
||||
// catch (TransactionVerificationException e) {
|
||||
// errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString();
|
||||
// log.error(errorMessage, e);
|
||||
// success = false;
|
||||
//
|
||||
// // We prefer to close the dispute in that case. If there was no deposit tx and a random tx was used
|
||||
// // we get a TransactionVerificationException. No reason to keep that dispute open...
|
||||
// updateTradeOrOpenOfferManager(tradeId);
|
||||
//
|
||||
// throw new RuntimeException(errorMessage);
|
||||
// }
|
||||
// catch (AddressFormatException | WalletException e) {
|
||||
catch (Exception e) {
|
||||
errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString();
|
||||
log.error(errorMessage, e);
|
||||
success = false;
|
||||
|
||||
// We prefer to close the dispute in that case. If there was no deposit tx and a random tx was used
|
||||
// we get a TransactionVerificationException. No reason to keep that dispute open...
|
||||
updateTradeOrOpenOfferManager(tradeId);
|
||||
updateTradeOrOpenOfferManager(tradeId); // TODO (woodser): only close in case of verification exception?
|
||||
|
||||
throw new RuntimeException(errorMessage);
|
||||
} catch (AddressFormatException | WalletException | SignatureDecodeException e) {
|
||||
errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString();
|
||||
log.error(errorMessage, e);
|
||||
success = false;
|
||||
throw new RuntimeException(errorMessage);
|
||||
} finally {
|
||||
// We use the chatMessage as we only persist those not the disputeResultMessage.
|
||||
// If we would use the disputeResultMessage we could not lookup for the msg when we receive the AckMessage.
|
||||
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), success, errorMessage);
|
||||
|
||||
// If dispute opener's peer is co-signer, send updated multisig hex to arbitrator to receive updated payout tx
|
||||
if (requestUpdatedPayoutTx) sendArbitratorPayoutTxRequest(multisigWallet.getMultisigHex(), dispute, contract);
|
||||
}
|
||||
|
||||
requestPersistence();
|
||||
@ -366,27 +365,147 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
|
||||
cleanupRetryMap(uid);
|
||||
|
||||
Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
|
||||
// update multisig wallet
|
||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
|
||||
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
|
||||
|
||||
dispute.setDisputePayoutTxId(committedDisputePayoutTx.getTxId().toString());
|
||||
BtcWalletService.printTx("Disputed payoutTx received from peer", committedDisputePayoutTx);
|
||||
// parse payout tx
|
||||
MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
|
||||
|
||||
// System.out.println("LOSER'S VIEW OF MULTISIG WALLET (SHOULD INCLUDE PAYOUT TX):\n" + multisigWallet.getTxs());
|
||||
// if (multisigWallet.getTxs().size() != 3) throw new RuntimeException("Loser's multisig wallet does not include record of payout tx");
|
||||
// Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
|
||||
|
||||
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
|
||||
XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
|
||||
|
||||
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
|
||||
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
// Arbitrator receives updated multisig hex from dispute opener's peer (if co-signer) and returns updated payout tx to be signed and published
|
||||
private void onArbitratorPayoutTxRequest(ArbitratorPayoutTxRequest request) {
|
||||
String tradeId = request.getTradeId();
|
||||
Dispute dispute = findDispute(request.getDispute().getTradeId(), request.getDispute().getTraderId()).get();
|
||||
DisputeResult disputeResult = dispute.getDisputeResultProperty().get();
|
||||
Contract contract = dispute.getContract();
|
||||
|
||||
// verify sender is co-signer and receiver is arbitrator
|
||||
System.out.println("Any of these null???"); // TODO (woodser): NPE if dispute opener's peer-as-cosigner's ticket is closed first
|
||||
System.out.println(disputeResult);
|
||||
System.out.println(disputeResult.getWinner());
|
||||
System.out.println(contract.getBuyerNodeAddress());
|
||||
System.out.println(contract.getSellerNodeAddress());
|
||||
boolean senderIsWinner = (disputeResult.getWinner() == Winner.BUYER && contract.getBuyerNodeAddress().equals(request.getSenderNodeAddress())) || (disputeResult.getWinner() == Winner.SELLER && contract.getSellerNodeAddress().equals(request.getSenderNodeAddress()));
|
||||
boolean senderIsCosigner = senderIsWinner || disputeResult.isLoserPublisher();
|
||||
boolean receiverIsArbitrator = pubKeyRing.equals(dispute.getAgentPubKeyRing());
|
||||
|
||||
System.out.println("TESTING PUB KEY RINGS");
|
||||
System.out.println(pubKeyRing);
|
||||
System.out.println(dispute.getAgentPubKeyRing());
|
||||
System.out.println("Receiver is arbitrator: " + receiverIsArbitrator);
|
||||
|
||||
if (!senderIsCosigner) {
|
||||
log.warn("Received ArbitratorPayoutTxRequest but sender is not co-signer for trade id " + tradeId);
|
||||
return;
|
||||
}
|
||||
if (!receiverIsArbitrator) {
|
||||
log.warn("Received ArbitratorPayoutTxRequest but receiver is not arbitrator for trade id " + tradeId);
|
||||
return;
|
||||
}
|
||||
|
||||
// update arbitrator's multisig wallet with co-signer's multisig hex
|
||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
|
||||
try {
|
||||
multisigWallet.importMultisigHex(Arrays.asList(request.getUpdatedMultisigHex()));
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to import multisig hex from payout co-signer for trade id " + tradeId);
|
||||
return;
|
||||
}
|
||||
|
||||
// create updated payout tx
|
||||
MoneroTxWallet payoutTx = arbitratorCreatesDisputedPayoutTx(contract, dispute, disputeResult, multisigWallet);
|
||||
System.out.println("Arbitrator created updated payout tx for co-signer!!!");
|
||||
System.out.println(payoutTx);
|
||||
|
||||
// send updated payout tx to sender
|
||||
PubKeyRing senderPubKeyRing = contract.getBuyerNodeAddress().equals(request.getSenderNodeAddress()) ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
|
||||
ArbitratorPayoutTxResponse response = new ArbitratorPayoutTxResponse(
|
||||
tradeId,
|
||||
p2PService.getAddress(),
|
||||
UUID.randomUUID().toString(),
|
||||
SupportType.ARBITRATION,
|
||||
payoutTx.getTxSet().getMultisigTxHex());
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}", response.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), response.getUid());
|
||||
p2PService.sendEncryptedDirectMessage(request.getSenderNodeAddress(),
|
||||
senderPubKeyRing,
|
||||
response,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at peer {}. tradeId={}, uid={}",
|
||||
response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
|
||||
response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid(), errorMessage);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Dispute opener's peer receives updated payout tx after providing updated multisig hex (if co-signer)
|
||||
private void onArbitratorPayoutTxResponse(ArbitratorPayoutTxResponse response) {
|
||||
|
||||
// gather and verify trade info // TODO (woodser): verify response is from arbitrator, etc
|
||||
String tradeId = response.getTradeId();
|
||||
|
||||
// verify and sign dispute payout tx
|
||||
MoneroTxSet signedPayoutTx = traderSignsDisputePayoutTx(tradeId, response.getArbitratorSignedPayoutTxHex());
|
||||
|
||||
// process fully signed payout tx (publish, notify peer, etc)
|
||||
onTraderSignedDisputePayoutTx(tradeId, signedPayoutTx);
|
||||
}
|
||||
|
||||
private void onTraderSignedDisputePayoutTx(String tradeId, MoneroTxSet txSet) {
|
||||
|
||||
// gather trade info
|
||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
|
||||
Optional<Dispute> 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<ArbitrationDisputeL
|
||||
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
|
||||
}
|
||||
}
|
||||
|
||||
// dispute opener's peer signs payout tx by sending updated multisig hex to arbitrator who returns updated payout tx
|
||||
private void sendArbitratorPayoutTxRequest(String updatedMultisigHex, Dispute dispute, Contract contract) {
|
||||
ArbitratorPayoutTxRequest request = new ArbitratorPayoutTxRequest(
|
||||
dispute,
|
||||
p2PService.getAddress(),
|
||||
UUID.randomUUID().toString(),
|
||||
SupportType.ARBITRATION,
|
||||
updatedMultisigHex);
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}",
|
||||
request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid());
|
||||
p2PService.sendEncryptedDirectMessage(contract.getArbitratorNodeAddress(),
|
||||
dispute.getAgentPubKeyRing(),
|
||||
request,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at peer {}. tradeId={}, uid={}",
|
||||
request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
|
||||
request.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), request.getUid(), errorMessage);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Disputed payout tx signing
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO (woodser): where to move this common logic?
|
||||
public static MoneroTxWallet arbitratorCreatesDisputedPayoutTx(Contract contract, Dispute dispute, DisputeResult disputeResult, MoneroWallet multisigWallet) {
|
||||
|
||||
//System.out.println("DisputeSummaryWindow.arbitratorSignsDisputedPayoutTx()");
|
||||
//System.out.println("=== DISPUTE ===");
|
||||
//System.out.println(dispute);
|
||||
//System.out.println("=== CONTRACT ===");
|
||||
//System.out.println(contract); // TODO (woodser): contract should include deposit tx hashes (pre-created then hash shared then contract signed)
|
||||
//System.out.println("=== DISPUTE RESULT ===");
|
||||
//System.out.println(disputeResult);
|
||||
|
||||
// gather relevant trade info
|
||||
String buyerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString();
|
||||
String sellerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString();
|
||||
Preconditions.checkNotNull(buyerPayoutAddress, "buyerPayoutAddress must not be null");
|
||||
Preconditions.checkNotNull(sellerPayoutAddress, "sellerPayoutAddress must not be null");
|
||||
BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
|
||||
BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
|
||||
|
||||
//System.out.println("buyerPayoutAddress: " + buyerPayoutAddress);
|
||||
//System.out.println("buyerPayoutAmount: " + buyerPayoutAmount);
|
||||
|
||||
// Offer offer = new Offer(contract.getOfferPayload());
|
||||
// System.out.println("Buyer deposit tx fee: " +
|
||||
|
||||
//System.out.println("sellerPayoutAddress: " + sellerPayoutAddress);
|
||||
//System.out.println("sellerPayoutAmount: " + sellerPayoutAmount);
|
||||
//System.out.println("Multisig balance: " + multisigWallet.getBalance());
|
||||
//System.out.println("Multisig unlocked balance: " + multisigWallet.getUnlockedBalance());
|
||||
//System.out.println("Multisig txs");
|
||||
//System.out.println(multisigWallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true)));
|
||||
|
||||
// create transaction to get fee estimate
|
||||
if (multisigWallet.isMultisigImportNeeded()) {
|
||||
log.info("Arbitrator's wallet needs updated multisig hex to create payout tx which means a trader must have already broadcast the payout tx");
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO (woodser): include arbitration fee
|
||||
//System.out.println("Creating feeEstimateTx!");
|
||||
MoneroTxWallet feeEstimateTx = multisigWallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(new MoneroDestination(buyerPayoutAddress, buyerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5)))) // reduce payment amount to compute fee of similar tx
|
||||
.addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5)))) // TODO (woodser): support addDestination(addr, amt) without new
|
||||
.setRelay(false)
|
||||
);
|
||||
|
||||
System.out.println("Created fee estimate tx!");
|
||||
System.out.println(feeEstimateTx);
|
||||
//BigInteger estimatedFee = feeEstimateTx.getFee();
|
||||
|
||||
// attempt to create payout tx by increasing estimated fee until successful
|
||||
MoneroTxWallet payoutTx = null;
|
||||
int numAttempts = 0;
|
||||
while (payoutTx == null && numAttempts < 50) {
|
||||
BigInteger feeEstimate = feeEstimateTx.getFee().add(feeEstimateTx.getFee().multiply(BigInteger.valueOf(numAttempts)).divide(BigInteger.valueOf(10))); // add 1/10 of fee until tx is successful
|
||||
try {
|
||||
numAttempts++;
|
||||
payoutTx = multisigWallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(new MoneroDestination(buyerPayoutAddress, buyerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // split fee subtracted from each payout amount
|
||||
.addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // TODO (woodser): support addDestination(addr, amt) without new
|
||||
.setRelay(false));
|
||||
} catch (MoneroError e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("FAILED TO CREATE PAYOUT TX, ITERATING...");
|
||||
}
|
||||
}
|
||||
|
||||
if (payoutTx == null) throw new RuntimeException("Failed to generate dispute payout tx");
|
||||
System.out.println("DISPUTE PAYOUT TX GENERATED ON ATTEMPT " + numAttempts);
|
||||
System.out.println(payoutTx);
|
||||
return payoutTx;
|
||||
}
|
||||
|
||||
private MoneroTxSet traderSignsDisputePayoutTx(String tradeId, String payoutTxHex) {
|
||||
|
||||
// gather trade info
|
||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
|
||||
Optional<Dispute> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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 + '\'' +
|
||||
|
@ -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<MediationDisputeList>
|
||||
@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<MediationDisputeList>
|
||||
@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,
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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<RefundDisputeList> {
|
||||
@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<RefundDisputeList> {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
84
core/src/main/java/bisq/core/trade/ArbitratorTrade.java
Normal file
84
core/src/main/java/bisq/core/trade/ArbitratorTrade.java
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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}";
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<T extends Tradable> extends PersistableListAsObs
|
||||
|
||||
public static TradableList<Tradable> fromProto(protobuf.TradableList proto,
|
||||
CoreProtoResolver coreProtoResolver,
|
||||
BtcWalletService btcWalletService) {
|
||||
XmrWalletService xmrWalletService) {
|
||||
List<Tradable> 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() = " +
|
||||
|
@ -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<State> stateProperty = new SimpleObjectProperty<>(state);
|
||||
transient final private ObjectProperty<Phase> 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<TradeMessageListener> 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<TradeMessageListener>();
|
||||
}
|
||||
|
||||
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
return depositTx;
|
||||
}
|
||||
|
||||
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<TradeMessageListener>(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<TransactionConfidence> 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<TransactionConfidence> 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}";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
@ -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<OpenOffer> 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<Trade>) 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<Trade> 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<OpenOffer> 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<Trade> 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<Trade> 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<Trade> 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<Trade> 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<Trade> 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());
|
||||
}
|
||||
@ -490,13 +651,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
@Nullable String memo,
|
||||
ResultHandler resultHandler,
|
||||
FaultHandler faultHandler) {
|
||||
String fromAddress = btcWalletService.getOrCreateAddressEntry(trade.getId(),
|
||||
AddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
FutureCallback<Transaction> callback = new FutureCallback<>() {
|
||||
int fromAccountIdx = xmrWalletService.getOrCreateAddressEntry(trade.getId(),
|
||||
XmrAddressEntry.Context.TRADE_PAYOUT).getAccountIndex();
|
||||
FutureCallback<MoneroTxWallet> callback = new FutureCallback<MoneroTxWallet>() {
|
||||
@Override
|
||||
public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
|
||||
public void onSuccess(@javax.annotation.Nullable MoneroTxWallet transaction) {
|
||||
if (transaction != null) {
|
||||
log.debug("onWithdraw onSuccess tx ID:" + transaction.getTxId().toString());
|
||||
log.debug("onWithdraw onSuccess tx ID:" + transaction.getHash());
|
||||
onTradeCompleted(trade);
|
||||
trade.setState(Trade.State.WITHDRAW_COMPLETED);
|
||||
getTradeProtocol(trade).onWithdrawCompleted();
|
||||
@ -513,8 +674,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
}
|
||||
};
|
||||
try {
|
||||
btcWalletService.sendFunds(fromAddress, toAddress, amount, fee, aesKey,
|
||||
AddressEntry.Context.TRADE_PAYOUT, memo, callback);
|
||||
xmrWalletService.sendFunds(fromAccountIdx, toAddress, amount, XmrAddressEntry.Context.TRADE_PAYOUT, callback);
|
||||
} catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.getMessage());
|
||||
@ -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,10 +784,21 @@ 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) {
|
||||
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. " +
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -86,38 +86,39 @@ public class TradeUtil {
|
||||
* @return Tuple2 tuple containing MULTI_SIG, TRADE_PAYOUT addresses for trade
|
||||
*/
|
||||
public Tuple2<String, String> 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) {
|
||||
|
81
core/src/main/java/bisq/core/trade/TradeUtils.java
Normal file
81
core/src/main/java/bisq/core/trade/TradeUtils.java
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 <MULTI_SIG, TRADE_PAYOUT> if both are AVAILABLE, otherwise null
|
||||
static Tuple2<String, String> 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 <MULTI_SIG, TRADE_PAYOUT> addresses as strings if they're known by the wallet
|
||||
public static Tuple2<String, String> 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);
|
||||
}
|
||||
}
|
@ -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<Trade> 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<TradableList<Trade>> 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<TradableList<Trade>> 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) ||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
protobuf.DepositTxMessage.Builder builder = protobuf.DepositTxMessage.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setDepositTxWithoutWitnesses(ByteString.copyFrom(depositTxWithoutWitnesses)))
|
||||
.build();
|
||||
.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();
|
||||
}
|
||||
}
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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,21 +97,19 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
||||
// Take offer
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO (woodser): this implementation is duplicated with SellerAsTakerProtocol
|
||||
@Override
|
||||
public void onTakeOffer() {
|
||||
System.out.println("onTakeOffer()");
|
||||
|
||||
expect(phase(Trade.Phase.INIT)
|
||||
.with(TakerEvent.TAKE_OFFER))
|
||||
.with(TakerEvent.TAKE_OFFER)
|
||||
.from(trade.getTradingPeerNodeAddress()))
|
||||
.setup(tasks(
|
||||
ApplyFilter.class,
|
||||
getVerifyPeersFeePaymentClass(),
|
||||
CreateTakerFeeTx.class,
|
||||
BuyerAsTakerCreatesDepositTxInputs.class,
|
||||
TakerSendInputsForDepositTxRequest.class)
|
||||
.withTimeout(60))
|
||||
.run(() -> {
|
||||
processModel.setTempTradingPeerNodeAddress(trade.getTradingPeerNodeAddress());
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
})
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
.setup(tasks(
|
||||
getVerifyPeersFeePaymentClass(),
|
||||
BuyerProcessPayoutTxPublishedMessage.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -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();
|
||||
// }
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<MessageState> 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<RawTransactionInput> 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,12 +325,9 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
return Coin.valueOf(fundsNeededForTradeAsLong);
|
||||
}
|
||||
|
||||
public Transaction resolveTakeOfferFeeTx(Trade trade) {
|
||||
public MoneroTxWallet resolveTakeOfferFeeTx(Trade trade) {
|
||||
if (takeOfferFeeTx == null) {
|
||||
if (!trade.isCurrencyForTakerFeeBtc())
|
||||
takeOfferFeeTx = getBsqWalletService().getTransaction(takeOfferFeeTxId);
|
||||
else
|
||||
takeOfferFeeTx = getBtcWalletService().getTransaction(takeOfferFeeTxId);
|
||||
takeOfferFeeTx = provider.getXmrWalletService().getWallet().getTx(takeOfferFeeTxId);
|
||||
}
|
||||
return takeOfferFeeTx;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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,16 +90,16 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
||||
|
||||
@Override
|
||||
public void onTakeOffer() {
|
||||
System.out.println("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))
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
() -> {
|
||||
.using(new TradeTaskRunner(trade, () -> {
|
||||
resultHandler.handleResult();
|
||||
handleTaskRunnerSuccess(event);
|
||||
},
|
||||
(errorMessage) -> {
|
||||
}, (errorMessage) -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(event, errorMessage);
|
||||
})))
|
||||
.run(() -> {
|
||||
trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
})
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user