Merge remote-tracking branch 'bisq-network/hotfix/v1.5.6' into upgrade-javafax-14

This commit is contained in:
cd2357 2021-02-17 23:16:52 +01:00
commit 7c2b4e02b8
No known key found for this signature in database
GPG key ID: F26C56748514D0D3
91 changed files with 1887 additions and 749 deletions

1
.gitignore vendored
View file

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

View file

@ -34,57 +34,58 @@
#
# `$ apitest/scripts/limit-order-simulation.sh -l 40000 -d sell -c fr -m 0.00 -a 0.125`
APP_BASE_NAME=$(basename "$0")
APP_HOME=$(pwd -P)
APITEST_SCRIPTS_HOME="${APP_HOME}/apitest/scripts"
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/trade-simulation-env.sh"
source "$APITEST_SCRIPTS_HOME/trade-simulation-utils.sh"
checksetup
parselimitorderopts "$@"
printdate "Started ${APP_BASE_NAME} with parameters:"
printdate "Started $APP_BASE_NAME with parameters:"
printscriptparams
printbreak
editpaymentaccountform "$COUNTRY_CODE"
exitoncommandalert $?
cat "${APITEST_SCRIPTS_HOME}/${F2F_ACCT_FORM}"
cat "$APITEST_SCRIPTS_HOME/$F2F_ACCT_FORM"
printbreak
# Create F2F payment accounts for $COUNTRY_CODE, and get the $CURRENCY_CODE.
printdate "Creating Alice's face to face ${COUNTRY_CODE} payment account."
CMD="${CLI_BASE} --port=${ALICE_PORT} createpaymentacct --payment-account-form=${APITEST_SCRIPTS_HOME}/${F2F_ACCT_FORM}"
printdate "ALICE CLI: ${CMD}"
CMD_OUTPUT=$(createpaymentacct "${CMD}")
printdate "Creating Alice's face to face $COUNTRY_CODE payment account."
CMD="$CLI_BASE --port=$ALICE_PORT createpaymentacct --payment-account-form=$APITEST_SCRIPTS_HOME/$F2F_ACCT_FORM"
printdate "ALICE CLI: $CMD"
CMD_OUTPUT=$(createpaymentacct "$CMD")
exitoncommandalert $?
echo "${CMD_OUTPUT}"
ALICE_ACCT_ID=$(getnewpaymentacctid "${CMD_OUTPUT}")
echo "$CMD_OUTPUT"
ALICE_ACCT_ID=$(getnewpaymentacctid "$CMD_OUTPUT")
exitoncommandalert $?
CURRENCY_CODE=$(getnewpaymentacctcurrency "${CMD_OUTPUT}")
CURRENCY_CODE=$(getnewpaymentacctcurrency "$CMD_OUTPUT")
exitoncommandalert $?
printdate "ALICE F2F payment-account-id = ${ALICE_ACCT_ID}, currency-code = ${CURRENCY_CODE}."
printdate "ALICE F2F payment-account-id = $ALICE_ACCT_ID, currency-code = $CURRENCY_CODE."
printbreak
printdate "Creating Bob's face to face ${COUNTRY_CODE} payment account."
CMD="${CLI_BASE} --port=${BOB_PORT} createpaymentacct --payment-account-form=${APITEST_SCRIPTS_HOME}/${F2F_ACCT_FORM}"
printdate "BOB CLI: ${CMD}"
CMD_OUTPUT=$(createpaymentacct "${CMD}")
printdate "Creating Bob's face to face $COUNTRY_CODE payment account."
CMD="$CLI_BASE --port=$BOB_PORT createpaymentacct --payment-account-form=$APITEST_SCRIPTS_HOME/$F2F_ACCT_FORM"
printdate "BOB CLI: $CMD"
CMD_OUTPUT=$(createpaymentacct "$CMD")
exitoncommandalert $?
echo "${CMD_OUTPUT}"
BOB_ACCT_ID=$(getnewpaymentacctid "${CMD_OUTPUT}")
echo "$CMD_OUTPUT"
BOB_ACCT_ID=$(getnewpaymentacctid "$CMD_OUTPUT")
exitoncommandalert $?
CURRENCY_CODE=$(getnewpaymentacctcurrency "${CMD_OUTPUT}")
CURRENCY_CODE=$(getnewpaymentacctcurrency "$CMD_OUTPUT")
exitoncommandalert $?
printdate "BOB F2F payment-account-id = ${BOB_ACCT_ID}, currency-code = ${CURRENCY_CODE}."
printdate "BOB F2F payment-account-id = $BOB_ACCT_ID, currency-code = $CURRENCY_CODE."
printbreak
# Bob & Alice now have matching payment accounts, now loop until the price limit is reached, then create an offer.
if [ "$DIRECTION" = "BUY" ]
then
printdate "Create a BUY / ${CURRENCY_CODE} offer when the market price falls to or below ${LIMIT_PRICE} ${CURRENCY_CODE}."
printdate "Create a BUY / $CURRENCY_CODE offer when the market price falls to or below $LIMIT_PRICE $CURRENCY_CODE."
else
printdate "Create a SELL / ${CURRENCY_CODE} offer when the market price rises to or above ${LIMIT_PRICE} ${CURRENCY_CODE}."
printdate "Create a SELL / $CURRENCY_CODE offer when the market price rises to or above $LIMIT_PRICE $CURRENCY_CODE."
fi
DONE=0
@ -112,48 +113,47 @@ while : ; do
sleep "$WAIT"
done
printdate "ALICE: Creating ${DIRECTION} ${CURRENCY_CODE} offer with payment acct ${ALICE_ACCT_ID}."
CMD="$CLI_BASE --port=${ALICE_PORT} createoffer"
CMD+=" --payment-account=${ALICE_ACCT_ID}"
CMD+=" --direction=${DIRECTION}"
CMD+=" --currency-code=${CURRENCY_CODE}"
CMD+=" --amount=${AMOUNT}"
if [ -z "${MKT_PRICE_MARGIN}" ]; then
CMD+=" --fixed-price=${FIXED_PRICE}"
printdate "ALICE: Creating $DIRECTION $CURRENCY_CODE offer with payment acct $ALICE_ACCT_ID."
CMD="$CLI_BASE --port=$ALICE_PORT createoffer"
CMD+=" --payment-account=$ALICE_ACCT_ID"
CMD+=" --direction=$DIRECTION"
CMD+=" --currency-code=$CURRENCY_CODE"
CMD+=" --amount=$AMOUNT"
if [ -z "$MKT_PRICE_MARGIN" ]; then
CMD+=" --fixed-price=$FIXED_PRICE"
else
CMD+=" --market-price-margin=${MKT_PRICE_MARGIN}"
CMD+=" --market-price-margin=$MKT_PRICE_MARGIN"
fi
CMD+=" --security-deposit=50.0"
CMD+=" --fee-currency=BSQ"
printdate "ALICE CLI: ${CMD}"
OFFER_ID=$(createoffer "${CMD}")
printdate "ALICE CLI: $CMD"
OFFER_ID=$(createoffer "$CMD")
exitoncommandalert $?
printdate "ALICE: Created offer with id: ${OFFER_ID}."
printdate "ALICE: Created offer with id: $OFFER_ID."
printbreak
sleeptraced 3
# Show Alice's new 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}"
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}"
echo "$OFFER"
printbreak
sleeptraced 7
sleeptraced 4
# Generate some btc blocks.
printdate "Generating btc blocks after publishing Alice's offer."
genbtcblocks 3 3
printbreak
sleeptraced 5
# Show Alice's offer in Bob's CLI.
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}"
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}"
echo "$OFFERS"
exit 0

View file

@ -0,0 +1,119 @@
#! /bin/bash
# Demonstrates a way to always keep one offer in the market, using the API CLI with a local regtest bitcoin node.
# Alice creates an offer, waits for Bob to take it, and completes the trade protocol with him. Then Alice
# creates a new offer...
#
# Stop the script by entering ^C.
#
# A country code argument is used to create a country based face to face payment account for the simulated offer.
#
# Prerequisites:
#
# - Linux or OSX with bash, Java 10, or Java 11-12 (JDK language compatibility 10), and bitcoin-core (v0.19, v0.20).
#
# - 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`
#
# - Only regtest btc can be bought or sold with the test payment account.
#
# Usage:
#
# This script must be run from the root of the project, e.g.:
#
# `$ apitest/scripts/rolling-offer-simulation.sh -d buy -c us -m 2.00 -a 0.125`
#
# Script options: -d <direction> -c <country-code> (-m <mkt-price-margin(%)> || -f <fixed-price>) -a <amount(btc)>
#
# Example:
#
# Create a buy/usd offer to sell 0.1 btc at 2% above market price, using a US face to face payment account:
#
# `$ apitest/scripts/rolling-offer-simulation.sh -d sell -c us -m 2.00 -a 0.1`
APP_BASE_NAME=$(basename "$0")
APP_HOME=$(pwd -P)
APITEST_SCRIPTS_HOME="$APP_HOME/apitest/scripts"
source "$APITEST_SCRIPTS_HOME/trade-simulation-env.sh"
source "$APITEST_SCRIPTS_HOME/trade-simulation-utils.sh"
checksetup
parseopts "$@"
printdate "Started $APP_BASE_NAME with parameters:"
printscriptparams
printbreak
registerdisputeagents
showcreatepaymentacctsteps "Alice" "$ALICE_PORT"
CMD="$CLI_BASE --port=$ALICE_PORT createpaymentacct --payment-account-form=$APITEST_SCRIPTS_HOME/$F2F_ACCT_FORM"
printdate "ALICE CLI: $CMD"
CMD_OUTPUT=$(createpaymentacct "$CMD")
echo "$CMD_OUTPUT"
printbreak
export ALICE_ACCT_ID=$(getnewpaymentacctid "$CMD_OUTPUT")
export CURRENCY_CODE=$(getnewpaymentacctcurrency "$CMD_OUTPUT")
printdate "Alice's F2F payment-account-id: $ALICE_ACCT_ID, currency-code: $CURRENCY_CODE"
exitoncommandalert $?
printbreak
printdate "Bob creates his F2F payment account."
CMD="$CLI_BASE --port=$BOB_PORT createpaymentacct --payment-account-form=$APITEST_SCRIPTS_HOME/$F2F_ACCT_FORM"
printdate "BOB CLI: $CMD"
CMD_OUTPUT=$(createpaymentacct "$CMD")
echo "$CMD_OUTPUT"
printbreak
export BOB_ACCT_ID=$(getnewpaymentacctid "$CMD_OUTPUT")
export CURRENCY_CODE=$(getnewpaymentacctcurrency "$CMD_OUTPUT")
printdate "Bob's F2F payment-account-id: $BOB_ACCT_ID, currency-code: $CURRENCY_CODE"
exitoncommandalert $?
printbreak
while : ; do
printdate "ALICE $ALICE_ROLE: Creating $DIRECTION $CURRENCY_CODE offer with payment acct $ALICE_ACCT_ID."
CURRENT_PRICE=$(getcurrentprice "$ALICE_PORT" "$CURRENCY_CODE")
exitoncommandalert $?
printdate "Current Market Price: $CURRENT_PRICE"
CMD=$(gencreateoffercommand "$ALICE_PORT" "$ALICE_ACCT_ID")
printdate "ALICE CLI: $CMD"
OFFER_ID=$(createoffer "$CMD")
exitoncommandalert $?
printdate "ALICE $ALICE_ROLE: Created offer with id: $OFFER_ID."
printbreak
sleeptraced 3
# Show Alice's new offer.
printdate "ALICE $ALICE_ROLE: 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 3
# Generate some btc blocks.
printdate "Generating btc blocks after publishing Alice's offer."
genbtcblocks 3 2
printbreak
RANDOM_WAIT=$(echo $[$RANDOM % 10 + 1])
printdate "Bob will take Alice's offer in $RANDOM_WAIT seconds..."
sleeptraced "$RANDOM_WAIT"
executetrade
exitoncommandalert $?
printbreak
done
exit 0

View file

