mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-22 11:39:29 +00:00
remove DAO
Co-authored-by: premek <1145361+premek@users.noreply.github.com>
This commit is contained in:
parent
f9f2cd07c3
commit
cefba8e4b5
621 changed files with 583 additions and 68805 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -34,4 +34,3 @@ deploy
|
||||||
/monitor/monitor-tor/*
|
/monitor/monitor-tor/*
|
||||||
.java-version
|
.java-version
|
||||||
.localnet
|
.localnet
|
||||||
/apitest/src/main/resources/dao-setup*
|
|
||||||
|
|
10
Makefile
10
Makefile
|
@ -1,6 +1,6 @@
|
||||||
# See docs/installing.md
|
# See docs/installing.md
|
||||||
|
|
||||||
build: nodes localnet build-haveno
|
build: nodes localnet build-haveno
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
./gradlew clean
|
./gradlew clean
|
||||||
|
@ -9,7 +9,7 @@ clean-localnet:
|
||||||
rm -rf .localnet
|
rm -rf .localnet
|
||||||
|
|
||||||
localnet:
|
localnet:
|
||||||
mkdir -p .localnet
|
mkdir -p .localnet
|
||||||
|
|
||||||
nodes: localnet
|
nodes: localnet
|
||||||
./scripts/xmr_btc_deps.sh
|
./scripts/xmr_btc_deps.sh
|
||||||
|
@ -39,7 +39,6 @@ seednode:
|
||||||
--useDevPrivilegeKeys=true \
|
--useDevPrivilegeKeys=true \
|
||||||
--nodePort=2002 \
|
--nodePort=2002 \
|
||||||
--appName=haveno-XMR_STAGENET_Seed_2002 \
|
--appName=haveno-XMR_STAGENET_Seed_2002 \
|
||||||
--daoActivated=false
|
|
||||||
|
|
||||||
arbitrator-desktop:
|
arbitrator-desktop:
|
||||||
# Arbitrator and mediator need to be registerd in the UI after launching it.
|
# Arbitrator and mediator need to be registerd in the UI after launching it.
|
||||||
|
@ -49,7 +48,6 @@ arbitrator-desktop:
|
||||||
--useDevPrivilegeKeys=true \
|
--useDevPrivilegeKeys=true \
|
||||||
--nodePort=4444 \
|
--nodePort=4444 \
|
||||||
--appName=haveno-XMR_STAGENET_arbitrator \
|
--appName=haveno-XMR_STAGENET_arbitrator \
|
||||||
--daoActivated=false \
|
|
||||||
--apiPassword=apitest \
|
--apiPassword=apitest \
|
||||||
--apiPort=9998
|
--apiPort=9998
|
||||||
|
|
||||||
|
@ -60,7 +58,6 @@ alice-desktop:
|
||||||
--useDevPrivilegeKeys=true \
|
--useDevPrivilegeKeys=true \
|
||||||
--nodePort=5555 \
|
--nodePort=5555 \
|
||||||
--appName=haveno-XMR_STAGENET_Alice \
|
--appName=haveno-XMR_STAGENET_Alice \
|
||||||
--daoActivated=false \
|
|
||||||
--apiPassword=apitest \
|
--apiPassword=apitest \
|
||||||
--apiPort=9999
|
--apiPort=9999
|
||||||
|
|
||||||
|
@ -71,7 +68,6 @@ alice-daemon:
|
||||||
--useDevPrivilegeKeys=true \
|
--useDevPrivilegeKeys=true \
|
||||||
--nodePort=5555 \
|
--nodePort=5555 \
|
||||||
--appName=haveno-XMR_STAGENET_Alice \
|
--appName=haveno-XMR_STAGENET_Alice \
|
||||||
--daoActivated=false \
|
|
||||||
--apiPassword=apitest \
|
--apiPassword=apitest \
|
||||||
--apiPort=9999
|
--apiPort=9999
|
||||||
|
|
||||||
|
@ -82,7 +78,6 @@ bob-desktop:
|
||||||
--useDevPrivilegeKeys=true \
|
--useDevPrivilegeKeys=true \
|
||||||
--nodePort=6666 \
|
--nodePort=6666 \
|
||||||
--appName=haveno-XMR_STAGENET_Bob \
|
--appName=haveno-XMR_STAGENET_Bob \
|
||||||
--daoActivated=false \
|
|
||||||
--apiPassword=apitest \
|
--apiPassword=apitest \
|
||||||
--apiPort=10000
|
--apiPort=10000
|
||||||
|
|
||||||
|
@ -93,7 +88,6 @@ bob-daemon:
|
||||||
--useDevPrivilegeKeys=true \
|
--useDevPrivilegeKeys=true \
|
||||||
--nodePort=6666 \
|
--nodePort=6666 \
|
||||||
--appName=haveno-XMR_STAGENET_Bob \
|
--appName=haveno-XMR_STAGENET_Bob \
|
||||||
--daoActivated=false \
|
|
||||||
--apiPassword=apitest \
|
--apiPassword=apitest \
|
||||||
--apiPort=10000
|
--apiPort=10000
|
||||||
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
// This gradle file contains tasks to install and clean dao-setup files downloaded from
|
|
||||||
// https://github.com/bisq-network/bisq/raw/master/docs/dao-setup.zip
|
|
||||||
// These tasks are not run by the default build, but they can can be run during a full
|
|
||||||
// or partial builds, or by themselves.
|
|
||||||
// To run a full Bisq clean build, test, and install dao-setup files:
|
|
||||||
// ./gradlew clean build :apitest:installDaoSetup
|
|
||||||
// To install or re-install dao-setup file only:
|
|
||||||
// ./gradlew :apitest:installDaoSetup -x test
|
|
||||||
// To clean installed dao-setup files:
|
|
||||||
// ./gradlew :apitest:cleanDaoSetup -x test
|
|
||||||
//
|
|
||||||
// The :apitest subproject will not run on Windows, and these tasks have not been
|
|
||||||
// tested on Windows.
|
|
||||||
def buildResourcesDir = project(":apitest").buildDir.path + '/resources/main'
|
|
||||||
|
|
||||||
// This task requires ant in the system $PATH.
|
|
||||||
task installDaoSetup(dependsOn: 'cleanDaoSetup') {
|
|
||||||
doLast {
|
|
||||||
println "Installing dao-setup directories in build dir $buildResourcesDir ..."
|
|
||||||
def src = 'https://github.com/bisq-network/bisq/raw/master/docs/dao-setup.zip'
|
|
||||||
def destfile = project.rootDir.path + '/apitest/src/main/resources/dao-setup.zip'
|
|
||||||
def url = new URL(src)
|
|
||||||
def f = new File(destfile)
|
|
||||||
if (f.exists()) {
|
|
||||||
println "File $destfile already exists, skipping download."
|
|
||||||
} else {
|
|
||||||
if (!f.parentFile.exists())
|
|
||||||
mkdir "$buildResourcesDir"
|
|
||||||
|
|
||||||
println "Downloading $url to $buildResourcesDir ..."
|
|
||||||
url.withInputStream { i -> f.withOutputStream { it << i } }
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need an ant task for unzipping the dao-setup.zip file.
|
|
||||||
println "Unzipping $destfile to $buildResourcesDir ..."
|
|
||||||
ant.unzip(src: 'src/main/resources/dao-setup.zip',
|
|
||||||
dest: 'src/main/resources',
|
|
||||||
overwrite: "true") {
|
|
||||||
// Warning: overwrite: "true" does not work if empty dirs exist, so the
|
|
||||||
// cleanDaoSetup task should be run before trying to re-install fresh
|
|
||||||
// dao-setup files.
|
|
||||||
patternset() {
|
|
||||||
include(name: '**')
|
|
||||||
exclude(name: '**/bitcoin.conf') // installed at runtime with correct blocknotify script path
|
|
||||||
exclude(name: '**/blocknotify') // installed from src/main/resources to allow port configs
|
|
||||||
}
|
|
||||||
mapper(type: "identity")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy files from unzip target dir 'dao-setup' to build/resources/main.
|
|
||||||
def daoSetupSrc = project.rootDir.path + '/apitest/src/main/resources/dao-setup'
|
|
||||||
def daoSetupDest = buildResourcesDir + '/dao-setup'
|
|
||||||
println "Copying $daoSetupSrc to $daoSetupDest ..."
|
|
||||||
copy {
|
|
||||||
from daoSetupSrc
|
|
||||||
into daoSetupDest
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move dao-setup files from build/resources/main/dao-setup to build/resources/main
|
|
||||||
file(buildResourcesDir + '/dao-setup/Bitcoin-regtest')
|
|
||||||
.renameTo(file(buildResourcesDir + '/Bitcoin-regtest'))
|
|
||||||
file(buildResourcesDir + '/dao-setup/bisq-XMR_STAGENET_Alice_dao')
|
|
||||||
.renameTo(file(buildResourcesDir + '/bisq-XMR_STAGENET_Alice_dao'))
|
|
||||||
file(buildResourcesDir + '/dao-setup/bisq-XMR_STAGENET_Bob_dao')
|
|
||||||
.renameTo(file(buildResourcesDir + '/bisq-XMR_STAGENET_Bob_dao'))
|
|
||||||
delete file(buildResourcesDir + '/dao-setup')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task cleanDaoSetup {
|
|
||||||
doLast {
|
|
||||||
// When re-installing dao-setup files before re-running tests, the bitcoin
|
|
||||||
// datadir and dao-setup dirs have to be cleaned first. This task allows
|
|
||||||
// you to re-install dao-setup files and re-run tests without having to
|
|
||||||
// re-compile any code.
|
|
||||||
println "Deleting dao-setup directories in build dir $buildResourcesDir ..."
|
|
||||||
delete file(buildResourcesDir + '/Bitcoin-regtest')
|
|
||||||
delete file(buildResourcesDir + '/bisq-XMR_STAGENET_Seed_2002')
|
|
||||||
delete file(buildResourcesDir + '/bisq-XMR_STAGENET_Arb_dao')
|
|
||||||
delete file(buildResourcesDir + '/bisq-XMR_STAGENET_Alice_dao')
|
|
||||||
delete file(buildResourcesDir + '/bisq-XMR_STAGENET_Bob_dao')
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -33,14 +33,6 @@ called `api-beta-test`.
|
||||||
$ git clone https://github.com/bisq-network/bisq.git api-beta-test
|
$ git clone https://github.com/bisq-network/bisq.git api-beta-test
|
||||||
```
|
```
|
||||||
|
|
||||||
Change your current working directory to `api-beta-test`, build the source, and download / install Bisq’s
|
|
||||||
pre-configured DAO / dev / regtest setup files.
|
|
||||||
```
|
|
||||||
$ cd api-beta-test
|
|
||||||
$ ./gradlew clean build :apitest:installDaoSetup -x test # if you want to skip Bisq tests
|
|
||||||
$ ./gradlew clean build :apitest:installDaoSetup # if you want to run Bisq tests
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running Api Test Harness
|
## Running Api Test Harness
|
||||||
|
|
||||||
If your bitcoin-core binaries are in your system `PATH`, start bitcoind in regtest-mode, Bisq seednode and arbitration
|
If your bitcoin-core binaries are in your system `PATH`, start bitcoind in regtest-mode, Bisq seednode and arbitration
|
||||||
|
@ -131,7 +123,7 @@ The script takes four options:
|
||||||
This simulation creates US / USD face-to-face payment accounts for Bob and Alice. Alice (always the trade maker)
|
This simulation creates US / USD face-to-face payment accounts for Bob and Alice. Alice (always the trade maker)
|
||||||
creates a SELL / USD offer for the amount of 0.1 BTC, at a price 2% below the current market price.
|
creates a SELL / USD offer for the amount of 0.1 BTC, at a price 2% below the current market price.
|
||||||
Bob (always the taker), will use his face-to-face account to take the offer, then the two sides will complete
|
Bob (always the taker), will use his face-to-face account to take the offer, then the two sides will complete
|
||||||
the trade, checking their trade status along the way, and their BSQ / BTC balances when the trade is closed.
|
the trade, checking their trade status along the way, and their BTC balances when the trade is closed.
|
||||||
```
|
```
|
||||||
$ apitest/scripts/trade-simulation.sh -d sell -c us -m 2.00 -a 0.1
|
$ apitest/scripts/trade-simulation.sh -d sell -c us -m 2.00 -a 0.1
|
||||||
```
|
```
|
||||||
|
@ -175,7 +167,7 @@ method help will be returned from the server. Also note an api password is requ
|
||||||
There is no need to secure your regtest Bisq wallet with an encryption password when running these examples,
|
There is no need to secure your regtest Bisq wallet with an encryption password when running these examples,
|
||||||
but you should encrypt your mainnet wallet as you probably already do when using the Bisq UI to transact in
|
but you should encrypt your mainnet wallet as you probably already do when using the Bisq UI to transact in
|
||||||
real BTC. This section explains how to encrypt your Bisq wallet with the CLI, and unlock it before performing wallet
|
real BTC. This section explains how to encrypt your Bisq wallet with the CLI, and unlock it before performing wallet
|
||||||
related operations such as creating and taking offers, checking balances, and sending BSQ and BTC to external wallets.
|
related operations such as creating and taking offers, checking balances, and sending BTC to external wallets.
|
||||||
|
|
||||||
Encrypt your wallet with a password:
|
Encrypt your wallet with a password:
|
||||||
```
|
```
|
||||||
|
@ -201,17 +193,6 @@ $ ./bisq-cli --password=xyz lockwallet
|
||||||
|
|
||||||
### Checking Balances
|
### Checking Balances
|
||||||
|
|
||||||
Show full BSQ and BTC wallet balance information:
|
|
||||||
```
|
|
||||||
$ ./bisq-cli --password=xyz --port=9998 getbalance
|
|
||||||
```
|
|
||||||
|
|
||||||
Show full BSQ wallet balance information:
|
|
||||||
```
|
|
||||||
$ ./bisq-cli --password=xyz --port=9999 getbalance --currency-code=bsq
|
|
||||||
```
|
|
||||||
_Note: The example above is asking for Bob’s balance (using port `9999`), not Alice’s balance._
|
|
||||||
|
|
||||||
Show Bob’s full BTC wallet balance information:
|
Show Bob’s full BTC wallet balance information:
|
||||||
```
|
```
|
||||||
$ ./bisq-cli --password=xyz --port=9999 getbalance --currency-code=btc
|
$ ./bisq-cli --password=xyz --port=9999 getbalance --currency-code=btc
|
||||||
|
@ -230,33 +211,9 @@ You can check a block explorer for the status of a transaction, or you can check
|
||||||
$ ./bisq-cli --password=xyz --port=9998 getaddressbalance --address=<btc-address>
|
$ ./bisq-cli --password=xyz --port=9998 getaddressbalance --address=<btc-address>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Receiving BSQ
|
### Sending BTC to External Wallets
|
||||||
To receive BSQ from an external wallet, find an unused BSQ address:
|
|
||||||
```
|
|
||||||
$ ./bisq-cli --password=xyz --port=9998 getunusedbsqaddress
|
|
||||||
```
|
|
||||||
|
|
||||||
Give the public address to the sender. After the BSQ is sent, you can check block explorers for the status of
|
Below are commands for sending BTC to external wallets.
|
||||||
the transaction. There is no support (yet) to check the balance of an individual BSQ address in your wallet,
|
|
||||||
but you can check your BSQ wallet’s balance to determine if the new funds have arrived:
|
|
||||||
```
|
|
||||||
$ ./bisq-cli --password=xyz --port=9999 getbalance --currency-code=bsq
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sending BSQ and BTC to External Wallets
|
|
||||||
|
|
||||||
Below are commands for sending BSQ and BTC to external wallets.
|
|
||||||
|
|
||||||
Send BSQ:
|
|
||||||
```
|
|
||||||
$ ./bisq-cli --password=xyz --port=9998 sendbsq --address=<bsq-address> --amount=<bsq-amount>
|
|
||||||
```
|
|
||||||
_Note: Sending BSQ to non-Bisq wallets is not supported and highly discouraged._
|
|
||||||
|
|
||||||
Send BSQ with a withdrawal transaction fee of 10 sats/byte:
|
|
||||||
```
|
|
||||||
$ ./bisq-cli --password=xyz --port=9998 sendbsq --address=<bsq-address> --amount=<bsq-amount> --tx-fee-rate=10
|
|
||||||
```
|
|
||||||
|
|
||||||
Send BTC:
|
Send BTC:
|
||||||
```
|
```
|
||||||
|
@ -272,7 +229,7 @@ $ ./bisq-cli --password=xyz --port=9998 sendbtc --address=<btc-address> --amount
|
||||||
If you have traded using the Bisq UI, you are probably aware of the default network bitcoin withdrawal transaction
|
If you have traded using the Bisq UI, you are probably aware of the default network bitcoin withdrawal transaction
|
||||||
fee and custom withdrawal transaction fee user preference in the UI’s setting view. The Api uses these same
|
fee and custom withdrawal transaction fee user preference in the UI’s setting view. The Api uses these same
|
||||||
withdrawal transaction fee rates, and affords a third – as mentioned in the previous section -- withdrawal
|
withdrawal transaction fee rates, and affords a third – as mentioned in the previous section -- withdrawal
|
||||||
transaction fee option in the `sendbsq` and `sendbtc` commands. The `sendbsq` and `sendbtc` commands'
|
transaction fee option in the `sendbtc` commands. The `sendbtc` commands'
|
||||||
`--tx-fee-rate=<sats/byte>` options override both the default network fee rate, and your custom transaction fee
|
`--tx-fee-rate=<sats/byte>` options override both the default network fee rate, and your custom transaction fee
|
||||||
setting for the execution of those commands.
|
setting for the execution of those commands.
|
||||||
|
|
||||||
|
@ -341,7 +298,7 @@ $ ./bisq-cli --password=xyz --port=9998 createoffer --help
|
||||||
|
|
||||||
The `trade-simulation.sh` script described above is an easy way to figure out how to use this command.
|
The `trade-simulation.sh` script described above is an easy way to figure out how to use this command.
|
||||||
In a previous example, Alice created a BUY/ EUR offer to buy 0.125 BTC at a fixed price of 30,800 EUR,
|
In a previous example, Alice created a BUY/ EUR offer to buy 0.125 BTC at a fixed price of 30,800 EUR,
|
||||||
and pay the Bisq maker fee in BSQ. Alice had already created an EUR face-to-face payment account with id
|
and pay the Bisq maker fee in BTC. Alice had already created an EUR face-to-face payment account with id
|
||||||
`f3c1ec8b-9761-458d-b13d-9039c6892413`, and used this `createoffer` command:
|
`f3c1ec8b-9761-458d-b13d-9039c6892413`, and used this `createoffer` command:
|
||||||
```
|
```
|
||||||
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||||
|
@ -351,7 +308,7 @@ $ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||||
--amount=0.125 \
|
--amount=0.125 \
|
||||||
--fixed-price=30800 \
|
--fixed-price=30800 \
|
||||||
--security-deposit=15.0 \
|
--security-deposit=15.0 \
|
||||||
--fee-currency=BSQ
|
--fee-currency=BTC
|
||||||
```
|
```
|
||||||
|
|
||||||
If Alice was in Japan, and wanted to create an offer to sell 0.125 BTC at 0.5% above the current market JPY price,
|
If Alice was in Japan, and wanted to create an offer to sell 0.125 BTC at 0.5% above the current market JPY price,
|
||||||
|
@ -364,7 +321,7 @@ $ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||||
--amount=0.125 \
|
--amount=0.125 \
|
||||||
--market-price-margin=0.5 \
|
--market-price-margin=0.5 \
|
||||||
--security-deposit=15.0 \
|
--security-deposit=15.0 \
|
||||||
--fee-currency=BSQ
|
--fee-currency=BTC
|
||||||
```
|
```
|
||||||
|
|
||||||
The `trade-simulation.sh` script options that would generate the previous `createoffer` example is:
|
The `trade-simulation.sh` script options that would generate the previous `createoffer` example is:
|
||||||
|
|
|
@ -26,31 +26,6 @@ with specific command line options, i.e., unique appDatadir and ports, but this
|
||||||
|
|
||||||
The API test harness uses the GNU Bourne-Again SHell `bash`, and is not supported on Windows.
|
The API test harness uses the GNU Bourne-Again SHell `bash`, and is not supported on Windows.
|
||||||
|
|
||||||
### Predefined DAO / Regtest Setup
|
|
||||||
|
|
||||||
The API test harness depends on the contents of https://github.com/bisq-network/bisq/raw/master/docs/dao-setup.zip.
|
|
||||||
The files contained in dao-setup.zip include a bitcoin-core wallet, a regtest genesis tx and chain of 111 blocks, plus
|
|
||||||
data directories for Bob and Alice Bisq instances. Bob & Alice wallets are pre-configured with 10 BTC each, and the
|
|
||||||
equivalent of 2.5 BTC in BSQ distributed among Bob & Alice's BSQ wallets.
|
|
||||||
|
|
||||||
See https://github.com/bisq-network/bisq/blob/master/docs/dao-setup.md for details.
|
|
||||||
|
|
||||||
### Install DAO / Regtest Setup Files
|
|
||||||
|
|
||||||
Bisq's gradle build file defines a task for downloading dao-setup.zip and extracting its contents to the
|
|
||||||
`apitest/src/main/resources` folder, and the test harness will install a fresh set of data files to the
|
|
||||||
`apitest/build/resources/main` folder during a test case's scaffold setup phase -- normally a static `@BeforeAll` method.
|
|
||||||
|
|
||||||
The dao-setup files can be downloaded during a normal build:
|
|
||||||
|
|
||||||
$ ./gradlew clean build :apitest:installDaoSetup
|
|
||||||
|
|
||||||
Or by running a single task:
|
|
||||||
|
|
||||||
$ ./gradlew :apitest:installDaoSetup
|
|
||||||
|
|
||||||
The `:apitest:installDaoSetup` task does not need to be run again until after the next time you run the gradle `clean` task.
|
|
||||||
|
|
||||||
### Run API Tests
|
### Run API Tests
|
||||||
|
|
||||||
The API test harness supports narrow & broad functional and full end to end test cases requiring
|
The API test harness supports narrow & broad functional and full end to end test cases requiring
|
||||||
|
|
|
@ -125,7 +125,6 @@ else
|
||||||
CMD+=" --market-price-margin=$MKT_PRICE_MARGIN"
|
CMD+=" --market-price-margin=$MKT_PRICE_MARGIN"
|
||||||
fi
|
fi
|
||||||
CMD+=" --security-deposit=50.0"
|
CMD+=" --security-deposit=50.0"
|
||||||
CMD+=" --fee-currency=BSQ"
|
|
||||||
printdate "ALICE CLI: $CMD"
|
printdate "ALICE CLI: $CMD"
|
||||||
OFFER_ID=$(createoffer "$CMD")
|
OFFER_ID=$(createoffer "$CMD")
|
||||||
exitoncommandalert $?
|
exitoncommandalert $?
|
||||||
|
|
|
@ -154,11 +154,6 @@
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "test getunusedbsqaddress" {
|
|
||||||
run ./bisq-cli --password=xyz getunusedbsqaddress
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "test getaddressbalance missing address argument" {
|
@test "test getaddressbalance missing address argument" {
|
||||||
run ./bisq-cli --password=xyz getaddressbalance
|
run ./bisq-cli --password=xyz getaddressbalance
|
||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 1 ]
|
||||||
|
|
|
@ -226,14 +226,14 @@ checkseednoderunning() {
|
||||||
|
|
||||||
checkarbnoderunning() {
|
checkarbnoderunning() {
|
||||||
if [[ "$LINUX" == "TRUE" ]]; then
|
if [[ "$LINUX" == "TRUE" ]]; then
|
||||||
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Arb_dao" > /dev/null ; then
|
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Arb" > /dev/null ; then
|
||||||
printdate "The arbitration node is running on host."
|
printdate "The arbitration node is running on host."
|
||||||
else
|
else
|
||||||
printdate "Error: arbitration node is not running on host, exiting."
|
printdate "Error: arbitration node is not running on host, exiting."
|
||||||
apitestusage
|
apitestusage
|
||||||
fi
|
fi
|
||||||
elif [[ "$DARWIN" == "TRUE" ]]; then
|
elif [[ "$DARWIN" == "TRUE" ]]; then
|
||||||
if ps -A | awk '/[b]isq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Arb_dao/ {print $1}' > /dev/null ; then
|
if ps -A | awk '/[b]isq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Arb/ {print $1}' > /dev/null ; then
|
||||||
printdate "The arbitration node is running on host."
|
printdate "The arbitration node is running on host."
|
||||||
else
|
else
|
||||||
printdate "Error: arbitration node is not running on host, exiting."
|
printdate "Error: arbitration node is not running on host, exiting."
|
||||||
|
@ -247,14 +247,14 @@ checkarbnoderunning() {
|
||||||
|
|
||||||
checkalicenoderunning() {
|
checkalicenoderunning() {
|
||||||
if [[ "$LINUX" == "TRUE" ]]; then
|
if [[ "$LINUX" == "TRUE" ]]; then
|
||||||
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Alice_dao" > /dev/null ; then
|
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Alice" > /dev/null ; then
|
||||||
printdate "Alice's node is running on host."
|
printdate "Alice's node is running on host."
|
||||||
else
|
else
|
||||||
printdate "Error: Alice's node is not running on host, exiting."
|
printdate "Error: Alice's node is not running on host, exiting."
|
||||||
apitestusage
|
apitestusage
|
||||||
fi
|
fi
|
||||||
elif [[ "$DARWIN" == "TRUE" ]]; then
|
elif [[ "$DARWIN" == "TRUE" ]]; then
|
||||||
if ps -A | awk '/[b]isq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Alice_dao/ {print $1}' > /dev/null ; then
|
if ps -A | awk '/[b]isq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Alice/ {print $1}' > /dev/null ; then
|
||||||
printdate "Alice's node node is running on host."
|
printdate "Alice's node node is running on host."
|
||||||
else
|
else
|
||||||
printdate "Error: Alice's node is not running on host, exiting."
|
printdate "Error: Alice's node is not running on host, exiting."
|
||||||
|
@ -268,14 +268,14 @@ checkalicenoderunning() {
|
||||||
|
|
||||||
checkbobnoderunning() {
|
checkbobnoderunning() {
|
||||||
if [[ "$LINUX" == "TRUE" ]]; then
|
if [[ "$LINUX" == "TRUE" ]]; then
|
||||||
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Alice_dao" > /dev/null ; then
|
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Alice" > /dev/null ; then
|
||||||
printdate "Bob's node is running on host."
|
printdate "Bob's node is running on host."
|
||||||
else
|
else
|
||||||
printdate "Error: Bob's node is not running on host, exiting."
|
printdate "Error: Bob's node is not running on host, exiting."
|
||||||
apitestusage
|
apitestusage
|
||||||
fi
|
fi
|
||||||
elif [[ "$DARWIN" == "TRUE" ]]; then
|
elif [[ "$DARWIN" == "TRUE" ]]; then
|
||||||
if ps -A | awk '/[b]isq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Alice_dao/ {print $1}' > /dev/null ; then
|
if ps -A | awk '/[b]isq.daemon.app.BisqDaemonMain --appName=bisq-XMR_STAGENET_Alice/ {print $1}' > /dev/null ; then
|
||||||
printdate "Bob's node node is running on host."
|
printdate "Bob's node node is running on host."
|
||||||
else
|
else
|
||||||
printdate "Error: Bob's node is not running on host, exiting."
|
printdate "Error: Bob's node is not running on host, exiting."
|
||||||
|
|
|
@ -193,7 +193,7 @@ gencreateoffercommand() {
|
||||||
CMD+=" --market-price-margin=$MKT_PRICE_MARGIN"
|
CMD+=" --market-price-margin=$MKT_PRICE_MARGIN"
|
||||||
fi
|
fi
|
||||||
CMD+=" --security-deposit=15.0"
|
CMD+=" --security-deposit=15.0"
|
||||||
CMD+=" --fee-currency=BSQ"
|
CMD+=" --fee-currency=BTC"
|
||||||
echo "$CMD"
|
echo "$CMD"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,7 +477,7 @@ executetrade() {
|
||||||
printdate "First offer found: $OFFER_ID"
|
printdate "First offer found: $OFFER_ID"
|
||||||
|
|
||||||
# Take Alice's offer.
|
# Take Alice's offer.
|
||||||
CMD="$CLI_BASE --port=$BOB_PORT takeoffer --offer-id=$OFFER_ID --payment-account=$BOB_ACCT_ID --fee-currency=bsq"
|
CMD="$CLI_BASE --port=$BOB_PORT takeoffer --offer-id=$OFFER_ID --payment-account=$BOB_ACCT_ID"
|
||||||
printdate "BOB CLI: $CMD"
|
printdate "BOB CLI: $CMD"
|
||||||
TRADE=$($CMD)
|
TRADE=$($CMD)
|
||||||
commandalert $? "Could not take offer."
|
commandalert $? "Could not take offer."
|
||||||
|
|
|
@ -38,7 +38,7 @@ import bisq.apitest.config.ApiTestConfig;
|
||||||
*
|
*
|
||||||
* It can be used to smoke test your bitcoind environment: bisq-apitest.
|
* It can be used to smoke test your bitcoind environment: bisq-apitest.
|
||||||
*
|
*
|
||||||
* It can be used to run the regtest/dao environment for release testing:
|
* It can be used to run the regtest environment for release testing:
|
||||||
* bisq-test --shutdownAfterTests=false
|
* bisq-test --shutdownAfterTests=false
|
||||||
*
|
*
|
||||||
* All method, scenario and end to end tests are found in the test sources folder.
|
* All method, scenario and end to end tests are found in the test sources folder.
|
||||||
|
|
|
@ -139,7 +139,6 @@ public class Scaffold {
|
||||||
|
|
||||||
|
|
||||||
public Scaffold setUp() throws IOException, InterruptedException, ExecutionException {
|
public Scaffold setUp() throws IOException, InterruptedException, ExecutionException {
|
||||||
installDaoSetupDirectories();
|
|
||||||
|
|
||||||
// Start each background process from an executor, then add a shutdown hook.
|
// Start each background process from an executor, then add a shutdown hook.
|
||||||
CountDownLatch countdownLatch = new CountDownLatch(config.supportingApps.size());
|
CountDownLatch countdownLatch = new CountDownLatch(config.supportingApps.size());
|
||||||
|
@ -161,7 +160,6 @@ public class Scaffold {
|
||||||
try {
|
try {
|
||||||
log.info("Shutting down executor service ...");
|
log.info("Shutting down executor service ...");
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
executor.awaitTermination(config.supportingApps.size() * 2000L, MILLISECONDS);
|
executor.awaitTermination(config.supportingApps.size() * 2000L, MILLISECONDS);
|
||||||
|
|
||||||
SetupTask[] orderedTasks = new SetupTask[]{
|
SetupTask[] orderedTasks = new SetupTask[]{
|
||||||
|
@ -208,84 +206,6 @@ public class Scaffold {
|
||||||
return firstException;
|
return firstException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void installDaoSetupDirectories() {
|
|
||||||
cleanDaoSetupDirectories();
|
|
||||||
|
|
||||||
String daoSetupDir = Paths.get(config.baseSrcResourcesDir, "dao-setup").toFile().getAbsolutePath();
|
|
||||||
String buildDataDir = config.rootAppDataDir.getAbsolutePath();
|
|
||||||
try {
|
|
||||||
if (!new File(daoSetupDir).exists())
|
|
||||||
throw new FileNotFoundException(
|
|
||||||
format("Dao setup dir '%s' not found. Run gradle :apitest:installDaoSetup"
|
|
||||||
+ " to download dao-setup.zip and extract contents to resources folder",
|
|
||||||
daoSetupDir));
|
|
||||||
|
|
||||||
BashCommand copyBitcoinRegtestDir = new BashCommand(
|
|
||||||
"cp -rf " + daoSetupDir + "/Bitcoin-regtest/regtest"
|
|
||||||
+ " " + config.bitcoinDatadir);
|
|
||||||
if (copyBitcoinRegtestDir.run().getExitStatus() != 0)
|
|
||||||
throw new IllegalStateException("Could not install bitcoin regtest dir");
|
|
||||||
|
|
||||||
String aliceDataDir = daoSetupDir + "/" + alicedaemon.appName;
|
|
||||||
BashCommand copyAliceDataDir = new BashCommand(
|
|
||||||
"cp -rf " + aliceDataDir + " " + config.rootAppDataDir);
|
|
||||||
if (copyAliceDataDir.run().getExitStatus() != 0)
|
|
||||||
throw new IllegalStateException("Could not install alice data dir");
|
|
||||||
|
|
||||||
String bobDataDir = daoSetupDir + "/" + bobdaemon.appName;
|
|
||||||
BashCommand copyBobDataDir = new BashCommand(
|
|
||||||
"cp -rf " + bobDataDir + " " + config.rootAppDataDir);
|
|
||||||
if (copyBobDataDir.run().getExitStatus() != 0)
|
|
||||||
throw new IllegalStateException("Could not install bob data dir");
|
|
||||||
|
|
||||||
log.info("Installed dao-setup files into {}", buildDataDir);
|
|
||||||
|
|
||||||
if (!config.callRateMeteringConfigPath.isEmpty()) {
|
|
||||||
installCallRateMeteringConfiguration(aliceDataDir);
|
|
||||||
installCallRateMeteringConfiguration(bobDataDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the blocknotify script from the src resources dir to the build
|
|
||||||
// resources dir. Users may want to edit comment out some lines when all
|
|
||||||
// of the default block notifcation ports being will not be used (to avoid
|
|
||||||
// seeing rpc notifcation warnings in log files).
|
|
||||||
installBitcoinBlocknotify();
|
|
||||||
|
|
||||||
} catch (IOException | InterruptedException ex) {
|
|
||||||
throw new IllegalStateException("Could not install dao-setup files from " + daoSetupDir, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanDaoSetupDirectories() {
|
|
||||||
String buildDataDir = config.rootAppDataDir.getAbsolutePath();
|
|
||||||
log.info("Cleaning dao-setup data in {}", buildDataDir);
|
|
||||||
|
|
||||||
try {
|
|
||||||
BashCommand rmBobDataDir = new BashCommand("rm -rf " + config.rootAppDataDir + "/" + bobdaemon.appName);
|
|
||||||
if (rmBobDataDir.run().getExitStatus() != 0)
|
|
||||||
throw new IllegalStateException("Could not delete bob data dir");
|
|
||||||
|
|
||||||
BashCommand rmAliceDataDir = new BashCommand("rm -rf " + config.rootAppDataDir + "/" + alicedaemon.appName);
|
|
||||||
if (rmAliceDataDir.run().getExitStatus() != 0)
|
|
||||||
throw new IllegalStateException("Could not delete alice data dir");
|
|
||||||
|
|
||||||
BashCommand rmArbNodeDataDir = new BashCommand("rm -rf " + config.rootAppDataDir + "/" + arbdaemon.appName);
|
|
||||||
if (rmArbNodeDataDir.run().getExitStatus() != 0)
|
|
||||||
throw new IllegalStateException("Could not delete arbitrator data dir");
|
|
||||||
|
|
||||||
BashCommand rmSeedNodeDataDir = new BashCommand("rm -rf " + config.rootAppDataDir + "/" + seednode.appName);
|
|
||||||
if (rmSeedNodeDataDir.run().getExitStatus() != 0)
|
|
||||||
throw new IllegalStateException("Could not delete seednode data dir");
|
|
||||||
|
|
||||||
BashCommand rmBitcoinRegtestDir = new BashCommand("rm -rf " + config.bitcoinDatadir + "/regtest");
|
|
||||||
if (rmBitcoinRegtestDir.run().getExitStatus() != 0)
|
|
||||||
throw new IllegalStateException("Could not clean bitcoind regtest dir");
|
|
||||||
|
|
||||||
} catch (IOException | InterruptedException ex) {
|
|
||||||
throw new IllegalStateException("Could not clean dao-setup files from " + buildDataDir, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void installBitcoinBlocknotify() {
|
private void installBitcoinBlocknotify() {
|
||||||
// gradle is not working for this
|
// gradle is not working for this
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -55,7 +55,6 @@ import static joptsimple.internal.Strings.EMPTY;
|
||||||
public class ApiTestConfig {
|
public class ApiTestConfig {
|
||||||
|
|
||||||
// Global constants
|
// Global constants
|
||||||
public static final String BSQ = "BSQ";
|
|
||||||
public static final String BTC = "BTC";
|
public static final String BTC = "BTC";
|
||||||
public static final String XMR = "XMR";
|
public static final String XMR = "XMR";
|
||||||
public static final String ARBITRATOR = "arbitrator";
|
public static final String ARBITRATOR = "arbitrator";
|
||||||
|
|
|
@ -28,7 +28,6 @@ import bisq.daemon.app.BisqDaemonMain;
|
||||||
/**
|
/**
|
||||||
Some non user configurable Bisq seednode, arb node, bob and alice daemon option values.
|
Some non user configurable Bisq seednode, arb node, bob and alice daemon option values.
|
||||||
@see <a href="https://github.com/bisq-network/bisq/blob/master/docs/dev-setup.md">dev-setup.md</a>
|
@see <a href="https://github.com/bisq-network/bisq/blob/master/docs/dev-setup.md">dev-setup.md</a>
|
||||||
@see <a href="https://github.com/bisq-network/bisq/blob/master/docs/dao-setup.md">dao-setup.md</a>
|
|
||||||
*/
|
*/
|
||||||
public enum BisqAppConfig {
|
public enum BisqAppConfig {
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ public enum BisqAppConfig {
|
||||||
5120,
|
5120,
|
||||||
-1,
|
-1,
|
||||||
49996),
|
49996),
|
||||||
arbdaemon("bisq-XMR_STAGENET_Arb_dao",
|
arbdaemon("bisq-XMR_STAGENET_Arb",
|
||||||
"bisq-daemon",
|
"bisq-daemon",
|
||||||
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqDaemonMain.class.getName(),
|
BisqDaemonMain.class.getName(),
|
||||||
|
@ -48,7 +47,7 @@ public enum BisqAppConfig {
|
||||||
5121,
|
5121,
|
||||||
9997,
|
9997,
|
||||||
49997),
|
49997),
|
||||||
arbdesktop("bisq-XMR_STAGENET_Arb_dao",
|
arbdesktop("bisq-XMR_STAGENET_Arb",
|
||||||
"bisq-desktop",
|
"bisq-desktop",
|
||||||
"-XX:MaxRAM=3g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
"-XX:MaxRAM=3g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqAppMain.class.getName(),
|
BisqAppMain.class.getName(),
|
||||||
|
@ -56,7 +55,7 @@ public enum BisqAppConfig {
|
||||||
5121,
|
5121,
|
||||||
-1,
|
-1,
|
||||||
49997),
|
49997),
|
||||||
alicedaemon("bisq-XMR_STAGENET_Alice_dao",
|
alicedaemon("bisq-XMR_STAGENET_Alice",
|
||||||
"bisq-daemon",
|
"bisq-daemon",
|
||||||
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqDaemonMain.class.getName(),
|
BisqDaemonMain.class.getName(),
|
||||||
|
@ -64,7 +63,7 @@ public enum BisqAppConfig {
|
||||||
5122,
|
5122,
|
||||||
9998,
|
9998,
|
||||||
49998),
|
49998),
|
||||||
alicedesktop("bisq-XMR_STAGENET_Alice_dao",
|
alicedesktop("bisq-XMR_STAGENET_Alice",
|
||||||
"bisq-desktop",
|
"bisq-desktop",
|
||||||
"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqAppMain.class.getName(),
|
BisqAppMain.class.getName(),
|
||||||
|
@ -72,7 +71,7 @@ public enum BisqAppConfig {
|
||||||
5122,
|
5122,
|
||||||
-1,
|
-1,
|
||||||
49998),
|
49998),
|
||||||
bobdaemon("bisq-XMR_STAGENET_Bob_dao",
|
bobdaemon("bisq-XMR_STAGENET_Bob",
|
||||||
"bisq-daemon",
|
"bisq-daemon",
|
||||||
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqDaemonMain.class.getName(),
|
BisqDaemonMain.class.getName(),
|
||||||
|
@ -80,7 +79,7 @@ public enum BisqAppConfig {
|
||||||
5123,
|
5123,
|
||||||
9999,
|
9999,
|
||||||
49999),
|
49999),
|
||||||
bobdesktop("bisq-XMR_STAGENET_Bob_dao",
|
bobdesktop("bisq-XMR_STAGENET_Bob",
|
||||||
"bisq-desktop",
|
"bisq-desktop",
|
||||||
"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqAppMain.class.getName(),
|
BisqAppMain.class.getName(),
|
||||||
|
|
|
@ -48,8 +48,6 @@ public class BisqProcess extends AbstractLinuxProcess implements LinuxProcess {
|
||||||
private final String genesisTxId;
|
private final String genesisTxId;
|
||||||
private final int genesisBlockHeight;
|
private final int genesisBlockHeight;
|
||||||
private final String seedNodes;
|
private final String seedNodes;
|
||||||
private final boolean daoActivated;
|
|
||||||
private final boolean fullDaoNode;
|
|
||||||
private final boolean useLocalhostForP2P;
|
private final boolean useLocalhostForP2P;
|
||||||
public final boolean useDevPrivilegeKeys;
|
public final boolean useDevPrivilegeKeys;
|
||||||
private final String findBisqPidScript;
|
private final String findBisqPidScript;
|
||||||
|
@ -62,8 +60,6 @@ public class BisqProcess extends AbstractLinuxProcess implements LinuxProcess {
|
||||||
this.genesisTxId = "30af0050040befd8af25068cc697e418e09c2d8ebd8d411d2240591b9ec203cf";
|
this.genesisTxId = "30af0050040befd8af25068cc697e418e09c2d8ebd8d411d2240591b9ec203cf";
|
||||||
this.genesisBlockHeight = 111;
|
this.genesisBlockHeight = 111;
|
||||||
this.seedNodes = "localhost:2002";
|
this.seedNodes = "localhost:2002";
|
||||||
this.daoActivated = true;
|
|
||||||
this.fullDaoNode = true;
|
|
||||||
this.useLocalhostForP2P = true;
|
this.useLocalhostForP2P = true;
|
||||||
this.useDevPrivilegeKeys = true;
|
this.useDevPrivilegeKeys = true;
|
||||||
this.findBisqPidScript = (config.isRunningTest ? "." : "./apitest")
|
this.findBisqPidScript = (config.isRunningTest ? "." : "./apitest")
|
||||||
|
@ -224,8 +220,6 @@ public class BisqProcess extends AbstractLinuxProcess implements LinuxProcess {
|
||||||
add("--rpcUser=" + config.bitcoinRpcUser);
|
add("--rpcUser=" + config.bitcoinRpcUser);
|
||||||
add("--rpcPassword=" + config.bitcoinRpcPassword);
|
add("--rpcPassword=" + config.bitcoinRpcPassword);
|
||||||
add("--rpcPort=" + config.bitcoinRpcPort);
|
add("--rpcPort=" + config.bitcoinRpcPort);
|
||||||
add("--daoActivated=" + daoActivated);
|
|
||||||
add("--fullDaoNode=" + fullDaoNode);
|
|
||||||
add("--seedNodes=" + seedNodes);
|
add("--seedNodes=" + seedNodes);
|
||||||
add("--baseCurrencyNetwork=" + baseCurrencyNetwork);
|
add("--baseCurrencyNetwork=" + baseCurrencyNetwork);
|
||||||
add("--useDevPrivilegeKeys=" + useDevPrivilegeKeys);
|
add("--useDevPrivilegeKeys=" + useDevPrivilegeKeys);
|
||||||
|
|
|
@ -59,7 +59,7 @@ import bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig;
|
||||||
* <p>
|
* <p>
|
||||||
* Those documents contain information about the configurations used by this test harness:
|
* Those documents contain information about the configurations used by this test harness:
|
||||||
* bitcoin-core's bitcoin.conf and blocknotify values, bisq instance options, the DAO genesis
|
* bitcoin-core's bitcoin.conf and blocknotify values, bisq instance options, the DAO genesis
|
||||||
* transaction id, initial BSQ and BTC balances for Bob & Alice accounts, and Bob and
|
* transaction id, initial BTC balances for Bob & Alice accounts, and Bob and
|
||||||
* Alice's default payment accounts.
|
* Alice's default payment accounts.
|
||||||
* <p>
|
* <p>
|
||||||
* During a build, the
|
* During a build, the
|
||||||
|
@ -68,9 +68,9 @@ import bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig;
|
||||||
* method, the DAO setup files are re-installed into the run time's data directories
|
* method, the DAO setup files are re-installed into the run time's data directories
|
||||||
* (each test case runs on a refreshed DAO/regtest environment setup).
|
* (each test case runs on a refreshed DAO/regtest environment setup).
|
||||||
* <p>
|
* <p>
|
||||||
* Initial Alice balances & accounts: 10.0 BTC, 1000000.00 BSQ, USD PerfectMoney dummy
|
* Initial Alice balances & accounts: 10.0 BTC, USD PerfectMoney dummy
|
||||||
* <p>
|
* <p>
|
||||||
* Initial Bob balances & accounts: 10.0 BTC, 1500000.00 BSQ, USD PerfectMoney dummy
|
* Initial Bob balances & accounts: 10.0 BTC, USD PerfectMoney dummy
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ApiTestCase {
|
public class ApiTestCase {
|
||||||
|
|
|
@ -32,7 +32,6 @@ import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
|
||||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
|
||||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||||
|
@ -52,9 +51,6 @@ public abstract class AbstractOfferTest extends MethodTest {
|
||||||
@Setter
|
@Setter
|
||||||
protected static boolean isLongRunningTest;
|
protected static boolean isLongRunningTest;
|
||||||
|
|
||||||
protected static PaymentAccount alicesBsqAcct;
|
|
||||||
protected static PaymentAccount bobsBsqAcct;
|
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setUp() {
|
public static void setUp() {
|
||||||
startSupportingApps(true,
|
startSupportingApps(true,
|
||||||
|
@ -66,18 +62,6 @@ public abstract class AbstractOfferTest extends MethodTest {
|
||||||
bobdaemon);
|
bobdaemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void createBsqPaymentAccounts() {
|
|
||||||
alicesBsqAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's BSQ Account",
|
|
||||||
BSQ,
|
|
||||||
aliceClient.getUnusedBsqAddress(),
|
|
||||||
false);
|
|
||||||
bobsBsqAcct = bobClient.createCryptoCurrencyPaymentAccount("Bob's BSQ Account",
|
|
||||||
BSQ,
|
|
||||||
bobClient.getUnusedBsqAddress(),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected double getScaledOfferPrice(double offerPrice, String currencyCode) {
|
protected double getScaledOfferPrice(double offerPrice, String currencyCode) {
|
||||||
int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
||||||
return scaleDownByPowerOf10(offerPrice, precision);
|
return scaleDownByPowerOf10(offerPrice, precision);
|
||||||
|
|
|
@ -32,7 +32,6 @@ import org.junit.jupiter.api.Order;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferPayload.Direction.BUY;
|
||||||
|
@ -53,8 +52,7 @@ public class CancelOfferTest extends AbstractOfferTest {
|
||||||
10000000L,
|
10000000L,
|
||||||
0.00,
|
0.00,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
paymentAccountId,
|
paymentAccountId);
|
||||||
BSQ);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -1,252 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.apitest.method.offer;
|
|
||||||
|
|
||||||
import bisq.proto.grpc.OfferInfo;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
|
||||||
import org.junit.jupiter.api.Order;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
|
||||||
import static bisq.cli.TableFormat.formatOfferTable;
|
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@Slf4j
|
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
|
||||||
public class CreateBSQOffersTest extends AbstractOfferTest {
|
|
||||||
|
|
||||||
private static final String MAKER_FEE_CURRENCY_CODE = BSQ;
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void setUp() {
|
|
||||||
AbstractOfferTest.setUp();
|
|
||||||
createBsqPaymentAccounts();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
public void testCreateBuy1BTCFor20KBSQOffer() {
|
|
||||||
// Remember alt coin trades are BTC trades. When placing an offer, you are
|
|
||||||
// offering to buy or sell BTC, not BSQ, XMR, etc. In this test case,
|
|
||||||
// Alice places an offer to BUY BTC with BSQ.
|
|
||||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
|
||||||
BSQ,
|
|
||||||
100_000_000L,
|
|
||||||
100_000_000L,
|
|
||||||
"0.00005", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
|
||||||
alicesBsqAcct.getId(),
|
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("Sell BSQ (Buy BTC) OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
|
||||||
String newOfferId = newOffer.getId();
|
|
||||||
assertNotEquals("", newOfferId);
|
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
|
||||||
assertEquals(5_000, newOffer.getPrice());
|
|
||||||
assertEquals(100_000_000L, newOffer.getAmount());
|
|
||||||
assertEquals(100_000_000L, newOffer.getMinAmount());
|
|
||||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
|
||||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
|
||||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
|
||||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
genBtcBlockAndWaitForOfferPreparation();
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
|
||||||
assertEquals(5_000, newOffer.getPrice());
|
|
||||||
assertEquals(100_000_000L, newOffer.getAmount());
|
|
||||||
assertEquals(100_000_000L, newOffer.getMinAmount());
|
|
||||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
|
||||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
|
||||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
|
||||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
public void testCreateSell1BTCFor20KBSQOffer() {
|
|
||||||
// Alice places an offer to SELL BTC for BSQ.
|
|
||||||
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
|
|
||||||
BSQ,
|
|
||||||
100_000_000L,
|
|
||||||
100_000_000L,
|
|
||||||
"0.00005", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
|
||||||
alicesBsqAcct.getId(),
|
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("SELL 20K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
|
||||||
String newOfferId = newOffer.getId();
|
|
||||||
assertNotEquals("", newOfferId);
|
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
|
||||||
assertEquals(5_000, newOffer.getPrice());
|
|
||||||
assertEquals(100_000_000L, newOffer.getAmount());
|
|
||||||
assertEquals(100_000_000L, newOffer.getMinAmount());
|
|
||||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
|
||||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
|
||||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
|
||||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
genBtcBlockAndWaitForOfferPreparation();
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
|
||||||
assertEquals(5_000, newOffer.getPrice());
|
|
||||||
assertEquals(100_000_000L, newOffer.getAmount());
|
|
||||||
assertEquals(100_000_000L, newOffer.getMinAmount());
|
|
||||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
|
||||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
|
||||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
|
||||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(3)
|
|
||||||
public void testCreateBuyBTCWith1To2KBSQOffer() {
|
|
||||||
// Alice places an offer to BUY 0.05 - 0.10 BTC with BSQ.
|
|
||||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
|
||||||
BSQ,
|
|
||||||
10_000_000L,
|
|
||||||
5_000_000L,
|
|
||||||
"0.00005", // FIXED PRICE IN BTC sats FOR 1 BSQ
|
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
|
||||||
alicesBsqAcct.getId(),
|
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("BUY 1-2K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
|
||||||
String newOfferId = newOffer.getId();
|
|
||||||
assertNotEquals("", newOfferId);
|
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
|
||||||
assertEquals(5_000, newOffer.getPrice());
|
|
||||||
assertEquals(10_000_000L, newOffer.getAmount());
|
|
||||||
assertEquals(5_000_000L, newOffer.getMinAmount());
|
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
|
||||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
|
||||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
|
||||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
genBtcBlockAndWaitForOfferPreparation();
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
|
||||||
assertEquals(5_000, newOffer.getPrice());
|
|
||||||
assertEquals(10_000_000L, newOffer.getAmount());
|
|
||||||
assertEquals(5_000_000L, newOffer.getMinAmount());
|
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
|
||||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
|
||||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
|
||||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(4)
|
|
||||||
public void testCreateSellBTCFor5To10KBSQOffer() {
|
|
||||||
// Alice places an offer to SELL 0.25 - 0.50 BTC for BSQ.
|
|
||||||
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
|
|
||||||
BSQ,
|
|
||||||
50_000_000L,
|
|
||||||
25_000_000L,
|
|
||||||
"0.00005", // FIXED PRICE IN BTC sats FOR 1 BSQ
|
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
|
||||||
alicesBsqAcct.getId(),
|
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("SELL 5-10K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
|
||||||
String newOfferId = newOffer.getId();
|
|
||||||
assertNotEquals("", newOfferId);
|
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
|
||||||
assertEquals(5_000, newOffer.getPrice());
|
|
||||||
assertEquals(50_000_000L, newOffer.getAmount());
|
|
||||||
assertEquals(25_000_000L, newOffer.getMinAmount());
|
|
||||||
assertEquals(7_500_000, newOffer.getBuyerSecurityDeposit());
|
|
||||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
|
||||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
|
||||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
genBtcBlockAndWaitForOfferPreparation();
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
|
||||||
assertEquals(5_000, newOffer.getPrice());
|
|
||||||
assertEquals(50_000_000L, newOffer.getAmount());
|
|
||||||
assertEquals(25_000_000L, newOffer.getMinAmount());
|
|
||||||
assertEquals(7_500_000, newOffer.getBuyerSecurityDeposit());
|
|
||||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
|
||||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
|
||||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(5)
|
|
||||||
public void testGetAllMyBsqOffers() {
|
|
||||||
List<OfferInfo> offers = aliceClient.getMyBsqOffersSortedByDate();
|
|
||||||
log.info("ALL ALICE'S BSQ OFFERS:\n{}", formatOfferTable(offers, BSQ));
|
|
||||||
assertEquals(4, offers.size());
|
|
||||||
log.info("ALICE'S BALANCES\n{}", formatBalancesTbls(aliceClient.getBalances()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(6)
|
|
||||||
public void testGetAvailableBsqOffers() {
|
|
||||||
List<OfferInfo> offers = bobClient.getBsqOffersSortedByDate();
|
|
||||||
log.info("ALL BOB'S AVAILABLE BSQ OFFERS:\n{}", formatOfferTable(offers, BSQ));
|
|
||||||
assertEquals(4, offers.size());
|
|
||||||
log.info("BOB'S BALANCES\n{}", formatBalancesTbls(bobClient.getBalances()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void genBtcBlockAndWaitForOfferPreparation() {
|
|
||||||
// Extra time is needed for the OfferUtils#isBsqForMakerFeeAvailable, which
|
|
||||||
// can sometimes return an incorrect false value if the BsqWallet's
|
|
||||||
// available confirmed balance is temporarily = zero during BSQ offer prep.
|
|
||||||
genBtcBlocksThenWait(1, 5000);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,7 +27,6 @@ import org.junit.jupiter.api.Order;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||||
import static bisq.cli.TableFormat.formatOfferTable;
|
import static bisq.cli.TableFormat.formatOfferTable;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
|
@ -43,8 +42,6 @@ import static protobuf.OfferPayload.Direction.SELL;
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
|
|
||||||
private static final String MAKER_FEE_CURRENCY_CODE = BSQ;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
public void testCreateAUDXMRBuyOfferUsingFixedPrice16000() {
|
public void testCreateAUDXMRBuyOfferUsingFixedPrice16000() {
|
||||||
|
@ -55,8 +52,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
"36000",
|
"36000",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
audAccount.getId(),
|
audAccount.getId());
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "AUD"));
|
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "AUD"));
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
|
@ -69,7 +65,6 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
|
@ -82,7 +77,6 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -95,8 +89,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
"30000.1234",
|
"30000.1234",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
usdAccount.getId(),
|
usdAccount.getId());
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "USD"));
|
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "USD"));
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
|
@ -109,7 +102,6 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
|
@ -122,7 +114,6 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -135,8 +126,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
5_000_000L,
|
5_000_000L,
|
||||||
"29500.1234",
|
"29500.1234",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
eurAccount.getId(),
|
eurAccount.getId());
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "EUR"));
|
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "EUR"));
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
|
@ -149,7 +139,6 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("EUR", newOffer.getCounterCurrencyCode());
|
assertEquals("EUR", newOffer.getCounterCurrencyCode());
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
|
@ -162,6 +151,5 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("EUR", newOffer.getCounterCurrencyCode());
|
assertEquals("EUR", newOffer.getCounterCurrencyCode());
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,8 +54,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
private static final double MKT_PRICE_MARGIN_ERROR_TOLERANCE = 0.0050; // 0.50%
|
private static final double MKT_PRICE_MARGIN_ERROR_TOLERANCE = 0.0050; // 0.50%
|
||||||
private static final double MKT_PRICE_MARGIN_WARNING_TOLERANCE = 0.0001; // 0.01%
|
private static final double MKT_PRICE_MARGIN_WARNING_TOLERANCE = 0.0001; // 0.01%
|
||||||
|
|
||||||
private static final String MAKER_FEE_CURRENCY_CODE = XMR;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
public void testCreateUSDXMRBuyOffer5PctPriceMargin() {
|
public void testCreateUSDXMRBuyOffer5PctPriceMargin() {
|
||||||
|
@ -67,8 +65,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
usdAccount.getId(),
|
usdAccount.getId());
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
|
@ -80,7 +77,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
|
@ -92,7 +88,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||||
}
|
}
|
||||||
|
@ -108,8 +103,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
nzdAccount.getId(),
|
nzdAccount.getId());
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
|
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
|
@ -121,7 +115,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
|
@ -133,7 +126,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||||
}
|
}
|
||||||
|
@ -149,8 +141,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
5_000_000L,
|
5_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
gbpAccount.getId(),
|
gbpAccount.getId());
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
|
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
|
@ -162,7 +153,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
|
@ -174,7 +164,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||||
}
|
}
|
||||||
|
@ -190,8 +179,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
5_000_000L,
|
5_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
brlAccount.getId(),
|
brlAccount.getId());
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
|
||||||
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
|
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
|
@ -203,7 +191,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
|
@ -215,7 +202,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import org.junit.jupiter.api.Order;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
|
@ -54,8 +53,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||||
100000000000L,
|
100000000000L,
|
||||||
"10000.0000",
|
"10000.0000",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
usdAccount.getId(),
|
usdAccount.getId()));
|
||||||
BSQ));
|
|
||||||
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer", exception.getMessage());
|
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,8 +69,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||||
10000000L,
|
10000000L,
|
||||||
"40000.0000",
|
"40000.0000",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
chfAccount.getId(),
|
chfAccount.getId()));
|
||||||
BTC));
|
|
||||||
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
|
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
|
||||||
assertEquals(expectedError, exception.getMessage());
|
assertEquals(expectedError, exception.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -89,8 +86,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||||
10000000L,
|
10000000L,
|
||||||
"63000.0000",
|
"63000.0000",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
audAccount.getId(),
|
audAccount.getId()));
|
||||||
BTC));
|
|
||||||
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
|
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
|
||||||
assertEquals(expectedError, exception.getMessage());
|
assertEquals(expectedError, exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import org.slf4j.Logger;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.TestInfo;
|
import org.junit.jupiter.api.TestInfo;
|
||||||
|
|
||||||
import static bisq.cli.CurrencyFormat.formatBsqAmount;
|
|
||||||
import static bisq.cli.TradeFormat.format;
|
import static bisq.cli.TradeFormat.format;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
@ -35,16 +34,14 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final TradeInfo takeAlicesOffer(String offerId,
|
protected final TradeInfo takeAlicesOffer(String offerId,
|
||||||
String paymentAccountId,
|
String paymentAccountId) {
|
||||||
String takerFeeCurrencyCode) {
|
return bobClient.takeOffer(offerId, paymentAccountId);
|
||||||
return bobClient.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
protected final TradeInfo takeBobsOffer(String offerId,
|
protected final TradeInfo takeBobsOffer(String offerId,
|
||||||
String paymentAccountId,
|
String paymentAccountId) {
|
||||||
String takerFeeCurrencyCode) {
|
return aliceClient.takeOffer(offerId, paymentAccountId);
|
||||||
return aliceClient.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void verifyExpectedProtocolStatus(TradeInfo trade) {
|
protected final void verifyExpectedProtocolStatus(TradeInfo trade) {
|
||||||
|
@ -62,40 +59,6 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isWithdrawn, trade.getIsWithdrawn());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.isWithdrawn, trade.getIsWithdrawn());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void sendBsqPayment(Logger log,
|
|
||||||
GrpcClient grpcClient,
|
|
||||||
TradeInfo trade) {
|
|
||||||
var contract = trade.getContract();
|
|
||||||
String receiverAddress = contract.getIsBuyerMakerAndSellerTaker()
|
|
||||||
? contract.getTakerPaymentAccountPayload().getAddress()
|
|
||||||
: contract.getMakerPaymentAccountPayload().getAddress();
|
|
||||||
String sendBsqAmount = formatBsqAmount(trade.getOffer().getVolume());
|
|
||||||
log.info("Sending {} BSQ to address {}", sendBsqAmount, receiverAddress);
|
|
||||||
grpcClient.sendBsq(receiverAddress, sendBsqAmount, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final void verifyBsqPaymentHasBeenReceived(Logger log,
|
|
||||||
GrpcClient grpcClient,
|
|
||||||
TradeInfo trade) {
|
|
||||||
var contract = trade.getContract();
|
|
||||||
var bsqSats = trade.getOffer().getVolume();
|
|
||||||
var receiveAmountAsString = formatBsqAmount(bsqSats);
|
|
||||||
var address = contract.getIsBuyerMakerAndSellerTaker()
|
|
||||||
? contract.getTakerPaymentAccountPayload().getAddress()
|
|
||||||
: contract.getMakerPaymentAccountPayload().getAddress();
|
|
||||||
boolean receivedBsqSatoshis = grpcClient.verifyBsqSentToAddress(address, receiveAmountAsString);
|
|
||||||
if (receivedBsqSatoshis)
|
|
||||||
log.info("Payment of {} BSQ was received to address {} for trade with id {}.",
|
|
||||||
receiveAmountAsString,
|
|
||||||
address,
|
|
||||||
trade.getTradeId());
|
|
||||||
else
|
|
||||||
fail(String.format("Payment of %s BSQ was was not sent to address %s for trade with id %s.",
|
|
||||||
receiveAmountAsString,
|
|
||||||
address,
|
|
||||||
trade.getTradeId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final void logTrade(Logger log,
|
protected final void logTrade(Logger log,
|
||||||
TestInfo testInfo,
|
TestInfo testInfo,
|
||||||
String description,
|
String description,
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.apitest.method.trade;
|
|
||||||
|
|
||||||
import bisq.proto.grpc.TradeInfo;
|
|
||||||
|
|
||||||
import io.grpc.StatusRuntimeException;
|
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
|
||||||
import org.junit.jupiter.api.Order;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.TestInfo;
|
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
|
||||||
import static bisq.cli.TableFormat.formatOfferTable;
|
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
|
||||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
|
||||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
|
||||||
import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
|
|
||||||
import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
|
|
||||||
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
|
|
||||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
|
||||||
import static java.lang.String.format;
|
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
|
||||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
|
||||||
|
|
||||||
// https://github.com/ghubstan/bisq/blob/master/cli/src/main/java/bisq/cli/TradeFormat.java
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@Slf4j
|
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
|
||||||
public class TakeBuyBSQOfferTest extends AbstractTradeTest {
|
|
||||||
|
|
||||||
// Alice is maker / bsq buyer (btc seller), Bob is taker / bsq seller (btc buyer).
|
|
||||||
|
|
||||||
// Maker and Taker fees are in BSQ.
|
|
||||||
private static final String TRADE_FEE_CURRENCY_CODE = BSQ;
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void setUp() {
|
|
||||||
AbstractOfferTest.setUp();
|
|
||||||
createBsqPaymentAccounts();
|
|
||||||
EXPECTED_PROTOCOL_STATUS.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
public void testTakeAlicesSellBTCForBSQOffer(final TestInfo testInfo) {
|
|
||||||
try {
|
|
||||||
// Alice is going to BUY BSQ, but the Offer direction = SELL because it is a
|
|
||||||
// BTC trade; Alice will SELL BTC for BSQ. Bob will send Alice BSQ.
|
|
||||||
// Confused me, but just need to remember there are only BTC offers.
|
|
||||||
var btcTradeDirection = SELL.name();
|
|
||||||
var alicesOffer = aliceClient.createFixedPricedOffer(btcTradeDirection,
|
|
||||||
BSQ,
|
|
||||||
15_000_000L,
|
|
||||||
7_500_000L,
|
|
||||||
"0.000035", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
|
||||||
alicesBsqAcct.getId(),
|
|
||||||
TRADE_FEE_CURRENCY_CODE);
|
|
||||||
log.info("ALICE'S BUY BSQ (SELL BTC) OFFER:\n{}", formatOfferTable(singletonList(alicesOffer), BSQ));
|
|
||||||
genBtcBlocksThenWait(1, 5000);
|
|
||||||
var offerId = alicesOffer.getId();
|
|
||||||
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
var alicesBsqOffers = aliceClient.getMyCryptoCurrencyOffers(btcTradeDirection, BSQ);
|
|
||||||
assertEquals(1, alicesBsqOffers.size());
|
|
||||||
|
|
||||||
var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE);
|
|
||||||
assertNotNull(trade);
|
|
||||||
assertEquals(offerId, trade.getTradeId());
|
|
||||||
// Cache the trade id for the other tests.
|
|
||||||
tradeId = trade.getTradeId();
|
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 6000);
|
|
||||||
alicesBsqOffers = aliceClient.getMyBsqOffersSortedByDate();
|
|
||||||
assertEquals(0, alicesBsqOffers.size());
|
|
||||||
|
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
|
||||||
trade = bobClient.getTrade(trade.getTradeId());
|
|
||||||
|
|
||||||
if (!trade.getIsDepositConfirmed()) {
|
|
||||||
log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getMakerDepositTxId(),
|
|
||||||
trade.getTakerDepositTxId(),
|
|
||||||
i);
|
|
||||||
genBtcBlocksThenWait(1, 4000);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
|
||||||
.setPhase(DEPOSIT_CONFIRMED)
|
|
||||||
.setDepositPublished(true)
|
|
||||||
.setDepositConfirmed(true);
|
|
||||||
verifyExpectedProtocolStatus(trade);
|
|
||||||
logTrade(log, testInfo, "Bob's view after taking offer and deposit confirmed", trade);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 2500);
|
|
||||||
|
|
||||||
if (!trade.getIsDepositConfirmed()) {
|
|
||||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getState(),
|
|
||||||
trade.getPhase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId), true);
|
|
||||||
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId), true);
|
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
|
||||||
fail(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
|
|
||||||
try {
|
|
||||||
var trade = bobClient.getTrade(tradeId);
|
|
||||||
|
|
||||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
|
||||||
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
|
|
||||||
&& t.getPhase().equals(DEPOSIT_CONFIRMED.name());
|
|
||||||
|
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
|
||||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
|
||||||
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot send payment started msg yet.",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getState(),
|
|
||||||
trade.getPhase());
|
|
||||||
sleep(10_000);
|
|
||||||
trade = bobClient.getTrade(tradeId);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
|
||||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, could not send payment started msg.",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getState(),
|
|
||||||
trade.getPhase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
sendBsqPayment(log, bobClient, trade);
|
|
||||||
genBtcBlocksThenWait(1, 2500);
|
|
||||||
bobClient.confirmPaymentStarted(trade.getTradeId());
|
|
||||||
sleep(6000);
|
|
||||||
|
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
|
||||||
trade = aliceClient.getTrade(tradeId);
|
|
||||||
|
|
||||||
if (!trade.getIsFiatSent()) {
|
|
||||||
log.warn("Alice still waiting for trade {} SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
|
|
||||||
trade.getShortId(),
|
|
||||||
i);
|
|
||||||
sleep(5000);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_PAID.
|
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG)
|
|
||||||
.setPhase(FIAT_SENT)
|
|
||||||
.setFiatSent(true);
|
|
||||||
verifyExpectedProtocolStatus(trade);
|
|
||||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId), true);
|
|
||||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Sent)", bobClient.getTrade(tradeId), true);
|
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
|
||||||
fail(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(3)
|
|
||||||
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
|
|
||||||
try {
|
|
||||||
var trade = aliceClient.getTrade(tradeId);
|
|
||||||
|
|
||||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
|
||||||
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
|
|
||||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name()));
|
|
||||||
|
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
|
||||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
|
||||||
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getState(),
|
|
||||||
trade.getPhase());
|
|
||||||
sleep(1000 * 10);
|
|
||||||
trade = aliceClient.getTrade(tradeId);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
|
||||||
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getState(),
|
|
||||||
trade.getPhase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(2000);
|
|
||||||
verifyBsqPaymentHasBeenReceived(log, aliceClient, trade);
|
|
||||||
|
|
||||||
aliceClient.confirmPaymentReceived(trade.getTradeId());
|
|
||||||
sleep(3000);
|
|
||||||
|
|
||||||
trade = aliceClient.getTrade(tradeId);
|
|
||||||
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
|
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
|
||||||
.setPhase(PAYOUT_PUBLISHED)
|
|
||||||
.setPayoutPublished(true)
|
|
||||||
.setFiatReceived(true);
|
|
||||||
verifyExpectedProtocolStatus(trade);
|
|
||||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
|
||||||
|
|
||||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Received)", aliceClient.getTrade(tradeId), true);
|
|
||||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Received)", bobClient.getTrade(tradeId), true);
|
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
|
||||||
fail(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(4)
|
|
||||||
public void testBobsKeepFunds(final TestInfo testInfo) {
|
|
||||||
try {
|
|
||||||
genBtcBlocksThenWait(1, 1000);
|
|
||||||
|
|
||||||
var trade = bobClient.getTrade(tradeId);
|
|
||||||
logTrade(log, testInfo, "Alice's view before keeping funds", trade);
|
|
||||||
|
|
||||||
bobClient.keepFunds(tradeId);
|
|
||||||
genBtcBlocksThenWait(1, 1000);
|
|
||||||
|
|
||||||
trade = bobClient.getTrade(tradeId);
|
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG)
|
|
||||||
.setPhase(PAYOUT_PUBLISHED);
|
|
||||||
verifyExpectedProtocolStatus(trade);
|
|
||||||
logTrade(log, testInfo, "Alice's view after keeping funds", trade);
|
|
||||||
|
|
||||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId), true);
|
|
||||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId), true);
|
|
||||||
|
|
||||||
var alicesBalances = aliceClient.getBalances();
|
|
||||||
log.info("{} Alice's Current Balance:\n{}",
|
|
||||||
testName(testInfo),
|
|
||||||
formatBalancesTbls(alicesBalances));
|
|
||||||
var bobsBalances = bobClient.getBalances();
|
|
||||||
log.info("{} Bob's Current Balance:\n{}",
|
|
||||||
testName(testInfo),
|
|
||||||
formatBalancesTbls(bobsBalances));
|
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
|
||||||
fail(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -34,7 +34,6 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestInfo;
|
import org.junit.jupiter.api.TestInfo;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||||
|
@ -57,9 +56,6 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
|
|
||||||
// Alice is maker/buyer, Bob is taker/seller.
|
// Alice is maker/buyer, Bob is taker/seller.
|
||||||
|
|
||||||
// Maker and Taker fees are in BSQ.
|
|
||||||
private static final String TRADE_FEE_CURRENCY_CODE = BSQ;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
|
public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
|
||||||
|
@ -71,10 +67,8 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
12_500_000L, // min-amount = amount
|
12_500_000L, // min-amount = amount
|
||||||
0.00,
|
0.00,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
alicesUsdAccount.getId(),
|
alicesUsdAccount.getId());
|
||||||
TRADE_FEE_CURRENCY_CODE);
|
|
||||||
var offerId = alicesOffer.getId();
|
var offerId = alicesOffer.getId();
|
||||||
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
// Wait for Alice's AddToOfferBook task.
|
// Wait for Alice's AddToOfferBook task.
|
||||||
// Wait times vary; my logs show >= 2 second delay.
|
// Wait times vary; my logs show >= 2 second delay.
|
||||||
|
@ -83,7 +77,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
assertEquals(1, alicesUsdOffers.size());
|
assertEquals(1, alicesUsdOffers.size());
|
||||||
|
|
||||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
|
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId());
|
||||||
assertNotNull(trade);
|
assertNotNull(trade);
|
||||||
assertEquals(offerId, trade.getTradeId());
|
assertEquals(offerId, trade.getTradeId());
|
||||||
// Cache the trade id for the other tests.
|
// Cache the trade id for the other tests.
|
||||||
|
|
|
@ -1,309 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.apitest.method.trade;
|
|
||||||
|
|
||||||
import bisq.proto.grpc.TradeInfo;
|
|
||||||
|
|
||||||
import io.grpc.StatusRuntimeException;
|
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
|
||||||
import org.junit.jupiter.api.Order;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.TestInfo;
|
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
|
||||||
import static bisq.cli.TableFormat.formatOfferTable;
|
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
|
||||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
|
||||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
|
||||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
|
||||||
import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
|
|
||||||
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
|
|
||||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
|
||||||
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
|
|
||||||
import static java.lang.String.format;
|
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@Slf4j
|
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
|
||||||
public class TakeSellBSQOfferTest extends AbstractTradeTest {
|
|
||||||
|
|
||||||
// Alice is maker / bsq seller (btc buyer), Bob is taker / bsq buyer (btc seller).
|
|
||||||
|
|
||||||
// Maker and Taker fees are in BTC.
|
|
||||||
private static final String TRADE_FEE_CURRENCY_CODE = BTC;
|
|
||||||
|
|
||||||
private static final String WITHDRAWAL_TX_MEMO = "Bob's trade withdrawal";
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void setUp() {
|
|
||||||
AbstractOfferTest.setUp();
|
|
||||||
createBsqPaymentAccounts();
|
|
||||||
EXPECTED_PROTOCOL_STATUS.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
public void testTakeAlicesBuyBTCForBSQOffer(final TestInfo testInfo) {
|
|
||||||
try {
|
|
||||||
// Alice is going to SELL BSQ, but the Offer direction = BUY because it is a
|
|
||||||
// BTC trade; Alice will BUY BTC for BSQ. Alice will send Bob BSQ.
|
|
||||||
// Confused me, but just need to remember there are only BTC offers.
|
|
||||||
var btcTradeDirection = BUY.name();
|
|
||||||
var alicesOffer = aliceClient.createFixedPricedOffer(btcTradeDirection,
|
|
||||||
BSQ,
|
|
||||||
15_000_000L,
|
|
||||||
7_500_000L,
|
|
||||||
"0.000035", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
|
||||||
alicesBsqAcct.getId(),
|
|
||||||
TRADE_FEE_CURRENCY_CODE);
|
|
||||||
log.info("ALICE'S SELL BSQ (BUY BTC) OFFER:\n{}", formatOfferTable(singletonList(alicesOffer), BSQ));
|
|
||||||
genBtcBlocksThenWait(1, 4000);
|
|
||||||
var offerId = alicesOffer.getId();
|
|
||||||
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
var alicesBsqOffers = aliceClient.getMyCryptoCurrencyOffers(btcTradeDirection, BSQ);
|
|
||||||
assertEquals(1, alicesBsqOffers.size());
|
|
||||||
|
|
||||||
var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE);
|
|
||||||
assertNotNull(trade);
|
|
||||||
assertEquals(offerId, trade.getTradeId());
|
|
||||||
// Cache the trade id for the other tests.
|
|
||||||
tradeId = trade.getTradeId();
|
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 6000);
|
|
||||||
alicesBsqOffers = aliceClient.getMyBsqOffersSortedByDate();
|
|
||||||
assertEquals(0, alicesBsqOffers.size());
|
|
||||||
|
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
|
||||||
trade = bobClient.getTrade(trade.getTradeId());
|
|
||||||
|
|
||||||
if (!trade.getIsDepositConfirmed()) {
|
|
||||||
log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getMakerDepositTxId(),
|
|
||||||
trade.getTakerDepositTxId(),
|
|
||||||
i);
|
|
||||||
genBtcBlocksThenWait(1, 4000);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
|
||||||
.setPhase(DEPOSIT_CONFIRMED)
|
|
||||||
.setDepositPublished(true)
|
|
||||||
.setDepositConfirmed(true);
|
|
||||||
verifyExpectedProtocolStatus(trade);
|
|
||||||
logTrade(log, testInfo, "Bob's view after taking offer and deposit confirmed", trade);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 2500);
|
|
||||||
|
|
||||||
if (!trade.getIsDepositConfirmed()) {
|
|
||||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getState(),
|
|
||||||
trade.getPhase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
logTrade(log, testInfo, "Alice's Maker/Seller View", aliceClient.getTrade(tradeId), true);
|
|
||||||
logTrade(log, testInfo, "Bob's Taker/Buyer View", bobClient.getTrade(tradeId), true);
|
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
|
||||||
fail(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
|
||||||
try {
|
|
||||||
var trade = aliceClient.getTrade(tradeId);
|
|
||||||
|
|
||||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
|
||||||
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
|
|
||||||
&& t.getPhase().equals(DEPOSIT_CONFIRMED.name());
|
|
||||||
|
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
|
||||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
|
||||||
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot send payment started msg yet.",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getState(),
|
|
||||||
trade.getPhase());
|
|
||||||
sleep(10_000);
|
|
||||||
trade = aliceClient.getTrade(tradeId);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
|
||||||
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not send payment started msg.",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getState(),
|
|
||||||
trade.getPhase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
sendBsqPayment(log, aliceClient, trade);
|
|
||||||
genBtcBlocksThenWait(1, 2500);
|
|
||||||
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
|
||||||
sleep(6000);
|
|
||||||
|
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
|
||||||
trade = bobClient.getTrade(tradeId);
|
|
||||||
|
|
||||||
if (!trade.getIsFiatSent()) {
|
|
||||||
log.warn("Bob still waiting for trade {} SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
|
|
||||||
trade.getShortId(),
|
|
||||||
i);
|
|
||||||
sleep(5000);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_PAID.
|
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG)
|
|
||||||
.setPhase(FIAT_SENT)
|
|
||||||
.setFiatSent(true);
|
|
||||||
verifyExpectedProtocolStatus(trade);
|
|
||||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Sent)", aliceClient.getTrade(tradeId), true);
|
|
||||||
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Sent)", bobClient.getTrade(tradeId), true);
|
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
|
||||||
fail(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(3)
|
|
||||||
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
|
||||||
try {
|
|
||||||
var trade = bobClient.getTrade(tradeId);
|
|
||||||
|
|
||||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
|
||||||
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
|
|
||||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name()));
|
|
||||||
|
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
|
||||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
|
||||||
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getState(),
|
|
||||||
trade.getPhase());
|
|
||||||
sleep(1000 * 10);
|
|
||||||
trade = bobClient.getTrade(tradeId);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
|
||||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
|
|
||||||
trade.getShortId(),
|
|
||||||
trade.getState(),
|
|
||||||
trade.getPhase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(2000);
|
|
||||||
verifyBsqPaymentHasBeenReceived(log, bobClient, trade);
|
|
||||||
|
|
||||||
bobClient.confirmPaymentReceived(trade.getTradeId());
|
|
||||||
sleep(3000);
|
|
||||||
|
|
||||||
trade = bobClient.getTrade(tradeId);
|
|
||||||
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_PAID.
|
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
|
||||||
.setPhase(PAYOUT_PUBLISHED)
|
|
||||||
.setPayoutPublished(true)
|
|
||||||
.setFiatReceived(true);
|
|
||||||
verifyExpectedProtocolStatus(trade);
|
|
||||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
|
||||||
|
|
||||||
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Received)", aliceClient.getTrade(tradeId), true);
|
|
||||||
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Received)", bobClient.getTrade(tradeId), true);
|
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
|
||||||
fail(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(4)
|
|
||||||
public void testAlicesBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
|
|
||||||
try {
|
|
||||||
genBtcBlocksThenWait(1, 1000);
|
|
||||||
|
|
||||||
var trade = aliceClient.getTrade(tradeId);
|
|
||||||
logTrade(log, testInfo, "Alice's view before withdrawing BTC funds to external wallet", trade);
|
|
||||||
|
|
||||||
String toAddress = bitcoinCli.getNewBtcAddress();
|
|
||||||
aliceClient.withdrawFunds(tradeId, toAddress, WITHDRAWAL_TX_MEMO);
|
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 1000);
|
|
||||||
|
|
||||||
trade = aliceClient.getTrade(tradeId);
|
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(WITHDRAW_COMPLETED)
|
|
||||||
.setPhase(WITHDRAWN)
|
|
||||||
.setWithdrawn(true);
|
|
||||||
verifyExpectedProtocolStatus(trade);
|
|
||||||
logTrade(log, testInfo, "Alice's view after withdrawing funds to external wallet", trade);
|
|
||||||
|
|
||||||
|
|
||||||
logTrade(log, testInfo, "Alice's Maker/Seller View (Done)", aliceClient.getTrade(tradeId), true);
|
|
||||||
logTrade(log, testInfo, "Bob's Taker/Buyer View (Done)", bobClient.getTrade(tradeId), true);
|
|
||||||
|
|
||||||
var alicesBalances = aliceClient.getBalances();
|
|
||||||
log.info("{} Alice's Current Balance:\n{}",
|
|
||||||
testName(testInfo),
|
|
||||||
formatBalancesTbls(alicesBalances));
|
|
||||||
var bobsBalances = bobClient.getBalances();
|
|
||||||
log.info("{} Bob's Current Balance:\n{}",
|
|
||||||
testName(testInfo),
|
|
||||||
formatBalancesTbls(bobsBalances));
|
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
|
||||||
fail(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -58,9 +58,6 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
|
|
||||||
// Alice is maker/seller, Bob is taker/buyer.
|
// Alice is maker/seller, Bob is taker/buyer.
|
||||||
|
|
||||||
// Maker and Taker fees are in BTC.
|
|
||||||
private static final String TRADE_FEE_CURRENCY_CODE = BTC;
|
|
||||||
|
|
||||||
private static final String WITHDRAWAL_TX_MEMO = "Bob's trade withdrawal";
|
private static final String WITHDRAWAL_TX_MEMO = "Bob's trade withdrawal";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -74,10 +71,8 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
12_500_000L, // min-amount = amount
|
12_500_000L, // min-amount = amount
|
||||||
0.00,
|
0.00,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
alicesUsdAccount.getId(),
|
alicesUsdAccount.getId());
|
||||||
TRADE_FEE_CURRENCY_CODE);
|
|
||||||
var offerId = alicesOffer.getId();
|
var offerId = alicesOffer.getId();
|
||||||
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
|
||||||
|
|
||||||
// Wait for Alice's AddToOfferBook task.
|
// Wait for Alice's AddToOfferBook task.
|
||||||
// Wait times vary; my logs show >= 2 second delay, but taking sell offers
|
// Wait times vary; my logs show >= 2 second delay, but taking sell offers
|
||||||
|
@ -87,7 +82,7 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
assertEquals(1, alicesUsdOffers.size());
|
assertEquals(1, alicesUsdOffers.size());
|
||||||
|
|
||||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
|
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId());
|
||||||
assertNotNull(trade);
|
assertNotNull(trade);
|
||||||
assertEquals(offerId, trade.getTradeId());
|
assertEquals(offerId, trade.getTradeId());
|
||||||
// Cache the trade id for the other tests.
|
// Cache the trade id for the other tests.
|
||||||
|
|
|
@ -1,203 +0,0 @@
|
||||||
package bisq.apitest.method.wallet;
|
|
||||||
|
|
||||||
import bisq.proto.grpc.BsqBalanceInfo;
|
|
||||||
|
|
||||||
import org.bitcoinj.core.LegacyAddress;
|
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Order;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.TestInfo;
|
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
|
||||||
|
|
||||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
|
||||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
|
||||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
|
||||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
|
||||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
|
||||||
import static bisq.apitest.method.wallet.WalletTestUtil.ALICES_INITIAL_BSQ_BALANCES;
|
|
||||||
import static bisq.apitest.method.wallet.WalletTestUtil.BOBS_INITIAL_BSQ_BALANCES;
|
|
||||||
import static bisq.apitest.method.wallet.WalletTestUtil.bsqBalanceModel;
|
|
||||||
import static bisq.apitest.method.wallet.WalletTestUtil.verifyBsqBalances;
|
|
||||||
import static bisq.cli.TableFormat.formatBsqBalanceInfoTbl;
|
|
||||||
import static org.bitcoinj.core.NetworkParameters.PAYMENT_PROTOCOL_ID_MAINNET;
|
|
||||||
import static org.bitcoinj.core.NetworkParameters.PAYMENT_PROTOCOL_ID_REGTEST;
|
|
||||||
import static org.bitcoinj.core.NetworkParameters.PAYMENT_PROTOCOL_ID_TESTNET;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import bisq.apitest.config.BisqAppConfig;
|
|
||||||
import bisq.apitest.method.MethodTest;
|
|
||||||
import bisq.cli.GrpcClient;
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@Slf4j
|
|
||||||
@TestMethodOrder(OrderAnnotation.class)
|
|
||||||
public class BsqWalletTest extends MethodTest {
|
|
||||||
|
|
||||||
private static final String SEND_BSQ_AMOUNT = "25000.50";
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void setUp() {
|
|
||||||
startSupportingApps(false,
|
|
||||||
true,
|
|
||||||
bitcoind,
|
|
||||||
seednode,
|
|
||||||
arbdaemon,
|
|
||||||
alicedaemon,
|
|
||||||
bobdaemon);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
public void testGetUnusedBsqAddress() {
|
|
||||||
var address = aliceClient.getUnusedBsqAddress();
|
|
||||||
assertFalse(address.isEmpty());
|
|
||||||
assertTrue(address.startsWith("B"));
|
|
||||||
|
|
||||||
NetworkParameters networkParameters = LegacyAddress.getParametersFromAddress(address.substring(1));
|
|
||||||
String addressNetwork = networkParameters.getPaymentProtocolId();
|
|
||||||
assertNotEquals(PAYMENT_PROTOCOL_ID_MAINNET, addressNetwork);
|
|
||||||
// TODO Fix bug causing the regtest bsq address network to be evaluated as 'testnet' here.
|
|
||||||
assertTrue(addressNetwork.equals(PAYMENT_PROTOCOL_ID_TESTNET)
|
|
||||||
|| addressNetwork.equals(PAYMENT_PROTOCOL_ID_REGTEST));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
public void testInitialBsqBalances(final TestInfo testInfo) {
|
|
||||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBsqBalances();
|
|
||||||
log.debug("{} -> Alice's BSQ Initial Balances -> \n{}",
|
|
||||||
testName(testInfo),
|
|
||||||
formatBsqBalanceInfoTbl(alicesBsqBalances));
|
|
||||||
verifyBsqBalances(ALICES_INITIAL_BSQ_BALANCES, alicesBsqBalances);
|
|
||||||
|
|
||||||
BsqBalanceInfo bobsBsqBalances = bobClient.getBsqBalances();
|
|
||||||
log.debug("{} -> Bob's BSQ Initial Balances -> \n{}",
|
|
||||||
testName(testInfo),
|
|
||||||
formatBsqBalanceInfoTbl(bobsBsqBalances));
|
|
||||||
verifyBsqBalances(BOBS_INITIAL_BSQ_BALANCES, bobsBsqBalances);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(3)
|
|
||||||
public void testSendBsqAndCheckBalancesBeforeGeneratingBtcBlock(final TestInfo testInfo) {
|
|
||||||
String bobsBsqAddress = bobClient.getUnusedBsqAddress();
|
|
||||||
aliceClient.sendBsq(bobsBsqAddress, SEND_BSQ_AMOUNT, "100");
|
|
||||||
sleep(2000);
|
|
||||||
|
|
||||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBsqBalances();
|
|
||||||
BsqBalanceInfo bobsBsqBalances = waitForNonZeroBsqUnverifiedBalance(bobClient);
|
|
||||||
|
|
||||||
log.debug("BSQ Balances Before BTC Block Gen...");
|
|
||||||
printBobAndAliceBsqBalances(testInfo,
|
|
||||||
bobsBsqBalances,
|
|
||||||
alicesBsqBalances,
|
|
||||||
alicedaemon);
|
|
||||||
|
|
||||||
verifyBsqBalances(bsqBalanceModel(150000000,
|
|
||||||
2500050,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0),
|
|
||||||
bobsBsqBalances);
|
|
||||||
|
|
||||||
verifyBsqBalances(bsqBalanceModel(97499950,
|
|
||||||
97499950,
|
|
||||||
97499950,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0),
|
|
||||||
alicesBsqBalances);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(4)
|
|
||||||
public void testBalancesAfterSendingBsqAndGeneratingBtcBlock(final TestInfo testInfo) {
|
|
||||||
// There is a wallet persist delay; we have to
|
|
||||||
// wait for both wallets to be saved to disk.
|
|
||||||
genBtcBlocksThenWait(1, 4000);
|
|
||||||
|
|
||||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBalances().getBsq();
|
|
||||||
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableConfirmedBalance(bobClient, 150000000);
|
|
||||||
|
|
||||||
log.debug("See Available Confirmed BSQ Balances...");
|
|
||||||
printBobAndAliceBsqBalances(testInfo,
|
|
||||||
bobsBsqBalances,
|
|
||||||
alicesBsqBalances,
|
|
||||||
alicedaemon);
|
|
||||||
|
|
||||||
verifyBsqBalances(bsqBalanceModel(152500050,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0),
|
|
||||||
bobsBsqBalances);
|
|
||||||
|
|
||||||
verifyBsqBalances(bsqBalanceModel(97499950,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0),
|
|
||||||
alicesBsqBalances);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterAll
|
|
||||||
public static void tearDown() {
|
|
||||||
tearDownScaffold();
|
|
||||||
}
|
|
||||||
|
|
||||||
private BsqBalanceInfo waitForNonZeroBsqUnverifiedBalance(GrpcClient grpcClient) {
|
|
||||||
// A BSQ recipient needs to wait for her daemon to detect a new tx.
|
|
||||||
// Loop here until her unverifiedBalance != 0, or give up after 15 seconds.
|
|
||||||
// A slow test is preferred over a flaky test.
|
|
||||||
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
|
|
||||||
for (int numRequests = 1; numRequests <= 15 && bsqBalance.getUnverifiedBalance() == 0; numRequests++) {
|
|
||||||
sleep(1000);
|
|
||||||
bsqBalance = grpcClient.getBsqBalances();
|
|
||||||
}
|
|
||||||
return bsqBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BsqBalanceInfo waitForBsqNewAvailableConfirmedBalance(GrpcClient grpcClient,
|
|
||||||
long staleBalance) {
|
|
||||||
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
|
|
||||||
for (int numRequests = 1;
|
|
||||||
numRequests <= 15 && bsqBalance.getAvailableConfirmedBalance() == staleBalance;
|
|
||||||
numRequests++) {
|
|
||||||
sleep(1000);
|
|
||||||
bsqBalance = grpcClient.getBsqBalances();
|
|
||||||
}
|
|
||||||
return bsqBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
|
||||||
private void printBobAndAliceBsqBalances(final TestInfo testInfo,
|
|
||||||
BsqBalanceInfo bobsBsqBalances,
|
|
||||||
BsqBalanceInfo alicesBsqBalances,
|
|
||||||
BisqAppConfig senderApp) {
|
|
||||||
log.debug("{} -> Bob's BSQ Balances After {} {} BSQ-> \n{}",
|
|
||||||
testName(testInfo),
|
|
||||||
senderApp.equals(bobdaemon) ? "Sending" : "Receiving",
|
|
||||||
SEND_BSQ_AMOUNT,
|
|
||||||
formatBsqBalanceInfoTbl(bobsBsqBalances));
|
|
||||||
|
|
||||||
log.debug("{} -> Alice's Balances After {} {} BSQ-> \n{}",
|
|
||||||
testName(testInfo),
|
|
||||||
senderApp.equals(alicedaemon) ? "Sending" : "Receiving",
|
|
||||||
SEND_BSQ_AMOUNT,
|
|
||||||
formatBsqBalanceInfoTbl(alicesBsqBalances));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
package bisq.apitest.method.wallet;
|
package bisq.apitest.method.wallet;
|
||||||
|
|
||||||
import bisq.proto.grpc.BsqBalanceInfo;
|
|
||||||
import bisq.proto.grpc.BtcBalanceInfo;
|
import bisq.proto.grpc.BtcBalanceInfo;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -10,7 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class WalletTestUtil {
|
public class WalletTestUtil {
|
||||||
|
|
||||||
// All api tests depend on the DAO / regtest environment, and Bob & Alice's wallets
|
// All api tests depend on the regtest environment, and Bob & Alice's wallets
|
||||||
// are initialized with 10 BTC during the scaffolding setup.
|
// are initialized with 10 BTC during the scaffolding setup.
|
||||||
public static final bisq.core.api.model.BtcBalanceInfo INITIAL_BTC_BALANCES =
|
public static final bisq.core.api.model.BtcBalanceInfo INITIAL_BTC_BALANCES =
|
||||||
bisq.core.api.model.BtcBalanceInfo.valueOf(1000000000,
|
bisq.core.api.model.BtcBalanceInfo.valueOf(1000000000,
|
||||||
|
@ -19,49 +18,6 @@ public class WalletTestUtil {
|
||||||
0);
|
0);
|
||||||
|
|
||||||
|
|
||||||
// Alice's regtest BSQ wallet is initialized with 1,000,000 BSQ.
|
|
||||||
public static final bisq.core.api.model.BsqBalanceInfo ALICES_INITIAL_BSQ_BALANCES =
|
|
||||||
bsqBalanceModel(100000000,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0);
|
|
||||||
|
|
||||||
// Bob's regtest BSQ wallet is initialized with 1,500,000 BSQ.
|
|
||||||
public static final bisq.core.api.model.BsqBalanceInfo BOBS_INITIAL_BSQ_BALANCES =
|
|
||||||
bsqBalanceModel(150000000,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0);
|
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
|
||||||
public static bisq.core.api.model.BsqBalanceInfo bsqBalanceModel(long availableConfirmedBalance,
|
|
||||||
long unverifiedBalance,
|
|
||||||
long unconfirmedChangeBalance,
|
|
||||||
long lockedForVotingBalance,
|
|
||||||
long lockupBondsBalance,
|
|
||||||
long unlockingBondsBalance) {
|
|
||||||
return bisq.core.api.model.BsqBalanceInfo.valueOf(availableConfirmedBalance,
|
|
||||||
unverifiedBalance,
|
|
||||||
unconfirmedChangeBalance,
|
|
||||||
lockedForVotingBalance,
|
|
||||||
lockupBondsBalance,
|
|
||||||
unlockingBondsBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void verifyBsqBalances(bisq.core.api.model.BsqBalanceInfo expected,
|
|
||||||
BsqBalanceInfo actual) {
|
|
||||||
assertEquals(expected.getAvailableConfirmedBalance(), actual.getAvailableConfirmedBalance());
|
|
||||||
assertEquals(expected.getUnverifiedBalance(), actual.getUnverifiedBalance());
|
|
||||||
assertEquals(expected.getUnconfirmedChangeBalance(), actual.getUnconfirmedChangeBalance());
|
|
||||||
assertEquals(expected.getLockedForVotingBalance(), actual.getLockedForVotingBalance());
|
|
||||||
assertEquals(expected.getLockupBondsBalance(), actual.getLockupBondsBalance());
|
|
||||||
assertEquals(expected.getUnlockingBondsBalance(), actual.getUnlockingBondsBalance());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void verifyBtcBalances(bisq.core.api.model.BtcBalanceInfo expected,
|
public static void verifyBtcBalances(bisq.core.api.model.BtcBalanceInfo expected,
|
||||||
BtcBalanceInfo actual) {
|
BtcBalanceInfo actual) {
|
||||||
assertEquals(expected.getAvailableBalance(), actual.getAvailableBalance());
|
assertEquals(expected.getAvailableBalance(), actual.getAvailableBalance());
|
||||||
|
|
|
@ -29,7 +29,6 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||||
import bisq.apitest.method.offer.CancelOfferTest;
|
import bisq.apitest.method.offer.CancelOfferTest;
|
||||||
import bisq.apitest.method.offer.CreateBSQOffersTest;
|
|
||||||
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
||||||
import bisq.apitest.method.offer.CreateOfferUsingMarketPriceMarginTest;
|
import bisq.apitest.method.offer.CreateOfferUsingMarketPriceMarginTest;
|
||||||
import bisq.apitest.method.offer.ValidateCreateOfferTest;
|
import bisq.apitest.method.offer.ValidateCreateOfferTest;
|
||||||
|
@ -72,17 +71,4 @@ public class OfferTest extends AbstractOfferTest {
|
||||||
test.testCreateGBPXMRSellOfferMinus1Point5PctPriceMargin();
|
test.testCreateGBPXMRSellOfferMinus1Point5PctPriceMargin();
|
||||||
test.testCreateBRLXMRSellOffer6Point55PctPriceMargin();
|
test.testCreateBRLXMRSellOffer6Point55PctPriceMargin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(5)
|
|
||||||
public void testCreateBSQOffersTest() {
|
|
||||||
CreateBSQOffersTest test = new CreateBSQOffersTest();
|
|
||||||
CreateBSQOffersTest.createBsqPaymentAccounts();
|
|
||||||
test.testCreateBuy1BTCFor20KBSQOffer();
|
|
||||||
test.testCreateSell1BTCFor20KBSQOffer();
|
|
||||||
test.testCreateBuyBTCWith1To2KBSQOffer();
|
|
||||||
test.testCreateSellBTCFor5To10KBSQOffer();
|
|
||||||
test.testGetAllMyBsqOffers();
|
|
||||||
test.testGetAvailableBsqOffers();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,9 +29,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
|
|
||||||
import bisq.apitest.method.trade.AbstractTradeTest;
|
import bisq.apitest.method.trade.AbstractTradeTest;
|
||||||
import bisq.apitest.method.trade.TakeBuyBSQOfferTest;
|
|
||||||
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
||||||
import bisq.apitest.method.trade.TakeSellBSQOfferTest;
|
|
||||||
import bisq.apitest.method.trade.TakeSellBTCOfferTest;
|
import bisq.apitest.method.trade.TakeSellBTCOfferTest;
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,26 +61,4 @@ public class TradeTest extends AbstractTradeTest {
|
||||||
test.testAlicesConfirmPaymentReceived(testInfo);
|
test.testAlicesConfirmPaymentReceived(testInfo);
|
||||||
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
|
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(3)
|
|
||||||
public void testTakeBuyBSQOffer(final TestInfo testInfo) {
|
|
||||||
TakeBuyBSQOfferTest test = new TakeBuyBSQOfferTest();
|
|
||||||
TakeBuyBSQOfferTest.createBsqPaymentAccounts();
|
|
||||||
test.testTakeAlicesSellBTCForBSQOffer(testInfo);
|
|
||||||
test.testBobsConfirmPaymentStarted(testInfo);
|
|
||||||
test.testAlicesConfirmPaymentReceived(testInfo);
|
|
||||||
test.testBobsKeepFunds(testInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(4)
|
|
||||||
public void testTakeSellBSQOffer(final TestInfo testInfo) {
|
|
||||||
TakeSellBSQOfferTest test = new TakeSellBSQOfferTest();
|
|
||||||
TakeSellBSQOfferTest.createBsqPaymentAccounts();
|
|
||||||
test.testTakeAlicesBuyBTCForBSQOffer(testInfo);
|
|
||||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
|
||||||
test.testBobsConfirmPaymentReceived(testInfo);
|
|
||||||
test.testAlicesBtcWithdrawalToExternalAddress(testInfo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||||
|
|
||||||
|
|
||||||
import bisq.apitest.method.MethodTest;
|
import bisq.apitest.method.MethodTest;
|
||||||
import bisq.apitest.method.wallet.BsqWalletTest;
|
|
||||||
import bisq.apitest.method.wallet.BtcTxFeeRateTest;
|
import bisq.apitest.method.wallet.BtcTxFeeRateTest;
|
||||||
import bisq.apitest.method.wallet.BtcWalletTest;
|
import bisq.apitest.method.wallet.BtcWalletTest;
|
||||||
import bisq.apitest.method.wallet.WalletProtectionTest;
|
import bisq.apitest.method.wallet.WalletProtectionTest;
|
||||||
|
@ -70,17 +69,6 @@ public class WalletTest extends MethodTest {
|
||||||
btcWalletTest.testAliceSendBTCToBob(testInfo);
|
btcWalletTest.testAliceSendBTCToBob(testInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
public void testBsqWalletFunding(final TestInfo testInfo) {
|
|
||||||
BsqWalletTest bsqWalletTest = new BsqWalletTest();
|
|
||||||
|
|
||||||
bsqWalletTest.testGetUnusedBsqAddress();
|
|
||||||
bsqWalletTest.testInitialBsqBalances(testInfo);
|
|
||||||
bsqWalletTest.testSendBsqAndCheckBalancesBeforeGeneratingBtcBlock(testInfo);
|
|
||||||
bsqWalletTest.testBalancesAfterSendingBsqAndGeneratingBtcBlock(testInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(3)
|
@Order(3)
|
||||||
public void testWalletProtection() {
|
public void testWalletProtection() {
|
||||||
|
|
|
@ -58,7 +58,7 @@ public class BotClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns current BSQ and BTC balance information.
|
* Returns current balance information.
|
||||||
* @return BalancesInfo
|
* @return BalancesInfo
|
||||||
*/
|
*/
|
||||||
public BalancesInfo getBalance() {
|
public BalancesInfo getBalance() {
|
||||||
|
@ -124,7 +124,6 @@ public class BotClient {
|
||||||
* @param minAmountInSatoshis
|
* @param minAmountInSatoshis
|
||||||
* @param priceMarginAsPercent
|
* @param priceMarginAsPercent
|
||||||
* @param securityDepositAsPercent
|
* @param securityDepositAsPercent
|
||||||
* @param feeCurrency
|
|
||||||
* @return OfferInfo
|
* @return OfferInfo
|
||||||
*/
|
*/
|
||||||
public OfferInfo createOfferAtMarketBasedPrice(PaymentAccount paymentAccount,
|
public OfferInfo createOfferAtMarketBasedPrice(PaymentAccount paymentAccount,
|
||||||
|
@ -133,16 +132,14 @@ public class BotClient {
|
||||||
long amountInSatoshis,
|
long amountInSatoshis,
|
||||||
long minAmountInSatoshis,
|
long minAmountInSatoshis,
|
||||||
double priceMarginAsPercent,
|
double priceMarginAsPercent,
|
||||||
double securityDepositAsPercent,
|
double securityDepositAsPercent) {
|
||||||
String feeCurrency) {
|
|
||||||
return grpcClient.createMarketBasedPricedOffer(direction,
|
return grpcClient.createMarketBasedPricedOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amountInSatoshis,
|
amountInSatoshis,
|
||||||
minAmountInSatoshis,
|
minAmountInSatoshis,
|
||||||
priceMarginAsPercent,
|
priceMarginAsPercent,
|
||||||
securityDepositAsPercent,
|
securityDepositAsPercent,
|
||||||
paymentAccount.getId(),
|
paymentAccount.getId());
|
||||||
feeCurrency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,7 +151,6 @@ public class BotClient {
|
||||||
* @param minAmountInSatoshis
|
* @param minAmountInSatoshis
|
||||||
* @param fixedOfferPriceAsString
|
* @param fixedOfferPriceAsString
|
||||||
* @param securityDepositAsPercent
|
* @param securityDepositAsPercent
|
||||||
* @param feeCurrency
|
|
||||||
* @return OfferInfo
|
* @return OfferInfo
|
||||||
*/
|
*/
|
||||||
public OfferInfo createOfferAtFixedPrice(PaymentAccount paymentAccount,
|
public OfferInfo createOfferAtFixedPrice(PaymentAccount paymentAccount,
|
||||||
|
@ -163,20 +159,18 @@ public class BotClient {
|
||||||
long amountInSatoshis,
|
long amountInSatoshis,
|
||||||
long minAmountInSatoshis,
|
long minAmountInSatoshis,
|
||||||
String fixedOfferPriceAsString,
|
String fixedOfferPriceAsString,
|
||||||
double securityDepositAsPercent,
|
double securityDepositAsPercent) {
|
||||||
String feeCurrency) {
|
|
||||||
return grpcClient.createFixedPricedOffer(direction,
|
return grpcClient.createFixedPricedOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amountInSatoshis,
|
amountInSatoshis,
|
||||||
minAmountInSatoshis,
|
minAmountInSatoshis,
|
||||||
fixedOfferPriceAsString,
|
fixedOfferPriceAsString,
|
||||||
securityDepositAsPercent,
|
securityDepositAsPercent,
|
||||||
paymentAccount.getId(),
|
paymentAccount.getId());
|
||||||
feeCurrency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeInfo takeOffer(String offerId, PaymentAccount paymentAccount, String feeCurrency) {
|
public TradeInfo takeOffer(String offerId, PaymentAccount paymentAccount) {
|
||||||
return grpcClient.takeOffer(offerId, paymentAccount.getId(), feeCurrency);
|
return grpcClient.takeOffer(offerId, paymentAccount.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -95,8 +95,6 @@ public class RandomOffer {
|
||||||
private final boolean useMarketBasedPrice;
|
private final boolean useMarketBasedPrice;
|
||||||
@Getter
|
@Getter
|
||||||
private final double priceMargin;
|
private final double priceMargin;
|
||||||
@Getter
|
|
||||||
private final String feeCurrency;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private String fixedOfferPrice = "0";
|
private String fixedOfferPrice = "0";
|
||||||
|
@ -114,7 +112,6 @@ public class RandomOffer {
|
||||||
this.minAmount = nextMinAmount.get();
|
this.minAmount = nextMinAmount.get();
|
||||||
this.useMarketBasedPrice = RANDOM.nextBoolean();
|
this.useMarketBasedPrice = RANDOM.nextBoolean();
|
||||||
this.priceMargin = nextPriceMargin.get();
|
this.priceMargin = nextPriceMargin.get();
|
||||||
this.feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RandomOffer create() throws InvalidRandomOfferException {
|
public RandomOffer create() throws InvalidRandomOfferException {
|
||||||
|
@ -127,8 +124,7 @@ public class RandomOffer {
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
priceMargin,
|
priceMargin,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent());
|
||||||
feeCurrency);
|
|
||||||
} else {
|
} else {
|
||||||
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
||||||
direction,
|
direction,
|
||||||
|
@ -136,8 +132,7 @@ public class RandomOffer {
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
fixedOfferPrice,
|
fixedOfferPrice,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent());
|
||||||
feeCurrency);
|
|
||||||
}
|
}
|
||||||
this.id = offer.getId();
|
this.id = offer.getId();
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -102,13 +102,12 @@ public class TakerBotProtocol extends BotProtocol {
|
||||||
private final Function<OfferInfo, TradeInfo> takeOffer = (offer) -> {
|
private final Function<OfferInfo, TradeInfo> takeOffer = (offer) -> {
|
||||||
initProtocolStep.accept(TAKE_OFFER);
|
initProtocolStep.accept(TAKE_OFFER);
|
||||||
checkIfShutdownCalled("Interrupted before taking offer.");
|
checkIfShutdownCalled("Interrupted before taking offer.");
|
||||||
String feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
return botClient.takeOffer(offer.getId(), paymentAccount);
|
||||||
return botClient.takeOffer(offer.getId(), paymentAccount, feeCurrency);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private void createMakeOfferScript() {
|
private void createMakeOfferScript() {
|
||||||
String direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
|
String direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
|
||||||
String feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
String feeCurrency = "BTC";
|
||||||
boolean createMarginPricedOffer = RANDOM.nextBoolean();
|
boolean createMarginPricedOffer = RANDOM.nextBoolean();
|
||||||
// If not using an F2F account, don't go over possible 0.01 BTC
|
// If not using an F2F account, don't go over possible 0.01 BTC
|
||||||
// limit if account is not signed.
|
// limit if account is not signed.
|
||||||
|
|
|
@ -130,7 +130,7 @@ public class BashScriptGenerator {
|
||||||
cliBase,
|
cliBase,
|
||||||
offer.getDirection(),
|
offer.getDirection(),
|
||||||
offer.getCounterCurrencyCode());
|
offer.getCounterCurrencyCode());
|
||||||
String takeOfferCmd = format("%s takeoffer --offer-id=%s --payment-account=%s --fee-currency=BSQ",
|
String takeOfferCmd = format("%s takeoffer --offer-id=%s --payment-account=%s",
|
||||||
cliBase,
|
cliBase,
|
||||||
offer.getId(),
|
offer.getId(),
|
||||||
this.getPaymentAccountId());
|
this.getPaymentAccountId());
|
||||||
|
|
|
@ -27,7 +27,6 @@ public class PrintTool {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
new AssetRegistry().stream()
|
new AssetRegistry().stream()
|
||||||
.sorted(Comparator.comparing(o -> o.getName().toLowerCase()))
|
.sorted(Comparator.comparing(o -> o.getName().toLowerCase()))
|
||||||
.filter(e -> !e.getTickerSymbol().equals("BSQ")) // BSQ is not out yet...
|
|
||||||
.filter(e -> !e.getTickerSymbol().equals("BTC"))
|
.filter(e -> !e.getTickerSymbol().equals("BTC"))
|
||||||
.map(e -> new Pair(e.getName(), e.getTickerSymbol())) // We want to get rid of duplicated entries for regtest/testnet...
|
.map(e -> new Pair(e.getName(), e.getTickerSymbol())) // We want to get rid of duplicated entries for regtest/testnet...
|
||||||
.distinct()
|
.distinct()
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.asset.coins;
|
|
||||||
|
|
||||||
import bisq.asset.AddressValidationResult;
|
|
||||||
import bisq.asset.Base58AddressValidator;
|
|
||||||
import bisq.asset.Coin;
|
|
||||||
|
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
|
||||||
import org.bitcoinj.params.MainNetParams;
|
|
||||||
import org.bitcoinj.params.RegTestParams;
|
|
||||||
import org.bitcoinj.params.TestNet3Params;
|
|
||||||
|
|
||||||
public class BSQ extends Coin {
|
|
||||||
|
|
||||||
public BSQ(Network network, NetworkParameters networkParameters) {
|
|
||||||
super("BSQ", "BSQ", new BSQAddressValidator(networkParameters), network);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class Mainnet extends BSQ {
|
|
||||||
|
|
||||||
public Mainnet() {
|
|
||||||
super(Network.MAINNET, MainNetParams.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class Testnet extends BSQ {
|
|
||||||
|
|
||||||
public Testnet() {
|
|
||||||
super(Network.TESTNET, TestNet3Params.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class Regtest extends BSQ {
|
|
||||||
|
|
||||||
public Regtest() {
|
|
||||||
super(Network.STAGENET, RegTestParams.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class BSQAddressValidator extends Base58AddressValidator {
|
|
||||||
|
|
||||||
public BSQAddressValidator(NetworkParameters networkParameters) {
|
|
||||||
super(networkParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AddressValidationResult validate(String address) {
|
|
||||||
if (!address.startsWith("B"))
|
|
||||||
return AddressValidationResult.invalidAddress("BSQ address must start with 'B'");
|
|
||||||
|
|
||||||
String addressAsBtc = address.substring(1, address.length());
|
|
||||||
|
|
||||||
return super.validate(addressAsBtc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,9 +19,6 @@ bisq.asset.coins.BitDaric
|
||||||
bisq.asset.coins.Bitmark
|
bisq.asset.coins.Bitmark
|
||||||
bisq.asset.coins.Bitzec
|
bisq.asset.coins.Bitzec
|
||||||
bisq.asset.coins.Blur
|
bisq.asset.coins.Blur
|
||||||
bisq.asset.coins.BSQ$Mainnet
|
|
||||||
bisq.asset.coins.BSQ$Regtest
|
|
||||||
bisq.asset.coins.BSQ$Testnet
|
|
||||||
bisq.asset.coins.BurntBlackCoin
|
bisq.asset.coins.BurntBlackCoin
|
||||||
bisq.asset.coins.Cash2
|
bisq.asset.coins.Cash2
|
||||||
bisq.asset.coins.Chaucha
|
bisq.asset.coins.Chaucha
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.asset.coins;
|
|
||||||
|
|
||||||
import bisq.asset.AbstractAssetTest;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class BSQTest extends AbstractAssetTest {
|
|
||||||
|
|
||||||
public BSQTest() {
|
|
||||||
super(new BSQ.Mainnet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidAddresses() {
|
|
||||||
assertValidAddress("B17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem");
|
|
||||||
assertValidAddress("B3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX");
|
|
||||||
assertValidAddress("B1111111111111111111114oLvT2");
|
|
||||||
assertValidAddress("B1BitcoinEaterAddressDontSendf59kuE");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInvalidAddresses() {
|
|
||||||
assertInvalidAddress("B17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhemqq");
|
|
||||||
assertInvalidAddress("B17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYheO");
|
|
||||||
assertInvalidAddress("B17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhek#");
|
|
||||||
}
|
|
||||||
}
|
|
22
build.gradle
22
build.gradle
|
@ -631,34 +631,12 @@ configure(project(':inventory')) {
|
||||||
configure(project(':apitest')) {
|
configure(project(':apitest')) {
|
||||||
mainClassName = 'bisq.apitest.ApiTestMain'
|
mainClassName = 'bisq.apitest.ApiTestMain'
|
||||||
|
|
||||||
// The external dao-setup.gradle file contains tasks to install and clean dao-setup
|
|
||||||
// files downloaded from
|
|
||||||
// https://github.com/bisq-network/bisq/raw/master/docs/dao-setup.zip
|
|
||||||
// These tasks are not run by the default build, but they can can be run during
|
|
||||||
// full or partial builds, or by themselves.
|
|
||||||
// To run the regular clean + build + test (non api), and install dao-setup files:
|
|
||||||
// ./gradlew clean build :apitest:installDaoSetup
|
|
||||||
// To install or re-install dao-setup file only:
|
|
||||||
// ./gradlew :apitest:installDaoSetup -x test
|
|
||||||
// To clean installed dao-setup files:
|
|
||||||
// ./gradlew :apitest:cleanDaoSetup -x test
|
|
||||||
apply from: 'dao-setup.gradle'
|
|
||||||
|
|
||||||
// We have to disable the :apitest 'test' task by default because we do not want
|
// We have to disable the :apitest 'test' task by default because we do not want
|
||||||
// to interfere with normal builds. To run JUnit tests in this subproject:
|
// to interfere with normal builds. To run JUnit tests in this subproject:
|
||||||
// Run a normal build and install dao-setup files first, then run:
|
// Run a normal build and install dao-setup files first, then run:
|
||||||
// 'gradle :apitest:test -DrunApiTests=true'
|
// 'gradle :apitest:test -DrunApiTests=true'
|
||||||
test.enabled = System.getProperty("runApiTests") == "true"
|
test.enabled = System.getProperty("runApiTests") == "true"
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
main {
|
|
||||||
resources {
|
|
||||||
exclude 'dao-setup'
|
|
||||||
exclude 'dao-setup.zip'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
outputs.upToDateWhen { false } // Don't use previously cached test outputs.
|
outputs.upToDateWhen { false } // Don't use previously cached test outputs.
|
||||||
|
|
|
@ -69,14 +69,12 @@ import bisq.cli.opts.GetTradeOptionParser;
|
||||||
import bisq.cli.opts.GetTransactionOptionParser;
|
import bisq.cli.opts.GetTransactionOptionParser;
|
||||||
import bisq.cli.opts.RegisterDisputeAgentOptionParser;
|
import bisq.cli.opts.RegisterDisputeAgentOptionParser;
|
||||||
import bisq.cli.opts.RemoveWalletPasswordOptionParser;
|
import bisq.cli.opts.RemoveWalletPasswordOptionParser;
|
||||||
import bisq.cli.opts.SendBsqOptionParser;
|
|
||||||
import bisq.cli.opts.SendBtcOptionParser;
|
import bisq.cli.opts.SendBtcOptionParser;
|
||||||
import bisq.cli.opts.SetTxFeeRateOptionParser;
|
import bisq.cli.opts.SetTxFeeRateOptionParser;
|
||||||
import bisq.cli.opts.SetWalletPasswordOptionParser;
|
import bisq.cli.opts.SetWalletPasswordOptionParser;
|
||||||
import bisq.cli.opts.SimpleMethodOptionParser;
|
import bisq.cli.opts.SimpleMethodOptionParser;
|
||||||
import bisq.cli.opts.TakeOfferOptionParser;
|
import bisq.cli.opts.TakeOfferOptionParser;
|
||||||
import bisq.cli.opts.UnlockWalletOptionParser;
|
import bisq.cli.opts.UnlockWalletOptionParser;
|
||||||
import bisq.cli.opts.VerifyBsqSentToAddressOptionParser;
|
|
||||||
import bisq.cli.opts.WithdrawFundsOptionParser;
|
import bisq.cli.opts.WithdrawFundsOptionParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,9 +166,6 @@ public class CliMain {
|
||||||
var currencyCode = opts.getCurrencyCode();
|
var currencyCode = opts.getCurrencyCode();
|
||||||
var balances = client.getBalances(currencyCode);
|
var balances = client.getBalances(currencyCode);
|
||||||
switch (currencyCode.toUpperCase()) {
|
switch (currencyCode.toUpperCase()) {
|
||||||
case "BSQ":
|
|
||||||
out.println(formatBsqBalanceInfoTbl(balances.getBsq()));
|
|
||||||
break;
|
|
||||||
case "BTC":
|
case "BTC":
|
||||||
out.println(formatBtcBalanceInfoTbl(balances.getBtc()));
|
out.println(formatBtcBalanceInfoTbl(balances.getBtc()));
|
||||||
break;
|
break;
|
||||||
|
@ -215,36 +210,6 @@ public class CliMain {
|
||||||
out.println(formatAddressBalanceTbl(fundingAddresses));
|
out.println(formatAddressBalanceTbl(fundingAddresses));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case getunusedbsqaddress: {
|
|
||||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
|
||||||
out.println(client.getMethodHelp(method));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var address = client.getUnusedBsqAddress();
|
|
||||||
out.println(address);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case sendbsq: {
|
|
||||||
var opts = new SendBsqOptionParser(args).parse();
|
|
||||||
if (opts.isForHelp()) {
|
|
||||||
out.println(client.getMethodHelp(method));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var address = opts.getAddress();
|
|
||||||
var amount = opts.getAmount();
|
|
||||||
verifyStringIsValidDecimal(OPT_AMOUNT, amount);
|
|
||||||
|
|
||||||
var txFeeRate = opts.getFeeRate();
|
|
||||||
if (!txFeeRate.isEmpty())
|
|
||||||
verifyStringIsValidLong(OPT_TX_FEE_RATE, txFeeRate);
|
|
||||||
|
|
||||||
var txInfo = client.sendBsq(address, amount, txFeeRate);
|
|
||||||
out.printf("%s bsq sent to %s in tx %s%n",
|
|
||||||
amount,
|
|
||||||
address,
|
|
||||||
txInfo.getTxId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case sendbtc: {
|
case sendbtc: {
|
||||||
var opts = new SendBtcOptionParser(args).parse();
|
var opts = new SendBtcOptionParser(args).parse();
|
||||||
if (opts.isForHelp()) {
|
if (opts.isForHelp()) {
|
||||||
|
@ -268,23 +233,6 @@ public class CliMain {
|
||||||
txInfo.getTxId());
|
txInfo.getTxId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case verifybsqsenttoaddress: {
|
|
||||||
var opts = new VerifyBsqSentToAddressOptionParser(args).parse();
|
|
||||||
if (opts.isForHelp()) {
|
|
||||||
out.println(client.getMethodHelp(method));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var address = opts.getAddress();
|
|
||||||
var amount = opts.getAmount();
|
|
||||||
verifyStringIsValidDecimal(OPT_AMOUNT, amount);
|
|
||||||
|
|
||||||
var bsqWasSent = client.verifyBsqSentToAddress(address, amount);
|
|
||||||
out.printf("%s bsq %s sent to address %s%n",
|
|
||||||
amount,
|
|
||||||
bsqWasSent ? "has been" : "has not been",
|
|
||||||
address);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case gettxfeerate: {
|
case gettxfeerate: {
|
||||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||||
out.println(client.getMethodHelp(method));
|
out.println(client.getMethodHelp(method));
|
||||||
|
@ -339,7 +287,6 @@ public class CliMain {
|
||||||
var fixedPrice = opts.getFixedPrice();
|
var fixedPrice = opts.getFixedPrice();
|
||||||
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
|
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
|
||||||
var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit());
|
var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit());
|
||||||
var makerFeeCurrencyCode = opts.getMakerFeeCurrencyCode();
|
|
||||||
var offer = client.createOffer(direction,
|
var offer = client.createOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
|
@ -348,8 +295,7 @@ public class CliMain {
|
||||||
fixedPrice,
|
fixedPrice,
|
||||||
marketPriceMargin.doubleValue(),
|
marketPriceMargin.doubleValue(),
|
||||||
securityDeposit,
|
securityDeposit,
|
||||||
paymentAcctId,
|
paymentAcctId);
|
||||||
makerFeeCurrencyCode);
|
|
||||||
out.println(formatOfferTable(singletonList(offer), currencyCode));
|
out.println(formatOfferTable(singletonList(offer), currencyCode));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -426,8 +372,7 @@ public class CliMain {
|
||||||
}
|
}
|
||||||
var offerId = opts.getOfferId();
|
var offerId = opts.getOfferId();
|
||||||
var paymentAccountId = opts.getPaymentAccountId();
|
var paymentAccountId = opts.getPaymentAccountId();
|
||||||
var takerFeeCurrencyCode = opts.getTakerFeeCurrencyCode();
|
var trade = client.takeOffer(offerId, paymentAccountId);
|
||||||
var trade = client.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
|
||||||
out.printf("trade %s successfully taken%n", trade.getTradeId());
|
out.printf("trade %s successfully taken%n", trade.getTradeId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -721,7 +666,7 @@ public class CliMain {
|
||||||
stream.format(rowFormat, "------", "------", "------------");
|
stream.format(rowFormat, "------", "------", "------------");
|
||||||
stream.format(rowFormat, getversion.name(), "", "Get server version");
|
stream.format(rowFormat, getversion.name(), "", "Get server version");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, getbalance.name(), "[--currency-code=<bsq|btc>]", "Get server wallet balances");
|
stream.format(rowFormat, getbalance.name(), "[--currency-code=<btc>]", "Get server wallet balances");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, getaddressbalance.name(), "--address=<btc-address>", "Get server wallet address balance");
|
stream.format(rowFormat, getaddressbalance.name(), "--address=<btc-address>", "Get server wallet address balance");
|
||||||
stream.println();
|
stream.println();
|
||||||
|
@ -729,17 +674,10 @@ public class CliMain {
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, getfundingaddresses.name(), "", "Get BTC funding addresses");
|
stream.format(rowFormat, getfundingaddresses.name(), "", "Get BTC funding addresses");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, getunusedbsqaddress.name(), "", "Get unused BSQ address");
|
|
||||||
stream.println();
|
|
||||||
stream.format(rowFormat, sendbsq.name(), "--address=<bsq-address> --amount=<bsq-amount> \\", "Send BSQ");
|
|
||||||
stream.format(rowFormat, "", "[--tx-fee-rate=<sats/byte>]", "");
|
|
||||||
stream.println();
|
|
||||||
stream.format(rowFormat, sendbtc.name(), "--address=<btc-address> --amount=<btc-amount> \\", "Send BTC");
|
stream.format(rowFormat, sendbtc.name(), "--address=<btc-address> --amount=<btc-amount> \\", "Send BTC");
|
||||||
stream.format(rowFormat, "", "[--tx-fee-rate=<sats/byte>]", "");
|
stream.format(rowFormat, "", "[--tx-fee-rate=<sats/byte>]", "");
|
||||||
stream.format(rowFormat, "", "[--memo=<\"memo\">]", "");
|
stream.format(rowFormat, "", "[--memo=<\"memo\">]", "");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, verifybsqsenttoaddress.name(), "--address=<bsq-address> --amount=<bsq-amount>",
|
|
||||||
"Verify amount was sent to BSQ wallet address");
|
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, gettxfeerate.name(), "", "Get current tx fee rate in sats/byte");
|
stream.format(rowFormat, gettxfeerate.name(), "", "Get current tx fee rate in sats/byte");
|
||||||
stream.println();
|
stream.println();
|
||||||
|
@ -756,7 +694,7 @@ public class CliMain {
|
||||||
stream.format(rowFormat, "", "[--min-amount=<min-btc-amount>] \\", "");
|
stream.format(rowFormat, "", "[--min-amount=<min-btc-amount>] \\", "");
|
||||||
stream.format(rowFormat, "", "--fixed-price=<price> | --market-price=margin=<percent> \\", "");
|
stream.format(rowFormat, "", "--fixed-price=<price> | --market-price=margin=<percent> \\", "");
|
||||||
stream.format(rowFormat, "", "--security-deposit=<percent> \\", "");
|
stream.format(rowFormat, "", "--security-deposit=<percent> \\", "");
|
||||||
stream.format(rowFormat, "", "[--fee-currency=<bsq|btc>]", "");
|
stream.format(rowFormat, "", "[--fee-currency=<btc>]", "");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, canceloffer.name(), "--offer-id=<offer-id>", "Cancel offer with id");
|
stream.format(rowFormat, canceloffer.name(), "--offer-id=<offer-id>", "Cancel offer with id");
|
||||||
stream.println();
|
stream.println();
|
||||||
|
@ -772,7 +710,7 @@ public class CliMain {
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> \\", "Take offer with id");
|
stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> \\", "Take offer with id");
|
||||||
stream.format(rowFormat, "", "--payment-account=<payment-account-id>", "");
|
stream.format(rowFormat, "", "--payment-account=<payment-account-id>", "");
|
||||||
stream.format(rowFormat, "", "[--fee-currency=<btc|bsq>]", "");
|
stream.format(rowFormat, "", "[--fee-currency=<btc>]", "");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, gettrade.name(), "--trade-id=<trade-id> \\", "Get trade summary or full contract");
|
stream.format(rowFormat, gettrade.name(), "--trade-id=<trade-id> \\", "Get trade summary or full contract");
|
||||||
stream.format(rowFormat, "", "[--show-contract=<true|false>]", "");
|
stream.format(rowFormat, "", "[--show-contract=<true|false>]", "");
|
||||||
|
@ -794,8 +732,8 @@ public class CliMain {
|
||||||
stream.format(rowFormat, createpaymentacct.name(), "--payment-account-form=<path>", "Create a new payment account");
|
stream.format(rowFormat, createpaymentacct.name(), "--payment-account-form=<path>", "Create a new payment account");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, createcryptopaymentacct.name(), "--account-name=<name> \\", "Create a new cryptocurrency payment account");
|
stream.format(rowFormat, createcryptopaymentacct.name(), "--account-name=<name> \\", "Create a new cryptocurrency payment account");
|
||||||
stream.format(rowFormat, "", "--currency-code=<bsq> \\", "");
|
stream.format(rowFormat, "", "--currency-code=<btc> \\", "");
|
||||||
stream.format(rowFormat, "", "--address=<bsq-address>", "");
|
stream.format(rowFormat, "", "--address=<address>", "");
|
||||||
stream.format(rowFormat, "", "--trade-instant=<true|false>", "");
|
stream.format(rowFormat, "", "--trade-instant=<true|false>", "");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, getpaymentaccts.name(), "", "Get user payment accounts");
|
stream.format(rowFormat, getpaymentaccts.name(), "", "Get user payment accounts");
|
||||||
|
|
|
@ -54,7 +54,6 @@ class ColumnHeaderConstants {
|
||||||
static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC";
|
static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC";
|
||||||
static final String COL_HEADER_PRICE_OF_ALTCOIN = "Price in BTC for 1 %-3s";
|
static final String COL_HEADER_PRICE_OF_ALTCOIN = "Price in BTC for 1 %-3s";
|
||||||
static final String COL_HEADER_TRADE_AMOUNT = padStart("Amount(%-3s)", 12, ' ');
|
static final String COL_HEADER_TRADE_AMOUNT = padStart("Amount(%-3s)", 12, ' ');
|
||||||
static final String COL_HEADER_TRADE_BSQ_BUYER_ADDRESS = "BSQ Buyer Address";
|
|
||||||
static final String COL_HEADER_TRADE_BUYER_COST = padEnd("Buyer Cost(%-3s)", 15, ' ');
|
static final String COL_HEADER_TRADE_BUYER_COST = padEnd("Buyer Cost(%-3s)", 15, ' ');
|
||||||
static final String COL_HEADER_TRADE_DEPOSIT_CONFIRMED = "Deposit Confirmed";
|
static final String COL_HEADER_TRADE_DEPOSIT_CONFIRMED = "Deposit Confirmed";
|
||||||
static final String COL_HEADER_TRADE_DEPOSIT_PUBLISHED = "Deposit Published";
|
static final String COL_HEADER_TRADE_DEPOSIT_PUBLISHED = "Deposit Published";
|
||||||
|
|
|
@ -28,7 +28,6 @@ class CryptoCurrencyUtil {
|
||||||
|
|
||||||
public static List<String> getSupportedCryptoCurrencies() {
|
public static List<String> getSupportedCryptoCurrencies() {
|
||||||
final List<String> result = new ArrayList<>();
|
final List<String> result = new ArrayList<>();
|
||||||
result.add("BSQ");
|
|
||||||
result.sort(String::compareTo);
|
result.sort(String::compareTo);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,12 +46,8 @@ public class CurrencyFormat {
|
||||||
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
|
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
|
||||||
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,###,##0");
|
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,###,##0");
|
||||||
|
|
||||||
static final BigDecimal BSQ_SATOSHI_DIVISOR = new BigDecimal(100);
|
|
||||||
static final DecimalFormat BSQ_FORMAT = new DecimalFormat("###,###,###,##0.00");
|
|
||||||
static final DecimalFormat SEND_BSQ_FORMAT = new DecimalFormat("###########0.00");
|
|
||||||
|
|
||||||
static final BigDecimal SECURITY_DEPOSIT_MULTIPLICAND = new BigDecimal("0.01");
|
static final BigDecimal SECURITY_DEPOSIT_MULTIPLICAND = new BigDecimal("0.01");
|
||||||
|
|
||||||
// TODO: (woodser): replace formatSatoshis(), formatBsq() with formatXmr()
|
// TODO: (woodser): replace formatSatoshis(), formatBsq() with formatXmr()
|
||||||
|
|
||||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||||
|
@ -59,22 +55,10 @@ public class CurrencyFormat {
|
||||||
return BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
|
return BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
|
||||||
public static String formatBsq(long sats) {
|
|
||||||
return BSQ_FORMAT.format(BigDecimal.valueOf(sats).divide(BSQ_SATOSHI_DIVISOR));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String formatXmr(BigInteger amount) {
|
public static String formatXmr(BigInteger amount) {
|
||||||
return "" + MoneroUtils.atomicUnitsToXmr(amount);
|
return "" + MoneroUtils.atomicUnitsToXmr(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatBsqAmount(long bsqSats) {
|
|
||||||
// BSQ sats = trade.getOffer().getVolume()
|
|
||||||
NUMBER_FORMAT.setMinimumFractionDigits(2);
|
|
||||||
NUMBER_FORMAT.setMaximumFractionDigits(2);
|
|
||||||
NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
|
||||||
return SEND_BSQ_FORMAT.format((double) bsqSats / SATOSHI_DIVISOR.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String formatTxFeeRateInfo(TxFeeRateInfo txFeeRateInfo) {
|
public static String formatTxFeeRateInfo(TxFeeRateInfo txFeeRateInfo) {
|
||||||
if (txFeeRateInfo.getUseCustomTxFeeRate())
|
if (txFeeRateInfo.getUseCustomTxFeeRate())
|
||||||
|
|
|
@ -19,7 +19,6 @@ package bisq.cli;
|
||||||
|
|
||||||
import bisq.proto.grpc.AddressBalanceInfo;
|
import bisq.proto.grpc.AddressBalanceInfo;
|
||||||
import bisq.proto.grpc.BalancesInfo;
|
import bisq.proto.grpc.BalancesInfo;
|
||||||
import bisq.proto.grpc.BsqBalanceInfo;
|
|
||||||
import bisq.proto.grpc.BtcBalanceInfo;
|
import bisq.proto.grpc.BtcBalanceInfo;
|
||||||
import bisq.proto.grpc.CancelOfferRequest;
|
import bisq.proto.grpc.CancelOfferRequest;
|
||||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||||
|
@ -42,7 +41,6 @@ import bisq.proto.grpc.GetPaymentMethodsRequest;
|
||||||
import bisq.proto.grpc.GetTradeRequest;
|
import bisq.proto.grpc.GetTradeRequest;
|
||||||
import bisq.proto.grpc.GetTransactionRequest;
|
import bisq.proto.grpc.GetTransactionRequest;
|
||||||
import bisq.proto.grpc.GetTxFeeRateRequest;
|
import bisq.proto.grpc.GetTxFeeRateRequest;
|
||||||
import bisq.proto.grpc.GetUnusedBsqAddressRequest;
|
|
||||||
import bisq.proto.grpc.GetVersionRequest;
|
import bisq.proto.grpc.GetVersionRequest;
|
||||||
import bisq.proto.grpc.KeepFundsRequest;
|
import bisq.proto.grpc.KeepFundsRequest;
|
||||||
import bisq.proto.grpc.LockWalletRequest;
|
import bisq.proto.grpc.LockWalletRequest;
|
||||||
|
@ -50,7 +48,6 @@ import bisq.proto.grpc.MarketPriceRequest;
|
||||||
import bisq.proto.grpc.OfferInfo;
|
import bisq.proto.grpc.OfferInfo;
|
||||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||||
import bisq.proto.grpc.SendBsqRequest;
|
|
||||||
import bisq.proto.grpc.SendBtcRequest;
|
import bisq.proto.grpc.SendBtcRequest;
|
||||||
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
|
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
|
||||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||||
|
@ -62,7 +59,6 @@ import bisq.proto.grpc.TxFeeRateInfo;
|
||||||
import bisq.proto.grpc.TxInfo;
|
import bisq.proto.grpc.TxInfo;
|
||||||
import bisq.proto.grpc.UnlockWalletRequest;
|
import bisq.proto.grpc.UnlockWalletRequest;
|
||||||
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
||||||
import bisq.proto.grpc.VerifyBsqSentToAddressRequest;
|
|
||||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||||
import bisq.proto.grpc.XmrBalanceInfo;
|
import bisq.proto.grpc.XmrBalanceInfo;
|
||||||
|
|
||||||
|
@ -100,10 +96,6 @@ public final class GrpcClient {
|
||||||
return getBalances("");
|
return getBalances("");
|
||||||
}
|
}
|
||||||
|
|
||||||
public BsqBalanceInfo getBsqBalances() {
|
|
||||||
return getBalances("BSQ").getBsq();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BtcBalanceInfo getBtcBalances() {
|
public BtcBalanceInfo getBtcBalances() {
|
||||||
return getBalances("BTC").getBtc();
|
return getBalances("BTC").getBtc();
|
||||||
}
|
}
|
||||||
|
@ -137,11 +129,6 @@ public final class GrpcClient {
|
||||||
return grpcStubs.walletsService.getFundingAddresses(request).getAddressBalanceInfoList();
|
return grpcStubs.walletsService.getFundingAddresses(request).getAddressBalanceInfoList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUnusedBsqAddress() {
|
|
||||||
var request = GetUnusedBsqAddressRequest.newBuilder().build();
|
|
||||||
return grpcStubs.walletsService.getUnusedBsqAddress(request).getAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUnusedBtcAddress() {
|
public String getUnusedBtcAddress() {
|
||||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||||
var addressBalances = grpcStubs.walletsService.getFundingAddresses(request)
|
var addressBalances = grpcStubs.walletsService.getFundingAddresses(request)
|
||||||
|
@ -154,15 +141,6 @@ public final class GrpcClient {
|
||||||
.getAddress();
|
.getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxInfo sendBsq(String address, String amount, String txFeeRate) {
|
|
||||||
var request = SendBsqRequest.newBuilder()
|
|
||||||
.setAddress(address)
|
|
||||||
.setAmount(amount)
|
|
||||||
.setTxFeeRate(txFeeRate)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.sendBsq(request).getTxInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TxInfo sendBtc(String address, String amount, String txFeeRate, String memo) {
|
public TxInfo sendBtc(String address, String amount, String txFeeRate, String memo) {
|
||||||
var request = SendBtcRequest.newBuilder()
|
var request = SendBtcRequest.newBuilder()
|
||||||
.setAddress(address)
|
.setAddress(address)
|
||||||
|
@ -173,14 +151,6 @@ public final class GrpcClient {
|
||||||
return grpcStubs.walletsService.sendBtc(request).getTxInfo();
|
return grpcStubs.walletsService.sendBtc(request).getTxInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean verifyBsqSentToAddress(String address, String amount) {
|
|
||||||
var request = VerifyBsqSentToAddressRequest.newBuilder()
|
|
||||||
.setAddress(address)
|
|
||||||
.setAmount(amount)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.verifyBsqSentToAddress(request).getIsAmountReceived();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TxFeeRateInfo getTxFeeRate() {
|
public TxFeeRateInfo getTxFeeRate() {
|
||||||
var request = GetTxFeeRateRequest.newBuilder().build();
|
var request = GetTxFeeRateRequest.newBuilder().build();
|
||||||
return grpcStubs.walletsService.getTxFeeRate(request).getTxFeeRateInfo();
|
return grpcStubs.walletsService.getTxFeeRate(request).getTxFeeRateInfo();
|
||||||
|
@ -211,8 +181,7 @@ public final class GrpcClient {
|
||||||
long minAmount,
|
long minAmount,
|
||||||
String fixedPrice,
|
String fixedPrice,
|
||||||
double securityDeposit,
|
double securityDeposit,
|
||||||
String paymentAcctId,
|
String paymentAcctId) {
|
||||||
String makerFeeCurrencyCode) {
|
|
||||||
return createOffer(direction,
|
return createOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
|
@ -221,8 +190,7 @@ public final class GrpcClient {
|
||||||
fixedPrice,
|
fixedPrice,
|
||||||
0.00,
|
0.00,
|
||||||
securityDeposit,
|
securityDeposit,
|
||||||
paymentAcctId,
|
paymentAcctId);
|
||||||
makerFeeCurrencyCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo createMarketBasedPricedOffer(String direction,
|
public OfferInfo createMarketBasedPricedOffer(String direction,
|
||||||
|
@ -231,8 +199,7 @@ public final class GrpcClient {
|
||||||
long minAmount,
|
long minAmount,
|
||||||
double marketPriceMargin,
|
double marketPriceMargin,
|
||||||
double securityDeposit,
|
double securityDeposit,
|
||||||
String paymentAcctId,
|
String paymentAcctId) {
|
||||||
String makerFeeCurrencyCode) {
|
|
||||||
return createOffer(direction,
|
return createOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
|
@ -241,8 +208,7 @@ public final class GrpcClient {
|
||||||
"0",
|
"0",
|
||||||
marketPriceMargin,
|
marketPriceMargin,
|
||||||
securityDeposit,
|
securityDeposit,
|
||||||
paymentAcctId,
|
paymentAcctId);
|
||||||
makerFeeCurrencyCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo createOffer(String direction,
|
public OfferInfo createOffer(String direction,
|
||||||
|
@ -253,8 +219,7 @@ public final class GrpcClient {
|
||||||
String fixedPrice,
|
String fixedPrice,
|
||||||
double marketPriceMargin,
|
double marketPriceMargin,
|
||||||
double securityDeposit,
|
double securityDeposit,
|
||||||
String paymentAcctId,
|
String paymentAcctId) {
|
||||||
String makerFeeCurrencyCode) {
|
|
||||||
var request = CreateOfferRequest.newBuilder()
|
var request = CreateOfferRequest.newBuilder()
|
||||||
.setDirection(direction)
|
.setDirection(direction)
|
||||||
.setCurrencyCode(currencyCode)
|
.setCurrencyCode(currencyCode)
|
||||||
|
@ -265,7 +230,6 @@ public final class GrpcClient {
|
||||||
.setMarketPriceMargin(marketPriceMargin)
|
.setMarketPriceMargin(marketPriceMargin)
|
||||||
.setBuyerSecurityDeposit(securityDeposit)
|
.setBuyerSecurityDeposit(securityDeposit)
|
||||||
.setPaymentAccountId(paymentAcctId)
|
.setPaymentAccountId(paymentAcctId)
|
||||||
.setMakerFeeCurrencyCode(makerFeeCurrencyCode)
|
|
||||||
.build();
|
.build();
|
||||||
return grpcStubs.offersService.createOffer(request).getOffer();
|
return grpcStubs.offersService.createOffer(request).getOffer();
|
||||||
}
|
}
|
||||||
|
@ -321,13 +285,6 @@ public final class GrpcClient {
|
||||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getBsqOffersSortedByDate() {
|
|
||||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
|
||||||
offers.addAll(getCryptoCurrencyOffers(BUY.name(), "BSQ"));
|
|
||||||
offers.addAll(getCryptoCurrencyOffers(SELL.name(), "BSQ"));
|
|
||||||
return sortOffersByDate(offers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
if (isSupportedCryptoCurrency(currencyCode)) {
|
||||||
return getMyCryptoCurrencyOffers(direction, currencyCode);
|
return getMyCryptoCurrencyOffers(direction, currencyCode);
|
||||||
|
@ -358,13 +315,6 @@ public final class GrpcClient {
|
||||||
return sortOffersByDate(offers);
|
return sortOffersByDate(offers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getMyBsqOffersSortedByDate() {
|
|
||||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
|
||||||
offers.addAll(getMyCryptoCurrencyOffers(BUY.name(), "BSQ"));
|
|
||||||
offers.addAll(getMyCryptoCurrencyOffers(SELL.name(), "BSQ"));
|
|
||||||
return sortOffersByDate(offers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||||
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
||||||
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
||||||
|
@ -376,17 +326,16 @@ public final class GrpcClient {
|
||||||
.collect(toList());
|
.collect(toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId) {
|
||||||
var request = TakeOfferRequest.newBuilder()
|
var request = TakeOfferRequest.newBuilder()
|
||||||
.setOfferId(offerId)
|
.setOfferId(offerId)
|
||||||
.setPaymentAccountId(paymentAccountId)
|
.setPaymentAccountId(paymentAccountId)
|
||||||
.setTakerFeeCurrencyCode(takerFeeCurrencyCode)
|
|
||||||
.build();
|
.build();
|
||||||
return grpcStubs.tradesService.takeOffer(request);
|
return grpcStubs.tradesService.takeOffer(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
public TradeInfo takeOffer(String offerId, String paymentAccountId) {
|
||||||
var reply = getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
|
var reply = getTakeOfferReply(offerId, paymentAccountId);
|
||||||
if (reply.hasTrade())
|
if (reply.hasTrade())
|
||||||
return reply.getTrade();
|
return reply.getTrade();
|
||||||
else
|
else
|
||||||
|
|
|
@ -41,15 +41,12 @@ public enum Method {
|
||||||
gettrade,
|
gettrade,
|
||||||
gettransaction,
|
gettransaction,
|
||||||
gettxfeerate,
|
gettxfeerate,
|
||||||
getunusedbsqaddress,
|
|
||||||
getversion,
|
getversion,
|
||||||
keepfunds,
|
keepfunds,
|
||||||
lockwallet,
|
lockwallet,
|
||||||
registerdisputeagent,
|
registerdisputeagent,
|
||||||
removewalletpassword,
|
removewalletpassword,
|
||||||
sendbsq,
|
|
||||||
sendbtc,
|
sendbtc,
|
||||||
verifybsqsenttoaddress,
|
|
||||||
settxfeerate,
|
settxfeerate,
|
||||||
setwalletpassword,
|
setwalletpassword,
|
||||||
takeoffer,
|
takeoffer,
|
||||||
|
|
|
@ -19,7 +19,6 @@ package bisq.cli;
|
||||||
|
|
||||||
import bisq.proto.grpc.AddressBalanceInfo;
|
import bisq.proto.grpc.AddressBalanceInfo;
|
||||||
import bisq.proto.grpc.BalancesInfo;
|
import bisq.proto.grpc.BalancesInfo;
|
||||||
import bisq.proto.grpc.BsqBalanceInfo;
|
|
||||||
import bisq.proto.grpc.BtcBalanceInfo;
|
import bisq.proto.grpc.BtcBalanceInfo;
|
||||||
import bisq.proto.grpc.OfferInfo;
|
import bisq.proto.grpc.OfferInfo;
|
||||||
import bisq.proto.grpc.XmrBalanceInfo;
|
import bisq.proto.grpc.XmrBalanceInfo;
|
||||||
|
@ -75,31 +74,7 @@ public class TableFormat {
|
||||||
|
|
||||||
public static String formatBalancesTbls(BalancesInfo balancesInfo) {
|
public static String formatBalancesTbls(BalancesInfo balancesInfo) {
|
||||||
return "XMR" + "\n"
|
return "XMR" + "\n"
|
||||||
+ formatBtcBalanceInfoTbl(balancesInfo.getBtc()) + "\n"
|
+ formatBtcBalanceInfoTbl(balancesInfo.getBtc());
|
||||||
+ "BSQ" + "\n"
|
|
||||||
+ formatBsqBalanceInfoTbl(balancesInfo.getBsq());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String formatBsqBalanceInfoTbl(BsqBalanceInfo bsqBalanceInfo) {
|
|
||||||
String headerLine = COL_HEADER_AVAILABLE_CONFIRMED_BALANCE + COL_HEADER_DELIMITER
|
|
||||||
+ COL_HEADER_UNVERIFIED_BALANCE + COL_HEADER_DELIMITER
|
|
||||||
+ COL_HEADER_UNCONFIRMED_CHANGE_BALANCE + COL_HEADER_DELIMITER
|
|
||||||
+ COL_HEADER_LOCKED_FOR_VOTING_BALANCE + COL_HEADER_DELIMITER
|
|
||||||
+ COL_HEADER_LOCKUP_BONDS_BALANCE + COL_HEADER_DELIMITER
|
|
||||||
+ COL_HEADER_UNLOCKING_BONDS_BALANCE + COL_HEADER_DELIMITER + "\n";
|
|
||||||
String colDataFormat = "%" + COL_HEADER_AVAILABLE_CONFIRMED_BALANCE.length() + "s" // rt justify
|
|
||||||
+ " %" + (COL_HEADER_UNVERIFIED_BALANCE.length() + 1) + "s" // rt justify
|
|
||||||
+ " %" + (COL_HEADER_UNCONFIRMED_CHANGE_BALANCE.length() + 1) + "s" // rt justify
|
|
||||||
+ " %" + (COL_HEADER_LOCKED_FOR_VOTING_BALANCE.length() + 1) + "s" // rt justify
|
|
||||||
+ " %" + (COL_HEADER_LOCKUP_BONDS_BALANCE.length() + 1) + "s" // rt justify
|
|
||||||
+ " %" + (COL_HEADER_UNLOCKING_BONDS_BALANCE.length() + 1) + "s"; // rt justify
|
|
||||||
return headerLine + format(colDataFormat,
|
|
||||||
formatBsq(bsqBalanceInfo.getAvailableConfirmedBalance()),
|
|
||||||
formatBsq(bsqBalanceInfo.getUnverifiedBalance()),
|
|
||||||
formatBsq(bsqBalanceInfo.getUnconfirmedChangeBalance()),
|
|
||||||
formatBsq(bsqBalanceInfo.getLockedForVotingBalance()),
|
|
||||||
formatBsq(bsqBalanceInfo.getLockupBondsBalance()),
|
|
||||||
formatBsq(bsqBalanceInfo.getUnlockingBondsBalance()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatBtcBalanceInfoTbl(BtcBalanceInfo btcBalanceInfo) {
|
public static String formatBtcBalanceInfoTbl(BtcBalanceInfo btcBalanceInfo) {
|
||||||
|
|
|
@ -61,9 +61,6 @@ public class TradeFormat {
|
||||||
"%" + (COL_HEADER_TRADE_TAKER_FEE.length() + 2) + "s"
|
"%" + (COL_HEADER_TRADE_TAKER_FEE.length() + 2) + "s"
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
boolean showBsqBuyerAddress = shouldShowBsqBuyerAddress(tradeInfo, isTaker);
|
|
||||||
Supplier<String> bsqBuyerAddressHeader = () -> showBsqBuyerAddress ? COL_HEADER_TRADE_BSQ_BUYER_ADDRESS : "";
|
|
||||||
Supplier<String> bsqBuyerAddressHeaderSpec = () -> showBsqBuyerAddress ? "%s" : "";
|
|
||||||
|
|
||||||
String headersFormat = padEnd(COL_HEADER_TRADE_SHORT_ID, shortIdColWidth, ' ') + COL_HEADER_DELIMITER
|
String headersFormat = padEnd(COL_HEADER_TRADE_SHORT_ID, shortIdColWidth, ' ') + COL_HEADER_DELIMITER
|
||||||
+ padEnd(COL_HEADER_TRADE_ROLE, roleColWidth, ' ') + COL_HEADER_DELIMITER
|
+ padEnd(COL_HEADER_TRADE_ROLE, roleColWidth, ' ') + COL_HEADER_DELIMITER
|
||||||
|
@ -80,7 +77,6 @@ public class TradeFormat {
|
||||||
+ COL_HEADER_TRADE_PAYMENT_RECEIVED + COL_HEADER_DELIMITER
|
+ COL_HEADER_TRADE_PAYMENT_RECEIVED + COL_HEADER_DELIMITER
|
||||||
+ COL_HEADER_TRADE_PAYOUT_PUBLISHED + COL_HEADER_DELIMITER
|
+ COL_HEADER_TRADE_PAYOUT_PUBLISHED + COL_HEADER_DELIMITER
|
||||||
+ COL_HEADER_TRADE_WITHDRAWN + COL_HEADER_DELIMITER
|
+ COL_HEADER_TRADE_WITHDRAWN + COL_HEADER_DELIMITER
|
||||||
+ bsqBuyerAddressHeader.get()
|
|
||||||
+ "%n";
|
+ "%n";
|
||||||
|
|
||||||
String counterCurrencyCode = tradeInfo.getOffer().getCounterCurrencyCode();
|
String counterCurrencyCode = tradeInfo.getOffer().getCounterCurrencyCode();
|
||||||
|
@ -108,16 +104,14 @@ public class TradeFormat {
|
||||||
+ " %-" + (COL_HEADER_TRADE_PAYMENT_SENT.length() - 1) + "s" // left
|
+ " %-" + (COL_HEADER_TRADE_PAYMENT_SENT.length() - 1) + "s" // left
|
||||||
+ " %-" + (COL_HEADER_TRADE_PAYMENT_RECEIVED.length() - 1) + "s" // left
|
+ " %-" + (COL_HEADER_TRADE_PAYMENT_RECEIVED.length() - 1) + "s" // left
|
||||||
+ " %-" + COL_HEADER_TRADE_PAYOUT_PUBLISHED.length() + "s" // lt justify
|
+ " %-" + COL_HEADER_TRADE_PAYOUT_PUBLISHED.length() + "s" // lt justify
|
||||||
+ " %-" + (COL_HEADER_TRADE_WITHDRAWN.length() + 2) + "s"
|
+ " %-" + (COL_HEADER_TRADE_WITHDRAWN.length() + 2) + "s";
|
||||||
+ bsqBuyerAddressHeaderSpec.get();
|
|
||||||
|
|
||||||
return headerLine + formatTradeData(colDataFormat, tradeInfo, isTaker, showBsqBuyerAddress);
|
return headerLine + formatTradeData(colDataFormat, tradeInfo, isTaker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatTradeData(String format,
|
private static String formatTradeData(String format,
|
||||||
TradeInfo tradeInfo,
|
TradeInfo tradeInfo,
|
||||||
boolean isTaker,
|
boolean isTaker) {
|
||||||
boolean showBsqBuyerAddress) {
|
|
||||||
return String.format(format,
|
return String.format(format,
|
||||||
tradeInfo.getShortId(),
|
tradeInfo.getShortId(),
|
||||||
tradeInfo.getRole(),
|
tradeInfo.getRole(),
|
||||||
|
@ -131,8 +125,7 @@ public class TradeFormat {
|
||||||
tradeInfo.getIsFiatSent() ? YES : NO,
|
tradeInfo.getIsFiatSent() ? YES : NO,
|
||||||
tradeInfo.getIsFiatReceived() ? YES : NO,
|
tradeInfo.getIsFiatReceived() ? YES : NO,
|
||||||
tradeInfo.getIsPayoutPublished() ? YES : NO,
|
tradeInfo.getIsPayoutPublished() ? YES : NO,
|
||||||
tradeInfo.getIsWithdrawn() ? YES : NO,
|
tradeInfo.getIsWithdrawn() ? YES : NO);
|
||||||
bsqReceiveAddress.apply(tradeInfo, showBsqBuyerAddress));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Function<TradeInfo, String> priceHeader = (t) ->
|
private static final Function<TradeInfo, String> priceHeader = (t) ->
|
||||||
|
@ -181,31 +174,4 @@ public class TradeFormat {
|
||||||
? formatOfferVolume(t.getOffer().getVolume())
|
? formatOfferVolume(t.getOffer().getVolume())
|
||||||
: formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong()));
|
: formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong()));
|
||||||
|
|
||||||
private static final BiFunction<TradeInfo, Boolean, String> bsqReceiveAddress = (t, showBsqBuyerAddress) -> {
|
|
||||||
if (showBsqBuyerAddress) {
|
|
||||||
ContractInfo contract = t.getContract();
|
|
||||||
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
|
|
||||||
return isBuyerMakerAndSellerTaker // (is XMR buyer / maker)
|
|
||||||
? contract.getTakerPaymentAccountPayload().getAddress()
|
|
||||||
: contract.getMakerPaymentAccountPayload().getAddress();
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static boolean shouldShowBsqBuyerAddress(TradeInfo tradeInfo, boolean isTaker) {
|
|
||||||
if (tradeInfo.getOffer().getBaseCurrencyCode().equals("XMR")) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
ContractInfo contract = tradeInfo.getContract();
|
|
||||||
// Do not forget buyer and seller refer to XMR buyer and seller, not BSQ
|
|
||||||
// buyer and seller. If you are buying BSQ, you are the XMR seller.
|
|
||||||
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
|
|
||||||
if (isTaker) {
|
|
||||||
return !isBuyerMakerAndSellerTaker;
|
|
||||||
} else {
|
|
||||||
return isBuyerMakerAndSellerTaker;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,10 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
|
||||||
final OptionSpec<String> accountNameOpt = parser.accepts(OPT_ACCOUNT_NAME, "crypto currency account name")
|
final OptionSpec<String> accountNameOpt = parser.accepts(OPT_ACCOUNT_NAME, "crypto currency account name")
|
||||||
.withRequiredArg();
|
.withRequiredArg();
|
||||||
|
|
||||||
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "crypto currency code (bsq only)")
|
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "crypto currency code")
|
||||||
.withRequiredArg();
|
.withRequiredArg();
|
||||||
|
|
||||||
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "bsq address")
|
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "address")
|
||||||
.withRequiredArg();
|
.withRequiredArg();
|
||||||
|
|
||||||
final OptionSpec<Boolean> tradeInstantOpt = parser.accepts(OPT_TRADE_INSTANT, "create trade instant account")
|
final OptionSpec<Boolean> tradeInstantOpt = parser.accepts(OPT_TRADE_INSTANT, "create trade instant account")
|
||||||
|
@ -54,16 +54,9 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
|
||||||
|
|
||||||
if (!options.has(accountNameOpt) || options.valueOf(accountNameOpt).isEmpty())
|
if (!options.has(accountNameOpt) || options.valueOf(accountNameOpt).isEmpty())
|
||||||
throw new IllegalArgumentException("no payment account name specified");
|
throw new IllegalArgumentException("no payment account name specified");
|
||||||
|
|
||||||
if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty())
|
if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty())
|
||||||
throw new IllegalArgumentException("no currency code specified");
|
throw new IllegalArgumentException("no currency code specified");
|
||||||
|
|
||||||
if (!options.valueOf(currencyCodeOpt).equalsIgnoreCase("bsq"))
|
|
||||||
throw new IllegalArgumentException("api only supports bsq crypto currency payment accounts");
|
|
||||||
|
|
||||||
if (!options.has(addressOpt) || options.valueOf(addressOpt).isEmpty())
|
|
||||||
throw new IllegalArgumentException("no bsq address specified");
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,10 +55,6 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||||
final OptionSpec<String> securityDepositOpt = parser.accepts(OPT_SECURITY_DEPOSIT, "maker security deposit (%)")
|
final OptionSpec<String> securityDepositOpt = parser.accepts(OPT_SECURITY_DEPOSIT, "maker security deposit (%)")
|
||||||
.withRequiredArg();
|
.withRequiredArg();
|
||||||
|
|
||||||
final OptionSpec<String> makerFeeCurrencyCodeOpt = parser.accepts(OPT_FEE_CURRENCY, "maker fee currency code (bsq|btc)")
|
|
||||||
.withOptionalArg()
|
|
||||||
.defaultsTo("btc");
|
|
||||||
|
|
||||||
public CreateOfferOptionParser(String[] args) {
|
public CreateOfferOptionParser(String[] args) {
|
||||||
super(args);
|
super(args);
|
||||||
}
|
}
|
||||||
|
@ -137,8 +133,4 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||||
public String getSecurityDeposit() {
|
public String getSecurityDeposit() {
|
||||||
return options.valueOf(securityDepositOpt);
|
return options.valueOf(securityDepositOpt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMakerFeeCurrencyCode() {
|
|
||||||
return options.has(makerFeeCurrencyCodeOpt) ? options.valueOf(makerFeeCurrencyCodeOpt) : "btc";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ import static joptsimple.internal.Strings.EMPTY;
|
||||||
|
|
||||||
public class GetBalanceOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
public class GetBalanceOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||||
|
|
||||||
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "wallet currency code (bsq|btc)")
|
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "wallet currency code (btc)")
|
||||||
.withOptionalArg()
|
.withOptionalArg()
|
||||||
.defaultsTo(EMPTY);
|
.defaultsTo(EMPTY);
|
||||||
|
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.cli.opts;
|
|
||||||
|
|
||||||
|
|
||||||
import joptsimple.OptionSpec;
|
|
||||||
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_AMOUNT;
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_TX_FEE_RATE;
|
|
||||||
import static joptsimple.internal.Strings.EMPTY;
|
|
||||||
|
|
||||||
public class SendBsqOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
|
||||||
|
|
||||||
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "destination bsq address")
|
|
||||||
.withRequiredArg();
|
|
||||||
|
|
||||||
final OptionSpec<String> amountOpt = parser.accepts(OPT_AMOUNT, "amount of bsq to send")
|
|
||||||
.withRequiredArg();
|
|
||||||
|
|
||||||
final OptionSpec<String> feeRateOpt = parser.accepts(OPT_TX_FEE_RATE, "optional tx fee rate (sats/byte)")
|
|
||||||
.withOptionalArg()
|
|
||||||
.defaultsTo(EMPTY);
|
|
||||||
|
|
||||||
public SendBsqOptionParser(String[] args) {
|
|
||||||
super(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SendBsqOptionParser parse() {
|
|
||||||
super.parse();
|
|
||||||
|
|
||||||
// Short circuit opt validation if user just wants help.
|
|
||||||
if (options.has(helpOpt))
|
|
||||||
return this;
|
|
||||||
|
|
||||||
if (!options.has(addressOpt) || options.valueOf(addressOpt).isEmpty())
|
|
||||||
throw new IllegalArgumentException("no bsq address specified");
|
|
||||||
|
|
||||||
if (!options.has(amountOpt) || options.valueOf(amountOpt).isEmpty())
|
|
||||||
throw new IllegalArgumentException("no bsq amount specified");
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return options.valueOf(addressOpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAmount() {
|
|
||||||
return options.valueOf(amountOpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFeeRate() {
|
|
||||||
return options.has(feeRateOpt) ? options.valueOf(feeRateOpt) : "";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,10 +32,6 @@ public class TakeOfferOptionParser extends AbstractMethodOptionParser implements
|
||||||
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT, "id of payment account used for trade")
|
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT, "id of payment account used for trade")
|
||||||
.withRequiredArg();
|
.withRequiredArg();
|
||||||
|
|
||||||
final OptionSpec<String> takerFeeCurrencyCodeOpt = parser.accepts(OPT_FEE_CURRENCY, "taker fee currency code (bsq|btc)")
|
|
||||||
.withOptionalArg()
|
|
||||||
.defaultsTo("btc");
|
|
||||||
|
|
||||||
public TakeOfferOptionParser(String[] args) {
|
public TakeOfferOptionParser(String[] args) {
|
||||||
super(args);
|
super(args);
|
||||||
}
|
}
|
||||||
|
@ -63,8 +59,4 @@ public class TakeOfferOptionParser extends AbstractMethodOptionParser implements
|
||||||
public String getPaymentAccountId() {
|
public String getPaymentAccountId() {
|
||||||
return options.valueOf(paymentAccountIdOpt);
|
return options.valueOf(paymentAccountIdOpt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTakerFeeCurrencyCode() {
|
|
||||||
return options.has(takerFeeCurrencyCodeOpt) ? options.valueOf(takerFeeCurrencyCodeOpt) : "btc";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.cli.opts;
|
|
||||||
|
|
||||||
|
|
||||||
import joptsimple.OptionSpec;
|
|
||||||
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_AMOUNT;
|
|
||||||
|
|
||||||
public class VerifyBsqSentToAddressOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
|
||||||
|
|
||||||
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "receiving bsq address")
|
|
||||||
.withRequiredArg();
|
|
||||||
|
|
||||||
final OptionSpec<String> amountOpt = parser.accepts(OPT_AMOUNT, "amount of bsq received")
|
|
||||||
.withRequiredArg();
|
|
||||||
|
|
||||||
public VerifyBsqSentToAddressOptionParser(String[] args) {
|
|
||||||
super(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VerifyBsqSentToAddressOptionParser parse() {
|
|
||||||
super.parse();
|
|
||||||
|
|
||||||
// Short circuit opt validation if user just wants help.
|
|
||||||
if (options.has(helpOpt))
|
|
||||||
return this;
|
|
||||||
|
|
||||||
if (!options.has(addressOpt) || options.valueOf(addressOpt).isEmpty())
|
|
||||||
throw new IllegalArgumentException("no bsq address specified");
|
|
||||||
|
|
||||||
if (!options.has(amountOpt) || options.valueOf(amountOpt).isEmpty())
|
|
||||||
throw new IllegalArgumentException("no bsq amount specified");
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return options.valueOf(addressOpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAmount() {
|
|
||||||
return options.valueOf(amountOpt);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -184,84 +184,4 @@ public class OptionParsersTest {
|
||||||
exception.getMessage());
|
exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// createcryptopaymentacct parser tests
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingAcctNameOptShouldThrowException() {
|
|
||||||
String[] args = new String[]{
|
|
||||||
PASSWORD_OPT,
|
|
||||||
createcryptopaymentacct.name()
|
|
||||||
};
|
|
||||||
Throwable exception = assertThrows(RuntimeException.class, () ->
|
|
||||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse());
|
|
||||||
assertEquals("no payment account name specified", exception.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithEmptyAcctNameOptShouldThrowException() {
|
|
||||||
String[] args = new String[]{
|
|
||||||
PASSWORD_OPT,
|
|
||||||
createcryptopaymentacct.name(),
|
|
||||||
"--" + OPT_ACCOUNT_NAME
|
|
||||||
};
|
|
||||||
Throwable exception = assertThrows(RuntimeException.class, () ->
|
|
||||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse());
|
|
||||||
assertEquals("account-name requires an argument", exception.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingCurrencyCodeOptShouldThrowException() {
|
|
||||||
String[] args = new String[]{
|
|
||||||
PASSWORD_OPT,
|
|
||||||
createcryptopaymentacct.name(),
|
|
||||||
"--" + OPT_ACCOUNT_NAME + "=" + "bsq payment account"
|
|
||||||
};
|
|
||||||
Throwable exception = assertThrows(RuntimeException.class, () ->
|
|
||||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse());
|
|
||||||
assertEquals("no currency code specified", exception.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithInvalidCurrencyCodeOptShouldThrowException() {
|
|
||||||
String[] args = new String[]{
|
|
||||||
PASSWORD_OPT,
|
|
||||||
createcryptopaymentacct.name(),
|
|
||||||
"--" + OPT_ACCOUNT_NAME + "=" + "bsq payment account",
|
|
||||||
"--" + OPT_CURRENCY_CODE + "=" + "xmr"
|
|
||||||
};
|
|
||||||
Throwable exception = assertThrows(RuntimeException.class, () ->
|
|
||||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse());
|
|
||||||
assertEquals("api only supports bsq crypto currency payment accounts", exception.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingAddressOptShouldThrowException() {
|
|
||||||
String[] args = new String[]{
|
|
||||||
PASSWORD_OPT,
|
|
||||||
createcryptopaymentacct.name(),
|
|
||||||
"--" + OPT_ACCOUNT_NAME + "=" + "bsq payment account",
|
|
||||||
"--" + OPT_CURRENCY_CODE + "=" + "bsq"
|
|
||||||
};
|
|
||||||
Throwable exception = assertThrows(RuntimeException.class, () ->
|
|
||||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse());
|
|
||||||
assertEquals("no bsq address specified", exception.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateCryptoCurrencyPaymentAcctOptionParser() {
|
|
||||||
var acctName = "bsq payment account";
|
|
||||||
var currencyCode = "bsq";
|
|
||||||
var address = "B1nXyZ"; // address is validated on server
|
|
||||||
String[] args = new String[]{
|
|
||||||
PASSWORD_OPT,
|
|
||||||
createcryptopaymentacct.name(),
|
|
||||||
"--" + OPT_ACCOUNT_NAME + "=" + acctName,
|
|
||||||
"--" + OPT_CURRENCY_CODE + "=" + currencyCode,
|
|
||||||
"--" + OPT_ADDRESS + "=" + address
|
|
||||||
};
|
|
||||||
var parser = new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse();
|
|
||||||
assertEquals(acctName, parser.getAccountName());
|
|
||||||
assertEquals(currencyCode, parser.getCurrencyCode());
|
|
||||||
assertEquals(address, parser.getAddress());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,7 @@ public class Capabilities {
|
||||||
|
|
||||||
// Defines which most recent capability any node need to support.
|
// Defines which most recent capability any node need to support.
|
||||||
// This helps to clean network from very old inactive but still running nodes.
|
// This helps to clean network from very old inactive but still running nodes.
|
||||||
@SuppressWarnings("deprecation")
|
private static final Capability MANDATORY_CAPABILITY = Capability.TRADE_STATISTICS_3;
|
||||||
private static final Capability MANDATORY_CAPABILITY = Capability.DAO_STATE;
|
|
||||||
|
|
||||||
protected final Set<Capability> capabilities = new HashSet<>();
|
protected final Set<Capability> capabilities = new HashSet<>();
|
||||||
|
|
||||||
|
|
|
@ -28,11 +28,11 @@ public enum Capability {
|
||||||
@Deprecated TRADE_STATISTICS_2, // Not required anymore as no old clients out there not having that support
|
@Deprecated TRADE_STATISTICS_2, // Not required anymore as no old clients out there not having that support
|
||||||
@Deprecated ACCOUNT_AGE_WITNESS, // Not required anymore as no old clients out there not having that support
|
@Deprecated ACCOUNT_AGE_WITNESS, // Not required anymore as no old clients out there not having that support
|
||||||
SEED_NODE, // Node is a seed node
|
SEED_NODE, // Node is a seed node
|
||||||
DAO_FULL_NODE, // DAO full node can deliver BSQ blocks
|
@Deprecated DAO_FULL_NODE, // DAO full node can deliver BSQ blocks
|
||||||
@Deprecated PROPOSAL, // Not required anymore as no old clients out there not having that support
|
@Deprecated PROPOSAL, // Not required anymore as no old clients out there not having that support
|
||||||
@Deprecated BLIND_VOTE, // Not required anymore as no old clients out there not having that support
|
@Deprecated BLIND_VOTE, // Not required anymore as no old clients out there not having that support
|
||||||
@Deprecated ACK_MSG, // Not required anymore as no old clients out there not having that support
|
@Deprecated ACK_MSG, // Not required anymore as no old clients out there not having that support
|
||||||
RECEIVE_BSQ_BLOCK, // Signaling that node which wants to receive BSQ blocks (DAO lite node)
|
@Deprecated RECEIVE_BSQ_BLOCK, // Signaling that node which wants to receive BSQ blocks (DAO lite node)
|
||||||
@Deprecated DAO_STATE, // Not required anymore as no old clients out there not having that support
|
@Deprecated DAO_STATE, // Not required anymore as no old clients out there not having that support
|
||||||
|
|
||||||
@Deprecated BUNDLE_OF_ENVELOPES, // Supports bundling of messages if many messages are sent in short interval
|
@Deprecated BUNDLE_OF_ENVELOPES, // Supports bundling of messages if many messages are sent in short interval
|
||||||
|
|
|
@ -34,7 +34,6 @@ public class DevEnv {
|
||||||
|
|
||||||
public static void setup(Config config) {
|
public static void setup(Config config) {
|
||||||
DevEnv.setDevMode(config.useDevMode);
|
DevEnv.setDevMode(config.useDevMode);
|
||||||
DevEnv.setDaoActivated(config.daoActivated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If set to true we ignore several UI behavior like confirmation popups as well dummy accounts are created and
|
// If set to true we ignore several UI behavior like confirmation popups as well dummy accounts are created and
|
||||||
|
@ -49,23 +48,10 @@ public class DevEnv {
|
||||||
DevEnv.devMode = devMode;
|
DevEnv.devMode = devMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean daoActivated = true;
|
|
||||||
|
|
||||||
public static boolean isDaoActivated() {
|
|
||||||
return daoActivated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setDaoActivated(boolean daoActivated) {
|
|
||||||
DevEnv.daoActivated = daoActivated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void logErrorAndThrowIfDevMode(String msg) {
|
public static void logErrorAndThrowIfDevMode(String msg) {
|
||||||
log.error(msg);
|
log.error(msg);
|
||||||
if (devMode)
|
if (devMode)
|
||||||
throw new RuntimeException(msg);
|
throw new RuntimeException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isDaoTradingActivated() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,8 +102,6 @@ public class Version {
|
||||||
public static final int TRADE_PROTOCOL_VERSION = 3;
|
public static final int TRADE_PROTOCOL_VERSION = 3;
|
||||||
private static int p2pMessageVersion;
|
private static int p2pMessageVersion;
|
||||||
|
|
||||||
public static final String BSQ_TX_VERSION = "1";
|
|
||||||
|
|
||||||
public static int getP2PMessageVersion() {
|
public static int getP2PMessageVersion() {
|
||||||
return p2pMessageVersion;
|
return p2pMessageVersion;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,10 +28,7 @@ import lombok.Getter;
|
||||||
public enum BaseCurrencyNetwork {
|
public enum BaseCurrencyNetwork {
|
||||||
XMR_MAINNET(new XmrMainNetParams(), "XMR", "MAINNET", "Monero"), // TODO (woodser): network params are part of bitcoinj and shouldn't be needed. only used to get MonetaryFormat? replace with MonetaryFormat if so
|
XMR_MAINNET(new XmrMainNetParams(), "XMR", "MAINNET", "Monero"), // TODO (woodser): network params are part of bitcoinj and shouldn't be needed. only used to get MonetaryFormat? replace with MonetaryFormat if so
|
||||||
XMR_TESTNET(new XmrTestNet3Params(), "XMR", "TESTNET", "Monero"),
|
XMR_TESTNET(new XmrTestNet3Params(), "XMR", "TESTNET", "Monero"),
|
||||||
XMR_STAGENET(new XmrRegTestParams(), "XMR", "STAGENET", "Monero"),
|
XMR_STAGENET(new XmrRegTestParams(), "XMR", "STAGENET", "Monero");
|
||||||
BTC_DAO_TESTNET(RegTestParams.get(), "XMR", "STAGENET", "Monero"),
|
|
||||||
BTC_DAO_BETANET(MainNetParams.get(), "XMR", "MAINNET", "Monero"), // mainnet test genesis
|
|
||||||
BTC_DAO_REGTEST(RegTestParams.get(), "XMR", "STAGENET", "Monero");
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final NetworkParameters parameters;
|
private final NetworkParameters parameters;
|
||||||
|
@ -57,18 +54,6 @@ public enum BaseCurrencyNetwork {
|
||||||
return "XMR_TESTNET".equals(name());
|
return "XMR_TESTNET".equals(name());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDaoTestNet() {
|
|
||||||
return "BTC_DAO_TESTNET".equals(name());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDaoRegTest() {
|
|
||||||
return "BTC_DAO_REGTEST".equals(name());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDaoBetaNet() {
|
|
||||||
return "BTC_DAO_BETANET".equals(name());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStagenet() {
|
public boolean isStagenet() {
|
||||||
return "XMR_STAGENET".equals(name());
|
return "XMR_STAGENET".equals(name());
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,18 +103,6 @@ public class Config {
|
||||||
public static final String USE_ALL_PROVIDED_NODES = "useAllProvidedNodes";
|
public static final String USE_ALL_PROVIDED_NODES = "useAllProvidedNodes";
|
||||||
public static final String USER_AGENT = "userAgent";
|
public static final String USER_AGENT = "userAgent";
|
||||||
public static final String NUM_CONNECTIONS_FOR_BTC = "numConnectionsForBtc";
|
public static final String NUM_CONNECTIONS_FOR_BTC = "numConnectionsForBtc";
|
||||||
public static final String RPC_USER = "rpcUser";
|
|
||||||
public static final String RPC_PASSWORD = "rpcPassword";
|
|
||||||
public static final String RPC_HOST = "rpcHost";
|
|
||||||
public static final String RPC_PORT = "rpcPort";
|
|
||||||
public static final String RPC_BLOCK_NOTIFICATION_PORT = "rpcBlockNotificationPort";
|
|
||||||
public static final String RPC_BLOCK_NOTIFICATION_HOST = "rpcBlockNotificationHost";
|
|
||||||
public static final String DUMP_BLOCKCHAIN_DATA = "dumpBlockchainData";
|
|
||||||
public static final String FULL_DAO_NODE = "fullDaoNode";
|
|
||||||
public static final String GENESIS_TX_ID = "genesisTxId";
|
|
||||||
public static final String GENESIS_BLOCK_HEIGHT = "genesisBlockHeight";
|
|
||||||
public static final String GENESIS_TOTAL_SUPPLY = "genesisTotalSupply";
|
|
||||||
public static final String DAO_ACTIVATED = "daoActivated";
|
|
||||||
public static final String DUMP_DELAYED_PAYOUT_TXS = "dumpDelayedPayoutTxs";
|
public static final String DUMP_DELAYED_PAYOUT_TXS = "dumpDelayedPayoutTxs";
|
||||||
public static final String ALLOW_FAULTY_DELAYED_TXS = "allowFaultyDelayedTxs";
|
public static final String ALLOW_FAULTY_DELAYED_TXS = "allowFaultyDelayedTxs";
|
||||||
public static final String API_PASSWORD = "apiPassword";
|
public static final String API_PASSWORD = "apiPassword";
|
||||||
|
@ -130,7 +118,6 @@ public class Config {
|
||||||
public static final int UNSPECIFIED_PORT = -1;
|
public static final int UNSPECIFIED_PORT = -1;
|
||||||
public static final String DEFAULT_REGTEST_HOST = "localhost";
|
public static final String DEFAULT_REGTEST_HOST = "localhost";
|
||||||
public static final int DEFAULT_NUM_CONNECTIONS_FOR_BTC = 9; // down from BitcoinJ default of 12
|
public static final int DEFAULT_NUM_CONNECTIONS_FOR_BTC = 9; // down from BitcoinJ default of 12
|
||||||
public static final boolean DEFAULT_FULL_DAO_NODE = false;
|
|
||||||
static final String DEFAULT_CONFIG_FILE_NAME = "bisq.properties";
|
static final String DEFAULT_CONFIG_FILE_NAME = "bisq.properties";
|
||||||
|
|
||||||
// Static fields that provide access to Config properties in locations where injecting
|
// Static fields that provide access to Config properties in locations where injecting
|
||||||
|
@ -163,7 +150,6 @@ public class Config {
|
||||||
public final NetworkParameters networkParameters;
|
public final NetworkParameters networkParameters;
|
||||||
public final boolean ignoreLocalBtcNode;
|
public final boolean ignoreLocalBtcNode;
|
||||||
public final String bitcoinRegtestHost;
|
public final String bitcoinRegtestHost;
|
||||||
public final boolean daoActivated;
|
|
||||||
public final String referralId;
|
public final String referralId;
|
||||||
public final boolean useDevMode;
|
public final boolean useDevMode;
|
||||||
public final boolean useDevModeHeader;
|
public final boolean useDevModeHeader;
|
||||||
|
@ -195,18 +181,6 @@ public class Config {
|
||||||
public final boolean useAllProvidedNodes;
|
public final boolean useAllProvidedNodes;
|
||||||
public final String userAgent;
|
public final String userAgent;
|
||||||
public final int numConnectionsForBtc;
|
public final int numConnectionsForBtc;
|
||||||
public final String rpcUser;
|
|
||||||
public final String rpcPassword;
|
|
||||||
public final String rpcHost;
|
|
||||||
public final int rpcPort;
|
|
||||||
public final int rpcBlockNotificationPort;
|
|
||||||
public final String rpcBlockNotificationHost;
|
|
||||||
public final boolean dumpBlockchainData;
|
|
||||||
public final boolean fullDaoNode;
|
|
||||||
public final boolean fullDaoNodeOptionSetExplicitly;
|
|
||||||
public final String genesisTxId;
|
|
||||||
public final int genesisBlockHeight;
|
|
||||||
public final long genesisTotalSupply;
|
|
||||||
public final boolean dumpDelayedPayoutTxs;
|
public final boolean dumpDelayedPayoutTxs;
|
||||||
public final boolean allowFaultyDelayedTxs;
|
public final boolean allowFaultyDelayedTxs;
|
||||||
public final String apiPassword;
|
public final String apiPassword;
|
||||||
|
@ -552,78 +526,6 @@ public class Config {
|
||||||
.ofType(int.class)
|
.ofType(int.class)
|
||||||
.defaultsTo(DEFAULT_NUM_CONNECTIONS_FOR_BTC);
|
.defaultsTo(DEFAULT_NUM_CONNECTIONS_FOR_BTC);
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<String> rpcUserOpt =
|
|
||||||
parser.accepts(RPC_USER, "Bitcoind rpc username")
|
|
||||||
.withRequiredArg()
|
|
||||||
.defaultsTo("");
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<String> rpcPasswordOpt =
|
|
||||||
parser.accepts(RPC_PASSWORD, "Bitcoind rpc password")
|
|
||||||
.withRequiredArg()
|
|
||||||
.defaultsTo("");
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<String> rpcHostOpt =
|
|
||||||
parser.accepts(RPC_HOST, "Bitcoind rpc host")
|
|
||||||
.withRequiredArg()
|
|
||||||
.defaultsTo("");
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Integer> rpcPortOpt =
|
|
||||||
parser.accepts(RPC_PORT, "Bitcoind rpc port")
|
|
||||||
.withRequiredArg()
|
|
||||||
.ofType(int.class)
|
|
||||||
.defaultsTo(UNSPECIFIED_PORT);
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Integer> rpcBlockNotificationPortOpt =
|
|
||||||
parser.accepts(RPC_BLOCK_NOTIFICATION_PORT, "Bitcoind rpc port for block notifications")
|
|
||||||
.withRequiredArg()
|
|
||||||
.ofType(int.class)
|
|
||||||
.defaultsTo(UNSPECIFIED_PORT);
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<String> rpcBlockNotificationHostOpt =
|
|
||||||
parser.accepts(RPC_BLOCK_NOTIFICATION_HOST,
|
|
||||||
"Bitcoind rpc accepted incoming host for block notifications")
|
|
||||||
.withRequiredArg()
|
|
||||||
.defaultsTo("");
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> dumpBlockchainDataOpt =
|
|
||||||
parser.accepts(DUMP_BLOCKCHAIN_DATA, "If set to true the blockchain data " +
|
|
||||||
"from RPC requests to Bitcoin Core are stored as json file in the data dir.")
|
|
||||||
.withRequiredArg()
|
|
||||||
.ofType(boolean.class)
|
|
||||||
.defaultsTo(false);
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> fullDaoNodeOpt =
|
|
||||||
parser.accepts(FULL_DAO_NODE, "If set to true the node requests the blockchain data via RPC requests " +
|
|
||||||
"from Bitcoin Core and provide the validated BSQ txs to the network. It requires that the " +
|
|
||||||
"other RPC properties are set as well.")
|
|
||||||
.withRequiredArg()
|
|
||||||
.ofType(Boolean.class)
|
|
||||||
.defaultsTo(DEFAULT_FULL_DAO_NODE);
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<String> genesisTxIdOpt =
|
|
||||||
parser.accepts(GENESIS_TX_ID, "Genesis transaction ID when not using the hard coded one")
|
|
||||||
.withRequiredArg()
|
|
||||||
.defaultsTo("");
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Integer> genesisBlockHeightOpt =
|
|
||||||
parser.accepts(GENESIS_BLOCK_HEIGHT,
|
|
||||||
"Genesis transaction block height when not using the hard coded one")
|
|
||||||
.withRequiredArg()
|
|
||||||
.ofType(int.class)
|
|
||||||
.defaultsTo(-1);
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Long> genesisTotalSupplyOpt =
|
|
||||||
parser.accepts(GENESIS_TOTAL_SUPPLY, "Genesis total supply when not using the hard coded one")
|
|
||||||
.withRequiredArg()
|
|
||||||
.ofType(long.class)
|
|
||||||
.defaultsTo(-1L);
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> daoActivatedOpt =
|
|
||||||
parser.accepts(DAO_ACTIVATED, "Developer flag. If true it enables dao phase 2 features.")
|
|
||||||
.withRequiredArg()
|
|
||||||
.ofType(boolean.class)
|
|
||||||
.defaultsTo(true);
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> dumpDelayedPayoutTxsOpt =
|
ArgumentAcceptingOptionSpec<Boolean> dumpDelayedPayoutTxsOpt =
|
||||||
parser.accepts(DUMP_DELAYED_PAYOUT_TXS, "Dump delayed payout transactions to file")
|
parser.accepts(DUMP_DELAYED_PAYOUT_TXS, "Dump delayed payout transactions to file")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
|
@ -767,19 +669,7 @@ public class Config {
|
||||||
this.useAllProvidedNodes = options.valueOf(useAllProvidedNodesOpt);
|
this.useAllProvidedNodes = options.valueOf(useAllProvidedNodesOpt);
|
||||||
this.userAgent = options.valueOf(userAgentOpt);
|
this.userAgent = options.valueOf(userAgentOpt);
|
||||||
this.numConnectionsForBtc = options.valueOf(numConnectionsForBtcOpt);
|
this.numConnectionsForBtc = options.valueOf(numConnectionsForBtcOpt);
|
||||||
this.rpcUser = options.valueOf(rpcUserOpt);
|
|
||||||
this.rpcPassword = options.valueOf(rpcPasswordOpt);
|
|
||||||
this.rpcHost = options.valueOf(rpcHostOpt);
|
|
||||||
this.rpcPort = options.valueOf(rpcPortOpt);
|
|
||||||
this.rpcBlockNotificationPort = options.valueOf(rpcBlockNotificationPortOpt);
|
|
||||||
this.rpcBlockNotificationHost = options.valueOf(rpcBlockNotificationHostOpt);
|
|
||||||
this.dumpBlockchainData = options.valueOf(dumpBlockchainDataOpt);
|
|
||||||
this.fullDaoNode = options.valueOf(fullDaoNodeOpt);
|
|
||||||
this.fullDaoNodeOptionSetExplicitly = options.has(fullDaoNodeOpt);
|
|
||||||
this.genesisTxId = options.valueOf(genesisTxIdOpt);
|
|
||||||
this.genesisBlockHeight = options.valueOf(genesisBlockHeightOpt);
|
|
||||||
this.genesisTotalSupply = options.valueOf(genesisTotalSupplyOpt);
|
|
||||||
this.daoActivated = options.valueOf(daoActivatedOpt);
|
|
||||||
this.dumpDelayedPayoutTxs = options.valueOf(dumpDelayedPayoutTxsOpt);
|
this.dumpDelayedPayoutTxs = options.valueOf(dumpDelayedPayoutTxsOpt);
|
||||||
this.allowFaultyDelayedTxs = options.valueOf(allowFaultyDelayedTxsOpt);
|
this.allowFaultyDelayedTxs = options.valueOf(allowFaultyDelayedTxsOpt);
|
||||||
this.apiPassword = options.valueOf(apiPasswordOpt);
|
this.apiPassword = options.valueOf(apiPasswordOpt);
|
||||||
|
|
|
@ -67,8 +67,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
* with the modified model that data is written at shut down we eliminate frequent and expensive disk I/O. Risks of
|
* with the modified model that data is written at shut down we eliminate frequent and expensive disk I/O. Risks of
|
||||||
* deadlock or data inconsistency and a more complex model have been a further argument for that model. In fact
|
* deadlock or data inconsistency and a more complex model have been a further argument for that model. In fact
|
||||||
* previously we wasted a lot of resources as way too many threads have been created without doing actual work as well
|
* previously we wasted a lot of resources as way too many threads have been created without doing actual work as well
|
||||||
* the write operations got triggered way too often specially for the very frequent changes at SequenceNumberMap and
|
* the write operations got triggered way too often specially for the very frequent changes at SequenceNumberMap
|
||||||
* the very large DaoState (at dao blockchain sync that slowed down sync).
|
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param <T> The type of the {@link PersistableEnvelope} to be written or read from disk
|
* @param <T> The type of the {@link PersistableEnvelope} to be written or read from disk
|
||||||
|
|
|
@ -23,7 +23,6 @@ import java.util.HashSet;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static bisq.common.app.Capability.DAO_FULL_NODE;
|
|
||||||
import static bisq.common.app.Capability.SEED_NODE;
|
import static bisq.common.app.Capability.SEED_NODE;
|
||||||
import static bisq.common.app.Capability.TRADE_STATISTICS;
|
import static bisq.common.app.Capability.TRADE_STATISTICS;
|
||||||
import static bisq.common.app.Capability.TRADE_STATISTICS_2;
|
import static bisq.common.app.Capability.TRADE_STATISTICS_2;
|
||||||
|
@ -46,17 +45,12 @@ public class CapabilitiesTest {
|
||||||
assertTrue(new Capabilities().hasLess(new Capabilities(SEED_NODE)));
|
assertTrue(new Capabilities().hasLess(new Capabilities(SEED_NODE)));
|
||||||
assertFalse(new Capabilities().hasLess(new Capabilities()));
|
assertFalse(new Capabilities().hasLess(new Capabilities()));
|
||||||
assertFalse(new Capabilities(SEED_NODE).hasLess(new Capabilities()));
|
assertFalse(new Capabilities(SEED_NODE).hasLess(new Capabilities()));
|
||||||
assertTrue(new Capabilities(SEED_NODE).hasLess(new Capabilities(DAO_FULL_NODE)));
|
|
||||||
assertFalse(new Capabilities(DAO_FULL_NODE).hasLess(new Capabilities(SEED_NODE)));
|
|
||||||
|
|
||||||
Capabilities all = new Capabilities(
|
Capabilities all = new Capabilities(
|
||||||
TRADE_STATISTICS,
|
TRADE_STATISTICS,
|
||||||
TRADE_STATISTICS_2,
|
TRADE_STATISTICS_2,
|
||||||
Capability.ACCOUNT_AGE_WITNESS,
|
Capability.ACCOUNT_AGE_WITNESS,
|
||||||
Capability.ACK_MSG,
|
Capability.ACK_MSG,
|
||||||
Capability.PROPOSAL,
|
|
||||||
Capability.BLIND_VOTE,
|
|
||||||
Capability.DAO_STATE,
|
|
||||||
Capability.BUNDLE_OF_ENVELOPES,
|
Capability.BUNDLE_OF_ENVELOPES,
|
||||||
Capability.MEDIATION,
|
Capability.MEDIATION,
|
||||||
Capability.SIGNED_ACCOUNT_AGE_WITNESS,
|
Capability.SIGNED_ACCOUNT_AGE_WITNESS,
|
||||||
|
@ -68,9 +62,6 @@ public class CapabilitiesTest {
|
||||||
TRADE_STATISTICS_2,
|
TRADE_STATISTICS_2,
|
||||||
Capability.ACCOUNT_AGE_WITNESS,
|
Capability.ACCOUNT_AGE_WITNESS,
|
||||||
Capability.ACK_MSG,
|
Capability.ACK_MSG,
|
||||||
Capability.PROPOSAL,
|
|
||||||
Capability.BLIND_VOTE,
|
|
||||||
Capability.DAO_STATE,
|
|
||||||
Capability.BUNDLE_OF_ENVELOPES,
|
Capability.BUNDLE_OF_ENVELOPES,
|
||||||
Capability.MEDIATION,
|
Capability.MEDIATION,
|
||||||
Capability.SIGNED_ACCOUNT_AGE_WITNESS,
|
Capability.SIGNED_ACCOUNT_AGE_WITNESS,
|
||||||
|
|
|
@ -20,7 +20,6 @@ package bisq.core.api;
|
||||||
import bisq.core.api.model.AddressBalanceInfo;
|
import bisq.core.api.model.AddressBalanceInfo;
|
||||||
import bisq.core.api.model.BalancesInfo;
|
import bisq.core.api.model.BalancesInfo;
|
||||||
import bisq.core.api.model.TxFeeRateInfo;
|
import bisq.core.api.model.TxFeeRateInfo;
|
||||||
import bisq.core.btc.wallet.TxBroadcaster;
|
|
||||||
import bisq.core.monetary.Price;
|
import bisq.core.monetary.Price;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferPayload;
|
import bisq.core.offer.OfferPayload;
|
||||||
|
@ -148,7 +147,6 @@ public class CoreApi {
|
||||||
double buyerSecurityDeposit,
|
double buyerSecurityDeposit,
|
||||||
long triggerPrice,
|
long triggerPrice,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
String makerFeeCurrencyCode,
|
|
||||||
Consumer<Offer> resultHandler) {
|
Consumer<Offer> resultHandler) {
|
||||||
coreOffersService.createAndPlaceOffer(currencyCode,
|
coreOffersService.createAndPlaceOffer(currencyCode,
|
||||||
directionAsString,
|
directionAsString,
|
||||||
|
@ -160,7 +158,6 @@ public class CoreApi {
|
||||||
buyerSecurityDeposit,
|
buyerSecurityDeposit,
|
||||||
triggerPrice,
|
triggerPrice,
|
||||||
paymentAccountId,
|
paymentAccountId,
|
||||||
makerFeeCurrencyCode,
|
|
||||||
resultHandler);
|
resultHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,13 +235,11 @@ public class CoreApi {
|
||||||
|
|
||||||
public void takeOffer(String offerId,
|
public void takeOffer(String offerId,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
String takerFeeCurrencyCode,
|
|
||||||
Consumer<Trade> resultHandler,
|
Consumer<Trade> resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
Offer offer = coreOffersService.getOffer(offerId);
|
Offer offer = coreOffersService.getOffer(offerId);
|
||||||
coreTradesService.takeOffer(offer,
|
coreTradesService.takeOffer(offer,
|
||||||
paymentAccountId,
|
paymentAccountId,
|
||||||
takerFeeCurrencyCode,
|
|
||||||
resultHandler,
|
resultHandler,
|
||||||
errorMessageHandler);
|
errorMessageHandler);
|
||||||
}
|
}
|
||||||
|
@ -297,17 +292,6 @@ public class CoreApi {
|
||||||
return walletsService.getFundingAddresses();
|
return walletsService.getFundingAddresses();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUnusedBsqAddress() {
|
|
||||||
return walletsService.getUnusedBsqAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendBsq(String address,
|
|
||||||
String amount,
|
|
||||||
String txFeeRate,
|
|
||||||
TxBroadcaster.Callback callback) {
|
|
||||||
walletsService.sendBsq(address, amount, txFeeRate, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendBtc(String address,
|
public void sendBtc(String address,
|
||||||
String amount,
|
String amount,
|
||||||
String txFeeRate,
|
String txFeeRate,
|
||||||
|
@ -316,9 +300,6 @@ public class CoreApi {
|
||||||
walletsService.sendBtc(address, amount, txFeeRate, memo, callback);
|
walletsService.sendBtc(address, amount, txFeeRate, memo, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean verifyBsqSentToAddress(String address, String amount) {
|
|
||||||
return walletsService.verifyBsqSentToAddress(address, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void getTxFeeRate(ResultHandler resultHandler) {
|
public void getTxFeeRate(ResultHandler resultHandler) {
|
||||||
walletsService.getTxFeeRate(resultHandler);
|
walletsService.getTxFeeRate(resultHandler);
|
||||||
|
|
|
@ -82,7 +82,6 @@ class CoreDisputeAgentsService {
|
||||||
throw new IllegalStateException("p2p service is not bootstrapped yet");
|
throw new IllegalStateException("p2p service is not bootstrapped yet");
|
||||||
|
|
||||||
if (config.baseCurrencyNetwork.isMainnet()
|
if (config.baseCurrencyNetwork.isMainnet()
|
||||||
|| config.baseCurrencyNetwork.isDaoBetaNet()
|
|
||||||
|| !config.useLocalhostForP2P)
|
|| !config.useLocalhostForP2P)
|
||||||
throw new IllegalStateException("dispute agents must be registered in a Bisq UI");
|
throw new IllegalStateException("dispute agents must be registered in a Bisq UI");
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,6 @@ class CoreHelpService {
|
||||||
out.println(coreHelpService.getMethodHelp("getversion"));
|
out.println(coreHelpService.getMethodHelp("getversion"));
|
||||||
// out.println(coreHelpService.getMethodHelp("getfundingaddresses"));
|
// out.println(coreHelpService.getMethodHelp("getfundingaddresses"));
|
||||||
// out.println(coreHelpService.getMethodHelp("getfundingaddresses"));
|
// out.println(coreHelpService.getMethodHelp("getfundingaddresses"));
|
||||||
// out.println(coreHelpService.getMethodHelp("getunusedbsqaddress"));
|
|
||||||
// out.println(coreHelpService.getMethodHelp("unsettxfeerate"));
|
// out.println(coreHelpService.getMethodHelp("unsettxfeerate"));
|
||||||
// out.println(coreHelpService.getMethodHelp("getpaymentmethods"));
|
// out.println(coreHelpService.getMethodHelp("getpaymentmethods"));
|
||||||
// out.println(coreHelpService.getMethodHelp("getpaymentaccts"));
|
// out.println(coreHelpService.getMethodHelp("getpaymentaccts"));
|
||||||
|
|
|
@ -200,11 +200,9 @@ class CoreOffersService {
|
||||||
double buyerSecurityDeposit,
|
double buyerSecurityDeposit,
|
||||||
long triggerPrice,
|
long triggerPrice,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
String makerFeeCurrencyCode,
|
|
||||||
Consumer<Offer> resultHandler) {
|
Consumer<Offer> resultHandler) {
|
||||||
coreWalletsService.verifyWalletsAreAvailable();
|
coreWalletsService.verifyWalletsAreAvailable();
|
||||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||||
offerUtil.maybeSetFeePaymentCurrencyPreference(makerFeeCurrencyCode);
|
|
||||||
|
|
||||||
PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId);
|
PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId);
|
||||||
if (paymentAccount == null)
|
if (paymentAccount == null)
|
||||||
|
|
|
@ -19,7 +19,6 @@ package bisq.core.api;
|
||||||
|
|
||||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.api.model.PaymentAccountForm;
|
import bisq.core.api.model.PaymentAccountForm;
|
||||||
import bisq.core.locale.CryptoCurrency;
|
|
||||||
import bisq.core.payment.CryptoCurrencyAccount;
|
import bisq.core.payment.CryptoCurrencyAccount;
|
||||||
import bisq.core.payment.InstantCryptoCurrencyAccount;
|
import bisq.core.payment.InstantCryptoCurrencyAccount;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
@ -34,13 +33,11 @@ import java.io.File;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static bisq.core.locale.CurrencyUtil.getCryptoCurrency;
|
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
@ -103,21 +100,12 @@ class CorePaymentAccountsService {
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
String address,
|
String address,
|
||||||
boolean tradeInstant) {
|
boolean tradeInstant) {
|
||||||
String bsqCode = currencyCode.toUpperCase();
|
|
||||||
if (!bsqCode.equals("BSQ"))
|
|
||||||
throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts");
|
|
||||||
|
|
||||||
// Validate the BSQ address string but ignore the return value.
|
|
||||||
coreWalletsService.getValidBsqLegacyAddress(address);
|
|
||||||
|
|
||||||
var cryptoCurrencyAccount = tradeInstant
|
var cryptoCurrencyAccount = tradeInstant
|
||||||
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
|
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
|
||||||
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
|
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
|
||||||
cryptoCurrencyAccount.init();
|
cryptoCurrencyAccount.init();
|
||||||
cryptoCurrencyAccount.setAccountName(accountName);
|
cryptoCurrencyAccount.setAccountName(accountName);
|
||||||
cryptoCurrencyAccount.setAddress(address);
|
cryptoCurrencyAccount.setAddress(address);
|
||||||
Optional<CryptoCurrency> cryptoCurrency = getCryptoCurrency(bsqCode);
|
|
||||||
cryptoCurrency.ifPresent(cryptoCurrencyAccount::setSingleTradeCurrency);
|
|
||||||
user.addPaymentAccount(cryptoCurrencyAccount);
|
user.addPaymentAccount(cryptoCurrencyAccount);
|
||||||
accountAgeWitnessService.publishMyAccountAgeWitness(cryptoCurrencyAccount.getPaymentAccountPayload());
|
accountAgeWitnessService.publishMyAccountAgeWitness(cryptoCurrencyAccount.getPaymentAccountPayload());
|
||||||
log.info("Saved crypto payment account with id {} and payment method {}.",
|
log.info("Saved crypto payment account with id {} and payment method {}.",
|
||||||
|
|
|
@ -87,14 +87,11 @@ class CoreTradesService {
|
||||||
|
|
||||||
void takeOffer(Offer offer,
|
void takeOffer(Offer offer,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
String takerFeeCurrencyCode,
|
|
||||||
Consumer<Trade> resultHandler,
|
Consumer<Trade> resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
coreWalletsService.verifyWalletsAreAvailable();
|
coreWalletsService.verifyWalletsAreAvailable();
|
||||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||||
|
|
||||||
offerUtil.maybeSetFeePaymentCurrencyPreference(takerFeeCurrencyCode);
|
|
||||||
|
|
||||||
var paymentAccount = user.getPaymentAccount(paymentAccountId);
|
var paymentAccount = user.getPaymentAccount(paymentAccountId);
|
||||||
if (paymentAccount == null)
|
if (paymentAccount == null)
|
||||||
throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId));
|
throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId));
|
||||||
|
|
|
@ -19,22 +19,17 @@ package bisq.core.api;
|
||||||
|
|
||||||
import bisq.core.api.model.AddressBalanceInfo;
|
import bisq.core.api.model.AddressBalanceInfo;
|
||||||
import bisq.core.api.model.BalancesInfo;
|
import bisq.core.api.model.BalancesInfo;
|
||||||
import bisq.core.api.model.BsqBalanceInfo;
|
|
||||||
import bisq.core.api.model.BtcBalanceInfo;
|
import bisq.core.api.model.BtcBalanceInfo;
|
||||||
import bisq.core.api.model.TxFeeRateInfo;
|
import bisq.core.api.model.TxFeeRateInfo;
|
||||||
import bisq.core.api.model.XmrBalanceInfo;
|
import bisq.core.api.model.XmrBalanceInfo;
|
||||||
import bisq.core.app.AppStartupState;
|
import bisq.core.app.AppStartupState;
|
||||||
import bisq.core.btc.Balances;
|
import bisq.core.btc.Balances;
|
||||||
import bisq.core.btc.exceptions.AddressEntryException;
|
import bisq.core.btc.exceptions.AddressEntryException;
|
||||||
import bisq.core.btc.exceptions.BsqChangeBelowDustException;
|
|
||||||
import bisq.core.btc.exceptions.InsufficientFundsException;
|
import bisq.core.btc.exceptions.InsufficientFundsException;
|
||||||
import bisq.core.btc.exceptions.TransactionVerificationException;
|
import bisq.core.btc.exceptions.TransactionVerificationException;
|
||||||
import bisq.core.btc.exceptions.WalletException;
|
import bisq.core.btc.exceptions.WalletException;
|
||||||
import bisq.core.btc.model.AddressEntry;
|
import bisq.core.btc.model.AddressEntry;
|
||||||
import bisq.core.btc.model.BsqTransferModel;
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.btc.wallet.BsqTransferService;
|
|
||||||
import bisq.core.btc.wallet.BsqWalletService;
|
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.TxBroadcaster;
|
import bisq.core.btc.wallet.TxBroadcaster;
|
||||||
import bisq.core.btc.wallet.WalletsManager;
|
import bisq.core.btc.wallet.WalletsManager;
|
||||||
|
@ -42,7 +37,6 @@ import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
import bisq.core.util.coin.BsqFormatter;
|
|
||||||
import bisq.core.util.coin.CoinFormatter;
|
import bisq.core.util.coin.CoinFormatter;
|
||||||
|
|
||||||
import bisq.common.Timer;
|
import bisq.common.Timer;
|
||||||
|
@ -100,9 +94,6 @@ class CoreWalletsService {
|
||||||
private final Balances balances;
|
private final Balances balances;
|
||||||
private final WalletsManager walletsManager;
|
private final WalletsManager walletsManager;
|
||||||
private final WalletsSetup walletsSetup;
|
private final WalletsSetup walletsSetup;
|
||||||
private final BsqWalletService bsqWalletService;
|
|
||||||
private final BsqTransferService bsqTransferService;
|
|
||||||
private final BsqFormatter bsqFormatter;
|
|
||||||
private final BtcWalletService btcWalletService;
|
private final BtcWalletService btcWalletService;
|
||||||
private final XmrWalletService xmrWalletService;
|
private final XmrWalletService xmrWalletService;
|
||||||
private final CoinFormatter btcFormatter;
|
private final CoinFormatter btcFormatter;
|
||||||
|
@ -123,9 +114,6 @@ class CoreWalletsService {
|
||||||
Balances balances,
|
Balances balances,
|
||||||
WalletsManager walletsManager,
|
WalletsManager walletsManager,
|
||||||
WalletsSetup walletsSetup,
|
WalletsSetup walletsSetup,
|
||||||
BsqWalletService bsqWalletService,
|
|
||||||
BsqTransferService bsqTransferService,
|
|
||||||
BsqFormatter bsqFormatter,
|
|
||||||
BtcWalletService btcWalletService,
|
BtcWalletService btcWalletService,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||||
|
@ -136,9 +124,6 @@ class CoreWalletsService {
|
||||||
this.balances = balances;
|
this.balances = balances;
|
||||||
this.walletsManager = walletsManager;
|
this.walletsManager = walletsManager;
|
||||||
this.walletsSetup = walletsSetup;
|
this.walletsSetup = walletsSetup;
|
||||||
this.bsqWalletService = bsqWalletService;
|
|
||||||
this.bsqTransferService = bsqTransferService;
|
|
||||||
this.bsqFormatter = bsqFormatter;
|
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
this.btcFormatter = btcFormatter;
|
this.btcFormatter = btcFormatter;
|
||||||
|
@ -164,14 +149,12 @@ class CoreWalletsService {
|
||||||
throw new IllegalStateException("balance is not yet available");
|
throw new IllegalStateException("balance is not yet available");
|
||||||
|
|
||||||
switch (currencyCode.trim().toUpperCase()) {
|
switch (currencyCode.trim().toUpperCase()) {
|
||||||
case "BSQ":
|
|
||||||
return new BalancesInfo(getBsqBalances(), BtcBalanceInfo.EMPTY, XmrBalanceInfo.EMPTY);
|
|
||||||
case "BTC":
|
case "BTC":
|
||||||
return new BalancesInfo(BsqBalanceInfo.EMPTY, getBtcBalances(), XmrBalanceInfo.EMPTY);
|
return new BalancesInfo(getBtcBalances(), XmrBalanceInfo.EMPTY);
|
||||||
case "XMR":
|
case "XMR":
|
||||||
return new BalancesInfo(BsqBalanceInfo.EMPTY, BtcBalanceInfo.EMPTY, getXmrBalances());
|
return new BalancesInfo(BtcBalanceInfo.EMPTY, getXmrBalances());
|
||||||
default:
|
default:
|
||||||
return new BalancesInfo(getBsqBalances(), getBtcBalances(), getXmrBalances());
|
return new BalancesInfo(getBtcBalances(), getXmrBalances());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,40 +215,6 @@ class CoreWalletsService {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
String getUnusedBsqAddress() {
|
|
||||||
return bsqWalletService.getUnusedBsqAddressAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendBsq(String address,
|
|
||||||
String amount,
|
|
||||||
String txFeeRate,
|
|
||||||
TxBroadcaster.Callback callback) {
|
|
||||||
verifyWalletsAreAvailable();
|
|
||||||
verifyEncryptedWalletIsUnlocked();
|
|
||||||
|
|
||||||
try {
|
|
||||||
LegacyAddress legacyAddress = getValidBsqLegacyAddress(address);
|
|
||||||
Coin receiverAmount = getValidTransferAmount(amount, bsqFormatter);
|
|
||||||
Coin txFeePerVbyte = getTxFeeRateFromParamOrPreferenceOrFeeService(txFeeRate);
|
|
||||||
BsqTransferModel model = bsqTransferService.getBsqTransferModel(legacyAddress,
|
|
||||||
receiverAmount,
|
|
||||||
txFeePerVbyte);
|
|
||||||
log.info("Sending {} BSQ to {} with tx fee rate {} sats/byte.",
|
|
||||||
amount,
|
|
||||||
address,
|
|
||||||
txFeePerVbyte.value);
|
|
||||||
bsqTransferService.sendFunds(model, callback);
|
|
||||||
} catch (InsufficientMoneyException ex) {
|
|
||||||
log.error("", ex);
|
|
||||||
throw new IllegalStateException("cannot send bsq due to insufficient funds", ex);
|
|
||||||
} catch (NumberFormatException
|
|
||||||
| BsqChangeBelowDustException
|
|
||||||
| TransactionVerificationException
|
|
||||||
| WalletException ex) {
|
|
||||||
log.error("", ex);
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendBtc(String address,
|
void sendBtc(String address,
|
||||||
String amount,
|
String amount,
|
||||||
|
@ -322,40 +271,6 @@ class CoreWalletsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean verifyBsqSentToAddress(String address, String amount) {
|
|
||||||
Address receiverAddress = getValidBsqLegacyAddress(address);
|
|
||||||
NetworkParameters networkParameters = getNetworkParameters();
|
|
||||||
Predicate<TransactionOutput> isTxOutputAddressMatch = (txOut) ->
|
|
||||||
txOut.getScriptPubKey().getToAddress(networkParameters).equals(receiverAddress);
|
|
||||||
Coin coinValue = parseToCoin(amount, bsqFormatter);
|
|
||||||
Predicate<TransactionOutput> isTxOutputValueMatch = (txOut) ->
|
|
||||||
txOut.getValue().longValue() == coinValue.longValue();
|
|
||||||
List<TransactionOutput> spendableBsqTxOutputs = bsqWalletService.getSpendableBsqTransactionOutputs();
|
|
||||||
|
|
||||||
log.info("Searching {} spendable tx outputs for matching address {} and value {}:",
|
|
||||||
spendableBsqTxOutputs.size(),
|
|
||||||
address,
|
|
||||||
coinValue.toPlainString());
|
|
||||||
long numMatches = 0;
|
|
||||||
for (TransactionOutput txOut : spendableBsqTxOutputs) {
|
|
||||||
if (isTxOutputAddressMatch.test(txOut) && isTxOutputValueMatch.test(txOut)) {
|
|
||||||
log.info("\t\tTx {} output has matching address {} and value {}.",
|
|
||||||
txOut.getParentTransaction().getTxId(),
|
|
||||||
address,
|
|
||||||
txOut.getValue().toPlainString());
|
|
||||||
numMatches++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (numMatches > 1) {
|
|
||||||
log.warn("{} tx outputs matched address {} and value {}, could be a"
|
|
||||||
+ " false positive BSQ payment verification result.",
|
|
||||||
numMatches,
|
|
||||||
address,
|
|
||||||
coinValue.toPlainString());
|
|
||||||
|
|
||||||
}
|
|
||||||
return numMatches > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void getTxFeeRate(ResultHandler resultHandler) {
|
void getTxFeeRate(ResultHandler resultHandler) {
|
||||||
try {
|
try {
|
||||||
|
@ -561,23 +476,13 @@ class CoreWalletsService {
|
||||||
throw new IllegalStateException("server is not fully initialized");
|
throw new IllegalStateException("server is not fully initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a LegacyAddress for the string, or a RuntimeException if invalid.
|
|
||||||
LegacyAddress getValidBsqLegacyAddress(String address) {
|
|
||||||
try {
|
|
||||||
return bsqFormatter.getAddressFromBsqAddress(address);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
log.error("", t);
|
|
||||||
throw new IllegalStateException(format("%s is not a valid bsq address", address));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throws a RuntimeException if wallet currency code is not BSQ or BTC.
|
// Throws a RuntimeException if wallet currency code is not BTC.
|
||||||
private void verifyWalletCurrencyCodeIsValid(String currencyCode) {
|
private void verifyWalletCurrencyCodeIsValid(String currencyCode) {
|
||||||
if (currencyCode == null || currencyCode.isEmpty())
|
if (currencyCode == null || currencyCode.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!currencyCode.equalsIgnoreCase("BSQ")
|
if (!currencyCode.equalsIgnoreCase("BTC"))
|
||||||
&& !currencyCode.equalsIgnoreCase("BTC"))
|
|
||||||
throw new IllegalStateException(format("wallet does not support %s", currencyCode));
|
throw new IllegalStateException(format("wallet does not support %s", currencyCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -587,31 +492,13 @@ class CoreWalletsService {
|
||||||
if (tempAesKey == null)
|
if (tempAesKey == null)
|
||||||
throw new IllegalStateException("cannot use null key, unlockwallet timeout may have expired");
|
throw new IllegalStateException("cannot use null key, unlockwallet timeout may have expired");
|
||||||
|
|
||||||
if (btcWalletService.getAesKey() == null || bsqWalletService.getAesKey() == null) {
|
if (btcWalletService.getAesKey() == null) {
|
||||||
KeyParameter aesKey = new KeyParameter(tempAesKey.getKey());
|
KeyParameter aesKey = new KeyParameter(tempAesKey.getKey());
|
||||||
walletsManager.setAesKey(aesKey);
|
walletsManager.setAesKey(aesKey);
|
||||||
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(), aesKey);
|
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(), aesKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BsqBalanceInfo getBsqBalances() {
|
|
||||||
verifyWalletsAreAvailable();
|
|
||||||
verifyEncryptedWalletIsUnlocked();
|
|
||||||
|
|
||||||
var availableConfirmedBalance = bsqWalletService.getAvailableConfirmedBalance();
|
|
||||||
var unverifiedBalance = bsqWalletService.getUnverifiedBalance();
|
|
||||||
var unconfirmedChangeBalance = bsqWalletService.getUnconfirmedChangeBalance();
|
|
||||||
var lockedForVotingBalance = bsqWalletService.getLockedForVotingBalance();
|
|
||||||
var lockupBondsBalance = bsqWalletService.getLockupBondsBalance();
|
|
||||||
var unlockingBondsBalance = bsqWalletService.getUnlockingBondsBalance();
|
|
||||||
|
|
||||||
return new BsqBalanceInfo(availableConfirmedBalance.value,
|
|
||||||
unverifiedBalance.value,
|
|
||||||
unconfirmedChangeBalance.value,
|
|
||||||
lockedForVotingBalance.value,
|
|
||||||
lockupBondsBalance.value,
|
|
||||||
unlockingBondsBalance.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (woodser): delete this since it's serving XMR balances
|
// TODO (woodser): delete this since it's serving XMR balances
|
||||||
private BtcBalanceInfo getBtcBalances() {
|
private BtcBalanceInfo getBtcBalances() {
|
||||||
|
|
|
@ -10,12 +10,10 @@ public class BalancesInfo implements Payload {
|
||||||
// Getter names are shortened for readability's sake, i.e.,
|
// Getter names are shortened for readability's sake, i.e.,
|
||||||
// balancesInfo.getBtc().getAvailableBalance() is cleaner than
|
// balancesInfo.getBtc().getAvailableBalance() is cleaner than
|
||||||
// balancesInfo.getBtcBalanceInfo().getAvailableBalance().
|
// balancesInfo.getBtcBalanceInfo().getAvailableBalance().
|
||||||
private final BsqBalanceInfo bsq;
|
|
||||||
private final BtcBalanceInfo btc;
|
private final BtcBalanceInfo btc;
|
||||||
private final XmrBalanceInfo xmr;
|
private final XmrBalanceInfo xmr;
|
||||||
|
|
||||||
public BalancesInfo(BsqBalanceInfo bsq, BtcBalanceInfo btc, XmrBalanceInfo xmr) {
|
public BalancesInfo(BtcBalanceInfo btc, XmrBalanceInfo xmr) {
|
||||||
this.bsq = bsq;
|
|
||||||
this.btc = btc;
|
this.btc = btc;
|
||||||
this.xmr = xmr;
|
this.xmr = xmr;
|
||||||
}
|
}
|
||||||
|
@ -27,14 +25,13 @@ public class BalancesInfo implements Payload {
|
||||||
@Override
|
@Override
|
||||||
public bisq.proto.grpc.BalancesInfo toProtoMessage() {
|
public bisq.proto.grpc.BalancesInfo toProtoMessage() {
|
||||||
return bisq.proto.grpc.BalancesInfo.newBuilder()
|
return bisq.proto.grpc.BalancesInfo.newBuilder()
|
||||||
.setBsq(bsq.toProtoMessage())
|
|
||||||
.setBtc(btc.toProtoMessage())
|
.setBtc(btc.toProtoMessage())
|
||||||
.setXmr(xmr.toProtoMessage())
|
.setXmr(xmr.toProtoMessage())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BalancesInfo fromProto(bisq.proto.grpc.BalancesInfo proto) {
|
public static BalancesInfo fromProto(bisq.proto.grpc.BalancesInfo proto) {
|
||||||
return new BalancesInfo(BsqBalanceInfo.fromProto(proto.getBsq()),
|
return new BalancesInfo(
|
||||||
BtcBalanceInfo.fromProto(proto.getBtc()),
|
BtcBalanceInfo.fromProto(proto.getBtc()),
|
||||||
XmrBalanceInfo.fromProto(proto.getXmr()));
|
XmrBalanceInfo.fromProto(proto.getXmr()));
|
||||||
}
|
}
|
||||||
|
@ -42,8 +39,7 @@ public class BalancesInfo implements Payload {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "BalancesInfo{" + "\n" +
|
return "BalancesInfo{" + "\n" +
|
||||||
" " + bsq.toString() + "\n" +
|
" " + btc.toString() + "\n" +
|
||||||
", " + btc.toString() + "\n" +
|
|
||||||
", " + xmr.toString() + "\n" +
|
", " + xmr.toString() + "\n" +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
package bisq.core.api.model;
|
|
||||||
|
|
||||||
import bisq.common.Payload;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class BsqBalanceInfo implements Payload {
|
|
||||||
|
|
||||||
public static final BsqBalanceInfo EMPTY = new BsqBalanceInfo(-1,
|
|
||||||
-1,
|
|
||||||
-1,
|
|
||||||
-1,
|
|
||||||
-1,
|
|
||||||
-1);
|
|
||||||
|
|
||||||
// All balances are in BSQ satoshis.
|
|
||||||
private final long availableConfirmedBalance;
|
|
||||||
private final long unverifiedBalance;
|
|
||||||
private final long unconfirmedChangeBalance;
|
|
||||||
private final long lockedForVotingBalance;
|
|
||||||
private final long lockupBondsBalance;
|
|
||||||
private final long unlockingBondsBalance;
|
|
||||||
|
|
||||||
public BsqBalanceInfo(long availableConfirmedBalance,
|
|
||||||
long unverifiedBalance,
|
|
||||||
long unconfirmedChangeBalance,
|
|
||||||
long lockedForVotingBalance,
|
|
||||||
long lockupBondsBalance,
|
|
||||||
long unlockingBondsBalance) {
|
|
||||||
this.availableConfirmedBalance = availableConfirmedBalance;
|
|
||||||
this.unverifiedBalance = unverifiedBalance;
|
|
||||||
this.unconfirmedChangeBalance = unconfirmedChangeBalance;
|
|
||||||
this.lockedForVotingBalance = lockedForVotingBalance;
|
|
||||||
this.lockupBondsBalance = lockupBondsBalance;
|
|
||||||
this.unlockingBondsBalance = unlockingBondsBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public static BsqBalanceInfo valueOf(long availableConfirmedBalance,
|
|
||||||
long unverifiedBalance,
|
|
||||||
long unconfirmedChangeBalance,
|
|
||||||
long lockedForVotingBalance,
|
|
||||||
long lockupBondsBalance,
|
|
||||||
long unlockingBondsBalance) {
|
|
||||||
// Convenience for creating a model instance instead of a proto.
|
|
||||||
return new BsqBalanceInfo(availableConfirmedBalance,
|
|
||||||
unverifiedBalance,
|
|
||||||
unconfirmedChangeBalance,
|
|
||||||
lockedForVotingBalance,
|
|
||||||
lockupBondsBalance,
|
|
||||||
unlockingBondsBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// PROTO BUFFER
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public bisq.proto.grpc.BsqBalanceInfo toProtoMessage() {
|
|
||||||
return bisq.proto.grpc.BsqBalanceInfo.newBuilder()
|
|
||||||
.setAvailableConfirmedBalance(availableConfirmedBalance)
|
|
||||||
.setUnverifiedBalance(unverifiedBalance)
|
|
||||||
.setUnconfirmedChangeBalance(unconfirmedChangeBalance)
|
|
||||||
.setLockedForVotingBalance(lockedForVotingBalance)
|
|
||||||
.setLockupBondsBalance(lockupBondsBalance)
|
|
||||||
.setUnlockingBondsBalance(unlockingBondsBalance)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BsqBalanceInfo fromProto(bisq.proto.grpc.BsqBalanceInfo proto) {
|
|
||||||
return new BsqBalanceInfo(proto.getAvailableConfirmedBalance(),
|
|
||||||
proto.getUnverifiedBalance(),
|
|
||||||
proto.getUnconfirmedChangeBalance(),
|
|
||||||
proto.getLockedForVotingBalance(),
|
|
||||||
proto.getLockupBondsBalance(),
|
|
||||||
proto.getUnlockingBondsBalance());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "BsqBalanceInfo{" +
|
|
||||||
"availableConfirmedBalance=" + availableConfirmedBalance +
|
|
||||||
", unverifiedBalance=" + unverifiedBalance +
|
|
||||||
", unconfirmedChangeBalance=" + unconfirmedChangeBalance +
|
|
||||||
", lockedForVotingBalance=" + lockedForVotingBalance +
|
|
||||||
", lockupBondsBalance=" + lockupBondsBalance +
|
|
||||||
", unlockingBondsBalance=" + unlockingBondsBalance +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -51,7 +51,6 @@ public class OfferInfo implements Payload {
|
||||||
private final long buyerSecurityDeposit;
|
private final long buyerSecurityDeposit;
|
||||||
private final long sellerSecurityDeposit;
|
private final long sellerSecurityDeposit;
|
||||||
private final long triggerPrice;
|
private final long triggerPrice;
|
||||||
private final boolean isCurrencyForMakerFeeBtc;
|
|
||||||
private final String paymentAccountId;
|
private final String paymentAccountId;
|
||||||
private final String paymentMethodId;
|
private final String paymentMethodId;
|
||||||
private final String paymentMethodShortName;
|
private final String paymentMethodShortName;
|
||||||
|
@ -79,7 +78,6 @@ public class OfferInfo implements Payload {
|
||||||
this.buyerSecurityDeposit = builder.buyerSecurityDeposit;
|
this.buyerSecurityDeposit = builder.buyerSecurityDeposit;
|
||||||
this.sellerSecurityDeposit = builder.sellerSecurityDeposit;
|
this.sellerSecurityDeposit = builder.sellerSecurityDeposit;
|
||||||
this.triggerPrice = builder.triggerPrice;
|
this.triggerPrice = builder.triggerPrice;
|
||||||
this.isCurrencyForMakerFeeBtc = builder.isCurrencyForMakerFeeBtc;
|
|
||||||
this.paymentAccountId = builder.paymentAccountId;
|
this.paymentAccountId = builder.paymentAccountId;
|
||||||
this.paymentMethodId = builder.paymentMethodId;
|
this.paymentMethodId = builder.paymentMethodId;
|
||||||
this.paymentMethodShortName = builder.paymentMethodShortName;
|
this.paymentMethodShortName = builder.paymentMethodShortName;
|
||||||
|
@ -116,7 +114,6 @@ public class OfferInfo implements Payload {
|
||||||
.withOfferFeePaymentTxId(offer.getOfferFeePaymentTxId())
|
.withOfferFeePaymentTxId(offer.getOfferFeePaymentTxId())
|
||||||
.withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value)
|
.withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value)
|
||||||
.withSellerSecurityDeposit(offer.getSellerSecurityDeposit().value)
|
.withSellerSecurityDeposit(offer.getSellerSecurityDeposit().value)
|
||||||
.withIsCurrencyForMakerFeeBtc(offer.isCurrencyForMakerFeeBtc())
|
|
||||||
.withPaymentAccountId(offer.getMakerPaymentAccountId())
|
.withPaymentAccountId(offer.getMakerPaymentAccountId())
|
||||||
.withPaymentMethodId(offer.getPaymentMethod().getId())
|
.withPaymentMethodId(offer.getPaymentMethod().getId())
|
||||||
.withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
|
.withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
|
||||||
|
@ -148,7 +145,6 @@ public class OfferInfo implements Payload {
|
||||||
.setBuyerSecurityDeposit(buyerSecurityDeposit)
|
.setBuyerSecurityDeposit(buyerSecurityDeposit)
|
||||||
.setSellerSecurityDeposit(sellerSecurityDeposit)
|
.setSellerSecurityDeposit(sellerSecurityDeposit)
|
||||||
.setTriggerPrice(triggerPrice)
|
.setTriggerPrice(triggerPrice)
|
||||||
.setIsCurrencyForMakerFeeBtc(isCurrencyForMakerFeeBtc)
|
|
||||||
.setPaymentAccountId(paymentAccountId)
|
.setPaymentAccountId(paymentAccountId)
|
||||||
.setPaymentMethodId(paymentMethodId)
|
.setPaymentMethodId(paymentMethodId)
|
||||||
.setPaymentMethodShortName(paymentMethodShortName)
|
.setPaymentMethodShortName(paymentMethodShortName)
|
||||||
|
@ -177,7 +173,6 @@ public class OfferInfo implements Payload {
|
||||||
.withBuyerSecurityDeposit(proto.getBuyerSecurityDeposit())
|
.withBuyerSecurityDeposit(proto.getBuyerSecurityDeposit())
|
||||||
.withSellerSecurityDeposit(proto.getSellerSecurityDeposit())
|
.withSellerSecurityDeposit(proto.getSellerSecurityDeposit())
|
||||||
.withTriggerPrice(proto.getTriggerPrice())
|
.withTriggerPrice(proto.getTriggerPrice())
|
||||||
.withIsCurrencyForMakerFeeBtc(proto.getIsCurrencyForMakerFeeBtc())
|
|
||||||
.withPaymentAccountId(proto.getPaymentAccountId())
|
.withPaymentAccountId(proto.getPaymentAccountId())
|
||||||
.withPaymentMethodId(proto.getPaymentMethodId())
|
.withPaymentMethodId(proto.getPaymentMethodId())
|
||||||
.withPaymentMethodShortName(proto.getPaymentMethodShortName())
|
.withPaymentMethodShortName(proto.getPaymentMethodShortName())
|
||||||
|
@ -210,7 +205,6 @@ public class OfferInfo implements Payload {
|
||||||
private long buyerSecurityDeposit;
|
private long buyerSecurityDeposit;
|
||||||
private long sellerSecurityDeposit;
|
private long sellerSecurityDeposit;
|
||||||
private long triggerPrice;
|
private long triggerPrice;
|
||||||
private boolean isCurrencyForMakerFeeBtc;
|
|
||||||
private String paymentAccountId;
|
private String paymentAccountId;
|
||||||
private String paymentMethodId;
|
private String paymentMethodId;
|
||||||
private String paymentMethodShortName;
|
private String paymentMethodShortName;
|
||||||
|
@ -294,10 +288,6 @@ public class OfferInfo implements Payload {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfoBuilder withIsCurrencyForMakerFeeBtc(boolean isCurrencyForMakerFeeBtc) {
|
|
||||||
this.isCurrencyForMakerFeeBtc = isCurrencyForMakerFeeBtc;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OfferInfoBuilder withPaymentAccountId(String paymentAccountId) {
|
public OfferInfoBuilder withPaymentAccountId(String paymentAccountId) {
|
||||||
this.paymentAccountId = paymentAccountId;
|
this.paymentAccountId = paymentAccountId;
|
||||||
|
|
|
@ -43,7 +43,6 @@ public class TradeInfo implements Payload {
|
||||||
private final String shortId;
|
private final String shortId;
|
||||||
private final long date;
|
private final long date;
|
||||||
private final String role;
|
private final String role;
|
||||||
private final boolean isCurrencyForTakerFeeBtc;
|
|
||||||
private final long txFeeAsLong;
|
private final long txFeeAsLong;
|
||||||
private final long takerFeeAsLong;
|
private final long takerFeeAsLong;
|
||||||
private final String takerFeeTxId;
|
private final String takerFeeTxId;
|
||||||
|
@ -71,7 +70,6 @@ public class TradeInfo implements Payload {
|
||||||
this.shortId = builder.shortId;
|
this.shortId = builder.shortId;
|
||||||
this.date = builder.date;
|
this.date = builder.date;
|
||||||
this.role = builder.role;
|
this.role = builder.role;
|
||||||
this.isCurrencyForTakerFeeBtc = builder.isCurrencyForTakerFeeBtc;
|
|
||||||
this.txFeeAsLong = builder.txFeeAsLong;
|
this.txFeeAsLong = builder.txFeeAsLong;
|
||||||
this.takerFeeAsLong = builder.takerFeeAsLong;
|
this.takerFeeAsLong = builder.takerFeeAsLong;
|
||||||
this.takerFeeTxId = builder.takerFeeTxId;
|
this.takerFeeTxId = builder.takerFeeTxId;
|
||||||
|
@ -225,7 +223,6 @@ public class TradeInfo implements Payload {
|
||||||
private String shortId;
|
private String shortId;
|
||||||
private long date;
|
private long date;
|
||||||
private String role;
|
private String role;
|
||||||
private boolean isCurrencyForTakerFeeBtc;
|
|
||||||
private long txFeeAsLong;
|
private long txFeeAsLong;
|
||||||
private long takerFeeAsLong;
|
private long takerFeeAsLong;
|
||||||
private String takerFeeTxId;
|
private String takerFeeTxId;
|
||||||
|
@ -272,11 +269,6 @@ public class TradeInfo implements Payload {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeInfoBuilder withIsCurrencyForTakerFeeBtc(boolean isCurrencyForTakerFeeBtc) {
|
|
||||||
this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TradeInfoBuilder withTxFeeAsLong(long txFeeAsLong) {
|
public TradeInfoBuilder withTxFeeAsLong(long txFeeAsLong) {
|
||||||
this.txFeeAsLong = txFeeAsLong;
|
this.txFeeAsLong = txFeeAsLong;
|
||||||
return this;
|
return this;
|
||||||
|
@ -296,7 +288,7 @@ public class TradeInfo implements Payload {
|
||||||
this.makerDepositTxId = makerDepositTxId;
|
this.makerDepositTxId = makerDepositTxId;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeInfoBuilder withTakerDepositTxId(String takerDepositTxId) {
|
public TradeInfoBuilder withTakerDepositTxId(String takerDepositTxId) {
|
||||||
this.takerDepositTxId = takerDepositTxId;
|
this.takerDepositTxId = takerDepositTxId;
|
||||||
return this;
|
return this;
|
||||||
|
@ -389,7 +381,6 @@ public class TradeInfo implements Payload {
|
||||||
", shortId='" + shortId + '\'' + "\n" +
|
", shortId='" + shortId + '\'' + "\n" +
|
||||||
", date='" + date + '\'' + "\n" +
|
", date='" + date + '\'' + "\n" +
|
||||||
", role='" + role + '\'' + "\n" +
|
", role='" + role + '\'' + "\n" +
|
||||||
", isCurrencyForTakerFeeBtc='" + isCurrencyForTakerFeeBtc + '\'' + "\n" +
|
|
||||||
", txFeeAsLong='" + txFeeAsLong + '\'' + "\n" +
|
", txFeeAsLong='" + txFeeAsLong + '\'' + "\n" +
|
||||||
", takerFeeAsLong='" + takerFeeAsLong + '\'' + "\n" +
|
", takerFeeAsLong='" + takerFeeAsLong + '\'' + "\n" +
|
||||||
", takerFeeTxId='" + takerFeeTxId + '\'' + "\n" +
|
", takerFeeTxId='" + takerFeeTxId + '\'' + "\n" +
|
||||||
|
|
|
@ -18,11 +18,8 @@
|
||||||
package bisq.core.app;
|
package bisq.core.app;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.btc.wallet.BsqWalletService;
|
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.dao.DaoSetup;
|
|
||||||
import bisq.core.dao.node.full.RpcService;
|
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.setup.CorePersistedDataHost;
|
import bisq.core.setup.CorePersistedDataHost;
|
||||||
|
@ -235,8 +232,6 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
||||||
injector.getInstance(ArbitratorManager.class).shutDown();
|
injector.getInstance(ArbitratorManager.class).shutDown();
|
||||||
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
||||||
injector.getInstance(XmrTxProofService.class).shutDown();
|
injector.getInstance(XmrTxProofService.class).shutDown();
|
||||||
injector.getInstance(RpcService.class).shutDown();
|
|
||||||
injector.getInstance(DaoSetup.class).shutDown();
|
|
||||||
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
||||||
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc?
|
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc?
|
||||||
log.info("OpenOfferManager shutdown started");
|
log.info("OpenOfferManager shutdown started");
|
||||||
|
@ -244,7 +239,6 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
||||||
log.info("OpenOfferManager shutdown completed");
|
log.info("OpenOfferManager shutdown completed");
|
||||||
|
|
||||||
injector.getInstance(BtcWalletService.class).shutDown();
|
injector.getInstance(BtcWalletService.class).shutDown();
|
||||||
injector.getInstance(BsqWalletService.class).shutDown();
|
|
||||||
|
|
||||||
// We need to shutdown BitcoinJ before the P2PService as it uses Tor.
|
// We need to shutdown BitcoinJ before the P2PService as it uses Tor.
|
||||||
WalletsSetup walletsSetup = injector.getInstance(WalletsSetup.class);
|
WalletsSetup walletsSetup = injector.getInstance(WalletsSetup.class);
|
||||||
|
|
|
@ -84,12 +84,8 @@ public class BisqHeadlessApp implements HeadlessApp {
|
||||||
bisqSetup.setDisplayUpdateHandler((alert, key) -> log.info("onDisplayUpdateHandler"));
|
bisqSetup.setDisplayUpdateHandler((alert, key) -> log.info("onDisplayUpdateHandler"));
|
||||||
bisqSetup.setDisplayAlertHandler(alert -> log.info("onDisplayAlertHandler. alert={}", alert));
|
bisqSetup.setDisplayAlertHandler(alert -> log.info("onDisplayAlertHandler. alert={}", alert));
|
||||||
bisqSetup.setDisplayPrivateNotificationHandler(privateNotification -> log.info("onDisplayPrivateNotificationHandler. privateNotification={}", privateNotification));
|
bisqSetup.setDisplayPrivateNotificationHandler(privateNotification -> log.info("onDisplayPrivateNotificationHandler. privateNotification={}", privateNotification));
|
||||||
bisqSetup.setDaoErrorMessageHandler(errorMessage -> log.error("onDaoErrorMessageHandler. errorMessage={}", errorMessage));
|
|
||||||
bisqSetup.setDaoWarnMessageHandler(warnMessage -> log.warn("onDaoWarnMessageHandler. warnMessage={}", warnMessage));
|
|
||||||
bisqSetup.setDisplaySecurityRecommendationHandler(key -> log.info("onDisplaySecurityRecommendationHandler"));
|
bisqSetup.setDisplaySecurityRecommendationHandler(key -> log.info("onDisplaySecurityRecommendationHandler"));
|
||||||
bisqSetup.setDisplayLocalhostHandler(key -> log.info("onDisplayLocalhostHandler"));
|
|
||||||
bisqSetup.setWrongOSArchitectureHandler(msg -> log.error("onWrongOSArchitectureHandler. msg={}", msg));
|
bisqSetup.setWrongOSArchitectureHandler(msg -> log.error("onWrongOSArchitectureHandler. msg={}", msg));
|
||||||
bisqSetup.setVoteResultExceptionHandler(voteResultException -> log.warn("voteResultException={}", voteResultException.toString()));
|
|
||||||
bisqSetup.setRejectedTxErrorMessageHandler(errorMessage -> log.warn("setRejectedTxErrorMessageHandler. errorMessage={}", errorMessage));
|
bisqSetup.setRejectedTxErrorMessageHandler(errorMessage -> log.warn("setRejectedTxErrorMessageHandler. errorMessage={}", errorMessage));
|
||||||
bisqSetup.setShowPopupIfInvalidBtcConfigHandler(() -> log.error("onShowPopupIfInvalidBtcConfigHandler"));
|
bisqSetup.setShowPopupIfInvalidBtcConfigHandler(() -> log.error("onShowPopupIfInvalidBtcConfigHandler"));
|
||||||
bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList));
|
bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList));
|
||||||
|
@ -97,12 +93,6 @@ public class BisqHeadlessApp implements HeadlessApp {
|
||||||
bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
|
bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
|
||||||
bisqSetup.setDownGradePreventionHandler(lastVersion -> log.info("Downgrade from version {} to version {} is not supported",
|
bisqSetup.setDownGradePreventionHandler(lastVersion -> log.info("Downgrade from version {} to version {} is not supported",
|
||||||
lastVersion, Version.VERSION));
|
lastVersion, Version.VERSION));
|
||||||
bisqSetup.setDaoRequiresRestartHandler(() -> {
|
|
||||||
log.info("There was a problem with synchronizing the DAO state. " +
|
|
||||||
"A restart of the application is required to fix the issue.");
|
|
||||||
gracefulShutDownHandler.gracefulShutDown(() -> {
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
corruptedStorageFileHandler.getFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));
|
corruptedStorageFileHandler.getFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));
|
||||||
tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.error("onTakeOfferRequestErrorMessageHandler"));
|
tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.error("onTakeOfferRequestErrorMessageHandler"));
|
||||||
|
|
|
@ -30,8 +30,6 @@ import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.WalletsManager;
|
import bisq.core.btc.wallet.WalletsManager;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
|
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
|
||||||
import bisq.core.dao.governance.voteresult.VoteResultException;
|
|
||||||
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
|
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.payment.AmazonGiftCardAccount;
|
import bisq.core.payment.AmazonGiftCardAccount;
|
||||||
|
@ -135,7 +133,6 @@ public class BisqSetup {
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
private final User user;
|
private final User user;
|
||||||
private final AlertManager alertManager;
|
private final AlertManager alertManager;
|
||||||
private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService;
|
|
||||||
private final Config config;
|
private final Config config;
|
||||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||||
private final TorSetup torSetup;
|
private final TorSetup torSetup;
|
||||||
|
@ -149,7 +146,7 @@ public class BisqSetup {
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
private Consumer<String> chainFileLockedExceptionHandler,
|
private Consumer<String> chainFileLockedExceptionHandler,
|
||||||
spvFileCorruptedHandler, lockedUpFundsHandler, daoErrorMessageHandler, daoWarnMessageHandler,
|
spvFileCorruptedHandler, lockedUpFundsHandler,
|
||||||
filterWarningHandler, displaySecurityRecommendationHandler, displayLocalhostHandler,
|
filterWarningHandler, displaySecurityRecommendationHandler, displayLocalhostHandler,
|
||||||
wrongOSArchitectureHandler, displaySignedByArbitratorHandler,
|
wrongOSArchitectureHandler, displaySignedByArbitratorHandler,
|
||||||
displaySignedByPeerHandler, displayPeerLimitLiftedHandler, displayPeerSignerHandler,
|
displaySignedByPeerHandler, displayPeerLimitLiftedHandler, displayPeerSignerHandler,
|
||||||
|
@ -171,9 +168,6 @@ public class BisqSetup {
|
||||||
private BiConsumer<Alert, String> displayUpdateHandler;
|
private BiConsumer<Alert, String> displayUpdateHandler;
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
private Consumer<VoteResultException> voteResultExceptionHandler;
|
|
||||||
@Setter
|
|
||||||
@Nullable
|
|
||||||
private Consumer<PrivateNotificationPayload> displayPrivateNotificationHandler;
|
private Consumer<PrivateNotificationPayload> displayPrivateNotificationHandler;
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -192,9 +186,6 @@ public class BisqSetup {
|
||||||
private Runnable qubesOSInfoHandler;
|
private Runnable qubesOSInfoHandler;
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
private Runnable daoRequiresRestartHandler;
|
|
||||||
@Setter
|
|
||||||
@Nullable
|
|
||||||
private Consumer<String> downGradePreventionHandler;
|
private Consumer<String> downGradePreventionHandler;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -221,7 +212,6 @@ public class BisqSetup {
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
User user,
|
User user,
|
||||||
AlertManager alertManager,
|
AlertManager alertManager,
|
||||||
UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService,
|
|
||||||
Config config,
|
Config config,
|
||||||
AccountAgeWitnessService accountAgeWitnessService,
|
AccountAgeWitnessService accountAgeWitnessService,
|
||||||
TorSetup torSetup,
|
TorSetup torSetup,
|
||||||
|
@ -243,7 +233,6 @@ public class BisqSetup {
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.alertManager = alertManager;
|
this.alertManager = alertManager;
|
||||||
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
|
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||||
this.torSetup = torSetup;
|
this.torSetup = torSetup;
|
||||||
|
@ -334,10 +323,6 @@ public class BisqSetup {
|
||||||
try {
|
try {
|
||||||
walletsSetup.reSyncSPVChain();
|
walletsSetup.reSyncSPVChain();
|
||||||
|
|
||||||
// In case we had an unconfirmed change output we reset the unconfirmedBsqChangeOutputList so that
|
|
||||||
// after a SPV resync we do not have any dangling BSQ utxos in that list which would cause an incorrect
|
|
||||||
// BSQ balance state after the SPV resync.
|
|
||||||
unconfirmedBsqChangeOutputListService.onSpvResync();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error(e.toString());
|
log.error(e.toString());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -461,13 +446,9 @@ public class BisqSetup {
|
||||||
|
|
||||||
domainInitialisation.initDomainServices(rejectedTxErrorMessageHandler,
|
domainInitialisation.initDomainServices(rejectedTxErrorMessageHandler,
|
||||||
displayPrivateNotificationHandler,
|
displayPrivateNotificationHandler,
|
||||||
daoErrorMessageHandler,
|
|
||||||
daoWarnMessageHandler,
|
|
||||||
filterWarningHandler,
|
filterWarningHandler,
|
||||||
voteResultExceptionHandler,
|
|
||||||
revolutAccountsUpdateHandler,
|
revolutAccountsUpdateHandler,
|
||||||
amazonGiftCardAccountsUpdateHandler,
|
amazonGiftCardAccountsUpdateHandler);
|
||||||
daoRequiresRestartHandler);
|
|
||||||
|
|
||||||
if (walletsSetup.downloadPercentageProperty().get() == 1) {
|
if (walletsSetup.downloadPercentageProperty().get() == 1) {
|
||||||
checkForLockedUpFunds();
|
checkForLockedUpFunds();
|
||||||
|
|
|
@ -19,7 +19,6 @@ package bisq.core.app;
|
||||||
|
|
||||||
import bisq.core.alert.AlertModule;
|
import bisq.core.alert.AlertModule;
|
||||||
import bisq.core.btc.BitcoinModule;
|
import bisq.core.btc.BitcoinModule;
|
||||||
import bisq.core.dao.DaoModule;
|
|
||||||
import bisq.core.filter.FilterModule;
|
import bisq.core.filter.FilterModule;
|
||||||
import bisq.core.network.CoreNetworkFilter;
|
import bisq.core.network.CoreNetworkFilter;
|
||||||
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
|
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
|
||||||
|
@ -89,7 +88,6 @@ public class CoreModule extends AppModule {
|
||||||
install(new OfferModule(config));
|
install(new OfferModule(config));
|
||||||
install(new P2PModule(config));
|
install(new P2PModule(config));
|
||||||
install(new BitcoinModule(config));
|
install(new BitcoinModule(config));
|
||||||
install(new DaoModule(config));
|
|
||||||
install(new AlertModule(config));
|
install(new AlertModule(config));
|
||||||
install(new FilterModule(config));
|
install(new FilterModule(config));
|
||||||
install(new CorePresentationModule(config));
|
install(new CorePresentationModule(config));
|
||||||
|
|
|
@ -22,10 +22,6 @@ import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.alert.PrivateNotificationManager;
|
import bisq.core.alert.PrivateNotificationManager;
|
||||||
import bisq.core.alert.PrivateNotificationPayload;
|
import bisq.core.alert.PrivateNotificationPayload;
|
||||||
import bisq.core.btc.Balances;
|
import bisq.core.btc.Balances;
|
||||||
import bisq.core.dao.DaoSetup;
|
|
||||||
import bisq.core.dao.governance.voteresult.VoteResultException;
|
|
||||||
import bisq.core.dao.governance.voteresult.VoteResultService;
|
|
||||||
import bisq.core.dao.state.DaoStateSnapshotService;
|
|
||||||
import bisq.core.filter.FilterManager;
|
import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.notifications.MobileNotificationService;
|
import bisq.core.notifications.MobileNotificationService;
|
||||||
import bisq.core.notifications.alerts.DisputeMsgEvents;
|
import bisq.core.notifications.alerts.DisputeMsgEvents;
|
||||||
|
@ -37,7 +33,6 @@ import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.offer.TriggerPriceService;
|
import bisq.core.offer.TriggerPriceService;
|
||||||
import bisq.core.payment.AmazonGiftCardAccount;
|
import bisq.core.payment.AmazonGiftCardAccount;
|
||||||
import bisq.core.payment.RevolutAccount;
|
import bisq.core.payment.RevolutAccount;
|
||||||
import bisq.core.payment.TradeLimits;
|
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.mempool.MempoolService;
|
import bisq.core.provider.mempool.MempoolService;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
|
@ -77,7 +72,6 @@ import java.util.stream.Collectors;
|
||||||
*/
|
*/
|
||||||
public class DomainInitialisation {
|
public class DomainInitialisation {
|
||||||
private final ClockWatcher clockWatcher;
|
private final ClockWatcher clockWatcher;
|
||||||
private final TradeLimits tradeLimits;
|
|
||||||
private final ArbitrationManager arbitrationManager;
|
private final ArbitrationManager arbitrationManager;
|
||||||
private final MediationManager mediationManager;
|
private final MediationManager mediationManager;
|
||||||
private final RefundManager refundManager;
|
private final RefundManager refundManager;
|
||||||
|
@ -95,13 +89,11 @@ public class DomainInitialisation {
|
||||||
private final PrivateNotificationManager privateNotificationManager;
|
private final PrivateNotificationManager privateNotificationManager;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final FeeService feeService;
|
private final FeeService feeService;
|
||||||
private final DaoSetup daoSetup;
|
|
||||||
private final TradeStatisticsManager tradeStatisticsManager;
|
private final TradeStatisticsManager tradeStatisticsManager;
|
||||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||||
private final SignedWitnessService signedWitnessService;
|
private final SignedWitnessService signedWitnessService;
|
||||||
private final PriceFeedService priceFeedService;
|
private final PriceFeedService priceFeedService;
|
||||||
private final FilterManager filterManager;
|
private final FilterManager filterManager;
|
||||||
private final VoteResultService voteResultService;
|
|
||||||
private final MobileNotificationService mobileNotificationService;
|
private final MobileNotificationService mobileNotificationService;
|
||||||
private final MyOfferTakenEvents myOfferTakenEvents;
|
private final MyOfferTakenEvents myOfferTakenEvents;
|
||||||
private final TradeEvents tradeEvents;
|
private final TradeEvents tradeEvents;
|
||||||
|
@ -109,13 +101,11 @@ public class DomainInitialisation {
|
||||||
private final PriceAlert priceAlert;
|
private final PriceAlert priceAlert;
|
||||||
private final MarketAlerts marketAlerts;
|
private final MarketAlerts marketAlerts;
|
||||||
private final User user;
|
private final User user;
|
||||||
private final DaoStateSnapshotService daoStateSnapshotService;
|
|
||||||
private final TriggerPriceService triggerPriceService;
|
private final TriggerPriceService triggerPriceService;
|
||||||
private final MempoolService mempoolService;
|
private final MempoolService mempoolService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DomainInitialisation(ClockWatcher clockWatcher,
|
public DomainInitialisation(ClockWatcher clockWatcher,
|
||||||
TradeLimits tradeLimits,
|
|
||||||
ArbitrationManager arbitrationManager,
|
ArbitrationManager arbitrationManager,
|
||||||
MediationManager mediationManager,
|
MediationManager mediationManager,
|
||||||
RefundManager refundManager,
|
RefundManager refundManager,
|
||||||
|
@ -133,13 +123,11 @@ public class DomainInitialisation {
|
||||||
PrivateNotificationManager privateNotificationManager,
|
PrivateNotificationManager privateNotificationManager,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
FeeService feeService,
|
FeeService feeService,
|
||||||
DaoSetup daoSetup,
|
|
||||||
TradeStatisticsManager tradeStatisticsManager,
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
AccountAgeWitnessService accountAgeWitnessService,
|
AccountAgeWitnessService accountAgeWitnessService,
|
||||||
SignedWitnessService signedWitnessService,
|
SignedWitnessService signedWitnessService,
|
||||||
PriceFeedService priceFeedService,
|
PriceFeedService priceFeedService,
|
||||||
FilterManager filterManager,
|
FilterManager filterManager,
|
||||||
VoteResultService voteResultService,
|
|
||||||
MobileNotificationService mobileNotificationService,
|
MobileNotificationService mobileNotificationService,
|
||||||
MyOfferTakenEvents myOfferTakenEvents,
|
MyOfferTakenEvents myOfferTakenEvents,
|
||||||
TradeEvents tradeEvents,
|
TradeEvents tradeEvents,
|
||||||
|
@ -147,11 +135,9 @@ public class DomainInitialisation {
|
||||||
PriceAlert priceAlert,
|
PriceAlert priceAlert,
|
||||||
MarketAlerts marketAlerts,
|
MarketAlerts marketAlerts,
|
||||||
User user,
|
User user,
|
||||||
DaoStateSnapshotService daoStateSnapshotService,
|
|
||||||
TriggerPriceService triggerPriceService,
|
TriggerPriceService triggerPriceService,
|
||||||
MempoolService mempoolService) {
|
MempoolService mempoolService) {
|
||||||
this.clockWatcher = clockWatcher;
|
this.clockWatcher = clockWatcher;
|
||||||
this.tradeLimits = tradeLimits;
|
|
||||||
this.arbitrationManager = arbitrationManager;
|
this.arbitrationManager = arbitrationManager;
|
||||||
this.mediationManager = mediationManager;
|
this.mediationManager = mediationManager;
|
||||||
this.refundManager = refundManager;
|
this.refundManager = refundManager;
|
||||||
|
@ -169,13 +155,11 @@ public class DomainInitialisation {
|
||||||
this.privateNotificationManager = privateNotificationManager;
|
this.privateNotificationManager = privateNotificationManager;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.feeService = feeService;
|
this.feeService = feeService;
|
||||||
this.daoSetup = daoSetup;
|
|
||||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||||
this.signedWitnessService = signedWitnessService;
|
this.signedWitnessService = signedWitnessService;
|
||||||
this.priceFeedService = priceFeedService;
|
this.priceFeedService = priceFeedService;
|
||||||
this.filterManager = filterManager;
|
this.filterManager = filterManager;
|
||||||
this.voteResultService = voteResultService;
|
|
||||||
this.mobileNotificationService = mobileNotificationService;
|
this.mobileNotificationService = mobileNotificationService;
|
||||||
this.myOfferTakenEvents = myOfferTakenEvents;
|
this.myOfferTakenEvents = myOfferTakenEvents;
|
||||||
this.tradeEvents = tradeEvents;
|
this.tradeEvents = tradeEvents;
|
||||||
|
@ -183,26 +167,19 @@ public class DomainInitialisation {
|
||||||
this.priceAlert = priceAlert;
|
this.priceAlert = priceAlert;
|
||||||
this.marketAlerts = marketAlerts;
|
this.marketAlerts = marketAlerts;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.daoStateSnapshotService = daoStateSnapshotService;
|
|
||||||
this.triggerPriceService = triggerPriceService;
|
this.triggerPriceService = triggerPriceService;
|
||||||
this.mempoolService = mempoolService;
|
this.mempoolService = mempoolService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
|
public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
|
||||||
Consumer<PrivateNotificationPayload> displayPrivateNotificationHandler,
|
Consumer<PrivateNotificationPayload> displayPrivateNotificationHandler,
|
||||||
Consumer<String> daoErrorMessageHandler,
|
|
||||||
Consumer<String> daoWarnMessageHandler,
|
|
||||||
Consumer<String> filterWarningHandler,
|
Consumer<String> filterWarningHandler,
|
||||||
Consumer<VoteResultException> voteResultExceptionHandler,
|
|
||||||
Consumer<List<RevolutAccount>> revolutAccountsUpdateHandler,
|
Consumer<List<RevolutAccount>> revolutAccountsUpdateHandler,
|
||||||
Consumer<List<AmazonGiftCardAccount>> amazonGiftCardAccountsUpdateHandler,
|
Consumer<List<AmazonGiftCardAccount>> amazonGiftCardAccountsUpdateHandler) {
|
||||||
Runnable daoRequiresRestartHandler) {
|
|
||||||
clockWatcher.start();
|
clockWatcher.start();
|
||||||
|
|
||||||
PersistenceManager.onAllServicesInitialized();
|
PersistenceManager.onAllServicesInitialized();
|
||||||
|
|
||||||
tradeLimits.onAllServicesInitialized();
|
|
||||||
|
|
||||||
tradeManager.onAllServicesInitialized();
|
tradeManager.onAllServicesInitialized();
|
||||||
arbitrationManager.onAllServicesInitialized();
|
arbitrationManager.onAllServicesInitialized();
|
||||||
mediationManager.onAllServicesInitialized();
|
mediationManager.onAllServicesInitialized();
|
||||||
|
@ -232,17 +209,6 @@ public class DomainInitialisation {
|
||||||
|
|
||||||
feeService.onAllServicesInitialized();
|
feeService.onAllServicesInitialized();
|
||||||
|
|
||||||
if (DevEnv.isDaoActivated()) {
|
|
||||||
daoSetup.onAllServicesInitialized(errorMessage -> {
|
|
||||||
if (daoErrorMessageHandler != null)
|
|
||||||
daoErrorMessageHandler.accept(errorMessage);
|
|
||||||
}, warningMessage -> {
|
|
||||||
if (daoWarnMessageHandler != null)
|
|
||||||
daoWarnMessageHandler.accept(warningMessage);
|
|
||||||
});
|
|
||||||
|
|
||||||
daoStateSnapshotService.setDaoRequiresRestartHandler(daoRequiresRestartHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
tradeStatisticsManager.onAllServicesInitialized();
|
tradeStatisticsManager.onAllServicesInitialized();
|
||||||
|
|
||||||
|
@ -254,12 +220,6 @@ public class DomainInitialisation {
|
||||||
filterManager.setFilterWarningHandler(filterWarningHandler);
|
filterManager.setFilterWarningHandler(filterWarningHandler);
|
||||||
filterManager.onAllServicesInitialized();
|
filterManager.onAllServicesInitialized();
|
||||||
|
|
||||||
voteResultService.getVoteResultExceptions().addListener((ListChangeListener<VoteResultException>) c -> {
|
|
||||||
c.next();
|
|
||||||
if (c.wasAdded() && voteResultExceptionHandler != null) {
|
|
||||||
c.getAddedSubList().forEach(voteResultExceptionHandler);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mobileNotificationService.onAllServicesInitialized();
|
mobileNotificationService.onAllServicesInitialized();
|
||||||
myOfferTakenEvents.onAllServicesInitialized();
|
myOfferTakenEvents.onAllServicesInitialized();
|
||||||
|
|
|
@ -94,7 +94,6 @@ public class P2PNetworkSetup {
|
||||||
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
|
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
|
||||||
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
|
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
|
||||||
String result;
|
String result;
|
||||||
String daoFullNode = preferences.isDaoFullNode() ? Res.get("mainView.footer.daoFullNode") + " / " : "";
|
|
||||||
int p2pPeers = (int) numP2pPeers;
|
int p2pPeers = (int) numP2pPeers;
|
||||||
if (warning != null && p2pPeers == 0) {
|
if (warning != null && p2pPeers == 0) {
|
||||||
result = warning;
|
result = warning;
|
||||||
|
@ -107,7 +106,7 @@ public class P2PNetworkSetup {
|
||||||
else
|
else
|
||||||
result = state + " / " + p2pInfo;
|
result = state + " / " + p2pInfo;
|
||||||
}
|
}
|
||||||
return daoFullNode + result;
|
return result;
|
||||||
});
|
});
|
||||||
p2PNetworkInfoBinding.subscribe((observable, oldValue, newValue) -> {
|
p2PNetworkInfoBinding.subscribe((observable, oldValue, newValue) -> {
|
||||||
p2PNetworkInfo.set(newValue);
|
p2PNetworkInfo.set(newValue);
|
||||||
|
|
|
@ -19,13 +19,6 @@ package bisq.core.app.misc;
|
||||||
|
|
||||||
import bisq.core.account.sign.SignedWitnessService;
|
import bisq.core.account.sign.SignedWitnessService;
|
||||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.dao.DaoSetup;
|
|
||||||
import bisq.core.dao.governance.ballot.BallotListService;
|
|
||||||
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
|
|
||||||
import bisq.core.dao.governance.bond.reputation.MyReputationListService;
|
|
||||||
import bisq.core.dao.governance.myvote.MyVoteListService;
|
|
||||||
import bisq.core.dao.governance.proofofburn.MyProofOfBurnListService;
|
|
||||||
import bisq.core.dao.governance.proposal.MyProposalListService;
|
|
||||||
import bisq.core.filter.FilterManager;
|
import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
|
|
||||||
|
@ -41,7 +34,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||||
private final DaoSetup daoSetup;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AppSetupWithP2PAndDAO(P2PService p2PService,
|
public AppSetupWithP2PAndDAO(P2PService p2PService,
|
||||||
|
@ -51,13 +43,6 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||||
AccountAgeWitnessService accountAgeWitnessService,
|
AccountAgeWitnessService accountAgeWitnessService,
|
||||||
SignedWitnessService signedWitnessService,
|
SignedWitnessService signedWitnessService,
|
||||||
FilterManager filterManager,
|
FilterManager filterManager,
|
||||||
DaoSetup daoSetup,
|
|
||||||
MyVoteListService myVoteListService,
|
|
||||||
BallotListService ballotListService,
|
|
||||||
MyBlindVoteListService myBlindVoteListService,
|
|
||||||
MyProposalListService myProposalListService,
|
|
||||||
MyReputationListService myReputationListService,
|
|
||||||
MyProofOfBurnListService myProofOfBurnListService,
|
|
||||||
Config config) {
|
Config config) {
|
||||||
super(p2PService,
|
super(p2PService,
|
||||||
p2PDataStorage,
|
p2PDataStorage,
|
||||||
|
@ -68,23 +53,11 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||||
filterManager,
|
filterManager,
|
||||||
config);
|
config);
|
||||||
|
|
||||||
this.daoSetup = daoSetup;
|
|
||||||
|
|
||||||
// TODO Should be refactored/removed. In the meantime keep in sync with CorePersistedDataHost
|
|
||||||
if (config.daoActivated) {
|
|
||||||
persistedDataHosts.add(myVoteListService);
|
|
||||||
persistedDataHosts.add(ballotListService);
|
|
||||||
persistedDataHosts.add(myBlindVoteListService);
|
|
||||||
persistedDataHosts.add(myProposalListService);
|
|
||||||
persistedDataHosts.add(myReputationListService);
|
|
||||||
persistedDataHosts.add(myProofOfBurnListService);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onBasicServicesInitialized() {
|
protected void onBasicServicesInitialized() {
|
||||||
super.onBasicServicesInitialized();
|
super.onBasicServicesInitialized();
|
||||||
|
|
||||||
daoSetup.onAllServicesInitialized(log::error, log::warn);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,8 @@ package bisq.core.app.misc;
|
||||||
|
|
||||||
import bisq.core.app.BisqExecutable;
|
import bisq.core.app.BisqExecutable;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.btc.wallet.BsqWalletService;
|
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.dao.DaoSetup;
|
|
||||||
import bisq.core.dao.node.full.RpcService;
|
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||||
|
|
||||||
|
@ -88,8 +85,6 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
|
||||||
try {
|
try {
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
JsonFileManager.shutDownAllInstances();
|
JsonFileManager.shutDownAllInstances();
|
||||||
injector.getInstance(RpcService.class).shutDown();
|
|
||||||
injector.getInstance(DaoSetup.class).shutDown();
|
|
||||||
injector.getInstance(ArbitratorManager.class).shutDown();
|
injector.getInstance(ArbitratorManager.class).shutDown();
|
||||||
injector.getInstance(OpenOfferManager.class).shutDown(() -> injector.getInstance(P2PService.class).shutDown(() -> {
|
injector.getInstance(OpenOfferManager.class).shutDown(() -> injector.getInstance(P2PService.class).shutDown(() -> {
|
||||||
injector.getInstance(WalletsSetup.class).shutDownComplete.addListener((ov, o, n) -> {
|
injector.getInstance(WalletsSetup.class).shutDownComplete.addListener((ov, o, n) -> {
|
||||||
|
@ -104,7 +99,6 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
|
||||||
injector.getInstance(WalletsSetup.class).shutDown();
|
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(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(BtcWalletService.class).shutDown();
|
||||||
injector.getInstance(BsqWalletService.class).shutDown();
|
|
||||||
}));
|
}));
|
||||||
// we wait max 5 sec.
|
// we wait max 5 sec.
|
||||||
UserThread.runAfter(() -> {
|
UserThread.runAfter(() -> {
|
||||||
|
|
|
@ -20,7 +20,6 @@ package bisq.core.app.misc;
|
||||||
import bisq.core.alert.AlertModule;
|
import bisq.core.alert.AlertModule;
|
||||||
import bisq.core.app.TorSetup;
|
import bisq.core.app.TorSetup;
|
||||||
import bisq.core.btc.BitcoinModule;
|
import bisq.core.btc.BitcoinModule;
|
||||||
import bisq.core.dao.DaoModule;
|
|
||||||
import bisq.core.filter.FilterModule;
|
import bisq.core.filter.FilterModule;
|
||||||
import bisq.core.network.CoreNetworkFilter;
|
import bisq.core.network.CoreNetworkFilter;
|
||||||
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
|
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
|
||||||
|
@ -92,7 +91,6 @@ public class ModuleForAppWithP2p extends AppModule {
|
||||||
install(new OfferModule(config));
|
install(new OfferModule(config));
|
||||||
install(new P2PModule(config));
|
install(new P2PModule(config));
|
||||||
install(new BitcoinModule(config));
|
install(new BitcoinModule(config));
|
||||||
install(new DaoModule(config));
|
|
||||||
install(new AlertModule(config));
|
install(new AlertModule(config));
|
||||||
install(new FilterModule(config));
|
install(new FilterModule(config));
|
||||||
bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class);
|
bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class);
|
||||||
|
|
|
@ -22,8 +22,6 @@ import bisq.core.btc.model.XmrAddressEntryList;
|
||||||
import bisq.core.btc.nodes.BtcNodes;
|
import bisq.core.btc.nodes.BtcNodes;
|
||||||
import bisq.core.btc.setup.RegTestHost;
|
import bisq.core.btc.setup.RegTestHost;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.btc.wallet.BsqCoinSelector;
|
|
||||||
import bisq.core.btc.wallet.BsqWalletService;
|
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.NonBsqCoinSelector;
|
import bisq.core.btc.wallet.NonBsqCoinSelector;
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
|
@ -60,11 +58,7 @@ public class BitcoinModule extends AppModule {
|
||||||
// otherwise the specified host or default (localhost)
|
// otherwise the specified host or default (localhost)
|
||||||
String regTestHost = config.bitcoinRegtestHost;
|
String regTestHost = config.bitcoinRegtestHost;
|
||||||
if (regTestHost.isEmpty()) {
|
if (regTestHost.isEmpty()) {
|
||||||
regTestHost = config.baseCurrencyNetwork.isDaoTestNet() ?
|
regTestHost = Config.DEFAULT_REGTEST_HOST;
|
||||||
"104.248.31.39" :
|
|
||||||
config.baseCurrencyNetwork.isDaoRegTest() ?
|
|
||||||
"134.209.242.206" :
|
|
||||||
Config.DEFAULT_REGTEST_HOST;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RegTestHost.HOST = regTestHost;
|
RegTestHost.HOST = regTestHost;
|
||||||
|
@ -82,7 +76,6 @@ public class BitcoinModule extends AppModule {
|
||||||
bindConstant().annotatedWith(named(Config.USER_AGENT)).to(config.userAgent);
|
bindConstant().annotatedWith(named(Config.USER_AGENT)).to(config.userAgent);
|
||||||
bindConstant().annotatedWith(named(Config.NUM_CONNECTIONS_FOR_BTC)).to(config.numConnectionsForBtc);
|
bindConstant().annotatedWith(named(Config.NUM_CONNECTIONS_FOR_BTC)).to(config.numConnectionsForBtc);
|
||||||
bindConstant().annotatedWith(named(Config.USE_ALL_PROVIDED_NODES)).to(config.useAllProvidedNodes);
|
bindConstant().annotatedWith(named(Config.USE_ALL_PROVIDED_NODES)).to(config.useAllProvidedNodes);
|
||||||
bindConstant().annotatedWith(named(Config.IGNORE_LOCAL_BTC_NODE)).to(config.ignoreLocalBtcNode);
|
|
||||||
bindConstant().annotatedWith(named(Config.SOCKS5_DISCOVER_MODE)).to(config.socks5DiscoverMode);
|
bindConstant().annotatedWith(named(Config.SOCKS5_DISCOVER_MODE)).to(config.socks5DiscoverMode);
|
||||||
bind(new TypeLiteral<List<String>>(){}).annotatedWith(named(PROVIDERS)).toInstance(config.providers);
|
bind(new TypeLiteral<List<String>>(){}).annotatedWith(named(PROVIDERS)).toInstance(config.providers);
|
||||||
|
|
||||||
|
@ -91,9 +84,7 @@ public class BitcoinModule extends AppModule {
|
||||||
bind(WalletsSetup.class).in(Singleton.class);
|
bind(WalletsSetup.class).in(Singleton.class);
|
||||||
bind(XmrWalletService.class).in(Singleton.class);
|
bind(XmrWalletService.class).in(Singleton.class);
|
||||||
bind(BtcWalletService.class).in(Singleton.class);
|
bind(BtcWalletService.class).in(Singleton.class);
|
||||||
bind(BsqWalletService.class).in(Singleton.class);
|
|
||||||
bind(TradeWalletService.class).in(Singleton.class);
|
bind(TradeWalletService.class).in(Singleton.class);
|
||||||
bind(BsqCoinSelector.class).in(Singleton.class);
|
|
||||||
bind(NonBsqCoinSelector.class).in(Singleton.class);
|
bind(NonBsqCoinSelector.class).in(Singleton.class);
|
||||||
bind(BtcNodes.class).in(Singleton.class);
|
bind(BtcNodes.class).in(Singleton.class);
|
||||||
bind(Balances.class).in(Singleton.class);
|
bind(Balances.class).in(Singleton.class);
|
||||||
|
|
|
@ -58,7 +58,6 @@ public class TxFeeEstimationService {
|
||||||
public static int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175;
|
public static int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175;
|
||||||
private static int DEPOSIT_TX_VSIZE = 233;
|
private static int DEPOSIT_TX_VSIZE = 233;
|
||||||
|
|
||||||
private static int BSQ_INPUT_INCREASE = 150;
|
|
||||||
private static int MAX_ITERATIONS = 10;
|
private static int MAX_ITERATIONS = 10;
|
||||||
|
|
||||||
private final FeeService feeService;
|
private final FeeService feeService;
|
||||||
|
@ -114,12 +113,6 @@ public class TxFeeEstimationService {
|
||||||
"if the user pays from an external wallet. In that case we use an estimated tx vsize of {} vbytes.", estimatedTxVsize);
|
"if the user pays from an external wallet. In that case we use an estimated tx vsize of {} vbytes.", estimatedTxVsize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preferences.isPayFeeInBtc()) {
|
|
||||||
// If we pay the fee in BSQ we have one input more which adds about 150 bytes
|
|
||||||
// TODO: Clarify if there is always just one additional input or if there can be more.
|
|
||||||
estimatedTxVsize += BSQ_INPUT_INCREASE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Coin txFee;
|
Coin txFee;
|
||||||
int vsize;
|
int vsize;
|
||||||
if (isTaker) {
|
if (isTaker) {
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.btc.exceptions;
|
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
public class BsqChangeBelowDustException extends Exception {
|
|
||||||
@Getter
|
|
||||||
private final Coin outputValue;
|
|
||||||
|
|
||||||
public BsqChangeBelowDustException(String message, Coin outputValue) {
|
|
||||||
super(message);
|
|
||||||
|
|
||||||
this.outputValue = outputValue;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.btc.exceptions;
|
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
|
||||||
import org.bitcoinj.core.InsufficientMoneyException;
|
|
||||||
|
|
||||||
public class InsufficientBsqException extends InsufficientMoneyException {
|
|
||||||
|
|
||||||
public InsufficientBsqException(Coin missing) {
|
|
||||||
super(missing, "Insufficient BSQ, missing " + missing.value / 100D + " BSQ");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.btc.listeners;
|
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
|
||||||
|
|
||||||
public interface BsqBalanceListener {
|
|
||||||
void onUpdateBalances(Coin availableConfirmedBalance,
|
|
||||||
Coin availableNonBsqBalance,
|
|
||||||
Coin unverifiedBalance,
|
|
||||||
Coin unconfirmedChangeBalance,
|
|
||||||
Coin lockedForVotingBalance,
|
|
||||||
Coin lockedInBondsBalance,
|
|
||||||
Coin unlockingBondsBalance);
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
package bisq.core.btc.model;
|
|
||||||
|
|
||||||
import bisq.core.dao.state.model.blockchain.TxType;
|
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
|
||||||
import org.bitcoinj.core.LegacyAddress;
|
|
||||||
import org.bitcoinj.core.Transaction;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public final class BsqTransferModel {
|
|
||||||
|
|
||||||
private final LegacyAddress receiverAddress;
|
|
||||||
private final Coin receiverAmount;
|
|
||||||
private final Transaction preparedSendTx;
|
|
||||||
private final Transaction txWithBtcFee;
|
|
||||||
private final Transaction signedTx;
|
|
||||||
private final Coin miningFee;
|
|
||||||
private final int txSize;
|
|
||||||
private final TxType txType;
|
|
||||||
|
|
||||||
public BsqTransferModel(LegacyAddress receiverAddress,
|
|
||||||
Coin receiverAmount,
|
|
||||||
Transaction preparedSendTx,
|
|
||||||
Transaction txWithBtcFee,
|
|
||||||
Transaction signedTx) {
|
|
||||||
this.receiverAddress = receiverAddress;
|
|
||||||
this.receiverAmount = receiverAmount;
|
|
||||||
this.preparedSendTx = preparedSendTx;
|
|
||||||
this.txWithBtcFee = txWithBtcFee;
|
|
||||||
this.signedTx = signedTx;
|
|
||||||
this.miningFee = signedTx.getFee();
|
|
||||||
this.txSize = signedTx.bitcoinSerialize().length;
|
|
||||||
this.txType = TxType.TRANSFER_BSQ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getReceiverAddressAsString() {
|
|
||||||
return receiverAddress.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getTxSizeInKb() {
|
|
||||||
return txSize / 1000d;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toShortString() {
|
|
||||||
return "{" + "\n" +
|
|
||||||
" receiverAddress='" + getReceiverAddressAsString() + '\'' + "\n" +
|
|
||||||
", receiverAmount=" + receiverAmount + "\n" +
|
|
||||||
", txWithBtcFee.txId=" + txWithBtcFee.getTxId() + "\n" +
|
|
||||||
", miningFee=" + miningFee + "\n" +
|
|
||||||
", txSizeInKb=" + getTxSizeInKb() + "\n" +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "BsqTransferModel{" + "\n" +
|
|
||||||
" receiverAddress='" + getReceiverAddressAsString() + '\'' + "\n" +
|
|
||||||
", receiverAmount=" + receiverAmount + "\n" +
|
|
||||||
", preparedSendTx=" + preparedSendTx + "\n" +
|
|
||||||
", txWithBtcFee=" + txWithBtcFee + "\n" +
|
|
||||||
", signedTx=" + signedTx + "\n" +
|
|
||||||
", miningFee=" + miningFee + "\n" +
|
|
||||||
", txSize=" + txSize + "\n" +
|
|
||||||
", txSizeInKb=" + getTxSizeInKb() + "\n" +
|
|
||||||
", txType=" + txType + "\n" +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -52,13 +52,7 @@ public class LocalBitcoinNode {
|
||||||
*/
|
*/
|
||||||
public boolean shouldBeIgnored() {
|
public boolean shouldBeIgnored() {
|
||||||
BaseCurrencyNetwork baseCurrencyNetwork = config.baseCurrencyNetwork;
|
BaseCurrencyNetwork baseCurrencyNetwork = config.baseCurrencyNetwork;
|
||||||
|
return config.ignoreLocalBtcNode;
|
||||||
// For dao testnet (server side regtest) we disable the use of local bitcoin node
|
|
||||||
// to avoid confusion if local btc node is not synced with our dao testnet master
|
|
||||||
// node. Note: above comment was previously in WalletConfig::createPeerGroup.
|
|
||||||
return config.ignoreLocalBtcNode ||
|
|
||||||
baseCurrencyNetwork.isDaoRegTest() ||
|
|
||||||
baseCurrencyNetwork.isDaoTestNet();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,29 +32,23 @@ import com.google.common.collect.ImmutableList;
|
||||||
/**
|
/**
|
||||||
* Hack to convert bitcoinj 0.14 wallets to bitcoinj 0.15 format.
|
* Hack to convert bitcoinj 0.14 wallets to bitcoinj 0.15 format.
|
||||||
*
|
*
|
||||||
* This code is required to be executed only once per user (actually twice, for btc and bsq wallets).
|
* This code is required to be executed only once per user (actually twice, for btc wallets).
|
||||||
* Once all users using bitcoinj 0.14 wallets have executed this code, this class will be no longer needed.
|
* Once all users using bitcoinj 0.14 wallets have executed this code, this class will be no longer needed.
|
||||||
*
|
*
|
||||||
* Since that is almost impossible to guarantee, this hack will stay until we decide to don't be
|
* Since that is almost impossible to guarantee, this hack will stay until we decide to don't be
|
||||||
* backwards compatible with pre bitcoinj 0.15 wallets.
|
* backwards compatible with pre bitcoinj 0.15 wallets.
|
||||||
* In that scenario, users will have to migrate using this procedure:
|
* In that scenario, users will have to migrate using this procedure:
|
||||||
* 1) Run pre bitcoinj 0.15 bisq and copy their seed words on a piece of paper.
|
* 1) Run pre bitcoinj 0.15 btc and copy their seed words on a piece of paper.
|
||||||
* 2) Run post bitcoinj 0.15 bisq and use recover from seed.
|
* 2) Run post bitcoinj 0.15 btc and use recover from seed.
|
||||||
* */
|
* */
|
||||||
public class BisqKeyChainFactory extends DefaultKeyChainFactory {
|
public class BisqKeyChainFactory extends DefaultKeyChainFactory {
|
||||||
|
|
||||||
private boolean isBsqWallet;
|
|
||||||
|
|
||||||
public BisqKeyChainFactory(boolean isBsqWallet) {
|
|
||||||
this.isBsqWallet = isBsqWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeterministicKeyChain makeKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicSeed seed, KeyCrypter crypter, boolean isMarried, Script.ScriptType outputScriptType, ImmutableList<ChildNumber> accountPath) {
|
public DeterministicKeyChain makeKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicSeed seed, KeyCrypter crypter, boolean isMarried, Script.ScriptType outputScriptType, ImmutableList<ChildNumber> accountPath) {
|
||||||
ImmutableList<ChildNumber> maybeUpdatedAccountPath = accountPath;
|
ImmutableList<ChildNumber> maybeUpdatedAccountPath = accountPath;
|
||||||
if (DeterministicKeyChain.ACCOUNT_ZERO_PATH.equals(accountPath)) {
|
if (DeterministicKeyChain.ACCOUNT_ZERO_PATH.equals(accountPath)) {
|
||||||
// This is a bitcoinj 0.14 wallet that has no account path in the serialized mnemonic
|
// This is a bitcoinj 0.14 wallet that has no account path in the serialized mnemonic
|
||||||
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure(isBsqWallet);
|
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure();
|
||||||
maybeUpdatedAccountPath = structure.accountPathFor(outputScriptType);
|
maybeUpdatedAccountPath = structure.accountPathFor(outputScriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,43 +39,13 @@ public class BisqKeyChainGroupStructure implements KeyChainGroupStructure {
|
||||||
new ChildNumber(0, true),
|
new ChildNumber(0, true),
|
||||||
ChildNumber.ONE_HARDENED);
|
ChildNumber.ONE_HARDENED);
|
||||||
|
|
||||||
// See https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
|
||||||
// https://github.com/satoshilabs/slips/blob/master/slip-0044.md
|
|
||||||
// We have registered 142 (0x8000008E) as coin_type for BSQ
|
|
||||||
public static final ImmutableList<ChildNumber> BIP44_BSQ_NON_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
|
|
||||||
new ChildNumber(44, true),
|
|
||||||
new ChildNumber(142, true),
|
|
||||||
ChildNumber.ZERO_HARDENED);
|
|
||||||
|
|
||||||
// We don't use segwit for BSQ
|
|
||||||
// public static final ImmutableList<ChildNumber> BIP44_BSQ_SEGWIT_ACCOUNT_PATH = ImmutableList.of(
|
|
||||||
// new ChildNumber(44, true),
|
|
||||||
// new ChildNumber(142, true),
|
|
||||||
// ChildNumber.ONE_HARDENED);
|
|
||||||
|
|
||||||
private boolean isBsqWallet;
|
|
||||||
|
|
||||||
public BisqKeyChainGroupStructure (boolean isBsqWallet) {
|
|
||||||
this.isBsqWallet = isBsqWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImmutableList<ChildNumber> accountPathFor(Script.ScriptType outputScriptType) {
|
public ImmutableList<ChildNumber> accountPathFor(Script.ScriptType outputScriptType) {
|
||||||
if (!isBsqWallet) {
|
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
|
||||||
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
|
return BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH;
|
||||||
return BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH;
|
else if (outputScriptType == Script.ScriptType.P2WPKH)
|
||||||
else if (outputScriptType == Script.ScriptType.P2WPKH)
|
return BIP44_BTC_SEGWIT_ACCOUNT_PATH;
|
||||||
return BIP44_BTC_SEGWIT_ACCOUNT_PATH;
|
else
|
||||||
else
|
throw new IllegalArgumentException(outputScriptType.toString());
|
||||||
throw new IllegalArgumentException(outputScriptType.toString());
|
|
||||||
} else {
|
|
||||||
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
|
|
||||||
return BIP44_BSQ_NON_SEGWIT_ACCOUNT_PATH;
|
|
||||||
else if (outputScriptType == Script.ScriptType.P2WPKH)
|
|
||||||
//return BIP44_BSQ_SEGWIT_ACCOUNT_PATH;
|
|
||||||
throw new IllegalArgumentException(outputScriptType.toString());
|
|
||||||
else
|
|
||||||
throw new IllegalArgumentException(outputScriptType.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,13 +142,11 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
protected volatile MoneroDaemon vXmrDaemon;
|
protected volatile MoneroDaemon vXmrDaemon;
|
||||||
protected volatile MoneroWalletRpc vXmrWallet;
|
protected volatile MoneroWalletRpc vXmrWallet;
|
||||||
protected volatile Wallet vBtcWallet;
|
protected volatile Wallet vBtcWallet;
|
||||||
protected volatile Wallet vBsqWallet;
|
|
||||||
protected volatile PeerGroup vPeerGroup;
|
protected volatile PeerGroup vPeerGroup;
|
||||||
|
|
||||||
protected final File directory;
|
protected final File directory;
|
||||||
protected volatile File vXmrWalletFile;
|
protected volatile File vXmrWalletFile;
|
||||||
protected volatile File vBtcWalletFile;
|
protected volatile File vBtcWalletFile;
|
||||||
protected volatile File vBsqWalletFile;
|
|
||||||
|
|
||||||
protected PeerAddress[] peerAddresses;
|
protected PeerAddress[] peerAddresses;
|
||||||
protected DownloadProgressTracker downloadListener;
|
protected DownloadProgressTracker downloadListener;
|
||||||
|
@ -373,15 +371,10 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
String btcPrefix = "_BTC";
|
String btcPrefix = "_BTC";
|
||||||
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
|
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
|
||||||
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
|
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
|
||||||
vBtcWallet = createOrLoadWallet(shouldReplayWallet, vBtcWalletFile, false);
|
vBtcWallet = createOrLoadWallet(shouldReplayWallet, vBtcWalletFile);
|
||||||
vBtcWallet.allowSpendingUnconfirmedTransactions();
|
vBtcWallet.allowSpendingUnconfirmedTransactions();
|
||||||
vBtcWallet.setRiskAnalyzer(new BisqRiskAnalysis.Analyzer());
|
vBtcWallet.setRiskAnalyzer(new BisqRiskAnalysis.Analyzer());
|
||||||
|
|
||||||
String bsqPrefix = "_BSQ";
|
|
||||||
vBsqWalletFile = new File(directory, filePrefix + bsqPrefix + ".wallet");
|
|
||||||
vBsqWallet = createOrLoadWallet(shouldReplayWallet, vBsqWalletFile, true);
|
|
||||||
vBsqWallet.setRiskAnalyzer(new BisqRiskAnalysis.Analyzer());
|
|
||||||
|
|
||||||
// Initiate Bitcoin network objects (block store, blockchain and peer group)
|
// Initiate Bitcoin network objects (block store, blockchain and peer group)
|
||||||
vStore = new SPVBlockStore(params, chainFile);
|
vStore = new SPVBlockStore(params, chainFile);
|
||||||
if (!chainFileExists || restoreFromSeed != null) {
|
if (!chainFileExists || restoreFromSeed != null) {
|
||||||
|
@ -431,8 +424,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
}
|
}
|
||||||
vChain.addWallet(vBtcWallet);
|
vChain.addWallet(vBtcWallet);
|
||||||
vPeerGroup.addWallet(vBtcWallet);
|
vPeerGroup.addWallet(vBtcWallet);
|
||||||
vChain.addWallet(vBsqWallet);
|
|
||||||
vPeerGroup.addWallet(vBsqWallet);
|
|
||||||
onSetupCompleted();
|
onSetupCompleted();
|
||||||
|
|
||||||
if (migratedWalletToSegwit.get()) {
|
if (migratedWalletToSegwit.get()) {
|
||||||
|
@ -468,23 +459,22 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Wallet createOrLoadWallet(boolean shouldReplayWallet,
|
private Wallet createOrLoadWallet(boolean shouldReplayWallet,
|
||||||
File walletFile,
|
File walletFile) throws Exception {
|
||||||
boolean isBsqWallet) throws Exception {
|
|
||||||
Wallet wallet;
|
Wallet wallet;
|
||||||
|
|
||||||
maybeMoveOldWalletOutOfTheWay(walletFile);
|
maybeMoveOldWalletOutOfTheWay(walletFile);
|
||||||
|
|
||||||
if (walletFile.exists()) {
|
if (walletFile.exists()) {
|
||||||
wallet = loadWallet(shouldReplayWallet, walletFile, isBsqWallet);
|
wallet = loadWallet(shouldReplayWallet, walletFile);
|
||||||
} else {
|
} else {
|
||||||
wallet = createWallet(isBsqWallet);
|
wallet = createWallet();
|
||||||
//wallet.freshReceiveKey();
|
//wallet.freshReceiveKey();
|
||||||
|
|
||||||
// Currently the only way we can be sure that an extension is aware of its containing wallet is by
|
// Currently the only way we can be sure that an extension is aware of its containing wallet is by
|
||||||
// deserializing the extension (see WalletExtension#deserializeWalletExtension(Wallet, byte[]))
|
// deserializing the extension (see WalletExtension#deserializeWalletExtension(Wallet, byte[]))
|
||||||
// Hence, we first save and then load wallet to ensure any extensions are correctly initialized.
|
// Hence, we first save and then load wallet to ensure any extensions are correctly initialized.
|
||||||
wallet.saveToFile(walletFile);
|
wallet.saveToFile(walletFile);
|
||||||
wallet = loadWallet(false, walletFile, isBsqWallet);
|
wallet = loadWallet(false, walletFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setupAutoSave(wallet, walletFile);
|
this.setupAutoSave(wallet, walletFile);
|
||||||
|
@ -496,7 +486,7 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
wallet.autosaveToFile(walletFile, 5, TimeUnit.SECONDS, null);
|
wallet.autosaveToFile(walletFile, 5, TimeUnit.SECONDS, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Wallet loadWallet(boolean shouldReplayWallet, File walletFile, boolean isBsqWallet) throws Exception {
|
private Wallet loadWallet(boolean shouldReplayWallet, File walletFile) throws Exception {
|
||||||
Wallet wallet;
|
Wallet wallet;
|
||||||
try (FileInputStream walletStream = new FileInputStream(walletFile)) {
|
try (FileInputStream walletStream = new FileInputStream(walletFile)) {
|
||||||
WalletExtension[] extArray = new WalletExtension[]{};
|
WalletExtension[] extArray = new WalletExtension[]{};
|
||||||
|
@ -504,32 +494,25 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
final WalletProtobufSerializer serializer;
|
final WalletProtobufSerializer serializer;
|
||||||
serializer = new WalletProtobufSerializer();
|
serializer = new WalletProtobufSerializer();
|
||||||
// Hack to convert bitcoinj 0.14 wallets to bitcoinj 0.15 format
|
// Hack to convert bitcoinj 0.14 wallets to bitcoinj 0.15 format
|
||||||
serializer.setKeyChainFactory(new BisqKeyChainFactory(isBsqWallet));
|
serializer.setKeyChainFactory(new BisqKeyChainFactory());
|
||||||
wallet = serializer.readWallet(params, extArray, proto);
|
wallet = serializer.readWallet(params, extArray, proto);
|
||||||
if (shouldReplayWallet)
|
if (shouldReplayWallet)
|
||||||
wallet.reset();
|
wallet.reset();
|
||||||
if (!isBsqWallet) {
|
maybeAddSegwitKeychain(wallet, null);
|
||||||
maybeAddSegwitKeychain(wallet, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Wallet createWallet(boolean isBsqWallet) {
|
protected Wallet createWallet() {
|
||||||
Script.ScriptType preferredOutputScriptType = isBsqWallet ? Script.ScriptType.P2PKH : Script.ScriptType.P2WPKH;
|
Script.ScriptType preferredOutputScriptType = Script.ScriptType.P2WPKH;
|
||||||
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure(isBsqWallet);
|
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure();
|
||||||
KeyChainGroup.Builder kcgBuilder = KeyChainGroup.builder(params, structure);
|
KeyChainGroup.Builder kcgBuilder = KeyChainGroup.builder(params, structure);
|
||||||
if (restoreFromSeed != null) {
|
if (restoreFromSeed != null) {
|
||||||
kcgBuilder.fromSeed(restoreFromSeed, preferredOutputScriptType);
|
kcgBuilder.fromSeed(restoreFromSeed, preferredOutputScriptType);
|
||||||
} else {
|
} else {
|
||||||
// new wallet
|
// new wallet
|
||||||
if (!isBsqWallet) {
|
// btc wallet uses a new random seed.
|
||||||
// btc wallet uses a new random seed.
|
kcgBuilder.fromRandom(preferredOutputScriptType);
|
||||||
kcgBuilder.fromRandom(preferredOutputScriptType);
|
|
||||||
} else {
|
|
||||||
// bsq wallet uses btc wallet's seed created a few milliseconds ago.
|
|
||||||
kcgBuilder.fromSeed(vBtcWallet.getKeyChainSeed(), preferredOutputScriptType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return new Wallet(params, kcgBuilder.build());
|
return new Wallet(params, kcgBuilder.build());
|
||||||
}
|
}
|
||||||
|
@ -588,12 +571,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
vBtcWallet = null;
|
vBtcWallet = null;
|
||||||
log.info("BtcWallet saved to file");
|
log.info("BtcWallet saved to file");
|
||||||
|
|
||||||
if (vBsqWallet != null && vBsqWalletFile != null) {
|
|
||||||
vBsqWallet.saveToFile(vBsqWalletFile);
|
|
||||||
vBsqWallet = null;
|
|
||||||
log.info("BsqWallet saved to file");
|
|
||||||
}
|
|
||||||
|
|
||||||
vStore.close();
|
vStore.close();
|
||||||
vStore = null;
|
vStore = null;
|
||||||
log.info("SPV file closed");
|
log.info("SPV file closed");
|
||||||
|
@ -644,11 +621,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
return vXmrWallet;
|
return vXmrWallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Wallet bsqWallet() {
|
|
||||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
|
||||||
return vBsqWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PeerGroup peerGroup() {
|
public PeerGroup peerGroup() {
|
||||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||||
return vPeerGroup;
|
return vPeerGroup;
|
||||||
|
@ -681,7 +653,7 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
}
|
}
|
||||||
DeterministicKeyChain nativeSegwitKeyChain = DeterministicKeyChain.builder().seed(seed)
|
DeterministicKeyChain nativeSegwitKeyChain = DeterministicKeyChain.builder().seed(seed)
|
||||||
.outputScriptType(Script.ScriptType.P2WPKH)
|
.outputScriptType(Script.ScriptType.P2WPKH)
|
||||||
.accountPath(new BisqKeyChainGroupStructure(false).accountPathFor(Script.ScriptType.P2WPKH)).build();
|
.accountPath(new BisqKeyChainGroupStructure().accountPathFor(Script.ScriptType.P2WPKH)).build();
|
||||||
if (aesKey != null) {
|
if (aesKey != null) {
|
||||||
// If wallet is encrypted, encrypt the new keychain.
|
// If wallet is encrypted, encrypt the new keychain.
|
||||||
KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
||||||
|
|
|
@ -120,7 +120,6 @@ public class WalletsSetup {
|
||||||
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
|
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
|
||||||
|
|
||||||
private static final long STARTUP_TIMEOUT = 180;
|
private static final long STARTUP_TIMEOUT = 180;
|
||||||
private static final String BSQ_WALLET_FILE_NAME = "haveno_BSQ.wallet";
|
|
||||||
private static final String SPV_CHAIN_FILE_NAME = "haveno.spvchain";
|
private static final String SPV_CHAIN_FILE_NAME = "haveno.spvchain";
|
||||||
|
|
||||||
private final RegTestHost regTestHost;
|
private final RegTestHost regTestHost;
|
||||||
|
@ -427,7 +426,6 @@ public class WalletsSetup {
|
||||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName, 20);
|
FileUtil.rollingBackup(walletDir, xmrWalletFileName, 20);
|
||||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".keys", 20);
|
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".keys", 20);
|
||||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".address.txt", 20);
|
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".address.txt", 20);
|
||||||
FileUtil.rollingBackup(walletDir, BSQ_WALLET_FILE_NAME, 20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearBackups() {
|
public void clearBackups() {
|
||||||
|
@ -498,11 +496,6 @@ public class WalletsSetup {
|
||||||
return walletConfig.getXmrWallet();
|
return walletConfig.getXmrWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Wallet getBsqWallet() {
|
|
||||||
return walletConfig.bsqWallet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkParameters getParams() {
|
public NetworkParameters getParams() {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.btc.wallet;
|
|
||||||
|
|
||||||
import bisq.core.dao.state.DaoStateService;
|
|
||||||
import bisq.core.dao.state.model.blockchain.TxOutputKey;
|
|
||||||
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
|
|
||||||
|
|
||||||
import org.bitcoinj.core.Transaction;
|
|
||||||
import org.bitcoinj.core.TransactionOutput;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation.
|
|
||||||
* We lookup for spendable outputs which matches our address of our address.
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class BsqCoinSelector extends BisqDefaultCoinSelector {
|
|
||||||
private final DaoStateService daoStateService;
|
|
||||||
private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public BsqCoinSelector(DaoStateService daoStateService, UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService) {
|
|
||||||
// permitForeignPendingTx is not relevant here as we do not support pending foreign utxos anyway.
|
|
||||||
super(false);
|
|
||||||
this.daoStateService = daoStateService;
|
|
||||||
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isTxOutputSpendable(TransactionOutput output) {
|
|
||||||
// output.getParentTransaction() cannot be null as it is checked in calling method
|
|
||||||
Transaction parentTransaction = output.getParentTransaction();
|
|
||||||
if (parentTransaction == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// If it is a normal confirmed BSQ output we use the default lookup at the daoState
|
|
||||||
if (daoStateService.isTxOutputSpendable(new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex())))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// It might be that it is an unconfirmed change output which we allow to be used for spending without requiring a confirmation.
|
|
||||||
// We check if we have the output in the dao state, if so we have a confirmed but unspendable output (e.g. confiscated).
|
|
||||||
if (daoStateService.getTxOutput(new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex())).isPresent())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Only if it's not existing yet in the dao state (unconfirmed) we use our unconfirmedBsqChangeOutputList to
|
|
||||||
// check if it is an own change output.
|
|
||||||
return unconfirmedBsqChangeOutputListService.hasTransactionOutput(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For BSQ we do not check for dust attack utxos as they are 5.46 BSQ and a considerable value.
|
|
||||||
// The default 546 sat dust limit is handled in the BitcoinJ side anyway.
|
|
||||||
@Override
|
|
||||||
protected boolean isDustAttackUtxo(TransactionOutput output) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package bisq.core.btc.wallet;
|
|
||||||
|
|
||||||
import bisq.core.btc.exceptions.BsqChangeBelowDustException;
|
|
||||||
import bisq.core.btc.exceptions.TransactionVerificationException;
|
|
||||||
import bisq.core.btc.exceptions.WalletException;
|
|
||||||
import bisq.core.btc.model.BsqTransferModel;
|
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
|
||||||
import org.bitcoinj.core.InsufficientMoneyException;
|
|
||||||
import org.bitcoinj.core.LegacyAddress;
|
|
||||||
import org.bitcoinj.core.Transaction;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Singleton
|
|
||||||
public class BsqTransferService {
|
|
||||||
|
|
||||||
private final WalletsManager walletsManager;
|
|
||||||
private final BsqWalletService bsqWalletService;
|
|
||||||
private final BtcWalletService btcWalletService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public BsqTransferService(WalletsManager walletsManager,
|
|
||||||
BsqWalletService bsqWalletService,
|
|
||||||
BtcWalletService btcWalletService) {
|
|
||||||
this.walletsManager = walletsManager;
|
|
||||||
this.bsqWalletService = bsqWalletService;
|
|
||||||
this.btcWalletService = btcWalletService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BsqTransferModel getBsqTransferModel(LegacyAddress address,
|
|
||||||
Coin receiverAmount,
|
|
||||||
Coin txFeePerVbyte)
|
|
||||||
throws TransactionVerificationException,
|
|
||||||
WalletException,
|
|
||||||
BsqChangeBelowDustException,
|
|
||||||
InsufficientMoneyException {
|
|
||||||
|
|
||||||
Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(address.toString(), receiverAmount);
|
|
||||||
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, txFeePerVbyte);
|
|
||||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
|
||||||
|
|
||||||
return new BsqTransferModel(address,
|
|
||||||
receiverAmount,
|
|
||||||
preparedSendTx,
|
|
||||||
txWithBtcFee,
|
|
||||||
signedTx);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendFunds(BsqTransferModel bsqTransferModel, TxBroadcaster.Callback callback) {
|
|
||||||
log.info("Publishing BSQ transfer {}", bsqTransferModel.toShortString());
|
|
||||||
walletsManager.publishAndCommitBsqTx(bsqTransferModel.getTxWithBtcFee(),
|
|
||||||
bsqTransferModel.getTxType(),
|
|
||||||
callback);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,836 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.btc.wallet;
|
|
||||||
|
|
||||||
import bisq.core.btc.exceptions.BsqChangeBelowDustException;
|
|
||||||
import bisq.core.btc.exceptions.InsufficientBsqException;
|
|
||||||
import bisq.core.btc.exceptions.TransactionVerificationException;
|
|
||||||
import bisq.core.btc.exceptions.WalletException;
|
|
||||||
import bisq.core.btc.listeners.BsqBalanceListener;
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
|
||||||
import bisq.core.dao.DaoKillSwitch;
|
|
||||||
import bisq.core.dao.state.DaoStateListener;
|
|
||||||
import bisq.core.dao.state.DaoStateService;
|
|
||||||
import bisq.core.dao.state.model.blockchain.Block;
|
|
||||||
import bisq.core.dao.state.model.blockchain.Tx;
|
|
||||||
import bisq.core.dao.state.model.blockchain.TxOutput;
|
|
||||||
import bisq.core.dao.state.model.blockchain.TxOutputKey;
|
|
||||||
import bisq.core.dao.state.model.blockchain.TxType;
|
|
||||||
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
|
|
||||||
import bisq.core.provider.fee.FeeService;
|
|
||||||
import bisq.core.user.Preferences;
|
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
|
||||||
|
|
||||||
import org.bitcoinj.core.Address;
|
|
||||||
import org.bitcoinj.core.AddressFormatException;
|
|
||||||
import org.bitcoinj.core.BlockChain;
|
|
||||||
import org.bitcoinj.core.Coin;
|
|
||||||
import org.bitcoinj.core.InsufficientMoneyException;
|
|
||||||
import org.bitcoinj.core.LegacyAddress;
|
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
|
||||||
import org.bitcoinj.core.Sha256Hash;
|
|
||||||
import org.bitcoinj.core.Transaction;
|
|
||||||
import org.bitcoinj.core.TransactionConfidence;
|
|
||||||
import org.bitcoinj.core.TransactionInput;
|
|
||||||
import org.bitcoinj.core.TransactionOutPoint;
|
|
||||||
import org.bitcoinj.core.TransactionOutput;
|
|
||||||
import org.bitcoinj.script.ScriptException;
|
|
||||||
import org.bitcoinj.wallet.CoinSelection;
|
|
||||||
import org.bitcoinj.wallet.CoinSelector;
|
|
||||||
import org.bitcoinj.wallet.SendRequest;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.BUILDING;
|
|
||||||
import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.PENDING;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class BsqWalletService extends WalletService implements DaoStateListener {
|
|
||||||
|
|
||||||
public interface WalletTransactionsChangeListener {
|
|
||||||
|
|
||||||
void onWalletTransactionsChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final DaoKillSwitch daoKillSwitch;
|
|
||||||
private final BsqCoinSelector bsqCoinSelector;
|
|
||||||
private final NonBsqCoinSelector nonBsqCoinSelector;
|
|
||||||
private final DaoStateService daoStateService;
|
|
||||||
private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService;
|
|
||||||
private final List<Transaction> walletTransactions = new ArrayList<>();
|
|
||||||
private final CopyOnWriteArraySet<BsqBalanceListener> bsqBalanceListeners = new CopyOnWriteArraySet<>();
|
|
||||||
private final List<WalletTransactionsChangeListener> walletTransactionsChangeListeners = new ArrayList<>();
|
|
||||||
private boolean updateBsqWalletTransactionsPending;
|
|
||||||
|
|
||||||
// balance of non BSQ satoshis
|
|
||||||
@Getter
|
|
||||||
private Coin availableNonBsqBalance = Coin.ZERO;
|
|
||||||
@Getter
|
|
||||||
private Coin availableConfirmedBalance = Coin.ZERO;
|
|
||||||
@Getter
|
|
||||||
private Coin unverifiedBalance = Coin.ZERO;
|
|
||||||
@Getter
|
|
||||||
private Coin unconfirmedChangeBalance = Coin.ZERO;
|
|
||||||
@Getter
|
|
||||||
private Coin lockedForVotingBalance = Coin.ZERO;
|
|
||||||
@Getter
|
|
||||||
private Coin lockupBondsBalance = Coin.ZERO;
|
|
||||||
@Getter
|
|
||||||
private Coin unlockingBondsBalance = Coin.ZERO;
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Constructor
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public BsqWalletService(WalletsSetup walletsSetup,
|
|
||||||
BsqCoinSelector bsqCoinSelector,
|
|
||||||
NonBsqCoinSelector nonBsqCoinSelector,
|
|
||||||
DaoStateService daoStateService,
|
|
||||||
UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService,
|
|
||||||
Preferences preferences,
|
|
||||||
FeeService feeService,
|
|
||||||
DaoKillSwitch daoKillSwitch) {
|
|
||||||
super(walletsSetup,
|
|
||||||
preferences,
|
|
||||||
feeService);
|
|
||||||
|
|
||||||
this.bsqCoinSelector = bsqCoinSelector;
|
|
||||||
this.nonBsqCoinSelector = nonBsqCoinSelector;
|
|
||||||
this.daoStateService = daoStateService;
|
|
||||||
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
|
|
||||||
this.daoKillSwitch = daoKillSwitch;
|
|
||||||
|
|
||||||
nonBsqCoinSelector.setPreferences(preferences);
|
|
||||||
|
|
||||||
walletsSetup.addSetupCompletedHandler(() -> {
|
|
||||||
wallet = walletsSetup.getBsqWallet();
|
|
||||||
if (wallet != null) {
|
|
||||||
wallet.setCoinSelector(bsqCoinSelector);
|
|
||||||
addListenersToWallet();
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockChain chain = walletsSetup.getChain();
|
|
||||||
if (chain != null) {
|
|
||||||
chain.addNewBestBlockListener(block -> chainHeightProperty.set(block.getHeight()));
|
|
||||||
chainHeightProperty.set(chain.getBestChainHeight());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
daoStateService.addDaoStateListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void addListenersToWallet() {
|
|
||||||
super.addListenersToWallet();
|
|
||||||
|
|
||||||
wallet.addCoinsReceivedEventListener((wallet, tx, prevBalance, newBalance) ->
|
|
||||||
updateBsqWalletTransactions()
|
|
||||||
);
|
|
||||||
wallet.addCoinsSentEventListener((wallet, tx, prevBalance, newBalance) ->
|
|
||||||
updateBsqWalletTransactions()
|
|
||||||
);
|
|
||||||
wallet.addReorganizeEventListener(wallet -> {
|
|
||||||
log.warn("onReorganize ");
|
|
||||||
updateBsqWalletTransactions();
|
|
||||||
unconfirmedBsqChangeOutputListService.onReorganize();
|
|
||||||
});
|
|
||||||
wallet.addTransactionConfidenceEventListener((wallet, tx) -> {
|
|
||||||
// We are only interested in updates from unconfirmed txs and confirmed txs at the
|
|
||||||
// time when it gets into a block. Otherwise we would get called
|
|
||||||
// updateBsqWalletTransactions for each tx as the block depth changes for all.
|
|
||||||
if (tx != null && tx.getConfidence() != null && tx.getConfidence().getDepthInBlocks() <= 1 &&
|
|
||||||
daoStateService.isParseBlockChainComplete()) {
|
|
||||||
updateBsqWalletTransactions();
|
|
||||||
}
|
|
||||||
unconfirmedBsqChangeOutputListService.onTransactionConfidenceChanged(tx);
|
|
||||||
});
|
|
||||||
wallet.addKeyChainEventListener(keys ->
|
|
||||||
updateBsqWalletTransactions()
|
|
||||||
);
|
|
||||||
wallet.addScriptsChangeEventListener((wallet, scripts, isAddingScripts) ->
|
|
||||||
updateBsqWalletTransactions()
|
|
||||||
);
|
|
||||||
wallet.addChangeEventListener(wallet ->
|
|
||||||
updateBsqWalletTransactions()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// DaoStateListener
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
|
|
||||||
if (isWalletReady()) {
|
|
||||||
wallet.getTransactions(false).forEach(unconfirmedBsqChangeOutputListService::onTransactionConfidenceChanged);
|
|
||||||
updateBsqWalletTransactions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Overridden Methods
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
|
||||||
String getWalletAsString(boolean includePrivKeys) {
|
|
||||||
return wallet.toString(true, includePrivKeys, this.aesKey, true, true, walletsSetup.getChain()) + "\n\n" +
|
|
||||||
"All pubKeys as hex:\n" +
|
|
||||||
wallet.printAllPubKeysAsHex();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Balance
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private void updateBsqBalance() {
|
|
||||||
long ts = System.currentTimeMillis();
|
|
||||||
unverifiedBalance = Coin.valueOf(
|
|
||||||
getTransactions(false).stream()
|
|
||||||
.filter(tx -> tx.getConfidence().getConfidenceType() == PENDING)
|
|
||||||
.mapToLong(tx -> {
|
|
||||||
// Sum up outputs into BSQ wallet and subtract the inputs using lockup or unlocking
|
|
||||||
// outputs since those inputs will be accounted for in lockupBondsBalance and
|
|
||||||
// unlockingBondsBalance
|
|
||||||
long outputs = tx.getOutputs().stream()
|
|
||||||
.filter(out -> out.isMine(wallet))
|
|
||||||
.filter(TransactionOutput::isAvailableForSpending)
|
|
||||||
.mapToLong(out -> out.getValue().value)
|
|
||||||
.sum();
|
|
||||||
// Account for spending of locked connectedOutputs
|
|
||||||
long lockedInputs = tx.getInputs().stream()
|
|
||||||
.filter(in -> {
|
|
||||||
TransactionOutput connectedOutput = in.getConnectedOutput();
|
|
||||||
if (connectedOutput != null) {
|
|
||||||
Transaction parentTransaction = connectedOutput.getParentTransaction();
|
|
||||||
// TODO SQ
|
|
||||||
if (parentTransaction != null/* &&
|
|
||||||
parentTransaction.getConfidence().getConfidenceType() == BUILDING*/) {
|
|
||||||
TxOutputKey key = new TxOutputKey(parentTransaction.getTxId().toString(),
|
|
||||||
connectedOutput.getIndex());
|
|
||||||
|
|
||||||
return (connectedOutput.isMine(wallet)
|
|
||||||
&& (daoStateService.isLockupOutput(key)
|
|
||||||
|| daoStateService.isUnlockingAndUnspent(key)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
.mapToLong(in -> in.getValue() != null ? in.getValue().value : 0)
|
|
||||||
.sum();
|
|
||||||
return outputs - lockedInputs;
|
|
||||||
})
|
|
||||||
.sum()
|
|
||||||
);
|
|
||||||
|
|
||||||
Set<String> confirmedTxIdSet = getTransactions(false).stream()
|
|
||||||
.filter(tx -> tx.getConfidence().getConfidenceType() == BUILDING)
|
|
||||||
.map(Transaction::getTxId)
|
|
||||||
.map(Sha256Hash::toString)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
lockedForVotingBalance = Coin.valueOf(daoStateService.getUnspentBlindVoteStakeTxOutputs().stream()
|
|
||||||
.filter(txOutput -> confirmedTxIdSet.contains(txOutput.getTxId()))
|
|
||||||
.mapToLong(TxOutput::getValue)
|
|
||||||
.sum());
|
|
||||||
|
|
||||||
lockupBondsBalance = Coin.valueOf(daoStateService.getLockupTxOutputs().stream()
|
|
||||||
.filter(txOutput -> daoStateService.isUnspent(txOutput.getKey()))
|
|
||||||
.filter(txOutput -> !daoStateService.isConfiscatedLockupTxOutput(txOutput.getTxId()))
|
|
||||||
.filter(txOutput -> confirmedTxIdSet.contains(txOutput.getTxId()))
|
|
||||||
.mapToLong(TxOutput::getValue)
|
|
||||||
.sum());
|
|
||||||
|
|
||||||
unlockingBondsBalance = Coin.valueOf(daoStateService.getUnspentUnlockingTxOutputsStream()
|
|
||||||
.filter(txOutput -> confirmedTxIdSet.contains(txOutput.getTxId()))
|
|
||||||
.filter(txOutput -> !daoStateService.isConfiscatedUnlockTxOutput(txOutput.getTxId()))
|
|
||||||
.mapToLong(TxOutput::getValue)
|
|
||||||
.sum());
|
|
||||||
|
|
||||||
availableConfirmedBalance = bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
|
||||||
wallet.calculateAllSpendCandidates()).valueGathered;
|
|
||||||
|
|
||||||
if (availableConfirmedBalance.isNegative())
|
|
||||||
availableConfirmedBalance = Coin.ZERO;
|
|
||||||
|
|
||||||
unconfirmedChangeBalance = unconfirmedBsqChangeOutputListService.getBalance();
|
|
||||||
|
|
||||||
availableNonBsqBalance = nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
|
||||||
wallet.calculateAllSpendCandidates()).valueGathered;
|
|
||||||
|
|
||||||
bsqBalanceListeners.forEach(e -> e.onUpdateBalances(availableConfirmedBalance, availableNonBsqBalance, unverifiedBalance,
|
|
||||||
unconfirmedChangeBalance, lockedForVotingBalance, lockupBondsBalance, unlockingBondsBalance));
|
|
||||||
log.info("updateBsqBalance took {} ms", System.currentTimeMillis() - ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addBsqBalanceListener(BsqBalanceListener listener) {
|
|
||||||
bsqBalanceListeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeBsqBalanceListener(BsqBalanceListener listener) {
|
|
||||||
bsqBalanceListeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addWalletTransactionsChangeListener(WalletTransactionsChangeListener listener) {
|
|
||||||
walletTransactionsChangeListeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeWalletTransactionsChangeListener(WalletTransactionsChangeListener listener) {
|
|
||||||
walletTransactionsChangeListeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TransactionOutput> getSpendableBsqTransactionOutputs() {
|
|
||||||
return new ArrayList<>(bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
|
||||||
wallet.calculateAllSpendCandidates()).gathered);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TransactionOutput> getSpendableNonBsqTransactionOutputs() {
|
|
||||||
return new ArrayList<>(nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
|
||||||
wallet.calculateAllSpendCandidates()).gathered);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// BSQ TransactionOutputs and Transactions
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public List<Transaction> getClonedWalletTransactions() {
|
|
||||||
return new ArrayList<>(walletTransactions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream<Transaction> getPendingWalletTransactionsStream() {
|
|
||||||
return walletTransactions.stream()
|
|
||||||
.filter(transaction -> transaction.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBsqWalletTransactions() {
|
|
||||||
if (daoStateService.isParseBlockChainComplete()) {
|
|
||||||
// We get called updateBsqWalletTransactions multiple times from onWalletChanged, onTransactionConfidenceChanged
|
|
||||||
// and from onParseBlockCompleteAfterBatchProcessing. But as updateBsqBalance is an expensive operation we do
|
|
||||||
// not want to call it in a short interval series so we use a flag and a delay to not call it multiple times
|
|
||||||
// in a 100 ms period.
|
|
||||||
if (!updateBsqWalletTransactionsPending) {
|
|
||||||
updateBsqWalletTransactionsPending = true;
|
|
||||||
UserThread.runAfter(() -> {
|
|
||||||
walletTransactions.clear();
|
|
||||||
walletTransactions.addAll(getTransactions(false));
|
|
||||||
walletTransactionsChangeListeners.forEach(WalletTransactionsChangeListener::onWalletTransactionsChange);
|
|
||||||
updateBsqBalance();
|
|
||||||
updateBsqWalletTransactionsPending = false;
|
|
||||||
}, 100, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<Transaction> getBsqWalletTransactions() {
|
|
||||||
return getTransactions(false).stream()
|
|
||||||
.filter(transaction -> transaction.getConfidence().getConfidenceType() == PENDING ||
|
|
||||||
daoStateService.containsTx(transaction.getTxId().toString()))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Transaction> getUnverifiedBsqTransactions() {
|
|
||||||
Set<Transaction> bsqWalletTransactions = getBsqWalletTransactions();
|
|
||||||
Set<Transaction> walletTxs = new HashSet<>(getTransactions(false));
|
|
||||||
checkArgument(walletTxs.size() >= bsqWalletTransactions.size(),
|
|
||||||
"We cannot have more txsWithOutputsFoundInBsqTxo than walletTxs");
|
|
||||||
if (walletTxs.size() == bsqWalletTransactions.size()) {
|
|
||||||
// As expected
|
|
||||||
return new HashSet<>();
|
|
||||||
} else {
|
|
||||||
Map<String, Transaction> map = walletTxs.stream()
|
|
||||||
.collect(Collectors.toMap(t -> t.getTxId().toString(), Function.identity()));
|
|
||||||
|
|
||||||
Set<String> walletTxIds = walletTxs.stream()
|
|
||||||
.map(Transaction::getTxId).map(Sha256Hash::toString).collect(Collectors.toSet());
|
|
||||||
Set<String> bsqTxIds = bsqWalletTransactions.stream()
|
|
||||||
.map(Transaction::getTxId).map(Sha256Hash::toString).collect(Collectors.toSet());
|
|
||||||
|
|
||||||
walletTxIds.stream()
|
|
||||||
.filter(bsqTxIds::contains)
|
|
||||||
.forEach(map::remove);
|
|
||||||
return new HashSet<>(map.values());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Coin getValueSentFromMeForTransaction(Transaction transaction) throws ScriptException {
|
|
||||||
Coin result = Coin.ZERO;
|
|
||||||
// We check all our inputs and get the connected outputs.
|
|
||||||
for (int i = 0; i < transaction.getInputs().size(); i++) {
|
|
||||||
TransactionInput input = transaction.getInputs().get(i);
|
|
||||||
// We grab the connected output for that input
|
|
||||||
TransactionOutput connectedOutput = input.getConnectedOutput();
|
|
||||||
if (connectedOutput != null) {
|
|
||||||
// We grab the parent tx of the connected output
|
|
||||||
final Transaction parentTransaction = connectedOutput.getParentTransaction();
|
|
||||||
final boolean isConfirmed = parentTransaction != null &&
|
|
||||||
parentTransaction.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING;
|
|
||||||
if (connectedOutput.isMineOrWatched(wallet)) {
|
|
||||||
if (isConfirmed) {
|
|
||||||
// We lookup if we have a BSQ tx matching the parent tx
|
|
||||||
// We cannot make that findTx call outside of the loop as the parent tx can change at each iteration
|
|
||||||
Optional<Tx> txOptional = daoStateService.getTx(parentTransaction.getTxId().toString());
|
|
||||||
if (txOptional.isPresent()) {
|
|
||||||
TxOutput txOutput = txOptional.get().getTxOutputs().get(connectedOutput.getIndex());
|
|
||||||
if (daoStateService.isBsqTxOutputType(txOutput)) {
|
|
||||||
//TODO check why values are not the same
|
|
||||||
if (txOutput.getValue() != connectedOutput.getValue().value)
|
|
||||||
log.warn("getValueSentToMeForTransaction: Value of BSQ output do not match BitcoinJ tx output. " +
|
|
||||||
"txOutput.getValue()={}, output.getValue().value={}, txId={}",
|
|
||||||
txOutput.getValue(), connectedOutput.getValue().value, txOptional.get().getId());
|
|
||||||
|
|
||||||
// If it is a valid BSQ output we add it
|
|
||||||
result = result.add(Coin.valueOf(txOutput.getValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} /*else {
|
|
||||||
// TODO atm we don't display amounts of unconfirmed txs but that might change so we leave that code
|
|
||||||
// if it will be required
|
|
||||||
// If the tx is not confirmed yet we add the value and assume it is a valid BSQ output.
|
|
||||||
result = result.add(connectedOutput.getValue());
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Coin getValueSentToMeForTransaction(Transaction transaction) throws ScriptException {
|
|
||||||
Coin result = Coin.ZERO;
|
|
||||||
final String txId = transaction.getTxId().toString();
|
|
||||||
// We check if we have a matching BSQ tx. We do that call here to avoid repeated calls in the loop.
|
|
||||||
Optional<Tx> txOptional = daoStateService.getTx(txId);
|
|
||||||
// We check all the outputs of our tx
|
|
||||||
for (int i = 0; i < transaction.getOutputs().size(); i++) {
|
|
||||||
TransactionOutput output = transaction.getOutputs().get(i);
|
|
||||||
final boolean isConfirmed = output.getParentTransaction() != null &&
|
|
||||||
output.getParentTransaction().getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING;
|
|
||||||
if (output.isMineOrWatched(wallet)) {
|
|
||||||
if (isConfirmed) {
|
|
||||||
if (txOptional.isPresent()) {
|
|
||||||
// The index of the BSQ tx outputs are the same like the bitcoinj tx outputs
|
|
||||||
TxOutput txOutput = txOptional.get().getTxOutputs().get(i);
|
|
||||||
if (daoStateService.isBsqTxOutputType(txOutput)) {
|
|
||||||
//TODO check why values are not the same
|
|
||||||
if (txOutput.getValue() != output.getValue().value) {
|
|
||||||
log.warn("getValueSentToMeForTransaction: Value of BSQ output do not match BitcoinJ tx output. " +
|
|
||||||
"txOutput.getValue()={}, output.getValue().value={}, txId={}",
|
|
||||||
txOutput.getValue(), output.getValue().value, txId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it is a valid BSQ output we add it
|
|
||||||
result = result.add(Coin.valueOf(txOutput.getValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} /*else {
|
|
||||||
// TODO atm we don't display amounts of unconfirmed txs but that might change so we leave that code
|
|
||||||
// if it will be required
|
|
||||||
// If the tx is not confirmed yet we add the value and assume it is a valid BSQ output.
|
|
||||||
result = result.add(output.getValue());
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<Transaction> isWalletTransaction(String txId) {
|
|
||||||
return walletTransactions.stream().filter(e -> e.getTxId().toString().equals(txId)).findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Sign tx
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public Transaction signTx(Transaction tx) throws WalletException, TransactionVerificationException {
|
|
||||||
for (int i = 0; i < tx.getInputs().size(); i++) {
|
|
||||||
TransactionInput txIn = tx.getInputs().get(i);
|
|
||||||
TransactionOutput connectedOutput = txIn.getConnectedOutput();
|
|
||||||
if (connectedOutput != null && connectedOutput.isMine(wallet)) {
|
|
||||||
signTransactionInput(wallet, aesKey, tx, txIn, i);
|
|
||||||
checkScriptSig(tx, txIn, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TransactionOutput txo : tx.getOutputs()) {
|
|
||||||
Coin value = txo.getValue();
|
|
||||||
// OpReturn outputs have value 0
|
|
||||||
if (value.isPositive()) {
|
|
||||||
checkArgument(Restrictions.isAboveDust(txo.getValue()),
|
|
||||||
"An output value is below dust limit. Transaction=" + tx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkWalletConsistency(wallet);
|
|
||||||
verifyTransaction(tx);
|
|
||||||
printTx("BSQ wallet: Signed Tx", tx);
|
|
||||||
return tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Commit tx
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public void commitTx(Transaction tx, TxType txType) {
|
|
||||||
wallet.commitTx(tx);
|
|
||||||
//printTx("BSQ commit Tx", tx);
|
|
||||||
|
|
||||||
unconfirmedBsqChangeOutputListService.onCommitTx(tx, txType, wallet);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Send BSQ with BTC fee
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public Transaction getPreparedSendBsqTx(String receiverAddress, Coin receiverAmount)
|
|
||||||
throws AddressFormatException, InsufficientBsqException, WalletException,
|
|
||||||
TransactionVerificationException, BsqChangeBelowDustException {
|
|
||||||
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction getPreparedSendBsqTx(String receiverAddress,
|
|
||||||
Coin receiverAmount,
|
|
||||||
@Nullable Set<TransactionOutput> utxoCandidates)
|
|
||||||
throws AddressFormatException, InsufficientBsqException, WalletException,
|
|
||||||
TransactionVerificationException, BsqChangeBelowDustException {
|
|
||||||
if (utxoCandidates != null) {
|
|
||||||
bsqCoinSelector.setUtxoCandidates(utxoCandidates);
|
|
||||||
}
|
|
||||||
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Send BTC (non-BSQ) with BTC fee (e.g. the issuance output from a lost comp. request)
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public Transaction getPreparedSendBtcTx(String receiverAddress, Coin receiverAmount)
|
|
||||||
throws AddressFormatException, InsufficientBsqException, WalletException,
|
|
||||||
TransactionVerificationException, BsqChangeBelowDustException {
|
|
||||||
return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction getPreparedSendBtcTx(String receiverAddress,
|
|
||||||
Coin receiverAmount,
|
|
||||||
@Nullable Set<TransactionOutput> utxoCandidates)
|
|
||||||
throws AddressFormatException, InsufficientBsqException, WalletException,
|
|
||||||
TransactionVerificationException, BsqChangeBelowDustException {
|
|
||||||
if (utxoCandidates != null) {
|
|
||||||
nonBsqCoinSelector.setUtxoCandidates(utxoCandidates);
|
|
||||||
}
|
|
||||||
return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Transaction getPreparedSendTx(String receiverAddress, Coin receiverAmount, CoinSelector coinSelector,
|
|
||||||
boolean allowSegwitOuput)
|
|
||||||
throws AddressFormatException, InsufficientBsqException, WalletException, TransactionVerificationException, BsqChangeBelowDustException {
|
|
||||||
daoKillSwitch.assertDaoIsNotDisabled();
|
|
||||||
Transaction tx = new Transaction(params);
|
|
||||||
checkArgument(Restrictions.isAboveDust(receiverAmount),
|
|
||||||
"The amount is too low (dust limit).");
|
|
||||||
if (allowSegwitOuput) {
|
|
||||||
tx.addOutput(receiverAmount, Address.fromString(params, receiverAddress));
|
|
||||||
} else {
|
|
||||||
tx.addOutput(receiverAmount, LegacyAddress.fromBase58(params, receiverAddress));
|
|
||||||
}
|
|
||||||
SendRequest sendRequest = SendRequest.forTx(tx);
|
|
||||||
sendRequest.fee = Coin.ZERO;
|
|
||||||
sendRequest.feePerKb = Coin.ZERO;
|
|
||||||
sendRequest.ensureMinRequiredFee = false;
|
|
||||||
sendRequest.aesKey = aesKey;
|
|
||||||
sendRequest.shuffleOutputs = false;
|
|
||||||
sendRequest.signInputs = false;
|
|
||||||
sendRequest.changeAddress = getChangeAddress();
|
|
||||||
sendRequest.coinSelector = coinSelector;
|
|
||||||
try {
|
|
||||||
wallet.completeTx(sendRequest);
|
|
||||||
checkWalletConsistency(wallet);
|
|
||||||
verifyTransaction(tx);
|
|
||||||
// printTx("prepareSendTx", tx);
|
|
||||||
|
|
||||||
// Tx has as first output BSQ and an optional second BSQ change output.
|
|
||||||
// At that stage we do not have added the BTC inputs so there is no BTC change output here.
|
|
||||||
if (tx.getOutputs().size() == 2) {
|
|
||||||
Coin bsqChangeOutputValue = tx.getOutputs().get(1).getValue();
|
|
||||||
if (!Restrictions.isAboveDust(bsqChangeOutputValue)) {
|
|
||||||
String msg = "BSQ change output is below dust limit. outputValue=" + bsqChangeOutputValue.value / 100 + " BSQ";
|
|
||||||
log.warn(msg);
|
|
||||||
throw new BsqChangeBelowDustException(msg, bsqChangeOutputValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx;
|
|
||||||
} catch (InsufficientMoneyException e) {
|
|
||||||
log.error("getPreparedSendTx: tx={}", tx.toString());
|
|
||||||
log.error(e.toString());
|
|
||||||
throw new InsufficientBsqException(e.missing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Burn fee txs
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public Transaction getPreparedTradeFeeTx(Coin fee) throws InsufficientBsqException {
|
|
||||||
daoKillSwitch.assertDaoIsNotDisabled();
|
|
||||||
|
|
||||||
Transaction tx = new Transaction(params);
|
|
||||||
addInputsAndChangeOutputForTx(tx, fee, bsqCoinSelector);
|
|
||||||
return tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We create a tx with Bsq inputs for the fee and optional BSQ change output.
|
|
||||||
// As the fee amount will be missing in the output those BSQ fees are burned.
|
|
||||||
public Transaction getPreparedProposalTx(Coin fee) throws InsufficientBsqException {
|
|
||||||
return getPreparedTxWithMandatoryBsqChangeOutput(fee);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction getPreparedIssuanceTx(Coin fee) throws InsufficientBsqException {
|
|
||||||
return getPreparedTxWithMandatoryBsqChangeOutput(fee);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction getPreparedProofOfBurnTx(Coin fee) throws InsufficientBsqException {
|
|
||||||
return getPreparedTxWithMandatoryBsqChangeOutput(fee);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction getPreparedBurnFeeTxForAssetListing(Coin fee) throws InsufficientBsqException {
|
|
||||||
return getPreparedTxWithMandatoryBsqChangeOutput(fee);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to require one BSQ change output as we could otherwise not be able to distinguish between 2
|
|
||||||
// structurally same transactions where only the BSQ fee is different. In case of asset listing fee and proof of
|
|
||||||
// burn it is a user input, so it is not known to the parser, instead we derive the burned fee from the parser.
|
|
||||||
|
|
||||||
// In case of proposal fee we could derive it from the params.
|
|
||||||
|
|
||||||
// For issuance txs we also require a BSQ change output before the issuance output gets added. There was a
|
|
||||||
// minor bug with the old version that multiple inputs would have caused an exception in case there was no
|
|
||||||
// change output (e.g. inputs of 21 and 6 BSQ for BSQ fee of 21 BSQ would have caused that only 1 input was used
|
|
||||||
// and then caused an error as we enforced a change output. This new version handles such cases correctly.
|
|
||||||
|
|
||||||
// Examples for the structurally indistinguishable transactions:
|
|
||||||
// Case 1: 10 BSQ fee to burn
|
|
||||||
// In: 17 BSQ
|
|
||||||
// Out: BSQ change 7 BSQ -> valid BSQ
|
|
||||||
// Out: OpReturn
|
|
||||||
// Miner fee: 1000 sat (10 BSQ burned)
|
|
||||||
|
|
||||||
// Case 2: 17 BSQ fee to burn
|
|
||||||
// In: 17 BSQ
|
|
||||||
// Out: burned BSQ change 7 BSQ -> BTC (7 BSQ burned)
|
|
||||||
// Out: OpReturn
|
|
||||||
// Miner fee: 1000 sat (10 BSQ burned)
|
|
||||||
|
|
||||||
private Transaction getPreparedTxWithMandatoryBsqChangeOutput(Coin fee) throws InsufficientBsqException {
|
|
||||||
daoKillSwitch.assertDaoIsNotDisabled();
|
|
||||||
|
|
||||||
Transaction tx = new Transaction(params);
|
|
||||||
// We look for inputs covering out BSQ fee we want to pay.
|
|
||||||
CoinSelection coinSelection = bsqCoinSelector.select(fee, wallet.calculateAllSpendCandidates());
|
|
||||||
try {
|
|
||||||
Coin change = bsqCoinSelector.getChange(fee, coinSelection);
|
|
||||||
if (change.isZero() || Restrictions.isDust(change)) {
|
|
||||||
// If change is zero or below dust we increase required input amount to enforce a BSQ change output.
|
|
||||||
// All outputs after that are considered BTC and therefore would be burned BSQ if BSQ is left from what
|
|
||||||
// we use for miner fee.
|
|
||||||
|
|
||||||
Coin minDustThreshold = Coin.valueOf(preferences.getIgnoreDustThreshold());
|
|
||||||
Coin increasedRequiredInput = fee.add(minDustThreshold);
|
|
||||||
coinSelection = bsqCoinSelector.select(increasedRequiredInput, wallet.calculateAllSpendCandidates());
|
|
||||||
change = bsqCoinSelector.getChange(fee, coinSelection);
|
|
||||||
|
|
||||||
log.warn("We increased required input as change output was zero or dust: New change value={}", change);
|
|
||||||
String info = "Available BSQ balance=" + coinSelection.valueGathered.value / 100 + " BSQ. " +
|
|
||||||
"Intended fee to burn=" + fee.value / 100 + " BSQ. " +
|
|
||||||
"Please increase your balance to at least " + (coinSelection.valueGathered.value + minDustThreshold.value) / 100 + " BSQ.";
|
|
||||||
checkArgument(coinSelection.valueGathered.compareTo(fee) > 0,
|
|
||||||
"This transaction require a change output of at least " + minDustThreshold.value / 100 + " BSQ (dust limit). " +
|
|
||||||
info);
|
|
||||||
|
|
||||||
checkArgument(!Restrictions.isDust(change),
|
|
||||||
"This transaction would create a dust output of " + change.value / 100 + " BSQ. " +
|
|
||||||
"It requires a change output of at least " + minDustThreshold.value / 100 + " BSQ (dust limit). " +
|
|
||||||
info);
|
|
||||||
}
|
|
||||||
|
|
||||||
coinSelection.gathered.forEach(tx::addInput);
|
|
||||||
tx.addOutput(change, getChangeAddress());
|
|
||||||
|
|
||||||
return tx;
|
|
||||||
|
|
||||||
} catch (InsufficientMoneyException e) {
|
|
||||||
log.error("coinSelection.gathered={}", coinSelection.gathered);
|
|
||||||
throw new InsufficientBsqException(e.missing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addInputsAndChangeOutputForTx(Transaction tx,
|
|
||||||
Coin fee,
|
|
||||||
BsqCoinSelector bsqCoinSelector)
|
|
||||||
throws InsufficientBsqException {
|
|
||||||
Coin requiredInput;
|
|
||||||
// If our fee is less then dust limit we increase it so we are sure to not get any dust output.
|
|
||||||
if (Restrictions.isDust(fee)) {
|
|
||||||
requiredInput = fee.add(Restrictions.getMinNonDustOutput());
|
|
||||||
} else {
|
|
||||||
requiredInput = fee;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoinSelection coinSelection = bsqCoinSelector.select(requiredInput, wallet.calculateAllSpendCandidates());
|
|
||||||
coinSelection.gathered.forEach(tx::addInput);
|
|
||||||
try {
|
|
||||||
Coin change = bsqCoinSelector.getChange(fee, coinSelection);
|
|
||||||
// Change can be ZERO, then no change output is created so don't rely on a BSQ change output
|
|
||||||
if (change.isPositive()) {
|
|
||||||
checkArgument(Restrictions.isAboveDust(change),
|
|
||||||
"The change output of " + change.value / 100d + " BSQ is below the min. dust value of "
|
|
||||||
+ Restrictions.getMinNonDustOutput().value / 100d +
|
|
||||||
". At least " + Restrictions.getMinNonDustOutput().add(fee).value / 100d +
|
|
||||||
" BSQ is needed for this transaction");
|
|
||||||
tx.addOutput(change, getChangeAddress());
|
|
||||||
}
|
|
||||||
} catch (InsufficientMoneyException e) {
|
|
||||||
log.error(tx.toString());
|
|
||||||
throw new InsufficientBsqException(e.missing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Blind vote tx
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// We create a tx with Bsq inputs for the fee, one output for the stake and optional one BSQ change output.
|
|
||||||
// As the fee amount will be missing in the output those BSQ fees are burned.
|
|
||||||
public Transaction getPreparedBlindVoteTx(Coin fee, Coin stake) throws InsufficientBsqException {
|
|
||||||
daoKillSwitch.assertDaoIsNotDisabled();
|
|
||||||
Transaction tx = new Transaction(params);
|
|
||||||
tx.addOutput(new TransactionOutput(params, tx, stake, getUnusedAddress()));
|
|
||||||
addInputsAndChangeOutputForTx(tx, fee.add(stake), bsqCoinSelector);
|
|
||||||
//printTx("getPreparedBlindVoteTx", tx);
|
|
||||||
return tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// MyVote reveal tx
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public Transaction getPreparedVoteRevealTx(TxOutput stakeTxOutput) {
|
|
||||||
daoKillSwitch.assertDaoIsNotDisabled();
|
|
||||||
Transaction tx = new Transaction(params);
|
|
||||||
final Coin stake = Coin.valueOf(stakeTxOutput.getValue());
|
|
||||||
Transaction blindVoteTx = getTransaction(stakeTxOutput.getTxId());
|
|
||||||
checkNotNull(blindVoteTx, "blindVoteTx must not be null");
|
|
||||||
TransactionOutPoint outPoint = new TransactionOutPoint(params, stakeTxOutput.getIndex(), blindVoteTx);
|
|
||||||
// Input is not signed yet so we use new byte[]{}
|
|
||||||
tx.addInput(new TransactionInput(params, tx, new byte[]{}, outPoint, stake));
|
|
||||||
tx.addOutput(new TransactionOutput(params, tx, stake, getUnusedAddress()));
|
|
||||||
// printTx("getPreparedVoteRevealTx", tx);
|
|
||||||
return tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Lockup bond tx
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public Transaction getPreparedLockupTx(Coin lockupAmount) throws AddressFormatException, InsufficientBsqException {
|
|
||||||
daoKillSwitch.assertDaoIsNotDisabled();
|
|
||||||
Transaction tx = new Transaction(params);
|
|
||||||
checkArgument(Restrictions.isAboveDust(lockupAmount), "The amount is too low (dust limit).");
|
|
||||||
tx.addOutput(new TransactionOutput(params, tx, lockupAmount, getUnusedAddress()));
|
|
||||||
addInputsAndChangeOutputForTx(tx, lockupAmount, bsqCoinSelector);
|
|
||||||
printTx("prepareLockupTx", tx);
|
|
||||||
return tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Unlock bond tx
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public Transaction getPreparedUnlockTx(TxOutput lockupTxOutput) throws AddressFormatException {
|
|
||||||
daoKillSwitch.assertDaoIsNotDisabled();
|
|
||||||
Transaction tx = new Transaction(params);
|
|
||||||
// Unlocking means spending the full value of the locked txOutput to another txOutput with the same value
|
|
||||||
Coin amountToUnlock = Coin.valueOf(lockupTxOutput.getValue());
|
|
||||||
checkArgument(Restrictions.isAboveDust(amountToUnlock), "The amount is too low (dust limit).");
|
|
||||||
Transaction lockupTx = getTransaction(lockupTxOutput.getTxId());
|
|
||||||
checkNotNull(lockupTx, "lockupTx must not be null");
|
|
||||||
TransactionOutPoint outPoint = new TransactionOutPoint(params, lockupTxOutput.getIndex(), lockupTx);
|
|
||||||
// Input is not signed yet so we use new byte[]{}
|
|
||||||
tx.addInput(new TransactionInput(params, tx, new byte[]{}, outPoint, amountToUnlock));
|
|
||||||
tx.addOutput(new TransactionOutput(params, tx, amountToUnlock, getUnusedAddress()));
|
|
||||||
printTx("prepareUnlockTx", tx);
|
|
||||||
return tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Addresses
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private LegacyAddress getChangeAddress() {
|
|
||||||
return getUnusedAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
public LegacyAddress getUnusedAddress() {
|
|
||||||
return (LegacyAddress) wallet.getIssuedReceiveAddresses().stream()
|
|
||||||
.filter(this::isAddressUnused)
|
|
||||||
.findAny()
|
|
||||||
.orElse(wallet.freshReceiveAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUnusedBsqAddressAsString() {
|
|
||||||
return "B" + getUnusedAddress().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// For BSQ we do not check for dust attack utxos as they are 5.46 BSQ and a considerable value.
|
|
||||||
// The default 546 sat dust limit is handled in the BitcoinJ side anyway.
|
|
||||||
@Override
|
|
||||||
protected boolean isDustAttackUtxo(TransactionOutput output) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -147,444 +147,6 @@ public class BtcWalletService extends WalletService {
|
||||||
wallet.printAllPubKeysAsHex();
|
wallet.printAllPubKeysAsHex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Public Methods
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Burn BSQ txs (some proposal txs, asset listing fee tx, proof of burn tx)
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public Transaction completePreparedBurnBsqTx(Transaction preparedBurnFeeTx, byte[] opReturnData)
|
|
||||||
throws WalletException, InsufficientMoneyException, TransactionVerificationException {
|
|
||||||
return completePreparedProposalTx(preparedBurnFeeTx, opReturnData, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Proposal txs
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public Transaction completePreparedReimbursementRequestTx(Coin issuanceAmount,
|
|
||||||
Address issuanceAddress,
|
|
||||||
Transaction feeTx,
|
|
||||||
byte[] opReturnData)
|
|
||||||
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
|
|
||||||
return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction completePreparedCompensationRequestTx(Coin issuanceAmount,
|
|
||||||
Address issuanceAddress,
|
|
||||||
Transaction feeTx,
|
|
||||||
byte[] opReturnData)
|
|
||||||
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
|
|
||||||
return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Transaction completePreparedProposalTx(Transaction feeTx, byte[] opReturnData,
|
|
||||||
@Nullable Coin issuanceAmount, @Nullable Address issuanceAddress)
|
|
||||||
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
|
|
||||||
|
|
||||||
// (BsqFee)tx has following structure:
|
|
||||||
// inputs [1-n] BSQ inputs (fee)
|
|
||||||
// outputs [0-1] BSQ request fee change output (>= 546 Satoshi)
|
|
||||||
|
|
||||||
// preparedCompensationRequestTx has following structure:
|
|
||||||
// inputs [1-n] BSQ inputs for request fee
|
|
||||||
// inputs [1-n] BTC inputs for BSQ issuance and miner fee
|
|
||||||
// outputs [1] Mandatory BSQ request fee change output (>= 546 Satoshi)
|
|
||||||
// outputs [1] Potentially BSQ issuance output (>= 546 Satoshi) - in case of a issuance tx, otherwise that output does not exist
|
|
||||||
// outputs [0-1] BTC change output from issuance and miner fee inputs (>= 546 Satoshi)
|
|
||||||
// outputs [1] OP_RETURN with opReturnData and amount 0
|
|
||||||
// mining fee: BTC mining fee + burned BSQ fee
|
|
||||||
|
|
||||||
Transaction preparedTx = new Transaction(params);
|
|
||||||
// Copy inputs from BSQ fee tx
|
|
||||||
feeTx.getInputs().forEach(preparedTx::addInput);
|
|
||||||
int indexOfBtcFirstInput = feeTx.getInputs().size();
|
|
||||||
|
|
||||||
// Need to be first because issuance is not guaranteed to be valid and would otherwise burn change output!
|
|
||||||
// BSQ change outputs from BSQ fee inputs.
|
|
||||||
feeTx.getOutputs().forEach(preparedTx::addOutput);
|
|
||||||
|
|
||||||
// For generic proposals there is no issuance output, for compensation and reimburse requests there is
|
|
||||||
if (issuanceAmount != null && issuanceAddress != null) {
|
|
||||||
// BSQ issuance output
|
|
||||||
preparedTx.addOutput(issuanceAmount, issuanceAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// safety check counter to avoid endless loops
|
|
||||||
int counter = 0;
|
|
||||||
// estimated size of input sig
|
|
||||||
int sigSizePerInput = 106;
|
|
||||||
// typical size for a tx with 3 inputs
|
|
||||||
int txVsizeWithUnsignedInputs = 300;
|
|
||||||
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
|
|
||||||
|
|
||||||
Address changeAddress = getFreshAddressEntry().getAddress();
|
|
||||||
checkNotNull(changeAddress, "changeAddress must not be null");
|
|
||||||
|
|
||||||
BtcCoinSelector coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE),
|
|
||||||
preferences.getIgnoreDustThreshold());
|
|
||||||
List<TransactionInput> preparedBsqTxInputs = preparedTx.getInputs();
|
|
||||||
List<TransactionOutput> preparedBsqTxOutputs = preparedTx.getOutputs();
|
|
||||||
Tuple2<Integer, Integer> numInputs = getNumInputs(preparedTx);
|
|
||||||
int numLegacyInputs = numInputs.first;
|
|
||||||
int numSegwitInputs = numInputs.second;
|
|
||||||
Transaction resultTx = null;
|
|
||||||
boolean isFeeOutsideTolerance;
|
|
||||||
do {
|
|
||||||
counter++;
|
|
||||||
if (counter >= 10) {
|
|
||||||
checkNotNull(resultTx, "resultTx must not be null");
|
|
||||||
log.error("Could not calculate the fee. Tx=" + resultTx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction tx = new Transaction(params);
|
|
||||||
preparedBsqTxInputs.forEach(tx::addInput);
|
|
||||||
preparedBsqTxOutputs.forEach(tx::addOutput);
|
|
||||||
|
|
||||||
SendRequest sendRequest = SendRequest.forTx(tx);
|
|
||||||
sendRequest.shuffleOutputs = false;
|
|
||||||
sendRequest.aesKey = aesKey;
|
|
||||||
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
|
|
||||||
sendRequest.signInputs = false;
|
|
||||||
|
|
||||||
sendRequest.fee = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
|
||||||
sigSizePerInput * numLegacyInputs +
|
|
||||||
sigSizePerInput * numSegwitInputs / 4);
|
|
||||||
|
|
||||||
sendRequest.feePerKb = Coin.ZERO;
|
|
||||||
sendRequest.ensureMinRequiredFee = false;
|
|
||||||
|
|
||||||
sendRequest.coinSelector = coinSelector;
|
|
||||||
sendRequest.changeAddress = changeAddress;
|
|
||||||
wallet.completeTx(sendRequest);
|
|
||||||
|
|
||||||
resultTx = sendRequest.tx;
|
|
||||||
|
|
||||||
// add OP_RETURN output
|
|
||||||
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
|
|
||||||
|
|
||||||
numInputs = getNumInputs(resultTx);
|
|
||||||
numLegacyInputs = numInputs.first;
|
|
||||||
numSegwitInputs = numInputs.second;
|
|
||||||
txVsizeWithUnsignedInputs = resultTx.getVsize();
|
|
||||||
long estimatedFeeAsLong = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
|
||||||
sigSizePerInput * numLegacyInputs +
|
|
||||||
sigSizePerInput * numSegwitInputs / 4).value;
|
|
||||||
|
|
||||||
// calculated fee must be inside of a tolerance range with tx fee
|
|
||||||
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
|
|
||||||
}
|
|
||||||
while (isFeeOutsideTolerance);
|
|
||||||
|
|
||||||
// Sign all BTC inputs
|
|
||||||
signAllBtcInputs(indexOfBtcFirstInput, resultTx);
|
|
||||||
|
|
||||||
checkWalletConsistency(wallet);
|
|
||||||
verifyTransaction(resultTx);
|
|
||||||
|
|
||||||
// printTx("BTC wallet: Signed tx", resultTx);
|
|
||||||
return resultTx;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Blind vote tx
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// We add BTC inputs to pay miner fees and sign the BTC tx inputs
|
|
||||||
|
|
||||||
// (BsqFee)tx has following structure:
|
|
||||||
// inputs [1-n] BSQ inputs (fee + stake)
|
|
||||||
// outputs [1] BSQ stake
|
|
||||||
// outputs [0-1] BSQ change output (>= 546 Satoshi)
|
|
||||||
|
|
||||||
// preparedVoteTx has following structure:
|
|
||||||
// inputs [1-n] BSQ inputs (fee + stake)
|
|
||||||
// inputs [1-n] BTC inputs for miner fee
|
|
||||||
// outputs [1] BSQ stake
|
|
||||||
// outputs [0-1] BSQ change output (>= 546 Satoshi)
|
|
||||||
// outputs [0-1] BTC change output from miner fee inputs (>= 546 Satoshi)
|
|
||||||
// outputs [1] OP_RETURN with opReturnData and amount 0
|
|
||||||
// mining fee: BTC mining fee + burned BSQ fee
|
|
||||||
public Transaction completePreparedBlindVoteTx(Transaction preparedTx, byte[] opReturnData)
|
|
||||||
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
|
|
||||||
// First input index for btc inputs (they get added after bsq inputs)
|
|
||||||
return completePreparedBsqTxWithBtcFee(preparedTx, opReturnData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Transaction completePreparedBsqTxWithBtcFee(Transaction preparedTx,
|
|
||||||
byte[] opReturnData) throws InsufficientMoneyException, TransactionVerificationException, WalletException {
|
|
||||||
// Remember index for first BTC input
|
|
||||||
int indexOfBtcFirstInput = preparedTx.getInputs().size();
|
|
||||||
|
|
||||||
Transaction tx = addInputsForMinerFee(preparedTx, opReturnData);
|
|
||||||
signAllBtcInputs(indexOfBtcFirstInput, tx);
|
|
||||||
|
|
||||||
checkWalletConsistency(wallet);
|
|
||||||
verifyTransaction(tx);
|
|
||||||
|
|
||||||
// printTx("BTC wallet: Signed tx", tx);
|
|
||||||
return tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Transaction addInputsForMinerFee(Transaction preparedTx,
|
|
||||||
byte[] opReturnData) throws InsufficientMoneyException {
|
|
||||||
// safety check counter to avoid endless loops
|
|
||||||
int counter = 0;
|
|
||||||
// estimated size of input sig
|
|
||||||
int sigSizePerInput = 106;
|
|
||||||
// typical size for a tx with 3 inputs
|
|
||||||
int txVsizeWithUnsignedInputs = 300;
|
|
||||||
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
|
|
||||||
|
|
||||||
Address changeAddress = getFreshAddressEntry().getAddress();
|
|
||||||
checkNotNull(changeAddress, "changeAddress must not be null");
|
|
||||||
|
|
||||||
BtcCoinSelector coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE),
|
|
||||||
preferences.getIgnoreDustThreshold());
|
|
||||||
List<TransactionInput> preparedBsqTxInputs = preparedTx.getInputs();
|
|
||||||
List<TransactionOutput> preparedBsqTxOutputs = preparedTx.getOutputs();
|
|
||||||
Tuple2<Integer, Integer> numInputs = getNumInputs(preparedTx);
|
|
||||||
int numLegacyInputs = numInputs.first;
|
|
||||||
int numSegwitInputs = numInputs.second;
|
|
||||||
Transaction resultTx = null;
|
|
||||||
boolean isFeeOutsideTolerance;
|
|
||||||
do {
|
|
||||||
counter++;
|
|
||||||
if (counter >= 10) {
|
|
||||||
checkNotNull(resultTx, "resultTx must not be null");
|
|
||||||
log.error("Could not calculate the fee. Tx=" + resultTx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction tx = new Transaction(params);
|
|
||||||
preparedBsqTxInputs.forEach(tx::addInput);
|
|
||||||
preparedBsqTxOutputs.forEach(tx::addOutput);
|
|
||||||
|
|
||||||
SendRequest sendRequest = SendRequest.forTx(tx);
|
|
||||||
sendRequest.shuffleOutputs = false;
|
|
||||||
sendRequest.aesKey = aesKey;
|
|
||||||
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
|
|
||||||
sendRequest.signInputs = false;
|
|
||||||
|
|
||||||
sendRequest.fee = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
|
||||||
sigSizePerInput * numLegacyInputs +
|
|
||||||
sigSizePerInput * numSegwitInputs / 4);
|
|
||||||
sendRequest.feePerKb = Coin.ZERO;
|
|
||||||
sendRequest.ensureMinRequiredFee = false;
|
|
||||||
|
|
||||||
sendRequest.coinSelector = coinSelector;
|
|
||||||
sendRequest.changeAddress = changeAddress;
|
|
||||||
wallet.completeTx(sendRequest);
|
|
||||||
|
|
||||||
resultTx = sendRequest.tx;
|
|
||||||
|
|
||||||
// add OP_RETURN output
|
|
||||||
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
|
|
||||||
|
|
||||||
numInputs = getNumInputs(resultTx);
|
|
||||||
numLegacyInputs = numInputs.first;
|
|
||||||
numSegwitInputs = numInputs.second;
|
|
||||||
txVsizeWithUnsignedInputs = resultTx.getVsize();
|
|
||||||
final long estimatedFeeAsLong = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
|
||||||
sigSizePerInput * numLegacyInputs +
|
|
||||||
sigSizePerInput * numSegwitInputs / 4).value;
|
|
||||||
// calculated fee must be inside of a tolerance range with tx fee
|
|
||||||
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
|
|
||||||
}
|
|
||||||
while (isFeeOutsideTolerance);
|
|
||||||
return resultTx;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void signAllBtcInputs(int indexOfBtcFirstInput, Transaction tx) throws TransactionVerificationException {
|
|
||||||
for (int i = indexOfBtcFirstInput; i < tx.getInputs().size(); i++) {
|
|
||||||
TransactionInput input = tx.getInputs().get(i);
|
|
||||||
checkArgument(input.getConnectedOutput() != null && input.getConnectedOutput().isMine(wallet),
|
|
||||||
"input.getConnectedOutput() is not in our wallet. That must not happen.");
|
|
||||||
signTransactionInput(wallet, aesKey, tx, input, i);
|
|
||||||
checkScriptSig(tx, input, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Vote reveal tx
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// We add BTC fees to the prepared reveal tx
|
|
||||||
// (BsqFee)tx has following structure:
|
|
||||||
// inputs [1] BSQ input (stake)
|
|
||||||
// output [1] BSQ unlocked stake
|
|
||||||
|
|
||||||
// preparedVoteTx has following structure:
|
|
||||||
// inputs [1] BSQ inputs (stake)
|
|
||||||
// inputs [1-n] BTC inputs for miner fee
|
|
||||||
// outputs [1] BSQ unlocked stake
|
|
||||||
// outputs [0-1] BTC change output from miner fee inputs (>= 546 Satoshi)
|
|
||||||
// outputs [1] OP_RETURN with opReturnData and amount 0
|
|
||||||
// mining fee: BTC mining fee + burned BSQ fee
|
|
||||||
public Transaction completePreparedVoteRevealTx(Transaction preparedTx, byte[] opReturnData)
|
|
||||||
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
|
|
||||||
return completePreparedBsqTxWithBtcFee(preparedTx, opReturnData);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Add fee input to prepared BSQ send tx
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public Transaction completePreparedSendBsqTx(Transaction preparedBsqTx) throws
|
|
||||||
TransactionVerificationException, WalletException, InsufficientMoneyException {
|
|
||||||
// preparedBsqTx has following structure:
|
|
||||||
// inputs [1-n] BSQ inputs
|
|
||||||
// outputs [1] BSQ receiver's output
|
|
||||||
// outputs [0-1] BSQ change output
|
|
||||||
|
|
||||||
// We add BTC mining fee. Result tx looks like:
|
|
||||||
// inputs [1-n] BSQ inputs
|
|
||||||
// inputs [1-n] BTC inputs
|
|
||||||
// outputs [1] BSQ receiver's output
|
|
||||||
// outputs [0-1] BSQ change output
|
|
||||||
// outputs [0-1] BTC change output
|
|
||||||
// mining fee: BTC mining fee
|
|
||||||
Coin txFeePerVbyte = getTxFeeForWithdrawalPerVbyte();
|
|
||||||
return completePreparedBsqTx(preparedBsqTx, null, txFeePerVbyte);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction completePreparedSendBsqTx(Transaction preparedBsqTx, Coin txFeePerVbyte) throws
|
|
||||||
TransactionVerificationException, WalletException, InsufficientMoneyException {
|
|
||||||
return completePreparedBsqTx(preparedBsqTx, null, txFeePerVbyte);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction completePreparedBsqTx(Transaction preparedBsqTx,
|
|
||||||
@Nullable byte[] opReturnData) throws
|
|
||||||
TransactionVerificationException, WalletException, InsufficientMoneyException {
|
|
||||||
Coin txFeePerVbyte = getTxFeeForWithdrawalPerVbyte();
|
|
||||||
return completePreparedBsqTx(preparedBsqTx, opReturnData, txFeePerVbyte);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction completePreparedBsqTx(Transaction preparedBsqTx,
|
|
||||||
@Nullable byte[] opReturnData,
|
|
||||||
Coin txFeePerVbyte) throws
|
|
||||||
TransactionVerificationException, WalletException, InsufficientMoneyException {
|
|
||||||
|
|
||||||
// preparedBsqTx has following structure:
|
|
||||||
// inputs [1-n] BSQ inputs
|
|
||||||
// outputs [1] BSQ receiver's output
|
|
||||||
// outputs [0-1] BSQ change output
|
|
||||||
// mining fee: optional burned BSQ fee (only if opReturnData != null)
|
|
||||||
|
|
||||||
// We add BTC mining fee. Result tx looks like:
|
|
||||||
// inputs [1-n] BSQ inputs
|
|
||||||
// inputs [1-n] BTC inputs
|
|
||||||
// outputs [0-1] BSQ receiver's output
|
|
||||||
// outputs [0-1] BSQ change output
|
|
||||||
// outputs [0-1] BTC change output
|
|
||||||
// outputs [0-1] OP_RETURN with opReturnData (only if opReturnData != null)
|
|
||||||
// mining fee: BTC mining fee + optional burned BSQ fee (only if opReturnData != null)
|
|
||||||
|
|
||||||
// In case of txs for burned BSQ fees we have no receiver output and it might be that there is no change outputs
|
|
||||||
// We need to guarantee that min. 1 valid output is added (OP_RETURN does not count). So we use a higher input
|
|
||||||
// for BTC to force an additional change output.
|
|
||||||
|
|
||||||
// safety check counter to avoid endless loops
|
|
||||||
int counter = 0;
|
|
||||||
// estimated size of input sig
|
|
||||||
int sigSizePerInput = 106;
|
|
||||||
// typical size for a tx with 2 inputs
|
|
||||||
int txVsizeWithUnsignedInputs = 203;
|
|
||||||
// In case there are no change outputs we force a change by adding min dust to the BTC input
|
|
||||||
Coin forcedChangeValue = Coin.ZERO;
|
|
||||||
|
|
||||||
Address changeAddress = getFreshAddressEntry().getAddress();
|
|
||||||
checkNotNull(changeAddress, "changeAddress must not be null");
|
|
||||||
|
|
||||||
BtcCoinSelector coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE),
|
|
||||||
preferences.getIgnoreDustThreshold());
|
|
||||||
List<TransactionInput> preparedBsqTxInputs = preparedBsqTx.getInputs();
|
|
||||||
List<TransactionOutput> preparedBsqTxOutputs = preparedBsqTx.getOutputs();
|
|
||||||
// We don't know at this point what type the btc input would be (segwit/legacy).
|
|
||||||
// We use legacy to be on the safe side.
|
|
||||||
int numLegacyInputs = preparedBsqTxInputs.size() + 1; // We add 1 for the BTC fee input
|
|
||||||
int numSegwitInputs = 0;
|
|
||||||
Transaction resultTx = null;
|
|
||||||
boolean isFeeOutsideTolerance;
|
|
||||||
boolean opReturnIsOnlyOutput;
|
|
||||||
do {
|
|
||||||
counter++;
|
|
||||||
if (counter >= 10) {
|
|
||||||
checkNotNull(resultTx, "resultTx must not be null");
|
|
||||||
log.error("Could not calculate the fee. Tx=" + resultTx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction tx = new Transaction(params);
|
|
||||||
preparedBsqTxInputs.stream().forEach(tx::addInput);
|
|
||||||
|
|
||||||
if (forcedChangeValue.isZero()) {
|
|
||||||
preparedBsqTxOutputs.stream().forEach(tx::addOutput);
|
|
||||||
} else {
|
|
||||||
//TODO test that case
|
|
||||||
checkArgument(preparedBsqTxOutputs.size() == 0, "preparedBsqTxOutputs.size must be null in that code branch");
|
|
||||||
tx.addOutput(forcedChangeValue, changeAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
SendRequest sendRequest = SendRequest.forTx(tx);
|
|
||||||
sendRequest.shuffleOutputs = false;
|
|
||||||
sendRequest.aesKey = aesKey;
|
|
||||||
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
|
|
||||||
sendRequest.signInputs = false;
|
|
||||||
|
|
||||||
sendRequest.fee = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
|
||||||
sigSizePerInput * numLegacyInputs +
|
|
||||||
sigSizePerInput * numSegwitInputs / 4);
|
|
||||||
sendRequest.feePerKb = Coin.ZERO;
|
|
||||||
sendRequest.ensureMinRequiredFee = false;
|
|
||||||
|
|
||||||
sendRequest.coinSelector = coinSelector;
|
|
||||||
sendRequest.changeAddress = changeAddress;
|
|
||||||
wallet.completeTx(sendRequest);
|
|
||||||
|
|
||||||
resultTx = sendRequest.tx;
|
|
||||||
|
|
||||||
// We might have the rare case that both inputs matched the required fees, so both did not require
|
|
||||||
// a change output.
|
|
||||||
// In such cases we need to add artificially a change output (OP_RETURN is not allowed as only output)
|
|
||||||
opReturnIsOnlyOutput = resultTx.getOutputs().size() == 0;
|
|
||||||
forcedChangeValue = opReturnIsOnlyOutput ? Restrictions.getMinNonDustOutput() : Coin.ZERO;
|
|
||||||
|
|
||||||
// add OP_RETURN output
|
|
||||||
if (opReturnData != null)
|
|
||||||
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
|
|
||||||
|
|
||||||
Tuple2<Integer, Integer> numInputs = getNumInputs(resultTx);
|
|
||||||
numLegacyInputs = numInputs.first;
|
|
||||||
numSegwitInputs = numInputs.second;
|
|
||||||
txVsizeWithUnsignedInputs = resultTx.getVsize();
|
|
||||||
final long estimatedFeeAsLong = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
|
|
||||||
sigSizePerInput * numLegacyInputs +
|
|
||||||
sigSizePerInput * numSegwitInputs / 4).value;
|
|
||||||
// calculated fee must be inside of a tolerance range with tx fee
|
|
||||||
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
|
|
||||||
}
|
|
||||||
while (opReturnIsOnlyOutput ||
|
|
||||||
isFeeOutsideTolerance ||
|
|
||||||
resultTx.getFee().value < txFeePerVbyte.multiply(resultTx.getVsize()).value);
|
|
||||||
|
|
||||||
// Sign all BTC inputs
|
|
||||||
signAllBtcInputs(preparedBsqTxInputs.size(), resultTx);
|
|
||||||
|
|
||||||
checkWalletConsistency(wallet);
|
|
||||||
verifyTransaction(resultTx);
|
|
||||||
|
|
||||||
printTx("BTC wallet: Signed tx", resultTx);
|
|
||||||
return resultTx;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tuple2<Integer, Integer> getNumInputs(Transaction tx) {
|
private Tuple2<Integer, Integer> getNumInputs(Transaction tx) {
|
||||||
int numLegacyInputs = 0;
|
int numLegacyInputs = 0;
|
||||||
int numSegwitInputs = 0;
|
int numSegwitInputs = 0;
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
|
|
||||||
package bisq.core.btc.wallet;
|
package bisq.core.btc.wallet;
|
||||||
|
|
||||||
import bisq.core.dao.state.DaoStateService;
|
|
||||||
import bisq.core.dao.state.model.blockchain.TxOutputKey;
|
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
|
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
|
@ -36,14 +34,12 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class NonBsqCoinSelector extends BisqDefaultCoinSelector {
|
public class NonBsqCoinSelector extends BisqDefaultCoinSelector {
|
||||||
private DaoStateService daoStateService;
|
|
||||||
@Setter
|
@Setter
|
||||||
private Preferences preferences;
|
private Preferences preferences;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public NonBsqCoinSelector(DaoStateService daoStateService) {
|
public NonBsqCoinSelector() {
|
||||||
super(false);
|
super(false);
|
||||||
this.daoStateService = daoStateService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -58,10 +54,7 @@ public class NonBsqCoinSelector extends BisqDefaultCoinSelector {
|
||||||
if (parentTransaction.getConfidence().getConfidenceType() != TransactionConfidence.ConfidenceType.BUILDING)
|
if (parentTransaction.getConfidence().getConfidenceType() != TransactionConfidence.ConfidenceType.BUILDING)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
TxOutputKey key = new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex());
|
return true;
|
||||||
// It might be that we received BTC in a non-BSQ tx so that will not be stored in out state and not found.
|
|
||||||
// So we consider any txOutput which is not in the state as BTC output.
|
|
||||||
return !daoStateService.existsTxOutput(key) || daoStateService.isRejectedIssuanceOutput(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent usage of dust attack utxos
|
// Prevent usage of dust attack utxos
|
||||||
|
|
|
@ -151,177 +151,6 @@ public class TradeWalletService {
|
||||||
.setRelay(broadcastTx));
|
.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.
|
|
||||||
*
|
|
||||||
* @param fundingAddress the provided source of funds in case the savings wallet is not used
|
|
||||||
* @param reservedForTradeAddress the address of the trade reserve
|
|
||||||
* @param changeAddress the change address to use in case of overpayment or use of the savings wallet
|
|
||||||
* @param reservedFundsForOffer the amount to reserve for the trade
|
|
||||||
* @param useSavingsWallet {@code true} to use the savings wallet, {@code false} to use the funding address
|
|
||||||
* @param tradingFee the amount of the trading fee
|
|
||||||
* @param txFee the mining fee for this transaction
|
|
||||||
* @param feeReceiverAddress the address of the receiver of the trading fee
|
|
||||||
* @param doBroadcast {@code true} to broadcast the transaction, {@code false} otherwise
|
|
||||||
* @param callback an optional callback to use when broadcasting the transaction
|
|
||||||
* @return the optionally broadcast transaction
|
|
||||||
* @throws InsufficientMoneyException if the request could not be completed due to not enough balance
|
|
||||||
* @throws AddressFormatException if the fee receiver base58 address doesn't parse or its checksum is invalid
|
|
||||||
*/
|
|
||||||
public Transaction createBtcTradingFeeTx(Address fundingAddress,
|
|
||||||
Address reservedForTradeAddress,
|
|
||||||
Address changeAddress,
|
|
||||||
Coin reservedFundsForOffer,
|
|
||||||
boolean useSavingsWallet,
|
|
||||||
Coin tradingFee,
|
|
||||||
Coin txFee,
|
|
||||||
String feeReceiverAddress,
|
|
||||||
boolean doBroadcast,
|
|
||||||
@Nullable TxBroadcaster.Callback callback) throws InsufficientMoneyException, AddressFormatException {
|
|
||||||
Transaction tradingFeeTx = new Transaction(params);
|
|
||||||
SendRequest sendRequest = null;
|
|
||||||
try {
|
|
||||||
tradingFeeTx.addOutput(tradingFee, Address.fromString(params, feeReceiverAddress));
|
|
||||||
// the reserved amount we need for the trade we send to our trade reservedForTradeAddress
|
|
||||||
tradingFeeTx.addOutput(reservedFundsForOffer, reservedForTradeAddress);
|
|
||||||
|
|
||||||
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to
|
|
||||||
// wait for 1 confirmation)
|
|
||||||
// In case of double spend we will detect later in the trade process and use a ban score to penalize bad behaviour (not impl. yet)
|
|
||||||
sendRequest = SendRequest.forTx(tradingFeeTx);
|
|
||||||
sendRequest.shuffleOutputs = false;
|
|
||||||
sendRequest.aesKey = aesKey;
|
|
||||||
if (useSavingsWallet) {
|
|
||||||
sendRequest.coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE),
|
|
||||||
preferences.getIgnoreDustThreshold());
|
|
||||||
} else {
|
|
||||||
sendRequest.coinSelector = new BtcCoinSelector(fundingAddress, preferences.getIgnoreDustThreshold());
|
|
||||||
}
|
|
||||||
// We use a fixed fee
|
|
||||||
|
|
||||||
sendRequest.fee = txFee;
|
|
||||||
sendRequest.feePerKb = Coin.ZERO;
|
|
||||||
sendRequest.ensureMinRequiredFee = false;
|
|
||||||
|
|
||||||
// Change is optional in case of overpay or use of funds from savings wallet
|
|
||||||
sendRequest.changeAddress = changeAddress;
|
|
||||||
|
|
||||||
checkNotNull(wallet, "Wallet must not be null");
|
|
||||||
wallet.completeTx(sendRequest);
|
|
||||||
if (removeDust(tradingFeeTx)) {
|
|
||||||
wallet.signTransaction(sendRequest);
|
|
||||||
}
|
|
||||||
WalletService.printTx("tradingFeeTx", tradingFeeTx);
|
|
||||||
|
|
||||||
if (doBroadcast && callback != null) {
|
|
||||||
broadcastTx(tradingFeeTx, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tradingFeeTx;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
if (wallet != null && sendRequest != null && sendRequest.coinSelector != null) {
|
|
||||||
log.error("Balance = {}; CoinSelector = {}", wallet.getBalance(sendRequest.coinSelector), sendRequest.coinSelector);
|
|
||||||
}
|
|
||||||
log.error("createBtcTradingFeeTx failed: tradingFeeTx={}, txOutputs={}", tradingFeeTx.toString(),
|
|
||||||
tradingFeeTx.getOutputs());
|
|
||||||
throw t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx,
|
|
||||||
Address fundingAddress,
|
|
||||||
Address reservedForTradeAddress,
|
|
||||||
Address changeAddress,
|
|
||||||
Coin reservedFundsForOffer,
|
|
||||||
boolean useSavingsWallet,
|
|
||||||
Coin txFee)
|
|
||||||
throws TransactionVerificationException, WalletException, InsufficientMoneyException, AddressFormatException {
|
|
||||||
try {
|
|
||||||
// preparedBsqTx has following structure:
|
|
||||||
// inputs [1-n] BSQ inputs
|
|
||||||
// outputs [0-1] BSQ change output
|
|
||||||
// mining fee: burned BSQ fee
|
|
||||||
|
|
||||||
// We add BTC mining fee. Result tx looks like:
|
|
||||||
// inputs [1-n] BSQ inputs
|
|
||||||
// inputs [1-n] BTC inputs
|
|
||||||
// outputs [0-1] BSQ change output
|
|
||||||
// outputs [1] BTC reservedForTrade output
|
|
||||||
// outputs [0-1] BTC change output
|
|
||||||
// mining fee: BTC mining fee + burned BSQ fee
|
|
||||||
|
|
||||||
// In case all BSQ were burnt as fees we have no receiver output and it might be that there are no change outputs
|
|
||||||
// We need to guarantee that min. 1 valid output is added (OP_RETURN does not count). So we use a higher input
|
|
||||||
// for BTC to force an additional change output.
|
|
||||||
|
|
||||||
final int preparedBsqTxInputsSize = preparedBsqTx.getInputs().size();
|
|
||||||
final boolean hasBsqOutputs = !preparedBsqTx.getOutputs().isEmpty();
|
|
||||||
|
|
||||||
// If there are no BSQ change outputs an output larger than the burnt BSQ amount has to be added as the first
|
|
||||||
// output to make sure the reserved funds are in output 1, deposit tx input creation depends on the reserve
|
|
||||||
// being output 1. The amount has to be larger than the BSQ input to make sure the inputs get burnt.
|
|
||||||
// The BTC changeAddress is used, so it might get used for both output 0 and output 2.
|
|
||||||
if (!hasBsqOutputs) {
|
|
||||||
var bsqInputValue = preparedBsqTx.getInputs().stream()
|
|
||||||
.map(TransactionInput::getValue)
|
|
||||||
.reduce(Coin.valueOf(0), Coin::add);
|
|
||||||
|
|
||||||
preparedBsqTx.addOutput(bsqInputValue.add(Coin.valueOf(1)), changeAddress);
|
|
||||||
}
|
|
||||||
// the reserved amount we need for the trade we send to our trade reservedForTradeAddress
|
|
||||||
preparedBsqTx.addOutput(reservedFundsForOffer, reservedForTradeAddress);
|
|
||||||
|
|
||||||
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to
|
|
||||||
// wait for 1 confirmation)
|
|
||||||
// In case of double spend we will detect later in the trade process and use a ban score to penalize bad behaviour (not impl. yet)
|
|
||||||
|
|
||||||
SendRequest sendRequest = SendRequest.forTx(preparedBsqTx);
|
|
||||||
sendRequest.shuffleOutputs = false;
|
|
||||||
sendRequest.aesKey = aesKey;
|
|
||||||
if (useSavingsWallet) {
|
|
||||||
sendRequest.coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE),
|
|
||||||
preferences.getIgnoreDustThreshold());
|
|
||||||
} else {
|
|
||||||
sendRequest.coinSelector = new BtcCoinSelector(fundingAddress, preferences.getIgnoreDustThreshold());
|
|
||||||
}
|
|
||||||
// We use a fixed fee
|
|
||||||
sendRequest.fee = txFee;
|
|
||||||
sendRequest.feePerKb = Coin.ZERO;
|
|
||||||
sendRequest.ensureMinRequiredFee = false;
|
|
||||||
|
|
||||||
sendRequest.signInputs = false;
|
|
||||||
|
|
||||||
// Change is optional in case of overpay or use of funds from savings wallet
|
|
||||||
sendRequest.changeAddress = changeAddress;
|
|
||||||
|
|
||||||
checkNotNull(wallet, "Wallet must not be null");
|
|
||||||
wallet.completeTx(sendRequest);
|
|
||||||
Transaction resultTx = sendRequest.tx;
|
|
||||||
removeDust(resultTx);
|
|
||||||
|
|
||||||
// Sign all BTC inputs
|
|
||||||
for (int i = preparedBsqTxInputsSize; i < resultTx.getInputs().size(); i++) {
|
|
||||||
TransactionInput txIn = resultTx.getInputs().get(i);
|
|
||||||
checkArgument(txIn.getConnectedOutput() != null &&
|
|
||||||
txIn.getConnectedOutput().isMine(wallet),
|
|
||||||
"txIn.getConnectedOutput() is not in our wallet. That must not happen.");
|
|
||||||
WalletService.signTransactionInput(wallet, aesKey, resultTx, txIn, i);
|
|
||||||
WalletService.checkScriptSig(resultTx, txIn, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
WalletService.checkWalletConsistency(wallet);
|
|
||||||
WalletService.verifyTransaction(resultTx);
|
|
||||||
|
|
||||||
WalletService.printTx(Res.getBaseCurrencyCode() + " wallet: Signed tx", resultTx);
|
|
||||||
return resultTx;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
log.error("completeBsqTradingFeeTx: preparedBsqTx={}", preparedBsqTx.toString());
|
|
||||||
throw t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Deposit tx
|
// Deposit tx
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue