mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 23:06:39 +01:00
Merge branch 'master' of github.com:bisq-network/bisq into hotfix/v1.5.8
# Conflicts: # build.gradle # desktop/package/linux/Dockerfile # desktop/package/linux/package.sh # desktop/package/linux/release.sh # desktop/package/macosx/create_app.sh # desktop/package/macosx/finalize.sh # desktop/package/macosx/insert_snapshot_version.sh # desktop/package/windows/package.bat # desktop/package/windows/release.bat # relay/src/main/resources/version.txt
This commit is contained in:
commit
bdf6463e61
58 changed files with 3781 additions and 1221 deletions
|
@ -17,26 +17,27 @@
|
|||
|
||||
package bisq.apitest;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static java.net.InetAddress.getLoopbackAddress;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.config.ApiTestConfig;
|
||||
import bisq.apitest.config.BisqAppConfig;
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.cli.GrpcStubs;
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
/**
|
||||
* Base class for all test types: 'method', 'scenario' and 'e2e'.
|
||||
|
@ -50,8 +51,8 @@ import bisq.cli.GrpcStubs;
|
|||
* <p>
|
||||
* Those documents contain information about the configurations used by this test harness:
|
||||
* bitcoin-core's bitcoin.conf and blocknotify values, bisq instance options, the DAO genesis
|
||||
* transaction id, initial BSQ and BTC balances for Bob & Alice accounts, and default
|
||||
* PerfectMoney dummy payment accounts (USD) for Bob and Alice.
|
||||
* transaction id, initial BSQ and BTC balances for Bob & Alice accounts, and Bob and
|
||||
* Alice's default payment accounts.
|
||||
* <p>
|
||||
* During a build, the
|
||||
* <a href="https://github.com/bisq-network/bisq/blob/master/docs/dao-setup.zip">dao-setup.zip</a>
|
||||
|
@ -69,8 +70,12 @@ public class ApiTestCase {
|
|||
protected static ApiTestConfig config;
|
||||
protected static BitcoinCliHelper bitcoinCli;
|
||||
|
||||
// gRPC service stubs are used by method & scenario tests, but not e2e tests.
|
||||
private static final Map<BisqAppConfig, GrpcStubs> grpcStubsCache = new HashMap<>();
|
||||
@Nullable
|
||||
protected static GrpcClient arbClient;
|
||||
@Nullable
|
||||
protected static GrpcClient aliceClient;
|
||||
@Nullable
|
||||
protected static GrpcClient bobClient;
|
||||
|
||||
public static void setUpScaffold(Enum<?>... supportingApps)
|
||||
throws InterruptedException, ExecutionException, IOException {
|
||||
|
@ -79,6 +84,7 @@ public class ApiTestCase {
|
|||
.setUp();
|
||||
config = scaffold.config;
|
||||
bitcoinCli = new BitcoinCliHelper((config));
|
||||
createGrpcClients();
|
||||
}
|
||||
|
||||
public static void setUpScaffold(String[] params)
|
||||
|
@ -90,24 +96,28 @@ public class ApiTestCase {
|
|||
scaffold = new Scaffold(params).setUp();
|
||||
config = scaffold.config;
|
||||
bitcoinCli = new BitcoinCliHelper((config));
|
||||
createGrpcClients();
|
||||
}
|
||||
|
||||
public static void tearDownScaffold() {
|
||||
scaffold.tearDown();
|
||||
}
|
||||
|
||||
protected static String getEnumArrayAsString(Enum<?>[] supportingApps) {
|
||||
return stream(supportingApps).map(Enum::name).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
protected static GrpcStubs grpcStubs(BisqAppConfig bisqAppConfig) {
|
||||
if (grpcStubsCache.containsKey(bisqAppConfig)) {
|
||||
return grpcStubsCache.get(bisqAppConfig);
|
||||
} else {
|
||||
GrpcStubs stubs = new GrpcStubs(InetAddress.getLoopbackAddress().getHostAddress(),
|
||||
bisqAppConfig.apiPort, config.apiPassword);
|
||||
grpcStubsCache.put(bisqAppConfig, stubs);
|
||||
return stubs;
|
||||
protected static void createGrpcClients() {
|
||||
if (config.supportingApps.contains(alicedaemon.name())) {
|
||||
aliceClient = new GrpcClient(getLoopbackAddress().getHostAddress(),
|
||||
alicedaemon.apiPort,
|
||||
config.apiPassword);
|
||||
}
|
||||
if (config.supportingApps.contains(bobdaemon.name())) {
|
||||
bobClient = new GrpcClient(getLoopbackAddress().getHostAddress(),
|
||||
bobdaemon.apiPort,
|
||||
config.apiPassword);
|
||||
}
|
||||
if (config.supportingApps.contains(arbdaemon.name())) {
|
||||
arbClient = new GrpcClient(getLoopbackAddress().getHostAddress(),
|
||||
arbdaemon.apiPort,
|
||||
config.apiPassword);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
package bisq.apitest.method;
|
||||
|
||||
import bisq.proto.grpc.GetMethodHelpRequest;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
@ -51,10 +49,7 @@ public class GetMethodHelpTest extends MethodTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
public void testGetCreateOfferHelp() {
|
||||
var help = grpcStubs(alicedaemon).helpService
|
||||
.getMethodHelp(GetMethodHelpRequest.newBuilder()
|
||||
.setMethodName(createoffer.name()).build())
|
||||
.getMethodHelp();
|
||||
var help = aliceClient.getMethodHelp(createoffer);
|
||||
assertNotNull(help);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
package bisq.apitest.method;
|
||||
|
||||
import bisq.proto.grpc.GetVersionRequest;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
@ -51,8 +49,7 @@ public class GetVersionTest extends MethodTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
public void testGetVersion() {
|
||||
var version = grpcStubs(alicedaemon).versionService
|
||||
.getVersion(GetVersionRequest.newBuilder().build()).getVersion();
|
||||
var version = aliceClient.getVersion();
|
||||
assertEquals(VERSION, version);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,76 +18,27 @@
|
|||
package bisq.apitest.method;
|
||||
|
||||
import bisq.core.api.model.PaymentAccountForm;
|
||||
import bisq.core.api.model.TxFeeRateInfo;
|
||||
import bisq.core.payment.F2FAccount;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import bisq.proto.grpc.AddressBalanceInfo;
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.BsqBalanceInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.CancelOfferRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||
import bisq.proto.grpc.CreatePaymentAccountRequest;
|
||||
import bisq.proto.grpc.GetAddressBalanceRequest;
|
||||
import bisq.proto.grpc.GetBalancesRequest;
|
||||
import bisq.proto.grpc.GetFundingAddressesRequest;
|
||||
import bisq.proto.grpc.GetMethodHelpRequest;
|
||||
import bisq.proto.grpc.GetMyOfferRequest;
|
||||
import bisq.proto.grpc.GetOfferRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountFormRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||
import bisq.proto.grpc.GetPaymentMethodsRequest;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
import bisq.proto.grpc.GetTransactionRequest;
|
||||
import bisq.proto.grpc.GetTxFeeRateRequest;
|
||||
import bisq.proto.grpc.GetUnusedBsqAddressRequest;
|
||||
import bisq.proto.grpc.KeepFundsRequest;
|
||||
import bisq.proto.grpc.LockWalletRequest;
|
||||
import bisq.proto.grpc.MarketPriceRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||
import bisq.proto.grpc.SendBsqRequest;
|
||||
import bisq.proto.grpc.SendBtcRequest;
|
||||
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||
import bisq.proto.grpc.TakeOfferRequest;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
import bisq.proto.grpc.TxInfo;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
import protobuf.PaymentMethod;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.ApiTestCase;
|
||||
import bisq.apitest.config.BisqAppConfig;
|
||||
import bisq.cli.GrpcStubs;
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
public class MethodTest extends ApiTestCase {
|
||||
|
||||
|
@ -95,13 +46,7 @@ public class MethodTest extends ApiTestCase {
|
|||
protected static final String MEDIATOR = "mediator";
|
||||
protected static final String REFUND_AGENT = "refundagent";
|
||||
|
||||
protected static GrpcStubs aliceStubs;
|
||||
protected static GrpcStubs bobStubs;
|
||||
|
||||
protected static PaymentAccount alicesDummyAcct;
|
||||
protected static PaymentAccount bobsDummyAcct;
|
||||
|
||||
private static final CoreProtoResolver CORE_PROTO_RESOLVER = new CoreProtoResolver();
|
||||
protected static final CoreProtoResolver CORE_PROTO_RESOLVER = new CoreProtoResolver();
|
||||
|
||||
private static final Function<Enum<?>[], String> toNameList = (enums) ->
|
||||
stream(enums).map(Enum::name).collect(Collectors.joining(","));
|
||||
|
@ -116,7 +61,7 @@ public class MethodTest extends ApiTestCase {
|
|||
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
|
||||
"--enableBisqDebugging", "false"
|
||||
});
|
||||
doPostStartup(registerDisputeAgents, generateBtcBlock, supportingApps);
|
||||
doPostStartup(registerDisputeAgents, generateBtcBlock);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
|
@ -130,27 +75,16 @@ public class MethodTest extends ApiTestCase {
|
|||
"--supportingApps", toNameList.apply(supportingApps),
|
||||
"--enableBisqDebugging", "false"
|
||||
});
|
||||
doPostStartup(registerDisputeAgents, generateBtcBlock, supportingApps);
|
||||
doPostStartup(registerDisputeAgents, generateBtcBlock);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void doPostStartup(boolean registerDisputeAgents,
|
||||
boolean generateBtcBlock,
|
||||
Enum<?>... supportingApps) {
|
||||
boolean generateBtcBlock) {
|
||||
if (registerDisputeAgents) {
|
||||
registerDisputeAgents(arbdaemon);
|
||||
}
|
||||
|
||||
if (stream(supportingApps).map(Enum::name).anyMatch(name -> name.equals(alicedaemon.name()))) {
|
||||
aliceStubs = grpcStubs(alicedaemon);
|
||||
alicesDummyAcct = getDefaultPerfectDummyPaymentAccount(alicedaemon);
|
||||
}
|
||||
|
||||
if (stream(supportingApps).map(Enum::name).anyMatch(name -> name.equals(bobdaemon.name()))) {
|
||||
bobStubs = grpcStubs(bobdaemon);
|
||||
bobsDummyAcct = getDefaultPerfectDummyPaymentAccount(bobdaemon);
|
||||
registerDisputeAgents();
|
||||
}
|
||||
|
||||
// Generate 1 regtest block for alice's and/or bob's wallet to
|
||||
|
@ -159,212 +93,11 @@ public class MethodTest extends ApiTestCase {
|
|||
genBtcBlocksThenWait(1, 1500);
|
||||
}
|
||||
|
||||
// Convenience methods for building gRPC request objects
|
||||
protected final GetBalancesRequest createGetBalancesRequest(String currencyCode) {
|
||||
return GetBalancesRequest.newBuilder().setCurrencyCode(currencyCode).build();
|
||||
}
|
||||
|
||||
protected final GetAddressBalanceRequest createGetAddressBalanceRequest(String address) {
|
||||
return GetAddressBalanceRequest.newBuilder().setAddress(address).build();
|
||||
}
|
||||
|
||||
protected final SetWalletPasswordRequest createSetWalletPasswordRequest(String password) {
|
||||
return SetWalletPasswordRequest.newBuilder().setPassword(password).build();
|
||||
}
|
||||
|
||||
protected final SetWalletPasswordRequest createSetWalletPasswordRequest(String oldPassword, String newPassword) {
|
||||
return SetWalletPasswordRequest.newBuilder().setPassword(oldPassword).setNewPassword(newPassword).build();
|
||||
}
|
||||
|
||||
protected final RemoveWalletPasswordRequest createRemoveWalletPasswordRequest(String password) {
|
||||
return RemoveWalletPasswordRequest.newBuilder().setPassword(password).build();
|
||||
}
|
||||
|
||||
protected final UnlockWalletRequest createUnlockWalletRequest(String password, long timeout) {
|
||||
return UnlockWalletRequest.newBuilder().setPassword(password).setTimeout(timeout).build();
|
||||
}
|
||||
|
||||
protected final LockWalletRequest createLockWalletRequest() {
|
||||
return LockWalletRequest.newBuilder().build();
|
||||
}
|
||||
|
||||
protected final GetUnusedBsqAddressRequest createGetUnusedBsqAddressRequest() {
|
||||
return GetUnusedBsqAddressRequest.newBuilder().build();
|
||||
}
|
||||
|
||||
protected final SendBsqRequest createSendBsqRequest(String address,
|
||||
String amount,
|
||||
String txFeeRate) {
|
||||
return SendBsqRequest.newBuilder()
|
||||
.setAddress(address)
|
||||
.setAmount(amount)
|
||||
.setTxFeeRate(txFeeRate)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected final SendBtcRequest createSendBtcRequest(String address,
|
||||
String amount,
|
||||
String txFeeRate,
|
||||
String memo) {
|
||||
return SendBtcRequest.newBuilder()
|
||||
.setAddress(address)
|
||||
.setAmount(amount)
|
||||
.setTxFeeRate(txFeeRate)
|
||||
.setMemo(memo)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected final GetFundingAddressesRequest createGetFundingAddressesRequest() {
|
||||
return GetFundingAddressesRequest.newBuilder().build();
|
||||
}
|
||||
|
||||
protected final MarketPriceRequest createMarketPriceRequest(String currencyCode) {
|
||||
return MarketPriceRequest.newBuilder().setCurrencyCode(currencyCode).build();
|
||||
}
|
||||
|
||||
protected final GetOfferRequest createGetOfferRequest(String offerId) {
|
||||
return GetOfferRequest.newBuilder().setId(offerId).build();
|
||||
}
|
||||
|
||||
protected final GetMyOfferRequest createGetMyOfferRequest(String offerId) {
|
||||
return GetMyOfferRequest.newBuilder().setId(offerId).build();
|
||||
}
|
||||
|
||||
protected final CancelOfferRequest createCancelOfferRequest(String offerId) {
|
||||
return CancelOfferRequest.newBuilder().setId(offerId).build();
|
||||
}
|
||||
|
||||
protected final TakeOfferRequest createTakeOfferRequest(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode) {
|
||||
return TakeOfferRequest.newBuilder()
|
||||
.setOfferId(offerId)
|
||||
.setPaymentAccountId(paymentAccountId)
|
||||
.setTakerFeeCurrencyCode(takerFeeCurrencyCode)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected final GetTradeRequest createGetTradeRequest(String tradeId) {
|
||||
return GetTradeRequest.newBuilder().setTradeId(tradeId).build();
|
||||
}
|
||||
|
||||
protected final ConfirmPaymentStartedRequest createConfirmPaymentStartedRequest(String tradeId) {
|
||||
return ConfirmPaymentStartedRequest.newBuilder().setTradeId(tradeId).build();
|
||||
}
|
||||
|
||||
protected final ConfirmPaymentReceivedRequest createConfirmPaymentReceivedRequest(String tradeId) {
|
||||
return ConfirmPaymentReceivedRequest.newBuilder().setTradeId(tradeId).build();
|
||||
}
|
||||
|
||||
protected final KeepFundsRequest createKeepFundsRequest(String tradeId) {
|
||||
return KeepFundsRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected final WithdrawFundsRequest createWithdrawFundsRequest(String tradeId,
|
||||
String address,
|
||||
String memo) {
|
||||
return WithdrawFundsRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setAddress(address)
|
||||
.setMemo(memo)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected final GetMethodHelpRequest createGetMethodHelpRequest(String methodName) {
|
||||
return GetMethodHelpRequest.newBuilder().setMethodName(methodName).build();
|
||||
}
|
||||
|
||||
// Convenience methods for calling frequently used & thoroughly tested gRPC services.
|
||||
protected final BalancesInfo getBalances(BisqAppConfig bisqAppConfig, String currencyCode) {
|
||||
return grpcStubs(bisqAppConfig).walletsService.getBalances(
|
||||
createGetBalancesRequest(currencyCode)).getBalances();
|
||||
}
|
||||
|
||||
protected final BsqBalanceInfo getBsqBalances(BisqAppConfig bisqAppConfig) {
|
||||
return getBalances(bisqAppConfig, "bsq").getBsq();
|
||||
}
|
||||
|
||||
protected final BtcBalanceInfo getBtcBalances(BisqAppConfig bisqAppConfig) {
|
||||
return getBalances(bisqAppConfig, "btc").getBtc();
|
||||
}
|
||||
|
||||
protected final AddressBalanceInfo getAddressBalance(BisqAppConfig bisqAppConfig, String address) {
|
||||
return grpcStubs(bisqAppConfig).walletsService.getAddressBalance(createGetAddressBalanceRequest(address)).getAddressBalanceInfo();
|
||||
}
|
||||
|
||||
protected final void unlockWallet(BisqAppConfig bisqAppConfig, String password, long timeout) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs(bisqAppConfig).walletsService.unlockWallet(createUnlockWalletRequest(password, timeout));
|
||||
}
|
||||
|
||||
protected final void lockWallet(BisqAppConfig bisqAppConfig) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs(bisqAppConfig).walletsService.lockWallet(createLockWalletRequest());
|
||||
}
|
||||
|
||||
protected final String getUnusedBsqAddress(BisqAppConfig bisqAppConfig) {
|
||||
return grpcStubs(bisqAppConfig).walletsService.getUnusedBsqAddress(createGetUnusedBsqAddressRequest()).getAddress();
|
||||
}
|
||||
|
||||
protected final TxInfo sendBsq(BisqAppConfig bisqAppConfig,
|
||||
String address,
|
||||
String amount) {
|
||||
return sendBsq(bisqAppConfig, address, amount, "");
|
||||
}
|
||||
|
||||
protected final TxInfo sendBsq(BisqAppConfig bisqAppConfig,
|
||||
String address,
|
||||
String amount,
|
||||
String txFeeRate) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
return grpcStubs(bisqAppConfig).walletsService.sendBsq(createSendBsqRequest(address,
|
||||
amount,
|
||||
txFeeRate))
|
||||
.getTxInfo();
|
||||
}
|
||||
|
||||
protected final TxInfo sendBtc(BisqAppConfig bisqAppConfig, String address, String amount) {
|
||||
return sendBtc(bisqAppConfig, address, amount, "", "");
|
||||
}
|
||||
|
||||
protected final TxInfo sendBtc(BisqAppConfig bisqAppConfig,
|
||||
String address,
|
||||
String amount,
|
||||
String txFeeRate,
|
||||
String memo) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
return grpcStubs(bisqAppConfig).walletsService.sendBtc(
|
||||
createSendBtcRequest(address, amount, txFeeRate, memo))
|
||||
.getTxInfo();
|
||||
}
|
||||
|
||||
protected final String getUnusedBtcAddress(BisqAppConfig bisqAppConfig) {
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
return grpcStubs(bisqAppConfig).walletsService.getFundingAddresses(createGetFundingAddressesRequest())
|
||||
.getAddressBalanceInfoList()
|
||||
.stream()
|
||||
.filter(a -> a.getBalance() == 0 && a.getNumConfirmations() == 0)
|
||||
.findFirst()
|
||||
.get()
|
||||
.getAddress();
|
||||
}
|
||||
|
||||
protected final List<PaymentMethod> getPaymentMethods(BisqAppConfig bisqAppConfig) {
|
||||
var req = GetPaymentMethodsRequest.newBuilder().build();
|
||||
return grpcStubs(bisqAppConfig).paymentAccountsService.getPaymentMethods(req).getPaymentMethodsList();
|
||||
}
|
||||
|
||||
protected final File getPaymentAccountForm(BisqAppConfig bisqAppConfig, String paymentMethodId) {
|
||||
protected final File getPaymentAccountForm(GrpcClient grpcClient, String paymentMethodId) {
|
||||
// We take seemingly unnecessary steps to get a File object, but the point is to
|
||||
// test the API, and we do not directly ask bisq.core.api.model.PaymentAccountForm
|
||||
// for an empty json form (file).
|
||||
var req = GetPaymentAccountFormRequest.newBuilder()
|
||||
.setPaymentMethodId(paymentMethodId)
|
||||
.build();
|
||||
String jsonString = grpcStubs(bisqAppConfig).paymentAccountsService.getPaymentAccountForm(req)
|
||||
.getPaymentAccountFormJson();
|
||||
String jsonString = grpcClient.getPaymentAcctFormAsJson(paymentMethodId);
|
||||
// Write the json string to a file here in the test case.
|
||||
File jsonFile = PaymentAccountForm.getTmpJsonFile(paymentMethodId);
|
||||
try (PrintWriter out = new PrintWriter(jsonFile, UTF_8)) {
|
||||
|
@ -375,113 +108,9 @@ public class MethodTest extends ApiTestCase {
|
|||
return jsonFile;
|
||||
}
|
||||
|
||||
protected final bisq.core.payment.PaymentAccount createPaymentAccount(BisqAppConfig bisqAppConfig,
|
||||
String jsonString) {
|
||||
var req = CreatePaymentAccountRequest.newBuilder()
|
||||
.setPaymentAccountForm(jsonString)
|
||||
.build();
|
||||
var paymentAccountsService = grpcStubs(bisqAppConfig).paymentAccountsService;
|
||||
// Normally, we can do asserts on the protos from the gRPC service, but in this
|
||||
// case we need to return a bisq.core.payment.PaymentAccount so it can be cast
|
||||
// to its sub type.
|
||||
return fromProto(paymentAccountsService.createPaymentAccount(req).getPaymentAccount());
|
||||
}
|
||||
|
||||
protected static List<PaymentAccount> getPaymentAccounts(BisqAppConfig bisqAppConfig) {
|
||||
var req = GetPaymentAccountsRequest.newBuilder().build();
|
||||
return grpcStubs(bisqAppConfig).paymentAccountsService.getPaymentAccounts(req)
|
||||
.getPaymentAccountsList()
|
||||
.stream()
|
||||
.sorted(comparing(PaymentAccount::getCreationDate))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected static PaymentAccount getDefaultPerfectDummyPaymentAccount(BisqAppConfig bisqAppConfig) {
|
||||
PaymentAccount paymentAccount = getPaymentAccounts(bisqAppConfig).get(0);
|
||||
assertEquals("PerfectMoney dummy", paymentAccount.getAccountName());
|
||||
return paymentAccount;
|
||||
}
|
||||
|
||||
protected final double getMarketPrice(BisqAppConfig bisqAppConfig, String currencyCode) {
|
||||
var req = createMarketPriceRequest(currencyCode);
|
||||
return grpcStubs(bisqAppConfig).priceService.getMarketPrice(req).getPrice();
|
||||
}
|
||||
|
||||
protected final OfferInfo getOffer(BisqAppConfig bisqAppConfig, String offerId) {
|
||||
var req = createGetOfferRequest(offerId);
|
||||
return grpcStubs(bisqAppConfig).offersService.getOffer(req).getOffer();
|
||||
}
|
||||
|
||||
protected final OfferInfo getMyOffer(BisqAppConfig bisqAppConfig, String offerId) {
|
||||
var req = createGetMyOfferRequest(offerId);
|
||||
return grpcStubs(bisqAppConfig).offersService.getMyOffer(req).getOffer();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
protected final void cancelOffer(BisqAppConfig bisqAppConfig, String offerId) {
|
||||
var req = createCancelOfferRequest(offerId);
|
||||
grpcStubs(bisqAppConfig).offersService.cancelOffer(req);
|
||||
}
|
||||
|
||||
protected final TradeInfo getTrade(BisqAppConfig bisqAppConfig, String tradeId) {
|
||||
var req = createGetTradeRequest(tradeId);
|
||||
return grpcStubs(bisqAppConfig).tradesService.getTrade(req).getTrade();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
protected final void confirmPaymentStarted(BisqAppConfig bisqAppConfig, String tradeId) {
|
||||
var req = createConfirmPaymentStartedRequest(tradeId);
|
||||
grpcStubs(bisqAppConfig).tradesService.confirmPaymentStarted(req);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
protected final void confirmPaymentReceived(BisqAppConfig bisqAppConfig, String tradeId) {
|
||||
var req = createConfirmPaymentReceivedRequest(tradeId);
|
||||
grpcStubs(bisqAppConfig).tradesService.confirmPaymentReceived(req);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
protected final void keepFunds(BisqAppConfig bisqAppConfig, String tradeId) {
|
||||
var req = createKeepFundsRequest(tradeId);
|
||||
grpcStubs(bisqAppConfig).tradesService.keepFunds(req);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
protected final void withdrawFunds(BisqAppConfig bisqAppConfig,
|
||||
String tradeId,
|
||||
String address,
|
||||
String memo) {
|
||||
var req = createWithdrawFundsRequest(tradeId, address, memo);
|
||||
grpcStubs(bisqAppConfig).tradesService.withdrawFunds(req);
|
||||
}
|
||||
|
||||
protected final TxFeeRateInfo getTxFeeRate(BisqAppConfig bisqAppConfig) {
|
||||
var req = GetTxFeeRateRequest.newBuilder().build();
|
||||
return TxFeeRateInfo.fromProto(
|
||||
grpcStubs(bisqAppConfig).walletsService.getTxFeeRate(req).getTxFeeRateInfo());
|
||||
}
|
||||
|
||||
protected final TxFeeRateInfo setTxFeeRate(BisqAppConfig bisqAppConfig, long feeRate) {
|
||||
var req = SetTxFeeRatePreferenceRequest.newBuilder()
|
||||
.setTxFeeRatePreference(feeRate)
|
||||
.build();
|
||||
return TxFeeRateInfo.fromProto(
|
||||
grpcStubs(bisqAppConfig).walletsService.setTxFeeRatePreference(req).getTxFeeRateInfo());
|
||||
}
|
||||
|
||||
protected final TxFeeRateInfo unsetTxFeeRate(BisqAppConfig bisqAppConfig) {
|
||||
var req = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
|
||||
return TxFeeRateInfo.fromProto(
|
||||
grpcStubs(bisqAppConfig).walletsService.unsetTxFeeRatePreference(req).getTxFeeRateInfo());
|
||||
}
|
||||
|
||||
protected final TxInfo getTransaction(BisqAppConfig bisqAppConfig, String txId) {
|
||||
var req = GetTransactionRequest.newBuilder().setTxId(txId).build();
|
||||
return grpcStubs(bisqAppConfig).walletsService.getTransaction(req).getTxInfo();
|
||||
}
|
||||
|
||||
public bisq.core.payment.PaymentAccount createDummyF2FAccount(BisqAppConfig bisqAppConfig,
|
||||
String countryCode) {
|
||||
protected bisq.core.payment.PaymentAccount createDummyF2FAccount(GrpcClient grpcClient,
|
||||
String countryCode) {
|
||||
String f2fAccountJsonString = "{\n" +
|
||||
" \"_COMMENTS_\": \"This is a dummy account.\",\n" +
|
||||
" \"paymentMethodId\": \"F2F\",\n" +
|
||||
|
@ -491,35 +120,26 @@ public class MethodTest extends ApiTestCase {
|
|||
" \"country\": \"" + countryCode.toUpperCase() + "\",\n" +
|
||||
" \"extraInfo\": \"Salt Lick #213\"\n" +
|
||||
"}\n";
|
||||
F2FAccount f2FAccount = (F2FAccount) createPaymentAccount(bisqAppConfig, f2fAccountJsonString);
|
||||
F2FAccount f2FAccount = (F2FAccount) createPaymentAccount(grpcClient, f2fAccountJsonString);
|
||||
return f2FAccount;
|
||||
}
|
||||
|
||||
protected final String getMethodHelp(BisqAppConfig bisqAppConfig, String methodName) {
|
||||
var req = createGetMethodHelpRequest(methodName);
|
||||
return grpcStubs(bisqAppConfig).helpService.getMethodHelp(req).getMethodHelp();
|
||||
protected final bisq.core.payment.PaymentAccount createPaymentAccount(GrpcClient grpcClient, String jsonString) {
|
||||
// Normally, we do asserts on the protos from the gRPC service, but in this
|
||||
// case we need a bisq.core.payment.PaymentAccount so it can be cast to its
|
||||
// sub type.
|
||||
var paymentAccount = grpcClient.createPaymentAccount(jsonString);
|
||||
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
|
||||
}
|
||||
|
||||
// Static conveniences for test methods and test case fixture setups.
|
||||
|
||||
protected static RegisterDisputeAgentRequest createRegisterDisputeAgentRequest(String disputeAgentType) {
|
||||
return RegisterDisputeAgentRequest.newBuilder()
|
||||
.setDisputeAgentType(disputeAgentType.toLowerCase())
|
||||
.setRegistrationKey(DEV_PRIVILEGE_PRIV_KEY).build();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ResultOfMethodCallIgnored", "SameParameterValue"})
|
||||
protected static void registerDisputeAgents(BisqAppConfig bisqAppConfig) {
|
||||
var disputeAgentsService = grpcStubs(bisqAppConfig).disputeAgentsService;
|
||||
disputeAgentsService.registerDisputeAgent(createRegisterDisputeAgentRequest(MEDIATOR));
|
||||
disputeAgentsService.registerDisputeAgent(createRegisterDisputeAgentRequest(REFUND_AGENT));
|
||||
protected static void registerDisputeAgents() {
|
||||
arbClient.registerDisputeAgent(MEDIATOR, DEV_PRIVILEGE_PRIV_KEY);
|
||||
arbClient.registerDisputeAgent(REFUND_AGENT, DEV_PRIVILEGE_PRIV_KEY);
|
||||
}
|
||||
|
||||
protected static String encodeToHex(String s) {
|
||||
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
|
||||
}
|
||||
|
||||
private bisq.core.payment.PaymentAccount fromProto(PaymentAccount proto) {
|
||||
return bisq.core.payment.PaymentAccount.fromProto(proto, CORE_PROTO_RESOLVER);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
package bisq.apitest.method;
|
||||
|
||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -58,9 +56,8 @@ public class RegisterDisputeAgentsTest extends MethodTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
public void testRegisterArbitratorShouldThrowException() {
|
||||
var req = createRegisterDisputeAgentRequest(ARBITRATOR);
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req));
|
||||
arbClient.registerDisputeAgent(ARBITRATOR, DEV_PRIVILEGE_PRIV_KEY));
|
||||
assertEquals("INVALID_ARGUMENT: arbitrators must be registered in a Bisq UI",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
@ -68,9 +65,8 @@ public class RegisterDisputeAgentsTest extends MethodTest {
|
|||
@Test
|
||||
@Order(2)
|
||||
public void testInvalidDisputeAgentTypeArgShouldThrowException() {
|
||||
var req = createRegisterDisputeAgentRequest("badagent");
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req));
|
||||
arbClient.registerDisputeAgent("badagent", DEV_PRIVILEGE_PRIV_KEY));
|
||||
assertEquals("INVALID_ARGUMENT: unknown dispute agent type 'badagent'",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
@ -78,11 +74,8 @@ public class RegisterDisputeAgentsTest extends MethodTest {
|
|||
@Test
|
||||
@Order(3)
|
||||
public void testInvalidRegistrationKeyArgShouldThrowException() {
|
||||
var req = RegisterDisputeAgentRequest.newBuilder()
|
||||
.setDisputeAgentType(REFUND_AGENT)
|
||||
.setRegistrationKey("invalid" + DEV_PRIVILEGE_PRIV_KEY).build();
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req));
|
||||
arbClient.registerDisputeAgent(REFUND_AGENT, "invalid" + DEV_PRIVILEGE_PRIV_KEY));
|
||||
assertEquals("INVALID_ARGUMENT: invalid registration key",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
@ -90,15 +83,13 @@ public class RegisterDisputeAgentsTest extends MethodTest {
|
|||
@Test
|
||||
@Order(4)
|
||||
public void testRegisterMediator() {
|
||||
var req = createRegisterDisputeAgentRequest(MEDIATOR);
|
||||
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req);
|
||||
arbClient.registerDisputeAgent(MEDIATOR, DEV_PRIVILEGE_PRIV_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
public void testRegisterRefundAgent() {
|
||||
var req = createRegisterDisputeAgentRequest(REFUND_AGENT);
|
||||
grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req);
|
||||
arbClient.registerDisputeAgent(REFUND_AGENT, DEV_PRIVILEGE_PRIV_KEY);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
|
|
|
@ -18,20 +18,11 @@
|
|||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
import bisq.proto.grpc.GetMyOffersRequest;
|
||||
import bisq.proto.grpc.GetOffersRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
@ -44,17 +35,12 @@ import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
|||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static bisq.common.util.MathUtils.roundDouble;
|
||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
||||
import static java.lang.String.format;
|
||||
import static java.math.RoundingMode.HALF_UP;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.cli.GrpcStubs;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractOfferTest extends MethodTest {
|
||||
|
@ -70,109 +56,11 @@ public abstract class AbstractOfferTest extends MethodTest {
|
|||
bobdaemon);
|
||||
}
|
||||
|
||||
protected final OfferInfo createAliceOffer(PaymentAccount paymentAccount,
|
||||
String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
String makerFeeCurrencyCode) {
|
||||
return createMarketBasedPricedOffer(aliceStubs,
|
||||
paymentAccount,
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
makerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
protected final OfferInfo createBobOffer(PaymentAccount paymentAccount,
|
||||
String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
String makerFeeCurrencyCode) {
|
||||
return createMarketBasedPricedOffer(bobStubs,
|
||||
paymentAccount,
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
makerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
protected final OfferInfo createMarketBasedPricedOffer(GrpcStubs grpcStubs,
|
||||
PaymentAccount paymentAccount,
|
||||
String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
String makerFeeCurrencyCode) {
|
||||
var req = CreateOfferRequest.newBuilder()
|
||||
.setPaymentAccountId(paymentAccount.getId())
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.setAmount(amount)
|
||||
.setMinAmount(amount)
|
||||
.setUseMarketBasedPrice(true)
|
||||
.setMarketPriceMargin(0.00)
|
||||
.setPrice("0")
|
||||
.setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent())
|
||||
.setMakerFeeCurrencyCode(makerFeeCurrencyCode)
|
||||
.build();
|
||||
return grpcStubs.offersService.createOffer(req).getOffer();
|
||||
}
|
||||
|
||||
protected final OfferInfo getOffer(String offerId) {
|
||||
return aliceStubs.offersService.getOffer(createGetOfferRequest(offerId)).getOffer();
|
||||
}
|
||||
|
||||
protected final OfferInfo getMyOffer(String offerId) {
|
||||
return aliceStubs.offersService.getMyOffer(createGetMyOfferRequest(offerId)).getOffer();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
protected final void cancelOffer(GrpcStubs grpcStubs, String offerId) {
|
||||
grpcStubs.offersService.cancelOffer(createCancelOfferRequest(offerId));
|
||||
}
|
||||
|
||||
protected final OfferInfo getMostRecentOffer(GrpcStubs grpcStubs, String direction, String currencyCode) {
|
||||
List<OfferInfo> offerInfoList = getOffersSortedByDate(grpcStubs, direction, currencyCode);
|
||||
if (offerInfoList.isEmpty())
|
||||
fail(format("No %s offers found for currency %s", direction, currencyCode));
|
||||
|
||||
return offerInfoList.get(offerInfoList.size() - 1);
|
||||
}
|
||||
|
||||
protected final List<OfferInfo> getOffersSortedByDate(GrpcStubs grpcStubs,
|
||||
String direction,
|
||||
String currencyCode) {
|
||||
var req = GetOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode).build();
|
||||
var reply = grpcStubs.offersService.getOffers(req);
|
||||
return sortOffersByDate(reply.getOffersList());
|
||||
}
|
||||
|
||||
protected final List<OfferInfo> getMyOffersSortedByDate(GrpcStubs grpcStubs,
|
||||
String direction,
|
||||
String currencyCode) {
|
||||
var req = GetMyOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode).build();
|
||||
var reply = grpcStubs.offersService.getMyOffers(req);
|
||||
return sortOffersByDate(reply.getOffersList());
|
||||
}
|
||||
|
||||
protected final List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||
return offerInfoList.stream()
|
||||
.sorted(comparing(OfferInfo::getDate))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected double getScaledOfferPrice(double offerPrice, String currencyCode) {
|
||||
int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
return scaleDownByPowerOf10(offerPrice, precision);
|
||||
}
|
||||
|
||||
protected final double getMarketPrice(String currencyCode) {
|
||||
return getMarketPrice(alicedaemon, currencyCode);
|
||||
}
|
||||
|
||||
protected final double getPercentageDifference(double price1, double price2) {
|
||||
return BigDecimal.valueOf(roundDouble((1 - (price1 / price2)), 5))
|
||||
.setScale(4, HALF_UP)
|
||||
|
|
|
@ -17,13 +17,12 @@
|
|||
|
||||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.core.btc.wallet.Restrictions;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
@ -33,7 +32,7 @@ import org.junit.jupiter.api.Order;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@Disabled
|
||||
|
@ -45,45 +44,43 @@ public class CancelOfferTest extends AbstractOfferTest {
|
|||
private static final String CURRENCY_CODE = "cad";
|
||||
private static final int MAX_OFFERS = 3;
|
||||
|
||||
private final Consumer<String> createOfferToCancel = (paymentAccountId) -> {
|
||||
aliceClient.createMarketBasedPricedOffer(DIRECTION,
|
||||
CURRENCY_CODE,
|
||||
10000000L,
|
||||
10000000L,
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
paymentAccountId,
|
||||
"bsq");
|
||||
};
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCancelOffer() {
|
||||
PaymentAccount cadAccount = createDummyF2FAccount(alicedaemon, "CA");
|
||||
var req = CreateOfferRequest.newBuilder()
|
||||
.setPaymentAccountId(cadAccount.getId())
|
||||
.setDirection(DIRECTION)
|
||||
.setCurrencyCode(CURRENCY_CODE)
|
||||
.setAmount(10000000)
|
||||
.setMinAmount(10000000)
|
||||
.setUseMarketBasedPrice(true)
|
||||
.setMarketPriceMargin(0.00)
|
||||
.setPrice("0")
|
||||
.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent())
|
||||
.setMakerFeeCurrencyCode("bsq")
|
||||
.build();
|
||||
PaymentAccount cadAccount = createDummyF2FAccount(aliceClient, "CA");
|
||||
|
||||
// Create some offers.
|
||||
for (int i = 1; i <= MAX_OFFERS; i++) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
aliceStubs.offersService.createOffer(req);
|
||||
createOfferToCancel.accept(cadAccount.getId());
|
||||
// Wait for Alice's AddToOfferBook task.
|
||||
// Wait times vary; my logs show >= 2 second delay.
|
||||
sleep(2500);
|
||||
}
|
||||
|
||||
List<OfferInfo> offers = getMyOffersSortedByDate(aliceStubs, DIRECTION, CURRENCY_CODE);
|
||||
List<OfferInfo> offers = aliceClient.getMyOffersSortedByDate(DIRECTION, CURRENCY_CODE);
|
||||
assertEquals(MAX_OFFERS, offers.size());
|
||||
|
||||
// Cancel the offers, checking the open offer count after each offer removal.
|
||||
for (int i = 1; i <= MAX_OFFERS; i++) {
|
||||
cancelOffer(aliceStubs, offers.remove(0).getId());
|
||||
offers = getMyOffersSortedByDate(aliceStubs, DIRECTION, CURRENCY_CODE);
|
||||
aliceClient.cancelOffer(offers.remove(0).getId());
|
||||
offers = aliceClient.getMyOffersSortedByDate(DIRECTION, CURRENCY_CODE);
|
||||
assertEquals(MAX_OFFERS - i, offers.size());
|
||||
}
|
||||
|
||||
sleep(1000); // wait for offer removal
|
||||
|
||||
offers = getMyOffersSortedByDate(aliceStubs, DIRECTION, CURRENCY_CODE);
|
||||
offers = aliceClient.getMyOffersSortedByDate(DIRECTION, CURRENCY_CODE);
|
||||
assertEquals(0, offers.size());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@ package bisq.apitest.method.offer;
|
|||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
|
@ -29,7 +27,6 @@ import org.junit.jupiter.api.Order;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
@ -45,20 +42,15 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() {
|
||||
PaymentAccount audAccount = createDummyF2FAccount(alicedaemon, "AU");
|
||||
var req = CreateOfferRequest.newBuilder()
|
||||
.setPaymentAccountId(audAccount.getId())
|
||||
.setDirection("buy")
|
||||
.setCurrencyCode("aud")
|
||||
.setAmount(10000000)
|
||||
.setMinAmount(10000000)
|
||||
.setUseMarketBasedPrice(false)
|
||||
.setMarketPriceMargin(0.00)
|
||||
.setPrice("36000")
|
||||
.setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent())
|
||||
.setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
|
||||
.build();
|
||||
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
|
||||
PaymentAccount audAccount = createDummyF2FAccount(aliceClient, "AU");
|
||||
var newOffer = aliceClient.createFixedPricedOffer("buy",
|
||||
"aud",
|
||||
10000000L,
|
||||
10000000L,
|
||||
"36000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
audAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals("BUY", newOffer.getDirection());
|
||||
|
@ -72,7 +64,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
|||
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals("BUY", newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
|
@ -89,20 +81,15 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
|||
@Test
|
||||
@Order(2)
|
||||
public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() {
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(alicedaemon, "US");
|
||||
var req = CreateOfferRequest.newBuilder()
|
||||
.setPaymentAccountId(usdAccount.getId())
|
||||
.setDirection("buy")
|
||||
.setCurrencyCode("usd")
|
||||
.setAmount(10000000)
|
||||
.setMinAmount(10000000)
|
||||
.setUseMarketBasedPrice(false)
|
||||
.setMarketPriceMargin(0.00)
|
||||
.setPrice("30000.1234")
|
||||
.setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent())
|
||||
.setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
|
||||
.build();
|
||||
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
var newOffer = aliceClient.createFixedPricedOffer("buy",
|
||||
"usd",
|
||||
10000000L,
|
||||
10000000L,
|
||||
"30000.1234",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
usdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals("BUY", newOffer.getDirection());
|
||||
|
@ -116,7 +103,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
|||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals("BUY", newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
|
@ -133,20 +120,15 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
|||
@Test
|
||||
@Order(3)
|
||||
public void testCreateEURBTCSellOfferUsingFixedPrice95001234() {
|
||||
PaymentAccount eurAccount = createDummyF2FAccount(alicedaemon, "FR");
|
||||
var req = CreateOfferRequest.newBuilder()
|
||||
.setPaymentAccountId(eurAccount.getId())
|
||||
.setDirection("sell")
|
||||
.setCurrencyCode("eur")
|
||||
.setAmount(10000000)
|
||||
.setMinAmount(10000000)
|
||||
.setUseMarketBasedPrice(false)
|
||||
.setMarketPriceMargin(0.00)
|
||||
.setPrice("29500.1234")
|
||||
.setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent())
|
||||
.setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
|
||||
.build();
|
||||
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
|
||||
PaymentAccount eurAccount = createDummyF2FAccount(aliceClient, "FR");
|
||||
var newOffer = aliceClient.createFixedPricedOffer("sell",
|
||||
"eur",
|
||||
10000000L,
|
||||
10000000L,
|
||||
"29500.1234",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
eurAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals("SELL", newOffer.getDirection());
|
||||
|
@ -160,7 +142,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
|||
assertEquals("EUR", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals("SELL", newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
|
|
|
@ -19,7 +19,6 @@ package bisq.apitest.method.offer;
|
|||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
@ -32,7 +31,6 @@ import org.junit.jupiter.api.Order;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
|
@ -57,21 +55,16 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
public void testCreateUSDBTCBuyOffer5PctPriceMargin() {
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(alicedaemon, "US");
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
double priceMarginPctInput = 5.00;
|
||||
var req = CreateOfferRequest.newBuilder()
|
||||
.setPaymentAccountId(usdAccount.getId())
|
||||
.setDirection("buy")
|
||||
.setCurrencyCode("usd")
|
||||
.setAmount(10000000)
|
||||
.setMinAmount(10000000)
|
||||
.setUseMarketBasedPrice(true)
|
||||
.setMarketPriceMargin(priceMarginPctInput)
|
||||
.setPrice("0")
|
||||
.setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent())
|
||||
.setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
|
||||
.build();
|
||||
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer("buy",
|
||||
"usd",
|
||||
10000000L,
|
||||
10000000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
usdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals("BUY", newOffer.getDirection());
|
||||
|
@ -84,7 +77,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals("BUY", newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
|
@ -102,8 +95,9 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||
@Test
|
||||
@Order(2)
|
||||
public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() {
|
||||
PaymentAccount nzdAccount = createDummyF2FAccount(alicedaemon, "NZ");
|
||||
PaymentAccount nzdAccount = createDummyF2FAccount(aliceClient, "NZ");
|
||||
double priceMarginPctInput = -2.00;
|
||||
/*
|
||||
var req = CreateOfferRequest.newBuilder()
|
||||
.setPaymentAccountId(nzdAccount.getId())
|
||||
.setDirection("buy")
|
||||
|
@ -117,6 +111,16 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||
.setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
|
||||
.build();
|
||||
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
|
||||
|
||||
*/
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer("buy",
|
||||
"nzd",
|
||||
10000000L,
|
||||
10000000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
nzdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals("BUY", newOffer.getDirection());
|
||||
|
@ -129,7 +133,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals("BUY", newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
|
@ -147,22 +151,16 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||
@Test
|
||||
@Order(3)
|
||||
public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() {
|
||||
PaymentAccount gbpAccount = createDummyF2FAccount(alicedaemon, "GB");
|
||||
PaymentAccount gbpAccount = createDummyF2FAccount(aliceClient, "GB");
|
||||
double priceMarginPctInput = -1.5;
|
||||
var req = CreateOfferRequest.newBuilder()
|
||||
.setPaymentAccountId(gbpAccount.getId())
|
||||
.setDirection("sell")
|
||||
.setCurrencyCode("gbp")
|
||||
.setAmount(10000000)
|
||||
.setMinAmount(10000000)
|
||||
.setUseMarketBasedPrice(true)
|
||||
.setMarketPriceMargin(priceMarginPctInput)
|
||||
.setPrice("0")
|
||||
.setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent())
|
||||
.setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
|
||||
.build();
|
||||
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
|
||||
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer("sell",
|
||||
"gbp",
|
||||
10000000L,
|
||||
10000000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
gbpAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals("SELL", newOffer.getDirection());
|
||||
|
@ -175,7 +173,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals("SELL", newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
|
@ -193,22 +191,16 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||
@Test
|
||||
@Order(4)
|
||||
public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() {
|
||||
PaymentAccount brlAccount = createDummyF2FAccount(alicedaemon, "BR");
|
||||
PaymentAccount brlAccount = createDummyF2FAccount(aliceClient, "BR");
|
||||
double priceMarginPctInput = 6.55;
|
||||
var req = CreateOfferRequest.newBuilder()
|
||||
.setPaymentAccountId(brlAccount.getId())
|
||||
.setDirection("sell")
|
||||
.setCurrencyCode("brl")
|
||||
.setAmount(10000000)
|
||||
.setMinAmount(10000000)
|
||||
.setUseMarketBasedPrice(true)
|
||||
.setMarketPriceMargin(priceMarginPctInput)
|
||||
.setPrice("0")
|
||||
.setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent())
|
||||
.setMakerFeeCurrencyCode(MAKER_FEE_CURRENCY_CODE)
|
||||
.build();
|
||||
var newOffer = aliceStubs.offersService.createOffer(req).getOffer();
|
||||
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer("sell",
|
||||
"brl",
|
||||
10000000L,
|
||||
10000000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
brlAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals("SELL", newOffer.getDirection());
|
||||
|
@ -221,7 +213,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals("SELL", newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
|
@ -239,7 +231,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||
private void assertCalculatedPriceIsCorrect(OfferInfo offer, double priceMarginPctInput) {
|
||||
assertTrue(() -> {
|
||||
String counterCurrencyCode = offer.getCounterCurrencyCode();
|
||||
double mktPrice = getMarketPrice(counterCurrencyCode);
|
||||
double mktPrice = aliceClient.getBtcPrice(counterCurrencyCode);
|
||||
double scaledOfferPrice = getScaledOfferPrice(offer.getPrice(), counterCurrencyCode);
|
||||
double expectedDiffPct = scaleDownByPowerOf10(priceMarginPctInput, 2);
|
||||
double actualDiffPct = offer.getDirection().equals(BUY.name())
|
||||
|
|
|
@ -19,8 +19,6 @@ package bisq.apitest.method.offer;
|
|||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -31,7 +29,6 @@ import org.junit.jupiter.api.Order;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
@ -44,22 +41,17 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
public void testAmtTooLargeShouldThrowException() {
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(alicedaemon, "US");
|
||||
var req = CreateOfferRequest.newBuilder()
|
||||
.setPaymentAccountId(usdAccount.getId())
|
||||
.setDirection("buy")
|
||||
.setCurrencyCode("usd")
|
||||
.setAmount(100000000000L)
|
||||
.setMinAmount(100000000000L)
|
||||
.setUseMarketBasedPrice(false)
|
||||
.setMarketPriceMargin(0.00)
|
||||
.setPrice("10000.0000")
|
||||
.setBuyerSecurityDeposit(getDefaultBuyerSecurityDepositAsPercent())
|
||||
.setMakerFeeCurrencyCode("bsq")
|
||||
.build();
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
aliceStubs.offersService.createOffer(req).getOffer());
|
||||
aliceClient.createFixedPricedOffer("buy",
|
||||
"usd",
|
||||
100000000000L, // exceeds amount limit
|
||||
100000000000L,
|
||||
"10000.0000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
usdAccount.getId(),
|
||||
"bsq"));
|
||||
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ import bisq.core.locale.Res;
|
|||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
@ -29,7 +27,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.getProperty;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
@ -38,6 +35,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
@Slf4j
|
||||
public class AbstractPaymentAccountTest extends MethodTest {
|
||||
|
@ -110,7 +108,7 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
|||
// would be skipped.
|
||||
COMPLETED_FORM_MAP.clear();
|
||||
|
||||
File emptyForm = getPaymentAccountForm(alicedaemon, paymentMethodId);
|
||||
File emptyForm = getPaymentAccountForm(aliceClient, paymentMethodId);
|
||||
// A short cut over the API:
|
||||
// File emptyForm = PAYMENT_ACCOUNT_FORM.getPaymentAccountForm(paymentMethodId);
|
||||
log.debug("{} Empty form saved to {}",
|
||||
|
@ -153,11 +151,10 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
|||
assertArrayEquals(expectedTradeCurrencies.toArray(), paymentAccount.getTradeCurrencies().toArray());
|
||||
}
|
||||
|
||||
protected final void verifyUserPayloadHasPaymentAccountWithId(String paymentAccountId) {
|
||||
var getPaymentAccountsRequest = GetPaymentAccountsRequest.newBuilder().build();
|
||||
var reply = grpcStubs(alicedaemon)
|
||||
.paymentAccountsService.getPaymentAccounts(getPaymentAccountsRequest);
|
||||
Optional<protobuf.PaymentAccount> paymentAccount = reply.getPaymentAccountsList().stream()
|
||||
protected final void verifyUserPayloadHasPaymentAccountWithId(GrpcClient grpcClient,
|
||||
String paymentAccountId) {
|
||||
Optional<protobuf.PaymentAccount> paymentAccount = grpcClient.getPaymentAccounts()
|
||||
.stream()
|
||||
.filter(a -> a.getId().equals(paymentAccountId))
|
||||
.findFirst();
|
||||
assertTrue(paymentAccount.isPresent());
|
||||
|
|
|
@ -99,8 +99,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "0000 1111 2222");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Advanced Cash Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
AdvancedCashAccount paymentAccount = (AdvancedCashAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
AdvancedCashAccount paymentAccount = (AdvancedCashAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllAdvancedCashCurrencies(), paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
|
@ -119,8 +119,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "2222 3333 4444");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
AliPayAccount paymentAccount = (AliPayAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
AliPayAccount paymentAccount = (AliPayAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("CNY", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
|
@ -139,8 +139,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_NAME, "Credit Union Australia");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Australia Pay ID Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
AustraliaPayid paymentAccount = (AustraliaPayid) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
AustraliaPayid paymentAccount = (AustraliaPayid) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("AUD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_PAY_ID), paymentAccount.getPayid());
|
||||
|
@ -180,8 +180,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_REQUIREMENTS, "Requirements...");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
CashDepositAccount paymentAccount = (CashDepositAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
CashDepositAccount paymentAccount = (CashDepositAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
|
@ -226,8 +226,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Banco do Brasil Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
NationalBankAccount paymentAccount = (NationalBankAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
NationalBankAccount paymentAccount = (NationalBankAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("BRL", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
|
@ -259,8 +259,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "John Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
ChaseQuickPayAccount paymentAccount = (ChaseQuickPayAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
ChaseQuickPayAccount paymentAccount = (ChaseQuickPayAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
|
@ -281,8 +281,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Zelle Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
ClearXchangeAccount paymentAccount = (ClearXchangeAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
ClearXchangeAccount paymentAccount = (ClearXchangeAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL_OR_MOBILE_NR), paymentAccount.getEmailOrMobileNr());
|
||||
|
@ -308,8 +308,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EXTRA_INFO, "So fim de semana");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
F2FAccount paymentAccount = (F2FAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
F2FAccount paymentAccount = (F2FAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("BRL", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
|
@ -333,8 +333,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SORT_CODE, "3127");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Faster Payments Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
FasterPaymentsAccount paymentAccount = (FasterPaymentsAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
FasterPaymentsAccount paymentAccount = (FasterPaymentsAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("GBP", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
|
@ -354,8 +354,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_MOBILE_NR, "798 123 456");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
HalCashAccount paymentAccount = (HalCashAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
HalCashAccount paymentAccount = (HalCashAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
|
||||
|
@ -379,8 +379,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ANSWER, "Fido");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Interac Transfer Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
InteracETransferAccount paymentAccount = (InteracETransferAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
InteracETransferAccount paymentAccount = (InteracETransferAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("CAD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
|
@ -414,8 +414,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_NUMBER, "8100-8727-0000");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
JapanBankAccount paymentAccount = (JapanBankAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
JapanBankAccount paymentAccount = (JapanBankAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("JPY", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_CODE), paymentAccount.getBankCode());
|
||||
|
@ -439,8 +439,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_ID, "MB 0000 1111");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Money Beam Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
MoneyBeamAccount paymentAccount = (MoneyBeamAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
MoneyBeamAccount paymentAccount = (MoneyBeamAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||
|
@ -465,8 +465,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_STATE, "NY");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
MoneyGramAccount paymentAccount = (MoneyGramAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
MoneyGramAccount paymentAccount = (MoneyGramAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllMoneyGramCurrencies(), paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
|
||||
|
@ -488,8 +488,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "PM 0000 1111");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Perfect Money Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
PerfectMoneyAccount paymentAccount = (PerfectMoneyAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
PerfectMoneyAccount paymentAccount = (PerfectMoneyAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
|
@ -510,8 +510,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
PopmoneyAccount paymentAccount = (PopmoneyAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
PopmoneyAccount paymentAccount = (PopmoneyAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||
|
@ -530,8 +530,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PROMPT_PAY_ID, "PP 0000 1111");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Prompt Pay Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
PromptPayAccount paymentAccount = (PromptPayAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
PromptPayAccount paymentAccount = (PromptPayAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("THB", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_PROMPT_PAY_ID), paymentAccount.getPromptPayId());
|
||||
|
@ -550,8 +550,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_USERNAME, "revolut123");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
RevolutAccount paymentAccount = (RevolutAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
RevolutAccount paymentAccount = (RevolutAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllRevolutCurrencies(), paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_USERNAME), paymentAccount.getUserName());
|
||||
|
@ -583,8 +583,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Same Bank Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
SameBankAccount paymentAccount = (SameBankAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
SameBankAccount paymentAccount = (SameBankAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("GBP", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
|
@ -620,8 +620,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BIC, "909");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
SepaInstantAccount paymentAccount = (SepaInstantAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
SepaInstantAccount paymentAccount = (SepaInstantAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
|
@ -651,8 +651,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BIC, "909");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Conta Sepa Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
SepaAccount paymentAccount = (SepaAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
SepaAccount paymentAccount = (SepaAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
|
@ -694,8 +694,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
SpecificBanksAccount paymentAccount = (SpecificBanksAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
SpecificBanksAccount paymentAccount = (SpecificBanksAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("GBP", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
|
@ -726,8 +726,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Swish Acct Holder");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Swish Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
SwishAccount paymentAccount = (SwishAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
SwishAccount paymentAccount = (SwishAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("SEK", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
|
||||
|
@ -747,8 +747,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jan@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
// As per commit 88f26f93241af698ae689bf081205d0f9dc929fa
|
||||
// Do not autofill all currencies by default but keep all unselected.
|
||||
// verifyAccountTradeCurrencies(getAllTransferwiseCurrencies(), paymentAccount);
|
||||
|
@ -769,8 +769,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_ID, "UA 9876");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Uphold Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
UpholdAccount paymentAccount = (UpholdAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
UpholdAccount paymentAccount = (UpholdAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllUpholdCurrencies(), paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||
|
@ -791,8 +791,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_POSTAL_ADDRESS, "000 Westwood Terrace Austin, TX 78700");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
USPostalMoneyOrderAccount paymentAccount = (USPostalMoneyOrderAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
USPostalMoneyOrderAccount paymentAccount = (USPostalMoneyOrderAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
|
@ -811,8 +811,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "WC 1234");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored WeChat Pay Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
WeChatPayAccount paymentAccount = (WeChatPayAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
WeChatPayAccount paymentAccount = (WeChatPayAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("CNY", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
|
@ -839,8 +839,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
WesternUnionAccount paymentAccount = (WesternUnionAccount) createPaymentAccount(alicedaemon, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(paymentAccount.getId());
|
||||
WesternUnionAccount paymentAccount = (WesternUnionAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
|
||||
|
|
|
@ -41,7 +41,7 @@ public class GetPaymentMethodsTest extends MethodTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
public void testGetPaymentMethods() {
|
||||
List<String> paymentMethodIds = getPaymentMethods(alicedaemon)
|
||||
List<String> paymentMethodIds = aliceClient.getPaymentMethods()
|
||||
.stream()
|
||||
.map(PaymentMethod::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
|
|
@ -30,22 +30,14 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
|||
protected final TradeInfo takeAlicesOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode) {
|
||||
return bobStubs.tradesService.takeOffer(
|
||||
createTakeOfferRequest(offerId,
|
||||
paymentAccountId,
|
||||
takerFeeCurrencyCode))
|
||||
.getTrade();
|
||||
return bobClient.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected final TradeInfo takeBobsOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode) {
|
||||
return aliceStubs.tradesService.takeOffer(
|
||||
createTakeOfferRequest(offerId,
|
||||
paymentAccountId,
|
||||
takerFeeCurrencyCode))
|
||||
.getTrade();
|
||||
return aliceClient.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
protected final void verifyExpectedProtocolStatus(TradeInfo trade) {
|
||||
|
|
|
@ -32,9 +32,8 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.cli.CurrencyFormat.formatSatoshis;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
|
@ -61,11 +60,14 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
|||
@Order(1)
|
||||
public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
|
||||
try {
|
||||
PaymentAccount alicesUsdAccount = createDummyF2FAccount(alicedaemon, "US");
|
||||
var alicesOffer = createAliceOffer(alicesUsdAccount,
|
||||
"buy",
|
||||
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
var alicesOffer = aliceClient.createMarketBasedPricedOffer("buy",
|
||||
"usd",
|
||||
12500000,
|
||||
12500000, // min-amount = amount
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesUsdAccount.getId(),
|
||||
TRADE_FEE_CURRENCY_CODE);
|
||||
var offerId = alicesOffer.getId();
|
||||
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
@ -73,10 +75,10 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
|||
// Wait for Alice's AddToOfferBook task.
|
||||
// Wait times vary; my logs show >= 2 second delay.
|
||||
sleep(3000); // TODO loop instead of hard code wait time
|
||||
var alicesUsdOffers = getMyOffersSortedByDate(aliceStubs, "buy", "usd");
|
||||
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate("buy", "usd");
|
||||
assertEquals(1, alicesUsdOffers.size());
|
||||
|
||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobdaemon, "US");
|
||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
|
@ -85,10 +87,10 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
|||
tradeId = trade.getTradeId();
|
||||
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
alicesUsdOffers = getMyOffersSortedByDate(aliceStubs, "buy", "usd");
|
||||
alicesUsdOffers = aliceClient.getMyOffersSortedByDate("buy", "usd");
|
||||
assertEquals(0, alicesUsdOffers.size());
|
||||
|
||||
trade = getTrade(bobdaemon, trade.getTradeId());
|
||||
trade = bobClient.getTrade(trade.getTradeId());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_PUBLISHED_DEPOSIT_TX)
|
||||
.setPhase(DEPOSIT_PUBLISHED)
|
||||
.setDepositPublished(true);
|
||||
|
@ -96,7 +98,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
|||
logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
trade = getTrade(bobdaemon, trade.getTradeId());
|
||||
trade = bobClient.getTrade(trade.getTradeId());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
||||
.setPhase(DEPOSIT_CONFIRMED)
|
||||
.setDepositConfirmed(true);
|
||||
|
@ -111,11 +113,11 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
|||
@Order(2)
|
||||
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = getTrade(alicedaemon, tradeId);
|
||||
confirmPaymentStarted(alicedaemon, trade.getTradeId());
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
||||
sleep(3000);
|
||||
|
||||
trade = getTrade(alicedaemon, tradeId);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
|
||||
.setPhase(FIAT_SENT)
|
||||
|
@ -130,11 +132,11 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
|||
@Test
|
||||
@Order(3)
|
||||
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
var trade = getTrade(bobdaemon, tradeId);
|
||||
confirmPaymentReceived(bobdaemon, trade.getTradeId());
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
bobClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3000);
|
||||
|
||||
trade = getTrade(bobdaemon, tradeId);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
// Note: offer.state == available
|
||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
|
@ -150,19 +152,19 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
|||
public void testAlicesKeepFunds(final TestInfo testInfo) {
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
var trade = getTrade(alicedaemon, tradeId);
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
logTrade(log, testInfo, "Alice's view before keeping funds", trade);
|
||||
|
||||
keepFunds(alicedaemon, tradeId);
|
||||
aliceClient.keepFunds(tradeId);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
trade = getTrade(alicedaemon, tradeId);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after keeping funds", trade);
|
||||
BtcBalanceInfo currentBalance = getBtcBalances(bobdaemon);
|
||||
BtcBalanceInfo currentBalance = aliceClient.getBtcBalances();
|
||||
log.debug("{} Alice's current available balance: {} BTC",
|
||||
testName(testInfo),
|
||||
formatSatoshis(currentBalance.getAvailableBalance()));
|
||||
|
|
|
@ -32,9 +32,8 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.cli.CurrencyFormat.formatSatoshis;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.*;
|
||||
import static bisq.core.trade.Trade.State.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -60,11 +59,14 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
|||
@Order(1)
|
||||
public void testTakeAlicesSellOffer(final TestInfo testInfo) {
|
||||
try {
|
||||
PaymentAccount alicesUsdAccount = createDummyF2FAccount(alicedaemon, "US");
|
||||
var alicesOffer = createAliceOffer(alicesUsdAccount,
|
||||
"sell",
|
||||
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
var alicesOffer = aliceClient.createMarketBasedPricedOffer("sell",
|
||||
"usd",
|
||||
12500000,
|
||||
12500000L,
|
||||
12500000L, // min-amount = amount
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesUsdAccount.getId(),
|
||||
TRADE_FEE_CURRENCY_CODE);
|
||||
var offerId = alicesOffer.getId();
|
||||
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
@ -73,10 +75,10 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
|||
// Wait times vary; my logs show >= 2 second delay, but taking sell offers
|
||||
// seems to require more time to prepare.
|
||||
sleep(3000); // TODO loop instead of hard code wait time
|
||||
var alicesUsdOffers = getMyOffersSortedByDate(aliceStubs, "sell", "usd");
|
||||
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate("sell", "usd");
|
||||
assertEquals(1, alicesUsdOffers.size());
|
||||
|
||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobdaemon, "US");
|
||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
|
@ -85,10 +87,10 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
|||
tradeId = trade.getTradeId();
|
||||
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
var takeableUsdOffers = getOffersSortedByDate(bobStubs, "sell", "usd");
|
||||
var takeableUsdOffers = bobClient.getOffersSortedByDate("sell", "usd");
|
||||
assertEquals(0, takeableUsdOffers.size());
|
||||
|
||||
trade = getTrade(bobdaemon, trade.getTradeId());
|
||||
trade = bobClient.getTrade(trade.getTradeId());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG)
|
||||
.setPhase(DEPOSIT_PUBLISHED)
|
||||
.setDepositPublished(true);
|
||||
|
@ -97,7 +99,7 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
|||
logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
trade = getTrade(bobdaemon, trade.getTradeId());
|
||||
trade = bobClient.getTrade(trade.getTradeId());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
||||
.setPhase(DEPOSIT_CONFIRMED)
|
||||
.setDepositConfirmed(true);
|
||||
|
@ -112,11 +114,11 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
|||
@Order(2)
|
||||
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = getTrade(bobdaemon, tradeId);
|
||||
confirmPaymentStarted(bobdaemon, trade.getTradeId());
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
bobClient.confirmPaymentStarted(tradeId);
|
||||
sleep(3000);
|
||||
|
||||
trade = getTrade(bobdaemon, tradeId);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
// Note: offer.state == available
|
||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
|
||||
|
@ -132,11 +134,11 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
|||
@Test
|
||||
@Order(3)
|
||||
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
var trade = getTrade(alicedaemon, tradeId);
|
||||
confirmPaymentReceived(alicedaemon, trade.getTradeId());
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
aliceClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3000);
|
||||
|
||||
trade = getTrade(alicedaemon, tradeId);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED)
|
||||
|
@ -151,21 +153,21 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
|||
public void testBobsBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
var trade = getTrade(bobdaemon, tradeId);
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
logTrade(log, testInfo, "Bob's view before withdrawing funds to external wallet", trade);
|
||||
|
||||
String toAddress = bitcoinCli.getNewBtcAddress();
|
||||
withdrawFunds(bobdaemon, tradeId, toAddress, WITHDRAWAL_TX_MEMO);
|
||||
bobClient.withdrawFunds(tradeId, toAddress, WITHDRAWAL_TX_MEMO);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
trade = getTrade(bobdaemon, tradeId);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
EXPECTED_PROTOCOL_STATUS.setState(WITHDRAW_COMPLETED)
|
||||
.setPhase(WITHDRAWN)
|
||||
.setWithdrawn(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after withdrawing funds to external wallet", trade);
|
||||
BtcBalanceInfo currentBalance = getBtcBalances(bobdaemon);
|
||||
BtcBalanceInfo currentBalance = bobClient.getBtcBalances();
|
||||
log.debug("{} Bob's current available balance: {} BTC",
|
||||
testName(testInfo),
|
||||
formatSatoshis(currentBalance.getAvailableBalance()));
|
||||
|
|
|
@ -37,6 +37,7 @@ import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
|||
|
||||
import bisq.apitest.config.BisqAppConfig;
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
|
@ -59,9 +60,7 @@ public class BsqWalletTest extends MethodTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
public void testGetUnusedBsqAddress() {
|
||||
var request = createGetUnusedBsqAddressRequest();
|
||||
|
||||
String address = grpcStubs(alicedaemon).walletsService.getUnusedBsqAddress(request).getAddress();
|
||||
var address = aliceClient.getUnusedBsqAddress();
|
||||
assertFalse(address.isEmpty());
|
||||
assertTrue(address.startsWith("B"));
|
||||
|
||||
|
@ -76,13 +75,13 @@ public class BsqWalletTest extends MethodTest {
|
|||
@Test
|
||||
@Order(2)
|
||||
public void testInitialBsqBalances(final TestInfo testInfo) {
|
||||
BsqBalanceInfo alicesBsqBalances = getBsqBalances(alicedaemon);
|
||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBsqBalances();
|
||||
log.debug("{} -> Alice's BSQ Initial Balances -> \n{}",
|
||||
testName(testInfo),
|
||||
formatBsqBalanceInfoTbl(alicesBsqBalances));
|
||||
verifyBsqBalances(ALICES_INITIAL_BSQ_BALANCES, alicesBsqBalances);
|
||||
|
||||
BsqBalanceInfo bobsBsqBalances = getBsqBalances(bobdaemon);
|
||||
BsqBalanceInfo bobsBsqBalances = bobClient.getBsqBalances();
|
||||
log.debug("{} -> Bob's BSQ Initial Balances -> \n{}",
|
||||
testName(testInfo),
|
||||
formatBsqBalanceInfoTbl(bobsBsqBalances));
|
||||
|
@ -92,12 +91,12 @@ public class BsqWalletTest extends MethodTest {
|
|||
@Test
|
||||
@Order(3)
|
||||
public void testSendBsqAndCheckBalancesBeforeGeneratingBtcBlock(final TestInfo testInfo) {
|
||||
String bobsBsqAddress = getUnusedBsqAddress(bobdaemon);
|
||||
sendBsq(alicedaemon, bobsBsqAddress, SEND_BSQ_AMOUNT, "100");
|
||||
String bobsBsqAddress = bobClient.getUnusedBsqAddress();
|
||||
aliceClient.sendBsq(bobsBsqAddress, SEND_BSQ_AMOUNT, "100");
|
||||
sleep(2000);
|
||||
|
||||
BsqBalanceInfo alicesBsqBalances = getBsqBalances(alicedaemon);
|
||||
BsqBalanceInfo bobsBsqBalances = waitForNonZeroBsqUnverifiedBalance(bobdaemon);
|
||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBsqBalances();
|
||||
BsqBalanceInfo bobsBsqBalances = waitForNonZeroBsqUnverifiedBalance(bobClient);
|
||||
|
||||
log.debug("BSQ Balances Before BTC Block Gen...");
|
||||
printBobAndAliceBsqBalances(testInfo,
|
||||
|
@ -129,8 +128,8 @@ public class BsqWalletTest extends MethodTest {
|
|||
// wait for both wallets to be saved to disk.
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
|
||||
BsqBalanceInfo alicesBsqBalances = getBsqBalances(alicedaemon);
|
||||
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableConfirmedBalance(bobdaemon, 150000000);
|
||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBalances().getBsq();
|
||||
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableConfirmedBalance(bobClient, 150000000);
|
||||
|
||||
log.debug("See Available Confirmed BSQ Balances...");
|
||||
printBobAndAliceBsqBalances(testInfo,
|
||||
|
@ -160,26 +159,26 @@ public class BsqWalletTest extends MethodTest {
|
|||
tearDownScaffold();
|
||||
}
|
||||
|
||||
private BsqBalanceInfo waitForNonZeroBsqUnverifiedBalance(BisqAppConfig daemon) {
|
||||
private BsqBalanceInfo waitForNonZeroBsqUnverifiedBalance(GrpcClient grpcClient) {
|
||||
// A BSQ recipient needs to wait for her daemon to detect a new tx.
|
||||
// Loop here until her unverifiedBalance != 0, or give up after 15 seconds.
|
||||
// A slow test is preferred over a flaky test.
|
||||
BsqBalanceInfo bsqBalance = getBsqBalances(daemon);
|
||||
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
|
||||
for (int numRequests = 1; numRequests <= 15 && bsqBalance.getUnverifiedBalance() == 0; numRequests++) {
|
||||
sleep(1000);
|
||||
bsqBalance = getBsqBalances(daemon);
|
||||
bsqBalance = grpcClient.getBsqBalances();
|
||||
}
|
||||
return bsqBalance;
|
||||
}
|
||||
|
||||
private BsqBalanceInfo waitForBsqNewAvailableConfirmedBalance(BisqAppConfig daemon,
|
||||
private BsqBalanceInfo waitForBsqNewAvailableConfirmedBalance(GrpcClient grpcClient,
|
||||
long staleBalance) {
|
||||
BsqBalanceInfo bsqBalance = getBsqBalances(daemon);
|
||||
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
|
||||
for (int numRequests = 1;
|
||||
numRequests <= 15 && bsqBalance.getAvailableConfirmedBalance() == staleBalance;
|
||||
numRequests++) {
|
||||
sleep(1000);
|
||||
bsqBalance = getBsqBalances(daemon);
|
||||
bsqBalance = grpcClient.getBsqBalances();
|
||||
}
|
||||
return bsqBalance;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package bisq.apitest.method.wallet;
|
|||
|
||||
import bisq.core.api.model.TxFeeRateInfo;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
@ -15,8 +17,11 @@ import org.junit.jupiter.api.TestMethodOrder;
|
|||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static bisq.common.config.BaseCurrencyNetwork.BTC_DAO_REGTEST;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
|
@ -41,7 +46,7 @@ public class BtcTxFeeRateTest extends MethodTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
public void testGetTxFeeRate(final TestInfo testInfo) {
|
||||
TxFeeRateInfo txFeeRateInfo = getTxFeeRate(alicedaemon);
|
||||
var txFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.getTxFeeRate());
|
||||
log.debug("{} -> Fee rate with no preference: {}", testName(testInfo), txFeeRateInfo);
|
||||
|
||||
assertFalse(txFeeRateInfo.isUseCustomTxFeeRate());
|
||||
|
@ -50,19 +55,30 @@ public class BtcTxFeeRateTest extends MethodTest {
|
|||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testSetTxFeeRate(final TestInfo testInfo) {
|
||||
TxFeeRateInfo txFeeRateInfo = setTxFeeRate(alicedaemon, 10);
|
||||
log.debug("{} -> Fee rates with custom preference: {}", testName(testInfo), txFeeRateInfo);
|
||||
|
||||
assertTrue(txFeeRateInfo.isUseCustomTxFeeRate());
|
||||
assertEquals(10, txFeeRateInfo.getCustomTxFeeRate());
|
||||
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
|
||||
public void testSetInvalidTxFeeRateShouldThrowException(final TestInfo testInfo) {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
aliceClient.setTxFeeRate(10));
|
||||
String expectedExceptionMessage =
|
||||
format("UNKNOWN: tx fee rate preference must be >= %d sats/byte",
|
||||
BTC_DAO_REGTEST.getDefaultMinFeePerVbyte());
|
||||
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testSetValidTxFeeRate(final TestInfo testInfo) {
|
||||
var txFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.setTxFeeRate(15));
|
||||
log.debug("{} -> Fee rates with custom preference: {}", testName(testInfo), txFeeRateInfo);
|
||||
|
||||
assertTrue(txFeeRateInfo.isUseCustomTxFeeRate());
|
||||
assertEquals(15, txFeeRateInfo.getCustomTxFeeRate());
|
||||
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testUnsetTxFeeRate(final TestInfo testInfo) {
|
||||
TxFeeRateInfo txFeeRateInfo = unsetTxFeeRate(alicedaemon);
|
||||
var txFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.unsetTxFeeRate());
|
||||
log.debug("{} -> Fee rate with no preference: {}", testName(testInfo), txFeeRateInfo);
|
||||
|
||||
assertFalse(txFeeRateInfo.isUseCustomTxFeeRate());
|
||||
|
|
|
@ -53,10 +53,10 @@ public class BtcWalletTest extends MethodTest {
|
|||
public void testInitialBtcBalances(final TestInfo testInfo) {
|
||||
// Bob & Alice's regtest Bisq wallets were initialized with 10 BTC.
|
||||
|
||||
BtcBalanceInfo alicesBalances = getBtcBalances(alicedaemon);
|
||||
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
|
||||
log.debug("{} Alice's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(alicesBalances));
|
||||
|
||||
BtcBalanceInfo bobsBalances = getBtcBalances(bobdaemon);
|
||||
BtcBalanceInfo bobsBalances = bobClient.getBtcBalances();
|
||||
log.debug("{} Bob's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(bobsBalances));
|
||||
|
||||
assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), alicesBalances.getAvailableBalance());
|
||||
|
@ -66,20 +66,20 @@ public class BtcWalletTest extends MethodTest {
|
|||
@Test
|
||||
@Order(2)
|
||||
public void testFundAlicesBtcWallet(final TestInfo testInfo) {
|
||||
String newAddress = getUnusedBtcAddress(alicedaemon);
|
||||
String newAddress = aliceClient.getUnusedBtcAddress();
|
||||
bitcoinCli.sendToAddress(newAddress, "2.5");
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
BtcBalanceInfo btcBalanceInfo = getBtcBalances(alicedaemon);
|
||||
BtcBalanceInfo btcBalanceInfo = aliceClient.getBtcBalances();
|
||||
// New balance is 12.5 BTC
|
||||
assertEquals(1250000000, btcBalanceInfo.getAvailableBalance());
|
||||
|
||||
log.debug("{} -> Alice's Funded Address Balance -> \n{}",
|
||||
testName(testInfo),
|
||||
formatAddressBalanceTbl(singletonList(getAddressBalance(alicedaemon, newAddress))));
|
||||
formatAddressBalanceTbl(singletonList(aliceClient.getAddressBalance(newAddress))));
|
||||
|
||||
// New balance is 12.5 BTC
|
||||
btcBalanceInfo = getBtcBalances(alicedaemon);
|
||||
btcBalanceInfo = aliceClient.getBtcBalances();
|
||||
bisq.core.api.model.BtcBalanceInfo alicesExpectedBalances =
|
||||
bisq.core.api.model.BtcBalanceInfo.valueOf(1250000000,
|
||||
0,
|
||||
|
@ -94,11 +94,10 @@ public class BtcWalletTest extends MethodTest {
|
|||
@Test
|
||||
@Order(3)
|
||||
public void testAliceSendBTCToBob(TestInfo testInfo) {
|
||||
String bobsBtcAddress = getUnusedBtcAddress(bobdaemon);
|
||||
String bobsBtcAddress = bobClient.getUnusedBtcAddress();
|
||||
log.debug("Sending 5.5 BTC From Alice to Bob @ {}", bobsBtcAddress);
|
||||
|
||||
TxInfo txInfo = sendBtc(alicedaemon,
|
||||
bobsBtcAddress,
|
||||
TxInfo txInfo = aliceClient.sendBtc(bobsBtcAddress,
|
||||
"5.50",
|
||||
"100",
|
||||
TX_MEMO);
|
||||
|
@ -109,11 +108,11 @@ public class BtcWalletTest extends MethodTest {
|
|||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
// Fetch the tx and check for confirmation and memo.
|
||||
txInfo = getTransaction(alicedaemon, txInfo.getTxId());
|
||||
txInfo = aliceClient.getTransaction(txInfo.getTxId());
|
||||
assertFalse(txInfo.getIsPending());
|
||||
assertEquals(TX_MEMO, txInfo.getMemo());
|
||||
|
||||
BtcBalanceInfo alicesBalances = getBtcBalances(alicedaemon);
|
||||
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
|
||||
log.debug("{} Alice's BTC Balances:\n{}",
|
||||
testName(testInfo),
|
||||
formatBtcBalanceInfoTbl(alicesBalances));
|
||||
|
@ -124,7 +123,7 @@ public class BtcWalletTest extends MethodTest {
|
|||
0);
|
||||
verifyBtcBalances(alicesExpectedBalances, alicesBalances);
|
||||
|
||||
BtcBalanceInfo bobsBalances = getBtcBalances(bobdaemon);
|
||||
BtcBalanceInfo bobsBalances = bobClient.getBtcBalances();
|
||||
log.debug("{} Bob's BTC Balances:\n{}",
|
||||
testName(testInfo),
|
||||
formatBtcBalanceInfoTbl(bobsBalances));
|
||||
|
|
|
@ -41,94 +41,83 @@ public class WalletProtectionTest extends MethodTest {
|
|||
@Test
|
||||
@Order(1)
|
||||
public void testSetWalletPassword() {
|
||||
var request = createSetWalletPasswordRequest("first-password");
|
||||
grpcStubs(alicedaemon).walletsService.setWalletPassword(request);
|
||||
aliceClient.setWalletPassword("first-password");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testGetBalanceOnEncryptedWalletShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBtcBalances(alicedaemon));
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testUnlockWalletFor4Seconds() {
|
||||
var request = createUnlockWalletRequest("first-password", 4);
|
||||
grpcStubs(alicedaemon).walletsService.unlockWallet(request);
|
||||
getBtcBalances(alicedaemon); // should not throw 'wallet locked' exception
|
||||
aliceClient.unlockWallet("first-password", 4);
|
||||
aliceClient.getBtcBalances(); // should not throw 'wallet locked' exception
|
||||
sleep(4500); // let unlock timeout expire
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBtcBalances(alicedaemon));
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testGetBalanceAfterUnlockTimeExpiryShouldThrowException() {
|
||||
var request = createUnlockWalletRequest("first-password", 3);
|
||||
grpcStubs(alicedaemon).walletsService.unlockWallet(request);
|
||||
aliceClient.unlockWallet("first-password", 3);
|
||||
sleep(4000); // let unlock timeout expire
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBtcBalances(alicedaemon));
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
public void testLockWalletBeforeUnlockTimeoutExpiry() {
|
||||
unlockWallet(alicedaemon, "first-password", 60);
|
||||
var request = createLockWalletRequest();
|
||||
grpcStubs(alicedaemon).walletsService.lockWallet(request);
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBtcBalances(alicedaemon));
|
||||
aliceClient.unlockWallet("first-password", 60);
|
||||
aliceClient.lockWallet();
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(6)
|
||||
public void testLockWalletWhenWalletAlreadyLockedShouldThrowException() {
|
||||
var request = createLockWalletRequest();
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
grpcStubs(alicedaemon).walletsService.lockWallet(request));
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.lockWallet());
|
||||
assertEquals("UNKNOWN: wallet is already locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(7)
|
||||
public void testUnlockWalletTimeoutOverride() {
|
||||
unlockWallet(alicedaemon, "first-password", 2);
|
||||
aliceClient.unlockWallet("first-password", 2);
|
||||
sleep(500); // override unlock timeout after 0.5s
|
||||
unlockWallet(alicedaemon, "first-password", 6);
|
||||
aliceClient.unlockWallet("first-password", 6);
|
||||
sleep(5000);
|
||||
getBtcBalances(alicedaemon); // getbalance 5s after overriding timeout to 6s
|
||||
aliceClient.getBtcBalances(); // getbalance 5s after overriding timeout to 6s
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(8)
|
||||
public void testSetNewWalletPassword() {
|
||||
var request = createSetWalletPasswordRequest(
|
||||
"first-password", "second-password");
|
||||
grpcStubs(alicedaemon).walletsService.setWalletPassword(request);
|
||||
unlockWallet(alicedaemon, "second-password", 2);
|
||||
getBtcBalances(alicedaemon);
|
||||
aliceClient.setWalletPassword("first-password", "second-password");
|
||||
sleep(2500); // allow time for wallet save
|
||||
aliceClient.unlockWallet("second-password", 2);
|
||||
aliceClient.getBtcBalances();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(9)
|
||||
public void testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException() {
|
||||
var request = createSetWalletPasswordRequest(
|
||||
"bad old password", "irrelevant");
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
grpcStubs(alicedaemon).walletsService.setWalletPassword(request));
|
||||
aliceClient.setWalletPassword("bad old password", "irrelevant"));
|
||||
assertEquals("UNKNOWN: incorrect old password", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(10)
|
||||
public void testRemoveNewWalletPassword() {
|
||||
var request = createRemoveWalletPasswordRequest("second-password");
|
||||
grpcStubs(alicedaemon).walletsService.removeWalletPassword(request);
|
||||
getBtcBalances(alicedaemon); // should not throw 'wallet locked' exception
|
||||
aliceClient.removeWalletPassword("second-password");
|
||||
aliceClient.getBtcBalances(); // should not throw 'wallet locked' exception
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
|||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
@ -80,7 +81,8 @@ public class PaymentAccountTest extends AbstractPaymentAccountTest {
|
|||
test.testCreateWeChatPayAccount(testInfo);
|
||||
test.testCreateWesternUnionAccount(testInfo);
|
||||
|
||||
assertEquals(EXPECTED_NUM_PAYMENT_ACCOUNTS, getPaymentAccounts(alicedaemon).size());
|
||||
var paymentAccounts = requireNonNull(aliceClient).getPaymentAccounts();
|
||||
assertEquals(EXPECTED_NUM_PAYMENT_ACCOUNTS, paymentAccounts.size());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
|
|
121
apitest/src/test/java/bisq/apitest/scenario/ScriptedBotTest.java
Normal file
121
apitest/src/test/java/bisq/apitest/scenario/ScriptedBotTest.java
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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.apitest.scenario;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.startShutdownTimer;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.config.ApiTestConfig;
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.AbstractBotTest;
|
||||
import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.RobotBob;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
|
||||
// The test case is enabled if AbstractBotTest#botScriptExists() returns true.
|
||||
@EnabledIf("botScriptExists")
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class ScriptedBotTest extends AbstractBotTest {
|
||||
|
||||
private RobotBob robotBob;
|
||||
|
||||
@BeforeAll
|
||||
public static void startTestHarness() {
|
||||
botScript = deserializeBotScript();
|
||||
|
||||
if (botScript.isUseTestHarness()) {
|
||||
startSupportingApps(true,
|
||||
true,
|
||||
bitcoind,
|
||||
seednode,
|
||||
arbdaemon,
|
||||
alicedaemon,
|
||||
bobdaemon);
|
||||
} else {
|
||||
// We need just enough configurations to make sure Bob and testers use
|
||||
// the right apiPassword, to create a bitcoin-cli helper, and RobotBob's
|
||||
// gRPC stubs. But the user will have to register dispute agents before
|
||||
// an offer can be taken.
|
||||
config = new ApiTestConfig("--apiPassword", "xyz");
|
||||
bitcoinCli = new BitcoinCliHelper(config);
|
||||
log.warn("Don't forget to register dispute agents before trying to trade with me.");
|
||||
}
|
||||
|
||||
botClient = new BotClient(bobClient);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void initRobotBob() {
|
||||
try {
|
||||
BashScriptGenerator bashScriptGenerator = getBashScriptGenerator();
|
||||
robotBob = new RobotBob(botClient, botScript, bitcoinCli, bashScriptGenerator);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void runRobotBob() {
|
||||
try {
|
||||
|
||||
startShutdownTimer();
|
||||
robotBob.run();
|
||||
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
// This exception is thrown if a /tmp/bottest-shutdown file was found.
|
||||
// You can also kill -15 <pid>
|
||||
// of worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor #'
|
||||
//
|
||||
// This will cleanly shut everything down as well, but you will see a
|
||||
// Process 'Gradle Test Executor #' finished with non-zero exit value 143 error,
|
||||
// which you may think is a test failure.
|
||||
log.warn("{} Shutting down test case before test completion;"
|
||||
+ " this is not a test failure.",
|
||||
ex.getMessage());
|
||||
} catch (Throwable throwable) {
|
||||
fail(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
if (botScript.isUseTestHarness())
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
|
@ -104,7 +104,8 @@ public class WalletTest extends MethodTest {
|
|||
BtcTxFeeRateTest test = new BtcTxFeeRateTest();
|
||||
|
||||
test.testGetTxFeeRate(testInfo);
|
||||
test.testSetTxFeeRate(testInfo);
|
||||
test.testSetInvalidTxFeeRateShouldThrowException(testInfo);
|
||||
test.testSetValidTxFeeRate(testInfo);
|
||||
test.testUnsetTxFeeRate(testInfo);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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.apitest.scenario.bot;
|
||||
|
||||
import bisq.core.locale.Country;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.locale.CountryUtil.findCountryByCode;
|
||||
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
|
||||
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.getProperty;
|
||||
import static java.nio.file.Files.readAllBytes;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.script.BotScript;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractBotTest extends MethodTest {
|
||||
|
||||
protected static final String BOT_SCRIPT_NAME = "bot-script.json";
|
||||
protected static BotScript botScript;
|
||||
protected static BotClient botClient;
|
||||
|
||||
protected BashScriptGenerator getBashScriptGenerator() {
|
||||
if (botScript.isUseTestHarness()) {
|
||||
PaymentAccount alicesAccount = createAlicesPaymentAccount();
|
||||
botScript.setPaymentAccountIdForCliScripts(alicesAccount.getId());
|
||||
}
|
||||
return new BashScriptGenerator(config.apiPassword,
|
||||
botScript.getApiPortForCliScripts(),
|
||||
botScript.getPaymentAccountIdForCliScripts(),
|
||||
botScript.isPrintCliScripts());
|
||||
}
|
||||
|
||||
private PaymentAccount createAlicesPaymentAccount() {
|
||||
BotPaymentAccountGenerator accountGenerator =
|
||||
new BotPaymentAccountGenerator(new BotClient(aliceClient));
|
||||
String paymentMethodId = botScript.getBotPaymentMethodId();
|
||||
if (paymentMethodId != null) {
|
||||
if (paymentMethodId.equals(CLEAR_X_CHANGE_ID)) {
|
||||
// Only Zelle test accts are supported now.
|
||||
return accountGenerator.createZellePaymentAccount(
|
||||
"Alice's Zelle Account",
|
||||
"Alice");
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
format("This test harness bot does not work with %s payment accounts yet.",
|
||||
getPaymentMethodById(paymentMethodId).getDisplayString()));
|
||||
}
|
||||
} else {
|
||||
String countryCode = botScript.getCountryCode();
|
||||
Country country = findCountryByCode(countryCode).orElseThrow(() ->
|
||||
new IllegalArgumentException(countryCode + " is not a valid iso country code."));
|
||||
return accountGenerator.createF2FPaymentAccount(country,
|
||||
"Alice's " + country.name + " F2F Account");
|
||||
}
|
||||
}
|
||||
|
||||
protected static BotScript deserializeBotScript() {
|
||||
try {
|
||||
File botScriptFile = new File(getProperty("java.io.tmpdir"), BOT_SCRIPT_NAME);
|
||||
String json = new String(readAllBytes(Paths.get(botScriptFile.getPath())));
|
||||
return new GsonBuilder().setPrettyPrinting().create().fromJson(json, BotScript.class);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("Error reading script bot file contents.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // This is used by the jupiter framework.
|
||||
protected static boolean botScriptExists() {
|
||||
File botScriptFile = new File(getProperty("java.io.tmpdir"), BOT_SCRIPT_NAME);
|
||||
if (botScriptFile.exists()) {
|
||||
botScriptFile.deleteOnExit();
|
||||
log.info("Enabled, found {}.", botScriptFile.getPath());
|
||||
return true;
|
||||
} else {
|
||||
log.info("Skipped, no bot script.\n\tTo generate a bot-script.json file, see BotScriptGenerator.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
77
apitest/src/test/java/bisq/apitest/scenario/bot/Bot.java
Normal file
77
apitest/src/test/java/bisq/apitest/scenario/bot/Bot.java
Normal file
|
@ -0,0 +1,77 @@
|
|||
package bisq.apitest.scenario.bot;
|
||||
|
||||
import bisq.core.locale.Country;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.locale.CountryUtil.findCountryByCode;
|
||||
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
|
||||
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.script.BotScript;
|
||||
|
||||
@Slf4j
|
||||
public
|
||||
class Bot {
|
||||
|
||||
static final String MAKE = "MAKE";
|
||||
static final String TAKE = "TAKE";
|
||||
|
||||
protected final BotClient botClient;
|
||||
protected final BitcoinCliHelper bitcoinCli;
|
||||
protected final BashScriptGenerator bashScriptGenerator;
|
||||
protected final String[] actions;
|
||||
protected final long protocolStepTimeLimitInMs;
|
||||
protected final boolean stayAlive;
|
||||
protected final boolean isUsingTestHarness;
|
||||
protected final PaymentAccount paymentAccount;
|
||||
|
||||
public Bot(BotClient botClient,
|
||||
BotScript botScript,
|
||||
BitcoinCliHelper bitcoinCli,
|
||||
BashScriptGenerator bashScriptGenerator) {
|
||||
this.botClient = botClient;
|
||||
this.bitcoinCli = bitcoinCli;
|
||||
this.bashScriptGenerator = bashScriptGenerator;
|
||||
this.actions = botScript.getActions();
|
||||
this.protocolStepTimeLimitInMs = MINUTES.toMillis(botScript.getProtocolStepTimeLimitInMinutes());
|
||||
this.stayAlive = botScript.isStayAlive();
|
||||
this.isUsingTestHarness = botScript.isUseTestHarness();
|
||||
if (isUsingTestHarness)
|
||||
this.paymentAccount = createBotPaymentAccount(botScript);
|
||||
else
|
||||
this.paymentAccount = botClient.getPaymentAccount(botScript.getPaymentAccountIdForBot());
|
||||
}
|
||||
|
||||
private PaymentAccount createBotPaymentAccount(BotScript botScript) {
|
||||
BotPaymentAccountGenerator accountGenerator = new BotPaymentAccountGenerator(botClient);
|
||||
|
||||
String paymentMethodId = botScript.getBotPaymentMethodId();
|
||||
if (paymentMethodId != null) {
|
||||
if (paymentMethodId.equals(CLEAR_X_CHANGE_ID)) {
|
||||
return accountGenerator.createZellePaymentAccount("Bob's Zelle Account",
|
||||
"Bob");
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
format("This bot test does not work with %s payment accounts yet.",
|
||||
getPaymentMethodById(paymentMethodId).getDisplayString()));
|
||||
}
|
||||
} else {
|
||||
Country country = findCountry(botScript.getCountryCode());
|
||||
return accountGenerator.createF2FPaymentAccount(country, country.name + " F2F Account");
|
||||
}
|
||||
}
|
||||
|
||||
private Country findCountry(String countryCode) {
|
||||
return findCountryByCode(countryCode).orElseThrow(() ->
|
||||
new IllegalArgumentException(countryCode + " is not a valid iso country code."));
|
||||
}
|
||||
}
|
339
apitest/src/test/java/bisq/apitest/scenario/bot/BotClient.java
Normal file
339
apitest/src/test/java/bisq/apitest/scenario/bot/BotClient.java
Normal file
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
* 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.apitest.scenario.bot;
|
||||
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
/**
|
||||
* Convenience GrpcClient wrapper for bots using gRPC services.
|
||||
*
|
||||
* TODO Consider if the duplication smell is bad enough to force a BotClient user
|
||||
* to use the GrpcClient instead (and delete this class). But right now, I think it is
|
||||
* OK because moving some of the non-gRPC related methods to GrpcClient is even smellier.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings({"JavaDoc", "unused"})
|
||||
@Slf4j
|
||||
public class BotClient {
|
||||
|
||||
private static final DecimalFormat FIXED_PRICE_FMT = new DecimalFormat("###########0");
|
||||
|
||||
private final GrpcClient grpcClient;
|
||||
|
||||
public BotClient(GrpcClient grpcClient) {
|
||||
this.grpcClient = grpcClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current BSQ and BTC balance information.
|
||||
* @return BalancesInfo
|
||||
*/
|
||||
public BalancesInfo getBalance() {
|
||||
return grpcClient.getBalances();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the most recent BTC market price for the given currencyCode.
|
||||
* @param currencyCode
|
||||
* @return double
|
||||
*/
|
||||
public double getCurrentBTCMarketPrice(String currencyCode) {
|
||||
return grpcClient.getBtcPrice(currencyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the most recent BTC market price for the given currencyCode as an integer string.
|
||||
* @param currencyCode
|
||||
* @return String
|
||||
*/
|
||||
public String getCurrentBTCMarketPriceAsIntegerString(String currencyCode) {
|
||||
return FIXED_PRICE_FMT.format(getCurrentBTCMarketPrice(currencyCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all BUY and SELL offers for the given currencyCode.
|
||||
* @param currencyCode
|
||||
* @return List<OfferInfo>
|
||||
*/
|
||||
public List<OfferInfo> getOffers(String currencyCode) {
|
||||
var buyOffers = getBuyOffers(currencyCode);
|
||||
if (buyOffers.size() > 0) {
|
||||
return buyOffers;
|
||||
} else {
|
||||
return getSellOffers(currencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return BUY offers for the given currencyCode.
|
||||
* @param currencyCode
|
||||
* @return List<OfferInfo>
|
||||
*/
|
||||
public List<OfferInfo> getBuyOffers(String currencyCode) {
|
||||
return grpcClient.getOffers("BUY", currencyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SELL offers for the given currencyCode.
|
||||
* @param currencyCode
|
||||
* @return List<OfferInfo>
|
||||
*/
|
||||
public List<OfferInfo> getSellOffers(String currencyCode) {
|
||||
return grpcClient.getOffers("SELL", currencyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new Offer using a market based price.
|
||||
* @param paymentAccount
|
||||
* @param direction
|
||||
* @param currencyCode
|
||||
* @param amountInSatoshis
|
||||
* @param minAmountInSatoshis
|
||||
* @param priceMarginAsPercent
|
||||
* @param securityDepositAsPercent
|
||||
* @param feeCurrency
|
||||
* @return OfferInfo
|
||||
*/
|
||||
public OfferInfo createOfferAtMarketBasedPrice(PaymentAccount paymentAccount,
|
||||
String direction,
|
||||
String currencyCode,
|
||||
long amountInSatoshis,
|
||||
long minAmountInSatoshis,
|
||||
double priceMarginAsPercent,
|
||||
double securityDepositAsPercent,
|
||||
String feeCurrency) {
|
||||
return grpcClient.createMarketBasedPricedOffer(direction,
|
||||
currencyCode,
|
||||
amountInSatoshis,
|
||||
minAmountInSatoshis,
|
||||
priceMarginAsPercent,
|
||||
securityDepositAsPercent,
|
||||
paymentAccount.getId(),
|
||||
feeCurrency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new Offer using a fixed price.
|
||||
* @param paymentAccount
|
||||
* @param direction
|
||||
* @param currencyCode
|
||||
* @param amountInSatoshis
|
||||
* @param minAmountInSatoshis
|
||||
* @param fixedOfferPriceAsString
|
||||
* @param securityDepositAsPercent
|
||||
* @param feeCurrency
|
||||
* @return OfferInfo
|
||||
*/
|
||||
public OfferInfo createOfferAtFixedPrice(PaymentAccount paymentAccount,
|
||||
String direction,
|
||||
String currencyCode,
|
||||
long amountInSatoshis,
|
||||
long minAmountInSatoshis,
|
||||
String fixedOfferPriceAsString,
|
||||
double securityDepositAsPercent,
|
||||
String feeCurrency) {
|
||||
return grpcClient.createFixedPricedOffer(direction,
|
||||
currencyCode,
|
||||
amountInSatoshis,
|
||||
minAmountInSatoshis,
|
||||
fixedOfferPriceAsString,
|
||||
securityDepositAsPercent,
|
||||
paymentAccount.getId(),
|
||||
feeCurrency);
|
||||
}
|
||||
|
||||
public TradeInfo takeOffer(String offerId, PaymentAccount paymentAccount, String feeCurrency) {
|
||||
return grpcClient.takeOffer(offerId, paymentAccount.getId(), feeCurrency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a persisted Trade with the given tradeId, or throws an exception.
|
||||
* @param tradeId
|
||||
* @return TradeInfo
|
||||
*/
|
||||
public TradeInfo getTrade(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate returns true if the given exception indicates the trade with the given
|
||||
* tradeId exists, but the trade's contract has not been fully prepared.
|
||||
*/
|
||||
public final BiPredicate<Exception, String> tradeContractIsNotReady = (exception, tradeId) -> {
|
||||
if (exception.getMessage().contains("no contract was found")) {
|
||||
log.warn("Trade {} exists but is not fully prepared: {}.",
|
||||
tradeId,
|
||||
toCleanGrpcExceptionMessage(exception));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a trade's contract as a Json string, or null if the trade exists
|
||||
* but the contract is not ready.
|
||||
* @param tradeId
|
||||
* @return String
|
||||
*/
|
||||
public String getTradeContract(String tradeId) {
|
||||
try {
|
||||
var trade = grpcClient.getTrade(tradeId);
|
||||
return trade.getContractAsJson();
|
||||
} catch (Exception ex) {
|
||||
if (tradeContractIsNotReady.test(ex, tradeId))
|
||||
return null;
|
||||
else
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's taker deposit fee transaction has been published.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTakerDepositFeeTxPublished(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsPayoutPublished();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's taker deposit fee transaction has been confirmed.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTakerDepositFeeTxConfirmed(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsDepositConfirmed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's 'start payment' message has been sent by the buyer.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTradePaymentStartedSent(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsFiatSent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's 'payment received' message has been sent by the seller.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTradePaymentReceivedConfirmationSent(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsFiatReceived();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's payout transaction has been published.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTradePayoutTxPublished(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsPayoutPublished();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a 'confirm payment started message' for a trade with the given tradeId,
|
||||
* or throws an exception.
|
||||
* @param tradeId
|
||||
*/
|
||||
public void sendConfirmPaymentStartedMessage(String tradeId) {
|
||||
grpcClient.confirmPaymentStarted(tradeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a 'confirm payment received message' for a trade with the given tradeId,
|
||||
* or throws an exception.
|
||||
* @param tradeId
|
||||
*/
|
||||
public void sendConfirmPaymentReceivedMessage(String tradeId) {
|
||||
grpcClient.confirmPaymentReceived(tradeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a 'keep funds in wallet message' for a trade with the given tradeId,
|
||||
* or throws an exception.
|
||||
* @param tradeId
|
||||
*/
|
||||
public void sendKeepFundsMessage(String tradeId) {
|
||||
grpcClient.keepFunds(tradeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and save a new PaymentAccount with details in the given json.
|
||||
* @param json
|
||||
* @return PaymentAccount
|
||||
*/
|
||||
public PaymentAccount createNewPaymentAccount(String json) {
|
||||
return grpcClient.createPaymentAccount(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a persisted PaymentAccount with the given paymentAccountId, or throws
|
||||
* an exception.
|
||||
* @param paymentAccountId The id of the PaymentAccount being looked up.
|
||||
* @return PaymentAccount
|
||||
*/
|
||||
public PaymentAccount getPaymentAccount(String paymentAccountId) {
|
||||
return grpcClient.getPaymentAccounts().stream()
|
||||
.filter(a -> (a.getId().equals(paymentAccountId)))
|
||||
.findFirst()
|
||||
.orElseThrow(() ->
|
||||
new PaymentAccountNotFoundException("Could not find a payment account with id "
|
||||
+ paymentAccountId + "."));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a persisted PaymentAccount with the given accountName, or throws
|
||||
* an exception.
|
||||
* @param accountName
|
||||
* @return PaymentAccount
|
||||
*/
|
||||
public PaymentAccount getPaymentAccountWithName(String accountName) {
|
||||
var req = GetPaymentAccountsRequest.newBuilder().build();
|
||||
return grpcClient.getPaymentAccounts().stream()
|
||||
.filter(a -> (a.getAccountName().equals(accountName)))
|
||||
.findFirst()
|
||||
.orElseThrow(() ->
|
||||
new PaymentAccountNotFoundException("Could not find a payment account with name "
|
||||
+ accountName + "."));
|
||||
}
|
||||
|
||||
public String toCleanGrpcExceptionMessage(Exception ex) {
|
||||
return capitalize(ex.getMessage().replaceFirst("^[A-Z_]+: ", ""));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package bisq.apitest.scenario.bot;
|
||||
|
||||
import bisq.core.api.model.PaymentAccountForm;
|
||||
import bisq.core.locale.Country;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
|
||||
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;
|
||||
|
||||
@Slf4j
|
||||
public class BotPaymentAccountGenerator {
|
||||
|
||||
private final Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
|
||||
|
||||
private final BotClient botClient;
|
||||
|
||||
public BotPaymentAccountGenerator(BotClient botClient) {
|
||||
this.botClient = botClient;
|
||||
}
|
||||
|
||||
public PaymentAccount createF2FPaymentAccount(Country country, String accountName) {
|
||||
try {
|
||||
return botClient.getPaymentAccountWithName(accountName);
|
||||
} catch (PaymentAccountNotFoundException ignored) {
|
||||
// Ignore not found exception, create a new account.
|
||||
}
|
||||
Map<String, Object> p = getPaymentAccountFormMap(F2F_ID);
|
||||
p.put("accountName", accountName);
|
||||
p.put("city", country.name + " City");
|
||||
p.put("country", country.code);
|
||||
p.put("contact", "By Semaphore");
|
||||
p.put("extraInfo", "");
|
||||
// Convert the map back to a json string and create the payment account over gRPC.
|
||||
return botClient.createNewPaymentAccount(gson.toJson(p));
|
||||
}
|
||||
|
||||
public PaymentAccount createZellePaymentAccount(String accountName, String holderName) {
|
||||
try {
|
||||
return botClient.getPaymentAccountWithName(accountName);
|
||||
} catch (PaymentAccountNotFoundException ignored) {
|
||||
// Ignore not found exception, create a new account.
|
||||
}
|
||||
Map<String, Object> p = getPaymentAccountFormMap(CLEAR_X_CHANGE_ID);
|
||||
p.put("accountName", accountName);
|
||||
p.put("emailOrMobileNr", holderName + "@zelle.com");
|
||||
p.put("holderName", holderName);
|
||||
return botClient.createNewPaymentAccount(gson.toJson(p));
|
||||
}
|
||||
|
||||
private Map<String, Object> getPaymentAccountFormMap(String paymentMethodId) {
|
||||
PaymentAccountForm paymentAccountForm = new PaymentAccountForm();
|
||||
File jsonFormTemplate = paymentAccountForm.getPaymentAccountForm(paymentMethodId);
|
||||
jsonFormTemplate.deleteOnExit();
|
||||
String jsonString = paymentAccountForm.toJsonString(jsonFormTemplate);
|
||||
//noinspection unchecked
|
||||
return (Map<String, Object>) gson.fromJson(jsonString, Object.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.apitest.scenario.bot;
|
||||
|
||||
import bisq.common.BisqException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class InvalidRandomOfferException extends BisqException {
|
||||
public InvalidRandomOfferException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public InvalidRandomOfferException(String format, Object... args) {
|
||||
super(format, args);
|
||||
}
|
||||
|
||||
public InvalidRandomOfferException(Throwable cause, String format, Object... args) {
|
||||
super(cause, format, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.apitest.scenario.bot;
|
||||
|
||||
import bisq.common.BisqException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class PaymentAccountNotFoundException extends BisqException {
|
||||
public PaymentAccountNotFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public PaymentAccountNotFoundException(String format, Object... args) {
|
||||
super(format, args);
|
||||
}
|
||||
|
||||
public PaymentAccountNotFoundException(Throwable cause, String format, Object... args) {
|
||||
super(cause, format, args);
|
||||
}
|
||||
}
|
177
apitest/src/test/java/bisq/apitest/scenario/bot/RandomOffer.java
Normal file
177
apitest/src/test/java/bisq/apitest/scenario/bot/RandomOffer.java
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* 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.apitest.scenario.bot;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.cli.CurrencyFormat.formatMarketPrice;
|
||||
import static bisq.cli.CurrencyFormat.formatSatoshis;
|
||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;
|
||||
import static java.lang.String.format;
|
||||
import static java.math.RoundingMode.HALF_UP;
|
||||
|
||||
@Slf4j
|
||||
public class RandomOffer {
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
private static final DecimalFormat FIXED_PRICE_FMT = new DecimalFormat("###########0");
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
// If not an F2F account, keep amount <= 0.01 BTC to avoid hitting unsigned
|
||||
// acct trading limit.
|
||||
private final Supplier<Long> nextAmount = () ->
|
||||
this.getPaymentAccount().getPaymentMethod().getId().equals(F2F_ID)
|
||||
? (long) (10000000 + RANDOM.nextInt(2500000))
|
||||
: (long) (750000 + RANDOM.nextInt(250000));
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final Supplier<Long> nextMinAmount = () -> {
|
||||
boolean useMinAmount = RANDOM.nextBoolean();
|
||||
if (useMinAmount) {
|
||||
return this.getPaymentAccount().getPaymentMethod().getId().equals(F2F_ID)
|
||||
? this.getAmount() - 5000000L
|
||||
: this.getAmount() - 50000L;
|
||||
} else {
|
||||
return this.getAmount();
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final Supplier<Double> nextPriceMargin = () -> {
|
||||
boolean useZeroMargin = RANDOM.nextBoolean();
|
||||
if (useZeroMargin) {
|
||||
return 0.00;
|
||||
} else {
|
||||
BigDecimal min = BigDecimal.valueOf(-5.0).setScale(2, HALF_UP);
|
||||
BigDecimal max = BigDecimal.valueOf(5.0).setScale(2, HALF_UP);
|
||||
BigDecimal randomBigDecimal = min.add(BigDecimal.valueOf(RANDOM.nextDouble()).multiply(max.subtract(min)));
|
||||
return randomBigDecimal.setScale(2, HALF_UP).doubleValue();
|
||||
}
|
||||
};
|
||||
|
||||
private final BotClient botClient;
|
||||
@Getter
|
||||
private final PaymentAccount paymentAccount;
|
||||
@Getter
|
||||
private final String direction;
|
||||
@Getter
|
||||
private final String currencyCode;
|
||||
@Getter
|
||||
private final long amount;
|
||||
@Getter
|
||||
private final long minAmount;
|
||||
@Getter
|
||||
private final boolean useMarketBasedPrice;
|
||||
@Getter
|
||||
private final double priceMargin;
|
||||
@Getter
|
||||
private final String feeCurrency;
|
||||
|
||||
@Getter
|
||||
private String fixedOfferPrice = "0";
|
||||
@Getter
|
||||
private OfferInfo offer;
|
||||
@Getter
|
||||
private String id;
|
||||
|
||||
public RandomOffer(BotClient botClient, PaymentAccount paymentAccount) {
|
||||
this.botClient = botClient;
|
||||
this.paymentAccount = paymentAccount;
|
||||
this.direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
|
||||
this.currencyCode = Objects.requireNonNull(paymentAccount.getSelectedTradeCurrency()).getCode();
|
||||
this.amount = nextAmount.get();
|
||||
this.minAmount = nextMinAmount.get();
|
||||
this.useMarketBasedPrice = RANDOM.nextBoolean();
|
||||
this.priceMargin = nextPriceMargin.get();
|
||||
this.feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
||||
}
|
||||
|
||||
public RandomOffer create() throws InvalidRandomOfferException {
|
||||
try {
|
||||
printDescription();
|
||||
if (useMarketBasedPrice) {
|
||||
this.offer = botClient.createOfferAtMarketBasedPrice(paymentAccount,
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
priceMargin,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
feeCurrency);
|
||||
} else {
|
||||
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
fixedOfferPrice,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
feeCurrency);
|
||||
}
|
||||
this.id = offer.getId();
|
||||
return this;
|
||||
} catch (Exception ex) {
|
||||
String error = format("Could not create valid %s offer for %s BTC: %s",
|
||||
currencyCode,
|
||||
formatSatoshis(amount),
|
||||
ex.getMessage());
|
||||
throw new InvalidRandomOfferException(error, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void printDescription() {
|
||||
double currentMarketPrice = botClient.getCurrentBTCMarketPrice(currencyCode);
|
||||
// Calculate a fixed price based on the random mkt price margin, even if we don't use it.
|
||||
double differenceFromMarketPrice = currentMarketPrice * scaleDownByPowerOf10(priceMargin, 2);
|
||||
double fixedOfferPriceAsDouble = direction.equals("BUY")
|
||||
? currentMarketPrice - differenceFromMarketPrice
|
||||
: currentMarketPrice + differenceFromMarketPrice;
|
||||
this.fixedOfferPrice = FIXED_PRICE_FMT.format(fixedOfferPriceAsDouble);
|
||||
String description = format("Creating new %s %s / %s offer for amount = %s BTC, min-amount = %s BTC.",
|
||||
useMarketBasedPrice ? "mkt-based-price" : "fixed-priced",
|
||||
direction,
|
||||
currencyCode,
|
||||
formatSatoshis(amount),
|
||||
formatSatoshis(minAmount));
|
||||
log.info(description);
|
||||
if (useMarketBasedPrice) {
|
||||
log.info("Offer Price Margin = {}%", priceMargin);
|
||||
log.info("Expected Offer Price = {} {}", formatMarketPrice(Double.parseDouble(fixedOfferPrice)), currencyCode);
|
||||
} else {
|
||||
|
||||
log.info("Fixed Offer Price = {} {}", fixedOfferPrice, currencyCode);
|
||||
}
|
||||
log.info("Current Market Price = {} {}", formatMarketPrice(currentMarketPrice), currencyCode);
|
||||
}
|
||||
}
|
141
apitest/src/test/java/bisq/apitest/scenario/bot/RobotBob.java
Normal file
141
apitest/src/test/java/bisq/apitest/scenario/bot/RobotBob.java
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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.apitest.scenario.bot;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.isShutdownCalled;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.protocol.BotProtocol;
|
||||
import bisq.apitest.scenario.bot.protocol.MakerBotProtocol;
|
||||
import bisq.apitest.scenario.bot.protocol.TakerBotProtocol;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.script.BotScript;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
|
||||
@Slf4j
|
||||
public
|
||||
class RobotBob extends Bot {
|
||||
|
||||
@Getter
|
||||
private int numTrades;
|
||||
|
||||
public RobotBob(BotClient botClient,
|
||||
BotScript botScript,
|
||||
BitcoinCliHelper bitcoinCli,
|
||||
BashScriptGenerator bashScriptGenerator) {
|
||||
super(botClient, botScript, bitcoinCli, bashScriptGenerator);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
for (String action : actions) {
|
||||
checkActionIsValid(action);
|
||||
|
||||
BotProtocol botProtocol;
|
||||
if (action.equalsIgnoreCase(MAKE)) {
|
||||
botProtocol = new MakerBotProtocol(botClient,
|
||||
paymentAccount,
|
||||
protocolStepTimeLimitInMs,
|
||||
bitcoinCli,
|
||||
bashScriptGenerator);
|
||||
} else {
|
||||
botProtocol = new TakerBotProtocol(botClient,
|
||||
paymentAccount,
|
||||
protocolStepTimeLimitInMs,
|
||||
bitcoinCli,
|
||||
bashScriptGenerator);
|
||||
}
|
||||
|
||||
botProtocol.run();
|
||||
|
||||
if (!botProtocol.getCurrentProtocolStep().equals(DONE)) {
|
||||
throw new IllegalStateException(botProtocol.getClass().getSimpleName() + " failed to complete.");
|
||||
}
|
||||
|
||||
log.info("Completed {} successful trade{}. Current Balance:\n{}",
|
||||
++numTrades,
|
||||
numTrades == 1 ? "" : "s",
|
||||
formatBalancesTbls(botClient.getBalance()));
|
||||
|
||||
if (numTrades < actions.length) {
|
||||
try {
|
||||
SECONDS.sleep(20);
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
||||
} // end of actions loop
|
||||
|
||||
if (stayAlive)
|
||||
waitForManualShutdown();
|
||||
else
|
||||
warnCLIUserBeforeShutdown();
|
||||
}
|
||||
|
||||
private void checkActionIsValid(String action) {
|
||||
if (!action.equalsIgnoreCase(MAKE) && !action.equalsIgnoreCase(TAKE))
|
||||
throw new IllegalStateException(action + " is not a valid bot action; must be 'make' or 'take'");
|
||||
}
|
||||
|
||||
private void waitForManualShutdown() {
|
||||
String harnessOrCase = isUsingTestHarness ? "harness" : "case";
|
||||
log.info("All script actions have been completed, but the test {} will stay alive"
|
||||
+ " until a /tmp/bottest-shutdown file is detected.",
|
||||
harnessOrCase);
|
||||
log.info("When ready to shutdown the test {}, run '$ touch /tmp/bottest-shutdown'.",
|
||||
harnessOrCase);
|
||||
if (!isUsingTestHarness) {
|
||||
log.warn("You will have to manually shutdown the bitcoind and Bisq nodes"
|
||||
+ " running outside of the test harness.");
|
||||
}
|
||||
try {
|
||||
while (!isShutdownCalled()) {
|
||||
SECONDS.sleep(10);
|
||||
}
|
||||
log.warn("Manual shutdown signal received.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
log.warn(ex.getMessage());
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
||||
private void warnCLIUserBeforeShutdown() {
|
||||
if (isUsingTestHarness) {
|
||||
long delayInSeconds = 30;
|
||||
log.warn("All script actions have been completed. You have {} seconds to complete any"
|
||||
+ " remaining tasks before the test harness shuts down.",
|
||||
delayInSeconds);
|
||||
try {
|
||||
SECONDS.sleep(delayInSeconds);
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
} else {
|
||||
log.info("Shutting down test case");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
* 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.apitest.scenario.bot.protocol;
|
||||
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.*;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.currentTimeMillis;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
import bisq.cli.TradeFormat;
|
||||
|
||||
@Slf4j
|
||||
public abstract class BotProtocol {
|
||||
|
||||
static final SecureRandom RANDOM = new SecureRandom();
|
||||
static final String BUY = "BUY";
|
||||
static final String SELL = "SELL";
|
||||
|
||||
protected final Supplier<Long> randomDelay = () -> (long) (2000 + RANDOM.nextInt(5000));
|
||||
|
||||
protected final AtomicLong protocolStepStartTime = new AtomicLong(0);
|
||||
protected final Consumer<ProtocolStep> initProtocolStep = (step) -> {
|
||||
currentProtocolStep = step;
|
||||
printBotProtocolStep();
|
||||
protocolStepStartTime.set(currentTimeMillis());
|
||||
};
|
||||
|
||||
@Getter
|
||||
protected ProtocolStep currentProtocolStep;
|
||||
|
||||
@Getter // Functions within 'this' need the @Getter.
|
||||
protected final BotClient botClient;
|
||||
protected final PaymentAccount paymentAccount;
|
||||
protected final String currencyCode;
|
||||
protected final long protocolStepTimeLimitInMs;
|
||||
protected final BitcoinCliHelper bitcoinCli;
|
||||
@Getter
|
||||
protected final BashScriptGenerator bashScriptGenerator;
|
||||
|
||||
public BotProtocol(BotClient botClient,
|
||||
PaymentAccount paymentAccount,
|
||||
long protocolStepTimeLimitInMs,
|
||||
BitcoinCliHelper bitcoinCli,
|
||||
BashScriptGenerator bashScriptGenerator) {
|
||||
this.botClient = botClient;
|
||||
this.paymentAccount = paymentAccount;
|
||||
this.currencyCode = Objects.requireNonNull(paymentAccount.getSelectedTradeCurrency()).getCode();
|
||||
this.protocolStepTimeLimitInMs = protocolStepTimeLimitInMs;
|
||||
this.bitcoinCli = bitcoinCli;
|
||||
this.bashScriptGenerator = bashScriptGenerator;
|
||||
this.currentProtocolStep = START;
|
||||
}
|
||||
|
||||
public abstract void run();
|
||||
|
||||
protected boolean isWithinProtocolStepTimeLimit() {
|
||||
return (currentTimeMillis() - protocolStepStartTime.get()) < protocolStepTimeLimitInMs;
|
||||
}
|
||||
|
||||
protected void checkIsStartStep() {
|
||||
if (currentProtocolStep != START) {
|
||||
throw new IllegalStateException("First bot protocol step must be " + START.name());
|
||||
}
|
||||
}
|
||||
|
||||
protected void printBotProtocolStep() {
|
||||
log.info("Starting protocol step {}. Bot will shutdown if step not completed within {} minutes.",
|
||||
currentProtocolStep.name(), MILLISECONDS.toMinutes(protocolStepTimeLimitInMs));
|
||||
|
||||
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED)) {
|
||||
log.info("Generate a btc block to trigger taker's deposit fee tx confirmation.");
|
||||
createGenerateBtcBlockScript();
|
||||
}
|
||||
}
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> waitForTakerFeeTxConfirm = (trade) -> {
|
||||
sleep(5000);
|
||||
waitForTakerFeeTxPublished(trade.getTradeId());
|
||||
waitForTakerFeeTxConfirmed(trade.getTradeId());
|
||||
return trade;
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> waitForPaymentStartedMessage = (trade) -> {
|
||||
initProtocolStep.accept(WAIT_FOR_PAYMENT_STARTED_MESSAGE);
|
||||
try {
|
||||
createPaymentStartedScript(trade);
|
||||
log.info(" Waiting for a 'payment started' message from buyer for trade with id {}.", trade.getTradeId());
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted before checking if 'payment started' message has been sent.");
|
||||
try {
|
||||
var t = this.getBotClient().getTrade(trade.getTradeId());
|
||||
if (t.getIsFiatSent()) {
|
||||
log.info("Buyer has started payment for trade:\n{}", TradeFormat.format(t));
|
||||
return t;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex));
|
||||
}
|
||||
sleep(randomDelay.get());
|
||||
} // end while
|
||||
|
||||
throw new IllegalStateException("Payment was never sent; we won't wait any longer.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting payment sent message.", ex);
|
||||
}
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> sendPaymentStartedMessage = (trade) -> {
|
||||
initProtocolStep.accept(SEND_PAYMENT_STARTED_MESSAGE);
|
||||
checkIfShutdownCalled("Interrupted before sending 'payment started' message.");
|
||||
this.getBotClient().sendConfirmPaymentStartedMessage(trade.getTradeId());
|
||||
return trade;
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> waitForPaymentReceivedConfirmation = (trade) -> {
|
||||
initProtocolStep.accept(WAIT_FOR_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE);
|
||||
createPaymentReceivedScript(trade);
|
||||
try {
|
||||
log.info("Waiting for a 'payment received confirmation' message from seller for trade with id {}.", trade.getTradeId());
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted before checking if 'payment received confirmation' message has been sent.");
|
||||
try {
|
||||
var t = this.getBotClient().getTrade(trade.getTradeId());
|
||||
if (t.getIsFiatReceived()) {
|
||||
log.info("Seller has received payment for trade:\n{}", TradeFormat.format(t));
|
||||
return t;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex));
|
||||
}
|
||||
sleep(randomDelay.get());
|
||||
} // end while
|
||||
|
||||
throw new IllegalStateException("Payment was never received; we won't wait any longer.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting payment received confirmation message.", ex);
|
||||
}
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> sendPaymentReceivedMessage = (trade) -> {
|
||||
initProtocolStep.accept(SEND_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE);
|
||||
checkIfShutdownCalled("Interrupted before sending 'payment received confirmation' message.");
|
||||
this.getBotClient().sendConfirmPaymentReceivedMessage(trade.getTradeId());
|
||||
return trade;
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> waitForPayoutTx = (trade) -> {
|
||||
initProtocolStep.accept(WAIT_FOR_PAYOUT_TX);
|
||||
try {
|
||||
log.info("Waiting on the 'payout tx published confirmation' for trade with id {}.", trade.getTradeId());
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted before checking if payout tx has been published.");
|
||||
try {
|
||||
var t = this.getBotClient().getTrade(trade.getTradeId());
|
||||
if (t.getIsPayoutPublished()) {
|
||||
log.info("Payout tx {} has been published for trade:\n{}",
|
||||
t.getPayoutTxId(),
|
||||
TradeFormat.format(t));
|
||||
return t;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex));
|
||||
}
|
||||
sleep(randomDelay.get());
|
||||
} // end while
|
||||
|
||||
throw new IllegalStateException("Payout tx was never published; we won't wait any longer.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting for published payout tx.", ex);
|
||||
}
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> keepFundsFromTrade = (trade) -> {
|
||||
initProtocolStep.accept(KEEP_FUNDS);
|
||||
var isBuy = trade.getOffer().getDirection().equalsIgnoreCase(BUY);
|
||||
var isSell = trade.getOffer().getDirection().equalsIgnoreCase(SELL);
|
||||
var cliUserIsSeller = (this instanceof MakerBotProtocol && isBuy) || (this instanceof TakerBotProtocol && isSell);
|
||||
if (cliUserIsSeller) {
|
||||
createKeepFundsScript(trade);
|
||||
} else {
|
||||
createGetBalanceScript();
|
||||
}
|
||||
checkIfShutdownCalled("Interrupted before closing trade with 'keep funds' command.");
|
||||
this.getBotClient().sendKeepFundsMessage(trade.getTradeId());
|
||||
return trade;
|
||||
};
|
||||
|
||||
protected void createPaymentStartedScript(TradeInfo trade) {
|
||||
File script = bashScriptGenerator.createPaymentStartedScript(trade);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can send a 'payment started' message");
|
||||
}
|
||||
|
||||
protected void createPaymentReceivedScript(TradeInfo trade) {
|
||||
File script = bashScriptGenerator.createPaymentReceivedScript(trade);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can sent a 'payment received confirmation' message");
|
||||
}
|
||||
|
||||
protected void createKeepFundsScript(TradeInfo trade) {
|
||||
File script = bashScriptGenerator.createKeepFundsScript(trade);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can close the trade");
|
||||
}
|
||||
|
||||
protected void createGetBalanceScript() {
|
||||
File script = bashScriptGenerator.createGetBalanceScript();
|
||||
printCliHintAndOrScript(script, "The manual CLI side can view current balances");
|
||||
}
|
||||
|
||||
protected void createGenerateBtcBlockScript() {
|
||||
String newBitcoinCoreAddress = bitcoinCli.getNewBtcAddress();
|
||||
File script = bashScriptGenerator.createGenerateBtcBlockScript(newBitcoinCoreAddress);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can generate 1 btc block");
|
||||
}
|
||||
|
||||
protected void printCliHintAndOrScript(File script, String hint) {
|
||||
log.info("{} by running bash script '{}'.", hint, script.getAbsolutePath());
|
||||
if (this.getBashScriptGenerator().isPrintCliScripts())
|
||||
this.getBashScriptGenerator().printCliScript(script, log);
|
||||
|
||||
sleep(5000); // Allow 5s for CLI user to read the hint.
|
||||
}
|
||||
|
||||
protected void sleep(long ms) {
|
||||
try {
|
||||
MILLISECONDS.sleep(ms);
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForTakerFeeTxPublished(String tradeId) {
|
||||
waitForTakerDepositFee(tradeId, WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED);
|
||||
}
|
||||
|
||||
private void waitForTakerFeeTxConfirmed(String tradeId) {
|
||||
waitForTakerDepositFee(tradeId, WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED);
|
||||
}
|
||||
|
||||
private void waitForTakerDepositFee(String tradeId, ProtocolStep depositTxProtocolStep) {
|
||||
initProtocolStep.accept(depositTxProtocolStep);
|
||||
validateCurrentProtocolStep(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED, WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED);
|
||||
try {
|
||||
log.info(waitingForDepositFeeTxMsg(tradeId));
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted before checking taker deposit fee tx is published and confirmed.");
|
||||
try {
|
||||
var trade = this.getBotClient().getTrade(tradeId);
|
||||
if (isDepositFeeTxStepComplete.test(trade))
|
||||
return;
|
||||
else
|
||||
sleep(randomDelay.get());
|
||||
} catch (Exception ex) {
|
||||
if (this.getBotClient().tradeContractIsNotReady.test(ex, tradeId))
|
||||
sleep(randomDelay.get());
|
||||
else
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex));
|
||||
}
|
||||
} // end while
|
||||
throw new IllegalStateException(stoppedWaitingForDepositFeeTxMsg(this.getBotClient().getTrade(tradeId).getDepositTxId()));
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting for taker deposit tx to be published or confirmed.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private final Predicate<TradeInfo> isDepositFeeTxStepComplete = (trade) -> {
|
||||
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) && trade.getIsDepositPublished()) {
|
||||
log.info("Taker deposit fee tx {} has been published.", trade.getDepositTxId());
|
||||
return true;
|
||||
} else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED) && trade.getIsDepositConfirmed()) {
|
||||
log.info("Taker deposit fee tx {} has been confirmed.", trade.getDepositTxId());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private void validateCurrentProtocolStep(Enum<?>... validBotSteps) {
|
||||
for (Enum<?> validBotStep : validBotSteps) {
|
||||
if (currentProtocolStep.equals(validBotStep))
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException("Unexpected bot step: " + currentProtocolStep.name() + ".\n"
|
||||
+ "Must be one of "
|
||||
+ stream(validBotSteps).map((Enum::name)).collect(Collectors.joining(","))
|
||||
+ ".");
|
||||
}
|
||||
|
||||
private String waitingForDepositFeeTxMsg(String tradeId) {
|
||||
return format("Waiting for taker deposit fee tx for trade %s to be %s.",
|
||||
tradeId,
|
||||
currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) ? "published" : "confirmed");
|
||||
}
|
||||
|
||||
private String stoppedWaitingForDepositFeeTxMsg(String txId) {
|
||||
return format("Taker deposit fee tx %s is took too long to be %s; we won't wait any longer.",
|
||||
txId,
|
||||
currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) ? "published" : "confirmed");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package bisq.apitest.scenario.bot.protocol;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.WAIT_FOR_OFFER_TAKER;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.RandomOffer;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
import bisq.cli.TradeFormat;
|
||||
|
||||
@Slf4j
|
||||
public class MakerBotProtocol extends BotProtocol {
|
||||
|
||||
public MakerBotProtocol(BotClient botClient,
|
||||
PaymentAccount paymentAccount,
|
||||
long protocolStepTimeLimitInMs,
|
||||
BitcoinCliHelper bitcoinCli,
|
||||
BashScriptGenerator bashScriptGenerator) {
|
||||
super(botClient,
|
||||
paymentAccount,
|
||||
protocolStepTimeLimitInMs,
|
||||
bitcoinCli,
|
||||
bashScriptGenerator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
checkIsStartStep();
|
||||
|
||||
Function<Supplier<OfferInfo>, TradeInfo> makeTrade = waitForNewTrade.andThen(waitForTakerFeeTxConfirm);
|
||||
var trade = makeTrade.apply(randomOffer);
|
||||
|
||||
var makerIsBuyer = trade.getOffer().getDirection().equalsIgnoreCase(BUY);
|
||||
Function<TradeInfo, TradeInfo> completeFiatTransaction = makerIsBuyer
|
||||
? sendPaymentStartedMessage.andThen(waitForPaymentReceivedConfirmation)
|
||||
: waitForPaymentStartedMessage.andThen(sendPaymentReceivedMessage);
|
||||
completeFiatTransaction.apply(trade);
|
||||
|
||||
Function<TradeInfo, TradeInfo> closeTrade = waitForPayoutTx.andThen(keepFundsFromTrade);
|
||||
closeTrade.apply(trade);
|
||||
|
||||
currentProtocolStep = DONE;
|
||||
}
|
||||
|
||||
private final Supplier<OfferInfo> randomOffer = () -> {
|
||||
checkIfShutdownCalled("Interrupted before creating random offer.");
|
||||
OfferInfo offer = new RandomOffer(botClient, paymentAccount).create().getOffer();
|
||||
log.info("Created random {} offer\n{}", currencyCode, formatOfferTable(singletonList(offer), currencyCode));
|
||||
return offer;
|
||||
};
|
||||
|
||||
private final Function<Supplier<OfferInfo>, TradeInfo> waitForNewTrade = (randomOffer) -> {
|
||||
initProtocolStep.accept(WAIT_FOR_OFFER_TAKER);
|
||||
OfferInfo offer = randomOffer.get();
|
||||
createTakeOfferCliScript(offer);
|
||||
try {
|
||||
log.info("Impatiently waiting for offer {} to be taken, repeatedly calling gettrade.", offer.getId());
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted while waiting for offer to be taken.");
|
||||
try {
|
||||
var trade = getNewTrade(offer.getId());
|
||||
if (trade.isPresent())
|
||||
return trade.get();
|
||||
else
|
||||
sleep(randomDelay.get());
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex), ex);
|
||||
}
|
||||
} // end while
|
||||
throw new IllegalStateException("Offer was never taken; we won't wait any longer.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting for offer to be taken.", ex);
|
||||
}
|
||||
};
|
||||
|
||||
private Optional<TradeInfo> getNewTrade(String offerId) {
|
||||
try {
|
||||
var trade = botClient.getTrade(offerId);
|
||||
log.info("Offer {} was taken, new trade:\n{}", offerId, TradeFormat.format(trade));
|
||||
return Optional.of(trade);
|
||||
} catch (Exception ex) {
|
||||
// Get trade will throw a non-fatal gRPC exception if not found.
|
||||
log.info(this.getBotClient().toCleanGrpcExceptionMessage(ex));
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void createTakeOfferCliScript(OfferInfo offer) {
|
||||
File script = bashScriptGenerator.createTakeOfferScript(offer);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can take the offer");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package bisq.apitest.scenario.bot.protocol;
|
||||
|
||||
public enum ProtocolStep {
|
||||
START,
|
||||
FIND_OFFER,
|
||||
TAKE_OFFER,
|
||||
WAIT_FOR_OFFER_TAKER,
|
||||
WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED,
|
||||
WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED,
|
||||
SEND_PAYMENT_STARTED_MESSAGE,
|
||||
WAIT_FOR_PAYMENT_STARTED_MESSAGE,
|
||||
SEND_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
|
||||
WAIT_FOR_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
|
||||
WAIT_FOR_PAYOUT_TX,
|
||||
KEEP_FUNDS,
|
||||
DONE
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package bisq.apitest.scenario.bot.protocol;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.FIND_OFFER;
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.TAKE_OFFER;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
|
||||
@Slf4j
|
||||
public class TakerBotProtocol extends BotProtocol {
|
||||
|
||||
public TakerBotProtocol(BotClient botClient,
|
||||
PaymentAccount paymentAccount,
|
||||
long protocolStepTimeLimitInMs,
|
||||
BitcoinCliHelper bitcoinCli,
|
||||
BashScriptGenerator bashScriptGenerator) {
|
||||
super(botClient,
|
||||
paymentAccount,
|
||||
protocolStepTimeLimitInMs,
|
||||
bitcoinCli,
|
||||
bashScriptGenerator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
checkIsStartStep();
|
||||
|
||||
Function<OfferInfo, TradeInfo> takeTrade = takeOffer.andThen(waitForTakerFeeTxConfirm);
|
||||
var trade = takeTrade.apply(findOffer.get());
|
||||
|
||||
var takerIsSeller = trade.getOffer().getDirection().equalsIgnoreCase(BUY);
|
||||
Function<TradeInfo, TradeInfo> completeFiatTransaction = takerIsSeller
|
||||
? waitForPaymentStartedMessage.andThen(sendPaymentReceivedMessage)
|
||||
: sendPaymentStartedMessage.andThen(waitForPaymentReceivedConfirmation);
|
||||
completeFiatTransaction.apply(trade);
|
||||
|
||||
Function<TradeInfo, TradeInfo> closeTrade = waitForPayoutTx.andThen(keepFundsFromTrade);
|
||||
closeTrade.apply(trade);
|
||||
|
||||
currentProtocolStep = DONE;
|
||||
}
|
||||
|
||||
private final Supplier<Optional<OfferInfo>> firstOffer = () -> {
|
||||
var offers = botClient.getOffers(currencyCode);
|
||||
if (offers.size() > 0) {
|
||||
log.info("Offers found:\n{}", formatOfferTable(offers, currencyCode));
|
||||
OfferInfo offer = offers.get(0);
|
||||
log.info("Will take first offer {}", offer.getId());
|
||||
return Optional.of(offer);
|
||||
} else {
|
||||
log.info("No buy or sell {} offers found.", currencyCode);
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
|
||||
private final Supplier<OfferInfo> findOffer = () -> {
|
||||
initProtocolStep.accept(FIND_OFFER);
|
||||
createMakeOfferScript();
|
||||
try {
|
||||
log.info("Impatiently waiting for at least one {} offer to be created, repeatedly calling getoffers.", currencyCode);
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted while checking offers.");
|
||||
try {
|
||||
Optional<OfferInfo> offer = firstOffer.get();
|
||||
if (offer.isPresent())
|
||||
return offer.get();
|
||||
else
|
||||
sleep(randomDelay.get());
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex), ex);
|
||||
}
|
||||
} // end while
|
||||
throw new IllegalStateException("Offer was never created; we won't wait any longer.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting for a new offer.", ex);
|
||||
}
|
||||
};
|
||||
|
||||
private final Function<OfferInfo, TradeInfo> takeOffer = (offer) -> {
|
||||
initProtocolStep.accept(TAKE_OFFER);
|
||||
checkIfShutdownCalled("Interrupted before taking offer.");
|
||||
String feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
||||
return botClient.takeOffer(offer.getId(), paymentAccount, feeCurrency);
|
||||
};
|
||||
|
||||
private void createMakeOfferScript() {
|
||||
String direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
|
||||
String feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
||||
boolean createMarginPricedOffer = RANDOM.nextBoolean();
|
||||
// If not using an F2F account, don't go over possible 0.01 BTC
|
||||
// limit if account is not signed.
|
||||
String amount = paymentAccount.getPaymentMethod().getId().equals(F2F_ID)
|
||||
? "0.25"
|
||||
: "0.01";
|
||||
File script;
|
||||
if (createMarginPricedOffer) {
|
||||
script = bashScriptGenerator.createMakeMarginPricedOfferScript(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
"0.0",
|
||||
"15.0",
|
||||
feeCurrency);
|
||||
} else {
|
||||
script = bashScriptGenerator.createMakeFixedPricedOfferScript(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
botClient.getCurrentBTCMarketPriceAsIntegerString(currencyCode),
|
||||
"15.0",
|
||||
feeCurrency);
|
||||
}
|
||||
printCliHintAndOrScript(script, "The manual CLI side can create an offer");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* 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.apitest.scenario.bot.script;
|
||||
|
||||
import bisq.common.file.FileUtil;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.io.FileWriteMode.APPEND;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.getProperty;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.file.Files.readAllBytes;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
public class BashScriptGenerator {
|
||||
|
||||
private final int apiPort;
|
||||
private final String apiPassword;
|
||||
private final String paymentAccountId;
|
||||
private final String cliBase;
|
||||
private final boolean printCliScripts;
|
||||
|
||||
public BashScriptGenerator(String apiPassword,
|
||||
int apiPort,
|
||||
String paymentAccountId,
|
||||
boolean printCliScripts) {
|
||||
this.apiPassword = apiPassword;
|
||||
this.apiPort = apiPort;
|
||||
this.paymentAccountId = paymentAccountId;
|
||||
this.printCliScripts = printCliScripts;
|
||||
this.cliBase = format("./bisq-cli --password=%s --port=%d", apiPassword, apiPort);
|
||||
}
|
||||
|
||||
public File createMakeMarginPricedOfferScript(String direction,
|
||||
String currencyCode,
|
||||
String amount,
|
||||
String marketPriceMargin,
|
||||
String securityDeposit,
|
||||
String feeCurrency) {
|
||||
String makeOfferCmd = format("%s createoffer --payment-account=%s "
|
||||
+ " --direction=%s"
|
||||
+ " --currency-code=%s"
|
||||
+ " --amount=%s"
|
||||
+ " --market-price-margin=%s"
|
||||
+ " --security-deposit=%s"
|
||||
+ " --fee-currency=%s",
|
||||
cliBase,
|
||||
this.getPaymentAccountId(),
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
marketPriceMargin,
|
||||
securityDeposit,
|
||||
feeCurrency);
|
||||
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
|
||||
cliBase,
|
||||
direction,
|
||||
currencyCode);
|
||||
return createCliScript("createoffer.sh",
|
||||
makeOfferCmd,
|
||||
"sleep 2",
|
||||
getOffersCmd);
|
||||
}
|
||||
|
||||
public File createMakeFixedPricedOfferScript(String direction,
|
||||
String currencyCode,
|
||||
String amount,
|
||||
String fixedPrice,
|
||||
String securityDeposit,
|
||||
String feeCurrency) {
|
||||
String makeOfferCmd = format("%s createoffer --payment-account=%s "
|
||||
+ " --direction=%s"
|
||||
+ " --currency-code=%s"
|
||||
+ " --amount=%s"
|
||||
+ " --fixed-price=%s"
|
||||
+ " --security-deposit=%s"
|
||||
+ " --fee-currency=%s",
|
||||
cliBase,
|
||||
this.getPaymentAccountId(),
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
fixedPrice,
|
||||
securityDeposit,
|
||||
feeCurrency);
|
||||
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
|
||||
cliBase,
|
||||
direction,
|
||||
currencyCode);
|
||||
return createCliScript("createoffer.sh",
|
||||
makeOfferCmd,
|
||||
"sleep 2",
|
||||
getOffersCmd);
|
||||
}
|
||||
|
||||
public File createTakeOfferScript(OfferInfo offer) {
|
||||
String getOffersCmd = format("%s getoffers --direction=%s --currency-code=%s",
|
||||
cliBase,
|
||||
offer.getDirection(),
|
||||
offer.getCounterCurrencyCode());
|
||||
String takeOfferCmd = format("%s takeoffer --offer-id=%s --payment-account=%s --fee-currency=BSQ",
|
||||
cliBase,
|
||||
offer.getId(),
|
||||
this.getPaymentAccountId());
|
||||
String getTradeCmd = format("%s gettrade --trade-id=%s",
|
||||
cliBase,
|
||||
offer.getId());
|
||||
return createCliScript("takeoffer.sh",
|
||||
getOffersCmd,
|
||||
takeOfferCmd,
|
||||
"sleep 5",
|
||||
getTradeCmd);
|
||||
}
|
||||
|
||||
public File createPaymentStartedScript(TradeInfo trade) {
|
||||
String paymentStartedCmd = format("%s confirmpaymentstarted --trade-id=%s",
|
||||
cliBase,
|
||||
trade.getTradeId());
|
||||
String getTradeCmd = format("%s gettrade --trade-id=%s", cliBase, trade.getTradeId());
|
||||
return createCliScript("confirmpaymentstarted.sh",
|
||||
paymentStartedCmd,
|
||||
"sleep 2",
|
||||
getTradeCmd);
|
||||
}
|
||||
|
||||
public File createPaymentReceivedScript(TradeInfo trade) {
|
||||
String paymentStartedCmd = format("%s confirmpaymentreceived --trade-id=%s",
|
||||
cliBase,
|
||||
trade.getTradeId());
|
||||
String getTradeCmd = format("%s gettrade --trade-id=%s", cliBase, trade.getTradeId());
|
||||
return createCliScript("confirmpaymentreceived.sh",
|
||||
paymentStartedCmd,
|
||||
"sleep 2",
|
||||
getTradeCmd);
|
||||
}
|
||||
|
||||
public File createKeepFundsScript(TradeInfo trade) {
|
||||
String paymentStartedCmd = format("%s keepfunds --trade-id=%s", cliBase, trade.getTradeId());
|
||||
String getTradeCmd = format("%s gettrade --trade-id=%s", cliBase, trade.getTradeId());
|
||||
String getBalanceCmd = format("%s getbalance", cliBase);
|
||||
return createCliScript("keepfunds.sh",
|
||||
paymentStartedCmd,
|
||||
"sleep 2",
|
||||
getTradeCmd,
|
||||
getBalanceCmd);
|
||||
}
|
||||
|
||||
public File createGetBalanceScript() {
|
||||
String getBalanceCmd = format("%s getbalance", cliBase);
|
||||
return createCliScript("getbalance.sh", getBalanceCmd);
|
||||
}
|
||||
|
||||
public File createGenerateBtcBlockScript(String address) {
|
||||
String bitcoinCliCmd = format("bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest"
|
||||
+ " -rpcpassword=apitest generatetoaddress 1 \"%s\"",
|
||||
address);
|
||||
return createCliScript("genbtcblk.sh",
|
||||
bitcoinCliCmd);
|
||||
}
|
||||
|
||||
public File createCliScript(String scriptName, String... commands) {
|
||||
String filename = getProperty("java.io.tmpdir") + File.separator + scriptName;
|
||||
File oldScript = new File(filename);
|
||||
if (oldScript.exists()) {
|
||||
try {
|
||||
FileUtil.deleteFileIfExists(oldScript);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("Unable to delete old script.", ex);
|
||||
}
|
||||
}
|
||||
File script = new File(filename);
|
||||
try {
|
||||
List<CharSequence> lines = new ArrayList<>();
|
||||
lines.add("#!/bin/bash");
|
||||
lines.add("############################################################");
|
||||
lines.add("# This example CLI script may be overwritten during the test");
|
||||
lines.add("# run, and will be deleted when the test harness shuts down.");
|
||||
lines.add("# Make a copy if you want to save it.");
|
||||
lines.add("############################################################");
|
||||
lines.add("set -x");
|
||||
Collections.addAll(lines, commands);
|
||||
Files.asCharSink(script, UTF_8, APPEND).writeLines(lines);
|
||||
if (!script.setExecutable(true))
|
||||
throw new IllegalStateException("Unable to set script owner's execute permission.");
|
||||
} catch (IOException ex) {
|
||||
log.error("", ex);
|
||||
throw new IllegalStateException(ex);
|
||||
} finally {
|
||||
script.deleteOnExit();
|
||||
}
|
||||
return script;
|
||||
}
|
||||
|
||||
public void printCliScript(File cliScript,
|
||||
org.slf4j.Logger logger) {
|
||||
try {
|
||||
String contents = new String(readAllBytes(Paths.get(cliScript.getPath())));
|
||||
logger.info("CLI script {}:\n{}", cliScript.getAbsolutePath(), contents);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("Error reading CLI script contents.", ex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.apitest.scenario.bot.script;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public
|
||||
class BotScript {
|
||||
|
||||
// Common, default is true.
|
||||
private final boolean useTestHarness;
|
||||
|
||||
// Used only with test harness. Mutually exclusive, but if both are not null,
|
||||
// the botPaymentMethodId takes precedence over countryCode.
|
||||
@Nullable
|
||||
private final String botPaymentMethodId;
|
||||
@Nullable
|
||||
private final String countryCode;
|
||||
|
||||
// Used only without test harness.
|
||||
@Nullable
|
||||
@Setter
|
||||
private String paymentAccountIdForBot;
|
||||
@Nullable
|
||||
@Setter
|
||||
private String paymentAccountIdForCliScripts;
|
||||
|
||||
// Common, used with or without test harness.
|
||||
private final int apiPortForCliScripts;
|
||||
private final String[] actions;
|
||||
private final long protocolStepTimeLimitInMinutes;
|
||||
private final boolean printCliScripts;
|
||||
private final boolean stayAlive;
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
BotScript(boolean useTestHarness,
|
||||
String botPaymentMethodId,
|
||||
String countryCode,
|
||||
String paymentAccountIdForBot,
|
||||
String paymentAccountIdForCliScripts,
|
||||
String[] actions,
|
||||
int apiPortForCliScripts,
|
||||
long protocolStepTimeLimitInMinutes,
|
||||
boolean printCliScripts,
|
||||
boolean stayAlive) {
|
||||
this.useTestHarness = useTestHarness;
|
||||
this.botPaymentMethodId = botPaymentMethodId;
|
||||
this.countryCode = countryCode != null ? countryCode.toUpperCase() : null;
|
||||
this.paymentAccountIdForBot = paymentAccountIdForBot;
|
||||
this.paymentAccountIdForCliScripts = paymentAccountIdForCliScripts;
|
||||
this.apiPortForCliScripts = apiPortForCliScripts;
|
||||
this.actions = actions;
|
||||
this.protocolStepTimeLimitInMinutes = protocolStepTimeLimitInMinutes;
|
||||
this.printCliScripts = printCliScripts;
|
||||
this.stayAlive = stayAlive;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* 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.apitest.scenario.bot.script;
|
||||
|
||||
import bisq.common.file.JsonFileManager;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import joptsimple.BuiltinHelpFormatter;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.lang.System.err;
|
||||
import static java.lang.System.exit;
|
||||
import static java.lang.System.getProperty;
|
||||
import static java.lang.System.out;
|
||||
|
||||
@Slf4j
|
||||
public class BotScriptGenerator {
|
||||
|
||||
private final boolean useTestHarness;
|
||||
@Nullable
|
||||
private final String countryCode;
|
||||
@Nullable
|
||||
private final String botPaymentMethodId;
|
||||
@Nullable
|
||||
private final String paymentAccountIdForBot;
|
||||
@Nullable
|
||||
private final String paymentAccountIdForCliScripts;
|
||||
private final int apiPortForCliScripts;
|
||||
private final String actions;
|
||||
private final int protocolStepTimeLimitInMinutes;
|
||||
private final boolean printCliScripts;
|
||||
private final boolean stayAlive;
|
||||
|
||||
public BotScriptGenerator(String[] args) {
|
||||
OptionParser parser = new OptionParser();
|
||||
var helpOpt = parser.accepts("help", "Print this help text.")
|
||||
.forHelp();
|
||||
OptionSpec<Boolean> useTestHarnessOpt = parser
|
||||
.accepts("use-testharness", "Use the test harness, or manually start your own nodes.")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(true);
|
||||
OptionSpec<String> actionsOpt = parser
|
||||
.accepts("actions", "A comma delimited list with no spaces, e.g., make,take,take,make,...")
|
||||
.withRequiredArg();
|
||||
OptionSpec<String> botPaymentMethodIdOpt = parser
|
||||
.accepts("bot-payment-method",
|
||||
"The bot's (Bob) payment method id. If using the test harness,"
|
||||
+ " the id will be used to automatically create a payment account.")
|
||||
.withRequiredArg();
|
||||
OptionSpec<String> countryCodeOpt = parser
|
||||
.accepts("country-code",
|
||||
"The two letter country-code for an F2F payment account if using the test harness,"
|
||||
+ " but the bot-payment-method option takes precedence.")
|
||||
.withRequiredArg();
|
||||
OptionSpec<Integer> apiPortForCliScriptsOpt = parser
|
||||
.accepts("api-port-for-cli-scripts",
|
||||
"The api port used in bot generated bash/cli scripts.")
|
||||
.withRequiredArg()
|
||||
.ofType(Integer.class)
|
||||
.defaultsTo(9998);
|
||||
OptionSpec<String> paymentAccountIdForBotOpt = parser
|
||||
.accepts("payment-account-for-bot",
|
||||
"The bot side's payment account id, when the test harness is not used,"
|
||||
+ " and Bob & Alice accounts are not automatically created.")
|
||||
.withRequiredArg();
|
||||
OptionSpec<String> paymentAccountIdForCliScriptsOpt = parser
|
||||
.accepts("payment-account-for-cli-scripts",
|
||||
"The other side's payment account id, used in generated bash/cli scripts when"
|
||||
+ " the test harness is not used, and Bob & Alice accounts are not automatically created.")
|
||||
.withRequiredArg();
|
||||
OptionSpec<Integer> protocolStepTimeLimitInMinutesOpt = parser
|
||||
.accepts("step-time-limit", "Each protocol step's time limit in minutes")
|
||||
.withRequiredArg()
|
||||
.ofType(Integer.class)
|
||||
.defaultsTo(60);
|
||||
OptionSpec<Boolean> printCliScriptsOpt = parser
|
||||
.accepts("print-cli-scripts", "Print the generated CLI scripts from bot")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(false);
|
||||
OptionSpec<Boolean> stayAliveOpt = parser
|
||||
.accepts("stay-alive", "Leave test harness nodes running after the last action.")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(true);
|
||||
OptionSet options = parser.parse(args);
|
||||
|
||||
if (options.has(helpOpt)) {
|
||||
printHelp(parser, out);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (!options.has(actionsOpt)) {
|
||||
printHelp(parser, err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
this.useTestHarness = options.has(useTestHarnessOpt) ? options.valueOf(useTestHarnessOpt) : true;
|
||||
this.actions = options.valueOf(actionsOpt);
|
||||
this.apiPortForCliScripts = options.has(apiPortForCliScriptsOpt) ? options.valueOf(apiPortForCliScriptsOpt) : 9998;
|
||||
this.botPaymentMethodId = options.has(botPaymentMethodIdOpt) ? options.valueOf(botPaymentMethodIdOpt) : null;
|
||||
this.countryCode = options.has(countryCodeOpt) ? options.valueOf(countryCodeOpt) : null;
|
||||
this.paymentAccountIdForBot = options.has(paymentAccountIdForBotOpt) ? options.valueOf(paymentAccountIdForBotOpt) : null;
|
||||
this.paymentAccountIdForCliScripts = options.has(paymentAccountIdForCliScriptsOpt) ? options.valueOf(paymentAccountIdForCliScriptsOpt) : null;
|
||||
this.protocolStepTimeLimitInMinutes = options.valueOf(protocolStepTimeLimitInMinutesOpt);
|
||||
this.printCliScripts = options.valueOf(printCliScriptsOpt);
|
||||
this.stayAlive = options.valueOf(stayAliveOpt);
|
||||
|
||||
var noPaymentAccountCountryOrMethodForTestHarness = useTestHarness &&
|
||||
(!options.has(countryCodeOpt) && !options.has(botPaymentMethodIdOpt));
|
||||
if (noPaymentAccountCountryOrMethodForTestHarness) {
|
||||
log.error("When running the test harness, payment accounts are automatically generated,");
|
||||
log.error("and you must provide one of the following options:");
|
||||
log.error(" \t\t(1) --bot-payment-method=<payment-method-id> OR");
|
||||
log.error(" \t\t(2) --country-code=<country-code>");
|
||||
log.error("If the bot-payment-method option is not present, the bot will create"
|
||||
+ " a country based F2F account using the country-code.");
|
||||
log.error("If both are present, the bot-payment-method will take precedence. "
|
||||
+ "Currently, only the CLEAR_X_CHANGE_ID bot-payment-method is supported.");
|
||||
printHelp(parser, err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
var noPaymentAccountIdOrApiPortForCliScripts = !useTestHarness &&
|
||||
(!options.has(paymentAccountIdForCliScriptsOpt) || !options.has(paymentAccountIdForBotOpt));
|
||||
if (noPaymentAccountIdOrApiPortForCliScripts) {
|
||||
log.error("If not running the test harness, payment accounts are not automatically generated,");
|
||||
log.error("and you must provide three options:");
|
||||
log.error(" \t\t(1) --api-port-for-cli-scripts=<port>");
|
||||
log.error(" \t\t(2) --payment-account-for-bot=<payment-account-id>");
|
||||
log.error(" \t\t(3) --payment-account-for-cli-scripts=<payment-account-id>");
|
||||
log.error("These will be used by the bot and in CLI scripts the bot will generate when creating an offer.");
|
||||
printHelp(parser, err);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void printHelp(OptionParser parser, PrintStream stream) {
|
||||
try {
|
||||
String usage = "Examples\n--------\n"
|
||||
+ examplesUsingTestHarness()
|
||||
+ examplesNotUsingTestHarness();
|
||||
stream.println();
|
||||
parser.formatHelpWith(new HelpFormatter());
|
||||
parser.printHelpOn(stream);
|
||||
stream.println();
|
||||
stream.println(usage);
|
||||
stream.println();
|
||||
} catch (IOException ex) {
|
||||
log.error("", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String examplesUsingTestHarness() {
|
||||
@SuppressWarnings("StringBufferReplaceableByString") StringBuilder builder = new StringBuilder();
|
||||
builder.append("To generate a bot-script.json file that will start the test harness,");
|
||||
builder.append(" create F2F accounts for Bob and Alice,");
|
||||
builder.append(" and take an offer created by Alice's CLI:").append("\n");
|
||||
builder.append("\tUsage: BotScriptGenerator").append("\n");
|
||||
builder.append("\t\t").append("--use-testharness=true").append("\n");
|
||||
builder.append("\t\t").append("--country-code=<country-code>").append("\n");
|
||||
builder.append("\t\t").append("--actions=take").append("\n");
|
||||
builder.append("\n");
|
||||
builder.append("To generate a bot-script.json file that will start the test harness,");
|
||||
builder.append(" create Zelle accounts for Bob and Alice,");
|
||||
builder.append(" and create an offer to be taken by Alice's CLI:").append("\n");
|
||||
builder.append("\tUsage: BotScriptGenerator").append("\n");
|
||||
builder.append("\t\t").append("--use-testharness=true").append("\n");
|
||||
builder.append("\t\t").append("--bot-payment-method=CLEAR_X_CHANGE").append("\n");
|
||||
builder.append("\t\t").append("--actions=make").append("\n");
|
||||
builder.append("\n");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String examplesNotUsingTestHarness() {
|
||||
@SuppressWarnings("StringBufferReplaceableByString") StringBuilder builder = new StringBuilder();
|
||||
builder.append("To generate a bot-script.json file that will not start the test harness,");
|
||||
builder.append(" but will create useful bash scripts for the CLI user,");
|
||||
builder.append(" and make two offers, then take two offers:").append("\n");
|
||||
builder.append("\tUsage: BotScriptGenerator").append("\n");
|
||||
builder.append("\t\t").append("--use-testharness=false").append("\n");
|
||||
builder.append("\t\t").append("--api-port-for-cli-scripts=<port>").append("\n");
|
||||
builder.append("\t\t").append("--payment-account-for-bot=<payment-account-id>").append("\n");
|
||||
builder.append("\t\t").append("--payment-account-for-cli-scripts=<payment-account-id>").append("\n");
|
||||
builder.append("\t\t").append("--actions=make,make,take,take").append("\n");
|
||||
builder.append("\n");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String generateBotScriptTemplate() {
|
||||
return Utilities.objectToJson(new BotScript(
|
||||
useTestHarness,
|
||||
botPaymentMethodId,
|
||||
countryCode,
|
||||
paymentAccountIdForBot,
|
||||
paymentAccountIdForCliScripts,
|
||||
actions.split("\\s*,\\s*").clone(),
|
||||
apiPortForCliScripts,
|
||||
protocolStepTimeLimitInMinutes,
|
||||
printCliScripts,
|
||||
stayAlive));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
BotScriptGenerator generator = new BotScriptGenerator(args);
|
||||
String json = generator.generateBotScriptTemplate();
|
||||
String destDir = getProperty("java.io.tmpdir");
|
||||
JsonFileManager jsonFileManager = new JsonFileManager(new File(destDir));
|
||||
jsonFileManager.writeToDisc(json, "bot-script");
|
||||
JsonFileManager.shutDownAllInstances();
|
||||
log.info("Saved {}/bot-script.json", destDir);
|
||||
log.info("bot-script.json contents\n{}", json);
|
||||
}
|
||||
|
||||
// Makes a formatter with a given overall row width of 120 and column separator width of 2.
|
||||
private static class HelpFormatter extends BuiltinHelpFormatter {
|
||||
public HelpFormatter() {
|
||||
super(120, 2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.apitest.scenario.bot.shutdown;
|
||||
|
||||
import bisq.common.BisqException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ManualBotShutdownException extends BisqException {
|
||||
public ManualBotShutdownException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ManualBotShutdownException(String format, Object... args) {
|
||||
super(format, args);
|
||||
}
|
||||
|
||||
public ManualBotShutdownException(Throwable cause, String format, Object... args) {
|
||||
super(cause, format, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package bisq.apitest.scenario.bot.shutdown;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.common.file.FileUtil.deleteFileIfExists;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
@Slf4j
|
||||
public class ManualShutdown {
|
||||
|
||||
public static final String SHUTDOWN_FILENAME = "/tmp/bottest-shutdown";
|
||||
|
||||
private static final AtomicBoolean SHUTDOWN_CALLED = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Looks for a /tmp/bottest-shutdown file and throws a BotShutdownException if found.
|
||||
*
|
||||
* Running '$ touch /tmp/bottest-shutdown' could be used to trigger a scaffold teardown.
|
||||
*
|
||||
* This is much easier than manually shutdown down bisq apps & bitcoind.
|
||||
*/
|
||||
public static void startShutdownTimer() {
|
||||
deleteStaleShutdownFile();
|
||||
|
||||
UserThread.runPeriodically(() -> {
|
||||
File shutdownFile = new File(SHUTDOWN_FILENAME);
|
||||
if (shutdownFile.exists()) {
|
||||
log.warn("Caught manual shutdown signal: /tmp/bottest-shutdown file exists.");
|
||||
try {
|
||||
deleteFileIfExists(shutdownFile);
|
||||
} catch (IOException ex) {
|
||||
log.error("", ex);
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
SHUTDOWN_CALLED.set(true);
|
||||
}
|
||||
}, 2000, MILLISECONDS);
|
||||
}
|
||||
|
||||
public static boolean isShutdownCalled() {
|
||||
return SHUTDOWN_CALLED.get();
|
||||
}
|
||||
|
||||
public static void checkIfShutdownCalled(String warning) throws ManualBotShutdownException {
|
||||
if (isShutdownCalled())
|
||||
throw new ManualBotShutdownException(warning);
|
||||
}
|
||||
|
||||
private static void deleteStaleShutdownFile() {
|
||||
try {
|
||||
deleteFileIfExists(new File(SHUTDOWN_FILENAME));
|
||||
} catch (IOException ex) {
|
||||
log.error("", ex);
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,45 +17,7 @@
|
|||
|
||||
package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.CancelOfferRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
import bisq.proto.grpc.CreatePaymentAccountRequest;
|
||||
import bisq.proto.grpc.GetAddressBalanceRequest;
|
||||
import bisq.proto.grpc.GetBalancesRequest;
|
||||
import bisq.proto.grpc.GetFundingAddressesRequest;
|
||||
import bisq.proto.grpc.GetMethodHelpRequest;
|
||||
import bisq.proto.grpc.GetMyOfferRequest;
|
||||
import bisq.proto.grpc.GetMyOffersRequest;
|
||||
import bisq.proto.grpc.GetOfferRequest;
|
||||
import bisq.proto.grpc.GetOffersRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountFormRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||
import bisq.proto.grpc.GetPaymentMethodsRequest;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
import bisq.proto.grpc.GetTransactionRequest;
|
||||
import bisq.proto.grpc.GetTxFeeRateRequest;
|
||||
import bisq.proto.grpc.GetUnusedBsqAddressRequest;
|
||||
import bisq.proto.grpc.GetVersionRequest;
|
||||
import bisq.proto.grpc.KeepFundsRequest;
|
||||
import bisq.proto.grpc.LockWalletRequest;
|
||||
import bisq.proto.grpc.MarketPriceRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||
import bisq.proto.grpc.SendBsqRequest;
|
||||
import bisq.proto.grpc.SendBtcRequest;
|
||||
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||
import bisq.proto.grpc.StopRequest;
|
||||
import bisq.proto.grpc.TakeOfferRequest;
|
||||
import bisq.proto.grpc.TxInfo;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
|
@ -87,7 +49,6 @@ import static bisq.cli.opts.OptLabel.OPT_HELP;
|
|||
import static bisq.cli.opts.OptLabel.OPT_HOST;
|
||||
import static bisq.cli.opts.OptLabel.OPT_PASSWORD;
|
||||
import static bisq.cli.opts.OptLabel.OPT_PORT;
|
||||
import static bisq.proto.grpc.HelpGrpc.HelpBlockingStub;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.err;
|
||||
import static java.lang.System.exit;
|
||||
|
@ -122,7 +83,6 @@ import bisq.cli.opts.WithdrawFundsOptionParser;
|
|||
/**
|
||||
* A command-line client for the Bisq gRPC API.
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@Slf4j
|
||||
public class CliMain {
|
||||
|
||||
|
@ -188,50 +148,36 @@ public class CliMain {
|
|||
throw new IllegalArgumentException(format("'%s' is not a supported method", methodName));
|
||||
}
|
||||
|
||||
GrpcStubs grpcStubs = new GrpcStubs(host, port, password);
|
||||
var disputeAgentsService = grpcStubs.disputeAgentsService;
|
||||
var helpService = grpcStubs.helpService;
|
||||
var offersService = grpcStubs.offersService;
|
||||
var paymentAccountsService = grpcStubs.paymentAccountsService;
|
||||
var priceService = grpcStubs.priceService;
|
||||
var shutdownService = grpcStubs.shutdownService;
|
||||
var tradesService = grpcStubs.tradesService;
|
||||
var versionService = grpcStubs.versionService;
|
||||
var walletsService = grpcStubs.walletsService;
|
||||
|
||||
GrpcClient client = new GrpcClient(host, port, password);
|
||||
try {
|
||||
switch (method) {
|
||||
case getversion: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var request = GetVersionRequest.newBuilder().build();
|
||||
var version = versionService.getVersion(request).getVersion();
|
||||
var version = client.getVersion();
|
||||
out.println(version);
|
||||
return;
|
||||
}
|
||||
case getbalance: {
|
||||
var opts = new GetBalanceOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var currencyCode = opts.getCurrencyCode();
|
||||
var request = GetBalancesRequest.newBuilder()
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
var reply = walletsService.getBalances(request);
|
||||
var balances = client.getBalances(currencyCode);
|
||||
switch (currencyCode.toUpperCase()) {
|
||||
case "BSQ":
|
||||
out.println(formatBsqBalanceInfoTbl(reply.getBalances().getBsq()));
|
||||
out.println(formatBsqBalanceInfoTbl(balances.getBsq()));
|
||||
break;
|
||||
case "BTC":
|
||||
out.println(formatBtcBalanceInfoTbl(reply.getBalances().getBtc()));
|
||||
out.println(formatBtcBalanceInfoTbl(balances.getBtc()));
|
||||
break;
|
||||
case "":
|
||||
default:
|
||||
out.println(formatBalancesTbls(reply.getBalances()));
|
||||
out.println(formatBalancesTbls(balances));
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
@ -239,54 +185,47 @@ public class CliMain {
|
|||
case getaddressbalance: {
|
||||
var opts = new GetAddressBalanceOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var address = opts.getAddress();
|
||||
var request = GetAddressBalanceRequest.newBuilder()
|
||||
.setAddress(address).build();
|
||||
var reply = walletsService.getAddressBalance(request);
|
||||
out.println(formatAddressBalanceTbl(singletonList(reply.getAddressBalanceInfo())));
|
||||
var addressBalance = client.getAddressBalance(address);
|
||||
out.println(formatAddressBalanceTbl(singletonList(addressBalance)));
|
||||
return;
|
||||
}
|
||||
case getbtcprice: {
|
||||
var opts = new GetBTCMarketPriceOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var currencyCode = opts.getCurrencyCode();
|
||||
var request = MarketPriceRequest.newBuilder()
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
var reply = priceService.getMarketPrice(request);
|
||||
out.println(formatMarketPrice(reply.getPrice()));
|
||||
var price = client.getBtcPrice(currencyCode);
|
||||
out.println(formatMarketPrice(price));
|
||||
return;
|
||||
}
|
||||
case getfundingaddresses: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||
var reply = walletsService.getFundingAddresses(request);
|
||||
out.println(formatAddressBalanceTbl(reply.getAddressBalanceInfoList()));
|
||||
var fundingAddresses = client.getFundingAddresses();
|
||||
out.println(formatAddressBalanceTbl(fundingAddresses));
|
||||
return;
|
||||
}
|
||||
case getunusedbsqaddress: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var request = GetUnusedBsqAddressRequest.newBuilder().build();
|
||||
var reply = walletsService.getUnusedBsqAddress(request);
|
||||
out.println(reply.getAddress());
|
||||
var address = client.getUnusedBsqAddress();
|
||||
out.println(address);
|
||||
return;
|
||||
}
|
||||
case sendbsq: {
|
||||
var opts = new SendBsqOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var address = opts.getAddress();
|
||||
|
@ -297,13 +236,7 @@ public class CliMain {
|
|||
if (txFeeRate.isEmpty())
|
||||
verifyStringIsValidLong(txFeeRate);
|
||||
|
||||
var request = SendBsqRequest.newBuilder()
|
||||
.setAddress(address)
|
||||
.setAmount(amount)
|
||||
.setTxFeeRate(txFeeRate)
|
||||
.build();
|
||||
var reply = walletsService.sendBsq(request);
|
||||
TxInfo txInfo = reply.getTxInfo();
|
||||
var txInfo = client.sendBsq(address, amount, txFeeRate);
|
||||
out.printf("%s bsq sent to %s in tx %s%n",
|
||||
amount,
|
||||
address,
|
||||
|
@ -313,7 +246,7 @@ public class CliMain {
|
|||
case sendbtc: {
|
||||
var opts = new SendBtcOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var address = opts.getAddress();
|
||||
|
@ -325,14 +258,8 @@ public class CliMain {
|
|||
verifyStringIsValidLong(txFeeRate);
|
||||
|
||||
var memo = opts.getMemo();
|
||||
var request = SendBtcRequest.newBuilder()
|
||||
.setAddress(address)
|
||||
.setAmount(amount)
|
||||
.setTxFeeRate(txFeeRate)
|
||||
.setMemo(memo)
|
||||
.build();
|
||||
var reply = walletsService.sendBtc(request);
|
||||
TxInfo txInfo = reply.getTxInfo();
|
||||
|
||||
var txInfo = client.sendBtc(address, amount, txFeeRate, memo);
|
||||
out.printf("%s btc sent to %s in tx %s%n",
|
||||
amount,
|
||||
address,
|
||||
|
@ -341,56 +268,47 @@ public class CliMain {
|
|||
}
|
||||
case gettxfeerate: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var request = GetTxFeeRateRequest.newBuilder().build();
|
||||
var reply = walletsService.getTxFeeRate(request);
|
||||
out.println(formatTxFeeRateInfo(reply.getTxFeeRateInfo()));
|
||||
var txFeeRate = client.getTxFeeRate();
|
||||
out.println(formatTxFeeRateInfo(txFeeRate));
|
||||
return;
|
||||
}
|
||||
case settxfeerate: {
|
||||
var opts = new SetTxFeeRateOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var txFeeRate = toLong(opts.getFeeRate());
|
||||
var request = SetTxFeeRatePreferenceRequest.newBuilder()
|
||||
.setTxFeeRatePreference(txFeeRate)
|
||||
.build();
|
||||
var reply = walletsService.setTxFeeRatePreference(request);
|
||||
out.println(formatTxFeeRateInfo(reply.getTxFeeRateInfo()));
|
||||
var txFeeRate = client.setTxFeeRate(toLong(opts.getFeeRate()));
|
||||
out.println(formatTxFeeRateInfo(txFeeRate));
|
||||
return;
|
||||
}
|
||||
case unsettxfeerate: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var request = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
|
||||
var reply = walletsService.unsetTxFeeRatePreference(request);
|
||||
out.println(formatTxFeeRateInfo(reply.getTxFeeRateInfo()));
|
||||
var txFeeRate = client.unsetTxFeeRate();
|
||||
out.println(formatTxFeeRateInfo(txFeeRate));
|
||||
return;
|
||||
}
|
||||
case gettransaction: {
|
||||
var opts = new GetTransactionOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var txId = opts.getTxId();
|
||||
var request = GetTransactionRequest.newBuilder()
|
||||
.setTxId(txId)
|
||||
.build();
|
||||
var reply = walletsService.getTransaction(request);
|
||||
out.println(TransactionFormat.format(reply.getTxInfo()));
|
||||
var tx = client.getTransaction(txId);
|
||||
out.println(TransactionFormat.format(tx));
|
||||
return;
|
||||
}
|
||||
case createoffer: {
|
||||
var opts = new CreateOfferOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var paymentAcctId = opts.getPaymentAccountId();
|
||||
|
@ -403,232 +321,178 @@ public class CliMain {
|
|||
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
|
||||
var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit());
|
||||
var makerFeeCurrencyCode = opts.getMakerFeeCurrencyCode();
|
||||
var request = CreateOfferRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.setAmount(amount)
|
||||
.setMinAmount(minAmount)
|
||||
.setUseMarketBasedPrice(useMarketBasedPrice)
|
||||
.setPrice(fixedPrice)
|
||||
.setMarketPriceMargin(marketPriceMargin.doubleValue())
|
||||
.setBuyerSecurityDeposit(securityDeposit)
|
||||
.setPaymentAccountId(paymentAcctId)
|
||||
.setMakerFeeCurrencyCode(makerFeeCurrencyCode)
|
||||
.build();
|
||||
var reply = offersService.createOffer(request);
|
||||
out.println(formatOfferTable(singletonList(reply.getOffer()), currencyCode));
|
||||
var offer = client.createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
useMarketBasedPrice,
|
||||
fixedPrice,
|
||||
marketPriceMargin.doubleValue(),
|
||||
securityDeposit,
|
||||
paymentAcctId,
|
||||
makerFeeCurrencyCode);
|
||||
out.println(formatOfferTable(singletonList(offer), currencyCode));
|
||||
return;
|
||||
}
|
||||
case canceloffer: {
|
||||
var opts = new CancelOfferOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var offerId = opts.getOfferId();
|
||||
var request = CancelOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
offersService.cancelOffer(request);
|
||||
client.cancelOffer(offerId);
|
||||
out.println("offer canceled and removed from offer book");
|
||||
return;
|
||||
}
|
||||
case getoffer: {
|
||||
var opts = new GetOfferOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var offerId = opts.getOfferId();
|
||||
var request = GetOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
var reply = offersService.getOffer(request);
|
||||
out.println(formatOfferTable(singletonList(reply.getOffer()),
|
||||
reply.getOffer().getCounterCurrencyCode()));
|
||||
var offer = client.getOffer(offerId);
|
||||
out.println(formatOfferTable(singletonList(offer), offer.getCounterCurrencyCode()));
|
||||
return;
|
||||
}
|
||||
case getmyoffer: {
|
||||
var opts = new GetOfferOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var offerId = opts.getOfferId();
|
||||
var request = GetMyOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
var reply = offersService.getMyOffer(request);
|
||||
out.println(formatOfferTable(singletonList(reply.getOffer()),
|
||||
reply.getOffer().getCounterCurrencyCode()));
|
||||
var offer = client.getMyOffer(offerId);
|
||||
out.println(formatOfferTable(singletonList(offer), offer.getCounterCurrencyCode()));
|
||||
return;
|
||||
}
|
||||
case getoffers: {
|
||||
var opts = new GetOffersOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var direction = opts.getDirection();
|
||||
var currencyCode = opts.getCurrencyCode();
|
||||
var request = GetOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
var reply = offersService.getOffers(request);
|
||||
|
||||
List<OfferInfo> offers = reply.getOffersList();
|
||||
List<OfferInfo> offers = client.getOffers(direction, currencyCode);
|
||||
if (offers.isEmpty())
|
||||
out.printf("no %s %s offers found%n", direction, currencyCode);
|
||||
else
|
||||
out.println(formatOfferTable(reply.getOffersList(), currencyCode));
|
||||
out.println(formatOfferTable(offers, currencyCode));
|
||||
|
||||
return;
|
||||
}
|
||||
case getmyoffers: {
|
||||
var opts = new GetOffersOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var direction = opts.getDirection();
|
||||
var currencyCode = opts.getCurrencyCode();
|
||||
var request = GetMyOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
var reply = offersService.getMyOffers(request);
|
||||
|
||||
List<OfferInfo> offers = reply.getOffersList();
|
||||
List<OfferInfo> offers = client.getMyOffers(direction, currencyCode);
|
||||
if (offers.isEmpty())
|
||||
out.printf("no %s %s offers found%n", direction, currencyCode);
|
||||
else
|
||||
out.println(formatOfferTable(reply.getOffersList(), currencyCode));
|
||||
out.println(formatOfferTable(offers, currencyCode));
|
||||
|
||||
return;
|
||||
}
|
||||
case takeoffer: {
|
||||
var opts = new TakeOfferOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var offerId = opts.getOfferId();
|
||||
var paymentAccountId = opts.getPaymentAccountId();
|
||||
var takerFeeCurrencyCode = opts.getTakerFeeCurrencyCode();
|
||||
var request = TakeOfferRequest.newBuilder()
|
||||
.setOfferId(offerId)
|
||||
.setPaymentAccountId(paymentAccountId)
|
||||
.setTakerFeeCurrencyCode(takerFeeCurrencyCode)
|
||||
.build();
|
||||
var reply = tradesService.takeOffer(request);
|
||||
out.printf("trade %s successfully taken%n", reply.getTrade().getTradeId());
|
||||
var trade = client.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
out.printf("trade %s successfully taken%n", trade.getTradeId());
|
||||
return;
|
||||
}
|
||||
case gettrade: {
|
||||
// TODO make short-id a valid argument?
|
||||
var opts = new GetTradeOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var tradeId = opts.getTradeId();
|
||||
var showContract = opts.getShowContract();
|
||||
var request = GetTradeRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
var reply = tradesService.getTrade(request);
|
||||
|
||||
var trade = client.getTrade(tradeId);
|
||||
if (showContract)
|
||||
out.println(reply.getTrade().getContractAsJson());
|
||||
out.println(trade.getContractAsJson());
|
||||
else
|
||||
out.println(TradeFormat.format(reply.getTrade()));
|
||||
out.println(TradeFormat.format(trade));
|
||||
|
||||
return;
|
||||
}
|
||||
case confirmpaymentstarted: {
|
||||
var opts = new GetTradeOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var tradeId = opts.getTradeId();
|
||||
var request = ConfirmPaymentStartedRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
tradesService.confirmPaymentStarted(request);
|
||||
client.confirmPaymentStarted(tradeId);
|
||||
out.printf("trade %s payment started message sent%n", tradeId);
|
||||
return;
|
||||
}
|
||||
case confirmpaymentreceived: {
|
||||
var opts = new GetTradeOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var tradeId = opts.getTradeId();
|
||||
var request = ConfirmPaymentReceivedRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
tradesService.confirmPaymentReceived(request);
|
||||
client.confirmPaymentReceived(tradeId);
|
||||
out.printf("trade %s payment received message sent%n", tradeId);
|
||||
return;
|
||||
}
|
||||
case keepfunds: {
|
||||
var opts = new GetTradeOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var tradeId = opts.getTradeId();
|
||||
var request = KeepFundsRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
tradesService.keepFunds(request);
|
||||
client.keepFunds(tradeId);
|
||||
out.printf("funds from trade %s saved in bisq wallet%n", tradeId);
|
||||
return;
|
||||
}
|
||||
case withdrawfunds: {
|
||||
var opts = new WithdrawFundsOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var tradeId = opts.getTradeId();
|
||||
var address = opts.getAddress();
|
||||
// Multi-word memos must be double quoted.
|
||||
var memo = opts.getMemo();
|
||||
var request = WithdrawFundsRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setAddress(address)
|
||||
.setMemo(memo)
|
||||
.build();
|
||||
tradesService.withdrawFunds(request);
|
||||
client.withdrawFunds(tradeId, address, memo);
|
||||
out.printf("trade %s funds sent to btc address %s%n", tradeId, address);
|
||||
return;
|
||||
}
|
||||
case getpaymentmethods: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var request = GetPaymentMethodsRequest.newBuilder().build();
|
||||
var reply = paymentAccountsService.getPaymentMethods(request);
|
||||
reply.getPaymentMethodsList().forEach(p -> out.println(p.getId()));
|
||||
var paymentMethods = client.getPaymentMethods();
|
||||
paymentMethods.forEach(p -> out.println(p.getId()));
|
||||
return;
|
||||
}
|
||||
case getpaymentacctform: {
|
||||
var opts = new GetPaymentAcctFormOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var paymentMethodId = opts.getPaymentMethodId();
|
||||
var request = GetPaymentAccountFormRequest.newBuilder()
|
||||
.setPaymentMethodId(paymentMethodId)
|
||||
.build();
|
||||
String jsonString = paymentAccountsService.getPaymentAccountForm(request)
|
||||
.getPaymentAccountFormJson();
|
||||
String jsonString = client.getPaymentAcctFormAsJson(paymentMethodId);
|
||||
File jsonFile = saveFileToDisk(paymentMethodId.toLowerCase(),
|
||||
".json",
|
||||
jsonString);
|
||||
|
@ -640,7 +504,7 @@ public class CliMain {
|
|||
case createpaymentacct: {
|
||||
var opts = new CreatePaymentAcctOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var paymentAccountForm = opts.getPaymentAcctForm();
|
||||
|
@ -651,24 +515,17 @@ public class CliMain {
|
|||
throw new IllegalStateException(
|
||||
format("could not read %s", paymentAccountForm.toString()));
|
||||
}
|
||||
|
||||
var request = CreatePaymentAccountRequest.newBuilder()
|
||||
.setPaymentAccountForm(jsonString)
|
||||
.build();
|
||||
var reply = paymentAccountsService.createPaymentAccount(request);
|
||||
var paymentAccount = client.createPaymentAccount(jsonString);
|
||||
out.println("payment account saved");
|
||||
out.println(formatPaymentAcctTbl(singletonList(reply.getPaymentAccount())));
|
||||
out.println(formatPaymentAcctTbl(singletonList(paymentAccount)));
|
||||
return;
|
||||
}
|
||||
case getpaymentaccts: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var request = GetPaymentAccountsRequest.newBuilder().build();
|
||||
var reply = paymentAccountsService.getPaymentAccounts(request);
|
||||
|
||||
List<PaymentAccount> paymentAccounts = reply.getPaymentAccountsList();
|
||||
var paymentAccounts = client.getPaymentAccounts();
|
||||
if (paymentAccounts.size() > 0)
|
||||
out.println(formatPaymentAcctTbl(paymentAccounts));
|
||||
else
|
||||
|
@ -678,78 +535,66 @@ public class CliMain {
|
|||
}
|
||||
case lockwallet: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var request = LockWalletRequest.newBuilder().build();
|
||||
walletsService.lockWallet(request);
|
||||
client.lockWallet();
|
||||
out.println("wallet locked");
|
||||
return;
|
||||
}
|
||||
case unlockwallet: {
|
||||
var opts = new UnlockWalletOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var walletPassword = opts.getPassword();
|
||||
var timeout = opts.getUnlockTimeout();
|
||||
var request = UnlockWalletRequest.newBuilder()
|
||||
.setPassword(walletPassword)
|
||||
.setTimeout(timeout).build();
|
||||
walletsService.unlockWallet(request);
|
||||
client.unlockWallet(walletPassword, timeout);
|
||||
out.println("wallet unlocked");
|
||||
return;
|
||||
}
|
||||
case removewalletpassword: {
|
||||
var opts = new RemoveWalletPasswordOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var walletPassword = opts.getPassword();
|
||||
var request = RemoveWalletPasswordRequest.newBuilder()
|
||||
.setPassword(walletPassword).build();
|
||||
walletsService.removeWalletPassword(request);
|
||||
client.removeWalletPassword(walletPassword);
|
||||
out.println("wallet decrypted");
|
||||
return;
|
||||
}
|
||||
case setwalletpassword: {
|
||||
var opts = new SetWalletPasswordOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var walletPassword = opts.getPassword();
|
||||
var newWalletPassword = opts.getNewPassword();
|
||||
var requestBuilder = SetWalletPasswordRequest.newBuilder()
|
||||
.setPassword(walletPassword)
|
||||
.setNewPassword(newWalletPassword);
|
||||
walletsService.setWalletPassword(requestBuilder.build());
|
||||
client.setWalletPassword(walletPassword, newWalletPassword);
|
||||
out.println("wallet encrypted" + (!newWalletPassword.isEmpty() ? " with new password" : ""));
|
||||
return;
|
||||
}
|
||||
case registerdisputeagent: {
|
||||
var opts = new RegisterDisputeAgentOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var disputeAgentType = opts.getDisputeAgentType();
|
||||
var registrationKey = opts.getRegistrationKey();
|
||||
var requestBuilder = RegisterDisputeAgentRequest.newBuilder()
|
||||
.setDisputeAgentType(disputeAgentType).setRegistrationKey(registrationKey);
|
||||
disputeAgentsService.registerDisputeAgent(requestBuilder.build());
|
||||
client.registerDisputeAgent(disputeAgentType, registrationKey);
|
||||
out.println(disputeAgentType + " registered");
|
||||
return;
|
||||
}
|
||||
case stop: {
|
||||
if (new SimpleMethodOptionParser(args).parse().isForHelp()) {
|
||||
out.println(getMethodHelp(helpService, method));
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var request = StopRequest.newBuilder().build();
|
||||
shutdownService.stop(request);
|
||||
client.stopServer();
|
||||
out.println("server shutdown signal received");
|
||||
return;
|
||||
}
|
||||
|
@ -914,10 +759,4 @@ public class CliMain {
|
|||
ex.printStackTrace(stream);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getMethodHelp(HelpBlockingStub helpService, Method method) {
|
||||
var request = GetMethodHelpRequest.newBuilder().setMethodName(method.name()).build();
|
||||
var reply = helpService.getMethodHelp(request);
|
||||
return reply.getMethodHelp();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,37 +65,37 @@ public class CurrencyFormat {
|
|||
formatFeeSatoshis(txFeeRateInfo.getFeeServiceRate()));
|
||||
}
|
||||
|
||||
static String formatAmountRange(long minAmount, long amount) {
|
||||
public static String formatAmountRange(long minAmount, long amount) {
|
||||
return minAmount != amount
|
||||
? formatSatoshis(minAmount) + " - " + formatSatoshis(amount)
|
||||
: formatSatoshis(amount);
|
||||
}
|
||||
|
||||
static String formatVolumeRange(long minVolume, long volume) {
|
||||
public static String formatVolumeRange(long minVolume, long volume) {
|
||||
return minVolume != volume
|
||||
? formatOfferVolume(minVolume) + " - " + formatOfferVolume(volume)
|
||||
: formatOfferVolume(volume);
|
||||
}
|
||||
|
||||
static String formatMarketPrice(double price) {
|
||||
public static String formatMarketPrice(double price) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||
return NUMBER_FORMAT.format(price);
|
||||
}
|
||||
|
||||
static String formatOfferPrice(long price) {
|
||||
public 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) {
|
||||
public static String formatOfferVolume(long volume) {
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(0);
|
||||
NUMBER_FORMAT.setRoundingMode(RoundingMode.UNNECESSARY);
|
||||
return NUMBER_FORMAT.format((double) volume / 10000);
|
||||
}
|
||||
|
||||
static long toSatoshis(String btc) {
|
||||
public static long toSatoshis(String btc) {
|
||||
if (btc.startsWith("-"))
|
||||
throw new IllegalArgumentException(format("'%s' is not a positive number", btc));
|
||||
|
||||
|
@ -106,7 +106,7 @@ public class CurrencyFormat {
|
|||
}
|
||||
}
|
||||
|
||||
static double toSecurityDepositAsPct(String securityDepositInput) {
|
||||
public static double toSecurityDepositAsPct(String securityDepositInput) {
|
||||
try {
|
||||
return new BigDecimal(securityDepositInput)
|
||||
.multiply(SECURITY_DEPOSIT_MULTIPLICAND).doubleValue();
|
||||
|
@ -116,7 +116,7 @@ public class CurrencyFormat {
|
|||
}
|
||||
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
private static String formatFeeSatoshis(long sats) {
|
||||
public static String formatFeeSatoshis(long sats) {
|
||||
return BTC_TX_FEE_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
|
||||
}
|
||||
}
|
||||
|
|
424
cli/src/main/java/bisq/cli/GrpcClient.java
Normal file
424
cli/src/main/java/bisq/cli/GrpcClient.java
Normal file
|
@ -0,0 +1,424 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.AddressBalanceInfo;
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.BsqBalanceInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.CancelOfferRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
import bisq.proto.grpc.CreatePaymentAccountRequest;
|
||||
import bisq.proto.grpc.GetAddressBalanceRequest;
|
||||
import bisq.proto.grpc.GetBalancesRequest;
|
||||
import bisq.proto.grpc.GetFundingAddressesRequest;
|
||||
import bisq.proto.grpc.GetMethodHelpRequest;
|
||||
import bisq.proto.grpc.GetMyOfferRequest;
|
||||
import bisq.proto.grpc.GetMyOffersRequest;
|
||||
import bisq.proto.grpc.GetOfferRequest;
|
||||
import bisq.proto.grpc.GetOffersRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountFormRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||
import bisq.proto.grpc.GetPaymentMethodsRequest;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
import bisq.proto.grpc.GetTransactionRequest;
|
||||
import bisq.proto.grpc.GetTxFeeRateRequest;
|
||||
import bisq.proto.grpc.GetUnusedBsqAddressRequest;
|
||||
import bisq.proto.grpc.GetVersionRequest;
|
||||
import bisq.proto.grpc.KeepFundsRequest;
|
||||
import bisq.proto.grpc.LockWalletRequest;
|
||||
import bisq.proto.grpc.MarketPriceRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||
import bisq.proto.grpc.SendBsqRequest;
|
||||
import bisq.proto.grpc.SendBtcRequest;
|
||||
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||
import bisq.proto.grpc.StopRequest;
|
||||
import bisq.proto.grpc.TakeOfferRequest;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
import bisq.proto.grpc.TxFeeRateInfo;
|
||||
import bisq.proto.grpc.TxInfo;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
import protobuf.PaymentMethod;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public final class GrpcClient {
|
||||
|
||||
private final GrpcStubs grpcStubs;
|
||||
|
||||
public GrpcClient(String apiHost, int apiPort, String apiPassword) {
|
||||
this.grpcStubs = new GrpcStubs(apiHost, apiPort, apiPassword);
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
var request = GetVersionRequest.newBuilder().build();
|
||||
return grpcStubs.versionService.getVersion(request).getVersion();
|
||||
}
|
||||
|
||||
public BalancesInfo getBalances() {
|
||||
return getBalances("");
|
||||
}
|
||||
|
||||
public BsqBalanceInfo getBsqBalances() {
|
||||
return getBalances("BSQ").getBsq();
|
||||
}
|
||||
|
||||
public BtcBalanceInfo getBtcBalances() {
|
||||
return getBalances("BTC").getBtc();
|
||||
}
|
||||
|
||||
public BalancesInfo getBalances(String currencyCode) {
|
||||
var request = GetBalancesRequest.newBuilder()
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.walletsService.getBalances(request).getBalances();
|
||||
}
|
||||
|
||||
public AddressBalanceInfo getAddressBalance(String address) {
|
||||
var request = GetAddressBalanceRequest.newBuilder()
|
||||
.setAddress(address).build();
|
||||
return grpcStubs.walletsService.getAddressBalance(request).getAddressBalanceInfo();
|
||||
}
|
||||
|
||||
public double getBtcPrice(String currencyCode) {
|
||||
var request = MarketPriceRequest.newBuilder()
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.priceService.getMarketPrice(request).getPrice();
|
||||
}
|
||||
|
||||
public List<AddressBalanceInfo> getFundingAddresses() {
|
||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.getFundingAddresses(request).getAddressBalanceInfoList();
|
||||
}
|
||||
|
||||
public String getUnusedBsqAddress() {
|
||||
var request = GetUnusedBsqAddressRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.getUnusedBsqAddress(request).getAddress();
|
||||
}
|
||||
|
||||
public String getUnusedBtcAddress() {
|
||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
return grpcStubs.walletsService.getFundingAddresses(request)
|
||||
.getAddressBalanceInfoList()
|
||||
.stream()
|
||||
.filter(a -> a.getBalance() == 0 && a.getNumConfirmations() == 0)
|
||||
.findFirst()
|
||||
.get()
|
||||
.getAddress();
|
||||
}
|
||||
|
||||
public TxInfo sendBsq(String address, String amount, String txFeeRate) {
|
||||
var request = SendBsqRequest.newBuilder()
|
||||
.setAddress(address)
|
||||
.setAmount(amount)
|
||||
.setTxFeeRate(txFeeRate)
|
||||
.build();
|
||||
return grpcStubs.walletsService.sendBsq(request).getTxInfo();
|
||||
}
|
||||
|
||||
public TxInfo sendBtc(String address, String amount, String txFeeRate, String memo) {
|
||||
var request = SendBtcRequest.newBuilder()
|
||||
.setAddress(address)
|
||||
.setAmount(amount)
|
||||
.setTxFeeRate(txFeeRate)
|
||||
.setMemo(memo)
|
||||
.build();
|
||||
return grpcStubs.walletsService.sendBtc(request).getTxInfo();
|
||||
}
|
||||
|
||||
public TxFeeRateInfo getTxFeeRate() {
|
||||
var request = GetTxFeeRateRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.getTxFeeRate(request).getTxFeeRateInfo();
|
||||
}
|
||||
|
||||
public TxFeeRateInfo setTxFeeRate(long txFeeRate) {
|
||||
var request = SetTxFeeRatePreferenceRequest.newBuilder()
|
||||
.setTxFeeRatePreference(txFeeRate)
|
||||
.build();
|
||||
return grpcStubs.walletsService.setTxFeeRatePreference(request).getTxFeeRateInfo();
|
||||
}
|
||||
|
||||
public TxFeeRateInfo unsetTxFeeRate() {
|
||||
var request = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.unsetTxFeeRatePreference(request).getTxFeeRateInfo();
|
||||
}
|
||||
|
||||
public TxInfo getTransaction(String txId) {
|
||||
var request = GetTransactionRequest.newBuilder()
|
||||
.setTxId(txId)
|
||||
.build();
|
||||
return grpcStubs.walletsService.getTransaction(request).getTxInfo();
|
||||
}
|
||||
|
||||
public OfferInfo createFixedPricedOffer(String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
long minAmount,
|
||||
String fixedPrice,
|
||||
double securityDeposit,
|
||||
String paymentAcctId,
|
||||
String makerFeeCurrencyCode) {
|
||||
return createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
false,
|
||||
fixedPrice,
|
||||
0.00,
|
||||
securityDeposit,
|
||||
paymentAcctId,
|
||||
makerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
public OfferInfo createMarketBasedPricedOffer(String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
long minAmount,
|
||||
double marketPriceMargin,
|
||||
double securityDeposit,
|
||||
String paymentAcctId,
|
||||
String makerFeeCurrencyCode) {
|
||||
return createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
true,
|
||||
"0",
|
||||
marketPriceMargin,
|
||||
securityDeposit,
|
||||
paymentAcctId,
|
||||
makerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
// TODO make private, move to bottom of class
|
||||
public OfferInfo createOffer(String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
long minAmount,
|
||||
boolean useMarketBasedPrice,
|
||||
String fixedPrice,
|
||||
double marketPriceMargin,
|
||||
double securityDeposit,
|
||||
String paymentAcctId,
|
||||
String makerFeeCurrencyCode) {
|
||||
var request = CreateOfferRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.setAmount(amount)
|
||||
.setMinAmount(minAmount)
|
||||
.setUseMarketBasedPrice(useMarketBasedPrice)
|
||||
.setPrice(fixedPrice)
|
||||
.setMarketPriceMargin(marketPriceMargin)
|
||||
.setBuyerSecurityDeposit(securityDeposit)
|
||||
.setPaymentAccountId(paymentAcctId)
|
||||
.setMakerFeeCurrencyCode(makerFeeCurrencyCode)
|
||||
.build();
|
||||
return grpcStubs.offersService.createOffer(request).getOffer();
|
||||
}
|
||||
|
||||
public void cancelOffer(String offerId) {
|
||||
var request = CancelOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
grpcStubs.offersService.cancelOffer(request);
|
||||
}
|
||||
|
||||
public OfferInfo getOffer(String offerId) {
|
||||
var request = GetOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
return grpcStubs.offersService.getOffer(request).getOffer();
|
||||
}
|
||||
|
||||
public OfferInfo getMyOffer(String offerId) {
|
||||
var request = GetMyOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
return grpcStubs.offersService.getMyOffer(request).getOffer();
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
||||
var request = GetOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.offersService.getOffers(request).getOffersList();
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffersSortedByDate(String direction, String currencyCode) {
|
||||
var offers = getOffers(direction, currencyCode);
|
||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||
var request = GetMyOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.offersService.getMyOffers(request).getOffersList();
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffersSortedByDate(String direction, String currencyCode) {
|
||||
var offers = getMyOffers(direction, currencyCode);
|
||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
||||
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
||||
}
|
||||
|
||||
// TODO move to bottom of class
|
||||
private List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||
return offerInfoList.stream()
|
||||
.sorted(comparing(OfferInfo::getDate))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||
var request = TakeOfferRequest.newBuilder()
|
||||
.setOfferId(offerId)
|
||||
.setPaymentAccountId(paymentAccountId)
|
||||
.setTakerFeeCurrencyCode(takerFeeCurrencyCode)
|
||||
.build();
|
||||
return grpcStubs.tradesService.takeOffer(request).getTrade();
|
||||
}
|
||||
|
||||
public TradeInfo getTrade(String tradeId) {
|
||||
var request = GetTradeRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
return grpcStubs.tradesService.getTrade(request).getTrade();
|
||||
}
|
||||
|
||||
public void confirmPaymentStarted(String tradeId) {
|
||||
var request = ConfirmPaymentStartedRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
grpcStubs.tradesService.confirmPaymentStarted(request);
|
||||
}
|
||||
|
||||
public void confirmPaymentReceived(String tradeId) {
|
||||
var request = ConfirmPaymentReceivedRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
grpcStubs.tradesService.confirmPaymentReceived(request);
|
||||
}
|
||||
|
||||
public void keepFunds(String tradeId) {
|
||||
var request = KeepFundsRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
grpcStubs.tradesService.keepFunds(request);
|
||||
}
|
||||
|
||||
public void withdrawFunds(String tradeId, String address, String memo) {
|
||||
var request = WithdrawFundsRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setAddress(address)
|
||||
.setMemo(memo)
|
||||
.build();
|
||||
grpcStubs.tradesService.withdrawFunds(request);
|
||||
}
|
||||
|
||||
public List<PaymentMethod> getPaymentMethods() {
|
||||
var request = GetPaymentMethodsRequest.newBuilder().build();
|
||||
return grpcStubs.paymentAccountsService.getPaymentMethods(request).getPaymentMethodsList();
|
||||
}
|
||||
|
||||
public String getPaymentAcctFormAsJson(String paymentMethodId) {
|
||||
var request = GetPaymentAccountFormRequest.newBuilder()
|
||||
.setPaymentMethodId(paymentMethodId)
|
||||
.build();
|
||||
return grpcStubs.paymentAccountsService.getPaymentAccountForm(request).getPaymentAccountFormJson();
|
||||
}
|
||||
|
||||
public PaymentAccount createPaymentAccount(String json) {
|
||||
var request = CreatePaymentAccountRequest.newBuilder()
|
||||
.setPaymentAccountForm(json)
|
||||
.build();
|
||||
return grpcStubs.paymentAccountsService.createPaymentAccount(request).getPaymentAccount();
|
||||
}
|
||||
|
||||
public List<PaymentAccount> getPaymentAccounts() {
|
||||
var request = GetPaymentAccountsRequest.newBuilder().build();
|
||||
return grpcStubs.paymentAccountsService.getPaymentAccounts(request).getPaymentAccountsList();
|
||||
}
|
||||
|
||||
public void lockWallet() {
|
||||
var request = LockWalletRequest.newBuilder().build();
|
||||
grpcStubs.walletsService.lockWallet(request);
|
||||
}
|
||||
|
||||
public void unlockWallet(String walletPassword, long timeout) {
|
||||
var request = UnlockWalletRequest.newBuilder()
|
||||
.setPassword(walletPassword)
|
||||
.setTimeout(timeout).build();
|
||||
grpcStubs.walletsService.unlockWallet(request);
|
||||
}
|
||||
|
||||
public void removeWalletPassword(String walletPassword) {
|
||||
var request = RemoveWalletPasswordRequest.newBuilder()
|
||||
.setPassword(walletPassword).build();
|
||||
grpcStubs.walletsService.removeWalletPassword(request);
|
||||
}
|
||||
|
||||
public void setWalletPassword(String walletPassword) {
|
||||
var request = SetWalletPasswordRequest.newBuilder()
|
||||
.setPassword(walletPassword).build();
|
||||
grpcStubs.walletsService.setWalletPassword(request);
|
||||
}
|
||||
|
||||
public void setWalletPassword(String oldWalletPassword, String newWalletPassword) {
|
||||
var request = SetWalletPasswordRequest.newBuilder()
|
||||
.setPassword(oldWalletPassword)
|
||||
.setNewPassword(newWalletPassword).build();
|
||||
grpcStubs.walletsService.setWalletPassword(request);
|
||||
}
|
||||
|
||||
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
|
||||
var request = RegisterDisputeAgentRequest.newBuilder()
|
||||
.setDisputeAgentType(disputeAgentType).setRegistrationKey(registrationKey).build();
|
||||
grpcStubs.disputeAgentsService.registerDisputeAgent(request);
|
||||
}
|
||||
|
||||
public void stopServer() {
|
||||
var request = StopRequest.newBuilder().build();
|
||||
grpcStubs.shutdownService.stop(request);
|
||||
}
|
||||
|
||||
public String getMethodHelp(Method method) {
|
||||
var request = GetMethodHelpRequest.newBuilder().setMethodName(method.name()).build();
|
||||
return grpcStubs.helpService.getMethodHelp(request).getMethodHelp();
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ import io.grpc.ManagedChannelBuilder;
|
|||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
public class GrpcStubs {
|
||||
public final class GrpcStubs {
|
||||
|
||||
public final DisputeAgentsGrpc.DisputeAgentsBlockingStub disputeAgentsService;
|
||||
public final HelpGrpc.HelpBlockingStub helpService;
|
||||
|
|
|
@ -111,7 +111,7 @@ public class TableFormat {
|
|||
formatSatoshis(btcBalanceInfo.getLockedBalance()));
|
||||
}
|
||||
|
||||
static String formatOfferTable(List<OfferInfo> offerInfo, String fiatCurrency) {
|
||||
public static String formatOfferTable(List<OfferInfo> offerInfo, String fiatCurrency) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
int paymentMethodColWidth = getLengthOfLongestColumn(
|
||||
COL_HEADER_PAYMENT_METHOD.length(),
|
||||
|
@ -147,7 +147,7 @@ public class TableFormat {
|
|||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
static String formatPaymentAcctTbl(List<PaymentAccount> paymentAccounts) {
|
||||
public static String formatPaymentAcctTbl(List<PaymentAccount> paymentAccounts) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
int nameColWidth = getLengthOfLongestColumn(
|
||||
COL_HEADER_NAME.length(),
|
||||
|
|
|
@ -79,6 +79,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.common.config.BaseCurrencyNetwork.BTC_DAO_REGTEST;
|
||||
import static bisq.core.btc.wallet.Restrictions.getMinNonDustOutput;
|
||||
import static bisq.core.util.ParsingUtils.parseToCoin;
|
||||
import static java.lang.String.format;
|
||||
|
@ -311,8 +312,13 @@ class CoreWalletsService {
|
|||
|
||||
void setTxFeeRatePreference(long txFeeRate,
|
||||
ResultHandler resultHandler) {
|
||||
if (txFeeRate <= 0)
|
||||
throw new IllegalStateException("cannot create transactions without fees");
|
||||
long minFeePerVbyte = BTC_DAO_REGTEST.getDefaultMinFeePerVbyte();
|
||||
// TODO Replace line above with line below, after commit
|
||||
// c33ac1b9834fb9f7f14e553d09776f94efc9d13d is merged.
|
||||
// long minFeePerVbyte = feeService.getMinFeePerVByte();
|
||||
if (txFeeRate < minFeePerVbyte)
|
||||
throw new IllegalStateException(
|
||||
format("tx fee rate preference must be >= %d sats/byte", minFeePerVbyte));
|
||||
|
||||
preferences.setUseCustomWithdrawalTxFee(true);
|
||||
Coin satsPerByte = Coin.valueOf(txFeeRate);
|
||||
|
|
|
@ -67,8 +67,8 @@ public class TxFeeRateInfo implements Payload {
|
|||
public String toString() {
|
||||
return "TxFeeRateInfo{" + "\n" +
|
||||
" useCustomTxFeeRate=" + useCustomTxFeeRate + "\n" +
|
||||
", customTxFeeRate=" + customTxFeeRate + "sats/byte" + "\n" +
|
||||
", feeServiceRate=" + feeServiceRate + "sats/byte" + "\n" +
|
||||
", customTxFeeRate=" + customTxFeeRate + " sats/byte" + "\n" +
|
||||
", feeServiceRate=" + feeServiceRate + " sats/byte" + "\n" +
|
||||
", lastFeeServiceRequestTs=" + lastFeeServiceRequestTs + "\n" +
|
||||
'}';
|
||||
}
|
||||
|
|
|
@ -33,8 +33,11 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Used from org.bitcoinj.wallet.DefaultCoinSelector but added selectOutput method and changed static methods to
|
||||
* instance methods.
|
||||
|
@ -49,6 +52,12 @@ public abstract class BisqDefaultCoinSelector implements CoinSelector {
|
|||
|
||||
protected final boolean permitForeignPendingTx;
|
||||
|
||||
// TransactionOutputs to be used as candidates in the select method.
|
||||
// We reset the value to null just after we have applied it inside the select method.
|
||||
@Nullable
|
||||
@Setter
|
||||
protected Set<TransactionOutput> utxoCandidates;
|
||||
|
||||
public CoinSelection select(Coin target, Set<TransactionOutput> candidates) {
|
||||
return select(target, new ArrayList<>(candidates));
|
||||
}
|
||||
|
@ -65,7 +74,16 @@ public abstract class BisqDefaultCoinSelector implements CoinSelector {
|
|||
public CoinSelection select(Coin target, List<TransactionOutput> candidates) {
|
||||
ArrayList<TransactionOutput> selected = new ArrayList<>();
|
||||
// Sort the inputs by age*value so we get the highest "coin days" spent.
|
||||
ArrayList<TransactionOutput> sortedOutputs = new ArrayList<>(candidates);
|
||||
|
||||
ArrayList<TransactionOutput> sortedOutputs;
|
||||
if (utxoCandidates != null) {
|
||||
sortedOutputs = new ArrayList<>(utxoCandidates);
|
||||
// We reuse the selectors. Reset the transactionOutputCandidates field
|
||||
utxoCandidates = null;
|
||||
} else {
|
||||
sortedOutputs = new ArrayList<>(candidates);
|
||||
}
|
||||
|
||||
// If we spend all we don't need to sort
|
||||
if (!target.equals(NetworkParameters.MAX_MONEY))
|
||||
sortOutputs(sortedOutputs);
|
||||
|
@ -120,6 +138,9 @@ public abstract class BisqDefaultCoinSelector implements CoinSelector {
|
|||
|
||||
abstract boolean isTxOutputSpendable(TransactionOutput output);
|
||||
|
||||
// TODO Why it uses coin age and not try to minimize number of inputs as the highest priority?
|
||||
// Asked Oscar and he also don't knows why coin age is used. Should be changed so that min. number of inputs is
|
||||
// target.
|
||||
protected void sortOutputs(ArrayList<TransactionOutput> outputs) {
|
||||
Collections.sort(outputs, (a, b) -> {
|
||||
int depth1 = a.getParentTransactionDepthInBlocks();
|
||||
|
|
|
@ -72,6 +72,8 @@ import java.util.stream.Stream;
|
|||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.BUILDING;
|
||||
|
@ -135,6 +137,8 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
|
||||
this.daoKillSwitch = daoKillSwitch;
|
||||
|
||||
nonBsqCoinSelector.setPreferences(preferences);
|
||||
|
||||
walletsSetup.addSetupCompletedHandler(() -> {
|
||||
wallet = walletsSetup.getBsqWallet();
|
||||
if (wallet != null) {
|
||||
|
@ -313,6 +317,16 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
walletTransactionsChangeListeners.remove(listener);
|
||||
}
|
||||
|
||||
public List<TransactionOutput> getSpendableBsqTransactionOutputs() {
|
||||
return new ArrayList<>(bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
||||
wallet.calculateAllSpendCandidates()).gathered);
|
||||
}
|
||||
|
||||
public List<TransactionOutput> getSpendableNonBsqTransactionOutputs() {
|
||||
return new ArrayList<>(nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY,
|
||||
wallet.calculateAllSpendCandidates()).gathered);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// BSQ TransactionOutputs and Transactions
|
||||
|
@ -511,7 +525,19 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction getPreparedSendBsqTx(String receiverAddress, Coin receiverAmount)
|
||||
throws AddressFormatException, InsufficientBsqException, WalletException, TransactionVerificationException, BsqChangeBelowDustException {
|
||||
throws AddressFormatException, InsufficientBsqException, WalletException,
|
||||
TransactionVerificationException, BsqChangeBelowDustException {
|
||||
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector, false);
|
||||
}
|
||||
|
||||
public Transaction getPreparedSendBsqTx(String receiverAddress,
|
||||
Coin receiverAmount,
|
||||
@Nullable Set<TransactionOutput> utxoCandidates)
|
||||
throws AddressFormatException, InsufficientBsqException, WalletException,
|
||||
TransactionVerificationException, BsqChangeBelowDustException {
|
||||
if (utxoCandidates != null) {
|
||||
bsqCoinSelector.setUtxoCandidates(utxoCandidates);
|
||||
}
|
||||
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector, false);
|
||||
}
|
||||
|
||||
|
@ -520,7 +546,19 @@ public class BsqWalletService extends WalletService implements DaoStateListener
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction getPreparedSendBtcTx(String receiverAddress, Coin receiverAmount)
|
||||
throws AddressFormatException, InsufficientBsqException, WalletException, TransactionVerificationException, BsqChangeBelowDustException {
|
||||
throws AddressFormatException, InsufficientBsqException, WalletException,
|
||||
TransactionVerificationException, BsqChangeBelowDustException {
|
||||
return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector, true);
|
||||
}
|
||||
|
||||
public Transaction getPreparedSendBtcTx(String receiverAddress,
|
||||
Coin receiverAmount,
|
||||
@Nullable Set<TransactionOutput> utxoCandidates)
|
||||
throws AddressFormatException, InsufficientBsqException, WalletException,
|
||||
TransactionVerificationException, BsqChangeBelowDustException {
|
||||
if (utxoCandidates != null) {
|
||||
nonBsqCoinSelector.setUtxoCandidates(utxoCandidates);
|
||||
}
|
||||
return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ package bisq.core.btc.wallet;
|
|||
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.model.blockchain.TxOutputKey;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
|
@ -26,6 +27,7 @@ import org.bitcoinj.core.TransactionOutput;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
|
@ -35,6 +37,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||
@Slf4j
|
||||
public class NonBsqCoinSelector extends BisqDefaultCoinSelector {
|
||||
private DaoStateService daoStateService;
|
||||
@Setter
|
||||
private Preferences preferences;
|
||||
|
||||
@Inject
|
||||
public NonBsqCoinSelector(DaoStateService daoStateService) {
|
||||
|
@ -60,9 +64,9 @@ public class NonBsqCoinSelector extends BisqDefaultCoinSelector {
|
|||
return !daoStateService.existsTxOutput(key) || daoStateService.isRejectedIssuanceOutput(key);
|
||||
}
|
||||
|
||||
// BTC utxo in the BSQ wallet are usually from rejected comp request so we don't expect dust attack utxos here.
|
||||
// Prevent usage of dust attack utxos
|
||||
@Override
|
||||
protected boolean isDustAttackUtxo(TransactionOutput output) {
|
||||
return false;
|
||||
return output.getValue().value < preferences.getIgnoreDustThreshold();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ shared.offerType=Offer type
|
|||
shared.details=Details
|
||||
shared.address=Address
|
||||
shared.balanceWithCur=Balance in {0}
|
||||
shared.utxo=Unspent transaction output
|
||||
shared.txId=Transaction ID
|
||||
shared.confirmations=Confirmations
|
||||
shared.revert=Revert Tx
|
||||
|
@ -2270,6 +2271,7 @@ dao.wallet.send.receiverAddress=Receiver's BSQ address
|
|||
dao.wallet.send.receiverBtcAddress=Receiver's BTC address
|
||||
dao.wallet.send.setDestinationAddress=Fill in your destination address
|
||||
dao.wallet.send.send=Send BSQ funds
|
||||
dao.wallet.send.inputControl=Select inputs
|
||||
dao.wallet.send.sendBtc=Send BTC funds
|
||||
dao.wallet.send.sendFunds.headline=Confirm withdrawal request
|
||||
dao.wallet.send.sendFunds.details=Sending: {0}\nTo receiving address: {1}.\nRequired mining fee is: {2} ({3} satoshis/vbyte)\nTransaction vsize: {4} vKb\n\nThe recipient will receive: {5}\n\nAre you sure you want to withdraw that amount?
|
||||
|
@ -2487,6 +2489,9 @@ dao.factsAndFigures.transactions.irregularTx=No. of all irregular transactions
|
|||
# Windows
|
||||
####################################################################
|
||||
|
||||
inputControlWindow.headline=Select inputs for transaction
|
||||
inputControlWindow.balanceLabel=Available balance
|
||||
|
||||
contractWindow.title=Dispute details
|
||||
contractWindow.dates=Offer date / Trade date
|
||||
contractWindow.btcAddresses=Bitcoin address BTC buyer / BTC seller
|
||||
|
@ -3619,6 +3624,7 @@ validation.inputTooSmall=Input has to be larger than {0}
|
|||
validation.inputToBeAtLeast=Input has to be at least {0}
|
||||
validation.amountBelowDust=An amount below the dust limit of {0} satoshi is not allowed.
|
||||
validation.length=Length must be between {0} and {1}
|
||||
validation.fixedLength=Length must be {0}
|
||||
validation.pattern=Input must be of format: {0}
|
||||
validation.noHexString=The input is not in HEX format.
|
||||
validation.advancedCash.invalidFormat=Must be a valid email or wallet id of format: X000000000000
|
||||
|
|
|
@ -76,7 +76,7 @@ public class InputTextField extends JFXTextField {
|
|||
|
||||
validationResult.addListener((ov, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
resetValidation();
|
||||
jfxValidationWrapper.resetValidation();
|
||||
if (!newValue.isValid) {
|
||||
if (!newValue.errorMessageEquals(oldValue)) { // avoid blinking
|
||||
validate(); // ensure that the new error message replaces the old one
|
||||
|
@ -92,9 +92,7 @@ public class InputTextField extends JFXTextField {
|
|||
});
|
||||
|
||||
textProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (validator != null) {
|
||||
this.validationResult.set(validator.validate(getText()));
|
||||
}
|
||||
refreshValidation();
|
||||
});
|
||||
|
||||
focusedProperty().addListener((o, oldValue, newValue) -> {
|
||||
|
@ -108,6 +106,7 @@ public class InputTextField extends JFXTextField {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
public InputTextField(double inputLineExtension) {
|
||||
this();
|
||||
this.inputLineExtension = inputLineExtension;
|
||||
|
@ -119,6 +118,19 @@ public class InputTextField extends JFXTextField {
|
|||
|
||||
public void resetValidation() {
|
||||
jfxValidationWrapper.resetValidation();
|
||||
|
||||
String input = getText();
|
||||
if (input.isEmpty()) {
|
||||
validationResult.set(new InputValidator.ValidationResult(true));
|
||||
} else {
|
||||
validationResult.set(validator.validate(input));
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshValidation() {
|
||||
if (validator != null) {
|
||||
this.validationResult.set(validator.validate(getText()));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -28,7 +28,9 @@ import bisq.desktop.main.funds.FundsView;
|
|||
import bisq.desktop.main.funds.deposit.DepositView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.main.overlays.windows.TxDetailsBsq;
|
||||
import bisq.desktop.main.overlays.windows.TxInputSelectionWindow;
|
||||
import bisq.desktop.main.overlays.windows.WalletPasswordWindow;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
import bisq.desktop.util.Layout;
|
||||
import bisq.desktop.util.validation.BsqAddressValidator;
|
||||
|
@ -47,6 +49,7 @@ import bisq.core.btc.wallet.WalletsManager;
|
|||
import bisq.core.dao.state.model.blockchain.TxType;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.user.DontShowAgainLookup;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
|
@ -58,10 +61,12 @@ import bisq.network.p2p.P2PService;
|
|||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
@ -71,9 +76,14 @@ import javafx.scene.layout.GridPane;
|
|||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
|
||||
import static bisq.desktop.util.FormBuilder.addInputTextField;
|
||||
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
|
||||
|
||||
|
@ -92,15 +102,20 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
private final BtcValidator btcValidator;
|
||||
private final BsqAddressValidator bsqAddressValidator;
|
||||
private final BtcAddressValidator btcAddressValidator;
|
||||
private final Preferences preferences;
|
||||
private final WalletPasswordWindow walletPasswordWindow;
|
||||
|
||||
private int gridRow = 0;
|
||||
private InputTextField amountInputTextField, btcAmountInputTextField;
|
||||
private Button sendBsqButton, sendBtcButton;
|
||||
private Button sendBsqButton, sendBtcButton, bsqInputControlButton, btcInputControlButton;
|
||||
private InputTextField receiversAddressInputTextField, receiversBtcAddressInputTextField;
|
||||
private ChangeListener<Boolean> focusOutListener;
|
||||
private TitledGroupBg btcTitledGroupBg;
|
||||
private ChangeListener<String> inputTextFieldListener;
|
||||
@Nullable
|
||||
private Set<TransactionOutput> bsqUtxoCandidates;
|
||||
@Nullable
|
||||
private Set<TransactionOutput> btcUtxoCandidates;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -121,6 +136,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
BtcValidator btcValidator,
|
||||
BsqAddressValidator bsqAddressValidator,
|
||||
BtcAddressValidator btcAddressValidator,
|
||||
Preferences preferences,
|
||||
WalletPasswordWindow walletPasswordWindow) {
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
|
@ -135,6 +151,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
this.btcValidator = btcValidator;
|
||||
this.bsqAddressValidator = bsqAddressValidator;
|
||||
this.btcAddressValidator = btcAddressValidator;
|
||||
this.preferences = preferences;
|
||||
this.walletPasswordWindow = walletPasswordWindow;
|
||||
}
|
||||
|
||||
|
@ -159,6 +176,16 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
setSendBtcGroupVisibleState(false);
|
||||
bsqBalanceUtil.activate();
|
||||
|
||||
receiversAddressInputTextField.resetValidation();
|
||||
amountInputTextField.resetValidation();
|
||||
receiversBtcAddressInputTextField.resetValidation();
|
||||
btcAmountInputTextField.resetValidation();
|
||||
|
||||
sendBsqButton.setOnAction((event) -> onSendBsq());
|
||||
bsqInputControlButton.setOnAction((event) -> onBsqInputControl());
|
||||
sendBtcButton.setOnAction((event) -> onSendBtc());
|
||||
btcInputControlButton.setOnAction((event) -> onBtcInputControl());
|
||||
|
||||
receiversAddressInputTextField.focusedProperty().addListener(focusOutListener);
|
||||
amountInputTextField.focusedProperty().addListener(focusOutListener);
|
||||
receiversBtcAddressInputTextField.focusedProperty().addListener(focusOutListener);
|
||||
|
@ -171,11 +198,16 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
|
||||
bsqWalletService.addBsqBalanceListener(this);
|
||||
|
||||
// We reset the input selection at activate to have all inputs selected, otherwise the user
|
||||
// might get confused if he had deselected inputs earlier and cannot spend the full balance.
|
||||
bsqUtxoCandidates = null;
|
||||
btcUtxoCandidates = null;
|
||||
|
||||
onUpdateBalances();
|
||||
}
|
||||
|
||||
private void onUpdateBalances() {
|
||||
onUpdateBalances(bsqWalletService.getAvailableConfirmedBalance(),
|
||||
onUpdateBalances(getSpendableBsqBalance(),
|
||||
bsqWalletService.getAvailableNonBsqBalance(),
|
||||
bsqWalletService.getUnverifiedBalance(),
|
||||
bsqWalletService.getUnconfirmedChangeBalance(),
|
||||
|
@ -200,6 +232,11 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
btcAmountInputTextField.textProperty().removeListener(inputTextFieldListener);
|
||||
|
||||
bsqWalletService.removeBsqBalanceListener(this);
|
||||
|
||||
sendBsqButton.setOnAction(null);
|
||||
btcInputControlButton.setOnAction(null);
|
||||
sendBtcButton.setOnAction(null);
|
||||
bsqInputControlButton.setOnAction(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -210,16 +247,24 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
Coin lockedForVotingBalance,
|
||||
Coin lockupBondsBalance,
|
||||
Coin unlockingBondsBalance) {
|
||||
updateBsqValidator(availableConfirmedBalance);
|
||||
updateBtcValidator(availableNonBsqBalance);
|
||||
|
||||
setSendBtcGroupVisibleState(availableNonBsqBalance.isPositive());
|
||||
}
|
||||
|
||||
private void updateBsqValidator(Coin availableConfirmedBalance) {
|
||||
bsqValidator.setAvailableBalance(availableConfirmedBalance);
|
||||
boolean isValid = bsqAddressValidator.validate(receiversAddressInputTextField.getText()).isValid &&
|
||||
bsqValidator.validate(amountInputTextField.getText()).isValid;
|
||||
sendBsqButton.setDisable(!isValid);
|
||||
}
|
||||
|
||||
boolean isBtcValid = btcAddressValidator.validate(receiversBtcAddressInputTextField.getText()).isValid &&
|
||||
private void updateBtcValidator(Coin availableConfirmedBalance) {
|
||||
btcValidator.setMaxValue(availableConfirmedBalance);
|
||||
boolean isValid = btcAddressValidator.validate(receiversBtcAddressInputTextField.getText()).isValid &&
|
||||
btcValidator.validate(btcAmountInputTextField.getText()).isValid;
|
||||
sendBtcButton.setDisable(!isBtcValid);
|
||||
|
||||
setSendBtcGroupVisibleState(availableNonBsqBalance.isPositive());
|
||||
sendBtcButton.setDisable(!isValid);
|
||||
}
|
||||
|
||||
private void addSendBsqGroup() {
|
||||
|
@ -240,9 +285,10 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
onUpdateBalances();
|
||||
};
|
||||
|
||||
sendBsqButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.wallet.send.send"));
|
||||
|
||||
sendBsqButton.setOnAction((event) -> onSendBsq());
|
||||
Tuple2<Button, Button> tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow,
|
||||
Res.get("dao.wallet.send.send"), Res.get("dao.wallet.send.inputControl"));
|
||||
sendBsqButton = tuple.first;
|
||||
bsqInputControlButton = tuple.second;
|
||||
}
|
||||
|
||||
private void onSendBsq() {
|
||||
|
@ -253,7 +299,8 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
String receiversAddressString = bsqFormatter.getAddressFromBsqAddress(receiversAddressInputTextField.getText()).toString();
|
||||
Coin receiverAmount = ParsingUtils.parseToCoin(amountInputTextField.getText(), bsqFormatter);
|
||||
try {
|
||||
Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(receiversAddressString, receiverAmount);
|
||||
Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(receiversAddressString,
|
||||
receiverAmount, bsqUtxoCandidates);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx);
|
||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
||||
Coin miningFee = signedTx.getFee();
|
||||
|
@ -267,12 +314,11 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
bsqFormatter,
|
||||
btcFormatter,
|
||||
() -> {
|
||||
receiversAddressInputTextField.setValidator(null);
|
||||
receiversAddressInputTextField.setText("");
|
||||
receiversAddressInputTextField.setValidator(bsqAddressValidator);
|
||||
amountInputTextField.setValidator(null);
|
||||
amountInputTextField.setText("");
|
||||
amountInputTextField.setValidator(bsqValidator);
|
||||
|
||||
receiversAddressInputTextField.resetValidation();
|
||||
amountInputTextField.resetValidation();
|
||||
});
|
||||
} catch (BsqChangeBelowDustException e) {
|
||||
String msg = Res.get("popup.warning.bsqChangeBelowDustException", bsqFormatter.formatCoinWithCode(e.getOutputValue()));
|
||||
|
@ -282,16 +328,49 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
}
|
||||
}
|
||||
|
||||
private void onBsqInputControl() {
|
||||
List<TransactionOutput> unspentTransactionOutputs = bsqWalletService.getSpendableBsqTransactionOutputs();
|
||||
if (bsqUtxoCandidates == null) {
|
||||
bsqUtxoCandidates = new HashSet<>(unspentTransactionOutputs);
|
||||
} else {
|
||||
// If we had some selection stored we need to update to already spent entries
|
||||
bsqUtxoCandidates = bsqUtxoCandidates.stream().
|
||||
filter(e -> unspentTransactionOutputs.contains(e)).
|
||||
collect(Collectors.toSet());
|
||||
}
|
||||
TxInputSelectionWindow txInputSelectionWindow = new TxInputSelectionWindow(unspentTransactionOutputs,
|
||||
bsqUtxoCandidates,
|
||||
preferences,
|
||||
bsqFormatter);
|
||||
txInputSelectionWindow.onAction(() -> setBsqUtxoCandidates(txInputSelectionWindow.getCandidates()))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void setBsqUtxoCandidates(Set<TransactionOutput> candidates) {
|
||||
this.bsqUtxoCandidates = candidates;
|
||||
updateBsqValidator(getSpendableBsqBalance());
|
||||
amountInputTextField.refreshValidation();
|
||||
}
|
||||
|
||||
// We have used input selection it is the sum of our selected inputs, otherwise the availableConfirmedBalance
|
||||
private Coin getSpendableBsqBalance() {
|
||||
return bsqUtxoCandidates != null ?
|
||||
Coin.valueOf(bsqUtxoCandidates.stream().mapToLong(e -> e.getValue().value).sum()) :
|
||||
bsqWalletService.getAvailableConfirmedBalance();
|
||||
}
|
||||
|
||||
private void setSendBtcGroupVisibleState(boolean visible) {
|
||||
btcTitledGroupBg.setVisible(visible);
|
||||
receiversBtcAddressInputTextField.setVisible(visible);
|
||||
btcAmountInputTextField.setVisible(visible);
|
||||
sendBtcButton.setVisible(visible);
|
||||
btcInputControlButton.setVisible(visible);
|
||||
|
||||
btcTitledGroupBg.setManaged(visible);
|
||||
receiversBtcAddressInputTextField.setManaged(visible);
|
||||
btcAmountInputTextField.setManaged(visible);
|
||||
sendBtcButton.setManaged(visible);
|
||||
btcInputControlButton.setManaged(visible);
|
||||
}
|
||||
|
||||
private void addSendBtcGroup() {
|
||||
|
@ -306,43 +385,81 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
btcAmountInputTextField.setValidator(btcValidator);
|
||||
GridPane.setColumnSpan(btcAmountInputTextField, 3);
|
||||
|
||||
sendBtcButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.wallet.send.sendBtc"));
|
||||
Tuple2<Button, Button> tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow,
|
||||
Res.get("dao.wallet.send.sendBtc"), Res.get("dao.wallet.send.inputControl"));
|
||||
sendBtcButton = tuple.first;
|
||||
btcInputControlButton = tuple.second;
|
||||
}
|
||||
|
||||
sendBtcButton.setOnAction((event) -> {
|
||||
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
|
||||
String receiversAddressString = receiversBtcAddressInputTextField.getText();
|
||||
Coin receiverAmount = bsqFormatter.parseToBTC(btcAmountInputTextField.getText());
|
||||
try {
|
||||
Transaction preparedSendTx = bsqWalletService.getPreparedSendBtcTx(receiversAddressString, receiverAmount);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx);
|
||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
||||
Coin miningFee = signedTx.getFee();
|
||||
private void onBtcInputControl() {
|
||||
List<TransactionOutput> unspentTransactionOutputs = bsqWalletService.getSpendableNonBsqTransactionOutputs();
|
||||
if (btcUtxoCandidates == null) {
|
||||
btcUtxoCandidates = new HashSet<>(unspentTransactionOutputs);
|
||||
} else {
|
||||
// If we had some selection stored we need to update to already spent entries
|
||||
btcUtxoCandidates = btcUtxoCandidates.stream().
|
||||
filter(e -> unspentTransactionOutputs.contains(e)).
|
||||
collect(Collectors.toSet());
|
||||
}
|
||||
TxInputSelectionWindow txInputSelectionWindow = new TxInputSelectionWindow(unspentTransactionOutputs,
|
||||
btcUtxoCandidates,
|
||||
preferences,
|
||||
btcFormatter);
|
||||
txInputSelectionWindow.onAction(() -> setBtcUtxoCandidates(txInputSelectionWindow.getCandidates())).
|
||||
show();
|
||||
}
|
||||
|
||||
if (miningFee.getValue() >= receiverAmount.getValue())
|
||||
GUIUtil.showWantToBurnBTCPopup(miningFee, receiverAmount, btcFormatter);
|
||||
else {
|
||||
int txVsize = signedTx.getVsize();
|
||||
showPublishTxPopup(receiverAmount,
|
||||
txWithBtcFee,
|
||||
TxType.INVALID,
|
||||
miningFee,
|
||||
txVsize, receiversBtcAddressInputTextField.getText(),
|
||||
btcFormatter,
|
||||
btcFormatter,
|
||||
() -> {
|
||||
receiversBtcAddressInputTextField.setText("");
|
||||
btcAmountInputTextField.setText("");
|
||||
});
|
||||
private void setBtcUtxoCandidates(Set<TransactionOutput> candidates) {
|
||||
this.btcUtxoCandidates = candidates;
|
||||
updateBtcValidator(getSpendableBtcBalance());
|
||||
btcAmountInputTextField.refreshValidation();
|
||||
}
|
||||
|
||||
}
|
||||
} catch (BsqChangeBelowDustException e) {
|
||||
String msg = Res.get("popup.warning.btcChangeBelowDustException", btcFormatter.formatCoinWithCode(e.getOutputValue()));
|
||||
new Popup().warning(msg).show();
|
||||
} catch (Throwable t) {
|
||||
handleError(t);
|
||||
}
|
||||
private Coin getSpendableBtcBalance() {
|
||||
return btcUtxoCandidates != null ?
|
||||
Coin.valueOf(btcUtxoCandidates.stream().mapToLong(e -> e.getValue().value).sum()) :
|
||||
bsqWalletService.getAvailableNonBsqBalance();
|
||||
}
|
||||
|
||||
private void onSendBtc() {
|
||||
if (!GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String receiversAddressString = receiversBtcAddressInputTextField.getText();
|
||||
Coin receiverAmount = bsqFormatter.parseToBTC(btcAmountInputTextField.getText());
|
||||
try {
|
||||
Transaction preparedSendTx = bsqWalletService.getPreparedSendBtcTx(receiversAddressString, receiverAmount, btcUtxoCandidates);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx);
|
||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
||||
Coin miningFee = signedTx.getFee();
|
||||
|
||||
if (miningFee.getValue() >= receiverAmount.getValue())
|
||||
GUIUtil.showWantToBurnBTCPopup(miningFee, receiverAmount, btcFormatter);
|
||||
else {
|
||||
int txVsize = signedTx.getVsize();
|
||||
showPublishTxPopup(receiverAmount,
|
||||
txWithBtcFee,
|
||||
TxType.INVALID,
|
||||
miningFee,
|
||||
txVsize, receiversBtcAddressInputTextField.getText(),
|
||||
btcFormatter,
|
||||
btcFormatter,
|
||||
() -> {
|
||||
receiversBtcAddressInputTextField.setText("");
|
||||
btcAmountInputTextField.setText("");
|
||||
|
||||
receiversBtcAddressInputTextField.resetValidation();
|
||||
btcAmountInputTextField.resetValidation();
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (BsqChangeBelowDustException e) {
|
||||
String msg = Res.get("popup.warning.btcChangeBelowDustException", btcFormatter.formatCoinWithCode(e.getOutputValue()));
|
||||
new Popup().warning(msg).show();
|
||||
} catch (Throwable t) {
|
||||
handleError(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleError(Throwable t) {
|
||||
|
@ -415,4 +532,3 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
|
|||
walletsManager.publishAndCommitBsqTx(txWithBtcFee, txType, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.desktop.main.overlays.windows;
|
||||
|
||||
import bisq.desktop.components.AutoTooltipCheckBox;
|
||||
import bisq.desktop.components.AutoTooltipLabel;
|
||||
import bisq.desktop.components.AutoTooltipTableColumn;
|
||||
import bisq.desktop.components.BalanceTextField;
|
||||
import bisq.desktop.components.ExternalHyperlink;
|
||||
import bisq.desktop.components.HyperlinkWithIcon;
|
||||
import bisq.desktop.main.overlays.Overlay;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
import bisq.desktop.util.Layout;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class TxInputSelectionWindow extends Overlay<TxInputSelectionWindow> {
|
||||
private static class TransactionOutputItem {
|
||||
@Getter
|
||||
private final TransactionOutput transactionOutput;
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean isSelected;
|
||||
|
||||
public TransactionOutputItem(TransactionOutput transactionOutput, boolean isSelected) {
|
||||
this.transactionOutput = transactionOutput;
|
||||
this.isSelected = isSelected;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<TransactionOutput> spendableTransactionOutputs;
|
||||
@Getter
|
||||
private final Set<TransactionOutput> candidates;
|
||||
private final Preferences preferences;
|
||||
private final CoinFormatter formatter;
|
||||
|
||||
private BalanceTextField balanceTextField;
|
||||
private TableView<TransactionOutputItem> tableView;
|
||||
|
||||
public TxInputSelectionWindow(List<TransactionOutput> spendableTransactionOutputs,
|
||||
Set<TransactionOutput> candidates,
|
||||
Preferences preferences,
|
||||
CoinFormatter formatter) {
|
||||
this.spendableTransactionOutputs = spendableTransactionOutputs;
|
||||
this.candidates = candidates;
|
||||
this.preferences = preferences;
|
||||
this.formatter = formatter;
|
||||
type = Type.Attention;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
rowIndex = 0;
|
||||
width = 900;
|
||||
if (headLine == null) {
|
||||
headLine = Res.get("inputControlWindow.headline");
|
||||
}
|
||||
createGridPane();
|
||||
gridPane.setHgap(15);
|
||||
addHeadLine();
|
||||
addContent();
|
||||
addButtons();
|
||||
addDontShowAgainCheckBox();
|
||||
applyStyles();
|
||||
display();
|
||||
}
|
||||
|
||||
protected void addContent() {
|
||||
tableView = new TableView<>();
|
||||
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
|
||||
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
GridPane.setRowIndex(tableView, rowIndex++);
|
||||
GridPane.setMargin(tableView, new Insets(Layout.GROUP_DISTANCE, 0, 0, 0));
|
||||
GridPane.setColumnSpan(tableView, 2);
|
||||
GridPane.setVgrow(tableView, Priority.ALWAYS);
|
||||
gridPane.getChildren().add(tableView);
|
||||
createColumns();
|
||||
ObservableList<TransactionOutputItem> items = FXCollections.observableArrayList(spendableTransactionOutputs.stream()
|
||||
.map(transactionOutput -> new TransactionOutputItem(transactionOutput, candidates.contains(transactionOutput)))
|
||||
.collect(Collectors.toList()));
|
||||
tableView.setItems(new SortedList<>(items));
|
||||
GUIUtil.setFitToRowsForTableView(tableView, 26, 28, 0, items.size());
|
||||
|
||||
balanceTextField = FormBuilder.addBalanceTextField(gridPane, rowIndex++, Res.get("inputControlWindow.balanceLabel"), Layout.FIRST_ROW_DISTANCE);
|
||||
balanceTextField.setFormatter(formatter);
|
||||
|
||||
updateBalance();
|
||||
}
|
||||
|
||||
private void updateBalance() {
|
||||
balanceTextField.setBalance(Coin.valueOf(candidates.stream()
|
||||
.mapToLong(transactionOutput -> transactionOutput.getValue().value)
|
||||
.sum()));
|
||||
}
|
||||
|
||||
private void onChangeCheckBox(TransactionOutputItem transactionOutputItem) {
|
||||
if (transactionOutputItem.isSelected()) {
|
||||
candidates.add(transactionOutputItem.getTransactionOutput());
|
||||
} else {
|
||||
candidates.remove(transactionOutputItem.getTransactionOutput());
|
||||
}
|
||||
|
||||
updateBalance();
|
||||
}
|
||||
|
||||
private void createColumns() {
|
||||
TableColumn<TransactionOutputItem, TransactionOutputItem> column;
|
||||
|
||||
column = new AutoTooltipTableColumn<>(Res.get("shared.select"));
|
||||
column.getStyleClass().add("first-column");
|
||||
column.setSortable(false);
|
||||
column.setMinWidth(60);
|
||||
column.setMaxWidth(column.getMinWidth());
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<TransactionOutputItem, TransactionOutputItem> call(
|
||||
TableColumn<TransactionOutputItem, TransactionOutputItem> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(TransactionOutputItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
final CheckBox checkBox = new AutoTooltipCheckBox();
|
||||
if (item != null && !empty) {
|
||||
checkBox.setSelected(item.isSelected());
|
||||
checkBox.setOnAction(e -> {
|
||||
item.setSelected(checkBox.isSelected());
|
||||
onChangeCheckBox(item);
|
||||
});
|
||||
setGraphic(checkBox);
|
||||
} else {
|
||||
if (checkBox != null) {
|
||||
checkBox.setOnAction(null);
|
||||
}
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
column = new AutoTooltipTableColumn<>(Res.get("shared.balance"));
|
||||
column.setMinWidth(100);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<TransactionOutputItem, TransactionOutputItem> call(
|
||||
TableColumn<TransactionOutputItem, TransactionOutputItem> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(TransactionOutputItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
setText(formatter.formatCoinWithCode(item.getTransactionOutput().getValue()));
|
||||
} else {
|
||||
setText("");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
tableView.getColumns().add(column);
|
||||
|
||||
column = new AutoTooltipTableColumn<>(Res.get("shared.utxo"));
|
||||
column.setSortable(false);
|
||||
column.setMinWidth(550);
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<TransactionOutputItem, TransactionOutputItem> call(
|
||||
TableColumn<TransactionOutputItem, TransactionOutputItem> column) {
|
||||
return new TableCell<>() {
|
||||
private HyperlinkWithIcon hyperlinkWithIcon;
|
||||
|
||||
@Override
|
||||
public void updateItem(TransactionOutputItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
TransactionOutput transactionOutput = item.getTransactionOutput();
|
||||
String txId = transactionOutput.getParentTransaction().getTxId().toString();
|
||||
hyperlinkWithIcon = new ExternalHyperlink(txId + ":" + transactionOutput.getIndex());
|
||||
hyperlinkWithIcon.setOnAction(event -> GUIUtil.openWebPage(preferences.getBsqBlockChainExplorer().txUrl + txId, false));
|
||||
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", txId)));
|
||||
setGraphic(hyperlinkWithIcon);
|
||||
} else {
|
||||
if (hyperlinkWithIcon != null) {
|
||||
hyperlinkWithIcon.setOnAction(null);
|
||||
}
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
tableView.getColumns().add(column);
|
||||
}
|
||||
}
|
|
@ -1681,11 +1681,14 @@ public class FormBuilder {
|
|||
|
||||
|
||||
public static BalanceTextField addBalanceTextField(GridPane gridPane, int rowIndex, String title) {
|
||||
return addBalanceTextField(gridPane, rowIndex, title, 20);
|
||||
}
|
||||
|
||||
public static BalanceTextField addBalanceTextField(GridPane gridPane, int rowIndex, String title, double top) {
|
||||
BalanceTextField balanceTextField = new BalanceTextField(title);
|
||||
GridPane.setRowIndex(balanceTextField, rowIndex);
|
||||
GridPane.setColumnIndex(balanceTextField, 0);
|
||||
GridPane.setMargin(balanceTextField, new Insets(20, 0, 0, 0));
|
||||
GridPane.setMargin(balanceTextField, new Insets(top, 0, 0, 0));
|
||||
gridPane.getChildren().add(balanceTextField);
|
||||
|
||||
return balanceTextField;
|
||||
|
|
|
@ -16,6 +16,7 @@ public class JFXInputValidator extends ValidatorBase {
|
|||
}
|
||||
|
||||
public void resetValidation() {
|
||||
message.set(null);
|
||||
hasErrors.set(false);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,10 @@ public class LengthValidator extends InputValidator {
|
|||
ValidationResult result = new ValidationResult(true);
|
||||
int length = (input == null) ? 0 : input.length();
|
||||
|
||||
if (this.minLength == this.maxLength) {
|
||||
if (length != this.minLength)
|
||||
result = new ValidationResult(false, Res.get("validation.fixedLength", this.minLength));
|
||||
} else
|
||||
if (length < this.minLength || length > this.maxLength)
|
||||
result = new ValidationResult(false, Res.get("validation.length", this.minLength, this.maxLength));
|
||||
|
||||
|
|
|
@ -69,5 +69,4 @@ public class LengthValidatorTest {
|
|||
assertFalse(validator2.validate(null).isValid); // too short
|
||||
assertFalse(validator2.validate("123456789").isValid); // too long
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue