Merge remote-tracking branch 'origin/master' into HEAD

This commit is contained in:
sqrrm 2020-07-03 16:58:13 +02:00
commit fabd592938
No known key found for this signature in database
GPG Key ID: 45235F9EF87089EC
98 changed files with 2242 additions and 466 deletions

View File

@ -1,8 +1,6 @@
# This doc specifies who gets requested to review GitHub pull requests. # This doc specifies who gets requested to review GitHub pull requests.
# See https://help.github.com/articles/about-codeowners/. # See https://help.github.com/articles/about-codeowners/.
*gradle* @cbeams
/pricenode/ @cbeams
/core/main/java/bisq/core/dao/ @ManfredKarrer /core/main/java/bisq/core/dao/ @ManfredKarrer
# For seednode configuration changes # For seednode configuration changes

View File

@ -97,4 +97,5 @@ See the issues in the [bisq-network/style](https://github.com/bisq-network/style
- [contributor checklist](https://docs.bisq.network/contributor-checklist.html) - [contributor checklist](https://docs.bisq.network/contributor-checklist.html)
- [developer docs](docs#readme) including build and dev environment setup instructions - [developer docs](docs#readme) including build and dev environment setup instructions
- [project management process](https://bisq.wiki/Project_management)

View File

@ -378,7 +378,7 @@ configure(project(':desktop')) {
apply plugin: 'witness' apply plugin: 'witness'
apply from: '../gradle/witness/gradle-witness.gradle' apply from: '../gradle/witness/gradle-witness.gradle'
version = '1.3.4-SNAPSHOT' version = '1.3.5-SNAPSHOT'
mainClassName = 'bisq.desktop.app.BisqAppMain' mainClassName = 'bisq.desktop.app.BisqAppMain'

View File

@ -17,14 +17,21 @@
package bisq.cli; package bisq.cli;
import bisq.proto.grpc.CreatePaymentAccountRequest;
import bisq.proto.grpc.GetAddressBalanceRequest;
import bisq.proto.grpc.GetBalanceRequest; 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.GetVersionGrpc;
import bisq.proto.grpc.GetVersionRequest; import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.LockWalletRequest; import bisq.proto.grpc.LockWalletRequest;
import bisq.proto.grpc.OffersGrpc;
import bisq.proto.grpc.PaymentAccountsGrpc;
import bisq.proto.grpc.RemoveWalletPasswordRequest; import bisq.proto.grpc.RemoveWalletPasswordRequest;
import bisq.proto.grpc.SetWalletPasswordRequest; import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.UnlockWalletRequest; import bisq.proto.grpc.UnlockWalletRequest;
import bisq.proto.grpc.WalletGrpc; import bisq.proto.grpc.WalletsGrpc;
import io.grpc.ManagedChannelBuilder; import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException; import io.grpc.StatusRuntimeException;
@ -32,32 +39,39 @@ import io.grpc.StatusRuntimeException;
import joptsimple.OptionParser; import joptsimple.OptionParser;
import joptsimple.OptionSet; import joptsimple.OptionSet;
import java.text.DecimalFormat;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j; 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.String.format;
import static java.lang.System.err; import static java.lang.System.err;
import static java.lang.System.exit; import static java.lang.System.exit;
import static java.lang.System.out; import static java.lang.System.out;
import static java.util.Collections.singletonList;
/** /**
* A command-line client for the Bisq gRPC API. * A command-line client for the Bisq gRPC API.
*/ */
@SuppressWarnings("ResultOfMethodCallIgnored")
@Slf4j @Slf4j
public class CliMain { public class CliMain {
private enum Method { private enum Method {
getoffers,
createpaymentacct,
getpaymentaccts,
getversion, getversion,
getbalance, getbalance,
getaddressbalance,
getfundingaddresses,
lockwallet, lockwallet,
unlockwallet, unlockwallet,
removewalletpassword, removewalletpassword,
@ -131,7 +145,9 @@ public class CliMain {
})); }));
var versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials); var versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
var walletService = WalletGrpc.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);
try { try {
switch (method) { switch (method) {
@ -143,18 +159,68 @@ public class CliMain {
} }
case getbalance: { case getbalance: {
var request = GetBalanceRequest.newBuilder().build(); var request = GetBalanceRequest.newBuilder().build();
var reply = walletService.getBalance(request); var reply = walletsService.getBalance(request);
var satoshiBalance = reply.getBalance(); var btcBalance = formatSatoshis(reply.getBalance());
var satoshiDivisor = new BigDecimal(100000000);
var btcFormat = new DecimalFormat("###,##0.00000000");
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
var btcBalance = btcFormat.format(BigDecimal.valueOf(satoshiBalance).divide(satoshiDivisor));
out.println(btcBalance); out.println(btcBalance);
return; return;
} }
case getaddressbalance: {
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no address specified");
var request = GetAddressBalanceRequest.newBuilder()
.setAddress(nonOptionArgs.get(1)).build();
var reply = walletsService.getAddressBalance(request);
out.println(formatAddressBalanceTbl(singletonList(reply.getAddressBalanceInfo())));
return;
}
case getfundingaddresses: {
var request = GetFundingAddressesRequest.newBuilder().build();
var reply = walletsService.getFundingAddresses(request);
out.println(formatAddressBalanceTbl(reply.getAddressBalanceInfoList()));
return;
}
case getoffers: {
if (nonOptionArgs.size() < 3)
throw new IllegalArgumentException("incorrect parameter count, expecting direction (buy|sell), currency code");
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(
"incorrect parameter count, expecting account name, account number, currency code");
var accountName = nonOptionArgs.get(1);
var accountNumber = nonOptionArgs.get(2);
var fiatCurrencyCode = nonOptionArgs.get(3);
var request = CreatePaymentAccountRequest.newBuilder()
.setAccountName(accountName)
.setAccountNumber(accountNumber)
.setFiatCurrencyCode(fiatCurrencyCode).build();
paymentAccountsService.createPaymentAccount(request);
out.println(format("payment account %s saved", accountName));
return;
}
case getpaymentaccts: {
var request = GetPaymentAccountsRequest.newBuilder().build();
var reply = paymentAccountsService.getPaymentAccounts(request);
out.println(formatPaymentAcctTbl(reply.getPaymentAccountsList()));
return;
}
case lockwallet: { case lockwallet: {
var request = LockWalletRequest.newBuilder().build(); var request = LockWalletRequest.newBuilder().build();
walletService.lockWallet(request); walletsService.lockWallet(request);
out.println("wallet locked"); out.println("wallet locked");
return; return;
} }
@ -174,7 +240,7 @@ public class CliMain {
var request = UnlockWalletRequest.newBuilder() var request = UnlockWalletRequest.newBuilder()
.setPassword(nonOptionArgs.get(1)) .setPassword(nonOptionArgs.get(1))
.setTimeout(timeout).build(); .setTimeout(timeout).build();
walletService.unlockWallet(request); walletsService.unlockWallet(request);
out.println("wallet unlocked"); out.println("wallet unlocked");
return; return;
} }
@ -183,7 +249,7 @@ public class CliMain {
throw new IllegalArgumentException("no password specified"); throw new IllegalArgumentException("no password specified");
var request = RemoveWalletPasswordRequest.newBuilder().setPassword(nonOptionArgs.get(1)).build(); var request = RemoveWalletPasswordRequest.newBuilder().setPassword(nonOptionArgs.get(1)).build();
walletService.removeWalletPassword(request); walletsService.removeWalletPassword(request);
out.println("wallet decrypted"); out.println("wallet decrypted");
return; return;
} }
@ -195,13 +261,13 @@ public class CliMain {
var hasNewPassword = nonOptionArgs.size() == 3; var hasNewPassword = nonOptionArgs.size() == 3;
if (hasNewPassword) if (hasNewPassword)
requestBuilder.setNewPassword(nonOptionArgs.get(2)); requestBuilder.setNewPassword(nonOptionArgs.get(2));
walletService.setWalletPassword(requestBuilder.build()); walletsService.setWalletPassword(requestBuilder.build());
out.println("wallet encrypted" + (hasNewPassword ? " with new password" : "")); out.println("wallet encrypted" + (hasNewPassword ? " with new password" : ""));
return; return;
} }
default: { default: {
throw new RuntimeException(format("unhandled method '%s'", method)); throw new RuntimeException(format("unhandled method '%s'", method));
} }
} }
} catch (StatusRuntimeException ex) { } catch (StatusRuntimeException ex) {
// Remove the leading gRPC status code (e.g. "UNKNOWN: ") from the message // Remove the leading gRPC status code (e.g. "UNKNOWN: ") from the message
@ -218,14 +284,19 @@ public class CliMain {
stream.println(); stream.println();
parser.printHelpOn(stream); parser.printHelpOn(stream);
stream.println(); stream.println();
stream.format("%-19s%-30s%s%n", "Method", "Params", "Description"); stream.format("%-22s%-50s%s%n", "Method", "Params", "Description");
stream.format("%-19s%-30s%s%n", "------", "------", "------------"); stream.format("%-22s%-50s%s%n", "------", "------", "------------");
stream.format("%-19s%-30s%s%n", "getversion", "", "Get server version"); stream.format("%-22s%-50s%s%n", "getversion", "", "Get server version");
stream.format("%-19s%-30s%s%n", "getbalance", "", "Get server wallet balance"); stream.format("%-22s%-50s%s%n", "getbalance", "", "Get server wallet balance");
stream.format("%-19s%-30s%s%n", "lockwallet", "", "Remove wallet password from memory, locking the wallet"); stream.format("%-22s%-50s%s%n", "getaddressbalance", "address", "Get server wallet address balance");
stream.format("%-19s%-30s%s%n", "unlockwallet", "password timeout", 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");
stream.format("%-22s%-50s%s%n", "unlockwallet", "password timeout",
"Store wallet password in memory for timeout seconds"); "Store wallet password in memory for timeout seconds");
stream.format("%-19s%-30s%s%n", "setwalletpassword", "password [newpassword]", stream.format("%-22s%-50s%s%n", "setwalletpassword", "password [newpassword]",
"Encrypt wallet with password, or set new password on encrypted wallet"); "Encrypt wallet with password, or set new password on encrypted wallet");
stream.println(); stream.println();
} catch (IOException ex) { } catch (IOException ex) {

View File

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

View File

@ -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; package bisq.cli;
import io.grpc.CallCredentials; import io.grpc.CallCredentials;

View File

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

View File

@ -0,0 +1,39 @@
package bisq.cli;
import static java.lang.System.out;
/**
Smoke tests for getoffers method. Useful for examining the format of the console output.
Prerequisites:
- Run `./bisq-daemon --apiPassword=xyz --appDataDir=$TESTDIR`
This can be run on mainnet.
*/
public class GetOffersSmokeTest {
public static void main(String[] args) {
out.println(">>> getoffers buy usd");
CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "usd"});
out.println(">>> getoffers sell usd");
CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "usd"});
out.println(">>> getoffers buy eur");
CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "eur"});
out.println(">>> getoffers sell eur");
CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "eur"});
out.println(">>> getoffers buy gbp");
CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "gbp"});
out.println(">>> getoffers sell gbp");
CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "gbp"});
out.println(">>> getoffers buy brl");
CliMain.main(new String[]{"--password=xyz", "getoffers", "buy", "brl"});
out.println(">>> getoffers sell brl");
CliMain.main(new String[]{"--password=xyz", "getoffers", "sell", "brl"});
}
}

View File

@ -48,28 +48,168 @@
run ./bisq-cli --password="xyz" getversion run ./bisq-cli --password="xyz" getversion
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
echo "actual output: $output" >&2 echo "actual output: $output" >&2
[ "$output" = "1.3.2" ] [ "$output" = "1.3.4" ]
} }
@test "test getversion" { @test "test getversion" {
run ./bisq-cli --password=xyz getversion run ./bisq-cli --password=xyz getversion
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
echo "actual output: $output" >&2 echo "actual output: $output" >&2
[ "$output" = "1.3.2" ] [ "$output" = "1.3.4" ]
} }
@test "test getbalance (available & unlocked wallet with 0 btc balance)" { @test "test setwalletpassword \"a b c\"" {
run ./bisq-cli --password=xyz setwalletpassword "a b c"
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "wallet encrypted" ]
sleep 1
}
@test "test unlockwallet without password & timeout args" {
run ./bisq-cli --password=xyz unlockwallet
[ "$status" -eq 1 ]
echo "actual output: $output" >&2
[ "$output" = "Error: no password specified" ]
}
@test "test unlockwallet without timeout arg" {
run ./bisq-cli --password=xyz unlockwallet "a b c"
[ "$status" -eq 1 ]
echo "actual output: $output" >&2
[ "$output" = "Error: no unlock timeout specified" ]
}
@test "test unlockwallet \"a b c\" 8" {
run ./bisq-cli --password=xyz unlockwallet "a b c" 8
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "wallet unlocked" ]
}
@test "test getbalance while wallet unlocked for 8s" {
run ./bisq-cli --password=xyz getbalance
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "0.00000000" ]
sleep 8
}
@test "test unlockwallet \"a b c\" 6" {
run ./bisq-cli --password=xyz unlockwallet "a b c" 6
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "wallet unlocked" ]
}
@test "test lockwallet before unlockwallet timeout=6s expires" {
run ./bisq-cli --password=xyz lockwallet
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "wallet locked" ]
}
@test "test setwalletpassword incorrect old pwd error" {
run ./bisq-cli --password=xyz setwalletpassword "z z z" "d e f"
[ "$status" -eq 1 ]
echo "actual output: $output" >&2
[ "$output" = "Error: incorrect old password" ]
}
@test "test setwalletpassword oldpwd newpwd" {
run ./bisq-cli --password=xyz setwalletpassword "a b c" "d e f"
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "wallet encrypted with new password" ]
sleep 1
}
@test "test getbalance wallet locked error" {
run ./bisq-cli --password=xyz getbalance
[ "$status" -eq 1 ]
echo "actual output: $output" >&2
[ "$output" = "Error: wallet is locked" ]
}
@test "test removewalletpassword" {
run ./bisq-cli --password=xyz removewalletpassword "d e f"
[ "$status" -eq 0 ]
echo "actual output: $output" >&2
[ "$output" = "wallet decrypted" ]
sleep 1
}
@test "test getbalance when wallet available & unlocked with 0 btc balance" {
run ./bisq-cli --password=xyz getbalance run ./bisq-cli --password=xyz getbalance
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
echo "actual output: $output" >&2 echo "actual output: $output" >&2
[ "$output" = "0.00000000" ] [ "$output" = "0.00000000" ]
} }
@test "test getfundingaddresses" {
run ./bisq-cli --password=xyz getfundingaddresses
[ "$status" -eq 0 ]
}
@test "test getaddressbalance missing address argument" {
run ./bisq-cli --password=xyz getaddressbalance
[ "$status" -eq 1 ]
echo "actual output: $output" >&2
[ "$output" = "Error: no address specified" ]
}
@test "test getaddressbalance bogus address argument" {
run ./bisq-cli --password=xyz getaddressbalance bogus
[ "$status" -eq 1 ]
echo "actual output: $output" >&2
[ "$output" = "Error: address bogus not found in wallet" ]
}
@test "test createpaymentacct PerfectMoneyDummy (missing nbr, ccy params)" {
run ./bisq-cli --password=xyz createpaymentacct PerfectMoneyDummy
[ "$status" -eq 1 ]
echo "actual output: $output" >&2
[ "$output" = "Error: incorrect parameter count, expecting account name, account number, currency code" ]
}
@test "test createpaymentacct PerfectMoneyDummy 0123456789 USD" {
run ./bisq-cli --password=xyz createpaymentacct PerfectMoneyDummy 0123456789 USD
[ "$status" -eq 0 ]
}
@test "test getpaymentaccts" {
run ./bisq-cli --password=xyz getpaymentaccts
[ "$status" -eq 0 ]
}
@test "test getoffers missing direction argument" {
run ./bisq-cli --password=xyz getoffers
[ "$status" -eq 1 ]
echo "actual output: $output" >&2
[ "$output" = "Error: incorrect parameter count, expecting direction (buy|sell), currency code" ]
}
@test "test getoffers buy eur check return status" {
run ./bisq-cli --password=xyz getoffers buy eur
[ "$status" -eq 0 ]
}
@test "test getoffers buy eur check return status" {
run ./bisq-cli --password=xyz getoffers buy eur
[ "$status" -eq 0 ]
}
@test "test getoffers sell gbp check return status" {
run ./bisq-cli --password=xyz getoffers sell gbp
[ "$status" -eq 0 ]
}
@test "test help displayed on stderr if no options or arguments" { @test "test help displayed on stderr if no options or arguments" {
run ./bisq-cli run ./bisq-cli
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
[ "${lines[0]}" = "Bisq RPC Client" ] [ "${lines[0]}" = "Bisq RPC Client" ]
[ "${lines[1]}" = "Usage: bisq-cli [options] <method>" ] [ "${lines[1]}" = "Usage: bisq-cli [options] <method> [params]" ]
# TODO add asserts after help text is modified for new endpoints # TODO add asserts after help text is modified for new endpoints
} }
@ -77,6 +217,6 @@
run ./bisq-cli --help run ./bisq-cli --help
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[ "${lines[0]}" = "Bisq RPC Client" ] [ "${lines[0]}" = "Bisq RPC Client" ]
[ "${lines[1]}" = "Usage: bisq-cli [options] <method>" ] [ "${lines[1]}" = "Usage: bisq-cli [options] <method> [params]" ]
# TODO add asserts after help text is modified for new endpoints # TODO add asserts after help text is modified for new endpoints
} }

View File

@ -27,7 +27,7 @@ public class Version {
// VERSION = 0.5.0 introduces proto buffer for the P2P network and local DB and is a not backward compatible update // VERSION = 0.5.0 introduces proto buffer for the P2P network and local DB and is a not backward compatible update
// Therefore all sub versions start again with 1 // Therefore all sub versions start again with 1
// We use semantic versioning with major, minor and patch // We use semantic versioning with major, minor and patch
public static final String VERSION = "1.3.4"; public static final String VERSION = "1.3.5";
public static int getMajorVersion(String version) { public static int getMajorVersion(String version) {
return getSubVersion(version, 0); return getSubVersion(version, 0);

View File

@ -262,6 +262,12 @@ public class SignedWitnessService {
return ""; return "";
} }
public void selfSignAccountAgeWitness(AccountAgeWitness accountAgeWitness) throws CryptoException {
log.info("Sign own accountAgeWitness {}", accountAgeWitness);
signAccountAgeWitness(MINIMUM_TRADE_AMOUNT_FOR_SIGNING, accountAgeWitness,
keyRing.getSignatureKeyPair().getPublic());
}
// Any peer can sign with DSA key // Any peer can sign with DSA key
public void signAccountAgeWitness(Coin tradeAmount, public void signAccountAgeWitness(Coin tradeAmount,
AccountAgeWitness accountAgeWitness, AccountAgeWitness accountAgeWitness,

View File

@ -185,17 +185,23 @@ public class AccountAgeWitnessService {
}); });
if (p2PService.isBootstrapped()) { if (p2PService.isBootstrapped()) {
republishAllFiatAccounts(); onBootStrapped();
} else { } else {
p2PService.addP2PServiceListener(new BootstrapListener() { p2PService.addP2PServiceListener(new BootstrapListener() {
@Override @Override
public void onUpdatedDataReceived() { public void onUpdatedDataReceived() {
republishAllFiatAccounts(); onBootStrapped();
} }
}); });
} }
} }
private void onBootStrapped() {
republishAllFiatAccounts();
signSameNameAccounts();
}
// At startup we re-publish the witness data of all fiat accounts to ensure we got our data well distributed. // At startup we re-publish the witness data of all fiat accounts to ensure we got our data well distributed.
private void republishAllFiatAccounts() { private void republishAllFiatAccounts() {
if (user.getPaymentAccounts() != null) if (user.getPaymentAccounts() != null)
@ -814,6 +820,30 @@ public class AccountAgeWitnessService {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
public void signSameNameAccounts() {
// Collect accounts that have ownerId to sign unsigned accounts with the same ownderId
var signerAccounts = Objects.requireNonNull(user.getPaymentAccounts()).stream()
.filter(account -> account.getOwnerId() != null &&
accountIsSigner(getMyWitness(account.getPaymentAccountPayload())))
.collect(Collectors.toSet());
var unsignedAccounts = user.getPaymentAccounts().stream()
.filter(account -> account.getOwnerId() != null &&
!signedWitnessService.isSignedAccountAgeWitness(
getMyWitness(account.getPaymentAccountPayload())))
.collect(Collectors.toSet());
signerAccounts.forEach(signer -> unsignedAccounts.forEach(unsigned -> {
if (signer.getOwnerId().equals(unsigned.getOwnerId())) {
try {
signedWitnessService.selfSignAccountAgeWitness(
getMyWitness(unsigned.getPaymentAccountPayload()));
} catch (CryptoException e) {
log.warn("Self signing failed, exception {}", e.toString());
}
}
}));
}
public Set<SignedWitness> getUnsignedSignerPubKeys() { public Set<SignedWitness> getUnsignedSignerPubKeys() {
return signedWitnessService.getUnsignedSignerPubKeys(); return signedWitnessService.getUnsignedSignerPubKeys();
} }

View File

@ -75,9 +75,10 @@ public class BtcNodes {
new BtcNode("btc.bisq.cc", "4nnuyxm5k5tlyjq3.onion", "167.71.168.194", BtcNode.DEFAULT_PORT, "@m52go"), new BtcNode("btc.bisq.cc", "4nnuyxm5k5tlyjq3.onion", "167.71.168.194", BtcNode.DEFAULT_PORT, "@m52go"),
// wiz // wiz
new BtcNode("node100.wiz.network", "m3yqzythryowgedc.onion", "103.99.168.100", BtcNode.DEFAULT_PORT, "@wiz"), new BtcNode("node100.hnl.wiz.biz", "m3yqzythryowgedc.onion", "103.99.168.100", BtcNode.DEFAULT_PORT, "@wiz"),
new BtcNode("node130.wiz.network", "22tg6ufbwz6o3l2u.onion", "103.99.168.130", BtcNode.DEFAULT_PORT, "@wiz"), new BtcNode("node140.hnl.wiz.biz", "jiuuuislm7ooesic.onion", "103.99.168.140", BtcNode.DEFAULT_PORT, "@wiz"),
new BtcNode("node140.wiz.network", "jiuuuislm7ooesic.onion", "103.99.168.140", BtcNode.DEFAULT_PORT, "@wiz"), new BtcNode("node210.fmt.wiz.biz", "orsy2v63ecrmdj55.onion", "103.99.170.210", BtcNode.DEFAULT_PORT, "@wiz"),
new BtcNode("node220.fmt.wiz.biz", "z6mbqq7llxlrn4kq.onion", "103.99.170.220", BtcNode.DEFAULT_PORT, "@wiz"),
// Rob Kaandorp // Rob Kaandorp
new BtcNode(null, "2pj2o2mrawj7yotg.onion", null, BtcNode.DEFAULT_PORT, "@robkaandorp") // cannot provide IP because no static IP new BtcNode(null, "2pj2o2mrawj7yotg.onion", null, BtcNode.DEFAULT_PORT, "@robkaandorp") // cannot provide IP because no static IP

View File

@ -105,6 +105,10 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
@Nullable @Nullable
private final List<String> bannedSignerPubKeys; private final List<String> bannedSignerPubKeys;
// added in v1.3.2
@Nullable
private final List<String> btcFeeReceiverAddresses;
public Filter(List<String> bannedOfferIds, public Filter(List<String> bannedOfferIds,
List<String> bannedNodeAddress, List<String> bannedNodeAddress,
List<PaymentAccountFilter> bannedPaymentAccounts, List<PaymentAccountFilter> bannedPaymentAccounts,
@ -120,7 +124,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
@Nullable String disableTradeBelowVersion, @Nullable String disableTradeBelowVersion,
@Nullable List<String> mediators, @Nullable List<String> mediators,
@Nullable List<String> refundAgents, @Nullable List<String> refundAgents,
@Nullable List<String> bannedSignerPubKeys) { @Nullable List<String> bannedSignerPubKeys,
@Nullable List<String> btcFeeReceiverAddresses) {
this.bannedOfferIds = bannedOfferIds; this.bannedOfferIds = bannedOfferIds;
this.bannedNodeAddress = bannedNodeAddress; this.bannedNodeAddress = bannedNodeAddress;
this.bannedPaymentAccounts = bannedPaymentAccounts; this.bannedPaymentAccounts = bannedPaymentAccounts;
@ -137,6 +142,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
this.mediators = mediators; this.mediators = mediators;
this.refundAgents = refundAgents; this.refundAgents = refundAgents;
this.bannedSignerPubKeys = bannedSignerPubKeys; this.bannedSignerPubKeys = bannedSignerPubKeys;
this.btcFeeReceiverAddresses = btcFeeReceiverAddresses;
} }
@ -163,7 +169,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
@Nullable Map<String, String> extraDataMap, @Nullable Map<String, String> extraDataMap,
@Nullable List<String> mediators, @Nullable List<String> mediators,
@Nullable List<String> refundAgents, @Nullable List<String> refundAgents,
@Nullable List<String> bannedSignerPubKeys) { @Nullable List<String> bannedSignerPubKeys,
@Nullable List<String> btcFeeReceiverAddresses) {
this(bannedOfferIds, this(bannedOfferIds,
bannedNodeAddress, bannedNodeAddress,
bannedPaymentAccounts, bannedPaymentAccounts,
@ -179,7 +186,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
disableTradeBelowVersion, disableTradeBelowVersion,
mediators, mediators,
refundAgents, refundAgents,
bannedSignerPubKeys); bannedSignerPubKeys,
btcFeeReceiverAddresses);
this.signatureAsBase64 = signatureAsBase64; this.signatureAsBase64 = signatureAsBase64;
this.ownerPubKeyBytes = ownerPubKeyBytes; this.ownerPubKeyBytes = ownerPubKeyBytes;
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
@ -215,6 +223,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
Optional.ofNullable(mediators).ifPresent(builder::addAllMediators); Optional.ofNullable(mediators).ifPresent(builder::addAllMediators);
Optional.ofNullable(refundAgents).ifPresent(builder::addAllRefundAgents); Optional.ofNullable(refundAgents).ifPresent(builder::addAllRefundAgents);
Optional.ofNullable(bannedSignerPubKeys).ifPresent(builder::addAllBannedSignerPubKeys); Optional.ofNullable(bannedSignerPubKeys).ifPresent(builder::addAllBannedSignerPubKeys);
Optional.ofNullable(btcFeeReceiverAddresses).ifPresent(builder::addAllBtcFeeReceiverAddresses);
return protobuf.StoragePayload.newBuilder().setFilter(builder).build(); return protobuf.StoragePayload.newBuilder().setFilter(builder).build();
} }
@ -241,7 +250,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()), CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()),
CollectionUtils.isEmpty(proto.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()), CollectionUtils.isEmpty(proto.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()),
CollectionUtils.isEmpty(proto.getBannedSignerPubKeysList()) ? CollectionUtils.isEmpty(proto.getBannedSignerPubKeysList()) ?
null : new ArrayList<>(proto.getBannedSignerPubKeysList())); null : new ArrayList<>(proto.getBannedSignerPubKeysList()),
CollectionUtils.isEmpty(proto.getBtcFeeReceiverAddressesList()) ? null :
new ArrayList<>(proto.getBtcFeeReceiverAddressesList()));
} }
@ -281,6 +292,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
",\n mediators=" + mediators + ",\n mediators=" + mediators +
",\n refundAgents=" + refundAgents + ",\n refundAgents=" + refundAgents +
",\n bannedSignerPubKeys=" + bannedSignerPubKeys + ",\n bannedSignerPubKeys=" + bannedSignerPubKeys +
",\n btcFeeReceiverAddresses=" + btcFeeReceiverAddresses +
"\n}"; "\n}";
} }
} }

View File

@ -17,7 +17,6 @@
package bisq.core.filter; package bisq.core.filter;
import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.btc.nodes.BtcNodes; import bisq.core.btc.nodes.BtcNodes;
import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.PaymentMethod;
@ -153,7 +152,7 @@ public class FilterManager {
protectedStorageEntries.forEach(protectedStorageEntry -> { protectedStorageEntries.forEach(protectedStorageEntry -> {
if (protectedStorageEntry.getProtectedStoragePayload() instanceof Filter) { if (protectedStorageEntry.getProtectedStoragePayload() instanceof Filter) {
Filter filter = (Filter) protectedStorageEntry.getProtectedStoragePayload(); Filter filter = (Filter) protectedStorageEntry.getProtectedStoragePayload();
if (verifySignature(filter)) if (verifySignature(filter) && getFilter().equals(filter))
resetFilters(); resetFilters();
} }
}); });

View File

@ -17,17 +17,14 @@
package bisq.core.grpc; package bisq.core.grpc;
import bisq.core.grpc.model.AddressBalanceInfo;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.core.offer.CreateOfferService;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferPayload;
import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccount;
import bisq.core.trade.handlers.TransactionResultHandler; import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics2;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User;
import bisq.common.app.Version; import bisq.common.app.Version;
@ -47,61 +44,70 @@ import lombok.extern.slf4j.Slf4j;
*/ */
@Slf4j @Slf4j
public class CoreApi { public class CoreApi {
private final OfferBookService offerBookService;
private final CoreOffersService coreOffersService;
private final CorePaymentAccountsService paymentAccountsService;
private final CoreWalletsService walletsService;
private final TradeStatisticsManager tradeStatisticsManager; private final TradeStatisticsManager tradeStatisticsManager;
private final CreateOfferService createOfferService;
private final OpenOfferManager openOfferManager;
private final User user;
@Inject @Inject
public CoreApi(OfferBookService offerBookService, public CoreApi(CoreOffersService coreOffersService,
TradeStatisticsManager tradeStatisticsManager, CorePaymentAccountsService paymentAccountsService,
CreateOfferService createOfferService, CoreWalletsService walletsService,
OpenOfferManager openOfferManager, TradeStatisticsManager tradeStatisticsManager) {
User user) { this.coreOffersService = coreOffersService;
this.offerBookService = offerBookService; this.paymentAccountsService = paymentAccountsService;
this.walletsService = walletsService;
this.tradeStatisticsManager = tradeStatisticsManager; this.tradeStatisticsManager = tradeStatisticsManager;
this.createOfferService = createOfferService;
this.openOfferManager = openOfferManager;
this.user = user;
} }
public String getVersion() { public String getVersion() {
return Version.VERSION; return Version.VERSION;
} }
public List<TradeStatistics2> getTradeStatistics() { ///////////////////////////////////////////////////////////////////////////////////////////
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet()); // Offers
///////////////////////////////////////////////////////////////////////////////////////////
public List<Offer> getOffers(String direction, String fiatCurrencyCode) {
return coreOffersService.getOffers(direction, fiatCurrencyCode);
} }
public List<Offer> getOffers() { public void createOffer(String currencyCode,
return offerBookService.getOffers(); String directionAsString,
long priceAsLong,
boolean useMarketBasedPrice,
double marketPriceMargin,
long amountAsLong,
long minAmountAsLong,
double buyerSecurityDeposit,
String paymentAccountId,
TransactionResultHandler resultHandler) {
coreOffersService.createOffer(currencyCode,
directionAsString,
priceAsLong,
useMarketBasedPrice,
marketPriceMargin,
amountAsLong,
minAmountAsLong,
buyerSecurityDeposit,
paymentAccountId,
resultHandler);
} }
public Set<PaymentAccount> getPaymentAccounts() { public void createOffer(String offerId,
return user.getPaymentAccounts(); String currencyCode,
} OfferPayload.Direction direction,
Price price,
public void placeOffer(String currencyCode, boolean useMarketBasedPrice,
String directionAsString, double marketPriceMargin,
long priceAsLong, Coin amount,
boolean useMarketBasedPrice, Coin minAmount,
double marketPriceMargin, double buyerSecurityDeposit,
long amountAsLong, PaymentAccount paymentAccount,
long minAmountAsLong, boolean useSavingsWallet,
double buyerSecurityDeposit, TransactionResultHandler resultHandler) {
String paymentAccountId, coreOffersService.createOffer(offerId,
TransactionResultHandler resultHandler) {
String offerId = createOfferService.getRandomOfferId();
OfferPayload.Direction direction = OfferPayload.Direction.valueOf(directionAsString);
Price price = Price.valueOf(currencyCode, priceAsLong);
Coin amount = Coin.valueOf(amountAsLong);
Coin minAmount = Coin.valueOf(minAmountAsLong);
PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId);
// We don't support atm funding from external wallet to keep it simple
boolean useSavingsWallet = true;
placeOffer(offerId,
currencyCode, currencyCode,
direction, direction,
price, price,
@ -115,34 +121,59 @@ public class CoreApi {
resultHandler); resultHandler);
} }
public void placeOffer(String offerId, ///////////////////////////////////////////////////////////////////////////////////////////
String currencyCode, // PaymentAccounts
OfferPayload.Direction direction, ///////////////////////////////////////////////////////////////////////////////////////////
Price price,
boolean useMarketBasedPrice,
double marketPriceMargin,
Coin amount,
Coin minAmount,
double buyerSecurityDeposit,
PaymentAccount paymentAccount,
boolean useSavingsWallet,
TransactionResultHandler resultHandler) {
Offer offer = createOfferService.createAndGetOffer(offerId,
direction,
currencyCode,
amount,
minAmount,
price,
useMarketBasedPrice,
marketPriceMargin,
buyerSecurityDeposit,
paymentAccount);
openOfferManager.placeOffer(offer, public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) {
buyerSecurityDeposit, paymentAccountsService.createPaymentAccount(accountName, accountNumber, fiatCurrencyCode);
useSavingsWallet,
resultHandler,
log::error);
} }
public Set<PaymentAccount> getPaymentAccounts() {
return paymentAccountsService.getPaymentAccounts();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Wallets
///////////////////////////////////////////////////////////////////////////////////////////
public long getAvailableBalance() {
return walletsService.getAvailableBalance();
}
public long getAddressBalance(String addressString) {
return walletsService.getAddressBalance(addressString);
}
public AddressBalanceInfo getAddressBalanceInfo(String addressString) {
return walletsService.getAddressBalanceInfo(addressString);
}
public List<AddressBalanceInfo> getFundingAddresses() {
return walletsService.getFundingAddresses();
}
public void setWalletPassword(String password, String newPassword) {
walletsService.setWalletPassword(password, newPassword);
}
public void lockWallet() {
walletsService.lockWallet();
}
public void unlockWallet(String password, long timeout) {
walletsService.unlockWallet(password, timeout);
}
public void removeWalletPassword(String password) {
walletsService.removeWalletPassword(password);
}
public List<TradeStatistics2> getTradeStatistics() {
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
}
public int getNumConfirmationsForMostRecentTransaction(String addressString) {
return walletsService.getNumConfirmationsForMostRecentTransaction(addressString);
}
} }

View File

@ -0,0 +1,145 @@
/*
* 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.grpc;
import bisq.core.monetary.Price;
import bisq.core.offer.CreateOfferService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.PaymentAccount;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.user.User;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import static bisq.core.offer.OfferPayload.Direction.BUY;
@Slf4j
public class CoreOffersService {
private final CreateOfferService createOfferService;
private final OfferBookService offerBookService;
private final OpenOfferManager openOfferManager;
private final User user;
@Inject
public CoreOffersService(CreateOfferService createOfferService,
OfferBookService offerBookService,
OpenOfferManager openOfferManager,
User user) {
this.createOfferService = createOfferService;
this.offerBookService = offerBookService;
this.openOfferManager = openOfferManager;
this.user = user;
}
public List<Offer> getOffers(String direction, String fiatCurrencyCode) {
List<Offer> offers = offerBookService.getOffers().stream()
.filter(o -> {
var offerOfWantedDirection = o.getDirection().name().equalsIgnoreCase(direction);
var offerInWantedCurrency = o.getOfferPayload().getCounterCurrencyCode().equalsIgnoreCase(fiatCurrencyCode);
return offerOfWantedDirection && offerInWantedCurrency;
})
.collect(Collectors.toList());
// A buyer probably wants to see sell orders in price ascending order.
// A seller probably wants to see buy orders in price descending order.
if (direction.equalsIgnoreCase(BUY.name()))
offers.sort(Comparator.comparing(Offer::getPrice).reversed());
else
offers.sort(Comparator.comparing(Offer::getPrice));
return offers;
}
public void createOffer(String currencyCode,
String directionAsString,
long priceAsLong,
boolean useMarketBasedPrice,
double marketPriceMargin,
long amountAsLong,
long minAmountAsLong,
double buyerSecurityDeposit,
String paymentAccountId,
TransactionResultHandler resultHandler) {
String offerId = createOfferService.getRandomOfferId();
OfferPayload.Direction direction = OfferPayload.Direction.valueOf(directionAsString);
Price price = Price.valueOf(currencyCode, priceAsLong);
Coin amount = Coin.valueOf(amountAsLong);
Coin minAmount = Coin.valueOf(minAmountAsLong);
PaymentAccount paymentAccount = user.getPaymentAccount(paymentAccountId);
// We don't support atm funding from external wallet to keep it simple
boolean useSavingsWallet = true;
//noinspection ConstantConditions
createOffer(offerId,
currencyCode,
direction,
price,
useMarketBasedPrice,
marketPriceMargin,
amount,
minAmount,
buyerSecurityDeposit,
paymentAccount,
useSavingsWallet,
resultHandler);
}
public void createOffer(String offerId,
String currencyCode,
OfferPayload.Direction direction,
Price price,
boolean useMarketBasedPrice,
double marketPriceMargin,
Coin amount,
Coin minAmount,
double buyerSecurityDeposit,
PaymentAccount paymentAccount,
boolean useSavingsWallet,
TransactionResultHandler resultHandler) {
Coin useDefaultTxFee = Coin.ZERO;
Offer offer = createOfferService.createAndGetOffer(offerId,
direction,
currencyCode,
amount,
minAmount,
price,
useDefaultTxFee,
useMarketBasedPrice,
marketPriceMargin,
buyerSecurityDeposit,
paymentAccount);
openOfferManager.placeOffer(offer,
buyerSecurityDeposit,
useSavingsWallet,
resultHandler,
log::error);
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.grpc;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.locale.FiatCurrency;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountFactory;
import bisq.core.payment.PerfectMoneyAccount;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.user.User;
import bisq.common.config.Config;
import javax.inject.Inject;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CorePaymentAccountsService {
private final Config config;
private final AccountAgeWitnessService accountAgeWitnessService;
private final User user;
@Inject
public CorePaymentAccountsService(Config config,
AccountAgeWitnessService accountAgeWitnessService,
User user) {
this.config = config;
this.accountAgeWitnessService = accountAgeWitnessService;
this.user = user;
}
public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) {
// Create and persist a PerfectMoney dummy payment account. There is no guard
// against creating accounts with duplicate names & numbers, only the uuid and
// creation date are unique.
PaymentMethod dummyPaymentMethod = PaymentMethod.getDummyPaymentMethod(PaymentMethod.PERFECT_MONEY_ID);
PaymentAccount paymentAccount = PaymentAccountFactory.getPaymentAccount(dummyPaymentMethod);
paymentAccount.init();
paymentAccount.setAccountName(accountName);
((PerfectMoneyAccount) paymentAccount).setAccountNr(accountNumber);
paymentAccount.setSingleTradeCurrency(new FiatCurrency(fiatCurrencyCode.toUpperCase()));
user.addPaymentAccount(paymentAccount);
// Don't do this on mainnet until thoroughly tested.
if (config.baseCurrencyNetwork.isRegtest())
accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload());
log.info("Payment account {} saved", paymentAccount.getId());
}
public Set<PaymentAccount> getPaymentAccounts() {
return user.getPaymentAccounts();
}
}

View File

@ -1,28 +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.core.grpc; package bisq.core.grpc;
import bisq.core.btc.Balances; import bisq.core.btc.Balances;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletsManager; import bisq.core.btc.wallet.WalletsManager;
import bisq.core.grpc.model.AddressBalanceInfo;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.crypto.KeyCrypterScrypt;
import javax.inject.Inject; import javax.inject.Inject;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.crypto.params.KeyParameter;
import java.util.List;
import java.util.Optional;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
@Slf4j @Slf4j
class CoreWalletService { class CoreWalletsService {
private final Balances balances; private final Balances balances;
private final WalletsManager walletsManager; private final WalletsManager walletsManager;
private final BtcWalletService btcWalletService;
@Nullable @Nullable
private TimerTask lockTask; private TimerTask lockTask;
@ -31,17 +63,17 @@ class CoreWalletService {
private KeyParameter tempAesKey; private KeyParameter tempAesKey;
@Inject @Inject
public CoreWalletService(Balances balances, WalletsManager walletsManager) { public CoreWalletsService(Balances balances,
WalletsManager walletsManager,
BtcWalletService btcWalletService) {
this.balances = balances; this.balances = balances;
this.walletsManager = walletsManager; this.walletsManager = walletsManager;
this.btcWalletService = btcWalletService;
} }
public long getAvailableBalance() { public long getAvailableBalance() {
if (!walletsManager.areWalletsAvailable()) verifyWalletsAreAvailable();
throw new IllegalStateException("wallet is not yet available"); verifyEncryptedWalletIsUnlocked();
if (walletsManager.areWalletsEncrypted() && tempAesKey == null)
throw new IllegalStateException("wallet is locked");
var balance = balances.getAvailableBalance().get(); var balance = balances.getAvailableBalance().get();
if (balance == null) if (balance == null)
@ -50,9 +82,61 @@ class CoreWalletService {
return balance.getValue(); return balance.getValue();
} }
public long getAddressBalance(String addressString) {
Address address = getAddressEntry(addressString).getAddress();
return btcWalletService.getBalanceForAddress(address).value;
}
public AddressBalanceInfo getAddressBalanceInfo(String addressString) {
var satoshiBalance = getAddressBalance(addressString);
var numConfirmations = getNumConfirmationsForMostRecentTransaction(addressString);
return new AddressBalanceInfo(addressString, satoshiBalance, numConfirmations);
}
public List<AddressBalanceInfo> getFundingAddresses() {
verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
// Create a new funding address if none exists.
if (btcWalletService.getAvailableAddressEntries().size() == 0)
btcWalletService.getFreshAddressEntry();
List<String> addressStrings =
btcWalletService
.getAvailableAddressEntries()
.stream()
.map(AddressEntry::getAddressString)
.collect(Collectors.toList());
// getAddressBalance is memoized, because we'll map it over addresses twice.
// To get the balances, we'll be using .getUnchecked, because we know that
// this::getAddressBalance cannot return null.
var balances = memoize(this::getAddressBalance);
boolean noAddressHasZeroBalance =
addressStrings.stream()
.allMatch(addressString -> balances.getUnchecked(addressString) != 0);
if (noAddressHasZeroBalance) {
var newZeroBalanceAddress = btcWalletService.getFreshAddressEntry();
addressStrings.add(newZeroBalanceAddress.getAddressString());
}
return addressStrings.stream().map(address ->
new AddressBalanceInfo(address,
balances.getUnchecked(address),
getNumConfirmationsForMostRecentTransaction(address)))
.collect(Collectors.toList());
}
public int getNumConfirmationsForMostRecentTransaction(String addressString) {
Address address = getAddressEntry(addressString).getAddress();
TransactionConfidence confidence = btcWalletService.getConfidenceForAddress(address);
return confidence == null ? 0 : confidence.getDepthInBlocks();
}
public void setWalletPassword(String password, String newPassword) { public void setWalletPassword(String password, String newPassword) {
if (!walletsManager.areWalletsAvailable()) verifyWalletsAreAvailable();
throw new IllegalStateException("wallet is not yet available");
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt(); KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();
@ -141,6 +225,12 @@ class CoreWalletService {
walletsManager.backupWallets(); walletsManager.backupWallets();
} }
// Throws a RuntimeException if wallets are not available (encrypted or not).
private void verifyWalletsAreAvailable() {
if (!walletsManager.areWalletsAvailable())
throw new IllegalStateException("wallet is not yet available");
}
// Throws a RuntimeException if wallets are not available or not encrypted. // Throws a RuntimeException if wallets are not available or not encrypted.
private void verifyWalletIsAvailableAndEncrypted() { private void verifyWalletIsAvailableAndEncrypted() {
if (!walletsManager.areWalletsAvailable()) if (!walletsManager.areWalletsAvailable())
@ -150,10 +240,42 @@ class CoreWalletService {
throw new IllegalStateException("wallet is not encrypted with a password"); throw new IllegalStateException("wallet is not encrypted with a password");
} }
// Throws a RuntimeException if wallets are encrypted and locked.
private void verifyEncryptedWalletIsUnlocked() {
if (walletsManager.areWalletsEncrypted() && tempAesKey == null)
throw new IllegalStateException("wallet is locked");
}
private KeyCrypterScrypt getKeyCrypterScrypt() { private KeyCrypterScrypt getKeyCrypterScrypt() {
KeyCrypterScrypt keyCrypterScrypt = walletsManager.getKeyCrypterScrypt(); KeyCrypterScrypt keyCrypterScrypt = walletsManager.getKeyCrypterScrypt();
if (keyCrypterScrypt == null) if (keyCrypterScrypt == null)
throw new IllegalStateException("wallet encrypter is not available"); throw new IllegalStateException("wallet encrypter is not available");
return keyCrypterScrypt; return keyCrypterScrypt;
} }
private AddressEntry getAddressEntry(String addressString) {
Optional<AddressEntry> addressEntry =
btcWalletService.getAddressEntryListAsImmutableList().stream()
.filter(e -> addressString.equals(e.getAddressString()))
.findFirst();
if (!addressEntry.isPresent())
throw new IllegalStateException(format("address %s not found in wallet", addressString));
return addressEntry.get();
}
/**
* Memoization stores the results of expensive function calls and returns
* the cached result when the same input occurs again.
*
* Resulting LoadingCache is used by calling `.get(input I)` or
* `.getUnchecked(input I)`, depending on whether or not `f` can return null.
* That's because CacheLoader throws an exception on null output from `f`.
*/
private static <I, O> LoadingCache<I, O> memoize(Function<I, O> f) {
// f::apply is used, because Guava 20.0 Function doesn't yet extend
// Java Function.
return CacheBuilder.newBuilder().build(CacheLoader.from(f::apply));
}
} }

View File

@ -0,0 +1,105 @@
/*
* 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.grpc;
import bisq.core.grpc.model.OfferInfo;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.proto.grpc.CreateOfferReply;
import bisq.proto.grpc.CreateOfferRequest;
import bisq.proto.grpc.GetOffersReply;
import bisq.proto.grpc.GetOffersRequest;
import bisq.proto.grpc.OffersGrpc;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class GrpcOffersService extends OffersGrpc.OffersImplBase {
private final CoreApi coreApi;
@Inject
public GrpcOffersService(CoreApi coreApi) {
this.coreApi = coreApi;
}
@Override
public void getOffers(GetOffersRequest req,
StreamObserver<GetOffersReply> responseObserver) {
// The client cannot see bisq.core.Offer or its fromProto method.
// We use the lighter weight OfferInfo proto wrapper instead, containing just
// enough fields to view and create offers.
List<OfferInfo> result = coreApi.getOffers(req.getDirection(), req.getFiatCurrencyCode())
.stream().map(offer -> new OfferInfo.OfferInfoBuilder()
.withId(offer.getId())
.withDirection(offer.getDirection().name())
.withPrice(offer.getPrice().getValue())
.withUseMarketBasedPrice(offer.isUseMarketBasedPrice())
.withMarketPriceMargin(offer.getMarketPriceMargin())
.withAmount(offer.getAmount().value)
.withMinAmount(offer.getMinAmount().value)
.withVolume(offer.getVolume().getValue())
.withMinVolume(offer.getMinVolume().getValue())
.withBuyerSecurityDeposit(offer.getBuyerSecurityDeposit().value)
.withPaymentAccountId("") // only used when creating offer (?)
.withPaymentMethodId(offer.getPaymentMethod().getId())
.withPaymentMethodShortName(offer.getPaymentMethod().getShortName())
.withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode())
.withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode())
.withDate(offer.getDate().getTime())
.build())
.collect(Collectors.toList());
var reply = GetOffersReply.newBuilder()
.addAllOffers(
result.stream()
.map(OfferInfo::toProtoMessage)
.collect(Collectors.toList()))
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@Override
public void createOffer(CreateOfferRequest req,
StreamObserver<CreateOfferReply> responseObserver) {
TransactionResultHandler resultHandler = transaction -> {
CreateOfferReply reply = CreateOfferReply.newBuilder().setResult(true).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
};
coreApi.createOffer(
req.getCurrencyCode(),
req.getDirection(),
req.getPrice(),
req.getUseMarketBasedPrice(),
req.getMarketPriceMargin(),
req.getAmount(),
req.getMinAmount(),
req.getBuyerSecurityDeposit(),
req.getPaymentAccountId(),
resultHandler);
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.grpc;
import bisq.core.payment.PaymentAccount;
import bisq.proto.grpc.CreatePaymentAccountReply;
import bisq.proto.grpc.CreatePaymentAccountRequest;
import bisq.proto.grpc.GetPaymentAccountsReply;
import bisq.proto.grpc.GetPaymentAccountsRequest;
import bisq.proto.grpc.PaymentAccountsGrpc;
import io.grpc.stub.StreamObserver;
import javax.inject.Inject;
import java.util.stream.Collectors;
public class GrpcPaymentAccountsService extends PaymentAccountsGrpc.PaymentAccountsImplBase {
private final CoreApi coreApi;
@Inject
public GrpcPaymentAccountsService(CoreApi coreApi) {
this.coreApi = coreApi;
}
@Override
public void createPaymentAccount(CreatePaymentAccountRequest req,
StreamObserver<CreatePaymentAccountReply> responseObserver) {
coreApi.createPaymentAccount(req.getAccountName(), req.getAccountNumber(), req.getFiatCurrencyCode());
var reply = CreatePaymentAccountReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@Override
public void getPaymentAccounts(GetPaymentAccountsRequest req,
StreamObserver<GetPaymentAccountsReply> responseObserver) {
var tradeStatistics = coreApi.getPaymentAccounts().stream()
.map(PaymentAccount::toProtoMessage)
.collect(Collectors.toList());
var reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}

View File

@ -17,28 +17,16 @@
package bisq.core.grpc; package bisq.core.grpc;
import bisq.core.offer.Offer;
import bisq.core.payment.PaymentAccount;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics2;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.proto.grpc.GetOffersGrpc;
import bisq.proto.grpc.GetOffersReply;
import bisq.proto.grpc.GetOffersRequest;
import bisq.proto.grpc.GetPaymentAccountsGrpc;
import bisq.proto.grpc.GetPaymentAccountsReply;
import bisq.proto.grpc.GetPaymentAccountsRequest;
import bisq.proto.grpc.GetTradeStatisticsGrpc; import bisq.proto.grpc.GetTradeStatisticsGrpc;
import bisq.proto.grpc.GetTradeStatisticsReply; import bisq.proto.grpc.GetTradeStatisticsReply;
import bisq.proto.grpc.GetTradeStatisticsRequest; import bisq.proto.grpc.GetTradeStatisticsRequest;
import bisq.proto.grpc.GetVersionGrpc; import bisq.proto.grpc.GetVersionGrpc;
import bisq.proto.grpc.GetVersionReply; import bisq.proto.grpc.GetVersionReply;
import bisq.proto.grpc.GetVersionRequest; import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.PlaceOfferGrpc;
import bisq.proto.grpc.PlaceOfferReply;
import bisq.proto.grpc.PlaceOfferRequest;
import io.grpc.Server; import io.grpc.Server;
import io.grpc.ServerBuilder; import io.grpc.ServerBuilder;
@ -60,15 +48,18 @@ public class GrpcServer {
private final Server server; private final Server server;
@Inject @Inject
public GrpcServer(Config config, CoreApi coreApi, GrpcWalletService walletService) { public GrpcServer(Config config,
CoreApi coreApi,
GrpcOffersService offersService,
GrpcPaymentAccountsService paymentAccountsService,
GrpcWalletsService walletsService) {
this.coreApi = coreApi; this.coreApi = coreApi;
this.server = ServerBuilder.forPort(config.apiPort) this.server = ServerBuilder.forPort(config.apiPort)
.addService(new GetVersionService()) .addService(new GetVersionService())
.addService(new GetTradeStatisticsService()) .addService(new GetTradeStatisticsService())
.addService(new GetOffersService()) .addService(offersService)
.addService(new GetPaymentAccountsService()) .addService(paymentAccountsService)
.addService(new PlaceOfferService()) .addService(walletsService)
.addService(walletService)
.intercept(new PasswordAuthInterceptor(config.apiPassword)) .intercept(new PasswordAuthInterceptor(config.apiPassword))
.build(); .build();
} }
@ -95,7 +86,6 @@ public class GrpcServer {
} }
} }
class GetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase { class GetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase {
@Override @Override
public void getTradeStatistics(GetTradeStatisticsRequest req, public void getTradeStatistics(GetTradeStatisticsRequest req,
@ -110,55 +100,4 @@ public class GrpcServer {
responseObserver.onCompleted(); responseObserver.onCompleted();
} }
} }
class GetOffersService extends GetOffersGrpc.GetOffersImplBase {
@Override
public void getOffers(GetOffersRequest req, StreamObserver<GetOffersReply> responseObserver) {
var tradeStatistics = coreApi.getOffers().stream()
.map(Offer::toProtoMessage)
.collect(Collectors.toList());
var reply = GetOffersReply.newBuilder().addAllOffers(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
class GetPaymentAccountsService extends GetPaymentAccountsGrpc.GetPaymentAccountsImplBase {
@Override
public void getPaymentAccounts(GetPaymentAccountsRequest req,
StreamObserver<GetPaymentAccountsReply> responseObserver) {
var tradeStatistics = coreApi.getPaymentAccounts().stream()
.map(PaymentAccount::toProtoMessage)
.collect(Collectors.toList());
var reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
class PlaceOfferService extends PlaceOfferGrpc.PlaceOfferImplBase {
@Override
public void placeOffer(PlaceOfferRequest req, StreamObserver<PlaceOfferReply> responseObserver) {
TransactionResultHandler resultHandler = transaction -> {
PlaceOfferReply reply = PlaceOfferReply.newBuilder().setResult(true).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
};
coreApi.placeOffer(
req.getCurrencyCode(),
req.getDirection(),
req.getPrice(),
req.getUseMarketBasedPrice(),
req.getMarketPriceMargin(),
req.getAmount(),
req.getMinAmount(),
req.getBuyerSecurityDeposit(),
req.getPaymentAccountId(),
resultHandler);
}
}
} }

View File

@ -1,7 +1,30 @@
/*
* 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.grpc; package bisq.core.grpc;
import bisq.core.grpc.model.AddressBalanceInfo;
import bisq.proto.grpc.GetAddressBalanceReply;
import bisq.proto.grpc.GetAddressBalanceRequest;
import bisq.proto.grpc.GetBalanceReply; import bisq.proto.grpc.GetBalanceReply;
import bisq.proto.grpc.GetBalanceRequest; import bisq.proto.grpc.GetBalanceRequest;
import bisq.proto.grpc.GetFundingAddressesReply;
import bisq.proto.grpc.GetFundingAddressesRequest;
import bisq.proto.grpc.LockWalletReply; import bisq.proto.grpc.LockWalletReply;
import bisq.proto.grpc.LockWalletRequest; import bisq.proto.grpc.LockWalletRequest;
import bisq.proto.grpc.RemoveWalletPasswordReply; import bisq.proto.grpc.RemoveWalletPasswordReply;
@ -10,7 +33,7 @@ import bisq.proto.grpc.SetWalletPasswordReply;
import bisq.proto.grpc.SetWalletPasswordRequest; import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.UnlockWalletReply; import bisq.proto.grpc.UnlockWalletReply;
import bisq.proto.grpc.UnlockWalletRequest; import bisq.proto.grpc.UnlockWalletRequest;
import bisq.proto.grpc.WalletGrpc; import bisq.proto.grpc.WalletsGrpc;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.StatusRuntimeException; import io.grpc.StatusRuntimeException;
@ -18,19 +41,22 @@ import io.grpc.stub.StreamObserver;
import javax.inject.Inject; import javax.inject.Inject;
class GrpcWalletService extends WalletGrpc.WalletImplBase { import java.util.List;
import java.util.stream.Collectors;
private final CoreWalletService walletService; class GrpcWalletsService extends WalletsGrpc.WalletsImplBase {
private final CoreApi coreApi;
@Inject @Inject
public GrpcWalletService(CoreWalletService walletService) { public GrpcWalletsService(CoreApi coreApi) {
this.walletService = walletService; this.coreApi = coreApi;
} }
@Override @Override
public void getBalance(GetBalanceRequest req, StreamObserver<GetBalanceReply> responseObserver) { public void getBalance(GetBalanceRequest req, StreamObserver<GetBalanceReply> responseObserver) {
try { try {
long result = walletService.getAvailableBalance(); long result = coreApi.getAvailableBalance();
var reply = GetBalanceReply.newBuilder().setBalance(result).build(); var reply = GetBalanceReply.newBuilder().setBalance(result).build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
@ -41,11 +67,46 @@ class GrpcWalletService extends WalletGrpc.WalletImplBase {
} }
} }
@Override
public void getAddressBalance(GetAddressBalanceRequest req,
StreamObserver<GetAddressBalanceReply> responseObserver) {
try {
AddressBalanceInfo result = coreApi.getAddressBalanceInfo(req.getAddress());
var reply = GetAddressBalanceReply.newBuilder().setAddressBalanceInfo(result.toProtoMessage()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (IllegalStateException cause) {
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
responseObserver.onError(ex);
throw ex;
}
}
@Override
public void getFundingAddresses(GetFundingAddressesRequest req,
StreamObserver<GetFundingAddressesReply> responseObserver) {
try {
List<AddressBalanceInfo> result = coreApi.getFundingAddresses();
var reply = GetFundingAddressesReply.newBuilder()
.addAllAddressBalanceInfo(
result.stream()
.map(AddressBalanceInfo::toProtoMessage)
.collect(Collectors.toList()))
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (IllegalStateException cause) {
var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage()));
responseObserver.onError(ex);
throw ex;
}
}
@Override @Override
public void setWalletPassword(SetWalletPasswordRequest req, public void setWalletPassword(SetWalletPasswordRequest req,
StreamObserver<SetWalletPasswordReply> responseObserver) { StreamObserver<SetWalletPasswordReply> responseObserver) {
try { try {
walletService.setWalletPassword(req.getPassword(), req.getNewPassword()); coreApi.setWalletPassword(req.getPassword(), req.getNewPassword());
var reply = SetWalletPasswordReply.newBuilder().build(); var reply = SetWalletPasswordReply.newBuilder().build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
@ -60,7 +121,7 @@ class GrpcWalletService extends WalletGrpc.WalletImplBase {
public void removeWalletPassword(RemoveWalletPasswordRequest req, public void removeWalletPassword(RemoveWalletPasswordRequest req,
StreamObserver<RemoveWalletPasswordReply> responseObserver) { StreamObserver<RemoveWalletPasswordReply> responseObserver) {
try { try {
walletService.removeWalletPassword(req.getPassword()); coreApi.removeWalletPassword(req.getPassword());
var reply = RemoveWalletPasswordReply.newBuilder().build(); var reply = RemoveWalletPasswordReply.newBuilder().build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
@ -75,7 +136,7 @@ class GrpcWalletService extends WalletGrpc.WalletImplBase {
public void lockWallet(LockWalletRequest req, public void lockWallet(LockWalletRequest req,
StreamObserver<LockWalletReply> responseObserver) { StreamObserver<LockWalletReply> responseObserver) {
try { try {
walletService.lockWallet(); coreApi.lockWallet();
var reply = LockWalletReply.newBuilder().build(); var reply = LockWalletReply.newBuilder().build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
@ -90,7 +151,7 @@ class GrpcWalletService extends WalletGrpc.WalletImplBase {
public void unlockWallet(UnlockWalletRequest req, public void unlockWallet(UnlockWalletRequest req,
StreamObserver<UnlockWalletReply> responseObserver) { StreamObserver<UnlockWalletReply> responseObserver) {
try { try {
walletService.unlockWallet(req.getPassword(), req.getTimeout()); coreApi.unlockWallet(req.getPassword(), req.getTimeout());
var reply = UnlockWalletReply.newBuilder().build(); var reply = UnlockWalletReply.newBuilder().build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();

View File

@ -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.core.grpc; package bisq.core.grpc;
import io.grpc.Metadata; import io.grpc.Metadata;

View 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.core.grpc.model;
import bisq.common.Payload;
public class AddressBalanceInfo implements Payload {
private final String address;
private final long balance; // address' balance in satoshis
private final long numConfirmations; // # confirmations for address' most recent tx
public AddressBalanceInfo(String address, long balance, long numConfirmations) {
this.address = address;
this.balance = balance;
this.numConfirmations = numConfirmations;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public bisq.proto.grpc.AddressBalanceInfo toProtoMessage() {
return bisq.proto.grpc.AddressBalanceInfo.newBuilder()
.setAddress(address)
.setBalance(balance)
.setNumConfirmations(numConfirmations).build();
}
public static AddressBalanceInfo fromProto(bisq.proto.grpc.AddressBalanceInfo proto) {
return new AddressBalanceInfo(proto.getAddress(),
proto.getBalance(),
proto.getNumConfirmations());
}
@Override
public String toString() {
return "AddressBalanceInfo{" +
"address='" + address + '\'' +
", balance=" + balance +
", numConfirmations=" + numConfirmations +
'}';
}
}

View File

@ -0,0 +1,215 @@
/*
* 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.grpc.model;
import bisq.common.Payload;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
@EqualsAndHashCode
@ToString
@Getter
public class OfferInfo implements Payload {
private final String id;
private final String direction;
private final long price;
private final boolean useMarketBasedPrice;
private final double marketPriceMargin;
private final long amount;
private final long minAmount;
private final long volume;
private final long minVolume;
private final long buyerSecurityDeposit;
private final String paymentAccountId; // only used when creating offer
private final String paymentMethodId;
private final String paymentMethodShortName;
// For fiat offer the baseCurrencyCode is BTC and the counterCurrencyCode is the fiat currency
// For altcoin offers it is the opposite. baseCurrencyCode is the altcoin and the counterCurrencyCode is BTC.
private final String baseCurrencyCode;
private final String counterCurrencyCode;
private final long date;
public OfferInfo(OfferInfoBuilder builder) {
this.id = builder.id;
this.direction = builder.direction;
this.price = builder.price;
this.useMarketBasedPrice = builder.useMarketBasedPrice;
this.marketPriceMargin = builder.marketPriceMargin;
this.amount = builder.amount;
this.minAmount = builder.minAmount;
this.volume = builder.volume;
this.minVolume = builder.minVolume;
this.buyerSecurityDeposit = builder.buyerSecurityDeposit;
this.paymentAccountId = builder.paymentAccountId;
this.paymentMethodId = builder.paymentMethodId;
this.paymentMethodShortName = builder.paymentMethodShortName;
this.baseCurrencyCode = builder.baseCurrencyCode;
this.counterCurrencyCode = builder.counterCurrencyCode;
this.date = builder.date;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public bisq.proto.grpc.OfferInfo toProtoMessage() {
return bisq.proto.grpc.OfferInfo.newBuilder()
.setId(id)
.setDirection(direction)
.setPrice(price)
.setUseMarketBasedPrice(useMarketBasedPrice)
.setMarketPriceMargin(marketPriceMargin)
.setAmount(amount)
.setMinAmount(minAmount)
.setVolume(volume)
.setMinVolume(minVolume)
.setBuyerSecurityDeposit(buyerSecurityDeposit)
.setPaymentAccountId(paymentAccountId)
.setPaymentMethodId(paymentMethodId)
.setPaymentMethodShortName(paymentMethodShortName)
.setBaseCurrencyCode(baseCurrencyCode)
.setCounterCurrencyCode(counterCurrencyCode)
.setDate(date)
.build();
}
public static OfferInfo fromProto(bisq.proto.grpc.OfferInfo proto) {
/*
TODO (will be needed by the createoffer method)
return new OfferInfo(proto.getOfferPayload().getId(),
proto.getOfferPayload().getDate());
*/
return null;
}
/*
* OfferInfoBuilder helps avoid bungling use of a large OfferInfo constructor
* argument list. If consecutive argument values of the same type are not
* ordered correctly, the compiler won't complain but the resulting bugs could
* be hard to find and fix.
*/
public static class OfferInfoBuilder {
private String id;
private String direction;
private long price;
private boolean useMarketBasedPrice;
private double marketPriceMargin;
private long amount;
private long minAmount;
private long volume;
private long minVolume;
private long buyerSecurityDeposit;
private String paymentAccountId;
private String paymentMethodId;
private String paymentMethodShortName;
private String baseCurrencyCode;
private String counterCurrencyCode;
private long date;
public OfferInfoBuilder() {
}
public OfferInfoBuilder withId(String id) {
this.id = id;
return this;
}
public OfferInfoBuilder withDirection(String direction) {
this.direction = direction;
return this;
}
public OfferInfoBuilder withPrice(long price) {
this.price = price;
return this;
}
public OfferInfoBuilder withUseMarketBasedPrice(boolean useMarketBasedPrice) {
this.useMarketBasedPrice = useMarketBasedPrice;
return this;
}
public OfferInfoBuilder withMarketPriceMargin(double useMarketBasedPrice) {
this.marketPriceMargin = useMarketBasedPrice;
return this;
}
public OfferInfoBuilder withAmount(long amount) {
this.amount = amount;
return this;
}
public OfferInfoBuilder withMinAmount(long minAmount) {
this.minAmount = minAmount;
return this;
}
public OfferInfoBuilder withVolume(long volume) {
this.volume = volume;
return this;
}
public OfferInfoBuilder withMinVolume(long minVolume) {
this.minVolume = minVolume;
return this;
}
public OfferInfoBuilder withBuyerSecurityDeposit(long buyerSecurityDeposit) {
this.buyerSecurityDeposit = buyerSecurityDeposit;
return this;
}
public OfferInfoBuilder withPaymentAccountId(String paymentAccountId) {
this.paymentAccountId = paymentAccountId;
return this;
}
public OfferInfoBuilder withPaymentMethodId(String paymentMethodId) {
this.paymentMethodId = paymentMethodId;
return this;
}
public OfferInfoBuilder withPaymentMethodShortName(String paymentMethodShortName) {
this.paymentMethodShortName = paymentMethodShortName;
return this;
}
public OfferInfoBuilder withBaseCurrencyCode(String baseCurrencyCode) {
this.baseCurrencyCode = baseCurrencyCode;
return this;
}
public OfferInfoBuilder withCounterCurrencyCode(String counterCurrencyCode) {
this.counterCurrencyCode = counterCurrencyCode;
return this;
}
public OfferInfoBuilder withDate(long date) {
this.date = date;
return this;
}
public OfferInfo build() {
return new OfferInfo(this);
}
}
}

View File

@ -267,32 +267,39 @@ public class CurrencyUtil {
return currencies; return currencies;
} }
//https://www.revolut.com/pa/faq#can-i-hold-multiple-currencies // https://www.revolut.com/help/getting-started/exchanging-currencies/what-fiat-currencies-are-supported-for-holding-and-exchange
public static List<TradeCurrency> getAllRevolutCurrencies() { public static List<TradeCurrency> getAllRevolutCurrencies() {
ArrayList<TradeCurrency> currencies = new ArrayList<>(Arrays.asList( ArrayList<TradeCurrency> currencies = new ArrayList<>(Arrays.asList(
new FiatCurrency("USD"),
new FiatCurrency("GBP"),
new FiatCurrency("EUR"),
new FiatCurrency("PLN"),
new FiatCurrency("CHF"),
new FiatCurrency("DKK"),
new FiatCurrency("NOK"),
new FiatCurrency("SEK"),
new FiatCurrency("RON"),
new FiatCurrency("SGD"),
new FiatCurrency("HKD"),
new FiatCurrency("AUD"),
new FiatCurrency("NZD"),
new FiatCurrency("TRY"),
new FiatCurrency("ILS"),
new FiatCurrency("AED"), new FiatCurrency("AED"),
new FiatCurrency("AUD"),
new FiatCurrency("BGN"),
new FiatCurrency("CAD"), new FiatCurrency("CAD"),
new FiatCurrency("CHF"),
new FiatCurrency("CZK"),
new FiatCurrency("DKK"),
new FiatCurrency("EUR"),
new FiatCurrency("GBP"),
new FiatCurrency("HKD"),
new FiatCurrency("HRK"),
new FiatCurrency("HUF"), new FiatCurrency("HUF"),
new FiatCurrency("INR"), new FiatCurrency("ILS"),
new FiatCurrency("ISK"),
new FiatCurrency("JPY"), new FiatCurrency("JPY"),
new FiatCurrency("MAD"), new FiatCurrency("MAD"),
new FiatCurrency("MXN"),
new FiatCurrency("NOK"),
new FiatCurrency("NZD"),
new FiatCurrency("PLN"),
new FiatCurrency("QAR"), new FiatCurrency("QAR"),
new FiatCurrency("RON"),
new FiatCurrency("RSD"),
new FiatCurrency("RUB"),
new FiatCurrency("SAR"),
new FiatCurrency("SEK"),
new FiatCurrency("SGD"),
new FiatCurrency("THB"), new FiatCurrency("THB"),
new FiatCurrency("TRY"),
new FiatCurrency("USD"),
new FiatCurrency("ZAR") new FiatCurrency("ZAR")
)); ));

View File

@ -124,6 +124,7 @@ public class CreateOfferService {
Coin amount, Coin amount,
Coin minAmount, Coin minAmount,
Price price, Price price,
Coin txFee,
boolean useMarketBasedPrice, boolean useMarketBasedPrice,
double marketPriceMargin, double marketPriceMargin,
double buyerSecurityDepositAsDouble, double buyerSecurityDepositAsDouble,
@ -183,6 +184,7 @@ public class CreateOfferService {
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount); List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(); double sellerSecurityDeposit = getSellerSecurityDepositAsDouble();
Coin txFeeFromFeeService = getEstimatedFeeAndTxSize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first; Coin txFeeFromFeeService = getEstimatedFeeAndTxSize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first;
Coin txFeeToUse = txFee.isPositive() ? txFee : txFeeFromFeeService;
Coin makerFeeAsCoin = getMakerFee(amount); Coin makerFeeAsCoin = getMakerFee(amount);
boolean isCurrencyForMakerFeeBtc = OfferUtil.isCurrencyForMakerFeeBtc(preferences, bsqWalletService, amount); boolean isCurrencyForMakerFeeBtc = OfferUtil.isCurrencyForMakerFeeBtc(preferences, bsqWalletService, amount);
Coin buyerSecurityDepositAsCoin = getBuyerSecurityDeposit(amount, buyerSecurityDepositAsDouble); Coin buyerSecurityDepositAsCoin = getBuyerSecurityDeposit(amount, buyerSecurityDepositAsDouble);
@ -233,7 +235,7 @@ public class CreateOfferService {
acceptedBanks, acceptedBanks,
Version.VERSION, Version.VERSION,
btcWalletService.getLastBlockSeenHeight(), btcWalletService.getLastBlockSeenHeight(),
txFeeFromFeeService.value, txFeeToUse.value,
makerFeeAsCoin.value, makerFeeAsCoin.value,
isCurrencyForMakerFeeBtc, isCurrencyForMakerFeeBtc,
buyerSecurityDepositAsCoin.value, buyerSecurityDepositAsCoin.value,

View File

@ -365,6 +365,14 @@ public class Offer implements NetworkPayload, PersistablePayload {
return ""; return "";
} }
public String getPaymentMethodNameWithCountryCode() {
String method = this.getPaymentMethod().getShortName();
String methodCountryCode = this.getCountryCode();
if (methodCountryCode != null)
method = method + " (" + methodCountryCode + ")";
return method;
}
// domain properties // domain properties
public Offer.State getState() { public Offer.State getState() {
return stateProperty.get(); return stateProperty.get();

View File

@ -23,6 +23,7 @@ import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.dao.DaoFacade; import bisq.core.dao.DaoFacade;
import bisq.core.exceptions.TradePriceOutOfToleranceException; import bisq.core.exceptions.TradePriceOutOfToleranceException;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.filter.FilterManager;
import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.offer.messages.OfferAvailabilityRequest; import bisq.core.offer.messages.OfferAvailabilityRequest;
import bisq.core.offer.messages.OfferAvailabilityResponse; import bisq.core.offer.messages.OfferAvailabilityResponse;
@ -111,6 +112,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final MediatorManager mediatorManager; private final MediatorManager mediatorManager;
private final RefundAgentManager refundAgentManager; private final RefundAgentManager refundAgentManager;
private final DaoFacade daoFacade; private final DaoFacade daoFacade;
private final FilterManager filterManager;
private final Storage<TradableList<OpenOffer>> openOfferTradableListStorage; private final Storage<TradableList<OpenOffer>> openOfferTradableListStorage;
private final Map<String, OpenOffer> offersToBeEdited = new HashMap<>(); private final Map<String, OpenOffer> offersToBeEdited = new HashMap<>();
private boolean stopped; private boolean stopped;
@ -139,6 +141,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
MediatorManager mediatorManager, MediatorManager mediatorManager,
RefundAgentManager refundAgentManager, RefundAgentManager refundAgentManager,
DaoFacade daoFacade, DaoFacade daoFacade,
FilterManager filterManager,
Storage<TradableList<OpenOffer>> storage) { Storage<TradableList<OpenOffer>> storage) {
this.createOfferService = createOfferService; this.createOfferService = createOfferService;
this.keyRing = keyRing; this.keyRing = keyRing;
@ -156,6 +159,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
this.mediatorManager = mediatorManager; this.mediatorManager = mediatorManager;
this.refundAgentManager = refundAgentManager; this.refundAgentManager = refundAgentManager;
this.daoFacade = daoFacade; this.daoFacade = daoFacade;
this.filterManager = filterManager;
openOfferTradableListStorage = storage; openOfferTradableListStorage = storage;
@ -361,7 +365,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
arbitratorManager, arbitratorManager,
tradeStatisticsManager, tradeStatisticsManager,
daoFacade, daoFacade,
user); user,
filterManager);
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol( PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
model, model,
transaction -> { transaction -> {

View File

@ -21,6 +21,7 @@ import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.dao.DaoFacade; import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService; import bisq.core.offer.OfferBookService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
@ -51,6 +52,8 @@ public class PlaceOfferModel implements Model {
private final TradeStatisticsManager tradeStatisticsManager; private final TradeStatisticsManager tradeStatisticsManager;
private final DaoFacade daoFacade; private final DaoFacade daoFacade;
private final User user; private final User user;
@Getter
private final FilterManager filterManager;
// Mutable // Mutable
@Setter @Setter
@ -68,7 +71,8 @@ public class PlaceOfferModel implements Model {
ArbitratorManager arbitratorManager, ArbitratorManager arbitratorManager,
TradeStatisticsManager tradeStatisticsManager, TradeStatisticsManager tradeStatisticsManager,
DaoFacade daoFacade, DaoFacade daoFacade,
User user) { User user,
FilterManager filterManager) {
this.offer = offer; this.offer = offer;
this.reservedFundsForOffer = reservedFundsForOffer; this.reservedFundsForOffer = reservedFundsForOffer;
this.useSavingsWallet = useSavingsWallet; this.useSavingsWallet = useSavingsWallet;
@ -80,6 +84,7 @@ public class PlaceOfferModel implements Model {
this.tradeStatisticsManager = tradeStatisticsManager; this.tradeStatisticsManager = tradeStatisticsManager;
this.daoFacade = daoFacade; this.daoFacade = daoFacade;
this.user = user; this.user = user;
this.filterManager = filterManager;
} }
@Override @Override

View File

@ -25,10 +25,10 @@ import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletService; import bisq.core.btc.wallet.WalletService;
import bisq.core.dao.exceptions.DaoDisabledException; import bisq.core.dao.exceptions.DaoDisabledException;
import bisq.core.dao.governance.param.Param;
import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.placeoffer.PlaceOfferModel; import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.core.util.FeeReceiverSelector;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.taskrunner.Task; import bisq.common.taskrunner.Task;
@ -65,7 +65,8 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
Address changeAddress = walletService.getFreshAddressEntry().getAddress(); Address changeAddress = walletService.getFreshAddressEntry().getAddress();
TradeWalletService tradeWalletService = model.getTradeWalletService(); TradeWalletService tradeWalletService = model.getTradeWalletService();
String feeReceiver = model.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS);
String feeReceiver = FeeReceiverSelector.getAddress(model.getDaoFacade(), model.getFilterManager());
if (offer.isCurrencyForMakerFeeBtc()) { if (offer.isCurrencyForMakerFeeBtc()) {
tradeWalletService.createBtcTradingFeeTx( tradeWalletService.createBtcTradingFeeTx(

View File

@ -169,4 +169,8 @@ public abstract class PaymentAccount implements PersistablePayload {
public String getSaltAsHex() { public String getSaltAsHex() {
return Utilities.bytesAsHexString(getSalt()); return Utilities.bytesAsHexString(getSalt());
} }
public String getOwnerId() {
return paymentAccountPayload.getOwnerId();
}
} }

View File

@ -179,4 +179,9 @@ public abstract class BankAccountPayload extends CountryBasedPaymentAccountPaylo
return super.getAgeWitnessInputData(all.getBytes(StandardCharsets.UTF_8)); return super.getAgeWitnessInputData(all.getBytes(StandardCharsets.UTF_8));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -224,4 +224,9 @@ public class CashDepositAccountPayload extends CountryBasedPaymentAccountPayload
return super.getAgeWitnessInputData(all.getBytes(StandardCharsets.UTF_8)); return super.getAgeWitnessInputData(all.getBytes(StandardCharsets.UTF_8));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -106,4 +106,9 @@ public final class ChaseQuickPayAccountPayload extends PaymentAccountPayload {
// slight changes in holder name (e.g. add or remove middle name) // slight changes in holder name (e.g. add or remove middle name)
return super.getAgeWitnessInputData(email.getBytes(StandardCharsets.UTF_8)); return super.getAgeWitnessInputData(email.getBytes(StandardCharsets.UTF_8));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -106,4 +106,9 @@ public final class ClearXchangeAccountPayload extends PaymentAccountPayload {
// slight changes in holder name (e.g. add or remove middle name) // slight changes in holder name (e.g. add or remove middle name)
return super.getAgeWitnessInputData(emailOrMobileNr.getBytes(StandardCharsets.UTF_8)); return super.getAgeWitnessInputData(emailOrMobileNr.getBytes(StandardCharsets.UTF_8));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -120,4 +120,9 @@ public final class InteracETransferAccountPayload extends PaymentAccountPayload
ArrayUtils.addAll(question.getBytes(StandardCharsets.UTF_8), ArrayUtils.addAll(question.getBytes(StandardCharsets.UTF_8),
answer.getBytes(StandardCharsets.UTF_8)))); answer.getBytes(StandardCharsets.UTF_8))));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -135,4 +135,8 @@ public abstract class PaymentAccountPayload implements NetworkPayload, UsedForTr
protected byte[] getAgeWitnessInputData(byte[] data) { protected byte[] getAgeWitnessInputData(byte[] data) {
return ArrayUtils.addAll(paymentMethodId.getBytes(StandardCharsets.UTF_8), data); return ArrayUtils.addAll(paymentMethodId.getBytes(StandardCharsets.UTF_8), data);
} }
public String getOwnerId() {
return null;
}
} }

View File

@ -313,6 +313,12 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
return Coin.valueOf(tradeLimits.getRoundedRiskBasedTradeLimit(maxTradeLimit, riskFactor)); return Coin.valueOf(tradeLimits.getRoundedRiskBasedTradeLimit(maxTradeLimit, riskFactor));
} }
public String getShortName() {
// in cases where translation is not found, Res.get() simply returns the key string
// so no need for special error-handling code.
return Res.get(this.id + "_SHORT");
}
@Override @Override
public int compareTo(@NotNull PaymentMethod other) { public int compareTo(@NotNull PaymentMethod other) {
return id.compareTo(other.id); return id.compareTo(other.id);

View File

@ -103,4 +103,9 @@ public final class PopmoneyAccountPayload extends PaymentAccountPayload {
public byte[] getAgeWitnessInputData() { public byte[] getAgeWitnessInputData() {
return super.getAgeWitnessInputData(accountId.getBytes(StandardCharsets.UTF_8)); return super.getAgeWitnessInputData(accountId.getBytes(StandardCharsets.UTF_8));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -158,4 +158,8 @@ public final class SepaAccountPayload extends CountryBasedPaymentAccountPayload
// slight changes in holder name (e.g. add or remove middle name) // slight changes in holder name (e.g. add or remove middle name)
return super.getAgeWitnessInputData(ArrayUtils.addAll(iban.getBytes(StandardCharsets.UTF_8), bic.getBytes(StandardCharsets.UTF_8))); return super.getAgeWitnessInputData(ArrayUtils.addAll(iban.getBytes(StandardCharsets.UTF_8), bic.getBytes(StandardCharsets.UTF_8)));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -153,4 +153,9 @@ public final class SepaInstantAccountPayload extends CountryBasedPaymentAccountP
// slight changes in holder name (e.g. add or remove middle name) // slight changes in holder name (e.g. add or remove middle name)
return super.getAgeWitnessInputData(ArrayUtils.addAll(iban.getBytes(StandardCharsets.UTF_8), bic.getBytes(StandardCharsets.UTF_8))); return super.getAgeWitnessInputData(ArrayUtils.addAll(iban.getBytes(StandardCharsets.UTF_8), bic.getBytes(StandardCharsets.UTF_8)));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -104,4 +104,9 @@ public final class SwishAccountPayload extends PaymentAccountPayload {
// slight changes in holder name (e.g. add or remove middle name) // slight changes in holder name (e.g. add or remove middle name)
return super.getAgeWitnessInputData(mobileNr.getBytes(StandardCharsets.UTF_8)); return super.getAgeWitnessInputData(mobileNr.getBytes(StandardCharsets.UTF_8));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -107,4 +107,9 @@ public final class USPostalMoneyOrderAccountPayload extends PaymentAccountPayloa
return super.getAgeWitnessInputData(ArrayUtils.addAll(holderName.getBytes(StandardCharsets.UTF_8), return super.getAgeWitnessInputData(ArrayUtils.addAll(holderName.getBytes(StandardCharsets.UTF_8),
postalAddress.getBytes(StandardCharsets.UTF_8))); postalAddress.getBytes(StandardCharsets.UTF_8)));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -106,4 +106,9 @@ public final class VenmoAccountPayload extends PaymentAccountPayload {
public byte[] getAgeWitnessInputData() { public byte[] getAgeWitnessInputData() {
return super.getAgeWitnessInputData(venmoUserName.getBytes(StandardCharsets.UTF_8)); return super.getAgeWitnessInputData(venmoUserName.getBytes(StandardCharsets.UTF_8));
} }
@Override
public String getOwnerId() {
return holderName;
}
} }

View File

@ -22,9 +22,9 @@ import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.WalletService; import bisq.core.btc.wallet.WalletService;
import bisq.core.dao.exceptions.DaoDisabledException; import bisq.core.dao.exceptions.DaoDisabledException;
import bisq.core.dao.governance.param.Param;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.util.FeeReceiverSelector;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
@ -65,7 +65,9 @@ public class CreateTakerFeeTx extends TradeTask {
Address changeAddress = changeAddressEntry.getAddress(); Address changeAddress = changeAddressEntry.getAddress();
TradeWalletService tradeWalletService = processModel.getTradeWalletService(); TradeWalletService tradeWalletService = processModel.getTradeWalletService();
Transaction transaction; Transaction transaction;
String feeReceiver = processModel.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS);
String feeReceiver = FeeReceiverSelector.getAddress(processModel.getDaoFacade(), processModel.getFilterManager());
if (trade.isCurrencyForTakerFeeBtc()) { if (trade.isCurrencyForTakerFeeBtc()) {
transaction = tradeWalletService.createBtcTradingFeeTx( transaction = tradeWalletService.createBtcTradingFeeTx(
fundingAddress, fundingAddress,

View File

@ -78,10 +78,12 @@ import static com.google.common.base.Preconditions.checkNotNull;
public final class Preferences implements PersistedDataHost, BridgeAddressProvider { public final class Preferences implements PersistedDataHost, BridgeAddressProvider {
private static final ArrayList<BlockChainExplorer> BTC_MAIN_NET_EXPLORERS = new ArrayList<>(Arrays.asList( private static final ArrayList<BlockChainExplorer> BTC_MAIN_NET_EXPLORERS = new ArrayList<>(Arrays.asList(
new BlockChainExplorer("mempool.space (@wiz)", "https://mempool.space/tx/", "https://mempool.space/address/"),
new BlockChainExplorer("mempool.space Tor V3", "http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/tx/", "http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/"),
new BlockChainExplorer("mempool.emzy.de (@emzy)", "https://mempool.emzy.de/tx/", "https://mempool.emzy.de/address/"),
new BlockChainExplorer("mempool.emzy.de Tor V3", "http://mempool4t6mypeemozyterviq3i5de4kpoua65r3qkn5i3kknu5l2cad.onion/tx/", "http://mempool4t6mypeemozyterviq3i5de4kpoua65r3qkn5i3kknu5l2cad.onion/address/"),
new BlockChainExplorer("Blockstream.info", "https://blockstream.info/tx/", "https://blockstream.info/address/"), new BlockChainExplorer("Blockstream.info", "https://blockstream.info/tx/", "https://blockstream.info/address/"),
new BlockChainExplorer("Blockstream.info Tor V3", "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/tx/", "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/address/"), new BlockChainExplorer("Blockstream.info Tor V3", "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/tx/", "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/address/"),
new BlockChainExplorer("mempool.ninja", "https://mempool.ninja/tx/", "https://mempool.ninja/address/"),
new BlockChainExplorer("mempool.ninja Tor V2", "http://mempooltxrqf4re5.onion/tx/", "http://mempooltxrqf4re5.onion/address/"),
new BlockChainExplorer("OXT", "https://oxt.me/transaction/", "https://oxt.me/address/"), new BlockChainExplorer("OXT", "https://oxt.me/transaction/", "https://oxt.me/address/"),
new BlockChainExplorer("Bitaps", "https://bitaps.com/", "https://bitaps.com/"), new BlockChainExplorer("Bitaps", "https://bitaps.com/", "https://bitaps.com/"),
new BlockChainExplorer("Blockcypher", "https://live.blockcypher.com/btc/tx/", "https://live.blockcypher.com/btc/address/"), new BlockChainExplorer("Blockcypher", "https://live.blockcypher.com/btc/tx/", "https://live.blockcypher.com/btc/address/"),
@ -92,7 +94,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
new BlockChainExplorer("Smartbit", "https://www.smartbit.com.au/tx/", "https://www.smartbit.com.au/address/"), new BlockChainExplorer("Smartbit", "https://www.smartbit.com.au/tx/", "https://www.smartbit.com.au/address/"),
new BlockChainExplorer("SoChain. Wow.", "https://chain.so/tx/BTC/", "https://chain.so/address/BTC/"), new BlockChainExplorer("SoChain. Wow.", "https://chain.so/tx/BTC/", "https://chain.so/address/BTC/"),
new BlockChainExplorer("Blockchain.info", "https://blockchain.info/tx/", "https://blockchain.info/address/"), new BlockChainExplorer("Blockchain.info", "https://blockchain.info/tx/", "https://blockchain.info/address/"),
new BlockChainExplorer("Insight", "https://insight.bitpay.com/tx/", "https://insight.bitpay.com/address/") new BlockChainExplorer("Insight", "https://insight.bitpay.com/tx/", "https://insight.bitpay.com/address/"),
new BlockChainExplorer("Blockchair", "https://blockchair.com/bitcoin/transaction/", "https://blockchair.com/bitcoin/address/")
)); ));
private static final ArrayList<BlockChainExplorer> BTC_TEST_NET_EXPLORERS = new ArrayList<>(Arrays.asList( private static final ArrayList<BlockChainExplorer> BTC_TEST_NET_EXPLORERS = new ArrayList<>(Arrays.asList(
new BlockChainExplorer("Blockstream.info", "https://blockstream.info/testnet/tx/", "https://blockstream.info/testnet/address/"), new BlockChainExplorer("Blockstream.info", "https://blockstream.info/testnet/tx/", "https://blockstream.info/testnet/address/"),
@ -101,7 +104,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
new BlockChainExplorer("Blocktrail", "https://www.blocktrail.com/tBTC/tx/", "https://www.blocktrail.com/tBTC/address/"), new BlockChainExplorer("Blocktrail", "https://www.blocktrail.com/tBTC/tx/", "https://www.blocktrail.com/tBTC/address/"),
new BlockChainExplorer("Biteasy", "https://www.biteasy.com/testnet/transactions/", "https://www.biteasy.com/testnet/addresses/"), new BlockChainExplorer("Biteasy", "https://www.biteasy.com/testnet/transactions/", "https://www.biteasy.com/testnet/addresses/"),
new BlockChainExplorer("Smartbit", "https://testnet.smartbit.com.au/tx/", "https://testnet.smartbit.com.au/address/"), new BlockChainExplorer("Smartbit", "https://testnet.smartbit.com.au/tx/", "https://testnet.smartbit.com.au/address/"),
new BlockChainExplorer("SoChain. Wow.", "https://chain.so/tx/BTCTEST/", "https://chain.so/address/BTCTEST/") new BlockChainExplorer("SoChain. Wow.", "https://chain.so/tx/BTCTEST/", "https://chain.so/address/BTCTEST/"),
new BlockChainExplorer("Blockchair", "https://blockchair.com/bitcoin/testnet/transaction/", "https://blockchair.com/bitcoin/testnet/address/")
)); ));
private static final ArrayList<BlockChainExplorer> BTC_DAO_TEST_NET_EXPLORERS = new ArrayList<>(Collections.singletonList( private static final ArrayList<BlockChainExplorer> BTC_DAO_TEST_NET_EXPLORERS = new ArrayList<>(Collections.singletonList(
new BlockChainExplorer("BTC DAO-testnet explorer", "https://bisq.network/explorer/btc/dao_testnet/tx/", "https://bisq.network/explorer/btc/dao_testnet/address/") new BlockChainExplorer("BTC DAO-testnet explorer", "https://bisq.network/explorer/btc/dao_testnet/tx/", "https://bisq.network/explorer/btc/dao_testnet/address/")

View File

@ -0,0 +1,78 @@
/*
* 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.util;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.param.Param;
import bisq.core.filter.FilterManager;
import org.bitcoinj.core.Coin;
import com.google.common.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FeeReceiverSelector {
public static String getAddress(DaoFacade daoFacade, FilterManager filterManager) {
return getAddress(daoFacade, filterManager, new Random());
}
@VisibleForTesting
static String getAddress(DaoFacade daoFacade, FilterManager filterManager, Random rnd) {
List<String> feeReceivers = Optional.ofNullable(filterManager.getFilter())
.flatMap(f -> Optional.ofNullable(f.getBtcFeeReceiverAddresses()))
.orElse(List.of());
List<Long> amountList = new ArrayList<>();
List<String> receiverAddressList = new ArrayList<>();
feeReceivers.forEach(e -> {
try {
String[] tokens = e.split("#");
amountList.add(Coin.parseCoin(tokens[1]).longValue()); // total amount the victim should receive
receiverAddressList.add(tokens[0]); // victim's receiver address
} catch (RuntimeException ignore) {
// If input format is not as expected we ignore entry
}
});
if (!amountList.isEmpty()) {
return receiverAddressList.get(weightedSelection(amountList, rnd));
}
// We keep default value as fallback in case no filter value is available or user has old version.
return daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS);
}
@VisibleForTesting
static int weightedSelection(List<Long> weights, Random rnd) {
long sum = weights.stream().mapToLong(n -> n).sum();
long target = rnd.longs(0, sum).findFirst().orElseThrow();
int i;
for (i = 0; i < weights.size() && target >= 0; i++) {
target -= weights.get(i);
}
return i - 1;
}
}

View File

@ -1,9 +1,12 @@
# nodeaddress.onion:port [(@owner,@backup)] # nodeaddress.onion:port [(@owner,@backup)]
5quyxpxheyvzmb2d.onion:8000 (@miker) 5quyxpxheyvzmb2d.onion:8000 (@miker)
s67qglwhkgkyvr74.onion:8000 (@emzy) s67qglwhkgkyvr74.onion:8000 (@emzy)
ef5qnzx6znifo3df.onion:8000 (@wiz,@emzy)
3f3cu2yw7u457ztq.onion:8000 (@devinbileck,@ripcurlx) 3f3cu2yw7u457ztq.onion:8000 (@devinbileck,@ripcurlx)
723ljisnynbtdohi.onion:8000 (@emzy) 723ljisnynbtdohi.onion:8000 (@emzy)
rm7b56wbrcczpjvl.onion:8000 (@miker) rm7b56wbrcczpjvl.onion:8000 (@miker)
fl3mmribyxgrv63c.onion:8000 (@devinbileck,@ripcurlx) fl3mmribyxgrv63c.onion:8000 (@devinbileck,@ripcurlx)
wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000 (@wiz) wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000 (@wiz)
wizseed3d376esppbmbjxk2fhk2jg5fpucddrzj2kxtbxbx4vrnwclad.onion:8000 (@wiz)
wizseed7ab2gi3x267xahrp2pkndyrovczezzb46jk6quvguciuyqrid.onion:8000 (@wiz)
devinv3rhon24gqf5v6ondoqgyrbzyqihzyouzv7ptltsewhfmox2zqd.onion:8000 (@devinbileck)
sn3emzy56u3mxzsr4geysc52feoq5qt7ja56km6gygwnszkshunn2sid.onion:8000 (@emzy)

View File

@ -747,7 +747,7 @@ portfolio.pending.step5_buyer.refunded=Refunded security deposit
portfolio.pending.step5_buyer.withdrawBTC=Withdraw your bitcoin portfolio.pending.step5_buyer.withdrawBTC=Withdraw your bitcoin
portfolio.pending.step5_buyer.amount=Amount to withdraw portfolio.pending.step5_buyer.amount=Amount to withdraw
portfolio.pending.step5_buyer.withdrawToAddress=Withdraw to address portfolio.pending.step5_buyer.withdrawToAddress=Withdraw to address
portfolio.pending.step5_buyer.moveToBisqWallet=Move funds to Bisq wallet portfolio.pending.step5_buyer.moveToBisqWallet=Keep funds in Bisq wallet
portfolio.pending.step5_buyer.withdrawExternal=Withdraw to external wallet portfolio.pending.step5_buyer.withdrawExternal=Withdraw to external wallet
portfolio.pending.step5_buyer.alreadyWithdrawn=Your funds have already been withdrawn.\nPlease check the transaction history. portfolio.pending.step5_buyer.alreadyWithdrawn=Your funds have already been withdrawn.\nPlease check the transaction history.
portfolio.pending.step5_buyer.confirmWithdrawal=Confirm withdrawal request portfolio.pending.step5_buyer.confirmWithdrawal=Confirm withdrawal request
@ -2429,6 +2429,7 @@ filterWindow.disableDaoBelowVersion=Min. version required for DAO
filterWindow.disableTradeBelowVersion=Min. version required for trading filterWindow.disableTradeBelowVersion=Min. version required for trading
filterWindow.add=Add filter filterWindow.add=Add filter
filterWindow.remove=Remove filter filterWindow.remove=Remove filter
filterWindow.btcFeeReceiverAddresses=BTC fee receiver addresses
offerDetailsWindow.minBtcAmount=Min. BTC amount offerDetailsWindow.minBtcAmount=Min. BTC amount
offerDetailsWindow.min=(min. {0}) offerDetailsWindow.min=(min. {0})
@ -2952,6 +2953,10 @@ seed.warn.walletNotEmpty.emptyWallet=I will empty my wallets first
seed.warn.notEncryptedAnymore=Your wallets are encrypted.\n\n\ seed.warn.notEncryptedAnymore=Your wallets are encrypted.\n\n\
After restore, the wallets will no longer be encrypted and you must set a new password.\n\n\ After restore, the wallets will no longer be encrypted and you must set a new password.\n\n\
Do you want to proceed? Do you want to proceed?
seed.warn.walletDateEmpty=As you have not specified a wallet date, bisq will have to scan the blockchain from 2013.10.09 (the BIP39 epoch date).\n\n\
BIP39 wallets were first introduced in bisq on 2017.06.28 (release v0.5). So you could save time by using that date.\n\n\
Ideally you should specify the date your wallet seed was created.\n\n\n\
Are you sure you want to go ahead without specifying a wallet date?
seed.restore.success=Wallets restored successfully with the new seed words.\n\nYou need to shut down and restart the application. seed.restore.success=Wallets restored successfully with the new seed words.\n\nYou need to shut down and restart the application.
seed.restore.error=An error occurred when restoring the wallets with seed words.{0} seed.restore.error=An error occurred when restoring the wallets with seed words.{0}
@ -3039,27 +3044,14 @@ payment.accountType=Account type
payment.checking=Checking payment.checking=Checking
payment.savings=Savings payment.savings=Savings
payment.personalId=Personal ID payment.personalId=Personal ID
payment.clearXchange.info=Please be sure that you fulfill the requirements for the usage of Zelle (ClearXchange).\n\n\ payment.clearXchange.info=Zelle is a money transfer service that works best *through* another bank.\n\n\
1. You need to have your Zelle (ClearXchange) account verified on their platform \ 1. Check this page to see if (and how) your bank works with Zelle:\nhttps://www.zellepay.com/get-started\n\n\
before starting a trade or creating an offer.\n\n\ 2. Take special note of your transfer limits—sending limits vary by bank, and banks often specify separate daily, weekly, and monthly limits.\n\n\
2. You need to have a bank account at one of the following member banks:\n\ 3. If your bank does not work with Zelle, you can still use it through the Zelle mobile app, but your transfer limits will be much lower.\n\n\
\t● Bank of America\n\ 4. The name specified on your Bisq account MUST match the name on your Zelle/bank account. \n\n\
\t● Capital One P2P Payments\n\ If you cannot complete a Zelle transaction as specified in your trade contract, you may lose some (or all) of your security deposit.\n\n\
\t● Chase QuickPay\n\ Because of Zelle''s somewhat higher chargeback risk, sellers are advised to contact unsigned buyers through email or SMS to verify that the buyer \
\t● FirstBank Person to Person Transfers\n\ really owns the Zelle account specified in Bisq.
\t● Frost Send Money\n\
\t● U.S. Bank Send Money\n\
\t● TD Bank\n\
\t● Citibank\n\
\t● Wells Fargo SurePay\n\n\
3. You need to be sure to not exceed the daily or monthly Zelle (ClearXchange) transfer limits.\n\n\
Please use Zelle (ClearXchange) only if you fulfill all those requirements, \
otherwise it is very likely that the Zelle (ClearXchange) transfer fails and the trade ends up in a dispute.\n\
If you have not fulfilled the above requirements you will lose your security deposit.\n\n\
Please also be aware of a higher chargeback risk when using Zelle (ClearXchange).\n\
For the {0} seller it is highly recommended \
to get in contact with the {1} buyer by using the provided email address or mobile number to verify that he or she \
is really the owner of the Zelle (ClearXchange) account.
payment.fasterPayments.newRequirements.info=Some banks have started verifying the receiver''s full name for Faster \ payment.fasterPayments.newRequirements.info=Some banks have started verifying the receiver''s full name for Faster \
Payments transfers. Your current Faster Payments account does not specify a full name.\n\n\ Payments transfers. Your current Faster Payments account does not specify a full name.\n\n\
Please consider recreating your Faster Payments account in Bisq to provide future {0} buyers with a full name.\n\n\ Please consider recreating your Faster Payments account in Bisq to provide future {0} buyers with a full name.\n\n\
@ -3086,29 +3078,31 @@ payment.limits.info=Please be aware that all bank transfers carry a certain amou
1. The estimated level of chargeback risk for the payment method used\n\ 1. The estimated level of chargeback risk for the payment method used\n\
2. The age of your account for that payment method\n\ 2. The age of your account for that payment method\n\
\n\ \n\
The account you are creating now is new and its age is zero. As your account grows in age over a two-month period, your per-trade limits will grow along with it:\n\ The account you are creating now is new and its age is zero. As your account ages, your per-trade limits will grow:\n\
\n\ \n\
During the 1st month, your per-trade limit will be {0}\n\ During the 1st month, your per-trade limit will be {0}\n\
During the 2nd month, your per-trade limit will be {1}\n\ During the 2nd month, your per-trade limit will be {1}\n\
After the 2nd month, your per-trade limit will be {2}\n\ After the 2nd month, your per-trade limit will be {2}\n\
\n\ \n\
Please note that there are no limits on the total number of times you can trade. Please note: limits only apply to trade size. You can place as many trades as you like.
payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade buy limits based on the following 2 factors:\n\ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade limits for this payment account type based \
\n\ on the following 2 factors:\n\n\
1. General chargeback risk for the payment method\n\ 1. General chargeback risk for the payment method\n\
2. Account signing status\n\ 2. Account signing status\n\
\n\ \n\
This payment account you just created carries some chargeback risk and is not yet signed by an arbitrator or trusted peer, \ This payment account is not yet signed, so it is limited to buying {0} per trade. \
so it is limited to buying {0} per trade. \ After signing, buy limits will increase as follows:\n\
After it is signed, buy limits will increase as follows:\n\
\n\ \n\
Before signing, and up to 30 days after signing, your per-trade buy limit will be {0}\n\ Before signing, and for 30 days after signing, your per-trade buy limit will be {0}\n\
30 days after signing, your per-trade buy limit will be {1}\n\ 30 days after signing, your per-trade buy limit will be {1}\n\
60 days after signing, your per-trade buy limit will be {2}\n\ 60 days after signing, your per-trade buy limit will be {2}\n\
\n\ \n\
Sell limits are not affected by account signing, and increase merely as account age increases. More information is at https://docs.bisq.network/payment-methods#account-signing.\n\ Sell limits are not affected by account signing, and increase with account age.\n\
\n\ \n\
There are no limits on the number of trades you can make. See more:\n\
https://bisq.wiki/Account_limits\n\
\n\
Please note: limits only apply to trade size. You can place as many trades as you like.
payment.cashDeposit.info=Please confirm your bank allows you to send cash deposits into other peoples' accounts. \ payment.cashDeposit.info=Please confirm your bank allows you to send cash deposits into other peoples' accounts. \
For example, Bank of America and Wells Fargo no longer allow such deposits. For example, Bank of America and Wells Fargo no longer allow such deposits.

View File

@ -1,6 +1,6 @@
TXT CHECKPOINTS 1 TXT CHECKPOINTS 1
0 0
310 313
AAAAAAAAB+EH4QfhAAAH4AEAAABjl7tqvU/FIcDT9gcbVlA4nwtFUbxAtOawZzBpAAAAAKzkcK7NqciBjI/ldojNKncrWleVSgDfBCCn3VRrbSxXaw5/Sf//AB0z8Bkv AAAAAAAAB+EH4QfhAAAH4AEAAABjl7tqvU/FIcDT9gcbVlA4nwtFUbxAtOawZzBpAAAAAKzkcK7NqciBjI/ldojNKncrWleVSgDfBCCn3VRrbSxXaw5/Sf//AB0z8Bkv
AAAAAAAAD8EPwQ/BAAAPwAEAAADfP83Sx8MZ9RsrnZCvqzAwqB2Ma+ZesNAJrTfwAAAAACwESaNKhvRgz6WuE7UFdFk1xwzfRY/OIdIOPzX5yaAdjnWUSf//AB0GrNq5 AAAAAAAAD8EPwQ/BAAAPwAEAAADfP83Sx8MZ9RsrnZCvqzAwqB2Ma+ZesNAJrTfwAAAAACwESaNKhvRgz6WuE7UFdFk1xwzfRY/OIdIOPzX5yaAdjnWUSf//AB0GrNq5
AAAAAAAAF6EXoRehAAAXoAEAAADonWzAaUAKd30XT3NnHKobZMnLOuHdzm/xtehsAAAAAD8cUJA6NBIHHcqPHLc4IrfHw+6mjCGu3e+wRO81EvpnMVqrSf//AB1ffy8G AAAAAAAAF6EXoRehAAAXoAEAAADonWzAaUAKd30XT3NnHKobZMnLOuHdzm/xtehsAAAAAD8cUJA6NBIHHcqPHLc4IrfHw+6mjCGu3e+wRO81EvpnMVqrSf//AB1ffy8G
@ -311,3 +311,6 @@ DQOCnM/692T/c/vpAAlxoAAgACBpKU8xBXETUdlfq3ziC5HqY2sbxpLaBgAAAAAAAAAAAC2Podb2UVE3
DXJv6TyZC6dJNCJfAAl5gAAAwCC821K+00n7cVERL9d6Jzo0wkE8NSY/BAAAAAAAAAAAANEvjjQwmKBYHwf7qsdzHSrM+7AC4wGhHxMBliBwDnPi7SJmXhkBERcyCjYC DXJv6TyZC6dJNCJfAAl5gAAAwCC821K+00n7cVERL9d6Jzo0wkE8NSY/BAAAAAAAAAAAANEvjjQwmKBYHwf7qsdzHSrM+7AC4wGhHxMBliBwDnPi7SJmXhkBERcyCjYC
Dej8cVmdICmGlGmsAAmBYAAAgCAMNQrVauCVnJhLXbUm+wi+s6FYCIDGCwAAAAAAAAAAAHSgbycFfzUIokW6wOwg6yoHf88c9CrCsoU3LGXqo65Awhh8XkE7FBcTwPq1 Dej8cVmdICmGlGmsAAmBYAAAgCAMNQrVauCVnJhLXbUm+wi+s6FYCIDGCwAAAAAAAAAAAHSgbycFfzUIokW6wOwg6yoHf88c9CrCsoU3LGXqo65Awhh8XkE7FBcTwPq1
Dkyiv/CRkmMKmkEzAAmJQADgACBD4xnvCIUw3EDxERdHu3/bAmPjwJ15DAAAAAAAAAAAAHiPGbRWrMMMyL6BA6eaJfNIo5XiUMXo0B7u72S1QqDZLpGNXrwgExchnuav Dkyiv/CRkmMKmkEzAAmJQADgACBD4xnvCIUw3EDxERdHu3/bAmPjwJ15DAAAAAAAAAAAAHiPGbRWrMMMyL6BA6eaJfNIo5XiUMXo0B7u72S1QqDZLpGNXrwgExchnuav
DrYJPtE6p0g5WpqUAAmRIAAAACAUOgCoJeMzIDa34vdMEznyR1cSUIbpDQAAAAAAAAAAAKlIg5av2zB0dTVJcJhbennqbQ9kisot+0OheDaM0jzyNJueXjOjEReyl0xD
DyhWrFlPqEc/c9+uAAmZAAAgACDcrvMMuRSy9GQxh8BFNt6wIDWYhvU4EQAAAAAAAAAAABVOP5QKoWx9atpYrUh6L+UeYRrgD4sj3ZOBngyn3WuRAeawXjl6EReMHTLH
D5uvExILinkBRJMsAAmg4ADg/yfO2pclcPlRE5XVKCPZeF63mn30v+yHDgAAAAAAAAAAAPaDA4g+h9tjjVWtJ587WMHb07Mcfa1BoDJCANQd+z94wJDEXvaXEhf5l8cN

View File

@ -1,7 +1,5 @@
package bisq.core.offer; package bisq.core.offer;
import bisq.core.trade.TradableList;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.network.p2p.peers.PeerManager; import bisq.network.p2p.peers.PeerManager;
@ -47,7 +45,7 @@ public class OpenOfferManagerTest {
final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService, final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService,
null, null, null, offerBookService, null, null, null, offerBookService,
null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null,
new Storage<>(storageDir, null, corruptedDatabaseFilesHandler)); new Storage<>(storageDir, null, corruptedDatabaseFilesHandler));
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
@ -83,7 +81,7 @@ public class OpenOfferManagerTest {
final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService, final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService,
null, null, null, offerBookService, null, null, null, offerBookService,
null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null,
new Storage<>(storageDir, null, corruptedDatabaseFilesHandler)); new Storage<>(storageDir, null, corruptedDatabaseFilesHandler));
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
@ -111,7 +109,7 @@ public class OpenOfferManagerTest {
final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService, final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService,
null, null, null, offerBookService, null, null, null, offerBookService,
null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null,
new Storage<>(storageDir, null, corruptedDatabaseFilesHandler)); new Storage<>(storageDir, null, corruptedDatabaseFilesHandler));
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);

View File

@ -27,7 +27,6 @@ import com.google.common.collect.Lists;
import org.junit.Ignore; import org.junit.Ignore;
@SuppressWarnings("UnusedAssignment")
public class UserPayloadModelVOTest { public class UserPayloadModelVOTest {
@Ignore("TODO InvalidKeySpecException at bisq.common.crypto.Sig.getPublicKeyFromBytes(Sig.java:135)") @Ignore("TODO InvalidKeySpecException at bisq.common.crypto.Sig.getPublicKeyFromBytes(Sig.java:135)")
public void testRoundtrip() { public void testRoundtrip() {
@ -59,6 +58,7 @@ public class UserPayloadModelVOTest {
null, null,
Lists.newArrayList(), Lists.newArrayList(),
Lists.newArrayList(), Lists.newArrayList(),
Lists.newArrayList(),
Lists.newArrayList())); Lists.newArrayList()));
vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock()); vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock());
vo.setRegisteredMediator(MediatorTest.getMediatorMock()); vo.setRegisteredMediator(MediatorTest.getMediatorMock());

View File

@ -0,0 +1,107 @@
/*
* 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.util;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.param.Param;
import bisq.core.filter.Filter;
import bisq.core.filter.FilterManager;
import com.google.common.primitives.Longs;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class FeeReceiverSelectorTest {
@Mock
private DaoFacade daoFacade;
@Mock
private FilterManager filterManager;
@Test
public void testGetAddress() {
Random rnd = new Random(123);
when(filterManager.getFilter()).thenReturn(filterWithReceivers(
List.of("", "foo#0.001", "ill-formed", "bar#0.002", "baz#0.001", "partial#bad")));
Map<String, Integer> selectionCounts = new HashMap<>();
for (int i = 0; i < 400; i++) {
String address = FeeReceiverSelector.getAddress(daoFacade, filterManager, rnd);
selectionCounts.compute(address, (k, n) -> n != null ? n + 1 : 1);
}
assertEquals(3, selectionCounts.size());
// Check within 2 std. of the expected values (95% confidence each):
assertEquals(100.0, selectionCounts.get("foo"), 18);
assertEquals(200.0, selectionCounts.get("bar"), 20);
assertEquals(100.0, selectionCounts.get("baz"), 18);
}
@Test
public void testGetAddress_noValidReceivers() {
when(daoFacade.getParamValue(Param.RECIPIENT_BTC_ADDRESS)).thenReturn("default");
when(filterManager.getFilter()).thenReturn(null);
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager));
when(filterManager.getFilter()).thenReturn(filterWithReceivers(null));
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager));
when(filterManager.getFilter()).thenReturn(filterWithReceivers(List.of()));
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager));
when(filterManager.getFilter()).thenReturn(filterWithReceivers(List.of("ill-formed")));
assertEquals("default", FeeReceiverSelector.getAddress(daoFacade, filterManager));
}
@Test
public void testWeightedSelection() {
Random rnd = new Random(456);
int[] selections = new int[3];
for (int i = 0; i < 6000; i++) {
selections[FeeReceiverSelector.weightedSelection(Longs.asList(1, 2, 3), rnd)]++;
}
// Check within 2 std. of the expected values (95% confidence each):
assertEquals(1000.0, selections[0], 58);
assertEquals(2000.0, selections[1], 74);
assertEquals(3000.0, selections[2], 78);
}
private static Filter filterWithReceivers(List<String> btcFeeReceiverAddresses) {
return new Filter(null, null, null, null,
null, null, null, null,
false, null, false, null,
null, null, null, null,
btcFeeReceiverAddresses);
}
}

View File

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

View File

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

View File

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

View File

@ -5,10 +5,10 @@
<!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html --> <!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -->
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.3.4</string> <string>1.3.5</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.3.4</string> <string>1.3.5</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>Bisq</string> <string>Bisq</string>
@ -26,7 +26,7 @@
<string>public.app-category.finance</string> <string>public.app-category.finance</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>Copyright © 2013-2019 - The Bisq developers</string> <string>Copyright © 2013-2020 - The Bisq developers</string>
<!-- Only supported in older OSX versions. <!-- Only supported in older OSX versions.
See: https://github.com/bitcoin/bitcoin/issues/11896#issuecomment-352148399--> See: https://github.com/bitcoin/bitcoin/issues/11896#issuecomment-352148399-->

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -237,6 +237,30 @@
-fx-background-color: -fx-selection-bar; -fx-background-color: -fx-selection-bar;
} }
/* list view */
.list-view .list-cell {
-fx-background-color: -bs-background-color;
}
.list-view .list-cell:odd {
-fx-background-color: derive(-bs-background-color, -5%);
}
.list-view .list-cell:even {
-fx-background-color: derive(-bs-background-color, 5%);
}
.list-view .list-cell:hover,
.list-view .list-cell:selected,
.table-view .table-cell:hover,
.table-view .table-cell:selected {
-fx-background: -fx-accent;
-fx-background-color: -fx-selection-bar;
-fx-border-color: -fx-selection-bar;
}
.number-column.table-cell {
-fx-background-color: -bs-background-color;
}
.list-view:focused, .list-view:focused,
.tree-view:focused, .tree-view:focused,
.table-view:focused, .table-view:focused,
@ -867,6 +891,32 @@ textfield */
* Table * * Table *
* * * *
******************************************************************************/ ******************************************************************************/
.table-view .table-row-cell:even .table-cell {
-fx-background-color: derive(-bs-background-color, 5%);
-fx-border-color: derive(-bs-background-color,5%);
}
.table-view .table-row-cell:odd .table-cell {
-fx-background-color: derive(-bs-background-color,-5%);
-fx-border-color: derive(-bs-background-color,-5%);
}
.table-view .table-row-cell:hover .table-cell,
.table-view .table-row-cell:selected .table-cell {
-fx-background: -fx-accent;
-fx-background-color: -fx-selection-bar;
-fx-border-color: -fx-selection-bar;
}
.table-row-cell {
-fx-border-color: -bs-background-color;
}
.table-row-cell:empty, .table-row-cell:empty:even, .table-row-cell:empty:odd {
-fx-background-color: -bs-background-color;
-fx-min-height: 36;
}
.offer-table .table-row-cell {
-fx-background: -fx-accent;
-fx-background-color: -bs-color-gray-6;
}
.table-view .table-cell { .table-view .table-cell {
-fx-alignment: center-left; -fx-alignment: center-left;
-fx-padding: 2 0 2 0; -fx-padding: 2 0 2 0;

View File

@ -147,8 +147,8 @@ public class RevolutForm extends PaymentMethodForm {
private void addCurrenciesGrid(boolean isEditable) { private void addCurrenciesGrid(boolean isEditable) {
FlowPane flowPane = addTopLabelFlowPane(gridPane, ++gridRow, FlowPane flowPane = addTopLabelFlowPane(gridPane, ++gridRow,
Res.get("payment.supportedCurrencies"), Layout.FLOATING_LABEL_DISTANCE, Res.get("payment.supportedCurrencies"), Layout.FLOATING_LABEL_DISTANCE * 3,
Layout.FLOATING_LABEL_DISTANCE).second; Layout.FLOATING_LABEL_DISTANCE * 3).second;
if (isEditable) if (isEditable)
flowPane.setId("flow-pane-checkboxes-bg"); flowPane.setId("flow-pane-checkboxes-bg");

View File

@ -130,6 +130,7 @@ class FiatAccountsDataModel extends ActivatableDataModel {
user.addPaymentAccount(paymentAccount); user.addPaymentAccount(paymentAccount);
accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload()); accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload());
accountAgeWitnessService.signSameNameAccounts();
} }
public boolean onDeleteAccount(PaymentAccount paymentAccount) { public boolean onDeleteAccount(PaymentAccount paymentAccount) {

View File

@ -239,6 +239,19 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
private void checkIfEncrypted() { private void checkIfEncrypted() {
if (walletsManager.areWalletsEncrypted()) { if (walletsManager.areWalletsEncrypted()) {
new Popup().information(Res.get("seed.warn.notEncryptedAnymore")) new Popup().information(Res.get("seed.warn.notEncryptedAnymore"))
.closeButtonText(Res.get("shared.no"))
.actionButtonText(Res.get("shared.yes"))
.onAction(this::doRestoreDateCheck)
.show();
} else {
doRestoreDateCheck();
}
}
private void doRestoreDateCheck() {
if (restoreDatePicker.getValue() == null) {
// Provide feedback when attempting to restore a wallet from seed words without specifying a date
new Popup().information(Res.get("seed.warn.walletDateEmpty"))
.closeButtonText(Res.get("shared.no")) .closeButtonText(Res.get("shared.no"))
.actionButtonText(Res.get("shared.yes")) .actionButtonText(Res.get("shared.yes"))
.onAction(this::doRestore) .onAction(this::doRestore)
@ -248,7 +261,7 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
} }
} }
private void doRestore() { private LocalDate getWalletDate() {
LocalDate walletDate = restoreDatePicker.getValue(); LocalDate walletDate = restoreDatePicker.getValue();
// Even though no current Bisq wallet could have been created before the v0.5 release date (2017.06.28), // Even though no current Bisq wallet could have been created before the v0.5 release date (2017.06.28),
// the user may want to import from a seed generated by another wallet. // the user may want to import from a seed generated by another wallet.
@ -264,7 +277,11 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
} else if (walletDate.isAfter(LocalDate.now())) { } else if (walletDate.isAfter(LocalDate.now())) {
walletDate = LocalDate.now(); walletDate = LocalDate.now();
} }
return walletDate;
}
private void doRestore() {
LocalDate walletDate = getWalletDate();
// We subtract 1 day to be sure to not have any issues with timezones. Even if we can be sure that the timezone // We subtract 1 day to be sure to not have any issues with timezones. Even if we can be sure that the timezone
// is handled correctly it could be that the user created the wallet in one timezone and make a restore at // is handled correctly it could be that the user created the wallet in one timezone and make a restore at
// a different timezone which could lead in the worst case that he miss the first day of the wallet transactions. // a different timezone which could lead in the worst case that he miss the first day of the wallet transactions.

View File

@ -186,8 +186,9 @@ public class BondsView extends ActivatableView<GridPane, Void> {
}; };
} }
}); });
column.setComparator(Comparator.comparing(e -> e.getBond().getAmount()));
tableView.getColumns().add(column); tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockTime")); column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockTime"));
column.setMinWidth(40); column.setMinWidth(40);
column.setComparator(Comparator.comparingInt(v -> v.getBond().getLockTime())); column.setComparator(Comparator.comparingInt(v -> v.getBond().getLockTime()));
@ -207,6 +208,7 @@ public class BondsView extends ActivatableView<GridPane, Void> {
}; };
} }
}); });
column.setComparator(Comparator.comparing(e -> e.getBond().getLockTime()));
tableView.getColumns().add(column); tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.bondState")); column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.bondState"));
@ -230,6 +232,7 @@ public class BondsView extends ActivatableView<GridPane, Void> {
}; };
} }
}); });
column.setComparator(Comparator.comparing(BondListItem::getBondStateString));
tableView.getColumns().add(column); tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.bondType")); column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.bondType"));
@ -251,6 +254,7 @@ public class BondsView extends ActivatableView<GridPane, Void> {
}; };
} }
}); });
column.setComparator(Comparator.comparing(BondListItem::getBondType));
tableView.getColumns().add(column); tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.details")); column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.details"));
@ -285,6 +289,7 @@ public class BondsView extends ActivatableView<GridPane, Void> {
}; };
} }
}); });
column.setComparator(Comparator.comparing(BondListItem::getBondDetails));
tableView.getColumns().add(column); tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockupDate")); column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockupDate"));
@ -306,6 +311,7 @@ public class BondsView extends ActivatableView<GridPane, Void> {
}; };
} }
}); });
column.setComparator(Comparator.comparing(e -> e.getBond().getLockupDate()));
tableView.getColumns().add(column); tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockupTxId")); column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockupTxId"));
@ -340,6 +346,7 @@ public class BondsView extends ActivatableView<GridPane, Void> {
}; };
} }
}); });
column.setComparator(Comparator.comparing(BondListItem::getLockupTxId));
tableView.getColumns().add(column); tableView.getColumns().add(column);
} }
} }

