mirror of
https://github.com/bisq-network/bisq.git
synced 2025-01-19 05:44:05 +01:00
Merge pull request #5357 from ghubstan/02-api-trade-contract-details
Provide more offer & contract detail to CLI
This commit is contained in:
commit
b6f9231af9
@ -238,65 +238,35 @@ gettradedetail() {
|
||||
|
||||
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
|
||||
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $10}')
|
||||
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
|
||||
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $11}')
|
||||
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 $12}')
|
||||
else
|
||||
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $13}')
|
||||
fi
|
||||
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $13}')
|
||||
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 $13}')
|
||||
else
|
||||
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $14}')
|
||||
fi
|
||||
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $14}')
|
||||
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 $14}')
|
||||
else
|
||||
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $15}')
|
||||
fi
|
||||
ANSWER=$(echo "$TRADE_DETAIL" | awk '{print $15}')
|
||||
commandalert $? "Could not parse istradepayoutpublished from trade detail."
|
||||
echo "$ANSWER"
|
||||
}
|
||||
@ -321,7 +291,7 @@ waitfortradedepositpublished() {
|
||||
TRADE_DETAIL=$(gettradedetail "$GETTRADE_CMD_OUTPUT")
|
||||
exitoncommandalert $?
|
||||
|
||||
IS_TRADE_DEPOSIT_PUBLISHED=$(istradedepositpublished "$TRADE_DETAIL" "TAKER")
|
||||
IS_TRADE_DEPOSIT_PUBLISHED=$(istradedepositpublished "$TRADE_DETAIL")
|
||||
exitoncommandalert $?
|
||||
|
||||
printdate "BOB $BOB_ROLE: Has taker's trade deposit been published? $IS_TRADE_DEPOSIT_PUBLISHED"
|
||||
@ -356,7 +326,7 @@ waitfortradedepositconfirmed() {
|
||||
TRADE_DETAIL=$(gettradedetail "$GETTRADE_CMD_OUTPUT")
|
||||
exitoncommandalert $?
|
||||
|
||||
IS_TRADE_DEPOSIT_CONFIRMED=$(istradedepositconfirmed "$TRADE_DETAIL" "TAKER")
|
||||
IS_TRADE_DEPOSIT_CONFIRMED=$(istradedepositconfirmed "$TRADE_DETAIL")
|
||||
exitoncommandalert $?
|
||||
printdate "BOB $BOB_ROLE: Has taker's trade deposit been confirmed? $IS_TRADE_DEPOSIT_CONFIRMED"
|
||||
printbreak
|
||||
@ -379,7 +349,6 @@ waitfortradepaymentsent() {
|
||||
PORT="$1"
|
||||
SELLER="$2"
|
||||
OFFER_ID="$3"
|
||||
MAKER_OR_TAKER="$4"
|
||||
DONE=0
|
||||
while : ; do
|
||||
if [ "$DONE" -ne 0 ]; then
|
||||
@ -397,7 +366,7 @@ waitfortradepaymentsent() {
|
||||
TRADE_DETAIL=$(gettradedetail "$GETTRADE_CMD_OUTPUT")
|
||||
exitoncommandalert $?
|
||||
|
||||
IS_TRADE_PAYMENT_SENT=$(istradepaymentsent "$TRADE_DETAIL" "$MAKER_OR_TAKER")
|
||||
IS_TRADE_PAYMENT_SENT=$(istradepaymentsent "$TRADE_DETAIL")
|
||||
exitoncommandalert $?
|
||||
printdate "$SELLER: Has buyer's fiat payment been initiated? $IS_TRADE_PAYMENT_SENT"
|
||||
if [ "$IS_TRADE_PAYMENT_SENT" = "YES" ]
|
||||
@ -416,7 +385,6 @@ waitfortradepaymentreceived() {
|
||||
PORT="$1"
|
||||
SELLER="$2"
|
||||
OFFER_ID="$3"
|
||||
MAKER_OR_TAKER="$4"
|
||||
DONE=0
|
||||
while : ; do
|
||||
if [ "$DONE" -ne 0 ]; then
|
||||
@ -437,7 +405,7 @@ waitfortradepaymentreceived() {
|
||||
# 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")
|
||||
IS_TRADE_PAYMENT_SENT=$(istradepaymentreceived "$TRADE_DETAIL")
|
||||
exitoncommandalert $?
|
||||
printdate "$SELLER: Has buyer's payment been transferred to seller's fiat account? $IS_TRADE_PAYMENT_SENT"
|
||||
if [ "$IS_TRADE_PAYMENT_SENT" = "YES" ]
|
||||
@ -544,10 +512,10 @@ executetrade() {
|
||||
if [ "$DIRECTION" = "BUY" ]
|
||||
then
|
||||
# Bob waits for payment, polling status in taker specific trade detail.
|
||||
waitfortradepaymentsent "$PAYEE_PORT" "$PAYEE" "$OFFER_ID" "TAKER"
|
||||
waitfortradepaymentsent "$PAYEE_PORT" "$PAYEE" "$OFFER_ID"
|
||||
else
|
||||
# Alice waits for payment, polling status in maker specific trade detail.
|
||||
waitfortradepaymentsent "$PAYEE_PORT" "$PAYEE" "$OFFER_ID" "MAKER"
|
||||
waitfortradepaymentsent "$PAYEE_PORT" "$PAYEE" "$OFFER_ID"
|
||||
fi
|
||||
|
||||
|
||||
@ -557,10 +525,10 @@ executetrade() {
|
||||
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"
|
||||
waitfortradepaymentreceived "$PAYER_PORT" "$PAYER" "$OFFER_ID"
|
||||
else
|
||||
# Bob waits for payment rcvd confirm from Alice, polling status in taker specific trade detail.
|
||||
waitfortradepaymentreceived "$PAYER_PORT" "$PAYER" "$OFFER_ID" "TAKER"
|
||||
waitfortradepaymentreceived "$PAYER_PORT" "$PAYER" "$OFFER_ID"
|
||||
fi
|
||||
|
||||
# Generate some btc blocks
|
||||
|
@ -56,6 +56,7 @@ import static java.util.Collections.singletonList;
|
||||
|
||||
import bisq.cli.opts.ArgumentList;
|
||||
import bisq.cli.opts.CancelOfferOptionParser;
|
||||
import bisq.cli.opts.CreateCryptoCurrencyPaymentAcctOptionParser;
|
||||
import bisq.cli.opts.CreateOfferOptionParser;
|
||||
import bisq.cli.opts.CreatePaymentAcctOptionParser;
|
||||
import bisq.cli.opts.GetAddressBalanceOptionParser;
|
||||
@ -517,6 +518,24 @@ public class CliMain {
|
||||
out.println(formatPaymentAcctTbl(singletonList(paymentAccount)));
|
||||
return;
|
||||
}
|
||||
case createcryptopaymentacct: {
|
||||
var opts = new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var accountName = opts.getAccountName();
|
||||
var currencyCode = opts.getCurrencyCode();
|
||||
var address = opts.getAddress();
|
||||
var isTradeInstant = opts.getIsTradeInstant();
|
||||
var paymentAccount = client.createCryptoCurrencyPaymentAccount(accountName,
|
||||
currencyCode,
|
||||
address,
|
||||
isTradeInstant);
|
||||
out.println("payment account saved");
|
||||
out.println(formatPaymentAcctTbl(singletonList(paymentAccount)));
|
||||
return;
|
||||
}
|
||||
case getpaymentaccts: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
@ -676,7 +695,7 @@ public class CliMain {
|
||||
stream.println();
|
||||
parser.printHelpOn(stream);
|
||||
stream.println();
|
||||
String rowFormat = "%-24s%-52s%s%n";
|
||||
String rowFormat = "%-25s%-52s%s%n";
|
||||
stream.format(rowFormat, "Method", "Params", "Description");
|
||||
stream.format(rowFormat, "------", "------", "------------");
|
||||
stream.format(rowFormat, getversion.name(), "", "Get server version");
|
||||
@ -727,7 +746,9 @@ public class CliMain {
|
||||
stream.format(rowFormat, getmyoffers.name(), "--direction=<buy|sell> \\", "Get my current offers");
|
||||
stream.format(rowFormat, "", "--currency-code=<currency-code>", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> [--fee-currency=<btc|bsq>]", "Take offer with id");
|
||||
stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> \\", "Take offer with id");
|
||||
stream.format(rowFormat, "", "--payment-account=<payment-account-id>", "");
|
||||
stream.format(rowFormat, "", "[--fee-currency=<btc|bsq>]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, gettrade.name(), "--trade-id=<trade-id> \\", "Get trade summary or full contract");
|
||||
stream.format(rowFormat, "", "[--show-contract=<true|false>]", "");
|
||||
@ -748,6 +769,11 @@ public class CliMain {
|
||||
stream.println();
|
||||
stream.format(rowFormat, createpaymentacct.name(), "--payment-account-form=<path>", "Create a new payment account");
|
||||
stream.println();
|
||||
stream.format(rowFormat, createcryptopaymentacct.name(), "--account-name=<name> \\", "Create a new cryptocurrency payment account");
|
||||
stream.format(rowFormat, "", "--currency-code=<bsq> \\", "");
|
||||
stream.format(rowFormat, "", "--address=<bsq-address>", "");
|
||||
stream.format(rowFormat, "", "--trade-instant=<true|false>", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, getpaymentaccts.name(), "", "Get user payment accounts");
|
||||
stream.println();
|
||||
stream.format(rowFormat, lockwallet.name(), "", "Remove wallet password from memory, locking the wallet");
|
||||
|
@ -30,7 +30,7 @@ class ColumnHeaderConstants {
|
||||
// expected max data string length is accounted for. In others, column header
|
||||
// lengths are expected to be greater than any column value length.
|
||||
static final String COL_HEADER_ADDRESS = padEnd("%-3s Address", 52, ' ');
|
||||
static final String COL_HEADER_AMOUNT = padEnd("BTC(min - max)", 24, ' ');
|
||||
static final String COL_HEADER_AMOUNT = "BTC(min - max)";
|
||||
static final String COL_HEADER_AVAILABLE_BALANCE = "Available Balance";
|
||||
static final String COL_HEADER_AVAILABLE_CONFIRMED_BALANCE = "Available Confirmed Balance";
|
||||
static final String COL_HEADER_UNCONFIRMED_CHANGE_BALANCE = "Unconfirmed Change Balance";
|
||||
@ -49,7 +49,9 @@ class ColumnHeaderConstants {
|
||||
static final String COL_HEADER_NAME = "Name";
|
||||
static final String COL_HEADER_PAYMENT_METHOD = "Payment Method";
|
||||
static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC";
|
||||
static final String COL_HEADER_PRICE_OF_ALTCOIN = "Price in BTC for 1 %-3s";
|
||||
static final String COL_HEADER_TRADE_AMOUNT = padStart("Amount(%-3s)", 12, ' ');
|
||||
static final String COL_HEADER_TRADE_BSQ_BUYER_ADDRESS = "BSQ Buyer Address";
|
||||
static final String COL_HEADER_TRADE_BUYER_COST = padEnd("Buyer Cost(%-3s)", 15, ' ');
|
||||
static final String COL_HEADER_TRADE_DEPOSIT_CONFIRMED = "Deposit Confirmed";
|
||||
static final String COL_HEADER_TRADE_DEPOSIT_PUBLISHED = "Deposit Published";
|
||||
@ -59,8 +61,9 @@ class ColumnHeaderConstants {
|
||||
static final String COL_HEADER_TRADE_WITHDRAWN = "Withdrawn";
|
||||
static final String COL_HEADER_TRADE_ROLE = "My Role";
|
||||
static final String COL_HEADER_TRADE_SHORT_ID = "ID";
|
||||
static final String COL_HEADER_TRADE_TX_FEE = "Tx Fee(%-3s)";
|
||||
static final String COL_HEADER_TRADE_TAKER_FEE = "Taker Fee(%-3s)";
|
||||
static final String COL_HEADER_TRADE_TX_FEE = padEnd("Tx Fee(BTC)", 12, ' ');
|
||||
static final String COL_HEADER_TRADE_MAKER_FEE = padEnd("Maker Fee(%-3s)", 12, ' '); // "Maker Fee(%-3s)";
|
||||
static final String COL_HEADER_TRADE_TAKER_FEE = padEnd("Taker Fee(%-3s)", 12, ' '); // "Taker Fee(%-3s)";
|
||||
|
||||
static final String COL_HEADER_TX_ID = "Tx ID";
|
||||
static final String COL_HEADER_TX_INPUT_SUM = "Tx Inputs (BTC)";
|
||||
@ -71,5 +74,6 @@ class ColumnHeaderConstants {
|
||||
static final String COL_HEADER_TX_MEMO = "Memo";
|
||||
|
||||
static final String COL_HEADER_VOLUME = padEnd("%-3s(min - max)", 15, ' ');
|
||||
|
||||
static final String COL_HEADER_UUID = padEnd("ID", 52, ' ');
|
||||
}
|
||||
|
@ -25,23 +25,25 @@ import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.math.RoundingMode.HALF_UP;
|
||||
import static java.math.RoundingMode.UNNECESSARY;
|
||||
|
||||
@VisibleForTesting
|
||||
public class CurrencyFormat {
|
||||
|
||||
private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
|
||||
static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100000000);
|
||||
static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100_000_000);
|
||||
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
|
||||
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,###,##0");
|
||||
|
||||
static final BigDecimal BSQ_SATOSHI_DIVISOR = new BigDecimal(100);
|
||||
static final DecimalFormat BSQ_FORMAT = new DecimalFormat("###,###,###,##0.00");
|
||||
static final DecimalFormat SEND_BSQ_FORMAT = new DecimalFormat("###########0.00");
|
||||
|
||||
static final BigDecimal SECURITY_DEPOSIT_MULTIPLICAND = new BigDecimal("0.01");
|
||||
|
||||
@ -55,6 +57,14 @@ public class CurrencyFormat {
|
||||
return BSQ_FORMAT.format(BigDecimal.valueOf(sats).divide(BSQ_SATOSHI_DIVISOR));
|
||||
}
|
||||
|
||||
public static String formatBsqAmount(long bsqSats) {
|
||||
// BSQ sats = trade.getOffer().getVolume()
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(2);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(2);
|
||||
NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
||||
return SEND_BSQ_FORMAT.format((double) bsqSats / SATOSHI_DIVISOR.doubleValue());
|
||||
}
|
||||
|
||||
public static String formatTxFeeRateInfo(TxFeeRateInfo txFeeRateInfo) {
|
||||
if (txFeeRateInfo.getUseCustomTxFeeRate())
|
||||
return format("custom tx fee rate: %s sats/byte, network rate: %s sats/byte",
|
||||
@ -77,22 +87,44 @@ public class CurrencyFormat {
|
||||
: formatOfferVolume(volume);
|
||||
}
|
||||
|
||||
public static String formatCryptoCurrencyVolumeRange(long minVolume, long volume) {
|
||||
return minVolume != volume
|
||||
? formatCryptoCurrencyOfferVolume(minVolume) + " - " + formatCryptoCurrencyOfferVolume(volume)
|
||||
: formatCryptoCurrencyOfferVolume(volume);
|
||||
}
|
||||
|
||||
public static String formatMarketPrice(double price) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||
return NUMBER_FORMAT.format(price);
|
||||
}
|
||||
|
||||
public static String formatOfferPrice(long price) {
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||
public static String formatPrice(long price) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||
NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
|
||||
return NUMBER_FORMAT.format((double) price / 10000);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||
NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
||||
return NUMBER_FORMAT.format((double) price / 10_000);
|
||||
}
|
||||
|
||||
public static String formatCryptoCurrencyPrice(long price) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(8);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(8);
|
||||
NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
||||
return NUMBER_FORMAT.format((double) price / SATOSHI_DIVISOR.doubleValue());
|
||||
}
|
||||
|
||||
public static String formatOfferVolume(long volume) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(0);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(0);
|
||||
NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
|
||||
return NUMBER_FORMAT.format((double) volume / 10000);
|
||||
NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
||||
return NUMBER_FORMAT.format((double) volume / 10_000);
|
||||
}
|
||||
|
||||
public static String formatCryptoCurrencyOfferVolume(long volume) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(2);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(2);
|
||||
NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
||||
return NUMBER_FORMAT.format((double) volume / SATOSHI_DIVISOR.doubleValue());
|
||||
}
|
||||
|
||||
public static long toSatoshis(String btc) {
|
||||
|
60
cli/src/main/java/bisq/cli/DirectionFormat.java
Normal file
60
cli/src/main/java/bisq/cli/DirectionFormat.java
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static bisq.cli.ColumnHeaderConstants.COL_HEADER_DIRECTION;
|
||||
import static java.lang.String.format;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
|
||||
class DirectionFormat {
|
||||
|
||||
static int getLongestDirectionColWidth(List<OfferInfo> offers) {
|
||||
if (offers.isEmpty() || offers.get(0).getBaseCurrencyCode().equals("BTC"))
|
||||
return COL_HEADER_DIRECTION.length();
|
||||
else
|
||||
return 18; // .e.g., "Sell BSQ (Buy BTC)".length()
|
||||
}
|
||||
|
||||
static final Function<OfferInfo, String> directionFormat = (offer) -> {
|
||||
String baseCurrencyCode = offer.getBaseCurrencyCode();
|
||||
boolean isCryptoCurrencyOffer = !baseCurrencyCode.equals("BTC");
|
||||
if (!isCryptoCurrencyOffer) {
|
||||
return baseCurrencyCode;
|
||||
} else {
|
||||
// Return "Sell BSQ (Buy BTC)", or "Buy BSQ (Sell BTC)".
|
||||
String direction = offer.getDirection();
|
||||
String mirroredDirection = getMirroredDirection(direction);
|
||||
Function<String, String> mixedCase = (word) -> word.charAt(0) + word.substring(1).toLowerCase();
|
||||
return format("%s %s (%s %s)",
|
||||
mixedCase.apply(mirroredDirection),
|
||||
baseCurrencyCode,
|
||||
mixedCase.apply(direction),
|
||||
offer.getCounterCurrencyCode());
|
||||
}
|
||||
};
|
||||
|
||||
static String getMirroredDirection(String directionAsString) {
|
||||
return directionAsString.equalsIgnoreCase(BUY.name()) ? SELL.name() : BUY.name();
|
||||
}
|
||||
}
|
@ -24,10 +24,12 @@ import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.CancelOfferRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||
import bisq.proto.grpc.CreateCryptoCurrencyPaymentAccountRequest;
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
import bisq.proto.grpc.CreatePaymentAccountRequest;
|
||||
import bisq.proto.grpc.GetAddressBalanceRequest;
|
||||
import bisq.proto.grpc.GetBalancesRequest;
|
||||
import bisq.proto.grpc.GetCryptoCurrencyPaymentMethodsRequest;
|
||||
import bisq.proto.grpc.GetFundingAddressesRequest;
|
||||
import bisq.proto.grpc.GetMethodHelpRequest;
|
||||
import bisq.proto.grpc.GetMyOfferRequest;
|
||||
@ -60,6 +62,7 @@ import bisq.proto.grpc.TxFeeRateInfo;
|
||||
import bisq.proto.grpc.TxInfo;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.VerifyBsqSentToAddressRequest;
|
||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
@ -67,11 +70,11 @@ import protobuf.PaymentMethod;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
|
||||
@ -164,6 +167,14 @@ public final class GrpcClient {
|
||||
return grpcStubs.walletsService.sendBtc(request).getTxInfo();
|
||||
}
|
||||
|
||||
public boolean verifyBsqSentToAddress(String address, String amount) {
|
||||
var request = VerifyBsqSentToAddressRequest.newBuilder()
|
||||
.setAddress(address)
|
||||
.setAmount(amount)
|
||||
.build();
|
||||
return grpcStubs.walletsService.verifyBsqSentToAddress(request).getIsAmountReceived();
|
||||
}
|
||||
|
||||
public TxFeeRateInfo getTxFeeRate() {
|
||||
var request = GetTxFeeRateRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.getTxFeeRate(request).getTxFeeRateInfo();
|
||||
@ -228,7 +239,6 @@ public final class GrpcClient {
|
||||
makerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
// TODO make private, move to bottom of class
|
||||
public OfferInfo createOffer(String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
@ -283,6 +293,12 @@ public final class GrpcClient {
|
||||
return grpcStubs.offersService.getOffers(request).getOffersList();
|
||||
}
|
||||
|
||||
public List<OfferInfo> getBsqOffers(String direction) {
|
||||
return getOffers(direction, "BTC").stream()
|
||||
.filter(o -> o.getBaseCurrencyCode().equals("BSQ"))
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffersSortedByDate(String currencyCode) {
|
||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||
offers.addAll(getOffers(BUY.name(), currencyCode));
|
||||
@ -295,6 +311,13 @@ public final class GrpcClient {
|
||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getBsqOffersSortedByDate() {
|
||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||
offers.addAll(getBsqOffers(BUY.name()));
|
||||
offers.addAll(getBsqOffers(SELL.name()));
|
||||
return sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||
var request = GetMyOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
@ -303,6 +326,12 @@ public final class GrpcClient {
|
||||
return grpcStubs.offersService.getMyOffers(request).getOffersList();
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyBsqOffers(String direction) {
|
||||
return getMyOffers(direction, "BTC").stream()
|
||||
.filter(o -> o.getBaseCurrencyCode().equals("BSQ"))
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffersSortedByDate(String direction, String currencyCode) {
|
||||
var offers = getMyOffers(direction, currencyCode);
|
||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
@ -315,6 +344,13 @@ public final class GrpcClient {
|
||||
return sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyBsqOffersSortedByDate() {
|
||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||
offers.addAll(getMyBsqOffers(BUY.name()));
|
||||
offers.addAll(getMyBsqOffers(SELL.name()));
|
||||
return sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
||||
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
||||
@ -323,7 +359,7 @@ public final class GrpcClient {
|
||||
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||
return offerInfoList.stream()
|
||||
.sorted(comparing(OfferInfo::getDate))
|
||||
.collect(Collectors.toList());
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||
@ -340,7 +376,7 @@ public final class GrpcClient {
|
||||
if (reply.hasTrade())
|
||||
return reply.getTrade();
|
||||
else
|
||||
throw new IllegalStateException(reply.getAvailabilityResultDescription());
|
||||
throw new IllegalStateException(reply.getFailureReason().getDescription());
|
||||
}
|
||||
|
||||
public TradeInfo getTrade(String tradeId) {
|
||||
@ -404,6 +440,24 @@ public final class GrpcClient {
|
||||
return grpcStubs.paymentAccountsService.getPaymentAccounts(request).getPaymentAccountsList();
|
||||
}
|
||||
|
||||
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||
String currencyCode,
|
||||
String address,
|
||||
boolean tradeInstant) {
|
||||
var request = CreateCryptoCurrencyPaymentAccountRequest.newBuilder()
|
||||
.setAccountName(accountName)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.setAddress(address)
|
||||
.setTradeInstant(tradeInstant)
|
||||
.build();
|
||||
return grpcStubs.paymentAccountsService.createCryptoCurrencyPaymentAccount(request).getPaymentAccount();
|
||||
}
|
||||
|
||||
public List<PaymentMethod> getCryptoPaymentMethods() {
|
||||
var request = GetCryptoCurrencyPaymentMethodsRequest.newBuilder().build();
|
||||
return grpcStubs.paymentAccountsService.getCryptoCurrencyPaymentMethods(request).getPaymentMethodsList();
|
||||
}
|
||||
|
||||
public void lockWallet() {
|
||||
var request = LockWalletRequest.newBuilder().build();
|
||||
grpcStubs.walletsService.lockWallet(request);
|
||||
|
@ -26,6 +26,7 @@ public enum Method {
|
||||
confirmpaymentstarted,
|
||||
createoffer,
|
||||
createpaymentacct,
|
||||
createcryptopaymentacct,
|
||||
getaddressbalance,
|
||||
getbalance,
|
||||
getbtcprice,
|
||||
|
@ -36,7 +36,10 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static bisq.cli.ColumnHeaderConstants.*;
|
||||
import static bisq.cli.CurrencyFormat.*;
|
||||
import static bisq.cli.DirectionFormat.directionFormat;
|
||||
import static bisq.cli.DirectionFormat.getLongestDirectionColWidth;
|
||||
import static com.google.common.base.Strings.padEnd;
|
||||
import static com.google.common.base.Strings.padStart;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.max;
|
||||
import static java.util.Comparator.comparing;
|
||||
@ -114,61 +117,24 @@ public class TableFormat {
|
||||
formatSatoshis(btcBalanceInfo.getLockedBalance()));
|
||||
}
|
||||
|
||||
public static String formatOfferTable(List<OfferInfo> offerInfo, String fiatCurrency) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
int paymentMethodColWidth = getLengthOfLongestColumn(
|
||||
COL_HEADER_PAYMENT_METHOD.length(),
|
||||
offerInfo.stream()
|
||||
.map(OfferInfo::getPaymentMethodShortName)
|
||||
.collect(Collectors.toList()));
|
||||
String headersFormat = COL_HEADER_DIRECTION + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_PRICE + COL_HEADER_DELIMITER // includes %s -> fiatCurrency
|
||||
+ COL_HEADER_AMOUNT + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_VOLUME + COL_HEADER_DELIMITER // includes %s -> fiatCurrency
|
||||
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_UUID.trim() + "%n";
|
||||
String headerLine = format(headersFormat, fiatCurrency.toUpperCase(), fiatCurrency.toUpperCase());
|
||||
|
||||
String colDataFormat = "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s" // left
|
||||
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s" // rt justify to end of hdr
|
||||
+ " %-" + (COL_HEADER_AMOUNT.length() - 1) + "s" // left justify
|
||||
+ " %" + COL_HEADER_VOLUME.length() + "s" // right justify
|
||||
+ " %-" + paymentMethodColWidth + "s" // left justify
|
||||
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s" // left justify
|
||||
+ " %-" + COL_HEADER_UUID.length() + "s";
|
||||
return headerLine
|
||||
+ offerInfo.stream()
|
||||
.map(o -> format(colDataFormat,
|
||||
o.getDirection(),
|
||||
formatOfferPrice(o.getPrice()),
|
||||
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||
formatVolumeRange(o.getMinVolume(), o.getVolume()),
|
||||
o.getPaymentMethodShortName(),
|
||||
formatTimestamp(o.getDate()),
|
||||
o.getId()))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
public static String formatPaymentAcctTbl(List<PaymentAccount> paymentAccounts) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
int nameColWidth = getLengthOfLongestColumn(
|
||||
int nameColWidth = getLongestColumnSize(
|
||||
COL_HEADER_NAME.length(),
|
||||
paymentAccounts.stream().map(PaymentAccount::getAccountName)
|
||||
.collect(Collectors.toList()));
|
||||
int paymentMethodColWidth = getLengthOfLongestColumn(
|
||||
int paymentMethodColWidth = getLongestColumnSize(
|
||||
COL_HEADER_PAYMENT_METHOD.length(),
|
||||
paymentAccounts.stream().map(a -> a.getPaymentMethod().getId())
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
String headerLine = padEnd(COL_HEADER_NAME, nameColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_CURRENCY + COL_HEADER_DELIMITER
|
||||
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_UUID + COL_HEADER_DELIMITER + "\n";
|
||||
String colDataFormat = "%-" + nameColWidth + "s" // left justify
|
||||
+ " %-" + COL_HEADER_CURRENCY.length() + "s" // left justify
|
||||
+ " %-" + paymentMethodColWidth + "s" // left justify
|
||||
+ " %-" + COL_HEADER_UUID.length() + "s"; // left justify
|
||||
String colDataFormat = "%-" + nameColWidth + "s" // left justify
|
||||
+ " %-" + COL_HEADER_CURRENCY.length() + "s" // left justify
|
||||
+ " %-" + paymentMethodColWidth + "s" // left justify
|
||||
+ " %-" + COL_HEADER_UUID.length() + "s"; // left justify
|
||||
return headerLine
|
||||
+ paymentAccounts.stream()
|
||||
.map(a -> format(colDataFormat,
|
||||
@ -179,8 +145,126 @@ public class TableFormat {
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
// Return length of the longest string value, or the header.len, whichever is greater.
|
||||
private static int getLengthOfLongestColumn(int headerLength, List<String> strings) {
|
||||
public static String formatOfferTable(List<OfferInfo> offers, String currencyCode) {
|
||||
if (offers == null || offers.isEmpty())
|
||||
throw new IllegalArgumentException(format("%s offers argument is empty", currencyCode.toLowerCase()));
|
||||
|
||||
String baseCurrencyCode = offers.get(0).getBaseCurrencyCode();
|
||||
return baseCurrencyCode.equalsIgnoreCase("BTC")
|
||||
? formatFiatOfferTable(offers, currencyCode)
|
||||
: formatCryptoCurrencyOfferTable(offers, baseCurrencyCode);
|
||||
}
|
||||
|
||||
private static String formatFiatOfferTable(List<OfferInfo> offers, String fiatCurrencyCode) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
int amountColWith = getLongestAmountColWidth(offers);
|
||||
int volumeColWidth = getLongestVolumeColWidth(offers);
|
||||
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
|
||||
String headersFormat = COL_HEADER_DIRECTION + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_PRICE + COL_HEADER_DELIMITER // includes %s -> fiatCurrencyCode
|
||||
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
|
||||
// COL_HEADER_VOLUME includes %s -> fiatCurrencyCode
|
||||
+ padStart(COL_HEADER_VOLUME, volumeColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_UUID.trim() + "%n";
|
||||
String headerLine = format(headersFormat,
|
||||
fiatCurrencyCode.toUpperCase(),
|
||||
fiatCurrencyCode.toUpperCase());
|
||||
String colDataFormat = "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s"
|
||||
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s"
|
||||
+ " %" + amountColWith + "s"
|
||||
+ " %" + (volumeColWidth - 1) + "s"
|
||||
+ " %-" + paymentMethodColWidth + "s"
|
||||
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
|
||||
+ " %-" + COL_HEADER_UUID.length() + "s";
|
||||
return headerLine
|
||||
+ offers.stream()
|
||||
.map(o -> format(colDataFormat,
|
||||
o.getDirection(),
|
||||
formatPrice(o.getPrice()),
|
||||
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||
formatVolumeRange(o.getMinVolume(), o.getVolume()),
|
||||
o.getPaymentMethodShortName(),
|
||||
formatTimestamp(o.getDate()),
|
||||
o.getId()))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private static String formatCryptoCurrencyOfferTable(List<OfferInfo> offers, String cryptoCurrencyCode) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
int directionColWidth = getLongestDirectionColWidth(offers);
|
||||
int amountColWith = getLongestAmountColWidth(offers);
|
||||
int volumeColWidth = getLongestCryptoCurrencyVolumeColWidth(offers);
|
||||
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
|
||||
// TODO use memoize function to avoid duplicate the formatting done above?
|
||||
String headersFormat = padEnd(COL_HEADER_DIRECTION, directionColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_PRICE_OF_ALTCOIN + COL_HEADER_DELIMITER // includes %s -> cryptoCurrencyCode
|
||||
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
|
||||
// COL_HEADER_VOLUME includes %s -> cryptoCurrencyCode
|
||||
+ padStart(COL_HEADER_VOLUME, volumeColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_UUID.trim() + "%n";
|
||||
String headerLine = format(headersFormat,
|
||||
cryptoCurrencyCode.toUpperCase(),
|
||||
cryptoCurrencyCode.toUpperCase());
|
||||
String colDataFormat = "%-" + directionColWidth + "s"
|
||||
+ "%" + (COL_HEADER_PRICE_OF_ALTCOIN.length() + 1) + "s"
|
||||
+ " %" + amountColWith + "s"
|
||||
+ " %" + (volumeColWidth - 1) + "s"
|
||||
+ " %-" + paymentMethodColWidth + "s"
|
||||
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
|
||||
+ " %-" + COL_HEADER_UUID.length() + "s";
|
||||
return headerLine
|
||||
+ offers.stream()
|
||||
.map(o -> format(colDataFormat,
|
||||
directionFormat.apply(o),
|
||||
formatCryptoCurrencyPrice(o.getPrice()),
|
||||
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||
formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()),
|
||||
o.getPaymentMethodShortName(),
|
||||
formatTimestamp(o.getDate()),
|
||||
o.getId()))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private static int getLongestPaymentMethodColWidth(List<OfferInfo> offers) {
|
||||
return getLongestColumnSize(
|
||||
COL_HEADER_PAYMENT_METHOD.length(),
|
||||
offers.stream()
|
||||
.map(OfferInfo::getPaymentMethodShortName)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static int getLongestAmountColWidth(List<OfferInfo> offers) {
|
||||
return getLongestColumnSize(
|
||||
COL_HEADER_AMOUNT.length(),
|
||||
offers.stream()
|
||||
.map(o -> formatAmountRange(o.getMinAmount(), o.getAmount()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static int getLongestVolumeColWidth(List<OfferInfo> offers) {
|
||||
// Pad this col width by 1 space.
|
||||
return 1 + getLongestColumnSize(
|
||||
COL_HEADER_VOLUME.length(),
|
||||
offers.stream()
|
||||
.map(o -> formatVolumeRange(o.getMinVolume(), o.getVolume()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static int getLongestCryptoCurrencyVolumeColWidth(List<OfferInfo> offers) {
|
||||
// Pad this col width by 1 space.
|
||||
return 1 + getLongestColumnSize(
|
||||
COL_HEADER_VOLUME.length(),
|
||||
offers.stream()
|
||||
.map(o -> formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
// Return size of the longest string value, or the header.len, whichever is greater.
|
||||
private static int getLongestColumnSize(int headerLength, List<String> strings) {
|
||||
int longest = max(strings, comparing(String::length)).length();
|
||||
return Math.max(longest, headerLength);
|
||||
}
|
||||
|
@ -17,21 +17,27 @@
|
||||
|
||||
package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.ContractInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static bisq.cli.ColumnHeaderConstants.*;
|
||||
import static bisq.cli.CurrencyFormat.formatOfferPrice;
|
||||
import static bisq.cli.CurrencyFormat.formatOfferVolume;
|
||||
import static bisq.cli.CurrencyFormat.formatSatoshis;
|
||||
import static bisq.cli.CurrencyFormat.*;
|
||||
import static com.google.common.base.Strings.padEnd;
|
||||
|
||||
@VisibleForTesting
|
||||
public class TradeFormat {
|
||||
|
||||
private static final String YES = "YES";
|
||||
private static final String NO = "NO";
|
||||
|
||||
// TODO add String format(List<TradeInfo> trades)
|
||||
|
||||
@VisibleForTesting
|
||||
public static String format(TradeInfo tradeInfo) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
@ -40,19 +46,31 @@ public class TradeFormat {
|
||||
|
||||
// We only show taker fee under its header when user is the taker.
|
||||
boolean isTaker = tradeInfo.getRole().toLowerCase().contains("taker");
|
||||
Supplier<String> takerFeeHeaderFormat = () -> isTaker ?
|
||||
padEnd(COL_HEADER_TRADE_TAKER_FEE, 12, ' ') + COL_HEADER_DELIMITER
|
||||
Supplier<String> makerFeeHeader = () -> !isTaker ?
|
||||
COL_HEADER_TRADE_MAKER_FEE + COL_HEADER_DELIMITER
|
||||
: "";
|
||||
Supplier<String> makerFeeHeaderSpec = () -> !isTaker ?
|
||||
"%" + (COL_HEADER_TRADE_MAKER_FEE.length() + 2) + "s"
|
||||
: "";
|
||||
Supplier<String> takerFeeHeader = () -> isTaker ?
|
||||
"%" + (COL_HEADER_TRADE_TAKER_FEE.length() + 1) + "s"
|
||||
COL_HEADER_TRADE_TAKER_FEE + COL_HEADER_DELIMITER
|
||||
: "";
|
||||
Supplier<String> takerFeeHeaderSpec = () -> isTaker ?
|
||||
"%" + (COL_HEADER_TRADE_TAKER_FEE.length() + 2) + "s"
|
||||
: "";
|
||||
|
||||
boolean showBsqBuyerAddress = shouldShowBsqBuyerAddress(tradeInfo, isTaker);
|
||||
Supplier<String> bsqBuyerAddressHeader = () -> showBsqBuyerAddress ? COL_HEADER_TRADE_BSQ_BUYER_ADDRESS : "";
|
||||
Supplier<String> bsqBuyerAddressHeaderSpec = () -> showBsqBuyerAddress ? "%s" : "";
|
||||
|
||||
String headersFormat = padEnd(COL_HEADER_TRADE_SHORT_ID, shortIdColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ padEnd(COL_HEADER_TRADE_ROLE, roleColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_PRICE + COL_HEADER_DELIMITER // includes %s -> currencyCode
|
||||
+ priceHeader.apply(tradeInfo) + COL_HEADER_DELIMITER // includes %s -> currencyCode
|
||||
+ padEnd(COL_HEADER_TRADE_AMOUNT, 12, ' ') + COL_HEADER_DELIMITER
|
||||
+ padEnd(COL_HEADER_TRADE_TX_FEE, 12, ' ') + COL_HEADER_DELIMITER
|
||||
+ takerFeeHeaderFormat.get()
|
||||
+ makerFeeHeader.get()
|
||||
// maker or taker fee header, not both
|
||||
+ takerFeeHeader.get()
|
||||
+ COL_HEADER_TRADE_DEPOSIT_PUBLISHED + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_DEPOSIT_CONFIRMED + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_BUYER_COST + COL_HEADER_DELIMITER
|
||||
@ -60,79 +78,144 @@ public class TradeFormat {
|
||||
+ COL_HEADER_TRADE_PAYMENT_RECEIVED + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_PAYOUT_PUBLISHED + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_WITHDRAWN + COL_HEADER_DELIMITER
|
||||
+ bsqBuyerAddressHeader.get()
|
||||
+ "%n";
|
||||
|
||||
String counterCurrencyCode = tradeInfo.getOffer().getCounterCurrencyCode();
|
||||
String baseCurrencyCode = tradeInfo.getOffer().getBaseCurrencyCode();
|
||||
|
||||
// The taker's output contains an extra taker tx fee column.
|
||||
String headerLine = isTaker
|
||||
? String.format(headersFormat,
|
||||
/* COL_HEADER_PRICE */ counterCurrencyCode,
|
||||
String headerLine = String.format(headersFormat,
|
||||
/* COL_HEADER_PRICE */ priceHeaderCurrencyCode.apply(tradeInfo),
|
||||
/* COL_HEADER_TRADE_AMOUNT */ baseCurrencyCode,
|
||||
/* COL_HEADER_TRADE_TX_FEE */ baseCurrencyCode,
|
||||
/* COL_HEADER_TRADE_TAKER_FEE */ baseCurrencyCode,
|
||||
/* COL_HEADER_TRADE_(M||T)AKER_FEE */ makerTakerFeeHeaderCurrencyCode.apply(tradeInfo, isTaker),
|
||||
/* COL_HEADER_TRADE_BUYER_COST */ counterCurrencyCode,
|
||||
/* COL_HEADER_TRADE_PAYMENT_SENT */ counterCurrencyCode,
|
||||
/* COL_HEADER_TRADE_PAYMENT_RECEIVED */ counterCurrencyCode)
|
||||
: String.format(headersFormat,
|
||||
/* COL_HEADER_PRICE */ counterCurrencyCode,
|
||||
/* COL_HEADER_TRADE_AMOUNT */ baseCurrencyCode,
|
||||
/* COL_HEADER_TRADE_TX_FEE */ baseCurrencyCode,
|
||||
/* COL_HEADER_TRADE_BUYER_COST */ counterCurrencyCode,
|
||||
/* COL_HEADER_TRADE_PAYMENT_SENT */ counterCurrencyCode,
|
||||
/* COL_HEADER_TRADE_PAYMENT_RECEIVED */ counterCurrencyCode);
|
||||
/* COL_HEADER_TRADE_PAYMENT_SENT */ paymentStatusHeaderCurrencyCode.apply(tradeInfo),
|
||||
/* COL_HEADER_TRADE_PAYMENT_RECEIVED */ paymentStatusHeaderCurrencyCode.apply(tradeInfo));
|
||||
|
||||
String colDataFormat = "%-" + shortIdColWidth + "s" // lt justify
|
||||
+ " %-" + (roleColWidth + COL_HEADER_DELIMITER.length()) + "s" // left
|
||||
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s" // rt justify
|
||||
+ "%" + (COL_HEADER_TRADE_AMOUNT.length() + 1) + "s" // rt justify
|
||||
+ "%" + (COL_HEADER_TRADE_TX_FEE.length() + 1) + "s" // rt justify
|
||||
+ takerFeeHeader.get() // rt justify
|
||||
+ makerFeeHeaderSpec.get() // rt justify
|
||||
// OR (one of them is an empty string)
|
||||
+ takerFeeHeaderSpec.get() // rt justify
|
||||
+ " %-" + COL_HEADER_TRADE_DEPOSIT_PUBLISHED.length() + "s" // lt justify
|
||||
+ " %-" + COL_HEADER_TRADE_DEPOSIT_CONFIRMED.length() + "s" // lt justify
|
||||
+ "%" + (COL_HEADER_TRADE_BUYER_COST.length() + 1) + "s" // rt justify
|
||||
+ " %-" + (COL_HEADER_TRADE_PAYMENT_SENT.length() - 1) + "s" // left
|
||||
+ " %-" + (COL_HEADER_TRADE_PAYMENT_RECEIVED.length() - 1) + "s" // left
|
||||
+ " %-" + COL_HEADER_TRADE_PAYOUT_PUBLISHED.length() + "s" // lt justify
|
||||
+ " %-" + COL_HEADER_TRADE_WITHDRAWN.length() + "s"; // lt justify
|
||||
+ " %-" + (COL_HEADER_TRADE_WITHDRAWN.length() + 2) + "s"
|
||||
+ bsqBuyerAddressHeaderSpec.get();
|
||||
|
||||
return headerLine +
|
||||
(isTaker
|
||||
? formatTradeForTaker(colDataFormat, tradeInfo)
|
||||
: formatTradeForMaker(colDataFormat, tradeInfo));
|
||||
return headerLine + formatTradeData(colDataFormat, tradeInfo, isTaker, showBsqBuyerAddress);
|
||||
}
|
||||
|
||||
private static String formatTradeForMaker(String format, TradeInfo tradeInfo) {
|
||||
private static String formatTradeData(String format,
|
||||
TradeInfo tradeInfo,
|
||||
boolean isTaker,
|
||||
boolean showBsqBuyerAddress) {
|
||||
return String.format(format,
|
||||
tradeInfo.getShortId(),
|
||||
tradeInfo.getRole(),
|
||||
formatOfferPrice(tradeInfo.getTradePrice()),
|
||||
formatSatoshis(tradeInfo.getTradeAmountAsLong()),
|
||||
formatSatoshis(tradeInfo.getTxFeeAsLong()),
|
||||
tradeInfo.getIsDepositPublished() ? "YES" : "NO",
|
||||
tradeInfo.getIsDepositConfirmed() ? "YES" : "NO",
|
||||
formatOfferVolume(tradeInfo.getOffer().getVolume()),
|
||||
tradeInfo.getIsFiatSent() ? "YES" : "NO",
|
||||
tradeInfo.getIsFiatReceived() ? "YES" : "NO",
|
||||
tradeInfo.getIsPayoutPublished() ? "YES" : "NO",
|
||||
tradeInfo.getIsWithdrawn() ? "YES" : "NO");
|
||||
priceFormat.apply(tradeInfo),
|
||||
amountFormat.apply(tradeInfo),
|
||||
makerTakerMinerTxFeeFormat.apply(tradeInfo, isTaker),
|
||||
makerTakerFeeFormat.apply(tradeInfo, isTaker),
|
||||
tradeInfo.getIsDepositPublished() ? YES : NO,
|
||||
tradeInfo.getIsDepositConfirmed() ? YES : NO,
|
||||
tradeCostFormat.apply(tradeInfo),
|
||||
tradeInfo.getIsFiatSent() ? YES : NO,
|
||||
tradeInfo.getIsFiatReceived() ? YES : NO,
|
||||
tradeInfo.getIsPayoutPublished() ? YES : NO,
|
||||
tradeInfo.getIsWithdrawn() ? YES : NO,
|
||||
bsqReceiveAddress.apply(tradeInfo, showBsqBuyerAddress));
|
||||
}
|
||||
|
||||
private static String formatTradeForTaker(String format, TradeInfo tradeInfo) {
|
||||
return String.format(format,
|
||||
tradeInfo.getShortId(),
|
||||
tradeInfo.getRole(),
|
||||
formatOfferPrice(tradeInfo.getTradePrice()),
|
||||
formatSatoshis(tradeInfo.getTradeAmountAsLong()),
|
||||
formatSatoshis(tradeInfo.getTxFeeAsLong()),
|
||||
formatSatoshis(tradeInfo.getTakerFeeAsLong()),
|
||||
tradeInfo.getIsDepositPublished() ? "YES" : "NO",
|
||||
tradeInfo.getIsDepositConfirmed() ? "YES" : "NO",
|
||||
formatOfferVolume(tradeInfo.getOffer().getVolume()),
|
||||
tradeInfo.getIsFiatSent() ? "YES" : "NO",
|
||||
tradeInfo.getIsFiatReceived() ? "YES" : "NO",
|
||||
tradeInfo.getIsPayoutPublished() ? "YES" : "NO",
|
||||
tradeInfo.getIsWithdrawn() ? "YES" : "NO");
|
||||
private static final Function<TradeInfo, String> priceHeader = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("BTC")
|
||||
? COL_HEADER_PRICE
|
||||
: COL_HEADER_PRICE_OF_ALTCOIN;
|
||||
|
||||
private static final Function<TradeInfo, String> priceHeaderCurrencyCode = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("BTC")
|
||||
? t.getOffer().getCounterCurrencyCode()
|
||||
: t.getOffer().getBaseCurrencyCode();
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> makerTakerFeeHeaderCurrencyCode = (t, isTaker) -> {
|
||||
if (isTaker) {
|
||||
return t.getIsCurrencyForTakerFeeBtc() ? "BTC" : "BSQ";
|
||||
} else {
|
||||
return t.getOffer().getIsCurrencyForMakerFeeBtc() ? "BTC" : "BSQ";
|
||||
}
|
||||
};
|
||||
|
||||
private static final Function<TradeInfo, String> paymentStatusHeaderCurrencyCode = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("BTC")
|
||||
? t.getOffer().getCounterCurrencyCode()
|
||||
: t.getOffer().getBaseCurrencyCode();
|
||||
|
||||
private static final Function<TradeInfo, String> priceFormat = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("BTC")
|
||||
? formatPrice(t.getTradePrice())
|
||||
: formatCryptoCurrencyPrice(t.getOffer().getPrice());
|
||||
|
||||
private static final Function<TradeInfo, String> amountFormat = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("BTC")
|
||||
? formatSatoshis(t.getTradeAmountAsLong())
|
||||
: formatCryptoCurrencyOfferVolume(t.getOffer().getVolume());
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> makerTakerMinerTxFeeFormat = (t, isTaker) -> {
|
||||
if (isTaker) {
|
||||
return formatSatoshis(t.getTxFeeAsLong());
|
||||
} else {
|
||||
return formatSatoshis(t.getOffer().getTxFee());
|
||||
}
|
||||
};
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> makerTakerFeeFormat = (t, isTaker) -> {
|
||||
if (isTaker) {
|
||||
return t.getIsCurrencyForTakerFeeBtc()
|
||||
? formatSatoshis(t.getTakerFeeAsLong())
|
||||
: formatBsq(t.getTakerFeeAsLong());
|
||||
} else {
|
||||
return t.getOffer().getIsCurrencyForMakerFeeBtc()
|
||||
? formatSatoshis(t.getOffer().getMakerFee())
|
||||
: formatBsq(t.getOffer().getMakerFee());
|
||||
}
|
||||
};
|
||||
|
||||
private static final Function<TradeInfo, String> tradeCostFormat = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("BTC")
|
||||
? formatOfferVolume(t.getOffer().getVolume())
|
||||
: formatSatoshis(t.getTradeAmountAsLong());
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> bsqReceiveAddress = (t, showBsqBuyerAddress) -> {
|
||||
if (showBsqBuyerAddress) {
|
||||
ContractInfo contract = t.getContract();
|
||||
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
|
||||
return isBuyerMakerAndSellerTaker // (is BTC buyer / maker)
|
||||
? contract.getTakerPaymentAccountPayload().getAddress()
|
||||
: contract.getMakerPaymentAccountPayload().getAddress();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
private static boolean shouldShowBsqBuyerAddress(TradeInfo tradeInfo, boolean isTaker) {
|
||||
if (tradeInfo.getOffer().getBaseCurrencyCode().equals("BTC")) {
|
||||
return false;
|
||||
} else {
|
||||
ContractInfo contract = tradeInfo.getContract();
|
||||
// Do not forget buyer and seller refer to BTC buyer and seller, not BSQ
|
||||
// buyer and seller. If you are buying BSQ, you are the (BTC) seller.
|
||||
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
|
||||
if (isTaker) {
|
||||
return !isBuyerMakerAndSellerTaker;
|
||||
} else {
|
||||
return isBuyerMakerAndSellerTaker;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,7 @@ public class ArgumentList {
|
||||
return currentIndex < arguments.length;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
String next() {
|
||||
return arguments[currentIndex++];
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.opts;
|
||||
|
||||
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import static bisq.cli.opts.OptLabel.OPT_ACCOUNT_NAME;
|
||||
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
|
||||
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
|
||||
import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT;
|
||||
|
||||
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
final OptionSpec<String> accountNameOpt = parser.accepts(OPT_ACCOUNT_NAME, "crypto currency account name")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "crypto currency code (bsq only)")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "bsq address")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<Boolean> tradeInstantOpt = parser.accepts(OPT_TRADE_INSTANT, "create trade instant account")
|
||||
.withOptionalArg()
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(Boolean.FALSE);
|
||||
|
||||
public CreateCryptoCurrencyPaymentAcctOptionParser(String[] args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
public CreateCryptoCurrencyPaymentAcctOptionParser parse() {
|
||||
super.parse();
|
||||
|
||||
// Short circuit opt validation if user just wants help.
|
||||
if (options.has(helpOpt))
|
||||
return this;
|
||||
|
||||
if (!options.has(accountNameOpt) || options.valueOf(accountNameOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no payment account name specified");
|
||||
|
||||
if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no currency code specified");
|
||||
|
||||
if (!options.valueOf(currencyCodeOpt).equalsIgnoreCase("bsq"))
|
||||
throw new IllegalArgumentException("api only supports bsq crypto currency payment accounts");
|
||||
|
||||
if (!options.has(addressOpt) || options.valueOf(addressOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no bsq address specified");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return options.valueOf(accountNameOpt);
|
||||
}
|
||||
|
||||
public String getCurrencyCode() {
|
||||
return options.valueOf(currencyCodeOpt);
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return options.valueOf(addressOpt);
|
||||
}
|
||||
|
||||
public boolean getIsTradeInstant() {
|
||||
return options.valueOf(tradeInstantOpt);
|
||||
}
|
||||
}
|
@ -22,5 +22,4 @@ public interface MethodOpts {
|
||||
MethodOpts parse();
|
||||
|
||||
boolean isForHelp();
|
||||
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ package bisq.cli.opts;
|
||||
* CLI opt label definitions.
|
||||
*/
|
||||
public class OptLabel {
|
||||
public final static String OPT_ACCOUNT_NAME = "account-name";
|
||||
public final static String OPT_ADDRESS = "address";
|
||||
public final static String OPT_AMOUNT = "amount";
|
||||
public final static String OPT_CURRENCY_CODE = "currency-code";
|
||||
@ -43,6 +44,7 @@ public class OptLabel {
|
||||
public final static String OPT_SECURITY_DEPOSIT = "security-deposit";
|
||||
public final static String OPT_SHOW_CONTRACT = "show-contract";
|
||||
public final static String OPT_TRADE_ID = "trade-id";
|
||||
public final static String OPT_TRADE_INSTANT = "trade-instant";
|
||||
public final static String OPT_TIMEOUT = "timeout";
|
||||
public final static String OPT_TRANSACTION_ID = "transaction-id";
|
||||
public final static String OPT_TX_FEE_RATE = "tx-fee-rate";
|
||||
|
@ -3,6 +3,7 @@ package bisq.cli.opt;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static bisq.cli.Method.canceloffer;
|
||||
import static bisq.cli.Method.createcryptopaymentacct;
|
||||
import static bisq.cli.Method.createoffer;
|
||||
import static bisq.cli.Method.createpaymentacct;
|
||||
import static bisq.cli.opts.OptLabel.*;
|
||||
@ -12,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
import bisq.cli.opts.CancelOfferOptionParser;
|
||||
import bisq.cli.opts.CreateCryptoCurrencyPaymentAcctOptionParser;
|
||||
import bisq.cli.opts.CreateOfferOptionParser;
|
||||
import bisq.cli.opts.CreatePaymentAcctOptionParser;
|
||||
|
||||
@ -20,7 +22,7 @@ public class OptionParsersTest {
|
||||
|
||||
private static final String PASSWORD_OPT = "--" + OPT_PASSWORD + "=" + "xyz";
|
||||
|
||||
// CancelOffer opt parsing tests
|
||||
// canceloffer opt parser tests
|
||||
|
||||
@Test
|
||||
public void testCancelOfferWithMissingOfferIdOptShouldThrowException() {
|
||||
@ -67,7 +69,7 @@ public class OptionParsersTest {
|
||||
new CancelOfferOptionParser(args).parse();
|
||||
}
|
||||
|
||||
// CreateOffer opt parsing tests
|
||||
// createoffer opt parser tests
|
||||
|
||||
@Test
|
||||
public void testCreateOfferOptParserWithMissingPaymentAccountIdOptShouldThrowException() {
|
||||
@ -139,7 +141,7 @@ public class OptionParsersTest {
|
||||
assertEquals("25.0", parser.getSecurityDeposit());
|
||||
}
|
||||
|
||||
// CreatePaymentAcct opt parser tests
|
||||
// createpaymentacct opt parser tests
|
||||
|
||||
@Test
|
||||
public void testCreatePaymentAcctOptParserWithMissingPaymentFormOptShouldThrowException() {
|
||||
@ -177,4 +179,85 @@ public class OptionParsersTest {
|
||||
assertEquals("json payment account form '/tmp/milkyway/solarsystem/mars' could not be found",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
// createcryptopaymentacct parser tests
|
||||
|
||||
@Test
|
||||
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingAcctNameOptShouldThrowException() {
|
||||
String[] args = new String[]{
|
||||
PASSWORD_OPT,
|
||||
createcryptopaymentacct.name()
|
||||
};
|
||||
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse());
|
||||
assertEquals("no payment account name specified", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithEmptyAcctNameOptShouldThrowException() {
|
||||
String[] args = new String[]{
|
||||
PASSWORD_OPT,
|
||||
createcryptopaymentacct.name(),
|
||||
"--" + OPT_ACCOUNT_NAME
|
||||
};
|
||||
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse());
|
||||
assertEquals("account-name requires an argument", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingCurrencyCodeOptShouldThrowException() {
|
||||
String[] args = new String[]{
|
||||
PASSWORD_OPT,
|
||||
createcryptopaymentacct.name(),
|
||||
"--" + OPT_ACCOUNT_NAME + "=" + "bsq payment account"
|
||||
};
|
||||
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse());
|
||||
assertEquals("no currency code specified", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithInvalidCurrencyCodeOptShouldThrowException() {
|
||||
String[] args = new String[]{
|
||||
PASSWORD_OPT,
|
||||
createcryptopaymentacct.name(),
|
||||
"--" + OPT_ACCOUNT_NAME + "=" + "bsq payment account",
|
||||
"--" + OPT_CURRENCY_CODE + "=" + "xmr"
|
||||
};
|
||||
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse());
|
||||
assertEquals("api only supports bsq crypto currency payment accounts", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCryptoCurrencyPaymentAcctOptionParserWithMissingAddressOptShouldThrowException() {
|
||||
String[] args = new String[]{
|
||||
PASSWORD_OPT,
|
||||
createcryptopaymentacct.name(),
|
||||
"--" + OPT_ACCOUNT_NAME + "=" + "bsq payment account",
|
||||
"--" + OPT_CURRENCY_CODE + "=" + "bsq"
|
||||
};
|
||||
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse());
|
||||
assertEquals("no bsq address specified", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCryptoCurrencyPaymentAcctOptionParser() {
|
||||
var acctName = "bsq payment account";
|
||||
var currencyCode = "bsq";
|
||||
var address = "B1nXyZ"; // address is validated on server
|
||||
String[] args = new String[]{
|
||||
PASSWORD_OPT,
|
||||
createcryptopaymentacct.name(),
|
||||
"--" + OPT_ACCOUNT_NAME + "=" + acctName,
|
||||
"--" + OPT_CURRENCY_CODE + "=" + currencyCode,
|
||||
"--" + OPT_ADDRESS + "=" + address
|
||||
};
|
||||
var parser = new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse();
|
||||
assertEquals(acctName, parser.getAccountName());
|
||||
assertEquals(currencyCode, parser.getCurrencyCode());
|
||||
assertEquals(address, parser.getAddress());
|
||||
}
|
||||
}
|
||||
|
@ -210,6 +210,20 @@ public class CoreApi {
|
||||
return paymentAccountsService.getPaymentAccountFormAsString(paymentMethodId);
|
||||
}
|
||||
|
||||
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||
String currencyCode,
|
||||
String address,
|
||||
boolean tradeInstant) {
|
||||
return paymentAccountsService.createCryptoCurrencyPaymentAccount(accountName,
|
||||
currencyCode,
|
||||
address,
|
||||
tradeInstant);
|
||||
}
|
||||
|
||||
public List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
|
||||
return paymentAccountsService.getCryptoCurrencyPaymentMethods();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Prices
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -19,7 +19,11 @@ package bisq.core.api;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.api.model.PaymentAccountForm;
|
||||
import bisq.core.locale.CryptoCurrency;
|
||||
import bisq.core.payment.CryptoCurrencyAccount;
|
||||
import bisq.core.payment.InstantCryptoCurrencyAccount;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.PaymentAccountFactory;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.user.User;
|
||||
|
||||
@ -30,30 +34,37 @@ import java.io.File;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.locale.CurrencyUtil.getCryptoCurrency;
|
||||
import static java.lang.String.format;
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
class CorePaymentAccountsService {
|
||||
|
||||
private final CoreWalletsService coreWalletsService;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final PaymentAccountForm paymentAccountForm;
|
||||
private final User user;
|
||||
|
||||
@Inject
|
||||
public CorePaymentAccountsService(AccountAgeWitnessService accountAgeWitnessService,
|
||||
public CorePaymentAccountsService(CoreWalletsService coreWalletsService,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
PaymentAccountForm paymentAccountForm,
|
||||
User user) {
|
||||
this.coreWalletsService = coreWalletsService;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.paymentAccountForm = paymentAccountForm;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
// Fiat Currency Accounts
|
||||
|
||||
PaymentAccount createPaymentAccount(String jsonString) {
|
||||
PaymentAccount paymentAccount = paymentAccountForm.toPaymentAccount(jsonString);
|
||||
verifyPaymentAccountHasRequiredFields(paymentAccount);
|
||||
@ -86,6 +97,46 @@ class CorePaymentAccountsService {
|
||||
return paymentAccountForm.getPaymentAccountForm(paymentMethodId);
|
||||
}
|
||||
|
||||
// Crypto Currency Accounts
|
||||
|
||||
PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||
String currencyCode,
|
||||
String address,
|
||||
boolean tradeInstant) {
|
||||
String bsqCode = currencyCode.toUpperCase();
|
||||
if (!bsqCode.equals("BSQ"))
|
||||
throw new IllegalArgumentException("api does not currently support " + currencyCode + " accounts");
|
||||
|
||||
// Validate the BSQ address string but ignore the return value.
|
||||
coreWalletsService.getValidBsqLegacyAddress(address);
|
||||
|
||||
var cryptoCurrencyAccount = tradeInstant
|
||||
? (InstantCryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS_INSTANT)
|
||||
: (CryptoCurrencyAccount) PaymentAccountFactory.getPaymentAccount(PaymentMethod.BLOCK_CHAINS);
|
||||
cryptoCurrencyAccount.init();
|
||||
cryptoCurrencyAccount.setAccountName(accountName);
|
||||
cryptoCurrencyAccount.setAddress(address);
|
||||
Optional<CryptoCurrency> cryptoCurrency = getCryptoCurrency(bsqCode);
|
||||
cryptoCurrency.ifPresent(cryptoCurrencyAccount::setSingleTradeCurrency);
|
||||
user.addPaymentAccount(cryptoCurrencyAccount);
|
||||
accountAgeWitnessService.publishMyAccountAgeWitness(cryptoCurrencyAccount.getPaymentAccountPayload());
|
||||
log.info("Saved crypto payment account with id {} and payment method {}.",
|
||||
cryptoCurrencyAccount.getId(),
|
||||
cryptoCurrencyAccount.getPaymentAccountPayload().getPaymentMethodId());
|
||||
return cryptoCurrencyAccount;
|
||||
}
|
||||
|
||||
// TODO Support all alt coin payment methods supported by UI.
|
||||
// The getCryptoCurrencyPaymentMethods method below will be
|
||||
// callable from the CLI when more are supported.
|
||||
|
||||
List<PaymentMethod> getCryptoCurrencyPaymentMethods() {
|
||||
return PaymentMethod.getPaymentMethods().stream()
|
||||
.filter(PaymentMethod::isAsset)
|
||||
.sorted(Comparator.comparing(PaymentMethod::getId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void verifyPaymentAccountHasRequiredFields(PaymentAccount paymentAccount) {
|
||||
// Do checks here to make sure required fields are populated.
|
||||
if (paymentAccount.isTransferwiseAccount() && paymentAccount.getTradeCurrencies().isEmpty())
|
||||
|
@ -52,8 +52,10 @@ import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -75,6 +77,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -145,6 +148,10 @@ class CoreWalletsService {
|
||||
return tempAesKey;
|
||||
}
|
||||
|
||||
NetworkParameters getNetworkParameters() {
|
||||
return btcWalletService.getWallet().getContext().getParams();
|
||||
}
|
||||
|
||||
BalancesInfo getBalances(String currencyCode) {
|
||||
verifyWalletCurrencyCodeIsValid(currencyCode);
|
||||
verifyWalletsAreAvailable();
|
||||
@ -305,6 +312,41 @@ class CoreWalletsService {
|
||||
}
|
||||
}
|
||||
|
||||
boolean verifyBsqSentToAddress(String address, String amount) {
|
||||
Address receiverAddress = getValidBsqLegacyAddress(address);
|
||||
NetworkParameters networkParameters = getNetworkParameters();
|
||||
Predicate<TransactionOutput> isTxOutputAddressMatch = (txOut) ->
|
||||
txOut.getScriptPubKey().getToAddress(networkParameters).equals(receiverAddress);
|
||||
Coin coinValue = parseToCoin(amount, bsqFormatter);
|
||||
Predicate<TransactionOutput> isTxOutputValueMatch = (txOut) ->
|
||||
txOut.getValue().longValue() == coinValue.longValue();
|
||||
List<TransactionOutput> spendableBsqTxOutputs = bsqWalletService.getSpendableBsqTransactionOutputs();
|
||||
|
||||
log.info("Searching {} spendable tx outputs for matching address {} and value {}:",
|
||||
spendableBsqTxOutputs.size(),
|
||||
address,
|
||||
coinValue.toPlainString());
|
||||
long numMatches = 0;
|
||||
for (TransactionOutput txOut : spendableBsqTxOutputs) {
|
||||
if (isTxOutputAddressMatch.test(txOut) && isTxOutputValueMatch.test(txOut)) {
|
||||
log.info("\t\tTx {} output has matching address {} and value {}.",
|
||||
txOut.getParentTransaction().getTxId(),
|
||||
address,
|
||||
txOut.getValue().toPlainString());
|
||||
numMatches++;
|
||||
}
|
||||
}
|
||||
if (numMatches > 1) {
|
||||
log.warn("{} tx outputs matched address {} and value {}, could be a"
|
||||
+ " false positive BSQ payment verification result.",
|
||||
numMatches,
|
||||
address,
|
||||
coinValue.toPlainString());
|
||||
|
||||
}
|
||||
return numMatches > 0;
|
||||
}
|
||||
|
||||
void getTxFeeRate(ResultHandler resultHandler) {
|
||||
try {
|
||||
@SuppressWarnings({"unchecked", "Convert2MethodRef"})
|
||||
@ -511,6 +553,16 @@ class CoreWalletsService {
|
||||
throw new IllegalStateException("server is not fully initialized");
|
||||
}
|
||||
|
||||
// Returns a LegacyAddress for the string, or a RuntimeException if invalid.
|
||||
LegacyAddress getValidBsqLegacyAddress(String address) {
|
||||
try {
|
||||
return bsqFormatter.getAddressFromBsqAddress(address);
|
||||
} catch (Throwable t) {
|
||||
log.error("", t);
|
||||
throw new IllegalStateException(format("%s is not a valid bsq address", address));
|
||||
}
|
||||
}
|
||||
|
||||
// Throws a RuntimeException if wallet currency code is not BSQ or BTC.
|
||||
private void verifyWalletCurrencyCodeIsValid(String currencyCode) {
|
||||
if (currencyCode == null || currencyCode.isEmpty())
|
||||
@ -575,16 +627,6 @@ class CoreWalletsService {
|
||||
lockedBalance.value);
|
||||
}
|
||||
|
||||
// Returns a LegacyAddress for the string, or a RuntimeException if invalid.
|
||||
private LegacyAddress getValidBsqLegacyAddress(String address) {
|
||||
try {
|
||||
return bsqFormatter.getAddressFromBsqAddress(address);
|
||||
} catch (Throwable t) {
|
||||
log.error("", t);
|
||||
throw new IllegalStateException(format("%s is not a valid bsq address", address));
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a Coin for the transfer amount string, or a RuntimeException if invalid.
|
||||
private Coin getValidTransferAmount(String amount, CoinFormatter coinFormatter) {
|
||||
Coin amountAsCoin = parseToCoin(amount, coinFormatter);
|
||||
|
126
core/src/main/java/bisq/core/api/model/ContractInfo.java
Normal file
126
core/src/main/java/bisq/core/api/model/ContractInfo.java
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.api.model;
|
||||
|
||||
import bisq.common.Payload;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import static bisq.core.api.model.PaymentAccountPayloadInfo.emptyPaymentAccountPayload;
|
||||
|
||||
/**
|
||||
* A lightweight Trade Contract constructed from a trade's json contract.
|
||||
* Many fields in the core Contract are ignored, but can be added as needed.
|
||||
*/
|
||||
@Getter
|
||||
public class ContractInfo implements Payload {
|
||||
|
||||
private final String buyerNodeAddress;
|
||||
private final String sellerNodeAddress;
|
||||
private final String mediatorNodeAddress;
|
||||
private final String refundAgentNodeAddress;
|
||||
private final boolean isBuyerMakerAndSellerTaker;
|
||||
private final String makerAccountId;
|
||||
private final String takerAccountId;
|
||||
private final PaymentAccountPayloadInfo makerPaymentAccountPayload;
|
||||
private final PaymentAccountPayloadInfo takerPaymentAccountPayload;
|
||||
private final String makerPayoutAddressString;
|
||||
private final String takerPayoutAddressString;
|
||||
private final long lockTime;
|
||||
|
||||
public ContractInfo(String buyerNodeAddress,
|
||||
String sellerNodeAddress,
|
||||
String mediatorNodeAddress,
|
||||
String refundAgentNodeAddress,
|
||||
boolean isBuyerMakerAndSellerTaker,
|
||||
String makerAccountId,
|
||||
String takerAccountId,
|
||||
PaymentAccountPayloadInfo makerPaymentAccountPayload,
|
||||
PaymentAccountPayloadInfo takerPaymentAccountPayload,
|
||||
String makerPayoutAddressString,
|
||||
String takerPayoutAddressString,
|
||||
long lockTime) {
|
||||
this.buyerNodeAddress = buyerNodeAddress;
|
||||
this.sellerNodeAddress = sellerNodeAddress;
|
||||
this.mediatorNodeAddress = mediatorNodeAddress;
|
||||
this.refundAgentNodeAddress = refundAgentNodeAddress;
|
||||
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
|
||||
this.makerAccountId = makerAccountId;
|
||||
this.takerAccountId = takerAccountId;
|
||||
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
|
||||
this.takerPaymentAccountPayload = takerPaymentAccountPayload;
|
||||
this.makerPayoutAddressString = makerPayoutAddressString;
|
||||
this.takerPayoutAddressString = takerPayoutAddressString;
|
||||
this.lockTime = lockTime;
|
||||
}
|
||||
|
||||
|
||||
// For transmitting TradeInfo messages when no contract is available.
|
||||
public static Supplier<ContractInfo> emptyContract = () ->
|
||||
new ContractInfo("",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
emptyPaymentAccountPayload.get(),
|
||||
emptyPaymentAccountPayload.get(),
|
||||
"",
|
||||
"",
|
||||
0);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static ContractInfo fromProto(bisq.proto.grpc.ContractInfo proto) {
|
||||
return new ContractInfo(proto.getBuyerNodeAddress(),
|
||||
proto.getSellerNodeAddress(),
|
||||
proto.getMediatorNodeAddress(),
|
||||
proto.getRefundAgentNodeAddress(),
|
||||
proto.getIsBuyerMakerAndSellerTaker(),
|
||||
proto.getMakerAccountId(),
|
||||
proto.getTakerAccountId(),
|
||||
PaymentAccountPayloadInfo.fromProto(proto.getMakerPaymentAccountPayload()),
|
||||
PaymentAccountPayloadInfo.fromProto(proto.getTakerPaymentAccountPayload()),
|
||||
proto.getMakerPayoutAddressString(),
|
||||
proto.getTakerPayoutAddressString(),
|
||||
proto.getLockTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public bisq.proto.grpc.ContractInfo toProtoMessage() {
|
||||
return bisq.proto.grpc.ContractInfo.newBuilder()
|
||||
.setBuyerNodeAddress(buyerNodeAddress)
|
||||
.setSellerNodeAddress(sellerNodeAddress)
|
||||
.setMediatorNodeAddress(mediatorNodeAddress)
|
||||
.setRefundAgentNodeAddress(refundAgentNodeAddress)
|
||||
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
|
||||
.setMakerAccountId(makerAccountId)
|
||||
.setTakerAccountId(takerAccountId)
|
||||
.setMakerPaymentAccountPayload(makerPaymentAccountPayload.toProtoMessage())
|
||||
.setTakerPaymentAccountPayload(takerPaymentAccountPayload.toProtoMessage())
|
||||
.setMakerPayoutAddressString(makerPayoutAddressString)
|
||||
.setTakerPayoutAddressString(takerPayoutAddressString)
|
||||
.setLockTime(lockTime)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -45,7 +45,11 @@ public class OfferInfo implements Payload {
|
||||
private final long minAmount;
|
||||
private final long volume;
|
||||
private final long minVolume;
|
||||
private final long txFee;
|
||||
private final long makerFee;
|
||||
private final String offerFeePaymentTxId;
|
||||
private final long buyerSecurityDeposit;
|
||||
private final long sellerSecurityDeposit;
|
||||
private final long triggerPrice;
|
||||
private final boolean isCurrencyForMakerFeeBtc;
|
||||
private final String paymentAccountId;
|
||||
@ -58,6 +62,7 @@ public class OfferInfo implements Payload {
|
||||
private final long date;
|
||||
private final String state;
|
||||
|
||||
|
||||
public OfferInfo(OfferInfoBuilder builder) {
|
||||
this.id = builder.id;
|
||||
this.direction = builder.direction;
|
||||
@ -68,7 +73,11 @@ public class OfferInfo implements Payload {
|
||||
this.minAmount = builder.minAmount;
|
||||
this.volume = builder.volume;
|
||||
this.minVolume = builder.minVolume;
|
||||
this.txFee = builder.txFee;
|
||||
this.makerFee = builder.makerFee;
|
||||
this.offerFeePaymentTxId = builder.offerFeePaymentTxId;
|
||||
this.buyerSecurityDeposit = builder.buyerSecurityDeposit;
|
||||
this.sellerSecurityDeposit = builder.sellerSecurityDeposit;
|
||||
this.triggerPrice = builder.triggerPrice;
|
||||
this.isCurrencyForMakerFeeBtc = builder.isCurrencyForMakerFeeBtc;
|
||||
this.paymentAccountId = builder.paymentAccountId;
|
||||
@ -78,6 +87,7 @@ public class OfferInfo implements Payload {
|
||||
this.counterCurrencyCode = builder.counterCurrencyCode;
|
||||
this.date = builder.date;
|
||||
this.state = builder.state;
|
||||
|
||||
}
|
||||
|
||||
public static OfferInfo toOfferInfo(Offer offer) {
|
||||
@ -90,8 +100,8 @@ public class OfferInfo implements Payload {
|
||||
return getOfferInfoBuilder(offer).withTriggerPrice(triggerPrice).build();
|
||||
}
|
||||
|
||||
private static OfferInfo.OfferInfoBuilder getOfferInfoBuilder(Offer offer) {
|
||||
return new OfferInfo.OfferInfoBuilder()
|
||||
private static OfferInfoBuilder getOfferInfoBuilder(Offer offer) {
|
||||
return new OfferInfoBuilder()
|
||||
.withId(offer.getId())
|
||||
.withDirection(offer.getDirection().name())
|
||||
.withPrice(Objects.requireNonNull(offer.getPrice()).getValue())
|
||||
@ -101,7 +111,11 @@ public class OfferInfo implements Payload {
|
||||
.withMinAmount(offer.getMinAmount().value)
|
||||
.withVolume(Objects.requireNonNull(offer.getVolume()).getValue())
|
||||
.withMinVolume(Objects.requireNonNull(offer.getMinVolume()).getValue())
|
||||
.withMakerFee(offer.getMakerFee().value)
|
||||
.withTxFee(offer.getTxFee().value)
|
||||
.withOfferFeePaymentTxId(offer.getOfferFeePaymentTxId())
|
||||
.withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value)
|
||||
.withSellerSecurityDeposit(offer.getSellerSecurityDeposit().value)
|
||||
.withIsCurrencyForMakerFeeBtc(offer.isCurrencyForMakerFeeBtc())
|
||||
.withPaymentAccountId(offer.getMakerPaymentAccountId())
|
||||
.withPaymentMethodId(offer.getPaymentMethod().getId())
|
||||
@ -128,7 +142,11 @@ public class OfferInfo implements Payload {
|
||||
.setMinAmount(minAmount)
|
||||
.setVolume(volume)
|
||||
.setMinVolume(minVolume)
|
||||
.setMakerFee(makerFee)
|
||||
.setTxFee(txFee)
|
||||
.setOfferFeePaymentTxId(offerFeePaymentTxId)
|
||||
.setBuyerSecurityDeposit(buyerSecurityDeposit)
|
||||
.setSellerSecurityDeposit(sellerSecurityDeposit)
|
||||
.setTriggerPrice(triggerPrice)
|
||||
.setIsCurrencyForMakerFeeBtc(isCurrencyForMakerFeeBtc)
|
||||
.setPaymentAccountId(paymentAccountId)
|
||||
@ -143,7 +161,7 @@ public class OfferInfo implements Payload {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static OfferInfo fromProto(bisq.proto.grpc.OfferInfo proto) {
|
||||
return new OfferInfo.OfferInfoBuilder()
|
||||
return new OfferInfoBuilder()
|
||||
.withId(proto.getId())
|
||||
.withDirection(proto.getDirection())
|
||||
.withPrice(proto.getPrice())
|
||||
@ -153,7 +171,11 @@ public class OfferInfo implements Payload {
|
||||
.withMinAmount(proto.getMinAmount())
|
||||
.withVolume(proto.getVolume())
|
||||
.withMinVolume(proto.getMinVolume())
|
||||
.withMakerFee(proto.getMakerFee())
|
||||
.withTxFee(proto.getTxFee())
|
||||
.withOfferFeePaymentTxId(proto.getOfferFeePaymentTxId())
|
||||
.withBuyerSecurityDeposit(proto.getBuyerSecurityDeposit())
|
||||
.withSellerSecurityDeposit(proto.getSellerSecurityDeposit())
|
||||
.withTriggerPrice(proto.getTriggerPrice())
|
||||
.withIsCurrencyForMakerFeeBtc(proto.getIsCurrencyForMakerFeeBtc())
|
||||
.withPaymentAccountId(proto.getPaymentAccountId())
|
||||
@ -182,7 +204,11 @@ public class OfferInfo implements Payload {
|
||||
private long minAmount;
|
||||
private long volume;
|
||||
private long minVolume;
|
||||
private long txFee;
|
||||
private long makerFee;
|
||||
private String offerFeePaymentTxId;
|
||||
private long buyerSecurityDeposit;
|
||||
private long sellerSecurityDeposit;
|
||||
private long triggerPrice;
|
||||
private boolean isCurrencyForMakerFeeBtc;
|
||||
private String paymentAccountId;
|
||||
@ -238,11 +264,31 @@ public class OfferInfo implements Payload {
|
||||
return this;
|
||||
}
|
||||
|
||||
public OfferInfoBuilder withTxFee(long txFee) {
|
||||
this.txFee = txFee;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OfferInfoBuilder withMakerFee(long makerFee) {
|
||||
this.makerFee = makerFee;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OfferInfoBuilder withOfferFeePaymentTxId(String offerFeePaymentTxId) {
|
||||
this.offerFeePaymentTxId = offerFeePaymentTxId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OfferInfoBuilder withBuyerSecurityDeposit(long buyerSecurityDeposit) {
|
||||
this.buyerSecurityDeposit = buyerSecurityDeposit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OfferInfoBuilder withSellerSecurityDeposit(long sellerSecurityDeposit) {
|
||||
this.sellerSecurityDeposit = sellerSecurityDeposit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OfferInfoBuilder withTriggerPrice(long triggerPrice) {
|
||||
this.triggerPrice = triggerPrice;
|
||||
return this;
|
||||
|
@ -57,7 +57,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
/**
|
||||
* <p>
|
||||
* An instance of this class can write new payment account forms (editable json files),
|
||||
* and de-serialize edited json files into {@link bisq.core.payment.PaymentAccount}
|
||||
* and de-serialize edited json files into {@link PaymentAccount}
|
||||
* instances.
|
||||
* </p>
|
||||
* <p>
|
||||
@ -66,8 +66,8 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
* </p>
|
||||
* <br>
|
||||
* <p>
|
||||
* (1) Ask for a hal cash account form: Pass a {@link bisq.core.payment.payload.PaymentMethod#HAL_CASH_ID}
|
||||
* to {@link bisq.core.api.model.PaymentAccountForm#getPaymentAccountForm(String)} to
|
||||
* (1) Ask for a hal cash account form: Pass a {@link PaymentMethod#HAL_CASH_ID}
|
||||
* to {@link PaymentAccountForm#getPaymentAccountForm(String)} to
|
||||
* get the json Hal Cash payment account form:
|
||||
* <pre>
|
||||
* {
|
||||
@ -98,8 +98,8 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
* </pre>
|
||||
* </p>
|
||||
* (3) De-serialize the edited json account form: Pass the edited json file to
|
||||
* {@link bisq.core.api.model.PaymentAccountForm#toPaymentAccount(File)}, or
|
||||
* a json string to {@link bisq.core.api.model.PaymentAccountForm#toPaymentAccount(String)}
|
||||
* {@link PaymentAccountForm#toPaymentAccount(File)}, or
|
||||
* a json string to {@link PaymentAccountForm#toPaymentAccount(String)}
|
||||
* and get a {@link bisq.core.payment.HalCashAccount} instance.
|
||||
* <pre>
|
||||
* PaymentAccount(
|
||||
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.api.model;
|
||||
|
||||
import bisq.core.payment.payload.CryptoCurrencyAccountPayload;
|
||||
import bisq.core.payment.payload.InstantCryptoCurrencyPayload;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
|
||||
import bisq.common.Payload;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Getter
|
||||
public class PaymentAccountPayloadInfo implements Payload {
|
||||
|
||||
private final String id;
|
||||
private final String paymentMethodId;
|
||||
@Nullable
|
||||
private final String address;
|
||||
|
||||
public PaymentAccountPayloadInfo(String id,
|
||||
String paymentMethodId,
|
||||
@Nullable String address) {
|
||||
this.id = id;
|
||||
this.paymentMethodId = paymentMethodId;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public static PaymentAccountPayloadInfo toPaymentAccountPayloadInfo(PaymentAccountPayload paymentAccountPayload) {
|
||||
Optional<String> address = Optional.empty();
|
||||
if (paymentAccountPayload instanceof CryptoCurrencyAccountPayload)
|
||||
address = Optional.of(((CryptoCurrencyAccountPayload) paymentAccountPayload).getAddress());
|
||||
else if (paymentAccountPayload instanceof InstantCryptoCurrencyPayload)
|
||||
address = Optional.of(((InstantCryptoCurrencyPayload) paymentAccountPayload).getAddress());
|
||||
|
||||
return new PaymentAccountPayloadInfo(paymentAccountPayload.getId(),
|
||||
paymentAccountPayload.getPaymentMethodId(),
|
||||
address.orElse(""));
|
||||
}
|
||||
|
||||
// For transmitting TradeInfo messages when no contract & payloads are available.
|
||||
public static Supplier<PaymentAccountPayloadInfo> emptyPaymentAccountPayload = () ->
|
||||
new PaymentAccountPayloadInfo("", "", "");
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static PaymentAccountPayloadInfo fromProto(bisq.proto.grpc.PaymentAccountPayloadInfo proto) {
|
||||
return new PaymentAccountPayloadInfo(proto.getId(), proto.getPaymentMethodId(), proto.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public bisq.proto.grpc.PaymentAccountPayloadInfo toProtoMessage() {
|
||||
return bisq.proto.grpc.PaymentAccountPayloadInfo.newBuilder()
|
||||
.setId(id)
|
||||
.setPaymentMethodId(paymentMethodId)
|
||||
.setAddress(address != null ? address : "")
|
||||
.build();
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
|
||||
package bisq.core.api.model;
|
||||
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
|
||||
import bisq.common.Payload;
|
||||
@ -27,6 +28,7 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import static bisq.core.api.model.OfferInfo.toOfferInfo;
|
||||
import static bisq.core.api.model.PaymentAccountPayloadInfo.toPaymentAccountPayloadInfo;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@ -60,6 +62,7 @@ public class TradeInfo implements Payload {
|
||||
private final boolean isPayoutPublished;
|
||||
private final boolean isWithdrawn;
|
||||
private final String contractAsJson;
|
||||
private final ContractInfo contract;
|
||||
|
||||
public TradeInfo(TradeInfoBuilder builder) {
|
||||
this.offer = builder.offer;
|
||||
@ -86,6 +89,7 @@ public class TradeInfo implements Payload {
|
||||
this.isPayoutPublished = builder.isPayoutPublished;
|
||||
this.isWithdrawn = builder.isWithdrawn;
|
||||
this.contractAsJson = builder.contractAsJson;
|
||||
this.contract = builder.contract;
|
||||
}
|
||||
|
||||
public static TradeInfo toTradeInfo(Trade trade) {
|
||||
@ -93,7 +97,26 @@ public class TradeInfo implements Payload {
|
||||
}
|
||||
|
||||
public static TradeInfo toTradeInfo(Trade trade, String role) {
|
||||
return new TradeInfo.TradeInfoBuilder()
|
||||
ContractInfo contractInfo;
|
||||
if (trade.getContract() != null) {
|
||||
Contract contract = trade.getContract();
|
||||
contractInfo = new ContractInfo(contract.getBuyerPayoutAddressString(),
|
||||
contract.getSellerPayoutAddressString(),
|
||||
contract.getMediatorNodeAddress().getFullAddress(),
|
||||
contract.getRefundAgentNodeAddress().getFullAddress(),
|
||||
contract.isBuyerMakerAndSellerTaker(),
|
||||
contract.getMakerAccountId(),
|
||||
contract.getTakerAccountId(),
|
||||
toPaymentAccountPayloadInfo(contract.getMakerPaymentAccountPayload()),
|
||||
toPaymentAccountPayloadInfo(contract.getTakerPaymentAccountPayload()),
|
||||
contract.getMakerPayoutAddressString(),
|
||||
contract.getTakerPayoutAddressString(),
|
||||
contract.getLockTime());
|
||||
} else {
|
||||
contractInfo = ContractInfo.emptyContract.get();
|
||||
}
|
||||
|
||||
return new TradeInfoBuilder()
|
||||
.withOffer(toOfferInfo(trade.getOffer()))
|
||||
.withTradeId(trade.getId())
|
||||
.withShortId(trade.getShortId())
|
||||
@ -120,6 +143,7 @@ public class TradeInfo implements Payload {
|
||||
.withIsPayoutPublished(trade.isPayoutPublished())
|
||||
.withIsWithdrawn(trade.isWithdrawn())
|
||||
.withContractAsJson(trade.getContractAsJson())
|
||||
.withContract(contractInfo)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -154,12 +178,38 @@ public class TradeInfo implements Payload {
|
||||
.setIsPayoutPublished(isPayoutPublished)
|
||||
.setIsWithdrawn(isWithdrawn)
|
||||
.setContractAsJson(contractAsJson == null ? "" : contractAsJson)
|
||||
.setContract(contract.toProtoMessage())
|
||||
.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused", "SameReturnValue"})
|
||||
public static TradeInfo fromProto(bisq.proto.grpc.TradeInfo proto) {
|
||||
return null; // TODO
|
||||
return new TradeInfoBuilder()
|
||||
.withOffer(OfferInfo.fromProto(proto.getOffer()))
|
||||
.withTradeId(proto.getTradeId())
|
||||
.withShortId(proto.getShortId())
|
||||
.withDate(proto.getDate())
|
||||
.withRole(proto.getRole())
|
||||
.withIsCurrencyForTakerFeeBtc(proto.getIsCurrencyForTakerFeeBtc())
|
||||
.withTxFeeAsLong(proto.getTxFeeAsLong())
|
||||
.withTakerFeeAsLong(proto.getTakerFeeAsLong())
|
||||
.withTakerFeeTxId(proto.getTakerFeeTxId())
|
||||
.withDepositTxId(proto.getDepositTxId())
|
||||
.withPayoutTxId(proto.getPayoutTxId())
|
||||
.withTradeAmountAsLong(proto.getTradeAmountAsLong())
|
||||
.withTradePrice(proto.getTradePrice())
|
||||
.withTradePeriodState(proto.getTradePeriodState())
|
||||
.withState(proto.getState())
|
||||
.withPhase(proto.getPhase())
|
||||
.withTradingPeerNodeAddress(proto.getTradingPeerNodeAddress())
|
||||
.withIsDepositPublished(proto.getIsDepositPublished())
|
||||
.withIsDepositConfirmed(proto.getIsDepositConfirmed())
|
||||
.withIsFiatSent(proto.getIsFiatSent())
|
||||
.withIsFiatReceived(proto.getIsFiatReceived())
|
||||
.withIsPayoutPublished(proto.getIsPayoutPublished())
|
||||
.withIsWithdrawn(proto.getIsWithdrawn())
|
||||
.withContractAsJson(proto.getContractAsJson())
|
||||
.withContract((ContractInfo.fromProto(proto.getContract())))
|
||||
.build();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -193,6 +243,7 @@ public class TradeInfo implements Payload {
|
||||
private boolean isPayoutPublished;
|
||||
private boolean isWithdrawn;
|
||||
private String contractAsJson;
|
||||
private ContractInfo contract;
|
||||
|
||||
public TradeInfoBuilder withOffer(OfferInfo offer) {
|
||||
this.offer = offer;
|
||||
@ -314,6 +365,11 @@ public class TradeInfo implements Payload {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TradeInfoBuilder withContract(ContractInfo contract) {
|
||||
this.contract = contract;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TradeInfo build() {
|
||||
return new TradeInfo(this);
|
||||
}
|
||||
@ -346,6 +402,7 @@ public class TradeInfo implements Payload {
|
||||
", isWithdrawn=" + isWithdrawn + "\n" +
|
||||
", offer=" + offer + "\n" +
|
||||
", contractAsJson=" + contractAsJson + "\n" +
|
||||
", contract=" + contract + "\n" +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ public class TxInfo implements Payload {
|
||||
private final boolean isPending;
|
||||
private final String memo;
|
||||
|
||||
public TxInfo(TxInfo.TxInfoBuilder builder) {
|
||||
public TxInfo(TxInfoBuilder builder) {
|
||||
this.txId = builder.txId;
|
||||
this.inputSum = builder.inputSum;
|
||||
this.outputSum = builder.outputSum;
|
||||
@ -61,7 +61,7 @@ public class TxInfo implements Payload {
|
||||
throw new IllegalStateException("server created a null transaction");
|
||||
|
||||
if (transaction.getFee() != null)
|
||||
return new TxInfo.TxInfoBuilder()
|
||||
return new TxInfoBuilder()
|
||||
.withTxId(transaction.getTxId().toString())
|
||||
.withInputSum(transaction.getInputSum().value)
|
||||
.withOutputSum(transaction.getOutputSum().value)
|
||||
@ -71,7 +71,7 @@ public class TxInfo implements Payload {
|
||||
.withMemo(transaction.getMemo())
|
||||
.build();
|
||||
else
|
||||
return new TxInfo.TxInfoBuilder()
|
||||
return new TxInfoBuilder()
|
||||
.withTxId(transaction.getTxId().toString())
|
||||
.withInputSum(transaction.getInputSum().value)
|
||||
.withOutputSum(transaction.getOutputSum().value)
|
||||
@ -101,7 +101,7 @@ public class TxInfo implements Payload {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static TxInfo fromProto(bisq.proto.grpc.TxInfo proto) {
|
||||
return new TxInfo.TxInfoBuilder()
|
||||
return new TxInfoBuilder()
|
||||
.withTxId(proto.getTxId())
|
||||
.withInputSum(proto.getInputSum())
|
||||
.withOutputSum(proto.getOutputSum())
|
||||
@ -121,37 +121,37 @@ public class TxInfo implements Payload {
|
||||
private boolean isPending;
|
||||
private String memo;
|
||||
|
||||
public TxInfo.TxInfoBuilder withTxId(String txId) {
|
||||
public TxInfoBuilder withTxId(String txId) {
|
||||
this.txId = txId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TxInfo.TxInfoBuilder withInputSum(long inputSum) {
|
||||
public TxInfoBuilder withInputSum(long inputSum) {
|
||||
this.inputSum = inputSum;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TxInfo.TxInfoBuilder withOutputSum(long outputSum) {
|
||||
public TxInfoBuilder withOutputSum(long outputSum) {
|
||||
this.outputSum = outputSum;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TxInfo.TxInfoBuilder withFee(long fee) {
|
||||
public TxInfoBuilder withFee(long fee) {
|
||||
this.fee = fee;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TxInfo.TxInfoBuilder withSize(int size) {
|
||||
public TxInfoBuilder withSize(int size) {
|
||||
this.size = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TxInfo.TxInfoBuilder withIsPending(boolean isPending) {
|
||||
public TxInfoBuilder withIsPending(boolean isPending) {
|
||||
this.isPending = isPending;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TxInfo.TxInfoBuilder withMemo(String memo) {
|
||||
public TxInfoBuilder withMemo(String memo) {
|
||||
this.memo = memo;
|
||||
return this;
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
createcryptopaymentacct
|
||||
|
||||
NAME
|
||||
----
|
||||
createcryptopaymentacct - create a cryptocurrency payment account
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
createcryptopaymentacct
|
||||
--account-name=<account-name>
|
||||
--currency-code=<bsq>
|
||||
--address=<unused-bsq-address>
|
||||
[--trade-instant=<true|false default=false>]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
Create an cryptocurrency (altcoin) payment account. Only BSQ payment accounts are currently supported.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
--account-name
|
||||
The name of the cryptocurrency payment account used to create and take altcoin offers.
|
||||
|
||||
--currency-code
|
||||
The three letter code for the altcoin, e.g., BSQ.
|
||||
|
||||
--address
|
||||
The altcoin address to be used receive cryptocurrency payment when selling BTC.
|
||||
|
||||
--trade-instant
|
||||
True for creating an instant cryptocurrency payment account, false otherwise.
|
||||
Default is false.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
||||
To create a BSQ Altcoin payment account:
|
||||
$ ./bisq-cli --password=xyz --port=9998 createcryptopaymentacct --account-name="My BSQ Account" \
|
||||
--currency-code=bsq \
|
||||
--address=Bn3PCQgRwhkrGnaMp1RYwt9tFwL51YELqne \
|
||||
--trade-instant=false
|
||||
|
||||
To create a BSQ Instant Altcoin payment account:
|
||||
$ ./bisq-cli --password=xyz --port=9998 createcryptopaymentacct --account-name="My Instant BSQ Account" \
|
||||
--currency-code=bsq \
|
||||
--address=Bn3PCQgRwhkrGnaMp1RYwt9tFwL51YELqne \
|
||||
--trade-instant=true
|
@ -19,6 +19,7 @@ package bisq.daemon.grpc;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
|
||||
import bisq.proto.grpc.AvailabilityResultWithDescription;
|
||||
import bisq.proto.grpc.TakeOfferReply;
|
||||
|
||||
import protobuf.AvailabilityResult;
|
||||
@ -77,7 +78,7 @@ public class GrpcErrorMessageHandler implements ErrorMessageHandler {
|
||||
this.isErrorHandled = true;
|
||||
log.error(errorMessage);
|
||||
|
||||
if (isTakeOfferError()) {
|
||||
if (takeOfferWasCalled()) {
|
||||
handleTakeOfferError(errorMessage);
|
||||
} else {
|
||||
exceptionHandler.handleErrorMessage(log,
|
||||
@ -88,14 +89,20 @@ public class GrpcErrorMessageHandler implements ErrorMessageHandler {
|
||||
}
|
||||
|
||||
private void handleTakeOfferError(String errorMessage) {
|
||||
// Send the AvailabilityResult to the client instead of throwing an exception.
|
||||
// The client should look at the grpc reply object's AvailabilityResult
|
||||
// field if reply.hasTrade = false, and use it give the user a human readable msg.
|
||||
// If the errorMessage originated from a UI purposed TaskRunner, it should
|
||||
// contain an AvailabilityResult enum name. If it does, derive the
|
||||
// AvailabilityResult enum from the errorMessage, wrap it in a new
|
||||
// AvailabilityResultWithDescription enum, then send the
|
||||
// AvailabilityResultWithDescription to the client instead of throwing
|
||||
// an exception. The client should use the grpc reply object's
|
||||
// AvailabilityResultWithDescription field if reply.hasTrade = false, and the
|
||||
// client can decide to throw an exception with the client friendly error
|
||||
// description, or take some other action based on the AvailabilityResult enum.
|
||||
// (Some offer availability problems are not fatal, and retries are appropriate.)
|
||||
try {
|
||||
AvailabilityResult availabilityResultProto = getAvailabilityResult(errorMessage);
|
||||
var failureReason = getAvailabilityResultWithDescription(errorMessage);
|
||||
var reply = TakeOfferReply.newBuilder()
|
||||
.setAvailabilityResult(availabilityResultProto)
|
||||
.setAvailabilityResultDescription(getAvailabilityResultDescription(availabilityResultProto))
|
||||
.setFailureReason(failureReason)
|
||||
.build();
|
||||
@SuppressWarnings("unchecked")
|
||||
var takeOfferResponseObserver = (StreamObserver<TakeOfferReply>) responseObserver;
|
||||
@ -109,6 +116,15 @@ public class GrpcErrorMessageHandler implements ErrorMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private AvailabilityResultWithDescription getAvailabilityResultWithDescription(String errorMessage) {
|
||||
AvailabilityResult proto = getAvailabilityResult(errorMessage);
|
||||
String description = getAvailabilityResultDescription(proto);
|
||||
return AvailabilityResultWithDescription.newBuilder()
|
||||
.setAvailabilityResult(proto)
|
||||
.setDescription(description)
|
||||
.build();
|
||||
}
|
||||
|
||||
private AvailabilityResult getAvailabilityResult(String errorMessage) {
|
||||
return stream(AvailabilityResult.values())
|
||||
.filter((e) -> errorMessage.toUpperCase().contains(e.name()))
|
||||
@ -121,7 +137,7 @@ public class GrpcErrorMessageHandler implements ErrorMessageHandler {
|
||||
return bisq.core.offer.AvailabilityResult.fromProto(proto).description();
|
||||
}
|
||||
|
||||
private boolean isTakeOfferError() {
|
||||
private boolean takeOfferWasCalled() {
|
||||
return fullMethodName.equals(getTakeOfferMethod().getFullMethodName());
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,12 @@ import bisq.core.api.CoreApi;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
|
||||
import bisq.proto.grpc.CreateCryptoCurrencyPaymentAccountReply;
|
||||
import bisq.proto.grpc.CreateCryptoCurrencyPaymentAccountRequest;
|
||||
import bisq.proto.grpc.CreatePaymentAccountReply;
|
||||
import bisq.proto.grpc.CreatePaymentAccountRequest;
|
||||
import bisq.proto.grpc.GetCryptoCurrencyPaymentMethodsReply;
|
||||
import bisq.proto.grpc.GetCryptoCurrencyPaymentMethodsRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountFormReply;
|
||||
import bisq.proto.grpc.GetPaymentAccountFormRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountsReply;
|
||||
@ -125,6 +129,40 @@ class GrpcPaymentAccountsService extends PaymentAccountsImplBase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createCryptoCurrencyPaymentAccount(CreateCryptoCurrencyPaymentAccountRequest req,
|
||||
StreamObserver<CreateCryptoCurrencyPaymentAccountReply> responseObserver) {
|
||||
try {
|
||||
PaymentAccount paymentAccount = coreApi.createCryptoCurrencyPaymentAccount(req.getAccountName(),
|
||||
req.getCurrencyCode(),
|
||||
req.getAddress(),
|
||||
req.getTradeInstant());
|
||||
var reply = CreateCryptoCurrencyPaymentAccountReply.newBuilder()
|
||||
.setPaymentAccount(paymentAccount.toProtoMessage())
|
||||
.build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
} catch (Throwable cause) {
|
||||
exceptionHandler.handleException(log, cause, responseObserver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCryptoCurrencyPaymentMethods(GetCryptoCurrencyPaymentMethodsRequest req,
|
||||
StreamObserver<GetCryptoCurrencyPaymentMethodsReply> responseObserver) {
|
||||
try {
|
||||
var paymentMethods = coreApi.getCryptoCurrencyPaymentMethods().stream()
|
||||
.map(PaymentMethod::toProtoMessage)
|
||||
.collect(Collectors.toList());
|
||||
var reply = GetCryptoCurrencyPaymentMethodsReply.newBuilder()
|
||||
.addAllPaymentMethods(paymentMethods).build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
} catch (Throwable cause) {
|
||||
exceptionHandler.handleException(log, cause, responseObserver);
|
||||
}
|
||||
}
|
||||
|
||||
final ServerInterceptor[] interceptors() {
|
||||
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
|
||||
return rateMeteringInterceptor.map(serverInterceptor ->
|
||||
|
@ -155,6 +155,15 @@ message OfferInfo {
|
||||
string counterCurrencyCode = 17;
|
||||
uint64 date = 18;
|
||||
string state = 19;
|
||||
uint64 sellerSecurityDeposit = 20;
|
||||
string offerFeePaymentTxId = 21;
|
||||
uint64 txFee = 22;
|
||||
uint64 makerFee = 23;
|
||||
}
|
||||
|
||||
message AvailabilityResultWithDescription {
|
||||
AvailabilityResult availabilityResult = 1;
|
||||
string description = 2;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -170,6 +179,10 @@ service PaymentAccounts {
|
||||
}
|
||||
rpc GetPaymentAccountForm (GetPaymentAccountFormRequest) returns (GetPaymentAccountFormReply) {
|
||||
}
|
||||
rpc CreateCryptoCurrencyPaymentAccount (CreateCryptoCurrencyPaymentAccountRequest) returns (CreateCryptoCurrencyPaymentAccountReply) {
|
||||
}
|
||||
rpc GetCryptoCurrencyPaymentMethods (GetCryptoCurrencyPaymentMethodsRequest) returns (GetCryptoCurrencyPaymentMethodsReply) {
|
||||
}
|
||||
}
|
||||
|
||||
message CreatePaymentAccountRequest {
|
||||
@ -202,6 +215,24 @@ message GetPaymentAccountFormReply {
|
||||
string paymentAccountFormJson = 1;
|
||||
}
|
||||
|
||||
message CreateCryptoCurrencyPaymentAccountRequest {
|
||||
string accountName = 1;
|
||||
string currencyCode = 2;
|
||||
string address = 3;
|
||||
bool tradeInstant = 4;
|
||||
}
|
||||
|
||||
message CreateCryptoCurrencyPaymentAccountReply {
|
||||
PaymentAccount paymentAccount = 1;
|
||||
}
|
||||
|
||||
message GetCryptoCurrencyPaymentMethodsRequest {
|
||||
}
|
||||
|
||||
message GetCryptoCurrencyPaymentMethodsReply {
|
||||
repeated PaymentMethod paymentMethods = 1;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Price
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -277,8 +308,7 @@ message TakeOfferRequest {
|
||||
|
||||
message TakeOfferReply {
|
||||
TradeInfo trade = 1;
|
||||
AvailabilityResult availabilityResult = 2;
|
||||
string availabilityResultDescription = 3;
|
||||
AvailabilityResultWithDescription failureReason = 2;
|
||||
}
|
||||
|
||||
message ConfirmPaymentStartedRequest {
|
||||
@ -344,6 +374,28 @@ message TradeInfo {
|
||||
bool isPayoutPublished = 22;
|
||||
bool isWithdrawn = 23;
|
||||
string contractAsJson = 24;
|
||||
ContractInfo contract = 25;
|
||||
}
|
||||
|
||||
message ContractInfo {
|
||||
string buyerNodeAddress = 1;
|
||||
string sellerNodeAddress = 2;
|
||||
string mediatorNodeAddress = 3;
|
||||
string refundAgentNodeAddress = 4;
|
||||
bool isBuyerMakerAndSellerTaker = 5;
|
||||
string makerAccountId = 6;
|
||||
string takerAccountId = 7;
|
||||
PaymentAccountPayloadInfo makerPaymentAccountPayload = 8;
|
||||
PaymentAccountPayloadInfo takerPaymentAccountPayload = 9;
|
||||
string makerPayoutAddressString = 10;
|
||||
string takerPayoutAddressString = 11;
|
||||
uint64 lockTime = 12;
|
||||
}
|
||||
|
||||
message PaymentAccountPayloadInfo {
|
||||
string id = 1;
|
||||
string paymentMethodId = 2;
|
||||
string address = 3;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -382,6 +434,8 @@ service Wallets {
|
||||
}
|
||||
rpc SendBtc (SendBtcRequest) returns (SendBtcReply) {
|
||||
}
|
||||
rpc VerifyBsqSentToAddress (VerifyBsqSentToAddressRequest) returns (VerifyBsqSentToAddressReply) {
|
||||
}
|
||||
rpc GetTxFeeRate (GetTxFeeRateRequest) returns (GetTxFeeRateReply) {
|
||||
}
|
||||
rpc SetTxFeeRatePreference (SetTxFeeRatePreferenceRequest) returns (SetTxFeeRatePreferenceReply) {
|
||||
@ -446,6 +500,15 @@ message SendBtcReply {
|
||||
TxInfo txInfo = 1;
|
||||
}
|
||||
|
||||
message VerifyBsqSentToAddressRequest {
|
||||
string address = 1;
|
||||
string amount = 2;
|
||||
}
|
||||
|
||||
message VerifyBsqSentToAddressReply {
|
||||
bool isAmountReceived = 1;
|
||||
}
|
||||
|
||||
message GetTxFeeRateRequest {
|
||||
}
|
||||
|
||||
@ -558,4 +621,3 @@ message GetVersionRequest {
|
||||
message GetVersionReply {
|
||||
string version = 1;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user