Merge pull request #5876 from ghubstan/5-api-bsqswap-simulation-n-docs-update

API BSQ swap simulation script and doc updates #5
This commit is contained in:
sqrrm 2021-12-14 15:47:51 +01:00 committed by GitHub
commit cf81fd451c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 2447 additions and 1605 deletions

View file

@ -17,9 +17,9 @@ option adjustments to compensate.
**Shell**: Bash **Shell**: Bash
**Java SDK**: Version 10, 11, 12 or 15 **Java SDK**: Version 11 - 15 (src and class generation version 11)
**Bitcoin-Core**: Version 0.19, 0.20, or 0.21 **Bitcoin-Core**: Version 0.19 - 22
**Git Client** **Git Client**
@ -372,6 +372,22 @@ The `trade-simulation.sh` script options that would generate the previous `creat
$ apitest/scripts/trade-simulation.sh -d sell -c jp -m 0.5 -a 0.125 $ apitest/scripts/trade-simulation.sh -d sell -c jp -m 0.5 -a 0.125
``` ```
The `createoffer` command can also be used to create BSQ swap offers, where trade execution is performed immediately
after a BSQ swap offer is taken. To swap 0.5 BTC for BSQ at a price of 0.00005 BSQ per 1 BTC:
```
$ ./bisq-cli --password=xyz --port=9998 createoffer \
--swap=true \
--direction=BUY \
--amount=0.5 \
--currency-code=BSQ \
--fixed-price=0.00005
```
The `bsqswap-simulation.sh` script options that would generate the previous `createoffer` example is:
```
$ apitest/scripts/bsqswap-simulation.sh -d buy -a 0.5 -f 0.00005
```
### Browsing Your Own Offers ### Browsing Your Own Offers
There are different commands to browse available offers you can take, and offers you created. There are different commands to browse available offers you can take, and offers you created.
@ -530,7 +546,7 @@ A CLI user browses available offers with the getoffers command. For example, th
$ ./bisq-cli --password=xyz --port=9998 getoffers --direction=SELL --currency-code=EUR $ ./bisq-cli --password=xyz --port=9998 getoffers --direction=SELL --currency-code=EUR
``` ```
And takes one of the available offers with an EUR payment account ( id `fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e`) Then takes one of the available offers with an EUR payment account ( id `fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e`)
with the `takeoffer` command: with the `takeoffer` command:
``` ```
$ ./bisq-cli --password=xyz --port=9998 takeoffer \ $ ./bisq-cli --password=xyz --port=9998 takeoffer \
@ -538,8 +554,10 @@ $ ./bisq-cli --password=xyz --port=9998 takeoffer \
--payment-account=fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e \ --payment-account=fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e \
--fee-currency=btc --fee-currency=btc
``` ```
The taken offer will be used to create a trade contract. The next section describes how to use the Api to execute Depending on the offer type, the taken offer will be used to (1) create a trade contract, or (2) execute a BSQ swap.
the trade.
The next section describes how to use the Api to execute a trade. The following <b>Completing a BSQ Swap Trade</b>
section explains how to use the `takeoffer` command to complete a BSQ swap.
### Completing Trade Protocol ### Completing Trade Protocol
@ -598,6 +616,14 @@ $ ./bisq-cli --password=xyz --port=9998 keepfunds --trade-id=<trade-id>
$ ./bisq-cli --password=xyz --port=9999 withdrawfunds --trade-id=<trade-id> --address=<btc-address> [--memo=<"memo">] $ ./bisq-cli --password=xyz --port=9999 withdrawfunds --trade-id=<trade-id> --address=<btc-address> [--memo=<"memo">]
``` ```
### Completing a BSQ Swap Trade
The `takeoffer` command will immediately perform the BSQ swap, assuming both sides' wallets have sufficient BTC and
BSQ to cover the trade amount, and maker or taker fee. It takes one argument: `--offer-id=<offer-id>`:
```
$ ./bisq-cli --password=xyz --port=9998 takeoffer --offer-id=Xge8b2e2-51b6-3TOOB-z748-3ebd29c2kj99
```
## Shutting Down Test Harness ## Shutting Down Test Harness
The test harness should cleanly shutdown all the background apps in proper order after entering ^C. The test harness should cleanly shutdown all the background apps in proper order after entering ^C.

View file

@ -4,7 +4,7 @@ The Java based API runs on Linux and OSX.
## Mainnet ## Mainnet
To build from the source, clone the github repository found at `https://github.com/bisq-network/bisq`, To build from the source, clone the GitHub repository found at `https://github.com/bisq-network/bisq`,
and build with gradle: and build with gradle:
$ ./gradlew clean build $ ./gradlew clean build

View file

@ -0,0 +1,79 @@
#! /bin/bash
# This file must be sourced by the driver script.
source "$APITEST_SCRIPTS_HOME/trade-simulation-env.sh"
source "$APITEST_SCRIPTS_HOME/trade-simulation-utils.sh"
gencreatebsqswapoffercommand() {
PORT="$1"
CMD="$CLI_BASE --port=$PORT createoffer"
CMD+=" --swap=true"
CMD+=" --direction=$DIRECTION"
CMD+=" --amount=$AMOUNT"
CMD+=" --fixed-price=$FIXED_PRICE"
CMD+=" --currency-code=$CURRENCY_CODE"
echo "$CMD"
}
createbsqswapoffer() {
CREATE_OFFER_CMD="$1"
OFFER_DESC=$($CREATE_OFFER_CMD)
# If the CLI command exited with an error, print the CLI error, and
# return from this function now, passing the error status code to the caller.
commandalert $? "Could not create offer."
OFFER_DETAIL=$(echo -e "$OFFER_DESC" | sed -n '2p')
NEW_OFFER_ID=$(echo -e "$OFFER_DETAIL" | awk '{print $NF}')
echo "$NEW_OFFER_ID"
}
executebsqswap() {
# Bob list available BSQ offers. (If a v1 BSQ offer is picked this simulation will break.)
printdate "Bob looking at $DIRECTION $CURRENCY_CODE offers."
CMD="$CLI_BASE --port=$BOB_PORT getoffers --direction=$DIRECTION --currency-code=$CURRENCY_CODE"
printdate "BOB CLI: $CMD"
OFFERS=$($CMD)
exitoncommandalert $?
echo "$OFFERS"
printbreak
OFFER_ID=$(getfirstofferid "$BOB_PORT")
exitoncommandalert $?
printdate "First BSQ offer found: $OFFER_ID"
# Take Alice's BSQ swap offer.
CMD="$CLI_BASE --port=$BOB_PORT takeoffer --offer-id=$OFFER_ID"
printdate "BOB CLI: $CMD"
TRADE=$($CMD)
commandalert $? "Could not take BSQ swap offer."
# Print the takeoffer command's console output.
printdate "$TRADE"
printbreak
# Generate 1 btc block
printdate "Generating 1 btc block after BSQ swap execution."
genbtcblocks 1 2
printbreak
printdate "BSQ swap trade $OFFER_ID complete."
printbreak
printdate "Alice looking at her trade with id $OFFER_ID."
CMD="$CLI_BASE --port=$ALICE_PORT gettrade --trade-id=$OFFER_ID"
printdate "ALICE CLI: $CMD"
GETTRADE_CMD_OUTPUT=$(gettrade "$CMD")
exitoncommandalert $?
echo "$GETTRADE_CMD_OUTPUT"
printbreak
printdate "Bob looking at his trade with id $OFFER_ID."
CMD="$CLI_BASE --port=$BOB_PORT gettrade --trade-id=$OFFER_ID"
printdate "BOB CLI: $CMD"
GETTRADE_CMD_OUTPUT=$(gettrade "$CMD")
exitoncommandalert $?
echo "$GETTRADE_CMD_OUTPUT"
printbreak
}

View file

@ -0,0 +1,90 @@
#! /bin/bash
# Demonstrates a bsq <-> btc swap trade using the API CLI with a local regtest bitcoin node.
#
# Prerequisites:
#
# - Linux or OSX with bash, Java 11-15 (JDK language compatibility 11), and bitcoin-core (v0.19 - v22).
#
# - Bisq must be fully built with apitest dao setup files installed.
# Build command: `./gradlew clean build :apitest:installDaoSetup`
#
# - All supporting nodes must be run locally, in dev/dao/regtest mode:
# bitcoind, seednode, arbdaemon, alicedaemon, bobdaemon
#
# These should be run using the apitest harness. From the root project dir, run:
# `$ ./bisq-apitest --apiPassword=xyz --supportingApps=bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon --shutdownAfterTests=false`
#
# Usage:
#
# This script must be run from the root of the project, e.g.:
#
# `$ apitest/scripts/bsqswap-simulation.sh -d buy -f 0.00005 -a 0.125`
#
# Script options: -d <direction> -c <country-code> -f <fixed-price> -a <amount(btc)>
#
# Examples:
#
# Create and take a bsq swap offer to buy 0.05 btc at a fixed-price of 0.00005 bsq (per 1 btc):
#
# `$ apitest/scripts/bsqswap-simulation.sh -d buy -a 0.05 -f 0.00005`
#
# Create and take a bsq swap offer to buy 1 btc at a fixed-price of 0.00005 bsq (per 1 btc):
#
# `$ apitest/scripts/bsqswap-simulation.sh -d buy -a 1 -f 0.0005`
export APP_BASE_NAME=$(basename "$0")
export APP_HOME=$(pwd -P)
export APITEST_SCRIPTS_HOME="$APP_HOME/apitest/scripts"
source "$APITEST_SCRIPTS_HOME/trade-simulation-env.sh"
source "$APITEST_SCRIPTS_HOME/trade-simulation-utils.sh"
source "$APITEST_SCRIPTS_HOME/bsqswap-simulation-utils.sh"
checksetup
parsebsqswaporderopts "$@"
printdate "Started $APP_BASE_NAME with parameters:"
printbsqswapscriptparams
printbreak
# Alice creates a bsq swap offer.
printdate "Alice creating BSQ swap offer: $DIRECTION $AMOUNT BTC for BSQ at fixed price of $FIXED_PRICE BTC per 1 BSQ."
CMD=$(gencreatebsqswapoffercommand "$ALICE_PORT" "$ALICE_ACCT_ID")
printdate "ALICE CLI: $CMD"
OFFER_ID=$(createbsqswapoffer "$CMD")
exitoncommandalert $?
printdate "Alice created bsq swap offer with id: $OFFER_ID."
printbreak
sleeptraced 2
# Show Alice's new bsq swap offer.
printdate "Alice looking at her new $DIRECTION $CURRENCY_CODE offer."
CMD="$CLI_BASE --port=$ALICE_PORT getmyoffer --offer-id=$OFFER_ID"
printdate "ALICE CLI: $CMD"
OFFER=$($CMD)
exitoncommandalert $?
echo "$OFFER"
printbreak
sleeptraced 2
# Generate 1 btc block.
printdate "Generating 1 btc block after publishing Alice's offer."
genbtcblocks 1 1
printbreak
# Execute the BSQ swap.
executebsqswap
exitoncommandalert $?
printbreak
# Get balances after trade completion.
printdate "Bob & Alice's balances after BSQ swap trade."
printdate "ALICE CLI:"
printbalances "$ALICE_PORT"
printbreak
printdate "BOB CLI:"
printbalances "$BOB_PORT"
printbreak
exit 0

View file

@ -6,7 +6,7 @@
# #
# Prerequisites: # Prerequisites:
# #
# - Linux or OSX with bash, Java 10, or Java 11-12 (JDK language compatibility 10), and bitcoin-core (v0.19, v0.20, v0.21). # - Linux or OSX with bash, Java 11-15 (JDK language compatibility 11), and bitcoin-core (v0.19 - v22).
# #
# - Bisq must be fully built with apitest dao setup files installed. # - Bisq must be fully built with apitest dao setup files installed.
# Build command: `./gradlew clean build :apitest:installDaoSetup` # Build command: `./gradlew clean build :apitest:installDaoSetup`

View file

@ -10,7 +10,7 @@
# #
# Prerequisites: # Prerequisites:
# #
# - Linux or OSX with bash, Java 10, or Java 11-12 (JDK language compatibility 10), and bitcoin-core (v0.19, v0.20, v0.21). # - Linux or OSX with bash, Java 11-15 (JDK language compatibility 11), and bitcoin-core (v0.19 - v22).
# #
# - Bisq must be fully built with apitest dao setup files installed. # - Bisq must be fully built with apitest dao setup files installed.
# Build command: `./gradlew clean build :apitest:installDaoSetup` # Build command: `./gradlew clean build :apitest:installDaoSetup`

View file