View File

@ -190,8 +190,8 @@ public class ProofOfBurnView extends ActivatableView<GridPane, Void> implements
preImageTextField.setValidator(new InputValidator()); preImageTextField.setValidator(new InputValidator());
updateList(); updateList();
GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 2, 4); GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 4, 6);
GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 100); GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 10);
updateButtonState(); updateButtonState();
} }
@ -265,13 +265,12 @@ public class ProofOfBurnView extends ActivatableView<GridPane, Void> implements
.map(myProofOfBurn -> new MyProofOfBurnListItem(myProofOfBurn, proofOfBurnService, bsqFormatter)) .map(myProofOfBurn -> new MyProofOfBurnListItem(myProofOfBurn, proofOfBurnService, bsqFormatter))
.sorted(Comparator.comparing(MyProofOfBurnListItem::getDate).reversed()) .sorted(Comparator.comparing(MyProofOfBurnListItem::getDate).reversed())
.collect(Collectors.toList())); .collect(Collectors.toList()));
GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 2, 4); GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 4, 6);
allItemsObservableList.setAll(proofOfBurnService.getProofOfBurnTxList().stream() allItemsObservableList.setAll(proofOfBurnService.getProofOfBurnTxList().stream()
.map(tx -> new ProofOfBurnListItem(tx, proofOfBurnService, bsqFormatter)) .map(tx -> new ProofOfBurnListItem(tx, proofOfBurnService, bsqFormatter))
.collect(Collectors.toList())); .collect(Collectors.toList()));
GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 100); GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 10);
} }
private void updateButtonState() { private void updateButtonState() {

View File

@ -286,6 +286,11 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
availableForVoting = Coin.valueOf(0); availableForVoting = Coin.valueOf(0);
stakeInputTextField.setPromptText(Res.get("dao.proposal.myVote.stake.prompt", stakeInputTextField.setPromptText(Res.get("dao.proposal.myVote.stake.prompt",
bsqFormatter.formatCoinWithCode(availableForVoting))); bsqFormatter.formatCoinWithCode(availableForVoting)));
BsqValidator stakeInputTextFieldValidator = new BsqValidator(bsqFormatter);
stakeInputTextFieldValidator.setMaxValue(availableForVoting);
stakeInputTextField.setValidator(stakeInputTextFieldValidator);
} else } else
stakeInputTextField.setPromptText(""); stakeInputTextField.setPromptText("");
} }