@ -8,60 +8,60 @@ export ALICE_PORT=9998
export BOB_PORT=9999
export F2F_ACCT_FORM="f2f-acct.json"
checkos() {
LINUX=FALSE
DARWIN=FALSE
UNAME=$(uname)
case "$UNAME" in
Linux* )
export LINUX=TRUE
;;
Darwin* )
export DARWIN=TRUE
;;
esac
if [[ "$LINUX" == "TRUE" ]]; then
printdate "Running on supported Linux OS."
elif [[ "$DARWIN" == "TRUE" ]]; then
printdate "Running on supported Mac OS."
else
printdate "Script cannot run on $OSTYPE OS, only Linux and OSX are supported."
exit 1
fi
}
checksetup() {
checkos
apitestusage() {
echo "The apitest harness must be running a local bitcoin regtest node, a seednode, arbitration node, and Bob & Alice daemons."
echo "The apitest harness must be running a local bitcoin regtest node, a seednode, an arbitration node,"
echo "Bob & Alice daemons, and bitcoin-core's bitcoin-cli must be in the system PATH."
echo ""
echo "From the project's root dir, start all supporting nodes from a terminal:"
echo "./bisq-apitest --apiPassword=xyz --supportingApps=bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon --shutdownAfterTests=false"
echo ""
echo "Register dispute agents in the arbitration daemon after it initializes."
echo "./bisq-cli --password=xyz --port=9997 registerdisputeagent --dispute-agent-type=mediator \
--registration-key=6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a"
echo "./bisq-cli --password=xyz --port=9997 registerdisputeagent --dispute-agent-type=refundagent \
--registration-key=6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a"
exit 1;
}
printdate "Checking ${APP_HOME} for some expected directories and files."
if [ -d "${APP_HOME}/apitest" ]; then
printdate "Checking $APP_HOME for some expected directories and files."
if [ -d "$APP_HOME/apitest" ]; then
printdate "Subproject apitest exists.";
else
printdate "Error: Subproject apitest not found, maybe because you are not running the script from the project root dir."
exit 1
fi
if [ -f "${APP_HOME}/bisq-cli" ]; then
if [ -f "$APP_HOME/bisq-cli" ]; then
printdate "The bisq-cli script exists.";
else
printdate "Error: The bisq-cli script not found, maybe because you are not running the script from the project root dir."
exit 1
fi
printdate "Checking to see local bitcoind is running."
printdate "Checking to see local bitcoind is running, and bitcoin-cli is in PATH."
checkbitcoindrunning
checkbitcoincliinpath
printdate "Checking to see bisq servers are running."
if pgrep -f "bisq.seednode.SeedNodeMain" > /dev/null ; then
printdate "The seednode is running on host."
else
printdate "Error: seed is not running on host, exiting."
apitestusage
fi
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Arb_dao" > /dev/null ; then
printdate "The arbitration node is running on host."
else
printdate "Error: arbitration node is not running on host, exiting."
apitestusage
fi
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Alice_dao" > /dev/null ; then
printdate "Alice's daemon node is running on host."
else
printdate "Error: Alice's daemon node is not running on host, exiting."
apitestusage
fi
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Bob_dao" > /dev/null ; then
printdate "Bob's daemon node is running on host."
else
printdate "Error: Bob's daemon node is not running on host, exiting."
apitestusage
fi
checkseednoderunning
checkarbnoderunning
checkalicenoderunning
checkbobnoderunning
}
parseopts() {
@ -181,12 +181,12 @@ parselimitorderopts() {
checkbitcoindrunning() {
# There may be a '+' char in the path and we have to escape it for pgrep.
if [[ ${APP_HOME} == *"+"* ]]; then
ESCAPED_APP_HOME=$(escapepluschar "${APP_HOME}")
if [[ $APP_HOME == *"+"* ]]; then
ESCAPED_APP_HOME=$(escapepluschar "$APP_HOME")
else
ESCAPED_APP_HOME="${APP_HOME}"
ESCAPED_APP_HOME="$APP_HOME"
fi
if pgrep -f "bitcoind -datadir=${ESCAPED_APP_HOME}/apitest/build/resources/main/Bitcoin-regtest" > /dev/null ; then
if pgrep -f "bitcoind -datadir=$ESCAPED_APP_HOME/apitest/build/resources/main/Bitcoin-regtest" > /dev/null ; then
printdate "The regtest bitcoind node is running on host."
else
printdate "Error: regtest bitcoind node is not running on host, exiting."
@ -194,26 +194,119 @@ checkbitcoindrunning() {
fi
}
checkbitcoincliinpath() {
if which bitcoin-cli > /dev/null ; then
printdate "The bitcoin-cli binary is in the system PATH."
else
printdate "Error: bitcoin-cli binary is not in the system PATH, exiting."
apitestusage
fi
}
checkseednoderunning() {
if [[ "$LINUX" == "TRUE" ]]; then
if pgrep -f "bisq.seednode.SeedNodeMain" > /dev/null ; then
printdate "The seed node is running on host."
else
printdate "Error: seed node is not running on host, exiting."
apitestusage
fi
elif [[ "$DARWIN" == "TRUE" ]]; then
if ps -A | awk '/[S]eedNodeMain/ {print $1}' > /dev/null ; then
printdate "The seednode is running on host."
else
printdate "Error: seed node is not running on host, exiting."
apitestusage
fi
else
printdate "Error: seed node is not running on host, exiting."
apitestusage
fi
}
checkarbnoderunning() {
if [[ "$LINUX" == "TRUE" ]]; then
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Arb_dao" > /dev/null ; then
printdate "The arbitration node is running on host."
else
printdate "Error: arbitration node is not running on host, exiting."
apitestusage
fi
elif [[ "$DARWIN" == "TRUE" ]]; then
if ps -A | awk '/[b]isq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Arb_dao/ {print $1}' > /dev/null ; then
printdate "The arbitration node is running on host."
else
printdate "Error: arbitration node is not running on host, exiting."
apitestusage
fi
else
printdate "Error: arbitration node is not running on host, exiting."
apitestusage
fi
}
checkalicenoderunning() {
if [[ "$LINUX" == "TRUE" ]]; then
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Alice_dao" > /dev/null ; then
printdate "Alice's node is running on host."
else
printdate "Error: Alice's node is not running on host, exiting."
apitestusage
fi
elif [[ "$DARWIN" == "TRUE" ]]; then
if ps -A | awk '/[b]isq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Alice_dao/ {print $1}' > /dev/null ; then
printdate "Alice's node node is running on host."
else
printdate "Error: Alice's node is not running on host, exiting."
apitestusage
fi
else
printdate "Error: Alice's node is not running on host, exiting."
apitestusage
fi
}
checkbobnoderunning() {
if [[ "$LINUX" == "TRUE" ]]; then
if pgrep -f "bisq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Alice_dao" > /dev/null ; then
printdate "Bob's node is running on host."
else
printdate "Error: Bob's node is not running on host, exiting."
apitestusage
fi
elif [[ "$DARWIN" == "TRUE" ]]; then
if ps -A | awk '/[b]isq.daemon.app.BisqDaemonMain --appName=bisq-BTC_REGTEST_Alice_dao/ {print $1}' > /dev/null ; then
printdate "Bob's node node is running on host."
else
printdate "Error: Bob's node is not running on host, exiting."
apitestusage
fi
else
printdate "Error: Bob's node is not running on host, exiting."
apitestusage
fi
}
printscriptparams() {
if [ -n "${LIMIT_PRICE+1}" ]; then
echo " LIMIT_PRICE = ${LIMIT_PRICE}"
echo " LIMIT_PRICE = $LIMIT_PRICE"
fi
echo " DIRECTION = ${DIRECTION}"
echo " COUNTRY_CODE = ${COUNTRY_CODE}"
echo " FIXED_PRICE = ${FIXED_PRICE}"
echo " MKT_PRICE_MARGIN = ${MKT_PRICE_MARGIN}"
echo " AMOUNT = ${AMOUNT}"
echo " DIRECTION = $DIRECTION"
echo " COUNTRY_CODE = $COUNTRY_CODE"
echo " FIXED_PRICE = $FIXED_PRICE"
echo " MKT_PRICE_MARGIN = $MKT_PRICE_MARGIN"
echo " AMOUNT = $AMOUNT"
if [ -n "${BOB_ROLE+1}" ]; then
echo " BOB_ROLE = ${BOB_ROLE}"
echo " BOB_ROLE = $BOB_ROLE"
fi
if [ -n "${ALICE_ROLE+1}" ]; then
echo " ALICE_ROLE = ${ALICE_ROLE}"
echo " ALICE_ROLE = $ALICE_ROLE"
fi
if [ -n "${WAIT+1}" ]; then
echo " WAIT = ${WAIT}"
echo " WAIT = $WAIT"
fi
}

View file

@ -2,7 +2,7 @@
# This file must be sourced by the main driver.
source "${APITEST_SCRIPTS_HOME}/trade-simulation-env.sh"
source "$APITEST_SCRIPTS_HOME/trade-simulation-env.sh"
printdate() {
echo "[$(date)] $@"
@ -46,10 +46,10 @@ exitoncommandalert() {
registerdisputeagents() {
# Silently register dev dispute agents. It's easy to forget.
REG_KEY="6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a"
CMD="${CLI_BASE} --port=${ARBITRATOR_PORT} registerdisputeagent --dispute-agent-type=mediator --registration-key=${REG_KEY}"
CMD="$CLI_BASE --port=$ARBITRATOR_PORT registerdisputeagent --dispute-agent-type=mediator --registration-key=$REG_KEY"
SILENT=$($CMD)
commandalert $? "Could not register dev/test mediator."
CMD="${CLI_BASE} --port=${ARBITRATOR_PORT} registerdisputeagent --dispute-agent-type=refundagent --registration-key=${REG_KEY}"
CMD="$CLI_BASE --port=$ARBITRATOR_PORT registerdisputeagent --dispute-agent-type=refundagent --registration-key=$REG_KEY"
SILENT=$($CMD)
commandalert $? "Could not register dev/test refundagent."
# Do something with $SILENT to keep codacy happy.
@ -59,7 +59,7 @@ registerdisputeagents() {
getbtcoreaddress() {
CMD="bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest -rpcpassword=apitest getnewaddress"
NEW_ADDRESS=$($CMD)
echo "${NEW_ADDRESS}"
echo "$NEW_ADDRESS"
}
genbtcblocks() {
@ -74,7 +74,7 @@ genbtcblocks() {
for i in $(seq -f "%02g" 1 "$NUM_BLOCKS")
do
NEW_BLOCK_HASH=$(genbtcblock "$CMD")
printdate "Block Hash #$i:${NEW_BLOCK_HASH}"
printdate "Block Hash #$i:$NEW_BLOCK_HASH"
sleep "$SECONDS_BETWEEN_BLOCKS"
done
}
@ -88,12 +88,12 @@ genbtcblock() {
escapepluschar() {
STRING="$1"
NEW_STRING=$(echo "${STRING//+/\\+}")
echo "${NEW_STRING}"
echo "$NEW_STRING"
}
printbalances() {
PORT="$1"
printcmd "${CLI_BASE} --port=${PORT} getbalance"
printcmd "$CLI_BASE --port=$PORT getbalance"
$CLI_BASE --port="$PORT" getbalance
}
@ -102,54 +102,99 @@ getpaymentaccountmethods() {
CMD_OUTPUT=$($CMD)
commandalert $? "Could not get payment method ids."
printdate "Payment Method IDs:"
echo "${CMD_OUTPUT}"
echo "$CMD_OUTPUT"
}
getpaymentaccountform() {
CMD="$1"
CMD_OUTPUT=$($CMD)
commandalert $? "Could not get new payment account form."
echo "${CMD_OUTPUT}"
echo "$CMD_OUTPUT"
}
editpaymentaccountform() {
COUNTRY_CODE="$1"
CMD="python3 ${APITEST_SCRIPTS_HOME}/editf2faccountform.py $COUNTRY_CODE"
CMD="python3 $APITEST_SCRIPTS_HOME/editf2faccountform.py $COUNTRY_CODE"
CMD_OUTPUT=$($CMD)
commandalert $? "Could not edit payment account form."
printdate "Saved payment account form as ${F2F_ACCT_FORM}."
printdate "Saved payment account form as $F2F_ACCT_FORM."
}
getnewpaymentacctid() {
CREATE_PAYMENT_ACCT_OUTPUT="$1"
PAYMENT_ACCT_DETAIL=$(echo -e "${CREATE_PAYMENT_ACCT_OUTPUT}" | sed -n '3p')
PAYMENT_ACCT_DETAIL=$(echo -e "$CREATE_PAYMENT_ACCT_OUTPUT" | sed -n '3p')
ACCT_ID=$(echo -e "$PAYMENT_ACCT_DETAIL" | awk '{print $NF}')
echo "${ACCT_ID}"
echo "$ACCT_ID"
}
getnewpaymentacctcurrency() {
CREATE_PAYMENT_ACCT_OUTPUT="$1"
PAYMENT_ACCT_DETAIL=$(echo -e "${CREATE_PAYMENT_ACCT_OUTPUT}" | sed -n '3p')
PAYMENT_ACCT_DETAIL=$(echo -e "$CREATE_PAYMENT_ACCT_OUTPUT" | sed -n '3p')
# This is brittle; it requires the account name field to have N words,
# e.g, "Face to Face Payment Account" as defined in editf2faccountform.py.
CURRENCY_CODE=$(echo -e "$PAYMENT_ACCT_DETAIL" | awk '{print $6}')
echo "${CURRENCY_CODE}"
echo "$CURRENCY_CODE"
}
createpaymentacct() {
CMD="$1"
CMD_OUTPUT=$($CMD)
commandalert $? "Could not create new payment account."
echo "${CMD_OUTPUT}"
echo "$CMD_OUTPUT"
}
getpaymentaccounts() {
PORT="$1"
printcmd "${CLI_BASE} --port=${PORT} getpaymentaccts"
printcmd "$CLI_BASE --port=$PORT getpaymentaccts"
CMD="$CLI_BASE --port=$PORT getpaymentaccts"
CMD_OUTPUT=$($CMD)
commandalert $? "Could not get payment accounts."
echo "${CMD_OUTPUT}"
echo "$CMD_OUTPUT"
}
showcreatepaymentacctsteps() {
USER="$1"
PORT="$2"
printdate "$USER looks for the ID of the face to face payment account method (Bob will use same payment method)."
CMD="$CLI_BASE --port=$PORT getpaymentmethods"
printdate "$USER CLI: $CMD"
PAYMENT_ACCT_METHODS=$(getpaymentaccountmethods "$CMD")
echo "$PAYMENT_ACCT_METHODS"
printbreak
printdate "$USER uses the F2F payment method id to create a face to face payment account in country $COUNTRY_CODE."
CMD="$CLI_BASE --port=$PORT getpaymentacctform --payment-method-id=F2F"
printdate "$USER CLI: $CMD"
getpaymentaccountform "$CMD"
printbreak
printdate "$USER edits the $COUNTRY_CODE payment account form, and (optionally) renames it as $F2F_ACCT_FORM"
editpaymentaccountform "$COUNTRY_CODE"
cat "$APITEST_SCRIPTS_HOME/$F2F_ACCT_FORM"
# Remove the autogenerated json template because we are going to use one created by a python script in the next step.
CMD="rm -v $APP_HOME/f2f_*.json"
DELETE_JSON_TEMPLATE=$($CMD)
printdate "$DELETE_JSON_TEMPLATE"
printbreak
}
gencreateoffercommand() {
PORT="$1"
ACCT_ID="$2"
CMD="$CLI_BASE --port=$PORT createoffer"
CMD+=" --payment-account=$ACCT_ID"
CMD+=" --direction=$DIRECTION"
CMD+=" --currency-code=$CURRENCY_CODE"
CMD+=" --amount=$AMOUNT"
if [ -z "$MKT_PRICE_MARGIN" ]; then
CMD+=" --fixed-price=$FIXED_PRICE"
else
CMD+=" --market-price-margin=$MKT_PRICE_MARGIN"
fi
CMD+=" --security-deposit=15.0"
CMD+=" --fee-currency=BSQ"
echo "$CMD"
}
createoffer() {
@ -160,9 +205,388 @@ createoffer() {
# 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}"
OFFER_DETAIL=$(echo -e "$OFFER_DESC" | sed -n '2p')
NEW_OFFER_ID=$(echo -e "$OFFER_DETAIL" | awk '{print $NF}')
echo "$NEW_OFFER_ID"
}
getfirstofferid() {
PORT="$1"
CMD="$CLI_BASE --port=$PORT getoffers --direction=$DIRECTION --currency-code=$CURRENCY_CODE"
CMD_OUTPUT=$($CMD)
commandalert $? "Could not get current $DIRECTION / $CURRENCY_CODE offers."
FIRST_OFFER_DETAIL=$(echo -e "$CMD_OUTPUT" | sed -n '2p')
FIRST_OFFER_ID=$(echo -e "$FIRST_OFFER_DETAIL" | awk '{print $NF}')
commandalert $? "Could parse the offer-id from the first listed offer."
echo "$FIRST_OFFER_ID"
}
gettrade() {
GET_TRADE_CMD="$1"
TRADE_DESC=$($GET_TRADE_CMD)
commandalert $? "Could not get trade."
echo "$TRADE_DESC"
}
gettradedetail() {
TRADE_DESC="$1"
# Get 2nd line of gettrade cmd output, and squeeze multi space delimiters into one space.
TRADE_DETAIL=$(echo "$TRADE_DESC" | sed -n '2p' | tr -s ' ')
commandalert $? "Could not get trade detail (line 2 of gettrade output)."
echo "$TRADE_DETAIL"
}
istradedepositpublished() {
TRADE_DETAIL="$1"
MAKER_OR_TAKER="$2"
if [ "$MAKER_OR_TAKER" = "MAKER" ]
then
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $9}')
else
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $10}')
fi
commandalert $? "Could not parse istradedepositpublished from trade detail."
echo "$ANSWER"
}
istradedepositconfirmed() {
TRADE_DETAIL="$1"
MAKER_OR_TAKER="$2"
if [ "$MAKER_OR_TAKER" = "MAKER" ]
then
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $10}')
else
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $11}')
fi
commandalert $? "Could not parse istradedepositconfirmed from trade detail."
echo "$ANSWER"
}
istradepaymentsent() {
TRADE_DETAIL="$1"
MAKER_OR_TAKER="$2"
if [ "$MAKER_OR_TAKER" = "MAKER" ]
then
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $11}')
else
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $12}')
fi
commandalert $? "Could not parse istradepaymentsent from trade detail."
echo "$ANSWER"
}
istradepaymentreceived() {
TRADE_DETAIL="$1"
MAKER_OR_TAKER="$2"
if [ "$MAKER_OR_TAKER" = "MAKER" ]
then
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $12}')
else
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $13}')
fi
commandalert $? "Could not parse istradepaymentreceived from trade detail."
echo "$ANSWER"
}
istradepayoutpublished() {
TRADE_DETAIL="$1"
MAKER_OR_TAKER="$2"
if [ "$MAKER_OR_TAKER" = "MAKER" ]
then
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $13}')
else
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $14}')
fi
commandalert $? "Could not parse istradepayoutpublished from trade detail."
echo "$ANSWER"
}
waitfortradedepositpublished() {
# Loops until Bob's trade deposit is published. (Bob is always the trade taker.)
OFFER_ID="$1"
DONE=0
while : ; do
if [ "$DONE" -ne 0 ]; then
break
fi
printdate "BOB $BOB_ROLE: 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
TRADE_DETAIL=$(gettradedetail "$GETTRADE_CMD_OUTPUT")
exitoncommandalert $?
IS_TRADE_DEPOSIT_PUBLISHED=$(istradedepositpublished "$TRADE_DETAIL" "TAKER")
exitoncommandalert $?
printdate "BOB $BOB_ROLE: Has taker's trade deposit been published? $IS_TRADE_DEPOSIT_PUBLISHED"
if [ "$IS_TRADE_DEPOSIT_PUBLISHED" = "YES" ]
then
DONE=1
else
RANDOM_WAIT=$(echo $[$RANDOM % 3 + 1])
sleeptraced "$RANDOM_WAIT"
fi
printbreak
done
}
waitfortradedepositconfirmed() {
# Loops until Bob's trade deposit is confirmed. (Bob is always the trade taker.)
OFFER_ID="$1"
DONE=0
while : ; do
if [ "$DONE" -ne 0 ]; then
break
fi
printdate "BOB $BOB_ROLE: 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
TRADE_DETAIL=$(gettradedetail "$GETTRADE_CMD_OUTPUT")
exitoncommandalert $?
IS_TRADE_DEPOSIT_CONFIRMED=$(istradedepositconfirmed "$TRADE_DETAIL" "TAKER")
exitoncommandalert $?
printdate "BOB $BOB_ROLE: Has taker's trade deposit been confirmed? $IS_TRADE_DEPOSIT_CONFIRMED"
printbreak
if [ "$IS_TRADE_DEPOSIT_CONFIRMED" = "YES" ]
then
DONE=1
else
printdate "Generating btc block while Bob waits for trade deposit to be confirmed."
genbtcblocks 1 0
RANDOM_WAIT=$(echo $[$RANDOM % 3 + 1])
sleeptraced "$RANDOM_WAIT"
fi
done
}
waitfortradepaymentsent() {
# Loops until buyer's trade payment has been sent.
PORT="$1"
SELLER="$2"
OFFER_ID="$3"
MAKER_OR_TAKER="$4"
DONE=0
while : ; do
if [ "$DONE" -ne 0 ]; then
break
fi
printdate "$SELLER: Looking at trade with id $OFFER_ID."
CMD="$CLI_BASE --port=$PORT gettrade --trade-id=$OFFER_ID"
printdate "$SELLER CLI: $CMD"
GETTRADE_CMD_OUTPUT=$(gettrade "$CMD")
exitoncommandalert $?
echo "$GETTRADE_CMD_OUTPUT"
printbreak
TRADE_DETAIL=$(gettradedetail "$GETTRADE_CMD_OUTPUT")
exitoncommandalert $?
IS_TRADE_PAYMENT_SENT=$(istradepaymentsent "$TRADE_DETAIL" "$MAKER_OR_TAKER")
exitoncommandalert $?
printdate "$SELLER: Has buyer's fiat payment been initiated? $IS_TRADE_PAYMENT_SENT"
if [ "$IS_TRADE_PAYMENT_SENT" = "YES" ]
then
DONE=1
else
RANDOM_WAIT=$(echo $[$RANDOM % 3 + 1])
sleeptraced "$RANDOM_WAIT"
fi
printbreak
done
}
waitfortradepaymentreceived() {
# Loops until buyer's trade payment has been received.
PORT="$1"
SELLER="$2"
OFFER_ID="$3"
MAKER_OR_TAKER="$4"
DONE=0
while : ; do
if [ "$DONE" -ne 0 ]; then
break
fi
printdate "$SELLER: Looking at trade with id $OFFER_ID."
CMD="$CLI_BASE --port=$PORT gettrade --trade-id=$OFFER_ID"
printdate "$SELLER CLI: $CMD"
GETTRADE_CMD_OUTPUT=$(gettrade "$CMD")
exitoncommandalert $?
echo "$GETTRADE_CMD_OUTPUT"
printbreak
TRADE_DETAIL=$(gettradedetail "$GETTRADE_CMD_OUTPUT")
exitoncommandalert $?
# When the seller receives a 'payment sent' message, it is assumed funds (fiat) have already been deposited.
# In a real trade, there is usually a delay between receipt of a 'payment sent' message, and the funds deposit,
# but we do not need to simulate that in this regtest script.
IS_TRADE_PAYMENT_SENT=$(istradepaymentreceived "$TRADE_DETAIL" "$MAKER_OR_TAKER")
exitoncommandalert $?
printdate "$SELLER: Has buyer's payment been transferred to seller's fiat account? $IS_TRADE_PAYMENT_SENT"
if [ "$IS_TRADE_PAYMENT_SENT" = "YES" ]
then
DONE=1
else
RANDOM_WAIT=$(echo $[$RANDOM % 3 + 1])
sleeptraced "$RANDOM_WAIT"
fi
printbreak
done
}
delayconfirmpaymentstarted() {
# Confirm payment started after a random delay. This should be run in the background
# while the payee polls the trade status, waiting for the message before confirming
# payment has been received.
PAYER="$1"
PORT="$2"
OFFER_ID="$3"
RANDOM_WAIT=$(echo $[$RANDOM % 5 + 1])
printdate "$PAYER: Sending fiat payment sent message to seller in $RANDOM_WAIT seconds..."
sleeptraced "$RANDOM_WAIT"
CMD="$CLI_BASE --port=$PORT confirmpaymentstarted --trade-id=$OFFER_ID"
printdate "$PAYER_CLI: $CMD"
SENT_MSG=$($CMD)
commandalert $? "Could not send confirmpaymentstarted message."
# Print the confirmpaymentstarted command's console output.
printdate "$SENT_MSG"
printbreak
}
delayconfirmpaymentreceived() {
# Confirm payment received after a random delay. This should be run in the background
# while the payer polls the trade status, waiting for the confirmation from the seller
# that funds have been received.
PAYEE="$1"
PORT="$2"
OFFER_ID="$3"
RANDOM_WAIT=$(echo $[$RANDOM % 5 + 1])
printdate "$PAYEE: Sending fiat payment sent message to seller in $RANDOM_WAIT seconds..."
sleeptraced "$RANDOM_WAIT"
CMD="$CLI_BASE --port=$PORT confirmpaymentreceived --trade-id=$OFFER_ID"
printdate "$PAYEE_CLI: $CMD"
RCVD_MSG=$($CMD)
commandalert $? "Could not send confirmpaymentstarted message."
# Print the confirmpaymentstarted command's console output.
printdate "$RCVD_MSG"
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 atomic 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.
executetrade() {
# Bob list available offers.
printdate "BOB $BOB_ROLE: 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 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"
printdate "BOB CLI: $CMD"
TRADE=$($CMD)
commandalert $? "Could not take offer."
# Print the takeoffer command's console output.
printdate "$TRADE"
printbreak
waitfortradedepositpublished "$OFFER_ID"
waitfortradedepositconfirmed "$OFFER_ID"
# Send payment sent and received messages.
if [ "$DIRECTION" = "BUY" ]
then
PAYER="ALICE $ALICE_ROLE"
PAYER_PORT=$ALICE_PORT
PAYER_CLI="ALICE CLI"
PAYEE="BOB $BOB_ROLE"
PAYEE_PORT=$BOB_PORT
PAYEE_CLI="BOB CLI"
else
PAYER="BOB $BOB_ROLE"
PAYER_PORT=$BOB_PORT
PAYER_CLI="BOB CLI"
PAYEE="ALICE $ALICE_ROLE"
PAYEE_PORT=$ALICE_PORT
PAYEE_CLI="ALICE CLI"
fi
# Asynchronously send a confirm payment started message after a random delay.
delayconfirmpaymentstarted "$PAYER" "$PAYER_PORT" "$OFFER_ID" &
if [ "$DIRECTION" = "BUY" ]
then
# Bob waits for payment, polling status in taker specific trade detail.
waitfortradepaymentsent "$PAYEE_PORT" "$PAYEE" "$OFFER_ID" "TAKER"
else
# Alice waits for payment, polling status in maker specific trade detail.
waitfortradepaymentsent "$PAYEE_PORT" "$PAYEE" "$OFFER_ID" "MAKER"
fi
# Asynchronously send a confirm payment received message after a random delay.
delayconfirmpaymentreceived "$PAYEE" "$PAYEE_PORT" "$OFFER_ID" &
if [ "$DIRECTION" = "BUY" ]
then
# Alice waits for payment rcvd confirm from Bob, polling status in maker specific trade detail.
waitfortradepaymentreceived "$PAYER_PORT" "$PAYER" "$OFFER_ID" "MAKER"
else
# Bob waits for payment rcvd confirm from Alice, polling status in taker specific trade detail.
waitfortradepaymentreceived "$PAYER_PORT" "$PAYER" "$OFFER_ID" "TAKER"
fi
# Generate some btc blocks
printdate "Generating btc blocks after fiat transfer."
genbtcblocks 2 2
printbreak
# Complete the trade on the seller side.
if [ "$DIRECTION" = "BUY" ]
then
printdate "BOB $BOB_ROLE: Closing trade by keeping funds in Bisq wallet."
CMD="$CLI_BASE --port=$BOB_PORT keepfunds --trade-id=$OFFER_ID"
printdate "BOB CLI: $CMD"
else
printdate "ALICE (taker): Closing trade by keeping funds in Bisq wallet."
CMD="$CLI_BASE --port=$ALICE_PORT keepfunds --trade-id=$OFFER_ID"
printdate "ALICE CLI: $CMD"
fi
KEEP_FUNDS_MSG=$($CMD)
commandalert $? "Could close trade with keepfunds command."
# Print the keepfunds command's console output.
printdate "$KEEP_FUNDS_MSG"
sleeptraced 3
printbreak
printdate "Trade $OFFER_ID complete."
}
getcurrentprice() {

View file

@ -41,191 +41,77 @@
export APP_BASE_NAME=$(basename "$0")
export APP_HOME=$(pwd -P)
export APITEST_SCRIPTS_HOME="${APP_HOME}/apitest/scripts"
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/trade-simulation-env.sh"
source "$APITEST_SCRIPTS_HOME/trade-simulation-utils.sh"
checksetup
parseopts "$@"
printdate "Started ${APP_BASE_NAME} with parameters:"
printdate "Started $APP_BASE_NAME with parameters:"
printscriptparams
printbreak
registerdisputeagents
printdate "Alice looks for the ID of the face to face payment account method (Bob will use same payment method)."
CMD="${CLI_BASE} --port=${ALICE_PORT} getpaymentmethods"
printdate "ALICE CLI: ${CMD}"
getpaymentaccountmethods "$CMD"
# Demonstrate how to create a country based, face to face account.
showcreatepaymentacctsteps "Alice" "$ALICE_PORT"
CMD="$CLI_BASE --port=$ALICE_PORT createpaymentacct --payment-account-form=$APITEST_SCRIPTS_HOME/$F2F_ACCT_FORM"
printdate "ALICE CLI: $CMD"
CMD_OUTPUT=$(createpaymentacct "$CMD")
echo "$CMD_OUTPUT"
printbreak
export ALICE_ACCT_ID=$(getnewpaymentacctid "$CMD_OUTPUT")
export CURRENCY_CODE=$(getnewpaymentacctcurrency "$CMD_OUTPUT")
printdate "Alice's F2F payment-account-id: $ALICE_ACCT_ID, currency-code: $CURRENCY_CODE"
exitoncommandalert $?
printbreak
printdate "Alice uses the F2F payment method id to create a face to face payment account in country ${COUNTRY_CODE}."
CMD="${CLI_BASE} --port=${ALICE_PORT} getpaymentacctform --payment-method-id=F2F"
printdate "ALICE CLI: ${CMD}"
getpaymentaccountform "$CMD"
printdate "Bob creates his F2F payment account."
CMD="$CLI_BASE --port=$BOB_PORT createpaymentacct --payment-account-form=$APITEST_SCRIPTS_HOME/$F2F_ACCT_FORM"
printdate "BOB CLI: $CMD"
CMD_OUTPUT=$(createpaymentacct "$CMD")
echo "$CMD_OUTPUT"
printbreak
export BOB_ACCT_ID=$(getnewpaymentacctid "$CMD_OUTPUT")
export CURRENCY_CODE=$(getnewpaymentacctcurrency "$CMD_OUTPUT")
printdate "Bob's F2F payment-account-id: $BOB_ACCT_ID, currency-code: $CURRENCY_CODE"
exitoncommandalert $?
printbreak
printdate "Bob & Alice edit their ${COUNTRY_CODE} payment account forms, and renames them to ${F2F_ACCT_FORM}"
editpaymentaccountform "$COUNTRY_CODE"
cat "${APITEST_SCRIPTS_HOME}/${F2F_ACCT_FORM}"
# Remove the autogenerated json template because we are going to use one created by a python script in the next step.
CMD="rm -v ${APP_HOME}/f2f_*.json"
DELETE_JSON_TEMPLATE=$($CMD)
printdate "$DELETE_JSON_TEMPLATE"
printbreak
printdate "Bob and Alice create their face to face ${COUNTRY_CODE} payment accounts."
CMD="${CLI_BASE} --port=${BOB_PORT} createpaymentacct --payment-account-form=${APITEST_SCRIPTS_HOME}/${F2F_ACCT_FORM}"
printdate "BOB CLI: ${CMD}"
CMD_OUTPUT=$(createpaymentacct "${CMD}")
echo "${CMD_OUTPUT}"
BOB_ACCT_ID=$(getnewpaymentacctid "${CMD_OUTPUT}")
BOB_ACCT_CURRENCY_CODE=$(getnewpaymentacctcurrency "${CMD_OUTPUT}")
printdate "BOB F2F payment-account-id = ${BOB_ACCT_ID}, currency-code = ${BOB_ACCT_CURRENCY_CODE}."
printbreak
CMD="${CLI_BASE} --port=${ALICE_PORT} createpaymentacct --payment-account-form=${APITEST_SCRIPTS_HOME}/${F2F_ACCT_FORM}"
printdate "ALICE CLI: ${CMD}"
CMD_OUTPUT=$(createpaymentacct "${CMD}")
echo "${CMD_OUTPUT}"
ALICE_ACCT_ID=$(getnewpaymentacctid "${CMD_OUTPUT}")
ALICE_ACCT_CURRENCY_CODE=$(getnewpaymentacctcurrency "${CMD_OUTPUT}")
printdate "ALICE F2F payment-account-id = ${ALICE_ACCT_ID}, currency-code = ${ALICE_ACCT_CURRENCY_CODE}."
printbreak
printdate "ALICE ${ALICE_ROLE}: Creating ${DIRECTION} ${ALICE_ACCT_CURRENCY_CODE} offer with payment acct ${ALICE_ACCT_ID}."
CURRENT_PRICE=$(getcurrentprice "$ALICE_PORT" "$ALICE_ACCT_CURRENCY_CODE")
# Alice creates an offer.
printdate "ALICE $ALICE_ROLE: Creating $DIRECTION $CURRENCY_CODE offer with payment acct $ALICE_ACCT_ID."
CURRENT_PRICE=$(getcurrentprice "$ALICE_PORT" "$CURRENCY_CODE")
exitoncommandalert $?
printdate "Current Market Price: $CURRENT_PRICE"
CMD="$CLI_BASE --port=${ALICE_PORT} createoffer"
CMD+=" --payment-account=${ALICE_ACCT_ID}"
CMD+=" --direction=${DIRECTION}"
CMD+=" --currency-code=${ALICE_ACCT_CURRENCY_CODE}"
CMD+=" --amount=${AMOUNT}"
if [ -z "${MKT_PRICE_MARGIN}" ]; then
CMD+=" --fixed-price=${FIXED_PRICE}"
else
CMD+=" --market-price-margin=${MKT_PRICE_MARGIN}"
fi
CMD+=" --security-deposit=15.0"
CMD+=" --fee-currency=BSQ"
printdate "ALICE CLI: ${CMD}"
OFFER_ID=$(createoffer "${CMD}")
CMD=$(gencreateoffercommand "$ALICE_PORT" "$ALICE_ACCT_ID")
printdate "ALICE CLI: $CMD"
OFFER_ID=$(createoffer "$CMD")
exitoncommandalert $?
printdate "ALICE ${ALICE_ROLE}: Created offer with id: ${OFFER_ID}."
printdate "ALICE $ALICE_ROLE: Created offer with id: $OFFER_ID."
printbreak
sleeptraced 3
# Show Alice's new offer.
printdate "ALICE ${ALICE_ROLE}: Looking at her new ${DIRECTION} ${CURRENCY_CODE} offer."
CMD="$CLI_BASE --port=${ALICE_PORT} getmyoffer --offer-id=${OFFER_ID}"
printdate "ALICE CLI: ${CMD}"
printdate "ALICE $ALICE_ROLE: 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}"
echo "$OFFER"
printbreak
sleeptraced 7
sleeptraced 3
# Generate some btc blocks.
printdate "Generating btc blocks after publishing Alice's offer."
genbtcblocks 3 5
genbtcblocks 3 1
printbreak
sleeptraced 10
# List offers.
printdate "BOB ${BOB_ROLE}: Looking at ${DIRECTION} ${BOB_ACCT_CURRENCY_CODE} offers."
CMD="$CLI_BASE --port=${BOB_PORT} getoffers --direction=${DIRECTION} --currency-code=${BOB_ACCT_CURRENCY_CODE}"
printdate "BOB CLI: ${CMD}"
OFFERS=$($CMD)
# Go through the trade protocol.
executetrade
exitoncommandalert $?
echo "${OFFERS}"
printbreak
sleeptraced 3
# Take offer.
printdate "BOB ${BOB_ROLE}: Taking offer ${OFFER_ID} with payment acct ${BOB_ACCT_ID}."
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."
echo "${TRADE}"
printbreak
sleeptraced 10
# Generating some btc blocks
printdate "Generating btc blocks after Bob takes Alice's offer."
genbtcblocks 3 3
printbreak
sleeptraced 6
# Send payment sent and received messages.
if [ "${DIRECTION}" = "BUY" ]
then
PAYER="ALICE ${ALICE_ROLE}"
PAYER_PORT=${ALICE_PORT}
PAYER_CLI="ALICE CLI"
PAYEE="BOB ${BOB_ROLE}"
PAYEE_PORT=${BOB_PORT}
PAYEE_CLI="BOB CLI"
else
PAYER="BOB ${BOB_ROLE}"
PAYER_PORT=${BOB_PORT}
PAYER_CLI="BOB CLI"
PAYEE="ALICE ${ALICE_ROLE}"
PAYEE_PORT=${ALICE_PORT}
PAYEE_CLI="ALICE CLI"
fi
# Confirm payment started.
printdate "${PAYER}: Sending fiat payment sent msg."
CMD="$CLI_BASE --port=${PAYER_PORT} confirmpaymentstarted --trade-id=${OFFER_ID}"
printdate "${PAYER_CLI}: ${CMD}"
SENT_MSG=$($CMD)
commandalert $? "Could not send confirmpaymentstarted message."
printdate "${SENT_MSG}"
printbreak
sleeptraced 2
printdate "Generating btc blocks after fiat payment sent msg."
genbtcblocks 3 5
sleeptraced 2
# Confirm payment received.
printdate "${PAYEE}: Sending fiat payment received msg."
CMD="$CLI_BASE --port=${PAYEE_PORT} confirmpaymentreceived --trade-id=${OFFER_ID}"
printdate "${PAYEE_CLI}: ${CMD}"
RCVD_MSG=$($CMD)
commandalert $? "Could not send confirmpaymentreceived message."
printdate "${RCVD_MSG}"
printbreak
sleeptraced 4
# Generate some btc blocks
printdate "Generating btc blocks after fiat transfer."
genbtcblocks 3 5
printbreak
sleeptraced 3
# Complete the trade on the seller side.
if [ "${DIRECTION}" = "BUY" ]
then
printdate "BOB ${BOB_ROLE}: Closing trade by keeping funds in Bisq wallet."
CMD="$CLI_BASE --port=${BOB_PORT} keepfunds --trade-id=${OFFER_ID}"
printdate "BOB CLI: ${CMD}"
else
printdate "ALICE (taker): Closing trade by keeping funds in Bisq wallet."
CMD="$CLI_BASE --port=${ALICE_PORT} keepfunds --trade-id=${OFFER_ID}"
printdate "ALICE CLI: ${CMD}"
fi
KEEP_FUNDS_MSG=$($CMD)
commandalert $? "Could close trade with keepfunds command."
printdate "${KEEP_FUNDS_MSG}"
sleeptraced 5
printbreak
# Get balances after trade completion.

View file

@ -55,7 +55,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.apitest.config.ApiTestConfig;
import bisq.apitest.config.BisqAppConfig;
import bisq.apitest.linux.BashCommand;
import bisq.apitest.linux.BisqApp;
import bisq.apitest.linux.BisqProcess;
import bisq.apitest.linux.BitcoinDaemon;
import bisq.apitest.linux.LinuxProcess;
@ -367,25 +367,25 @@ public class Scaffold {
CountDownLatch countdownLatch)
throws IOException, InterruptedException {
BisqApp bisqApp = createBisqApp(bisqAppConfig);
BisqProcess bisqProcess = createBisqProcess(bisqAppConfig);
switch (bisqAppConfig) {
case seednode:
seedNodeTask = new SetupTask(bisqApp, countdownLatch);
seedNodeTask = new SetupTask(bisqProcess, countdownLatch);
seedNodeTaskFuture = executor.submit(seedNodeTask);
break;
case arbdaemon:
case arbdesktop:
arbNodeTask = new SetupTask(bisqApp, countdownLatch);
arbNodeTask = new SetupTask(bisqProcess, countdownLatch);
arbNodeTaskFuture = executor.submit(arbNodeTask);
break;
case alicedaemon:
case alicedesktop:
aliceNodeTask = new SetupTask(bisqApp, countdownLatch);
aliceNodeTask = new SetupTask(bisqProcess, countdownLatch);
aliceNodeTaskFuture = executor.submit(aliceNodeTask);
break;
case bobdaemon:
case bobdesktop:
bobNodeTask = new SetupTask(bisqApp, countdownLatch);
bobNodeTask = new SetupTask(bisqProcess, countdownLatch);
bobNodeTaskFuture = executor.submit(bobNodeTask);
break;
default:
@ -393,18 +393,18 @@ public class Scaffold {
}
log.info("Giving {} ms for {} to initialize ...", config.bisqAppInitTime, bisqAppConfig.appName);
MILLISECONDS.sleep(config.bisqAppInitTime);
if (bisqApp.hasStartupExceptions()) {
bisqApp.logExceptions(bisqApp.getStartupExceptions(), log);
throw new IllegalStateException(bisqApp.getStartupExceptions().get(0));
if (bisqProcess.hasStartupExceptions()) {
bisqProcess.logExceptions(bisqProcess.getStartupExceptions(), log);
throw new IllegalStateException(bisqProcess.getStartupExceptions().get(0));
}
}
private BisqApp createBisqApp(BisqAppConfig bisqAppConfig)
private BisqProcess createBisqProcess(BisqAppConfig bisqAppConfig)
throws IOException, InterruptedException {
BisqApp bisqNode = new BisqApp(bisqAppConfig, config);
bisqNode.verifyAppNotRunning();
bisqNode.verifyAppDataDirInstalled();
return bisqNode;
BisqProcess bisqProcess = new BisqProcess(bisqAppConfig, config);
bisqProcess.verifyAppNotRunning();
bisqProcess.verifyAppDataDirInstalled();
return bisqProcess;
}
private void verifyStartupCompleted()

View file

@ -41,7 +41,7 @@ import bisq.daemon.app.BisqDaemonMain;
* Runs a regtest/dao Bisq application instance in the background.
*/
@Slf4j
public class BisqApp extends AbstractLinuxProcess implements LinuxProcess {
public class BisqProcess extends AbstractLinuxProcess implements LinuxProcess {
private final BisqAppConfig bisqAppConfig;
private final String baseCurrencyNetwork;
@ -55,7 +55,7 @@ public class BisqApp extends AbstractLinuxProcess implements LinuxProcess {
private final String findBisqPidScript;
private final String debugOpts;
public BisqApp(BisqAppConfig bisqAppConfig, ApiTestConfig config) {
public BisqProcess(BisqAppConfig bisqAppConfig, ApiTestConfig config) {
super(bisqAppConfig.appName, config);
this.bisqAppConfig = bisqAppConfig;
this.baseCurrencyNetwork = "BTC_REGTEST";

View file

@ -392,7 +392,7 @@ configure(project(':desktop')) {
apply from: '../gradle/witness/gradle-witness.gradle'
apply from: 'package/package.gradle'
version = '1.5.5-SNAPSHOT'
version = '1.5.6-SNAPSHOT'
jar.manifest.attributes(
"Implementation-Title": project.name,

View file

@ -30,7 +30,7 @@ public class Version {
// VERSION = 0.5.0 introduces proto buffer for the P2P network and local DB and is a not backward compatible update
// Therefore all sub versions start again with 1
// We use semantic versioning with major, minor and patch
public static final String VERSION = "1.5.5";
public static final String VERSION = "1.5.6";
/**
* Holds a list of the tagged resource files for optimizing the getData requests.

View file

@ -94,7 +94,7 @@ class DesktopUtil {
return true;
}
} catch (IOException e) {
log.warn("Error running command. {}", e);
log.warn("Error running command. {}", e.toString());
return false;
}
}

View file

@ -17,6 +17,8 @@
package bisq.core.alert;
import bisq.core.user.Preferences;
import bisq.network.p2p.storage.payload.ExpirablePayload;
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
@ -51,6 +53,7 @@ public final class Alert implements ProtectedStoragePayload, ExpirablePayload {
private final String message;
private final boolean isUpdateInfo;
private final boolean isPreReleaseInfo;
private final String version;
@Nullable
@ -68,9 +71,11 @@ public final class Alert implements ProtectedStoragePayload, ExpirablePayload {
public Alert(String message,
boolean isUpdateInfo,
boolean isPreReleaseInfo,
String version) {
this.message = message;
this.isUpdateInfo = isUpdateInfo;
this.isPreReleaseInfo = isPreReleaseInfo;
this.version = version;
}
@ -82,12 +87,14 @@ public final class Alert implements ProtectedStoragePayload, ExpirablePayload {
@SuppressWarnings("NullableProblems")
public Alert(String message,
boolean isUpdateInfo,
boolean isPreReleaseInfo,
String version,
byte[] ownerPubKeyBytes,
String signatureAsBase64,
Map<String, String> extraDataMap) {
this.message = message;
this.isUpdateInfo = isUpdateInfo;
this.isPreReleaseInfo = isPreReleaseInfo;
this.version = version;
this.ownerPubKeyBytes = ownerPubKeyBytes;
this.signatureAsBase64 = signatureAsBase64;
@ -103,6 +110,7 @@ public final class Alert implements ProtectedStoragePayload, ExpirablePayload {
protobuf.Alert.Builder builder = protobuf.Alert.newBuilder()
.setMessage(message)
.setIsUpdateInfo(isUpdateInfo)
.setIsPreReleaseInfo(isPreReleaseInfo)
.setVersion(version)
.setOwnerPubKeyBytes(ByteString.copyFrom(ownerPubKeyBytes))
.setSignatureAsBase64(signatureAsBase64);
@ -119,6 +127,7 @@ public final class Alert implements ProtectedStoragePayload, ExpirablePayload {
return new Alert(proto.getMessage(),
proto.getIsUpdateInfo(),
proto.getIsPreReleaseInfo(),
proto.getVersion(),
proto.getOwnerPubKeyBytes().toByteArray(),
proto.getSignatureAsBase64(),
@ -143,7 +152,28 @@ public final class Alert implements ProtectedStoragePayload, ExpirablePayload {
ownerPubKeyBytes = Sig.getPublicKeyBytes(ownerPubKey);
}
public boolean isNewVersion() {
return Version.isNewVersion(version);
public boolean isNewVersion(Preferences preferences) {
// regular release: always notify user
// pre-release: if user has set preference to receive pre-release notification
if (isUpdateInfo ||
(isPreReleaseInfo && preferences.isNotifyOnPreRelease())) {
return Version.isNewVersion(version);
}
return false;
}
public boolean isSoftwareUpdateNotification() {
return (isUpdateInfo || isPreReleaseInfo);
}
public boolean canShowPopup(Preferences preferences) {
// only show popup if its version is newer than current
// and only if user has not checked "don't show again"
return isNewVersion(preferences) && preferences.showAgain(showAgainKey());
}
public String showAgainKey() {
return "Update_" + version;
}
}

View file

@ -65,6 +65,10 @@ class CoreOffersService {
private final Supplier<Comparator<Offer>> reversePriceComparator = () -> comparing(Offer::getPrice).reversed();
private final KeyRing keyRing;
// Dependencies on core api services in this package must be kept to an absolute
// minimum, but some trading functions require an unlocked wallet's key, so an
// exception is made in this case.
private final CoreWalletsService coreWalletsService;
private final CreateOfferService createOfferService;
private final OfferBookService offerBookService;
private final OfferFilter offerFilter;
@ -76,6 +80,7 @@ class CoreOffersService {
@Inject
public CoreOffersService(CoreContext coreContext,
KeyRing keyRing,
CoreWalletsService coreWalletsService,
CreateOfferService createOfferService,
OfferBookService offerBookService,
OfferFilter offerFilter,
@ -83,6 +88,7 @@ class CoreOffersService {
OfferUtil offerUtil,
User user) {
this.keyRing = keyRing;
this.coreWalletsService = coreWalletsService;
this.createOfferService = createOfferService;
this.offerBookService = offerBookService;
this.offerFilter = offerFilter;
@ -144,7 +150,8 @@ class CoreOffersService {
String paymentAccountId,
String makerFeeCurrencyCode,
Consumer<Offer> resultHandler) {
coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked();
offerUtil.maybeSetFeePaymentCurrencyPreference(makerFeeCurrencyCode);
String upperCaseCurrencyCode = currencyCode.toUpperCase();

View file

@ -33,6 +33,7 @@ import bisq.core.dao.governance.voteresult.VoteResultException;
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.AmazonGiftCardAccount;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.RevolutAccount;
import bisq.core.payment.payload.PaymentMethod;
@ -179,6 +180,9 @@ public class BisqSetup {
private Consumer<List<RevolutAccount>> revolutAccountsUpdateHandler;
@Setter
@Nullable
private Consumer<List<AmazonGiftCardAccount>> amazonGiftCardAccountsUpdateHandler;
@Setter
@Nullable
private Runnable osxKeyLoggerWarningHandler;
@Setter
@Nullable
@ -250,20 +254,23 @@ public class BisqSetup {
///////////////////////////////////////////////////////////////////////////////////////////
public void displayAlertIfPresent(Alert alert, boolean openNewVersionPopup) {
if (alert != null) {
if (alert.isUpdateInfo()) {
user.setDisplayedAlert(alert);
final boolean isNewVersion = alert.isNewVersion();
newVersionAvailableProperty.set(isNewVersion);
String key = "Update_" + alert.getVersion();
if (isNewVersion && (preferences.showAgain(key) || openNewVersionPopup) && displayUpdateHandler != null) {
displayUpdateHandler.accept(alert, key);
if (alert == null)
return;
if (alert.isSoftwareUpdateNotification()) {
// only process if the alert version is "newer" than ours
if (alert.isNewVersion(preferences)) {
user.setDisplayedAlert(alert); // save context to compare later
newVersionAvailableProperty.set(true); // shows link in footer bar
if ((alert.canShowPopup(preferences) || openNewVersionPopup) && displayUpdateHandler != null) {
displayUpdateHandler.accept(alert, alert.showAgainKey());
}
} else {
final Alert displayedAlert = user.getDisplayedAlert();
if ((displayedAlert == null || !displayedAlert.equals(alert)) && displayAlertHandler != null)
displayAlertHandler.accept(alert);
}
} else {
// it is a normal message alert
final Alert displayedAlert = user.getDisplayedAlert();
if ((displayedAlert == null || !displayedAlert.equals(alert)) && displayAlertHandler != null)
displayAlertHandler.accept(alert);
}
}
@ -453,6 +460,7 @@ public class BisqSetup {
filterWarningHandler,
voteResultExceptionHandler,
revolutAccountsUpdateHandler,
amazonGiftCardAccountsUpdateHandler,
daoRequiresRestartHandler);
if (walletsSetup.downloadPercentageProperty().get() == 1) {

View file

@ -35,6 +35,7 @@ import bisq.core.notifications.alerts.market.MarketAlerts;
import bisq.core.notifications.alerts.price.PriceAlert;
import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.TriggerPriceService;
import bisq.core.payment.AmazonGiftCardAccount;
import bisq.core.payment.RevolutAccount;
import bisq.core.payment.TradeLimits;
import bisq.core.provider.fee.FeeService;
@ -189,6 +190,7 @@ public class DomainInitialisation {
Consumer<String> filterWarningHandler,
Consumer<VoteResultException> voteResultExceptionHandler,
Consumer<List<RevolutAccount>> revolutAccountsUpdateHandler,
Consumer<List<AmazonGiftCardAccount>> amazonGiftCardAccountsUpdateHandler,
Runnable daoRequiresRestartHandler) {
clockWatcher.start();
@ -242,8 +244,8 @@ public class DomainInitialisation {
priceFeedService.setCurrencyCodeOnInit();
filterManager.onAllServicesInitialized();
filterManager.setFilterWarningHandler(filterWarningHandler);
filterManager.onAllServicesInitialized();
voteResultService.getVoteResultExceptions().addListener((ListChangeListener<VoteResultException>) c -> {
c.next();
@ -267,5 +269,12 @@ public class DomainInitialisation {
.filter(RevolutAccount::userNameNotSet)
.collect(Collectors.toList()));
}
if (amazonGiftCardAccountsUpdateHandler != null) {
amazonGiftCardAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream()
.filter(paymentAccount -> paymentAccount instanceof AmazonGiftCardAccount)
.map(paymentAccount -> (AmazonGiftCardAccount) paymentAccount)
.filter(AmazonGiftCardAccount::countryNotSet)
.collect(Collectors.toList()));
}
}
}

View file

@ -141,9 +141,8 @@ public class TxFeeEstimationService {
}
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsize(Coin amount,
FeeService feeService,
BtcWalletService btcWalletService) {
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
Coin txFeePerVbyte = btcWalletService.getTxFeeForWithdrawalPerVbyte();
// We start with min taker fee vsize of 175
int estimatedTxVsize = TYPICAL_TX_WITH_1_INPUT_VSIZE;
try {

View file

@ -80,7 +80,6 @@ import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.PENDING;
@Slf4j
public class BsqWalletService extends WalletService implements DaoStateListener {
public interface WalletTransactionsChangeListener {
void onWalletTransactionsChange();
@ -140,42 +139,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
wallet = walletsSetup.getBsqWallet();
if (wallet != null) {
wallet.setCoinSelector(bsqCoinSelector);
wallet.addCoinsReceivedEventListener(walletEventListener);
wallet.addCoinsSentEventListener(walletEventListener);
wallet.addReorganizeEventListener(walletEventListener);
wallet.addTransactionConfidenceEventListener(walletEventListener);
wallet.addCoinsReceivedEventListener((wallet, tx, prevBalance, newBalance) -> {
updateBsqWalletTransactions();
});
wallet.addCoinsSentEventListener((wallet, tx, prevBalance, newBalance) -> {
updateBsqWalletTransactions();
});
wallet.addReorganizeEventListener(wallet -> {
log.warn("onReorganize ");
updateBsqWalletTransactions();
unconfirmedBsqChangeOutputListService.onReorganize();
});
wallet.addTransactionConfidenceEventListener((wallet, tx) -> {
// We are only interested in updates from unconfirmed txs and confirmed txs at the
// time when it gets into a block. Otherwise we would get called
// updateBsqWalletTransactions for each tx as the block depth changes for all.
if (tx != null && tx.getConfidence() != null && tx.getConfidence().getDepthInBlocks() <= 1 &&
daoStateService.isParseBlockChainComplete()) {
updateBsqWalletTransactions();
}
unconfirmedBsqChangeOutputListService.onTransactionConfidenceChanged(tx);
});
wallet.addKeyChainEventListener(keys -> {
updateBsqWalletTransactions();
});
wallet.addScriptsChangeEventListener((wallet, scripts, isAddingScripts) -> {
updateBsqWalletTransactions();
});
wallet.addChangeEventListener(wallet -> {
updateBsqWalletTransactions();
});
addListenersToWallet();
}
BlockChain chain = walletsSetup.getChain();
@ -188,6 +152,41 @@ public class BsqWalletService extends WalletService implements DaoStateListener
daoStateService.addDaoStateListener(this);
}
@Override
protected void addListenersToWallet() {
super.addListenersToWallet();
wallet.addCoinsReceivedEventListener((wallet, tx, prevBalance, newBalance) ->
updateBsqWalletTransactions()
);
wallet.addCoinsSentEventListener((wallet, tx, prevBalance, newBalance) ->
updateBsqWalletTransactions()
);
wallet.addReorganizeEventListener(wallet -> {
log.warn("onReorganize ");
updateBsqWalletTransactions();
unconfirmedBsqChangeOutputListService.onReorganize();
});
wallet.addTransactionConfidenceEventListener((wallet, tx) -> {
// We are only interested in updates from unconfirmed txs and confirmed txs at the
// time when it gets into a block. Otherwise we would get called
// updateBsqWalletTransactions for each tx as the block depth changes for all.
if (tx != null && tx.getConfidence() != null && tx.getConfidence().getDepthInBlocks() <= 1 &&
daoStateService.isParseBlockChainComplete()) {
updateBsqWalletTransactions();
}
unconfirmedBsqChangeOutputListService.onTransactionConfidenceChanged(tx);
});
wallet.addKeyChainEventListener(keys ->
updateBsqWalletTransactions()
);
wallet.addScriptsChangeEventListener((wallet, scripts, isAddingScripts) ->
updateBsqWalletTransactions()
);
wallet.addChangeEventListener(wallet ->
updateBsqWalletTransactions()
);
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener

View file

@ -99,10 +99,7 @@ public class BtcWalletService extends WalletService {
walletsSetup.addSetupCompletedHandler(() -> {
wallet = walletsSetup.getBtcWallet();
wallet.addCoinsReceivedEventListener(walletEventListener);
wallet.addCoinsSentEventListener(walletEventListener);
wallet.addReorganizeEventListener(walletEventListener);
wallet.addTransactionConfidenceEventListener(walletEventListener);
addListenersToWallet();
walletsSetup.getChain().addNewBestBlockListener(block -> chainHeightProperty.set(block.getHeight()));
chainHeightProperty.set(walletsSetup.getChain().getBestChainHeight());
@ -676,8 +673,7 @@ public class BtcWalletService extends WalletService {
.filter(e -> {
boolean isSegwitOutputScriptType = Script.ScriptType.P2WPKH.equals(e.getAddress().getOutputScriptType());
// We need to ensure that we take only addressEntries which matches our segWit flag
boolean isMatchingOutputScriptType = isSegwitOutputScriptType == segwit;
return isMatchingOutputScriptType;
return isSegwitOutputScriptType == segwit;
})
.findAny();
return getOrCreateAddressEntry(context, addressEntry, segwit);

View file

@ -72,6 +72,10 @@ import org.bitcoinj.wallet.listeners.WalletReorganizeEventListener;
import javax.inject.Inject;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multiset;
import com.google.common.collect.SetMultimap;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
@ -83,9 +87,13 @@ import org.bouncycastle.crypto.params.KeyParameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@ -106,10 +114,13 @@ public abstract class WalletService {
protected final Preferences preferences;
protected final FeeService feeService;
protected final NetworkParameters params;
protected final BisqWalletListener walletEventListener = new BisqWalletListener();
protected final CopyOnWriteArraySet<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet<BalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
private final BisqWalletListener walletEventListener = new BisqWalletListener();
private final CopyOnWriteArraySet<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<BalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
private final WalletChangeEventListener cacheInvalidationListener;
private final AtomicReference<Multiset<Address>> txOutputAddressCache = new AtomicReference<>();
private final AtomicReference<SetMultimap<Address, Transaction>> addressToMatchingTxSetCache = new AtomicReference<>();
@Getter
protected Wallet wallet;
@Getter
@ -131,6 +142,11 @@ public abstract class WalletService {
this.feeService = feeService;
params = walletsSetup.getParams();
cacheInvalidationListener = wallet -> {
txOutputAddressCache.set(null);
addressToMatchingTxSetCache.set(null);
};
}
@ -138,13 +154,21 @@ public abstract class WalletService {
// Lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
protected void addListenersToWallet() {
wallet.addCoinsReceivedEventListener(walletEventListener);
wallet.addCoinsSentEventListener(walletEventListener);
wallet.addReorganizeEventListener(walletEventListener);
wallet.addTransactionConfidenceEventListener(walletEventListener);
wallet.addChangeEventListener(Threading.SAME_THREAD, cacheInvalidationListener);
}
public void shutDown() {
if (wallet != null) {
//noinspection deprecation
wallet.removeCoinsReceivedEventListener(walletEventListener);
wallet.removeCoinsSentEventListener(walletEventListener);
wallet.removeReorganizeEventListener(walletEventListener);
wallet.removeTransactionConfidenceEventListener(walletEventListener);
wallet.removeChangeEventListener(cacheInvalidationListener);
}
}
@ -371,15 +395,28 @@ public abstract class WalletService {
public TransactionConfidence getConfidenceForAddress(Address address) {
List<TransactionConfidence> transactionConfidenceList = new ArrayList<>();
if (wallet != null) {
Set<Transaction> transactions = wallet.getTransactions(false);
if (transactions != null) {
transactionConfidenceList.addAll(transactions.stream().map(tx ->
getTransactionConfidence(tx, address)).collect(Collectors.toList()));
}
Set<Transaction> transactions = getAddressToMatchingTxSetMultiset().get(address);
transactionConfidenceList.addAll(transactions.stream().map(tx ->
getTransactionConfidence(tx, address)).collect(Collectors.toList()));
}
return getMostRecentConfidence(transactionConfidenceList);
}
private SetMultimap<Address, Transaction> getAddressToMatchingTxSetMultiset() {
return addressToMatchingTxSetCache.updateAndGet(set -> set != null ? set : computeAddressToMatchingTxSetMultimap());
}
private SetMultimap<Address, Transaction> computeAddressToMatchingTxSetMultimap() {
return wallet.getTransactions(false).stream()
.collect(ImmutableSetMultimap.flatteningToImmutableSetMultimap(
Function.identity(),
(Function<Transaction, Stream<Address>>) (
t -> getOutputsWithConnectedOutputs(t).stream()
.map(WalletService::getAddressFromOutput)
.filter(Objects::nonNull))))
.inverse();
}
@Nullable
public TransactionConfidence getConfidenceForTxId(String txId) {
if (wallet != null) {
@ -392,18 +429,18 @@ public abstract class WalletService {
return null;
}
protected TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
List<TransactionConfidence> transactionConfidenceList = getOutputsWithConnectedOutputs(tx)
.stream()
.filter(WalletService::isOutputScriptConvertibleToAddress)
@Nullable
private TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
List<TransactionConfidence> transactionConfidenceList = getOutputsWithConnectedOutputs(tx).stream()
.filter(output -> address != null && address.equals(getAddressFromOutput(output)))
.map(o -> tx.getConfidence())
.flatMap(o -> Stream.ofNullable(o.getParentTransaction()))
.map(Transaction::getConfidence)
.collect(Collectors.toList());
return getMostRecentConfidence(transactionConfidenceList);
}
protected List<TransactionOutput> getOutputsWithConnectedOutputs(Transaction tx) {
private List<TransactionOutput> getOutputsWithConnectedOutputs(Transaction tx) {
List<TransactionOutput> transactionOutputs = tx.getOutputs();
List<TransactionOutput> connectedOutputs = new ArrayList<>();
@ -423,7 +460,7 @@ public abstract class WalletService {
}
@Nullable
protected TransactionConfidence getMostRecentConfidence(List<TransactionConfidence> transactionConfidenceList) {
private TransactionConfidence getMostRecentConfidence(List<TransactionConfidence> transactionConfidenceList) {
TransactionConfidence transactionConfidence = null;
for (TransactionConfidence confidence : transactionConfidenceList) {
if (confidence != null) {
@ -490,16 +527,19 @@ public abstract class WalletService {
///////////////////////////////////////////////////////////////////////////////////////////
public int getNumTxOutputsForAddress(Address address) {
List<TransactionOutput> transactionOutputs = new ArrayList<>();
wallet.getTransactions(false).forEach(t -> transactionOutputs.addAll(t.getOutputs()));
int outputs = 0;
for (TransactionOutput output : transactionOutputs) {
if (isOutputScriptConvertibleToAddress(output) &&
address != null &&
address.equals(getAddressFromOutput(output)))
outputs++;
}
return outputs;
return getTxOutputAddressMultiset().count(address);
}
private Multiset<Address> getTxOutputAddressMultiset() {
return txOutputAddressCache.updateAndGet(set -> set != null ? set : computeTxOutputAddressMultiset());
}
private Multiset<Address> computeTxOutputAddressMultiset() {
return wallet.getTransactions(false).stream()
.flatMap(t -> t.getOutputs().stream())
.map(WalletService::getAddressFromOutput)
.filter(Objects::nonNull)
.collect(ImmutableMultiset.toImmutableMultiset());
}
public boolean isAddressUnused(Address address) {
@ -595,17 +635,13 @@ public abstract class WalletService {
wallet.removeChangeEventListener(listener);
}
@SuppressWarnings("deprecation")
public void addNewBestBlockListener(NewBestBlockListener listener) {
//noinspection deprecation
final BlockChain chain = walletsSetup.getChain();
if (isWalletReady() && chain != null)
chain.addNewBestBlockListener(listener);
}
@SuppressWarnings("deprecation")
public void removeNewBestBlockListener(NewBestBlockListener listener) {
//noinspection deprecation
final BlockChain chain = walletsSetup.getChain();
if (isWalletReady() && chain != null)
chain.removeNewBestBlockListener(listener);
@ -786,7 +822,6 @@ public abstract class WalletService {
// bisqWalletEventListener
///////////////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("deprecation")
public class BisqWalletListener implements WalletCoinsReceivedEventListener, WalletCoinsSentEventListener, WalletReorganizeEventListener, TransactionConfidenceEventListener {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
@ -806,11 +841,8 @@ public abstract class WalletService {
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
for (AddressConfidenceListener addressConfidenceListener : addressConfidenceListeners) {
List<TransactionConfidence> transactionConfidenceList = new ArrayList<>();
transactionConfidenceList.add(getTransactionConfidence(tx, addressConfidenceListener.getAddress()));
TransactionConfidence transactionConfidence = getMostRecentConfidence(transactionConfidenceList);
addressConfidenceListener.onTransactionConfidenceChanged(transactionConfidence);
TransactionConfidence confidence = getTransactionConfidence(tx, addressConfidenceListener.getAddress());
addressConfidenceListener.onTransactionConfidenceChanged(confidence);
}
txConfidenceListeners.stream()
.filter(txConfidenceListener -> tx != null &&

View file

@ -103,6 +103,7 @@ public class FilterManager {
private final List<String> publicKeys;
private ECKey filterSigningKey;
private final Set<Filter> invalidFilters = new HashSet<>();
private Consumer<String> filterWarningHandler;
///////////////////////////////////////////////////////////////////////////////////////////
@ -152,6 +153,12 @@ public class FilterManager {
.map(protectedStoragePayload -> (Filter) protectedStoragePayload)
.forEach(this::onFilterAddedFromNetwork);
// On mainNet we expect to have received a filter object, if not show a popup to the user to inform the
// Bisq devs.
if (Config.baseCurrencyNetwork().isMainnet() && getFilter() == null) {
filterWarningHandler.accept(Res.get("popup.warning.noFilter"));
}
p2PService.addHashSetChangedListener(new HashMapChangedListener() {
@Override
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
@ -216,6 +223,8 @@ public class FilterManager {
}
public void setFilterWarningHandler(Consumer<String> filterWarningHandler) {
this.filterWarningHandler = filterWarningHandler;
addListener(filter -> {
if (filter != null && filterWarningHandler != null) {
if (filter.getSeedNodes() != null && !filter.getSeedNodes().isEmpty()) {

View file

@ -37,7 +37,7 @@ public class CountryUtil {
public static List<Country> getAllSepaEuroCountries() {
List<Country> list = new ArrayList<>();
String[] codes = {"AT", "BE", "CY", "DE", "EE", "FI", "FR", "GR", "IE",
"IT", "LV", "LT", "LU", "MC", "MT", "NL", "PT", "SK", "SI", "ES"};
"IT", "LV", "LT", "LU", "MC", "MT", "NL", "PT", "SK", "SI", "ES", "AD", "SM", "VA"};
populateCountryListByCodes(list, codes);
list.sort((a, b) -> a.name.compareTo(b.name));
@ -56,6 +56,16 @@ public class CountryUtil {
return list;
}
public static List<Country> getAllAmazonGiftCardCountries() {
List<Country> list = new ArrayList<>();
String[] codes = {"AU", "CA", "FR", "DE", "IT", "NL", "ES", "GB", "IN", "JP",
"SA", "SE", "SG", "TR", "US"};
populateCountryListByCodes(list, codes);
list.sort((a, b) -> a.name.compareTo(b.name));
return list;
}
public static List<Country> getAllSepaInstantEuroCountries() {
return getAllSepaEuroCountries();
}
@ -86,7 +96,7 @@ public class CountryUtil {
public static List<Country> getAllSepaNonEuroCountries() {
List<Country> list = new ArrayList<>();
String[] codes = {"BG", "HR", "CZ", "DK", "GB", "HU", "PL", "RO",
"SE", "IS", "NO", "LI", "CH"};
"SE", "IS", "NO", "LI", "CH", "JE"};
populateCountryListByCodes(list, codes);
list.sort((a, b) -> a.name.compareTo(b.name));
return list;
@ -133,6 +143,8 @@ public class CountryUtil {
}
public static String getNameAndCode(String countryCode) {
if (countryCode.isEmpty())
return "";
return getNameByCode(countryCode) + " (" + countryCode + ")";
}

View file

@ -23,6 +23,9 @@ import bisq.core.monetary.Price;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.P2PService;
import bisq.common.util.MathUtils;
import org.bitcoinj.utils.Fiat;
@ -47,17 +50,34 @@ import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
@Slf4j
@Singleton
public class TriggerPriceService {
private final P2PService p2PService;
private final OpenOfferManager openOfferManager;
private final PriceFeedService priceFeedService;
private final Map<String, Set<OpenOffer>> openOffersByCurrency = new HashMap<>();
@Inject
public TriggerPriceService(OpenOfferManager openOfferManager, PriceFeedService priceFeedService) {
public TriggerPriceService(P2PService p2PService,
OpenOfferManager openOfferManager,
PriceFeedService priceFeedService) {
this.p2PService = p2PService;
this.openOfferManager = openOfferManager;
this.priceFeedService = priceFeedService;
}
public void onAllServicesInitialized() {
if (p2PService.isBootstrapped()) {
onBootstrapComplete();
} else {
p2PService.addP2PServiceListener(new BootstrapListener() {
@Override
public void onUpdatedDataReceived() {
onBootstrapComplete();
}
});
}
}
private void onBootstrapComplete() {
openOfferManager.getObservableList().addListener((ListChangeListener<OpenOffer>) c -> {
c.next();
if (c.wasAdded()) {

View file

@ -17,12 +17,21 @@
package bisq.core.payment;
import bisq.core.locale.Country;
import bisq.core.locale.CountryUtil;
import bisq.core.payment.payload.AmazonGiftCardAccountPayload;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
public final class AmazonGiftCardAccount extends PaymentAccount {
@Nullable
private Country country;
public AmazonGiftCardAccount() {
super(PaymentMethod.AMAZON_GIFT_CARD);
}
@ -40,6 +49,24 @@ public final class AmazonGiftCardAccount extends PaymentAccount {
getAmazonGiftCardAccountPayload().setEmailOrMobileNr(emailOrMobileNr);
}
public boolean countryNotSet() {
return (getAmazonGiftCardAccountPayload()).countryNotSet();
}
@Nullable
public Country getCountry() {
if (country == null) {
final String countryCode = getAmazonGiftCardAccountPayload().getCountryCode();
CountryUtil.findCountryByCode(countryCode).ifPresent(c -> this.country = c);
}
return country;
}
public void setCountry(@NotNull Country country) {
this.country = country;
getAmazonGiftCardAccountPayload().setCountryCode(country.code);
}
private AmazonGiftCardAccountPayload getAmazonGiftCardAccountPayload() {
return (AmazonGiftCardAccountPayload) paymentAccountPayload;
}

View file

@ -18,6 +18,7 @@
package bisq.core.payment.payload;
import bisq.core.locale.Res;
import bisq.common.util.JsonExclude;
import com.google.protobuf.Message;
@ -39,6 +40,10 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AmazonGiftCardAccountPayload extends PaymentAccountPayload {
private String emailOrMobileNr;
// For backward compatibility we need to exclude the new field for the contract json.
// We can remove that after a while when risk that users with pre 1.5.5 version is very low.
@JsonExclude
private String countryCode = "";
public AmazonGiftCardAccountPayload(String paymentMethod, String id) {
super(paymentMethod, id);
@ -52,6 +57,7 @@ public class AmazonGiftCardAccountPayload extends PaymentAccountPayload {
private AmazonGiftCardAccountPayload(String paymentMethodName,
String id,
String emailOrMobileNr,
String countryCode,
long maxTradePeriod,
Map<String, String> excludeFromJsonDataMap) {
super(paymentMethodName,
@ -59,12 +65,14 @@ public class AmazonGiftCardAccountPayload extends PaymentAccountPayload {
maxTradePeriod,
excludeFromJsonDataMap);
this.emailOrMobileNr = emailOrMobileNr;
this.countryCode = countryCode;
}
@Override
public Message toProtoMessage() {
protobuf.AmazonGiftCardAccountPayload.Builder builder =
protobuf.AmazonGiftCardAccountPayload.newBuilder()
.setCountryCode(countryCode)
.setEmailOrMobileNr(emailOrMobileNr);
return getPaymentAccountPayloadBuilder()
.setAmazonGiftCardAccountPayload(builder)
@ -76,6 +84,7 @@ public class AmazonGiftCardAccountPayload extends PaymentAccountPayload {
return new AmazonGiftCardAccountPayload(proto.getPaymentMethodId(),
proto.getId(),
amazonGiftCardAccountPayload.getEmailOrMobileNr(),
amazonGiftCardAccountPayload.getCountryCode(),
proto.getMaxTradePeriod(),
new HashMap<>(proto.getExcludeFromJsonDataMap()));
}
@ -100,4 +109,8 @@ public class AmazonGiftCardAccountPayload extends PaymentAccountPayload {
String data = "AmazonGiftCard" + emailOrMobileNr;
return super.getAgeWitnessInputData(data.getBytes(StandardCharsets.UTF_8));
}
public boolean countryNotSet() {
return countryCode.isEmpty();
}
}

View file

@ -18,6 +18,7 @@
package bisq.core.support;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.locale.Res;
import bisq.core.support.messages.ChatMessage;
import bisq.core.support.messages.SupportMessage;
@ -191,7 +192,10 @@ public abstract class SupportManager {
public ChatMessage sendChatMessage(ChatMessage message) {
NodeAddress peersNodeAddress = getPeerNodeAddress(message);
PubKeyRing receiverPubKeyRing = getPeerPubKeyRing(message);
if (receiverPubKeyRing != null) {
if (peersNodeAddress == null || receiverPubKeyRing == null) {
UserThread.runAfter(() ->
message.setSendMessageError(Res.get("support.receiverNotKnown")), 1);
} else {
log.info("Send {} to peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());

View file

@ -46,7 +46,7 @@ public abstract class SupportSession {
public abstract String getTradeId();
public abstract PubKeyRing getClientPubKeyRing();
public abstract int getClientId();
public abstract ObservableList<ChatMessage> getObservableChatMessageList();

View file

@ -34,8 +34,10 @@ import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.ObservableList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -53,6 +55,8 @@ public abstract class DisputeListService<T extends DisputeList<Dispute>> impleme
private final Map<String, Subscription> disputeIsClosedSubscriptionsMap = new HashMap<>();
@Getter
private final IntegerProperty numOpenDisputes = new SimpleIntegerProperty();
@Getter
private final Set<String> disputedTradeIds = new HashSet<>();
///////////////////////////////////////////////////////////////////////////////////////////
@ -154,6 +158,7 @@ public abstract class DisputeListService<T extends DisputeList<Dispute>> impleme
disputeIsClosedSubscriptionsMap.get(id).unsubscribe();
disputeIsClosedSubscriptionsMap.remove(id);
}
disputedTradeIds.remove(dispute.getTradeId());
});
}
addedList.forEach(dispute -> {
@ -168,6 +173,7 @@ public abstract class DisputeListService<T extends DisputeList<Dispute>> impleme
});
});
disputeIsClosedSubscriptionsMap.put(id, disputeStateSubscription);
disputedTradeIds.add(dispute.getTradeId());
});
}

View file

@ -69,6 +69,7 @@ import java.security.KeyPair;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -231,6 +232,9 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
return disputeListService.getDisputeList();
}
public Set<String> getDisputedTradeIds() {
return disputeListService.getDisputedTradeIds();
}
///////////////////////////////////////////////////////////////////////////////////////////
// API

View file

@ -57,9 +57,14 @@ public abstract class DisputeSession extends SupportSession {
}
@Override
public PubKeyRing getClientPubKeyRing() {
public int getClientId() {
// Get pubKeyRing of trader. Arbitrator is considered server for the chat session
return dispute != null ? dispute.getTraderPubKeyRing() : null;
try {
return dispute.getTraderPubKeyRing().hashCode();
} catch (NullPointerException e) {
log.warn("Unable to get traderPubKeyRing from Dispute - {}", e.toString());
}
return 0;
}
@Override

View file

@ -37,7 +37,7 @@ public class DisputeAgentLookupMap {
case "apbp7ubuyezav4hy.onion:9999":
return "bisq_knight";
case "a56olqlmmpxrn5q34itq5g5tb5d3fg7vxekpbceq7xqvfl3cieocgsyd.onion:9999":
return "leo816";
return "huey735";
case "3z5jnirlccgxzoxc6zwkcgwj66bugvqplzf6z2iyd5oxifiaorhnanqd.onion:9999":
return "refundagent2";
default:

View file

@ -48,12 +48,15 @@ public class TradeChatSession extends SupportSession {
}
@Override
public PubKeyRing getClientPubKeyRing() {
public int getClientId() {
// TODO remove that client-server concept for trade chat
// Get pubKeyRing of taker. Maker is considered server for chat sessions
if (trade != null && trade.getContract() != null)
return trade.getContract().getTakerPubKeyRing();
return null;
try {
return trade.getContract().getTakerPubKeyRing().hashCode();
} catch (NullPointerException e) {
log.warn("Unable to get takerPubKeyRing from Trade Contract - {}", e.toString());
}
return 0;
}
@Override

View file

@ -309,15 +309,25 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
useStandbyModeProperty.set(prefPayload.isUseStandbyMode());
cssThemeProperty.set(prefPayload.getCssTheme());
// a list of previously-used federated explorers
// if user preference references any deprecated explorers we need to select a new valid explorer
String deprecatedExplorers = "(bsq.bisq.cc|bsq.vante.me|bsq.emzy.de|bsq.sqrrm.net|bsq.bisq.services|bsq.ninja).*";
// if no valid Bitcoin block explorer is set, select the 1st valid Bitcoin block explorer
ArrayList<BlockChainExplorer> btcExplorers = getBlockChainExplorers();
if (getBlockChainExplorer() == null || getBlockChainExplorer().name.length() == 0)
if (getBlockChainExplorer() == null ||
getBlockChainExplorer().name.length() == 0 ||
getBlockChainExplorer().name.matches(deprecatedExplorers)) {
setBlockChainExplorer(btcExplorers.get(0));
}
// if no valid BSQ block explorer is set, randomly select a valid BSQ block explorer
ArrayList<BlockChainExplorer> bsqExplorers = getBsqBlockChainExplorers();
if (getBsqBlockChainExplorer() == null || getBsqBlockChainExplorer().name.length() == 0)
if (getBsqBlockChainExplorer() == null ||
getBsqBlockChainExplorer().name.length() == 0 ||
getBsqBlockChainExplorer().name.matches(deprecatedExplorers)) {
setBsqBlockChainExplorer(bsqExplorers.get((new Random()).nextInt(bsqExplorers.size())));
}
tradeCurrenciesAsObservable.addAll(prefPayload.getFiatCurrencies());
tradeCurrenciesAsObservable.addAll(prefPayload.getCryptoCurrencies());
@ -782,6 +792,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
requestPersistence();
}
public void setNotifyOnPreRelease(boolean value) {
prefPayload.setNotifyOnPreRelease(value);
requestPersistence();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
@ -1095,5 +1110,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setShowOffersMatchingMyAccounts(boolean value);
void setDenyApiTaker(boolean value);
void setNotifyOnPreRelease(boolean value);
}
}

View file

@ -133,6 +133,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
private boolean hideNonAccountPaymentMethods;
private boolean showOffersMatchingMyAccounts;
private boolean denyApiTaker;
private boolean notifyOnPreRelease;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -199,7 +200,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
.collect(Collectors.toList()))
.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods)
.setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts)
.setDenyApiTaker(denyApiTaker);
.setDenyApiTaker(denyApiTaker)
.setNotifyOnPreRelease(notifyOnPreRelease);
Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory);
Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage()));
@ -296,7 +298,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
.collect(Collectors.toList())),
proto.getHideNonAccountPaymentMethods(),
proto.getShowOffersMatchingMyAccounts(),
proto.getDenyApiTaker()
proto.getDenyApiTaker(),
proto.getNotifyOnPreRelease()
);
}
}

View file

@ -22,5 +22,5 @@ OPTIONS
EXAMPLES
--------
To cancel an offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea:
$ ./bisq-cli --password=xyz --port=9998 canceloffer -offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea
$ ./bisq-cli --password=xyz --port=9998 canceloffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea

View file

@ -24,4 +24,4 @@ EXAMPLES
--------
A BTC seller has taken an offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea, and has recently
received the required fiat payment from the buyer's fiat account:
$ ./bisq-cli --password=xyz --port=9998 confirmpaymentreceived -trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea
$ ./bisq-cli --password=xyz --port=9998 confirmpaymentreceived --trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea

View file

@ -23,4 +23,4 @@ EXAMPLES
--------
A BTC buyer has taken an offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea, and has recently
initiated the required fiat payment to the seller's fiat account:
$ ./bisq-cli --password=xyz --port=9998 confirmpaymentstarted -trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea
$ ./bisq-cli --password=xyz --port=9998 confirmpaymentstarted --trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea

View file

@ -54,11 +54,29 @@ OPTIONS
EXAMPLES
--------
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,
putting up a 30 percent security deposit,
and paying the Bisq maker trading fee in BSQ:
$ ./bisq-cli --password=xyz --port=9998 createoffer --payment-account=7413d263-225a-4f1b-837a-1e3094dc0d77 --direction=buy --currency-code=eur --amount=0.125 --market-price-margin=0.00 --security-deposit=30.0 --fee-currency=bsq
$ ./bisq-cli --password=xyz --port=9998 createoffer --payment-account=7413d263-225a-4f1b-837a-1e3094dc0d77 \
--direction=buy \
--currency-code=eur \
--amount=0.125 \
--market-price-margin=0.00 \
--security-deposit=30.0 \
--fee-currency=bsq
(TODO another 3 examples: selling @ mkt price, buying a fixed price, selling at fixed price...)
To create a SELL 0.006 BTC for USD offer
at a fixed price of 40,000 USD,
using a payment account with ID 7413d263-225a-4f1b-837a-1e3094dc0d77,
putting up a 25 percent security deposit,
and paying the Bisq maker trading fee in BTC:
$ ./bisq-cli --password=xyz --port=9998 createoffer --payment-account=7413d263-225a-4f1b-837a-1e3094dc0d77 \
--direction=sell \
--currency-code=usd \
--amount=0.006 \
--fixed-price=40000 \
--security-deposit=25.0 \
--fee-currency=btc

View file

@ -24,7 +24,7 @@ Show full BSQ and BTC wallet balance information:
$ ./bisq-cli --password=xyz --port=9998 getbalance
Show full BSQ wallet balance information:
$ ./bisq-cli --password=xyz --port=9998 getbalance --currency-code=bsq
$ ./bisq-cli --password=xyz --port=9998 getbalance --currency-code=bsq
Show full BTC wallet balance information:
$ ./bisq-cli --password=xyz --port=9998 getbalance --currency-code=btc
$ ./bisq-cli --password=xyz --port=9998 getbalance --currency-code=btc

View file

@ -21,5 +21,5 @@ OPTIONS
EXAMPLES
--------
To view your offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea:
$ ./bisq-cli --password=xyz --port=9998 getmyoffer -offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea
$ ./bisq-cli --password=xyz --port=9998 getmyoffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea

View file

@ -22,5 +22,5 @@ OPTIONS
EXAMPLES
--------
To view an offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea:
$ ./bisq-cli --password=xyz --port=9998 getoffer -offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea
$ ./bisq-cli --password=xyz --port=9998 getoffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea

View file

@ -26,7 +26,7 @@ OPTIONS
EXAMPLES
--------
To see the summary of a trade with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea:
$ ./bisq-cli --password=xyz --port=9998 gettrade -trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea
$ ./bisq-cli --password=xyz --port=9998 gettrade --trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea
To see the full contract for a trade with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea:
$ ./bisq-cli --password=xyz --port=9998 gettrade -trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea --show-contract=true
$ ./bisq-cli --password=xyz --port=9998 gettrade --trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea --show-contract=true

View file

@ -24,4 +24,4 @@ EXAMPLES
--------
To see the summary of a transaction with ID 282dc2a5755219a49ee9f6d46a31a2cbaec6624beba96548180eccb1f004cdd8:
$ ./bisq-cli --password=xyz --port=9998 gettransaction \
-transaction-id=282dc2a5755219a49ee9f6d46a31a2cbaec6624beba96548180eccb1f004cdd8
--transaction-id=282dc2a5755219a49ee9f6d46a31a2cbaec6624beba96548180eccb1f004cdd8

View file

@ -28,4 +28,4 @@ EXAMPLES
A BTC seller has informed the buyer that fiat payment has been received for trade with ID
83e8b2e2-51b6-4f39-a748-3ebd29c22aea, and locked BTC has been released to the buyer.
The BTC buyer closes out the trade by keeping the received BTC in her Bisq wallet:
$ ./bisq-cli --password=xyz --port=9998 keepfunds -trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea
$ ./bisq-cli --password=xyz --port=9998 keepfunds --trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea

View file

@ -7,8 +7,8 @@ sendbsq - send BSQ to an external wallet
SYNOPSIS
--------
sendbsq
--address=<btc-address>
--amount=<btc-amount>
--address=<bsq-address>
--amount=<bsq-amount>
[--tx-fee-rate=<sats/byte>]
DESCRIPTION

View file

@ -47,5 +47,5 @@ $ ./bisq-cli --password=xyz --port=9998 sendbtc --address=bcrt1qygvsqmyt8jyhtp7l
Send 0.005 BTC to address bcrt1qygvsqmyt8jyhtp7l3zwqm7s7v3nar6vkc2luz3 with a transaction
fee rate of 40 sats/byte, and save a memo with the send transaction:
$ ./bisq-cli --password=xyz --port=9998 sendbtc --address=bcrt1qygvsqmyt8jyhtp7l3zwqm7s7v3nar6vkc2luz3 --amount=0.005 \
--tx-fee-rate=40
--tx-fee-rate=40 \
--memo="note to self"

View file

@ -32,4 +32,6 @@ EXAMPLES
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:
$ ./bisq-cli --password=xyz --port=9998 takeoffer -offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea -payment-account=fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e -fee-currency=bsq
$ ./bisq-cli --password=xyz --port=9998 takeoffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
--payment-account=fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e \
-fee-currency=bsq

View file

@ -40,11 +40,11 @@ EXAMPLES
A BTC seller has informed the buyer that fiat payment has been received for trade with ID
83e8b2e2-51b6-4f39-a748-3ebd29c22aea, and locked BTC has been released to the buyer.
The BTC buyer closes out the trade by sending the received BTC to an external BTC wallet:
$ ./bisq-cli --password=xyz --port=9998 withdrawfunds -trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
$ ./bisq-cli --password=xyz --port=9998 withdrawfunds --trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
--address=2N5J6MyjAsWnashimGiNwoRzUXThsQzRmbv (bitcoin regtest address)
A seller sends a trade's BTC proceeds to an external wallet, and includes an optional memo:
$ ./bisq-cli --password=xyz --port=9998 withdrawfunds -trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
--address=2N5J6MyjAsWnashimGiNwoRzUXThsQzRmbv
$ ./bisq-cli --password=xyz --port=9998 withdrawfunds --trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
--address=2N5J6MyjAsWnashimGiNwoRzUXThsQzRmbv \
--memo="note to self"

View file

@ -653,10 +653,6 @@ portfolio.pending.step2_buyer.moneyGram.extra=IMPORTANT REQUIREMENT:\nAfter you
portfolio.pending.step2_buyer.westernUnion=Please pay {0} to the BTC seller by using Western Union.\n\n
portfolio.pending.step2_buyer.westernUnion.extra=IMPORTANT REQUIREMENT:\nAfter you have done the payment send the MTCN (tracking number) and a photo of the receipt by email to the BTC seller.\n\
The receipt must clearly show the seller''s full name, city, country and the amount. The seller''s email is: {0}.
# suppress inspection "TrailingSpacesInProperty"
portfolio.pending.step2_buyer.amazonGiftCard=Please purchase an Amazon eGift Card for {0} at your Amazon account and \
use the BTC seller''s email or mobile number as receiver. \
In case the trade amount exceeds the permitted amount send multiple cards.\n\n
# suppress inspection "TrailingSpacesInProperty"
portfolio.pending.step2_buyer.postal=Please send {0} by \"US Postal Money Order\" to the BTC seller.\n\n
@ -1105,6 +1101,7 @@ support.noTickets=There are no open tickets
support.sendingMessage=Sending Message...
support.receiverNotOnline=Receiver is not online. Message is saved to their mailbox.
support.sendMessageError=Sending message failed. Error: {0}
support.receiverNotKnown=Receiver not known
support.wrongVersion=The offer in that dispute has been created with an older version of Bisq.\n\
You cannot close that dispute with your version of the application.\n\n\
Please use an older version with protocol version {0}
@ -1223,6 +1220,7 @@ setting.preferences.useDarkMode=Use dark mode
setting.preferences.sortWithNumOffers=Sort market lists with no. of offers/trades
setting.preferences.onlyShowPaymentMethodsFromAccount=Hide non-supported payment methods
setting.preferences.denyApiTaker=Deny takers using the API
setting.preferences.notifyOnPreRelease=Receive pre-release notifications
setting.preferences.resetAllFlags=Reset all \"Don't show again\" flags
settings.preferences.languageChange=To apply the language change to all screens requires a restart.
settings.preferences.supportLanguageWarning=In case of a dispute, please note that mediation is handled in {0} and arbitration in {1}.
@ -2658,7 +2656,9 @@ selectDepositTxWindow.select=Select deposit transaction
sendAlertMessageWindow.headline=Send global notification
sendAlertMessageWindow.alertMsg=Alert message
sendAlertMessageWindow.enterMsg=Enter message
sendAlertMessageWindow.isUpdate=Is update notification
sendAlertMessageWindow.isSoftwareUpdate=Software download notification
sendAlertMessageWindow.isUpdate=Is full release
sendAlertMessageWindow.isPreRelease=Is pre-release
sendAlertMessageWindow.version=New version no.
sendAlertMessageWindow.send=Send notification
sendAlertMessageWindow.remove=Remove notification
@ -2841,6 +2841,7 @@ popup.warning.mandatoryUpdate.dao=Please update to the latest Bisq version. \
Please check out the Bisq Forum for more information.
popup.warning.disable.dao=The Bisq DAO and BSQ are temporary disabled. \
Please check out the Bisq Forum for more information.
popup.warning.noFilter=We did not receive a filter object from the seed nodes. This is a not expected situation. Please inform the Bisq developers.
popup.warning.burnBTC=This transaction is not possible, as the mining fees of {0} would exceed the amount to transfer of {1}. \
Please wait until the mining fees are low again or until you''ve accumulated more BTC to transfer.
@ -3198,6 +3199,8 @@ payment.select.altcoin=Select or search Altcoin
payment.secret=Secret question
payment.answer=Answer
payment.wallet=Wallet ID
payment.amazon.site=Buy giftcard at
payment.ask=Ask in Trader Chat
payment.uphold.accountId=Username or email or phone no.
payment.moneyBeam.accountId=Email or phone no.
payment.venmo.venmoUserName=Venmo username
@ -3308,6 +3311,13 @@ payment.account.revolut.addUserNameInfo={0}\n\
This will not affect your account age signing status.
payment.revolut.addUserNameInfo.headLine=Update Revolut account
payment.amazonGiftCard.upgrade=Amazon gift cards payment method requires the country to be specified.
payment.account.amazonGiftCard.addCountryInfo={0}\n\
Your existing Amazon Gift Card account ({1}) does not have a Country specified.\n\
Please enter your Amazon Gift Card Country to update your account data.\n\
This will not affect your account age status.
payment.amazonGiftCard.upgrade.headLine=Update Amazon Gift Card account
payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\
\n\
- BTC buyers must write the BTC Sellers name in both the Payer and the Payees fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n\

View file

@ -41,7 +41,7 @@ public class UserPayloadModelVOTest {
public void testRoundtripFull() {
UserPayload vo = new UserPayload();
vo.setAccountId("accountId");
vo.setDisplayedAlert(new Alert("message", true, "version", new byte[]{12, -64, 12}, "string", null));
vo.setDisplayedAlert(new Alert("message", true, false, "version", new byte[]{12, -64, 12}, "string", null));
vo.setDevelopersFilter(new Filter(Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList(),

View file

@ -6,12 +6,24 @@ import bisq.proto.grpc.DisputeAgentsGrpc;
import bisq.proto.grpc.RegisterDisputeAgentReply;
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import io.grpc.ServerInterceptor;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
@Slf4j
class GrpcDisputeAgentsService extends DisputeAgentsGrpc.DisputeAgentsImplBase {
@ -36,4 +48,21 @@ class GrpcDisputeAgentsService extends DisputeAgentsGrpc.DisputeAgentsImplBase {
exceptionHandler.handleException(cause, responseObserver);
}
}
final ServerInterceptor[] interceptors() {
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
return rateMeteringInterceptor.map(serverInterceptor ->
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
}
final Optional<ServerInterceptor> rateMeteringInterceptor() {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
// You can only register mainnet dispute agents in the UI.
// Do not limit devs' ability to register test agents.
put("registerDisputeAgent", new GrpcCallRateMeter(1, SECONDS));
}}
)));
}
}

View file

@ -7,12 +7,23 @@ import bisq.proto.grpc.GetTradeStatisticsGrpc;
import bisq.proto.grpc.GetTradeStatisticsReply;
import bisq.proto.grpc.GetTradeStatisticsRequest;
import io.grpc.ServerInterceptor;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.Optional;
import java.util.stream.Collectors;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
class GrpcGetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase {
private final CoreApi coreApi;
@ -39,4 +50,19 @@ class GrpcGetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStati
exceptionHandler.handleException(cause, responseObserver);
}
}
final ServerInterceptor[] interceptors() {
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
return rateMeteringInterceptor.map(serverInterceptor ->
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
}
final Optional<ServerInterceptor> rateMeteringInterceptor() {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put("getTradeStatistics", new GrpcCallRateMeter(1, SECONDS));
}}
)));
}
}

View file

@ -23,12 +23,24 @@ import bisq.proto.grpc.GetMethodHelpReply;
import bisq.proto.grpc.GetMethodHelpRequest;
import bisq.proto.grpc.HelpGrpc;
import io.grpc.ServerInterceptor;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
@Slf4j
class GrpcHelpService extends HelpGrpc.HelpImplBase {
@ -53,4 +65,19 @@ class GrpcHelpService extends HelpGrpc.HelpImplBase {
exceptionHandler.handleException(cause, responseObserver);
}
}
final ServerInterceptor[] interceptors() {
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
return rateMeteringInterceptor.map(serverInterceptor ->
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
}
final Optional<ServerInterceptor> rateMeteringInterceptor() {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put("getMethodHelp", new GrpcCallRateMeter(1, SECONDS));
}}
)));
}
}

View file

@ -36,16 +36,27 @@ import bisq.proto.grpc.GetOffersReply;
import bisq.proto.grpc.GetOffersRequest;
import bisq.proto.grpc.OffersGrpc;
import io.grpc.ServerInterceptor;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import static bisq.core.api.model.OfferInfo.toOfferInfo;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
@Slf4j
class GrpcOffersService extends OffersGrpc.OffersImplBase {
@ -171,4 +182,24 @@ class GrpcOffersService extends OffersGrpc.OffersImplBase {
exceptionHandler.handleException(cause, responseObserver);
}
}
final ServerInterceptor[] interceptors() {
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
return rateMeteringInterceptor.map(serverInterceptor ->
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
}
final Optional<ServerInterceptor> rateMeteringInterceptor() {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put("getOffer", new GrpcCallRateMeter(1, SECONDS));
put("getMyOffer", new GrpcCallRateMeter(1, SECONDS));
put("getOffers", new GrpcCallRateMeter(1, SECONDS));
put("getMyOffers", new GrpcCallRateMeter(1, SECONDS));
put("createOffer", new GrpcCallRateMeter(1, MINUTES));
put("cancelOffer", new GrpcCallRateMeter(1, MINUTES));
}}
)));
}
}

View file

@ -31,12 +31,24 @@ import bisq.proto.grpc.GetPaymentMethodsReply;
import bisq.proto.grpc.GetPaymentMethodsRequest;
import bisq.proto.grpc.PaymentAccountsGrpc;
import io.grpc.ServerInterceptor;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.Optional;
import java.util.stream.Collectors;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
class GrpcPaymentAccountsService extends PaymentAccountsGrpc.PaymentAccountsImplBase {
@ -110,4 +122,22 @@ class GrpcPaymentAccountsService extends PaymentAccountsGrpc.PaymentAccountsImpl
exceptionHandler.handleException(cause, responseObserver);
}
}
final ServerInterceptor[] interceptors() {
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
return rateMeteringInterceptor.map(serverInterceptor ->
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
}
final Optional<ServerInterceptor> rateMeteringInterceptor() {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put("createPaymentAccount", new GrpcCallRateMeter(1, MINUTES));
put("getPaymentAccounts", new GrpcCallRateMeter(1, SECONDS));
put("getPaymentMethods", new GrpcCallRateMeter(1, SECONDS));
put("getPaymentAccountForm", new GrpcCallRateMeter(1, SECONDS));
}}
)));
}
}

View file

@ -23,12 +23,24 @@ import bisq.proto.grpc.MarketPriceReply;
import bisq.proto.grpc.MarketPriceRequest;
import bisq.proto.grpc.PriceGrpc;
import io.grpc.ServerInterceptor;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
@Slf4j
class GrpcPriceService extends PriceGrpc.PriceImplBase {
@ -55,4 +67,19 @@ class GrpcPriceService extends PriceGrpc.PriceImplBase {
exceptionHandler.handleException(cause, responseObserver);
}
}
final ServerInterceptor[] interceptors() {
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
return rateMeteringInterceptor.map(serverInterceptor ->
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
}
final Optional<ServerInterceptor> rateMeteringInterceptor() {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put("getMarketPrice", new GrpcCallRateMeter(1, SECONDS));
}}
)));
}
}

View file

@ -60,15 +60,15 @@ public class GrpcServer {
GrpcWalletsService walletsService) {
this.server = ServerBuilder.forPort(config.apiPort)
.executor(UserThread.getExecutor())
.addService(disputeAgentsService)
.addService(helpService)
.addService(offersService)
.addService(paymentAccountsService)
.addService(priceService)
.addService(tradeStatisticsService)
.addService(tradesService)
.addService(interceptForward(disputeAgentsService, disputeAgentsService.interceptors()))
.addService(interceptForward(helpService, helpService.interceptors()))
.addService(interceptForward(offersService, offersService.interceptors()))
.addService(interceptForward(paymentAccountsService, paymentAccountsService.interceptors()))
.addService(interceptForward(priceService, priceService.interceptors()))
.addService(interceptForward(tradeStatisticsService, tradeStatisticsService.interceptors()))
.addService(interceptForward(tradesService, tradesService.interceptors()))
.addService(interceptForward(versionService, versionService.interceptors()))
.addService(walletsService)
.addService(interceptForward(walletsService, walletsService.interceptors()))
.intercept(passwordAuthInterceptor)
.build();
coreContext.setApiUser(true);

View file

@ -35,13 +35,25 @@ import bisq.proto.grpc.TradesGrpc;
import bisq.proto.grpc.WithdrawFundsReply;
import bisq.proto.grpc.WithdrawFundsRequest;
import io.grpc.ServerInterceptor;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import static bisq.core.api.model.TradeInfo.toTradeInfo;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
@Slf4j
class GrpcTradesService extends TradesGrpc.TradesImplBase {
@ -142,4 +154,24 @@ class GrpcTradesService extends TradesGrpc.TradesImplBase {
exceptionHandler.handleException(cause, responseObserver);
}
}
final ServerInterceptor[] interceptors() {
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
return rateMeteringInterceptor.map(serverInterceptor ->
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
}
final Optional<ServerInterceptor> rateMeteringInterceptor() {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put("getTrade", new GrpcCallRateMeter(1, SECONDS));
put("takeOffer", new GrpcCallRateMeter(1, MINUTES));
put("confirmPaymentStarted", new GrpcCallRateMeter(1, MINUTES));
put("confirmPaymentReceived", new GrpcCallRateMeter(1, MINUTES));
put("keepFunds", new GrpcCallRateMeter(1, MINUTES));
put("withdrawFunds", new GrpcCallRateMeter(1, MINUTES));
}}
)));
}
}

View file

@ -74,13 +74,11 @@ public class GrpcVersionService extends GetVersionGrpc.GetVersionImplBase {
}
final Optional<ServerInterceptor> rateMeteringInterceptor() {
@SuppressWarnings("unused") // Defined as a usage example.
CallRateMeteringInterceptor defaultCallRateMeteringInterceptor =
new CallRateMeteringInterceptor(new HashMap<>() {{
put("getVersion", new GrpcCallRateMeter(100, SECONDS));
}});
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(Optional::empty /* Optional.of(defaultCallRateMeteringInterceptor) */);
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put("getVersion", new GrpcCallRateMeter(1, SECONDS));
}}
)));
}
}

View file

@ -53,6 +53,7 @@ import bisq.proto.grpc.UnsetTxFeeRatePreferenceReply;
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.WalletsGrpc;
import io.grpc.ServerInterceptor;
import io.grpc.stub.StreamObserver;
import org.bitcoinj.core.Transaction;
@ -61,7 +62,9 @@ import javax.inject.Inject;
import com.google.common.util.concurrent.FutureCallback;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@ -69,6 +72,14 @@ import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import static bisq.core.api.model.TxInfo.toTxInfo;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
@Slf4j
class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
@ -330,4 +341,36 @@ class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
exceptionHandler.handleException(cause, responseObserver);
}
}
final ServerInterceptor[] interceptors() {
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
return rateMeteringInterceptor.map(serverInterceptor ->
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
}
final Optional<ServerInterceptor> rateMeteringInterceptor() {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put("getBalances", new GrpcCallRateMeter(1, SECONDS));
put("getAddressBalance", new GrpcCallRateMeter(1, SECONDS));
put("getFundingAddresses", new GrpcCallRateMeter(1, SECONDS));
put("getUnusedBsqAddress", new GrpcCallRateMeter(1, SECONDS));
put("sendBsq", new GrpcCallRateMeter(1, MINUTES));
put("sendBtc", new GrpcCallRateMeter(1, MINUTES));
put("getTxFeeRate", new GrpcCallRateMeter(1, SECONDS));
put("setTxFeeRatePreference", new GrpcCallRateMeter(1, SECONDS));
put("unsetTxFeeRatePreference", new GrpcCallRateMeter(1, SECONDS));
put("getTransaction", new GrpcCallRateMeter(1, SECONDS));
// Trying to set or remove a wallet password several times before the 1st attempt has time to
// persist the change to disk may corrupt the wallet, so allow only 1 attempt per 5 seconds.
put("setWalletPassword", new GrpcCallRateMeter(1, SECONDS, 5));
put("removeWalletPassword", new GrpcCallRateMeter(1, SECONDS, 5));
put("lockWallet", new GrpcCallRateMeter(1, SECONDS));
put("unlockWallet", new GrpcCallRateMeter(1, SECONDS));
}}
)));
}
}

