mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 01:41:11 +01:00
Merge remote-tracking branch 'origin/master' into HEAD
This commit is contained in:
commit
fabd592938
@ -1,8 +1,6 @@
|
||||
# This doc specifies who gets requested to review GitHub pull requests.
|
||||
# See https://help.github.com/articles/about-codeowners/.
|
||||
|
||||
*gradle* @cbeams
|
||||
/pricenode/ @cbeams
|
||||
/core/main/java/bisq/core/dao/ @ManfredKarrer
|
||||
|
||||
# For seednode configuration changes
|
||||
|
@ -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)
|
||||
- [developer docs](docs#readme) including build and dev environment setup instructions
|
||||
- [project management process](https://bisq.wiki/Project_management)
|
||||
|
||||
|
@ -378,7 +378,7 @@ configure(project(':desktop')) {
|
||||
apply plugin: 'witness'
|
||||
apply from: '../gradle/witness/gradle-witness.gradle'
|
||||
|
||||
version = '1.3.4-SNAPSHOT'
|
||||
version = '1.3.5-SNAPSHOT'
|
||||
|
||||
mainClassName = 'bisq.desktop.app.BisqAppMain'
|
||||
|
||||
|
@ -17,14 +17,21 @@
|
||||
|
||||
package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.CreatePaymentAccountRequest;
|
||||
import bisq.proto.grpc.GetAddressBalanceRequest;
|
||||
import bisq.proto.grpc.GetBalanceRequest;
|
||||
import bisq.proto.grpc.GetFundingAddressesRequest;
|
||||
import bisq.proto.grpc.GetOffersRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||
import bisq.proto.grpc.GetVersionGrpc;
|
||||
import bisq.proto.grpc.GetVersionRequest;
|
||||
import bisq.proto.grpc.LockWalletRequest;
|
||||
import bisq.proto.grpc.OffersGrpc;
|
||||
import bisq.proto.grpc.PaymentAccountsGrpc;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.WalletGrpc;
|
||||
import bisq.proto.grpc.WalletsGrpc;
|
||||
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
@ -32,32 +39,39 @@ import io.grpc.StatusRuntimeException;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.cli.CurrencyFormat.formatSatoshis;
|
||||
import static bisq.cli.TableFormat.formatAddressBalanceTbl;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.cli.TableFormat.formatPaymentAcctTbl;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.err;
|
||||
import static java.lang.System.exit;
|
||||
import static java.lang.System.out;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
/**
|
||||
* A command-line client for the Bisq gRPC API.
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@Slf4j
|
||||
public class CliMain {
|
||||
|
||||
private enum Method {
|
||||
getoffers,
|
||||
createpaymentacct,
|
||||
getpaymentaccts,
|
||||
getversion,
|
||||
getbalance,
|
||||
getaddressbalance,
|
||||
getfundingaddresses,
|
||||
lockwallet,
|
||||
unlockwallet,
|
||||
removewalletpassword,
|
||||
@ -131,7 +145,9 @@ public class CliMain {
|
||||
}));
|
||||
|
||||
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 {
|
||||
switch (method) {
|
||||
@ -143,18 +159,68 @@ public class CliMain {
|
||||
}
|
||||
case getbalance: {
|
||||
var request = GetBalanceRequest.newBuilder().build();
|
||||
var reply = walletService.getBalance(request);
|
||||
var satoshiBalance = reply.getBalance();
|
||||
var satoshiDivisor = new BigDecimal(100000000);
|
||||
var btcFormat = new DecimalFormat("###,##0.00000000");
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
var btcBalance = btcFormat.format(BigDecimal.valueOf(satoshiBalance).divide(satoshiDivisor));
|
||||
var reply = walletsService.getBalance(request);
|
||||
var btcBalance = formatSatoshis(reply.getBalance());
|
||||
out.println(btcBalance);
|
||||
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: {
|
||||
var request = LockWalletRequest.newBuilder().build();
|
||||
walletService.lockWallet(request);
|
||||
walletsService.lockWallet(request);
|
||||
out.println("wallet locked");
|
||||
return;
|
||||
}
|
||||
@ -174,7 +240,7 @@ public class CliMain {
|
||||
var request = UnlockWalletRequest.newBuilder()
|
||||
.setPassword(nonOptionArgs.get(1))
|
||||
.setTimeout(timeout).build();
|
||||
walletService.unlockWallet(request);
|
||||
walletsService.unlockWallet(request);
|
||||
out.println("wallet unlocked");
|
||||
return;
|
||||
}
|
||||
@ -183,7 +249,7 @@ public class CliMain {
|
||||
throw new IllegalArgumentException("no password specified");
|
||||
|
||||
var request = RemoveWalletPasswordRequest.newBuilder().setPassword(nonOptionArgs.get(1)).build();
|
||||
walletService.removeWalletPassword(request);
|
||||
walletsService.removeWalletPassword(request);
|
||||
out.println("wallet decrypted");
|
||||
return;
|
||||
}
|
||||
@ -195,13 +261,13 @@ public class CliMain {
|
||||
var hasNewPassword = nonOptionArgs.size() == 3;
|
||||
if (hasNewPassword)
|
||||
requestBuilder.setNewPassword(nonOptionArgs.get(2));
|
||||
walletService.setWalletPassword(requestBuilder.build());
|
||||
walletsService.setWalletPassword(requestBuilder.build());
|
||||
out.println("wallet encrypted" + (hasNewPassword ? " with new password" : ""));
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
throw new RuntimeException(format("unhandled method '%s'", method));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (StatusRuntimeException ex) {
|
||||
// Remove the leading gRPC status code (e.g. "UNKNOWN: ") from the message
|
||||
@ -218,14 +284,19 @@ public class CliMain {
|
||||
stream.println();
|
||||
parser.printHelpOn(stream);
|
||||
stream.println();
|
||||
stream.format("%-19s%-30s%s%n", "Method", "Params", "Description");
|
||||
stream.format("%-19s%-30s%s%n", "------", "------", "------------");
|
||||
stream.format("%-19s%-30s%s%n", "getversion", "", "Get server version");
|
||||
stream.format("%-19s%-30s%s%n", "getbalance", "", "Get server wallet balance");
|
||||
stream.format("%-19s%-30s%s%n", "lockwallet", "", "Remove wallet password from memory, locking the wallet");
|
||||
stream.format("%-19s%-30s%s%n", "unlockwallet", "password timeout",
|
||||
stream.format("%-22s%-50s%s%n", "Method", "Params", "Description");
|
||||
stream.format("%-22s%-50s%s%n", "------", "------", "------------");
|
||||
stream.format("%-22s%-50s%s%n", "getversion", "", "Get server version");
|
||||
stream.format("%-22s%-50s%s%n", "getbalance", "", "Get server wallet balance");
|
||||
stream.format("%-22s%-50s%s%n", "getaddressbalance", "address", "Get server wallet address balance");
|
||||
stream.format("%-22s%-50s%s%n", "getfundingaddresses", "", "Get BTC funding addresses");
|
||||
stream.format("%-22s%-50s%s%n", "getoffers", "buy | sell, fiat currency code", "Get current offers");
|
||||
stream.format("%-22s%-50s%s%n", "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account");
|
||||
stream.format("%-22s%-50s%s%n", "getpaymentaccts", "", "Get user payment accounts");
|
||||
stream.format("%-22s%-50s%s%n", "lockwallet", "", "Remove wallet password from memory, locking the wallet");
|
||||
stream.format("%-22s%-50s%s%n", "unlockwallet", "password timeout",
|
||||
"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");
|
||||
stream.println();
|
||||
} catch (IOException ex) {
|
||||
|
47
cli/src/main/java/bisq/cli/CurrencyFormat.java
Normal file
47
cli/src/main/java/bisq/cli/CurrencyFormat.java
Normal 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);
|
||||
}
|
||||
}
|
@ -1,3 +1,20 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli;
|
||||
|
||||
import io.grpc.CallCredentials;
|
||||
|
144
cli/src/main/java/bisq/cli/TableFormat.java
Normal file
144
cli/src/main/java/bisq/cli/TableFormat.java
Normal 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));
|
||||
}
|
||||
}
|
39
cli/src/test/java/bisq/cli/GetOffersSmokeTest.java
Normal file
39
cli/src/test/java/bisq/cli/GetOffersSmokeTest.java
Normal 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"});
|
||||
}
|
||||
|
||||
}
|
150
cli/test.sh
150
cli/test.sh
@ -48,28 +48,168 @@
|
||||
run ./bisq-cli --password="xyz" getversion
|
||||
[ "$status" -eq 0 ]
|
||||
echo "actual output: $output" >&2
|
||||
[ "$output" = "1.3.2" ]
|
||||
[ "$output" = "1.3.4" ]
|
||||
}
|
||||
|
||||
@test "test getversion" {
|
||||
run ./bisq-cli --password=xyz getversion
|
||||
[ "$status" -eq 0 ]
|
||||
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
|
||||
[ "$status" -eq 0 ]
|
||||
echo "actual output: $output" >&2
|
||||
[ "$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" {
|
||||
run ./bisq-cli
|
||||
[ "$status" -eq 1 ]
|
||||
[ "${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
|
||||
}
|
||||
|
||||
@ -77,6 +217,6 @@
|
||||
run ./bisq-cli --help
|
||||
[ "$status" -eq 0 ]
|
||||
[ "${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
|
||||
}
|
||||
|
@ -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
|
||||
// Therefore all sub versions start again with 1
|
||||
// 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) {
|
||||
return getSubVersion(version, 0);
|
||||
|
@ -262,6 +262,12 @@ public class SignedWitnessService {
|
||||
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
|
||||
public void signAccountAgeWitness(Coin tradeAmount,
|
||||
AccountAgeWitness accountAgeWitness,
|
||||
|
@ -185,17 +185,23 @@ public class AccountAgeWitnessService {
|
||||
});
|
||||
|
||||
if (p2PService.isBootstrapped()) {
|
||||
republishAllFiatAccounts();
|
||||
onBootStrapped();
|
||||
} else {
|
||||
p2PService.addP2PServiceListener(new BootstrapListener() {
|
||||
@Override
|
||||
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.
|
||||
private void republishAllFiatAccounts() {
|
||||
if (user.getPaymentAccounts() != null)
|
||||
@ -814,6 +820,30 @@ public class AccountAgeWitnessService {
|
||||
.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() {
|
||||
return signedWitnessService.getUnsignedSignerPubKeys();
|
||||
}
|
||||
|
@ -75,9 +75,10 @@ public class BtcNodes {
|
||||
new BtcNode("btc.bisq.cc", "4nnuyxm5k5tlyjq3.onion", "167.71.168.194", BtcNode.DEFAULT_PORT, "@m52go"),
|
||||
|
||||
// wiz
|
||||
new BtcNode("node100.wiz.network", "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.wiz.network", "jiuuuislm7ooesic.onion", "103.99.168.140", BtcNode.DEFAULT_PORT, "@wiz"),
|
||||
new BtcNode("node100.hnl.wiz.biz", "m3yqzythryowgedc.onion", "103.99.168.100", BtcNode.DEFAULT_PORT, "@wiz"),
|
||||
new BtcNode("node140.hnl.wiz.biz", "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
|
||||
new BtcNode(null, "2pj2o2mrawj7yotg.onion", null, BtcNode.DEFAULT_PORT, "@robkaandorp") // cannot provide IP because no static IP
|
||||
|
@ -105,6 +105,10 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
@Nullable
|
||||
private final List<String> bannedSignerPubKeys;
|
||||
|
||||
// added in v1.3.2
|
||||
@Nullable
|
||||
private final List<String> btcFeeReceiverAddresses;
|
||||
|
||||
public Filter(List<String> bannedOfferIds,
|
||||
List<String> bannedNodeAddress,
|
||||
List<PaymentAccountFilter> bannedPaymentAccounts,
|
||||
@ -120,7 +124,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
@Nullable String disableTradeBelowVersion,
|
||||
@Nullable List<String> mediators,
|
||||
@Nullable List<String> refundAgents,
|
||||
@Nullable List<String> bannedSignerPubKeys) {
|
||||
@Nullable List<String> bannedSignerPubKeys,
|
||||
@Nullable List<String> btcFeeReceiverAddresses) {
|
||||
this.bannedOfferIds = bannedOfferIds;
|
||||
this.bannedNodeAddress = bannedNodeAddress;
|
||||
this.bannedPaymentAccounts = bannedPaymentAccounts;
|
||||
@ -137,6 +142,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
this.mediators = mediators;
|
||||
this.refundAgents = refundAgents;
|
||||
this.bannedSignerPubKeys = bannedSignerPubKeys;
|
||||
this.btcFeeReceiverAddresses = btcFeeReceiverAddresses;
|
||||
}
|
||||
|
||||
|
||||
@ -163,7 +169,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
@Nullable Map<String, String> extraDataMap,
|
||||
@Nullable List<String> mediators,
|
||||
@Nullable List<String> refundAgents,
|
||||
@Nullable List<String> bannedSignerPubKeys) {
|
||||
@Nullable List<String> bannedSignerPubKeys,
|
||||
@Nullable List<String> btcFeeReceiverAddresses) {
|
||||
this(bannedOfferIds,
|
||||
bannedNodeAddress,
|
||||
bannedPaymentAccounts,
|
||||
@ -179,7 +186,8 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
disableTradeBelowVersion,
|
||||
mediators,
|
||||
refundAgents,
|
||||
bannedSignerPubKeys);
|
||||
bannedSignerPubKeys,
|
||||
btcFeeReceiverAddresses);
|
||||
this.signatureAsBase64 = signatureAsBase64;
|
||||
this.ownerPubKeyBytes = ownerPubKeyBytes;
|
||||
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
|
||||
@ -215,6 +223,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||
Optional.ofNullable(mediators).ifPresent(builder::addAllMediators);
|
||||
Optional.ofNullable(refundAgents).ifPresent(builder::addAllRefundAgents);
|
||||
Optional.ofNullable(bannedSignerPubKeys).ifPresent(builder::addAllBannedSignerPubKeys);
|
||||
Optional.ofNullable(btcFeeReceiverAddresses).ifPresent(builder::addAllBtcFeeReceiverAddresses);
|
||||
|
||||
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.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()),
|
||||
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 refundAgents=" + refundAgents +
|
||||
",\n bannedSignerPubKeys=" + bannedSignerPubKeys +
|
||||
",\n btcFeeReceiverAddresses=" + btcFeeReceiverAddresses +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
package bisq.core.filter;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitness;
|
||||
import bisq.core.btc.nodes.BtcNodes;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
@ -153,7 +152,7 @@ public class FilterManager {
|
||||
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof Filter) {
|
||||
Filter filter = (Filter) protectedStorageEntry.getProtectedStoragePayload();
|
||||
if (verifySignature(filter))
|
||||
if (verifySignature(filter) && getFilter().equals(filter))
|
||||
resetFilters();
|
||||
}
|
||||
});
|
||||
|
@ -17,17 +17,14 @@
|
||||
|
||||
package bisq.core.grpc;
|
||||
|
||||
import bisq.core.grpc.model.AddressBalanceInfo;
|
||||
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.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
|
||||
@ -47,61 +44,70 @@ import lombok.extern.slf4j.Slf4j;
|
||||
*/
|
||||
@Slf4j
|
||||
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 CreateOfferService createOfferService;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
private final User user;
|
||||
|
||||
@Inject
|
||||
public CoreApi(OfferBookService offerBookService,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
CreateOfferService createOfferService,
|
||||
OpenOfferManager openOfferManager,
|
||||
User user) {
|
||||
this.offerBookService = offerBookService;
|
||||
public CoreApi(CoreOffersService coreOffersService,
|
||||
CorePaymentAccountsService paymentAccountsService,
|
||||
CoreWalletsService walletsService,
|
||||
TradeStatisticsManager tradeStatisticsManager) {
|
||||
this.coreOffersService = coreOffersService;
|
||||
this.paymentAccountsService = paymentAccountsService;
|
||||
this.walletsService = walletsService;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.createOfferService = createOfferService;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
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() {
|
||||
return offerBookService.getOffers();
|
||||
public void createOffer(String currencyCode,
|
||||
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() {
|
||||
return user.getPaymentAccounts();
|
||||
}
|
||||
|
||||
public void placeOffer(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;
|
||||
|
||||
placeOffer(offerId,
|
||||
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) {
|
||||
coreOffersService.createOffer(offerId,
|
||||
currencyCode,
|
||||
direction,
|
||||
price,
|
||||
@ -115,34 +121,59 @@ public class CoreApi {
|
||||
resultHandler);
|
||||
}
|
||||
|
||||
public void placeOffer(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) {
|
||||
Offer offer = createOfferService.createAndGetOffer(offerId,
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
price,
|
||||
useMarketBasedPrice,
|
||||
marketPriceMargin,
|
||||
buyerSecurityDeposit,
|
||||
paymentAccount);
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PaymentAccounts
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
openOfferManager.placeOffer(offer,
|
||||
buyerSecurityDeposit,
|
||||
useSavingsWallet,
|
||||
resultHandler,
|
||||
log::error);
|
||||
public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) {
|
||||
paymentAccountsService.createPaymentAccount(accountName, accountNumber, fiatCurrencyCode);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
145
core/src/main/java/bisq/core/grpc/CoreOffersService.java
Normal file
145
core/src/main/java/bisq/core/grpc/CoreOffersService.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
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.grpc.model.AddressBalanceInfo;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
|
||||
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 java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
@Slf4j
|
||||
class CoreWalletService {
|
||||
class CoreWalletsService {
|
||||
|
||||
private final Balances balances;
|
||||
private final WalletsManager walletsManager;
|
||||
private final BtcWalletService btcWalletService;
|
||||
|
||||
@Nullable
|
||||
private TimerTask lockTask;
|
||||
@ -31,17 +63,17 @@ class CoreWalletService {
|
||||
private KeyParameter tempAesKey;
|
||||
|
||||
@Inject
|
||||
public CoreWalletService(Balances balances, WalletsManager walletsManager) {
|
||||
public CoreWalletsService(Balances balances,
|
||||
WalletsManager walletsManager,
|
||||
BtcWalletService btcWalletService) {
|
||||
this.balances = balances;
|
||||
this.walletsManager = walletsManager;
|
||||
this.btcWalletService = btcWalletService;
|
||||
}
|
||||
|
||||
public long getAvailableBalance() {
|
||||
if (!walletsManager.areWalletsAvailable())
|
||||
throw new IllegalStateException("wallet is not yet available");
|
||||
|
||||
if (walletsManager.areWalletsEncrypted() && tempAesKey == null)
|
||||
throw new IllegalStateException("wallet is locked");
|
||||
verifyWalletsAreAvailable();
|
||||
verifyEncryptedWalletIsUnlocked();
|
||||
|
||||
var balance = balances.getAvailableBalance().get();
|
||||
if (balance == null)
|
||||
@ -50,9 +82,61 @@ class CoreWalletService {
|
||||
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) {
|
||||
if (!walletsManager.areWalletsAvailable())
|
||||
throw new IllegalStateException("wallet is not yet available");
|
||||
verifyWalletsAreAvailable();
|
||||
|
||||
KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt();
|
||||
|
||||
@ -141,6 +225,12 @@ class CoreWalletService {
|
||||
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.
|
||||
private void verifyWalletIsAvailableAndEncrypted() {
|
||||
if (!walletsManager.areWalletsAvailable())
|
||||
@ -150,10 +240,42 @@ class CoreWalletService {
|
||||
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() {
|
||||
KeyCrypterScrypt keyCrypterScrypt = walletsManager.getKeyCrypterScrypt();
|
||||
if (keyCrypterScrypt == null)
|
||||
throw new IllegalStateException("wallet encrypter is not available");
|
||||
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));
|
||||
}
|
||||
}
|
105
core/src/main/java/bisq/core/grpc/GrpcOffersService.java
Normal file
105
core/src/main/java/bisq/core/grpc/GrpcOffersService.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -17,28 +17,16 @@
|
||||
|
||||
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.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.GetTradeStatisticsReply;
|
||||
import bisq.proto.grpc.GetTradeStatisticsRequest;
|
||||
import bisq.proto.grpc.GetVersionGrpc;
|
||||
import bisq.proto.grpc.GetVersionReply;
|
||||
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.ServerBuilder;
|
||||
@ -60,15 +48,18 @@ public class GrpcServer {
|
||||
private final Server server;
|
||||
|
||||
@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.server = ServerBuilder.forPort(config.apiPort)
|
||||
.addService(new GetVersionService())
|
||||
.addService(new GetTradeStatisticsService())
|
||||
.addService(new GetOffersService())
|
||||
.addService(new GetPaymentAccountsService())
|
||||
.addService(new PlaceOfferService())
|
||||
.addService(walletService)
|
||||
.addService(offersService)
|
||||
.addService(paymentAccountsService)
|
||||
.addService(walletsService)
|
||||
.intercept(new PasswordAuthInterceptor(config.apiPassword))
|
||||
.build();
|
||||
}
|
||||
@ -95,7 +86,6 @@ public class GrpcServer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class GetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase {
|
||||
@Override
|
||||
public void getTradeStatistics(GetTradeStatisticsRequest req,
|
||||
@ -110,55 +100,4 @@ public class GrpcServer {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
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.GetBalanceRequest;
|
||||
import bisq.proto.grpc.GetFundingAddressesReply;
|
||||
import bisq.proto.grpc.GetFundingAddressesRequest;
|
||||
import bisq.proto.grpc.LockWalletReply;
|
||||
import bisq.proto.grpc.LockWalletRequest;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordReply;
|
||||
@ -10,7 +33,7 @@ import bisq.proto.grpc.SetWalletPasswordReply;
|
||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||
import bisq.proto.grpc.UnlockWalletReply;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.WalletGrpc;
|
||||
import bisq.proto.grpc.WalletsGrpc;
|
||||
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
@ -18,19 +41,22 @@ import io.grpc.stub.StreamObserver;
|
||||
|
||||
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
|
||||
public GrpcWalletService(CoreWalletService walletService) {
|
||||
this.walletService = walletService;
|
||||
public GrpcWalletsService(CoreApi coreApi) {
|
||||
this.coreApi = coreApi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getBalance(GetBalanceRequest req, StreamObserver<GetBalanceReply> responseObserver) {
|
||||
try {
|
||||
long result = walletService.getAvailableBalance();
|
||||
long result = coreApi.getAvailableBalance();
|
||||
var reply = GetBalanceReply.newBuilder().setBalance(result).build();
|
||||
responseObserver.onNext(reply);
|
||||
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
|
||||
public void setWalletPassword(SetWalletPasswordRequest req,
|
||||
StreamObserver<SetWalletPasswordReply> responseObserver) {
|
||||
try {
|
||||
walletService.setWalletPassword(req.getPassword(), req.getNewPassword());
|
||||
coreApi.setWalletPassword(req.getPassword(), req.getNewPassword());
|
||||
var reply = SetWalletPasswordReply.newBuilder().build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
@ -60,7 +121,7 @@ class GrpcWalletService extends WalletGrpc.WalletImplBase {
|
||||
public void removeWalletPassword(RemoveWalletPasswordRequest req,
|
||||
StreamObserver<RemoveWalletPasswordReply> responseObserver) {
|
||||
try {
|
||||
walletService.removeWalletPassword(req.getPassword());
|
||||
coreApi.removeWalletPassword(req.getPassword());
|
||||
var reply = RemoveWalletPasswordReply.newBuilder().build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
@ -75,7 +136,7 @@ class GrpcWalletService extends WalletGrpc.WalletImplBase {
|
||||
public void lockWallet(LockWalletRequest req,
|
||||
StreamObserver<LockWalletReply> responseObserver) {
|
||||
try {
|
||||
walletService.lockWallet();
|
||||
coreApi.lockWallet();
|
||||
var reply = LockWalletReply.newBuilder().build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
@ -90,7 +151,7 @@ class GrpcWalletService extends WalletGrpc.WalletImplBase {
|
||||
public void unlockWallet(UnlockWalletRequest req,
|
||||
StreamObserver<UnlockWalletReply> responseObserver) {
|
||||
try {
|
||||
walletService.unlockWallet(req.getPassword(), req.getTimeout());
|
||||
coreApi.unlockWallet(req.getPassword(), req.getTimeout());
|
||||
var reply = UnlockWalletReply.newBuilder().build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
@ -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;
|
||||
|
||||
import io.grpc.Metadata;
|
||||
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
215
core/src/main/java/bisq/core/grpc/model/OfferInfo.java
Normal file
215
core/src/main/java/bisq/core/grpc/model/OfferInfo.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -267,32 +267,39 @@ public class CurrencyUtil {
|
||||
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() {
|
||||
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("AUD"),
|
||||
new FiatCurrency("BGN"),
|
||||
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("INR"),
|
||||
new FiatCurrency("ILS"),
|
||||
new FiatCurrency("ISK"),
|
||||
new FiatCurrency("JPY"),
|
||||
new FiatCurrency("MAD"),
|
||||
new FiatCurrency("MXN"),
|
||||
new FiatCurrency("NOK"),
|
||||
new FiatCurrency("NZD"),
|
||||
new FiatCurrency("PLN"),
|
||||
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("TRY"),
|
||||
new FiatCurrency("USD"),
|
||||
new FiatCurrency("ZAR")
|
||||
));
|
||||
|
||||
|
@ -124,6 +124,7 @@ public class CreateOfferService {
|
||||
Coin amount,
|
||||
Coin minAmount,
|
||||
Price price,
|
||||
Coin txFee,
|
||||
boolean useMarketBasedPrice,
|
||||
double marketPriceMargin,
|
||||
double buyerSecurityDepositAsDouble,
|
||||
@ -183,6 +184,7 @@ public class CreateOfferService {
|
||||
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
|
||||
double sellerSecurityDeposit = getSellerSecurityDepositAsDouble();
|
||||
Coin txFeeFromFeeService = getEstimatedFeeAndTxSize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first;
|
||||
Coin txFeeToUse = txFee.isPositive() ? txFee : txFeeFromFeeService;
|
||||
Coin makerFeeAsCoin = getMakerFee(amount);
|
||||
boolean isCurrencyForMakerFeeBtc = OfferUtil.isCurrencyForMakerFeeBtc(preferences, bsqWalletService, amount);
|
||||
Coin buyerSecurityDepositAsCoin = getBuyerSecurityDeposit(amount, buyerSecurityDepositAsDouble);
|
||||
@ -233,7 +235,7 @@ public class CreateOfferService {
|
||||
acceptedBanks,
|
||||
Version.VERSION,
|
||||
btcWalletService.getLastBlockSeenHeight(),
|
||||
txFeeFromFeeService.value,
|
||||
txFeeToUse.value,
|
||||
makerFeeAsCoin.value,
|
||||
isCurrencyForMakerFeeBtc,
|
||||
buyerSecurityDepositAsCoin.value,
|
||||
|
@ -365,6 +365,14 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getPaymentMethodNameWithCountryCode() {
|
||||
String method = this.getPaymentMethod().getShortName();
|
||||
String methodCountryCode = this.getCountryCode();
|
||||
if (methodCountryCode != null)
|
||||
method = method + " (" + methodCountryCode + ")";
|
||||
return method;
|
||||
}
|
||||
|
||||
// domain properties
|
||||
public Offer.State getState() {
|
||||
return stateProperty.get();
|
||||
|
@ -23,6 +23,7 @@ import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||
@ -111,6 +112,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
private final MediatorManager mediatorManager;
|
||||
private final RefundAgentManager refundAgentManager;
|
||||
private final DaoFacade daoFacade;
|
||||
private final FilterManager filterManager;
|
||||
private final Storage<TradableList<OpenOffer>> openOfferTradableListStorage;
|
||||
private final Map<String, OpenOffer> offersToBeEdited = new HashMap<>();
|
||||
private boolean stopped;
|
||||
@ -139,6 +141,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
MediatorManager mediatorManager,
|
||||
RefundAgentManager refundAgentManager,
|
||||
DaoFacade daoFacade,
|
||||
FilterManager filterManager,
|
||||
Storage<TradableList<OpenOffer>> storage) {
|
||||
this.createOfferService = createOfferService;
|
||||
this.keyRing = keyRing;
|
||||
@ -156,6 +159,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
this.mediatorManager = mediatorManager;
|
||||
this.refundAgentManager = refundAgentManager;
|
||||
this.daoFacade = daoFacade;
|
||||
this.filterManager = filterManager;
|
||||
|
||||
openOfferTradableListStorage = storage;
|
||||
|
||||
@ -361,7 +365,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
arbitratorManager,
|
||||
tradeStatisticsManager,
|
||||
daoFacade,
|
||||
user);
|
||||
user,
|
||||
filterManager);
|
||||
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
|
||||
model,
|
||||
transaction -> {
|
||||
|
@ -21,6 +21,7 @@ import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferBookService;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
@ -51,6 +52,8 @@ public class PlaceOfferModel implements Model {
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final DaoFacade daoFacade;
|
||||
private final User user;
|
||||
@Getter
|
||||
private final FilterManager filterManager;
|
||||
|
||||
// Mutable
|
||||
@Setter
|
||||
@ -68,7 +71,8 @@ public class PlaceOfferModel implements Model {
|
||||
ArbitratorManager arbitratorManager,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
DaoFacade daoFacade,
|
||||
User user) {
|
||||
User user,
|
||||
FilterManager filterManager) {
|
||||
this.offer = offer;
|
||||
this.reservedFundsForOffer = reservedFundsForOffer;
|
||||
this.useSavingsWallet = useSavingsWallet;
|
||||
@ -80,6 +84,7 @@ public class PlaceOfferModel implements Model {
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.daoFacade = daoFacade;
|
||||
this.user = user;
|
||||
this.filterManager = filterManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,10 +25,10 @@ import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.TxBroadcaster;
|
||||
import bisq.core.btc.wallet.WalletService;
|
||||
import bisq.core.dao.exceptions.DaoDisabledException;
|
||||
import bisq.core.dao.governance.param.Param;
|
||||
import bisq.core.dao.state.model.blockchain.TxType;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
||||
import bisq.core.util.FeeReceiverSelector;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.taskrunner.Task;
|
||||
@ -65,7 +65,8 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
|
||||
Address changeAddress = walletService.getFreshAddressEntry().getAddress();
|
||||
|
||||
TradeWalletService tradeWalletService = model.getTradeWalletService();
|
||||
String feeReceiver = model.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS);
|
||||
|
||||
String feeReceiver = FeeReceiverSelector.getAddress(model.getDaoFacade(), model.getFilterManager());
|
||||
|
||||
if (offer.isCurrencyForMakerFeeBtc()) {
|
||||
tradeWalletService.createBtcTradingFeeTx(
|
||||
|
@ -169,4 +169,8 @@ public abstract class PaymentAccount implements PersistablePayload {
|
||||
public String getSaltAsHex() {
|
||||
return Utilities.bytesAsHexString(getSalt());
|
||||
}
|
||||
|
||||
public String getOwnerId() {
|
||||
return paymentAccountPayload.getOwnerId();
|
||||
}
|
||||
}
|
||||
|
@ -179,4 +179,9 @@ public abstract class BankAccountPayload extends CountryBasedPaymentAccountPaylo
|
||||
|
||||
return super.getAgeWitnessInputData(all.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -224,4 +224,9 @@ public class CashDepositAccountPayload extends CountryBasedPaymentAccountPayload
|
||||
|
||||
return super.getAgeWitnessInputData(all.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -106,4 +106,9 @@ public final class ChaseQuickPayAccountPayload extends PaymentAccountPayload {
|
||||
// slight changes in holder name (e.g. add or remove middle name)
|
||||
return super.getAgeWitnessInputData(email.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -106,4 +106,9 @@ public final class ClearXchangeAccountPayload extends PaymentAccountPayload {
|
||||
// slight changes in holder name (e.g. add or remove middle name)
|
||||
return super.getAgeWitnessInputData(emailOrMobileNr.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -120,4 +120,9 @@ public final class InteracETransferAccountPayload extends PaymentAccountPayload
|
||||
ArrayUtils.addAll(question.getBytes(StandardCharsets.UTF_8),
|
||||
answer.getBytes(StandardCharsets.UTF_8))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -135,4 +135,8 @@ public abstract class PaymentAccountPayload implements NetworkPayload, UsedForTr
|
||||
protected byte[] getAgeWitnessInputData(byte[] data) {
|
||||
return ArrayUtils.addAll(paymentMethodId.getBytes(StandardCharsets.UTF_8), data);
|
||||
}
|
||||
|
||||
public String getOwnerId() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -313,6 +313,12 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
|
||||
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
|
||||
public int compareTo(@NotNull PaymentMethod other) {
|
||||
return id.compareTo(other.id);
|
||||
|
@ -103,4 +103,9 @@ public final class PopmoneyAccountPayload extends PaymentAccountPayload {
|
||||
public byte[] getAgeWitnessInputData() {
|
||||
return super.getAgeWitnessInputData(accountId.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -158,4 +158,8 @@ public final class SepaAccountPayload extends CountryBasedPaymentAccountPayload
|
||||
// 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)));
|
||||
}
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -153,4 +153,9 @@ public final class SepaInstantAccountPayload extends CountryBasedPaymentAccountP
|
||||
// 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)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -104,4 +104,9 @@ public final class SwishAccountPayload extends PaymentAccountPayload {
|
||||
// slight changes in holder name (e.g. add or remove middle name)
|
||||
return super.getAgeWitnessInputData(mobileNr.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -107,4 +107,9 @@ public final class USPostalMoneyOrderAccountPayload extends PaymentAccountPayloa
|
||||
return super.getAgeWitnessInputData(ArrayUtils.addAll(holderName.getBytes(StandardCharsets.UTF_8),
|
||||
postalAddress.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -106,4 +106,9 @@ public final class VenmoAccountPayload extends PaymentAccountPayload {
|
||||
public byte[] getAgeWitnessInputData() {
|
||||
return super.getAgeWitnessInputData(venmoUserName.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return holderName;
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,9 @@ import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.WalletService;
|
||||
import bisq.core.dao.exceptions.DaoDisabledException;
|
||||
import bisq.core.dao.governance.param.Param;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.util.FeeReceiverSelector;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
@ -65,7 +65,9 @@ public class CreateTakerFeeTx extends TradeTask {
|
||||
Address changeAddress = changeAddressEntry.getAddress();
|
||||
TradeWalletService tradeWalletService = processModel.getTradeWalletService();
|
||||
Transaction transaction;
|
||||
String feeReceiver = processModel.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS);
|
||||
|
||||
String feeReceiver = FeeReceiverSelector.getAddress(processModel.getDaoFacade(), processModel.getFilterManager());
|
||||
|
||||
if (trade.isCurrencyForTakerFeeBtc()) {
|
||||
transaction = tradeWalletService.createBtcTradingFeeTx(
|
||||
fundingAddress,
|
||||
|
@ -78,10 +78,12 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
public final class Preferences implements PersistedDataHost, BridgeAddressProvider {
|
||||
|
||||
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 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("Bitaps", "https://bitaps.com/", "https://bitaps.com/"),
|
||||
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("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("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(
|
||||
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("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("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(
|
||||
new BlockChainExplorer("BTC DAO-testnet explorer", "https://bisq.network/explorer/btc/dao_testnet/tx/", "https://bisq.network/explorer/btc/dao_testnet/address/")
|
||||
|
78
core/src/main/java/bisq/core/util/FeeReceiverSelector.java
Normal file
78
core/src/main/java/bisq/core/util/FeeReceiverSelector.java
Normal 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;
|
||||
}
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
# nodeaddress.onion:port [(@owner,@backup)]
|
||||
5quyxpxheyvzmb2d.onion:8000 (@miker)
|
||||
s67qglwhkgkyvr74.onion:8000 (@emzy)
|
||||
ef5qnzx6znifo3df.onion:8000 (@wiz,@emzy)
|
||||
3f3cu2yw7u457ztq.onion:8000 (@devinbileck,@ripcurlx)
|
||||
723ljisnynbtdohi.onion:8000 (@emzy)
|
||||
rm7b56wbrcczpjvl.onion:8000 (@miker)
|
||||
fl3mmribyxgrv63c.onion:8000 (@devinbileck,@ripcurlx)
|
||||
wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000 (@wiz)
|
||||
wizseed3d376esppbmbjxk2fhk2jg5fpucddrzj2kxtbxbx4vrnwclad.onion:8000 (@wiz)
|
||||
wizseed7ab2gi3x267xahrp2pkndyrovczezzb46jk6quvguciuyqrid.onion:8000 (@wiz)
|
||||
devinv3rhon24gqf5v6ondoqgyrbzyqihzyouzv7ptltsewhfmox2zqd.onion:8000 (@devinbileck)
|
||||
sn3emzy56u3mxzsr4geysc52feoq5qt7ja56km6gygwnszkshunn2sid.onion:8000 (@emzy)
|
||||
|
@ -747,7 +747,7 @@ portfolio.pending.step5_buyer.refunded=Refunded security deposit
|
||||
portfolio.pending.step5_buyer.withdrawBTC=Withdraw your bitcoin
|
||||
portfolio.pending.step5_buyer.amount=Amount to withdraw
|
||||
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.alreadyWithdrawn=Your funds have already been withdrawn.\nPlease check the transaction history.
|
||||
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.add=Add filter
|
||||
filterWindow.remove=Remove filter
|
||||
filterWindow.btcFeeReceiverAddresses=BTC fee receiver addresses
|
||||
|
||||
offerDetailsWindow.minBtcAmount=Min. BTC amount
|
||||
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\
|
||||
After restore, the wallets will no longer be encrypted and you must set a new password.\n\n\
|
||||
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.error=An error occurred when restoring the wallets with seed words.{0}
|
||||
|
||||
@ -3039,27 +3044,14 @@ payment.accountType=Account type
|
||||
payment.checking=Checking
|
||||
payment.savings=Savings
|
||||
payment.personalId=Personal ID
|
||||
payment.clearXchange.info=Please be sure that you fulfill the requirements for the usage of Zelle (ClearXchange).\n\n\
|
||||
1. You need to have your Zelle (ClearXchange) account verified on their platform \
|
||||
before starting a trade or creating an offer.\n\n\
|
||||
2. You need to have a bank account at one of the following member banks:\n\
|
||||
\t● Bank of America\n\
|
||||
\t● Capital One P2P Payments\n\
|
||||
\t● Chase QuickPay\n\
|
||||
\t● FirstBank Person to Person Transfers\n\
|
||||
\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.clearXchange.info=Zelle is a money transfer service that works best *through* another bank.\n\n\
|
||||
1. Check this page to see if (and how) your bank works with Zelle:\nhttps://www.zellepay.com/get-started\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\
|
||||
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\
|
||||
4. The name specified on your Bisq account MUST match the name on your Zelle/bank account. \n\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\
|
||||
Because of Zelle''s somewhat higher chargeback risk, sellers are advised to contact unsigned buyers through email or SMS to verify that the buyer \
|
||||
really owns the Zelle account specified in Bisq.
|
||||
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\
|
||||
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\
|
||||
2. The age of your account for that payment method\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\
|
||||
● During the 1st month, your per-trade limit will be {0}\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\
|
||||
\n\
|
||||
Please note that there are no limits on the total number of times you can trade.
|
||||
payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade buy limits based on the following 2 factors:\n\
|
||||
\n\
|
||||
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 limits for this payment account type based \
|
||||
on the following 2 factors:\n\n\
|
||||
1. General chargeback risk for the payment method\n\
|
||||
2. Account signing status\n\
|
||||
\n\
|
||||
This payment account you just created carries some chargeback risk and is not yet signed by an arbitrator or trusted peer, \
|
||||
so it is limited to buying {0} per trade. \
|
||||
After it is signed, buy limits will increase as follows:\n\
|
||||
This payment account is not yet signed, so it is limited to buying {0} per trade. \
|
||||
After signing, buy limits will increase as follows:\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\
|
||||
● 60 days after signing, your per-trade buy limit will be {2}\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\
|
||||
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. \
|
||||
For example, Bank of America and Wells Fargo no longer allow such deposits.
|
||||
|
@ -1,6 +1,6 @@
|
||||
TXT CHECKPOINTS 1
|
||||
0
|
||||
310
|
||||
313
|
||||
AAAAAAAAB+EH4QfhAAAH4AEAAABjl7tqvU/FIcDT9gcbVlA4nwtFUbxAtOawZzBpAAAAAKzkcK7NqciBjI/ldojNKncrWleVSgDfBCCn3VRrbSxXaw5/Sf//AB0z8Bkv
|
||||
AAAAAAAAD8EPwQ/BAAAPwAEAAADfP83Sx8MZ9RsrnZCvqzAwqB2Ma+ZesNAJrTfwAAAAACwESaNKhvRgz6WuE7UFdFk1xwzfRY/OIdIOPzX5yaAdjnWUSf//AB0GrNq5
|
||||
AAAAAAAAF6EXoRehAAAXoAEAAADonWzAaUAKd30XT3NnHKobZMnLOuHdzm/xtehsAAAAAD8cUJA6NBIHHcqPHLc4IrfHw+6mjCGu3e+wRO81EvpnMVqrSf//AB1ffy8G
|
||||
@ -311,3 +311,6 @@ DQOCnM/692T/c/vpAAlxoAAgACBpKU8xBXETUdlfq3ziC5HqY2sbxpLaBgAAAAAAAAAAAC2Podb2UVE3
|
||||
DXJv6TyZC6dJNCJfAAl5gAAAwCC821K+00n7cVERL9d6Jzo0wkE8NSY/BAAAAAAAAAAAANEvjjQwmKBYHwf7qsdzHSrM+7AC4wGhHxMBliBwDnPi7SJmXhkBERcyCjYC
|
||||
Dej8cVmdICmGlGmsAAmBYAAAgCAMNQrVauCVnJhLXbUm+wi+s6FYCIDGCwAAAAAAAAAAAHSgbycFfzUIokW6wOwg6yoHf88c9CrCsoU3LGXqo65Awhh8XkE7FBcTwPq1
|
||||
Dkyiv/CRkmMKmkEzAAmJQADgACBD4xnvCIUw3EDxERdHu3/bAmPjwJ15DAAAAAAAAAAAAHiPGbRWrMMMyL6BA6eaJfNIo5XiUMXo0B7u72S1QqDZLpGNXrwgExchnuav
|
||||
DrYJPtE6p0g5WpqUAAmRIAAAACAUOgCoJeMzIDa34vdMEznyR1cSUIbpDQAAAAAAAAAAAKlIg5av2zB0dTVJcJhbennqbQ9kisot+0OheDaM0jzyNJueXjOjEReyl0xD
|
||||
DyhWrFlPqEc/c9+uAAmZAAAgACDcrvMMuRSy9GQxh8BFNt6wIDWYhvU4EQAAAAAAAAAAABVOP5QKoWx9atpYrUh6L+UeYRrgD4sj3ZOBngyn3WuRAeawXjl6EReMHTLH
|
||||
D5uvExILinkBRJMsAAmg4ADg/yfO2pclcPlRE5XVKCPZeF63mn30v+yHDgAAAAAAAAAAAPaDA4g+h9tjjVWtJ587WMHb07Mcfa1BoDJCANQd+z94wJDEXvaXEhf5l8cN
|
||||
|
@ -1,7 +1,5 @@
|
||||
package bisq.core.offer;
|
||||
|
||||
import bisq.core.trade.TradableList;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
@ -47,7 +45,7 @@ public class OpenOfferManagerTest {
|
||||
final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService,
|
||||
null, null, null, offerBookService,
|
||||
null, null, null,
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null, null,
|
||||
new Storage<>(storageDir, null, corruptedDatabaseFilesHandler));
|
||||
|
||||
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
|
||||
@ -83,7 +81,7 @@ public class OpenOfferManagerTest {
|
||||
final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService,
|
||||
null, null, null, offerBookService,
|
||||
null, null, null,
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null, null,
|
||||
new Storage<>(storageDir, null, corruptedDatabaseFilesHandler));
|
||||
|
||||
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
|
||||
@ -111,7 +109,7 @@ public class OpenOfferManagerTest {
|
||||
final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService,
|
||||
null, null, null, offerBookService,
|
||||
null, null, null,
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null, null,
|
||||
new Storage<>(storageDir, null, corruptedDatabaseFilesHandler));
|
||||
|
||||
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
|
||||
|
@ -27,7 +27,6 @@ import com.google.common.collect.Lists;
|
||||
|
||||
import org.junit.Ignore;
|
||||
|
||||
@SuppressWarnings("UnusedAssignment")
|
||||
public class UserPayloadModelVOTest {
|
||||
@Ignore("TODO InvalidKeySpecException at bisq.common.crypto.Sig.getPublicKeyFromBytes(Sig.java:135)")
|
||||
public void testRoundtrip() {
|
||||
@ -59,6 +58,7 @@ public class UserPayloadModelVOTest {
|
||||
null,
|
||||
Lists.newArrayList(),
|
||||
Lists.newArrayList(),
|
||||
Lists.newArrayList(),
|
||||
Lists.newArrayList()));
|
||||
vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock());
|
||||
vo.setRegisteredMediator(MediatorTest.getMediatorMock());
|
||||
|
107
core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java
Normal file
107
core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java
Normal 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);
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
|
||||
# pull base image
|
||||
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/* &&
|
||||
apt-get install -y vim fakeroot
|
||||
|
@ -6,7 +6,7 @@
|
||||
# - Update version below
|
||||
# - 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}')
|
||||
if [ ! -f "$JAVA_HOME/bin/javapackager" ]; then
|
||||
if [ -d "/usr/lib/jvm/jdk-10.0.2" ]; then
|
||||
|
@ -4,7 +4,7 @@
|
||||
# Prior to running this script:
|
||||
# - Update version below
|
||||
|
||||
version=1.3.4-SNAPSHOT
|
||||
version=1.3.5-SNAPSHOT
|
||||
base_dir=$( cd "$(dirname "$0")" ; pwd -P )/../../..
|
||||
package_dir=$base_dir/desktop/package
|
||||
release_dir=$base_dir/desktop/release/$version
|
||||
|
@ -5,10 +5,10 @@
|
||||
<!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -->
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.3.4</string>
|
||||
<string>1.3.5</string>
|
||||
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.3.4</string>
|
||||
<string>1.3.5</string>
|
||||
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Bisq</string>
|
||||
@ -26,7 +26,7 @@
|
||||
<string>public.app-category.finance</string>
|
||||
|
||||
<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.
|
||||
See: https://github.com/bitcoin/bitcoin/issues/11896#issuecomment-352148399-->
|
||||
|
@ -6,7 +6,7 @@ mkdir -p deploy
|
||||
|
||||
set -e
|
||||
|
||||
version="1.3.4-SNAPSHOT"
|
||||
version="1.3.5-SNAPSHOT"
|
||||
|
||||
cd ..
|
||||
./gradlew :desktop:build -x test shadowJar
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
cd ../../
|
||||
|
||||
version="1.3.4-SNAPSHOT"
|
||||
version="1.3.5-SNAPSHOT"
|
||||
|
||||
target_dir="releases/$version"
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
cd $(dirname $0)/../../../
|
||||
|
||||
version=1.3.4
|
||||
version=1.3.5
|
||||
|
||||
find . -type f \( -name "finalize.sh" \
|
||||
-o -name "create_app.sh" \
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
cd $(dirname $0)/../../../
|
||||
|
||||
oldVersion=1.3.3
|
||||
newVersion=1.3.4
|
||||
oldVersion=1.3.4
|
||||
newVersion=1.3.5
|
||||
|
||||
find . -type f \( -name "finalize.sh" \
|
||||
-o -name "create_app.sh" \
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
@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 "%ProgramFiles%\Java\jdk-10.0.2" (
|
||||
echo Javapackager not found. Update JAVA_HOME variable to point to OracleJDK.
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
@echo off
|
||||
|
||||
set version=1.3.4-SNAPSHOT
|
||||
set version=1.3.5-SNAPSHOT
|
||||
set release_dir=%~dp0..\..\..\releases\%version%
|
||||
set package_dir=%~dp0..
|
||||
|
||||
|
@ -237,6 +237,30 @@
|
||||
-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,
|
||||
.tree-view:focused,
|
||||
.table-view:focused,
|
||||
@ -867,6 +891,32 @@ textfield */
|
||||
* 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 {
|
||||
-fx-alignment: center-left;
|
||||
-fx-padding: 2 0 2 0;
|
||||
|
@ -147,8 +147,8 @@ public class RevolutForm extends PaymentMethodForm {
|
||||
|
||||
private void addCurrenciesGrid(boolean isEditable) {
|
||||
FlowPane flowPane = addTopLabelFlowPane(gridPane, ++gridRow,
|
||||
Res.get("payment.supportedCurrencies"), Layout.FLOATING_LABEL_DISTANCE,
|
||||
Layout.FLOATING_LABEL_DISTANCE).second;
|
||||
Res.get("payment.supportedCurrencies"), Layout.FLOATING_LABEL_DISTANCE * 3,
|
||||
Layout.FLOATING_LABEL_DISTANCE * 3).second;
|
||||
|
||||
if (isEditable)
|
||||
flowPane.setId("flow-pane-checkboxes-bg");
|
||||
|
@ -130,6 +130,7 @@ class FiatAccountsDataModel extends ActivatableDataModel {
|
||||
user.addPaymentAccount(paymentAccount);
|
||||
|
||||
accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload());
|
||||
accountAgeWitnessService.signSameNameAccounts();
|
||||
}
|
||||
|
||||
public boolean onDeleteAccount(PaymentAccount paymentAccount) {
|
||||
|
@ -239,6 +239,19 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
|
||||
private void checkIfEncrypted() {
|
||||
if (walletsManager.areWalletsEncrypted()) {
|
||||
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"))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(this::doRestore)
|
||||
@ -248,7 +261,7 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
|
||||
}
|
||||
}
|
||||
|
||||
private void doRestore() {
|
||||
private LocalDate getWalletDate() {
|
||||
LocalDate walletDate = restoreDatePicker.getValue();
|
||||
// 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.
|
||||
@ -264,7 +277,11 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
|
||||
} else if (walletDate.isAfter(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
|
||||
// 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.
|
||||
|
@ -186,8 +186,9 @@ public class BondsView extends ActivatableView<GridPane, Void> {
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
column.setComparator(Comparator.comparing(e -> e.getBond().getAmount()));
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockTime"));
|
||||
column.setMinWidth(40);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -190,8 +190,8 @@ public class ProofOfBurnView extends ActivatableView<GridPane, Void> implements
|
||||
preImageTextField.setValidator(new InputValidator());
|
||||
|
||||
updateList();
|
||||
GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 2, 4);
|
||||
GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 100);
|
||||
GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 4, 6);
|
||||
GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 10);
|
||||
updateButtonState();
|
||||
}
|
||||
|
||||
@ -265,13 +265,12 @@ public class ProofOfBurnView extends ActivatableView<GridPane, Void> implements
|
||||
.map(myProofOfBurn -> new MyProofOfBurnListItem(myProofOfBurn, proofOfBurnService, bsqFormatter))
|
||||
.sorted(Comparator.comparing(MyProofOfBurnListItem::getDate).reversed())
|
||||
.collect(Collectors.toList()));
|
||||
GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 2, 4);
|
||||
|
||||
GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 4, 6);
|
||||
|
||||
allItemsObservableList.setAll(proofOfBurnService.getProofOfBurnTxList().stream()
|
||||
.map(tx -> new ProofOfBurnListItem(tx, proofOfBurnService, bsqFormatter))
|
||||
.collect(Collectors.toList()));
|
||||
GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 100);
|
||||
GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 10);
|
||||
}
|
||||
|
||||
private void updateButtonState() {
|
||||
|
@ -286,6 +286,11 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
|
||||
availableForVoting = Coin.valueOf(0);
|
||||
stakeInputTextField.setPromptText(Res.get("dao.proposal.myVote.stake.prompt",
|
||||
bsqFormatter.formatCoinWithCode(availableForVoting)));
|
||||
|
||||
BsqValidator stakeInputTextFieldValidator = new BsqValidator(bsqFormatter);
|
||||
stakeInputTextFieldValidator.setMaxValue(availableForVoting);
|
||||
|
||||
stakeInputTextField.setValidator(stakeInputTextFieldValidator);
|
||||
} else
|
||||
stakeInputTextField.setPromptText("");
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ public class OfferBookChartView extends ActivatableViewAndModel<VBox, OfferBookC
|
||||
final double doubleValue = (double) object;
|
||||
if (CurrencyUtil.isCryptoCurrency(model.getCurrencyCode())) {
|
||||
final String withCryptoPrecision = FormattingUtils.formatRoundedDoubleWithPrecision(doubleValue, cryptoPrecision);
|
||||
if (withCryptoPrecision.equals("0.000")) {
|
||||
if (withCryptoPrecision.substring(0,3).equals("0.0")) {
|
||||
cryptoPrecision = 8;
|
||||
return FormattingUtils.formatRoundedDoubleWithPrecision(doubleValue, cryptoPrecision);
|
||||
} else {
|
||||
|
@ -282,6 +282,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||
amount.get(),
|
||||
minAmount.get(),
|
||||
price.get(),
|
||||
txFeeFromFeeService,
|
||||
useMarketBasedPrice.get(),
|
||||
marketPriceMargin,
|
||||
buyerSecurityDeposit.get(),
|
||||
|
@ -812,14 +812,16 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
// We want to trigger a recalculation of the volume and minAmount
|
||||
UserThread.execute(() -> {
|
||||
onFocusOutVolumeTextField(true, false);
|
||||
// do not update BTC Amount or minAmount here
|
||||
// issue 2798: "after a few edits of offer the BTC amount has increased"
|
||||
// intentionally removed: onFocusOutAmountTextField(true, false);
|
||||
// intentionally removed: onFocusOutMinAmountTextField(true, false);
|
||||
triggerFocusOutOnAmountFields();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void triggerFocusOutOnAmountFields() {
|
||||
onFocusOutAmountTextField(true, false);
|
||||
onFocusOutMinAmountTextField(true, false);
|
||||
}
|
||||
|
||||
public void onFocusOutPriceAsPercentageTextField(boolean oldValue, boolean newValue) {
|
||||
inputIsMarketBasedPrice = !oldValue && newValue;
|
||||
if (oldValue && !newValue) {
|
||||
|
@ -130,6 +130,7 @@ public class FilterWindow extends Overlay<FilterWindow> {
|
||||
InputTextField arbitratorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.arbitrators"));
|
||||
InputTextField mediatorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.mediators"));
|
||||
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 priceRelayNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.priceRelayNode"));
|
||||
InputTextField btcNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.btcNode"));
|
||||
@ -149,6 +150,7 @@ public class FilterWindow extends Overlay<FilterWindow> {
|
||||
setupFieldFromList(arbitratorsInputTextField, filter.getArbitrators());
|
||||
setupFieldFromList(mediatorsInputTextField, filter.getMediators());
|
||||
setupFieldFromList(refundAgentsInputTextField, filter.getRefundAgents());
|
||||
setupFieldFromList(btcFeeReceiverAddressesInputTextField, filter.getBtcFeeReceiverAddresses());
|
||||
setupFieldFromList(seedNodesInputTextField, filter.getSeedNodes());
|
||||
setupFieldFromList(priceRelayNodesInputTextField, filter.getPriceRelayNodes());
|
||||
setupFieldFromList(btcNodesInputTextField, filter.getBtcNodes());
|
||||
@ -177,7 +179,8 @@ public class FilterWindow extends Overlay<FilterWindow> {
|
||||
disableTradeBelowVersionInputTextField.getText(),
|
||||
readAsList(mediatorsInputTextField),
|
||||
readAsList(refundAgentsInputTextField),
|
||||
readAsList(bannedSignerPubKeysInputTextField)
|
||||
readAsList(bannedSignerPubKeysInputTextField),
|
||||
readAsList(btcFeeReceiverAddressesInputTextField)
|
||||
),
|
||||
keyInputTextField.getText())
|
||||
)
|
||||
|
@ -30,8 +30,8 @@ import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
@ -108,4 +108,10 @@ class EditOfferViewModel extends MutableOfferViewModel<EditOfferDataModel> {
|
||||
public boolean isSecurityDepositValid() {
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
@ -31,14 +31,15 @@
|
||||
<columns>
|
||||
<TableColumn fx:id="offerIdColumn" minWidth="110" maxWidth="130"/>
|
||||
<TableColumn fx:id="dateColumn" minWidth="180"/>
|
||||
<TableColumn fx:id="marketColumn" minWidth="90"/>
|
||||
<TableColumn fx:id="priceColumn" minWidth="150"/>
|
||||
<TableColumn fx:id="amountColumn" minWidth="150"/>
|
||||
<TableColumn fx:id="volumeColumn" minWidth="170"/>
|
||||
<TableColumn fx:id="marketColumn" minWidth="75"/>
|
||||
<TableColumn fx:id="priceColumn" minWidth="110"/>
|
||||
<TableColumn fx:id="amountColumn" minWidth="100"/>
|
||||
<TableColumn fx:id="volumeColumn" minWidth="150"/>
|
||||
<TableColumn fx:id="paymentMethodColumn" minWidth="120" maxWidth="170"/>
|
||||
<TableColumn fx:id="directionColumn" minWidth="80"/>
|
||||
<TableColumn fx:id="deactivateItemColumn" minWidth="100" maxWidth="100" sortable="false"/>
|
||||
<TableColumn fx:id="editItemColumn" minWidth="60" maxWidth="60" sortable="false"/>
|
||||
<TableColumn fx:id="removeItemColumn" minWidth="60" maxWidth="60" sortable="false"/>
|
||||
<TableColumn fx:id="deactivateItemColumn" minWidth="60" maxWidth="60" sortable="false"/>
|
||||
<TableColumn fx:id="editItemColumn" minWidth="50" maxWidth="60" sortable="false"/>
|
||||
<TableColumn fx:id="removeItemColumn" minWidth="50" maxWidth="60" sortable="false"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
|
||||
|
@ -69,7 +69,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
@FXML
|
||||
TableColumn<OpenOfferListItem, OpenOfferListItem> priceColumn, amountColumn, volumeColumn,
|
||||
marketColumn, directionColumn, dateColumn, offerIdColumn, deactivateItemColumn,
|
||||
removeItemColumn, editItemColumn;
|
||||
removeItemColumn, editItemColumn, paymentMethodColumn;
|
||||
private final Navigation navigation;
|
||||
private final OfferDetailsWindow offerDetailsWindow;
|
||||
private SortedList<OpenOfferListItem> sortedList;
|
||||
@ -84,6 +84,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
paymentMethodColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.paymentMethod")));
|
||||
priceColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.price")));
|
||||
amountColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.BTCMinMax")));
|
||||
volumeColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amountMinMax")));
|
||||
@ -101,6 +102,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
setPriceColumnCellFactory();
|
||||
setAmountColumnCellFactory();
|
||||
setVolumeColumnCellFactory();
|
||||
setPaymentMethodColumnCellFactory();
|
||||
setDateColumnCellFactory();
|
||||
setDeactivateColumnCellFactory();
|
||||
setEditColumnCellFactory();
|
||||
@ -116,6 +118,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
|
||||
priceColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPrice(), Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
volumeColumn.setComparator(Comparator.comparing(o -> o.getOffer().getVolume(), Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
dateColumn.setComparator(Comparator.comparing(o -> o.getOffer().getDate()));
|
||||
paymentMethodColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPaymentMethod().getId()));
|
||||
|
||||
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
|
||||
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() {
|
||||
directionColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
|
||||
directionColumn.setCellFactory(
|
||||
|
@ -42,6 +42,8 @@ import javax.inject.Named;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel> implements ViewModel {
|
||||
private final P2PService p2PService;
|
||||
private final CoinFormatter btcFormatter;
|
||||
@ -118,6 +120,17 @@ class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel>
|
||||
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) {
|
||||
return DisplayUtils.formatDateTime(item.getOffer().getDate());
|
||||
}
|
||||
|
@ -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
|
||||
// and after a short moment in the correct position
|
||||
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
|
||||
@ -729,17 +747,17 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
|
||||
}
|
||||
|
||||
button.setOnAction(e -> {
|
||||
tableView.getSelectionModel().select(this.getIndex());
|
||||
openChat(trade);
|
||||
update(trade, badge);
|
||||
});
|
||||
|
||||
if (!listenerByTrade.containsKey(id)) {
|
||||
ListChangeListener<ChatMessage> listener = c -> update(trade, badge);
|
||||
ListChangeListener<ChatMessage> listener = c -> updateChatMessageCount(trade, badge);
|
||||
listenerByTrade.put(id, listener);
|
||||
trade.getChatMessages().addListener(listener);
|
||||
}
|
||||
|
||||
update(trade, badge);
|
||||
updateChatMessageCount(trade, badge);
|
||||
|
||||
setGraphic(badge);
|
||||
} 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -276,13 +276,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||
Offer offer = item.getTrade().getOffer();
|
||||
checkNotNull(offer);
|
||||
checkNotNull(offer.getPaymentMethod());
|
||||
String method = Res.get(offer.getPaymentMethod().getId() + "_SHORT");
|
||||
String methodCountryCode = offer.getCountryCode();
|
||||
|
||||
if (methodCountryCode != null)
|
||||
result = method + " (" + methodCountryCode + ")";
|
||||
else
|
||||
result = method;
|
||||
result = offer.getPaymentMethodNameWithCountryCode();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -135,55 +135,6 @@
|
||||
-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 */
|
||||
.jfx-tab-pane .tab-content-area {
|
||||
|
@ -1129,6 +1129,7 @@ public class GUIUtil {
|
||||
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 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}" +
|
||||
"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" +
|
||||
"(?:\\:%1$s)?", portRegexPattern);
|
||||
@ -1152,8 +1153,8 @@ public class GUIUtil {
|
||||
")"; // (IPv4-Embedded IPv6 Address)
|
||||
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);
|
||||
regexValidator.setPattern(String.format("^(?:(?:(?:%1$s)|(?:%2$s)|(?:%3$s)|(?:%4$s)),\\s*)*(?:(?:%1$s)|(?:%2$s)|(?:%3$s)|(?:%4$s))*$",
|
||||
onionV2RegexPattern, ipv4RegexPattern, ipv6RegexPattern, fqdnRegexPattern));
|
||||
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, onionV3RegexPattern, ipv4RegexPattern, ipv6RegexPattern, fqdnRegexPattern));
|
||||
return regexValidator;
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,8 @@ public class GUIUtilTest {
|
||||
assertFalse(regexValidator.validate("abcdefghijklmnop.onion:").isValid);
|
||||
|
||||
// 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
|
||||
assertTrue(regexValidator.validate("12.34.56.78").isValid);
|
||||
|
@ -22,7 +22,7 @@ You do _not_ need to install Gradle to complete the following command. The `grad
|
||||
./gradlew build
|
||||
|
||||
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
|
||||
|
||||
@ -33,7 +33,7 @@ Note: bisq runs fine on jdk10 and jdk11. jdk12 is currently not supported.
|
||||
./bisq-desktop
|
||||
|
||||
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
|
||||
|
||||
|
BIN
p2p/src/main/resources/AccountAgeWitnessStore_BTC_MAINNET
(Stored with Git LFS)
BIN
p2p/src/main/resources/AccountAgeWitnessStore_BTC_MAINNET
(Stored with Git LFS)
Binary file not shown.
BIN
p2p/src/main/resources/DaoStateStore_BTC_MAINNET
(Stored with Git LFS)
BIN
p2p/src/main/resources/DaoStateStore_BTC_MAINNET
(Stored with Git LFS)
Binary file not shown.
BIN
p2p/src/main/resources/SignedWitnessStore_BTC_MAINNET
(Stored with Git LFS)
BIN
p2p/src/main/resources/SignedWitnessStore_BTC_MAINNET
(Stored with Git LFS)
Binary file not shown.
BIN
p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET
(Stored with Git LFS)
BIN
p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET
(Stored with Git LFS)
Binary file not shown.
@ -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
|
||||
|
||||
```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!
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
|
@ -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 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"
|
||||
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}"
|
||||
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"
|
||||
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
|
||||
|
||||
|
@ -39,6 +39,88 @@ message GetVersionReply {
|
||||
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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -56,69 +138,16 @@ message GetTradeStatisticsReply {
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Offer
|
||||
// Wallets
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
service GetOffers {
|
||||
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 {
|
||||
service Wallets {
|
||||
rpc GetBalance (GetBalanceRequest) returns (GetBalanceReply) {
|
||||
}
|
||||
rpc GetAddressBalance (GetAddressBalanceRequest) returns (GetAddressBalanceReply) {
|
||||
}
|
||||
rpc GetFundingAddresses (GetFundingAddressesRequest) returns (GetFundingAddressesReply) {
|
||||
}
|
||||
rpc SetWalletPassword (SetWalletPasswordRequest) returns (SetWalletPasswordReply) {
|
||||
}
|
||||
rpc RemoveWalletPassword (RemoveWalletPasswordRequest) returns (RemoveWalletPasswordReply) {
|
||||
@ -136,6 +165,21 @@ message GetBalanceReply {
|
||||
uint64 balance = 1;
|
||||
}
|
||||
|
||||
message GetAddressBalanceRequest {
|
||||
string address = 1;
|
||||
}
|
||||
|
||||
message GetAddressBalanceReply {
|
||||
AddressBalanceInfo addressBalanceInfo = 1;
|
||||
}
|
||||
|
||||
message GetFundingAddressesRequest {
|
||||
}
|
||||
|
||||
message GetFundingAddressesReply {
|
||||
repeated AddressBalanceInfo addressBalanceInfo = 1;
|
||||
}
|
||||
|
||||
message SetWalletPasswordRequest {
|
||||
string password = 1;
|
||||
string newPassword = 2;
|
||||
@ -164,3 +208,9 @@ message UnlockWalletRequest {
|
||||
|
||||
message UnlockWalletReply {
|
||||
}
|
||||
|
||||
message AddressBalanceInfo {
|
||||
string address = 1;
|
||||
int64 balance = 2;
|
||||
int64 numConfirmations = 3;
|
||||
}
|
||||
|
@ -629,6 +629,7 @@ message Filter {
|
||||
repeated string mediators = 17;
|
||||
repeated string refundAgents = 18;
|
||||
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
|
||||
|
@ -1 +1 @@
|
||||
1.3.4-SNAPSHOT
|
||||
1.3.5-SNAPSHOT
|
||||
|
@ -10,14 +10,15 @@ Highly recommended to use SSD! Minimum specs:
|
||||
|
||||
## 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
|
||||
|
||||
### 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
|
||||
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
|
||||
|
||||
```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!
|
||||
|
||||
@ -78,8 +79,7 @@ sudo -u bisq -s
|
||||
cd bisq
|
||||
git fetch origin
|
||||
git checkout v1.2.5 # new tag
|
||||
./gradlew clean
|
||||
./gradlew build -x test
|
||||
./gradlew clean build -x test
|
||||
exit
|
||||
sudo service bisq restart
|
||||
sudo journalctl --unit bisq --follow
|
||||
|
@ -60,6 +60,11 @@ sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get upgrade -qq
|
||||
echo "[*] Installing base packages"
|
||||
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"
|
||||
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}"
|
||||
@ -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}"
|
||||
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"
|
||||
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
|
||||
|
||||
|
@ -30,7 +30,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SeedNodeMain extends ExecutableForAppWithP2p {
|
||||
private static final String VERSION = "1.3.4";
|
||||
private static final String VERSION = "1.3.5";
|
||||
private SeedNode seedNode;
|
||||
|
||||
public SeedNodeMain() {
|
||||
|
Loading…
Reference in New Issue
Block a user