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
**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**
@ -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
```
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
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
```
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:
```
$ ./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 \
--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
the trade.
Depending on the offer type, the taken offer will be used to (1) create a trade contract, or (2) execute a BSQ swap.
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
@ -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">]
```
### 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
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
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:
$ ./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:
#
# - 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.
# Build command: `./gradlew clean build :apitest:installDaoSetup`

View file

@ -10,7 +10,7 @@
#
# 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.
# Build command: `./gradlew clean build :apitest:installDaoSetup`

View file

@ -179,6 +179,41 @@ parselimitorderopts() {
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() {
# There may be a '+' char in the path and we have to escape it for pgrep.
if [[ $APP_HOME == *"+"* ]]; then
@ -310,3 +345,9 @@ printscriptparams() {
echo " WAIT = $WAIT"
fi
}
printbsqswapscriptparams() {
echo " DIRECTION = $DIRECTION"
echo " FIXED_PRICE = $FIXED_PRICE"
echo " AMOUNT = $AMOUNT"
}

View file

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

View file

@ -1,13 +1,13 @@
#! /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
# trade, and the maker's face to face payment account's currency code is used when creating the offer.
#
# 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.
# Build command: `./gradlew clean build :apitest:installDaoSetup`
@ -30,11 +30,12 @@
#
# 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`
#
# 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:
#
# `$ 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
printbreak
registerdisputeagents
# Demonstrate how to create a country based, face to face account.
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.
*
* Requires bitcoind v0.19, v0.20, or v0.21.
* Requires bitcoind v0.19 - v22.
*/
@Slf4j
public class ApiTestMain {

View file

@ -108,7 +108,7 @@ abstract class AbstractLinuxProcess implements LinuxProcess {
File bitcoindExecutable = Paths.get(config.bitcoinPath, "bitcoind").toFile();
if (!bitcoindExecutable.exists() || !bitcoindExecutable.canExecute())
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'",
bitcoindExecutable.getAbsolutePath()));

View file

@ -17,7 +17,6 @@
package bisq.apitest.method.offer;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.OfferInfo;
import protobuf.PaymentAccount;
@ -42,10 +41,12 @@ import static bisq.apitest.config.BisqAppConfig.bobdaemon;
import static bisq.apitest.config.BisqAppConfig.seednode;
import static bisq.cli.table.builder.TableType.OFFER_TBL;
import static bisq.common.util.MathUtils.exactMultiply;
import static java.lang.System.out;
import bisq.apitest.method.MethodTest;
import bisq.cli.CliMain;
import bisq.cli.table.builder.TableBuilder;
@Slf4j
@ -112,11 +113,6 @@ public abstract class AbstractOfferTest extends MethodTest {
protected final Function<List<OfferInfo>, String> toOffersTable = (offers) ->
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() {
// 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'.
@ -140,4 +136,11 @@ public abstract class AbstractOfferTest extends MethodTest {
public static void tearDown() {
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;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.OfferInfo;
import lombok.extern.slf4j.Slf4j;
@ -112,8 +112,7 @@ public class BsqSwapOfferTest extends AbstractOfferTest {
var bsqSwapOffer = aliceClient.createBsqSwapOffer(BUY.name(),
1_000_000L,
1_000_000L,
"0.00005",
alicesBsqSwapAcct.getId());
"0.00005");
log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer);
var newOfferId = bsqSwapOffer.getId();
assertNotEquals("", newOfferId);
@ -121,7 +120,6 @@ public class BsqSwapOfferTest extends AbstractOfferTest {
assertEquals(5_000, bsqSwapOffer.getPrice());
assertEquals(1_000_000L, bsqSwapOffer.getAmount());
assertEquals(1_000_000L, bsqSwapOffer.getMinAmount());
// assertEquals(alicesBsqAcct.getId(), atomicOffer.getMakerPaymentAccountId());
assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode());
assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode());
@ -129,13 +127,13 @@ public class BsqSwapOfferTest extends AbstractOfferTest {
testGetBsqSwapOffer(bsqSwapOffer);
}
private void testGetMyBsqSwapOffer(BsqSwapOfferInfo bsqSwapOfferInfo) {
private void testGetMyBsqSwapOffer(OfferInfo bsqSwapOffer) {
int numFetchAttempts = 0;
while (true) {
try {
numFetchAttempts++;
var fetchedBsqSwapOffer = aliceClient.getMyBsqSwapOffer(bsqSwapOfferInfo.getId());
assertEquals(bsqSwapOfferInfo.getId(), fetchedBsqSwapOffer.getId());
var fetchedBsqSwapOffer = aliceClient.getMyOffer(bsqSwapOffer.getId());
assertEquals(bsqSwapOffer.getId(), fetchedBsqSwapOffer.getId());
log.debug("Alice found her (my) new bsq swap offer on attempt # {}.", numFetchAttempts);
break;
} 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;
while (true) {
try {
numFetchAttempts++;
var fetchedBsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOfferInfo.getId());
assertEquals(bsqSwapOfferInfo.getId(), fetchedBsqSwapOffer.getId());
var fetchedBsqSwapOffer = bobClient.getOffer(bsqSwapOffer.getId());
assertEquals(bsqSwapOffer.getId(), fetchedBsqSwapOffer.getId());
log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts);
break;
} catch (Exception ex) {

View file

@ -38,15 +38,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static bisq.apitest.config.ApiTestConfig.BSQ;
import static bisq.apitest.config.ApiTestConfig.BTC;
import static bisq.apitest.config.ApiTestConfig.EUR;
import static bisq.apitest.config.ApiTestConfig.USD;
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
import static bisq.proto.grpc.EditOfferRequest.EditType.*;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
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 org.junit.jupiter.api.Assertions.*;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
@ -70,22 +68,22 @@ public class EditOfferTest extends AbstractOfferTest {
paymentAcct.getId(),
0.0,
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.
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertTrue(originalOffer.getIsActivated());
// Disable offer
aliceClient.editOfferActivationState(originalOffer.getId(), DEACTIVATE_OFFER);
genBtcBlocksThenWait(1, 1500); // Wait for offer book removal.
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());
// Re-enable offer
aliceClient.editOfferActivationState(editedOffer.getId(), ACTIVATE_OFFER);
genBtcBlocksThenWait(1, 1500); // Wait for offer book re-entry.
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());
doSanityCheck(originalOffer, editedOffer);
@ -100,8 +98,8 @@ public class EditOfferTest extends AbstractOfferTest {
paymentAcct.getId(),
0.0,
NO_TRIGGER_PRICE);
log.debug("ORIGINAL EUR OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original EUR offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertEquals(0 /*no trigger price*/, originalOffer.getTriggerPrice());
@ -111,9 +109,9 @@ public class EditOfferTest extends AbstractOfferTest {
var newTriggerPriceAsLong = calcPriceAsLong.apply(mktPrice, delta);
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());
log.debug("EDITED EUR OFFER:\n{}", toOfferTable.apply(editedOffer));
log.debug("Edited EUR offer:\n{}", toOfferTable.apply(editedOffer));
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
assertTrue(editedOffer.getUseMarketBasedPrice());
@ -124,13 +122,13 @@ public class EditOfferTest extends AbstractOfferTest {
@Order(3)
public void testSetTriggerPriceToNegativeValueShouldThrowException() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("FI");
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
var originalOffer = createMktPricedOfferForEdit(SELL.name(),
EUR,
paymentAcct.getId(),
0.0,
NO_TRIGGER_PRICE);
log.debug("ORIGINAL EUR OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original EUR offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
// Edit the offer's trigger price, set to -1, check error.
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.editOfferTriggerPrice(originalOffer.getId(), -1L));
@ -145,19 +143,19 @@ public class EditOfferTest extends AbstractOfferTest {
public void testEditMktPriceMargin() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
var originalMktPriceMargin = new BigDecimal("0.1").doubleValue();
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
var originalOffer = createMktPricedOfferForEdit(SELL.name(),
USD,
paymentAcct.getId(),
originalMktPriceMargin,
NO_TRIGGER_PRICE);
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original USD offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
// Edit the offer's price margin, nothing else.
var newMktPriceMargin = new BigDecimal("0.5").doubleValue();
aliceClient.editOfferPriceMargin(originalOffer.getId(), newMktPriceMargin);
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());
doSanityCheck(originalOffer, editedOffer);
@ -169,19 +167,19 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU");
double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE);
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000);
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(),
var originalOffer = createFixedPricedOfferForEdit(BUY.name(),
RUBLE,
paymentAcct.getId(),
fixedPriceAsString);
log.debug("ORIGINAL RUB OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original RUB offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
// Edit the offer's fixed price, nothing else.
String editedFixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 100_000.0000);
aliceClient.editOfferFixedPrice(originalOffer.getId(), editedFixedPriceAsString);
// 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());
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));
assertEquals(expectedNewFixedPrice, editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice());
@ -195,12 +193,12 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU");
double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE);
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000);
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(),
var originalOffer = createFixedPricedOfferForEdit(BUY.name(),
RUBLE,
paymentAcct.getId(),
fixedPriceAsString);
log.debug("ORIGINAL RUB OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original RUB offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
// Edit the offer's fixed price and deactivate it.
String editedFixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 100_000.0000);
aliceClient.editOffer(originalOffer.getId(),
@ -211,9 +209,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER,
FIXED_PRICE_AND_ACTIVATION_STATE);
// 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());
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));
assertEquals(expectedNewFixedPrice, editedOffer.getPrice());
assertFalse(editedOffer.getIsActivated());
@ -232,8 +230,8 @@ public class EditOfferTest extends AbstractOfferTest {
paymentAcct.getId(),
originalMktPriceMargin,
0);
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original USD offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
@ -247,9 +245,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER,
MKT_PRICE_MARGIN_AND_ACTIVATION_STATE);
// 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());
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(0, editedOffer.getTriggerPrice());
assertFalse(editedOffer.getIsActivated());
@ -261,18 +259,16 @@ public class EditOfferTest extends AbstractOfferTest {
@Order(8)
public void testEditMktPriceMarginAndTriggerPriceAndDeactivation() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
var originalMktPriceMargin = new BigDecimal("0.0").doubleValue();
var mktPriceAsDouble = aliceClient.getBtcPrice(USD);
var originalTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, -5_000.0000);
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
USD,
paymentAcct.getId(),
originalMktPriceMargin,
originalTriggerPriceAsLong);
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original USD offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
assertEquals(originalTriggerPriceAsLong, originalOffer.getTriggerPrice());
@ -288,9 +284,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER,
MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE);
// 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());
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(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
assertFalse(editedOffer.getIsActivated());
@ -303,13 +299,13 @@ public class EditOfferTest extends AbstractOfferTest {
public void testEditingFixedPriceInMktPriceMarginBasedOfferShouldThrowException() {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
var originalMktPriceMargin = new BigDecimal("0.0").doubleValue();
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
var originalOffer = createMktPricedOfferForEdit(SELL.name(),
USD,
paymentAcct.getId(),
originalMktPriceMargin,
NO_TRIGGER_PRICE);
log.debug("ORIGINAL USD OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original USD offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
// Try to edit both the fixed price and mkt price margin.
var newMktPriceMargin = new BigDecimal("0.25").doubleValue();
var newFixedPrice = "50000.0000";
@ -335,12 +331,12 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU");
double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE);
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000);
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(),
var originalOffer = createFixedPricedOfferForEdit(BUY.name(),
RUBLE,
paymentAcct.getId(),
fixedPriceAsString);
log.debug("ORIGINAL RUB OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original RUB offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
long newTriggerPrice = 1000000L;
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.editOfferTriggerPrice(originalOffer.getId(), newTriggerPrice));
@ -358,12 +354,12 @@ public class EditOfferTest extends AbstractOfferTest {
PaymentAccount paymentAcct = getOrCreatePaymentAccount("MX");
double mktPriceAsDouble = aliceClient.getBtcPrice("MXN");
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 0.00);
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(),
var originalOffer = createFixedPricedOfferForEdit(BUY.name(),
"MXN",
paymentAcct.getId(),
fixedPriceAsString);
log.debug("ORIGINAL MXN OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original MXN offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
// Change the offer to mkt price based and set a trigger price.
var newMktPriceMargin = new BigDecimal("0.05").doubleValue();
@ -377,9 +373,9 @@ public class EditOfferTest extends AbstractOfferTest {
ACTIVATE_OFFER,
MKT_PRICE_MARGIN_AND_TRIGGER_PRICE);
// 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());
log.debug("EDITED MXN OFFER:\n{}", toOfferTable.apply(editedOffer));
log.debug("Edited MXN offer:\n{}", toOfferTable.apply(editedOffer));
assertTrue(editedOffer.getUseMarketBasedPrice());
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
@ -396,13 +392,13 @@ public class EditOfferTest extends AbstractOfferTest {
var originalMktPriceMargin = new BigDecimal("0.25").doubleValue();
var delta = 1_000.0000; // trigger price on sell offer is 1K below mkt price
var originalTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, delta);
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
var originalOffer = createMktPricedOfferForEdit(SELL.name(),
"GBP",
paymentAcct.getId(),
originalMktPriceMargin,
originalTriggerPriceAsLong);
log.debug("ORIGINAL GBP OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original GBP offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 0.00);
aliceClient.editOffer(originalOffer.getId(),
@ -413,9 +409,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER,
FIXED_PRICE_AND_ACTIVATION_STATE);
// 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());
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());
assertFalse(editedOffer.getUseMarketBasedPrice());
assertEquals(0.00, editedOffer.getMarketPriceMargin());
@ -426,7 +422,7 @@ public class EditOfferTest extends AbstractOfferTest {
@Test
@Order(13)
public void testChangeFixedPricedBsqOfferToPriceMarginBasedOfferShouldThrowException() {
OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
var originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ,
100_000_000L,
100_000_000L,
@ -434,8 +430,8 @@ public class EditOfferTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(),
alicesLegacyBsqAcct.getId(),
BSQ);
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original BSQ offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.editOffer(originalOffer.getId(),
"0.00",
@ -453,7 +449,7 @@ public class EditOfferTest extends AbstractOfferTest {
@Test
@Order(14)
public void testEditTriggerPriceOnFixedPriceBsqOfferShouldThrowException() {
OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
var originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
BSQ,
100_000_000L,
100_000_000L,
@ -461,8 +457,8 @@ public class EditOfferTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(),
alicesLegacyBsqAcct.getId(),
BSQ);
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original BSQ offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
var newTriggerPriceAsLong = calcPriceAsLong.apply(0.00005, 0.00);
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
aliceClient.editOffer(originalOffer.getId(),
@ -482,7 +478,7 @@ public class EditOfferTest extends AbstractOfferTest {
@Order(15)
public void testEditFixedPriceOnBsqOffer() {
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,
100_000_000L,
100_000_000L,
@ -490,8 +486,8 @@ public class EditOfferTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(),
alicesLegacyBsqAcct.getId(),
BSQ);
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original BSQ offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
String newFixedPriceAsString = "0.00003111";
aliceClient.editOffer(originalOffer.getId(),
newFixedPriceAsString,
@ -501,9 +497,9 @@ public class EditOfferTest extends AbstractOfferTest {
ACTIVATE_OFFER,
FIXED_PRICE_ONLY);
// 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());
log.debug("EDITED BSQ OFFER:\n{}", toOfferTable.apply(editedOffer));
log.debug("Edited BSQ offer:\n{}", toOfferTable.apply(editedOffer));
assertEquals(scaledUpAltcoinOfferPrice.apply(newFixedPriceAsString), editedOffer.getPrice());
assertTrue(editedOffer.getIsActivated());
assertFalse(editedOffer.getUseMarketBasedPrice());
@ -515,7 +511,7 @@ public class EditOfferTest extends AbstractOfferTest {
@Order(16)
public void testDisableBsqOffer() {
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,
100_000_000L,
100_000_000L,
@ -523,8 +519,8 @@ public class EditOfferTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(),
alicesLegacyBsqAcct.getId(),
BSQ);
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original BSQ offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
aliceClient.editOffer(originalOffer.getId(),
fixedPriceAsString,
false,
@ -533,9 +529,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER,
ACTIVATION_STATE_ONLY);
// Wait for edited offer to be removed from offer-book.
genBtcBlocksThenWait(1, 2500);
genBtcBlocksThenWait(1, 2_500);
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());
assertEquals(scaledUpAltcoinOfferPrice.apply(fixedPriceAsString), editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice());
@ -547,7 +543,7 @@ public class EditOfferTest extends AbstractOfferTest {
@Order(17)
public void testEditFixedPriceAndDisableBsqOffer() {
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,
100_000_000L,
100_000_000L,
@ -555,8 +551,8 @@ public class EditOfferTest extends AbstractOfferTest {
getDefaultBuyerSecurityDepositAsPercent(),
alicesLegacyBsqAcct.getId(),
BSQ);
log.debug("ORIGINAL BSQ OFFER:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
log.debug("Original BSQ offer:\n{}", toOfferTable.apply(originalOffer));
genBtcBlocksThenWait(1, 2_500); // Wait for offer book entry.
String newFixedPriceAsString = "0.000045";
aliceClient.editOffer(originalOffer.getId(),
newFixedPriceAsString,
@ -566,9 +562,9 @@ public class EditOfferTest extends AbstractOfferTest {
DEACTIVATE_OFFER,
FIXED_PRICE_AND_ACTIVATION_STATE);
// 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());
log.debug("EDITED BSQ OFFER:\n{}", toOfferTable.apply(editedOffer));
log.debug("Edited BSQ offer:\n{}", toOfferTable.apply(editedOffer));
assertFalse(editedOffer.getIsActivated());
assertEquals(scaledUpAltcoinOfferPrice.apply(newFixedPriceAsString), editedOffer.getPrice());
assertFalse(editedOffer.getUseMarketBasedPrice());
@ -576,6 +572,39 @@ public class EditOfferTest extends AbstractOfferTest {
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,
String currencyCode,
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.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
import static java.lang.String.format;
import static java.lang.System.out;
import static org.junit.jupiter.api.Assertions.*;
import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.CliMain;
import bisq.cli.GrpcClient;
import bisq.cli.table.builder.TableBuilder;
@ -37,7 +39,9 @@ public class AbstractTradeTest extends AbstractOfferTest {
protected static String tradeId;
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
public static void initStaticFixtures() {
@ -241,4 +245,11 @@ public class AbstractTradeTest extends AbstractOfferTest {
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;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.BsqSwapTradeInfo;
import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.TradeInfo;
import java.util.ArrayList;
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.BTC;
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory.BSQ_SWAP;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
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 protobuf.BsqSwapTrade.State.COMPLETED;
import static protobuf.BsqSwapTrade.State.PREPARATION;
@ -47,13 +49,12 @@ import static protobuf.OfferDirection.BUY;
import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.GrpcClient;
@Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BsqSwapTradeTest extends AbstractOfferTest {
private static final String BISQ_FEE_CURRENCY_CODE = BSQ;
public class BsqSwapTradeTest extends AbstractTradeTest {
// Long-running swap trade tests might want to check node logs for exceptions.
@Setter
@ -66,7 +67,7 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
@BeforeEach
public void generateBtcBlock() {
genBtcBlocksThenWait(1, 2000);
genBtcBlocksThenWait(1, 2_000);
}
@Test
@ -81,54 +82,91 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
@Test
@Order(2)
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,
"0.00005",
alicesBsqSwapAcct.getId());
log.debug("BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", bsqSwapOffer);
var newOfferId = bsqSwapOffer.getId();
"0.00005");
log.debug("Pending BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", toOfferTable.apply(mySwapOffer));
var newOfferId = mySwapOffer.getId();
assertNotEquals("", newOfferId);
assertEquals(BUY.name(), bsqSwapOffer.getDirection());
assertEquals(5_000, bsqSwapOffer.getPrice());
assertEquals(1_000_000L, bsqSwapOffer.getAmount());
assertEquals(1_000_000L, bsqSwapOffer.getMinAmount());
// assertEquals(alicesBsqAcct.getId(), atomicOffer.getMakerPaymentAccountId());
assertEquals(BSQ, bsqSwapOffer.getBaseCurrencyCode());
assertEquals(BTC, bsqSwapOffer.getCounterCurrencyCode());
assertEquals(BUY.name(), mySwapOffer.getDirection());
assertEquals(5_000, mySwapOffer.getPrice());
assertEquals(1_000_000L, mySwapOffer.getAmount());
assertEquals(1_000_000L, mySwapOffer.getMinAmount());
assertEquals(BSQ, mySwapOffer.getBaseCurrencyCode());
assertEquals(BTC, mySwapOffer.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
@Order(3)
public void testBobTakesBsqSwapOffer() {
var bsqSwapOffer = getAvailableBsqSwapOffer();
var bsqSwapTradeInfo = bobClient.takeBsqSwapOffer(bsqSwapOffer.getId(),
bobsBsqSwapAcct.getId(),
BISQ_FEE_CURRENCY_CODE);
log.debug("Trade at t1: {}", bsqSwapTradeInfo);
assertEquals(PREPARATION.name(), bsqSwapTradeInfo.getState());
var availableSwapOffer = getAvailableBsqSwapOffer(bobClient);
// Before sending a TakeOfferRequest, the CLI needs to know what kind of Offer
// it is taking (v1 or BsqSwap). Only BSQ swap offers can be taken with a
// single offerId parameter. Taking v1 offers requires an additional
// 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);
bsqSwapTradeInfo = getBsqSwapTrade(bsqSwapTradeInfo.getTradeId());
log.debug("Trade at t2: {}", bsqSwapTradeInfo);
assertEquals(COMPLETED.name(), bsqSwapTradeInfo.getState());
swapTrade = getBsqSwapTrade(bobClient, tradeId);
log.debug("BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(swapTrade));
assertEquals(COMPLETED.name(), swapTrade.getState());
runCliGetTrade(tradeId);
}
@Test
@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() {
sleep(2_500); // Give wallet time to finish processing TX.
var alicesBalances = aliceClient.getBalances();
log.debug("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
var bobsBalances = bobClient.getBalances();
log.debug("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
}
private BsqSwapOfferInfo getAvailableBsqSwapOffer() {
List<BsqSwapOfferInfo> bsqSwapOffers = new ArrayList<>();
private OfferInfo getAvailableBsqSwapOffer(GrpcClient client) {
List<OfferInfo> bsqSwapOffers = new ArrayList<>();
int numFetchAttempts = 0;
while (bsqSwapOffers.size() == 0) {
bsqSwapOffers.addAll(bobClient.getBsqSwapOffers(BUY.name(), BSQ));
bsqSwapOffers.addAll(client.getBsqSwapOffers(BUY.name()));
numFetchAttempts++;
if (bsqSwapOffers.size() == 0) {
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));
}
sleep(1000);
sleep(1_000);
} else {
assertEquals(1, bsqSwapOffers.size());
log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts);
@ -150,12 +188,12 @@ public class BsqSwapTradeTest extends AbstractOfferTest {
return bsqSwapOffer;
}
private BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
private TradeInfo getBsqSwapTrade(GrpcClient client, String tradeId) {
int numFetchAttempts = 0;
while (true) {
try {
numFetchAttempts++;
return bobClient.getBsqSwapTrade(tradeId);
return client.getTrade(tradeId);
} catch (Exception ex) {
log.warn(ex.getMessage());
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));
} else {
sleep(1000);
sleep(1_000);
}
}
}

View file

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

View file

@ -18,6 +18,7 @@
package bisq.cli;
import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.TradeInfo;
import io.grpc.StatusRuntimeException;
@ -45,6 +46,7 @@ import static bisq.cli.CurrencyFormat.*;
import static bisq.cli.Method.*;
import static bisq.cli.opts.OptLabel.*;
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.System.err;
import static java.lang.System.exit;
@ -62,11 +64,11 @@ import bisq.cli.opts.EditOfferOptionParser;
import bisq.cli.opts.GetAddressBalanceOptionParser;
import bisq.cli.opts.GetBTCMarketPriceOptionParser;
import bisq.cli.opts.GetBalanceOptionParser;
import bisq.cli.opts.GetOfferOptionParser;
import bisq.cli.opts.GetOffersOptionParser;
import bisq.cli.opts.GetPaymentAcctFormOptionParser;
import bisq.cli.opts.GetTradeOptionParser;
import bisq.cli.opts.GetTransactionOptionParser;
import bisq.cli.opts.OfferIdOptionParser;
import bisq.cli.opts.RegisterDisputeAgentOptionParser;
import bisq.cli.opts.RemoveWalletPasswordOptionParser;
import bisq.cli.opts.SendBsqOptionParser;
@ -332,6 +334,7 @@ public class CliMain {
out.println(client.getMethodHelp(method));
return;
}
var isSwap = opts.getIsSwap();
var paymentAcctId = opts.getPaymentAccountId();
var direction = opts.getDirection();
var currencyCode = opts.getCurrencyCode();
@ -340,10 +343,17 @@ public class CliMain {
var useMarketBasedPrice = opts.isUsingMktPriceMargin();
var fixedPrice = opts.getFixedPrice();
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit());
var securityDeposit = isSwap ? 0.00 : toSecurityDepositAsPct(opts.getSecurityDeposit());
var makerFeeCurrencyCode = opts.getMakerFeeCurrencyCode();
var triggerPrice = 0; // Cannot be defined until offer is in book.
var offer = client.createOffer(direction,
OfferInfo offer;
if (isSwap) {
offer = client.createBsqSwapOffer(direction,
amount,
minAmount,
fixedPrice);
} else {
offer = client.createOffer(direction,
currencyCode,
amount,
minAmount,
@ -354,16 +364,24 @@ public class CliMain {
paymentAcctId,
makerFeeCurrencyCode,
triggerPrice);
}
new TableBuilder(OFFER_TBL, offer).build().print(out);
return;
}
case editoffer: {
var opts = new EditOfferOptionParser(args).parse();
if (opts.isForHelp()) {
var offerIdOpt = new OfferIdOptionParser(args, true).parse();
if (offerIdOpt.isForHelp()) {
out.println(client.getMethodHelp(method));
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 isUsingMktPriceMargin = opts.isUsingMktPriceMargin();
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
@ -392,7 +410,7 @@ public class CliMain {
return;
}
case getoffer: {
var opts = new GetOfferOptionParser(args).parse();
var opts = new OfferIdOptionParser(args).parse();
if (opts.isForHelp()) {
out.println(client.getMethodHelp(method));
return;
@ -403,7 +421,7 @@ public class CliMain {
return;
}
case getmyoffer: {
var opts = new GetOfferOptionParser(args).parse();
var opts = new OfferIdOptionParser(args).parse();
if (opts.isForHelp()) {
out.println(client.getMethodHelp(method));
return;
@ -446,15 +464,25 @@ public class CliMain {
return;
}
case takeoffer: {
var opts = new TakeOfferOptionParser(args).parse();
if (opts.isForHelp()) {
var offerIdOpt = new OfferIdOptionParser(args, true).parse();
if (offerIdOpt.isForHelp()) {
out.println(client.getMethodHelp(method));
return;
}
var offerId = opts.getOfferId();
var offerId = offerIdOpt.getOfferId();
TradeInfo trade;
// We only send an 'offer-id' param when taking a BsqSwapOffer.
// 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();
var trade = client.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
trade = client.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
}
out.printf("trade %s successfully taken%n", trade.getTradeId());
return;
}
@ -801,10 +829,11 @@ public class CliMain {
stream.format(rowFormat, "", "--currency-code=<currency-code> \\", "");
stream.format(rowFormat, "", "--amount=<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, "", "[--fee-currency=<bsq|btc>]", "");
stream.format(rowFormat, "", "[--trigger-price=<price>]", "");
stream.format(rowFormat, "", "[--swap=<true|false>]", "");
stream.println();
stream.format(rowFormat, editoffer.name(), "--offer-id=<offer-id> \\", "Edit offer with id");
stream.format(rowFormat, "", "[--fixed-price=<price>] \\", "");
@ -825,7 +854,7 @@ public class CliMain {
stream.format(rowFormat, "", "--currency-code=<currency-code>", "");
stream.println();
stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> \\", "Take offer with id");
stream.format(rowFormat, "", "--payment-account=<payment-account-id>", "");
stream.format(rowFormat, "", "[--payment-account=<payment-account-id>]", "");
stream.format(rowFormat, "", "[--fee-currency=<btc|bsq>]", "");
stream.println();
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.BalancesInfo;
import bisq.proto.grpc.BsqBalanceInfo;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.BsqSwapTradeInfo;
import bisq.proto.grpc.BtcBalanceInfo;
import bisq.proto.grpc.CreateBsqSwapOfferRequest;
import bisq.proto.grpc.GetMethodHelpRequest;
import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.StopRequest;
import bisq.proto.grpc.TakeBsqSwapOfferReply;
import bisq.proto.grpc.TakeBsqSwapOfferRequest;
import bisq.proto.grpc.TakeOfferReply;
import bisq.proto.grpc.TradeInfo;
import bisq.proto.grpc.TxFeeRateInfo;
@ -44,6 +39,7 @@ import java.util.List;
import lombok.extern.slf4j.Slf4j;
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);
}
public BsqSwapOfferInfo createBsqSwapOffer(String direction,
public OfferCategory getAvailableOfferCategory(String offerId) {
return offersServiceRequest.getAvailableOfferCategory(offerId);
}
public OfferCategory getMyOfferCategory(String offerId) {
return offersServiceRequest.getMyOfferCategory(offerId);
}
public OfferInfo createBsqSwapOffer(String direction,
long amount,
long minAmount,
String fixedPrice,
String paymentAcctId) {
var request = CreateBsqSwapOfferRequest.newBuilder()
.setDirection(direction)
.setAmount(amount)
.setMinAmount(minAmount)
.setPrice(fixedPrice)
.setPaymentAccountId(paymentAcctId)
.build();
return grpcStubs.offersService.createBsqSwapOffer(request).getBsqSwapOffer();
String fixedPrice) {
return offersServiceRequest.createBsqSwapOffer(direction,
amount,
minAmount,
fixedPrice);
}
public OfferInfo createFixedPricedOffer(String direction,
@ -263,7 +262,7 @@ public final class GrpcClient {
offersServiceRequest.cancelOffer(offerId);
}
public BsqSwapOfferInfo getBsqSwapOffer(String offerId) {
public OfferInfo getBsqSwapOffer(String offerId) {
return offersServiceRequest.getBsqSwapOffer(offerId);
}
@ -271,7 +270,7 @@ public final class GrpcClient {
return offersServiceRequest.getOffer(offerId);
}
public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) {
public OfferInfo getMyBsqSwapOffer(String offerId) {
return offersServiceRequest.getMyBsqSwapOffer(offerId);
}
@ -279,8 +278,8 @@ public final class GrpcClient {
return offersServiceRequest.getMyOffer(offerId);
}
public List<BsqSwapOfferInfo> getBsqSwapOffers(String direction, String currencyCode) {
return offersServiceRequest.getBsqSwapOffers(direction, currencyCode);
public List<OfferInfo> getBsqSwapOffers(String direction) {
return offersServiceRequest.getBsqSwapOffers(direction);
}
public List<OfferInfo> getOffers(String direction, String currencyCode) {
@ -303,12 +302,12 @@ public final class GrpcClient {
return offersServiceRequest.getBsqOffersSortedByDate();
}
public List<BsqSwapOfferInfo> getBsqSwapOffersSortedByDate() {
public List<OfferInfo> getBsqSwapOffersSortedByDate() {
return offersServiceRequest.getBsqSwapOffersSortedByDate();
}
public List<BsqSwapOfferInfo> getMyBsqSwapOffers(String direction, String currencyCode) {
return offersServiceRequest.getMyBsqSwapOffers(direction, currencyCode);
public List<OfferInfo> getMyBsqSwapOffers(String direction) {
return offersServiceRequest.getMyBsqSwapOffers(direction);
}
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
@ -335,7 +334,7 @@ public final class GrpcClient {
return offersServiceRequest.getMyBsqOffersSortedByDate();
}
public List<BsqSwapOfferInfo> getMyBsqSwapBsqOffersSortedByDate() {
public List<OfferInfo> getMyBsqSwapBsqOffersSortedByDate() {
return offersServiceRequest.getMyBsqSwapOffersSortedByDate();
}
@ -343,45 +342,26 @@ public final class GrpcClient {
return offersServiceRequest.getMostRecentOffer(direction, currencyCode);
}
public List<BsqSwapOfferInfo> sortBsqSwapOffersByDate(List<BsqSwapOfferInfo> offerInfoList) {
return offersServiceRequest.sortBsqSwapOffersByDate(offerInfoList);
public List<OfferInfo> sortBsqSwapOffersByDate(List<OfferInfo> offers) {
return offersServiceRequest.sortOffersByDate(offers);
}
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
return offersServiceRequest.sortOffersByDate(offerInfoList);
}
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 List<OfferInfo> sortOffersByDate(List<OfferInfo> offers) {
return offersServiceRequest.sortOffersByDate(offers);
}
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
return tradesServiceRequest.getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
}
public BsqSwapTradeInfo takeBsqSwapOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
var reply = getTakeBsqSwapOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
if (reply.hasBsqSwapTrade())
return reply.getBsqSwapTrade();
else
throw new IllegalStateException(reply.getFailureReason().getDescription());
public TradeInfo takeBsqSwapOffer(String offerId) {
return tradesServiceRequest.takeBsqSwapOffer(offerId);
}
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
}
public BsqSwapTradeInfo getBsqSwapTrade(String tradeId) {
return tradesServiceRequest.getBsqSwapTrade(tradeId);
}
public TradeInfo getTrade(String tradeId) {
return tradesServiceRequest.getTrade(tradeId);
}

View file

@ -39,7 +39,7 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
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();
@Getter

View file

@ -18,14 +18,7 @@
package bisq.cli.opts;
import joptsimple.OptionSpec;
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 class CancelOfferOptionParser extends OfferIdOptionParser implements MethodOpts {
public CancelOfferOptionParser(String[] args) {
super(args);
@ -34,12 +27,7 @@ public class CancelOfferOptionParser extends AbstractMethodOptionParser implemen
public CancelOfferOptionParser parse() {
super.parse();
// Short circuit opt validation if user just wants help.
if (options.has(helpOpt))
return this;
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
throw new IllegalArgumentException("no offer id specified");
// Super class will short-circuit parsing if help option is present.
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_CURRENCY_CODE;
import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT;
import static java.lang.Boolean.FALSE;
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")
.withOptionalArg()
.ofType(boolean.class)
.defaultsTo(Boolean.FALSE);
.defaultsTo(FALSE);
public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) {
super(args);

View file

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

View file

@ -24,7 +24,10 @@ import joptsimple.OptionSpec;
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 java.lang.String.format;
@ -32,15 +35,12 @@ import static java.lang.String.format;
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_OFF = 0;
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")
.withOptionalArg()
.defaultsTo("0");
@ -66,18 +66,13 @@ public class EditOfferOptionParser extends AbstractMethodOptionParser implements
private EditOfferRequest.EditType offerEditType;
public EditOfferOptionParser(String[] args) {
super(args);
super(args, true);
}
public EditOfferOptionParser parse() {
super.parse();
// Short circuit opt validation if user just wants help.
if (options.has(helpOpt))
return this;
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
throw new IllegalArgumentException("no offer id specified");
// Super class will short-circuit parsing if help option is present.
boolean hasNoEditDetails = !options.has(fixedPriceOpt)
&& !options.has(mktPriceMarginOpt)
@ -201,10 +196,6 @@ public class EditOfferOptionParser extends AbstractMethodOptionParser implements
return this;
}
public String getOfferId() {
return options.valueOf(offerIdOpt);
}
public String getFixedPrice() {
if (offerEditType.equals(FIXED_PRICE_ONLY) || offerEditType.equals(FIXED_PRICE_AND_ACTIVATION_STATE)) {
return options.has(fixedPriceOpt) ? options.valueOf(fixedPriceOpt) : "0";

View file

@ -22,16 +22,26 @@ import joptsimple.OptionSpec;
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();
public GetOfferOptionParser(String[] args) {
super(args);
public OfferIdOptionParser(String[] 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();
// 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_SECURITY_DEPOSIT = "security-deposit";
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_INSTANT = "trade-instant";
public final static String OPT_TIMEOUT = "timeout";

View file

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

View file

@ -17,12 +17,14 @@
package bisq.cli.request;
import bisq.proto.grpc.BsqSwapOfferInfo;
import bisq.proto.grpc.CancelOfferRequest;
import bisq.proto.grpc.CreateBsqSwapOfferRequest;
import bisq.proto.grpc.CreateOfferRequest;
import bisq.proto.grpc.EditOfferRequest;
import bisq.proto.grpc.GetBsqSwapOffersRequest;
import bisq.proto.grpc.GetMyOfferRequest;
import bisq.proto.grpc.GetMyOffersRequest;
import bisq.proto.grpc.GetOfferCategoryRequest;
import bisq.proto.grpc.GetOfferRequest;
import bisq.proto.grpc.GetOffersRequest;
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.MKT_PRICE_MARGIN_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.stream.Collectors.toList;
import static protobuf.OfferDirection.BUY;
@ -60,6 +63,27 @@ public class OffersServiceRequest {
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")
public OfferInfo createFixedPricedOffer(String direction,
String currencyCode,
@ -210,7 +234,7 @@ public class OffersServiceRequest {
grpcStubs.offersService.cancelOffer(request);
}
public BsqSwapOfferInfo getBsqSwapOffer(String offerId) {
public OfferInfo getBsqSwapOffer(String offerId) {
var request = GetOfferRequest.newBuilder()
.setId(offerId)
.build();
@ -224,7 +248,7 @@ public class OffersServiceRequest {
return grpcStubs.offersService.getOffer(request).getOffer();
}
public BsqSwapOfferInfo getMyBsqSwapOffer(String offerId) {
public OfferInfo getMyBsqSwapOffer(String offerId) {
var request = GetMyOfferRequest.newBuilder()
.setId(offerId)
.build();
@ -239,10 +263,9 @@ public class OffersServiceRequest {
return grpcStubs.offersService.getMyOffer(request).getOffer();
}
public List<BsqSwapOfferInfo> getBsqSwapOffers(String direction, String currencyCode) {
var request = GetOffersRequest.newBuilder()
public List<OfferInfo> getBsqSwapOffers(String direction) {
var request = GetBsqSwapOffersRequest.newBuilder()
.setDirection(direction)
.setCurrencyCode(currencyCode)
.build();
return grpcStubs.offersService.getBsqSwapOffers(request).getBsqSwapOffersList();
@ -278,11 +301,11 @@ public class OffersServiceRequest {
return offers.isEmpty() ? offers : sortOffersByDate(offers);
}
public List<BsqSwapOfferInfo> getBsqSwapOffersSortedByDate() {
ArrayList<BsqSwapOfferInfo> offers = new ArrayList<>();
offers.addAll(getBsqSwapOffers(BUY.name(), "BSQ"));
offers.addAll(getBsqSwapOffers(SELL.name(), "BSQ"));
return sortBsqSwapOffersByDate(offers);
public List<OfferInfo> getBsqSwapOffersSortedByDate() {
ArrayList<OfferInfo> offers = new ArrayList<>();
offers.addAll(getBsqSwapOffers(BUY.name()));
offers.addAll(getBsqSwapOffers(SELL.name()));
return sortOffersByDate(offers);
}
public List<OfferInfo> getBsqOffersSortedByDate() {
@ -292,10 +315,9 @@ public class OffersServiceRequest {
return sortOffersByDate(offers);
}
public List<BsqSwapOfferInfo> getMyBsqSwapOffers(String direction, String currencyCode) {
var request = GetMyOffersRequest.newBuilder()
public List<OfferInfo> getMyBsqSwapOffers(String direction) {
var request = GetBsqSwapOffersRequest.newBuilder()
.setDirection(direction)
.setCurrencyCode(currencyCode)
.build();
return grpcStubs.offersService.getMyBsqSwapOffers(request).getBsqSwapOffersList();
}
@ -344,11 +366,11 @@ public class OffersServiceRequest {
return sortOffersByDate(offers);
}
public List<BsqSwapOfferInfo> getMyBsqSwapOffersSortedByDate() {
ArrayList<BsqSwapOfferInfo> offers = new ArrayList<>();
offers.addAll(getMyBsqSwapOffers(BUY.name(), "BSQ"));
offers.addAll(getMyBsqSwapOffers(SELL.name(), "BSQ"));
return sortBsqSwapOffersByDate(offers);
public List<OfferInfo> getMyBsqSwapOffersSortedByDate() {
ArrayList<OfferInfo> offers = new ArrayList<>();
offers.addAll(getMyBsqSwapOffers(BUY.name()));
offers.addAll(getMyBsqSwapOffers(SELL.name()));
return sortOffersByDate(offers);
}
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
@ -356,19 +378,20 @@ public class OffersServiceRequest {
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) {
return offerInfoList.stream()
.sorted(comparing(OfferInfo::getDate))
.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) {
return getSupportedCryptoCurrencies().contains(currencyCode.toUpperCase());
}

View file

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

View file

@ -102,6 +102,15 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
@Nullable
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) {
super(tableType, protos);
validate();
@ -125,7 +134,9 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
this.colRole = colSupplier.roleColumn.get();
this.colOfferType = colSupplier.offerTypeColumn.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.colIsDepositConfirmed = colSupplier.depositConfirmedColumn.get();
this.colIsPayoutPublished = colSupplier.payoutPublishedColumn.get();
@ -135,6 +146,12 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
this.colIsPaymentSent = colSupplier.paymentSentColumn.get();
this.colIsPaymentReceived = colSupplier.paymentReceivedColumn.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() {
@ -149,9 +166,8 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
// Helper Functions
private final Supplier<Boolean> isTradeDetailTblBuilder = () -> tableType.equals(TRADE_DETAIL_TBL);
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");
// Column Value Functions
@ -185,7 +201,6 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
}
};
// TODO Move to TradeUtil ?
protected final Function<TradeInfo, String> toPriceDeviation = (t) ->
t.getOffer().getUseMarketBasedPrice()
? formatToPercent(t.getOffer().getMarketPriceMargin())
@ -196,8 +211,6 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
? t.getTxFeeAsLong()
: t.getOffer().getTxFee();
// TODO Move to TradeUtil ?
protected final BiFunction<TradeInfo, Boolean, Long> toTradeFeeBsq = (t, isMyOffer) -> {
if (isMyOffer) {
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) -> {
if (isMyOffer) {
return t.getOffer().getIsCurrencyForMakerFeeBtc()
@ -223,12 +235,18 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
}
};
protected final Function<TradeInfo, Long> toMyMakerOrTakerFee = (t) ->
isTaker.test(t)
protected final Function<TradeInfo, Long> toMyMakerOrTakerFee = (t) -> {
if (isBsqSwapTrade.test(t)) {
return isTaker.test(t)
? t.getBsqSwapTradeInfo().getBsqTakerTradeFee()
: t.getBsqSwapTradeInfo().getBsqMakerTradeFee();
} else {
return isTaker.test(t)
? t.getTakerFeeAsLong()
: t.getOffer().getMakerFee();
}
};
// TODO Move to TradeUtil ? SEE ClosedTradesViewModel # getDirectionLabel
protected final Function<TradeInfo, String> toOfferType = (t) -> {
if (isFiatTrade.test(t)) {
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) {
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_UNLOCKING_BONDS_BALANCE = "Unlocking Bonds 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_SELLER_DEPOSIT = "Seller Deposit";
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_ROLE = "My Role";
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_TAKER_FEE = "Taker Fee(%-3s)";
static final String COL_HEADER_TRADE_FEE = "Trade Fee";

View file

@ -17,10 +17,16 @@
package bisq.cli.table.builder;
import bisq.proto.grpc.TradeInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
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.
*/
@SuppressWarnings("ConstantConditions")
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) {
super(TRADE_DETAIL_TBL, protos);
}
@ -41,33 +51,70 @@ class TradeDetailTableBuilder extends AbstractTradeListBuilder {
* @return Table containing one row
*/
public Table build() {
populateColumns();
List<Column<?>> columns = defineColumnList();
// A trade detail table only has one row.
var trade = trades.get(0);
populateColumns(trade);
List<Column<?>> columns = defineColumnList(trade);
return new Table(columns.toArray(new Column<?>[0]));
}
private void populateColumns() {
trades.stream().forEachOrdered(t -> {
colTradeId.addRow(t.getShortId());
colRole.addRow(t.getRole());
colPrice.addRow(t.getTradePrice());
colAmountInBtc.addRow(toAmount.apply(t));
colMinerTxFee.addRow(toMyMinerTxFee.apply(t));
colBisqTradeFee.addRow(toMyMakerOrTakerFee.apply(t));
colIsDepositPublished.addRow(t.getIsDepositPublished());
colIsDepositConfirmed.addRow(t.getIsDepositConfirmed());
colTradeCost.addRow(toTradeVolume.apply(t));
colIsPaymentSent.addRow(t.getIsFiatSent());
colIsPaymentReceived.addRow(t.getIsFiatReceived());
colIsPayoutPublished.addRow(t.getIsPayoutPublished());
colIsFundsWithdrawn.addRow(t.getIsWithdrawn());
if (colAltcoinReceiveAddressColumn != null)
colAltcoinReceiveAddressColumn.addRow(toAltcoinReceiveAddress.apply(t));
});
private void populateColumns(TradeInfo trade) {
if (isBsqSwapTrade.test(trade)) {
var isPending = isPendingBsqSwap.test(trade);
var isCompleted = isCompletedBsqSwap.test(trade);
if (isPending == isCompleted)
throw new IllegalStateException(
format("programmer error: trade must be either pending or completed, is pending=%s and completed=%s",
isPending,
isCompleted));
populateBsqSwapTradeColumns(trade);
} else {
populateBisqV1TradeColumns(trade);
}
}
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<>() {{
add(colTradeId);
add(colRole);
@ -89,4 +136,25 @@ class TradeDetailTableBuilder extends AbstractTradeListBuilder {
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.RIGHT;
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.FiatColumn;
import bisq.cli.table.column.Iso8601DateTimeColumn;
import bisq.cli.table.column.LongColumn;
import bisq.cli.table.column.MixedPriceColumn;
import bisq.cli.table.column.MixedTradeFeeColumn;
import bisq.cli.table.column.MixedVolumeColumn;
@ -79,7 +81,10 @@ class TradeTableColumnSupplier {
private final Supplier<TradeInfo> firstRow = () -> getTrades().get(0);
private final Predicate<OfferInfo> isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC");
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 Supplier<Boolean> isSwapTradeDetail = () ->
isTradeDetailTblBuilder.get() && isBsqSwapTrade.test(firstRow.get());
final Supplier<StringColumn> tradeIdColumn = () -> isTradeDetailTblBuilder.get()
? new StringColumn(COL_HEADER_TRADE_SHORT_ID)
@ -95,8 +100,8 @@ class TradeTableColumnSupplier {
private final Function<TradeInfo, Column<Long>> toDetailedPriceColumn = (t) -> {
String colHeader = isFiatTrade.test(t)
? String.format(COL_HEADER_DETAILED_PRICE, t.getOffer().getCounterCurrencyCode())
: String.format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, t.getOffer().getBaseCurrencyCode());
? format(COL_HEADER_DETAILED_PRICE, t.getOffer().getCounterCurrencyCode())
: format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, t.getOffer().getBaseCurrencyCode());
return isFiatTrade.test(t)
? new FiatColumn(colHeader)
: new AltcoinColumn(colHeader);
@ -116,7 +121,7 @@ class TradeTableColumnSupplier {
private final Function<TradeInfo, Column<Long>> toDetailedAmountColumn = (t) -> {
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)
? new SatoshiColumn(colHeader)
: new AltcoinColumn(colHeader, ALTCOIN_OFFER_VOLUME);
@ -142,10 +147,14 @@ class TradeTableColumnSupplier {
? null
: new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT);
final Supplier<StringColumn> roleColumn = () ->
isTradeDetailTblBuilder.get() || isOpenTradeTblBuilder.get() || isFailedTradeTblBuilder.get()
final Supplier<StringColumn> roleColumn = () -> {
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)
: null;
};
final Function<String, Column<Long>> toSecurityDepositColumn = (name) -> isClosedTradeTblBuilder.get()
? new SatoshiColumn(name)
@ -161,21 +170,42 @@ class TradeTableColumnSupplier {
private final Function<String, Column<Boolean>> toBooleanColumn = BooleanColumn::new;
final Supplier<Column<Boolean>> depositPublishedColumn = () -> isTradeDetailTblBuilder.get()
final Supplier<Column<Boolean>> depositPublishedColumn = () -> {
if (isSwapTradeDetail.get())
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 = () -> {
if (isSwapTradeDetail.get())
return null;
else
return isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_CONFIRMED)
: null;
final Supplier<Column<Boolean>> payoutPublishedColumn = () -> isTradeDetailTblBuilder.get()
};
final Supplier<Column<Boolean>> payoutPublishedColumn = () -> {
if (isSwapTradeDetail.get())
return null;
else
return isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_PAYOUT_PUBLISHED)
: null;
};
final Supplier<Column<Boolean>> fundsWithdrawnColumn = () -> isTradeDetailTblBuilder.get()
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 = () -> {
if (isTradeDetailTblBuilder.get()) {
@ -184,8 +214,8 @@ class TradeTableColumnSupplier {
? t.getIsCurrencyForTakerFeeBtc() ? "BTC" : "BSQ"
: t.getOffer().getIsCurrencyForMakerFeeBtc() ? "BTC" : "BSQ";
String colHeader = isTaker.test(t)
? String.format(COL_HEADER_TRADE_TAKER_FEE, headerCurrencyCode)
: String.format(COL_HEADER_TRADE_MAKER_FEE, headerCurrencyCode);
? format(COL_HEADER_TRADE_TAKER_FEE, headerCurrencyCode)
: format(COL_HEADER_TRADE_MAKER_FEE, headerCurrencyCode);
boolean isBsqSatoshis = headerCurrencyCode.equals("BSQ");
return new SatoshiColumn(colHeader, isBsqSatoshis);
} else {
@ -201,7 +231,7 @@ class TradeTableColumnSupplier {
final Supplier<Column<Boolean>> paymentSentColumn = () -> {
if (isTradeDetailTblBuilder.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);
} else {
return null;
@ -211,7 +241,7 @@ class TradeTableColumnSupplier {
final Supplier<Column<Boolean>> paymentReceivedColumn = () -> {
if (isTradeDetailTblBuilder.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);
} else {
return null;
@ -222,7 +252,7 @@ class TradeTableColumnSupplier {
if (isTradeDetailTblBuilder.get()) {
TradeInfo t = firstRow.get();
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)
? new FiatColumn(colHeader, VOLUME)
: 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) -> {
if (isFiatTrade.test(t)) {
return false;
@ -251,7 +293,7 @@ class TradeTableColumnSupplier {
TradeInfo t = firstRow.get();
if (showAltCoinBuyerAddress.test(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);
} else {
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.
*/
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) {
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");
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");
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");
CliMain.main(new String[]{"--password=xyz", "getoffers", "--direction=buy", "--currency-code=eur"});
out.println(">>> getoffers sell eur");
@ -35,5 +70,4 @@ public class GetOffersSmokeTest {
out.println(">>> getoffers sell 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 org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class OptionParsersTest {
@ -62,13 +63,16 @@ public class OptionParsersTest {
new CancelOfferOptionParser(args).parse();
}
// createoffer opt parser tests
// createoffer (v1) opt parser tests
@Test
public void testCreateOfferOptParserWithMissingPaymentAccountIdOptShouldThrowException() {
public void testCreateOfferWithMissingPaymentAccountIdOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name()
createoffer.name(),
"--" + OPT_DIRECTION + "=" + "SELL",
"--" + OPT_CURRENCY_CODE + "=" + "JPY",
"--" + OPT_AMOUNT + "=" + "0.1"
};
Throwable exception = assertThrows(RuntimeException.class, () ->
new CreateOfferOptionParser(args).parse());
@ -76,7 +80,7 @@ public class OptionParsersTest {
}
@Test
public void testCreateOfferOptParserWithEmptyPaymentAccountIdOptShouldThrowException() {
public void testCreateOfferWithEmptyPaymentAccountIdOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
@ -88,7 +92,7 @@ public class OptionParsersTest {
}
@Test
public void testCreateOfferOptParserWithMissingDirectionOptShouldThrowException() {
public void testCreateOfferWithMissingDirectionOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
@ -101,7 +105,7 @@ public class OptionParsersTest {
@Test
public void testCreateOfferOptParserWithMissingDirectionOptValueShouldThrowException() {
public void testCreateOfferWithMissingDirectionOptValueShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createoffer.name(),
@ -134,10 +138,114 @@ public class OptionParsersTest {
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
@Test
public void testCreatePaymentAcctOptParserWithMissingPaymentFormOptShouldThrowException() {
public void testCreatePaymentAcctWithMissingPaymentFormOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createpaymentacct.name()
@ -149,7 +257,7 @@ public class OptionParsersTest {
}
@Test
public void testCreatePaymentAcctOptParserWithMissingPaymentFormOptValueShouldThrowException() {
public void testCreatePaymentAcctWithMissingPaymentFormOptValueShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createpaymentacct.name(),
@ -161,7 +269,7 @@ public class OptionParsersTest {
}
@Test
public void testCreatePaymentAcctOptParserWithInvalidPaymentFormOptValueShouldThrowException() {
public void testCreatePaymentAcctWithInvalidPaymentFormOptValueShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createpaymentacct.name(),
@ -180,7 +288,7 @@ public class OptionParsersTest {
// createcryptopaymentacct parser tests
@Test
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingAcctNameOptShouldThrowException() {
public void testCreateCryptoCurrencyPaymentAcctWithMissingAcctNameOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createcryptopaymentacct.name()
@ -191,7 +299,7 @@ public class OptionParsersTest {
}
@Test
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithEmptyAcctNameOptShouldThrowException() {
public void testCreateCryptoCurrencyPaymentAcctWithEmptyAcctNameOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createcryptopaymentacct.name(),
@ -203,7 +311,7 @@ public class OptionParsersTest {
}
@Test
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingCurrencyCodeOptShouldThrowException() {
public void testCreateCryptoCurrencyPaymentAcctWithMissingCurrencyCodeOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createcryptopaymentacct.name(),
@ -215,7 +323,7 @@ public class OptionParsersTest {
}
@Test
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithInvalidCurrencyCodeOptShouldThrowException() {
public void testCreateCryptoCurrencyPaymentAcctWithInvalidCurrencyCodeOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createcryptopaymentacct.name(),
@ -228,7 +336,7 @@ public class OptionParsersTest {
}
@Test
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingAddressOptShouldThrowException() {
public void testCreateCryptoCurrencyPaymentAcctWithMissingAddressOptShouldThrowException() {
String[] args = new String[]{
PASSWORD_OPT,
createcryptopaymentacct.name(),
@ -241,7 +349,7 @@ public class OptionParsersTest {
}
@Test
public void testCreateCryptoCurrencyPaymentAcctOptionParser() {
public void testCreateCryptoCurrencyPaymentAcct() {
var acctName = "bsq payment account";
var currencyCode = "bsq";
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.payload.PaymentMethod;
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.bsq_swap.BsqSwapTrade;
import bisq.core.trade.statistics.TradeStatistics3;
@ -119,6 +120,18 @@ public class CoreApi {
// 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) {
return coreOffersService.getBsqSwapOffer(id);
}
@ -264,14 +277,10 @@ public class CoreApi {
///////////////////////////////////////////////////////////////////////////////////////////
public void takeBsqSwapOffer(String offerId,
String paymentAccountId,
String takerFeeCurrencyCode,
TradeResultHandler<BsqSwapTrade> tradeResultHandler,
ErrorMessageHandler errorMessageHandler) {
Offer bsqSwapOffer = coreOffersService.getBsqSwapOffer(offerId);
coreTradesService.takeBsqSwapOffer(bsqSwapOffer,
paymentAccountId,
takerFeeCurrencyCode,
tradeResultHandler,
errorMessageHandler);
}
@ -305,18 +314,18 @@ public class CoreApi {
coreTradesService.withdrawFunds(tradeId, address, memo);
}
public BsqSwapTrade getBsqSwapTrade(String tradeId) {
return coreTradesService.getBsqSwapTrade(tradeId);
}
public Trade getTrade(String tradeId) {
return coreTradesService.getTrade(tradeId);
public TradeModel getTradeModel(String tradeId) {
return coreTradesService.getTradeModel(tradeId);
}
public String getTradeRole(String tradeId) {
return coreTradesService.getTradeRole(tradeId);
}
public String getBsqSwapTradeRole(BsqSwapTrade bsqSwapTrade) {
return coreTradesService.getBsqSwapTradeRole(bsqSwapTrade);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Wallets
///////////////////////////////////////////////////////////////////////////////////////////
@ -381,6 +390,10 @@ public class CoreApi {
return walletsService.getTransaction(txId);
}
public int getTransactionConfirmations(String txId) {
return walletsService.getTransactionConfirmations(txId);
}
public void setWalletPassword(String password, String newPassword) {
walletsService.setWalletPassword(password, newPassword);
}

View file

@ -48,6 +48,8 @@ import java.math.BigDecimal;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
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.offer.Offer.State;
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.DEACTIVATED;
import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer;
@ -78,6 +81,9 @@ class CoreOffersService {
private final Supplier<Comparator<OpenOffer>> openOfferPriceComparator = () ->
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 KeyRing keyRing;
// Dependencies on core api services in this package must be kept to an absolute
@ -118,51 +124,48 @@ class CoreOffersService {
this.user = user;
}
Offer getBsqSwapOffer(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(o -> o.isBsqSwapOffer())
.findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
boolean isFiatOffer(String id, boolean isMyOffer) {
var offer = toOfferWithId.apply(id, isMyOffer);
return OfferUtil.isFiatOffer(offer);
}
boolean isAltcoinOffer(String id, boolean isMyOffer) {
var offer = toOfferWithId.apply(id, isMyOffer);
return OfferUtil.isAltcoinOffer(offer);
}
boolean isBsqSwapOffer(String id, boolean isMyOffer) {
var offer = toOfferWithId.apply(id, isMyOffer);
return offer.isBsqSwapOffer();
}
Offer getOffer(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().orElseThrow(() ->
return findAvailableOffer(id).orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
}
OpenOffer getMyOffer(String id) {
return openOfferManager.getObservableList().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> o.getOffer().isMyOffer(keyRing))
.findAny().orElseThrow(() ->
return findMyOpenOffer(id).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) {
return offerBookService.getOffers().stream()
.filter(o -> o.getId().equals(id))
.filter(o -> o.isMyOffer(keyRing))
.filter(o -> o.isBsqSwapOffer())
.findAny().orElseThrow(() ->
return findMyBsqSwapOffer(id).orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));
}
List<Offer> getBsqSwapOffers(String direction) {
var offers = offerBookService.getOffers().stream()
return offerBookService.getOffers().stream()
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> o.getDirection().name().equalsIgnoreCase(direction))
.filter(o -> o.isBsqSwapOffer())
.filter(Offer::isBsqSwapOffer)
.sorted(priceComparator(direction))
.collect(Collectors.toList());
return offers;
}
List<Offer> getOffers(String direction, String currencyCode) {
@ -183,13 +186,12 @@ class CoreOffersService {
}
List<Offer> getMyBsqSwapOffers(String direction) {
var offers = offerBookService.getOffers().stream()
return offerBookService.getOffers().stream()
.filter(o -> o.isMyOffer(keyRing))
.filter(o -> o.getDirection().name().equalsIgnoreCase(direction))
.filter(Offer::isBsqSwapOffer)
.sorted(priceComparator(direction))
.collect(Collectors.toList());
return offers;
}
OpenOffer getMyOpenBsqSwapOffer(String id) {
@ -208,9 +210,12 @@ class CoreOffersService {
}
boolean isMyOffer(String id) {
return openOfferManager.getOpenOfferById(id)
boolean isMyOpenOffer = openOfferManager.getOpenOfferById(id)
.filter(open -> open.getOffer().isMyOffer(keyRing))
.isPresent();
boolean wasMyOffer = offerBookService.getOffers().stream()
.anyMatch(o -> o.getId().equals(id) && o.isMyOffer(keyRing));
return isMyOpenOffer || wasMyOffer;
}
void createAndPlaceBsqSwapOffer(String directionAsString,
@ -222,7 +227,7 @@ class CoreOffersService {
coreWalletsService.verifyEncryptedWalletIsUnlocked();
String currencyCode = "BSQ";
String offerId = OfferUtil.getRandomOfferId();
String offerId = getRandomOfferId();
OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase());
Coin amount = Coin.valueOf(amountAsLong);
Coin minAmount = Coin.valueOf(minAmountAsLong);
@ -256,7 +261,7 @@ class CoreOffersService {
throw new IllegalArgumentException(format("payment account with id %s not found", paymentAccountId));
String upperCaseCurrencyCode = currencyCode.toUpperCase();
String offerId = OfferUtil.getRandomOfferId();
String offerId = getRandomOfferId();
OfferDirection direction = OfferDirection.valueOf(directionAsString.toUpperCase());
Price price = Price.valueOf(upperCaseCurrencyCode, priceStringToLong(priceAsString, upperCaseCurrencyCode));
Coin amount = Coin.valueOf(amountAsLong);
@ -375,6 +380,38 @@ class CoreOffersService {
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,
String editedPriceAsString,
double editedMarketPriceMargin,
@ -427,10 +464,9 @@ class CoreOffersService {
private boolean offerMatchesDirectionAndCurrency(Offer offer,
String direction,
String currencyCode) {
var offerOfWantedDirection = offer.getDirection().name().equalsIgnoreCase(direction);
var offerInWantedCurrency = offer.getCounterCurrencyCode()
.equalsIgnoreCase(currencyCode);
return offerOfWantedDirection && offerInWantedCurrency;
var isDirectionMatch = offer.getDirection().name().equalsIgnoreCase(direction);
var isCurrencyMatch = offer.getCounterCurrencyCode().equalsIgnoreCase(currencyCode);
return isDirectionMatch && isCurrencyMatch;
}
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.TradeUtil;
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.bsq_swap.BsqSwapTrade;
import bisq.core.trade.protocol.bisq_v1.BuyerProtocol;
@ -91,27 +92,26 @@ class CoreTradesService {
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,
String paymentAccountId,
String takerFeeCurrencyCode,
TradeResultHandler<BsqSwapTrade> tradeResultHandler,
ErrorMessageHandler errorMessageHandler) {
coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked();
bsqSwapTakeOfferModel.initWithData(offer);
//todo use the intended trade amount
bsqSwapTakeOfferModel.applyAmount(offer.getAmount());
log.info("Initiating take {} offer, {}",
offer.isBuyOffer() ? "buy" : "sell",
bsqSwapTakeOfferModel);
bsqSwapTakeOfferModel.onTakeOffer(tradeResultHandler, log::warn, errorMessageHandler, coreContext.isApiUser());
bsqSwapTakeOfferModel.onTakeOffer(tradeResultHandler,
log::warn,
errorMessageHandler,
coreContext.isApiUser());
}
// TODO We need to pass the intended trade amount, not default to the maximum.
void takeOffer(Offer offer,
String paymentAccountId,
String takerFeeCurrencyCode,
@ -232,13 +232,28 @@ class CoreTradesService {
});
}
BsqSwapTrade getBsqSwapTrade(String tradeId) {
TradeModel getTradeModel(String tradeId) {
coreWalletsService.verifyWalletsAreAvailable();
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(() ->
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) {
coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked();

View file

@ -85,6 +85,7 @@ import javax.annotation.Nullable;
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
import static bisq.core.util.ParsingUtils.parseToCoin;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.SECONDS;
@Singleton
@ -324,7 +325,7 @@ class CoreWalletsService {
for (TransactionOutput txOut : spendableBsqTxOutputs) {
if (isTxOutputAddressMatch.test(txOut) && isTxOutputValueMatch.test(txOut)) {
log.info("\t\tTx {} output has matching address {} and value {}.",
txOut.getParentTransaction().getTxId(),
requireNonNull(txOut.getParentTransaction()).getTxId(),
address,
txOut.getValue().toPlainString());
numMatches++;
@ -346,6 +347,7 @@ class CoreWalletsService {
@SuppressWarnings({"unchecked", "Convert2MethodRef"})
ListenableFuture<Void> future =
(ListenableFuture<Void>) executor.submit(() -> feeService.requestFees());
//noinspection NullableProblems
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void ignored) {
@ -393,23 +395,11 @@ class CoreWalletsService {
}
Transaction getTransaction(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()));
return getTransactionWithId(txId);
}
int getTransactionConfirmations(String txId) {
return getTransactionWithId(txId).getConfidence().getDepthInBlocks();
}
int getNumConfirmationsForMostRecentTransaction(String addressString) {
@ -654,12 +644,32 @@ class CoreWalletsService {
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
* the cached result when the same input occurs again.
*
* 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`.
*/
private static <I, O> LoadingCache<I, O> memoize(Function<I, O> f) {

View file

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

View file

@ -73,6 +73,7 @@ public class ContractInfo implements Payload {
// 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 = () ->
new ContractInfo("",
"",

View file

@ -17,17 +17,19 @@
package bisq.core.api.model;
import bisq.core.api.model.builder.OfferInfoBuilder;
import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
import bisq.core.util.coin.CoinUtil;
import bisq.common.Payload;
import java.util.Objects;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import static java.util.Objects.requireNonNull;
@EqualsAndHashCode
@ToString
@Getter
@ -56,86 +58,95 @@ public class OfferInfo implements Payload {
private final String paymentAccountId;
private final String paymentMethodId;
private final String paymentMethodShortName;
// For fiat offer the baseCurrencyCode is BTC and the counterCurrencyCode is the fiat currency
// For altcoin offers it is the opposite. baseCurrencyCode is the altcoin and the counterCurrencyCode is BTC.
// Fiat offer: baseCurrencyCode = BTC, counterCurrencyCode = fiat ccy code.
// Altcoin offer: baseCurrencyCode = altcoin ccy code, counterCurrencyCode = BTC.
private final String baseCurrencyCode;
private final String counterCurrencyCode;
private final long date;
private final String state;
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 isBsqSwapOffer;
private final String ownerNodeAddress;
private final String pubKeyRing;
private final String versionNumber;
private final int protocolVersion;
public OfferInfo(OfferInfoBuilder builder) {
this.id = builder.id;
this.direction = builder.direction;
this.price = builder.price;
this.useMarketBasedPrice = builder.useMarketBasedPrice;
this.marketPriceMargin = builder.marketPriceMargin;
this.amount = builder.amount;
this.minAmount = builder.minAmount;
this.volume = builder.volume;
this.minVolume = builder.minVolume;
this.txFee = builder.txFee;
this.makerFee = builder.makerFee;
this.offerFeePaymentTxId = builder.offerFeePaymentTxId;
this.buyerSecurityDeposit = builder.buyerSecurityDeposit;
this.sellerSecurityDeposit = builder.sellerSecurityDeposit;
this.triggerPrice = builder.triggerPrice;
this.isCurrencyForMakerFeeBtc = builder.isCurrencyForMakerFeeBtc;
this.paymentAccountId = builder.paymentAccountId;
this.paymentMethodId = builder.paymentMethodId;
this.paymentMethodShortName = builder.paymentMethodShortName;
this.baseCurrencyCode = builder.baseCurrencyCode;
this.counterCurrencyCode = builder.counterCurrencyCode;
this.date = builder.date;
this.state = builder.state;
this.isActivated = builder.isActivated;
this.isMyOffer = builder.isMyOffer;
this.isMyPendingOffer = builder.isMyPendingOffer;
this.id = builder.getId();
this.direction = builder.getDirection();
this.price = builder.getPrice();
this.useMarketBasedPrice = builder.isUseMarketBasedPrice();
this.marketPriceMargin = builder.getMarketPriceMargin();
this.amount = builder.getAmount();
this.minAmount = builder.getMinAmount();
this.volume = builder.getVolume();
this.minVolume = builder.getMinVolume();
this.txFee = builder.getTxFee();
this.makerFee = builder.getMakerFee();
this.offerFeePaymentTxId = builder.getOfferFeePaymentTxId();
this.buyerSecurityDeposit = builder.getBuyerSecurityDeposit();
this.sellerSecurityDeposit = builder.getSellerSecurityDeposit();
this.triggerPrice = builder.getTriggerPrice();
this.isCurrencyForMakerFeeBtc = builder.isCurrencyForMakerFeeBtc();
this.paymentAccountId = builder.getPaymentAccountId();
this.paymentMethodId = builder.getPaymentMethodId();
this.paymentMethodShortName = builder.getPaymentMethodShortName();
this.baseCurrencyCode = builder.getBaseCurrencyCode();
this.counterCurrencyCode = builder.getCounterCurrencyCode();
this.date = builder.getDate();
this.state = builder.getState();
this.isActivated = builder.isActivated();
this.isMyOffer = builder.isMyOffer();
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 void setIsMyOffer(boolean isMyOffer) {
this.isMyOffer = isMyOffer;
public static OfferInfo toMyOfferInfo(Offer offer) {
return getBuilder(offer, true).build();
}
public static OfferInfo toOfferInfo(Offer offer) {
// 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);
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
// prepared, and no valid OpenOffer state (AVAILABLE, DEACTIVATED) exists.
// 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.
return getOfferInfoBuilder(myNewOffer, true)
return getBuilder(myNewOffer, true)
.withIsMyPendingOffer(true)
.build();
}
public static OfferInfo toOfferInfo(OpenOffer openOffer) {
public static OfferInfo toMyOfferInfo(OpenOffer openOffer) {
// An OpenOffer is always my offer.
return getOfferInfoBuilder(openOffer.getOffer(), true)
return getBuilder(openOffer.getOffer(), true)
.withTriggerPrice(openOffer.getTriggerPrice())
.withIsActivated(!openOffer.isDeactivated())
.build();
}
private static OfferInfoBuilder getOfferInfoBuilder(Offer offer, boolean isMyOffer) {
private static OfferInfoBuilder getBuilder(Offer offer, boolean isMyOffer) {
return new OfferInfoBuilder()
.withId(offer.getId())
.withDirection(offer.getDirection().name())
.withPrice(Objects.requireNonNull(offer.getPrice()).getValue())
.withPrice(requireNonNull(offer.getPrice()).getValue())
.withUseMarketBasedPrice(offer.isUseMarketBasedPrice())
.withMarketPriceMargin(offer.getMarketPriceMargin())
.withAmount(offer.getAmount().value)
.withMinAmount(offer.getMinAmount().value)
.withVolume(Objects.requireNonNull(offer.getVolume()).getValue())
.withMinVolume(Objects.requireNonNull(offer.getMinVolume()).getValue())
.withMakerFee(offer.getMakerFee().value)
.withVolume(requireNonNull(offer.getVolume()).getValue())
.withMinVolume(requireNonNull(offer.getMinVolume()).getValue())
.withMakerFee(getMakerFee(offer, isMyOffer))
.withTxFee(offer.getTxFee().value)
.withOfferFeePaymentTxId(offer.getOfferFeePaymentTxId())
.withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value)
@ -148,7 +159,18 @@ public class OfferInfo implements Payload {
.withCounterCurrencyCode(offer.getCounterCurrencyCode())
.withDate(offer.getDate().getTime())
.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)
.setMakerFee(makerFee)
.setTxFee(txFee)
.setOfferFeePaymentTxId(offerFeePaymentTxId)
.setOfferFeePaymentTxId(isBsqSwapOffer ? "" : offerFeePaymentTxId)
.setBuyerSecurityDeposit(buyerSecurityDeposit)
.setSellerSecurityDeposit(sellerSecurityDeposit)
.setTriggerPrice(triggerPrice)
@ -184,6 +206,11 @@ public class OfferInfo implements Payload {
.setIsActivated(isActivated)
.setIsMyOffer(isMyOffer)
.setIsMyPendingOffer(isMyPendingOffer)
.setIsBsqSwapOffer(isBsqSwapOffer)
.setOwnerNodeAddress(ownerNodeAddress)
.setPubKeyRing(pubKeyRing)
.setVersionNr(versionNumber)
.setProtocolVersion(protocolVersion)
.build();
}
@ -216,175 +243,11 @@ public class OfferInfo implements Payload {
.withIsActivated(proto.getIsActivated())
.withIsMyOffer(proto.getIsMyOffer())
.withIsMyPendingOffer(proto.getIsMyPendingOffer())
.withIsBsqSwapOffer(proto.getIsBsqSwapOffer())
.withOwnerNodeAddress(proto.getOwnerNodeAddress())
.withPubKeyRing(proto.getPubKeyRing())
.withVersionNumber(proto.getVersionNr())
.withProtocolVersion(proto.getProtocolVersion())
.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;
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.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.common.Payload;
import java.util.Objects;
import lombok.EqualsAndHashCode;
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.PaymentAccountPayloadInfo.toPaymentAccountPayloadInfo;
import static java.util.Objects.requireNonNull;
@EqualsAndHashCode
@Getter
@ -38,6 +42,7 @@ public class TradeInfo implements Payload {
// lighter weight TradeInfo proto wrapper instead, containing just enough fields to
// 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 String tradeId;
private final String shortId;
@ -64,41 +69,88 @@ public class TradeInfo implements Payload {
private final boolean isWithdrawn;
private final String contractAsJson;
private final ContractInfo contract;
// Optional BSQ swap trade protocol details (post v1).
private BsqSwapTradeInfo bsqSwapTradeInfo;
public TradeInfo(TradeInfoBuilder builder) {
this.offer = builder.offer;
this.tradeId = builder.tradeId;
this.shortId = builder.shortId;
this.date = builder.date;
this.role = builder.role;
this.isCurrencyForTakerFeeBtc = builder.isCurrencyForTakerFeeBtc;
this.txFeeAsLong = builder.txFeeAsLong;
this.takerFeeAsLong = builder.takerFeeAsLong;
this.takerFeeTxId = builder.takerFeeTxId;
this.depositTxId = builder.depositTxId;
this.payoutTxId = builder.payoutTxId;
this.tradeAmountAsLong = builder.tradeAmountAsLong;
this.tradePrice = builder.tradePrice;
this.tradeVolume = builder.tradeVolume;
this.tradingPeerNodeAddress = builder.tradingPeerNodeAddress;
this.state = builder.state;
this.phase = builder.phase;
this.tradePeriodState = builder.tradePeriodState;
this.isDepositPublished = builder.isDepositPublished;
this.isDepositConfirmed = builder.isDepositConfirmed;
this.isFiatSent = builder.isFiatSent;
this.isFiatReceived = builder.isFiatReceived;
this.isPayoutPublished = builder.isPayoutPublished;
this.isWithdrawn = builder.isWithdrawn;
this.contractAsJson = builder.contractAsJson;
this.contract = builder.contract;
public TradeInfo(TradeInfoV1Builder builder) {
this.offer = builder.getOffer();
this.tradeId = builder.getTradeId();
this.shortId = builder.getShortId();
this.date = builder.getDate();
this.role = builder.getRole();
this.isCurrencyForTakerFeeBtc = builder.isCurrencyForTakerFeeBtc();
this.txFeeAsLong = builder.getTxFeeAsLong();
this.takerFeeAsLong = builder.getTakerFeeAsLong();
this.takerFeeTxId = builder.getTakerFeeTxId();
this.depositTxId = builder.getDepositTxId();
this.payoutTxId = builder.getPayoutTxId();
this.tradeAmountAsLong = builder.getTradeAmountAsLong();
this.tradePrice = builder.getTradePrice();
this.tradeVolume = builder.getTradeVolume();
this.tradingPeerNodeAddress = builder.getTradingPeerNodeAddress();
this.state = builder.getState();
this.phase = builder.getPhase();
this.tradePeriodState = builder.getTradePeriodState();
this.isDepositPublished = builder.isDepositPublished();
this.isDepositConfirmed = builder.isDepositConfirmed();
this.isFiatSent = builder.isFiatSent();
this.isFiatReceived = builder.isFiatReceived();
this.isPayoutPublished = builder.isPayoutPublished();
this.isWithdrawn = builder.isWithdrawn();
this.contractAsJson = builder.getContractAsJson();
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) {
// Always called by the taker, isMyOffer=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;
if (trade.getContract() != null) {
Contract contract = trade.getContract();
@ -118,9 +170,8 @@ public class TradeInfo implements Payload {
contractInfo = ContractInfo.emptyContract.get();
}
OfferInfo offerInfo = toOfferInfo(trade.getOffer());
offerInfo.setIsMyOffer(isMyOffer);
return new TradeInfoBuilder()
OfferInfo offerInfo = isMyOffer ? toMyOfferInfo(trade.getOffer()) : toOfferInfo(trade.getOffer());
return new TradeInfoV1Builder()
.withOffer(offerInfo)
.withTradeId(trade.getId())
.withShortId(trade.getShortId())
@ -129,15 +180,13 @@ public class TradeInfo implements Payload {
.withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc())
.withTxFeeAsLong(trade.getTradeTxFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeTxId(trade.getTakerFeeTxId())
.withDepositTxId(trade.getDepositTxId())
.withPayoutTxId(trade.getPayoutTxId())
.withTradeAmountAsLong(trade.getAmountAsLong())
.withTradePrice(trade.getPrice().getValue())
.withTradeVolume(trade.getVolume() == null ? 0 : trade.getVolume().getValue())
.withTradingPeerNodeAddress(Objects.requireNonNull(
trade.getTradingPeerNodeAddress()).getHostNameWithoutPostFix())
.withTradingPeerNodeAddress(requireNonNull(trade.getTradingPeerNodeAddress().getFullAddress()))
.withState(trade.getTradeState().name())
.withPhase(trade.getTradePhase().name())
.withTradePeriodState(trade.getTradePeriodState().name())
@ -158,7 +207,8 @@ public class TradeInfo implements Payload {
@Override
public bisq.proto.grpc.TradeInfo toProtoMessage() {
return bisq.proto.grpc.TradeInfo.newBuilder()
var protoBuilder =
bisq.proto.grpc.TradeInfo.newBuilder()
.setOffer(offer.toProtoMessage())
.setTradeId(tradeId)
.setShortId(shortId)
@ -174,22 +224,28 @@ public class TradeInfo implements Payload {
.setTradePrice(tradePrice)
.setTradeVolume(tradeVolume)
.setTradingPeerNodeAddress(tradingPeerNodeAddress)
.setState(state)
.setPhase(phase)
.setTradePeriodState(tradePeriodState)
.setState(state == null ? "" : state)
.setPhase(phase == null ? "" : phase)
.setTradePeriodState(tradePeriodState == null ? "" : tradePeriodState)
.setIsDepositPublished(isDepositPublished)
.setIsDepositConfirmed(isDepositConfirmed)
.setIsFiatSent(isFiatSent)
.setIsFiatReceived(isFiatReceived)
.setIsPayoutPublished(isPayoutPublished)
.setIsWithdrawn(isWithdrawn)
.setContractAsJson(contractAsJson == null ? "" : contractAsJson)
.setContract(contract.toProtoMessage())
.build();
.setIsWithdrawn(isWithdrawn);
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) {
return new TradeInfoBuilder()
var tradeInfo = new TradeInfoV1Builder()
.withOffer(OfferInfo.fromProto(proto.getOffer()))
.withTradeId(proto.getTradeId())
.withShortId(proto.getShortId())
@ -217,175 +273,11 @@ public class TradeInfo implements Payload {
.withContractAsJson(proto.getContractAsJson())
.withContract((ContractInfo.fromProto(proto.getContract())))
.build();
}
/*
* TradeInfoBuilder 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.
*/
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;
if (proto.getOffer().getIsBsqSwapOffer())
tradeInfo.bsqSwapTradeInfo = BsqSwapTradeInfo.fromProto(proto.getBsqSwapTradeInfo());
public TradeInfoBuilder withOffer(OfferInfo offer) {
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);
}
return tradeInfo;
}
@Override
@ -417,6 +309,7 @@ public class TradeInfo implements Payload {
", offer=" + offer + "\n" +
", contractAsJson=" + contractAsJson + "\n" +
", contract=" + contract + "\n" +
", bsqSwapTradeInfo=" + bsqSwapTradeInfo + "\n" +
'}';
}
}

View file

@ -46,7 +46,7 @@ public class TxInfo implements Payload {
private final boolean isPending;
private final String memo;
public TxInfo(TxInfoBuilder builder) {
public TxInfo(Builder builder) {
this.txId = builder.txId;
this.inputSum = builder.inputSum;
this.outputSum = builder.outputSum;
@ -61,7 +61,7 @@ public class TxInfo implements Payload {
throw new IllegalStateException("server created a null transaction");
if (transaction.getFee() != null)
return new TxInfoBuilder()
return new Builder()
.withTxId(transaction.getTxId().toString())
.withInputSum(transaction.getInputSum().value)
.withOutputSum(transaction.getOutputSum().value)
@ -71,7 +71,7 @@ public class TxInfo implements Payload {
.withMemo(transaction.getMemo())
.build();
else
return new TxInfoBuilder()
return new Builder()
.withTxId(transaction.getTxId().toString())
.withInputSum(transaction.getInputSum().value)
.withOutputSum(transaction.getOutputSum().value)
@ -101,7 +101,7 @@ public class TxInfo implements Payload {
@SuppressWarnings("unused")
public static TxInfo fromProto(bisq.proto.grpc.TxInfo proto) {
return new TxInfoBuilder()
return new Builder()
.withTxId(proto.getTxId())
.withInputSum(proto.getInputSum())
.withOutputSum(proto.getOutputSum())
@ -112,7 +112,7 @@ public class TxInfo implements Payload {
.build();
}
public static class TxInfoBuilder {
private static class Builder {
private String txId;
private long inputSum;
private long outputSum;
@ -121,37 +121,37 @@ public class TxInfo implements Payload {
private boolean isPending;
private String memo;
public TxInfoBuilder withTxId(String txId) {
public Builder withTxId(String txId) {
this.txId = txId;
return this;
}
public TxInfoBuilder withInputSum(long inputSum) {
public Builder withInputSum(long inputSum) {
this.inputSum = inputSum;
return this;
}
public TxInfoBuilder withOutputSum(long outputSum) {
public Builder withOutputSum(long outputSum) {
this.outputSum = outputSum;
return this;
}
public TxInfoBuilder withFee(long fee) {
public Builder withFee(long fee) {
this.fee = fee;
return this;
}
public TxInfoBuilder withSize(int size) {
public Builder withSize(int size) {
this.size = size;
return this;
}
public TxInfoBuilder withIsPending(boolean isPending) {
public Builder withIsPending(boolean isPending) {
this.isPending = isPending;
return this;
}
public TxInfoBuilder withMemo(String memo) {
public Builder withMemo(String memo) {
this.memo = memo;
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) {
String offerFeePaymentTxId = offer.getOfferFeePaymentTxId();
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.bisq_v1.Contract;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.network.p2p.NodeAddress;
@ -57,6 +58,8 @@ import static java.lang.String.format;
@Singleton
public class TradeUtil {
// TODO change non-state dependent instance methods to static methods.
private final BtcWalletService btcWalletService;
private final KeyRing keyRing;
@ -203,6 +206,24 @@ public class TradeUtil {
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.
*

View file

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

View file

@ -7,38 +7,34 @@ createoffer - create offer to buy or sell BTC
SYNOPSIS
--------
createoffer
--payment-account=<payment-acct-id>
--direction=<buy|sell>
--currency-code=<eur|usd>
--market-price-margin=<percent> | --fixed-price=<btc-price>
--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>
--swap=<true|false>
[--fee-currency=<bsq|btc>]
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
-------
--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
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.
If --min-amount is not present, it defaults to the --amount value.
--security-deposit
The percentage of the BTC amount being traded for the security deposit, e.g., 60.0 (60%).
--currency-code
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
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
--------
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
at the current market price,
using a payment account with ID 7413d263-225a-4f1b-837a-1e3094dc0d77,

View file

@ -9,11 +9,22 @@ SYNOPSIS
takeoffer
--offer-id=<offer-id>
--payment-account=<payment-acct-id>
--fee-currency=<eur|usd>
[--fee-currency=<btc|bsq>]
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
-------
@ -29,6 +40,9 @@ OPTIONS
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
using a payment account with ID fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e,
and paying the Bisq trading fee in BSQ:

View file

@ -18,7 +18,6 @@
package bisq.daemon.grpc;
import bisq.core.api.CoreApi;
import bisq.core.api.model.BsqSwapOfferInfo;
import bisq.core.api.model.OfferInfo;
import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
@ -33,12 +32,15 @@ import bisq.proto.grpc.EditOfferReply;
import bisq.proto.grpc.EditOfferRequest;
import bisq.proto.grpc.GetBsqSwapOfferReply;
import bisq.proto.grpc.GetBsqSwapOffersReply;
import bisq.proto.grpc.GetBsqSwapOffersRequest;
import bisq.proto.grpc.GetMyBsqSwapOfferReply;
import bisq.proto.grpc.GetMyBsqSwapOffersReply;
import bisq.proto.grpc.GetMyOfferReply;
import bisq.proto.grpc.GetMyOfferRequest;
import bisq.proto.grpc.GetMyOffersReply;
import bisq.proto.grpc.GetMyOffersRequest;
import bisq.proto.grpc.GetOfferCategoryReply;
import bisq.proto.grpc.GetOfferCategoryRequest;
import bisq.proto.grpc.GetOfferReply;
import bisq.proto.grpc.GetOfferRequest;
import bisq.proto.grpc.GetOffersReply;
@ -56,10 +58,15 @@ import java.util.stream.Collectors;
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.toPendingOfferInfo;
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 java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
@ -81,13 +88,28 @@ class GrpcOffersService extends OffersImplBase {
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
public void getBsqSwapOffer(GetOfferRequest req,
StreamObserver<GetBsqSwapOfferReply> responseObserver) {
try {
Offer offer = coreApi.getOffer(req.getId());
var reply = GetBsqSwapOfferReply.newBuilder()
.setBsqSwapOffer(toBsqSwapOfferInfo(offer).toProtoMessage())
.setBsqSwapOffer(toOfferInfo(offer).toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
@ -117,7 +139,7 @@ class GrpcOffersService extends OffersImplBase {
try {
Offer offer = coreApi.getMyBsqSwapOffer(req.getId());
var reply = GetMyBsqSwapOfferReply.newBuilder()
.setBsqSwapOffer(toBsqSwapOfferInfo(offer /* TODO support triggerPrice */).toProtoMessage())
.setBsqSwapOffer(toOfferInfo(offer /* TODO support triggerPrice */).toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
@ -132,7 +154,7 @@ class GrpcOffersService extends OffersImplBase {
try {
OpenOffer openOffer = coreApi.getMyOffer(req.getId());
var reply = GetMyOfferReply.newBuilder()
.setOffer(toOfferInfo(openOffer).toProtoMessage())
.setOffer(OfferInfo.toMyOfferInfo(openOffer).toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
@ -142,15 +164,15 @@ class GrpcOffersService extends OffersImplBase {
}
@Override
public void getBsqSwapOffers(GetOffersRequest req,
public void getBsqSwapOffers(GetBsqSwapOffersRequest req,
StreamObserver<GetBsqSwapOffersReply> responseObserver) {
try {
List<BsqSwapOfferInfo> result = coreApi.getBsqSwapOffers(req.getDirection())
.stream().map(BsqSwapOfferInfo::toBsqSwapOfferInfo)
List<OfferInfo> result = coreApi.getBsqSwapOffers(req.getDirection())
.stream().map(OfferInfo::toOfferInfo)
.collect(Collectors.toList());
var reply = GetBsqSwapOffersReply.newBuilder()
.addAllBsqSwapOffers(result.stream()
.map(BsqSwapOfferInfo::toProtoMessage)
.map(OfferInfo::toProtoMessage)
.collect(Collectors.toList()))
.build();
responseObserver.onNext(reply);
@ -180,15 +202,15 @@ class GrpcOffersService extends OffersImplBase {
}
@Override
public void getMyBsqSwapOffers(GetMyOffersRequest req,
public void getMyBsqSwapOffers(GetBsqSwapOffersRequest req,
StreamObserver<GetMyBsqSwapOffersReply> responseObserver) {
try {
List<BsqSwapOfferInfo> result = coreApi.getMyBsqSwapOffers(req.getDirection())
.stream().map(BsqSwapOfferInfo::toBsqSwapOfferInfo)
List<OfferInfo> result = coreApi.getMyBsqSwapOffers(req.getDirection())
.stream().map(OfferInfo::toOfferInfo)
.collect(Collectors.toList());
var reply = GetMyBsqSwapOffersReply.newBuilder()
.addAllBsqSwapOffers(result.stream()
.map(BsqSwapOfferInfo::toProtoMessage)
.map(OfferInfo::toProtoMessage)
.collect(Collectors.toList()))
.build();
responseObserver.onNext(reply);
@ -204,7 +226,7 @@ class GrpcOffersService extends OffersImplBase {
try {
List<OfferInfo> result = coreApi.getMyOffers(req.getDirection(), req.getCurrencyCode())
.stream()
.map(OfferInfo::toOfferInfo)
.map(OfferInfo::toMyOfferInfo)
.collect(Collectors.toList());
var reply = GetMyOffersReply.newBuilder()
.addAllOffers(result.stream()
@ -222,17 +244,15 @@ class GrpcOffersService extends OffersImplBase {
public void createBsqSwapOffer(CreateBsqSwapOfferRequest req,
StreamObserver<CreateBsqSwapOfferReply> responseObserver) {
try {
//todo PaymentAccount for bsq swap not needed as its just a dummy account
coreApi.createAndPlaceBsqSwapOffer(
req.getDirection(),
req.getAmount(),
req.getMinAmount(),
req.getPrice(),
/* req.getPaymentAccountId(),*/
offer -> {
BsqSwapOfferInfo bsqSwapOfferInfo = toBsqSwapOfferInfo(offer);
OfferInfo offerInfo = toMyPendingOfferInfo(offer);
CreateBsqSwapOfferReply reply = CreateBsqSwapOfferReply.newBuilder()
.setBsqSwapOffer(bsqSwapOfferInfo.toProtoMessage())
.setBsqSwapOffer(offerInfo.toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
@ -261,7 +281,7 @@ class GrpcOffersService extends OffersImplBase {
offer -> {
// This result handling consumer's accept operation will return
// the new offer to the gRPC client after async placement is done.
OfferInfo offerInfo = toPendingOfferInfo(offer);
OfferInfo offerInfo = toMyPendingOfferInfo(offer);
CreateOfferReply reply = CreateOfferReply.newBuilder()
.setOffer(offerInfo.toProtoMessage())
.build();
@ -315,6 +335,7 @@ class GrpcOffersService extends OffersImplBase {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put(getGetOfferCategoryMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getGetOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getGetMyOfferMethod().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;
import bisq.core.api.CoreApi;
import bisq.core.api.model.BsqSwapTradeInfo;
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.bsq_swap.BsqSwapTrade;
import bisq.proto.grpc.ConfirmPaymentReceivedReply;
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
import bisq.proto.grpc.ConfirmPaymentStartedReply;
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
import bisq.proto.grpc.GetBsqSwapTradeReply;
import bisq.proto.grpc.GetTradeReply;
import bisq.proto.grpc.GetTradeRequest;
import bisq.proto.grpc.KeepFundsReply;
import bisq.proto.grpc.KeepFundsRequest;
import bisq.proto.grpc.TakeBsqSwapOfferReply;
import bisq.proto.grpc.TakeBsqSwapOfferRequest;
import bisq.proto.grpc.TakeOfferReply;
import bisq.proto.grpc.TakeOfferRequest;
import bisq.proto.grpc.WithdrawFundsReply;
@ -48,7 +46,6 @@ import java.util.Optional;
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.toTradeInfo;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
@ -74,41 +71,31 @@ class GrpcTradesService extends TradesImplBase {
}
@Override
public void getBsqSwapTrade(GetTradeRequest req,
StreamObserver<GetBsqSwapTradeReply> 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) {
public void takeOffer(TakeOfferRequest req,
StreamObserver<TakeOfferReply> responseObserver) {
GrpcErrorMessageHandler errorMessageHandler =
new GrpcErrorMessageHandler(getTakeOfferMethod().getFullMethodName(),
responseObserver,
exceptionHandler,
log);
if (coreApi.isBsqSwapOffer(req.getOfferId(), false)) {
coreApi.takeBsqSwapOffer(req.getOfferId(),
bsqSwapTrade -> {
var reply = buildTakeOfferReply(bsqSwapTrade);
responseObserver.onNext(reply);
responseObserver.onCompleted();
},
errorMessage -> {
if (!errorMessageHandler.isErrorHandled())
errorMessageHandler.handleErrorMessage(errorMessage);
});
} else {
coreApi.takeOffer(req.getOfferId(),
req.getPaymentAccountId(),
req.getTakerFeeCurrencyCode(),
bsqSwapTrade -> {
BsqSwapTradeInfo bsqSwapTradeInfo = toBsqSwapTradeInfo(bsqSwapTrade);
var reply = TakeBsqSwapOfferReply.newBuilder()
.setBsqSwapTrade(bsqSwapTradeInfo.toProtoMessage())
.build();
trade -> {
var reply = buildTakeOfferReply(trade);
responseObserver.onNext(reply);
responseObserver.onCompleted();
},
@ -117,17 +104,16 @@ class GrpcTradesService extends TradesImplBase {
errorMessageHandler.handleErrorMessage(errorMessage);
});
}
}
@Override
public void getTrade(GetTradeRequest req,
StreamObserver<GetTradeReply> responseObserver) {
try {
Trade trade = coreApi.getTrade(req.getTradeId());
boolean isMyOffer = coreApi.isMyOffer(trade.getOffer().getId());
String role = coreApi.getTradeRole(req.getTradeId());
var reply = GetTradeReply.newBuilder()
.setTrade(toTradeInfo(trade, role, isMyOffer).toProtoMessage())
.build();
var tradeModel = coreApi.getTradeModel(req.getTradeId());
var reply = tradeModel.getOffer().isBsqSwapOffer()
? buildGetTradeReply((BsqSwapTrade) tradeModel)
: buildGetTradeReply((Trade) tradeModel);
responseObserver.onNext(reply);
responseObserver.onCompleted();
} 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
public void confirmPaymentStarted(ConfirmPaymentStartedRequest req,
StreamObserver<ConfirmPaymentStartedReply> responseObserver) {
@ -228,6 +189,7 @@ class GrpcTradesService extends TradesImplBase {
new HashMap<>() {{
put(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
// put(getTakeBsqSwapOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
put(getConfirmPaymentStartedMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
put(getConfirmPaymentReceivedMethod().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 {
rpc GetOfferCategory (GetOfferCategoryRequest) returns (GetOfferCategoryReply) {
}
rpc GetBsqSwapOffer (GetOfferRequest) returns (GetBsqSwapOfferReply) {
}
rpc GetOffer (GetOfferRequest) returns (GetOfferReply) {
@ -70,11 +72,11 @@ service Offers {
}
rpc GetMyOffer (GetMyOfferRequest) returns (GetMyOfferReply) {
}
rpc GetBsqSwapOffers (GetOffersRequest) returns (GetBsqSwapOffersReply) {
rpc GetBsqSwapOffers (GetBsqSwapOffersRequest) returns (GetBsqSwapOffersReply) {
}
rpc GetOffers (GetOffersRequest) returns (GetOffersReply) {
}
rpc GetMyBsqSwapOffers (GetMyOffersRequest) returns (GetMyBsqSwapOffersReply) {
rpc GetMyBsqSwapOffers (GetBsqSwapOffersRequest) returns (GetMyBsqSwapOffersReply) {
}
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 {
BsqSwapOfferInfo bsqSwapOffer = 1;
OfferInfo bsqSwapOffer = 1;
}
message GetOfferRequest {
@ -101,7 +118,7 @@ message GetOfferReply {
}
message GetMyBsqSwapOfferReply {
BsqSwapOfferInfo bsqSwapOffer = 1;
OfferInfo bsqSwapOffer = 1;
}
message GetMyOfferRequest {
@ -121,8 +138,12 @@ message GetOffersReply {
repeated OfferInfo offers = 1;
}
message GetBsqSwapOffersRequest {
string direction = 1;
}
message GetBsqSwapOffersReply {
repeated BsqSwapOfferInfo bsqSwapOffers = 1;
repeated OfferInfo bsqSwapOffers = 1;
}
message GetMyOffersRequest {
@ -135,7 +156,7 @@ message GetMyOffersReply {
}
message GetMyBsqSwapOffersReply {
repeated BsqSwapOfferInfo bsqSwapOffers = 1;
repeated OfferInfo bsqSwapOffers = 1;
}
message CreateBsqSwapOfferRequest {
@ -143,11 +164,10 @@ message CreateBsqSwapOfferRequest {
uint64 amount = 2;
uint64 minAmount = 3;
string price = 4;
string paymentAccountId = 5;
}
message CreateBsqSwapOfferReply {
BsqSwapOfferInfo bsqSwapOffer = 1;
OfferInfo bsqSwapOffer = 1;
}
message CreateOfferRequest {
@ -204,25 +224,6 @@ message CancelOfferRequest {
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 {
string id = 1;
string direction = 2;
@ -250,6 +251,11 @@ message OfferInfo {
bool isActivated = 24;
bool isMyOffer = 25;
bool isMyPendingOffer = 26;
bool isBsqSwapOffer = 27;
string ownerNodeAddress = 28;
string pubKeyRing = 29;
string versionNr = 30;
int32 protocolVersion = 31;
}
message AvailabilityResultWithDescription {
@ -377,12 +383,8 @@ message StopReply {
///////////////////////////////////////////////////////////////////////////////////////////
service Trades {
rpc GetBsqSwapTrade (GetTradeRequest) returns (GetBsqSwapTradeReply) {
}
rpc GetTrade (GetTradeRequest) returns (GetTradeReply) {
}
rpc TakeBsqSwapOffer (TakeBsqSwapOfferRequest) returns (TakeBsqSwapOfferReply) {
}
rpc TakeOffer (TakeOfferRequest) returns (TakeOfferReply) {
}
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 {
string offerId = 1;
string paymentAccountId = 2;
@ -431,10 +422,6 @@ message ConfirmPaymentReceivedRequest {
message ConfirmPaymentReceivedReply {
}
message GetBsqSwapTradeReply {
BsqSwapTradeInfo bsqSwapTrade = 1;
}
message GetTradeRequest {
string tradeId = 1;
}
@ -459,37 +446,8 @@ message WithdrawFundsRequest {
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 {
// Bisq v1 trade protocol fields.
OfferInfo offer = 1;
string tradeId = 2;
string shortId = 3;
@ -516,6 +474,8 @@ message TradeInfo {
string contractAsJson = 24;
ContractInfo contract = 25;
uint64 tradeVolume = 26;
// Optional Bisq v2+ trade protocol fields.
BsqSwapTradeInfo bsqSwapTradeInfo = 28;
}
message ContractInfo {
@ -533,6 +493,22 @@ message ContractInfo {
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 {
string id = 1;
string paymentMethodId = 2;