View File

@ -226,7 +226,7 @@ public class OfferBookChartView extends ActivatableViewAndModel<VBox, OfferBookC
final double doubleValue = (double) object; final double doubleValue = (double) object;
if (CurrencyUtil.isCryptoCurrency(model.getCurrencyCode())) { if (CurrencyUtil.isCryptoCurrency(model.getCurrencyCode())) {
final String withCryptoPrecision = FormattingUtils.formatRoundedDoubleWithPrecision(doubleValue, cryptoPrecision); final String withCryptoPrecision = FormattingUtils.formatRoundedDoubleWithPrecision(doubleValue, cryptoPrecision);
if (withCryptoPrecision.equals("0.000")) { if (withCryptoPrecision.substring(0,3).equals("0.0")) {
cryptoPrecision = 8; cryptoPrecision = 8;
return FormattingUtils.formatRoundedDoubleWithPrecision(doubleValue, cryptoPrecision); return FormattingUtils.formatRoundedDoubleWithPrecision(doubleValue, cryptoPrecision);
} else { } else {

View File

@ -282,6 +282,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
amount.get(), amount.get(),
minAmount.get(), minAmount.get(),
price.get(), price.get(),
txFeeFromFeeService,
useMarketBasedPrice.get(), useMarketBasedPrice.get(),
marketPriceMargin, marketPriceMargin,
buyerSecurityDeposit.get(), buyerSecurityDeposit.get(),

View File

@ -812,14 +812,16 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
// We want to trigger a recalculation of the volume and minAmount // We want to trigger a recalculation of the volume and minAmount
UserThread.execute(() -> { UserThread.execute(() -> {
onFocusOutVolumeTextField(true, false); onFocusOutVolumeTextField(true, false);
// do not update BTC Amount or minAmount here triggerFocusOutOnAmountFields();
// issue 2798: "after a few edits of offer the BTC amount has increased"
// intentionally removed: onFocusOutAmountTextField(true, false);
// intentionally removed: onFocusOutMinAmountTextField(true, false);
}); });
} }
} }
public void triggerFocusOutOnAmountFields() {
onFocusOutAmountTextField(true, false);
onFocusOutMinAmountTextField(true, false);
}
public void onFocusOutPriceAsPercentageTextField(boolean oldValue, boolean newValue) { public void onFocusOutPriceAsPercentageTextField(boolean oldValue, boolean newValue) {
inputIsMarketBasedPrice = !oldValue && newValue; inputIsMarketBasedPrice = !oldValue && newValue;
if (oldValue && !newValue) { if (oldValue && !newValue) {

View File

@ -130,6 +130,7 @@ public class FilterWindow extends Overlay<FilterWindow> {
InputTextField arbitratorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.arbitrators")); InputTextField arbitratorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.arbitrators"));
InputTextField mediatorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.mediators")); InputTextField mediatorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.mediators"));
InputTextField refundAgentsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.refundAgents")); InputTextField refundAgentsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.refundAgents"));
InputTextField btcFeeReceiverAddressesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.btcFeeReceiverAddresses"));
InputTextField seedNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.seedNode")); InputTextField seedNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.seedNode"));
InputTextField priceRelayNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.priceRelayNode")); InputTextField priceRelayNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.priceRelayNode"));
InputTextField btcNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.btcNode")); InputTextField btcNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.btcNode"));
@ -149,6 +150,7 @@ public class FilterWindow extends Overlay<FilterWindow> {
setupFieldFromList(arbitratorsInputTextField, filter.getArbitrators()); setupFieldFromList(arbitratorsInputTextField, filter.getArbitrators());
setupFieldFromList(mediatorsInputTextField, filter.getMediators()); setupFieldFromList(mediatorsInputTextField, filter.getMediators());
setupFieldFromList(refundAgentsInputTextField, filter.getRefundAgents()); setupFieldFromList(refundAgentsInputTextField, filter.getRefundAgents());
setupFieldFromList(btcFeeReceiverAddressesInputTextField, filter.getBtcFeeReceiverAddresses());
setupFieldFromList(seedNodesInputTextField, filter.getSeedNodes()); setupFieldFromList(seedNodesInputTextField, filter.getSeedNodes());
setupFieldFromList(priceRelayNodesInputTextField, filter.getPriceRelayNodes()); setupFieldFromList(priceRelayNodesInputTextField, filter.getPriceRelayNodes());
setupFieldFromList(btcNodesInputTextField, filter.getBtcNodes()); setupFieldFromList(btcNodesInputTextField, filter.getBtcNodes());
@ -177,7 +179,8 @@ public class FilterWindow extends Overlay<FilterWindow> {
disableTradeBelowVersionInputTextField.getText(), disableTradeBelowVersionInputTextField.getText(),
readAsList(mediatorsInputTextField), readAsList(mediatorsInputTextField),
readAsList(refundAgentsInputTextField), readAsList(refundAgentsInputTextField),
readAsList(bannedSignerPubKeysInputTextField) readAsList(bannedSignerPubKeysInputTextField),
readAsList(btcFeeReceiverAddressesInputTextField)
), ),
keyInputTextField.getText()) keyInputTextField.getText())
) )

