mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 01:41:11 +01:00
Merge pull request #5976 from ghubstan/1-gettrades
Add API 'gettrades' method
This commit is contained in:
commit
fcb13ed772
@ -17,12 +17,15 @@
|
||||
|
||||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.core.offer.OfferDirection;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
@ -42,12 +45,16 @@ import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
||||
import static bisq.common.util.MathUtils.exactMultiply;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.out;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.cli.CliMain;
|
||||
import bisq.cli.GrpcClient;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@Slf4j
|
||||
@ -122,6 +129,34 @@ public abstract class AbstractOfferTest extends MethodTest {
|
||||
protected final Function<List<OfferInfo>, String> toOffersTable = (offers) ->
|
||||
new TableBuilder(OFFER_TBL, offers).build().toString();
|
||||
|
||||
protected OfferInfo getAvailableBsqSwapOffer(GrpcClient client,
|
||||
OfferDirection direction,
|
||||
boolean checkForLoggedExceptions) {
|
||||
List<OfferInfo> bsqSwapOffers = new ArrayList<>();
|
||||
int numFetchAttempts = 0;
|
||||
while (bsqSwapOffers.size() == 0) {
|
||||
bsqSwapOffers.addAll(client.getBsqSwapOffers(direction.name()));
|
||||
numFetchAttempts++;
|
||||
if (bsqSwapOffers.size() == 0) {
|
||||
log.warn("No available bsq swap offers found after {} fetch attempts.", numFetchAttempts);
|
||||
if (numFetchAttempts > 9) {
|
||||
if (checkForLoggedExceptions) {
|
||||
printNodeExceptionMessages(log);
|
||||
}
|
||||
fail(format("Bob gave up on fetching available bsq swap offers after %d attempts.", numFetchAttempts));
|
||||
}
|
||||
sleep(1_000);
|
||||
} else {
|
||||
assertEquals(1, bsqSwapOffers.size());
|
||||
log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var bsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOffers.get(0).getId());
|
||||
assertEquals(bsqSwapOffers.get(0).getId(), bsqSwapOffer.getId());
|
||||
return bsqSwapOffer;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static void initSwapPaymentAccounts() {
|
||||
// A bot may not know what the default 'BSQ Swap' account name is,
|
||||
|
@ -264,4 +264,18 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||
out.println("Bob's CLI 'gettrade' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9999", "gettrade", "--trade-id=" + tradeId});
|
||||
}
|
||||
|
||||
protected static void runCliGetOpenTrades() {
|
||||
out.println("Alice's CLI 'gettrades --category=open' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9998", "gettrades", "--category=open"});
|
||||
out.println("Bob's CLI 'gettrades --category=open' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9999", "gettrades", "--category=open"});
|
||||
}
|
||||
|
||||
protected static void runCliGetClosedTrades() {
|
||||
out.println("Alice's CLI 'gettrades --category=closed' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9998", "gettrades", "--category=closed"});
|
||||
out.println("Bob's CLI 'gettrades --category=closed' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9999", "gettrades", "--category=closed"});
|
||||
}
|
||||
}
|
||||
|
@ -17,11 +17,9 @@
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import protobuf.OfferDirection;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -36,6 +34,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.core.offer.OfferDirection.BUY;
|
||||
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory.BSQ_SWAP;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@ -44,7 +43,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.BsqSwapTrade.State.COMPLETED;
|
||||
import static protobuf.BsqSwapTrade.State.PREPARATION;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
|
||||
|
||||
|
||||
@ -54,7 +52,7 @@ import bisq.cli.GrpcClient;
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class BsqSwapTradeTest extends AbstractTradeTest {
|
||||
public class BsqSwapBuyBtcTradeTest extends AbstractTradeTest {
|
||||
|
||||
// Long-running swap trade tests might want to check node logs for exceptions.
|
||||
@Setter
|
||||
@ -81,15 +79,16 @@ public class BsqSwapTradeTest extends AbstractTradeTest {
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testAliceCreateBsqSwapBuyOffer() {
|
||||
var mySwapOffer = aliceClient.createBsqSwapOffer(BUY.name(),
|
||||
public void testAliceCreateBsqSwapBuyBtcOffer() {
|
||||
// Alice buys BTC, pays trade fee. Bob (BTC seller) pays miner tx fee.
|
||||
var mySwapOffer = aliceClient.createBsqSwapOffer(OfferDirection.BUY.name(),
|
||||
1_000_000L, // 0.01 BTC
|
||||
1_000_000L,
|
||||
"0.00005");
|
||||
log.debug("Pending BsqSwap Sell BSQ (Buy BTC) OFFER:\n{}", toOfferTable.apply(mySwapOffer));
|
||||
var newOfferId = mySwapOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), mySwapOffer.getDirection());
|
||||
assertEquals(OfferDirection.BUY.name(), mySwapOffer.getDirection());
|
||||
assertEquals(5_000, mySwapOffer.getPrice());
|
||||
assertEquals(1_000_000L, mySwapOffer.getAmount());
|
||||
assertEquals(1_000_000L, mySwapOffer.getMinAmount());
|
||||
@ -108,7 +107,7 @@ public class BsqSwapTradeTest extends AbstractTradeTest {
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testBobTakesBsqSwapOffer() {
|
||||
var availableSwapOffer = getAvailableBsqSwapOffer(bobClient);
|
||||
var availableSwapOffer = getAvailableBsqSwapOffer(bobClient, BUY, true);
|
||||
|
||||
// Before sending a TakeOfferRequest, the CLI needs to know what kind of Offer
|
||||
// it is taking (v1 or BsqSwap). Only BSQ swap offers can be taken with a
|
||||
@ -118,7 +117,7 @@ public class BsqSwapTradeTest extends AbstractTradeTest {
|
||||
var availableOfferCategory = bobClient.getAvailableOfferCategory(availableSwapOffer.getId());
|
||||
assertTrue(availableOfferCategory.equals(BSQ_SWAP));
|
||||
|
||||
sleep(30_000);
|
||||
sleep(3_000);
|
||||
|
||||
var swapTrade = bobClient.takeBsqSwapOffer(availableSwapOffer.getId());
|
||||
tradeId = swapTrade.getTradeId(); // Cache the tradeId for following test case(s).
|
||||
@ -130,7 +129,7 @@ public class BsqSwapTradeTest extends AbstractTradeTest {
|
||||
log.debug("BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(swapTrade));
|
||||
assertEquals(COMPLETED.name(), swapTrade.getState());
|
||||
|
||||
runCliGetTrade(tradeId);
|
||||
runCliGetClosedTrades();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -151,6 +150,8 @@ public class BsqSwapTradeTest extends AbstractTradeTest {
|
||||
bobsTrade = getBsqSwapTrade(bobClient, tradeId);
|
||||
log.debug("Bob's BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(bobsTrade));
|
||||
assertEquals(2, bobsTrade.getBsqSwapTradeInfo().getNumConfirmations());
|
||||
|
||||
runCliGetClosedTrades();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -162,32 +163,6 @@ public class BsqSwapTradeTest extends AbstractTradeTest {
|
||||
log.debug("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
|
||||
}
|
||||
|
||||
private OfferInfo getAvailableBsqSwapOffer(GrpcClient client) {
|
||||
List<OfferInfo> bsqSwapOffers = new ArrayList<>();
|
||||
int numFetchAttempts = 0;
|
||||
while (bsqSwapOffers.size() == 0) {
|
||||
bsqSwapOffers.addAll(client.getBsqSwapOffers(BUY.name()));
|
||||
numFetchAttempts++;
|
||||
if (bsqSwapOffers.size() == 0) {
|
||||
log.warn("No available bsq swap offers found after {} fetch attempts.", numFetchAttempts);
|
||||
if (numFetchAttempts > 9) {
|
||||
if (checkForLoggedExceptions) {
|
||||
printNodeExceptionMessages(log);
|
||||
}
|
||||
fail(format("Bob gave up on fetching available bsq swap offers after %d attempts.", numFetchAttempts));
|
||||
}
|
||||
sleep(1_000);
|
||||
} else {
|
||||
assertEquals(1, bsqSwapOffers.size());
|
||||
log.debug("Bob found new available bsq swap offer on attempt # {}.", numFetchAttempts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var bsqSwapOffer = bobClient.getBsqSwapOffer(bsqSwapOffers.get(0).getId());
|
||||
assertEquals(bsqSwapOffers.get(0).getId(), bsqSwapOffer.getId());
|
||||
return bsqSwapOffer;
|
||||
}
|
||||
|
||||
private TradeInfo getBsqSwapTrade(GrpcClient client, String tradeId) {
|
||||
int numFetchAttempts = 0;
|
||||
while (true) {
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.method.trade;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
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 static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.core.offer.OfferDirection.SELL;
|
||||
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory.BSQ_SWAP;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.BsqSwapTrade.State.COMPLETED;
|
||||
import static protobuf.BsqSwapTrade.State.PREPARATION;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class BsqSwapSellBtcTradeTest extends AbstractTradeTest {
|
||||
|
||||
// Long-running swap trade tests might want to check node logs for exceptions.
|
||||
@Setter
|
||||
private boolean checkForLoggedExceptions;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
AbstractOfferTest.setUp();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void generateBtcBlock() {
|
||||
genBtcBlocksThenWait(1, 2_000);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testGetBalancesBeforeTrade() {
|
||||
var alicesBalances = aliceClient.getBalances();
|
||||
log.debug("Alice's Before Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
|
||||
var bobsBalances = bobClient.getBalances();
|
||||
log.debug("Bob's Before Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testAliceCreateBsqSwapSellBtcOffer() {
|
||||
// Alice sells BTC, pays miner tx fee. Bob (BTC buyer) pays trade fee.
|
||||
var mySwapOffer = aliceClient.createBsqSwapOffer(SELL.name(),
|
||||
1_000_000L, // 0.01 BTC
|
||||
1_000_000L,
|
||||
"0.00005");
|
||||
log.debug("Pending BsqSwap Buy BSQ (Sell BTC) OFFER:\n{}", toOfferTable.apply(mySwapOffer));
|
||||
var newOfferId = mySwapOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), mySwapOffer.getDirection());
|
||||
assertEquals(5_000, mySwapOffer.getPrice());
|
||||
assertEquals(1_000_000L, mySwapOffer.getAmount());
|
||||
assertEquals(1_000_000L, mySwapOffer.getMinAmount());
|
||||
assertEquals(BSQ, mySwapOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, mySwapOffer.getCounterCurrencyCode());
|
||||
|
||||
genBtcBlocksThenWait(1, 2_500);
|
||||
|
||||
mySwapOffer = aliceClient.getOffer(newOfferId);
|
||||
log.debug("My fetched BsqSwap Buy BSQ (Sell BTC) OFFER:\n{}", toOfferTable.apply(mySwapOffer));
|
||||
assertNotEquals(0, mySwapOffer.getMakerFee());
|
||||
|
||||
runCliGetOffer(newOfferId);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testBobTakesBsqSwapOffer() {
|
||||
var availableSwapOffer = getAvailableBsqSwapOffer(bobClient, SELL, true);
|
||||
|
||||
// Before sending a TakeOfferRequest, the CLI needs to know what kind of Offer
|
||||
// it is taking (v1 or BsqSwap). Only BSQ swap offers can be taken with a
|
||||
// single offerId parameter. Taking v1 offers requires an additional
|
||||
// paymentAccountId param. The test case knows what kind of offer is being taken,
|
||||
// but we test the gRPC GetOfferCategory service here.
|
||||
var availableOfferCategory = bobClient.getAvailableOfferCategory(availableSwapOffer.getId());
|
||||
assertTrue(availableOfferCategory.equals(BSQ_SWAP));
|
||||
|
||||
sleep(10_000);
|
||||
|
||||
var swapTrade = bobClient.takeBsqSwapOffer(availableSwapOffer.getId());
|
||||
tradeId = swapTrade.getTradeId(); // Cache the tradeId for following test case(s).
|
||||
log.debug("BsqSwap Trade at PREPARATION:\n{}", toTradeDetailTable.apply(swapTrade));
|
||||
assertEquals(PREPARATION.name(), swapTrade.getState());
|
||||
genBtcBlocksThenWait(1, 3_000);
|
||||
|
||||
swapTrade = getBsqSwapTrade(bobClient, tradeId);
|
||||
log.debug("BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(swapTrade));
|
||||
assertEquals(COMPLETED.name(), swapTrade.getState());
|
||||
|
||||
runCliGetClosedTrades();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testCompletedSwapTxConfirmations() {
|
||||
sleep(2_000); // Wait for TX confirmation to happen on node.
|
||||
|
||||
var alicesTrade = getBsqSwapTrade(aliceClient, tradeId);
|
||||
log.debug("Alice's BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(alicesTrade));
|
||||
assertEquals(1, alicesTrade.getBsqSwapTradeInfo().getNumConfirmations());
|
||||
|
||||
var bobsTrade = getBsqSwapTrade(bobClient, tradeId);
|
||||
log.debug("Bob's BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(bobsTrade));
|
||||
assertEquals(1, bobsTrade.getBsqSwapTradeInfo().getNumConfirmations());
|
||||
|
||||
genBtcBlocksThenWait(1, 2_000);
|
||||
|
||||
bobsTrade = getBsqSwapTrade(bobClient, tradeId);
|
||||
log.debug("Bob's BsqSwap Trade at COMPLETION:\n{}", toTradeDetailTable.apply(bobsTrade));
|
||||
assertEquals(2, bobsTrade.getBsqSwapTradeInfo().getNumConfirmations());
|
||||
|
||||
runCliGetClosedTrades();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
public void testGetBalancesAfterTrade() {
|
||||
var alicesBalances = aliceClient.getBalances();
|
||||
log.debug("Alice's After Trade Balance:\n{}", formatBalancesTbls(alicesBalances));
|
||||
var bobsBalances = bobClient.getBalances();
|
||||
log.debug("Bob's After Trade Balance:\n{}", formatBalancesTbls(bobsBalances));
|
||||
}
|
||||
|
||||
private TradeInfo getBsqSwapTrade(GrpcClient client, String tradeId) {
|
||||
int numFetchAttempts = 0;
|
||||
while (true) {
|
||||
try {
|
||||
numFetchAttempts++;
|
||||
return client.getTrade(tradeId);
|
||||
} catch (Exception ex) {
|
||||
log.warn(ex.getMessage());
|
||||
if (numFetchAttempts > 9) {
|
||||
if (checkForLoggedExceptions) {
|
||||
printNodeExceptionMessages(log);
|
||||
}
|
||||
fail(format("Could not find new bsq swap trade after %d attempts.", numFetchAttempts));
|
||||
} else {
|
||||
sleep(1_000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -133,7 +133,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testKeepFunds(final TestInfo testInfo) {
|
||||
public void testCloseTrade(final TestInfo testInfo) {
|
||||
try {
|
||||
genBtcBlocksThenWait(1, 1_000);
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
@ -147,6 +147,9 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId));
|
||||
logBalances(log, testInfo);
|
||||
|
||||
runCliGetClosedTrades();
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ public class TakeBuyXMROfferTest extends AbstractTradeTest {
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testKeepFunds(final TestInfo testInfo) {
|
||||
public void testCloseTrade(final TestInfo testInfo) {
|
||||
try {
|
||||
genBtcBlocksThenWait(1, 1_000);
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
|
@ -31,7 +31,7 @@ import static java.lang.System.getenv;
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
import bisq.apitest.method.trade.BsqSwapTradeTest;
|
||||
import bisq.apitest.method.trade.BsqSwapBuyBtcTradeTest;
|
||||
|
||||
@EnabledIf("envLongRunningTestEnabled")
|
||||
@Slf4j
|
||||
@ -49,7 +49,7 @@ public class LongRunningBsqSwapTest extends AbstractOfferTest {
|
||||
@Order(1)
|
||||
public void testBsqSwaps() {
|
||||
// TODO Fix wallet inconsistency bugs after N(?) trades.
|
||||
BsqSwapTradeTest test = new BsqSwapTradeTest();
|
||||
BsqSwapBuyBtcTradeTest test = new BsqSwapBuyBtcTradeTest();
|
||||
test.setCheckForLoggedExceptions(true);
|
||||
|
||||
for (int swapCount = 1; swapCount <= MAX_SWAPS; swapCount++) {
|
||||
@ -57,7 +57,7 @@ public class LongRunningBsqSwapTest extends AbstractOfferTest {
|
||||
|
||||
test.testGetBalancesBeforeTrade();
|
||||
|
||||
test.testAliceCreateBsqSwapBuyOffer();
|
||||
test.testAliceCreateBsqSwapBuyBtcOffer();
|
||||
genBtcBlocksThenWait(1, 8_000);
|
||||
|
||||
test.testBobTakesBsqSwapOffer();
|
||||
|
@ -71,7 +71,7 @@ public class LongRunningTradesTest extends AbstractTradeTest {
|
||||
test.testTakeAlicesBuyOffer(testInfo);
|
||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||
test.testBobsConfirmPaymentReceived(testInfo);
|
||||
test.testKeepFunds(testInfo);
|
||||
test.testCloseTrade(testInfo);
|
||||
}
|
||||
|
||||
public void testTakeSellBTCOffer(final TestInfo testInfo) {
|
||||
|
@ -29,7 +29,8 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
|
||||
import bisq.apitest.method.trade.AbstractTradeTest;
|
||||
import bisq.apitest.method.trade.BsqSwapTradeTest;
|
||||
import bisq.apitest.method.trade.BsqSwapBuyBtcTradeTest;
|
||||
import bisq.apitest.method.trade.BsqSwapSellBtcTradeTest;
|
||||
import bisq.apitest.method.trade.FailUnfailTradeTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBSQOfferTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
||||
@ -56,7 +57,7 @@ public class TradeTest extends AbstractTradeTest {
|
||||
test.testTakeAlicesBuyOffer(testInfo);
|
||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||
test.testBobsConfirmPaymentReceived(testInfo);
|
||||
test.testKeepFunds(testInfo);
|
||||
test.testCloseTrade(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -108,7 +109,7 @@ public class TradeTest extends AbstractTradeTest {
|
||||
test.testTakeAlicesSellBTCForXMROffer(testInfo);
|
||||
test.testBobsConfirmPaymentStarted(testInfo);
|
||||
test.testAlicesConfirmPaymentReceived(testInfo);
|
||||
test.testKeepFunds(testInfo);
|
||||
test.testCloseTrade(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -124,16 +125,26 @@ public class TradeTest extends AbstractTradeTest {
|
||||
|
||||
@Test
|
||||
@Order(8)
|
||||
public void testBsqSwapTradeTest(final TestInfo testInfo) {
|
||||
BsqSwapTradeTest test = new BsqSwapTradeTest();
|
||||
public void testBsqSwapBuyBtcTrade(final TestInfo testInfo) {
|
||||
BsqSwapBuyBtcTradeTest test = new BsqSwapBuyBtcTradeTest();
|
||||
test.testGetBalancesBeforeTrade();
|
||||
test.testAliceCreateBsqSwapBuyOffer();
|
||||
test.testAliceCreateBsqSwapBuyBtcOffer();
|
||||
test.testBobTakesBsqSwapOffer();
|
||||
test.testGetBalancesAfterTrade();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(9)
|
||||
public void testBsqSwapSellBtcTrade(final TestInfo testInfo) {
|
||||
BsqSwapSellBtcTradeTest test = new BsqSwapSellBtcTradeTest();
|
||||
test.testGetBalancesBeforeTrade();
|
||||
test.testAliceCreateBsqSwapSellBtcOffer();
|
||||
test.testBobTakesBsqSwapOffer();
|
||||
test.testGetBalancesAfterTrade();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(10)
|
||||
public void testFailUnfailTrade(final TestInfo testInfo) {
|
||||
FailUnfailTradeTest test = new FailUnfailTradeTest();
|
||||
test.testFailAndUnFailBuyBTCTrade(testInfo);
|
||||
|
@ -47,6 +47,8 @@ import static bisq.cli.Method.*;
|
||||
import static bisq.cli.opts.OptLabel.*;
|
||||
import static bisq.cli.table.builder.TableType.*;
|
||||
import static bisq.proto.grpc.GetOfferCategoryReply.OfferCategory.BSQ_SWAP;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.CLOSED;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.OPEN;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.err;
|
||||
import static java.lang.System.exit;
|
||||
@ -67,6 +69,7 @@ import bisq.cli.opts.GetBalanceOptionParser;
|
||||
import bisq.cli.opts.GetOffersOptionParser;
|
||||
import bisq.cli.opts.GetPaymentAcctFormOptionParser;
|
||||
import bisq.cli.opts.GetTradeOptionParser;
|
||||
import bisq.cli.opts.GetTradesOptionParser;
|
||||
import bisq.cli.opts.GetTransactionOptionParser;
|
||||
import bisq.cli.opts.OfferIdOptionParser;
|
||||
import bisq.cli.opts.RegisterDisputeAgentOptionParser;
|
||||
@ -503,6 +506,26 @@ public class CliMain {
|
||||
|
||||
return;
|
||||
}
|
||||
case gettrades: {
|
||||
var opts = new GetTradesOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var category = opts.getCategory();
|
||||
var trades = category.equals(OPEN)
|
||||
? client.getOpenTrades()
|
||||
: client.getTradeHistory(category);
|
||||
if (trades.isEmpty()) {
|
||||
out.println(format("no %s trades found", category.name().toLowerCase()));
|
||||
} else {
|
||||
var tableType = category.equals(OPEN)
|
||||
? OPEN_TRADES_TBL
|
||||
: category.equals(CLOSED) ? CLOSED_TRADES_TBL : FAILED_TRADES_TBL;
|
||||
new TableBuilder(tableType, trades).build().print(out);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case confirmpaymentstarted: {
|
||||
var opts = new GetTradeOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
@ -882,6 +905,8 @@ public class CliMain {
|
||||
stream.format(rowFormat, gettrade.name(), "--trade-id=<trade-id> \\", "Get trade summary or full contract");
|
||||
stream.format(rowFormat, "", "[--show-contract=<true|false>]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, gettrades.name(), "[--category=<open|closed|failed>]", "Get open (default), closed, or failed trades");
|
||||
stream.println();
|
||||
stream.format(rowFormat, confirmpaymentstarted.name(), "--trade-id=<trade-id>", "Confirm payment started");
|
||||
stream.println();
|
||||
stream.format(rowFormat, confirmpaymentreceived.name(), "--trade-id=<trade-id>", "Confirm payment received");
|
||||
|
@ -22,6 +22,7 @@ import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.BsqBalanceInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.GetMethodHelpRequest;
|
||||
import bisq.proto.grpc.GetTradesRequest;
|
||||
import bisq.proto.grpc.GetVersionRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||
@ -364,6 +365,14 @@ public final class GrpcClient {
|
||||
return tradesServiceRequest.getTrade(tradeId);
|
||||
}
|
||||
|
||||
public List<TradeInfo> getOpenTrades() {
|
||||
return tradesServiceRequest.getOpenTrades();
|
||||
}
|
||||
|
||||
public List<TradeInfo> getTradeHistory(GetTradesRequest.Category category) {
|
||||
return tradesServiceRequest.getTradeHistory(category);
|
||||
}
|
||||
|
||||
public void confirmPaymentStarted(String tradeId) {
|
||||
tradesServiceRequest.confirmPaymentStarted(tradeId);
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ public enum Method {
|
||||
getpaymentaccts,
|
||||
getpaymentmethods,
|
||||
gettrade,
|
||||
gettrades,
|
||||
failtrade,
|
||||
unfailtrade,
|
||||
gettransaction,
|
||||
|
84
cli/src/main/java/bisq/cli/opts/GetTradesOptionParser.java
Normal file
84
cli/src/main/java/bisq/cli/opts/GetTradesOptionParser.java
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.opts;
|
||||
|
||||
|
||||
import bisq.proto.grpc.GetTradesRequest;
|
||||
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static bisq.cli.opts.OptLabel.OPT_CATEGORY;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.CLOSED;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.FAILED;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.OPEN;
|
||||
import static java.util.Arrays.stream;
|
||||
|
||||
public class GetTradesOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
// Map valid CLI option values to gRPC request parameters.
|
||||
private enum CATEGORY {
|
||||
// Lower case enum fits CLI method and parameter style.
|
||||
open(OPEN),
|
||||
closed(CLOSED),
|
||||
failed(FAILED);
|
||||
|
||||
private final GetTradesRequest.Category grpcRequestCategory;
|
||||
|
||||
CATEGORY(GetTradesRequest.Category grpcRequestCategory) {
|
||||
this.grpcRequestCategory = grpcRequestCategory;
|
||||
}
|
||||
}
|
||||
|
||||
final OptionSpec<String> categoryOpt = parser.accepts(OPT_CATEGORY,
|
||||
"category of trades (open|closed|failed)")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(CATEGORY.open.name());
|
||||
|
||||
private final Predicate<String> isValidCategory = (c) ->
|
||||
stream(CATEGORY.values()).anyMatch(v -> v.name().equalsIgnoreCase(c));
|
||||
|
||||
public GetTradesOptionParser(String[] args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
public GetTradesOptionParser parse() {
|
||||
super.parse();
|
||||
|
||||
// Short circuit opt validation if user just wants help.
|
||||
if (options.has(helpOpt))
|
||||
return this;
|
||||
|
||||
if (options.has(categoryOpt)) {
|
||||
String category = options.valueOf(categoryOpt);
|
||||
if (category.isEmpty())
|
||||
throw new IllegalArgumentException("no category (open|closed|failed) specified");
|
||||
|
||||
if (!isValidCategory.test(category))
|
||||
throw new IllegalArgumentException("category must be open|closed|failed");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public GetTradesRequest.Category getCategory() {
|
||||
String cliOpt = options.valueOf(categoryOpt);
|
||||
return CATEGORY.valueOf(cliOpt).grpcRequestCategory;
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ public class OptLabel {
|
||||
public final static String OPT_ACCOUNT_NAME = "account-name";
|
||||
public final static String OPT_ADDRESS = "address";
|
||||
public final static String OPT_AMOUNT = "amount";
|
||||
public final static String OPT_CATEGORY = "category";
|
||||
public final static String OPT_CURRENCY_CODE = "currency-code";
|
||||
public final static String OPT_DIRECTION = "direction";
|
||||
public final static String OPT_DISPUTE_AGENT_TYPE = "dispute-agent-type";
|
||||
|
@ -22,12 +22,18 @@ import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||
import bisq.proto.grpc.FailTradeRequest;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
import bisq.proto.grpc.GetTradesRequest;
|
||||
import bisq.proto.grpc.TakeOfferReply;
|
||||
import bisq.proto.grpc.TakeOfferRequest;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
import bisq.proto.grpc.UnFailTradeRequest;
|
||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.CLOSED;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.FAILED;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.GrpcStubs;
|
||||
@ -72,6 +78,22 @@ public class TradesServiceRequest {
|
||||
return grpcStubs.tradesService.getTrade(request).getTrade();
|
||||
}
|
||||
|
||||
public List<TradeInfo> getOpenTrades() {
|
||||
var request = GetTradesRequest.newBuilder()
|
||||
.build();
|
||||
return grpcStubs.tradesService.getTrades(request).getTradesList();
|
||||
}
|
||||
|
||||
public List<TradeInfo> getTradeHistory(GetTradesRequest.Category category) {
|
||||
if (!category.equals(CLOSED) && !category.equals(FAILED))
|
||||
throw new IllegalStateException("unrecognized gettrades category parameter " + category.name());
|
||||
|
||||
var request = GetTradesRequest.newBuilder()
|
||||
.setCategory(category)
|
||||
.build();
|
||||
return grpcStubs.tradesService.getTrades(request).getTradesList();
|
||||
}
|
||||
|
||||
public void confirmPaymentStarted(String tradeId) {
|
||||
var request = ConfirmPaymentStartedRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
|
@ -26,7 +26,6 @@ import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
@ -37,6 +36,7 @@ import javax.annotation.Nullable;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_BUYER_DEPOSIT;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_SELLER_DEPOSIT;
|
||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
|
||||
@Nullable
|
||||
protected final Column<String> colOfferType;
|
||||
@Nullable
|
||||
protected final Column<String> colStatusDescription;
|
||||
protected final Column<String> colClosingStatus;
|
||||
|
||||
// Trade detail tbl specific columns
|
||||
|
||||
@ -133,7 +133,7 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
|
||||
this.colPaymentMethod = colSupplier.paymentMethodColumn.get();
|
||||
this.colRole = colSupplier.roleColumn.get();
|
||||
this.colOfferType = colSupplier.offerTypeColumn.get();
|
||||
this.colStatusDescription = colSupplier.statusDescriptionColumn.get();
|
||||
this.colClosingStatus = colSupplier.statusDescriptionColumn.get();
|
||||
|
||||
// Trade detail specific columns, some in common with BSQ swap trades detail.
|
||||
|
||||
@ -168,7 +168,15 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
|
||||
private final Supplier<Boolean> isTradeDetailTblBuilder = () -> tableType.equals(TRADE_DETAIL_TBL);
|
||||
protected final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
|
||||
protected final Predicate<TradeInfo> isBsqSwapTrade = (t) -> t.getOffer().getIsBsqSwapOffer();
|
||||
protected final Predicate<TradeInfo> isMyOffer = (t) -> t.getOffer().getIsMyOffer();
|
||||
protected final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
|
||||
protected final Predicate<TradeInfo> isSellOffer = (t) -> t.getOffer().getDirection().equals(SELL.name());
|
||||
protected final Predicate<TradeInfo> isBtcSeller = (t) -> (isMyOffer.test(t) && isSellOffer.test(t))
|
||||
|| (!isMyOffer.test(t) && !isSellOffer.test(t));
|
||||
protected final Predicate<TradeInfo> isTradeFeeBtc = (t) -> isMyOffer.test(t)
|
||||
? t.getOffer().getIsCurrencyForMakerFeeBtc()
|
||||
: t.getIsCurrencyForTakerFeeBtc();
|
||||
|
||||
|
||||
// Column Value Functions
|
||||
|
||||
@ -206,12 +214,19 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
|
||||
? formatToPercent(t.getOffer().getMarketPriceMargin())
|
||||
: "N/A";
|
||||
|
||||
protected final Function<TradeInfo, Long> toMyMinerTxFee = (t) ->
|
||||
isTaker.test(t)
|
||||
protected final Function<TradeInfo, Long> toMyMinerTxFee = (t) -> {
|
||||
if (isBsqSwapTrade.test(t)) {
|
||||
// The BTC seller pays the miner fee for both sides.
|
||||
return isBtcSeller.test(t) ? t.getTxFeeAsLong() : 0L;
|
||||
} else {
|
||||
return isTaker.test(t)
|
||||
? t.getTxFeeAsLong()
|
||||
: t.getOffer().getTxFee();
|
||||
}
|
||||
};
|
||||
|
||||
protected final BiFunction<TradeInfo, Boolean, Long> toTradeFeeBsq = (t, isMyOffer) -> {
|
||||
protected final Function<TradeInfo, Long> toTradeFeeBsq = (t) -> {
|
||||
var isMyOffer = t.getOffer().getIsMyOffer();
|
||||
if (isMyOffer) {
|
||||
return t.getOffer().getIsCurrencyForMakerFeeBtc()
|
||||
? 0L // Maker paid BTC fee, return 0.
|
||||
@ -223,7 +238,8 @@ abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
|
||||
}
|
||||
};
|
||||
|
||||
protected final BiFunction<TradeInfo, Boolean, Long> toTradeFeeBtc = (t, isMyOffer) -> {
|
||||
protected final Function<TradeInfo, Long> toTradeFeeBtc = (t) -> {
|
||||
var isMyOffer = t.getOffer().getIsMyOffer();
|
||||
if (isMyOffer) {
|
||||
return t.getOffer().getIsCurrencyForMakerFeeBtc()
|
||||
? t.getOffer().getMakerFee()
|
||||
|
@ -1,106 +0,0 @@
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.*;
|
||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.column.BtcColumn;
|
||||
import bisq.cli.table.column.Column;
|
||||
import bisq.cli.table.column.Iso8601DateTimeColumn;
|
||||
import bisq.cli.table.column.MixedPriceColumn;
|
||||
import bisq.cli.table.column.MixedTradeFeeColumn;
|
||||
import bisq.cli.table.column.MixedVolumeColumn;
|
||||
import bisq.cli.table.column.SatoshiColumn;
|
||||
import bisq.cli.table.column.StringColumn;
|
||||
|
||||
/**
|
||||
* Builds a {@code bisq.cli.table.Table} from one or more {@code bisq.proto.grpc.TradeInfo} objects.
|
||||
*/
|
||||
abstract class AbstractTradeTableBuilder extends AbstractTableBuilder {
|
||||
|
||||
@Nullable
|
||||
protected final Column<String> colTradeId;
|
||||
@Nullable
|
||||
protected final Column<Long> colCreateDate;
|
||||
@Nullable
|
||||
protected final Column<String> colMarket;
|
||||
@Nullable
|
||||
protected final MixedPriceColumn colMixedPrice;
|
||||
@Nullable
|
||||
protected final Column<String> colPriceDeviation;
|
||||
@Nullable
|
||||
protected final Column<Long> colAmountInBtc;
|
||||
@Nullable
|
||||
protected final MixedVolumeColumn colMixedAmount;
|
||||
@Nullable
|
||||
protected final Column<String> colCurrency;
|
||||
@Nullable
|
||||
protected final MixedTradeFeeColumn colMixedTradeFee;
|
||||
@Nullable
|
||||
protected final Column<Long> colBuyerDeposit;
|
||||
@Nullable
|
||||
protected final Column<Long> colSellerDeposit;
|
||||
@Nullable
|
||||
protected final Column<String> colOfferType;
|
||||
@Nullable
|
||||
protected final Column<String> colStatus;
|
||||
protected final Column<Long> colMinerTxFee;
|
||||
|
||||
AbstractTradeTableBuilder(TableType tableType, List<?> protos) {
|
||||
super(tableType, protos);
|
||||
boolean isTradeDetail = tableType.equals(TRADE_DETAIL_TBL);
|
||||
this.colTradeId = isTradeDetail ? null : new StringColumn(COL_HEADER_TRADE_ID);
|
||||
this.colCreateDate = isTradeDetail ? null : new Iso8601DateTimeColumn(COL_HEADER_DATE_TIME);
|
||||
this.colMarket = isTradeDetail ? null : new StringColumn(COL_HEADER_MARKET);
|
||||
this.colMixedPrice = isTradeDetail ? null : new MixedPriceColumn(COL_HEADER_PRICE);
|
||||
this.colPriceDeviation = isTradeDetail ? null : new StringColumn(COL_HEADER_DEVIATION, RIGHT);
|
||||
this.colAmountInBtc = isTradeDetail ? null : new BtcColumn(COL_HEADER_AMOUNT_IN_BTC);
|
||||
this.colMixedAmount = isTradeDetail ? null : new MixedVolumeColumn(COL_HEADER_AMOUNT);
|
||||
this.colCurrency = isTradeDetail ? null : new StringColumn(COL_HEADER_CURRENCY);
|
||||
this.colMixedTradeFee = isTradeDetail ? null : new MixedTradeFeeColumn(COL_HEADER_TRADE_FEE);
|
||||
this.colBuyerDeposit = isTradeDetail ? null : new SatoshiColumn(COL_HEADER_BUYER_DEPOSIT);
|
||||
this.colSellerDeposit = isTradeDetail ? null : new SatoshiColumn(COL_HEADER_SELLER_DEPOSIT);
|
||||
this.colOfferType = isTradeDetail ? null : new StringColumn(COL_HEADER_OFFER_TYPE);
|
||||
this.colStatus = isTradeDetail ? null : new StringColumn(COL_HEADER_STATUS);
|
||||
this.colMinerTxFee = new SatoshiColumn(COL_HEADER_TX_FEE);
|
||||
}
|
||||
|
||||
protected final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
|
||||
|
||||
protected final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
|
||||
|
||||
protected final Function<TradeInfo, String> toPaymentCurrencyCode = (t) ->
|
||||
isFiatTrade.test(t)
|
||||
? t.getOffer().getCounterCurrencyCode()
|
||||
: t.getOffer().getBaseCurrencyCode();
|
||||
|
||||
protected final Function<TradeInfo, Long> toAmount = (t) ->
|
||||
isFiatTrade.test(t)
|
||||
? t.getTradeAmountAsLong()
|
||||
: t.getTradeVolume();
|
||||
|
||||
protected final Function<TradeInfo, Long> toMinerTxFee = (t) ->
|
||||
isTaker.test(t)
|
||||
? t.getTxFeeAsLong()
|
||||
: t.getOffer().getTxFee();
|
||||
|
||||
protected final Function<TradeInfo, Long> toMakerTakerFee = (t) ->
|
||||
isTaker.test(t)
|
||||
? t.getTakerFeeAsLong()
|
||||
: t.getOffer().getMakerFee();
|
||||
|
||||
protected final Function<TradeInfo, Long> toTradeCost = (t) ->
|
||||
isFiatTrade.test(t)
|
||||
? t.getTradeVolume()
|
||||
: t.getTradeAmountAsLong();
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.table.builder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static bisq.cli.table.builder.TableType.CLOSED_TRADES_TBL;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
import bisq.cli.table.column.MixedPriceColumn;
|
||||
|
||||
class ClosedTradeTableBuilder extends AbstractTradeListBuilder {
|
||||
|
||||
ClosedTradeTableBuilder(List<?> protos) {
|
||||
super(CLOSED_TRADES_TBL, protos);
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
populateColumns();
|
||||
return new Table(colTradeId,
|
||||
colCreateDate.asStringColumn(),
|
||||
colMarket,
|
||||
colPrice.asStringColumn(),
|
||||
colPriceDeviation.justify(),
|
||||
colAmountInBtc.asStringColumn(),
|
||||
colMixedAmount.asStringColumn(),
|
||||
colCurrency,
|
||||
colMinerTxFee.asStringColumn(),
|
||||
colMixedTradeFee.asStringColumn(),
|
||||
colBuyerDeposit.asStringColumn(),
|
||||
colSellerDeposit.asStringColumn(),
|
||||
colOfferType,
|
||||
colClosingStatus);
|
||||
}
|
||||
|
||||
private void populateColumns() {
|
||||
trades.stream().forEachOrdered(t -> {
|
||||
colTradeId.addRow(t.getTradeId());
|
||||
colCreateDate.addRow(t.getDate());
|
||||
colMarket.addRow(toMarket.apply(t));
|
||||
((MixedPriceColumn) colPrice).addRow(t.getTradePrice(), isFiatTrade.test(t));
|
||||
colPriceDeviation.addRow(toPriceDeviation.apply(t));
|
||||
colAmountInBtc.addRow(t.getTradeAmountAsLong());
|
||||
colMixedAmount.addRow(t.getTradeVolume(), toDisplayedVolumePrecision.apply(t));
|
||||
colCurrency.addRow(toPaymentCurrencyCode.apply(t));
|
||||
colMinerTxFee.addRow(toMyMinerTxFee.apply(t));
|
||||
|
||||
if (t.getOffer().getIsBsqSwapOffer()) {
|
||||
// For BSQ Swaps, BTC buyer pays the BSQ trade fee for both sides (BTC seller pays no fee).
|
||||
var optionalTradeFeeBsq = isBtcSeller.test(t) ? 0L : toTradeFeeBsq.apply(t);
|
||||
colMixedTradeFee.addRow(optionalTradeFeeBsq, true);
|
||||
} else if (isTradeFeeBtc.test(t)) {
|
||||
colMixedTradeFee.addRow(toTradeFeeBtc.apply(t), false);
|
||||
} else {
|
||||
// V1 trade fee paid in BSQ.
|
||||
colMixedTradeFee.addRow(toTradeFeeBsq.apply(t), true);
|
||||
}
|
||||
|
||||
colBuyerDeposit.addRow(t.getOffer().getBuyerSecurityDeposit());
|
||||
colSellerDeposit.addRow(t.getOffer().getSellerSecurityDeposit());
|
||||
colOfferType.addRow(toOfferType.apply(t));
|
||||
colClosingStatus.addRow(t.getClosingStatus());
|
||||
});
|
||||
}
|
||||
}
|
@ -1,8 +1,25 @@
|
||||
/*
|
||||
* 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.table.builder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static bisq.cli.table.builder.TableType.FAILED_TRADE_TBL;
|
||||
import static bisq.cli.table.builder.TableType.FAILED_TRADES_TBL;
|
||||
|
||||
|
||||
|
||||
@ -15,7 +32,7 @@ import bisq.cli.table.column.MixedPriceColumn;
|
||||
class FailedTradeTableBuilder extends AbstractTradeListBuilder {
|
||||
|
||||
FailedTradeTableBuilder(List<?> protos) {
|
||||
super(FAILED_TRADE_TBL, protos);
|
||||
super(FAILED_TRADES_TBL, protos);
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
@ -29,7 +46,7 @@ class FailedTradeTableBuilder extends AbstractTradeListBuilder {
|
||||
colCurrency,
|
||||
colOfferType,
|
||||
colRole,
|
||||
colStatusDescription);
|
||||
colClosingStatus);
|
||||
}
|
||||
|
||||
private void populateColumns() {
|
||||
@ -43,7 +60,7 @@ class FailedTradeTableBuilder extends AbstractTradeListBuilder {
|
||||
colCurrency.addRow(toPaymentCurrencyCode.apply(t));
|
||||
colOfferType.addRow(toOfferType.apply(t));
|
||||
colRole.addRow(t.getRole());
|
||||
colStatusDescription.addRow("Failed");
|
||||
colClosingStatus.addRow("Failed");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,25 @@
|
||||
/*
|
||||
* 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.table.builder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static bisq.cli.table.builder.TableType.OPEN_TRADE_TBL;
|
||||
import static bisq.cli.table.builder.TableType.OPEN_TRADES_TBL;
|
||||
|
||||
|
||||
|
||||
@ -15,7 +32,7 @@ import bisq.cli.table.column.MixedPriceColumn;
|
||||
class OpenTradeTableBuilder extends AbstractTradeListBuilder {
|
||||
|
||||
OpenTradeTableBuilder(List<?> protos) {
|
||||
super(OPEN_TRADE_TBL, protos);
|
||||
super(OPEN_TRADES_TBL, protos);
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
|
@ -49,14 +49,14 @@ public class TableBuilder extends AbstractTableBuilder {
|
||||
return new BsqBalanceTableBuilder(protos).build();
|
||||
case BTC_BALANCE_TBL:
|
||||
return new BtcBalanceTableBuilder(protos).build();
|
||||
case CLOSED_TRADE_TBL:
|
||||
throw new UnsupportedOperationException("TODO return new ClosedTradeTableBuilder(protos).build()");
|
||||
case FAILED_TRADE_TBL:
|
||||
throw new UnsupportedOperationException("TODO return new FailedTradeTableBuilder(protos).build()");
|
||||
case CLOSED_TRADES_TBL:
|
||||
return new ClosedTradeTableBuilder(protos).build();
|
||||
case FAILED_TRADES_TBL:
|
||||
return new FailedTradeTableBuilder(protos).build();
|
||||
case OFFER_TBL:
|
||||
return new OfferTableBuilder(protos).build();
|
||||
case OPEN_TRADE_TBL:
|
||||
throw new UnsupportedOperationException("TODO return new OpenTradeTableBuilder(protos).build()");
|
||||
case OPEN_TRADES_TBL:
|
||||
return new OpenTradeTableBuilder(protos).build();
|
||||
case PAYMENT_ACCOUNT_TBL:
|
||||
return new PaymentAccountTableBuilder(protos).build();
|
||||
case TRADE_DETAIL_TBL:
|
||||
|
@ -36,8 +36,8 @@ class TableBuilderConstants {
|
||||
static final String COL_HEADER_UNLOCKING_BONDS_BALANCE = "Unlocking Bonds Balance";
|
||||
static final String COL_HEADER_UNVERIFIED_BALANCE = "Unverified Balance";
|
||||
static final String COL_HEADER_BSQ_SWAP_TRADE_ROLE = "My BSQ Swap Role";
|
||||
static final String COL_HEADER_BUYER_DEPOSIT = "Buyer Deposit";
|
||||
static final String COL_HEADER_SELLER_DEPOSIT = "Seller Deposit";
|
||||
static final String COL_HEADER_BUYER_DEPOSIT = "Buyer Deposit (BTC)";
|
||||
static final String COL_HEADER_SELLER_DEPOSIT = "Seller Deposit (BTC)";
|
||||
static final String COL_HEADER_CONFIRMATIONS = "Confirmations";
|
||||
static final String COL_HEADER_DEVIATION = "Deviation";
|
||||
static final String COL_HEADER_IS_USED_ADDRESS = "Is Used";
|
||||
|
@ -25,10 +25,10 @@ public enum TableType {
|
||||
ADDRESS_BALANCE_TBL,
|
||||
BSQ_BALANCE_TBL,
|
||||
BTC_BALANCE_TBL,
|
||||
CLOSED_TRADE_TBL,
|
||||
FAILED_TRADE_TBL,
|
||||
CLOSED_TRADES_TBL,
|
||||
FAILED_TRADES_TBL,
|
||||
OFFER_TBL,
|
||||
OPEN_TRADE_TBL,
|
||||
OPEN_TRADES_TBL,
|
||||
PAYMENT_ACCOUNT_TBL,
|
||||
TRADE_DETAIL_TBL,
|
||||
TRANSACTION_TBL
|
||||
|
@ -32,9 +32,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.*;
|
||||
import static bisq.cli.table.builder.TableType.CLOSED_TRADE_TBL;
|
||||
import static bisq.cli.table.builder.TableType.FAILED_TRADE_TBL;
|
||||
import static bisq.cli.table.builder.TableType.OPEN_TRADE_TBL;
|
||||
import static bisq.cli.table.builder.TableType.CLOSED_TRADES_TBL;
|
||||
import static bisq.cli.table.builder.TableType.FAILED_TRADES_TBL;
|
||||
import static bisq.cli.table.builder.TableType.OPEN_TRADES_TBL;
|
||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||
import static bisq.cli.table.column.AltcoinColumn.DISPLAY_MODE.ALTCOIN_OFFER_VOLUME;
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT;
|
||||
@ -75,9 +75,9 @@ class TradeTableColumnSupplier {
|
||||
}
|
||||
|
||||
private final Supplier<Boolean> isTradeDetailTblBuilder = () -> getTableType().equals(TRADE_DETAIL_TBL);
|
||||
private final Supplier<Boolean> isOpenTradeTblBuilder = () -> getTableType().equals(OPEN_TRADE_TBL);
|
||||
private final Supplier<Boolean> isClosedTradeTblBuilder = () -> getTableType().equals(CLOSED_TRADE_TBL);
|
||||
private final Supplier<Boolean> isFailedTradeTblBuilder = () -> getTableType().equals(FAILED_TRADE_TBL);
|
||||
private final Supplier<Boolean> isOpenTradeTblBuilder = () -> getTableType().equals(OPEN_TRADES_TBL);
|
||||
private final Supplier<Boolean> isClosedTradeTblBuilder = () -> getTableType().equals(CLOSED_TRADES_TBL);
|
||||
private final Supplier<Boolean> isFailedTradeTblBuilder = () -> getTableType().equals(FAILED_TRADES_TBL);
|
||||
private final Supplier<TradeInfo> firstRow = () -> getTrades().get(0);
|
||||
private final Predicate<OfferInfo> isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC");
|
||||
private final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
|
||||
|
52
cli/src/test/java/bisq/cli/GetTradesSmokeTest.java
Normal file
52
cli/src/test/java/bisq/cli/GetTradesSmokeTest.java
Normal file
@ -0,0 +1,52 @@
|
||||
package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.CLOSED;
|
||||
import static java.lang.System.out;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class GetTradesSmokeTest extends AbstractCliTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
GetTradesSmokeTest test = new GetTradesSmokeTest();
|
||||
test.printAlicesTrades();
|
||||
test.printBobsTrades();
|
||||
}
|
||||
|
||||
private final List<TradeInfo> openTrades;
|
||||
private final List<TradeInfo> closedTrades;
|
||||
|
||||
public GetTradesSmokeTest() {
|
||||
super();
|
||||
this.openTrades = aliceClient.getOpenTrades();
|
||||
this.closedTrades = aliceClient.getTradeHistory(CLOSED);
|
||||
}
|
||||
|
||||
private void printAlicesTrades() {
|
||||
out.println("ALICE'S OPEN TRADES");
|
||||
openTrades.stream().forEachOrdered(t -> printTrade(aliceClient, t.getTradeId()));
|
||||
out.println("ALICE'S CLOSED TRADES");
|
||||
closedTrades.stream().forEachOrdered(t -> printTrade(aliceClient, t.getTradeId()));
|
||||
}
|
||||
|
||||
private void printBobsTrades() {
|
||||
out.println("BOB'S OPEN TRADES");
|
||||
openTrades.stream().forEachOrdered(t -> printTrade(bobClient, t.getTradeId()));
|
||||
out.println("BOB'S CLOSED TRADES");
|
||||
closedTrades.stream().forEachOrdered(t -> printTrade(bobClient, t.getTradeId()));
|
||||
}
|
||||
|
||||
private void printTrade(GrpcClient client, String tradeId) {
|
||||
var trade = client.getTrade(tradeId);
|
||||
var tbl = new TableBuilder(TRADE_DETAIL_TBL, trade).build().toString();
|
||||
out.println(tbl);
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.trade.bisq_v1.TradeResultHandler;
|
||||
import bisq.core.trade.model.Tradable;
|
||||
import bisq.core.trade.model.TradeModel;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
|
||||
@ -37,6 +38,8 @@ import bisq.common.config.Config;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
|
||||
import bisq.proto.grpc.GetTradesRequest;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -327,12 +330,16 @@ public class CoreApi {
|
||||
return coreTradesService.getTradeModel(tradeId);
|
||||
}
|
||||
|
||||
public String getTradeRole(String tradeId) {
|
||||
return coreTradesService.getTradeRole(tradeId);
|
||||
public List<TradeModel> getOpenTrades() {
|
||||
return coreTradesService.getOpenTrades();
|
||||
}
|
||||
|
||||
public String getBsqSwapTradeRole(BsqSwapTrade bsqSwapTrade) {
|
||||
return coreTradesService.getBsqSwapTradeRole(bsqSwapTrade);
|
||||
public List<TradeModel> getTradeHistory(GetTradesRequest.Category category) {
|
||||
return coreTradesService.getTradeHistory(category);
|
||||
}
|
||||
|
||||
public String getTradeRole(TradeModel tradeModel) {
|
||||
return coreTradesService.getTradeRole(tradeModel);
|
||||
}
|
||||
|
||||
public void failTrade(String tradeId) {
|
||||
@ -343,6 +350,14 @@ public class CoreApi {
|
||||
coreTradesService.unFailTrade(tradeId);
|
||||
}
|
||||
|
||||
public List<OpenOffer> getCanceledOpenOffers() {
|
||||
return coreTradesService.getCanceledOpenOffers();
|
||||
}
|
||||
|
||||
public String getClosedTradeStateAsString(Tradable tradable) {
|
||||
return coreTradesService.getClosedTradeStateAsString(tradable);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Wallets
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -21,13 +21,16 @@ import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferUtil;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.bisq_v1.TakeOfferModel;
|
||||
import bisq.core.offer.bsq_swap.BsqSwapTakeOfferModel;
|
||||
import bisq.core.trade.ClosedTradableFormatter;
|
||||
import bisq.core.trade.ClosedTradableManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.bisq_v1.FailedTradesManager;
|
||||
import bisq.core.trade.bisq_v1.TradeResultHandler;
|
||||
import bisq.core.trade.bisq_v1.TradeUtil;
|
||||
import bisq.core.trade.bsq_swap.BsqSwapTradeManager;
|
||||
import bisq.core.trade.model.Tradable;
|
||||
import bisq.core.trade.model.TradeModel;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
@ -39,17 +42,22 @@ import bisq.core.util.validation.BtcAddressValidator;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
|
||||
import bisq.proto.grpc.GetTradesRequest;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.btc.model.AddressEntry.Context.TRADE_PAYOUT;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.CLOSED;
|
||||
import static java.lang.String.format;
|
||||
|
||||
@Singleton
|
||||
@ -63,7 +71,9 @@ class CoreTradesService {
|
||||
private final CoreWalletsService coreWalletsService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final OfferUtil offerUtil;
|
||||
private final BsqSwapTradeManager bsqSwapTradeManager;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
private final ClosedTradableFormatter closedTradableFormatter;
|
||||
private final FailedTradesManager failedTradesManager;
|
||||
private final TakeOfferModel takeOfferModel;
|
||||
private final BsqSwapTakeOfferModel bsqSwapTakeOfferModel;
|
||||
@ -76,7 +86,9 @@ class CoreTradesService {
|
||||
CoreWalletsService coreWalletsService,
|
||||
BtcWalletService btcWalletService,
|
||||
OfferUtil offerUtil,
|
||||
BsqSwapTradeManager bsqSwapTradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
ClosedTradableFormatter closedTradableFormatter,
|
||||
FailedTradesManager failedTradesManager,
|
||||
TakeOfferModel takeOfferModel,
|
||||
BsqSwapTakeOfferModel bsqSwapTakeOfferModel,
|
||||
@ -87,7 +99,9 @@ class CoreTradesService {
|
||||
this.coreWalletsService = coreWalletsService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.offerUtil = offerUtil;
|
||||
this.bsqSwapTradeManager = bsqSwapTradeManager;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.closedTradableFormatter = closedTradableFormatter;
|
||||
this.failedTradesManager = failedTradesManager;
|
||||
this.takeOfferModel = takeOfferModel;
|
||||
this.bsqSwapTakeOfferModel = bsqSwapTakeOfferModel;
|
||||
@ -252,16 +266,18 @@ class CoreTradesService {
|
||||
new IllegalArgumentException(format("trade with id '%s' not found", tradeId)));
|
||||
}
|
||||
|
||||
String getBsqSwapTradeRole(BsqSwapTrade bsqSwapTrade) {
|
||||
String getTradeRole(TradeModel tradeModel) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
return tradeUtil.getRole(bsqSwapTrade);
|
||||
}
|
||||
|
||||
String getTradeRole(String tradeId) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
return tradeUtil.getRole(getTrade(tradeId));
|
||||
var isBsqSwapTrade = tradeModel instanceof BsqSwapTrade;
|
||||
try {
|
||||
return isBsqSwapTrade
|
||||
? tradeUtil.getRole((BsqSwapTrade) tradeModel)
|
||||
: tradeUtil.getRole((Trade) tradeModel);
|
||||
} catch (Exception ex) {
|
||||
log.error("Role not found for trade with Id {}.", tradeModel.getId(), ex);
|
||||
return "Not Available";
|
||||
}
|
||||
}
|
||||
|
||||
Trade getTrade(String tradeId) {
|
||||
@ -273,15 +289,32 @@ class CoreTradesService {
|
||||
));
|
||||
}
|
||||
|
||||
List<TradeModel> getOpenTrades() {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
return tradeManager.getTrades().stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<TradeModel> getTradeHistory(GetTradesRequest.Category category) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
if (category.equals(CLOSED)) {
|
||||
var closedTrades = closedTradableManager.getClosedTrades().stream()
|
||||
.map(t -> (TradeModel) t)
|
||||
.collect(Collectors.toList());
|
||||
closedTrades.addAll(bsqSwapTradeManager.getBsqSwapTrades());
|
||||
return closedTrades;
|
||||
} else {
|
||||
var failedV1Trades = failedTradesManager.getTrades();
|
||||
return failedV1Trades.stream().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
void failTrade(String tradeId) {
|
||||
// TODO Recommend that API users should use this method with extra care because
|
||||
// TODO Recommend API users call this method with extra care because
|
||||
// the API lacks methods for diagnosing trade problems, and does not support
|
||||
// interaction with mediators. Users may accidentally fail valid trades,
|
||||
// although they can easily be un-failed with the 'unfailtrade' method.
|
||||
// The 'failtrade' and 'unfailtrade' methods are implemented at this early
|
||||
// stage of API development to help efficiently test a new
|
||||
// 'gettrades --category=<open|closed|failed>'
|
||||
// method currently in development.
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
|
||||
@ -304,6 +337,14 @@ class CoreTradesService {
|
||||
});
|
||||
}
|
||||
|
||||
List<OpenOffer> getCanceledOpenOffers() {
|
||||
return closedTradableManager.getCanceledOpenOffers();
|
||||
}
|
||||
|
||||
String getClosedTradeStateAsString(Tradable tradable) {
|
||||
return closedTradableFormatter.getStateAsString(tradable);
|
||||
}
|
||||
|
||||
private Optional<Trade> getOpenTrade(String tradeId) {
|
||||
return tradeManager.getTradeById(tradeId);
|
||||
}
|
||||
|
@ -25,6 +25,9 @@ import bisq.common.Payload;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import static bisq.core.offer.OfferDirection.BUY;
|
||||
import static bisq.core.offer.OfferDirection.SELL;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
public class BsqSwapTradeInfo implements Payload {
|
||||
@ -41,6 +44,8 @@ public class BsqSwapTradeInfo implements Payload {
|
||||
private final String takerBtcAddress;
|
||||
private final long numConfirmations;
|
||||
private final String errorMessage;
|
||||
private final long payout;
|
||||
private final long swapPeerPayout;
|
||||
|
||||
public BsqSwapTradeInfo(BsqSwapTradeInfoBuilder builder) {
|
||||
this.txId = builder.getTxId();
|
||||
@ -55,6 +60,8 @@ public class BsqSwapTradeInfo implements Payload {
|
||||
this.takerBtcAddress = builder.getTakerBtcAddress();
|
||||
this.numConfirmations = builder.getNumConfirmations();
|
||||
this.errorMessage = builder.getErrorMessage();
|
||||
this.payout = builder.getPayout();
|
||||
this.swapPeerPayout = builder.getSwapPeerPayout();
|
||||
}
|
||||
|
||||
public static BsqSwapTradeInfo toBsqSwapTradeInfo(BsqSwapTrade trade,
|
||||
@ -66,12 +73,20 @@ public class BsqSwapTradeInfo implements Payload {
|
||||
var makerBtcAddress = wasMyOffer ? protocolModel.getBtcAddress() : swapPeer.getBtcAddress();
|
||||
var takerBsqAddress = wasMyOffer ? swapPeer.getBsqAddress() : protocolModel.getBsqAddress();
|
||||
var takerBtcAddress = wasMyOffer ? swapPeer.getBtcAddress() : protocolModel.getBtcAddress();
|
||||
// A BSQ Swap trade fee is paid in full by the BTC buyer (selling BSQ).
|
||||
// The transferred BSQ (payout) is reduced by the fee of the peer.
|
||||
var makerTradeFee = wasMyOffer && trade.getOffer().getDirection().equals(BUY)
|
||||
? trade.getMakerFeeAsLong()
|
||||
: 0L;
|
||||
var takerTradeFee = !wasMyOffer && trade.getOffer().getDirection().equals(SELL)
|
||||
? trade.getTakerFeeAsLong()
|
||||
: 0L;
|
||||
return new BsqSwapTradeInfoBuilder()
|
||||
.withTxId(trade.getTxId())
|
||||
.withBsqTradeAmount(trade.getBsqTradeAmount())
|
||||
.withBtcTradeAmount(trade.getAmountAsLong())
|
||||
.withBsqMakerTradeFee(trade.getMakerFeeAsLong())
|
||||
.withBsqTakerTradeFee(trade.getTakerFeeAsLong())
|
||||
.withBsqMakerTradeFee(makerTradeFee)
|
||||
.withBsqTakerTradeFee(takerTradeFee)
|
||||
.withTxFeePerVbyte(trade.getTxFeePerVbyte())
|
||||
.withMakerBsqAddress(makerBsqAddress)
|
||||
.withMakerBtcAddress(makerBtcAddress)
|
||||
@ -79,6 +94,8 @@ public class BsqSwapTradeInfo implements Payload {
|
||||
.withTakerBtcAddress(takerBtcAddress)
|
||||
.withNumConfirmations(numConfirmations)
|
||||
.withErrorMessage(trade.getErrorMessage())
|
||||
.withPayout(protocolModel.getPayout())
|
||||
.withSwapPeerPayout(protocolModel.getTradePeer().getPayout())
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -101,6 +118,9 @@ public class BsqSwapTradeInfo implements Payload {
|
||||
.setTakerBtcAddress(takerBtcAddress != null ? takerBtcAddress : "")
|
||||
.setTakerBtcAddress(takerBtcAddress != null ? takerBtcAddress : "")
|
||||
.setNumConfirmations(numConfirmations)
|
||||
.setErrorMessage(errorMessage != null ? errorMessage : "")
|
||||
.setPayout(payout)
|
||||
.setSwapPeerPayout(swapPeerPayout)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -118,6 +138,8 @@ public class BsqSwapTradeInfo implements Payload {
|
||||
.withTakerBtcAddress(proto.getTakerBtcAddress())
|
||||
.withNumConfirmations(proto.getNumConfirmations())
|
||||
.withErrorMessage(proto.getErrorMessage())
|
||||
.withPayout(proto.getPayout())
|
||||
.withSwapPeerPayout(proto.getSwapPeerPayout())
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -136,6 +158,8 @@ public class BsqSwapTradeInfo implements Payload {
|
||||
", takerBtcAddress='" + takerBtcAddress + '\'' +
|
||||
", numConfirmations='" + numConfirmations + '\'' +
|
||||
", errorMessage='" + errorMessage + '\'' +
|
||||
", payout=" + payout +
|
||||
", swapPeerPayout=" + swapPeerPayout +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.api.model;
|
||||
|
||||
import bisq.core.api.model.builder.TradeInfoV1Builder;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
|
||||
import static bisq.core.api.model.ContractInfo.emptyContract;
|
||||
import static bisq.core.api.model.OfferInfo.toMyOfferInfo;
|
||||
import static bisq.core.offer.OpenOffer.State.CANCELED;
|
||||
import static java.lang.String.format;
|
||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||
|
||||
/**
|
||||
* Builds a TradeInfo instance from an OpenOffer with State = CANCELED.
|
||||
*/
|
||||
public class CanceledTradeInfo {
|
||||
|
||||
public static TradeInfo toCanceledTradeInfo(OpenOffer myCanceledOpenOffer) {
|
||||
if (!myCanceledOpenOffer.getState().equals(CANCELED))
|
||||
throw new IllegalArgumentException(format("offer '%s' is not canceled", myCanceledOpenOffer.getId()));
|
||||
|
||||
Offer offer = myCanceledOpenOffer.getOffer();
|
||||
OfferInfo offerInfo = toMyOfferInfo(offer);
|
||||
|
||||
return new TradeInfoV1Builder() // TODO May need to use BsqSwapTradeInfoBuilder?
|
||||
.withOffer(offerInfo)
|
||||
.withTradeId(myCanceledOpenOffer.getId())
|
||||
.withShortId(myCanceledOpenOffer.getShortId())
|
||||
.withDate(myCanceledOpenOffer.getDate().getTime())
|
||||
.withRole("")
|
||||
.withIsCurrencyForTakerFeeBtc(offer.isCurrencyForMakerFeeBtc())
|
||||
.withTxFeeAsLong(offer.getTxFee().value)
|
||||
.withTakerFeeAsLong(offer.getMakerFee().value)
|
||||
.withTakerFeeTxId("") // Ignored
|
||||
.withDepositTxId("") // Ignored
|
||||
.withPayoutTxId("") // Ignored
|
||||
.withTradeAmountAsLong(0) // Ignored
|
||||
.withTradePrice(offer.getPrice().getValue())
|
||||
.withTradeVolume(0) // Ignored
|
||||
.withTradingPeerNodeAddress("") // Ignored
|
||||
.withState("") // Ignored
|
||||
.withPhase("") // Ignored
|
||||
.withTradePeriodState("") // Ignored
|
||||
.withIsDepositPublished(false) // Ignored
|
||||
.withIsDepositConfirmed(false) // Ignored
|
||||
.withIsFiatSent(false) // Ignored
|
||||
.withIsFiatReceived(false) // Ignored
|
||||
.withIsPayoutPublished(false) // Ignored
|
||||
.withIsWithdrawn(false) // Ignored
|
||||
.withContractAsJson("") // Ignored
|
||||
.withContract(emptyContract.get()) // Ignored
|
||||
.withClosingStatus(capitalize(CANCELED.name().toLowerCase()))
|
||||
.build();
|
||||
}
|
||||
}
|
@ -32,6 +32,8 @@ import static bisq.core.api.model.BsqSwapTradeInfo.toBsqSwapTradeInfo;
|
||||
import static bisq.core.api.model.OfferInfo.toMyOfferInfo;
|
||||
import static bisq.core.api.model.OfferInfo.toOfferInfo;
|
||||
import static bisq.core.api.model.PaymentAccountPayloadInfo.toPaymentAccountPayloadInfo;
|
||||
import static bisq.core.offer.OfferDirection.BUY;
|
||||
import static bisq.core.offer.OfferDirection.SELL;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@ -71,6 +73,7 @@ public class TradeInfo implements Payload {
|
||||
private final ContractInfo contract;
|
||||
// Optional BSQ swap trade protocol details (post v1).
|
||||
private BsqSwapTradeInfo bsqSwapTradeInfo;
|
||||
private final String closingStatus;
|
||||
|
||||
public TradeInfo(TradeInfoV1Builder builder) {
|
||||
this.offer = builder.getOffer();
|
||||
@ -100,23 +103,27 @@ public class TradeInfo implements Payload {
|
||||
this.contractAsJson = builder.getContractAsJson();
|
||||
this.contract = builder.getContract();
|
||||
this.bsqSwapTradeInfo = null;
|
||||
this.closingStatus = builder.getClosingStatus();
|
||||
}
|
||||
|
||||
public static TradeInfo toNewTradeInfo(BsqSwapTrade trade, String role) {
|
||||
// Always called by the taker, isMyOffer=false.
|
||||
return toTradeInfo(trade, role, false, 0);
|
||||
return toTradeInfo(trade, role, false, 0, "Pending");
|
||||
}
|
||||
|
||||
public static TradeInfo toNewTradeInfo(Trade trade) {
|
||||
// Always called by the taker, isMyOffer=false.
|
||||
return toTradeInfo(trade, null, false);
|
||||
return toTradeInfo(trade, null, false, "Pending");
|
||||
}
|
||||
|
||||
public static TradeInfo toTradeInfo(TradeModel tradeModel, String role, boolean isMyOffer) {
|
||||
public static TradeInfo toTradeInfo(TradeModel tradeModel,
|
||||
String role,
|
||||
boolean isMyOffer,
|
||||
String closingStatus) {
|
||||
if (tradeModel instanceof Trade)
|
||||
return toTradeInfo((Trade) tradeModel, role, isMyOffer);
|
||||
return toTradeInfo((Trade) tradeModel, role, isMyOffer, closingStatus);
|
||||
else if (tradeModel instanceof BsqSwapTrade)
|
||||
return toTradeInfo(tradeModel, role, isMyOffer);
|
||||
return toTradeInfo(tradeModel, role, isMyOffer, closingStatus);
|
||||
else
|
||||
throw new IllegalStateException("unsupported trade type: " + tradeModel.getClass().getSimpleName());
|
||||
}
|
||||
@ -124,8 +131,21 @@ public class TradeInfo implements Payload {
|
||||
public static TradeInfo toTradeInfo(BsqSwapTrade bsqSwapTrade,
|
||||
String role,
|
||||
boolean isMyOffer,
|
||||
int numConfirmations) {
|
||||
int numConfirmations,
|
||||
String closingStatus) {
|
||||
OfferInfo offerInfo = isMyOffer ? toMyOfferInfo(bsqSwapTrade.getOffer()) : toOfferInfo(bsqSwapTrade.getOffer());
|
||||
// A BSQ Swap miner tx fee is paid in full by the BTC seller (buying BSQ).
|
||||
// The BTC buyer's payout = tradeamount minus his share of miner fee.
|
||||
var isBtcSeller = (isMyOffer && bsqSwapTrade.getOffer().getDirection().equals(SELL))
|
||||
|| (!isMyOffer && bsqSwapTrade.getOffer().getDirection().equals(BUY));
|
||||
var txFeeInBtc = isBtcSeller
|
||||
? bsqSwapTrade.getTxFee().value
|
||||
: 0L;
|
||||
// A BSQ Swap trade fee is paid in full by the BTC buyer (selling BSQ).
|
||||
// The transferred BSQ (payout) is reduced by the peer's trade fee.
|
||||
var takerFeeInBsq = !isMyOffer && bsqSwapTrade.getOffer().getDirection().equals(SELL)
|
||||
? bsqSwapTrade.getTakerFeeAsLong()
|
||||
: 0L;
|
||||
TradeInfo tradeInfo = new TradeInfoV1Builder()
|
||||
.withOffer(offerInfo)
|
||||
.withTradeId(bsqSwapTrade.getId())
|
||||
@ -133,24 +153,28 @@ public class TradeInfo implements Payload {
|
||||
.withDate(bsqSwapTrade.getDate().getTime())
|
||||
.withRole(role == null ? "" : role)
|
||||
.withIsCurrencyForTakerFeeBtc(false) // BSQ Swap fees always paid in BSQ.
|
||||
.withTxFeeAsLong(bsqSwapTrade.getTxFee().value)
|
||||
.withTakerFeeAsLong(bsqSwapTrade.getTakerFeeAsLong())
|
||||
// N/A: .withTakerFeeTxId(""), .withDepositTxId(""), .withPayoutTxId("")
|
||||
.withTxFeeAsLong(txFeeInBtc)
|
||||
.withTakerFeeAsLong(takerFeeInBsq)
|
||||
// N/A for bsq-swaps: .withTakerFeeTxId(""), .withDepositTxId(""), .withPayoutTxId("")
|
||||
.withTradeAmountAsLong(bsqSwapTrade.getAmountAsLong())
|
||||
.withTradePrice(bsqSwapTrade.getPrice().getValue())
|
||||
.withTradeVolume(bsqSwapTrade.getVolume() == null ? 0 : bsqSwapTrade.getVolume().getValue())
|
||||
.withTradingPeerNodeAddress(requireNonNull(bsqSwapTrade.getTradingPeerNodeAddress().getFullAddress()))
|
||||
.withState(bsqSwapTrade.getTradeState().name())
|
||||
.withPhase(bsqSwapTrade.getTradePhase().name())
|
||||
// N/A: .withTradePeriodState(""), .withIsDepositPublished(false), .withIsDepositConfirmed(false)
|
||||
// N/A: .withIsFiatSent(false), .withIsFiatReceived(false), .withIsPayoutPublished(false)
|
||||
// N/A: .withIsWithdrawn(false), .withContractAsJson(""), .withContract(null)
|
||||
// N/A for bsq-swaps: .withTradePeriodState(""), .withIsDepositPublished(false), .withIsDepositConfirmed(false)
|
||||
// N/A for bsq-swaps: .withIsFiatSent(false), .withIsFiatReceived(false), .withIsPayoutPublished(false)
|
||||
// N/A for bsq-swaps: .withIsWithdrawn(false), .withContractAsJson(""), .withContract(null)
|
||||
.withClosingStatus(closingStatus)
|
||||
.build();
|
||||
tradeInfo.bsqSwapTradeInfo = toBsqSwapTradeInfo(bsqSwapTrade, isMyOffer, numConfirmations);
|
||||
return tradeInfo;
|
||||
}
|
||||
|
||||
private static TradeInfo toTradeInfo(Trade trade, String role, boolean isMyOffer) {
|
||||
private static TradeInfo toTradeInfo(Trade trade,
|
||||
String role,
|
||||
boolean isMyOffer,
|
||||
String closingStatus) {
|
||||
ContractInfo contractInfo;
|
||||
if (trade.getContract() != null) {
|
||||
Contract contract = trade.getContract();
|
||||
@ -198,6 +222,7 @@ public class TradeInfo implements Payload {
|
||||
.withIsWithdrawn(trade.isWithdrawn())
|
||||
.withContractAsJson(trade.getContractAsJson())
|
||||
.withContract(contractInfo)
|
||||
.withClosingStatus(closingStatus)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -232,8 +257,8 @@ public class TradeInfo implements Payload {
|
||||
.setIsFiatSent(isFiatSent)
|
||||
.setIsFiatReceived(isFiatReceived)
|
||||
.setIsPayoutPublished(isPayoutPublished)
|
||||
.setIsWithdrawn(isWithdrawn);
|
||||
|
||||
.setIsWithdrawn(isWithdrawn)
|
||||
.setClosingStatus(closingStatus);
|
||||
if (offer.isBsqSwapOffer()) {
|
||||
protoBuilder.setBsqSwapTradeInfo(bsqSwapTradeInfo.toProtoMessage());
|
||||
} else {
|
||||
@ -272,6 +297,7 @@ public class TradeInfo implements Payload {
|
||||
.withIsWithdrawn(proto.getIsWithdrawn())
|
||||
.withContractAsJson(proto.getContractAsJson())
|
||||
.withContract((ContractInfo.fromProto(proto.getContract())))
|
||||
.withClosingStatus(proto.getClosingStatus())
|
||||
.build();
|
||||
|
||||
if (proto.getOffer().getIsBsqSwapOffer())
|
||||
@ -310,6 +336,7 @@ public class TradeInfo implements Payload {
|
||||
", contractAsJson=" + contractAsJson + "\n" +
|
||||
", contract=" + contract + "\n" +
|
||||
", bsqSwapTradeInfo=" + bsqSwapTradeInfo + "\n" +
|
||||
", closingStatus=" + closingStatus + "\n" +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,8 @@ public final class BsqSwapTradeInfoBuilder {
|
||||
private String takerBtcAddress;
|
||||
private long numConfirmations;
|
||||
private String errorMessage;
|
||||
private long payout;
|
||||
private long swapPeerPayout;
|
||||
|
||||
public BsqSwapTradeInfoBuilder withTxId(String txId) {
|
||||
this.txId = txId;
|
||||
@ -106,6 +108,16 @@ public final class BsqSwapTradeInfoBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public BsqSwapTradeInfoBuilder withPayout(long payout) {
|
||||
this.payout = payout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BsqSwapTradeInfoBuilder withSwapPeerPayout(long swapPeerPayout) {
|
||||
this.swapPeerPayout = swapPeerPayout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BsqSwapTradeInfo build() {
|
||||
return new BsqSwapTradeInfo(this);
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ public final class TradeInfoV1Builder {
|
||||
private boolean isWithdrawn;
|
||||
private String contractAsJson;
|
||||
private ContractInfo contract;
|
||||
private String closingStatus;
|
||||
|
||||
public TradeInfoV1Builder withOffer(OfferInfo offer) {
|
||||
this.offer = offer;
|
||||
@ -189,6 +190,12 @@ public final class TradeInfoV1Builder {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public TradeInfoV1Builder withClosingStatus(String closingStatus) {
|
||||
this.closingStatus = closingStatus;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TradeInfo build() {
|
||||
return new TradeInfo(this);
|
||||
}
|
||||
|
@ -53,6 +53,8 @@ import static bisq.core.util.FormattingUtils.formatPercentagePrice;
|
||||
import static bisq.core.util.FormattingUtils.formatToPercentWithSymbol;
|
||||
import static bisq.core.util.VolumeUtil.formatVolume;
|
||||
import static bisq.core.util.VolumeUtil.formatVolumeWithCode;
|
||||
import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.BUILDING;
|
||||
import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.PENDING;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
@ -212,11 +214,13 @@ public class ClosedTradableFormatter {
|
||||
} else if (isBsqSwapTrade(tradable)) {
|
||||
String txId = castToBsqSwapTrade(tradable).getTxId();
|
||||
TransactionConfidence confidence = bsqWalletService.getConfidenceForTxId(txId);
|
||||
if (confidence != null && confidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
|
||||
if (confidence != null && confidence.getConfidenceType() == BUILDING) {
|
||||
return Res.get("confidence.confirmed.short");
|
||||
} else if (confidence != null && confidence.getConfidenceType() == PENDING) {
|
||||
return Res.get("confidence.pending");
|
||||
} else {
|
||||
log.warn("Unexpected confidence in a BSQ swap trade which has been moved to closed trades. " +
|
||||
"This could happen at a wallet SPV resycn or a reorg. confidence={} tradeID={}",
|
||||
"This could happen at a wallet SPV resync or a reorg. confidence={} tradeID={}",
|
||||
confidence, tradable.getId());
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.bisq_v1.CleanupMailboxMessagesService;
|
||||
import bisq.core.trade.bisq_v1.DumpDelayedPayoutTx;
|
||||
@ -60,6 +61,7 @@ import java.util.stream.Stream;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.offer.OpenOffer.State.CANCELED;
|
||||
import static bisq.core.trade.ClosedTradableUtil.castToTrade;
|
||||
import static bisq.core.trade.ClosedTradableUtil.castToTradeModel;
|
||||
import static bisq.core.trade.ClosedTradableUtil.isBsqSwapTrade;
|
||||
@ -154,6 +156,13 @@ public class ClosedTradableManager implements PersistedDataHost {
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public List<OpenOffer> getCanceledOpenOffers() {
|
||||
return ImmutableList.copyOf(getObservableList().stream()
|
||||
.filter(e -> (e instanceof OpenOffer) && ((OpenOffer) e).getState().equals(CANCELED))
|
||||
.map(e -> (OpenOffer) e)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public Optional<Tradable> getTradableById(String id) {
|
||||
return closedTradables.stream().filter(e -> e.getId().equals(id)).findFirst();
|
||||
}
|
||||
|
@ -108,6 +108,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@ -870,6 +871,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
.map(tradeModel -> (Trade) tradeModel);
|
||||
}
|
||||
|
||||
public List<Trade> getTrades() {
|
||||
return getObservableList().stream()
|
||||
.filter(t -> !t.hasFailed())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void removeTrade(Trade trade) {
|
||||
if (tradableList.remove(trade)) {
|
||||
requestPersistence();
|
||||
|
@ -40,6 +40,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -122,6 +123,10 @@ public class FailedTradesManager implements PersistedDataHost {
|
||||
return failedTrades.getObservableList();
|
||||
}
|
||||
|
||||
public List<Trade> getTrades() {
|
||||
return getObservableList().stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Optional<Trade> getTradeById(String id) {
|
||||
return failedTrades.stream().filter(e -> e.getId().equals(id)).findFirst();
|
||||
}
|
||||
|
33
core/src/main/resources/help/gettrades-help.txt
Normal file
33
core/src/main/resources/help/gettrades-help.txt
Normal file
@ -0,0 +1,33 @@
|
||||
gettrades
|
||||
|
||||
NAME
|
||||
----
|
||||
gettrades - get open, closed, or failed trades
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
gettrades
|
||||
--category=<open|closed|failed>
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
Displays list of all currently open, closed, or failed trades.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
--category
|
||||
The category of trade summaries: open (pending), closed (historical), and failed.
|
||||
The default category option value is open.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
To see summaries of all open (pending) trades:
|
||||
$ ./bisq-cli --password=xyz --port=9998 gettrades
|
||||
OR
|
||||
$ ./bisq-cli --password=xyz --port=9998 gettrades --category=open
|
||||
|
||||
To see summaries of all closed (historical) trades:
|
||||
$ ./bisq-cli --password=xyz --port=9998 gettrades ---category=closed
|
||||
|
||||
To see summaries of all failed trades:
|
||||
$ ./bisq-cli --password=xyz --port=9998 gettrades ---category=failed
|
@ -3232,6 +3232,7 @@ confidence.seen=Seen by {0} peer(s) / 0 confirmations
|
||||
confidence.confirmed=Confirmed in {0} block(s)
|
||||
confidence.invalid=Transaction is invalid
|
||||
confidence.confirmed.short=Confirmed
|
||||
confidence.pending=Pending
|
||||
|
||||
peerInfo.title=Peer info
|
||||
peerInfo.nrOfTrades=Number of completed trades
|
||||
|
@ -18,7 +18,9 @@
|
||||
package bisq.daemon.grpc;
|
||||
|
||||
import bisq.core.api.CoreApi;
|
||||
import bisq.core.api.model.CanceledTradeInfo;
|
||||
import bisq.core.api.model.TradeInfo;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.trade.model.TradeModel;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
|
||||
@ -33,6 +35,8 @@ import bisq.proto.grpc.FailTradeReply;
|
||||
import bisq.proto.grpc.FailTradeRequest;
|
||||
import bisq.proto.grpc.GetTradeReply;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
import bisq.proto.grpc.GetTradesReply;
|
||||
import bisq.proto.grpc.GetTradesRequest;
|
||||
import bisq.proto.grpc.TakeOfferReply;
|
||||
import bisq.proto.grpc.TakeOfferRequest;
|
||||
import bisq.proto.grpc.UnFailTradeReply;
|
||||
@ -45,15 +49,22 @@ import io.grpc.stub.StreamObserver;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.api.model.TradeInfo.toNewTradeInfo;
|
||||
import static bisq.core.api.model.TradeInfo.toTradeInfo;
|
||||
import static bisq.core.trade.model.bsq_swap.BsqSwapTrade.State.COMPLETED;
|
||||
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.CLOSED;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.OPEN;
|
||||
import static bisq.proto.grpc.TradesGrpc.*;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
@ -129,6 +140,24 @@ class GrpcTradesService extends TradesImplBase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTrades(GetTradesRequest req,
|
||||
StreamObserver<GetTradesReply> responseObserver) {
|
||||
try {
|
||||
var category = req.getCategory();
|
||||
var trades = category.equals(OPEN)
|
||||
? coreApi.getOpenTrades()
|
||||
: coreApi.getTradeHistory(category);
|
||||
var reply = buildGetTradesReply(trades, category);
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
} catch (IllegalArgumentException cause) {
|
||||
exceptionHandler.handleExceptionAsWarning(log, "getTrades", cause, responseObserver);
|
||||
} catch (Throwable cause) {
|
||||
exceptionHandler.handleException(log, cause, responseObserver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void confirmPaymentStarted(ConfirmPaymentStartedRequest req,
|
||||
StreamObserver<ConfirmPaymentStartedReply> responseObserver) {
|
||||
@ -218,6 +247,7 @@ class GrpcTradesService extends TradesImplBase {
|
||||
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
|
||||
new HashMap<>() {{
|
||||
put(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
||||
put(getGetTradesMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
||||
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
||||
put(getConfirmPaymentStartedMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
||||
put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
||||
@ -245,10 +275,14 @@ class GrpcTradesService extends TradesImplBase {
|
||||
boolean wasMyOffer = wasMyOffer(bsqSwapTrade);
|
||||
String role = getMyRole(bsqSwapTrade);
|
||||
var numConfirmations = coreApi.getTransactionConfirmations(bsqSwapTrade.getTxId());
|
||||
var closingStatus = bsqSwapTrade.getTradeState().equals(COMPLETED)
|
||||
? coreApi.getClosedTradeStateAsString(bsqSwapTrade)
|
||||
: "Pending";
|
||||
var tradeInfo = toTradeInfo(bsqSwapTrade,
|
||||
role,
|
||||
wasMyOffer,
|
||||
numConfirmations);
|
||||
numConfirmations,
|
||||
closingStatus);
|
||||
return GetTradeReply.newBuilder()
|
||||
.setTrade(tradeInfo.toProtoMessage())
|
||||
.build();
|
||||
@ -257,8 +291,60 @@ class GrpcTradesService extends TradesImplBase {
|
||||
private GetTradeReply buildGetTradeReply(Trade trade) {
|
||||
boolean wasMyOffer = wasMyOffer(trade);
|
||||
String role = getMyRole(trade);
|
||||
var closingStatus = trade.isCompleted()
|
||||
? coreApi.getClosedTradeStateAsString(trade)
|
||||
: "Pending";
|
||||
return GetTradeReply.newBuilder()
|
||||
.setTrade(toTradeInfo(trade, role, wasMyOffer).toProtoMessage())
|
||||
.setTrade(toTradeInfo(trade,
|
||||
role,
|
||||
wasMyOffer,
|
||||
closingStatus).toProtoMessage())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private GetTradesReply buildGetTradesReply(List<TradeModel> trades, GetTradesRequest.Category category) {
|
||||
// Build an unsorted List<TradeInfo>, starting with
|
||||
// all pending, or all completed BsqSwap and v1 trades.
|
||||
List<TradeInfo> unsortedTrades = trades.stream()
|
||||
.map(tradeModel -> {
|
||||
var role = coreApi.getTradeRole(tradeModel);
|
||||
var isMyOffer = coreApi.isMyOffer(tradeModel.getOffer());
|
||||
var isBsqSwapTrade = tradeModel instanceof BsqSwapTrade;
|
||||
var numConfirmations = isBsqSwapTrade
|
||||
? coreApi.getTransactionConfirmations(((BsqSwapTrade) tradeModel).getTxId())
|
||||
: 0;
|
||||
var closingStatus = category.equals(OPEN)
|
||||
? "Pending"
|
||||
: coreApi.getClosedTradeStateAsString(tradeModel);
|
||||
return isBsqSwapTrade
|
||||
? toTradeInfo((BsqSwapTrade) tradeModel, role, isMyOffer, numConfirmations, closingStatus)
|
||||
: toTradeInfo(tradeModel, role, isMyOffer, closingStatus);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// If closed trades were requested, add any canceled
|
||||
// OpenOffers (canceled trades) to the unsorted List<TradeInfo>.
|
||||
Optional<List<OpenOffer>> canceledOpenOffers = category.equals(CLOSED)
|
||||
? Optional.of(coreApi.getCanceledOpenOffers())
|
||||
: Optional.empty();
|
||||
List<TradeInfo> canceledTrades = new ArrayList<>();
|
||||
canceledOpenOffers.ifPresent(openOffers -> canceledTrades.addAll(
|
||||
openOffers.stream()
|
||||
.map(CanceledTradeInfo::toCanceledTradeInfo)
|
||||
.collect(Collectors.toList())
|
||||
));
|
||||
unsortedTrades.addAll(canceledTrades);
|
||||
|
||||
// Sort the cumulative List<TradeInfo> by date before sending it to the client.
|
||||
List<TradeInfo> sortedTrades = unsortedTrades.stream()
|
||||
.sorted(comparing(TradeInfo::getDate))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return GetTradesReply.newBuilder()
|
||||
.addAllTrades(sortedTrades.stream()
|
||||
.map(TradeInfo::toProtoMessage)
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -267,8 +353,6 @@ class GrpcTradesService extends TradesImplBase {
|
||||
}
|
||||
|
||||
private String getMyRole(TradeModel tradeModel) {
|
||||
return tradeModel.getOffer().isBsqSwapOffer()
|
||||
? coreApi.getBsqSwapTradeRole((BsqSwapTrade) tradeModel)
|
||||
: coreApi.getTradeRole(tradeModel.getId());
|
||||
return coreApi.getTradeRole(tradeModel);
|
||||
}
|
||||
}
|
||||
|
@ -388,6 +388,8 @@ message StopReply {
|
||||
service Trades {
|
||||
rpc GetTrade (GetTradeRequest) returns (GetTradeReply) {
|
||||
}
|
||||
rpc GetTrades (GetTradesRequest) returns (GetTradesReply) {
|
||||
}
|
||||
rpc TakeOffer (TakeOfferRequest) returns (TakeOfferReply) {
|
||||
}
|
||||
rpc ConfirmPaymentStarted (ConfirmPaymentStartedRequest) returns (ConfirmPaymentStartedReply) {
|
||||
@ -437,6 +439,19 @@ message GetTradeReply {
|
||||
TradeInfo trade = 1;
|
||||
}
|
||||
|
||||
message GetTradesRequest {
|
||||
enum Category {
|
||||
OPEN = 0;
|
||||
CLOSED = 1;
|
||||
FAILED = 2;
|
||||
}
|
||||
Category category = 1;
|
||||
}
|
||||
|
||||
message GetTradesReply {
|
||||
repeated TradeInfo trades = 1;
|
||||
}
|
||||
|
||||
message CloseTradeRequest {
|
||||
string tradeId = 1;
|
||||
}
|
||||
@ -495,8 +510,14 @@ message TradeInfo {
|
||||
string contractAsJson = 24;
|
||||
ContractInfo contract = 25;
|
||||
uint64 tradeVolume = 26;
|
||||
|
||||
// Optional Bisq v2+ trade protocol fields.
|
||||
BsqSwapTradeInfo bsqSwapTradeInfo = 28;
|
||||
|
||||
// Needed by open/closed/failed trade list items.
|
||||
string closingStatus = 29;
|
||||
|
||||
// TODO? Field for displaying correct precision per coin type, e.g., int32 coinPrecision = 32;
|
||||
}
|
||||
|
||||
message ContractInfo {
|
||||
@ -528,6 +549,8 @@ message BsqSwapTradeInfo {
|
||||
string takerBtcAddress = 10;
|
||||
uint64 numConfirmations = 11;
|
||||
string errorMessage = 12;
|
||||
uint64 payout = 13;
|
||||
uint64 swapPeerPayout = 14;
|
||||
}
|
||||
|
||||
message PaymentAccountPayloadInfo {
|
||||
|
Loading…
Reference in New Issue
Block a user