@ -179,6 +179,41 @@ parselimitorderopts() {
fi fi
} }
parsebsqswaporderopts() {
usage() {
echo "Usage: $0 [-d buy|sell] [-f <fixed-price>] [-a <amount in btc>]" 1>&2
exit 1;
}
local OPTIND o d f a
while getopts "d:f:a:" o; do
case "${o}" in
d) d=$(echo "${OPTARG}" | tr '[:lower:]' '[:upper:]')
((d == "BUY" || d == "SELL")) || usage
export DIRECTION=${d}
;;
f) f=${OPTARG}
export FIXED_PRICE=${f}
;;
a) a=${OPTARG}
export AMOUNT=${a}
;;
*) usage ;;
esac
done
shift $((OPTIND-1))
if [ -z "${d}" ] || [ -z "${a}" ]; then
usage
fi
if [ -z "${f}" ] ; then
usage
fi
export CURRENCY_CODE="BSQ"
}
checkbitcoindrunning() { checkbitcoindrunning() {
# There may be a '+' char in the path and we have to escape it for pgrep. # There may be a '+' char in the path and we have to escape it for pgrep.
if [[ $APP_HOME == *"+"* ]]; then if [[ $APP_HOME == *"+"* ]]; then
@ -310,3 +345,9 @@ printscriptparams() {
echo " WAIT = $WAIT" echo " WAIT = $WAIT"
fi fi
} }
printbsqswapscriptparams() {
echo " DIRECTION = $DIRECTION"
echo " FIXED_PRICE = $FIXED_PRICE"
echo " AMOUNT = $AMOUNT"
}

View file

@ -457,11 +457,10 @@ delayconfirmpaymentreceived() {
printbreak printbreak
} }
# This is a large function that should be broken up if it ever makes sense to not treat a trade # This is a large function that might be split into smaller functions. But we are not testing
# execution simulation as an bsq swap operation. But we are not testing api methods here, just # api methods here, just demonstrating how to use them to get through the V1 trade protocol with
# demonstrating how to use them to get through the trade protocol. It should work for any trade # the CLI. It should work for any trade between Bob & Alice, as long as Alice is maker, Bob is
# between Bob & Alice, as long as Alice is maker, Bob is taker, and the offer to be taken is the # taker, and the offer to be taken is the first displayed in Bob's getoffers command output.
# first displayed in Bob's getoffers command output.
executetrade() { executetrade() {
# Bob list available offers. # Bob list available offers.
printdate "BOB $BOB_ROLE: Looking at $DIRECTION $CURRENCY_CODE offers." printdate "BOB $BOB_ROLE: Looking at $DIRECTION $CURRENCY_CODE offers."
@ -477,7 +476,7 @@ executetrade() {
printdate "First offer found: $OFFER_ID" printdate "First offer found: $OFFER_ID"
# Take Alice's offer. # Take Alice's offer.
CMD="$CLI_BASE --port=$BOB_PORT takeoffer --offer-id=$OFFER_ID --payment-account=$BOB_ACCT_ID --fee-currency=bsq" CMD="$CLI_BASE --port=$BOB_PORT takeoffer --offer-id=$OFFER_ID --payment-account=$BOB_ACCT_ID --fee-currency=BSQ"
printdate "BOB CLI: $CMD" printdate "BOB CLI: $CMD"
TRADE=$($CMD) TRADE=$($CMD)
commandalert $? "Could not take offer." commandalert $? "Could not take offer."

View file

@ -1,13 +1,13 @@
#! /bin/bash #! /bin/bash
# Runs fiat <-> btc trading scenarios using the API CLI with a local regtest bitcoin node. # Demonstrates a fiat <-> btc trade using the API CLI with a local regtest bitcoin node.
# #
# A country code argument is used to create a country based face to face payment account for the simulated # A country code argument is used to create a country based face to face payment account for the simulated
# trade, and the maker's face to face payment account's currency code is used when creating the offer. # trade, and the maker's face to face payment account's currency code is used when creating the offer.
# #
# Prerequisites: # Prerequisites:
# #
# - Linux or OSX with bash, Java 10, or Java 11-12 (JDK language compatibility 10), and bitcoin-core (v0.19, v0.20, or v0.21). # - Linux or OSX with bash, Java 11-15 (JDK language compatibility 11), and bitcoin-core (v0.19 - v22).
# #
# - Bisq must be fully built with apitest dao setup files installed. # - Bisq must be fully built with apitest dao setup files installed.
# Build command: `./gradlew clean build :apitest:installDaoSetup` # Build command: `./gradlew clean build :apitest:installDaoSetup`
@ -26,15 +26,16 @@
# #
# `$ apitest/scripts/trade-simulation.sh -d buy -c fr -m 3.00 -a 0.125` # `$ apitest/scripts/trade-simulation.sh -d buy -c fr -m 3.00 -a 0.125`
# #
# Script options: -d <direction> -c <country-code> -m <mkt-price-margin(%)> - f <fixed-price> -a <amount(btc)> # Script options: -d <direction> -c <country-code> -m <mkt-price-margin(%)> -f <fixed-price> -a <amount(btc)>
# #
# Examples: # Examples:
# #
# Create a buy/eur offer to buy 0.125 btc at a mkt-price-margin of 0%, using an Italy face to face payment account: # Create and take a buy/eur offer to buy 0.125 btc at a mkt-price-margin of 0%, using an Italy face to face
# payment account:
# #
# `$ apitest/scripts/trade-simulation.sh -d buy -c it -m 0.00 -a 0.125` # `$ apitest/scripts/trade-simulation.sh -d buy -c it -m 0.00 -a 0.125`
# #
# Create a sell/eur offer to sell 0.125 btc at a fixed-price of 38,000 euros, using a France face to face # Create and take a sell/eur offer to sell 0.125 btc at a fixed-price of 38,000 euros, using a France face to face
# payment account: # payment account:
# #
# `$ apitest/scripts/trade-simulation.sh -d sell -c fr -f 38000 -a 0.125` # `$ apitest/scripts/trade-simulation.sh -d sell -c fr -f 38000 -a 0.125`
@ -53,8 +54,6 @@ printdate "Started $APP_BASE_NAME with parameters:"
printscriptparams printscriptparams
printbreak printbreak
registerdisputeagents
# Demonstrate how to create a country based, face to face account. # Demonstrate how to create a country based, face to face account.
showcreatepaymentacctsteps "Alice" "$ALICE_PORT" showcreatepaymentacctsteps "Alice" "$ALICE_PORT"

View file

@ -48,7 +48,7 @@ import bisq.apitest.config.ApiTestConfig;
* *
* All method, scenario and end-to-end tests are found in the test sources folder. * All method, scenario and end-to-end tests are found in the test sources folder.
* *
* Requires bitcoind v0.19, v0.20, or v0.21. * Requires bitcoind v0.19 - v22.
*/ */
@Slf4j @Slf4j
public class ApiTestMain { public class ApiTestMain {

View file

@ -108,7 +108,7 @@ abstract class AbstractLinuxProcess implements LinuxProcess {
File bitcoindExecutable = Paths.get(config.bitcoinPath, "bitcoind").toFile(); File bitcoindExecutable = Paths.get(config.bitcoinPath, "bitcoind").toFile();
if (!bitcoindExecutable.exists() || !bitcoindExecutable.canExecute()) if (!bitcoindExecutable.exists() || !bitcoindExecutable.canExecute())
throw new IllegalStateException(format("'%s' cannot be found or executed.%n" throw new IllegalStateException(format("'%s' cannot be found or executed.%n"
+ "A bitcoin-core v0.19, v0.20, or v0.21 installation is required," + + "A bitcoin-core v0.19 - v22 installation is required," +
" and the 'bitcoinPath' must be configured in 'apitest.properties'", " and the 'bitcoinPath' must be configured in 'apitest.properties'",
bitcoindExecutable.getAbsolutePath())); bitcoindExecutable.getAbsolutePath()));

View file

@ -17,7 +17,6 @@
package bisq.apitest.method.offer; package bisq.apitest.method.offer;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.OfferInfo; import bisq.proto.grpc.OfferInfo;
import protobuf.PaymentAccount; import protobuf.PaymentAccount;
@ -42,10 +41,12 @@ import static bisq.apitest.config.BisqAppConfig.bobdaemon;
import static bisq.apitest.config.BisqAppConfig.seednode; import static bisq.apitest.config.BisqAppConfig.seednode;
import static bisq.cli.table.builder.TableType.OFFER_TBL; import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static bisq.common.util.MathUtils.exactMultiply; import static bisq.common.util.MathUtils.exactMultiply;
import static java.lang.System.out;
import bisq.apitest.method.MethodTest; import bisq.apitest.method.MethodTest;
import bisq.cli.CliMain;
import bisq.cli.table.builder.TableBuilder; import bisq.cli.table.builder.TableBuilder;
@Slf4j @Slf4j
@ -112,11 +113,6 @@ public abstract class AbstractOfferTest extends MethodTest {
protected final Function<List<OfferInfo>, String> toOffersTable = (offers) -> protected final Function<List<OfferInfo>, String> toOffersTable = (offers) ->
new TableBuilder(OFFER_TBL, offers).build().toString(); new TableBuilder(OFFER_TBL, offers).build().toString();
// TODO
protected final Function<BsqSwapOfferInfo, String> toBsqSwapOfferTable = (offer) ->
new TableBuilder(OFFER_TBL, offer).build().toString();
public static void initSwapPaymentAccounts() { public static void initSwapPaymentAccounts() {
// A bot may not know what the default 'BSQ Swap' account name is, // A bot may not know what the default 'BSQ Swap' account name is,
// but API test cases do: the value of the i18n property 'BSQ_SWAP'. // but API test cases do: the value of the i18n property 'BSQ_SWAP'.
@ -140,4 +136,11 @@ public abstract class AbstractOfferTest extends MethodTest {
public static void tearDown() { public static void tearDown() {
tearDownScaffold(); tearDownScaffold();
} }
protected static void runCliGetOffer(String offerId) {
out.println("Alice's CLI 'getmyoffer' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "getmyoffer", "--offer-id=" + offerId});
out.println("Bob's CLI 'getoffer' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9999", "getoffer", "--offer-id=" + offerId});
}
} }

View file

@ -17,7 +17,7 @@
package bisq.apitest.method.offer; package bisq.apitest.method.offer;
import bisq.proto.grpc.BsqSwapOfferInfo; import bisq.proto.grpc.OfferInfo;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -112,8 +112,7 @@ public class BsqSwapOfferTest extends AbstractOfferTest {
var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(), var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(),
1_000_000L, 1_000_000L,
1_000_000L, 1_000_000L,
"0.00005", "0.00005");
alicesBsqSwapAcct.getId());
log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer); log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer);
var newOfferId = bsqSwapOffer.getId(); var newOfferId = bsqSwapOffer.getId();
assertNotEquals("", newOfferId); assertNotEquals("", newOfferId);
@ -121,7 +120,6 @@ public class BsqSwapOfferTest extends AbstractOfferTest {
assertEquals(5_000, bsqSwapOffer.getPrice()); assertEquals(5_000, bsqSwapOffer.getPrice());
assertEquals(1_000_000L, bsqSwapOffer.getAmount()); assertEquals(1_000_000L, bsqSwapOffer.getAmount());
assertEquals(1_000_000L, bsqSwapOffer.getMinAmount()); assertEquals(1_000_000L, bsqSwapOffer.getMinAmount());
// assertEquals(alicesBsqAcct.getId(), atomicOffer.getMakerPaymentAccountId());
assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode()); assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode());
assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode()); assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode());
@ -129,13 +127,13 @@ public class BsqSwapOfferTest extends AbstractOfferTest {
testGetBsqSwapOffer(bsqSwapOffer); testGetBsqSwapOffer(bsqSwapOffer);
} }
private void testGetMyBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) { private void testGetMyBsqSwapOffer(OfferInfo bsqSwapOffer) {
int numFetchAttempts = 0; int numFetchAttempts = 0;
while (true) { while (true) {
try { try {
numFetchAttempts++; numFetchAttempts++;
var fetchedBsqSwapOffer = aliceClient.getMyBsqSwapOffer(bsqSwapOfferInfo.getId()); var fetchedBsqSwapOffer = aliceClient.getMyOffer(bsqSwapOffer.getId());
assertEquals(bsqSwapOfferInfo.getId(), fetchedBsqSwapOffer.getId()); assertEquals(bsqSwapOffer.getId(), fetchedBsqSwapOffer.getId());
log.debug("Alice found her (my) new bsq swap offer on attempt # {}.", numFetchAttempts); log.debug("Alice found her (my) new bsq swap offer on attempt # {}.", numFetchAttempts);
break; break;
} catch (Exception ex) { } catch (Exception ex) {
@ -149,13 +147,13 @@ public class BsqSwapOfferTest extends AbstractOfferTest {
} }
} }
private void testGetBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) { private void testGetBsqSwapOffer(OfferInfo bsqSwapOffer) {
int numFetchAttempts = 0; int numFetchAttempts = 0;
while (true) { while (true) {
try { try {
numFetchAttempts++; numFetchAttempts++;
var fetchedBsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOfferInfo.getId()); var fetchedBsqSwapOffer = bobClient.getOffer(bsqSwapOffer.getId());
assertEquals(bsqSwapOfferInfo.getId(), fetchedBsqSwapOffer.getId()); assertEquals(bsqSwapOffer.getId(), fetchedBsqSwapOffer.getId());
log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts); log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts);
break; break;
} catch (Exception ex) { } catch (Exception ex) {

View file

@ -38,15 +38,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ; import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.apitest.config.ApiTestConfig.EUR; import static bisq.apitest.config.ApiTestConfig.EUR;
import static bisq.apitest.config.ApiTestConfig.USD; import static bisq.apitest.config.ApiTestConfig.USD;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent; import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.proto.grpc.EditOfferRequest.EditType.*; import static bisq.proto.grpc.EditOfferRequest.EditType.*;
import static java.lang.String.format; import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static protobuf.OfferDirection.BUY; import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL; import static protobuf.OfferDirection.SELL;
@ -70,22 +68,22 @@ public class EditOfferTest extends AbstractOfferTest {
paymentAcct.getId(), paymentAcct.getId(),
0.0, 0.0,
NO_TRIGGER_PRICE); NO_TRIGGER_PRICE);
log.debug("ORIGINAL EUR OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original EUR offer:\n{}", toOfferTable.apply(originalOffer));
assertFalse(originalOffer.getIsActivated()); // Not activated until prep is done. assertFalse(originalOffer.getIsActivated()); // Not activated until prep is done.
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId()); originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertTrue(originalOffer.getIsActivated()); assertTrue(originalOffer.getIsActivated());
// Disable offer // Disable offer
aliceClient.editOfferActivationState(originalOffer.getId(), DEACTIVATE_OFFER); aliceClient.editOfferActivationState(originalOffer.getId(), DEACTIVATE_OFFER);
genBtcBlocksThenWait(1, 1500); // Wait for offer book removal. genBtcBlocksThenWait(1, 1500); // Wait for offer book removal.
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED EUR OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited EUR offer:\n{}", toOfferTable.apply(editedOffer));
assertFalse(editedOffer.getIsActivated()); assertFalse(editedOffer.getIsActivated());
// Re-enable offer // Re-enable offer
aliceClient.editOfferActivationState(editedOffer.getId(), ACTIVATE_OFFER); aliceClient.editOfferActivationState(editedOffer.getId(), ACTIVATE_OFFER);
genBtcBlocksThenWait(1, 1500); // Wait for offer book re-entry. genBtcBlocksThenWait(1, 1500); // Wait for offer book re-entry.
editedOffer = aliceClient.getMyOffer(originalOffer.getId()); editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED EUR OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited EUR offer:\n{}", toOfferTable.apply(editedOffer));
assertTrue(editedOffer.getIsActivated()); assertTrue(editedOffer.getIsActivated());
doSanityCheck(originalOffer, editedOffer); doSanityCheck(originalOffer, editedOffer);
@ -100,8 +98,8 @@ public class EditOfferTest extends AbstractOfferTest {
paymentAcct.getId(), paymentAcct.getId(),
0.0, 0.0,
NO_TRIGGER_PRICE); NO_TRIGGER_PRICE);
log.debug("ORIGINAL EUR OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original EUR offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId()); originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertEquals(0 /*no trigger price*/, originalOffer.getTriggerPrice()); assertEquals(0 /*no trigger price*/, originalOffer.getTriggerPrice());
@ -111,9 +109,9 @@ public class EditOfferTest extends AbstractOfferTest {
var newTriggerPriceAsLong = calcPriceAsLong.apply(mktPrice, delta); var newTriggerPriceAsLong = calcPriceAsLong.apply(mktPrice, delta);
aliceClient.editOfferTriggerPrice(originalOffer.getId(), newTriggerPriceAsLong); aliceClient.editOfferTriggerPrice(originalOffer.getId(), newTriggerPriceAsLong);
sleep(2500); // Wait for offer book re-entry. sleep(2_500); // Wait for offer book re-entry.
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED EUR OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited EUR offer:\n{}", toOfferTable.apply(editedOffer));
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice()); assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
assertTrue(editedOffer.getUseMarketBasedPrice()); assertTrue(editedOffer.getUseMarketBasedPrice());
@ -124,13 +122,13 @@ public class EditOfferTest extends AbstractOfferTest {
@Order(3) @Order(3)
public void testSetTriggerPriceToNegativeValueShouldThrowException() { public void testSetTriggerPriceToNegativeValueShouldThrowException() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("FI"); PaymentAccount paymentAcct = getOrCreatePaymentAccount("FI");
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(), var originalOffer = createMktPricedOfferForEdit(SELL.name(),
EUR, EUR,
paymentAcct.getId(), paymentAcct.getId(),
0.0, 0.0,
NO_TRIGGER_PRICE); NO_TRIGGER_PRICE);
log.debug("ORIGINAL EUR OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original EUR offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
// Edit the offer's trigger price, set to -1, check error. // Edit the offer's trigger price, set to -1, check error.
Throwable exception = assertThrows(StatusRuntimeException.class, () -> Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.editOfferTriggerPrice(originalOffer.getId(), -1L)); aliceClient.editOfferTriggerPrice(originalOffer.getId(), -1L));
@ -145,19 +143,19 @@ public class EditOfferTest extends AbstractOfferTest {
public void testEditMktPriceMargin() { public void testEditMktPriceMargin() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US"); PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
var originalMktPriceMargin = new BigDecimal("0.1").doubleValue(); var originalMktPriceMargin = new BigDecimal("0.1").doubleValue();
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(), var originalOffer = createMktPricedOfferForEdit(SELL.name(),
USD, USD,
paymentAcct.getId(), paymentAcct.getId(),
originalMktPriceMargin, originalMktPriceMargin,
NO_TRIGGER_PRICE); NO_TRIGGER_PRICE);
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original USD offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin()); assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
// Edit the offer's price margin, nothing else. // Edit the offer's price margin, nothing else.
var newMktPriceMargin = new BigDecimal("0.5").doubleValue(); var newMktPriceMargin = new BigDecimal("0.5").doubleValue();
aliceClient.editOfferPriceMargin(originalOffer.getId(), newMktPriceMargin); aliceClient.editOfferPriceMargin(originalOffer.getId(), newMktPriceMargin);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED USD OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited USD offer:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin()); assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
doSanityCheck(originalOffer, editedOffer); doSanityCheck(originalOffer, editedOffer);
@ -169,19 +167,19 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU"); PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU");
double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE); double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE);
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000); String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000);
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(), var originalOffer = createFixedPricedOfferForEdit(BUY.name(),
RUBLE, RUBLE,
paymentAcct.getId(), paymentAcct.getId(),
fixedPriceAsString); fixedPriceAsString);
log.debug("ORIGINAL RUB OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original RUB offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
// Edit the offer's fixed price, nothing else. // Edit the offer's fixed price, nothing else.
String editedFixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 100_000.0000); String editedFixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 100_000.0000);
aliceClient.editOfferFixedPrice(originalOffer.getId(), editedFixedPriceAsString); aliceClient.editOfferFixedPrice(originalOffer.getId(), editedFixedPriceAsString);
// Wait for edited offer to be removed from offer-book, edited, and re-published. // Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500); genBtcBlocksThenWait(1, 2_500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED RUB OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited RUB offer:\n{}", toOfferTable.apply(editedOffer));
var expectedNewFixedPrice = scaledUpFiatOfferPrice.apply(new BigDecimal(editedFixedPriceAsString)); var expectedNewFixedPrice = scaledUpFiatOfferPrice.apply(new BigDecimal(editedFixedPriceAsString));
assertEquals(expectedNewFixedPrice, editedOffer.getPrice()); assertEquals(expectedNewFixedPrice, editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice()); assertFalse(editedOffer.getUseMarketBasedPrice());
@ -195,12 +193,12 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU"); PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU");
double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE); double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE);
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000); String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000);
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(), var originalOffer = createFixedPricedOfferForEdit(BUY.name(),
RUBLE, RUBLE,
paymentAcct.getId(), paymentAcct.getId(),
fixedPriceAsString); fixedPriceAsString);
log.debug("ORIGINAL RUB OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original RUB offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
// Edit the offer's fixed price and deactivate it. // Edit the offer's fixed price and deactivate it.
String editedFixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 100_000.0000); String editedFixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 100_000.0000);
aliceClient.editOffer(originalOffer.getId(), aliceClient.editOffer(originalOffer.getId(),
@ -211,9 +209,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER, DEACTIVATE_OFFER,
FIXED_PRICE_AND_ACTIVATION_STATE); FIXED_PRICE_AND_ACTIVATION_STATE);
// Wait for edited offer to be removed from offer-book, edited, and re-published. // Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500); genBtcBlocksThenWait(1, 2_500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED RUB OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited RUB offer:\n{}", toOfferTable.apply(editedOffer));
var expectedNewFixedPrice = scaledUpFiatOfferPrice.apply(new BigDecimal(editedFixedPriceAsString)); var expectedNewFixedPrice = scaledUpFiatOfferPrice.apply(new BigDecimal(editedFixedPriceAsString));
assertEquals(expectedNewFixedPrice, editedOffer.getPrice()); assertEquals(expectedNewFixedPrice, editedOffer.getPrice());
assertFalse(editedOffer.getIsActivated()); assertFalse(editedOffer.getIsActivated());
@ -232,8 +230,8 @@ public class EditOfferTest extends AbstractOfferTest {
paymentAcct.getId(), paymentAcct.getId(),
originalMktPriceMargin, originalMktPriceMargin,
0); 0);
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original USD offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId()); originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin()); assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
@ -247,9 +245,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER, DEACTIVATE_OFFER,
MKT_PRICE_MARGIN_AND_ACTIVATION_STATE); MKT_PRICE_MARGIN_AND_ACTIVATION_STATE);
// Wait for edited offer to be removed from offer-book, edited, and re-published. // Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500); genBtcBlocksThenWait(1, 2_500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED USD OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited USD offer:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin()); assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
assertEquals(0, editedOffer.getTriggerPrice()); assertEquals(0, editedOffer.getTriggerPrice());
assertFalse(editedOffer.getIsActivated()); assertFalse(editedOffer.getIsActivated());
@ -261,18 +259,16 @@ public class EditOfferTest extends AbstractOfferTest {
@Order(8) @Order(8)
public void testEditMktPriceMarginAndTriggerPriceAndDeactivation() { public void testEditMktPriceMarginAndTriggerPriceAndDeactivation() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US"); PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
var originalMktPriceMargin = new BigDecimal("0.0").doubleValue(); var originalMktPriceMargin = new BigDecimal("0.0").doubleValue();
var mktPriceAsDouble = aliceClient.getBtcPrice(USD); var mktPriceAsDouble = aliceClient.getBtcPrice(USD);
var originalTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, -5_000.0000); var originalTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, -5_000.0000);
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(), OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
USD, USD,
paymentAcct.getId(), paymentAcct.getId(),
originalMktPriceMargin, originalMktPriceMargin,
originalTriggerPriceAsLong); originalTriggerPriceAsLong);
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original USD offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId()); originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin()); assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
assertEquals(originalTriggerPriceAsLong, originalOffer.getTriggerPrice()); assertEquals(originalTriggerPriceAsLong, originalOffer.getTriggerPrice());
@ -288,9 +284,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER, DEACTIVATE_OFFER,
MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE); MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE);
// Wait for edited offer to be removed from offer-book, edited, and re-published. // Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500); genBtcBlocksThenWait(1, 2_500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED USD OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited USD offer:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin()); assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice()); assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
assertFalse(editedOffer.getIsActivated()); assertFalse(editedOffer.getIsActivated());
@ -303,13 +299,13 @@ public class EditOfferTest extends AbstractOfferTest {
public void testEditingFixedPriceInMktPriceMarginBasedOfferShouldThrowException() { public void testEditingFixedPriceInMktPriceMarginBasedOfferShouldThrowException() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US"); PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
var originalMktPriceMargin = new BigDecimal("0.0").doubleValue(); var originalMktPriceMargin = new BigDecimal("0.0").doubleValue();
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(), var originalOffer = createMktPricedOfferForEdit(SELL.name(),
USD, USD,
paymentAcct.getId(), paymentAcct.getId(),
originalMktPriceMargin, originalMktPriceMargin,
NO_TRIGGER_PRICE); NO_TRIGGER_PRICE);
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original USD offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
// Try to edit both the fixed price and mkt price margin. // Try to edit both the fixed price and mkt price margin.
var newMktPriceMargin = new BigDecimal("0.25").doubleValue(); var newMktPriceMargin = new BigDecimal("0.25").doubleValue();
var newFixedPrice = "50000.0000"; var newFixedPrice = "50000.0000";
@ -335,12 +331,12 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU"); PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU");
double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE); double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE);
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000); String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000);
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(), var originalOffer = createFixedPricedOfferForEdit(BUY.name(),
RUBLE, RUBLE,
paymentAcct.getId(), paymentAcct.getId(),
fixedPriceAsString); fixedPriceAsString);
log.debug("ORIGINAL RUB OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original RUB offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
long newTriggerPrice = 1000000L; long newTriggerPrice = 1000000L;
Throwable exception = assertThrows(StatusRuntimeException.class, () -> Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.editOfferTriggerPrice(originalOffer.getId(), newTriggerPrice)); aliceClient.editOfferTriggerPrice(originalOffer.getId(), newTriggerPrice));
@ -358,12 +354,12 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("MX"); PaymentAccount paymentAcct = getOrCreatePaymentAccount("MX");
double mktPriceAsDouble = aliceClient.getBtcPrice("MXN"); double mktPriceAsDouble = aliceClient.getBtcPrice("MXN");
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 0.00); String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 0.00);
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(), var originalOffer = createFixedPricedOfferForEdit(BUY.name(),
"MXN", "MXN",
paymentAcct.getId(), paymentAcct.getId(),
fixedPriceAsString); fixedPriceAsString);
log.debug("ORIGINAL MXN OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original MXN offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
// Change the offer to mkt price based and set a trigger price. // Change the offer to mkt price based and set a trigger price.
var newMktPriceMargin = new BigDecimal("0.05").doubleValue(); var newMktPriceMargin = new BigDecimal("0.05").doubleValue();
@ -377,9 +373,9 @@ public class EditOfferTest extends AbstractOfferTest {
ACTIVATE_OFFER, ACTIVATE_OFFER,
MKT_PRICE_MARGIN_AND_TRIGGER_PRICE); MKT_PRICE_MARGIN_AND_TRIGGER_PRICE);
// Wait for edited offer to be removed from offer-book, edited, and re-published. // Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500); genBtcBlocksThenWait(1, 2_500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED MXN OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited MXN offer:\n{}", toOfferTable.apply(editedOffer));
assertTrue(editedOffer.getUseMarketBasedPrice()); assertTrue(editedOffer.getUseMarketBasedPrice());
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin()); assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice()); assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
@ -396,13 +392,13 @@ public class EditOfferTest extends AbstractOfferTest {
var originalMktPriceMargin = new BigDecimal("0.25").doubleValue(); var originalMktPriceMargin = new BigDecimal("0.25").doubleValue();
var delta = 1_000.0000; // trigger price on sell offer is 1K below mkt price var delta = 1_000.0000; // trigger price on sell offer is 1K below mkt price
var originalTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, delta); var originalTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, delta);
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(), var originalOffer = createMktPricedOfferForEdit(SELL.name(),
"GBP", "GBP",
paymentAcct.getId(), paymentAcct.getId(),
originalMktPriceMargin, originalMktPriceMargin,
originalTriggerPriceAsLong); originalTriggerPriceAsLong);
log.debug("ORIGINAL GBP OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original GBP offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 0.00); String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 0.00);
aliceClient.editOffer(originalOffer.getId(), aliceClient.editOffer(originalOffer.getId(),
@ -413,9 +409,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER, DEACTIVATE_OFFER,
FIXED_PRICE_AND_ACTIVATION_STATE); FIXED_PRICE_AND_ACTIVATION_STATE);
// Wait for edited offer to be removed from offer-book, edited, and re-published. // Wait for edited offer to be removed from offer-book, edited, and re-published.
genBtcBlocksThenWait(1, 2500); genBtcBlocksThenWait(1, 2_500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED GBP OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited GBP offer:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledUpFiatOfferPrice.apply(new BigDecimal(fixedPriceAsString)), editedOffer.getPrice()); assertEquals(scaledUpFiatOfferPrice.apply(new BigDecimal(fixedPriceAsString)), editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice()); assertFalse(editedOffer.getUseMarketBasedPrice());
assertEquals(0.00, editedOffer.getMarketPriceMargin()); assertEquals(0.00, editedOffer.getMarketPriceMargin());
@ -426,7 +422,7 @@ public class EditOfferTest extends AbstractOfferTest {
@Test @Test
@Order(13) @Order(13)
public void testChangeFixedPricedBsqOfferToPriceMarginBasedOfferShouldThrowException() { public void testChangeFixedPricedBsqOfferToPriceMarginBasedOfferShouldThrowException() {
OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(), var originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ, BSQ,
100_000_000L, 100_000_000L,
100_000_000L, 100_000_000L,
@ -434,8 +430,8 @@ public class EditOfferTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(), getDefaultBuyerSecurityDepositAsPercent(),
alicesLegacyBsqAcct.getId(), alicesLegacyBsqAcct.getId(),
BSQ); BSQ);
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original BSQ offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
Throwable exception = assertThrows(StatusRuntimeException.class, () -> Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.editOffer(originalOffer.getId(), aliceClient.editOffer(originalOffer.getId(),
"0.00", "0.00",
@ -453,7 +449,7 @@ public class EditOfferTest extends AbstractOfferTest {
@Test @Test
@Order(14) @Order(14)
public void testEditTriggerPriceOnFixedPriceBsqOfferShouldThrowException() { public void testEditTriggerPriceOnFixedPriceBsqOfferShouldThrowException() {
OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(), var originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ, BSQ,
100_000_000L, 100_000_000L,
100_000_000L, 100_000_000L,
@ -461,8 +457,8 @@ public class EditOfferTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(), getDefaultBuyerSecurityDepositAsPercent(),
alicesLegacyBsqAcct.getId(), alicesLegacyBsqAcct.getId(),
BSQ); BSQ);
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original BSQ offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
var newTriggerPriceAsLong = calcPriceAsLong.apply(0.00005, 0.00); var newTriggerPriceAsLong = calcPriceAsLong.apply(0.00005, 0.00);
Throwable exception = assertThrows(StatusRuntimeException.class, () -> Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.editOffer(originalOffer.getId(), aliceClient.editOffer(originalOffer.getId(),
@ -482,7 +478,7 @@ public class EditOfferTest extends AbstractOfferTest {
@Order(15) @Order(15)
public void testEditFixedPriceOnBsqOffer() { public void testEditFixedPriceOnBsqOffer() {
String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
final OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(), var originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ, BSQ,
100_000_000L, 100_000_000L,
100_000_000L, 100_000_000L,
@ -490,8 +486,8 @@ public class EditOfferTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(), getDefaultBuyerSecurityDepositAsPercent(),
alicesLegacyBsqAcct.getId(), alicesLegacyBsqAcct.getId(),
BSQ); BSQ);
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original BSQ offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
String newFixedPriceAsString = "0.00003111"; String newFixedPriceAsString = "0.00003111";
aliceClient.editOffer(originalOffer.getId(), aliceClient.editOffer(originalOffer.getId(),
newFixedPriceAsString, newFixedPriceAsString,
@ -501,9 +497,9 @@ public class EditOfferTest extends AbstractOfferTest {
ACTIVATE_OFFER, ACTIVATE_OFFER,
FIXED_PRICE_ONLY); FIXED_PRICE_ONLY);
// Wait for edited offer to be edited and removed from offer-book. // Wait for edited offer to be edited and removed from offer-book.
genBtcBlocksThenWait(1, 2500); genBtcBlocksThenWait(1, 2_500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED BSQ OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited BSQ offer:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledUpAltcoinOfferPrice.apply(newFixedPriceAsString), editedOffer.getPrice()); assertEquals(scaledUpAltcoinOfferPrice.apply(newFixedPriceAsString), editedOffer.getPrice());
assertTrue(editedOffer.getIsActivated()); assertTrue(editedOffer.getIsActivated());
assertFalse(editedOffer.getUseMarketBasedPrice()); assertFalse(editedOffer.getUseMarketBasedPrice());
@ -515,7 +511,7 @@ public class EditOfferTest extends AbstractOfferTest {
@Order(16) @Order(16)
public void testDisableBsqOffer() { public void testDisableBsqOffer() {
String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
final OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(), var originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ, BSQ,
100_000_000L, 100_000_000L,
100_000_000L, 100_000_000L,
@ -523,8 +519,8 @@ public class EditOfferTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(), getDefaultBuyerSecurityDepositAsPercent(),
alicesLegacyBsqAcct.getId(), alicesLegacyBsqAcct.getId(),
BSQ); BSQ);
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original BSQ offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
aliceClient.editOffer(originalOffer.getId(), aliceClient.editOffer(originalOffer.getId(),
fixedPriceAsString, fixedPriceAsString,
false, false,
@ -533,9 +529,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER, DEACTIVATE_OFFER,
ACTIVATION_STATE_ONLY); ACTIVATION_STATE_ONLY);
// Wait for edited offer to be removed from offer-book. // Wait for edited offer to be removed from offer-book.
genBtcBlocksThenWait(1, 2500); genBtcBlocksThenWait(1, 2_500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED BSQ OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited BSQ offer:\n{}", toOfferTable.apply(editedOffer));
assertFalse(editedOffer.getIsActivated()); assertFalse(editedOffer.getIsActivated());
assertEquals(scaledUpAltcoinOfferPrice.apply(fixedPriceAsString), editedOffer.getPrice()); assertEquals(scaledUpAltcoinOfferPrice.apply(fixedPriceAsString), editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice()); assertFalse(editedOffer.getUseMarketBasedPrice());
@ -547,7 +543,7 @@ public class EditOfferTest extends AbstractOfferTest {
@Order(17) @Order(17)
public void testEditFixedPriceAndDisableBsqOffer() { public void testEditFixedPriceAndDisableBsqOffer() {
String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
final OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(), var originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ, BSQ,
100_000_000L, 100_000_000L,
100_000_000L, 100_000_000L,
@ -555,8 +551,8 @@ public class EditOfferTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(), getDefaultBuyerSecurityDepositAsPercent(),
alicesLegacyBsqAcct.getId(), alicesLegacyBsqAcct.getId(),
BSQ); BSQ);
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer)); log.debug("Original BSQ offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry. genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
String newFixedPriceAsString = "0.000045"; String newFixedPriceAsString = "0.000045";
aliceClient.editOffer(originalOffer.getId(), aliceClient.editOffer(originalOffer.getId(),
newFixedPriceAsString, newFixedPriceAsString,
@ -566,9 +562,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER, DEACTIVATE_OFFER,
FIXED_PRICE_AND_ACTIVATION_STATE); FIXED_PRICE_AND_ACTIVATION_STATE);
// Wait for edited offer to be edited and removed from offer-book. // Wait for edited offer to be edited and removed from offer-book.
genBtcBlocksThenWait(1, 2500); genBtcBlocksThenWait(1, 2_500);
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId()); OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
log.debug("EDITED BSQ OFFER:\n{}", toOfferTable.apply(editedOffer)); log.debug("Edited BSQ offer:\n{}", toOfferTable.apply(editedOffer));
assertFalse(editedOffer.getIsActivated()); assertFalse(editedOffer.getIsActivated());
assertEquals(scaledUpAltcoinOfferPrice.apply(newFixedPriceAsString), editedOffer.getPrice()); assertEquals(scaledUpAltcoinOfferPrice.apply(newFixedPriceAsString), editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice()); assertFalse(editedOffer.getUseMarketBasedPrice());
@ -576,6 +572,39 @@ public class EditOfferTest extends AbstractOfferTest {
assertEquals(0, editedOffer.getTriggerPrice()); assertEquals(0, editedOffer.getTriggerPrice());
} }
@Test
@Order(18)
public void testEditBsqSwapOfferShouldThrowException() {
var originalOffer = aliceClient.createBsqSwapOffer(SELL.name(),
1_250_000L,
750_000L,
"0.00005");
log.debug("BsqSwap Buy BSQ (Buy BTC) offer:\n{}", originalOffer);
var newOfferId = originalOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(SELL.name(), originalOffer.getDirection());
assertEquals(5_000, originalOffer.getPrice());
assertEquals(1_250_000L, originalOffer.getAmount());
assertEquals(750_000L, originalOffer.getMinAmount());
assertEquals(BSQ, originalOffer.getBaseCurrencyCode());
assertEquals(BTC, originalOffer.getCounterCurrencyCode());
log.debug("Original BsqSwap offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
var newFixedPrice = "0.000055";
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.editOffer(originalOffer.getId(),
newFixedPrice,
false,
0.0,
0,
ACTIVATE_OFFER,
TRIGGER_PRICE_ONLY));
String expectedExceptionMessage = format("UNKNOWN: cannot edit bsq swap offer with id '%s'",
originalOffer.getId());
assertEquals(expectedExceptionMessage, exception.getMessage());
}
private OfferInfo createMktPricedOfferForEdit(String direction, private OfferInfo createMktPricedOfferForEdit(String direction,
String currencyCode, String currencyCode,
String paymentAccountId, String paymentAccountId,

View file

@ -21,11 +21,13 @@ import static bisq.core.trade.model.bisq_v1.Trade.State.BUYER_SAW_ARRIVED_FIAT_P
import static bisq.core.trade.model.bisq_v1.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN; import static bisq.core.trade.model.bisq_v1.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG; import static bisq.core.trade.model.bisq_v1.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
import static java.lang.String.format; import static java.lang.String.format;
import static java.lang.System.out;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import bisq.apitest.method.offer.AbstractOfferTest; import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.CliMain;
import bisq.cli.GrpcClient; import bisq.cli.GrpcClient;
import bisq.cli.table.builder.TableBuilder; import bisq.cli.table.builder.TableBuilder;
@ -37,7 +39,9 @@ public class AbstractTradeTest extends AbstractOfferTest {
protected static String tradeId; protected static String tradeId;
protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2; protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2;
private final Function<GrpcClient, String> toUserName = (client) -> client.equals(aliceClient) ? "Alice" : "Bob"; protected final Function<TradeInfo, String> toTradeDetailTable = (trade) ->
new TableBuilder(TRADE_DETAIL_TBL, trade).build().toString();
protected final Function<GrpcClient, String> toUserName = (client) -> client.equals(aliceClient) ? "Alice" : "Bob";
@BeforeAll @BeforeAll
public static void initStaticFixtures() { public static void initStaticFixtures() {
@ -241,4 +245,11 @@ public class AbstractTradeTest extends AbstractOfferTest {
new TableBuilder(TRADE_DETAIL_TBL, trade).build())); new TableBuilder(TRADE_DETAIL_TBL, trade).build()));
} }
} }
protected static void runCliGetTrade(String tradeId) {
out.println("Alice's CLI 'gettrade' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "gettrade", "--trade-id=" + tradeId});
out.println("Bob's CLI 'gettrade' response:");
CliMain.main(new String[]{"--password=xyz", "--port=9999", "gettrade", "--trade-id=" + tradeId});
}
} }

View file

@ -17,8 +17,8 @@
package bisq.apitest.method.trade; package bisq.apitest.method.trade;
import bisq.proto.grpc.BsqSwapOfferInfo; import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.BsqSwapTradeInfo; import bisq.proto.grpc.TradeInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -36,9 +36,11 @@ import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ; import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC; import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory.BSQ_SWAP;
import static java.lang.String.format; import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static protobuf.BsqSwapTrade.State.COMPLETED; import static protobuf.BsqSwapTrade.State.COMPLETED;
import static protobuf.BsqSwapTrade.State.PREPARATION; import static protobuf.BsqSwapTrade.State.PREPARATION;
@ -47,13 +49,12 @@ import static protobuf.OfferDirection.BUY;
import bisq.apitest.method.offer.AbstractOfferTest; import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.GrpcClient;
@Disabled @Disabled
@Slf4j @Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BsqSwapTradeTest extends AbstractOfferTest { public class BsqSwapTradeTest extends AbstractTradeTest {
private static final String BISQ_FEE_CURRENCY_CODE = BSQ;
// Long-running swap trade tests might want to check node logs for exceptions. // Long-running swap trade tests might want to check node logs for exceptions.
@Setter @Setter
@ -66,7 +67,7 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
@BeforeEach @BeforeEach
public void generateBtcBlock() { public void generateBtcBlock() {
genBtcBlocksThenWait(1, 2000); genBtcBlocksThenWait(1, 2_000);
} }
@Test @Test
@ -81,54 +82,91 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
@Test @Test
@Order(2) @Order(2)
public void testAliceCreateBsqSwapBuyOffer() { public void testAliceCreateBsqSwapBuyOffer() {
var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(), var mySwapOffer = aliceClient.createBsqSwapOffer(BUY.name(),
1_000_000L, // 0.01 BTC
1_000_000L, 1_000_000L,
1_000_000L, "0.00005");
"0.00005", log.debug("Pending BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", toOfferTable.apply(mySwapOffer));
alicesBsqSwapAcct.getId()); var newOfferId = mySwapOffer.getId();
log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer);
var newOfferId = bsqSwapOffer.getId();
assertNotEquals("", newOfferId); assertNotEquals("", newOfferId);
assertEquals(BUY.name(), bsqSwapOffer.getDirection()); assertEquals(BUY.name(), mySwapOffer.getDirection());
assertEquals(5_000, bsqSwapOffer.getPrice()); assertEquals(5_000, mySwapOffer.getPrice());
assertEquals(1_000_000L, bsqSwapOffer.getAmount()); assertEquals(1_000_000L, mySwapOffer.getAmount());
assertEquals(1_000_000L, bsqSwapOffer.getMinAmount()); assertEquals(1_000_000L, mySwapOffer.getMinAmount());
// assertEquals(alicesBsqAcct.getId(), atomicOffer.getMakerPaymentAccountId()); assertEquals(BSQ, mySwapOffer.getBaseCurrencyCode());
assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode()); assertEquals(BTC, mySwapOffer.getCounterCurrencyCode());
assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode());
genBtcBlocksThenWait(1, 2_500);
mySwapOffer = aliceClient.getMyOffer(newOfferId);
log.debug("My fetched BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", toOfferTable.apply(mySwapOffer));
assertNotEquals(0, mySwapOffer.getMakerFee());
runCliGetOffer(newOfferId);
} }
@Test @Test
@Order(3) @Order(3)
public void testBobTakesBsqSwapOffer() { public void testBobTakesBsqSwapOffer() {
var bsqSwapOffer = getAvailableBsqSwapOffer(); var availableSwapOffer = getAvailableBsqSwapOffer(bobClient);
var bsqSwapTradeInfo = bobClient.takeBsqSwapOffer(bsqSwapOffer.getId(),
bobsBsqSwapAcct.getId(), // Before sending a TakeOfferRequest, the CLI needs to know what kind of Offer
BISQ_FEE_CURRENCY_CODE); // it is taking (v1 or BsqSwap). Only BSQ swap offers can be taken with a
log.debug("Trade at t1: {}", bsqSwapTradeInfo); // single offerId parameter. Taking v1 offers requires an additional
assertEquals(PREPARATION.name(), bsqSwapTradeInfo.getState()); // paymentAccountId param. The test case knows what kind of offer is being taken,
// but we test the gRPC GetOfferCategory service here.
var availableOfferCategory = bobClient.getAvailableOfferCategory(availableSwapOffer.getId());
assertTrue(availableOfferCategory.equals(BSQ_SWAP));
sleep(30_000);
var swapTrade = bobClient.takeBsqSwapOffer(availableSwapOffer.getId());
tradeId = swapTrade.getTradeId(); // Cache the tradeId for following test case(s).
log.debug("BsqSwap Trade at PREPARATION:\n{}", toTradeDetailTable.apply(swapTrade));
assertEquals(PREPARATION.name(), swapTrade.getState());
genBtcBlocksThenWait(1, 3_000); genBtcBlocksThenWait(1, 3_000);
bsqSwapTradeInfo = getBsqSwapTrade(bsqSwapTradeInfo.getTradeId()); swapTrade = getBsqSwapTrade(bobClient, tradeId);
log.debug("Trade at t2: {}", bsqSwapTradeInfo); log.debug("BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(swapTrade));
assertEquals(COMPLETED.name(), bsqSwapTradeInfo.getState()); assertEquals(COMPLETED.name(), swapTrade.getState());
runCliGetTrade(tradeId);
} }
@Test @Test
@Order(4) @Order(4)
public void testCompletedSwapTxConfirmations() {
sleep(2_000); // Wait for TX confirmation to happen on node.
var alicesTrade = getBsqSwapTrade(aliceClient, tradeId);
log.debug("Alice's BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(alicesTrade));
assertEquals(1, alicesTrade.getBsqSwapTradeInfo().getNumConfirmations());
var bobsTrade = getBsqSwapTrade(bobClient, tradeId);
log.debug("Bob's BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(bobsTrade));
assertEquals(1, bobsTrade.getBsqSwapTradeInfo().getNumConfirmations());
genBtcBlocksThenWait(1, 2_000);
bobsTrade = getBsqSwapTrade(bobClient, tradeId);
log.debug("Bob's BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(bobsTrade));
assertEquals(2, bobsTrade.getBsqSwapTradeInfo().getNumConfirmations());
}
@Test
@Order(5)
public void testGetBalancesAfterTrade() { public void testGetBalancesAfterTrade() {
sleep(2_500); // Give wallet time to finish processing TX.
var alicesBalances = aliceClient.getBalances(); var alicesBalances = aliceClient.getBalances();
log.debug("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances)); log.debug("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances(); var bobsBalances = bobClient.getBalances();
log.debug("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances)); log.debug("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
} }
private BsqSwapOfferInfo getAvailableBsqSwapOffer() { private OfferInfo getAvailableBsqSwapOffer(GrpcClient client) {
List<BsqSwapOfferInfo> bsqSwapOffers = new ArrayList<>(); List<OfferInfo> bsqSwapOffers = new ArrayList<>();
int numFetchAttempts = 0; int numFetchAttempts = 0;
while (bsqSwapOffers.size() == 0) { while (bsqSwapOffers.size() == 0) {
bsqSwapOffers.addAll(bobClient.getBsqSwapOffers(BUY.name(), BSQ)); bsqSwapOffers.addAll(client.getBsqSwapOffers(BUY.name()));
numFetchAttempts++; numFetchAttempts++;
if (bsqSwapOffers.size() == 0) { if (bsqSwapOffers.size() == 0) {
log.warn("No available bsq swap offers found after {} fetch attempts.", numFetchAttempts); log.warn("No available bsq swap offers found after {} fetch attempts.", numFetchAttempts);
@ -138,7 +176,7 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
} }
fail(format("Bob gave up on fetching available bsq swap offers after %d attempts.", numFetchAttempts)); fail(format("Bob gave up on fetching available bsq swap offers after %d attempts.", numFetchAttempts));
} }
sleep(1000); sleep(1_000);
} else { } else {
assertEquals(1, bsqSwapOffers.size()); assertEquals(1, bsqSwapOffers.size());
log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts); log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts);
@ -150,12 +188,12 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
return bsqSwapOffer; return bsqSwapOffer;
} }
private BsqSwapTradeInfo getBsqSwapTrade(String tradeId) { private TradeInfo getBsqSwapTrade(GrpcClient client, String tradeId) {
int numFetchAttempts = 0; int numFetchAttempts = 0;
while (true) { while (true) {
try { try {
numFetchAttempts++; numFetchAttempts++;
return bobClient.getBsqSwapTrade(tradeId); return client.getTrade(tradeId);
} catch (Exception ex) { } catch (Exception ex) {
log.warn(ex.getMessage()); log.warn(ex.getMessage());
if (numFetchAttempts > 9) { if (numFetchAttempts > 9) {
@ -164,7 +202,7 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
} }
fail(format("Could not find new bsq swap trade after %d attempts.", numFetchAttempts)); fail(format("Could not find new bsq swap trade after %d attempts.", numFetchAttempts));
} else { } else {
sleep(1000); sleep(1_000);
} }
} }
} }

View file

@ -101,7 +101,9 @@ public class TradeTest extends AbstractTradeTest {
@Order(6) @Order(6)
public void testBsqSwapTradeTest(final TestInfo testInfo) { public void testBsqSwapTradeTest(final TestInfo testInfo) {
BsqSwapTradeTest test = new BsqSwapTradeTest(); BsqSwapTradeTest test = new BsqSwapTradeTest();
test.testGetBalancesBeforeTrade();
test.testAliceCreateBsqSwapBuyOffer(); test.testAliceCreateBsqSwapBuyOffer();
test.testBobTakesBsqSwapOffer(); test.testBobTakesBsqSwapOffer();
test.testGetBalancesAfterTrade();
} }
} }

View file

@ -18,6 +18,7 @@
package bisq.cli; package bisq.cli;
import bisq.proto.grpc.OfferInfo; import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.TradeInfo;
import io.grpc.StatusRuntimeException; import io.grpc.StatusRuntimeException;
@ -45,6 +46,7 @@ import static bisq.cli.CurrencyFormat.*;
import static bisq.cli.Method.*; import static bisq.cli.Method.*;
import static bisq.cli.opts.OptLabel.*; import static bisq.cli.opts.OptLabel.*;
import static bisq.cli.table.builder.TableType.*; import static bisq.cli.table.builder.TableType.*;
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory.BSQ_SWAP;
import static java.lang.String.format; import static java.lang.String.format;
import static java.lang.System.err; import static java.lang.System.err;
import static java.lang.System.exit; import static java.lang.System.exit;
@ -62,11 +64,11 @@ import bisq.cli.opts.EditOfferOptionParser;
import bisq.cli.opts.GetAddressBalanceOptionParser; import bisq.cli.opts.GetAddressBalanceOptionParser;
import bisq.cli.opts.GetBTCMarketPriceOptionParser; import bisq.cli.opts.GetBTCMarketPriceOptionParser;
import bisq.cli.opts.GetBalanceOptionParser; import bisq.cli.opts.GetBalanceOptionParser;
import bisq.cli.opts.GetOfferOptionParser;
import bisq.cli.opts.GetOffersOptionParser; import bisq.cli.opts.GetOffersOptionParser;
import bisq.cli.opts.GetPaymentAcctFormOptionParser; import bisq.cli.opts.GetPaymentAcctFormOptionParser;
import bisq.cli.opts.GetTradeOptionParser; import bisq.cli.opts.GetTradeOptionParser;
import bisq.cli.opts.GetTransactionOptionParser; import bisq.cli.opts.GetTransactionOptionParser;
import bisq.cli.opts.OfferIdOptionParser;
import bisq.cli.opts.RegisterDisputeAgentOptionParser; import bisq.cli.opts.RegisterDisputeAgentOptionParser;
import bisq.cli.opts.RemoveWalletPasswordOptionParser; import bisq.cli.opts.RemoveWalletPasswordOptionParser;
import bisq.cli.opts.SendBsqOptionParser; import bisq.cli.opts.SendBsqOptionParser;
@ -332,6 +334,7 @@ public class CliMain {
out.println(client.getMethodHelp(method)); out.println(client.getMethodHelp(method));
return; return;
} }
var isSwap = opts.getIsSwap();
var paymentAcctId = opts.getPaymentAccountId(); var paymentAcctId = opts.getPaymentAccountId();
var direction = opts.getDirection(); var direction = opts.getDirection();
var currencyCode = opts.getCurrencyCode(); var currencyCode = opts.getCurrencyCode();
@ -340,30 +343,45 @@ public class CliMain {
var useMarketBasedPrice = opts.isUsingMktPriceMargin(); var useMarketBasedPrice = opts.isUsingMktPriceMargin();
var fixedPrice = opts.getFixedPrice(); var fixedPrice = opts.getFixedPrice();
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal(); var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit()); var securityDeposit = isSwap ? 0.00 : toSecurityDepositAsPct(opts.getSecurityDeposit());
var makerFeeCurrencyCode = opts.getMakerFeeCurrencyCode(); var makerFeeCurrencyCode = opts.getMakerFeeCurrencyCode();
var triggerPrice = 0; // Cannot be defined until offer is in book. var triggerPrice = 0; // Cannot be defined until offer is in book.
var offer = client.createOffer(direction, OfferInfo offer;
currencyCode, if (isSwap) {
amount, offer = client.createBsqSwapOffer(direction,
minAmount, amount,
useMarketBasedPrice, minAmount,
fixedPrice, fixedPrice);
marketPriceMargin.doubleValue(), } else {
securityDeposit, offer = client.createOffer(direction,
paymentAcctId, currencyCode,
makerFeeCurrencyCode, amount,
triggerPrice); minAmount,
useMarketBasedPrice,
fixedPrice,
marketPriceMargin.doubleValue(),
securityDeposit,
paymentAcctId,
makerFeeCurrencyCode,
triggerPrice);
}
new TableBuilder(OFFER_TBL, offer).build().print(out); new TableBuilder(OFFER_TBL, offer).build().print(out);
return; return;
} }
case editoffer: { case editoffer: {
var opts = new EditOfferOptionParser(args).parse(); var offerIdOpt = new OfferIdOptionParser(args, true).parse();
if (opts.isForHelp()) { if (offerIdOpt.isForHelp()) {
out.println(client.getMethodHelp(method)); out.println(client.getMethodHelp(method));
return; return;
} }
var offerId = opts.getOfferId(); // What kind of offer is being edited? BSQ swaps cannot be edited.
var offerId = offerIdOpt.getOfferId();
var offerCategory = client.getMyOfferCategory(offerId);
if (offerCategory.equals(BSQ_SWAP))
throw new IllegalStateException("bsq swap offers cannot be edited,"
+ " but you may cancel them without forfeiting any funds");
var opts = new EditOfferOptionParser(args).parse();
var fixedPrice = opts.getFixedPrice(); var fixedPrice = opts.getFixedPrice();
var isUsingMktPriceMargin = opts.isUsingMktPriceMargin(); var isUsingMktPriceMargin = opts.isUsingMktPriceMargin();
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal(); var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
@ -392,7 +410,7 @@ public class CliMain {
return; return;
} }
case getoffer: { case getoffer: {
var opts = new GetOfferOptionParser(args).parse(); var opts = new OfferIdOptionParser(args).parse();
if (opts.isForHelp()) { if (opts.isForHelp()) {
out.println(client.getMethodHelp(method)); out.println(client.getMethodHelp(method));
return; return;
@ -403,7 +421,7 @@ public class CliMain {
return; return;
} }
case getmyoffer: { case getmyoffer: {
var opts = new GetOfferOptionParser(args).parse(); var opts = new OfferIdOptionParser(args).parse();
if (opts.isForHelp()) { if (opts.isForHelp()) {
out.println(client.getMethodHelp(method)); out.println(client.getMethodHelp(method));
return; return;
@ -446,15 +464,25 @@ public class CliMain {
return; return;
} }
case takeoffer: { case takeoffer: {
var opts = new TakeOfferOptionParser(args).parse(); var offerIdOpt = new OfferIdOptionParser(args, true).parse();
if (opts.isForHelp()) { if (offerIdOpt.isForHelp()) {
out.println(client.getMethodHelp(method)); out.println(client.getMethodHelp(method));
return; return;
} }
var offerId = opts.getOfferId(); var offerId = offerIdOpt.getOfferId();
var paymentAccountId = opts.getPaymentAccountId(); TradeInfo trade;
var takerFeeCurrencyCode = opts.getTakerFeeCurrencyCode(); // We only send an 'offer-id' param when taking a BsqSwapOffer.
var trade = client.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode); // Find out what kind of offer is being taken before sending a
// 'takeoffer' request.
var offerCategory = client.getAvailableOfferCategory(offerId);
if (offerCategory.equals(BSQ_SWAP)) {
trade = client.takeBsqSwapOffer(offerId);
} else {
var opts = new TakeOfferOptionParser(args).parse();
var paymentAccountId = opts.getPaymentAccountId();
var takerFeeCurrencyCode = opts.getTakerFeeCurrencyCode();
trade = client.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
}
out.printf("trade %s successfully taken%n", trade.getTradeId()); out.printf("trade %s successfully taken%n", trade.getTradeId());
return; return;
} }
@ -801,10 +829,11 @@ public class CliMain {
stream.format(rowFormat, "", "--currency-code=<currency-code> \\", ""); stream.format(rowFormat, "", "--currency-code=<currency-code> \\", "");
stream.format(rowFormat, "", "--amount=<btc-amount> \\", ""); stream.format(rowFormat, "", "--amount=<btc-amount> \\", "");
stream.format(rowFormat, "", "[--min-amount=<min-btc-amount>] \\", ""); stream.format(rowFormat, "", "[--min-amount=<min-btc-amount>] \\", "");
stream.format(rowFormat, "", "--fixed-price=<price> | --market-price=margin=<percent> \\", ""); stream.format(rowFormat, "", "--fixed-price=<price> | --market-price-margin=<percent> \\", "");
stream.format(rowFormat, "", "--security-deposit=<percent> \\", ""); stream.format(rowFormat, "", "--security-deposit=<percent> \\", "");
stream.format(rowFormat, "", "[--fee-currency=<bsq|btc>]", ""); stream.format(rowFormat, "", "[--fee-currency=<bsq|btc>]", "");
stream.format(rowFormat, "", "[--trigger-price=<price>]", ""); stream.format(rowFormat, "", "[--trigger-price=<price>]", "");
stream.format(rowFormat, "", "[--swap=<true|false>]", "");
stream.println(); stream.println();
stream.format(rowFormat, editoffer.name(), "--offer-id=<offer-id> \\", "Edit offer with id"); stream.format(rowFormat, editoffer.name(), "--offer-id=<offer-id> \\", "Edit offer with id");
stream.format(rowFormat, "", "[--fixed-price=<price>] \\", ""); stream.format(rowFormat, "", "[--fixed-price=<price>] \\", "");
@ -825,7 +854,7 @@ public class CliMain {
stream.format(rowFormat, "", "--currency-code=<currency-code>", ""); stream.format(rowFormat, "", "--currency-code=<currency-code>", "");
stream.println(); stream.println();
stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> \\", "Take offer with id"); stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> \\", "Take offer with id");
stream.format(rowFormat, "", "--payment-account=<payment-account-id>", ""); stream.format(rowFormat, "", "[--payment-account=<payment-account-id>]", "");
stream.format(rowFormat, "", "[--fee-currency=<btc|bsq>]", ""); stream.format(rowFormat, "", "[--fee-currency=<btc|bsq>]", "");
stream.println(); stream.println();
stream.format(rowFormat, gettrade.name(), "--trade-id=<trade-id> \\", "Get trade summary or full contract"); stream.format(rowFormat, gettrade.name(), "--trade-id=<trade-id> \\", "Get trade summary or full contract");

View file

@ -20,17 +20,12 @@ package bisq.cli;
import bisq.proto.grpc.AddressBalanceInfo; import bisq.proto.grpc.AddressBalanceInfo;
import bisq.proto.grpc.BalancesInfo; import bisq.proto.grpc.BalancesInfo;
import bisq.proto.grpc.BsqBalanceInfo; import bisq.proto.grpc.BsqBalanceInfo;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.BsqSwapTradeInfo;
import bisq.proto.grpc.BtcBalanceInfo; import bisq.proto.grpc.BtcBalanceInfo;
import bisq.proto.grpc.CreateBsqSwapOfferRequest;
import bisq.proto.grpc.GetMethodHelpRequest; import bisq.proto.grpc.GetMethodHelpRequest;
import bisq.proto.grpc.GetVersionRequest; import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.OfferInfo; import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.StopRequest; import bisq.proto.grpc.StopRequest;
import bisq.proto.grpc.TakeBsqSwapOfferReply;
import bisq.proto.grpc.TakeBsqSwapOfferRequest;
import bisq.proto.grpc.TakeOfferReply; import bisq.proto.grpc.TakeOfferReply;
import bisq.proto.grpc.TradeInfo; import bisq.proto.grpc.TradeInfo;
import bisq.proto.grpc.TxFeeRateInfo; import bisq.proto.grpc.TxFeeRateInfo;
@ -44,6 +39,7 @@ import java.util.List;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static bisq.proto.grpc.EditOfferRequest.EditType; import static bisq.proto.grpc.EditOfferRequest.EditType;
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory;
@ -142,19 +138,22 @@ public final class GrpcClient {
return walletsServiceRequest.getTransaction(txId); return walletsServiceRequest.getTransaction(txId);
} }
public BsqSwapOfferInfo createBsqSwapOffer(String direction, public OfferCategory getAvailableOfferCategory(String offerId) {
long amount, return offersServiceRequest.getAvailableOfferCategory(offerId);
long minAmount, }
String fixedPrice,
String paymentAcctId) { public OfferCategory getMyOfferCategory(String offerId) {
var request = CreateBsqSwapOfferRequest.newBuilder() return offersServiceRequest.getMyOfferCategory(offerId);
.setDirection(direction) }
.setAmount(amount)
.setMinAmount(minAmount) public OfferInfo createBsqSwapOffer(String direction,
.setPrice(fixedPrice) long amount,
.setPaymentAccountId(paymentAcctId) long minAmount,
.build(); String fixedPrice) {
return grpcStubs.offersService.createBsqSwapOffer(request).getBsqSwapOffer(); return offersServiceRequest.createBsqSwapOffer(direction,
amount,
minAmount,
fixedPrice);
} }
public OfferInfo createFixedPricedOffer(String direction, public OfferInfo createFixedPricedOffer(String direction,
@ -263,7 +262,7 @@ public final class GrpcClient {
offersServiceRequest.cancelOffer(offerId); offersServiceRequest.cancelOffer(offerId);
} }
public BsqSwapOfferInfo getBsqSwapOffer(String offerId) { public OfferInfo getBsqSwapOffer(String offerId) {
return offersServiceRequest.getBsqSwapOffer(offerId); return offersServiceRequest.getBsqSwapOffer(offerId);
} }
@ -271,7 +270,7 @@ public final class GrpcClient {
return offersServiceRequest.getOffer(offerId); return offersServiceRequest.getOffer(offerId);
} }
public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) { public OfferInfo getMyBsqSwapOffer(String offerId) {
return offersServiceRequest.getMyBsqSwapOffer(offerId); return offersServiceRequest.getMyBsqSwapOffer(offerId);
} }
@ -279,8 +278,8 @@ public final class GrpcClient {
return offersServiceRequest.getMyOffer(offerId); return offersServiceRequest.getMyOffer(offerId);
} }
public List<BsqSwapOfferInfo> getBsqSwapOffers(String direction, String currencyCode) { public List<OfferInfo> getBsqSwapOffers(String direction) {
return offersServiceRequest.getBsqSwapOffers(direction, currencyCode); return offersServiceRequest.getBsqSwapOffers(direction);
} }
public List<OfferInfo> getOffers(String direction, String currencyCode) { public List<OfferInfo> getOffers(String direction, String currencyCode) {
@ -303,12 +302,12 @@ public final class GrpcClient {
return offersServiceRequest.getBsqOffersSortedByDate(); return offersServiceRequest.getBsqOffersSortedByDate();
} }
public List<BsqSwapOfferInfo> getBsqSwapOffersSortedByDate() { public List<OfferInfo> getBsqSwapOffersSortedByDate() {
return offersServiceRequest.getBsqSwapOffersSortedByDate(); return offersServiceRequest.getBsqSwapOffersSortedByDate();
} }
public List<BsqSwapOfferInfo> getMyBsqSwapOffers(String direction, String currencyCode) { public List<OfferInfo> getMyBsqSwapOffers(String direction) {
return offersServiceRequest.getMyBsqSwapOffers(direction, currencyCode); return offersServiceRequest.getMyBsqSwapOffers(direction);
} }
public List<OfferInfo> getMyOffers(String direction, String currencyCode) { public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
@ -335,7 +334,7 @@ public final class GrpcClient {
return offersServiceRequest.getMyBsqOffersSortedByDate(); return offersServiceRequest.getMyBsqOffersSortedByDate();
} }
public List<BsqSwapOfferInfo> getMyBsqSwapBsqOffersSortedByDate() { public List<OfferInfo> getMyBsqSwapBsqOffersSortedByDate() {
return offersServiceRequest.getMyBsqSwapOffersSortedByDate(); return offersServiceRequest.getMyBsqSwapOffersSortedByDate();
} }
@ -343,45 +342,26 @@ public final class GrpcClient {
return offersServiceRequest.getMostRecentOffer(direction, currencyCode); return offersServiceRequest.getMostRecentOffer(direction, currencyCode);
} }
public List<BsqSwapOfferInfo> sortBsqSwapOffersByDate(List<BsqSwapOfferInfo> offerInfoList) { public List<OfferInfo> sortBsqSwapOffersByDate(List<OfferInfo> offers) {
return offersServiceRequest.sortBsqSwapOffersByDate(offerInfoList); return offersServiceRequest.sortOffersByDate(offers);
} }
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) { public List<OfferInfo> sortOffersByDate(List<OfferInfo> offers) {
return offersServiceRequest.sortOffersByDate(offerInfoList); return offersServiceRequest.sortOffersByDate(offers);
}
public TakeBsqSwapOfferReply getTakeBsqSwapOfferReply(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode) {
var request = TakeBsqSwapOfferRequest.newBuilder()
.setOfferId(offerId)
.setPaymentAccountId(paymentAccountId)
.setTakerFeeCurrencyCode(takerFeeCurrencyCode)
.build();
return grpcStubs.tradesService.takeBsqSwapOffer(request);
} }
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
return tradesServiceRequest.getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode); return tradesServiceRequest.getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
} }
public BsqSwapTradeInfo takeBsqSwapOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { public TradeInfo takeBsqSwapOffer(String offerId) {
var reply = getTakeBsqSwapOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode); return tradesServiceRequest.takeBsqSwapOffer(offerId);
if (reply.hasBsqSwapTrade())
return reply.getBsqSwapTrade();
else
throw new IllegalStateException(reply.getFailureReason().getDescription());
} }
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode); return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
} }
public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
return tradesServiceRequest.getBsqSwapTrade(tradeId);
}
public TradeInfo getTrade(String tradeId) { public TradeInfo getTrade(String tradeId) {
return tradesServiceRequest.getTrade(tradeId); return tradesServiceRequest.getTrade(tradeId);
} }

View file

@ -39,7 +39,7 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
protected final OptionParser parser = new OptionParser(); protected final OptionParser parser = new OptionParser();
// The help option for a specific api method, e.g., takeoffer -help. // The help option for a specific api method, e.g., takeoffer --help.
protected final OptionSpec<Void> helpOpt = parser.accepts(OPT_HELP, "Print method help").forHelp(); protected final OptionSpec<Void> helpOpt = parser.accepts(OPT_HELP, "Print method help").forHelp();
@Getter @Getter

View file

@ -18,14 +18,7 @@
package bisq.cli.opts; package bisq.cli.opts;
import joptsimple.OptionSpec; public class CancelOfferOptionParser extends OfferIdOptionParser implements MethodOpts {
import static bisq.cli.opts.OptLabel.OPT_OFFER_ID;
public class CancelOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to cancel")
.withRequiredArg();
public CancelOfferOptionParser(String[] args) { public CancelOfferOptionParser(String[] args) {
super(args); super(args);
@ -34,12 +27,7 @@ public class CancelOfferOptionParser extends AbstractMethodOptionParser implemen
public CancelOfferOptionParser parse() { public CancelOfferOptionParser parse() {
super.parse(); super.parse();
// Short circuit opt validation if user just wants help. // Super class will short-circuit parsing if help option is present.
if (options.has(helpOpt))
return this;
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
throw new IllegalArgumentException("no offer id specified");
return this; return this;
} }

View file

@ -24,6 +24,7 @@ import static bisq.cli.opts.OptLabel.OPT_ACCOUNT_NAME;
import static bisq.cli.opts.OptLabel.OPT_ADDRESS; import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE; import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT; import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT;
import static java.lang.Boolean.FALSE;
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts { public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
@ -39,7 +40,7 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
final OptionSpec<Boolean> tradeInstantOpt = parser.accepts(OPT_TRADE_INSTANT, "create trade instant account") final OptionSpec<Boolean> tradeInstantOpt = parser.accepts(OPT_TRADE_INSTANT, "create trade instant account")
.withOptionalArg() .withOptionalArg()
.ofType(boolean.class) .ofType(boolean.class)
.defaultsTo(Boolean.FALSE); .defaultsTo(FALSE);
public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) { public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) {
super(args); super(args);

View file

@ -23,12 +23,13 @@ import joptsimple.OptionSpec;
import java.math.BigDecimal; import java.math.BigDecimal;
import static bisq.cli.opts.OptLabel.*; import static bisq.cli.opts.OptLabel.*;
import static java.lang.Boolean.FALSE;
import static joptsimple.internal.Strings.EMPTY; import static joptsimple.internal.Strings.EMPTY;
public class CreateOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts { public class CreateOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT, final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT,
"id of payment account used for offer") "id of payment account used for offer")
.withRequiredArg() .withRequiredArg()
.defaultsTo(EMPTY); .defaultsTo(EMPTY);
@ -59,6 +60,11 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
.withOptionalArg() .withOptionalArg()
.defaultsTo("btc"); .defaultsTo("btc");
final OptionSpec<Boolean> isSwapOpt = parser.accepts(OPT_SWAP, "create bsq swap offer")
.withOptionalArg()
.ofType(boolean.class)
.defaultsTo(FALSE);
public CreateOfferOptionParser(String[] args) { public CreateOfferOptionParser(String[] args) {
super(args); super(args);
} }
@ -70,9 +76,6 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
if (options.has(helpOpt)) if (options.has(helpOpt))
return this; return this;
if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty())
throw new IllegalArgumentException("no payment account id specified");
if (!options.has(directionOpt) || options.valueOf(directionOpt).isEmpty()) if (!options.has(directionOpt) || options.valueOf(directionOpt).isEmpty())
throw new IllegalArgumentException("no direction (buy|sell) specified"); throw new IllegalArgumentException("no direction (buy|sell) specified");
@ -82,17 +85,38 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
if (!options.has(amountOpt) || options.valueOf(amountOpt).isEmpty()) if (!options.has(amountOpt) || options.valueOf(amountOpt).isEmpty())
throw new IllegalArgumentException("no btc amount specified"); throw new IllegalArgumentException("no btc amount specified");
if (!options.has(mktPriceMarginOpt) && !options.has(fixedPriceOpt)) if (getIsSwap()) {
throw new IllegalArgumentException("no market price margin or fixed price specified"); if (!options.valueOf(currencyCodeOpt).equalsIgnoreCase("bsq"))
throw new IllegalArgumentException("only bsq swaps are currently supported");
if (options.has(mktPriceMarginOpt) && options.valueOf(mktPriceMarginOpt).isEmpty()) if (options.has(paymentAccountIdOpt))
throw new IllegalArgumentException("no market price margin specified"); throw new IllegalArgumentException("cannot use a payment account id in bsq swap offer");
if (options.has(fixedPriceOpt) && options.valueOf(fixedPriceOpt).isEmpty()) if (options.has(mktPriceMarginOpt))
throw new IllegalArgumentException("no fixed price specified"); throw new IllegalArgumentException("cannot use a market price margin in bsq swap offer");
if (!options.has(securityDepositOpt) || options.valueOf(securityDepositOpt).isEmpty()) if (options.has(securityDepositOpt))
throw new IllegalArgumentException("no security deposit specified"); throw new IllegalArgumentException("cannot use a security deposit in bsq swap offer");
if (!options.has(fixedPriceOpt) || options.valueOf(fixedPriceOpt).isEmpty())
throw new IllegalArgumentException("no fixed price specified");
} else {
if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty())
throw new IllegalArgumentException("no payment account id specified");
if (!options.has(mktPriceMarginOpt) && !options.has(fixedPriceOpt))
throw new IllegalArgumentException("no market price margin or fixed price specified");
if (options.has(mktPriceMarginOpt) && options.valueOf(mktPriceMarginOpt).isEmpty())
throw new IllegalArgumentException("no market price margin specified");
if (options.has(fixedPriceOpt) && options.valueOf(fixedPriceOpt).isEmpty())
throw new IllegalArgumentException("no fixed price specified");
if (!options.has(securityDepositOpt) || options.valueOf(securityDepositOpt).isEmpty())
throw new IllegalArgumentException("no security deposit specified");
}
return this; return this;
} }
@ -141,4 +165,8 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
public String getMakerFeeCurrencyCode() { public String getMakerFeeCurrencyCode() {
return options.has(makerFeeCurrencyCodeOpt) ? options.valueOf(makerFeeCurrencyCodeOpt) : "btc"; return options.has(makerFeeCurrencyCodeOpt) ? options.valueOf(makerFeeCurrencyCodeOpt) : "btc";
} }
public boolean getIsSwap() {
return options.valueOf(isSwapOpt);
}
} }