View File

@ -30,8 +30,8 @@ import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOffer;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.FormattingUtils; import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.CoinFormatter;
import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ErrorMessageHandler;
@ -108,4 +108,10 @@ class EditOfferViewModel extends MutableOfferViewModel<EditOfferDataModel> {
public boolean isSecurityDepositValid() { public boolean isSecurityDepositValid() {
return securityDepositValidator.validate(buyerSecurityDeposit.get()).isValid; return securityDepositValidator.validate(buyerSecurityDeposit.get()).isValid;
} }
@Override
public void triggerFocusOutOnAmountFields() {
// do not update BTC Amount or minAmount here
// issue 2798: "after a few edits of offer the BTC amount has increased"
}
} }

View File

@ -31,14 +31,15 @@
<columns> <columns>
<TableColumn fx:id="offerIdColumn" minWidth="110" maxWidth="130"/> <TableColumn fx:id="offerIdColumn" minWidth="110" maxWidth="130"/>
<TableColumn fx:id="dateColumn" minWidth="180"/> <TableColumn fx:id="dateColumn" minWidth="180"/>
<TableColumn fx:id="marketColumn" minWidth="90"/> <TableColumn fx:id="marketColumn" minWidth="75"/>
<TableColumn fx:id="priceColumn" minWidth="150"/> <TableColumn fx:id="priceColumn" minWidth="110"/>
<TableColumn fx:id="amountColumn" minWidth="150"/> <TableColumn fx:id="amountColumn" minWidth="100"/>
<TableColumn fx:id="volumeColumn" minWidth="170"/> <TableColumn fx:id="volumeColumn" minWidth="150"/>
<TableColumn fx:id="paymentMethodColumn" minWidth="120" maxWidth="170"/>
<TableColumn fx:id="directionColumn" minWidth="80"/> <TableColumn fx:id="directionColumn" minWidth="80"/>
<TableColumn fx:id="deactivateItemColumn" minWidth="100" maxWidth="100" sortable="false"/> <TableColumn fx:id="deactivateItemColumn" minWidth="60" maxWidth="60" sortable="false"/>
<TableColumn fx:id="editItemColumn" minWidth="60" maxWidth="60" sortable="false"/> <TableColumn fx:id="editItemColumn" minWidth="50" maxWidth="60" sortable="false"/>
<TableColumn fx:id="removeItemColumn" minWidth="60" maxWidth="60" sortable="false"/> <TableColumn fx:id="removeItemColumn" minWidth="50" maxWidth="60" sortable="false"/>
</columns> </columns>
</TableView> </TableView>

View File

@ -69,7 +69,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
@FXML @FXML
TableColumn<OpenOfferListItem, OpenOfferListItem> priceColumn, amountColumn, volumeColumn, TableColumn<OpenOfferListItem, OpenOfferListItem> priceColumn, amountColumn, volumeColumn,
marketColumn, directionColumn, dateColumn, offerIdColumn, deactivateItemColumn, marketColumn, directionColumn, dateColumn, offerIdColumn, deactivateItemColumn,
removeItemColumn, editItemColumn; removeItemColumn, editItemColumn, paymentMethodColumn;
private final Navigation navigation; private final Navigation navigation;
private final OfferDetailsWindow offerDetailsWindow; private final OfferDetailsWindow offerDetailsWindow;
private SortedList<OpenOfferListItem> sortedList; private SortedList<OpenOfferListItem> sortedList;
@ -84,6 +84,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
@Override @Override
public void initialize() { public void initialize() {
paymentMethodColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.paymentMethod")));
priceColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.price"))); priceColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.price")));
amountColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.BTCMinMax"))); amountColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.BTCMinMax")));
volumeColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amountMinMax"))); volumeColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amountMinMax")));
@ -101,6 +102,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
setPriceColumnCellFactory(); setPriceColumnCellFactory();
setAmountColumnCellFactory(); setAmountColumnCellFactory();
setVolumeColumnCellFactory(); setVolumeColumnCellFactory();
setPaymentMethodColumnCellFactory();
setDateColumnCellFactory(); setDateColumnCellFactory();
setDeactivateColumnCellFactory(); setDeactivateColumnCellFactory();
setEditColumnCellFactory(); setEditColumnCellFactory();
@ -116,6 +118,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
priceColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPrice(), Comparator.nullsFirst(Comparator.naturalOrder()))); priceColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPrice(), Comparator.nullsFirst(Comparator.naturalOrder())));
volumeColumn.setComparator(Comparator.comparing(o -> o.getOffer().getVolume(), Comparator.nullsFirst(Comparator.naturalOrder()))); volumeColumn.setComparator(Comparator.comparing(o -> o.getOffer().getVolume(), Comparator.nullsFirst(Comparator.naturalOrder())));
dateColumn.setComparator(Comparator.comparing(o -> o.getOffer().getDate())); dateColumn.setComparator(Comparator.comparing(o -> o.getOffer().getDate()));
paymentMethodColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPaymentMethod().getId()));
dateColumn.setSortType(TableColumn.SortType.DESCENDING); dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn); tableView.getSortOrder().add(dateColumn);
@ -330,6 +333,31 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
}); });
} }
private void setPaymentMethodColumnCellFactory() {
paymentMethodColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
paymentMethodColumn.setCellFactory(
new Callback<>() {
@Override
public TableCell<OpenOfferListItem, OpenOfferListItem> call(
TableColumn<OpenOfferListItem, OpenOfferListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final OpenOfferListItem item, boolean empty) {
super.updateItem(item, empty);
getStyleClass().removeAll("offer-disabled");
if (item != null) {
if (model.isDeactivated(item)) getStyleClass().add("offer-disabled");
setGraphic(new AutoTooltipLabel(model.getPaymentMethod(item)));
} else {
setGraphic(null);
}
}
};
}
});
}
private void setDirectionColumnCellFactory() { private void setDirectionColumnCellFactory() {
directionColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); directionColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
directionColumn.setCellFactory( directionColumn.setCellFactory(

View File

@ -42,6 +42,8 @@ import javax.inject.Named;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import static com.google.common.base.Preconditions.checkNotNull;
class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel> implements ViewModel { class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel> implements ViewModel {
private final P2PService p2PService; private final P2PService p2PService;
private final CoinFormatter btcFormatter; private final CoinFormatter btcFormatter;
@ -118,6 +120,17 @@ class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel>
return CurrencyUtil.getCurrencyPair(item.getOffer().getCurrencyCode()); return CurrencyUtil.getCurrencyPair(item.getOffer().getCurrencyCode());
} }
String getPaymentMethod(OpenOfferListItem item) {
String result = "";
if (item != null) {
Offer offer = item.getOffer();
checkNotNull(offer);
checkNotNull(offer.getPaymentMethod());
result = offer.getPaymentMethodNameWithCountryCode();
}
return result;
}
String getDate(OpenOfferListItem item) { String getDate(OpenOfferListItem item) {
return DisplayUtils.formatDateTime(item.getOffer().getDate()); return DisplayUtils.formatDateTime(item.getOffer().getDate());
} }

View File

@ -442,8 +442,26 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
// Delay display to next render frame to avoid that the popup is first quickly displayed in default position // Delay display to next render frame to avoid that the popup is first quickly displayed in default position
// and after a short moment in the correct position // and after a short moment in the correct position
UserThread.execute(() -> chatPopupStage.setOpacity(1)); UserThread.execute(() -> chatPopupStage.setOpacity(1));
updateChatMessageCount(trade, badgeByTrade.get(trade.getId()));
} }
private void updateChatMessageCount(Trade trade, JFXBadge badge) {
if (!trade.getId().equals(tradeIdOfOpenChat)) {
updateNewChatMessagesByTradeMap();
long num = newChatMessagesByTradeMap.get(trade.getId());
if (num > 0) {
badge.setText(String.valueOf(num));
badge.setEnabled(true);
} else {
badge.setText("");
badge.setEnabled(false);
}
} else {
badge.setText("");
badge.setEnabled(false);
}
badge.refreshBadge();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Private // Private
@ -729,17 +747,17 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
} }
button.setOnAction(e -> { button.setOnAction(e -> {
tableView.getSelectionModel().select(this.getIndex());
openChat(trade); openChat(trade);
update(trade, badge);
}); });
if (!listenerByTrade.containsKey(id)) { if (!listenerByTrade.containsKey(id)) {
ListChangeListener<ChatMessage> listener = c -> update(trade, badge); ListChangeListener<ChatMessage> listener = c -> updateChatMessageCount(trade, badge);
listenerByTrade.put(id, listener); listenerByTrade.put(id, listener);
trade.getChatMessages().addListener(listener); trade.getChatMessages().addListener(listener);
} }
update(trade, badge); updateChatMessageCount(trade, badge);
setGraphic(badge); setGraphic(badge);
} else { } else {
@ -747,23 +765,6 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
} }
} }
private void update(Trade trade, JFXBadge badge) {
if (!trade.getId().equals(tradeIdOfOpenChat)) {
updateNewChatMessagesByTradeMap();
long num = newChatMessagesByTradeMap.get(trade.getId());
if (num > 0) {
badge.setText(String.valueOf(num));
badge.setEnabled(true);
} else {
badge.setText("");
badge.setEnabled(false);
}
} else {
badge.setText("");
badge.setEnabled(false);
}
badge.refreshBadge();
}
}; };
} }
}); });

