Skip to content

Commit

Permalink
Merge pull request #4329 from ghubstan/6-getoffers
Browse files Browse the repository at this point in the history
Add rpc method 'getoffers'
  • Loading branch information
sqrrm authored Jun 25, 2020
2 parents 79f0dac + f820897 commit a54eeea
Show file tree
Hide file tree
Showing 18 changed files with 1,005 additions and 231 deletions.
76 changes: 31 additions & 45 deletions cli/src/main/java/bisq/cli/CliMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@

package bisq.cli;

import bisq.proto.grpc.AddressBalanceInfo;
import bisq.proto.grpc.CreatePaymentAccountRequest;
import bisq.proto.grpc.GetAddressBalanceRequest;
import bisq.proto.grpc.GetBalanceRequest;
import bisq.proto.grpc.GetFundingAddressesRequest;
import bisq.proto.grpc.GetOffersRequest;
import bisq.proto.grpc.GetPaymentAccountsRequest;
import bisq.proto.grpc.GetVersionGrpc;
import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.LockWalletRequest;
import bisq.proto.grpc.OffersGrpc;
import bisq.proto.grpc.PaymentAccountsGrpc;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
import bisq.proto.grpc.SetWalletPasswordRequest;
Expand All @@ -38,20 +39,18 @@
import joptsimple.OptionParser;
import joptsimple.OptionSet;

import java.text.DecimalFormat;

import java.io.IOException;
import java.io.PrintStream;

import java.math.BigDecimal;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;

import static bisq.cli.CurrencyFormat.formatSatoshis;
import static bisq.cli.TableFormat.formatAddressBalanceTbl;
import static bisq.cli.TableFormat.formatOfferTable;
import static bisq.cli.TableFormat.formatPaymentAcctTbl;
import static java.lang.String.format;
import static java.lang.System.err;
import static java.lang.System.exit;
Expand All @@ -65,13 +64,8 @@
@Slf4j
public class CliMain {

private static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100000000);
private static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
private static final Function<Long, String> formatSatoshis = (sats) ->
BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));

private enum Method {
getoffers,
createpaymentacct,
getpaymentaccts,
getversion,
Expand Down Expand Up @@ -151,6 +145,7 @@ public static void run(String[] args) {
}));

var versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
var offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials);
var paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
var walletsService = WalletsGrpc.newBlockingStub(channel).withCallCredentials(credentials);