View file

@ -25,6 +25,7 @@ import io.grpc.StatusRuntimeException;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@ -124,4 +125,10 @@ public final class CallRateMeteringInterceptor implements ServerInterceptor {
rateMetersString + "\n\t" + "}" + "\n"
+ "}";
}
public static CallRateMeteringInterceptor valueOf(Map<String, GrpcCallRateMeter> rateMeters) {
return new CallRateMeteringInterceptor(new HashMap<>() {{
putAll(rateMeters);
}});
}
}

View file

@ -76,6 +76,7 @@ public class GrpcServiceRateMeteringConfig {
this.methodRateMeters = methodRateMeters;
}
@SuppressWarnings("unused")
public GrpcServiceRateMeteringConfig addMethodCallRateMeter(String methodName,
int maxCalls,
TimeUnit timeUnit) {

View file

@ -8,7 +8,7 @@
# pull base image
FROM openjdk:8-jdk
ENV version 1.5.5-SNAPSHOT
ENV version 1.5.6-SNAPSHOT
RUN apt-get update && apt-get install -y --no-install-recommends openjfx && rm -rf /var/lib/apt/lists/* &&
apt-get install -y vim fakeroot

View file

@ -6,7 +6,7 @@
# - Update version below
# - Ensure JAVA_HOME below is pointing to OracleJDK 10 directory
version=1.5.5-SNAPSHOT
version=1.5.6-SNAPSHOT
version_base=$(echo $version | awk -F'[_-]' '{print $1}')
if [ ! -f "$JAVA_HOME/bin/javapackager" ]; then
if [ -d "/usr/lib/jvm/jdk-10.0.2" ]; then

View file

@ -4,7 +4,7 @@
# Prior to running this script:
# - Update version below
version=1.5.5-SNAPSHOT
version=1.5.6-SNAPSHOT
base_dir=$( cd "$(dirname "$0")" ; pwd -P )/../../..
package_dir=$base_dir/desktop/package
release_dir=$base_dir/desktop/release/$version

View file

@ -5,10 +5,10 @@
<!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -->
<key>CFBundleVersion</key>
<string>1.5.5</string>
<string>1.5.6</string>
<key>CFBundleShortVersionString</key>
<string>1.5.5</string>
<string>1.5.6</string>
<key>CFBundleExecutable</key>
<string>Bisq</string>

View file

@ -6,7 +6,7 @@ mkdir -p deploy
set -e
version="1.5.5-SNAPSHOT"
version="1.5.6-SNAPSHOT"
cd ..
./gradlew :desktop:build -x test shadowJar

View file

@ -2,7 +2,7 @@
cd ../../
version="1.5.5-SNAPSHOT"
version="1.5.6-SNAPSHOT"
target_dir="releases/$version"

View file

@ -2,7 +2,7 @@
cd $(dirname $0)/../../../
version=1.5.5
version=1.5.6
find . -type f \( -name "finalize.sh" \
-o -name "create_app.sh" \

View file

@ -2,8 +2,8 @@
cd $(dirname $0)/../../../.
oldVersion=1.5.4
newVersion=1.5.5
oldVersion=1.5.5
newVersion=1.5.6
find . -type f \( -name "finalize.sh" \
-o -name "create_app.sh" \

View file

@ -11,7 +11,7 @@
@echo off
set version=1.5.5-SNAPSHOT
set version=1.5.6-SNAPSHOT
if not exist "%JAVA_HOME%\bin\javapackager.exe" (
if not exist "%ProgramFiles%\Java\jdk-10.0.2" (
echo Javapackager not found. Update JAVA_HOME variable to point to OracleJDK.

View file

@ -6,7 +6,7 @@
@echo off
set version=1.5.5-SNAPSHOT
set version=1.5.6-SNAPSHOT
set release_dir=%~dp0..\..\..\releases\%version%
set package_dir=%~dp0..

View file

@ -1,139 +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.desktop.components;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.listeners.AddressConfidenceListener;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import com.jfoenix.controls.JFXTextField;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
public class BalanceWithConfirmationTextField extends AnchorPane {
private static BtcWalletService walletService;
private BalanceListener balanceListener;
private AddressConfidenceListener confidenceListener;
public static void setWalletService(BtcWalletService walletService) {
BalanceWithConfirmationTextField.walletService = walletService;
}
private final TextField textField;
private final Tooltip progressIndicatorTooltip;
private final TxConfidenceIndicator txConfidenceIndicator;
private final Effect fundedEffect = new DropShadow(BlurType.THREE_PASS_BOX, Color.GREEN, 4, 0.0, 0, 0);
private final Effect notFundedEffect = new DropShadow(BlurType.THREE_PASS_BOX, Color.ORANGERED, 4, 0.0, 0, 0);
private CoinFormatter formatter;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public BalanceWithConfirmationTextField() {
textField = new JFXTextField();
textField.setFocusTraversable(false);
textField.setEditable(false);
txConfidenceIndicator = new TxConfidenceIndicator();
txConfidenceIndicator.setFocusTraversable(false);
txConfidenceIndicator.setPrefSize(24, 24);
txConfidenceIndicator.setId("funds-confidence");
txConfidenceIndicator.setLayoutY(1);
txConfidenceIndicator.setProgress(0);
txConfidenceIndicator.setVisible(false);
progressIndicatorTooltip = new Tooltip("-");
Tooltip.install(txConfidenceIndicator, progressIndicatorTooltip);
AnchorPane.setRightAnchor(txConfidenceIndicator, 0.0);
AnchorPane.setRightAnchor(textField, 55.0);
AnchorPane.setLeftAnchor(textField, 0.0);
getChildren().addAll(textField, txConfidenceIndicator);
}
public void cleanup() {
walletService.removeBalanceListener(balanceListener);
walletService.removeAddressConfidenceListener(confidenceListener);
}
public void setup(Address address, CoinFormatter formatter) {
this.formatter = formatter;
confidenceListener = new AddressConfidenceListener(address) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence);
}
};
walletService.addAddressConfidenceListener(confidenceListener);
updateConfidence(walletService.getConfidenceForAddress(address));
balanceListener = new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance);
}
};
walletService.addBalanceListener(balanceListener);
updateBalance(walletService.getBalanceForAddress(address));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
private void updateConfidence(TransactionConfidence confidence) {
GUIUtil.updateConfidence(confidence, progressIndicatorTooltip, txConfidenceIndicator);
if (confidence != null) {
if (txConfidenceIndicator.getProgress() != 0) {
txConfidenceIndicator.setVisible(true);
AnchorPane.setRightAnchor(txConfidenceIndicator, 0.0);
AnchorPane.setRightAnchor(textField, 35.0);
}
}
}
private void updateBalance(Coin balance) {
textField.setText(formatter.formatCoinWithCode(balance));
if (balance.isPositive())
textField.setEffect(fundedEffect);
else
textField.setEffect(notFundedEffect);
}
}

View file

@ -18,10 +18,11 @@
package bisq.desktop.components.paymentmethods;
import bisq.desktop.components.InputTextField;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.Layout;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.locale.Country;
import bisq.core.locale.CountryUtil;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.locale.TradeCurrency;
@ -33,25 +34,41 @@ import bisq.core.payment.payload.PaymentMethod;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.validation.InputValidator;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.collections.FXCollections;
import javafx.util.StringConverter;
import java.util.HashMap;
import lombok.extern.slf4j.Slf4j;
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
import static bisq.desktop.util.FormBuilder.addInputTextField;
import static bisq.desktop.util.FormBuilder.addTopLabelTextField;
import static bisq.desktop.util.FormBuilder.*;
@Slf4j
public class AmazonGiftCardForm extends PaymentMethodForm {
private InputTextField accountNrInputTextField;
ComboBox<Country> countryCombo;
private final AmazonGiftCardAccount amazonGiftCardAccount;
public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccountPayload paymentAccountPayload) {
FormBuilder.addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.get("payment.email.mobile"),
((AmazonGiftCardAccountPayload) paymentAccountPayload).getEmailOrMobileNr());
AmazonGiftCardAccountPayload amazonGiftCardAccountPayload = (AmazonGiftCardAccountPayload) paymentAccountPayload;
addTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, Res.get("payment.amazon.site"),
countryToAmazonSite(amazonGiftCardAccountPayload.getCountryCode()),
Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE);
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.get("payment.email.mobile"),
amazonGiftCardAccountPayload.getEmailOrMobileNr());
String countryText = CountryUtil.getNameAndCode(amazonGiftCardAccountPayload.getCountryCode());
if (countryText.isEmpty()) {
countryText = Res.get("payment.ask");
}
addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1,
Res.get("shared.country"),
countryText);
return gridRow;
}
@ -66,11 +83,6 @@ public class AmazonGiftCardForm extends PaymentMethodForm {
this.amazonGiftCardAccount = (AmazonGiftCardAccount) paymentAccount;
}
public void addTradeCurrency() {
addTradeCurrencyComboBox();
currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getAllAmazonGiftCardCurrencies()));
}
@Override
public void addFormForAddAccount() {
gridRowFrom = gridRow + 1;
@ -82,13 +94,33 @@ public class AmazonGiftCardForm extends PaymentMethodForm {
updateFromInputs();
});
addTradeCurrency();
countryCombo = addComboBox(gridPane, ++gridRow, Res.get("shared.country"));
countryCombo.setPromptText(Res.get("payment.select.country"));
countryCombo.setItems(FXCollections.observableArrayList(CountryUtil.getAllAmazonGiftCardCountries()));
TextField ccyField = addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.currency"), "").second;
countryCombo.setConverter(new StringConverter<>() {
@Override
public String toString(Country country) {
return country.name + " (" + country.code + ")";
}
@Override
public Country fromString(String s) {
return null;
}
});
countryCombo.setOnAction(e -> {
Country countryCode = countryCombo.getValue();
amazonGiftCardAccount.setCountry(countryCode);
TradeCurrency currency = CurrencyUtil.getCurrencyByCountryCode(countryCode.code);
paymentAccount.setSingleTradeCurrency(currency);
ccyField.setText(currency.getNameAndCode());
updateFromInputs();
});
addLimitations(false);
addAccountNameTextFieldWithAutoFillToggleButton();
}
@Override
protected void autoFillNameTextField() {
setAccountNameWithString(accountNrInputTextField.getText());
@ -121,9 +153,33 @@ public class AmazonGiftCardForm extends PaymentMethodForm {
Res.get("payment.email.mobile"), accountNr).second;
field.setMouseTransparent(false);
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.country"),
amazonGiftCardAccount.getCountry() != null ? amazonGiftCardAccount.getCountry().name : "");
String nameAndCode = singleTradeCurrency != null ? singleTradeCurrency.getNameAndCode() : "";
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.currency"), nameAndCode);
addLimitations(true);
}
private static String countryToAmazonSite(String countryCode) {
HashMap<String, String> mapCountryToSite = new HashMap<>() {{
put("AU", "https://www.amazon.au");
put("CA", "https://www.amazon.ca");
put("FR", "https://www.amazon.fr");
put("DE", "https://www.amazon.de");
put("IT", "https://www.amazon.it");
put("NL", "https://www.amazon.nl");
put("ES", "https://www.amazon.es");
put("UK", "https://www.amazon.co.uk");
put("IN", "https://www.amazon.in");
put("JP", "https://www.amazon.co.jp");
put("SA", "https://www.amazon.sa");
put("SE", "https://www.amazon.se");
put("SG", "https://www.amazon.sg");
put("TR", "https://www.amazon.tr");
put("US", "https://www.amazon.com");
put("", Res.get("payment.ask"));
}};
return mapCountryToSite.get(countryCode);
}
}

View file

@ -19,7 +19,6 @@ package bisq.desktop.main;
import bisq.desktop.app.BisqApp;
import bisq.desktop.common.model.ViewModel;
import bisq.desktop.components.BalanceWithConfirmationTextField;
import bisq.desktop.components.TxIdTextField;
import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.main.overlays.notifications.NotificationCenter;
@ -27,6 +26,7 @@ import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.DisplayAlertMessageWindow;
import bisq.desktop.main.overlays.windows.TacWindow;
import bisq.desktop.main.overlays.windows.TorNetworkSettingsWindow;
import bisq.desktop.main.overlays.windows.UpdateAmazonGiftCardAccountWindow;
import bisq.desktop.main.overlays.windows.UpdateRevolutAccountWindow;
import bisq.desktop.main.overlays.windows.WalletPasswordWindow;
import bisq.desktop.main.overlays.windows.downloadupdate.DisplayUpdateDownloadWindow;
@ -51,6 +51,7 @@ import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.AliPayAccount;
import bisq.core.payment.AmazonGiftCardAccount;
import bisq.core.payment.CryptoCurrencyAccount;
import bisq.core.payment.RevolutAccount;
import bisq.core.payment.payload.AssetsAccountPayload;
@ -207,7 +208,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
TxIdTextField.setPreferences(preferences);
TxIdTextField.setWalletService(btcWalletService);
BalanceWithConfirmationTextField.setWalletService(btcWalletService);
GUIUtil.setFeeService(feeService);
GUIUtil.setPreferences(preferences);
@ -410,6 +410,10 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
// We copy the array as we will mutate it later
showRevolutAccountUpdateWindow(new ArrayList<>(revolutAccountList));
});
bisqSetup.setAmazonGiftCardAccountsUpdateHandler(amazonGiftCardAccountList -> {
// We copy the array as we will mutate it later
showAmazonGiftCardAccountUpdateWindow(new ArrayList<>(amazonGiftCardAccountList));
});
bisqSetup.setOsxKeyLoggerWarningHandler(() -> {
String key = "osxKeyLoggerWarning";
if (preferences.showAgain(key)) {
@ -487,6 +491,17 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
}
}
private void showAmazonGiftCardAccountUpdateWindow(List<AmazonGiftCardAccount> amazonGiftCardAccountList) {
if (!amazonGiftCardAccountList.isEmpty()) {
AmazonGiftCardAccount amazonGiftCardAccount = amazonGiftCardAccountList.get(0);
amazonGiftCardAccountList.remove(0);
new UpdateAmazonGiftCardAccountWindow(amazonGiftCardAccount, user).onClose(() -> {
// We delay a bit in case we have multiple account for better UX
UserThread.runAfter(() -> showAmazonGiftCardAccountUpdateWindow(amazonGiftCardAccountList), 300, TimeUnit.MILLISECONDS);
}).show();
}
}
private void setupP2PNumPeersWatcher() {
p2PService.getNumConnectedPeers().addListener((observable, oldValue, newValue) -> {
int numPeers = (int) newValue;

View file

@ -32,67 +32,84 @@ import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import com.google.common.base.Suppliers;
import javafx.scene.control.Tooltip;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Supplier;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class DepositListItem {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final StringProperty balance = new SimpleStringProperty();
private final BtcWalletService walletService;
private Coin balanceAsCoin;
private final TxConfidenceIndicator txConfidenceIndicator;
private final Tooltip tooltip;
private final String addressString;
private String usage = "-";
private TxConfidenceListener txConfidenceListener;
private BalanceListener balanceListener;
private int numTxOutputs = 0;
private final Supplier<LazyFields> lazyFieldsSupplier;
public DepositListItem(AddressEntry addressEntry, BtcWalletService walletService, CoinFormatter formatter) {
private static class LazyFields {
TxConfidenceIndicator txConfidenceIndicator;
Tooltip tooltip;
}
private LazyFields lazy() {
return lazyFieldsSupplier.get();
}
DepositListItem(AddressEntry addressEntry, BtcWalletService walletService, CoinFormatter formatter) {
this.walletService = walletService;
addressString = addressEntry.getAddressString();
// confidence
txConfidenceIndicator = new TxConfidenceIndicator();
txConfidenceIndicator.setId("funds-confidence");
tooltip = new Tooltip(Res.get("shared.notUsedYet"));
txConfidenceIndicator.setProgress(0);
txConfidenceIndicator.setTooltip(tooltip);
Address address = addressEntry.getAddress();
TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
final Address address = addressEntry.getAddress();
walletService.addBalanceListener(new BalanceListener(address) {
// confidence
lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{
txConfidenceIndicator = new TxConfidenceIndicator();
txConfidenceIndicator.setId("funds-confidence");
tooltip = new Tooltip(Res.get("shared.notUsedYet"));
txConfidenceIndicator.setProgress(0);
txConfidenceIndicator.setTooltip(tooltip);
if (confidence != null) {
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
}
}});
if (confidence != null) {
txConfidenceListener = new TxConfidenceListener(confidence.getTransactionHash().toString()) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
GUIUtil.updateConfidence(confidence, lazy().tooltip, lazy().txConfidenceIndicator);
}
};
walletService.addTxConfidenceListener(txConfidenceListener);
}
balanceListener = new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) {
DepositListItem.this.balanceAsCoin = balanceAsCoin;
DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin));
GUIUtil.updateConfidence(walletService.getConfidenceForTxId(tx.getTxId().toString()), tooltip, txConfidenceIndicator);
var confidence = walletService.getConfidenceForTxId(tx.getTxId().toString());
GUIUtil.updateConfidence(confidence, lazy().tooltip, lazy().txConfidenceIndicator);
updateUsage(address);
}
});
};
walletService.addBalanceListener(balanceListener);
balanceAsCoin = walletService.getBalanceForAddress(address);
balance.set(formatter.formatCoin(balanceAsCoin));
updateUsage(address);
TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
if (confidence != null) {
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
txConfidenceListener = new TxConfidenceListener(confidence.getTransactionHash().toString()) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
}
};
walletService.addTxConfidenceListener(txConfidenceListener);
}
}
private void updateUsage(Address address) {
@ -102,10 +119,11 @@ class DepositListItem {
public void cleanup() {
walletService.removeTxConfidenceListener(txConfidenceListener);
walletService.removeBalanceListener(balanceListener);
}
public TxConfidenceIndicator getTxConfidenceIndicator() {
return txConfidenceIndicator;
return lazy().txConfidenceIndicator;
}
public String getAddressString() {

View file

@ -36,13 +36,11 @@ import org.bitcoinj.core.TransactionOutput;
import javafx.collections.ObservableList;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
class TransactionAwareTrade implements TransactionAwareTradable {
private final Trade trade;
@ -65,32 +63,31 @@ class TransactionAwareTrade implements TransactionAwareTradable {
@Override
public boolean isRelatedToTransaction(Transaction transaction) {
String txId = transaction.getTxId().toString();
Sha256Hash hash = transaction.getTxId();
String txId = hash.toString();
boolean isTakerOfferFeeTx = txId.equals(trade.getTakerFeeTxId());
boolean isOfferFeeTx = isOfferFeeTx(txId);
boolean isDepositTx = isDepositTx(txId);
boolean isPayoutTx = isPayoutTx(txId);
boolean isDepositTx = isDepositTx(hash);
boolean isPayoutTx = isPayoutTx(hash);
boolean isDisputedPayoutTx = isDisputedPayoutTx(txId);
boolean isDelayedPayoutTx = isDelayedPayoutTx(txId);
boolean isDelayedPayoutTx = transaction.getLockTime() != 0 && isDelayedPayoutTx(txId);
boolean isRefundPayoutTx = isRefundPayoutTx(txId);
return isTakerOfferFeeTx || isOfferFeeTx || isDepositTx || isPayoutTx ||
isDisputedPayoutTx || isDelayedPayoutTx || isRefundPayoutTx;
}
private boolean isPayoutTx(String txId) {
private boolean isPayoutTx(Sha256Hash txId) {
return Optional.ofNullable(trade.getPayoutTx())
.map(Transaction::getTxId)
.map(Sha256Hash::toString)
.map(hash -> hash.equals(txId))
.orElse(false);
}
private boolean isDepositTx(String txId) {
private boolean isDepositTx(Sha256Hash txId) {
return Optional.ofNullable(trade.getDepositTx())
.map(Transaction::getTxId)
.map(Sha256Hash::toString)
.map(hash -> hash.equals(txId))
.orElse(false);
}
@ -104,9 +101,11 @@ class TransactionAwareTrade implements TransactionAwareTradable {
private boolean isDisputedPayoutTx(String txId) {
String delegateId = trade.getId();
ObservableList<Dispute> disputes = arbitrationManager.getDisputesAsObservableList();
return disputes.stream()
boolean isAnyDisputeRelatedToThis = arbitrationManager.getDisputedTradeIds().contains(trade.getId());
return isAnyDisputeRelatedToThis && disputes.stream()
.anyMatch(dispute -> {
String disputePayoutTxId = dispute.getDisputePayoutTxId();
boolean isDisputePayoutTx = txId.equals(disputePayoutTxId);
@ -139,42 +138,37 @@ class TransactionAwareTrade implements TransactionAwareTradable {
if (parentTransaction == null) {
return false;
}
return isDepositTx(parentTransaction.getTxId().toString());
return isDepositTx(parentTransaction.getTxId());
});
}
private boolean isRefundPayoutTx(String txId) {
String tradeId = trade.getId();
ObservableList<Dispute> disputes = refundManager.getDisputesAsObservableList();
AtomicBoolean isRefundTx = new AtomicBoolean(false);
AtomicBoolean isDisputeRelatedToThis = new AtomicBoolean(false);
disputes.forEach(dispute -> {
String disputeTradeId = dispute.getTradeId();
isDisputeRelatedToThis.set(tradeId.equals(disputeTradeId));
if (isDisputeRelatedToThis.get()) {
Transaction tx = btcWalletService.getTransaction(txId);
if (tx != null) {
tx.getOutputs().forEach(txo -> {
if (btcWalletService.isTransactionOutputMine(txo)) {
try {
Address receiverAddress = txo.getScriptPubKey().getToAddress(btcWalletService.getParams());
Contract contract = checkNotNull(trade.getContract());
String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ?
contract.getBuyerPayoutAddressString() :
contract.getSellerPayoutAddressString();
if (receiverAddress != null && myPayoutAddressString.equals(receiverAddress.toString())) {
isRefundTx.set(true);
}
} catch (Throwable ignore) {
}
boolean isAnyDisputeRelatedToThis = refundManager.getDisputedTradeIds().contains(tradeId);
if (isAnyDisputeRelatedToThis) {
Transaction tx = btcWalletService.getTransaction(txId);
if (tx != null) {
for (TransactionOutput txo : tx.getOutputs()) {
if (btcWalletService.isTransactionOutputMine(txo)) {
try {
Address receiverAddress = txo.getScriptPubKey().getToAddress(btcWalletService.getParams());
Contract contract = checkNotNull(trade.getContract());
String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ?
contract.getBuyerPayoutAddressString() :
contract.getSellerPayoutAddressString();
if (receiverAddress != null && myPayoutAddressString.equals(receiverAddress.toString())) {
return true;
}
} catch (RuntimeException ignore) {
}
});
}
}
}
});
return isRefundTx.get() && isDisputeRelatedToThis.get();
}
return false;
}
@Override

View file

@ -40,10 +40,13 @@ import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionOutput;
import com.google.common.base.Suppliers;
import javafx.scene.control.Tooltip;
import java.util.Date;
import java.util.Optional;
import java.util.function.Supplier;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@ -57,8 +60,6 @@ class TransactionsListItem {
private String dateString;
private final Date date;
private final String txId;
private final TxConfidenceIndicator txConfidenceIndicator;
private final Tooltip tooltip;
@Nullable
private Tradable tradable;
private String details = "";
@ -72,16 +73,26 @@ class TransactionsListItem {
private int confirmations = 0;
@Getter
private final boolean isDustAttackTx;
private boolean initialTxConfidenceVisibility = true;
private final Supplier<LazyFields> lazyFieldsSupplier;
private static class LazyFields {
TxConfidenceIndicator txConfidenceIndicator;
Tooltip tooltip;
}
private LazyFields lazy() {
return lazyFieldsSupplier.get();
}
// used at exportCSV
TransactionsListItem() {
date = null;
btcWalletService = null;
txConfidenceIndicator = null;
tooltip = null;
txId = null;
formatter = null;
isDustAttackTx = false;
lazyFieldsSupplier = null;
}
TransactionsListItem(Transaction transaction,
@ -181,26 +192,6 @@ class TransactionsListItem {
//addressString = "";
}
// confidence
txConfidenceIndicator = new TxConfidenceIndicator();
txConfidenceIndicator.setId("funds-confidence");
tooltip = new Tooltip(Res.get("shared.notUsedYet"));
txConfidenceIndicator.setProgress(0);
txConfidenceIndicator.setTooltip(tooltip);
txConfidenceListener = new TxConfidenceListener(txId) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
confirmations = confidence.getDepthInBlocks();
}
};
btcWalletService.addTxConfidenceListener(txConfidenceListener);
TransactionConfidence confidence = transaction.getConfidence();
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
confirmations = confidence.getDepthInBlocks();
if (optionalTradable.isPresent()) {
tradable = optionalTradable.get();
detailsAvailable = true;
@ -225,7 +216,7 @@ class TransactionsListItem {
details = Res.get("funds.tx.multiSigPayout", tradeId);
if (amountAsCoin.isZero()) {
txConfidenceIndicator.setVisible(false);
initialTxConfidenceVisibility = false;
}
} else {
Trade.DisputeState disputeState = trade.getDisputeState();
@ -234,7 +225,7 @@ class TransactionsListItem {
details = Res.get("funds.tx.disputePayout", tradeId);
} else {
details = Res.get("funds.tx.disputeLost", tradeId);
txConfidenceIndicator.setVisible(false);
initialTxConfidenceVisibility = false;
}
} else if (disputeState == Trade.DisputeState.REFUND_REQUEST_CLOSED ||
disputeState == Trade.DisputeState.REFUND_REQUESTED ||
@ -249,12 +240,12 @@ class TransactionsListItem {
// left our wallet nor we received funds. So we set indicator invisible.
amountAsCoin = Coin.ZERO;
details = Res.get("funds.tx.collateralForRefund", tradeId);
txConfidenceIndicator.setVisible(false);
initialTxConfidenceVisibility = false;
}
} else {
if (transactionAwareTrade.isDelayedPayoutTx(txId)) {
details = Res.get("funds.tx.timeLockedPayoutTx", tradeId);
txConfidenceIndicator.setVisible(false);
initialTxConfidenceVisibility = false;
} else {
details = Res.get("funds.tx.unknown", tradeId);
}
@ -265,7 +256,7 @@ class TransactionsListItem {
} else {
if (amountAsCoin.isZero()) {
details = Res.get("funds.tx.noFundsFromDispute");
txConfidenceIndicator.setVisible(false);
initialTxConfidenceVisibility = false;
} else if (withdrawalFromBSQWallet) {
details = Res.get("funds.tx.withdrawnFromBSQWallet");
} else if (!txFeeForBsqPayment) {
@ -282,6 +273,29 @@ class TransactionsListItem {
if (isDustAttackTx) {
details = Res.get("funds.tx.dustAttackTx");
}
// confidence
lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{
txConfidenceIndicator = new TxConfidenceIndicator();
txConfidenceIndicator.setId("funds-confidence");
tooltip = new Tooltip(Res.get("shared.notUsedYet"));
txConfidenceIndicator.setProgress(0);
txConfidenceIndicator.setTooltip(tooltip);
txConfidenceIndicator.setVisible(initialTxConfidenceVisibility);
TransactionConfidence confidence = transaction.getConfidence();
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
confirmations = confidence.getDepthInBlocks();
}});
txConfidenceListener = new TxConfidenceListener(txId) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
GUIUtil.updateConfidence(confidence, lazy().tooltip, lazy().txConfidenceIndicator);
confirmations = confidence.getDepthInBlocks();
}
};
btcWalletService.addTxConfidenceListener(txConfidenceListener);
}
public void cleanup() {
@ -290,7 +304,7 @@ class TransactionsListItem {
public TxConfidenceIndicator getTxConfidenceIndicator() {
return txConfidenceIndicator;
return lazy().txConfidenceIndicator;
}
public final String getDateString() {
@ -344,6 +358,7 @@ class TransactionsListItem {
return String.valueOf(confirmations);
}
public String getMemo() { return memo; }
public String getMemo() {
return memo;
}
}

View file

@ -240,8 +240,10 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
volumeAxisYWidth = (double) newValue;
layoutChart();
};
tradeStatisticsByCurrencyListener = c -> nrOfTradeStatisticsLabel.setText(Res.get("market.trades.nrOfTrades",
model.selectedTradeStatistics.size()));
tradeStatisticsByCurrencyListener = c -> {
nrOfTradeStatisticsLabel.setText(Res.get("market.trades.nrOfTrades", model.selectedTradeStatistics.size()));
fillList();
};
parentHeightListener = (observable, oldValue, newValue) -> layout();
priceColumnLabelListener = (o, oldVal, newVal) -> priceColumn.setGraphic(new AutoTooltipLabel(newVal));
@ -308,7 +310,6 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
CurrencyListItem selectedItem = currencyComboBox.getSelectionModel().getSelectedItem();
if (selectedItem != null) {
model.onSetTradeCurrency(selectedItem.tradeCurrency);
fillList();
}
});
@ -396,14 +397,12 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
}
private void fillList() {
ObservableList<TradeStatistics3ListItem> tradeStatistics3ListItems = FXCollections.observableList(
model.selectedTradeStatistics.stream()
.map(tradeStatistics -> new TradeStatistics3ListItem(tradeStatistics,
coinFormatter,
model.showAllTradeCurrenciesProperty.get()))
.collect(Collectors.toList()));
listItems.clear();
listItems.addAll(tradeStatistics3ListItems);
List<TradeStatistics3ListItem> tradeStatistics3ListItems = model.selectedTradeStatistics.stream()
.map(tradeStatistics -> new TradeStatistics3ListItem(tradeStatistics,
coinFormatter,
model.showAllTradeCurrenciesProperty.get()))
.collect(Collectors.toList());
listItems.setAll(tradeStatistics3ListItems);
}
private void exportToCsv() {

View file

@ -792,6 +792,8 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
// if not reset here. Not clear why...
triggerPriceValidationResult.set(new InputValidator.ValidationResult(true));
if (dataModel.getPrice().get() == null) // fix NPE @ bisq/issues/5166
return;
InputValidator.ValidationResult result = PriceUtil.isTriggerPriceValid(triggerPriceAsString,
dataModel.getPrice().get(),
dataModel.isSellOffer(),

View file

@ -50,7 +50,7 @@ public class DisplayAlertMessageWindow extends Overlay<DisplayAlertMessageWindow
checkNotNull(alert, "alertMessage must not be null");
if (alert.isUpdateInfo()) {
if (alert.isSoftwareUpdateNotification()) {
information("");
headLine = Res.get("displayAlertMessageWindow.update.headline");
} else {
@ -78,7 +78,7 @@ public class DisplayAlertMessageWindow extends Overlay<DisplayAlertMessageWindow
private void addContent() {
checkNotNull(alert, "alertMessage must not be null");
addMultilineLabel(gridPane, ++rowIndex, alert.getMessage(), 10);
if (alert.isUpdateInfo()) {
if (alert.isSoftwareUpdateNotification()) {
String url = "https://bisq.network/downloads";
HyperlinkWithIcon hyperlinkWithIcon = FormBuilder.addLabelHyperlinkWithIcon(gridPane, ++rowIndex,
Res.get("displayAlertMessageWindow.update.download"), url, url).second;

View file

@ -38,7 +38,6 @@ import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.provider.fee.FeeService;
import bisq.core.support.SupportType;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeList;
@ -105,7 +104,6 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
private final TradeWalletService tradeWalletService;
private final BtcWalletService btcWalletService;
private final TxFeeEstimationService txFeeEstimationService;
private final FeeService feeService;
private final DaoFacade daoFacade;
private Dispute dispute;
private Optional<Runnable> finalizeDisputeHandlerOptional = Optional.empty();
@ -143,7 +141,6 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
TradeWalletService tradeWalletService,
BtcWalletService btcWalletService,
TxFeeEstimationService txFeeEstimationService,
FeeService feeService,
DaoFacade daoFacade) {
this.formatter = formatter;
@ -152,7 +149,6 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
this.tradeWalletService = tradeWalletService;
this.btcWalletService = btcWalletService;
this.txFeeEstimationService = txFeeEstimationService;
this.feeService = feeService;
this.daoFacade = daoFacade;
type = Type.Confirmation;
@ -672,7 +668,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
Coin sellerPayoutAmount = disputeResult.getSellerPayoutAmount();
String sellerPayoutAddressString = contract.getSellerPayoutAddressString();
Coin outputAmount = buyerPayoutAmount.add(sellerPayoutAmount);
Tuple2<Coin, Integer> feeTuple = txFeeEstimationService.getEstimatedFeeAndTxVsize(outputAmount, feeService, btcWalletService);
Tuple2<Coin, Integer> feeTuple = txFeeEstimationService.getEstimatedFeeAndTxVsize(outputAmount, btcWalletService);
Coin fee = feeTuple.first;
Integer txVsize = feeTuple.second;
double feePerVbyte = CoinUtil.getFeePerVbyte(fee, txVsize);

View file

@ -39,7 +39,9 @@ import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.control.ToggleGroup;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
@ -49,6 +51,7 @@ import javafx.geometry.Insets;
import static bisq.desktop.util.FormBuilder.addInputTextField;
import static bisq.desktop.util.FormBuilder.addLabelCheckBox;
import static bisq.desktop.util.FormBuilder.addRadioButton;
import static bisq.desktop.util.FormBuilder.addTopLabelTextArea;
public class SendAlertMessageWindow extends Overlay<SendAlertMessageWindow> {
@ -107,13 +110,26 @@ public class SendAlertMessageWindow extends Overlay<SendAlertMessageWindow> {
TextArea alertMessageTextArea = labelTextAreaTuple2.second;
Label first = labelTextAreaTuple2.first;
first.setMinWidth(150);
CheckBox isUpdateCheckBox = addLabelCheckBox(gridPane, ++rowIndex,
Res.get("sendAlertMessageWindow.isUpdate"));
CheckBox isSoftwareUpdateCheckBox = addLabelCheckBox(gridPane, ++rowIndex,
Res.get("sendAlertMessageWindow.isSoftwareUpdate"));
HBox hBoxRelease = new HBox();
hBoxRelease.setSpacing(10);
GridPane.setRowIndex(hBoxRelease, ++rowIndex);
ToggleGroup toggleGroup = new ToggleGroup();
RadioButton isUpdateCheckBox = addRadioButton(gridPane, rowIndex, toggleGroup, Res.get("sendAlertMessageWindow.isUpdate"));
RadioButton isPreReleaseCheckBox = addRadioButton(gridPane, rowIndex, toggleGroup, Res.get("sendAlertMessageWindow.isPreRelease"));
hBoxRelease.getChildren().addAll(new Label(""), isUpdateCheckBox, isPreReleaseCheckBox);
gridPane.getChildren().add(hBoxRelease);
isSoftwareUpdateCheckBox.setSelected(true);
isUpdateCheckBox.setSelected(true);
InputTextField versionInputTextField = FormBuilder.addInputTextField(gridPane, ++rowIndex,
Res.get("sendAlertMessageWindow.version"));
versionInputTextField.disableProperty().bind(isUpdateCheckBox.selectedProperty().not());
versionInputTextField.disableProperty().bind(isSoftwareUpdateCheckBox.selectedProperty().not());
isUpdateCheckBox.disableProperty().bind(isSoftwareUpdateCheckBox.selectedProperty().not());
isPreReleaseCheckBox.disableProperty().bind(isSoftwareUpdateCheckBox.selectedProperty().not());
Button sendButton = new AutoTooltipButton(Res.get("sendAlertMessageWindow.send"));
sendButton.getStyleClass().add("action-button");
@ -121,8 +137,9 @@ public class SendAlertMessageWindow extends Overlay<SendAlertMessageWindow> {
sendButton.setOnAction(e -> {
final String version = versionInputTextField.getText();
boolean versionOK = false;
final boolean isUpdate = isUpdateCheckBox.isSelected();
if (isUpdate) {
final boolean isUpdate = (isSoftwareUpdateCheckBox.isSelected() && isUpdateCheckBox.isSelected());
final boolean isPreRelease = (isSoftwareUpdateCheckBox.isSelected() && isPreReleaseCheckBox.isSelected());
if (isUpdate || isPreRelease) {
final String[] split = version.split("\\.");
versionOK = split.length == 3;
if (!versionOK) // Do not translate as only used by devs
@ -130,10 +147,10 @@ public class SendAlertMessageWindow extends Overlay<SendAlertMessageWindow> {
.onClose(this::blurAgain)
.show();
}
if (!isUpdate || versionOK) {
if (!isSoftwareUpdateCheckBox.isSelected() || versionOK) {
if (alertMessageTextArea.getText().length() > 0 && keyInputTextField.getText().length() > 0) {
if (alertManager.addAlertMessageIfKeyIsValid(
new Alert(alertMessageTextArea.getText(), isUpdate, version),
new Alert(alertMessageTextArea.getText(), isUpdate, isPreRelease, version),
keyInputTextField.getText())
)
hide();

View file

@ -0,0 +1,154 @@
/*
* 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.desktop.main.overlays.windows;
import bisq.desktop.main.overlays.Overlay;
import bisq.core.locale.Res;
import bisq.core.locale.Country;
import bisq.core.payment.AmazonGiftCardAccount;
import bisq.core.user.User;
import bisq.common.UserThread;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.collections.FXCollections;
import javafx.util.StringConverter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static bisq.core.locale.CountryUtil.findCountryByCode;
import static bisq.core.locale.CountryUtil.getAllAmazonGiftCardCountries;
import static bisq.desktop.util.FormBuilder.addComboBox;
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
import static bisq.desktop.util.FormBuilder.addLabel;
public class UpdateAmazonGiftCardAccountWindow extends Overlay<UpdateAmazonGiftCardAccountWindow> {
private final AmazonGiftCardAccount amazonGiftCardAccount;
private final User user;
private ComboBox<Country> countryCombo;
public UpdateAmazonGiftCardAccountWindow(AmazonGiftCardAccount amazonGiftCardAccount, User user) {
super();
this.amazonGiftCardAccount = amazonGiftCardAccount;
this.user = user;
type = Type.Attention;
hideCloseButton = true;
actionButtonText = Res.get("shared.save");
}
@Override
protected void setupKeyHandler(Scene scene) {
// We do not support enter or escape here
}
@Override
public void show() {
if (headLine == null)
headLine = Res.get("payment.amazonGiftCard.upgrade.headLine");
width = 868;
createGridPane();
addHeadLine();
addContent();
addButtons();
applyStyles();
display();
// when there is only one possible country to choose from just go ahead and choose it. e.g. UK, US, JP etc.
if (countryCombo.getItems().size() == 1) {
countryCombo.setValue(countryCombo.getItems().get(0));
UserThread.runAfter(() -> actionButton.fire(), 300, TimeUnit.MILLISECONDS);
}
}
private void addContent() {
addLabel(gridPane, ++rowIndex, Res.get("payment.account.amazonGiftCard.addCountryInfo", Res.get("payment.amazonGiftCard.upgrade"), amazonGiftCardAccount.getAccountName()));
addCompactTopLabelTextField(gridPane, ++rowIndex, Res.get("shared.currency"), amazonGiftCardAccount.getSingleTradeCurrency().getNameAndCode());
countryCombo = addComboBox(gridPane, ++rowIndex, Res.get("shared.country"));
countryCombo.setPromptText(Res.get("payment.select.country"));
countryCombo.setItems(FXCollections.observableArrayList(getAppropriateCountries(amazonGiftCardAccount.getSingleTradeCurrency().getCode())));
countryCombo.setConverter(new StringConverter<>() {
@Override
public String toString(Country country) {
return country.name + " (" + country.code + ")";
}
@Override
public Country fromString(String s) {
return null;
}
});
countryCombo.setOnAction(e -> {
Country countryCode = countryCombo.getValue();
actionButton.setDisable(countryCode == null || countryCode.code == null || countryCode.code.length() < 1);
});
}
@Override
protected void addButtons() {
super.addButtons();
Country countryCode = countryCombo.getValue();
if (countryCode == null || countryCode.code == null || countryCode.code.isEmpty())
actionButton.setDisable(true);
// We do not allow close in case the field is not correctly added
actionButton.setOnAction(event -> {
Country chosenCountryCode = countryCombo.getValue();
if (chosenCountryCode != null && chosenCountryCode.code != null && !chosenCountryCode.code.isEmpty()) {
amazonGiftCardAccount.setCountry(chosenCountryCode);
user.requestPersistence();
closeHandlerOptional.ifPresent(Runnable::run);
hide();
}
});
}
public static List<Country> getAppropriateCountries(String currency) {
List<Country> list = new ArrayList<>();
if (currency.equalsIgnoreCase("EUR")) {
// Eurozone countries using EUR
list = getAllAmazonGiftCardCountries();
list = list.stream().filter(e -> e.code.matches("FR|DE|IT|NL|ES")).collect(Collectors.toList());
} else {
// non-Eurozone with own ccy
HashMap<String, String> mapCcyToCountry = new HashMap<>(Map.of(
"AUD", "AU",
"CAD", "CA",
"GBP", "GB",
"INR", "IN",
"JPY", "JP",
"SAR", "SA",
"SEK", "SE",
"SGD", "SG",
"TRY", "TR",
"USD", "US"
));
Optional<Country> found = findCountryByCode(mapCcyToCountry.get(currency));
if (found.isPresent())
list.add(found.get());
}
return list;
}
}

View file

@ -63,7 +63,6 @@ import bisq.core.network.MessageState;
import bisq.core.offer.Offer;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountUtil;
import bisq.core.payment.payload.AmazonGiftCardAccountPayload;
import bisq.core.payment.payload.AssetsAccountPayload;
import bisq.core.payment.payload.CashByMailAccountPayload;
import bisq.core.payment.payload.CashDepositAccountPayload;
@ -569,9 +568,6 @@ public class BuyerStep2View extends TradeStepView {
Res.get("portfolio.pending.step2_buyer.fasterPaymentsHolderNameInfo") + "\n\n" +
refTextWarn + "\n\n" +
fees;
} else if (paymentAccountPayload instanceof AmazonGiftCardAccountPayload) {
message += Res.get("portfolio.pending.step2_buyer.amazonGiftCard", amount) +
refTextWarn;
} else if (paymentAccountPayload instanceof CashByMailAccountPayload ||
paymentAccountPayload instanceof HalCashAccountPayload) {
message += Res.get("portfolio.pending.step2_buyer.pay", amount);

View file

@ -17,7 +17,6 @@
package bisq.desktop.main.presentation;
import bisq.desktop.components.BalanceWithConfirmationTextField;
import bisq.desktop.components.TxIdTextField;
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
import bisq.desktop.util.GUIUtil;
@ -98,7 +97,6 @@ public class MarketPricePresentation {
// TODO
TxIdTextField.setWalletService(btcWalletService);
BalanceWithConfirmationTextField.setWalletService(btcWalletService);
GUIUtil.setFeeService(feeService);
}

View file

@ -120,7 +120,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox;
private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically,
avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle;
avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle,
notifyOnPreReleaseToggle;
private int gridRow = 0;
private int displayCurrenciesGridRowIndex = 0;
private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField,
@ -612,6 +613,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
sortMarketCurrenciesNumerically = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.sortWithNumOffers"));
hideNonAccountPaymentMethodsToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.onlyShowPaymentMethodsFromAccount"));
denyApiTakerToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.denyApiTaker"));
notifyOnPreReleaseToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.notifyOnPreRelease"));
resetDontShowAgainButton = addButton(root, ++gridRow, Res.get("setting.preferences.resetAllFlags"), 0);
resetDontShowAgainButton.getStyleClass().add("compact-button");
resetDontShowAgainButton.setMaxWidth(Double.MAX_VALUE);
@ -954,6 +956,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
denyApiTakerToggle.setSelected(preferences.isDenyApiTaker());
denyApiTakerToggle.setOnAction(e -> preferences.setDenyApiTaker(denyApiTakerToggle.isSelected()));
notifyOnPreReleaseToggle.setSelected(preferences.isNotifyOnPreRelease());
notifyOnPreReleaseToggle.setOnAction(e -> preferences.setNotifyOnPreRelease(notifyOnPreReleaseToggle.isSelected()));
resetDontShowAgainButton.setOnAction(e -> preferences.resetDontShowAgain());
editCustomBtcExplorer.setOnAction(e -> {
@ -1133,6 +1138,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
sortMarketCurrenciesNumerically.setOnAction(null);
hideNonAccountPaymentMethodsToggle.setOnAction(null);
denyApiTakerToggle.setOnAction(null);
notifyOnPreReleaseToggle.setOnAction(null);
showOwnOffersInOfferBook.setOnAction(null);
resetDontShowAgainButton.setOnAction(null);
if (displayStandbyModeFeature) {

View file

@ -648,7 +648,7 @@ public class ChatView extends AnchorPane {
ChatMessage message = new ChatMessage(
supportManager.getSupportType(),
supportSession.getTradeId(),
supportSession.getClientPubKeyRing().hashCode(),
supportSession.getClientId(),
supportSession.isClient(),
text,
supportManager.getMyAddress(),

View file

@ -28,7 +28,7 @@ import org.bitcoinj.core.Transaction;
import javafx.collections.FXCollections;
import java.util.Collections;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
@ -41,8 +41,6 @@ import static org.mockito.Mockito.when;
public class TransactionAwareTradeTest {
private static final Sha256Hash XID = Sha256Hash.wrap("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
private Transaction transaction;
private ArbitrationManager arbitrationManager;
private Trade delegate;
@ -95,7 +93,10 @@ public class TransactionAwareTradeTest {
when(dispute.getTradeId()).thenReturn(tradeId);
when(arbitrationManager.getDisputesAsObservableList())
.thenReturn(FXCollections.observableArrayList(Collections.singleton(dispute)));
.thenReturn(FXCollections.observableArrayList(Set.of(dispute)));
when(arbitrationManager.getDisputedTradeIds())
.thenReturn(Set.of(tradeId));
when(delegate.getId()).thenReturn(tradeId);

View file

@ -620,6 +620,7 @@ message Alert {
string signature_as_base64 = 4;
bytes owner_pub_key_bytes = 5;
map<string, string> extra_data = 6;
bool is_pre_release_info = 7;
}
message Arbitrator {
@ -1074,6 +1075,7 @@ message WesternUnionAccountPayload {
message AmazonGiftCardAccountPayload {
string email_or_mobile_nr = 1;
string country_code = 2;
}
message SepaAccountPayload {
@ -1638,6 +1640,7 @@ message PreferencesPayload {
bool hide_non_account_payment_methods = 58;
bool show_offers_matching_my_accounts = 59;
bool deny_api_taker = 60;
bool notify_on_pre_release = 61;
}
message AutoConfirmSettings {

View file

@ -1 +1 @@
1.5.5-SNAPSHOT
1.5.6-SNAPSHOT

View file

@ -47,7 +47,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SeedNodeMain extends ExecutableForAppWithP2p {
private static final long CHECK_CONNECTION_LOSS_SEC = 30;
private static final String VERSION = "1.5.5";
private static final String VERSION = "1.5.6";
private SeedNode seedNode;
private Timer checkConnectionLossTime;