View File

@ -276,13 +276,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
Offer offer = item.getTrade().getOffer(); Offer offer = item.getTrade().getOffer();
checkNotNull(offer); checkNotNull(offer);
checkNotNull(offer.getPaymentMethod()); checkNotNull(offer.getPaymentMethod());
String method = Res.get(offer.getPaymentMethod().getId() + "_SHORT"); result = offer.getPaymentMethodNameWithCountryCode();
String methodCountryCode = offer.getCountryCode();
if (methodCountryCode != null)
result = method + " (" + methodCountryCode + ")";
else
result = method;
} }
return result; return result;
} }

View File

@ -135,55 +135,6 @@
-bs-chart-dao-line2: -bs-color-blue-2; -bs-chart-dao-line2: -bs-color-blue-2;
} }
/* list view */
.list-view .list-cell {
-fx-background-color: -bs-background-color;
}
.list-view .list-cell:odd {
-fx-background-color: derive(-bs-background-color, -5%);
}
.list-view .list-cell:even {
-fx-background-color: derive(-bs-background-color, 5%);
}
.list-view .list-cell:selected,
.table-view .table-cell:selected {
-fx-background: -fx-accent;
-fx-background-color: -fx-selection-bar;
-fx-border-color: -fx-selection-bar;
}
.number-column.table-cell {
-fx-background-color: -bs-background-color;
}
/* table view */
.table-view, .table-cell:focused, .table-row-cell {
-fx-background: transparent;
}
.table-view .table-row-cell:even .table-cell {
-fx-background-color: derive(-bs-background-color, 5%);
-fx-border-color: derive(-bs-background-color,5%);
}
.table-view .table-row-cell:odd .table-cell {
-fx-background-color: derive(-bs-background-color,-5%);
-fx-border-color: derive(-bs-background-color,-5%);
}
.table-view .table-row-cell:selected .table-cell {
-fx-background: -fx-accent;
-fx-background-color: -fx-selection-bar;
-fx-border-color: -fx-selection-bar;
}
.table-row-cell {
-fx-border-color: -bs-background-color;
}
.table-row-cell:empty, .table-row-cell:empty:even, .table-row-cell:empty:odd {
-fx-background-color: -bs-background-color;
-fx-min-height: 36;
}
.offer-table .table-row-cell {
-fx-background: -fx-accent;
-fx-background-color: -bs-color-gray-6;
}
/* tab pane */ /* tab pane */
.jfx-tab-pane .tab-content-area { .jfx-tab-pane .tab-content-area {

View File

@ -1129,6 +1129,7 @@ public class GUIUtil {
RegexValidator regexValidator = new RegexValidator(); RegexValidator regexValidator = new RegexValidator();
String portRegexPattern = "(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])"; String portRegexPattern = "(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])";
String onionV2RegexPattern = String.format("[a-zA-Z2-7]{16}\\.onion(?:\\:%1$s)?", portRegexPattern); String onionV2RegexPattern = String.format("[a-zA-Z2-7]{16}\\.onion(?:\\:%1$s)?", portRegexPattern);
String onionV3RegexPattern = String.format("[a-zA-Z2-7]{56}\\.onion(?:\\:%1$s)?", portRegexPattern);
String ipv4RegexPattern = String.format("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}" + String ipv4RegexPattern = String.format("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}" +
"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" +
"(?:\\:%1$s)?", portRegexPattern); "(?:\\:%1$s)?", portRegexPattern);
@ -1152,8 +1153,8 @@ public class GUIUtil {
")"; // (IPv4-Embedded IPv6 Address) ")"; // (IPv4-Embedded IPv6 Address)
ipv6RegexPattern = String.format("(?:%1$s)|(?:\\[%1$s\\]\\:%2$s)", ipv6RegexPattern, portRegexPattern); ipv6RegexPattern = String.format("(?:%1$s)|(?:\\[%1$s\\]\\:%2$s)", ipv6RegexPattern, portRegexPattern);
String fqdnRegexPattern = String.format("(((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\\.)+(?!onion)[a-zA-Z]{2,63}(?:\\:%1$s)?)", portRegexPattern); String fqdnRegexPattern = String.format("(((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\\.)+(?!onion)[a-zA-Z]{2,63}(?:\\:%1$s)?)", portRegexPattern);
regexValidator.setPattern(String.format("^(?:(?:(?:%1$s)|(?:%2$s)|(?:%3$s)|(?:%4$s)),\\s*)*(?:(?:%1$s)|(?:%2$s)|(?:%3$s)|(?:%4$s))*$", regexValidator.setPattern(String.format("^(?:(?:(?:%1$s)|(?:%2$s)|(?:%3$s)|(?:%4$s)|(?:%5$s)),\\s*)*(?:(?:%1$s)|(?:%2$s)|(?:%3$s)|(?:%4$s)|(?:%5$s))*$",
onionV2RegexPattern, ipv4RegexPattern, ipv6RegexPattern, fqdnRegexPattern)); onionV2RegexPattern, onionV3RegexPattern, ipv4RegexPattern, ipv6RegexPattern, fqdnRegexPattern));
return regexValidator; return regexValidator;
} }
} }

View File

@ -141,7 +141,8 @@ public class GUIUtilTest {
assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid); assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid);
// onion v3 addresses // onion v3 addresses
assertFalse(regexValidator.validate("32zzibxmqi2ybxpqyggwwuwz7a3lbvtzoloti7cxoevyvijexvgsfeid.onion:8333").isValid); assertFalse(regexValidator.validate("32zzibxmqi2ybxpqyggwwuwz7a3lbvtzoloti7cxoevyvijexvgsfei.onion:8333").isValid); // 1 missing char
assertTrue(regexValidator.validate("wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000").isValid);
// ipv4 addresses // ipv4 addresses
assertTrue(regexValidator.validate("12.34.56.78").isValid); assertTrue(regexValidator.validate("12.34.56.78").isValid);

View File

@ -22,7 +22,7 @@ You do _not_ need to install Gradle to complete the following command. The `grad
./gradlew build ./gradlew build
If on Windows run `gradlew.bat build` instead. If on Windows run `gradlew.bat build` instead.
If in need to install JAVA check out - https://github.com/bisq-network/bisq/tree/master/scripts
## Run ## Run
@ -33,7 +33,7 @@ Note: bisq runs fine on jdk10 and jdk11. jdk12 is currently not supported.
./bisq-desktop ./bisq-desktop
If on Windows use the `bisq-desktop.bat` script instead. If on Windows use the `bisq-desktop.bat` script instead.
If in need to install JAVA checkout the install_java scripts at https://github.com/bisq-network/bisq/tree/master/scripts
## See also ## See also

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -72,7 +72,7 @@ curl http://localhost:8080/info
If you run a main pricenode, you also are obliged to activate the monitoring feed by running If you run a main pricenode, you also are obliged to activate the monitoring feed by running
```bash ```bash
curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/monitor/install_collectd_debian.sh | sudo bash bash <(curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/monitor/install_collectd_debian.sh)
``` ```
Follow the instruction given by the script and report your certificate to the [@bisq-network/monitoring](https://github.com/orgs/bisq-network/teams/monitoring-operators) team or via the [Keybase](https://keybase.io/team/bisq) `#monitoring` channel! Follow the instruction given by the script and report your certificate to the [@bisq-network/monitoring](https://github.com/orgs/bisq-network/teams/monitoring-operators) team or via the [Keybase](https://keybase.io/team/bisq) `#monitoring` channel!
@ -81,6 +81,12 @@ Furthermore, you are obliged to provide network size data to the monitor by runn
curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/pricenode/install_networksize_debian.sh | sudo bash curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/pricenode/install_networksize_debian.sh | sudo bash
``` ```
### Updating
Update your bisq code in /bisq/bisq with ```git pull```
Then build an updated pricenode:
```./gradlew :pricenode:installDist -x test```
## How to deploy elsewhere ## How to deploy elsewhere

View File

@ -34,6 +34,11 @@ echo "[*] Upgrading apt packages"
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get update -q sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get update -q
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get upgrade -qq -y sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get upgrade -qq -y
echo "[*] Installing Git LFS"
sudo -H -i -u "${ROOT_USER}" curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
sudo -H -i -u "${ROOT_USER}" apt-get install git-lfs
sudo -H -i -u "${ROOT_USER}" git lfs install
echo "[*] Installing Tor" echo "[*] Installing Tor"
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get install -qq -y "${TOR_PKG}" sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get install -qq -y "${TOR_PKG}"
@ -61,6 +66,9 @@ sudo -H -i -u "${ROOT_USER}" "${BISQ_HOME}/${BISQ_REPO_NAME}/scripts/install_jav
echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}" echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}"
sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}" sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}"
echo "[*] Performing Git LFS pull"
sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git lfs pull"
echo "[*] Building Bisq from source" echo "[*] Building Bisq from source"
sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && ./gradlew :pricenode:installDist -x test < /dev/null" # redirect from /dev/null is necessary to workaround gradlew non-interactive shell hanging issue sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && ./gradlew :pricenode:installDist -x test < /dev/null" # redirect from /dev/null is necessary to workaround gradlew non-interactive shell hanging issue

View File

@ -39,6 +39,88 @@ message GetVersionReply {
string version = 1; string version = 1;
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Offers
///////////////////////////////////////////////////////////////////////////////////////////
service Offers {
rpc GetOffers (GetOffersRequest) returns (GetOffersReply) {
}
rpc CreateOffer (CreateOfferRequest) returns (CreateOfferReply) {
}
}
message GetOffersRequest {
string direction = 1;
string fiatCurrencyCode = 2;
}
message GetOffersReply {
repeated OfferInfo offers = 1;
}
message CreateOfferRequest {
string currencyCode = 1; // TODO switch order w/ direction field in next PR
string direction = 2;
uint64 price = 3;
bool useMarketBasedPrice = 4;
double marketPriceMargin = 5;
uint64 amount = 6;
uint64 minAmount = 7;
double buyerSecurityDeposit = 8;
string paymentAccountId = 9;
}
message CreateOfferReply {
bool result = 1;
}
message OfferInfo {
string id = 1;
string direction = 2;
uint64 price = 3;
bool useMarketBasedPrice = 4;
double marketPriceMargin = 5;
uint64 amount = 6;
uint64 minAmount = 7;
uint64 volume = 8;
uint64 minVolume = 9;
uint64 buyerSecurityDeposit = 10;
string paymentAccountId = 11; // only used when creating offer
string paymentMethodId = 12;
string paymentMethodShortName = 13;
string baseCurrencyCode = 14;
string counterCurrencyCode = 15;
uint64 date = 16;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PaymentAccounts
///////////////////////////////////////////////////////////////////////////////////////////
service PaymentAccounts {
rpc CreatePaymentAccount (CreatePaymentAccountRequest) returns (CreatePaymentAccountReply) {
}
rpc GetPaymentAccounts (GetPaymentAccountsRequest) returns (GetPaymentAccountsReply) {
}
}
message CreatePaymentAccountRequest {
string accountName = 1;
string accountNumber = 2;
string fiatCurrencyCode = 3;
}
message CreatePaymentAccountReply {
}
message GetPaymentAccountsRequest {
}
message GetPaymentAccountsReply {
repeated PaymentAccount paymentAccounts = 1;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// TradeStatistics // TradeStatistics
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -56,69 +138,16 @@ message GetTradeStatisticsReply {
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Offer // Wallets
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
service GetOffers { service Wallets {
rpc GetOffers (GetOffersRequest) returns (GetOffersReply) {
}
}
message GetOffersRequest {
}
message GetOffersReply {
repeated Offer offers = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PaymentAccount
///////////////////////////////////////////////////////////////////////////////////////////
service GetPaymentAccounts {
rpc GetPaymentAccounts (GetPaymentAccountsRequest) returns (GetPaymentAccountsReply) {
}
}
message GetPaymentAccountsRequest {
}
message GetPaymentAccountsReply {
repeated PaymentAccount paymentAccounts = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PlaceOffer
///////////////////////////////////////////////////////////////////////////////////////////
service PlaceOffer {
rpc PlaceOffer (PlaceOfferRequest) returns (PlaceOfferReply) {
}
}
message PlaceOfferRequest {
string currencyCode = 1;
string direction = 2;
uint64 price = 3;
bool useMarketBasedPrice = 4;
double marketPriceMargin = 5;
uint64 amount = 6;
uint64 minAmount = 7;
double buyerSecurityDeposit = 8;
string paymentAccountId = 9;
}
message PlaceOfferReply {
bool result = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Wallet
///////////////////////////////////////////////////////////////////////////////////////////
service Wallet {
rpc GetBalance (GetBalanceRequest) returns (GetBalanceReply) { rpc GetBalance (GetBalanceRequest) returns (GetBalanceReply) {
} }
rpc GetAddressBalance (GetAddressBalanceRequest) returns (GetAddressBalanceReply) {
}
rpc GetFundingAddresses (GetFundingAddressesRequest) returns (GetFundingAddressesReply) {
}
rpc SetWalletPassword (SetWalletPasswordRequest) returns (SetWalletPasswordReply) { rpc SetWalletPassword (SetWalletPasswordRequest) returns (SetWalletPasswordReply) {
} }
rpc RemoveWalletPassword (RemoveWalletPasswordRequest) returns (RemoveWalletPasswordReply) { rpc RemoveWalletPassword (RemoveWalletPasswordRequest) returns (RemoveWalletPasswordReply) {
@ -136,6 +165,21 @@ message GetBalanceReply {
uint64 balance = 1; uint64 balance = 1;
} }
message GetAddressBalanceRequest {
string address = 1;
}
message GetAddressBalanceReply {
AddressBalanceInfo addressBalanceInfo = 1;
}
message GetFundingAddressesRequest {
}
message GetFundingAddressesReply {
repeated AddressBalanceInfo addressBalanceInfo = 1;
}
message SetWalletPasswordRequest { message SetWalletPasswordRequest {
string password = 1; string password = 1;
string newPassword = 2; string newPassword = 2;
@ -164,3 +208,9 @@ message UnlockWalletRequest {
message UnlockWalletReply { message UnlockWalletReply {
} }
message AddressBalanceInfo {
string address = 1;
int64 balance = 2;
int64 numConfirmations = 3;
}

View File

@ -629,6 +629,7 @@ message Filter {
repeated string mediators = 17; repeated string mediators = 17;
repeated string refundAgents = 18; repeated string refundAgents = 18;
repeated string bannedSignerPubKeys = 19; repeated string bannedSignerPubKeys = 19;
repeated string btc_fee_receiver_addresses = 20;
} }
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older // not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older

View File

@ -1 +1 @@
1.3.4-SNAPSHOT 1.3.5-SNAPSHOT

View File

@ -10,14 +10,15 @@ Highly recommended to use SSD! Minimum specs:
## Software ## Software
The following OS are known to work well: The following OS's are known to work well:
* Ubuntu 18 * Ubuntu 18.04
* Ubuntu 20.04
* FreeBSD 12 * FreeBSD 12
### Installation ### Installation
Start with a clean Ubuntu 18.04 LTS server installation, and run the script Start with a clean Ubuntu server installation, and run the script
```bash ```bash
curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/seednode/install_seednode_debian.sh | sudo bash curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/seednode/install_seednode_debian.sh | sudo bash
``` ```
@ -66,7 +67,7 @@ macOS:
If you run a main seednode, you also are obliged to activate the monitoring feed by running If you run a main seednode, you also are obliged to activate the monitoring feed by running
```bash ```bash
curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/monitor/install_collectd_debian.sh | sudo bash bash <(curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/monitor/install_collectd_debian.sh)
``` ```
Follow the instruction given by the script and report your certificate to the seednode group! Follow the instruction given by the script and report your certificate to the seednode group!
@ -78,8 +79,7 @@ sudo -u bisq -s
cd bisq cd bisq
git fetch origin git fetch origin
git checkout v1.2.5 # new tag git checkout v1.2.5 # new tag
./gradlew clean ./gradlew clean build -x test
./gradlew build -x test
exit exit
sudo service bisq restart sudo service bisq restart
sudo journalctl --unit bisq --follow sudo journalctl --unit bisq --follow

View File

@ -60,6 +60,11 @@ sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get upgrade -qq
echo "[*] Installing base packages" echo "[*] Installing base packages"
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get install -qq -y ${ROOT_PKG} sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get install -qq -y ${ROOT_PKG}
echo "[*] Installing Git LFS"
sudo -H -i -u "${ROOT_USER}" curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
sudo -H -i -u "${ROOT_USER}" apt-get install git-lfs
sudo -H -i -u "${ROOT_USER}" git lfs install
echo "[*] Cloning Bisq repo" echo "[*] Cloning Bisq repo"
sudo -H -i -u "${ROOT_USER}" git config --global advice.detachedHead false sudo -H -i -u "${ROOT_USER}" git config --global advice.detachedHead false
sudo -H -i -u "${ROOT_USER}" git clone --branch "${BISQ_REPO_TAG}" "${BISQ_REPO_URL}" "${ROOT_HOME}/${BISQ_REPO_NAME}" sudo -H -i -u "${ROOT_USER}" git clone --branch "${BISQ_REPO_TAG}" "${BISQ_REPO_URL}" "${ROOT_HOME}/${BISQ_REPO_NAME}"
@ -145,6 +150,9 @@ sudo sed -i -e "s!__BISQ_HOME__!${BISQ_HOME}!" "${SYSTEMD_ENV_HOME}/bisq.env"
echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}" echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}"
sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}" sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}"
echo "[*] Performing Git LFS pull"
sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git lfs pull"
echo "[*] Building Bisq from source" echo "[*] Building Bisq from source"
sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && ./gradlew build -x test < /dev/null" # redirect from /dev/null is necessary to workaround gradlew non-interactive shell hanging issue sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && ./gradlew build -x test < /dev/null" # redirect from /dev/null is necessary to workaround gradlew non-interactive shell hanging issue

View File

@ -30,7 +30,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class SeedNodeMain extends ExecutableForAppWithP2p { public class SeedNodeMain extends ExecutableForAppWithP2p {
private static final String VERSION = "1.3.4"; private static final String VERSION = "1.3.5";
private SeedNode seedNode; private SeedNode seedNode;
public SeedNodeMain() { public SeedNodeMain() {