remove DAO

Co-authored-by: premek <1145361+premek@users.noreply.github.com>
This commit is contained in:
l0nelyc0w 2021-10-19 20:45:55 +03:00 committed by woodser
parent f9f2cd07c3
commit cefba8e4b5
621 changed files with 583 additions and 68805 deletions

1
.gitignore vendored
View File

@ -34,4 +34,3 @@ deploy
/monitor/monitor-tor/*
.java-version
.localnet
/apitest/src/main/resources/dao-setup*

View File

@ -1,6 +1,6 @@
# See docs/installing.md
build: nodes localnet build-haveno
build: nodes localnet build-haveno
clean:
./gradlew clean
@ -9,7 +9,7 @@ clean-localnet:
rm -rf .localnet
localnet:
mkdir -p .localnet
mkdir -p .localnet
nodes: localnet
./scripts/xmr_btc_deps.sh
@ -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

View File

@ -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')
}
}

View File

@ -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 Bisqs
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 Bobs balance (using port `9999`), not Alices balance._
Show Bobs 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 wallets 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 UIs 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:

View File

@ -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

View File

@ -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 $?

View File

@ -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 ]

View File

@ -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."

View File

@ -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."

View File

@ -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.

View File

@ -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 {

View File

@ -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";

View File

@ -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(),

View File

@ -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);

View File

@ -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 {

View File

@ -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);

View File

@ -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

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -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());
}

View File

@ -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,

View File

@ -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);
}
}
}

View File

@ -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.

View File

@ -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);
}
}
}

View File

@ -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.

View File

@ -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));
}
}

View File

@ -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());

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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() {

View File

@ -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());
}
/**

View File

@ -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;

View File

@ -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.

View File

@ -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());

View File

@ -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()

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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#");
}
}

View File

@ -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.

View File

@ -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");

View File

@ -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";

View File

@ -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;
}

View File

@ -46,12 +46,8 @@ 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()
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
@ -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())

View File

@ -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

View File

@ -41,15 +41,12 @@ public enum Method {
gettrade,
gettransaction,
gettxfeerate,
getunusedbsqaddress,
getversion,
keepfunds,
lockwallet,
registerdisputeagent,
removewalletpassword,
sendbsq,
sendbtc,
verifybsqsenttoaddress,
settxfeerate,
setwalletpassword,
takeoffer,

View File

@ -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) {

View File

@ -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;
}
}
}
}

View File

@ -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")
@ -54,16 +54,9 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
if (!options.has(accountNameOpt) || options.valueOf(accountNameOpt).isEmpty())
throw new IllegalArgumentException("no payment account name specified");
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;
}

View File

@ -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";
}
}

View File

@ -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);

View File

@ -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) : "";
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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<>();

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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());
}

View File

@ -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);

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -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");

View File

@ -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"));

View File

@ -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)

View File

@ -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 {}.",

View File

@ -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));

View File

@ -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() {

View File

@ -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" +
'}';
}

View File

@ -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 +
'}';
}
}

View File

@ -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;

View File

@ -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;
@ -296,7 +288,7 @@ public class TradeInfo implements Payload {
this.makerDepositTxId = makerDepositTxId;
return this;
}
public TradeInfoBuilder withTakerDepositTxId(String takerDepositTxId) {
this.takerDepositTxId = takerDepositTxId;
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" +

View File

@ -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);

View File

@ -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"));

View File

@ -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();

View File

@ -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));

View File

@ -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();

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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(() -> {

View File

@ -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);

View File

@ -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);

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -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);
}

View File

@ -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" +
'}';
}
}

View File

@ -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;
}
/**

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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

View File

@ -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