remove DAO
Co-authored-by: premek <1145361+premek@users.noreply.github.com>
This commit is contained in:
parent
f9f2cd07c3
commit
cefba8e4b5
1
.gitignore
vendored
1
.gitignore
vendored
@ -34,4 +34,3 @@ deploy
|
||||
/monitor/monitor-tor/*
|
||||
.java-version
|
||||
.localnet
|
||||
/apitest/src/main/resources/dao-setup*
|
||||
|
6
Makefile
6
Makefile
@ -39,7 +39,6 @@ seednode:
|
||||
--useDevPrivilegeKeys=true \
|
||||
--nodePort=2002 \
|
||||
--appName=haveno-XMR_STAGENET_Seed_2002 \
|
||||
--daoActivated=false
|
||||
|
||||
arbitrator-desktop:
|
||||
# Arbitrator and mediator need to be registerd in the UI after launching it.
|
||||
@ -49,7 +48,6 @@ arbitrator-desktop:
|
||||
--useDevPrivilegeKeys=true \
|
||||
--nodePort=4444 \
|
||||
--appName=haveno-XMR_STAGENET_arbitrator \
|
||||
--daoActivated=false \
|
||||
--apiPassword=apitest \
|
||||
--apiPort=9998
|
||||
|
||||
@ -60,7 +58,6 @@ alice-desktop:
|
||||
--useDevPrivilegeKeys=true \
|
||||
--nodePort=5555 \
|
||||
--appName=haveno-XMR_STAGENET_Alice \
|
||||
--daoActivated=false \
|
||||
--apiPassword=apitest \
|
||||
--apiPort=9999
|
||||
|
||||
@ -71,7 +68,6 @@ alice-daemon:
|
||||
--useDevPrivilegeKeys=true \
|
||||
--nodePort=5555 \
|
||||
--appName=haveno-XMR_STAGENET_Alice \
|
||||
--daoActivated=false \
|
||||
--apiPassword=apitest \
|
||||
--apiPort=9999
|
||||
|
||||
@ -82,7 +78,6 @@ bob-desktop:
|
||||
--useDevPrivilegeKeys=true \
|
||||
--nodePort=6666 \
|
||||
--appName=haveno-XMR_STAGENET_Bob \
|
||||
--daoActivated=false \
|
||||
--apiPassword=apitest \
|
||||
--apiPort=10000
|
||||
|
||||
@ -93,7 +88,6 @@ bob-daemon:
|
||||
--useDevPrivilegeKeys=true \
|
||||
--nodePort=6666 \
|
||||
--appName=haveno-XMR_STAGENET_Bob \
|
||||
--daoActivated=false \
|
||||
--apiPassword=apitest \
|
||||
--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
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
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
|
||||
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
|
||||
```
|
||||
@ -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,
|
||||
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
|
||||
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:
|
||||
```
|
||||
@ -201,17 +193,6 @@ $ ./bisq-cli --password=xyz lockwallet
|
||||
|
||||
### 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:
|
||||
```
|
||||
$ ./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>
|
||||
```
|
||||
|
||||
#### Receiving BSQ
|
||||
To receive BSQ from an external wallet, find an unused BSQ address:
|
||||
```
|
||||
$ ./bisq-cli --password=xyz --port=9998 getunusedbsqaddress
|
||||
```
|
||||
### Sending BTC to External Wallets
|
||||
|
||||
Give the public address to the sender. After the BSQ is sent, you can check block explorers for the status of
|
||||
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
|
||||
```
|
||||
Below are commands for sending BTC to external wallets.
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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.
|
||||
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:
|
||||
```
|
||||
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||
@ -351,7 +308,7 @@ $ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||
--amount=0.125 \
|
||||
--fixed-price=30800 \
|
||||
--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,
|
||||
@ -364,7 +321,7 @@ $ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||
--amount=0.125 \
|
||||
--market-price-margin=0.5 \
|
||||
--security-deposit=15.0 \
|
||||
--fee-currency=BSQ
|
||||
--fee-currency=BTC
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### 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
|
||||
|
||||
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"
|
||||
fi
|
||||
CMD+=" --security-deposit=50.0"
|
||||
CMD+=" --fee-currency=BSQ"
|
||||
printdate "ALICE CLI: $CMD"
|
||||
OFFER_ID=$(createoffer "$CMD")
|
||||
exitoncommandalert $?
|
||||
|
@ -154,11 +154,6 @@
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "test getunusedbsqaddress" {
|
||||
run ./bisq-cli --password=xyz getunusedbsqaddress
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "test getaddressbalance missing address argument" {
|
||||
run ./bisq-cli --password=xyz getaddressbalance
|
||||
[ "$status" -eq 1 ]
|
||||
|
@ -226,14 +226,14 @@ checkseednoderunning() {
|
||||
|
||||
checkarbnoderunning() {
|
||||
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."
|
||||
else
|
||||
printdate "Error: arbitration node is not running on host, exiting."
|
||||
apitestusage
|
||||
fi
|
||||
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."
|
||||
else
|
||||
printdate "Error: arbitration node is not running on host, exiting."
|
||||
@ -247,14 +247,14 @@ checkarbnoderunning() {
|
||||
|
||||
checkalicenoderunning() {
|
||||
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."
|
||||
else
|
||||
printdate "Error: Alice's node is not running on host, exiting."
|
||||
apitestusage
|
||||
fi
|
||||
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."
|
||||
else
|
||||
printdate "Error: Alice's node is not running on host, exiting."
|
||||
@ -268,14 +268,14 @@ checkalicenoderunning() {
|
||||
|
||||
checkbobnoderunning() {
|
||||
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."
|
||||
else
|
||||
printdate "Error: Bob's node is not running on host, exiting."
|
||||
apitestusage
|
||||
fi
|
||||
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."
|
||||
else
|
||||
printdate "Error: Bob's node is not running on host, exiting."
|
||||
|
@ -193,7 +193,7 @@ gencreateoffercommand() {
|
||||
CMD+=" --market-price-margin=$MKT_PRICE_MARGIN"
|
||||
fi
|
||||
CMD+=" --security-deposit=15.0"
|
||||
CMD+=" --fee-currency=BSQ"
|
||||
CMD+=" --fee-currency=BTC"
|
||||
echo "$CMD"
|
||||
}
|
||||
|
||||
@ -477,7 +477,7 @@ executetrade() {
|
||||
printdate "First offer found: $OFFER_ID"
|
||||
|
||||
# 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"
|
||||
TRADE=$($CMD)
|
||||
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 run the regtest/dao environment for release testing:
|
||||
* It can be used to run the regtest environment for release testing:
|
||||
* bisq-test --shutdownAfterTests=false
|
||||
*
|
||||
* 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 {
|
||||
installDaoSetupDirectories();
|
||||
|
||||
// Start each background process from an executor, then add a shutdown hook.
|
||||
CountDownLatch countdownLatch = new CountDownLatch(config.supportingApps.size());
|
||||
@ -161,7 +160,6 @@ public class Scaffold {
|
||||
try {
|
||||
log.info("Shutting down executor service ...");
|
||||
executor.shutdownNow();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
executor.awaitTermination(config.supportingApps.size() * 2000L, MILLISECONDS);
|
||||
|
||||
SetupTask[] orderedTasks = new SetupTask[]{
|
||||
@ -208,84 +206,6 @@ public class Scaffold {
|
||||
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() {
|
||||
// gradle is not working for this
|
||||
try {
|
||||
|
@ -55,7 +55,6 @@ import static joptsimple.internal.Strings.EMPTY;
|
||||
public class ApiTestConfig {
|
||||
|
||||
// Global constants
|
||||
public static final String BSQ = "BSQ";
|
||||
public static final String BTC = "BTC";
|
||||
public static final String XMR = "XMR";
|
||||
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.
|
||||
@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 {
|
||||
|
||||
@ -40,7 +39,7 @@ public enum BisqAppConfig {
|
||||
5120,
|
||||
-1,
|
||||
49996),
|
||||
arbdaemon("bisq-XMR_STAGENET_Arb_dao",
|
||||
arbdaemon("bisq-XMR_STAGENET_Arb",
|
||||
"bisq-daemon",
|
||||
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||
BisqDaemonMain.class.getName(),
|
||||
@ -48,7 +47,7 @@ public enum BisqAppConfig {
|
||||
5121,
|
||||
9997,
|
||||
49997),
|
||||
arbdesktop("bisq-XMR_STAGENET_Arb_dao",
|
||||
arbdesktop("bisq-XMR_STAGENET_Arb",
|
||||
"bisq-desktop",
|
||||
"-XX:MaxRAM=3g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||
BisqAppMain.class.getName(),
|
||||
@ -56,7 +55,7 @@ public enum BisqAppConfig {
|
||||
5121,
|
||||
-1,
|
||||
49997),
|
||||
alicedaemon("bisq-XMR_STAGENET_Alice_dao",
|
||||
alicedaemon("bisq-XMR_STAGENET_Alice",
|
||||
"bisq-daemon",
|
||||
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||
BisqDaemonMain.class.getName(),
|
||||
@ -64,7 +63,7 @@ public enum BisqAppConfig {
|
||||
5122,
|
||||
9998,
|
||||
49998),
|
||||
alicedesktop("bisq-XMR_STAGENET_Alice_dao",
|
||||
alicedesktop("bisq-XMR_STAGENET_Alice",
|
||||
"bisq-desktop",
|
||||
"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||
BisqAppMain.class.getName(),
|
||||
@ -72,7 +71,7 @@ public enum BisqAppConfig {
|
||||
5122,
|
||||
-1,
|
||||
49998),
|
||||
bobdaemon("bisq-XMR_STAGENET_Bob_dao",
|
||||
bobdaemon("bisq-XMR_STAGENET_Bob",
|
||||
"bisq-daemon",
|
||||
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||
BisqDaemonMain.class.getName(),
|
||||
@ -80,7 +79,7 @@ public enum BisqAppConfig {
|
||||
5123,
|
||||
9999,
|
||||
49999),
|
||||
bobdesktop("bisq-XMR_STAGENET_Bob_dao",
|
||||
bobdesktop("bisq-XMR_STAGENET_Bob",
|
||||
"bisq-desktop",
|
||||
"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||
BisqAppMain.class.getName(),
|
||||
|
@ -48,8 +48,6 @@ public class BisqProcess extends AbstractLinuxProcess implements LinuxProcess {
|
||||
private final String genesisTxId;
|
||||
private final int genesisBlockHeight;
|
||||
private final String seedNodes;
|
||||
private final boolean daoActivated;
|
||||
private final boolean fullDaoNode;
|
||||
private final boolean useLocalhostForP2P;
|
||||
public final boolean useDevPrivilegeKeys;
|
||||
private final String findBisqPidScript;
|
||||
@ -62,8 +60,6 @@ public class BisqProcess extends AbstractLinuxProcess implements LinuxProcess {
|
||||
this.genesisTxId = "30af0050040befd8af25068cc697e418e09c2d8ebd8d411d2240591b9ec203cf";
|
||||
this.genesisBlockHeight = 111;
|
||||
this.seedNodes = "localhost:2002";
|
||||
this.daoActivated = true;
|
||||
this.fullDaoNode = true;
|
||||
this.useLocalhostForP2P = true;
|
||||
this.useDevPrivilegeKeys = true;
|
||||
this.findBisqPidScript = (config.isRunningTest ? "." : "./apitest")
|
||||
@ -224,8 +220,6 @@ public class BisqProcess extends AbstractLinuxProcess implements LinuxProcess {
|
||||
add("--rpcUser=" + config.bitcoinRpcUser);
|
||||
add("--rpcPassword=" + config.bitcoinRpcPassword);
|
||||
add("--rpcPort=" + config.bitcoinRpcPort);
|
||||
add("--daoActivated=" + daoActivated);
|
||||
add("--fullDaoNode=" + fullDaoNode);
|
||||
add("--seedNodes=" + seedNodes);
|
||||
add("--baseCurrencyNetwork=" + baseCurrencyNetwork);
|
||||
add("--useDevPrivilegeKeys=" + useDevPrivilegeKeys);
|
||||
|
@ -59,7 +59,7 @@ import bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig;
|
||||
* <p>
|
||||
* 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
|
||||
* 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.
|
||||
* <p>
|
||||
* 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
|
||||
* (each test case runs on a refreshed DAO/regtest environment setup).
|
||||
* <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>
|
||||
* Initial Bob balances & accounts: 10.0 BTC, 1500000.00 BSQ, USD PerfectMoney dummy
|
||||
* Initial Bob balances & accounts: 10.0 BTC, USD PerfectMoney dummy
|
||||
*/
|
||||
@Slf4j
|
||||
public class ApiTestCase {
|
||||
|
@ -32,7 +32,6 @@ import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
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.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
@ -52,9 +51,6 @@ public abstract class AbstractOfferTest extends MethodTest {
|
||||
@Setter
|
||||
protected static boolean isLongRunningTest;
|
||||
|
||||
protected static PaymentAccount alicesBsqAcct;
|
||||
protected static PaymentAccount bobsBsqAcct;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
startSupportingApps(true,
|
||||
@ -66,18 +62,6 @@ public abstract class AbstractOfferTest extends MethodTest {
|
||||
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) {
|
||||
int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
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.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
@ -53,8 +52,7 @@ public class CancelOfferTest extends AbstractOfferTest {
|
||||
10000000L,
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
paymentAccountId,
|
||||
BSQ);
|
||||
paymentAccountId);
|
||||
};
|
||||
|
||||
@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.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
@ -43,8 +42,6 @@ import static protobuf.OfferPayload.Direction.SELL;
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
|
||||
private static final String MAKER_FEE_CURRENCY_CODE = BSQ;
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCreateAUDXMRBuyOfferUsingFixedPrice16000() {
|
||||
@ -55,8 +52,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
10_000_000L,
|
||||
"36000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
audAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
audAccount.getId());
|
||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "AUD"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
@ -69,7 +65,6 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
@ -82,7 +77,6 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -95,8 +89,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
10_000_000L,
|
||||
"30000.1234",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
usdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
usdAccount.getId());
|
||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "USD"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
@ -109,7 +102,6 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
@ -122,7 +114,6 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -135,8 +126,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
5_000_000L,
|
||||
"29500.1234",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
eurAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
eurAccount.getId());
|
||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "EUR"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
@ -149,7 +139,6 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("EUR", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
@ -162,6 +151,5 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
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_WARNING_TOLERANCE = 0.0001; // 0.01%
|
||||
|
||||
private static final String MAKER_FEE_CURRENCY_CODE = XMR;
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCreateUSDXMRBuyOffer5PctPriceMargin() {
|
||||
@ -67,8 +65,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
10_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
usdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
usdAccount.getId());
|
||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
@ -80,7 +77,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
@ -92,7 +88,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
}
|
||||
@ -108,8 +103,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
10_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
nzdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
nzdAccount.getId());
|
||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
@ -121,7 +115,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
@ -133,7 +126,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
}
|
||||
@ -149,8 +141,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
5_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
gbpAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
gbpAccount.getId());
|
||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
@ -162,7 +153,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
@ -174,7 +164,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
}
|
||||
@ -190,8 +179,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
5_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
brlAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
brlAccount.getId());
|
||||
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
@ -203,7 +191,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
@ -215,7 +202,6 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ 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.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static java.lang.String.format;
|
||||
@ -54,8 +53,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||
100000000000L,
|
||||
"10000.0000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
usdAccount.getId(),
|
||||
BSQ));
|
||||
usdAccount.getId()));
|
||||
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer", exception.getMessage());
|
||||
}
|
||||
|
||||
@ -71,8 +69,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||
10000000L,
|
||||
"40000.0000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
chfAccount.getId(),
|
||||
BTC));
|
||||
chfAccount.getId()));
|
||||
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
|
||||
assertEquals(expectedError, exception.getMessage());
|
||||
}
|
||||
@ -89,8 +86,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||
10000000L,
|
||||
"63000.0000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
audAccount.getId(),
|
||||
BTC));
|
||||
audAccount.getId()));
|
||||
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
|
||||
assertEquals(expectedError, exception.getMessage());
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import org.slf4j.Logger;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import static bisq.cli.CurrencyFormat.formatBsqAmount;
|
||||
import static bisq.cli.TradeFormat.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
@ -35,16 +34,14 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||
}
|
||||
|
||||
protected final TradeInfo takeAlicesOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode) {
|
||||
return bobClient.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
String paymentAccountId) {
|
||||
return bobClient.takeOffer(offerId, paymentAccountId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected final TradeInfo takeBobsOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode) {
|
||||
return aliceClient.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
String paymentAccountId) {
|
||||
return aliceClient.takeOffer(offerId, paymentAccountId);
|
||||
}
|
||||
|
||||
protected final void verifyExpectedProtocolStatus(TradeInfo trade) {
|
||||
@ -62,40 +59,6 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||
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,
|
||||
TestInfo testInfo,
|
||||
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.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
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.
|
||||
|
||||
// Maker and Taker fees are in BSQ.
|
||||
private static final String TRADE_FEE_CURRENCY_CODE = BSQ;
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
|
||||
@ -71,10 +67,8 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||
12_500_000L, // min-amount = amount
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesUsdAccount.getId(),
|
||||
TRADE_FEE_CURRENCY_CODE);
|
||||
alicesUsdAccount.getId());
|
||||
var offerId = alicesOffer.getId();
|
||||
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
// Wait for Alice's AddToOfferBook task.
|
||||
// Wait times vary; my logs show >= 2 second delay.
|
||||
@ -83,7 +77,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||
assertEquals(1, alicesUsdOffers.size());
|
||||
|
||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
|
||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId());
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
// 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.
|
||||
|
||||
// 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";
|
||||
|
||||
@Test
|
||||
@ -74,10 +71,8 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||
12_500_000L, // min-amount = amount
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesUsdAccount.getId(),
|
||||
TRADE_FEE_CURRENCY_CODE);
|
||||
alicesUsdAccount.getId());
|
||||
var offerId = alicesOffer.getId();
|
||||
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
// Wait for Alice's AddToOfferBook task.
|
||||
// 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());
|
||||
|
||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
|
||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId());
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
// 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;
|
||||
|
||||
import bisq.proto.grpc.BsqBalanceInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -10,7 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@Slf4j
|
||||
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.
|
||||
public static final bisq.core.api.model.BtcBalanceInfo INITIAL_BTC_BALANCES =
|
||||
bisq.core.api.model.BtcBalanceInfo.valueOf(1000000000,
|
||||
@ -19,49 +18,6 @@ public class WalletTestUtil {
|
||||
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,
|
||||
BtcBalanceInfo actual) {
|
||||
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.CancelOfferTest;
|
||||
import bisq.apitest.method.offer.CreateBSQOffersTest;
|
||||
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
||||
import bisq.apitest.method.offer.CreateOfferUsingMarketPriceMarginTest;
|
||||
import bisq.apitest.method.offer.ValidateCreateOfferTest;
|
||||
@ -72,17 +71,4 @@ public class OfferTest extends AbstractOfferTest {
|
||||
test.testCreateGBPXMRSellOfferMinus1Point5PctPriceMargin();
|
||||
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.TakeBuyBSQOfferTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
||||
import bisq.apitest.method.trade.TakeSellBSQOfferTest;
|
||||
import bisq.apitest.method.trade.TakeSellBTCOfferTest;
|
||||
|
||||
|
||||
@ -63,26 +61,4 @@ public class TradeTest extends AbstractTradeTest {
|
||||
test.testAlicesConfirmPaymentReceived(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.wallet.BsqWalletTest;
|
||||
import bisq.apitest.method.wallet.BtcTxFeeRateTest;
|
||||
import bisq.apitest.method.wallet.BtcWalletTest;
|
||||
import bisq.apitest.method.wallet.WalletProtectionTest;
|
||||
@ -70,17 +69,6 @@ public class WalletTest extends MethodTest {
|
||||
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
|
||||
@Order(3)
|
||||
public void testWalletProtection() {
|
||||
|
@ -58,7 +58,7 @@ public class BotClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current BSQ and BTC balance information.
|
||||
* Returns current balance information.
|
||||
* @return BalancesInfo
|
||||
*/
|
||||
public BalancesInfo getBalance() {
|
||||
@ -124,7 +124,6 @@ public class BotClient {
|
||||
* @param minAmountInSatoshis
|
||||
* @param priceMarginAsPercent
|
||||
* @param securityDepositAsPercent
|
||||
* @param feeCurrency
|
||||
* @return OfferInfo
|
||||
*/
|
||||
public OfferInfo createOfferAtMarketBasedPrice(PaymentAccount paymentAccount,
|
||||
@ -133,16 +132,14 @@ public class BotClient {
|
||||
long amountInSatoshis,
|
||||
long minAmountInSatoshis,
|
||||
double priceMarginAsPercent,
|
||||
double securityDepositAsPercent,
|
||||
String feeCurrency) {
|
||||
double securityDepositAsPercent) {
|
||||
return grpcClient.createMarketBasedPricedOffer(direction,
|
||||
currencyCode,
|
||||
amountInSatoshis,
|
||||
minAmountInSatoshis,
|
||||
priceMarginAsPercent,
|
||||
securityDepositAsPercent,
|
||||
paymentAccount.getId(),
|
||||
feeCurrency);
|
||||
paymentAccount.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,7 +151,6 @@ public class BotClient {
|
||||
* @param minAmountInSatoshis
|
||||
* @param fixedOfferPriceAsString
|
||||
* @param securityDepositAsPercent
|
||||
* @param feeCurrency
|
||||
* @return OfferInfo
|
||||
*/
|
||||
public OfferInfo createOfferAtFixedPrice(PaymentAccount paymentAccount,
|
||||
@ -163,20 +159,18 @@ public class BotClient {
|
||||
long amountInSatoshis,
|
||||
long minAmountInSatoshis,
|
||||
String fixedOfferPriceAsString,
|
||||
double securityDepositAsPercent,
|
||||
String feeCurrency) {
|
||||
double securityDepositAsPercent) {
|
||||
return grpcClient.createFixedPricedOffer(direction,
|
||||
currencyCode,
|
||||
amountInSatoshis,
|
||||
minAmountInSatoshis,
|
||||
fixedOfferPriceAsString,
|
||||
securityDepositAsPercent,
|
||||
paymentAccount.getId(),
|
||||
feeCurrency);
|
||||
paymentAccount.getId());
|
||||
}
|
||||
|
||||
public TradeInfo takeOffer(String offerId, PaymentAccount paymentAccount, String feeCurrency) {
|
||||
return grpcClient.takeOffer(offerId, paymentAccount.getId(), feeCurrency);
|
||||
public TradeInfo takeOffer(String offerId, PaymentAccount paymentAccount) {
|
||||
return grpcClient.takeOffer(offerId, paymentAccount.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,8 +95,6 @@ public class RandomOffer {
|
||||
private final boolean useMarketBasedPrice;
|
||||
@Getter
|
||||
private final double priceMargin;
|
||||
@Getter
|
||||
private final String feeCurrency;
|
||||
|
||||
@Getter
|
||||
private String fixedOfferPrice = "0";
|
||||
@ -114,7 +112,6 @@ public class RandomOffer {
|
||||
this.minAmount = nextMinAmount.get();
|
||||
this.useMarketBasedPrice = RANDOM.nextBoolean();
|
||||
this.priceMargin = nextPriceMargin.get();
|
||||
this.feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
||||
}
|
||||
|
||||
public RandomOffer create() throws InvalidRandomOfferException {
|
||||
@ -127,8 +124,7 @@ public class RandomOffer {
|
||||
amount,
|
||||
minAmount,
|
||||
priceMargin,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
feeCurrency);
|
||||
getDefaultBuyerSecurityDepositAsPercent());
|
||||
} else {
|
||||
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
||||
direction,
|
||||
@ -136,8 +132,7 @@ public class RandomOffer {
|
||||
amount,
|
||||
minAmount,
|
||||
fixedOfferPrice,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
feeCurrency);
|
||||
getDefaultBuyerSecurityDepositAsPercent());
|
||||
}
|
||||
this.id = offer.getId();
|
||||
return this;
|
||||
|
@ -102,13 +102,12 @@ public class TakerBotProtocol extends BotProtocol {
|
||||
private final Function<OfferInfo, TradeInfo> takeOffer = (offer) -> {
|
||||
initProtocolStep.accept(TAKE_OFFER);
|
||||
checkIfShutdownCalled("Interrupted before taking offer.");
|
||||
String feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
||||
return botClient.takeOffer(offer.getId(), paymentAccount, feeCurrency);
|
||||
return botClient.takeOffer(offer.getId(), paymentAccount);
|
||||
};
|
||||
|
||||
private void createMakeOfferScript() {
|
||||
String direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
|
||||
String feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
||||
String feeCurrency = "BTC";
|
||||
boolean createMarginPricedOffer = RANDOM.nextBoolean();
|
||||
// If not using an F2F account, don't go over possible 0.01 BTC
|
||||
// limit if account is not signed.
|
||||
|
@ -130,7 +130,7 @@ public class BashScriptGenerator {
|
||||
cliBase,
|
||||
offer.getDirection(),
|
||||
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,
|
||||
offer.getId(),
|
||||
this.getPaymentAccountId());
|
||||
|
@ -27,7 +27,6 @@ public class PrintTool {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
new AssetRegistry().stream()
|
||||
.sorted(Comparator.comparing(o -> o.getName().toLowerCase()))
|
||||
.filter(e -> !e.getTickerSymbol().equals("BSQ")) // BSQ is not out yet...
|
||||
.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...
|
||||
.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.Bitzec
|
||||
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.Cash2
|
||||
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')) {
|
||||
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
|
||||
// to interfere with normal builds. To run JUnit tests in this subproject:
|
||||
// Run a normal build and install dao-setup files first, then run:
|
||||
// 'gradle :apitest:test -DrunApiTests=true'
|
||||
test.enabled = System.getProperty("runApiTests") == "true"
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
exclude 'dao-setup'
|
||||
exclude 'dao-setup.zip'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
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.RegisterDisputeAgentOptionParser;
|
||||
import bisq.cli.opts.RemoveWalletPasswordOptionParser;
|
||||
import bisq.cli.opts.SendBsqOptionParser;
|
||||
import bisq.cli.opts.SendBtcOptionParser;
|
||||
import bisq.cli.opts.SetTxFeeRateOptionParser;
|
||||
import bisq.cli.opts.SetWalletPasswordOptionParser;
|
||||
import bisq.cli.opts.SimpleMethodOptionParser;
|
||||
import bisq.cli.opts.TakeOfferOptionParser;
|
||||
import bisq.cli.opts.UnlockWalletOptionParser;
|
||||
import bisq.cli.opts.VerifyBsqSentToAddressOptionParser;
|
||||
import bisq.cli.opts.WithdrawFundsOptionParser;
|
||||
|
||||
/**
|
||||
@ -168,9 +166,6 @@ public class CliMain {
|
||||
var currencyCode = opts.getCurrencyCode();
|
||||
var balances = client.getBalances(currencyCode);
|
||||
switch (currencyCode.toUpperCase()) {
|
||||
case "BSQ":
|
||||
out.println(formatBsqBalanceInfoTbl(balances.getBsq()));
|
||||
break;
|
||||
case "BTC":
|
||||
out.println(formatBtcBalanceInfoTbl(balances.getBtc()));
|
||||
break;
|
||||
@ -215,36 +210,6 @@ public class CliMain {
|
||||
out.println(formatAddressBalanceTbl(fundingAddresses));
|
||||
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: {
|
||||
var opts = new SendBtcOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
@ -268,23 +233,6 @@ public class CliMain {
|
||||
txInfo.getTxId());
|
||||
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: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
@ -339,7 +287,6 @@ public class CliMain {
|
||||
var fixedPrice = opts.getFixedPrice();
|
||||
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
|
||||
var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit());
|
||||
var makerFeeCurrencyCode = opts.getMakerFeeCurrencyCode();
|
||||
var offer = client.createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
@ -348,8 +295,7 @@ public class CliMain {
|
||||
fixedPrice,
|
||||
marketPriceMargin.doubleValue(),
|
||||
securityDeposit,
|
||||
paymentAcctId,
|
||||
makerFeeCurrencyCode);
|
||||
paymentAcctId);
|
||||
out.println(formatOfferTable(singletonList(offer), currencyCode));
|
||||
return;
|
||||
}
|
||||
@ -426,8 +372,7 @@ public class CliMain {
|
||||
}
|
||||
var offerId = opts.getOfferId();
|
||||
var paymentAccountId = opts.getPaymentAccountId();
|
||||
var takerFeeCurrencyCode = opts.getTakerFeeCurrencyCode();
|
||||
var trade = client.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
var trade = client.takeOffer(offerId, paymentAccountId);
|
||||
out.printf("trade %s successfully taken%n", trade.getTradeId());
|
||||
return;
|
||||
}
|
||||
@ -721,7 +666,7 @@ public class CliMain {
|
||||
stream.format(rowFormat, "------", "------", "------------");
|
||||
stream.format(rowFormat, getversion.name(), "", "Get server version");
|
||||
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.format(rowFormat, getaddressbalance.name(), "--address=<btc-address>", "Get server wallet address balance");
|
||||
stream.println();
|
||||
@ -729,17 +674,10 @@ public class CliMain {
|
||||
stream.println();
|
||||
stream.format(rowFormat, getfundingaddresses.name(), "", "Get BTC funding addresses");
|
||||
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, "", "[--tx-fee-rate=<sats/byte>]", "");
|
||||
stream.format(rowFormat, "", "[--memo=<\"memo\">]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, verifybsqsenttoaddress.name(), "--address=<bsq-address> --amount=<bsq-amount>",
|
||||
"Verify amount was sent to BSQ wallet address");
|
||||
stream.println();
|
||||
stream.format(rowFormat, gettxfeerate.name(), "", "Get current tx fee rate in sats/byte");
|
||||
stream.println();
|
||||
@ -756,7 +694,7 @@ public class CliMain {
|
||||
stream.format(rowFormat, "", "[--min-amount=<min-btc-amount>] \\", "");
|
||||
stream.format(rowFormat, "", "--fixed-price=<price> | --market-price=margin=<percent> \\", "");
|
||||
stream.format(rowFormat, "", "--security-deposit=<percent> \\", "");
|
||||
stream.format(rowFormat, "", "[--fee-currency=<bsq|btc>]", "");
|
||||
stream.format(rowFormat, "", "[--fee-currency=<btc>]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, canceloffer.name(), "--offer-id=<offer-id>", "Cancel offer with id");
|
||||
stream.println();
|
||||
@ -772,7 +710,7 @@ public class CliMain {
|
||||
stream.println();
|
||||
stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> \\", "Take offer with 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.format(rowFormat, gettrade.name(), "--trade-id=<trade-id> \\", "Get trade summary or full contract");
|
||||
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.println();
|
||||
stream.format(rowFormat, createcryptopaymentacct.name(), "--account-name=<name> \\", "Create a new cryptocurrency payment account");
|
||||
stream.format(rowFormat, "", "--currency-code=<bsq> \\", "");
|
||||
stream.format(rowFormat, "", "--address=<bsq-address>", "");
|
||||
stream.format(rowFormat, "", "--currency-code=<btc> \\", "");
|
||||
stream.format(rowFormat, "", "--address=<address>", "");
|
||||
stream.format(rowFormat, "", "--trade-instant=<true|false>", "");
|
||||
stream.println();
|
||||
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_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_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_DEPOSIT_CONFIRMED = "Deposit Confirmed";
|
||||
static final String COL_HEADER_TRADE_DEPOSIT_PUBLISHED = "Deposit Published";
|
||||
|
@ -28,7 +28,6 @@ class CryptoCurrencyUtil {
|
||||
|
||||
public static List<String> getSupportedCryptoCurrencies() {
|
||||
final List<String> result = new ArrayList<>();
|
||||
result.add("BSQ");
|
||||
result.sort(String::compareTo);
|
||||
return result;
|
||||
}
|
||||
|
@ -46,10 +46,6 @@ public class CurrencyFormat {
|
||||
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
|
||||
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");
|
||||
|
||||
// TODO: (woodser): replace formatSatoshis(), formatBsq() with formatXmr()
|
||||
@ -59,22 +55,10 @@ public class CurrencyFormat {
|
||||
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) {
|
||||
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) {
|
||||
if (txFeeRateInfo.getUseCustomTxFeeRate())
|
||||
|
@ -19,7 +19,6 @@ package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.AddressBalanceInfo;
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.BsqBalanceInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.CancelOfferRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
@ -42,7 +41,6 @@ import bisq.proto.grpc.GetPaymentMethodsRequest;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
import bisq.proto.grpc.GetTransactionRequest;
|
||||
import bisq.proto.grpc.GetTxFeeRateRequest;
|
||||
import bisq.proto.grpc.GetUnusedBsqAddressRequest;
|
||||
import bisq.proto.grpc.GetVersionRequest;
|
||||
import bisq.proto.grpc.KeepFundsRequest;
|
||||
import bisq.proto.grpc.LockWalletRequest;
|
||||
@ -50,7 +48,6 @@ import bisq.proto.grpc.MarketPriceRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||
import bisq.proto.grpc.SendBsqRequest;
|
||||
import bisq.proto.grpc.SendBtcRequest;
|
||||
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||
@ -62,7 +59,6 @@ import bisq.proto.grpc.TxFeeRateInfo;
|
||||
import bisq.proto.grpc.TxInfo;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.VerifyBsqSentToAddressRequest;
|
||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||
import bisq.proto.grpc.XmrBalanceInfo;
|
||||
|
||||
@ -100,10 +96,6 @@ public final class GrpcClient {
|
||||
return getBalances("");
|
||||
}
|
||||
|
||||
public BsqBalanceInfo getBsqBalances() {
|
||||
return getBalances("BSQ").getBsq();
|
||||
}
|
||||
|
||||
public BtcBalanceInfo getBtcBalances() {
|
||||
return getBalances("BTC").getBtc();
|
||||
}
|
||||
@ -137,11 +129,6 @@ public final class GrpcClient {
|
||||
return grpcStubs.walletsService.getFundingAddresses(request).getAddressBalanceInfoList();
|
||||
}
|
||||
|
||||
public String getUnusedBsqAddress() {
|
||||
var request = GetUnusedBsqAddressRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.getUnusedBsqAddress(request).getAddress();
|
||||
}
|
||||
|
||||
public String getUnusedBtcAddress() {
|
||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||
var addressBalances = grpcStubs.walletsService.getFundingAddresses(request)
|
||||
@ -154,15 +141,6 @@ public final class GrpcClient {
|
||||
.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) {
|
||||
var request = SendBtcRequest.newBuilder()
|
||||
.setAddress(address)
|
||||
@ -173,14 +151,6 @@ public final class GrpcClient {
|
||||
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() {
|
||||
var request = GetTxFeeRateRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.getTxFeeRate(request).getTxFeeRateInfo();
|
||||
@ -211,8 +181,7 @@ public final class GrpcClient {
|
||||
long minAmount,
|
||||
String fixedPrice,
|
||||
double securityDeposit,
|
||||
String paymentAcctId,
|
||||
String makerFeeCurrencyCode) {
|
||||
String paymentAcctId) {
|
||||
return createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
@ -221,8 +190,7 @@ public final class GrpcClient {
|
||||
fixedPrice,
|
||||
0.00,
|
||||
securityDeposit,
|
||||
paymentAcctId,
|
||||
makerFeeCurrencyCode);
|
||||
paymentAcctId);
|
||||
}
|
||||
|
||||
public OfferInfo createMarketBasedPricedOffer(String direction,
|
||||
@ -231,8 +199,7 @@ public final class GrpcClient {
|
||||
long minAmount,
|
||||
double marketPriceMargin,
|
||||
double securityDeposit,
|
||||
String paymentAcctId,
|
||||
String makerFeeCurrencyCode) {
|
||||
String paymentAcctId) {
|
||||
return createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
@ -241,8 +208,7 @@ public final class GrpcClient {
|
||||
"0",
|
||||
marketPriceMargin,
|
||||
securityDeposit,
|
||||
paymentAcctId,
|
||||
makerFeeCurrencyCode);
|
||||
paymentAcctId);
|
||||
}
|
||||
|
||||
public OfferInfo createOffer(String direction,
|
||||
@ -253,8 +219,7 @@ public final class GrpcClient {
|
||||
String fixedPrice,
|
||||
double marketPriceMargin,
|
||||
double securityDeposit,
|
||||
String paymentAcctId,
|
||||
String makerFeeCurrencyCode) {
|
||||
String paymentAcctId) {
|
||||
var request = CreateOfferRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
@ -265,7 +230,6 @@ public final class GrpcClient {
|
||||
.setMarketPriceMargin(marketPriceMargin)
|
||||
.setBuyerSecurityDeposit(securityDeposit)
|
||||
.setPaymentAccountId(paymentAcctId)
|
||||
.setMakerFeeCurrencyCode(makerFeeCurrencyCode)
|
||||
.build();
|
||||
return grpcStubs.offersService.createOffer(request).getOffer();
|
||||
}
|
||||
@ -321,13 +285,6 @@ public final class GrpcClient {
|
||||
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) {
|
||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
||||
return getMyCryptoCurrencyOffers(direction, currencyCode);
|
||||
@ -358,13 +315,6 @@ public final class GrpcClient {
|
||||
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) {
|
||||
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
||||
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
||||
@ -376,17 +326,16 @@ public final class GrpcClient {
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId) {
|
||||
var request = TakeOfferRequest.newBuilder()
|
||||
.setOfferId(offerId)
|
||||
.setPaymentAccountId(paymentAccountId)
|
||||
.setTakerFeeCurrencyCode(takerFeeCurrencyCode)
|
||||
.build();
|
||||
return grpcStubs.tradesService.takeOffer(request);
|
||||
}
|
||||
|
||||
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||
var reply = getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
public TradeInfo takeOffer(String offerId, String paymentAccountId) {
|
||||
var reply = getTakeOfferReply(offerId, paymentAccountId);
|
||||
if (reply.hasTrade())
|
||||
return reply.getTrade();
|
||||
else
|
||||
|
@ -41,15 +41,12 @@ public enum Method {
|
||||
gettrade,
|
||||
gettransaction,
|
||||
gettxfeerate,
|
||||
getunusedbsqaddress,
|
||||
getversion,
|
||||
keepfunds,
|
||||
lockwallet,
|
||||
registerdisputeagent,
|
||||
removewalletpassword,
|
||||
sendbsq,
|
||||
sendbtc,
|
||||
verifybsqsenttoaddress,
|
||||
settxfeerate,
|
||||
setwalletpassword,
|
||||
takeoffer,
|
||||
|
@ -19,7 +19,6 @@ package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.AddressBalanceInfo;
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.BsqBalanceInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.XmrBalanceInfo;
|
||||
@ -75,31 +74,7 @@ public class TableFormat {
|
||||
|
||||
public static String formatBalancesTbls(BalancesInfo balancesInfo) {
|
||||
return "XMR" + "\n"
|
||||
+ formatBtcBalanceInfoTbl(balancesInfo.getBtc()) + "\n"
|
||||
+ "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()));
|
||||
+ formatBtcBalanceInfoTbl(balancesInfo.getBtc());
|
||||
}
|
||||
|
||||
public static String formatBtcBalanceInfoTbl(BtcBalanceInfo btcBalanceInfo) {
|
||||
|
@ -61,9 +61,6 @@ public class TradeFormat {
|
||||
"%" + (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
|
||||
+ 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_PAYOUT_PUBLISHED + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_WITHDRAWN + COL_HEADER_DELIMITER
|
||||
+ bsqBuyerAddressHeader.get()
|
||||
+ "%n";
|
||||
|
||||
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_RECEIVED.length() - 1) + "s" // left
|
||||
+ " %-" + COL_HEADER_TRADE_PAYOUT_PUBLISHED.length() + "s" // lt justify
|
||||
+ " %-" + (COL_HEADER_TRADE_WITHDRAWN.length() + 2) + "s"
|
||||
+ bsqBuyerAddressHeaderSpec.get();
|
||||
+ " %-" + (COL_HEADER_TRADE_WITHDRAWN.length() + 2) + "s";
|
||||
|
||||
return headerLine + formatTradeData(colDataFormat, tradeInfo, isTaker, showBsqBuyerAddress);
|
||||
return headerLine + formatTradeData(colDataFormat, tradeInfo, isTaker);
|
||||
}
|
||||
|
||||
private static String formatTradeData(String format,
|
||||
TradeInfo tradeInfo,
|
||||
boolean isTaker,
|
||||
boolean showBsqBuyerAddress) {
|
||||
boolean isTaker) {
|
||||
return String.format(format,
|
||||
tradeInfo.getShortId(),
|
||||
tradeInfo.getRole(),
|
||||
@ -131,8 +125,7 @@ public class TradeFormat {
|
||||
tradeInfo.getIsFiatSent() ? YES : NO,
|
||||
tradeInfo.getIsFiatReceived() ? YES : NO,
|
||||
tradeInfo.getIsPayoutPublished() ? YES : NO,
|
||||
tradeInfo.getIsWithdrawn() ? YES : NO,
|
||||
bsqReceiveAddress.apply(tradeInfo, showBsqBuyerAddress));
|
||||
tradeInfo.getIsWithdrawn() ? YES : NO);
|
||||
}
|
||||
|
||||
private static final Function<TradeInfo, String> priceHeader = (t) ->
|
||||
@ -181,31 +174,4 @@ public class TradeFormat {
|
||||
? formatOfferVolume(t.getOffer().getVolume())
|
||||
: 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")
|
||||
.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();
|
||||
|
||||
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "bsq address")
|
||||
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "address")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<Boolean> tradeInstantOpt = parser.accepts(OPT_TRADE_INSTANT, "create trade instant account")
|
||||
@ -57,13 +57,6 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
|
||||
|
||||
if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty())
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -55,10 +55,6 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||
final OptionSpec<String> securityDepositOpt = parser.accepts(OPT_SECURITY_DEPOSIT, "maker security deposit (%)")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<String> makerFeeCurrencyCodeOpt = parser.accepts(OPT_FEE_CURRENCY, "maker fee currency code (bsq|btc)")
|
||||
.withOptionalArg()
|
||||
.defaultsTo("btc");
|
||||
|
||||
public CreateOfferOptionParser(String[] args) {
|
||||
super(args);
|
||||
}
|
||||
@ -137,8 +133,4 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||
public String getSecurityDeposit() {
|
||||
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 {
|
||||
|
||||
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()
|
||||
.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")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<String> takerFeeCurrencyCodeOpt = parser.accepts(OPT_FEE_CURRENCY, "taker fee currency code (bsq|btc)")
|
||||
.withOptionalArg()
|
||||
.defaultsTo("btc");
|
||||
|
||||
public TakeOfferOptionParser(String[] args) {
|
||||
super(args);
|
||||
}
|
||||
@ -63,8 +59,4 @@ public class TakeOfferOptionParser extends AbstractMethodOptionParser implements
|
||||
public String getPaymentAccountId() {
|
||||
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());
|
||||
}
|
||||
|
||||
// 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.
|
||||
// This helps to clean network from very old inactive but still running nodes.
|
||||
@SuppressWarnings("deprecation")
|
||||
private static final Capability MANDATORY_CAPABILITY = Capability.DAO_STATE;
|
||||
private static final Capability MANDATORY_CAPABILITY = Capability.TRADE_STATISTICS_3;
|
||||
|
||||
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 ACCOUNT_AGE_WITNESS, // Not required anymore as no old clients out there not having that support
|
||||
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 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
|
||||
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 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) {
|
||||
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
|
||||
@ -49,23 +48,10 @@ public class DevEnv {
|
||||
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) {
|
||||
log.error(msg);
|
||||
if (devMode)
|
||||
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;
|
||||
private static int p2pMessageVersion;
|
||||
|
||||
public static final String BSQ_TX_VERSION = "1";
|
||||
|
||||
public static int getP2PMessageVersion() {
|
||||
return p2pMessageVersion;
|
||||
}
|
||||
|
@ -28,10 +28,7 @@ import lombok.Getter;
|
||||
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_TESTNET(new XmrTestNet3Params(), "XMR", "TESTNET", "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");
|
||||
XMR_STAGENET(new XmrRegTestParams(), "XMR", "STAGENET", "Monero");
|
||||
|
||||
@Getter
|
||||
private final NetworkParameters parameters;
|
||||
@ -57,18 +54,6 @@ public enum BaseCurrencyNetwork {
|
||||
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() {
|
||||
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 USER_AGENT = "userAgent";
|
||||
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 ALLOW_FAULTY_DELAYED_TXS = "allowFaultyDelayedTxs";
|
||||
public static final String API_PASSWORD = "apiPassword";
|
||||
@ -130,7 +118,6 @@ public class Config {
|
||||
public static final int UNSPECIFIED_PORT = -1;
|
||||
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 boolean DEFAULT_FULL_DAO_NODE = false;
|
||||
static final String DEFAULT_CONFIG_FILE_NAME = "bisq.properties";
|
||||
|
||||
// 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 boolean ignoreLocalBtcNode;
|
||||
public final String bitcoinRegtestHost;
|
||||
public final boolean daoActivated;
|
||||
public final String referralId;
|
||||
public final boolean useDevMode;
|
||||
public final boolean useDevModeHeader;
|
||||
@ -195,18 +181,6 @@ public class Config {
|
||||
public final boolean useAllProvidedNodes;
|
||||
public final String userAgent;
|
||||
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 allowFaultyDelayedTxs;
|
||||
public final String apiPassword;
|
||||
@ -552,78 +526,6 @@ public class Config {
|
||||
.ofType(int.class)
|
||||
.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 =
|
||||
parser.accepts(DUMP_DELAYED_PAYOUT_TXS, "Dump delayed payout transactions to file")
|
||||
.withRequiredArg()
|
||||
@ -767,19 +669,7 @@ public class Config {
|
||||
this.useAllProvidedNodes = options.valueOf(useAllProvidedNodesOpt);
|
||||
this.userAgent = options.valueOf(userAgentOpt);
|
||||
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.allowFaultyDelayedTxs = options.valueOf(allowFaultyDelayedTxsOpt);
|
||||
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
|
||||
* 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
|
||||
* the write operations got triggered way too often specially for the very frequent changes at SequenceNumberMap and
|
||||
* the very large DaoState (at dao blockchain sync that slowed down sync).
|
||||
* the write operations got triggered way too often specially for the very frequent changes at SequenceNumberMap
|
||||
*
|
||||
*
|
||||
* @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 static bisq.common.app.Capability.DAO_FULL_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_2;
|
||||
@ -46,17 +45,12 @@ public class CapabilitiesTest {
|
||||
assertTrue(new Capabilities().hasLess(new Capabilities(SEED_NODE)));
|
||||
assertFalse(new Capabilities().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(
|
||||
TRADE_STATISTICS,
|
||||
TRADE_STATISTICS_2,
|
||||
Capability.ACCOUNT_AGE_WITNESS,
|
||||
Capability.ACK_MSG,
|
||||
Capability.PROPOSAL,
|
||||
Capability.BLIND_VOTE,
|
||||
Capability.DAO_STATE,
|
||||
Capability.BUNDLE_OF_ENVELOPES,
|
||||
Capability.MEDIATION,
|
||||
Capability.SIGNED_ACCOUNT_AGE_WITNESS,
|
||||
@ -68,9 +62,6 @@ public class CapabilitiesTest {
|
||||
TRADE_STATISTICS_2,
|
||||
Capability.ACCOUNT_AGE_WITNESS,
|
||||
Capability.ACK_MSG,
|
||||
Capability.PROPOSAL,
|
||||
Capability.BLIND_VOTE,
|
||||
Capability.DAO_STATE,
|
||||
Capability.BUNDLE_OF_ENVELOPES,
|
||||
Capability.MEDIATION,
|
||||
Capability.SIGNED_ACCOUNT_AGE_WITNESS,
|
||||
|
@ -20,7 +20,6 @@ package bisq.core.api;
|
||||
import bisq.core.api.model.AddressBalanceInfo;
|
||||
import bisq.core.api.model.BalancesInfo;
|
||||
import bisq.core.api.model.TxFeeRateInfo;
|
||||
import bisq.core.btc.wallet.TxBroadcaster;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
@ -148,7 +147,6 @@ public class CoreApi {
|
||||
double buyerSecurityDeposit,
|
||||
long triggerPrice,
|
||||
String paymentAccountId,
|
||||
String makerFeeCurrencyCode,
|
||||
Consumer<Offer> resultHandler) {
|
||||
coreOffersService.createAndPlaceOffer(currencyCode,
|
||||
directionAsString,
|
||||
@ -160,7 +158,6 @@ public class CoreApi {
|
||||
buyerSecurityDeposit,
|
||||
triggerPrice,
|
||||
paymentAccountId,
|
||||
makerFeeCurrencyCode,
|
||||
resultHandler);
|
||||
}
|
||||
|
||||
@ -238,13 +235,11 @@ public class CoreApi {
|
||||
|
||||
public void takeOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode,
|
||||
Consumer<Trade> resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Offer offer = coreOffersService.getOffer(offerId);
|
||||
coreTradesService.takeOffer(offer,
|
||||
paymentAccountId,
|
||||
takerFeeCurrencyCode,
|
||||
resultHandler,
|
||||
errorMessageHandler);
|
||||
}
|
||||
@ -297,17 +292,6 @@ public class CoreApi {
|
||||
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,
|
||||
String amount,
|
||||
String txFeeRate,
|
||||
@ -316,9 +300,6 @@ public class CoreApi {
|
||||
walletsService.sendBtc(address, amount, txFeeRate, memo, callback);
|
||||
}
|
||||
|
||||
public boolean verifyBsqSentToAddress(String address, String amount) {
|
||||
return walletsService.verifyBsqSentToAddress(address, amount);
|
||||
}
|
||||
|
||||
public void getTxFeeRate(ResultHandler resultHandler) {
|
||||
walletsService.getTxFeeRate(resultHandler);
|
||||
|
@ -82,7 +82,6 @@ class CoreDisputeAgentsService {
|
||||
throw new IllegalStateException("p2p service is not bootstrapped yet");
|
||||
|
||||
if (config.baseCurrencyNetwork.isMainnet()
|
||||
|| config.baseCurrencyNetwork.isDaoBetaNet()
|
||||
|| !config.useLocalhostForP2P)
|
||||
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("getfundingaddresses"));
|
||||
// out.println(coreHelpService.getMethodHelp("getfundingaddresses"));
|
||||
// out.println(coreHelpService.getMethodHelp("getunusedbsqaddress"));
|
||||
// out.println(coreHelpService.getMethodHelp("unsettxfeerate"));
|
||||
// out.println(coreHelpService.getMethodHelp("getpaymentmethods"));
|
||||
// out.println(coreHelpService.getMethodHelp("getpaymentaccts"));
|
||||
|
@ -200,11 +200,9 @@ class CoreOffersService {
|
||||
double buyerSecurityDeposit,
|
||||
long triggerPrice,
|
||||
String paymentAccountId,
|
||||
String makerFeeCurrencyCode,
|
||||
Consumer<Offer> resultHandler) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
offerUtil.maybeSetFeePaymentCurrencyPreference(makerFeeCurrencyCode);
|
||||
|
||||
PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId);
|
||||
if (paymentAccount == null)
|
||||
|
@ -19,7 +19,6 @@ package bisq.core.api;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.api.model.PaymentAccountForm;
|
||||
import bisq.core.locale.CryptoCurrency;
|
||||
import bisq.core.payment.CryptoCurrencyAccount;
|
||||
import bisq.core.payment.InstantCryptoCurrencyAccount;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
@ -34,13 +33,11 @@ import java.io.File;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.locale.CurrencyUtil.getCryptoCurrency;
|
||||
import static java.lang.String.format;
|
||||
|
||||
@Singleton
|
||||
@ -103,21 +100,12 @@ class CorePaymentAccountsService {
|
||||
String currencyCode,
|
||||
String address,
|
||||
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
|
||||
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
|
||||
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
|
||||
cryptoCurrencyAccount.init();
|
||||
cryptoCurrencyAccount.setAccountName(accountName);
|
||||
cryptoCurrencyAccount.setAddress(address);
|
||||
Optional<CryptoCurrency> cryptoCurrency = getCryptoCurrency(bsqCode);
|
||||
cryptoCurrency.ifPresent(cryptoCurrencyAccount::setSingleTradeCurrency);
|
||||
user.addPaymentAccount(cryptoCurrencyAccount);
|
||||
accountAgeWitnessService.publishMyAccountAgeWitness(cryptoCurrencyAccount.getPaymentAccountPayload());
|
||||
log.info("Saved crypto payment account with id {} and payment method {}.",
|
||||
|
@ -87,14 +87,11 @@ class CoreTradesService {
|
||||
|
||||
void takeOffer(Offer offer,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode,
|
||||
Consumer<Trade> resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
|
||||
offerUtil.maybeSetFeePaymentCurrencyPreference(takerFeeCurrencyCode);
|
||||
|
||||
var paymentAccount = user.getPaymentAccount(paymentAccountId);
|
||||
if (paymentAccount == null)
|
||||
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.BalancesInfo;
|
||||
import bisq.core.api.model.BsqBalanceInfo;
|
||||
import bisq.core.api.model.BtcBalanceInfo;
|
||||
import bisq.core.api.model.TxFeeRateInfo;
|
||||
import bisq.core.api.model.XmrBalanceInfo;
|
||||
import bisq.core.app.AppStartupState;
|
||||
import bisq.core.btc.Balances;
|
||||
import bisq.core.btc.exceptions.AddressEntryException;
|
||||
import bisq.core.btc.exceptions.BsqChangeBelowDustException;
|
||||
import bisq.core.btc.exceptions.InsufficientFundsException;
|
||||
import bisq.core.btc.exceptions.TransactionVerificationException;
|
||||
import bisq.core.btc.exceptions.WalletException;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.model.BsqTransferModel;
|
||||
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.TxBroadcaster;
|
||||
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.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.common.Timer;
|
||||
@ -100,9 +94,6 @@ class CoreWalletsService {
|
||||
private final Balances balances;
|
||||
private final WalletsManager walletsManager;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final BsqTransferService bsqTransferService;
|
||||
private final BsqFormatter bsqFormatter;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final CoinFormatter btcFormatter;
|
||||
@ -123,9 +114,6 @@ class CoreWalletsService {
|
||||
Balances balances,
|
||||
WalletsManager walletsManager,
|
||||
WalletsSetup walletsSetup,
|
||||
BsqWalletService bsqWalletService,
|
||||
BsqTransferService bsqTransferService,
|
||||
BsqFormatter bsqFormatter,
|
||||
BtcWalletService btcWalletService,
|
||||
XmrWalletService xmrWalletService,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||
@ -136,9 +124,6 @@ class CoreWalletsService {
|
||||
this.balances = balances;
|
||||
this.walletsManager = walletsManager;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.bsqTransferService = bsqTransferService;
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.btcFormatter = btcFormatter;
|
||||
@ -164,14 +149,12 @@ class CoreWalletsService {
|
||||
throw new IllegalStateException("balance is not yet available");
|
||||
|
||||
switch (currencyCode.trim().toUpperCase()) {
|
||||
case "BSQ":
|
||||
return new BalancesInfo(getBsqBalances(), BtcBalanceInfo.EMPTY, XmrBalanceInfo.EMPTY);
|
||||
case "BTC":
|
||||
return new BalancesInfo(BsqBalanceInfo.EMPTY, getBtcBalances(), XmrBalanceInfo.EMPTY);
|
||||
return new BalancesInfo(getBtcBalances(), XmrBalanceInfo.EMPTY);
|
||||
case "XMR":
|
||||
return new BalancesInfo(BsqBalanceInfo.EMPTY, BtcBalanceInfo.EMPTY, getXmrBalances());
|
||||
return new BalancesInfo(BtcBalanceInfo.EMPTY, getXmrBalances());
|
||||
default:
|
||||
return new BalancesInfo(getBsqBalances(), getBtcBalances(), getXmrBalances());
|
||||
return new BalancesInfo(getBtcBalances(), getXmrBalances());
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,40 +215,6 @@ class CoreWalletsService {
|
||||
.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,
|
||||
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) {
|
||||
try {
|
||||
@ -561,23 +476,13 @@ class CoreWalletsService {
|
||||
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) {
|
||||
if (currencyCode == null || currencyCode.isEmpty())
|
||||
return;
|
||||
|
||||
if (!currencyCode.equalsIgnoreCase("BSQ")
|
||||
&& !currencyCode.equalsIgnoreCase("BTC"))
|
||||
if (!currencyCode.equalsIgnoreCase("BTC"))
|
||||
throw new IllegalStateException(format("wallet does not support %s", currencyCode));
|
||||
}
|
||||
|
||||
@ -587,31 +492,13 @@ class CoreWalletsService {
|
||||
if (tempAesKey == null)
|
||||
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());
|
||||
walletsManager.setAesKey(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
|
||||
private BtcBalanceInfo getBtcBalances() {
|
||||
|
@ -10,12 +10,10 @@ public class BalancesInfo implements Payload {
|
||||
// Getter names are shortened for readability's sake, i.e.,
|
||||
// balancesInfo.getBtc().getAvailableBalance() is cleaner than
|
||||
// balancesInfo.getBtcBalanceInfo().getAvailableBalance().
|
||||
private final BsqBalanceInfo bsq;
|
||||
private final BtcBalanceInfo btc;
|
||||
private final XmrBalanceInfo xmr;
|
||||
|
||||
public BalancesInfo(BsqBalanceInfo bsq, BtcBalanceInfo btc, XmrBalanceInfo xmr) {
|
||||
this.bsq = bsq;
|
||||
public BalancesInfo(BtcBalanceInfo btc, XmrBalanceInfo xmr) {
|
||||
this.btc = btc;
|
||||
this.xmr = xmr;
|
||||
}
|
||||
@ -27,14 +25,13 @@ public class BalancesInfo implements Payload {
|
||||
@Override
|
||||
public bisq.proto.grpc.BalancesInfo toProtoMessage() {
|
||||
return bisq.proto.grpc.BalancesInfo.newBuilder()
|
||||
.setBsq(bsq.toProtoMessage())
|
||||
.setBtc(btc.toProtoMessage())
|
||||
.setXmr(xmr.toProtoMessage())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static BalancesInfo fromProto(bisq.proto.grpc.BalancesInfo proto) {
|
||||
return new BalancesInfo(BsqBalanceInfo.fromProto(proto.getBsq()),
|
||||
return new BalancesInfo(
|
||||
BtcBalanceInfo.fromProto(proto.getBtc()),
|
||||
XmrBalanceInfo.fromProto(proto.getXmr()));
|
||||
}
|
||||
@ -42,8 +39,7 @@ public class BalancesInfo implements Payload {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BalancesInfo{" + "\n" +
|
||||
" " + bsq.toString() + "\n" +
|
||||
", " + btc.toString() + "\n" +
|
||||
" " + btc.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 sellerSecurityDeposit;
|
||||
private final long triggerPrice;
|
||||
private final boolean isCurrencyForMakerFeeBtc;
|
||||
private final String paymentAccountId;
|
||||
private final String paymentMethodId;
|
||||
private final String paymentMethodShortName;
|
||||
@ -79,7 +78,6 @@ public class OfferInfo implements Payload {
|
||||
this.buyerSecurityDeposit = builder.buyerSecurityDeposit;
|
||||
this.sellerSecurityDeposit = builder.sellerSecurityDeposit;
|
||||
this.triggerPrice = builder.triggerPrice;
|
||||
this.isCurrencyForMakerFeeBtc = builder.isCurrencyForMakerFeeBtc;
|
||||
this.paymentAccountId = builder.paymentAccountId;
|
||||
this.paymentMethodId = builder.paymentMethodId;
|
||||
this.paymentMethodShortName = builder.paymentMethodShortName;
|
||||
@ -116,7 +114,6 @@ public class OfferInfo implements Payload {
|
||||
.withOfferFeePaymentTxId(offer.getOfferFeePaymentTxId())
|
||||
.withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value)
|
||||
.withSellerSecurityDeposit(offer.getSellerSecurityDeposit().value)
|
||||
.withIsCurrencyForMakerFeeBtc(offer.isCurrencyForMakerFeeBtc())
|
||||
.withPaymentAccountId(offer.getMakerPaymentAccountId())
|
||||
.withPaymentMethodId(offer.getPaymentMethod().getId())
|
||||
.withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
|
||||
@ -148,7 +145,6 @@ public class OfferInfo implements Payload {
|
||||
.setBuyerSecurityDeposit(buyerSecurityDeposit)
|
||||
.setSellerSecurityDeposit(sellerSecurityDeposit)
|
||||
.setTriggerPrice(triggerPrice)
|
||||
.setIsCurrencyForMakerFeeBtc(isCurrencyForMakerFeeBtc)
|
||||
.setPaymentAccountId(paymentAccountId)
|
||||
.setPaymentMethodId(paymentMethodId)
|
||||
.setPaymentMethodShortName(paymentMethodShortName)
|
||||
@ -177,7 +173,6 @@ public class OfferInfo implements Payload {
|
||||
.withBuyerSecurityDeposit(proto.getBuyerSecurityDeposit())
|
||||
.withSellerSecurityDeposit(proto.getSellerSecurityDeposit())
|
||||
.withTriggerPrice(proto.getTriggerPrice())
|
||||
.withIsCurrencyForMakerFeeBtc(proto.getIsCurrencyForMakerFeeBtc())
|
||||
.withPaymentAccountId(proto.getPaymentAccountId())
|
||||
.withPaymentMethodId(proto.getPaymentMethodId())
|
||||
.withPaymentMethodShortName(proto.getPaymentMethodShortName())
|
||||
@ -210,7 +205,6 @@ public class OfferInfo implements Payload {
|
||||
private long buyerSecurityDeposit;
|
||||
private long sellerSecurityDeposit;
|
||||
private long triggerPrice;
|
||||
private boolean isCurrencyForMakerFeeBtc;
|
||||
private String paymentAccountId;
|
||||
private String paymentMethodId;
|
||||
private String paymentMethodShortName;
|
||||
@ -294,10 +288,6 @@ public class OfferInfo implements Payload {
|
||||
return this;
|
||||
}
|
||||
|
||||
public OfferInfoBuilder withIsCurrencyForMakerFeeBtc(boolean isCurrencyForMakerFeeBtc) {
|
||||
this.isCurrencyForMakerFeeBtc = isCurrencyForMakerFeeBtc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OfferInfoBuilder withPaymentAccountId(String paymentAccountId) {
|
||||
this.paymentAccountId = paymentAccountId;
|
||||
|
@ -43,7 +43,6 @@ public class TradeInfo implements Payload {
|
||||
private final String shortId;
|
||||
private final long date;
|
||||
private final String role;
|
||||
private final boolean isCurrencyForTakerFeeBtc;
|
||||
private final long txFeeAsLong;
|
||||
private final long takerFeeAsLong;
|
||||
private final String takerFeeTxId;
|
||||
@ -71,7 +70,6 @@ public class TradeInfo implements Payload {
|
||||
this.shortId = builder.shortId;
|
||||
this.date = builder.date;
|
||||
this.role = builder.role;
|
||||
this.isCurrencyForTakerFeeBtc = builder.isCurrencyForTakerFeeBtc;
|
||||
this.txFeeAsLong = builder.txFeeAsLong;
|
||||
this.takerFeeAsLong = builder.takerFeeAsLong;
|
||||
this.takerFeeTxId = builder.takerFeeTxId;
|
||||
@ -225,7 +223,6 @@ public class TradeInfo implements Payload {
|
||||
private String shortId;
|
||||
private long date;
|
||||
private String role;
|
||||
private boolean isCurrencyForTakerFeeBtc;
|
||||
private long txFeeAsLong;
|
||||
private long takerFeeAsLong;
|
||||
private String takerFeeTxId;
|
||||
@ -272,11 +269,6 @@ public class TradeInfo implements Payload {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TradeInfoBuilder withIsCurrencyForTakerFeeBtc(boolean isCurrencyForTakerFeeBtc) {
|
||||
this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TradeInfoBuilder withTxFeeAsLong(long txFeeAsLong) {
|
||||
this.txFeeAsLong = txFeeAsLong;
|
||||
return this;
|
||||
@ -389,7 +381,6 @@ public class TradeInfo implements Payload {
|
||||
", shortId='" + shortId + '\'' + "\n" +
|
||||
", date='" + date + '\'' + "\n" +
|
||||
", role='" + role + '\'' + "\n" +
|
||||
", isCurrencyForTakerFeeBtc='" + isCurrencyForTakerFeeBtc + '\'' + "\n" +
|
||||
", txFeeAsLong='" + txFeeAsLong + '\'' + "\n" +
|
||||
", takerFeeAsLong='" + takerFeeAsLong + '\'' + "\n" +
|
||||
", takerFeeTxId='" + takerFeeTxId + '\'' + "\n" +
|
||||
|
@ -18,11 +18,8 @@
|
||||
package bisq.core.app;
|
||||
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.dao.DaoSetup;
|
||||
import bisq.core.dao.node.full.RpcService;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.setup.CorePersistedDataHost;
|
||||
@ -235,8 +232,6 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
||||
injector.getInstance(ArbitratorManager.class).shutDown();
|
||||
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
||||
injector.getInstance(XmrTxProofService.class).shutDown();
|
||||
injector.getInstance(RpcService.class).shutDown();
|
||||
injector.getInstance(DaoSetup.class).shutDown();
|
||||
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
||||
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc?
|
||||
log.info("OpenOfferManager shutdown started");
|
||||
@ -244,7 +239,6 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
||||
log.info("OpenOfferManager shutdown completed");
|
||||
|
||||
injector.getInstance(BtcWalletService.class).shutDown();
|
||||
injector.getInstance(BsqWalletService.class).shutDown();
|
||||
|
||||
// We need to shutdown BitcoinJ before the P2PService as it uses Tor.
|
||||
WalletsSetup walletsSetup = injector.getInstance(WalletsSetup.class);
|
||||
|
@ -84,12 +84,8 @@ public class BisqHeadlessApp implements HeadlessApp {
|
||||
bisqSetup.setDisplayUpdateHandler((alert, key) -> log.info("onDisplayUpdateHandler"));
|
||||
bisqSetup.setDisplayAlertHandler(alert -> log.info("onDisplayAlertHandler. alert={}", alert));
|
||||
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.setDisplayLocalhostHandler(key -> log.info("onDisplayLocalhostHandler"));
|
||||
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.setShowPopupIfInvalidBtcConfigHandler(() -> log.error("onShowPopupIfInvalidBtcConfigHandler"));
|
||||
bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList));
|
||||
@ -97,12 +93,6 @@ public class BisqHeadlessApp implements HeadlessApp {
|
||||
bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
|
||||
bisqSetup.setDownGradePreventionHandler(lastVersion -> log.info("Downgrade from version {} to version {} is not supported",
|
||||
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));
|
||||
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.XmrWalletService;
|
||||
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.offer.OpenOfferManager;
|
||||
import bisq.core.payment.AmazonGiftCardAccount;
|
||||
@ -135,7 +133,6 @@ public class BisqSetup {
|
||||
private final Preferences preferences;
|
||||
private final User user;
|
||||
private final AlertManager alertManager;
|
||||
private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService;
|
||||
private final Config config;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final TorSetup torSetup;
|
||||
@ -149,7 +146,7 @@ public class BisqSetup {
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<String> chainFileLockedExceptionHandler,
|
||||
spvFileCorruptedHandler, lockedUpFundsHandler, daoErrorMessageHandler, daoWarnMessageHandler,
|
||||
spvFileCorruptedHandler, lockedUpFundsHandler,
|
||||
filterWarningHandler, displaySecurityRecommendationHandler, displayLocalhostHandler,
|
||||
wrongOSArchitectureHandler, displaySignedByArbitratorHandler,
|
||||
displaySignedByPeerHandler, displayPeerLimitLiftedHandler, displayPeerSignerHandler,
|
||||
@ -171,9 +168,6 @@ public class BisqSetup {
|
||||
private BiConsumer<Alert, String> displayUpdateHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<VoteResultException> voteResultExceptionHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<PrivateNotificationPayload> displayPrivateNotificationHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
@ -192,9 +186,6 @@ public class BisqSetup {
|
||||
private Runnable qubesOSInfoHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Runnable daoRequiresRestartHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<String> downGradePreventionHandler;
|
||||
|
||||
@Getter
|
||||
@ -221,7 +212,6 @@ public class BisqSetup {
|
||||
Preferences preferences,
|
||||
User user,
|
||||
AlertManager alertManager,
|
||||
UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService,
|
||||
Config config,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
TorSetup torSetup,
|
||||
@ -243,7 +233,6 @@ public class BisqSetup {
|
||||
this.preferences = preferences;
|
||||
this.user = user;
|
||||
this.alertManager = alertManager;
|
||||
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
|
||||
this.config = config;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.torSetup = torSetup;
|
||||
@ -334,10 +323,6 @@ public class BisqSetup {
|
||||
try {
|
||||
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) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
@ -461,13 +446,9 @@ public class BisqSetup {
|
||||
|
||||
domainInitialisation.initDomainServices(rejectedTxErrorMessageHandler,
|
||||
displayPrivateNotificationHandler,
|
||||
daoErrorMessageHandler,
|
||||
daoWarnMessageHandler,
|
||||
filterWarningHandler,
|
||||
voteResultExceptionHandler,
|
||||
revolutAccountsUpdateHandler,
|
||||
amazonGiftCardAccountsUpdateHandler,
|
||||
daoRequiresRestartHandler);
|
||||
amazonGiftCardAccountsUpdateHandler);
|
||||
|
||||
if (walletsSetup.downloadPercentageProperty().get() == 1) {
|
||||
checkForLockedUpFunds();
|
||||
|
@ -19,7 +19,6 @@ package bisq.core.app;
|
||||
|
||||
import bisq.core.alert.AlertModule;
|
||||
import bisq.core.btc.BitcoinModule;
|
||||
import bisq.core.dao.DaoModule;
|
||||
import bisq.core.filter.FilterModule;
|
||||
import bisq.core.network.CoreNetworkFilter;
|
||||
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
|
||||
@ -89,7 +88,6 @@ public class CoreModule extends AppModule {
|
||||
install(new OfferModule(config));
|
||||
install(new P2PModule(config));
|
||||
install(new BitcoinModule(config));
|
||||
install(new DaoModule(config));
|
||||
install(new AlertModule(config));
|
||||
install(new FilterModule(config));
|
||||
install(new CorePresentationModule(config));
|
||||
|
@ -22,10 +22,6 @@ import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.alert.PrivateNotificationManager;
|
||||
import bisq.core.alert.PrivateNotificationPayload;
|
||||
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.notifications.MobileNotificationService;
|
||||
import bisq.core.notifications.alerts.DisputeMsgEvents;
|
||||
@ -37,7 +33,6 @@ import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.offer.TriggerPriceService;
|
||||
import bisq.core.payment.AmazonGiftCardAccount;
|
||||
import bisq.core.payment.RevolutAccount;
|
||||
import bisq.core.payment.TradeLimits;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.provider.mempool.MempoolService;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
@ -77,7 +72,6 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class DomainInitialisation {
|
||||
private final ClockWatcher clockWatcher;
|
||||
private final TradeLimits tradeLimits;
|
||||
private final ArbitrationManager arbitrationManager;
|
||||
private final MediationManager mediationManager;
|
||||
private final RefundManager refundManager;
|
||||
@ -95,13 +89,11 @@ public class DomainInitialisation {
|
||||
private final PrivateNotificationManager privateNotificationManager;
|
||||
private final P2PService p2PService;
|
||||
private final FeeService feeService;
|
||||
private final DaoSetup daoSetup;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final SignedWitnessService signedWitnessService;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final FilterManager filterManager;
|
||||
private final VoteResultService voteResultService;
|
||||
private final MobileNotificationService mobileNotificationService;
|
||||
private final MyOfferTakenEvents myOfferTakenEvents;
|
||||
private final TradeEvents tradeEvents;
|
||||
@ -109,13 +101,11 @@ public class DomainInitialisation {
|
||||
private final PriceAlert priceAlert;
|
||||
private final MarketAlerts marketAlerts;
|
||||
private final User user;
|
||||
private final DaoStateSnapshotService daoStateSnapshotService;
|
||||
private final TriggerPriceService triggerPriceService;
|
||||
private final MempoolService mempoolService;
|
||||
|
||||
@Inject
|
||||
public DomainInitialisation(ClockWatcher clockWatcher,
|
||||
TradeLimits tradeLimits,
|
||||
ArbitrationManager arbitrationManager,
|
||||
MediationManager mediationManager,
|
||||
RefundManager refundManager,
|
||||
@ -133,13 +123,11 @@ public class DomainInitialisation {
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
P2PService p2PService,
|
||||
FeeService feeService,
|
||||
DaoSetup daoSetup,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
SignedWitnessService signedWitnessService,
|
||||
PriceFeedService priceFeedService,
|
||||
FilterManager filterManager,
|
||||
VoteResultService voteResultService,
|
||||
MobileNotificationService mobileNotificationService,
|
||||
MyOfferTakenEvents myOfferTakenEvents,
|
||||
TradeEvents tradeEvents,
|
||||
@ -147,11 +135,9 @@ public class DomainInitialisation {
|
||||
PriceAlert priceAlert,
|
||||
MarketAlerts marketAlerts,
|
||||
User user,
|
||||
DaoStateSnapshotService daoStateSnapshotService,
|
||||
TriggerPriceService triggerPriceService,
|
||||
MempoolService mempoolService) {
|
||||
this.clockWatcher = clockWatcher;
|
||||
this.tradeLimits = tradeLimits;
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
this.mediationManager = mediationManager;
|
||||
this.refundManager = refundManager;
|
||||
@ -169,13 +155,11 @@ public class DomainInitialisation {
|
||||
this.privateNotificationManager = privateNotificationManager;
|
||||
this.p2PService = p2PService;
|
||||
this.feeService = feeService;
|
||||
this.daoSetup = daoSetup;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.signedWitnessService = signedWitnessService;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.filterManager = filterManager;
|
||||
this.voteResultService = voteResultService;
|
||||
this.mobileNotificationService = mobileNotificationService;
|
||||
this.myOfferTakenEvents = myOfferTakenEvents;
|
||||
this.tradeEvents = tradeEvents;
|
||||
@ -183,26 +167,19 @@ public class DomainInitialisation {
|
||||
this.priceAlert = priceAlert;
|
||||
this.marketAlerts = marketAlerts;
|
||||
this.user = user;
|
||||
this.daoStateSnapshotService = daoStateSnapshotService;
|
||||
this.triggerPriceService = triggerPriceService;
|
||||
this.mempoolService = mempoolService;
|
||||
}
|
||||
|
||||
public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
|
||||
Consumer<PrivateNotificationPayload> displayPrivateNotificationHandler,
|
||||
Consumer<String> daoErrorMessageHandler,
|
||||
Consumer<String> daoWarnMessageHandler,
|
||||
Consumer<String> filterWarningHandler,
|
||||
Consumer<VoteResultException> voteResultExceptionHandler,
|
||||
Consumer<List<RevolutAccount>> revolutAccountsUpdateHandler,
|
||||
Consumer<List<AmazonGiftCardAccount>> amazonGiftCardAccountsUpdateHandler,
|
||||
Runnable daoRequiresRestartHandler) {
|
||||
Consumer<List<AmazonGiftCardAccount>> amazonGiftCardAccountsUpdateHandler) {
|
||||
clockWatcher.start();
|
||||
|
||||
PersistenceManager.onAllServicesInitialized();
|
||||
|
||||
tradeLimits.onAllServicesInitialized();
|
||||
|
||||
tradeManager.onAllServicesInitialized();
|
||||
arbitrationManager.onAllServicesInitialized();
|
||||
mediationManager.onAllServicesInitialized();
|
||||
@ -232,17 +209,6 @@ public class DomainInitialisation {
|
||||
|
||||
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();
|
||||
|
||||
@ -254,12 +220,6 @@ public class DomainInitialisation {
|
||||
filterManager.setFilterWarningHandler(filterWarningHandler);
|
||||
filterManager.onAllServicesInitialized();
|
||||
|
||||
voteResultService.getVoteResultExceptions().addListener((ListChangeListener<VoteResultException>) c -> {
|
||||
c.next();
|
||||
if (c.wasAdded() && voteResultExceptionHandler != null) {
|
||||
c.getAddedSubList().forEach(voteResultExceptionHandler);
|
||||
}
|
||||
});
|
||||
|
||||
mobileNotificationService.onAllServicesInitialized();
|
||||
myOfferTakenEvents.onAllServicesInitialized();
|
||||
|
@ -94,7 +94,6 @@ public class P2PNetworkSetup {
|
||||
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
|
||||
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
|
||||
String result;
|
||||
String daoFullNode = preferences.isDaoFullNode() ? Res.get("mainView.footer.daoFullNode") + " / " : "";
|
||||
int p2pPeers = (int) numP2pPeers;
|
||||
if (warning != null && p2pPeers == 0) {
|
||||
result = warning;
|
||||
@ -107,7 +106,7 @@ public class P2PNetworkSetup {
|
||||
else
|
||||
result = state + " / " + p2pInfo;
|
||||
}
|
||||
return daoFullNode + result;
|
||||
return result;
|
||||
});
|
||||
p2PNetworkInfoBinding.subscribe((observable, oldValue, newValue) -> {
|
||||
p2PNetworkInfo.set(newValue);
|
||||
|
@ -19,13 +19,6 @@ package bisq.core.app.misc;
|
||||
|
||||
import bisq.core.account.sign.SignedWitnessService;
|
||||
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.trade.statistics.TradeStatisticsManager;
|
||||
|
||||
@ -41,7 +34,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||
private final DaoSetup daoSetup;
|
||||
|
||||
@Inject
|
||||
public AppSetupWithP2PAndDAO(P2PService p2PService,
|
||||
@ -51,13 +43,6 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
SignedWitnessService signedWitnessService,
|
||||
FilterManager filterManager,
|
||||
DaoSetup daoSetup,
|
||||
MyVoteListService myVoteListService,
|
||||
BallotListService ballotListService,
|
||||
MyBlindVoteListService myBlindVoteListService,
|
||||
MyProposalListService myProposalListService,
|
||||
MyReputationListService myReputationListService,
|
||||
MyProofOfBurnListService myProofOfBurnListService,
|
||||
Config config) {
|
||||
super(p2PService,
|
||||
p2PDataStorage,
|
||||
@ -68,23 +53,11 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||
filterManager,
|
||||
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
|
||||
protected void 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.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.dao.DaoSetup;
|
||||
import bisq.core.dao.node.full.RpcService;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
|
||||
@ -88,8 +85,6 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable {
|
||||
try {
|
||||
if (injector != null) {
|
||||
JsonFileManager.shutDownAllInstances();
|
||||
injector.getInstance(RpcService.class).shutDown();
|
||||
injector.getInstance(DaoSetup.class).shutDown();
|
||||
injector.getInstance(ArbitratorManager.class).shutDown();
|
||||
injector.getInstance(OpenOfferManager.class).shutDown(() -> injector.getInstance(P2PService.class).shutDown(() -> {
|
||||
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(XmrWalletService.class).shutDown(); // TODO (woodser): this is not actually called, perhaps because WalletsSetup.class completes too quick so its listener calls System.exit(0)
|
||||
injector.getInstance(BtcWalletService.class).shutDown();
|
||||
injector.getInstance(BsqWalletService.class).shutDown();
|
||||
}));
|
||||
// we wait max 5 sec.
|
||||
UserThread.runAfter(() -> {
|
||||
|
@ -20,7 +20,6 @@ package bisq.core.app.misc;
|
||||
import bisq.core.alert.AlertModule;
|
||||
import bisq.core.app.TorSetup;
|
||||
import bisq.core.btc.BitcoinModule;
|
||||
import bisq.core.dao.DaoModule;
|
||||
import bisq.core.filter.FilterModule;
|
||||
import bisq.core.network.CoreNetworkFilter;
|
||||
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
|
||||
@ -92,7 +91,6 @@ public class ModuleForAppWithP2p extends AppModule {
|
||||
install(new OfferModule(config));
|
||||
install(new P2PModule(config));
|
||||
install(new BitcoinModule(config));
|
||||
install(new DaoModule(config));
|
||||
install(new AlertModule(config));
|
||||
install(new FilterModule(config));
|
||||
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.setup.RegTestHost;
|
||||
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.NonBsqCoinSelector;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
@ -60,11 +58,7 @@ public class BitcoinModule extends AppModule {
|
||||
// otherwise the specified host or default (localhost)
|
||||
String regTestHost = config.bitcoinRegtestHost;
|
||||
if (regTestHost.isEmpty()) {
|
||||
regTestHost = config.baseCurrencyNetwork.isDaoTestNet() ?
|
||||
"104.248.31.39" :
|
||||
config.baseCurrencyNetwork.isDaoRegTest() ?
|
||||
"134.209.242.206" :
|
||||
Config.DEFAULT_REGTEST_HOST;
|
||||
regTestHost = Config.DEFAULT_REGTEST_HOST;
|
||||
}
|
||||
|
||||
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.NUM_CONNECTIONS_FOR_BTC)).to(config.numConnectionsForBtc);
|
||||
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);
|
||||
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(XmrWalletService.class).in(Singleton.class);
|
||||
bind(BtcWalletService.class).in(Singleton.class);
|
||||
bind(BsqWalletService.class).in(Singleton.class);
|
||||
bind(TradeWalletService.class).in(Singleton.class);
|
||||
bind(BsqCoinSelector.class).in(Singleton.class);
|
||||
bind(NonBsqCoinSelector.class).in(Singleton.class);
|
||||
bind(BtcNodes.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;
|
||||
private static int DEPOSIT_TX_VSIZE = 233;
|
||||
|
||||
private static int BSQ_INPUT_INCREASE = 150;
|
||||
private static int MAX_ITERATIONS = 10;
|
||||
|
||||
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 (!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;
|
||||
int vsize;
|
||||
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() {
|
||||
BaseCurrencyNetwork baseCurrencyNetwork = config.baseCurrencyNetwork;
|
||||
|
||||
// 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();
|
||||
return config.ignoreLocalBtcNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,29 +32,23 @@ import com.google.common.collect.ImmutableList;
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
* 2) Run post bitcoinj 0.15 bisq and use recover from seed.
|
||||
* 1) Run pre bitcoinj 0.15 btc and copy their seed words on a piece of paper.
|
||||
* 2) Run post bitcoinj 0.15 btc and use recover from seed.
|
||||
* */
|
||||
public class BisqKeyChainFactory extends DefaultKeyChainFactory {
|
||||
|
||||
private boolean isBsqWallet;
|
||||
|
||||
public BisqKeyChainFactory(boolean isBsqWallet) {
|
||||
this.isBsqWallet = isBsqWallet;
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
if (DeterministicKeyChain.ACCOUNT_ZERO_PATH.equals(accountPath)) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -39,43 +39,13 @@ public class BisqKeyChainGroupStructure implements KeyChainGroupStructure {
|
||||
new ChildNumber(0, true),
|
||||
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
|
||||
public ImmutableList<ChildNumber> accountPathFor(Script.ScriptType outputScriptType) {
|
||||
if (!isBsqWallet) {
|
||||
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
|
||||
return BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH;
|
||||
else if (outputScriptType == Script.ScriptType.P2WPKH)
|
||||
return BIP44_BTC_SEGWIT_ACCOUNT_PATH;
|
||||
else
|
||||
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());
|
||||
}
|
||||
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
|
||||
return BIP44_BTC_NON_SEGWIT_ACCOUNT_PATH;
|
||||
else if (outputScriptType == Script.ScriptType.P2WPKH)
|
||||
return BIP44_BTC_SEGWIT_ACCOUNT_PATH;
|
||||
else
|
||||
throw new IllegalArgumentException(outputScriptType.toString());
|
||||
}
|
||||
}
|
||||
|
@ -142,13 +142,11 @@ public class WalletConfig extends AbstractIdleService {
|
||||
protected volatile MoneroDaemon vXmrDaemon;
|
||||
protected volatile MoneroWalletRpc vXmrWallet;
|
||||
protected volatile Wallet vBtcWallet;
|
||||
protected volatile Wallet vBsqWallet;
|
||||
protected volatile PeerGroup vPeerGroup;
|
||||
|
||||
protected final File directory;
|
||||
protected volatile File vXmrWalletFile;
|
||||
protected volatile File vBtcWalletFile;
|
||||
protected volatile File vBsqWalletFile;
|
||||
|
||||
protected PeerAddress[] peerAddresses;
|
||||
protected DownloadProgressTracker downloadListener;
|
||||
@ -373,15 +371,10 @@ public class WalletConfig extends AbstractIdleService {
|
||||
String btcPrefix = "_BTC";
|
||||
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
|
||||
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
|
||||
vBtcWallet = createOrLoadWallet(shouldReplayWallet, vBtcWalletFile, false);
|
||||
vBtcWallet = createOrLoadWallet(shouldReplayWallet, vBtcWalletFile);
|
||||
vBtcWallet.allowSpendingUnconfirmedTransactions();
|
||||
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)
|
||||
vStore = new SPVBlockStore(params, chainFile);
|
||||
if (!chainFileExists || restoreFromSeed != null) {
|
||||
@ -431,8 +424,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||
}
|
||||
vChain.addWallet(vBtcWallet);
|
||||
vPeerGroup.addWallet(vBtcWallet);
|
||||
vChain.addWallet(vBsqWallet);
|
||||
vPeerGroup.addWallet(vBsqWallet);
|
||||
onSetupCompleted();
|
||||
|
||||
if (migratedWalletToSegwit.get()) {
|
||||
@ -468,23 +459,22 @@ public class WalletConfig extends AbstractIdleService {
|
||||
}
|
||||
|
||||
private Wallet createOrLoadWallet(boolean shouldReplayWallet,
|
||||
File walletFile,
|
||||
boolean isBsqWallet) throws Exception {
|
||||
File walletFile) throws Exception {
|
||||
Wallet wallet;
|
||||
|
||||
maybeMoveOldWalletOutOfTheWay(walletFile);
|
||||
|
||||
if (walletFile.exists()) {
|
||||
wallet = loadWallet(shouldReplayWallet, walletFile, isBsqWallet);
|
||||
wallet = loadWallet(shouldReplayWallet, walletFile);
|
||||
} else {
|
||||
wallet = createWallet(isBsqWallet);
|
||||
wallet = createWallet();
|
||||
//wallet.freshReceiveKey();
|
||||
|
||||
// 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[]))
|
||||
// Hence, we first save and then load wallet to ensure any extensions are correctly initialized.
|
||||
wallet.saveToFile(walletFile);
|
||||
wallet = loadWallet(false, walletFile, isBsqWallet);
|
||||
wallet = loadWallet(false, walletFile);
|
||||
}
|
||||
|
||||
this.setupAutoSave(wallet, walletFile);
|
||||
@ -496,7 +486,7 @@ public class WalletConfig extends AbstractIdleService {
|
||||
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;
|
||||
try (FileInputStream walletStream = new FileInputStream(walletFile)) {
|
||||
WalletExtension[] extArray = new WalletExtension[]{};
|
||||
@ -504,32 +494,25 @@ public class WalletConfig extends AbstractIdleService {
|
||||
final WalletProtobufSerializer serializer;
|
||||
serializer = new WalletProtobufSerializer();
|
||||
// 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);
|
||||
if (shouldReplayWallet)
|
||||
wallet.reset();
|
||||
if (!isBsqWallet) {
|
||||
maybeAddSegwitKeychain(wallet, null);
|
||||
}
|
||||
maybeAddSegwitKeychain(wallet, null);
|
||||
}
|
||||
return wallet;
|
||||
}
|
||||
|
||||
protected Wallet createWallet(boolean isBsqWallet) {
|
||||
Script.ScriptType preferredOutputScriptType = isBsqWallet ? Script.ScriptType.P2PKH : Script.ScriptType.P2WPKH;
|
||||
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure(isBsqWallet);
|
||||
protected Wallet createWallet() {
|
||||
Script.ScriptType preferredOutputScriptType = Script.ScriptType.P2WPKH;
|
||||
KeyChainGroupStructure structure = new BisqKeyChainGroupStructure();
|
||||
KeyChainGroup.Builder kcgBuilder = KeyChainGroup.builder(params, structure);
|
||||
if (restoreFromSeed != null) {
|
||||
kcgBuilder.fromSeed(restoreFromSeed, preferredOutputScriptType);
|
||||
} else {
|
||||
// new wallet
|
||||
if (!isBsqWallet) {
|
||||
// btc wallet uses a new random seed.
|
||||
kcgBuilder.fromRandom(preferredOutputScriptType);
|
||||
} else {
|
||||
// bsq wallet uses btc wallet's seed created a few milliseconds ago.
|
||||
kcgBuilder.fromSeed(vBtcWallet.getKeyChainSeed(), preferredOutputScriptType);
|
||||
}
|
||||
// btc wallet uses a new random seed.
|
||||
kcgBuilder.fromRandom(preferredOutputScriptType);
|
||||
}
|
||||
return new Wallet(params, kcgBuilder.build());
|
||||
}
|
||||
@ -588,12 +571,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||
vBtcWallet = null;
|
||||
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 = null;
|
||||
log.info("SPV file closed");
|
||||
@ -644,11 +621,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||
return vXmrWallet;
|
||||
}
|
||||
|
||||
public Wallet bsqWallet() {
|
||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||
return vBsqWallet;
|
||||
}
|
||||
|
||||
public PeerGroup peerGroup() {
|
||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||
return vPeerGroup;
|
||||
@ -681,7 +653,7 @@ public class WalletConfig extends AbstractIdleService {
|
||||
}
|
||||
DeterministicKeyChain nativeSegwitKeyChain = DeterministicKeyChain.builder().seed(seed)
|
||||
.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 wallet is encrypted, encrypt the new keychain.
|
||||
KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
||||
|
@ -120,7 +120,6 @@ public class WalletsSetup {
|
||||
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
|
||||
|
||||
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 final RegTestHost regTestHost;
|
||||
@ -427,7 +426,6 @@ public class WalletsSetup {
|
||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName, 20);
|
||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".keys", 20);
|
||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".address.txt", 20);
|
||||
FileUtil.rollingBackup(walletDir, BSQ_WALLET_FILE_NAME, 20);
|
||||
}
|
||||
|
||||
public void clearBackups() {
|
||||
@ -498,11 +496,6 @@ public class WalletsSetup {
|
||||
return walletConfig.getXmrWallet();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Wallet getBsqWallet() {
|
||||
return walletConfig.bsqWallet();
|
||||
}
|
||||
|
||||
public NetworkParameters getParams() {
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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) {
|
||||
int numLegacyInputs = 0;
|
||||
int numSegwitInputs = 0;
|
||||
|
@ -17,8 +17,6 @@
|
||||
|
||||
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 org.bitcoinj.core.Transaction;
|
||||
@ -36,14 +34,12 @@ import lombok.extern.slf4j.Slf4j;
|
||||
*/
|
||||
@Slf4j
|
||||
public class NonBsqCoinSelector extends BisqDefaultCoinSelector {
|
||||
private DaoStateService daoStateService;
|
||||
@Setter
|
||||
private Preferences preferences;
|
||||
|
||||
@Inject
|
||||
public NonBsqCoinSelector(DaoStateService daoStateService) {
|
||||
public NonBsqCoinSelector() {
|
||||
super(false);
|
||||
this.daoStateService = daoStateService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -58,10 +54,7 @@ public class NonBsqCoinSelector extends BisqDefaultCoinSelector {
|
||||
if (parentTransaction.getConfidence().getConfidenceType() != TransactionConfidence.ConfidenceType.BUILDING)
|
||||
return false;
|
||||
|
||||
TxOutputKey key = new TxOutputKey(parentTransaction.getTxId().toString(), output.getIndex());
|
||||
// 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);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prevent usage of dust attack utxos
|
||||
|
@ -151,177 +151,6 @@ public class TradeWalletService {
|
||||
.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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user