Expand All @@ -165,7 +160,7 @@ public static void run(String[] args) {
case getbalance: {
var request = GetBalanceRequest.newBuilder().build();
var reply = walletsService.getBalance(request);
var btcBalance = formatSatoshis.apply(reply.getBalance());
var btcBalance = formatSatoshis(reply.getBalance());
out.println(btcBalance);
return;
}
Expand All @@ -176,30 +171,38 @@ public static void run(String[] args) {
var request = GetAddressBalanceRequest.newBuilder()
.setAddress(nonOptionArgs.get(1)).build();
var reply = walletsService.getAddressBalance(request);
out.println(formatTable(singletonList(reply.getAddressBalanceInfo())));
out.println(formatAddressBalanceTbl(singletonList(reply.getAddressBalanceInfo())));
return;
}
case getfundingaddresses: {
var request = GetFundingAddressesRequest.newBuilder().build();
var reply = walletsService.getFundingAddresses(request);
out.println(formatTable(reply.getAddressBalanceInfoList()));
out.println(formatAddressBalanceTbl(reply.getAddressBalanceInfoList()));
return;
}
case createpaymentacct: {
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no account name specified");

var accountName = nonOptionArgs.get(1);

case getoffers: {
if (nonOptionArgs.size() < 3)
throw new IllegalArgumentException("no account number specified");
throw new IllegalArgumentException("incorrect parameter count, expecting direction (buy|sell), currency code");

var accountNumber = nonOptionArgs.get(2);
var direction = nonOptionArgs.get(1);
var fiatCurrency = nonOptionArgs.get(2);

var request = GetOffersRequest.newBuilder()
.setDirection(direction)
.setFiatCurrencyCode(fiatCurrency)
.build();
var reply = offersService.getOffers(request);
out.println(formatOfferTable(reply.getOffersList(), fiatCurrency));
return;
}
case createpaymentacct: {
if (nonOptionArgs.size() < 4)
throw new IllegalArgumentException("no fiat currency specified");
throw new IllegalArgumentException(
"incorrect parameter count, expecting account name, account number, currency code");

var fiatCurrencyCode = nonOptionArgs.get(3).toUpperCase();
var accountName = nonOptionArgs.get(1);
var accountNumber = nonOptionArgs.get(2);
var fiatCurrencyCode = nonOptionArgs.get(3);

var request = CreatePaymentAccountRequest.newBuilder()
.setAccountName(accountName)
Expand All @@ -212,15 +215,7 @@ public static void run(String[] args) {
case getpaymentaccts: {
var request = GetPaymentAccountsRequest.newBuilder().build();
var reply = paymentAccountsService.getPaymentAccounts(request);
var columnFormatSpec = "%-41s %-25s %-14s %s";
out.println(format(columnFormatSpec, "ID", "Name", "Currency", "Payment Method"));
out.println(reply.getPaymentAccountsList().stream()
.map(a -> format(columnFormatSpec,
a.getId(),
a.getAccountName(),
a.getSelectedTradeCurrency().getCode(),
a.getPaymentMethod().getId()))
.collect(Collectors.joining("\n")));
out.println(formatPaymentAcctTbl(reply.getPaymentAccountsList()));
return;
}
case lockwallet: {
Expand Down Expand Up @@ -295,6 +290,7 @@ private static void printHelp(OptionParser parser, PrintStream stream) {
stream.format("%-22s%-50s%s%n", "getbalance", "", "Get server wallet balance");
stream.format("%-22s%-50s%s%n", "getaddressbalance", "address", "Get server wallet address balance");
stream.format("%-22s%-50s%s%n", "getfundingaddresses", "", "Get BTC funding addresses");
stream.format("%-22s%-50s%s%n", "getoffers", "buy | sell, fiat currency code", "Get current offers");
stream.format("%-22s%-50s%s%n", "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account");
stream.format("%-22s%-50s%s%n", "getpaymentaccts", "", "Get user payment accounts");
stream.format("%-22s%-50s%s%n", "lockwallet", "", "Remove wallet password from memory, locking the wallet");
Expand All @@ -307,14 +303,4 @@ private static void printHelp(OptionParser parser, PrintStream stream) {
ex.printStackTrace(stream);
}
}

private static String formatTable(List<AddressBalanceInfo> addressBalanceInfo) {
return format("%-35s %13s %s%n", "Address", "Balance", "Confirmations")
+ addressBalanceInfo.stream()
.map(info -> format("%-35s %13s %14d",
info.getAddress(),
formatSatoshis.apply(info.getBalance()),
info.getNumConfirmations()))
.collect(Collectors.joining("\n"));
}
}
47 changes: 47 additions & 0 deletions cli/src/main/java/bisq/cli/CurrencyFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package bisq.cli;

import java.text.DecimalFormat;
import java.text.NumberFormat;

import java.math.BigDecimal;
import java.math.RoundingMode;

import java.util.Locale;

class CurrencyFormat {

private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);

static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100000000);
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");

@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
static final String formatSatoshis(long sats) {
return BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
}

static String formatAmountRange(long minAmount, long amount) {
return minAmount != amount
? formatSatoshis(minAmount) + " - " + formatSatoshis(amount)
: formatSatoshis(amount);
}

static String formatVolumeRange(long minVolume, long volume) {
return minVolume != volume
? formatOfferVolume(minVolume) + " - " + formatOfferVolume(volume)
: formatOfferVolume(volume);
}

static String formatOfferPrice(long price) {
NUMBER_FORMAT.setMaximumFractionDigits(4);
NUMBER_FORMAT.setMinimumFractionDigits(4);
NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
return NUMBER_FORMAT.format((double) price / 10000);
}

static String formatOfferVolume(long volume) {
NUMBER_FORMAT.setMaximumFractionDigits(0);
NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
return NUMBER_FORMAT.format((double) volume / 10000);
}
}
17 changes: 17 additions & 0 deletions cli/src/main/java/bisq/cli/PasswordCallCredentials.java
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
/*
* 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 io.grpc.CallCredentials;
Expand Down
144 changes: 144 additions & 0 deletions cli/src/main/java/bisq/cli/TableFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package bisq.cli;

import bisq.proto.grpc.AddressBalanceInfo;
import bisq.proto.grpc.OfferInfo;

import protobuf.PaymentAccount;

import java.text.SimpleDateFormat;

import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.stream.Collectors;

import static bisq.cli.CurrencyFormat.formatAmountRange;
import static bisq.cli.CurrencyFormat.formatOfferPrice;
import static bisq.cli.CurrencyFormat.formatSatoshis;
import static bisq.cli.CurrencyFormat.formatVolumeRange;
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;
import static java.util.TimeZone.getTimeZone;

class TableFormat {

private static final TimeZone TZ_UTC = getTimeZone("UTC");
private static final SimpleDateFormat DATE_FORMAT_ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

// For inserting 2 spaces between column headers.
private static final String COL_HEADER_DELIMITER = " ";

// Table column header format specs, right padded with two spaces. In some cases
// such as COL_HEADER_CREATION_DATE, COL_HEADER_VOLUME and COL_HEADER_UUID, the
// expected max data string length is accounted for. In others, the column header length
// are expected to be greater than any column value length.
private static final String COL_HEADER_ADDRESS = padEnd("Address", 34, ' ');
private static final String COL_HEADER_AMOUNT = padEnd("BTC(min - max)", 24, ' ');
private static final String COL_HEADER_BALANCE = padStart("Balance", 12, ' ');
private static final String COL_HEADER_CONFIRMATIONS = "Confirmations";
private static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date (UTC)", 20, ' ');
private static final String COL_HEADER_CURRENCY = "Currency";
private static final String COL_HEADER_DIRECTION = "Buy/Sell"; // TODO "Take Offer to
private static final String COL_HEADER_NAME = "Name";
private static final String COL_HEADER_PAYMENT_METHOD = "Payment Method";
private static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC";
private static final String COL_HEADER_VOLUME = padEnd("%-3s(min - max)", 15, ' ');
private static final String COL_HEADER_UUID = padEnd("ID", 52, ' ');

static String formatAddressBalanceTbl(List<AddressBalanceInfo> addressBalanceInfo) {
String headerLine = (COL_HEADER_ADDRESS + COL_HEADER_DELIMITER
+ COL_HEADER_BALANCE + COL_HEADER_DELIMITER
+ COL_HEADER_CONFIRMATIONS + COL_HEADER_DELIMITER + "\n");
String colDataFormat = "%-" + COL_HEADER_ADDRESS.length() + "s" // left justify
+ " %" + COL_HEADER_BALANCE.length() + "s" // right justify
+ " %" + COL_HEADER_CONFIRMATIONS.length() + "d"; // right justify
return headerLine
+ addressBalanceInfo.stream()
.map(info -> format(colDataFormat,
info.getAddress(),
formatSatoshis(info.getBalance()),
info.getNumConfirmations()))
.collect(Collectors.joining("\n"));
}

static String formatOfferTable(List<OfferInfo> offerInfo, String fiatCurrency) {

// Some column values might be longer than header, so we need to calculated 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, fiatCurrency);

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

static String formatPaymentAcctTbl(List<PaymentAccount> paymentAccounts) {
// Some column values might be longer than header, so we need to calculated them.
int nameColWidth = getLengthOfLongestColumn(
COL_HEADER_NAME.length(),
paymentAccounts.stream().map(PaymentAccount::getAccountName)
.collect(Collectors.toList()));
int paymentMethodColWidth = getLengthOfLongestColumn(
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" // right justify
+ " %-" + paymentMethodColWidth + "s" // left justify
+ " %-" + COL_HEADER_UUID.length() + "s"; // left justify
return headerLine
+ paymentAccounts.stream()
.map(a -> format(colDataFormat,
a.getAccountName(),
a.getSelectedTradeCurrency().getCode(),
a.getPaymentMethod().getId(),
a.getId()))
.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) {
int longest = max(strings, comparing(String::length)).length();
return Math.max(longest, headerLength);
}

private static String formatTimestamp(long timestamp) {
DATE_FORMAT_ISO_8601.setTimeZone(TZ_UTC);
return DATE_FORMAT_ISO_8601.format(new Date(timestamp));
}
}
Loading

0 comments on commit a54eeea

Please sign in to comment.