View file

@ -29,7 +29,7 @@ import static java.lang.String.format;
public class CreatePaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts { public class CreatePaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> paymentAcctFormPathOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_FORM, final OptionSpec<String> paymentAcctFormPathOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_FORM,
"path to json payment account form") "path to json payment account form")
.withRequiredArg(); .withRequiredArg();
public CreatePaymentAcctOptionParser(String[] args) { public CreatePaymentAcctOptionParser(String[] args) {

View file

@ -24,7 +24,10 @@ import joptsimple.OptionSpec;
import java.math.BigDecimal; import java.math.BigDecimal;
import static bisq.cli.opts.OptLabel.*; import static bisq.cli.opts.OptLabel.OPT_ENABLE;
import static bisq.cli.opts.OptLabel.OPT_FIXED_PRICE;
import static bisq.cli.opts.OptLabel.OPT_MKT_PRICE_MARGIN;
import static bisq.cli.opts.OptLabel.OPT_TRIGGER_PRICE;
import static bisq.proto.grpc.EditOfferRequest.EditType.*; import static bisq.proto.grpc.EditOfferRequest.EditType.*;
import static java.lang.String.format; import static java.lang.String.format;
@ -32,26 +35,23 @@ import static java.lang.String.format;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class EditOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts { public class EditOfferOptionParser extends OfferIdOptionParser implements MethodOpts {
static int OPT_ENABLE_ON = 1; static int OPT_ENABLE_ON = 1;
static int OPT_ENABLE_OFF = 0; static int OPT_ENABLE_OFF = 0;
static int OPT_ENABLE_IGNORED = -1; static int OPT_ENABLE_IGNORED = -1;
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to cancel")
.withRequiredArg();
final OptionSpec<String> fixedPriceOpt = parser.accepts(OPT_FIXED_PRICE, "fixed btc price") final OptionSpec<String> fixedPriceOpt = parser.accepts(OPT_FIXED_PRICE, "fixed btc price")
.withOptionalArg() .withOptionalArg()
.defaultsTo("0"); .defaultsTo("0");
final OptionSpec<String> mktPriceMarginOpt = parser.accepts(OPT_MKT_PRICE_MARGIN, final OptionSpec<String> mktPriceMarginOpt = parser.accepts(OPT_MKT_PRICE_MARGIN,
"market btc price margin (%)") "market btc price margin (%)")
.withOptionalArg() .withOptionalArg()
.defaultsTo("0.00"); .defaultsTo("0.00");
final OptionSpec<String> triggerPriceOpt = parser.accepts(OPT_TRIGGER_PRICE, final OptionSpec<String> triggerPriceOpt = parser.accepts(OPT_TRIGGER_PRICE,
"trigger price (applies to mkt price margin based offers)") "trigger price (applies to mkt price margin based offers)")
.withOptionalArg() .withOptionalArg()
.defaultsTo("0"); .defaultsTo("0");
@ -59,25 +59,20 @@ public class EditOfferOptionParser extends AbstractMethodOptionParser implements
// activation state). For this reason, a boolean type is not used (can only be // activation state). For this reason, a boolean type is not used (can only be
// true or false). // true or false).
final OptionSpec<String> enableOpt = parser.accepts(OPT_ENABLE, final OptionSpec<String> enableOpt = parser.accepts(OPT_ENABLE,
"enable or disable offer") "enable or disable offer")
.withOptionalArg() .withOptionalArg()
.ofType(String.class); .ofType(String.class);
private EditOfferRequest.EditType offerEditType; private EditOfferRequest.EditType offerEditType;
public EditOfferOptionParser(String[] args) { public EditOfferOptionParser(String[] args) {
super(args); super(args, true);
} }
public EditOfferOptionParser parse() { public EditOfferOptionParser parse() {
super.parse(); super.parse();
// Short circuit opt validation if user just wants help. // Super class will short-circuit parsing if help option is present.
if (options.has(helpOpt))
return this;
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
throw new IllegalArgumentException("no offer id specified");
boolean hasNoEditDetails = !options.has(fixedPriceOpt) boolean hasNoEditDetails = !options.has(fixedPriceOpt)
&& !options.has(mktPriceMarginOpt) && !options.has(mktPriceMarginOpt)
@ -201,10 +196,6 @@ public class EditOfferOptionParser extends AbstractMethodOptionParser implements
return this; return this;
} }
public String getOfferId() {
return options.valueOf(offerIdOpt);
}
public String getFixedPrice() { public String getFixedPrice() {
if (offerEditType.equals(FIXED_PRICE_ONLY) || offerEditType.equals(FIXED_PRICE_AND_ACTIVATION_STATE)) { if (offerEditType.equals(FIXED_PRICE_ONLY) || offerEditType.equals(FIXED_PRICE_AND_ACTIVATION_STATE)) {
return options.has(fixedPriceOpt) ? options.valueOf(fixedPriceOpt) : "0"; return options.has(fixedPriceOpt) ? options.valueOf(fixedPriceOpt) : "0";

View file

@ -25,7 +25,7 @@ import static bisq.cli.opts.OptLabel.OPT_PAYMENT_METHOD_ID;
public class GetPaymentAcctFormOptionParser extends AbstractMethodOptionParser implements MethodOpts { public class GetPaymentAcctFormOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> paymentMethodIdOpt = parser.accepts(OPT_PAYMENT_METHOD_ID, final OptionSpec<String> paymentMethodIdOpt = parser.accepts(OPT_PAYMENT_METHOD_ID,
"id of payment method type used by a payment account") "id of payment method type used by a payment account")
.withRequiredArg(); .withRequiredArg();
public GetPaymentAcctFormOptionParser(String[] args) { public GetPaymentAcctFormOptionParser(String[] args) {

View file

@ -22,16 +22,26 @@ import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_OFFER_ID; import static bisq.cli.opts.OptLabel.OPT_OFFER_ID;
public class GetOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts { /**
* Superclass for option parsers requiring an offer-id. Avoids a small amount of
* duplicated boilerplate.
*/
public class OfferIdOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to get") final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer")
.withRequiredArg(); .withRequiredArg();
public GetOfferOptionParser(String[] args) { public OfferIdOptionParser(String[] args) {
super(args); this(args, false);
} }
public GetOfferOptionParser parse() { public OfferIdOptionParser(String[] args, boolean allowsUnrecognizedOptions) {
super(args);
if (allowsUnrecognizedOptions)
this.parser.allowsUnrecognizedOptions();
}
public OfferIdOptionParser parse() {
super.parse(); super.parse();
// Short circuit opt validation if user just wants help. // Short circuit opt validation if user just wants help.

View file

@ -44,6 +44,7 @@ public class OptLabel {
public final static String OPT_REGISTRATION_KEY = "registration-key"; public final static String OPT_REGISTRATION_KEY = "registration-key";
public final static String OPT_SECURITY_DEPOSIT = "security-deposit"; public final static String OPT_SECURITY_DEPOSIT = "security-deposit";
public final static String OPT_SHOW_CONTRACT = "show-contract"; public final static String OPT_SHOW_CONTRACT = "show-contract";
public final static String OPT_SWAP = "swap";
public final static String OPT_TRADE_ID = "trade-id"; public final static String OPT_TRADE_ID = "trade-id";
public final static String OPT_TRADE_INSTANT = "trade-instant"; public final static String OPT_TRADE_INSTANT = "trade-instant";
public final static String OPT_TIMEOUT = "timeout"; public final static String OPT_TIMEOUT = "timeout";

View file

@ -25,7 +25,7 @@ import static bisq.cli.opts.OptLabel.OPT_TX_FEE_RATE;
public class SetTxFeeRateOptionParser extends AbstractMethodOptionParser implements MethodOpts { public class SetTxFeeRateOptionParser extends AbstractMethodOptionParser implements MethodOpts {
final OptionSpec<String> feeRateOpt = parser.accepts(OPT_TX_FEE_RATE, final OptionSpec<String> feeRateOpt = parser.accepts(OPT_TX_FEE_RATE,
"tx fee rate preference (sats/byte)") "tx fee rate preference (sats/byte)")
.withRequiredArg(); .withRequiredArg();
public SetTxFeeRateOptionParser(String[] args) { public SetTxFeeRateOptionParser(String[] args) {

View file

@ -21,13 +21,9 @@ package bisq.cli.opts;
import joptsimple.OptionSpec; import joptsimple.OptionSpec;
import static bisq.cli.opts.OptLabel.OPT_FEE_CURRENCY; import static bisq.cli.opts.OptLabel.OPT_FEE_CURRENCY;
import static bisq.cli.opts.OptLabel.OPT_OFFER_ID;
import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT; import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT;
public class TakeOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts { public class TakeOfferOptionParser extends OfferIdOptionParser implements MethodOpts {
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to take")
.withRequiredArg();
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT, "id of payment account used for trade") final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT, "id of payment account used for trade")
.withRequiredArg(); .withRequiredArg();
@ -37,18 +33,13 @@ public class TakeOfferOptionParser extends AbstractMethodOptionParser implements
.defaultsTo("btc"); .defaultsTo("btc");
public TakeOfferOptionParser(String[] args) { public TakeOfferOptionParser(String[] args) {
super(args); super(args, true);
} }
public TakeOfferOptionParser parse() { public TakeOfferOptionParser parse() {
super.parse(); super.parse();
// Short circuit opt validation if user just wants help. // Super class will short-circuit parsing if help option is present.
if (options.has(helpOpt))
return this;
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
throw new IllegalArgumentException("no offer id specified");
if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty()) if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty())
throw new IllegalArgumentException("no payment account id specified"); throw new IllegalArgumentException("no payment account id specified");
@ -56,10 +47,6 @@ public class TakeOfferOptionParser extends AbstractMethodOptionParser implements
return this; return this;
} }
public String getOfferId() {
return options.valueOf(offerIdOpt);
}
public String getPaymentAccountId() { public String getPaymentAccountId() {
return options.valueOf(paymentAccountIdOpt); return options.valueOf(paymentAccountIdOpt);
} }

View file

@ -17,12 +17,14 @@
package bisq.cli.request; package bisq.cli.request;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.CancelOfferRequest; import bisq.proto.grpc.CancelOfferRequest;
import bisq.proto.grpc.CreateBsqSwapOfferRequest;
import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.CreateOfferRequest;
import bisq.proto.grpc.EditOfferRequest; import bisq.proto.grpc.EditOfferRequest;
import bisq.proto.grpc.GetBsqSwapOffersRequest;
import bisq.proto.grpc.GetMyOfferRequest; import bisq.proto.grpc.GetMyOfferRequest;
import bisq.proto.grpc.GetMyOffersRequest; import bisq.proto.grpc.GetMyOffersRequest;
import bisq.proto.grpc.GetOfferCategoryRequest;
import bisq.proto.grpc.GetOfferRequest; import bisq.proto.grpc.GetOfferRequest;
import bisq.proto.grpc.GetOffersRequest; import bisq.proto.grpc.GetOffersRequest;
import bisq.proto.grpc.OfferInfo; import bisq.proto.grpc.OfferInfo;
@ -37,6 +39,7 @@ import static bisq.proto.grpc.EditOfferRequest.EditType.ACTIVATION_STATE_ONLY;
import static bisq.proto.grpc.EditOfferRequest.EditType.FIXED_PRICE_ONLY; import static bisq.proto.grpc.EditOfferRequest.EditType.FIXED_PRICE_ONLY;
import static bisq.proto.grpc.EditOfferRequest.EditType.MKT_PRICE_MARGIN_ONLY; import static bisq.proto.grpc.EditOfferRequest.EditType.MKT_PRICE_MARGIN_ONLY;
import static bisq.proto.grpc.EditOfferRequest.EditType.TRIGGER_PRICE_ONLY; import static bisq.proto.grpc.EditOfferRequest.EditType.TRIGGER_PRICE_ONLY;
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory;
import static java.util.Comparator.comparing; import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static protobuf.OfferDirection.BUY; import static protobuf.OfferDirection.BUY;
@ -60,6 +63,27 @@ public class OffersServiceRequest {
this.grpcStubs = grpcStubs; this.grpcStubs = grpcStubs;
} }
public OfferCategory getAvailableOfferCategory(String offerId) {
return getOfferCategory(offerId, false);
}
public OfferCategory getMyOfferCategory(String offerId) {
return getOfferCategory(offerId, true);
}
public OfferInfo createBsqSwapOffer(String direction,
long amount,
long minAmount,
String fixedPrice) {
var request = CreateBsqSwapOfferRequest.newBuilder()
.setDirection(direction)
.setAmount(amount)
.setMinAmount(minAmount)
.setPrice(fixedPrice)
.build();
return grpcStubs.offersService.createBsqSwapOffer(request).getBsqSwapOffer();
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
public OfferInfo createFixedPricedOffer(String direction, public OfferInfo createFixedPricedOffer(String direction,
String currencyCode, String currencyCode,
@ -210,7 +234,7 @@ public class OffersServiceRequest {
grpcStubs.offersService.cancelOffer(request); grpcStubs.offersService.cancelOffer(request);
} }
public BsqSwapOfferInfo getBsqSwapOffer(String offerId) { public OfferInfo getBsqSwapOffer(String offerId) {
var request = GetOfferRequest.newBuilder() var request = GetOfferRequest.newBuilder()
.setId(offerId) .setId(offerId)
.build(); .build();
@ -224,7 +248,7 @@ public class OffersServiceRequest {
return grpcStubs.offersService.getOffer(request).getOffer(); return grpcStubs.offersService.getOffer(request).getOffer();
} }
public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) { public OfferInfo getMyBsqSwapOffer(String offerId) {
var request = GetMyOfferRequest.newBuilder() var request = GetMyOfferRequest.newBuilder()
.setId(offerId) .setId(offerId)
.build(); .build();
@ -239,10 +263,9 @@ public class OffersServiceRequest {
return grpcStubs.offersService.getMyOffer(request).getOffer(); return grpcStubs.offersService.getMyOffer(request).getOffer();
} }
public List<BsqSwapOfferInfo> getBsqSwapOffers(String direction, String currencyCode) { public List<OfferInfo> getBsqSwapOffers(String direction) {
var request = GetOffersRequest.newBuilder() var request = GetBsqSwapOffersRequest.newBuilder()
.setDirection(direction) .setDirection(direction)
.setCurrencyCode(currencyCode)
.build(); .build();
return grpcStubs.offersService.getBsqSwapOffers(request).getBsqSwapOffersList(); return grpcStubs.offersService.getBsqSwapOffers(request).getBsqSwapOffersList();
@ -278,11 +301,11 @@ public class OffersServiceRequest {
return offers.isEmpty() ? offers : sortOffersByDate(offers); return offers.isEmpty() ? offers : sortOffersByDate(offers);
} }
public List<BsqSwapOfferInfo> getBsqSwapOffersSortedByDate() { public List<OfferInfo> getBsqSwapOffersSortedByDate() {
ArrayList<BsqSwapOfferInfo> offers = new ArrayList<>(); ArrayList<OfferInfo> offers = new ArrayList<>();
offers.addAll(getBsqSwapOffers(BUY.name(), "BSQ")); offers.addAll(getBsqSwapOffers(BUY.name()));
offers.addAll(getBsqSwapOffers(SELL.name(), "BSQ")); offers.addAll(getBsqSwapOffers(SELL.name()));
return sortBsqSwapOffersByDate(offers); return sortOffersByDate(offers);
} }
public List<OfferInfo> getBsqOffersSortedByDate() { public List<OfferInfo> getBsqOffersSortedByDate() {
@ -292,10 +315,9 @@ public class OffersServiceRequest {
return sortOffersByDate(offers); return sortOffersByDate(offers);
} }
public List<BsqSwapOfferInfo> getMyBsqSwapOffers(String direction, String currencyCode) { public List<OfferInfo> getMyBsqSwapOffers(String direction) {
var request = GetMyOffersRequest.newBuilder() var request = GetBsqSwapOffersRequest.newBuilder()
.setDirection(direction) .setDirection(direction)
.setCurrencyCode(currencyCode)
.build(); .build();
return grpcStubs.offersService.getMyBsqSwapOffers(request).getBsqSwapOffersList(); return grpcStubs.offersService.getMyBsqSwapOffers(request).getBsqSwapOffersList();
} }
@ -344,11 +366,11 @@ public class OffersServiceRequest {
return sortOffersByDate(offers); return sortOffersByDate(offers);
} }
public List<BsqSwapOfferInfo> getMyBsqSwapOffersSortedByDate() { public List<OfferInfo> getMyBsqSwapOffersSortedByDate() {
ArrayList<BsqSwapOfferInfo> offers = new ArrayList<>(); ArrayList<OfferInfo> offers = new ArrayList<>();
offers.addAll(getMyBsqSwapOffers(BUY.name(), "BSQ")); offers.addAll(getMyBsqSwapOffers(BUY.name()));
offers.addAll(getMyBsqSwapOffers(SELL.name(), "BSQ")); offers.addAll(getMyBsqSwapOffers(SELL.name()));
return sortBsqSwapOffersByDate(offers); return sortOffersByDate(offers);
} }
public OfferInfo getMostRecentOffer(String direction, String currencyCode) { public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
@ -356,19 +378,20 @@ public class OffersServiceRequest {
return offers.isEmpty() ? null : offers.get(offers.size() - 1); return offers.isEmpty() ? null : offers.get(offers.size() - 1);
} }
public List<BsqSwapOfferInfo> sortBsqSwapOffersByDate(List<BsqSwapOfferInfo> offerInfoList) {
return offerInfoList.stream()
.sorted(comparing(BsqSwapOfferInfo::getDate))
.collect(toList());
}
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) { public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
return offerInfoList.stream() return offerInfoList.stream()
.sorted(comparing(OfferInfo::getDate)) .sorted(comparing(OfferInfo::getDate))
.collect(toList()); .collect(toList());
} }
private OfferCategory getOfferCategory(String offerId, boolean isMyOffer) {
var request = GetOfferCategoryRequest.newBuilder()
.setId(offerId)
.setIsMyOffer(isMyOffer)
.build();
return grpcStubs.offersService.getOfferCategory(request).getOfferCategory();
}
private static boolean isSupportedCryptoCurrency(String currencyCode) { private static boolean isSupportedCryptoCurrency(String currencyCode) {
return getSupportedCryptoCurrencies().contains(currencyCode.toUpperCase()); return getSupportedCryptoCurrencies().contains(currencyCode.toUpperCase());
} }

View file

@ -17,7 +17,6 @@
package bisq.cli.request; package bisq.cli.request;
import bisq.proto.grpc.BsqSwapTradeInfo;
import bisq.proto.grpc.ConfirmPaymentReceivedRequest; import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
import bisq.proto.grpc.ConfirmPaymentStartedRequest; import bisq.proto.grpc.ConfirmPaymentStartedRequest;
import bisq.proto.grpc.GetTradeRequest; import bisq.proto.grpc.GetTradeRequest;
@ -48,19 +47,20 @@ public class TradesServiceRequest {
return grpcStubs.tradesService.takeOffer(request); return grpcStubs.tradesService.takeOffer(request);
} }
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) { public TradeInfo takeBsqSwapOffer(String offerId) {
var reply = getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode); var reply = getTakeOfferReply(offerId, "", "");
if (reply.hasTrade()) if (reply.hasTrade())
return reply.getTrade(); return reply.getTrade();
else else
throw new IllegalStateException(reply.getFailureReason().getDescription()); throw new IllegalStateException(reply.getFailureReason().getDescription());
} }
public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) { public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
var request = GetTradeRequest.newBuilder() var reply = getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
.setTradeId(tradeId) if (reply.hasTrade())
.build(); return reply.getTrade();
return grpcStubs.tradesService.getBsqSwapTrade(request).getBsqSwapTrade(); else
throw new IllegalStateException(reply.getFailureReason().getDescription());
} }
public TradeInfo getTrade(String tradeId) { public TradeInfo getTrade(String tradeId) {

View file

@ -102,6 +102,15 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
@Nullable @Nullable
protected final Column<String> colAltcoinReceiveAddressColumn; protected final Column<String> colAltcoinReceiveAddressColumn;
// BSQ swap trade detail specific columns
@Nullable
protected final Column<String> status;
@Nullable
protected final Column<String> colTxId;
@Nullable
protected final Column<Long> colNumConfirmations;
AbstractTradeListBuilder(TableType tableType, List<?> protos) { AbstractTradeListBuilder(TableType tableType, List<?> protos) {
super(tableType, protos); super(tableType, protos);
validate(); validate();
@ -125,7 +134,9 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
this.colRole = colSupplier.roleColumn.get(); this.colRole = colSupplier.roleColumn.get();
this.colOfferType = colSupplier.offerTypeColumn.get(); this.colOfferType = colSupplier.offerTypeColumn.get();
this.colStatusDescription = colSupplier.statusDescriptionColumn.get(); this.colStatusDescription = colSupplier.statusDescriptionColumn.get();
// Trade detail specific columns
// Trade detail specific columns, some in common with BSQ swap trades detail.
this.colIsDepositPublished = colSupplier.depositPublishedColumn.get(); this.colIsDepositPublished = colSupplier.depositPublishedColumn.get();
this.colIsDepositConfirmed = colSupplier.depositConfirmedColumn.get(); this.colIsDepositConfirmed = colSupplier.depositConfirmedColumn.get();
this.colIsPayoutPublished = colSupplier.payoutPublishedColumn.get(); this.colIsPayoutPublished = colSupplier.payoutPublishedColumn.get();
@ -135,6 +146,12 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
this.colIsPaymentSent = colSupplier.paymentSentColumn.get(); this.colIsPaymentSent = colSupplier.paymentSentColumn.get();
this.colIsPaymentReceived = colSupplier.paymentReceivedColumn.get(); this.colIsPaymentReceived = colSupplier.paymentReceivedColumn.get();
this.colAltcoinReceiveAddressColumn = colSupplier.altcoinReceiveAddressColumn.get(); this.colAltcoinReceiveAddressColumn = colSupplier.altcoinReceiveAddressColumn.get();
// BSQ swap trade detail specific columns
this.status = colSupplier.bsqSwapStatusColumn.get();
this.colTxId = colSupplier.bsqSwapTxIdColumn.get();
this.colNumConfirmations = colSupplier.numConfirmationsColumn.get();
} }
protected void validate() { protected void validate() {
@ -149,9 +166,8 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
// Helper Functions // Helper Functions
private final Supplier<Boolean> isTradeDetailTblBuilder = () -> tableType.equals(TRADE_DETAIL_TBL); private final Supplier<Boolean> isTradeDetailTblBuilder = () -> tableType.equals(TRADE_DETAIL_TBL);
protected final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer()); protected final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
protected final Predicate<TradeInfo> isBsqSwapTrade = (t) -> t.getOffer().getIsBsqSwapOffer();
protected final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker"); protected final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
// Column Value Functions // Column Value Functions
@ -185,7 +201,6 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
} }
}; };
// TODO Move to TradeUtil ?
protected final Function<TradeInfo, String> toPriceDeviation = (t) -> protected final Function<TradeInfo, String> toPriceDeviation = (t) ->
t.getOffer().getUseMarketBasedPrice() t.getOffer().getUseMarketBasedPrice()
? formatToPercent(t.getOffer().getMarketPriceMargin()) ? formatToPercent(t.getOffer().getMarketPriceMargin())
@ -196,8 +211,6 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
? t.getTxFeeAsLong() ? t.getTxFeeAsLong()
: t.getOffer().getTxFee(); : t.getOffer().getTxFee();
// TODO Move to TradeUtil ?
protected final BiFunction<TradeInfo, Boolean, Long> toTradeFeeBsq = (t, isMyOffer) -> { protected final BiFunction<TradeInfo, Boolean, Long> toTradeFeeBsq = (t, isMyOffer) -> {
if (isMyOffer) { if (isMyOffer) {
return t.getOffer().getIsCurrencyForMakerFeeBtc() return t.getOffer().getIsCurrencyForMakerFeeBtc()
@ -210,7 +223,6 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
} }
}; };
// TODO Move to TradeUtil ?
protected final BiFunction<TradeInfo, Boolean, Long> toTradeFeeBtc = (t, isMyOffer) -> { protected final BiFunction<TradeInfo, Boolean, Long> toTradeFeeBtc = (t, isMyOffer) -> {
if (isMyOffer) { if (isMyOffer) {
return t.getOffer().getIsCurrencyForMakerFeeBtc() return t.getOffer().getIsCurrencyForMakerFeeBtc()
@ -223,12 +235,18 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
} }
}; };
protected final Function<TradeInfo, Long> toMyMakerOrTakerFee = (t) -> protected final Function<TradeInfo, Long> toMyMakerOrTakerFee = (t) -> {
isTaker.test(t) if (isBsqSwapTrade.test(t)) {
return isTaker.test(t)
? t.getBsqSwapTradeInfo().getBsqTakerTradeFee()
: t.getBsqSwapTradeInfo().getBsqMakerTradeFee();
} else {
return isTaker.test(t)
? t.getTakerFeeAsLong() ? t.getTakerFeeAsLong()
: t.getOffer().getMakerFee(); : t.getOffer().getMakerFee();
}
};
// TODO Move to TradeUtil ? SEE ClosedTradesViewModel # getDirectionLabel
protected final Function<TradeInfo, String> toOfferType = (t) -> { protected final Function<TradeInfo, String> toOfferType = (t) -> {
if (isFiatTrade.test(t)) { if (isFiatTrade.test(t)) {
return t.getOffer().getDirection() + " " + t.getOffer().getBaseCurrencyCode(); return t.getOffer().getDirection() + " " + t.getOffer().getBaseCurrencyCode();
@ -267,7 +285,7 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
} }
}; };
// TODO Stuff to move into bisq/cli/CurrencyFormat.java ? // TODO Move to bisq/cli/CurrencyFormat.java ?
public static String formatToPercent(double value) { public static String formatToPercent(double value) {
DecimalFormat decimalFormat = new DecimalFormat("#.##"); DecimalFormat decimalFormat = new DecimalFormat("#.##");

View file

@ -35,6 +35,7 @@ class TableBuilderConstants {
static final String COL_HEADER_LOCKUP_BONDS_BALANCE = "Lockup Bonds Balance"; static final String COL_HEADER_LOCKUP_BONDS_BALANCE = "Lockup Bonds Balance";
static final String COL_HEADER_UNLOCKING_BONDS_BALANCE = "Unlocking Bonds Balance"; static final String COL_HEADER_UNLOCKING_BONDS_BALANCE = "Unlocking Bonds Balance";
static final String COL_HEADER_UNVERIFIED_BALANCE = "Unverified Balance"; static final String COL_HEADER_UNVERIFIED_BALANCE = "Unverified Balance";
static final String COL_HEADER_BSQ_SWAP_TRADE_ROLE = "My BSQ Swap Role";
static final String COL_HEADER_BUYER_DEPOSIT = "Buyer Deposit"; static final String COL_HEADER_BUYER_DEPOSIT = "Buyer Deposit";
static final String COL_HEADER_SELLER_DEPOSIT = "Seller Deposit"; static final String COL_HEADER_SELLER_DEPOSIT = "Seller Deposit";
static final String COL_HEADER_CONFIRMATIONS = "Confirmations"; static final String COL_HEADER_CONFIRMATIONS = "Confirmations";
@ -65,8 +66,6 @@ class TableBuilderConstants {
static final String COL_HEADER_TRADE_ID = "Trade ID"; static final String COL_HEADER_TRADE_ID = "Trade ID";
static final String COL_HEADER_TRADE_ROLE = "My Role"; static final String COL_HEADER_TRADE_ROLE = "My Role";
static final String COL_HEADER_TRADE_SHORT_ID = "ID"; static final String COL_HEADER_TRADE_SHORT_ID = "ID";
@Deprecated
static final String COL_HEADER_TRADE_TX_FEE = "Tx Fee(BTC)";
static final String COL_HEADER_TRADE_MAKER_FEE = "Maker Fee(%-3s)"; static final String COL_HEADER_TRADE_MAKER_FEE = "Maker Fee(%-3s)";
static final String COL_HEADER_TRADE_TAKER_FEE = "Taker Fee(%-3s)"; static final String COL_HEADER_TRADE_TAKER_FEE = "Taker Fee(%-3s)";
static final String COL_HEADER_TRADE_FEE = "Trade Fee"; static final String COL_HEADER_TRADE_FEE = "Trade Fee";

View file

@ -17,10 +17,16 @@
package bisq.cli.table.builder; package bisq.cli.table.builder;
import bisq.proto.grpc.TradeInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL; import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
import static java.lang.String.format;
import static protobuf.BsqSwapTrade.State.COMPLETED;
import static protobuf.BsqSwapTrade.State.PREPARATION;
@ -30,8 +36,12 @@ import bisq.cli.table.column.Column;
/** /**
* Builds a {@code bisq.cli.table.Table} from a {@code bisq.proto.grpc.TradeInfo} object. * Builds a {@code bisq.cli.table.Table} from a {@code bisq.proto.grpc.TradeInfo} object.
*/ */
@SuppressWarnings("ConstantConditions")
class TradeDetailTableBuilder extends AbstractTradeListBuilder { class TradeDetailTableBuilder extends AbstractTradeListBuilder {
private final Predicate<TradeInfo> isPendingBsqSwap = (t) -> t.getState().equals(PREPARATION.name());
private final Predicate<TradeInfo> isCompletedBsqSwap = (t) -> t.getState().equals(COMPLETED.name());
TradeDetailTableBuilder(List<?> protos) { TradeDetailTableBuilder(List<?> protos) {
super(TRADE_DETAIL_TBL, protos); super(TRADE_DETAIL_TBL, protos);
} }
@ -41,33 +51,70 @@ class TradeDetailTableBuilder extends AbstractTradeListBuilder {
* @return Table containing one row * @return Table containing one row
*/ */
public Table build() { public Table build() {
populateColumns(); // A trade detail table only has one row.
List<Column<?>> columns = defineColumnList(); var trade = trades.get(0);
populateColumns(trade);
List<Column<?>> columns = defineColumnList(trade);
return new Table(columns.toArray(new Column<?>[0])); return new Table(columns.toArray(new Column<?>[0]));
} }
private void populateColumns() { private void populateColumns(TradeInfo trade) {
trades.stream().forEachOrdered(t -> { if (isBsqSwapTrade.test(trade)) {
colTradeId.addRow(t.getShortId()); var isPending = isPendingBsqSwap.test(trade);
colRole.addRow(t.getRole()); var isCompleted = isCompletedBsqSwap.test(trade);
colPrice.addRow(t.getTradePrice()); if (isPending == isCompleted)
colAmountInBtc.addRow(toAmount.apply(t)); throw new IllegalStateException(
colMinerTxFee.addRow(toMyMinerTxFee.apply(t)); format("programmer error: trade must be either pending or completed, is pending=%s and completed=%s",
colBisqTradeFee.addRow(toMyMakerOrTakerFee.apply(t)); isPending,
colIsDepositPublished.addRow(t.getIsDepositPublished()); isCompleted));
colIsDepositConfirmed.addRow(t.getIsDepositConfirmed()); populateBsqSwapTradeColumns(trade);
colTradeCost.addRow(toTradeVolume.apply(t)); } else {
colIsPaymentSent.addRow(t.getIsFiatSent()); populateBisqV1TradeColumns(trade);
colIsPaymentReceived.addRow(t.getIsFiatReceived()); }
colIsPayoutPublished.addRow(t.getIsPayoutPublished());
colIsFundsWithdrawn.addRow(t.getIsWithdrawn());
if (colAltcoinReceiveAddressColumn != null)
colAltcoinReceiveAddressColumn.addRow(toAltcoinReceiveAddress.apply(t));
});
} }
private List<Column<?>> defineColumnList() { private void populateBisqV1TradeColumns(TradeInfo trade) {
colTradeId.addRow(trade.getShortId());
colRole.addRow(trade.getRole());
colPrice.addRow(trade.getTradePrice());
colAmountInBtc.addRow(toAmount.apply(trade));
colMinerTxFee.addRow(toMyMinerTxFee.apply(trade));
colBisqTradeFee.addRow(toMyMakerOrTakerFee.apply(trade));
colIsDepositPublished.addRow(trade.getIsDepositPublished());
colIsDepositConfirmed.addRow(trade.getIsDepositConfirmed());
colTradeCost.addRow(toTradeVolume.apply(trade));
colIsPaymentSent.addRow(trade.getIsFiatSent());
colIsPaymentReceived.addRow(trade.getIsFiatReceived());
colIsPayoutPublished.addRow(trade.getIsPayoutPublished());
colIsFundsWithdrawn.addRow(trade.getIsWithdrawn());
if (colAltcoinReceiveAddressColumn != null)
colAltcoinReceiveAddressColumn.addRow(toAltcoinReceiveAddress.apply(trade));
}
private void populateBsqSwapTradeColumns(TradeInfo trade) {
colTradeId.addRow(trade.getShortId());
colRole.addRow(trade.getRole());
colPrice.addRow(trade.getTradePrice());
colAmountInBtc.addRow(toAmount.apply(trade));
colMinerTxFee.addRow(toMyMinerTxFee.apply(trade));
colBisqTradeFee.addRow(toMyMakerOrTakerFee.apply(trade));
colTradeCost.addRow(toTradeVolume.apply(trade));
var isCompleted = isCompletedBsqSwap.test(trade);
status.addRow(isCompleted ? "COMPLETED" : "PENDING");
if (isCompleted) {
colTxId.addRow(trade.getBsqSwapTradeInfo().getTxId());
colNumConfirmations.addRow(trade.getBsqSwapTradeInfo().getNumConfirmations());
}
}
private List<Column<?>> defineColumnList(TradeInfo trade) {
return isBsqSwapTrade.test(trade)
? getBsqSwapTradeColumnList(isCompletedBsqSwap.test(trade))
: getBisqV1TradeColumnList();
}
private List<Column<?>> getBisqV1TradeColumnList() {
List<Column<?>> columns = new ArrayList<>() {{ List<Column<?>> columns = new ArrayList<>() {{
add(colTradeId); add(colTradeId);
add(colRole); add(colRole);
@ -89,4 +136,25 @@ class TradeDetailTableBuilder extends AbstractTradeListBuilder {
return columns; return columns;
} }
private List<Column<?>> getBsqSwapTradeColumnList(boolean isCompleted) {
List<Column<?>> columns = new ArrayList<>() {{
add(colTradeId);
add(colRole);
add(colPrice.asStringColumn());
add(colAmountInBtc.asStringColumn());
add(colMinerTxFee.asStringColumn());
add(colBisqTradeFee.asStringColumn());
add(colTradeCost.asStringColumn());
add(status);
}};
if (isCompleted)
columns.add(colTxId);
if (!colNumConfirmations.isEmpty())
columns.add(colNumConfirmations.asStringColumn());
return columns;
}
} }

View file

@ -40,6 +40,7 @@ import static bisq.cli.table.column.AltcoinColumn.DISPLAY_MODE.ALTCOIN_OFFER_VOL
import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT; import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT; import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
import static bisq.cli.table.column.FiatColumn.DISPLAY_MODE.VOLUME; import static bisq.cli.table.column.FiatColumn.DISPLAY_MODE.VOLUME;
import static java.lang.String.format;
@ -49,6 +50,7 @@ import bisq.cli.table.column.BtcColumn;
import bisq.cli.table.column.Column; import bisq.cli.table.column.Column;
import bisq.cli.table.column.FiatColumn; import bisq.cli.table.column.FiatColumn;
import bisq.cli.table.column.Iso8601DateTimeColumn; import bisq.cli.table.column.Iso8601DateTimeColumn;
import bisq.cli.table.column.LongColumn;
import bisq.cli.table.column.MixedPriceColumn; import bisq.cli.table.column.MixedPriceColumn;
import bisq.cli.table.column.MixedTradeFeeColumn; import bisq.cli.table.column.MixedTradeFeeColumn;
import bisq.cli.table.column.MixedVolumeColumn; import bisq.cli.table.column.MixedVolumeColumn;
@ -79,7 +81,10 @@ class TradeTableColumnSupplier {
private final Supplier<TradeInfo> firstRow = () -> getTrades().get(0); private final Supplier<TradeInfo> firstRow = () -> getTrades().get(0);
private final Predicate<OfferInfo> isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC"); private final Predicate<OfferInfo> isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC");
private final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer()); private final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
private final Predicate<TradeInfo> isBsqSwapTrade = (t) -> t.getOffer().getIsBsqSwapOffer();
private final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker"); private final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
private final Supplier<Boolean> isSwapTradeDetail = () ->
isTradeDetailTblBuilder.get() && isBsqSwapTrade.test(firstRow.get());
final Supplier<StringColumn> tradeIdColumn = () -> isTradeDetailTblBuilder.get() final Supplier<StringColumn> tradeIdColumn = () -> isTradeDetailTblBuilder.get()
? new StringColumn(COL_HEADER_TRADE_SHORT_ID) ? new StringColumn(COL_HEADER_TRADE_SHORT_ID)
@ -95,8 +100,8 @@ class TradeTableColumnSupplier {
private final Function<TradeInfo, Column<Long>> toDetailedPriceColumn = (t) -> { private final Function<TradeInfo, Column<Long>> toDetailedPriceColumn = (t) -> {
String colHeader = isFiatTrade.test(t) String colHeader = isFiatTrade.test(t)
? String.format(COL_HEADER_DETAILED_PRICE, t.getOffer().getCounterCurrencyCode()) ? format(COL_HEADER_DETAILED_PRICE, t.getOffer().getCounterCurrencyCode())
: String.format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, t.getOffer().getBaseCurrencyCode()); : format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, t.getOffer().getBaseCurrencyCode());
return isFiatTrade.test(t) return isFiatTrade.test(t)
? new FiatColumn(colHeader) ? new FiatColumn(colHeader)
: new AltcoinColumn(colHeader); : new AltcoinColumn(colHeader);
@ -116,7 +121,7 @@ class TradeTableColumnSupplier {
private final Function<TradeInfo, Column<Long>> toDetailedAmountColumn = (t) -> { private final Function<TradeInfo, Column<Long>> toDetailedAmountColumn = (t) -> {
String headerCurrencyCode = t.getOffer().getBaseCurrencyCode(); String headerCurrencyCode = t.getOffer().getBaseCurrencyCode();
String colHeader = String.format(COL_HEADER_DETAILED_AMOUNT, headerCurrencyCode); String colHeader = format(COL_HEADER_DETAILED_AMOUNT, headerCurrencyCode);
return isFiatTrade.test(t) return isFiatTrade.test(t)
? new SatoshiColumn(colHeader) ? new SatoshiColumn(colHeader)
: new AltcoinColumn(colHeader, ALTCOIN_OFFER_VOLUME); : new AltcoinColumn(colHeader, ALTCOIN_OFFER_VOLUME);
@ -142,10 +147,14 @@ class TradeTableColumnSupplier {
? null ? null
: new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT); : new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT);
final Supplier<StringColumn> roleColumn = () -> final Supplier<StringColumn> roleColumn = () -> {
isTradeDetailTblBuilder.get() || isOpenTradeTblBuilder.get() || isFailedTradeTblBuilder.get() if (isSwapTradeDetail.get())
return new StringColumn(COL_HEADER_BSQ_SWAP_TRADE_ROLE);
else
return isTradeDetailTblBuilder.get() || isOpenTradeTblBuilder.get() || isFailedTradeTblBuilder.get()
? new StringColumn(COL_HEADER_TRADE_ROLE) ? new StringColumn(COL_HEADER_TRADE_ROLE)
: null; : null;
};
final Function<String, Column<Long>> toSecurityDepositColumn = (name) -> isClosedTradeTblBuilder.get() final Function<String, Column<Long>> toSecurityDepositColumn = (name) -> isClosedTradeTblBuilder.get()
? new SatoshiColumn(name) ? new SatoshiColumn(name)
@ -161,21 +170,42 @@ class TradeTableColumnSupplier {
private final Function<String, Column<Boolean>> toBooleanColumn = BooleanColumn::new; private final Function<String, Column<Boolean>> toBooleanColumn = BooleanColumn::new;
final Supplier<Column<Boolean>> depositPublishedColumn = () -> isTradeDetailTblBuilder.get() final Supplier<Column<Boolean>> depositPublishedColumn = () -> {
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_PUBLISHED) if (isSwapTradeDetail.get())
: null; return null;
else
return isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_PUBLISHED)
: null;
};
final Supplier<Column<Boolean>> depositConfirmedColumn = () -> isTradeDetailTblBuilder.get() final Supplier<Column<Boolean>> depositConfirmedColumn = () -> {
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_CONFIRMED) if (isSwapTradeDetail.get())
: null; return null;
else
return isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_CONFIRMED)
: null;
final Supplier<Column<Boolean>> payoutPublishedColumn = () -> isTradeDetailTblBuilder.get() };
? toBooleanColumn.apply(COL_HEADER_TRADE_PAYOUT_PUBLISHED)
: null;
final Supplier<Column<Boolean>> fundsWithdrawnColumn = () -> isTradeDetailTblBuilder.get() final Supplier<Column<Boolean>> payoutPublishedColumn = () -> {
? toBooleanColumn.apply(COL_HEADER_TRADE_WITHDRAWN) if (isSwapTradeDetail.get())
: null; return null;
else
return isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_PAYOUT_PUBLISHED)
: null;
};
final Supplier<Column<Boolean>> fundsWithdrawnColumn = () -> {
if (isSwapTradeDetail.get())
return null;
else
return isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_WITHDRAWN)
: null;
};
final Supplier<Column<Long>> bisqTradeDetailFeeColumn = () -> { final Supplier<Column<Long>> bisqTradeDetailFeeColumn = () -> {
if (isTradeDetailTblBuilder.get()) { if (isTradeDetailTblBuilder.get()) {
@ -184,8 +214,8 @@ class TradeTableColumnSupplier {
? t.getIsCurrencyForTakerFeeBtc() ? "BTC" : "BSQ" ? t.getIsCurrencyForTakerFeeBtc() ? "BTC" : "BSQ"
: t.getOffer().getIsCurrencyForMakerFeeBtc() ? "BTC" : "BSQ"; : t.getOffer().getIsCurrencyForMakerFeeBtc() ? "BTC" : "BSQ";
String colHeader = isTaker.test(t) String colHeader = isTaker.test(t)
? String.format(COL_HEADER_TRADE_TAKER_FEE, headerCurrencyCode) ? format(COL_HEADER_TRADE_TAKER_FEE, headerCurrencyCode)
: String.format(COL_HEADER_TRADE_MAKER_FEE, headerCurrencyCode); : format(COL_HEADER_TRADE_MAKER_FEE, headerCurrencyCode);
boolean isBsqSatoshis = headerCurrencyCode.equals("BSQ"); boolean isBsqSatoshis = headerCurrencyCode.equals("BSQ");
return new SatoshiColumn(colHeader, isBsqSatoshis); return new SatoshiColumn(colHeader, isBsqSatoshis);
} else { } else {
@ -201,7 +231,7 @@ class TradeTableColumnSupplier {
final Supplier<Column<Boolean>> paymentSentColumn = () -> { final Supplier<Column<Boolean>> paymentSentColumn = () -> {
if (isTradeDetailTblBuilder.get()) { if (isTradeDetailTblBuilder.get()) {
String headerCurrencyCode = toPaymentCurrencyCode.apply(firstRow.get()); String headerCurrencyCode = toPaymentCurrencyCode.apply(firstRow.get());
String colHeader = String.format(COL_HEADER_TRADE_PAYMENT_SENT, headerCurrencyCode); String colHeader = format(COL_HEADER_TRADE_PAYMENT_SENT, headerCurrencyCode);
return new BooleanColumn(colHeader); return new BooleanColumn(colHeader);
} else { } else {
return null; return null;
@ -211,7 +241,7 @@ class TradeTableColumnSupplier {
final Supplier<Column<Boolean>> paymentReceivedColumn = () -> { final Supplier<Column<Boolean>> paymentReceivedColumn = () -> {
if (isTradeDetailTblBuilder.get()) { if (isTradeDetailTblBuilder.get()) {
String headerCurrencyCode = toPaymentCurrencyCode.apply(firstRow.get()); String headerCurrencyCode = toPaymentCurrencyCode.apply(firstRow.get());
String colHeader = String.format(COL_HEADER_TRADE_PAYMENT_RECEIVED, headerCurrencyCode); String colHeader = format(COL_HEADER_TRADE_PAYMENT_RECEIVED, headerCurrencyCode);
return new BooleanColumn(colHeader); return new BooleanColumn(colHeader);
} else { } else {
return null; return null;
@ -222,7 +252,7 @@ class TradeTableColumnSupplier {
if (isTradeDetailTblBuilder.get()) { if (isTradeDetailTblBuilder.get()) {
TradeInfo t = firstRow.get(); TradeInfo t = firstRow.get();
String headerCurrencyCode = t.getOffer().getCounterCurrencyCode(); String headerCurrencyCode = t.getOffer().getCounterCurrencyCode();
String colHeader = String.format(COL_HEADER_TRADE_BUYER_COST, headerCurrencyCode); String colHeader = format(COL_HEADER_TRADE_BUYER_COST, headerCurrencyCode);
return isFiatTrade.test(t) return isFiatTrade.test(t)
? new FiatColumn(colHeader, VOLUME) ? new FiatColumn(colHeader, VOLUME)
: new SatoshiColumn(colHeader); : new SatoshiColumn(colHeader);
@ -231,6 +261,18 @@ class TradeTableColumnSupplier {
} }
}; };
final Supplier<Column<String>> bsqSwapTxIdColumn = () -> isSwapTradeDetail.get()
? new StringColumn(COL_HEADER_TX_ID)
: null;
final Supplier<Column<String>> bsqSwapStatusColumn = () -> isSwapTradeDetail.get()
? new StringColumn(COL_HEADER_STATUS)
: null;
final Supplier<Column<Long>> numConfirmationsColumn = () -> isSwapTradeDetail.get()
? new LongColumn(COL_HEADER_CONFIRMATIONS)
: null;
final Predicate<TradeInfo> showAltCoinBuyerAddress = (t) -> { final Predicate<TradeInfo> showAltCoinBuyerAddress = (t) -> {
if (isFiatTrade.test(t)) { if (isFiatTrade.test(t)) {
return false; return false;
@ -251,7 +293,7 @@ class TradeTableColumnSupplier {
TradeInfo t = firstRow.get(); TradeInfo t = firstRow.get();
if (showAltCoinBuyerAddress.test(t)) { if (showAltCoinBuyerAddress.test(t)) {
String headerCurrencyCode = toPaymentCurrencyCode.apply(t); String headerCurrencyCode = toPaymentCurrencyCode.apply(t);
String colHeader = String.format(COL_HEADER_TRADE_ALTCOIN_BUYER_ADDRESS, headerCurrencyCode); String colHeader = format(COL_HEADER_TRADE_ALTCOIN_BUYER_ADDRESS, headerCurrencyCode);
return new StringColumn(colHeader); return new StringColumn(colHeader);
} else { } else {
return null; return null;

View file

@ -0,0 +1,93 @@
/*
* 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.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying Integer values.
*/
public class IntegerColumn extends NumberColumn<IntegerColumn, Integer> {
protected final List<Integer> rows = new ArrayList<>();
protected final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
// The default IntegerColumn JUSTIFICATION is RIGHT.
public IntegerColumn(String name) {
this(name, RIGHT);
}
public IntegerColumn(String name, JUSTIFICATION justification) {
super(name, justification);
this.maxWidth = name.length();
}
@Override
public void addRow(Integer value) {
rows.add(value);
String s = String.valueOf(value);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public List<Integer> getRows() {
return rows;
}
@Override
public int rowCount() {
return rows.size();
}
@Override
public boolean isEmpty() {
return rows.isEmpty();
}
@Override
public Integer getRow(int rowIndex) {
return rows.get(rowIndex);
}
@Override
public void updateRow(int rowIndex, Integer newValue) {
rows.set(rowIndex, newValue);
}
@Override
public String getRowAsFormattedString(int rowIndex) {
String s = String.valueOf(getRow(rowIndex));
return toJustifiedString(s);
}
@Override
public StringColumn asStringColumn() {
IntStream.range(0, rows.size()).forEachOrdered(rowIndex ->
stringColumn.addRow(getRowAsFormattedString(rowIndex)));
return stringColumn;
}
}

View file

@ -0,0 +1,85 @@
package bisq.cli;
import static java.lang.System.out;
import static java.util.Arrays.stream;
/**
Smoke tests for createoffer method. Useful for testing CLI command and examining the
format of its console output.
Prerequisites:
- Run `./bisq-apitest --apiPassword=xyz --supportingApps=bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon --shutdownAfterTests=false --enableBisqDebugging=false`
Note: Test harness will not automatically generate BTC blocks to confirm transactions.
Never run on mainnet!
*/
@SuppressWarnings({"CommentedOutCode", "unused"})
public class CreateOfferSmokeTest extends AbstractCliTest {
public static void main(String[] args) {
createBsqSwapOffer("buy");
createBsqSwapOffer("sell");
}
private static void createBsqSwapOffer(String direction) {
String[] args = createBsqSwapOfferCommand(direction, "0.01", "0.005", "0.00005");
out.print(">>>>> bisq-cli ");
stream(args).forEach(a -> out.print(a + " "));
out.println();
CliMain.main(args);
out.println("<<<<<");
args = getMyOffersCommand(direction);
out.print(">>>>> bisq-cli ");
stream(args).forEach(a -> out.print(a + " "));
out.println();
CliMain.main(args);
out.println("<<<<<");
args = getAvailableOffersCommand(direction);
out.print(">>>>> bisq-cli ");
stream(args).forEach(a -> out.print(a + " "));
out.println();
CliMain.main(args);
out.println("<<<<<");
}
private static String[] createBsqSwapOfferCommand(String direction,
String amount,
String minAmount,
String fixedPrice) {
return new String[]{
PASSWORD_OPT,
ALICE_PORT_OPT,
"createoffer",
"--swap=true",
"--direction=" + direction,
"--currency-code=bsq",
"--amount=" + amount,
"--min-amount=" + minAmount,
"--fixed-price=" + fixedPrice
};
}
private static String[] getMyOffersCommand(String direction) {
return new String[]{
PASSWORD_OPT,
ALICE_PORT_OPT,
"getmyoffers",
"--direction=" + direction,
"--currency-code=bsq"
};
}
private static String[] getAvailableOffersCommand(String direction) {
return new String[]{
PASSWORD_OPT,
BOB_PORT_OPT,
"getoffers",
"--direction=" + direction,
"--currency-code=bsq"
};
}
}

View file

@ -11,15 +11,50 @@ import static java.lang.System.out;
This can be run on mainnet. This can be run on mainnet.
*/ */
public class GetOffersSmokeTest { @SuppressWarnings({"CommentedOutCode", "unused"})
public class GetOffersSmokeTest extends AbstractCliTest {
// TODO use the static password and port opt definitions in superclass
public static void main(String[] args) { public static void main(String[] args) {
getMyBsqOffers();
// getAvailableBsqOffers();
// getMyUsdOffers();
// getAvailableUsdOffers();
}
private static void getMyBsqOffers() {
out.println(">>> getmyoffers buy bsq");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "getmyoffers", "--direction=buy", "--currency-code=bsq"});
out.println(">>> getmyoffers sell bsq");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "getmyoffers", "--direction=sell", "--currency-code=bsq"});
out.println(">>> getmyoffer --offer-id=KRONTTMO-11cef1a9-c636-4dc7-b3f2-1616e4960c28-175");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "getmyoffer", "--offer-id=KRONTTMO-11cef1a9-c636-4dc7-b3f2-1616e4960c28-175"});
}
private static void getAvailableBsqOffers() {
out.println(">>> getoffers buy bsq");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "getoffers", "--direction=buy", "--currency-code=bsq"});
out.println(">>> getoffers sell bsq");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "getoffers", "--direction=sell", "--currency-code=bsq"});
}
private static void getMyUsdOffers() {
out.println(">>> getmyoffers buy usd");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "getmyoffers", "--direction=buy", "--currency-code=usd"});
out.println(">>> getmyoffers sell usd");
CliMain.main(new String[]{"--password=xyz", "--port=9998", "getmyoffers", "--direction=sell", "--currency-code=usd"});
}
private static void getAvailableUsdOffers() {
out.println(">>> getoffers buy usd"); out.println(">>> getoffers buy usd");
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=buy", "--currency-code=usd"}); CliMain.main(new String[]{"--password=xyz", "--port=9998", "getoffers", "--direction=buy", "--currency-code=usd"});
out.println(">>> getoffers sell usd"); out.println(">>> getoffers sell usd");
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=sell", "--currency-code=usd"}); CliMain.main(new String[]{"--password=xyz", "--port=9998", "getoffers", "--direction=sell", "--currency-code=usd"});
}
private static void TODO() {
out.println(">>> getoffers buy eur"); out.println(">>> getoffers buy eur");
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=buy", "--currency-code=eur"}); CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=buy", "--currency-code=eur"});
out.println(">>> getoffers sell eur"); out.println(">>> getoffers sell eur");
@ -35,5 +70,4 @@ public class GetOffersSmokeTest {
out.println(">>> getoffers sell brl"); out.println(">>> getoffers sell brl");
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=sell", "--currency-code=brl"}); CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=sell", "--currency-code=brl"});
} }
} }

View file

@ -9,6 +9,7 @@ import static bisq.cli.Method.createpaymentacct;
import static bisq.cli.opts.OptLabel.*; import static bisq.cli.opts.OptLabel.*;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class OptionParsersTest { public class OptionParsersTest {
@ -62,13 +63,16 @@ public class OptionParsersTest {
new CancelOfferOptionParser(args).parse(); new CancelOfferOptionParser(args).parse();
} }
// createoffer opt parser tests // createoffer (v1) opt parser tests
@Test @Test
public void testCreateOfferOptParserWithMissingPaymentAccountIdOptShouldThrowException() { public void testCreateOfferWithMissingPaymentAccountIdOptShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createoffer.name() createoffer.name(),
"--" + OPT_DIRECTION + "=" + "SELL",
"--" + OPT_CURRENCY_CODE + "=" + "JPY",
"--" + OPT_AMOUNT + "=" + "0.1"
}; };
Throwable exception = assertThrows(RuntimeException.class, () -> Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse()); new CreateOfferOptionParser(args).parse());
@ -76,7 +80,7 @@ public class OptionParsersTest {
} }
@Test @Test
public void testCreateOfferOptParserWithEmptyPaymentAccountIdOptShouldThrowException() { public void testCreateOfferWithEmptyPaymentAccountIdOptShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createoffer.name(), createoffer.name(),
@ -88,7 +92,7 @@ public class OptionParsersTest {
} }
@Test @Test
public void testCreateOfferOptParserWithMissingDirectionOptShouldThrowException() { public void testCreateOfferWithMissingDirectionOptShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createoffer.name(), createoffer.name(),
@ -101,7 +105,7 @@ public class OptionParsersTest {
@Test @Test
public void testCreateOfferOptParserWithMissingDirectionOptValueShouldThrowException() { public void testCreateOfferWithMissingDirectionOptValueShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createoffer.name(), createoffer.name(),
@ -134,10 +138,114 @@ public class OptionParsersTest {
assertEquals("25.0", parser.getSecurityDeposit()); assertEquals("25.0", parser.getSecurityDeposit());
} }
// createoffer (bsq swap) opt parser tests
@Test
public void testCreateBsqSwapOfferWithPaymentAcctIdOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
"--" + OPT_SWAP + "=" + "true",
"--" + OPT_PAYMENT_ACCOUNT + "=" + "abc",
"--" + OPT_DIRECTION + "=" + "buy",
"--" + OPT_CURRENCY_CODE + "=" + "bsq",
"--" + OPT_AMOUNT + "=" + "0.125"
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse());
assertEquals("cannot use a payment account id in bsq swap offer", exception.getMessage());
}
@Test
public void testCreateBsqSwapOfferWithMktMarginPriceOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
"--" + OPT_SWAP + "=" + "true",
"--" + OPT_DIRECTION + "=" + "buy",
"--" + OPT_CURRENCY_CODE + "=" + "bsq",
"--" + OPT_AMOUNT + "=" + "0.125",
"--" + OPT_MKT_PRICE_MARGIN + "=" + "0.0"
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse());
assertEquals("cannot use a market price margin in bsq swap offer", exception.getMessage());
}
@Test
public void testCreateBsqSwapOfferWithSecurityDepositOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
"--" + OPT_SWAP + "=" + "true",
"--" + OPT_DIRECTION + "=" + "buy",
"--" + OPT_CURRENCY_CODE + "=" + "bsq",
"--" + OPT_AMOUNT + "=" + "0.125",
"--" + OPT_SECURITY_DEPOSIT + "=" + "25.0"
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse());
assertEquals("cannot use a security deposit in bsq swap offer", exception.getMessage());
}
@Test
public void testCreateBsqSwapOfferWithMissingFixedPriceOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
"--" + OPT_SWAP + "=" + "true",
"--" + OPT_DIRECTION + "=" + "sell",
"--" + OPT_CURRENCY_CODE + "=" + "bsq",
"--" + OPT_MIN_AMOUNT + "=" + "0.075",
"--" + OPT_AMOUNT + "=" + "0.125"
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse());
assertEquals("no fixed price specified", exception.getMessage());
}
@Test
public void testCreateBsqSwapOfferWithIncorrectCurrencyCodeOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
"--" + OPT_SWAP + "=" + "true",
"--" + OPT_DIRECTION + "=" + "sell",
"--" + OPT_CURRENCY_CODE + "=" + "xmr",
"--" + OPT_MIN_AMOUNT + "=" + "0.075",
"--" + OPT_AMOUNT + "=" + "0.125",
"--" + OPT_FIXED_PRICE + "=" + "0.00005555"
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse());
assertEquals("only bsq swaps are currently supported", exception.getMessage());
}
@Test
public void testValidCreateBsqSwapOfferOpts() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
"--" + OPT_SWAP + "=" + "true",
"--" + OPT_DIRECTION + "=" + "sell",
"--" + OPT_CURRENCY_CODE + "=" + "bsq",
"--" + OPT_MIN_AMOUNT + "=" + "0.075",
"--" + OPT_AMOUNT + "=" + "0.125",
"--" + OPT_FIXED_PRICE + "=" + "0.00005555"
};
CreateOfferOptionParser parser = new CreateOfferOptionParser(args).parse();
assertTrue(parser.getIsSwap());
assertEquals("sell", parser.getDirection());
assertEquals("bsq", parser.getCurrencyCode());
assertEquals("0.075", parser.getMinAmount());
assertEquals("0.125", parser.getAmount());
assertEquals("0.00005555", parser.getFixedPrice());
}
// createpaymentacct opt parser tests // createpaymentacct opt parser tests
@Test @Test
public void testCreatePaymentAcctOptParserWithMissingPaymentFormOptShouldThrowException() { public void testCreatePaymentAcctWithMissingPaymentFormOptShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createpaymentacct.name() createpaymentacct.name()
@ -149,7 +257,7 @@ public class OptionParsersTest {
} }
@Test @Test
public void testCreatePaymentAcctOptParserWithMissingPaymentFormOptValueShouldThrowException() { public void testCreatePaymentAcctWithMissingPaymentFormOptValueShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createpaymentacct.name(), createpaymentacct.name(),
@ -161,7 +269,7 @@ public class OptionParsersTest {
} }
@Test @Test
public void testCreatePaymentAcctOptParserWithInvalidPaymentFormOptValueShouldThrowException() { public void testCreatePaymentAcctWithInvalidPaymentFormOptValueShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createpaymentacct.name(), createpaymentacct.name(),
@ -180,7 +288,7 @@ public class OptionParsersTest {
// createcryptopaymentacct parser tests // createcryptopaymentacct parser tests
@Test @Test
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingAcctNameOptShouldThrowException() { public void testCreateCryptoCurrencyPaymentAcctWithMissingAcctNameOptShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createcryptopaymentacct.name() createcryptopaymentacct.name()
@ -191,7 +299,7 @@ public class OptionParsersTest {
} }
@Test @Test
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithEmptyAcctNameOptShouldThrowException() { public void testCreateCryptoCurrencyPaymentAcctWithEmptyAcctNameOptShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createcryptopaymentacct.name(), createcryptopaymentacct.name(),
@ -203,7 +311,7 @@ public class OptionParsersTest {
} }
@Test @Test
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingCurrencyCodeOptShouldThrowException() { public void testCreateCryptoCurrencyPaymentAcctWithMissingCurrencyCodeOptShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createcryptopaymentacct.name(), createcryptopaymentacct.name(),
@ -215,7 +323,7 @@ public class OptionParsersTest {
} }
@Test @Test
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithInvalidCurrencyCodeOptShouldThrowException() { public void testCreateCryptoCurrencyPaymentAcctWithInvalidCurrencyCodeOptShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createcryptopaymentacct.name(), createcryptopaymentacct.name(),
@ -228,7 +336,7 @@ public class OptionParsersTest {
} }
@Test @Test
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingAddressOptShouldThrowException() { public void testCreateCryptoCurrencyPaymentAcctWithMissingAddressOptShouldThrowException() {
String[] args = new String[]{ String[] args = new String[]{
PASSWORD_OPT, PASSWORD_OPT,
createcryptopaymentacct.name(), createcryptopaymentacct.name(),
@ -241,7 +349,7 @@ public class OptionParsersTest {
} }
@Test @Test
public void testCreateCryptoCurrencyPaymentAcctOptionParser() { public void testCreateCryptoCurrencyPaymentAcct() {
var acctName = "bsq payment account"; var acctName = "bsq payment account";
var currencyCode = "bsq"; var currencyCode = "bsq";
var address = "B1nXyZ"; // address is validated on server var address = "B1nXyZ"; // address is validated on server

View file

@ -26,6 +26,7 @@ import bisq.core.offer.OpenOffer;
import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.PaymentMethod;
import bisq.core.trade.bisq_v1.TradeResultHandler; import bisq.core.trade.bisq_v1.TradeResultHandler;
import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.core.trade.statistics.TradeStatistics3; import bisq.core.trade.statistics.TradeStatistics3;
@ -119,6 +120,18 @@ public class CoreApi {
// Offers // Offers
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public boolean isFiatOffer(String id, boolean isMyOffer) {
return coreOffersService.isFiatOffer(id, isMyOffer);
}
public boolean isAltcoinOffer(String id, boolean isMyOffer) {
return coreOffersService.isAltcoinOffer(id, isMyOffer);
}
public boolean isBsqSwapOffer(String id, boolean isMyOffer) {
return coreOffersService.isBsqSwapOffer(id, isMyOffer);
}
public Offer getBsqSwapOffer(String id) { public Offer getBsqSwapOffer(String id) {
return coreOffersService.getBsqSwapOffer(id); return coreOffersService.getBsqSwapOffer(id);
} }
@ -264,14 +277,10 @@ public class CoreApi {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void takeBsqSwapOffer(String offerId, public void takeBsqSwapOffer(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode,
TradeResultHandler<BsqSwapTrade> tradeResultHandler, TradeResultHandler<BsqSwapTrade> tradeResultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
Offer bsqSwapOffer = coreOffersService.getBsqSwapOffer(offerId); Offer bsqSwapOffer = coreOffersService.getBsqSwapOffer(offerId);
coreTradesService.takeBsqSwapOffer(bsqSwapOffer, coreTradesService.takeBsqSwapOffer(bsqSwapOffer,
paymentAccountId,
takerFeeCurrencyCode,
tradeResultHandler, tradeResultHandler,
errorMessageHandler); errorMessageHandler);
} }
@ -305,18 +314,18 @@ public class CoreApi {
coreTradesService.withdrawFunds(tradeId, address, memo); coreTradesService.withdrawFunds(tradeId, address, memo);
} }
public BsqSwapTrade getBsqSwapTrade(String tradeId) { public TradeModel getTradeModel(String tradeId) {
return coreTradesService.getBsqSwapTrade(tradeId); return coreTradesService.getTradeModel(tradeId);
}
public Trade getTrade(String tradeId) {
return coreTradesService.getTrade(tradeId);
} }
public String getTradeRole(String tradeId) { public String getTradeRole(String tradeId) {
return coreTradesService.getTradeRole(tradeId); return coreTradesService.getTradeRole(tradeId);
} }
public String getBsqSwapTradeRole(BsqSwapTrade bsqSwapTrade) {
return coreTradesService.getBsqSwapTradeRole(bsqSwapTrade);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Wallets // Wallets
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -381,6 +390,10 @@ public class CoreApi {
return walletsService.getTransaction(txId); return walletsService.getTransaction(txId);
} }
public int getTransactionConfirmations(String txId) {
return walletsService.getTransactionConfirmations(txId);
}
public void setWalletPassword(String password, String newPassword) { public void setWalletPassword(String password, String newPassword) {
walletsService.setWalletPassword(password, newPassword); walletsService.setWalletPassword(password, newPassword);
} }

View file

@ -48,6 +48,8 @@ import java.math.BigDecimal;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -60,6 +62,7 @@ import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency; import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
import static bisq.core.offer.Offer.State; import static bisq.core.offer.Offer.State;
import static bisq.core.offer.OfferDirection.BUY; import static bisq.core.offer.OfferDirection.BUY;
import static bisq.core.offer.OfferUtil.getRandomOfferId;
import static bisq.core.offer.OpenOffer.State.AVAILABLE; import static bisq.core.offer.OpenOffer.State.AVAILABLE;
import static bisq.core.offer.OpenOffer.State.DEACTIVATED; import static bisq.core.offer.OpenOffer.State.DEACTIVATED;
import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer; import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer;
@ -78,6 +81,9 @@ class CoreOffersService {
private final Supplier<Comparator<OpenOffer>> openOfferPriceComparator = () -> private final Supplier<Comparator<OpenOffer>> openOfferPriceComparator = () ->
comparing(openOffer -> openOffer.getOffer().getPrice()); comparing(openOffer -> openOffer.getOffer().getPrice());
private final BiFunction<String, Boolean, Offer> toOfferWithId = (id, isMyOffer) ->
isMyOffer ? getMyOffer(id).getOffer() : getOffer(id);
private final CoreContext coreContext; private final CoreContext coreContext;
private final KeyRing keyRing; private final KeyRing keyRing;
// Dependencies on core api services in this package must be kept to an absolute // Dependencies on core api services in this package must be kept to an absolute
@ -118,51 +124,48 @@ class CoreOffersService {
this.user = user; this.user = user;
} }
Offer getBsqSwapOffer(String id) { boolean isFiatOffer(String id, boolean isMyOffer) {
return offerBookService.getOffers().stream() var offer = toOfferWithId.apply(id, isMyOffer);
.filter(o -> o.getId().equals(id)) return OfferUtil.isFiatOffer(offer);
.filter(o -> !o.isMyOffer(keyRing)) }
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
.filter(o -> o.isBsqSwapOffer()) boolean isAltcoinOffer(String id, boolean isMyOffer) {
.findAny().orElseThrow(() -> var offer = toOfferWithId.apply(id, isMyOffer);
new IllegalStateException(format("offer with id '%s' not found", id))); return OfferUtil.isAltcoinOffer(offer);
}
boolean isBsqSwapOffer(String id, boolean isMyOffer) {
var offer = toOfferWithId.apply(id, isMyOffer);
return offer.isBsqSwapOffer();
} }
Offer getOffer(String id) { Offer getOffer(String id) {
return offerBookService.getOffers().stream() return findAvailableOffer(id).orElseThrow(() ->
.filter(o -> o.getId().equals(id)) new IllegalStateException(format("offer with id '%s' not found", id)));
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
.findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
} }
OpenOffer getMyOffer(String id) { OpenOffer getMyOffer(String id) {
return openOfferManager.getObservableList().stream() return findMyOpenOffer(id).orElseThrow(() ->
.filter(o -> o.getId().equals(id)) new IllegalStateException(format("offer with id '%s' not found", id)));
.filter(o -> o.getOffer().isMyOffer(keyRing)) }
.findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id))); Offer getBsqSwapOffer(String id) {
return findAvailableBsqSwapOffer(id).orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
} }
Offer getMyBsqSwapOffer(String id) { Offer getMyBsqSwapOffer(String id) {
return offerBookService.getOffers().stream() return findMyBsqSwapOffer(id).orElseThrow(() ->
.filter(o -> o.getId().equals(id)) new IllegalStateException(format("offer with id '%s' not found", id)));
.filter(o -> o.isMyOffer(keyRing))
.filter(o -> o.isBsqSwapOffer())
.findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
} }
List<Offer> getBsqSwapOffers(String direction) { List<Offer> getBsqSwapOffers(String direction) {
var offers = offerBookService.getOffers().stream() return offerBookService.getOffers().stream()
.filter(o -> !o.isMyOffer(keyRing)) .filter(o -> !o.isMyOffer(keyRing))
.filter(o -> o.getDirection().name().equalsIgnoreCase(direction)) .filter(o -> o.getDirection().name().equalsIgnoreCase(direction))
.filter(o -> o.isBsqSwapOffer()) .filter(Offer::isBsqSwapOffer)
.sorted(priceComparator(direction)) .sorted(priceComparator(direction))
.collect(Collectors.toList()); .collect(Collectors.toList());
return offers;
} }
List<Offer> getOffers(String direction, String currencyCode) { List<Offer> getOffers(String direction, String currencyCode) {
@ -183,13 +186,12 @@ class CoreOffersService {
} }
List<Offer> getMyBsqSwapOffers(String direction) { List<Offer> getMyBsqSwapOffers(String direction) {
var offers = offerBookService.getOffers().stream() return offerBookService.getOffers().stream()
.filter(o -> o.isMyOffer(keyRing)) .filter(o -> o.isMyOffer(keyRing))
.filter(o -> o.getDirection().name().equalsIgnoreCase(direction)) .filter(o -> o.getDirection().name().equalsIgnoreCase(direction))
.filter(Offer::isBsqSwapOffer) .filter(Offer::isBsqSwapOffer)
.sorted(priceComparator(direction)) .sorted(priceComparator(direction))
.collect(Collectors.toList()); .collect(Collectors.toList());
return offers;
} }
OpenOffer getMyOpenBsqSwapOffer(String id) { OpenOffer getMyOpenBsqSwapOffer(String id) {
@ -208,9 +210,12 @@ class CoreOffersService {
} }
boolean isMyOffer(String id) { boolean isMyOffer(String id) {
return openOfferManager.getOpenOfferById(id) boolean isMyOpenOffer = openOfferManager.getOpenOfferById(id)
.filter(open -> open.getOffer().isMyOffer(keyRing)) .filter(open -> open.getOffer().isMyOffer(keyRing))
.isPresent(); .isPresent();
boolean wasMyOffer = offerBookService.getOffers().stream()
.anyMatch(o -> o.getId().equals(id) && o.isMyOffer(keyRing));
return isMyOpenOffer || wasMyOffer;
} }
void createAndPlaceBsqSwapOffer(String directionAsString, void createAndPlaceBsqSwapOffer(String directionAsString,
@ -222,7 +227,7 @@ class CoreOffersService {
coreWalletsService.verifyEncryptedWalletIsUnlocked(); coreWalletsService.verifyEncryptedWalletIsUnlocked();
String currencyCode = "BSQ"; String currencyCode = "BSQ";
String offerId = OfferUtil.getRandomOfferId(); String offerId = getRandomOfferId();
OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase()); OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase());
Coin amount = Coin.valueOf(amountAsLong); Coin amount = Coin.valueOf(amountAsLong);
Coin minAmount = Coin.valueOf(minAmountAsLong); Coin minAmount = Coin.valueOf(minAmountAsLong);
@ -256,7 +261,7 @@ class CoreOffersService {
throw new IllegalArgumentException(format("payment account with id %s not found", paymentAccountId)); throw new IllegalArgumentException(format("payment account with id %s not found", paymentAccountId));
String upperCaseCurrencyCode = currencyCode.toUpperCase(); String upperCaseCurrencyCode = currencyCode.toUpperCase();
String offerId = OfferUtil.getRandomOfferId(); String offerId = getRandomOfferId();
OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase()); OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase());
Price price = Price.valueOf(upperCaseCurrencyCode, priceStringToLong(priceAsString, upperCaseCurrencyCode)); Price price = Price.valueOf(upperCaseCurrencyCode, priceStringToLong(priceAsString, upperCaseCurrencyCode));
Coin amount = Coin.valueOf(amountAsLong); Coin amount = Coin.valueOf(amountAsLong);
@ -375,6 +380,38 @@ class CoreOffersService {
throw new IllegalStateException(offer.getErrorMessage()); throw new IllegalStateException(offer.getErrorMessage());
} }
private Optional<Offer> findAvailableBsqSwapOffer(String id) {
return offerBookService.getOffers().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
.filter(Offer::isBsqSwapOffer)
.findAny();
}
private Optional<Offer> findMyBsqSwapOffer(String id) {
return offerBookService.getOffers().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> o.isMyOffer(keyRing))
.filter(Offer::isBsqSwapOffer)
.findAny();
}
private Optional<Offer> findAvailableOffer(String id) {
return offerBookService.getOffers().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> offerFilterService.canTakeOffer(o, coreContext.isApiUser()).isValid())
.findAny();
}
private Optional<OpenOffer> findMyOpenOffer(String id) {
return openOfferManager.getObservableList().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> o.getOffer().isMyOffer(keyRing))
.findAny();
}
private OfferPayload getMergedOfferPayload(OpenOffer openOffer, private OfferPayload getMergedOfferPayload(OpenOffer openOffer,
String editedPriceAsString, String editedPriceAsString,
double editedMarketPriceMargin, double editedMarketPriceMargin,
@ -427,10 +464,9 @@ class CoreOffersService {
private boolean offerMatchesDirectionAndCurrency(Offer offer, private boolean offerMatchesDirectionAndCurrency(Offer offer,
String direction, String direction,
String currencyCode) { String currencyCode) {
var offerOfWantedDirection = offer.getDirection().name().equalsIgnoreCase(direction); var isDirectionMatch = offer.getDirection().name().equalsIgnoreCase(direction);
var offerInWantedCurrency = offer.getCounterCurrencyCode() var isCurrencyMatch = offer.getCounterCurrencyCode().equalsIgnoreCase(currencyCode);
.equalsIgnoreCase(currencyCode); return isDirectionMatch && isCurrencyMatch;
return offerOfWantedDirection && offerInWantedCurrency;
} }
private Comparator<OpenOffer> openOfferPriceComparator(String direction) { private Comparator<OpenOffer> openOfferPriceComparator(String direction) {

View file

@ -28,6 +28,7 @@ import bisq.core.trade.TradeManager;
import bisq.core.trade.bisq_v1.TradeResultHandler; import bisq.core.trade.bisq_v1.TradeResultHandler;
import bisq.core.trade.bisq_v1.TradeUtil; import bisq.core.trade.bisq_v1.TradeUtil;
import bisq.core.trade.model.Tradable; import bisq.core.trade.model.Tradable;
import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.core.trade.protocol.bisq_v1.BuyerProtocol; import bisq.core.trade.protocol.bisq_v1.BuyerProtocol;
@ -91,27 +92,26 @@ class CoreTradesService {
this.user = user; this.user = user;
} }
// todo we need to pass the intended trade amount // TODO We need to pass the intended trade amount, not default to the maximum.
void takeBsqSwapOffer(Offer offer, void takeBsqSwapOffer(Offer offer,
String paymentAccountId,
String takerFeeCurrencyCode,
TradeResultHandler<BsqSwapTrade> tradeResultHandler, TradeResultHandler<BsqSwapTrade> tradeResultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
coreWalletsService.verifyWalletsAreAvailable(); coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked(); coreWalletsService.verifyEncryptedWalletIsUnlocked();
bsqSwapTakeOfferModel.initWithData(offer); bsqSwapTakeOfferModel.initWithData(offer);
//todo use the intended trade amount
bsqSwapTakeOfferModel.applyAmount(offer.getAmount()); bsqSwapTakeOfferModel.applyAmount(offer.getAmount());
log.info("Initiating take {} offer, {}", log.info("Initiating take {} offer, {}",
offer.isBuyOffer() ? "buy" : "sell", offer.isBuyOffer() ? "buy" : "sell",
bsqSwapTakeOfferModel); bsqSwapTakeOfferModel);
bsqSwapTakeOfferModel.onTakeOffer(tradeResultHandler,
bsqSwapTakeOfferModel.onTakeOffer(tradeResultHandler, log::warn, errorMessageHandler, coreContext.isApiUser()); log::warn,
errorMessageHandler,
coreContext.isApiUser());
} }
// TODO We need to pass the intended trade amount, not default to the maximum.
void takeOffer(Offer offer, void takeOffer(Offer offer,
String paymentAccountId, String paymentAccountId,
String takerFeeCurrencyCode, String takerFeeCurrencyCode,
@ -232,13 +232,28 @@ class CoreTradesService {
}); });
} }
BsqSwapTrade getBsqSwapTrade(String tradeId) { TradeModel getTradeModel(String tradeId) {
coreWalletsService.verifyWalletsAreAvailable(); coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked(); coreWalletsService.verifyEncryptedWalletIsUnlocked();
Optional<Trade> openTrade = getOpenTrade(tradeId);
if (openTrade.isPresent())
return openTrade.get();
Optional<Trade> closedTrade = getClosedTrade(tradeId);
if (closedTrade.isPresent())
return closedTrade.get();
return tradeManager.findBsqSwapTradeById(tradeId).orElseThrow(() -> return tradeManager.findBsqSwapTradeById(tradeId).orElseThrow(() ->
new IllegalArgumentException(format("trade with id '%s' not found", tradeId))); new IllegalArgumentException(format("trade with id '%s' not found", tradeId)));
} }
String getBsqSwapTradeRole(BsqSwapTrade bsqSwapTrade) {
coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked();
return tradeUtil.getRole(bsqSwapTrade);
}
String getTradeRole(String tradeId) { String getTradeRole(String tradeId) {
coreWalletsService.verifyWalletsAreAvailable(); coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked(); coreWalletsService.verifyEncryptedWalletIsUnlocked();

View file

@ -85,6 +85,7 @@ import javax.annotation.Nullable;
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput; import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
import static bisq.core.util.ParsingUtils.parseToCoin; import static bisq.core.util.ParsingUtils.parseToCoin;
import static java.lang.String.format; import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
@Singleton @Singleton
@ -209,10 +210,10 @@ class CoreWalletsService {
} }
return addressStrings.stream().map(address -> return addressStrings.stream().map(address ->
new AddressBalanceInfo(address, new AddressBalanceInfo(address,
balances.getUnchecked(address), balances.getUnchecked(address),
getNumConfirmationsForMostRecentTransaction(address), getNumConfirmationsForMostRecentTransaction(address),
btcWalletService.isAddressUnused(getAddressEntry(address).getAddress()))) btcWalletService.isAddressUnused(getAddressEntry(address).getAddress())))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -324,7 +325,7 @@ class CoreWalletsService {
for (TransactionOutput txOut : spendableBsqTxOutputs) { for (TransactionOutput txOut : spendableBsqTxOutputs) {
if (isTxOutputAddressMatch.test(txOut) && isTxOutputValueMatch.test(txOut)) { if (isTxOutputAddressMatch.test(txOut) && isTxOutputValueMatch.test(txOut)) {
log.info("\t\tTx {} output has matching address {} and value {}.", log.info("\t\tTx {} output has matching address {} and value {}.",
txOut.getParentTransaction().getTxId(), requireNonNull(txOut.getParentTransaction()).getTxId(),
address, address,
txOut.getValue().toPlainString()); txOut.getValue().toPlainString());
numMatches++; numMatches++;
@ -346,6 +347,7 @@ class CoreWalletsService {
@SuppressWarnings({"unchecked", "Convert2MethodRef"}) @SuppressWarnings({"unchecked", "Convert2MethodRef"})
ListenableFuture<Void> future = ListenableFuture<Void> future =
(ListenableFuture<Void>) executor.submit(() -> feeService.requestFees()); (ListenableFuture<Void>) executor.submit(() -> feeService.requestFees());
//noinspection NullableProblems
Futures.addCallback(future, new FutureCallback<>() { Futures.addCallback(future, new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable Void ignored) { public void onSuccess(@Nullable Void ignored) {
@ -393,23 +395,11 @@ class CoreWalletsService {
} }
Transaction getTransaction(String txId) { Transaction getTransaction(String txId) {
if (txId.length() != 64) return getTransactionWithId(txId);
throw new IllegalArgumentException(format("%s is not a transaction id", txId)); }
try { int getTransactionConfirmations(String txId) {
Transaction tx = btcWalletService.getTransaction(txId); return getTransactionWithId(txId).getConfidence().getDepthInBlocks();
if (tx == null)
throw new IllegalArgumentException(format("tx with id %s not found", txId));
else
return tx;
} catch (IllegalArgumentException ex) {
log.error("", ex);
throw new IllegalArgumentException(
format("could not get transaction with id %s%ncause: %s",
txId,
ex.getMessage().toLowerCase()));
}
} }
int getNumConfirmationsForMostRecentTransaction(String addressString) { int getNumConfirmationsForMostRecentTransaction(String addressString) {
@ -654,12 +644,32 @@ class CoreWalletsService {
return addressEntry.get(); return addressEntry.get();
} }
private Transaction getTransactionWithId(String txId) {
if (txId.length() != 64)
throw new IllegalArgumentException(format("%s is not a transaction id", txId));
try {
Transaction tx = btcWalletService.getTransaction(txId);
if (tx == null)
throw new IllegalArgumentException(format("tx with id %s not found", txId));
else
return tx;
} catch (IllegalArgumentException ex) {
log.error("", ex);
throw new IllegalArgumentException(
format("could not get transaction with id %s%ncause: %s",
txId,
ex.getMessage().toLowerCase()));
}
}
/** /**
* Memoization stores the results of expensive function calls and returns * Memoization stores the results of expensive function calls and returns
* the cached result when the same input occurs again. * the cached result when the same input occurs again.
* *
* Resulting LoadingCache is used by calling `.get(input I)` or * Resulting LoadingCache is used by calling `.get(input I)` or
* `.getUnchecked(input I)`, depending on whether or not `f` can return null. * `.getUnchecked(input I)`, depending on whether `f` can return null.
* That's because CacheLoader throws an exception on null output from `f`. * That's because CacheLoader throws an exception on null output from `f`.
*/ */
private static <I, O> LoadingCache<I, O> memoize(Function<I, O> f) { private static <I, O> LoadingCache<I, O> memoize(Function<I, O> f) {

View file

@ -45,7 +45,8 @@ class EditOfferValidator {
} }
void validate() { void validate() {
log.info("Verifying 'editoffer' params OK for editType {}", editType); log.info("Verifying 'editoffer' params for editType {}", editType);
checkNotBsqSwapOffer();
switch (editType) { switch (editType) {
case ACTIVATION_STATE_ONLY: { case ACTIVATION_STATE_ONLY: {
validateEditedActivationState(); validateEditedActivationState();
@ -138,4 +139,11 @@ class EditOfferValidator {
currentlyOpenOffer.getId())); currentlyOpenOffer.getId()));
} }
} }
private void checkNotBsqSwapOffer() {
if (currentlyOpenOffer.getOffer().isBsqSwapOffer()) {
throw new IllegalStateException(
format("cannot edit bsq swap offer with id '%s'", currentlyOpenOffer.getId()));
}
}
} }

View file

@ -1,227 +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.api.model;
import bisq.core.offer.Offer;
import bisq.common.Payload;
import java.util.Objects;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
@EqualsAndHashCode
@ToString
@Getter
public class BsqSwapOfferInfo implements Payload {
private final String id;
private final String direction;
private final long amount;
private final long minAmount;
private final long price;
private final String makerPaymentAccountId;
private final String paymentMethodId;
private final String paymentMethodShortName;
private final String baseCurrencyCode;
private final String counterCurrencyCode;
private final long date;
private final String ownerNodeAddress;
private final String pubKeyRing; // TODO ?
private final String versionNumber;
private final int protocolVersion;
public BsqSwapOfferInfo(BsqSwapOfferInfoBuilder builder) {
this.id = builder.id;
this.direction = builder.direction;
this.amount = builder.amount;
this.minAmount = builder.minAmount;
this.price = builder.price;
this.makerPaymentAccountId = builder.makerPaymentAccountId;
this.paymentMethodId = builder.paymentMethodId;
this.paymentMethodShortName = builder.paymentMethodShortName;
this.baseCurrencyCode = builder.baseCurrencyCode;
this.counterCurrencyCode = builder.counterCurrencyCode;
this.date = builder.date;
this.ownerNodeAddress = builder.ownerNodeAddress;
this.pubKeyRing = builder.pubKeyRing;
this.versionNumber = builder.versionNumber;
this.protocolVersion = builder.protocolVersion;
}
public static BsqSwapOfferInfo toBsqSwapOfferInfo(Offer offer) {
// TODO support triggerPrice
return getAtomicOfferInfoBuilder(offer).build();
}
private static BsqSwapOfferInfoBuilder getAtomicOfferInfoBuilder(Offer offer) {
return new BsqSwapOfferInfoBuilder()
.withId(offer.getId())
.withDirection(offer.getDirection().name())
.withAmount(offer.getAmount().value)
.withMinAmount(offer.getMinAmount().value)
.withPrice(Objects.requireNonNull(offer.getPrice()).getValue())
//.withMakerPaymentAccountId(offer.getOfferPayloadI().getMakerPaymentAccountId())
//.withPaymentMethodId(offer.getOfferPayloadI().getPaymentMethodId())
//.withPaymentMethodShortName(getPaymentMethodById(offer.getOfferPayloadI().getPaymentMethodId()).getShortName())
.withBaseCurrencyCode(offer.getOfferPayloadBase().getBaseCurrencyCode())
.withCounterCurrencyCode(offer.getOfferPayloadBase().getCounterCurrencyCode())
.withDate(offer.getDate().getTime())
.withOwnerNodeAddress(offer.getOfferPayloadBase().getOwnerNodeAddress().getFullAddress())
.withPubKeyRing(offer.getOfferPayloadBase().getPubKeyRing().toString())
.withVersionNumber(offer.getOfferPayloadBase().getVersionNr())
.withProtocolVersion(offer.getOfferPayloadBase().getProtocolVersion());
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public bisq.proto.grpc.BsqSwapOfferInfo toProtoMessage() {
return bisq.proto.grpc.BsqSwapOfferInfo.newBuilder()
.setId(id)
.setDirection(direction)
.setAmount(amount)
.setMinAmount(minAmount)
.setPrice(price)
.setBaseCurrencyCode(baseCurrencyCode)
.setCounterCurrencyCode(counterCurrencyCode)
.setDate(date)
.setOwnerNodeAddress(ownerNodeAddress)
.setPubKeyRing(pubKeyRing)
.setVersionNr(versionNumber)
.setProtocolVersion(protocolVersion)
.build();
}
public static BsqSwapOfferInfo fromProto(bisq.proto.grpc.BsqSwapOfferInfo proto) {
return new BsqSwapOfferInfoBuilder()
.withId(proto.getId())
.withDirection(proto.getDirection())
.withAmount(proto.getAmount())
.withMinAmount(proto.getMinAmount())
.withPrice(proto.getPrice())
.withBaseCurrencyCode(proto.getBaseCurrencyCode())
.withCounterCurrencyCode(proto.getCounterCurrencyCode())
.withDate(proto.getDate())
.withOwnerNodeAddress(proto.getOwnerNodeAddress())
.withPubKeyRing(proto.getPubKeyRing())
.withVersionNumber(proto.getVersionNr())
.withProtocolVersion(proto.getProtocolVersion())
.build();
}
public static class BsqSwapOfferInfoBuilder {
private String id;
private String direction;
private long amount;
private long minAmount;
private long price;
private String makerPaymentAccountId;
private String paymentMethodId;
private String paymentMethodShortName;
private String baseCurrencyCode;
private String counterCurrencyCode;
private long date;
private String ownerNodeAddress;
private String pubKeyRing;
private String versionNumber;
private int protocolVersion;
public BsqSwapOfferInfoBuilder withId(String id) {
this.id = id;
return this;
}
public BsqSwapOfferInfoBuilder withDirection(String direction) {
this.direction = direction;
return this;
}
public BsqSwapOfferInfoBuilder withAmount(long amount) {
this.amount = amount;
return this;
}
public BsqSwapOfferInfoBuilder withMinAmount(long minAmount) {
this.minAmount = minAmount;
return this;
}
public BsqSwapOfferInfoBuilder withPrice(long price) {
this.price = price;
return this;
}
public BsqSwapOfferInfoBuilder withMakerPaymentAccountId(String makerPaymentAccountId) {
this.makerPaymentAccountId = makerPaymentAccountId;
return this;
}
public BsqSwapOfferInfoBuilder withPaymentMethodId(String paymentMethodId) {
this.paymentMethodId = paymentMethodId;
return this;
}
public BsqSwapOfferInfoBuilder withPaymentMethodShortName(String paymentMethodShortName) {
this.paymentMethodShortName = paymentMethodShortName;
return this;
}
public BsqSwapOfferInfoBuilder withBaseCurrencyCode(String baseCurrencyCode) {
this.baseCurrencyCode = baseCurrencyCode;
return this;
}
public BsqSwapOfferInfoBuilder withCounterCurrencyCode(String counterCurrencyCode) {
this.counterCurrencyCode = counterCurrencyCode;
return this;
}
public BsqSwapOfferInfoBuilder withDate(long date) {
this.date = date;
return this;
}
public BsqSwapOfferInfoBuilder withOwnerNodeAddress(String ownerNodeAddress) {
this.ownerNodeAddress = ownerNodeAddress;
return this;
}
public BsqSwapOfferInfoBuilder withPubKeyRing(String pubKeyRing) {
this.pubKeyRing = pubKeyRing;
return this;
}
public BsqSwapOfferInfoBuilder withVersionNumber(String versionNumber) {
this.versionNumber = versionNumber;
return this;
}
public BsqSwapOfferInfoBuilder withProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
return this;
}
public BsqSwapOfferInfo build() {
return new BsqSwapOfferInfo(this);
}
}
}

View file

@ -17,100 +17,67 @@
package bisq.core.api.model; package bisq.core.api.model;
import bisq.core.api.model.builder.BsqSwapTradeInfoBuilder;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.common.Payload; import bisq.common.Payload;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import static bisq.core.api.model.BsqSwapOfferInfo.toBsqSwapOfferInfo;
@EqualsAndHashCode @EqualsAndHashCode
@ToString
@Getter @Getter
public class BsqSwapTradeInfo implements Payload { public class BsqSwapTradeInfo implements Payload {
private final BsqSwapOfferInfo bsqSwapOffer;
private final String tradeId;
private final String tempTradingPeerNodeAddress;
private final String peerNodeAddress;
private final String txId; private final String txId;
private final long bsqTradeAmount; private final long bsqTradeAmount;
private final long bsqMaxTradeAmount;
private final long bsqMinTradeAmount;
private final long btcTradeAmount; private final long btcTradeAmount;
private final long btcMaxTradeAmount;
private final long btcMinTradeAmount;
private final long tradePrice;
private final long bsqMakerTradeFee; private final long bsqMakerTradeFee;
private final long bsqTakerTradeFee; private final long bsqTakerTradeFee;
private final long txFeePerVbyte; private final long txFeePerVbyte;
private final long txFee;
private final String makerBsqAddress; private final String makerBsqAddress;
private final String makerBtcAddress; private final String makerBtcAddress;
private final String takerBsqAddress; private final String takerBsqAddress;
private final String takerBtcAddress; private final String takerBtcAddress;
private final long takeOfferDate; private final long numConfirmations;
private final String state;
private final String errorMessage; private final String errorMessage;
public BsqSwapTradeInfo(BsqSwapTradeInfoBuilder builder) { public BsqSwapTradeInfo(BsqSwapTradeInfoBuilder builder) {
this.bsqSwapOffer = builder.bsqSwapOfferInfo; this.txId = builder.getTxId();
this.tradeId = builder.tradeId; this.bsqTradeAmount = builder.getBsqTradeAmount();
this.tempTradingPeerNodeAddress = builder.tempTradingPeerNodeAddress; this.btcTradeAmount = builder.getBtcTradeAmount();
this.peerNodeAddress = builder.peerNodeAddress; this.bsqMakerTradeFee = builder.getBsqMakerTradeFee();
this.txId = builder.txId; this.bsqTakerTradeFee = builder.getBsqTakerTradeFee();
this.bsqTradeAmount = builder.bsqTradeAmount; this.txFeePerVbyte = builder.getTxFeePerVbyte();
this.bsqMaxTradeAmount = builder.bsqMaxTradeAmount; this.makerBsqAddress = builder.getMakerBsqAddress();
this.bsqMinTradeAmount = builder.bsqMinTradeAmount; this.makerBtcAddress = builder.getMakerBtcAddress();
this.btcTradeAmount = builder.btcTradeAmount; this.takerBsqAddress = builder.getTakerBsqAddress();
this.btcMaxTradeAmount = builder.btcMaxTradeAmount; this.takerBtcAddress = builder.getTakerBtcAddress();
this.btcMinTradeAmount = builder.btcMinTradeAmount; this.numConfirmations = builder.getNumConfirmations();
this.tradePrice = builder.tradePrice; this.errorMessage = builder.getErrorMessage();
this.bsqMakerTradeFee = builder.bsqMakerTradeFee;
this.bsqTakerTradeFee = builder.bsqTakerTradeFee;
this.txFeePerVbyte = builder.txFeePerVbyte;
this.txFee = builder.txFee;
this.makerBsqAddress = builder.makerBsqAddress;
this.makerBtcAddress = builder.makerBtcAddress;
this.takerBsqAddress = builder.takerBsqAddress;
this.takerBtcAddress = builder.takerBtcAddress;
this.takeOfferDate = builder.takeOfferDate;
this.state = builder.state;
this.errorMessage = builder.errorMessage;
} }
public static BsqSwapTradeInfo toBsqSwapTradeInfo(BsqSwapTrade trade) { public static BsqSwapTradeInfo toBsqSwapTradeInfo(BsqSwapTrade trade,
return toBsqSwapTradeInfo(trade, null); boolean wasMyOffer,
} int numConfirmations) {
var protocolModel = trade.getBsqSwapProtocolModel();
//TODO var swapPeer = protocolModel.getTradePeer();
public static BsqSwapTradeInfo toBsqSwapTradeInfo(BsqSwapTrade trade, String role) { var makerBsqAddress = wasMyOffer ? protocolModel.getBsqAddress() : swapPeer.getBsqAddress();
var makerBtcAddress = wasMyOffer ? protocolModel.getBtcAddress() : swapPeer.getBtcAddress();
var takerBsqAddress = wasMyOffer ? swapPeer.getBsqAddress() : protocolModel.getBsqAddress();
var takerBtcAddress = wasMyOffer ? swapPeer.getBtcAddress() : protocolModel.getBtcAddress();
return new BsqSwapTradeInfoBuilder() return new BsqSwapTradeInfoBuilder()
.withBsqSwapOffer(toBsqSwapOfferInfo(trade.getOffer()))
.withTradeId(trade.getId())
.withTempTradingPeerNodeAddress(trade.getBsqSwapProtocolModel().getTempTradingPeerNodeAddress().getFullAddress())
.withPeerNodeAddress(trade.getTradingPeerNodeAddress().getFullAddress())
.withTxId(trade.getTxId()) .withTxId(trade.getTxId())
/* .withBsqTradeAmount(trade.getBsqSwapProtocolModel().getBsqTradeAmount()) .withBsqTradeAmount(trade.getBsqTradeAmount())
.withBsqMaxTradeAmount(trade.getBsqSwapProtocolModel().getBsqMaxTradeAmount()) .withBtcTradeAmount(trade.getAmountAsLong())
.withBsqMinTradeAmount(trade.getBsqSwapProtocolModel().getBsqMinTradeAmount()) .withBsqMakerTradeFee(trade.getMakerFeeAsLong())
.withBtcTradeAmount(trade.getBsqSwapProtocolModel().getBtcTradeAmount()) .withBsqTakerTradeFee(trade.getTakerFeeAsLong())
.withBtcMaxTradeAmount(trade.getBsqSwapProtocolModel().getBtcMaxTradeAmount()) .withTxFeePerVbyte(trade.getTxFeePerVbyte())
.withBtcMinTradeAmount(trade.getBsqSwapProtocolModel().getBtcMinTradeAmount()) .withMakerBsqAddress(makerBsqAddress)
.withTradePrice(trade.getBsqSwapProtocolModel().getTradePrice()) .withMakerBtcAddress(makerBtcAddress)
.withBsqMakerTradeFee(trade.getBsqSwapProtocolModel().getBsqMakerTradeFee()) .withTakerBsqAddress(takerBsqAddress)
.withBsqTakerTradeFee(trade.getBsqSwapProtocolModel().getBsqTakerTradeFee()) .withTakerBtcAddress(takerBtcAddress)
.withTxFeePerVbyte(trade.getBsqSwapProtocolModel().getTxFeePerVbyte()) .withNumConfirmations(numConfirmations)
.withTxFee(trade.getBsqSwapProtocolModel().getTxFee())
.withMakerBsqAddress(trade.getBsqSwapProtocolModel().getMakerBsqAddress())
.withMakerBtcAddress(trade.getBsqSwapProtocolModel().getMakerBtcAddress())
.withTakerBsqAddress(trade.getBsqSwapProtocolModel().getTakerBsqAddress())
.withTakerBtcAddress(trade.getBsqSwapProtocolModel().getTakerBtcAddress())*/
.withTakeOfferDate(trade.getTakeOfferDate())
.withState(trade.getTradeState().name())
.withErrorMessage(trade.getErrorMessage()) .withErrorMessage(trade.getErrorMessage())
.build(); .build();
} }
@ -122,202 +89,53 @@ public class BsqSwapTradeInfo implements Payload {
@Override @Override
public bisq.proto.grpc.BsqSwapTradeInfo toProtoMessage() { public bisq.proto.grpc.BsqSwapTradeInfo toProtoMessage() {
return bisq.proto.grpc.BsqSwapTradeInfo.newBuilder() return bisq.proto.grpc.BsqSwapTradeInfo.newBuilder()
.setBsqSwapOfferInfo(bsqSwapOffer.toProtoMessage())
.setTradeId(tradeId)
.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress != null ? tempTradingPeerNodeAddress : "")
.setPeerNodeAddress(peerNodeAddress != null ? peerNodeAddress : "")
.setTxId(txId != null ? txId : "") .setTxId(txId != null ? txId : "")
.setBsqTradeAmount(bsqTradeAmount) .setBsqTradeAmount(bsqTradeAmount)
.setBsqMaxTradeAmount(bsqMaxTradeAmount)
.setBsqMinTradeAmount(bsqMinTradeAmount)
.setBtcTradeAmount(btcTradeAmount) .setBtcTradeAmount(btcTradeAmount)
.setBtcMaxTradeAmount(btcMaxTradeAmount)
.setBtcMinTradeAmount(btcMinTradeAmount)
.setTradePrice(tradePrice)
.setBsqMakerTradeFee(bsqMakerTradeFee) .setBsqMakerTradeFee(bsqMakerTradeFee)
.setBsqTakerTradeFee(bsqTakerTradeFee) .setBsqTakerTradeFee(bsqTakerTradeFee)
.setTxFeePerVbyte(txFeePerVbyte) .setTxFeePerVbyte(txFeePerVbyte)
.setTxFee(txFee)
.setMakerBsqAddress(makerBsqAddress != null ? makerBsqAddress : "") .setMakerBsqAddress(makerBsqAddress != null ? makerBsqAddress : "")
.setTakerBsqAddress(takerBsqAddress != null ? takerBsqAddress : "") .setTakerBsqAddress(takerBsqAddress != null ? takerBsqAddress : "")
.setMakerBtcAddress(makerBtcAddress != null ? makerBtcAddress : "") .setMakerBtcAddress(makerBtcAddress != null ? makerBtcAddress : "")
.setTakerBtcAddress(takerBtcAddress != null ? takerBtcAddress : "") .setTakerBtcAddress(takerBtcAddress != null ? takerBtcAddress : "")
.setTakeOfferDate(takeOfferDate) .setTakerBtcAddress(takerBtcAddress != null ? takerBtcAddress : "")
.setState(state) .setNumConfirmations(numConfirmations)
.setErrorMessage(errorMessage != null ? errorMessage : "")
.build(); .build();
} }
public static BsqSwapTradeInfo fromProto(bisq.proto.grpc.BsqSwapTradeInfo proto) { public static BsqSwapTradeInfo fromProto(bisq.proto.grpc.BsqSwapTradeInfo proto) {
return new BsqSwapTradeInfoBuilder() return new BsqSwapTradeInfoBuilder()
.withBsqSwapOffer(BsqSwapOfferInfo.fromProto(proto.getBsqSwapOfferInfo()))
.withTradeId(proto.getTradeId())
.withTempTradingPeerNodeAddress(proto.getTempTradingPeerNodeAddress())
.withPeerNodeAddress(proto.getPeerNodeAddress())
.withTxId(proto.getTxId()) .withTxId(proto.getTxId())
.withBsqTradeAmount(proto.getBsqTradeAmount()) .withBsqTradeAmount(proto.getBsqTradeAmount())
.withBsqMaxTradeAmount(proto.getBsqMaxTradeAmount())
.withBsqMinTradeAmount(proto.getBsqMinTradeAmount())
.withBtcTradeAmount(proto.getBtcTradeAmount()) .withBtcTradeAmount(proto.getBtcTradeAmount())
.withBtcMaxTradeAmount(proto.getBtcMaxTradeAmount())
.withBtcMinTradeAmount(proto.getBtcMinTradeAmount())
.withTradePrice(proto.getTradePrice())
.withBsqMakerTradeFee(proto.getBsqMakerTradeFee()) .withBsqMakerTradeFee(proto.getBsqMakerTradeFee())
.withBsqTakerTradeFee(proto.getBsqTakerTradeFee()) .withBsqTakerTradeFee(proto.getBsqTakerTradeFee())
.withTxFeePerVbyte(proto.getTxFeePerVbyte()) .withTxFeePerVbyte(proto.getTxFeePerVbyte())
.withTxFee(proto.getTxFee())
.withMakerBsqAddress(proto.getMakerBsqAddress()) .withMakerBsqAddress(proto.getMakerBsqAddress())
.withMakerBtcAddress(proto.getMakerBtcAddress()) .withMakerBtcAddress(proto.getMakerBtcAddress())
.withTakerBsqAddress(proto.getTakerBsqAddress()) .withTakerBsqAddress(proto.getTakerBsqAddress())
.withTakerBtcAddress(proto.getTakerBtcAddress()) .withTakerBtcAddress(proto.getTakerBtcAddress())
.withTakeOfferDate(proto.getTakeOfferDate()) .withNumConfirmations(proto.getNumConfirmations())
.withState(proto.getState())
.withErrorMessage(proto.getErrorMessage()) .withErrorMessage(proto.getErrorMessage())
.build(); .build();
} }
public static class BsqSwapTradeInfoBuilder { @Override
private BsqSwapOfferInfo bsqSwapOfferInfo; public String toString() {
private String tradeId; return "BsqSwapTradeInfo{" +
private String tempTradingPeerNodeAddress; ", txId='" + txId + '\'' +
private String peerNodeAddress; ", bsqTradeAmount=" + bsqTradeAmount +
private String txId; ", btcTradeAmount=" + btcTradeAmount +
private long bsqTradeAmount; ", bsqMakerTradeFee=" + bsqMakerTradeFee +
private long bsqMaxTradeAmount; ", bsqTakerTradeFee=" + bsqTakerTradeFee +
private long bsqMinTradeAmount; ", txFeePerVbyte=" + txFeePerVbyte +
private long btcTradeAmount; ", makerBsqAddress='" + makerBsqAddress + '\'' +
private long btcMaxTradeAmount; ", makerBtcAddress='" + makerBtcAddress + '\'' +
private long btcMinTradeAmount; ", takerBsqAddress='" + takerBsqAddress + '\'' +
private long tradePrice; ", takerBtcAddress='" + takerBtcAddress + '\'' +
private long bsqMakerTradeFee; ", numConfirmations='" + numConfirmations + '\'' +
private long bsqTakerTradeFee; ", errorMessage='" + errorMessage + '\'' +
private long txFeePerVbyte; '}';
private long txFee;
private String makerBsqAddress;
private String makerBtcAddress;
private String takerBsqAddress;
private String takerBtcAddress;
private long takeOfferDate;
private String state;
private String errorMessage;
public BsqSwapTradeInfoBuilder withBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) {
this.bsqSwapOfferInfo = bsqSwapOfferInfo;
return this;
}
public BsqSwapTradeInfoBuilder withTradeId(String tradeId) {
this.tradeId = tradeId;
return this;
}
public BsqSwapTradeInfoBuilder withTempTradingPeerNodeAddress(String tempTradingPeerNodeAddress) {
this.tempTradingPeerNodeAddress = tempTradingPeerNodeAddress;
return this;
}
public BsqSwapTradeInfoBuilder withPeerNodeAddress(String peerNodeAddress) {
this.peerNodeAddress = peerNodeAddress;
return this;
}
public BsqSwapTradeInfoBuilder withTxId(String txId) {
this.txId = txId;
return this;
}
public BsqSwapTradeInfoBuilder withBsqTradeAmount(long bsqTradeAmount) {
this.bsqTradeAmount = bsqTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBsqMaxTradeAmount(long bsqMaxTradeAmount) {
this.bsqMaxTradeAmount = bsqMaxTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBsqMinTradeAmount(long bsqMinTradeAmount) {
this.bsqMinTradeAmount = bsqMinTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBtcTradeAmount(long btcTradeAmount) {
this.btcTradeAmount = btcTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBtcMaxTradeAmount(long btcMaxTradeAmount) {
this.btcMaxTradeAmount = btcMaxTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBtcMinTradeAmount(long btcMinTradeAmount) {
this.btcMinTradeAmount = btcMinTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withTradePrice(long tradePrice) {
this.tradePrice = tradePrice;
return this;
}
public BsqSwapTradeInfoBuilder withBsqMakerTradeFee(long bsqMakerTradeFee) {
this.bsqMakerTradeFee = bsqMakerTradeFee;
return this;
}
public BsqSwapTradeInfoBuilder withBsqTakerTradeFee(long bsqTakerTradeFee) {
this.bsqTakerTradeFee = bsqTakerTradeFee;
return this;
}
public BsqSwapTradeInfoBuilder withTxFeePerVbyte(long txFeePerVbyte) {
this.txFeePerVbyte = txFeePerVbyte;
return this;
}
public BsqSwapTradeInfoBuilder withTxFee(long txFee) {
this.txFee = txFee;
return this;
}
public BsqSwapTradeInfoBuilder withMakerBsqAddress(String makerBsqAddress) {
this.makerBsqAddress = makerBsqAddress;
return this;
}
public BsqSwapTradeInfoBuilder withMakerBtcAddress(String makerBtcAddress) {
this.makerBtcAddress = makerBtcAddress;
return this;
}
public BsqSwapTradeInfoBuilder withTakerBsqAddress(String takerBsqAddress) {
this.takerBsqAddress = takerBsqAddress;
return this;
}
public BsqSwapTradeInfoBuilder withTakerBtcAddress(String takerBtcAddress) {
this.takerBtcAddress = takerBtcAddress;
return this;
}
public BsqSwapTradeInfoBuilder withTakeOfferDate(long takeOfferDate) {
this.takeOfferDate = takeOfferDate;
return this;
}
public BsqSwapTradeInfoBuilder withState(String state) {
this.state = state;
return this;
}
public BsqSwapTradeInfoBuilder withErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
return this;
}
public BsqSwapTradeInfo build() {
return new BsqSwapTradeInfo(this);
}
} }
} }

View file

@ -73,6 +73,7 @@ public class ContractInfo implements Payload {
// For transmitting TradeInfo messages when no contract is available. // For transmitting TradeInfo messages when no contract is available.
// TODO Is this necessary as protobuf will send a DEFAULT_INSTANCE.
public static Supplier<ContractInfo> emptyContract = () -> public static Supplier<ContractInfo> emptyContract = () ->
new ContractInfo("", new ContractInfo("",
"", "",

View file

@ -17,17 +17,19 @@
package bisq.core.api.model; package bisq.core.api.model;
import bisq.core.api.model.builder.OfferInfoBuilder;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOffer;
import bisq.core.util.coin.CoinUtil;
import bisq.common.Payload; import bisq.common.Payload;
import java.util.Objects;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
import static java.util.Objects.requireNonNull;
@EqualsAndHashCode @EqualsAndHashCode
@ToString @ToString
@Getter @Getter
@ -56,86 +58,95 @@ public class OfferInfo implements Payload {
private final String paymentAccountId; private final String paymentAccountId;
private final String paymentMethodId; private final String paymentMethodId;
private final String paymentMethodShortName; private final String paymentMethodShortName;
// For fiat offer the baseCurrencyCode is BTC and the counterCurrencyCode is the fiat currency // Fiat offer: baseCurrencyCode = BTC, counterCurrencyCode = fiat ccy code.
// For altcoin offers it is the opposite. baseCurrencyCode is the altcoin and the counterCurrencyCode is BTC. // Altcoin offer: baseCurrencyCode = altcoin ccy code, counterCurrencyCode = BTC.
private final String baseCurrencyCode; private final String baseCurrencyCode;
private final String counterCurrencyCode; private final String counterCurrencyCode;
private final long date; private final long date;
private final String state; private final String state;
private final boolean isActivated; private final boolean isActivated;
private boolean isMyOffer; // Not final -- may be re-set after instantiation. private final boolean isMyOffer;
private final boolean isMyPendingOffer; private final boolean isMyPendingOffer;
private final boolean isBsqSwapOffer;
private final String ownerNodeAddress;
private final String pubKeyRing;
private final String versionNumber;
private final int protocolVersion;
public OfferInfo(OfferInfoBuilder builder) { public OfferInfo(OfferInfoBuilder builder) {
this.id = builder.id; this.id = builder.getId();
this.direction = builder.direction; this.direction = builder.getDirection();
this.price = builder.price; this.price = builder.getPrice();
this.useMarketBasedPrice = builder.useMarketBasedPrice; this.useMarketBasedPrice = builder.isUseMarketBasedPrice();
this.marketPriceMargin = builder.marketPriceMargin; this.marketPriceMargin = builder.getMarketPriceMargin();
this.amount = builder.amount; this.amount = builder.getAmount();
this.minAmount = builder.minAmount; this.minAmount = builder.getMinAmount();
this.volume = builder.volume; this.volume = builder.getVolume();
this.minVolume = builder.minVolume; this.minVolume = builder.getMinVolume();
this.txFee = builder.txFee; this.txFee = builder.getTxFee();
this.makerFee = builder.makerFee; this.makerFee = builder.getMakerFee();
this.offerFeePaymentTxId = builder.offerFeePaymentTxId; this.offerFeePaymentTxId = builder.getOfferFeePaymentTxId();
this.buyerSecurityDeposit = builder.buyerSecurityDeposit; this.buyerSecurityDeposit = builder.getBuyerSecurityDeposit();
this.sellerSecurityDeposit = builder.sellerSecurityDeposit; this.sellerSecurityDeposit = builder.getSellerSecurityDeposit();
this.triggerPrice = builder.triggerPrice; this.triggerPrice = builder.getTriggerPrice();
this.isCurrencyForMakerFeeBtc = builder.isCurrencyForMakerFeeBtc; this.isCurrencyForMakerFeeBtc = builder.isCurrencyForMakerFeeBtc();
this.paymentAccountId = builder.paymentAccountId; this.paymentAccountId = builder.getPaymentAccountId();
this.paymentMethodId = builder.paymentMethodId; this.paymentMethodId = builder.getPaymentMethodId();
this.paymentMethodShortName = builder.paymentMethodShortName; this.paymentMethodShortName = builder.getPaymentMethodShortName();
this.baseCurrencyCode = builder.baseCurrencyCode; this.baseCurrencyCode = builder.getBaseCurrencyCode();
this.counterCurrencyCode = builder.counterCurrencyCode; this.counterCurrencyCode = builder.getCounterCurrencyCode();
this.date = builder.date; this.date = builder.getDate();
this.state = builder.state; this.state = builder.getState();
this.isActivated = builder.isActivated; this.isActivated = builder.isActivated();
this.isMyOffer = builder.isMyOffer; this.isMyOffer = builder.isMyOffer();
this.isMyPendingOffer = builder.isMyPendingOffer; this.isMyPendingOffer = builder.isMyPendingOffer();
this.isBsqSwapOffer = builder.isBsqSwapOffer();
this.ownerNodeAddress = builder.getOwnerNodeAddress();
this.pubKeyRing = builder.getPubKeyRing();
this.versionNumber = builder.getVersionNumber();
this.protocolVersion = builder.getProtocolVersion();
} }
// Allow isMyOffer to be set on a new offer's OfferInfo instance. public static OfferInfo toMyOfferInfo(Offer offer) {
public void setIsMyOffer(boolean isMyOffer) { return getBuilder(offer, true).build();
this.isMyOffer = isMyOffer;
} }
public static OfferInfo toOfferInfo(Offer offer) { public static OfferInfo toOfferInfo(Offer offer) {
// Assume the offer is not mine, but isMyOffer can be reset to true, i.e., when // Assume the offer is not mine, but isMyOffer can be reset to true, i.e., when
// calling TradeInfo toTradeInfo(Trade trade, String role, boolean isMyOffer); // calling TradeInfo toTradeInfo(Trade trade, String role, boolean isMyOffer);
return getOfferInfoBuilder(offer, false).build(); return getBuilder(offer, false).build();
} }
public static OfferInfo toPendingOfferInfo(Offer myNewOffer) { public static OfferInfo toMyPendingOfferInfo(Offer myNewOffer) {
// Use this to build an OfferInfo instance when a new OpenOffer is being // Use this to build an OfferInfo instance when a new OpenOffer is being
// prepared, and no valid OpenOffer state (AVAILABLE, DEACTIVATED) exists. // prepared, and no valid OpenOffer state (AVAILABLE, DEACTIVATED) exists.
// It is needed for the CLI's 'createoffer' output, which has a boolean 'ENABLED' // It is needed for the CLI's 'createoffer' output, which has a boolean 'ENABLED'
// column that will show a PENDING value when this.isMyPendingOffer = true. // column that will show a PENDING value when this.isMyPendingOffer = true.
return getOfferInfoBuilder(myNewOffer, true) return getBuilder(myNewOffer, true)
.withIsMyPendingOffer(true) .withIsMyPendingOffer(true)
.build(); .build();
} }
public static OfferInfo toOfferInfo(OpenOffer openOffer) { public static OfferInfo toMyOfferInfo(OpenOffer openOffer) {
// An OpenOffer is always my offer. // An OpenOffer is always my offer.
return getOfferInfoBuilder(openOffer.getOffer(), true) return getBuilder(openOffer.getOffer(), true)
.withTriggerPrice(openOffer.getTriggerPrice()) .withTriggerPrice(openOffer.getTriggerPrice())
.withIsActivated(!openOffer.isDeactivated()) .withIsActivated(!openOffer.isDeactivated())
.build(); .build();
} }
private static OfferInfoBuilder getOfferInfoBuilder(Offer offer, boolean isMyOffer) { private static OfferInfoBuilder getBuilder(Offer offer, boolean isMyOffer) {
return new OfferInfoBuilder() return new OfferInfoBuilder()
.withId(offer.getId()) .withId(offer.getId())
.withDirection(offer.getDirection().name()) .withDirection(offer.getDirection().name())
.withPrice(Objects.requireNonNull(offer.getPrice()).getValue()) .withPrice(requireNonNull(offer.getPrice()).getValue())
.withUseMarketBasedPrice(offer.isUseMarketBasedPrice()) .withUseMarketBasedPrice(offer.isUseMarketBasedPrice())
.withMarketPriceMargin(offer.getMarketPriceMargin()) .withMarketPriceMargin(offer.getMarketPriceMargin())
.withAmount(offer.getAmount().value) .withAmount(offer.getAmount().value)
.withMinAmount(offer.getMinAmount().value) .withMinAmount(offer.getMinAmount().value)
.withVolume(Objects.requireNonNull(offer.getVolume()).getValue()) .withVolume(requireNonNull(offer.getVolume()).getValue())
.withMinVolume(Objects.requireNonNull(offer.getMinVolume()).getValue()) .withMinVolume(requireNonNull(offer.getMinVolume()).getValue())
.withMakerFee(offer.getMakerFee().value) .withMakerFee(getMakerFee(offer, isMyOffer))
.withTxFee(offer.getTxFee().value) .withTxFee(offer.getTxFee().value)
.withOfferFeePaymentTxId(offer.getOfferFeePaymentTxId()) .withOfferFeePaymentTxId(offer.getOfferFeePaymentTxId())
.withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value) .withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value)
@ -148,7 +159,18 @@ public class OfferInfo implements Payload {
.withCounterCurrencyCode(offer.getCounterCurrencyCode()) .withCounterCurrencyCode(offer.getCounterCurrencyCode())
.withDate(offer.getDate().getTime()) .withDate(offer.getDate().getTime())
.withState(offer.getState().name()) .withState(offer.getState().name())
.withIsMyOffer(isMyOffer); .withIsMyOffer(isMyOffer)
.withIsBsqSwapOffer(offer.isBsqSwapOffer())
.withOwnerNodeAddress(offer.getOfferPayloadBase().getOwnerNodeAddress().getFullAddress())
.withPubKeyRing(offer.getOfferPayloadBase().getPubKeyRing().toString())
.withVersionNumber(offer.getOfferPayloadBase().getVersionNr())
.withProtocolVersion(offer.getOfferPayloadBase().getProtocolVersion());
}
private static long getMakerFee(Offer offer, boolean isMyOffer) {
return isMyOffer
? requireNonNull(CoinUtil.getMakerFee(false, offer.getAmount())).value
: 0;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -169,7 +191,7 @@ public class OfferInfo implements Payload {
.setMinVolume(minVolume) .setMinVolume(minVolume)
.setMakerFee(makerFee) .setMakerFee(makerFee)
.setTxFee(txFee) .setTxFee(txFee)
.setOfferFeePaymentTxId(offerFeePaymentTxId) .setOfferFeePaymentTxId(isBsqSwapOffer ? "" : offerFeePaymentTxId)
.setBuyerSecurityDeposit(buyerSecurityDeposit) .setBuyerSecurityDeposit(buyerSecurityDeposit)
.setSellerSecurityDeposit(sellerSecurityDeposit) .setSellerSecurityDeposit(sellerSecurityDeposit)
.setTriggerPrice(triggerPrice) .setTriggerPrice(triggerPrice)
@ -184,6 +206,11 @@ public class OfferInfo implements Payload {
.setIsActivated(isActivated) .setIsActivated(isActivated)
.setIsMyOffer(isMyOffer) .setIsMyOffer(isMyOffer)
.setIsMyPendingOffer(isMyPendingOffer) .setIsMyPendingOffer(isMyPendingOffer)
.setIsBsqSwapOffer(isBsqSwapOffer)
.setOwnerNodeAddress(ownerNodeAddress)
.setPubKeyRing(pubKeyRing)
.setVersionNr(versionNumber)
.setProtocolVersion(protocolVersion)
.build(); .build();
} }
@ -216,175 +243,11 @@ public class OfferInfo implements Payload {
.withIsActivated(proto.getIsActivated()) .withIsActivated(proto.getIsActivated())
.withIsMyOffer(proto.getIsMyOffer()) .withIsMyOffer(proto.getIsMyOffer())
.withIsMyPendingOffer(proto.getIsMyPendingOffer()) .withIsMyPendingOffer(proto.getIsMyPendingOffer())
.withIsBsqSwapOffer(proto.getIsBsqSwapOffer())
.withOwnerNodeAddress(proto.getOwnerNodeAddress())
.withPubKeyRing(proto.getPubKeyRing())
.withVersionNumber(proto.getVersionNr())
.withProtocolVersion(proto.getProtocolVersion())
.build(); .build();
} }
/*
* OfferInfoBuilder helps avoid bungling use of a large OfferInfo constructor
* argument list. If consecutive argument values of the same type are not
* ordered correctly, the compiler won't complain but the resulting bugs could
* be hard to find and fix.
*/
public static class OfferInfoBuilder {
private String id;
private String direction;
private long price;
private boolean useMarketBasedPrice;
private double marketPriceMargin;
private long amount;
private long minAmount;
private long volume;
private long minVolume;
private long txFee;
private long makerFee;
private String offerFeePaymentTxId;
private long buyerSecurityDeposit;
private long sellerSecurityDeposit;
private long triggerPrice;
private boolean isCurrencyForMakerFeeBtc;
private String paymentAccountId;
private String paymentMethodId;
private String paymentMethodShortName;
private String baseCurrencyCode;
private String counterCurrencyCode;
private long date;
private String state;
private boolean isActivated;
private boolean isMyOffer;
private boolean isMyPendingOffer;
public OfferInfoBuilder withId(String id) {
this.id = id;
return this;
}
public OfferInfoBuilder withDirection(String direction) {
this.direction = direction;
return this;
}
public OfferInfoBuilder withPrice(long price) {
this.price = price;
return this;
}
public OfferInfoBuilder withUseMarketBasedPrice(boolean useMarketBasedPrice) {
this.useMarketBasedPrice = useMarketBasedPrice;
return this;
}
public OfferInfoBuilder withMarketPriceMargin(double useMarketBasedPrice) {
this.marketPriceMargin = useMarketBasedPrice;
return this;
}
public OfferInfoBuilder withAmount(long amount) {
this.amount = amount;
return this;
}
public OfferInfoBuilder withMinAmount(long minAmount) {
this.minAmount = minAmount;
return this;
}
public OfferInfoBuilder withVolume(long volume) {
this.volume = volume;
return this;
}
public OfferInfoBuilder withMinVolume(long minVolume) {
this.minVolume = minVolume;
return this;
}
public OfferInfoBuilder withTxFee(long txFee) {
this.txFee = txFee;
return this;
}
public OfferInfoBuilder withMakerFee(long makerFee) {
this.makerFee = makerFee;
return this;
}
public OfferInfoBuilder withOfferFeePaymentTxId(String offerFeePaymentTxId) {
this.offerFeePaymentTxId = offerFeePaymentTxId;
return this;
}
public OfferInfoBuilder withBuyerSecurityDeposit(long buyerSecurityDeposit) {
this.buyerSecurityDeposit = buyerSecurityDeposit;
return this;
}
public OfferInfoBuilder withSellerSecurityDeposit(long sellerSecurityDeposit) {
this.sellerSecurityDeposit = sellerSecurityDeposit;
return this;
}
public OfferInfoBuilder withTriggerPrice(long triggerPrice) {
this.triggerPrice = triggerPrice;
return this;
}
public OfferInfoBuilder withIsCurrencyForMakerFeeBtc(boolean isCurrencyForMakerFeeBtc) {
this.isCurrencyForMakerFeeBtc = isCurrencyForMakerFeeBtc;
return this;
}
public OfferInfoBuilder withPaymentAccountId(String paymentAccountId) {
this.paymentAccountId = paymentAccountId;
return this;
}
public OfferInfoBuilder withPaymentMethodId(String paymentMethodId) {
this.paymentMethodId = paymentMethodId;
return this;
}
public OfferInfoBuilder withPaymentMethodShortName(String paymentMethodShortName) {
this.paymentMethodShortName = paymentMethodShortName;
return this;
}
public OfferInfoBuilder withBaseCurrencyCode(String baseCurrencyCode) {
this.baseCurrencyCode = baseCurrencyCode;
return this;
}
public OfferInfoBuilder withCounterCurrencyCode(String counterCurrencyCode) {
this.counterCurrencyCode = counterCurrencyCode;
return this;
}
public OfferInfoBuilder withDate(long date) {
this.date = date;
return this;
}
public OfferInfoBuilder withState(String state) {
this.state = state;
return this;
}
public OfferInfoBuilder withIsActivated(boolean isActivated) {
this.isActivated = isActivated;
return this;
}
public OfferInfoBuilder withIsMyOffer(boolean isMyOffer) {
this.isMyOffer = isMyOffer;
return this;
}
public OfferInfoBuilder withIsMyPendingOffer(boolean isMyPendingOffer) {
this.isMyPendingOffer = isMyPendingOffer;
return this;
}
public OfferInfo build() {
return new OfferInfo(this);
}
}
} }

View file

@ -17,18 +17,22 @@
package bisq.core.api.model; package bisq.core.api.model;
import bisq.core.api.model.builder.TradeInfoV1Builder;
import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bisq_v1.Contract; import bisq.core.trade.model.bisq_v1.Contract;
import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.common.Payload; import bisq.common.Payload;
import java.util.Objects;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import static bisq.core.api.model.BsqSwapTradeInfo.toBsqSwapTradeInfo;
import static bisq.core.api.model.OfferInfo.toMyOfferInfo;
import static bisq.core.api.model.OfferInfo.toOfferInfo; import static bisq.core.api.model.OfferInfo.toOfferInfo;
import static bisq.core.api.model.PaymentAccountPayloadInfo.toPaymentAccountPayloadInfo; import static bisq.core.api.model.PaymentAccountPayloadInfo.toPaymentAccountPayloadInfo;
import static java.util.Objects.requireNonNull;
@EqualsAndHashCode @EqualsAndHashCode
@Getter @Getter
@ -38,6 +42,7 @@ public class TradeInfo implements Payload {
// lighter weight TradeInfo proto wrapper instead, containing just enough fields to // lighter weight TradeInfo proto wrapper instead, containing just enough fields to
// view and interact with trades. // view and interact with trades.
// Bisq v1 trade protocol fields (some are in common with the BSQ Swap protocol).
private final OfferInfo offer; private final OfferInfo offer;
private final String tradeId; private final String tradeId;
private final String shortId; private final String shortId;
@ -64,41 +69,88 @@ public class TradeInfo implements Payload {
private final boolean isWithdrawn; private final boolean isWithdrawn;
private final String contractAsJson; private final String contractAsJson;
private final ContractInfo contract; private final ContractInfo contract;
// Optional BSQ swap trade protocol details (post v1).
private BsqSwapTradeInfo bsqSwapTradeInfo;
public TradeInfo(TradeInfoBuilder builder) { public TradeInfo(TradeInfoV1Builder builder) {
this.offer = builder.offer; this.offer = builder.getOffer();
this.tradeId = builder.tradeId; this.tradeId = builder.getTradeId();
this.shortId = builder.shortId; this.shortId = builder.getShortId();
this.date = builder.date; this.date = builder.getDate();
this.role = builder.role; this.role = builder.getRole();
this.isCurrencyForTakerFeeBtc = builder.isCurrencyForTakerFeeBtc; this.isCurrencyForTakerFeeBtc = builder.isCurrencyForTakerFeeBtc();
this.txFeeAsLong = builder.txFeeAsLong; this.txFeeAsLong = builder.getTxFeeAsLong();
this.takerFeeAsLong = builder.takerFeeAsLong; this.takerFeeAsLong = builder.getTakerFeeAsLong();
this.takerFeeTxId = builder.takerFeeTxId; this.takerFeeTxId = builder.getTakerFeeTxId();
this.depositTxId = builder.depositTxId; this.depositTxId = builder.getDepositTxId();
this.payoutTxId = builder.payoutTxId; this.payoutTxId = builder.getPayoutTxId();
this.tradeAmountAsLong = builder.tradeAmountAsLong; this.tradeAmountAsLong = builder.getTradeAmountAsLong();
this.tradePrice = builder.tradePrice; this.tradePrice = builder.getTradePrice();
this.tradeVolume = builder.tradeVolume; this.tradeVolume = builder.getTradeVolume();
this.tradingPeerNodeAddress = builder.tradingPeerNodeAddress; this.tradingPeerNodeAddress = builder.getTradingPeerNodeAddress();
this.state = builder.state; this.state = builder.getState();
this.phase = builder.phase; this.phase = builder.getPhase();
this.tradePeriodState = builder.tradePeriodState; this.tradePeriodState = builder.getTradePeriodState();
this.isDepositPublished = builder.isDepositPublished; this.isDepositPublished = builder.isDepositPublished();
this.isDepositConfirmed = builder.isDepositConfirmed; this.isDepositConfirmed = builder.isDepositConfirmed();
this.isFiatSent = builder.isFiatSent; this.isFiatSent = builder.isFiatSent();
this.isFiatReceived = builder.isFiatReceived; this.isFiatReceived = builder.isFiatReceived();
this.isPayoutPublished = builder.isPayoutPublished; this.isPayoutPublished = builder.isPayoutPublished();
this.isWithdrawn = builder.isWithdrawn; this.isWithdrawn = builder.isWithdrawn();
this.contractAsJson = builder.contractAsJson; this.contractAsJson = builder.getContractAsJson();
this.contract = builder.contract; this.contract = builder.getContract();
this.bsqSwapTradeInfo = null;
}
public static TradeInfo toNewTradeInfo(BsqSwapTrade trade, String role) {
// Always called by the taker, isMyOffer=false.
return toTradeInfo(trade, role, false, 0);
} }
public static TradeInfo toNewTradeInfo(Trade trade) { public static TradeInfo toNewTradeInfo(Trade trade) {
// Always called by the taker, isMyOffer=false.
return toTradeInfo(trade, null, false); return toTradeInfo(trade, null, false);
} }
public static TradeInfo toTradeInfo(Trade trade, String role, boolean isMyOffer) { public static TradeInfo toTradeInfo(TradeModel tradeModel, String role, boolean isMyOffer) {
if (tradeModel instanceof Trade)
return toTradeInfo((Trade) tradeModel, role, isMyOffer);
else if (tradeModel instanceof BsqSwapTrade)
return toTradeInfo(tradeModel, role, isMyOffer);
else
throw new IllegalStateException("unsupported trade type: " + tradeModel.getClass().getSimpleName());
}
public static TradeInfo toTradeInfo(BsqSwapTrade bsqSwapTrade,
String role,
boolean isMyOffer,
int numConfirmations) {
OfferInfo offerInfo = isMyOffer ? toMyOfferInfo(bsqSwapTrade.getOffer()) : toOfferInfo(bsqSwapTrade.getOffer());
TradeInfo tradeInfo = new TradeInfoV1Builder()
.withOffer(offerInfo)
.withTradeId(bsqSwapTrade.getId())
.withShortId(bsqSwapTrade.getShortId())
.withDate(bsqSwapTrade.getDate().getTime())
.withRole(role == null ? "" : role)
.withIsCurrencyForTakerFeeBtc(false) // BSQ Swap fees always paid in BSQ.
.withTxFeeAsLong(bsqSwapTrade.getTxFee().value)
.withTakerFeeAsLong(bsqSwapTrade.getTakerFeeAsLong())
// N/A: .withTakerFeeTxId(""), .withDepositTxId(""), .withPayoutTxId("")
.withTradeAmountAsLong(bsqSwapTrade.getAmountAsLong())
.withTradePrice(bsqSwapTrade.getPrice().getValue())
.withTradeVolume(bsqSwapTrade.getVolume() == null ? 0 : bsqSwapTrade.getVolume().getValue())
.withTradingPeerNodeAddress(requireNonNull(bsqSwapTrade.getTradingPeerNodeAddress().getFullAddress()))
.withState(bsqSwapTrade.getTradeState().name())
.withPhase(bsqSwapTrade.getTradePhase().name())
// N/A: .withTradePeriodState(""), .withIsDepositPublished(false), .withIsDepositConfirmed(false)
// N/A: .withIsFiatSent(false), .withIsFiatReceived(false), .withIsPayoutPublished(false)
// N/A: .withIsWithdrawn(false), .withContractAsJson(""), .withContract(null)
.build();
tradeInfo.bsqSwapTradeInfo = toBsqSwapTradeInfo(bsqSwapTrade, isMyOffer, numConfirmations);
return tradeInfo;
}
private static TradeInfo toTradeInfo(Trade trade, String role, boolean isMyOffer) {
ContractInfo contractInfo; ContractInfo contractInfo;
if (trade.getContract() != null) { if (trade.getContract() != null) {
Contract contract = trade.getContract(); Contract contract = trade.getContract();
@ -118,9 +170,8 @@ public class TradeInfo implements Payload {
contractInfo = ContractInfo.emptyContract.get(); contractInfo = ContractInfo.emptyContract.get();
} }
OfferInfo offerInfo = toOfferInfo(trade.getOffer()); OfferInfo offerInfo = isMyOffer ? toMyOfferInfo(trade.getOffer()) : toOfferInfo(trade.getOffer());
offerInfo.setIsMyOffer(isMyOffer); return new TradeInfoV1Builder()
return new TradeInfoBuilder()
.withOffer(offerInfo) .withOffer(offerInfo)
.withTradeId(trade.getId()) .withTradeId(trade.getId())
.withShortId(trade.getShortId()) .withShortId(trade.getShortId())
@ -129,15 +180,13 @@ public class TradeInfo implements Payload {
.withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc()) .withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc())
.withTxFeeAsLong(trade.getTradeTxFeeAsLong()) .withTxFeeAsLong(trade.getTradeTxFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong()) .withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeTxId(trade.getTakerFeeTxId()) .withTakerFeeTxId(trade.getTakerFeeTxId())
.withDepositTxId(trade.getDepositTxId()) .withDepositTxId(trade.getDepositTxId())
.withPayoutTxId(trade.getPayoutTxId()) .withPayoutTxId(trade.getPayoutTxId())
.withTradeAmountAsLong(trade.getAmountAsLong()) .withTradeAmountAsLong(trade.getAmountAsLong())
.withTradePrice(trade.getPrice().getValue()) .withTradePrice(trade.getPrice().getValue())
.withTradeVolume(trade.getVolume() == null ? 0 : trade.getVolume().getValue()) .withTradeVolume(trade.getVolume() == null ? 0 : trade.getVolume().getValue())
.withTradingPeerNodeAddress(Objects.requireNonNull( .withTradingPeerNodeAddress(requireNonNull(trade.getTradingPeerNodeAddress().getFullAddress()))
trade.getTradingPeerNodeAddress()).getHostNameWithoutPostFix())
.withState(trade.getTradeState().name()) .withState(trade.getTradeState().name())
.withPhase(trade.getTradePhase().name()) .withPhase(trade.getTradePhase().name())
.withTradePeriodState(trade.getTradePeriodState().name()) .withTradePeriodState(trade.getTradePeriodState().name())
@ -158,38 +207,45 @@ public class TradeInfo implements Payload {
@Override @Override
public bisq.proto.grpc.TradeInfo toProtoMessage() { public bisq.proto.grpc.TradeInfo toProtoMessage() {
return bisq.proto.grpc.TradeInfo.newBuilder() var protoBuilder =
.setOffer(offer.toProtoMessage()) bisq.proto.grpc.TradeInfo.newBuilder()
.setTradeId(tradeId) .setOffer(offer.toProtoMessage())
.setShortId(shortId) .setTradeId(tradeId)
.setDate(date) .setShortId(shortId)
.setRole(role) .setDate(date)
.setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc) .setRole(role)
.setTxFeeAsLong(txFeeAsLong) .setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc)
.setTakerFeeAsLong(takerFeeAsLong) .setTxFeeAsLong(txFeeAsLong)
.setTakerFeeTxId(takerFeeTxId == null ? "" : takerFeeTxId) .setTakerFeeAsLong(takerFeeAsLong)
.setDepositTxId(depositTxId == null ? "" : depositTxId) .setTakerFeeTxId(takerFeeTxId == null ? "" : takerFeeTxId)
.setPayoutTxId(payoutTxId == null ? "" : payoutTxId) .setDepositTxId(depositTxId == null ? "" : depositTxId)
.setTradeAmountAsLong(tradeAmountAsLong) .setPayoutTxId(payoutTxId == null ? "" : payoutTxId)
.setTradePrice(tradePrice) .setTradeAmountAsLong(tradeAmountAsLong)
.setTradeVolume(tradeVolume) .setTradePrice(tradePrice)
.setTradingPeerNodeAddress(tradingPeerNodeAddress) .setTradeVolume(tradeVolume)
.setState(state) .setTradingPeerNodeAddress(tradingPeerNodeAddress)
.setPhase(phase) .setState(state == null ? "" : state)
.setTradePeriodState(tradePeriodState) .setPhase(phase == null ? "" : phase)
.setIsDepositPublished(isDepositPublished) .setTradePeriodState(tradePeriodState == null ? "" : tradePeriodState)
.setIsDepositConfirmed(isDepositConfirmed) .setIsDepositPublished(isDepositPublished)
.setIsFiatSent(isFiatSent) .setIsDepositConfirmed(isDepositConfirmed)
.setIsFiatReceived(isFiatReceived) .setIsFiatSent(isFiatSent)
.setIsPayoutPublished(isPayoutPublished) .setIsFiatReceived(isFiatReceived)
.setIsWithdrawn(isWithdrawn) .setIsPayoutPublished(isPayoutPublished)
.setContractAsJson(contractAsJson == null ? "" : contractAsJson) .setIsWithdrawn(isWithdrawn);
.setContract(contract.toProtoMessage())
.build(); if (offer.isBsqSwapOffer()) {
protoBuilder.setBsqSwapTradeInfo(bsqSwapTradeInfo.toProtoMessage());
} else {
protoBuilder.setContractAsJson(contractAsJson == null ? "" : contractAsJson);
protoBuilder.setContract(contract.toProtoMessage());
}
return protoBuilder.build();
} }
public static TradeInfo fromProto(bisq.proto.grpc.TradeInfo proto) { public static TradeInfo fromProto(bisq.proto.grpc.TradeInfo proto) {
return new TradeInfoBuilder() var tradeInfo = new TradeInfoV1Builder()
.withOffer(OfferInfo.fromProto(proto.getOffer())) .withOffer(OfferInfo.fromProto(proto.getOffer()))
.withTradeId(proto.getTradeId()) .withTradeId(proto.getTradeId())
.withShortId(proto.getShortId()) .withShortId(proto.getShortId())
@ -217,175 +273,11 @@ public class TradeInfo implements Payload {
.withContractAsJson(proto.getContractAsJson()) .withContractAsJson(proto.getContractAsJson())
.withContract((ContractInfo.fromProto(proto.getContract()))) .withContract((ContractInfo.fromProto(proto.getContract())))
.build(); .build();
}
/* if (proto.getOffer().getIsBsqSwapOffer())
* TradeInfoBuilder helps avoid bungling use of a large TradeInfo constructor tradeInfo.bsqSwapTradeInfo = BsqSwapTradeInfo.fromProto(proto.getBsqSwapTradeInfo());
* argument list. If consecutive argument values of the same type are not
* ordered correctly, the compiler won't complain but the resulting bugs could
* be hard to find and fix.
*/
public static class TradeInfoBuilder {
private OfferInfo offer;
private String tradeId;
private String shortId;
private long date;
private String role;
private boolean isCurrencyForTakerFeeBtc;
private long txFeeAsLong;
private long takerFeeAsLong;
private String takerFeeTxId;
private String depositTxId;
private String payoutTxId;
private long tradeAmountAsLong;
private long tradePrice;
private long tradeVolume;
private String tradingPeerNodeAddress;
private String state;
private String phase;
private String tradePeriodState;
private boolean isDepositPublished;
private boolean isDepositConfirmed;
private boolean isFiatSent;
private boolean isFiatReceived;
private boolean isPayoutPublished;
private boolean isWithdrawn;
private String contractAsJson;
private ContractInfo contract;
public TradeInfoBuilder withOffer(OfferInfo offer) { return tradeInfo;
this.offer = offer;
return this;
}
public TradeInfoBuilder withTradeId(String tradeId) {
this.tradeId = tradeId;
return this;
}
public TradeInfoBuilder withShortId(String shortId) {
this.shortId = shortId;
return this;
}
public TradeInfoBuilder withDate(long date) {
this.date = date;
return this;
}
public TradeInfoBuilder withRole(String role) {
this.role = role;
return this;
}
public TradeInfoBuilder withIsCurrencyForTakerFeeBtc(boolean isCurrencyForTakerFeeBtc) {
this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc;
return this;
}
public TradeInfoBuilder withTxFeeAsLong(long txFeeAsLong) {
this.txFeeAsLong = txFeeAsLong;
return this;
}
public TradeInfoBuilder withTakerFeeAsLong(long takerFeeAsLong) {
this.takerFeeAsLong = takerFeeAsLong;
return this;
}
public TradeInfoBuilder withTakerFeeTxId(String takerFeeTxId) {
this.takerFeeTxId = takerFeeTxId;
return this;
}
public TradeInfoBuilder withDepositTxId(String depositTxId) {
this.depositTxId = depositTxId;
return this;
}
public TradeInfoBuilder withPayoutTxId(String payoutTxId) {
this.payoutTxId = payoutTxId;
return this;
}
public TradeInfoBuilder withTradeAmountAsLong(long tradeAmountAsLong) {
this.tradeAmountAsLong = tradeAmountAsLong;
return this;
}
public TradeInfoBuilder withTradePrice(long tradePrice) {
this.tradePrice = tradePrice;
return this;
}
public TradeInfoBuilder withTradeVolume(long tradeVolume) {
this.tradeVolume = tradeVolume;
return this;
}
public TradeInfoBuilder withTradePeriodState(String tradePeriodState) {
this.tradePeriodState = tradePeriodState;
return this;
}
public TradeInfoBuilder withState(String state) {
this.state = state;
return this;
}
public TradeInfoBuilder withPhase(String phase) {
this.phase = phase;
return this;
}
public TradeInfoBuilder withTradingPeerNodeAddress(String tradingPeerNodeAddress) {
this.tradingPeerNodeAddress = tradingPeerNodeAddress;
return this;
}
public TradeInfoBuilder withIsDepositPublished(boolean isDepositPublished) {
this.isDepositPublished = isDepositPublished;
return this;
}
public TradeInfoBuilder withIsDepositConfirmed(boolean isDepositConfirmed) {
this.isDepositConfirmed = isDepositConfirmed;
return this;
}
public TradeInfoBuilder withIsFiatSent(boolean isFiatSent) {
this.isFiatSent = isFiatSent;
return this;
}
public TradeInfoBuilder withIsFiatReceived(boolean isFiatReceived) {
this.isFiatReceived = isFiatReceived;
return this;
}
public TradeInfoBuilder withIsPayoutPublished(boolean isPayoutPublished) {
this.isPayoutPublished = isPayoutPublished;
return this;
}
public TradeInfoBuilder withIsWithdrawn(boolean isWithdrawn) {
this.isWithdrawn = isWithdrawn;
return this;
}
public TradeInfoBuilder withContractAsJson(String contractAsJson) {
this.contractAsJson = contractAsJson;
return this;
}
public TradeInfoBuilder withContract(ContractInfo contract) {
this.contract = contract;
return this;
}
public TradeInfo build() {
return new TradeInfo(this);
}
} }
@Override @Override
@ -417,6 +309,7 @@ public class TradeInfo implements Payload {
", offer=" + offer + "\n" + ", offer=" + offer + "\n" +
", contractAsJson=" + contractAsJson + "\n" + ", contractAsJson=" + contractAsJson + "\n" +
", contract=" + contract + "\n" + ", contract=" + contract + "\n" +
", bsqSwapTradeInfo=" + bsqSwapTradeInfo + "\n" +
'}'; '}';
} }
} }

View file

@ -46,7 +46,7 @@ public class TxInfo implements Payload {
private final boolean isPending; private final boolean isPending;
private final String memo; private final String memo;
public TxInfo(TxInfoBuilder builder) { public TxInfo(Builder builder) {
this.txId = builder.txId; this.txId = builder.txId;
this.inputSum = builder.inputSum; this.inputSum = builder.inputSum;
this.outputSum = builder.outputSum; this.outputSum = builder.outputSum;
@ -61,7 +61,7 @@ public class TxInfo implements Payload {
throw new IllegalStateException("server created a null transaction"); throw new IllegalStateException("server created a null transaction");
if (transaction.getFee() != null) if (transaction.getFee() != null)
return new TxInfoBuilder() return new Builder()
.withTxId(transaction.getTxId().toString()) .withTxId(transaction.getTxId().toString())
.withInputSum(transaction.getInputSum().value) .withInputSum(transaction.getInputSum().value)
.withOutputSum(transaction.getOutputSum().value) .withOutputSum(transaction.getOutputSum().value)
@ -71,7 +71,7 @@ public class TxInfo implements Payload {
.withMemo(transaction.getMemo()) .withMemo(transaction.getMemo())
.build(); .build();
else else
return new TxInfoBuilder() return new Builder()
.withTxId(transaction.getTxId().toString()) .withTxId(transaction.getTxId().toString())
.withInputSum(transaction.getInputSum().value) .withInputSum(transaction.getInputSum().value)
.withOutputSum(transaction.getOutputSum().value) .withOutputSum(transaction.getOutputSum().value)
@ -101,7 +101,7 @@ public class TxInfo implements Payload {
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static TxInfo fromProto(bisq.proto.grpc.TxInfo proto) { public static TxInfo fromProto(bisq.proto.grpc.TxInfo proto) {
return new TxInfoBuilder() return new Builder()
.withTxId(proto.getTxId()) .withTxId(proto.getTxId())
.withInputSum(proto.getInputSum()) .withInputSum(proto.getInputSum())
.withOutputSum(proto.getOutputSum()) .withOutputSum(proto.getOutputSum())
@ -112,7 +112,7 @@ public class TxInfo implements Payload {
.build(); .build();
} }
public static class TxInfoBuilder { private static class Builder {
private String txId; private String txId;
private long inputSum; private long inputSum;
private long outputSum; private long outputSum;
@ -121,37 +121,37 @@ public class TxInfo implements Payload {
private boolean isPending; private boolean isPending;
private String memo; private String memo;
public TxInfoBuilder withTxId(String txId) { public Builder withTxId(String txId) {
this.txId = txId; this.txId = txId;
return this; return this;
} }
public TxInfoBuilder withInputSum(long inputSum) { public Builder withInputSum(long inputSum) {
this.inputSum = inputSum; this.inputSum = inputSum;
return this; return this;
} }
public TxInfoBuilder withOutputSum(long outputSum) { public Builder withOutputSum(long outputSum) {
this.outputSum = outputSum; this.outputSum = outputSum;
return this; return this;
} }
public TxInfoBuilder withFee(long fee) { public Builder withFee(long fee) {
this.fee = fee; this.fee = fee;
return this; return this;
} }
public TxInfoBuilder withSize(int size) { public Builder withSize(int size) {
this.size = size; this.size = size;
return this; return this;
} }
public TxInfoBuilder withIsPending(boolean isPending) { public Builder withIsPending(boolean isPending) {
this.isPending = isPending; this.isPending = isPending;
return this; return this;
} }
public TxInfoBuilder withMemo(String memo) { public Builder withMemo(String memo) {
this.memo = memo; this.memo = memo;
return this; return this;
} }

View file

@ -0,0 +1,113 @@
/*
* 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.api.model.builder;
import bisq.core.api.model.BsqSwapTradeInfo;
import lombok.Getter;
/**
* Proto wrapper for BSQ swap protocol details not common to Bisq v1
* trade protocol details.
*
* This builder helps avoid bungling use of a large BsqSwapTradeInfo constructor
* argument list. If consecutive argument values of the same type are not
* ordered correctly, the compiler won't complain but the resulting bugs could
* be hard to find and fix.
*/
@Getter
public final class BsqSwapTradeInfoBuilder {
private String txId;
private long bsqTradeAmount;
private long btcTradeAmount;
private long bsqMakerTradeFee;
private long bsqTakerTradeFee;
private long txFeePerVbyte;
private String makerBsqAddress;
private String makerBtcAddress;
private String takerBsqAddress;
private String takerBtcAddress;
private long numConfirmations;
private String errorMessage;
public BsqSwapTradeInfoBuilder withTxId(String txId) {
this.txId = txId;
return this;
}
public BsqSwapTradeInfoBuilder withBsqTradeAmount(long bsqTradeAmount) {
this.bsqTradeAmount = bsqTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBtcTradeAmount(long btcTradeAmount) {
this.btcTradeAmount = btcTradeAmount;
return this;
}
public BsqSwapTradeInfoBuilder withBsqMakerTradeFee(long bsqMakerTradeFee) {
this.bsqMakerTradeFee = bsqMakerTradeFee;
return this;
}
public BsqSwapTradeInfoBuilder withBsqTakerTradeFee(long bsqTakerTradeFee) {
this.bsqTakerTradeFee = bsqTakerTradeFee;
return this;
}
public BsqSwapTradeInfoBuilder withTxFeePerVbyte(long txFeePerVbyte) {
this.txFeePerVbyte = txFeePerVbyte;
return this;
}
public BsqSwapTradeInfoBuilder withMakerBsqAddress(String makerBsqAddress) {
this.makerBsqAddress = makerBsqAddress;
return this;
}
public BsqSwapTradeInfoBuilder withMakerBtcAddress(String makerBtcAddress) {
this.makerBtcAddress = makerBtcAddress;
return this;
}
public BsqSwapTradeInfoBuilder withTakerBsqAddress(String takerBsqAddress) {
this.takerBsqAddress = takerBsqAddress;
return this;
}
public BsqSwapTradeInfoBuilder withTakerBtcAddress(String takerBtcAddress) {
this.takerBtcAddress = takerBtcAddress;
return this;
}
public BsqSwapTradeInfoBuilder withNumConfirmations(long numConfirmations) {
this.numConfirmations = numConfirmations;
return this;
}
public BsqSwapTradeInfoBuilder withErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
return this;
}
public BsqSwapTradeInfo build() {
return new BsqSwapTradeInfo(this);
}
}

View file

@ -0,0 +1,223 @@
/*
* 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.api.model.builder;
import bisq.core.api.model.OfferInfo;
import lombok.Getter;
/*
* A builder helps avoid bungling use of a large OfferInfo constructor
* argument list. If consecutive argument values of the same type are not
* ordered correctly, the compiler won't complain but the resulting bugs could
* be hard to find and fix.
*/
@Getter
public final class OfferInfoBuilder {
private String id;
private String direction;
private long price;
private boolean useMarketBasedPrice;
private double marketPriceMargin;
private long amount;
private long minAmount;
private long volume;
private long minVolume;
private long txFee;
private long makerFee;
private String offerFeePaymentTxId;
private long buyerSecurityDeposit;
private long sellerSecurityDeposit;
private long triggerPrice;
private boolean isCurrencyForMakerFeeBtc;
private String paymentAccountId;
private String paymentMethodId;
private String paymentMethodShortName;
private String baseCurrencyCode;
private String counterCurrencyCode;
private long date;
private String state;
private boolean isActivated;
private boolean isMyOffer;
private boolean isMyPendingOffer;
private boolean isBsqSwapOffer;
private String ownerNodeAddress;
private String pubKeyRing;
private String versionNumber;
private int protocolVersion;
public OfferInfoBuilder withId(String id) {
this.id = id;
return this;
}
public OfferInfoBuilder withDirection(String direction) {
this.direction = direction;
return this;
}
public OfferInfoBuilder withPrice(long price) {
this.price = price;
return this;
}
public OfferInfoBuilder withUseMarketBasedPrice(boolean useMarketBasedPrice) {
this.useMarketBasedPrice = useMarketBasedPrice;
return this;
}
public OfferInfoBuilder withMarketPriceMargin(double useMarketBasedPrice) {
this.marketPriceMargin = useMarketBasedPrice;
return this;
}
public OfferInfoBuilder withAmount(long amount) {
this.amount = amount;
return this;
}
public OfferInfoBuilder withMinAmount(long minAmount) {
this.minAmount = minAmount;
return this;
}
public OfferInfoBuilder withVolume(long volume) {
this.volume = volume;
return this;
}
public OfferInfoBuilder withMinVolume(long minVolume) {
this.minVolume = minVolume;
return this;
}
public OfferInfoBuilder withTxFee(long txFee) {
this.txFee = txFee;
return this;
}
public OfferInfoBuilder withMakerFee(long makerFee) {
this.makerFee = makerFee;
return this;
}
public OfferInfoBuilder withOfferFeePaymentTxId(String offerFeePaymentTxId) {
this.offerFeePaymentTxId = offerFeePaymentTxId;
return this;
}
public OfferInfoBuilder withBuyerSecurityDeposit(long buyerSecurityDeposit) {
this.buyerSecurityDeposit = buyerSecurityDeposit;
return this;
}
public OfferInfoBuilder withSellerSecurityDeposit(long sellerSecurityDeposit) {
this.sellerSecurityDeposit = sellerSecurityDeposit;
return this;
}
public OfferInfoBuilder withTriggerPrice(long triggerPrice) {
this.triggerPrice = triggerPrice;
return this;
}
public OfferInfoBuilder withIsCurrencyForMakerFeeBtc(boolean isCurrencyForMakerFeeBtc) {
this.isCurrencyForMakerFeeBtc = isCurrencyForMakerFeeBtc;
return this;
}
public OfferInfoBuilder withPaymentAccountId(String paymentAccountId) {
this.paymentAccountId = paymentAccountId;
return this;
}
public OfferInfoBuilder withPaymentMethodId(String paymentMethodId) {
this.paymentMethodId = paymentMethodId;
return this;
}
public OfferInfoBuilder withPaymentMethodShortName(String paymentMethodShortName) {
this.paymentMethodShortName = paymentMethodShortName;
return this;
}
public OfferInfoBuilder withBaseCurrencyCode(String baseCurrencyCode) {
this.baseCurrencyCode = baseCurrencyCode;
return this;
}
public OfferInfoBuilder withCounterCurrencyCode(String counterCurrencyCode) {
this.counterCurrencyCode = counterCurrencyCode;
return this;
}
public OfferInfoBuilder withDate(long date) {
this.date = date;
return this;
}
public OfferInfoBuilder withState(String state) {
this.state = state;
return this;
}
public OfferInfoBuilder withIsActivated(boolean isActivated) {
this.isActivated = isActivated;
return this;
}
public OfferInfoBuilder withIsMyOffer(boolean isMyOffer) {
this.isMyOffer = isMyOffer;
return this;
}
public OfferInfoBuilder withIsMyPendingOffer(boolean isMyPendingOffer) {
this.isMyPendingOffer = isMyPendingOffer;
return this;
}
public OfferInfoBuilder withIsBsqSwapOffer(boolean isBsqSwapOffer) {
this.isBsqSwapOffer = isBsqSwapOffer;
return this;
}
public OfferInfoBuilder withOwnerNodeAddress(String ownerNodeAddress) {
this.ownerNodeAddress = ownerNodeAddress;
return this;
}
public OfferInfoBuilder withPubKeyRing(String pubKeyRing) {
this.pubKeyRing = pubKeyRing;
return this;
}
public OfferInfoBuilder withVersionNumber(String versionNumber) {
this.versionNumber = versionNumber;
return this;
}
public OfferInfoBuilder withProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
return this;
}
public OfferInfo build() {
return new OfferInfo(this);
}
}

View file

@ -0,0 +1,195 @@
/*
* 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.api.model.builder;
import bisq.core.api.model.ContractInfo;
import bisq.core.api.model.OfferInfo;
import bisq.core.api.model.TradeInfo;
import lombok.Getter;
/**
* A builder helps avoid bungling use of a large TradeInfo constructor
* argument list. If consecutive argument values of the same type are not
* ordered correctly, the compiler won't complain but the resulting bugs could
* be hard to find and fix.
*/
@Getter
public final class TradeInfoV1Builder {
private OfferInfo offer;
private String tradeId;
private String shortId;
private long date;
private String role;
private boolean isCurrencyForTakerFeeBtc;
private long txFeeAsLong;
private long takerFeeAsLong;
private String takerFeeTxId;
private String depositTxId;
private String payoutTxId;
private long tradeAmountAsLong;
private long tradePrice;
private long tradeVolume;
private String tradingPeerNodeAddress;
private String state;
private String phase;
private String tradePeriodState;
private boolean isDepositPublished;
private boolean isDepositConfirmed;
private boolean isFiatSent;
private boolean isFiatReceived;
private boolean isPayoutPublished;
private boolean isWithdrawn;
private String contractAsJson;
private ContractInfo contract;
public TradeInfoV1Builder withOffer(OfferInfo offer) {
this.offer = offer;
return this;
}
public TradeInfoV1Builder withTradeId(String tradeId) {
this.tradeId = tradeId;
return this;
}
public TradeInfoV1Builder withShortId(String shortId) {
this.shortId = shortId;
return this;
}
public TradeInfoV1Builder withDate(long date) {
this.date = date;
return this;
}
public TradeInfoV1Builder withRole(String role) {
this.role = role;
return this;
}
public TradeInfoV1Builder withIsCurrencyForTakerFeeBtc(boolean isCurrencyForTakerFeeBtc) {
this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc;
return this;
}
public TradeInfoV1Builder withTxFeeAsLong(long txFeeAsLong) {
this.txFeeAsLong = txFeeAsLong;
return this;
}
public TradeInfoV1Builder withTakerFeeAsLong(long takerFeeAsLong) {
this.takerFeeAsLong = takerFeeAsLong;
return this;
}
public TradeInfoV1Builder withTakerFeeTxId(String takerFeeTxId) {
this.takerFeeTxId = takerFeeTxId;
return this;
}
public TradeInfoV1Builder withDepositTxId(String depositTxId) {
this.depositTxId = depositTxId;
return this;
}
public TradeInfoV1Builder withPayoutTxId(String payoutTxId) {
this.payoutTxId = payoutTxId;
return this;
}
public TradeInfoV1Builder withTradeAmountAsLong(long tradeAmountAsLong) {
this.tradeAmountAsLong = tradeAmountAsLong;
return this;
}
public TradeInfoV1Builder withTradePrice(long tradePrice) {
this.tradePrice = tradePrice;
return this;
}
public TradeInfoV1Builder withTradeVolume(long tradeVolume) {
this.tradeVolume = tradeVolume;
return this;
}
public TradeInfoV1Builder withTradePeriodState(String tradePeriodState) {
this.tradePeriodState = tradePeriodState;
return this;
}
public TradeInfoV1Builder withState(String state) {
this.state = state;
return this;
}
public TradeInfoV1Builder withPhase(String phase) {
this.phase = phase;
return this;
}
public TradeInfoV1Builder withTradingPeerNodeAddress(String tradingPeerNodeAddress) {
this.tradingPeerNodeAddress = tradingPeerNodeAddress;
return this;
}
public TradeInfoV1Builder withIsDepositPublished(boolean isDepositPublished) {
this.isDepositPublished = isDepositPublished;
return this;
}
public TradeInfoV1Builder withIsDepositConfirmed(boolean isDepositConfirmed) {
this.isDepositConfirmed = isDepositConfirmed;
return this;
}
public TradeInfoV1Builder withIsFiatSent(boolean isFiatSent) {
this.isFiatSent = isFiatSent;
return this;
}
public TradeInfoV1Builder withIsFiatReceived(boolean isFiatReceived) {
this.isFiatReceived = isFiatReceived;
return this;
}
public TradeInfoV1Builder withIsPayoutPublished(boolean isPayoutPublished) {
this.isPayoutPublished = isPayoutPublished;
return this;
}
public TradeInfoV1Builder withIsWithdrawn(boolean isWithdrawn) {
this.isWithdrawn = isWithdrawn;
return this;
}
public TradeInfoV1Builder withContractAsJson(String contractAsJson) {
this.contractAsJson = contractAsJson;
return this;
}
public TradeInfoV1Builder withContract(ContractInfo contract) {
this.contract = contract;
return this;
}
public TradeInfo build() {
return new TradeInfo(this);
}
}

View file

@ -488,6 +488,14 @@ public class OfferUtil {
} }
} }
public static boolean isFiatOffer(Offer offer) {
return offer.getBaseCurrencyCode().equals("BTC") && !offer.isBsqSwapOffer();
}
public static boolean isAltcoinOffer(Offer offer) {
return offer.getCounterCurrencyCode().equals("BTC") && !offer.isBsqSwapOffer();
}
public static Optional<String> getInvalidMakerFeeTxErrorMessage(Offer offer, BtcWalletService btcWalletService) { public static Optional<String> getInvalidMakerFeeTxErrorMessage(Offer offer, BtcWalletService btcWalletService) {
String offerFeePaymentTxId = offer.getOfferFeePaymentTxId(); String offerFeePaymentTxId = offer.getOfferFeePaymentTxId();
if (offerFeePaymentTxId == null) { if (offerFeePaymentTxId == null) {

View file

@ -25,6 +25,7 @@ import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.model.TradeModel; import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bisq_v1.Contract; import bisq.core.trade.model.bisq_v1.Contract;
import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
@ -57,6 +58,8 @@ import static java.lang.String.format;
@Singleton @Singleton
public class TradeUtil { public class TradeUtil {
// TODO change non-state dependent instance methods to static methods.
private final BtcWalletService btcWalletService; private final BtcWalletService btcWalletService;
private final KeyRing keyRing; private final KeyRing keyRing;
@ -203,6 +206,24 @@ public class TradeUtil {
offer.getCurrencyCode()); offer.getCurrencyCode());
} }
/**
* Returns a string describing a trader's role for a given bsq swap.
* @param trade BsqSwapTrade
* @return String describing a trader's role for a given bsq swap
*/
public String getRole(BsqSwapTrade trade) {
Offer offer = trade.getOffer();
if (offer == null)
throw new IllegalStateException(
format("could not get role because no offer was found for bsq swap '%s'",
trade.getShortId()));
KeyRing keyRing = trade.getBsqSwapProtocolModel().getKeyRing();
return getRole(offer.isBuyOffer(),
offer.isMyOffer(keyRing),
offer.getCurrencyCode());
}
/** /**
* Returns a string describing a trader's role. * Returns a string describing a trader's role.
* *

View file

@ -69,6 +69,7 @@ public class BsqSwapProtocolModel implements ProtocolModel<BsqSwapTradePeer> {
transient private Offer offer; transient private Offer offer;
@Setter @Setter
transient private TradeMessage tradeMessage; transient private TradeMessage tradeMessage;
// TODO rename tradingPeerNodeAddress ?
@Nullable @Nullable
@Setter @Setter
transient private NodeAddress tempTradingPeerNodeAddress; transient private NodeAddress tempTradingPeerNodeAddress;

View file

@ -7,38 +7,34 @@ createoffer - create offer to buy or sell BTC
SYNOPSIS SYNOPSIS
-------- --------
createoffer createoffer
--payment-account=<payment-acct-id>
--direction=<buy|sell>
--currency-code=<eur|usd>
--market-price-margin=<percent> | --fixed-price=<btc-price>
--amount=<btc-amount> --amount=<btc-amount>
--min-amount=<btc-amount> --min-amount=<btc-amount>
--currency-code=<bsq|eur|usd|...>
--direction=<buy|sell>
--fixed-price=<btc-price> | --market-price-margin=<percent>
--payment-account=<payment-acct-id>
--security-deposit=<percent> --security-deposit=<percent>
--swap=<true|false>
[--fee-currency=<bsq|btc>] [--fee-currency=<bsq|btc>]
DESCRIPTION DESCRIPTION
----------- -----------
Create and place an offer to buy or sell BTC using a fiat account. Create and place an offer to buy or sell BTC. There are two types of offers.
BSQ swap offers
The createoffer command requires the swap, amount [, optional min-amount], direction,
and fixed-price parameters, where --swap=true and the user's wallet contains sufficient
BTC and/or BSQ to cover the trade amount and maker fee.
Version 1 protocol fiat and BSQ offers
The createoffer command requires the payment-account, amount [,optional min-amount],
currency-code, direction, fixed-price or market-price-margin, and security-deposit parameters.
The fee-currency parameter can be optionally used to pay the taker fee in BSQ.
OPTIONS OPTIONS
------- -------
--payment-account
The ID of the fiat payment account used to send or receive funds during the trade.
--direction
The direction of the trade (BUY or SELL).
--currency-code
The three letter code for the fiat used to buy or sell BTC, e.g., EUR, USD, BRL, ...
--market-price-margin
The % above or below market BTC price, e.g., 1.00 (1%).
If --market-price-margin is not present, --fixed-price must be.
--fixed-price
The fixed BTC price in fiat used to buy or sell BTC, e.g., 34000 (USD).
If --fixed-price is not present, --market-price-margin must be.
--amount --amount
The amount of BTC to buy or sell, e.g., 0.125. The amount of BTC to buy or sell, e.g., 0.125.
@ -46,15 +42,41 @@ OPTIONS
The minimum amount of BTC to buy or sell, e.g., 0.006. The minimum amount of BTC to buy or sell, e.g., 0.006.
If --min-amount is not present, it defaults to the --amount value. If --min-amount is not present, it defaults to the --amount value.
--security-deposit --currency-code
The percentage of the BTC amount being traded for the security deposit, e.g., 60.0 (60%). The three-letter code for the currency used to buy or sell BTC, e.g., BSQ, EUR, USD, BRL, ...
--direction
The direction of the trade (BUY or SELL).
--fee-currency --fee-currency
The wallet currency used to pay the Bisq trade maker fee (BSQ|BTC). Default is BTC The wallet currency used to pay the Bisq trade maker fee (BSQ|BTC). Default is BTC
--fixed-price
The fixed BTC price in fiat used to buy or sell BTC, e.g., 34000 (USD).
If --fixed-price is not present, --market-price-margin must be.
--market-price-margin
The % above or below market BTC price, e.g., 1.00 (1%).
If --market-price-margin is not present, --fixed-price must be.
--payment-account
The ID of the fiat payment account used to send or receive funds during the trade.
--security-deposit
The percentage of the BTC amount being traded for the security deposit, e.g., 60.0 (60%).
--swap
Flag determining whether the offer is a BSQ swap or version 1 protocol offer. Default is false.
EXAMPLES EXAMPLES
-------- --------
To create a BUY 0.25 BTC with BSQ swap offer at a fixed BSQ price of 0.00005 BSQ per 1 BTC:
$ ./bisq-cli --password=xyz --port=9998 createoffer --swap=true \
--direction=buy \
--amount=0.25 \
--fixed-price=0.00005
To create a BUY 0.125 BTC with EUR offer To create a BUY 0.125 BTC with EUR offer
at the current market price, at the current market price,
using a payment account with ID 7413d263-225a-4f1b-837a-1e3094dc0d77, using a payment account with ID 7413d263-225a-4f1b-837a-1e3094dc0d77,

View file

@ -9,11 +9,22 @@ SYNOPSIS
takeoffer takeoffer
--offer-id=<offer-id> --offer-id=<offer-id>
--payment-account=<payment-acct-id> --payment-account=<payment-acct-id>
--fee-currency=<eur|usd> [--fee-currency=<btc|bsq>]
DESCRIPTION DESCRIPTION
----------- -----------
Take an existing offer using a matching payment method. The Bisq trade fee can be paid in BSQ or BTC. Take an existing offer. There are currently two types offers and trade protocols.
BSQ swap offers
The takeoffer command only requires an offer-id parameter, and sufficient BSQ and BTC
to cover the trade amount and the taker fee. The trade (swap) will be executed immediately
after being successfully taken.
Version 1 protocol fiat and BSQ offers
The offer-id and payment-account parameters are required. The fee-currency parameter can
be optionally used to pay the taker fee in BSQ.
OPTIONS OPTIONS
------- -------
@ -29,6 +40,9 @@ OPTIONS
EXAMPLES EXAMPLES
-------- --------
To take a BSQ swap offer with ID y3a8b2e2-51b6-4f39-b6c1-3ebd52c22aea;
$ ./bisq-cli --password=xyz --port=9998 takeoffer --offer-id=y3a8b2e2-51b6-4f39-b6c1-3ebd52c22aea
To take an offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea To take an offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea
using a payment account with ID fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e, using a payment account with ID fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e,
and paying the Bisq trading fee in BSQ: and paying the Bisq trading fee in BSQ:

View file

@ -18,7 +18,6 @@
package bisq.daemon.grpc; package bisq.daemon.grpc;
import bisq.core.api.CoreApi; import bisq.core.api.CoreApi;
import bisq.core.api.model.BsqSwapOfferInfo;
import bisq.core.api.model.OfferInfo; import bisq.core.api.model.OfferInfo;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOffer;
@ -33,12 +32,15 @@ import bisq.proto.grpc.EditOfferReply;
import bisq.proto.grpc.EditOfferRequest; import bisq.proto.grpc.EditOfferRequest;
import bisq.proto.grpc.GetBsqSwapOfferReply; import bisq.proto.grpc.GetBsqSwapOfferReply;
import bisq.proto.grpc.GetBsqSwapOffersReply; import bisq.proto.grpc.GetBsqSwapOffersReply;
import bisq.proto.grpc.GetBsqSwapOffersRequest;
import bisq.proto.grpc.GetMyBsqSwapOfferReply; import bisq.proto.grpc.GetMyBsqSwapOfferReply;
import bisq.proto.grpc.GetMyBsqSwapOffersReply; import bisq.proto.grpc.GetMyBsqSwapOffersReply;
import bisq.proto.grpc.GetMyOfferReply; import bisq.proto.grpc.GetMyOfferReply;
import bisq.proto.grpc.GetMyOfferRequest; import bisq.proto.grpc.GetMyOfferRequest;
import bisq.proto.grpc.GetMyOffersReply; import bisq.proto.grpc.GetMyOffersReply;
import bisq.proto.grpc.GetMyOffersRequest; import bisq.proto.grpc.GetMyOffersRequest;
import bisq.proto.grpc.GetOfferCategoryReply;
import bisq.proto.grpc.GetOfferCategoryRequest;
import bisq.proto.grpc.GetOfferReply; import bisq.proto.grpc.GetOfferReply;
import bisq.proto.grpc.GetOfferRequest; import bisq.proto.grpc.GetOfferRequest;
import bisq.proto.grpc.GetOffersReply; import bisq.proto.grpc.GetOffersReply;
@ -56,10 +58,15 @@ import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static bisq.core.api.model.BsqSwapOfferInfo.toBsqSwapOfferInfo; import static bisq.core.api.model.OfferInfo.toMyPendingOfferInfo;
import static bisq.core.api.model.OfferInfo.toOfferInfo; import static bisq.core.api.model.OfferInfo.toOfferInfo;
import static bisq.core.api.model.OfferInfo.toPendingOfferInfo;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor; import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory;
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory.ALTCOIN;
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory.BSQ_SWAP;
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory.FIAT;
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory.UNKNOWN;
import static bisq.proto.grpc.GetOfferCategoryReply.newBuilder;
import static bisq.proto.grpc.OffersGrpc.*; import static bisq.proto.grpc.OffersGrpc.*;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
@ -81,13 +88,28 @@ class GrpcOffersService extends OffersImplBase {
this.exceptionHandler = exceptionHandler; this.exceptionHandler = exceptionHandler;
} }
@Override
public void getOfferCategory(GetOfferCategoryRequest req,
StreamObserver<GetOfferCategoryReply> responseObserver) {
try {
OfferCategory category = getOfferCategory(req.getId(), req.getIsMyOffer());
var reply = newBuilder()
.setOfferCategory(category)
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override @Override
public void getBsqSwapOffer(GetOfferRequest req, public void getBsqSwapOffer(GetOfferRequest req,
StreamObserver<GetBsqSwapOfferReply> responseObserver) { StreamObserver<GetBsqSwapOfferReply> responseObserver) {
try { try {
Offer offer = coreApi.getOffer(req.getId()); Offer offer = coreApi.getOffer(req.getId());
var reply = GetBsqSwapOfferReply.newBuilder() var reply = GetBsqSwapOfferReply.newBuilder()
.setBsqSwapOffer(toBsqSwapOfferInfo(offer).toProtoMessage()) .setBsqSwapOffer(toOfferInfo(offer).toProtoMessage())
.build(); .build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
@ -117,7 +139,7 @@ class GrpcOffersService extends OffersImplBase {
try { try {
Offer offer = coreApi.getMyBsqSwapOffer(req.getId()); Offer offer = coreApi.getMyBsqSwapOffer(req.getId());
var reply = GetMyBsqSwapOfferReply.newBuilder() var reply = GetMyBsqSwapOfferReply.newBuilder()
.setBsqSwapOffer(toBsqSwapOfferInfo(offer /* TODO support triggerPrice */).toProtoMessage()) .setBsqSwapOffer(toOfferInfo(offer /* TODO support triggerPrice */).toProtoMessage())
.build(); .build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
@ -132,7 +154,7 @@ class GrpcOffersService extends OffersImplBase {
try { try {
OpenOffer openOffer = coreApi.getMyOffer(req.getId()); OpenOffer openOffer = coreApi.getMyOffer(req.getId());
var reply = GetMyOfferReply.newBuilder() var reply = GetMyOfferReply.newBuilder()
.setOffer(toOfferInfo(openOffer).toProtoMessage()) .setOffer(OfferInfo.toMyOfferInfo(openOffer).toProtoMessage())
.build(); .build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
@ -142,15 +164,15 @@ class GrpcOffersService extends OffersImplBase {
} }
@Override @Override
public void getBsqSwapOffers(GetOffersRequest req, public void getBsqSwapOffers(GetBsqSwapOffersRequest req,
StreamObserver<GetBsqSwapOffersReply> responseObserver) { StreamObserver<GetBsqSwapOffersReply> responseObserver) {
try { try {
List<BsqSwapOfferInfo> result = coreApi.getBsqSwapOffers(req.getDirection()) List<OfferInfo> result = coreApi.getBsqSwapOffers(req.getDirection())
.stream().map(BsqSwapOfferInfo::toBsqSwapOfferInfo) .stream().map(OfferInfo::toOfferInfo)
.collect(Collectors.toList()); .collect(Collectors.toList());
var reply = GetBsqSwapOffersReply.newBuilder() var reply = GetBsqSwapOffersReply.newBuilder()
.addAllBsqSwapOffers(result.stream() .addAllBsqSwapOffers(result.stream()
.map(BsqSwapOfferInfo::toProtoMessage) .map(OfferInfo::toProtoMessage)
.collect(Collectors.toList())) .collect(Collectors.toList()))
.build(); .build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
@ -180,15 +202,15 @@ class GrpcOffersService extends OffersImplBase {
} }
@Override @Override
public void getMyBsqSwapOffers(GetMyOffersRequest req, public void getMyBsqSwapOffers(GetBsqSwapOffersRequest req,
StreamObserver<GetMyBsqSwapOffersReply> responseObserver) { StreamObserver<GetMyBsqSwapOffersReply> responseObserver) {
try { try {
List<BsqSwapOfferInfo> result = coreApi.getMyBsqSwapOffers(req.getDirection()) List<OfferInfo> result = coreApi.getMyBsqSwapOffers(req.getDirection())
.stream().map(BsqSwapOfferInfo::toBsqSwapOfferInfo) .stream().map(OfferInfo::toOfferInfo)
.collect(Collectors.toList()); .collect(Collectors.toList());
var reply = GetMyBsqSwapOffersReply.newBuilder() var reply = GetMyBsqSwapOffersReply.newBuilder()
.addAllBsqSwapOffers(result.stream() .addAllBsqSwapOffers(result.stream()
.map(BsqSwapOfferInfo::toProtoMessage) .map(OfferInfo::toProtoMessage)
.collect(Collectors.toList())) .collect(Collectors.toList()))
.build(); .build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
@ -204,7 +226,7 @@ class GrpcOffersService extends OffersImplBase {
try { try {
List<OfferInfo> result = coreApi.getMyOffers(req.getDirection(), req.getCurrencyCode()) List<OfferInfo> result = coreApi.getMyOffers(req.getDirection(), req.getCurrencyCode())
.stream() .stream()
.map(OfferInfo::toOfferInfo) .map(OfferInfo::toMyOfferInfo)
.collect(Collectors.toList()); .collect(Collectors.toList());
var reply = GetMyOffersReply.newBuilder() var reply = GetMyOffersReply.newBuilder()
.addAllOffers(result.stream() .addAllOffers(result.stream()
@ -222,17 +244,15 @@ class GrpcOffersService extends OffersImplBase {
public void createBsqSwapOffer(CreateBsqSwapOfferRequest req, public void createBsqSwapOffer(CreateBsqSwapOfferRequest req,
StreamObserver<CreateBsqSwapOfferReply> responseObserver) { StreamObserver<CreateBsqSwapOfferReply> responseObserver) {
try { try {
//todo PaymentAccount for bsq swap not needed as its just a dummy account
coreApi.createAndPlaceBsqSwapOffer( coreApi.createAndPlaceBsqSwapOffer(
req.getDirection(), req.getDirection(),
req.getAmount(), req.getAmount(),
req.getMinAmount(), req.getMinAmount(),
req.getPrice(), req.getPrice(),
/* req.getPaymentAccountId(),*/
offer -> { offer -> {
BsqSwapOfferInfo bsqSwapOfferInfo = toBsqSwapOfferInfo(offer); OfferInfo offerInfo = toMyPendingOfferInfo(offer);
CreateBsqSwapOfferReply reply = CreateBsqSwapOfferReply.newBuilder() CreateBsqSwapOfferReply reply = CreateBsqSwapOfferReply.newBuilder()
.setBsqSwapOffer(bsqSwapOfferInfo.toProtoMessage()) .setBsqSwapOffer(offerInfo.toProtoMessage())
.build(); .build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
@ -261,7 +281,7 @@ class GrpcOffersService extends OffersImplBase {
offer -> { offer -> {
// This result handling consumer's accept operation will return // This result handling consumer's accept operation will return
// the new offer to the gRPC client after async placement is done. // the new offer to the gRPC client after async placement is done.
OfferInfo offerInfo = toPendingOfferInfo(offer); OfferInfo offerInfo = toMyPendingOfferInfo(offer);
CreateOfferReply reply = CreateOfferReply.newBuilder() CreateOfferReply reply = CreateOfferReply.newBuilder()
.setOffer(offerInfo.toProtoMessage()) .setOffer(offerInfo.toProtoMessage())
.build(); .build();
@ -315,6 +335,7 @@ class GrpcOffersService extends OffersImplBase {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass()) return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf( .or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{ new HashMap<>() {{
put(getGetOfferCategoryMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getGetOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getGetOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getGetMyOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getGetMyOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getGetOffersMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getGetOffersMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
@ -325,4 +346,15 @@ class GrpcOffersService extends OffersImplBase {
}} }}
))); )));
} }
private OfferCategory getOfferCategory(String offerId, boolean isMyOffer) {
if (coreApi.isAltcoinOffer(offerId, isMyOffer))
return ALTCOIN;
else if (coreApi.isFiatOffer(offerId, isMyOffer))
return FIAT;
else if (coreApi.isBsqSwapOffer(offerId, isMyOffer))
return BSQ_SWAP;
else
return UNKNOWN;
}
} }

View file

@ -18,21 +18,19 @@
package bisq.daemon.grpc; package bisq.daemon.grpc;
import bisq.core.api.CoreApi; import bisq.core.api.CoreApi;
import bisq.core.api.model.BsqSwapTradeInfo;
import bisq.core.api.model.TradeInfo; import bisq.core.api.model.TradeInfo;
import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.proto.grpc.ConfirmPaymentReceivedReply; import bisq.proto.grpc.ConfirmPaymentReceivedReply;
import bisq.proto.grpc.ConfirmPaymentReceivedRequest; import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
import bisq.proto.grpc.ConfirmPaymentStartedReply; import bisq.proto.grpc.ConfirmPaymentStartedReply;
import bisq.proto.grpc.ConfirmPaymentStartedRequest; import bisq.proto.grpc.ConfirmPaymentStartedRequest;
import bisq.proto.grpc.GetBsqSwapTradeReply;
import bisq.proto.grpc.GetTradeReply; import bisq.proto.grpc.GetTradeReply;
import bisq.proto.grpc.GetTradeRequest; import bisq.proto.grpc.GetTradeRequest;
import bisq.proto.grpc.KeepFundsReply; import bisq.proto.grpc.KeepFundsReply;
import bisq.proto.grpc.KeepFundsRequest; import bisq.proto.grpc.KeepFundsRequest;
import bisq.proto.grpc.TakeBsqSwapOfferReply;
import bisq.proto.grpc.TakeBsqSwapOfferRequest;
import bisq.proto.grpc.TakeOfferReply; import bisq.proto.grpc.TakeOfferReply;
import bisq.proto.grpc.TakeOfferRequest; import bisq.proto.grpc.TakeOfferRequest;
import bisq.proto.grpc.WithdrawFundsReply; import bisq.proto.grpc.WithdrawFundsReply;
@ -48,7 +46,6 @@ import java.util.Optional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static bisq.core.api.model.BsqSwapTradeInfo.toBsqSwapTradeInfo;
import static bisq.core.api.model.TradeInfo.toNewTradeInfo; import static bisq.core.api.model.TradeInfo.toNewTradeInfo;
import static bisq.core.api.model.TradeInfo.toTradeInfo; import static bisq.core.api.model.TradeInfo.toTradeInfo;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor; import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
@ -74,60 +71,49 @@ class GrpcTradesService extends TradesImplBase {
} }
@Override @Override
public void getBsqSwapTrade(GetTradeRequest req, public void takeOffer(TakeOfferRequest req,
StreamObserver<GetBsqSwapTradeReply> responseObserver) { StreamObserver<TakeOfferReply> responseObserver) {
try {
var bsqSwapTrade = coreApi.getBsqSwapTrade(req.getTradeId());
// String role = coreApi.getBsqSwapTradeRole(req.getTradeId());
var reply = GetBsqSwapTradeReply.newBuilder()
.setBsqSwapTrade(toBsqSwapTradeInfo(bsqSwapTrade).toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (IllegalArgumentException cause) {
// Offer makers may call 'gettrade' many times before a trade exists.
// Log a 'trade not found' warning instead of a full stack trace.
exceptionHandler.handleExceptionAsWarning(log, "getBsqSwapTrade", cause, responseObserver);
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void takeBsqSwapOffer(TakeBsqSwapOfferRequest req,
StreamObserver<TakeBsqSwapOfferReply> responseObserver) {
GrpcErrorMessageHandler errorMessageHandler = GrpcErrorMessageHandler errorMessageHandler =
new GrpcErrorMessageHandler(getTakeOfferMethod().getFullMethodName(), new GrpcErrorMessageHandler(getTakeOfferMethod().getFullMethodName(),
responseObserver, responseObserver,
exceptionHandler, exceptionHandler,
log); log);
coreApi.takeBsqSwapOffer(req.getOfferId(),
req.getPaymentAccountId(), if (coreApi.isBsqSwapOffer(req.getOfferId(), false)) {
req.getTakerFeeCurrencyCode(), coreApi.takeBsqSwapOffer(req.getOfferId(),
bsqSwapTrade -> { bsqSwapTrade -> {
BsqSwapTradeInfo bsqSwapTradeInfo = toBsqSwapTradeInfo(bsqSwapTrade); var reply = buildTakeOfferReply(bsqSwapTrade);
var reply = TakeBsqSwapOfferReply.newBuilder() responseObserver.onNext(reply);
.setBsqSwapTrade(bsqSwapTradeInfo.toProtoMessage()) responseObserver.onCompleted();
.build(); },
responseObserver.onNext(reply); errorMessage -> {
responseObserver.onCompleted(); if (!errorMessageHandler.isErrorHandled())
}, errorMessageHandler.handleErrorMessage(errorMessage);
errorMessage -> { });
if (!errorMessageHandler.isErrorHandled()) } else {
errorMessageHandler.handleErrorMessage(errorMessage); coreApi.takeOffer(req.getOfferId(),
}); req.getPaymentAccountId(),
req.getTakerFeeCurrencyCode(),
trade -> {
var reply = buildTakeOfferReply(trade);
responseObserver.onNext(reply);
responseObserver.onCompleted();
},
errorMessage -> {
if (!errorMessageHandler.isErrorHandled())
errorMessageHandler.handleErrorMessage(errorMessage);
});
}
} }
@Override @Override
public void getTrade(GetTradeRequest req, public void getTrade(GetTradeRequest req,
StreamObserver<GetTradeReply> responseObserver) { StreamObserver<GetTradeReply> responseObserver) {
try { try {
Trade trade = coreApi.getTrade(req.getTradeId()); var tradeModel = coreApi.getTradeModel(req.getTradeId());
boolean isMyOffer = coreApi.isMyOffer(trade.getOffer().getId()); var reply = tradeModel.getOffer().isBsqSwapOffer()
String role = coreApi.getTradeRole(req.getTradeId()); ? buildGetTradeReply((BsqSwapTrade) tradeModel)
var reply = GetTradeReply.newBuilder() : buildGetTradeReply((Trade) tradeModel);
.setTrade(toTradeInfo(trade, role, isMyOffer).toProtoMessage())
.build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
} catch (IllegalArgumentException cause) { } catch (IllegalArgumentException cause) {
@ -139,31 +125,6 @@ class GrpcTradesService extends TradesImplBase {
} }
} }
@Override
public void takeOffer(TakeOfferRequest req,
StreamObserver<TakeOfferReply> responseObserver) {
GrpcErrorMessageHandler errorMessageHandler =
new GrpcErrorMessageHandler(getTakeOfferMethod().getFullMethodName(),
responseObserver,
exceptionHandler,
log);
coreApi.takeOffer(req.getOfferId(),
req.getPaymentAccountId(),
req.getTakerFeeCurrencyCode(),
trade -> {
TradeInfo tradeInfo = toNewTradeInfo(trade);
var reply = TakeOfferReply.newBuilder()
.setTrade(tradeInfo.toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
},
errorMessage -> {
if (!errorMessageHandler.isErrorHandled())
errorMessageHandler.handleErrorMessage(errorMessage);
});
}
@Override @Override
public void confirmPaymentStarted(ConfirmPaymentStartedRequest req, public void confirmPaymentStarted(ConfirmPaymentStartedRequest req,
StreamObserver<ConfirmPaymentStartedReply> responseObserver) { StreamObserver<ConfirmPaymentStartedReply> responseObserver) {
@ -228,6 +189,7 @@ class GrpcTradesService extends TradesImplBase {
new HashMap<>() {{ new HashMap<>() {{
put(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES)); put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
// put(getTakeBsqSwapOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
put(getConfirmPaymentStartedMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES)); put(getConfirmPaymentStartedMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES)); put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
put(getKeepFundsMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES)); put(getKeepFundsMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
@ -235,4 +197,49 @@ class GrpcTradesService extends TradesImplBase {
}} }}
))); )));
} }
private TakeOfferReply buildTakeOfferReply(TradeModel tradeModel) {
TradeInfo tradeInfo;
if (tradeModel.getOffer().isBsqSwapOffer()) {
BsqSwapTrade bsqSwapTrade = (BsqSwapTrade) tradeModel;
String role = getMyRole(bsqSwapTrade);
tradeInfo = toNewTradeInfo(bsqSwapTrade, role);
} else {
tradeInfo = toNewTradeInfo((Trade) tradeModel);
}
return TakeOfferReply.newBuilder()
.setTrade(tradeInfo.toProtoMessage())
.build();
}
private GetTradeReply buildGetTradeReply(BsqSwapTrade bsqSwapTrade) {
boolean wasMyOffer = wasMyOffer(bsqSwapTrade);
String role = getMyRole(bsqSwapTrade);
var numConfirmations = coreApi.getTransactionConfirmations(bsqSwapTrade.getTxId());
var tradeInfo = toTradeInfo(bsqSwapTrade,
role,
wasMyOffer,
numConfirmations);
return GetTradeReply.newBuilder()
.setTrade(tradeInfo.toProtoMessage())
.build();
}
private GetTradeReply buildGetTradeReply(Trade trade) {
boolean wasMyOffer = wasMyOffer(trade);
String role = getMyRole(trade);
return GetTradeReply.newBuilder()
.setTrade(toTradeInfo(trade, role, wasMyOffer).toProtoMessage())
.build();
}
private boolean wasMyOffer(TradeModel tradeModel) {
return coreApi.isMyOffer(tradeModel.getOffer().getId());
}
private String getMyRole(TradeModel tradeModel) {
return tradeModel.getOffer().isBsqSwapOffer()
? coreApi.getBsqSwapTradeRole((BsqSwapTrade) tradeModel)
: coreApi.getTradeRole(tradeModel.getId());
}
} }

View file

@ -62,6 +62,8 @@ message GetMethodHelpReply {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
service Offers { service Offers {
rpc GetOfferCategory (GetOfferCategoryRequest) returns (GetOfferCategoryReply) {
}
rpc GetBsqSwapOffer (GetOfferRequest) returns (GetBsqSwapOfferReply) { rpc GetBsqSwapOffer (GetOfferRequest) returns (GetBsqSwapOfferReply) {
} }
rpc GetOffer (GetOfferRequest) returns (GetOfferReply) { rpc GetOffer (GetOfferRequest) returns (GetOfferReply) {
@ -70,11 +72,11 @@ service Offers {
} }
rpc GetMyOffer (GetMyOfferRequest) returns (GetMyOfferReply) { rpc GetMyOffer (GetMyOfferRequest) returns (GetMyOfferReply) {
} }
rpc GetBsqSwapOffers (GetOffersRequest) returns (GetBsqSwapOffersReply) { rpc GetBsqSwapOffers (GetBsqSwapOffersRequest) returns (GetBsqSwapOffersReply) {
} }
rpc GetOffers (GetOffersRequest) returns (GetOffersReply) { rpc GetOffers (GetOffersRequest) returns (GetOffersReply) {
} }
rpc GetMyBsqSwapOffers (GetMyOffersRequest) returns (GetMyBsqSwapOffersReply) { rpc GetMyBsqSwapOffers (GetBsqSwapOffersRequest) returns (GetMyBsqSwapOffersReply) {
} }
rpc GetMyOffers (GetMyOffersRequest) returns (GetMyOffersReply) { rpc GetMyOffers (GetMyOffersRequest) returns (GetMyOffersReply) {
} }
@ -88,8 +90,23 @@ service Offers {
} }
} }
message GetOfferCategoryRequest {
string id = 1;
bool isMyOffer = 2;
}
message GetOfferCategoryReply {
enum OfferCategory {
UNKNOWN = 0;
FIAT = 1;
ALTCOIN = 2;
BSQ_SWAP = 3;
}
OfferCategory offerCategory = 1;
}
message GetBsqSwapOfferReply { message GetBsqSwapOfferReply {
BsqSwapOfferInfo bsqSwapOffer = 1; OfferInfo bsqSwapOffer = 1;
} }
message GetOfferRequest { message GetOfferRequest {
@ -101,7 +118,7 @@ message GetOfferReply {
} }
message GetMyBsqSwapOfferReply { message GetMyBsqSwapOfferReply {
BsqSwapOfferInfo bsqSwapOffer = 1; OfferInfo bsqSwapOffer = 1;
} }
message GetMyOfferRequest { message GetMyOfferRequest {
@ -121,8 +138,12 @@ message GetOffersReply {
repeated OfferInfo offers = 1; repeated OfferInfo offers = 1;
} }
message GetBsqSwapOffersRequest {
string direction = 1;
}
message GetBsqSwapOffersReply { message GetBsqSwapOffersReply {
repeated BsqSwapOfferInfo bsqSwapOffers = 1; repeated OfferInfo bsqSwapOffers = 1;
} }
message GetMyOffersRequest { message GetMyOffersRequest {
@ -135,7 +156,7 @@ message GetMyOffersReply {
} }
message GetMyBsqSwapOffersReply { message GetMyBsqSwapOffersReply {
repeated BsqSwapOfferInfo bsqSwapOffers = 1; repeated OfferInfo bsqSwapOffers = 1;
} }
message CreateBsqSwapOfferRequest { message CreateBsqSwapOfferRequest {
@ -143,11 +164,10 @@ message CreateBsqSwapOfferRequest {
uint64 amount = 2; uint64 amount = 2;
uint64 minAmount = 3; uint64 minAmount = 3;
string price = 4; string price = 4;
string paymentAccountId = 5;
} }
message CreateBsqSwapOfferReply { message CreateBsqSwapOfferReply {
BsqSwapOfferInfo bsqSwapOffer = 1; OfferInfo bsqSwapOffer = 1;
} }
message CreateOfferRequest { message CreateOfferRequest {
@ -204,25 +224,6 @@ message CancelOfferRequest {
message CancelOfferReply { message CancelOfferReply {
} }
message BsqSwapOfferInfo {
string id = 1;
string direction = 2;
uint64 amount = 3;
uint64 minAmount = 4;
uint64 price = 5;
string makerPaymentAccountId = 6;
string paymentMethodId = 7;
string paymentMethodShortName = 8;
string baseCurrencyCode = 9;
string counterCurrencyCode = 10;
uint64 getMakerFee = 11;
uint64 date = 12;
string ownerNodeAddress = 13;
string pubKeyRing = 14;
string versionNr = 15;
int32 protocolVersion = 16;
}
message OfferInfo { message OfferInfo {
string id = 1; string id = 1;
string direction = 2; string direction = 2;
@ -250,6 +251,11 @@ message OfferInfo {
bool isActivated = 24; bool isActivated = 24;
bool isMyOffer = 25; bool isMyOffer = 25;
bool isMyPendingOffer = 26; bool isMyPendingOffer = 26;
bool isBsqSwapOffer = 27;
string ownerNodeAddress = 28;
string pubKeyRing = 29;
string versionNr = 30;
int32 protocolVersion = 31;
} }
message AvailabilityResultWithDescription { message AvailabilityResultWithDescription {
@ -377,12 +383,8 @@ message StopReply {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
service Trades { service Trades {
rpc GetBsqSwapTrade (GetTradeRequest) returns (GetBsqSwapTradeReply) {
}
rpc GetTrade (GetTradeRequest) returns (GetTradeReply) { rpc GetTrade (GetTradeRequest) returns (GetTradeReply) {
} }
rpc TakeBsqSwapOffer (TakeBsqSwapOfferRequest) returns (TakeBsqSwapOfferReply) {
}
rpc TakeOffer (TakeOfferRequest) returns (TakeOfferReply) { rpc TakeOffer (TakeOfferRequest) returns (TakeOfferReply) {
} }
rpc ConfirmPaymentStarted (ConfirmPaymentStartedRequest) returns (ConfirmPaymentStartedReply) { rpc ConfirmPaymentStarted (ConfirmPaymentStartedRequest) returns (ConfirmPaymentStartedReply) {
@ -395,17 +397,6 @@ service Trades {
} }
} }
message TakeBsqSwapOfferRequest {
string offerId = 1;
string paymentAccountId = 2;
string takerFeeCurrencyCode = 3;
}
message TakeBsqSwapOfferReply {
BsqSwapTradeInfo bsqSwapTrade = 1;
AvailabilityResultWithDescription failureReason = 2;
}
message TakeOfferRequest { message TakeOfferRequest {
string offerId = 1; string offerId = 1;
string paymentAccountId = 2; string paymentAccountId = 2;
@ -431,10 +422,6 @@ message ConfirmPaymentReceivedRequest {
message ConfirmPaymentReceivedReply { message ConfirmPaymentReceivedReply {
} }
message GetBsqSwapTradeReply {
BsqSwapTradeInfo bsqSwapTrade = 1;
}
message GetTradeRequest { message GetTradeRequest {
string tradeId = 1; string tradeId = 1;
} }
@ -459,37 +446,8 @@ message WithdrawFundsRequest {
message WithdrawFundsReply { message WithdrawFundsReply {
} }
message BsqSwapTradeInfo {
BsqSwapOfferInfo bsqSwapOfferInfo = 1;
string tradeId = 2;
string tempTradingPeerNodeAddress = 3;
string peerNodeAddress = 4;
string txId = 5;
uint64 bsqTradeAmount = 6;
uint64 bsqMaxTradeAmount = 7;
uint64 bsqMinTradeAmount = 8;
uint64 btcTradeAmount = 9;
uint64 btcMaxTradeAmount = 10;
uint64 btcMinTradeAmount = 11;
uint64 tradePrice = 12;
bool isCurrencyForMakerFeeBtc = 13;
bool isCurrencyForTakerFeeBtc = 14;
uint64 bsqMakerTradeFee = 15;
uint64 btcMakerTradeFee = 16;
uint64 bsqTakerTradeFee = 17;
uint64 btcTakerTradeFee = 18;
uint64 txFeePerVbyte = 19;
uint64 txFee = 20;
string makerBsqAddress = 21;
string makerBtcAddress = 22;
string takerBsqAddress = 23;
string takerBtcAddress = 24;
uint64 takeOfferDate = 25;
string state = 26;
string errorMessage = 27;
}
message TradeInfo { message TradeInfo {
// Bisq v1 trade protocol fields.
OfferInfo offer = 1; OfferInfo offer = 1;
string tradeId = 2; string tradeId = 2;
string shortId = 3; string shortId = 3;
@ -516,6 +474,8 @@ message TradeInfo {
string contractAsJson = 24; string contractAsJson = 24;
ContractInfo contract = 25; ContractInfo contract = 25;
uint64 tradeVolume = 26; uint64 tradeVolume = 26;
// Optional Bisq v2+ trade protocol fields.
BsqSwapTradeInfo bsqSwapTradeInfo = 28;
} }
message ContractInfo { message ContractInfo {
@ -533,6 +493,22 @@ message ContractInfo {
uint64 lockTime = 12; uint64 lockTime = 12;
} }
message BsqSwapTradeInfo {
// BSQ Swap protocol specific fields not common to Bisq v1 trade protocol fields.
string txId = 1;
uint64 bsqTradeAmount = 2;
uint64 btcTradeAmount = 3;
uint64 bsqMakerTradeFee = 4;
uint64 bsqTakerTradeFee = 5;
uint64 txFeePerVbyte = 6;
string makerBsqAddress = 7;
string makerBtcAddress = 8;
string takerBsqAddress = 9;
string takerBtcAddress = 10;
uint64 numConfirmations = 11;
string errorMessage = 12;
}
message PaymentAccountPayloadInfo { message PaymentAccountPayloadInfo {
string id = 1; string id = 1;
string paymentMethodId = 2; string paymentMethodId = 2;