mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 23:06:39 +01:00
Merge pull request #5666 from ghubstan/08-handle-extradata-in-editoffer
Adjust API 'editoffer' to PR 5651 (include extraData field when editing offer)
This commit is contained in:
commit
58e09c96ee
47 changed files with 3655 additions and 562 deletions
|
@ -408,8 +408,118 @@ The offer will be removed from other Bisq users' offer views, and paid transacti
|
||||||
|
|
||||||
### Editing an Existing Offer
|
### Editing an Existing Offer
|
||||||
|
|
||||||
Editing existing offers is not yet supported. You can cancel and re-create an offer, but paid transaction fees
|
Offers you create can be edited in various ways:
|
||||||
for the canceled offer will be forfeited.
|
|
||||||
|
- Disable or re-enable an offer.
|
||||||
|
- Change an offer's price model and disable (or re-enable) it.
|
||||||
|
- Change a market price margin based offer to a fixed price offer.
|
||||||
|
- Change a market price margin based offer's price margin.
|
||||||
|
- Change, set, or remove a trigger price on a market price margin based offer.
|
||||||
|
- Change a market price margin based offer's price margin and trigger price.
|
||||||
|
- Change a market price margin based offer's price margin and remove its trigger price.
|
||||||
|
- Change a fixed price offer to a market price margin based offer.
|
||||||
|
- Change a fixed price offer's fixed price.
|
||||||
|
|
||||||
|
_Note: the API does not support editing an offer's payment account._
|
||||||
|
|
||||||
|
The subsections below contain examples related to specific use cases.
|
||||||
|
|
||||||
|
#### Enable and Disable Offer
|
||||||
|
|
||||||
|
Existing offers you create can be disabled (removed from offer book) and re-enabled (re-published to offer book).
|
||||||
|
|
||||||
|
To disable an offer:
|
||||||
|
```
|
||||||
|
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||||
|
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--enable=false
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable an offer:
|
||||||
|
```
|
||||||
|
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||||
|
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--enable=true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Change Offer Pricing Model
|
||||||
|
The `editoffer` command can be used to change an existing market price margin based offer to a fixed price offer,
|
||||||
|
and vice-versa.
|
||||||
|
|
||||||
|
##### Change Market Price Margin Based to Fixed Price Offer
|
||||||
|
Suppose you used `createoffer` to create a market price margin based offer as follows:
|
||||||
|
```
|
||||||
|
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||||
|
--payment-account=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
||||||
|
--direction=SELL \
|
||||||
|
--currency-code=JPY \
|
||||||
|
--amount=0.125 \
|
||||||
|
--market-price-margin=0.5 \
|
||||||
|
--security-deposit=15.0 \
|
||||||
|
--fee-currency=BSQ
|
||||||
|
```
|
||||||
|
To change the market price margin based offer to a fixed price offer:
|
||||||
|
```
|
||||||
|
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||||
|
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--fixed-price=3960000.5555
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Change Fixed Price Offer to Market Price Margin Based Offer
|
||||||
|
Suppose you used `createoffer` to create a fixed price offer as follows:
|
||||||
|
```
|
||||||
|
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||||
|
--payment-account=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
||||||
|
--direction=SELL \
|
||||||
|
--currency-code=JPY \
|
||||||
|
--amount=0.125 \
|
||||||
|
--fixed-price=3960000.0000 \
|
||||||
|
--security-deposit=15.0 \
|
||||||
|
--fee-currency=BSQ
|
||||||
|
```
|
||||||
|
To change the fixed price offer to a market price margin based offer:
|
||||||
|
```
|
||||||
|
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||||
|
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--market-price-margin=0.5
|
||||||
|
```
|
||||||
|
Alternatively, you can also set a trigger price on the re-published, market price margin based offer.
|
||||||
|
A trigger price on a SELL offer causes the offer to be automatically disabled when the market price
|
||||||
|
falls below the trigger price. In the `editoffer` example below, the SELL offer will be disabled when
|
||||||
|
the JPY market price falls below 3960000.0000.
|
||||||
|
|
||||||
|
```
|
||||||
|
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||||
|
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--market-price-margin=0.5 \
|
||||||
|
--trigger-price=3960000.0000
|
||||||
|
```
|
||||||
|
On a BUY offer, a trigger price causes the BUY offer to be automatically disabled when the market price
|
||||||
|
rises above the trigger price.
|
||||||
|
|
||||||
|
_Note: Disabled offers never automatically re-enable; they can only be manually re-enabled via
|
||||||
|
`editoffer --offer-id=<id> --enable=true`._
|
||||||
|
|
||||||
|
#### Remove Trigger Price
|
||||||
|
To remove a trigger price on a market price margin based offer, set the trigger price to 0:
|
||||||
|
```
|
||||||
|
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||||
|
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--trigger-price=0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Change Disabled Offer's Pricing Model and Enable It
|
||||||
|
You can use `editoffer` to simultaneously change an offer's price details and disable or re-enable it.
|
||||||
|
|
||||||
|
Suppose you have a disabled, fixed price offer, and want to change it to a market price margin based offer, set
|
||||||
|
a trigger price, and re-enable it:
|
||||||
|
```
|
||||||
|
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||||
|
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--market-price-margin=0.5 \
|
||||||
|
--trigger-price=3960000.0000 \
|
||||||
|
--enable=true
|
||||||
|
```
|
||||||
|
|
||||||
### Taking Offers
|
### Taking Offers
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,13 @@
|
||||||
|
|
||||||
package bisq.apitest.method.offer;
|
package bisq.apitest.method.offer;
|
||||||
|
|
||||||
import bisq.core.monetary.Altcoin;
|
|
||||||
|
|
||||||
import protobuf.PaymentAccount;
|
import protobuf.PaymentAccount;
|
||||||
|
|
||||||
import org.bitcoinj.utils.Fiat;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@ -37,10 +36,7 @@ import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||||
import static bisq.common.util.MathUtils.roundDouble;
|
import static bisq.common.util.MathUtils.exactMultiply;
|
||||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
|
||||||
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
|
||||||
import static java.math.RoundingMode.HALF_UP;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,6 +45,10 @@ import bisq.apitest.method.MethodTest;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class AbstractOfferTest extends MethodTest {
|
public abstract class AbstractOfferTest extends MethodTest {
|
||||||
|
|
||||||
|
protected static final int ACTIVATE_OFFER = 1;
|
||||||
|
protected static final int DEACTIVATE_OFFER = 0;
|
||||||
|
protected static final long NO_TRIGGER_PRICE = 0;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
protected static boolean isLongRunningTest;
|
protected static boolean isLongRunningTest;
|
||||||
|
|
||||||
|
@ -67,6 +67,35 @@ public abstract class AbstractOfferTest extends MethodTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Mkt Price Margin value of offer returned from server is scaled down by 10^-2.
|
||||||
|
protected final Function<Double, Double> scaledDownMktPriceMargin = (mktPriceMargin) ->
|
||||||
|
exactMultiply(mktPriceMargin, 0.01);
|
||||||
|
|
||||||
|
// Price value of fiat offer returned from server will be scaled up by 10^4.
|
||||||
|
protected final Function<BigDecimal, Long> scaledUpFiatOfferPrice = (price) -> {
|
||||||
|
BigDecimal factor = new BigDecimal(10).pow(4);
|
||||||
|
return price.multiply(factor).longValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Price value of altcoin offer returned from server will be scaled up by 10^8.
|
||||||
|
protected final Function<String, Long> scaledUpAltcoinOfferPrice = (altcoinPriceAsString) -> {
|
||||||
|
BigDecimal factor = new BigDecimal(10).pow(8);
|
||||||
|
BigDecimal priceAsBigDecimal = new BigDecimal(altcoinPriceAsString);
|
||||||
|
return priceAsBigDecimal.multiply(factor).longValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
protected final BiFunction<Double, Double, Long> calcPriceAsLong = (base, delta) -> {
|
||||||
|
var priceAsDouble = new BigDecimal(base).add(new BigDecimal(delta)).doubleValue();
|
||||||
|
return Double.valueOf(exactMultiply(priceAsDouble, 10_000)).longValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
protected final BiFunction<Double, Double, String> calcPriceAsString = (base, delta) -> {
|
||||||
|
var priceAsBigDecimal = new BigDecimal(Double.toString(base))
|
||||||
|
.add(new BigDecimal(Double.toString(delta)));
|
||||||
|
return priceAsBigDecimal.toPlainString();
|
||||||
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
public static void createBsqPaymentAccounts() {
|
public static void createBsqPaymentAccounts() {
|
||||||
alicesBsqAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's BSQ Account",
|
alicesBsqAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's BSQ Account",
|
||||||
BSQ,
|
BSQ,
|
||||||
|
@ -78,17 +107,6 @@ public abstract class AbstractOfferTest extends MethodTest {
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected double getScaledOfferPrice(double offerPrice, String currencyCode) {
|
|
||||||
int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
|
||||||
return scaleDownByPowerOf10(offerPrice, precision);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final double getPercentageDifference(double price1, double price2) {
|
|
||||||
return BigDecimal.valueOf(roundDouble((1 - (price1 / price2)), 5))
|
|
||||||
.setScale(4, HALF_UP)
|
|
||||||
.doubleValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
public static void tearDown() {
|
public static void tearDown() {
|
||||||
tearDownScaffold();
|
tearDownScaffold();
|
||||||
|
|
|
@ -54,7 +54,8 @@ public class CancelOfferTest extends AbstractOfferTest {
|
||||||
0.00,
|
0.00,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
paymentAccountId,
|
paymentAccountId,
|
||||||
BSQ);
|
BSQ,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
};
|
};
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -39,6 +39,7 @@ import static java.util.Collections.singletonList;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferPayload.Direction.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferPayload.Direction.SELL;
|
||||||
|
|
||||||
|
@ -70,6 +71,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||||
alicesBsqAcct.getId(),
|
alicesBsqAcct.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE);
|
||||||
log.info("Sell BSQ (Buy BTC) OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
log.info("Sell BSQ (Buy BTC) OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
|
@ -86,6 +90,8 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||||
genBtcBlockAndWaitForOfferPreparation();
|
genBtcBlockAndWaitForOfferPreparation();
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||||
|
@ -112,6 +118,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||||
alicesBsqAcct.getId(),
|
alicesBsqAcct.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE);
|
||||||
log.info("SELL 20K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
log.info("SELL 20K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
|
@ -128,6 +137,8 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||||
genBtcBlockAndWaitForOfferPreparation();
|
genBtcBlockAndWaitForOfferPreparation();
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||||
|
@ -154,6 +165,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||||
alicesBsqAcct.getId(),
|
alicesBsqAcct.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE);
|
||||||
log.info("BUY 1-2K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
log.info("BUY 1-2K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
|
@ -170,6 +184,8 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||||
genBtcBlockAndWaitForOfferPreparation();
|
genBtcBlockAndWaitForOfferPreparation();
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||||
|
@ -196,6 +212,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||||
alicesBsqAcct.getId(),
|
alicesBsqAcct.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE);
|
||||||
log.info("SELL 5-10K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
log.info("SELL 5-10K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
|
@ -212,6 +231,8 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||||
genBtcBlockAndWaitForOfferPreparation();
|
genBtcBlockAndWaitForOfferPreparation();
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||||
|
|
|
@ -35,6 +35,7 @@ import static java.util.Collections.singletonList;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferPayload.Direction.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferPayload.Direction.SELL;
|
||||||
|
|
||||||
|
@ -58,6 +59,9 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
audAccount.getId(),
|
audAccount.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE);
|
||||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "AUD"));
|
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "AUD"));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
|
@ -72,6 +76,8 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||||
|
@ -98,6 +104,9 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
usdAccount.getId(),
|
usdAccount.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE);
|
||||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "USD"));
|
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "USD"));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
|
@ -112,6 +121,8 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||||
|
@ -138,6 +149,9 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
eurAccount.getId(),
|
eurAccount.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE);
|
||||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "EUR"));
|
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "EUR"));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
|
@ -152,6 +166,8 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||||
|
|
|
@ -17,12 +17,18 @@
|
||||||
|
|
||||||
package bisq.apitest.method.offer;
|
package bisq.apitest.method.offer;
|
||||||
|
|
||||||
|
import bisq.core.monetary.Altcoin;
|
||||||
|
import bisq.core.monetary.Price;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
|
||||||
import bisq.proto.grpc.OfferInfo;
|
import bisq.proto.grpc.OfferInfo;
|
||||||
|
|
||||||
|
import org.bitcoinj.utils.Fiat;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
@ -33,18 +39,23 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.cli.TableFormat.formatOfferTable;
|
import static bisq.cli.TableFormat.formatOfferTable;
|
||||||
|
import static bisq.common.util.MathUtils.roundDouble;
|
||||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||||
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
|
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
||||||
import static java.lang.Math.abs;
|
import static java.lang.Math.abs;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
|
import static java.math.RoundingMode.HALF_UP;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferPayload.Direction.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferPayload.Direction.SELL;
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
@ -68,8 +79,12 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
usdAccount.getId(),
|
usdAccount.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
|
@ -83,6 +98,8 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
@ -109,8 +126,12 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
nzdAccount.getId(),
|
nzdAccount.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
|
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
|
@ -124,6 +145,8 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
@ -150,8 +173,12 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
gbpAccount.getId(),
|
gbpAccount.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
|
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
|
@ -165,6 +192,8 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
@ -191,8 +220,12 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
brlAccount.getId(),
|
brlAccount.getId(),
|
||||||
MAKER_FEE_CURRENCY_CODE);
|
MAKER_FEE_CURRENCY_CODE,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
|
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
|
@ -206,6 +239,8 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
@ -220,6 +255,35 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
public void testCreateUSDBTCBuyOfferWithTriggerPrice() {
|
||||||
|
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||||
|
double mktPriceAsDouble = aliceClient.getBtcPrice("usd");
|
||||||
|
BigDecimal mktPrice = new BigDecimal(Double.toString(mktPriceAsDouble));
|
||||||
|
BigDecimal triggerPrice = mktPrice.add(new BigDecimal("1000.9999"));
|
||||||
|
long triggerPriceAsLong = Price.parse("USD", triggerPrice.toString()).getValue();
|
||||||
|
|
||||||
|
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||||
|
"usd",
|
||||||
|
10_000_000L,
|
||||||
|
5_000_000L,
|
||||||
|
0.0,
|
||||||
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
usdAccount.getId(),
|
||||||
|
MAKER_FEE_CURRENCY_CODE,
|
||||||
|
triggerPriceAsLong);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsMyPendingOffer());
|
||||||
|
|
||||||
|
genBtcBlocksThenWait(1, 4000); // give time to add to offer book
|
||||||
|
newOffer = aliceClient.getMyOffer(newOffer.getId());
|
||||||
|
log.info("OFFER #5:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsMyPendingOffer());
|
||||||
|
assertEquals(triggerPriceAsLong, newOffer.getTriggerPrice());
|
||||||
|
}
|
||||||
|
|
||||||
private void assertCalculatedPriceIsCorrect(OfferInfo offer, double priceMarginPctInput) {
|
private void assertCalculatedPriceIsCorrect(OfferInfo offer, double priceMarginPctInput) {
|
||||||
assertTrue(() -> {
|
assertTrue(() -> {
|
||||||
String counterCurrencyCode = offer.getCounterCurrencyCode();
|
String counterCurrencyCode = offer.getCounterCurrencyCode();
|
||||||
|
@ -239,6 +303,17 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double getPercentageDifference(double price1, double price2) {
|
||||||
|
return BigDecimal.valueOf(roundDouble((1 - (price1 / price2)), 5))
|
||||||
|
.setScale(4, HALF_UP)
|
||||||
|
.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getScaledOfferPrice(double offerPrice, String currencyCode) {
|
||||||
|
int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
||||||
|
return scaleDownByPowerOf10(offerPrice, precision);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isCalculatedPriceWithinErrorTolerance(double delta,
|
private boolean isCalculatedPriceWithinErrorTolerance(double delta,
|
||||||
double expectedDiffPct,
|
double expectedDiffPct,
|
||||||
double actualDiffPct,
|
double actualDiffPct,
|
||||||
|
|
|
@ -0,0 +1,644 @@
|
||||||
|
/*
|
||||||
|
* 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.offer;
|
||||||
|
|
||||||
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.OfferInfo;
|
||||||
|
|
||||||
|
import io.grpc.StatusRuntimeException;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
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.cli.TableFormat.formatOfferTable;
|
||||||
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
|
import static bisq.proto.grpc.EditOfferRequest.EditType.*;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static protobuf.OfferPayload.Direction.BUY;
|
||||||
|
import static protobuf.OfferPayload.Direction.SELL;
|
||||||
|
|
||||||
|
@SuppressWarnings("ALL")
|
||||||
|
@Disabled
|
||||||
|
@Slf4j
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
public class EditOfferTest extends AbstractOfferTest {
|
||||||
|
|
||||||
|
// Some test fixtures to reduce duplication.
|
||||||
|
private static final Map<String, PaymentAccount> paymentAcctCache = new HashMap<>();
|
||||||
|
private static final String DOLLAR = "USD";
|
||||||
|
private static final String RUBLE = "RUB";
|
||||||
|
private static final long AMOUNT = 10000000L;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
public void testOfferDisableAndEnable() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("DE");
|
||||||
|
OfferInfo originalOffer = createMktPricedOfferForEdit(BUY.name(),
|
||||||
|
"EUR",
|
||||||
|
paymentAcct.getId(),
|
||||||
|
0.0,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
|
log.info("ORIGINAL EUR OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "EUR"));
|
||||||
|
assertFalse(originalOffer.getIsActivated()); // Not activated until prep is done.
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
assertTrue(originalOffer.getIsActivated());
|
||||||
|
// Disable offer
|
||||||
|
aliceClient.editOfferActivationState(originalOffer.getId(), DEACTIVATE_OFFER);
|
||||||
|
genBtcBlocksThenWait(1, 1500); // Wait for offer book removal.
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED EUR OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "EUR"));
|
||||||
|
assertFalse(editedOffer.getIsActivated());
|
||||||
|
// Re-enable offer
|
||||||
|
aliceClient.editOfferActivationState(editedOffer.getId(), ACTIVATE_OFFER);
|
||||||
|
genBtcBlocksThenWait(1, 1500); // Wait for offer book re-entry.
|
||||||
|
editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED EUR OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "EUR"));
|
||||||
|
assertTrue(editedOffer.getIsActivated());
|
||||||
|
|
||||||
|
doSanityCheck(originalOffer, editedOffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
public void testEditTriggerPrice() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("FI");
|
||||||
|
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
|
||||||
|
"EUR",
|
||||||
|
paymentAcct.getId(),
|
||||||
|
0.0,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
|
log.info("ORIGINAL EUR OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "EUR"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
assertEquals(0 /*no trigger price*/, originalOffer.getTriggerPrice());
|
||||||
|
|
||||||
|
// Edit the offer's trigger price, nothing else.
|
||||||
|
var mktPrice = aliceClient.getBtcPrice("EUR");
|
||||||
|
var delta = 5_000.00;
|
||||||
|
var newTriggerPriceAsLong = calcPriceAsLong.apply(mktPrice, delta);
|
||||||
|
|
||||||
|
aliceClient.editOfferTriggerPrice(originalOffer.getId(), newTriggerPriceAsLong);
|
||||||
|
sleep(2500); // Wait for offer book re-entry.
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED EUR OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "EUR"));
|
||||||
|
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
|
||||||
|
assertTrue(editedOffer.getUseMarketBasedPrice());
|
||||||
|
|
||||||
|
doSanityCheck(originalOffer, editedOffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
public void testSetTriggerPriceToNegativeValueShouldThrowException() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("FI");
|
||||||
|
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
|
||||||
|
"EUR",
|
||||||
|
paymentAcct.getId(),
|
||||||
|
0.0,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
|
log.info("ORIGINAL EUR OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "EUR"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
// Edit the offer's trigger price, set to -1, check error.
|
||||||
|
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||||
|
aliceClient.editOfferTriggerPrice(originalOffer.getId(), -1L));
|
||||||
|
String expectedExceptionMessage =
|
||||||
|
format("UNKNOWN: programmer error: cannot set trigger price to a negative value in offer with id '%s'",
|
||||||
|
originalOffer.getId());
|
||||||
|
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
public void testEditMktPriceMargin() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
|
||||||
|
var originalMktPriceMargin = new BigDecimal("0.1").doubleValue();
|
||||||
|
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
|
||||||
|
DOLLAR,
|
||||||
|
paymentAcct.getId(),
|
||||||
|
originalMktPriceMargin,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
|
log.info("ORIGINAL USD OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "USD"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
|
||||||
|
// Edit the offer's price margin, nothing else.
|
||||||
|
var newMktPriceMargin = new BigDecimal("0.5").doubleValue();
|
||||||
|
aliceClient.editOfferPriceMargin(originalOffer.getId(), newMktPriceMargin);
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED USD OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "USD"));
|
||||||
|
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
|
||||||
|
|
||||||
|
doSanityCheck(originalOffer, editedOffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
public void testEditFixedPrice() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU");
|
||||||
|
double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE);
|
||||||
|
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000);
|
||||||
|
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(),
|
||||||
|
RUBLE,
|
||||||
|
paymentAcct.getId(),
|
||||||
|
fixedPriceAsString);
|
||||||
|
log.info("ORIGINAL RUB OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "RUB"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
// Edit the offer's fixed price, nothing else.
|
||||||
|
String editedFixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 100_000.0000);
|
||||||
|
aliceClient.editOfferFixedPrice(originalOffer.getId(), editedFixedPriceAsString);
|
||||||
|
// Wait for edited offer to be removed from offer-book, edited, and re-published.
|
||||||
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED RUB OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "RUB"));
|
||||||
|
var expectedNewFixedPrice = scaledUpFiatOfferPrice.apply(new BigDecimal(editedFixedPriceAsString));
|
||||||
|
assertEquals(expectedNewFixedPrice, editedOffer.getPrice());
|
||||||
|
assertFalse(editedOffer.getUseMarketBasedPrice());
|
||||||
|
|
||||||
|
doSanityCheck(originalOffer, editedOffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
|
public void testEditFixedPriceAndDeactivation() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU");
|
||||||
|
double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE);
|
||||||
|
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000);
|
||||||
|
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(),
|
||||||
|
RUBLE,
|
||||||
|
paymentAcct.getId(),
|
||||||
|
fixedPriceAsString);
|
||||||
|
log.info("ORIGINAL RUB OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "RUB"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
// Edit the offer's fixed price and deactivate it.
|
||||||
|
String editedFixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 100_000.0000);
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
editedFixedPriceAsString,
|
||||||
|
originalOffer.getUseMarketBasedPrice(),
|
||||||
|
0.0,
|
||||||
|
NO_TRIGGER_PRICE,
|
||||||
|
DEACTIVATE_OFFER,
|
||||||
|
FIXED_PRICE_AND_ACTIVATION_STATE);
|
||||||
|
// Wait for edited offer to be removed from offer-book, edited, and re-published.
|
||||||
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED RUB OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "RUB"));
|
||||||
|
var expectedNewFixedPrice = scaledUpFiatOfferPrice.apply(new BigDecimal(editedFixedPriceAsString));
|
||||||
|
assertEquals(expectedNewFixedPrice, editedOffer.getPrice());
|
||||||
|
assertFalse(editedOffer.getIsActivated());
|
||||||
|
|
||||||
|
doSanityCheck(originalOffer, editedOffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(7)
|
||||||
|
public void testEditMktPriceMarginAndDeactivation() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
|
||||||
|
|
||||||
|
var originalMktPriceMargin = new BigDecimal("0.0").doubleValue();
|
||||||
|
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
|
||||||
|
DOLLAR,
|
||||||
|
paymentAcct.getId(),
|
||||||
|
originalMktPriceMargin,
|
||||||
|
0);
|
||||||
|
log.info("ORIGINAL USD OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "USD"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
|
||||||
|
|
||||||
|
// Edit the offer's price margin and trigger price, and deactivate it.
|
||||||
|
var newMktPriceMargin = new BigDecimal("1.50").doubleValue();
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
"0.00",
|
||||||
|
originalOffer.getUseMarketBasedPrice(),
|
||||||
|
newMktPriceMargin,
|
||||||
|
0,
|
||||||
|
DEACTIVATE_OFFER,
|
||||||
|
MKT_PRICE_MARGIN_AND_ACTIVATION_STATE);
|
||||||
|
// Wait for edited offer to be removed from offer-book, edited, and re-published.
|
||||||
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED USD OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "USD"));
|
||||||
|
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
|
||||||
|
assertEquals(0, editedOffer.getTriggerPrice());
|
||||||
|
assertFalse(editedOffer.getIsActivated());
|
||||||
|
|
||||||
|
doSanityCheck(originalOffer, editedOffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(8)
|
||||||
|
public void testEditMktPriceMarginAndTriggerPriceAndDeactivation() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
|
||||||
|
|
||||||
|
var originalMktPriceMargin = new BigDecimal("0.0").doubleValue();
|
||||||
|
var mktPriceAsDouble = aliceClient.getBtcPrice(DOLLAR);
|
||||||
|
var originalTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, -5_000.0000);
|
||||||
|
|
||||||
|
OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
|
||||||
|
DOLLAR,
|
||||||
|
paymentAcct.getId(),
|
||||||
|
originalMktPriceMargin,
|
||||||
|
originalTriggerPriceAsLong);
|
||||||
|
log.info("ORIGINAL USD OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "USD"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
originalOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
assertEquals(scaledDownMktPriceMargin.apply(originalMktPriceMargin), originalOffer.getMarketPriceMargin());
|
||||||
|
assertEquals(originalTriggerPriceAsLong, originalOffer.getTriggerPrice());
|
||||||
|
|
||||||
|
// Edit the offer's price margin and trigger price, and deactivate it.
|
||||||
|
var newMktPriceMargin = new BigDecimal("0.1").doubleValue();
|
||||||
|
var newTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, -2_000.0000);
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
"0.00",
|
||||||
|
originalOffer.getUseMarketBasedPrice(),
|
||||||
|
newMktPriceMargin,
|
||||||
|
newTriggerPriceAsLong,
|
||||||
|
DEACTIVATE_OFFER,
|
||||||
|
MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE);
|
||||||
|
// Wait for edited offer to be removed from offer-book, edited, and re-published.
|
||||||
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED USD OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "USD"));
|
||||||
|
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
|
||||||
|
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
|
||||||
|
assertFalse(editedOffer.getIsActivated());
|
||||||
|
|
||||||
|
doSanityCheck(originalOffer, editedOffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(9)
|
||||||
|
public void testEditingFixedPriceInMktPriceMarginBasedOfferShouldThrowException() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("US");
|
||||||
|
var originalMktPriceMargin = new BigDecimal("0.0").doubleValue();
|
||||||
|
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
|
||||||
|
DOLLAR,
|
||||||
|
paymentAcct.getId(),
|
||||||
|
originalMktPriceMargin,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
|
log.info("ORIGINAL USD OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "USD"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
// Try to edit both the fixed price and mkt price margin.
|
||||||
|
var newMktPriceMargin = new BigDecimal("0.25").doubleValue();
|
||||||
|
var newFixedPrice = "50000.0000";
|
||||||
|
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
newFixedPrice,
|
||||||
|
originalOffer.getUseMarketBasedPrice(),
|
||||||
|
newMktPriceMargin,
|
||||||
|
NO_TRIGGER_PRICE,
|
||||||
|
ACTIVATE_OFFER,
|
||||||
|
MKT_PRICE_MARGIN_ONLY));
|
||||||
|
String expectedExceptionMessage =
|
||||||
|
format("UNKNOWN: programmer error: cannot set fixed price (%s) in"
|
||||||
|
+ " mkt price margin based offer with id '%s'",
|
||||||
|
newFixedPrice,
|
||||||
|
originalOffer.getId());
|
||||||
|
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(10)
|
||||||
|
public void testEditingTriggerPriceInFixedPriceOfferShouldThrowException() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("RU");
|
||||||
|
double mktPriceAsDouble = aliceClient.getBtcPrice(RUBLE);
|
||||||
|
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 200_000.0000);
|
||||||
|
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(),
|
||||||
|
RUBLE,
|
||||||
|
paymentAcct.getId(),
|
||||||
|
fixedPriceAsString);
|
||||||
|
log.info("ORIGINAL RUB OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "RUB"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
long newTriggerPrice = 1000000L;
|
||||||
|
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||||
|
aliceClient.editOfferTriggerPrice(originalOffer.getId(), newTriggerPrice));
|
||||||
|
String expectedExceptionMessage =
|
||||||
|
format("UNKNOWN: programmer error: cannot set a trigger price (%s) in"
|
||||||
|
+ " fixed price offer with id '%s'",
|
||||||
|
newTriggerPrice,
|
||||||
|
originalOffer.getId());
|
||||||
|
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(11)
|
||||||
|
public void testChangeFixedPriceOfferToPriceMarginBasedOfferWithTriggerPrice() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("MX");
|
||||||
|
double mktPriceAsDouble = aliceClient.getBtcPrice("MXN");
|
||||||
|
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 0.00);
|
||||||
|
OfferInfo originalOffer = createFixedPricedOfferForEdit(BUY.name(),
|
||||||
|
"MXN",
|
||||||
|
paymentAcct.getId(),
|
||||||
|
fixedPriceAsString);
|
||||||
|
log.info("ORIGINAL MXN OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "MXN"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
|
||||||
|
// Change the offer to mkt price based and set a trigger price.
|
||||||
|
var newMktPriceMargin = new BigDecimal("0.05").doubleValue();
|
||||||
|
var delta = 200_000.0000; // trigger price on buy offer is 200K above mkt price
|
||||||
|
var newTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, delta);
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
"0.00",
|
||||||
|
true,
|
||||||
|
newMktPriceMargin,
|
||||||
|
newTriggerPriceAsLong,
|
||||||
|
ACTIVATE_OFFER,
|
||||||
|
MKT_PRICE_MARGIN_AND_TRIGGER_PRICE);
|
||||||
|
// Wait for edited offer to be removed from offer-book, edited, and re-published.
|
||||||
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED MXN OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "MXN"));
|
||||||
|
assertTrue(editedOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(scaledDownMktPriceMargin.apply(newMktPriceMargin), editedOffer.getMarketPriceMargin());
|
||||||
|
assertEquals(newTriggerPriceAsLong, editedOffer.getTriggerPrice());
|
||||||
|
assertTrue(editedOffer.getIsActivated());
|
||||||
|
|
||||||
|
doSanityCheck(originalOffer, editedOffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(12)
|
||||||
|
public void testChangePriceMarginBasedOfferToFixedPriceOfferAndDeactivateIt() {
|
||||||
|
PaymentAccount paymentAcct = getOrCreatePaymentAccount("GB");
|
||||||
|
double mktPriceAsDouble = aliceClient.getBtcPrice("GBP");
|
||||||
|
var originalMktPriceMargin = new BigDecimal("0.25").doubleValue();
|
||||||
|
var delta = 1_000.0000; // trigger price on sell offer is 1K below mkt price
|
||||||
|
var originalTriggerPriceAsLong = calcPriceAsLong.apply(mktPriceAsDouble, delta);
|
||||||
|
final OfferInfo originalOffer = createMktPricedOfferForEdit(SELL.name(),
|
||||||
|
"GBP",
|
||||||
|
paymentAcct.getId(),
|
||||||
|
originalMktPriceMargin,
|
||||||
|
originalTriggerPriceAsLong);
|
||||||
|
log.info("ORIGINAL GBP OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "GBP"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
|
||||||
|
String fixedPriceAsString = calcPriceAsString.apply(mktPriceAsDouble, 0.00);
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
fixedPriceAsString,
|
||||||
|
false,
|
||||||
|
0.00,
|
||||||
|
0,
|
||||||
|
DEACTIVATE_OFFER,
|
||||||
|
FIXED_PRICE_AND_ACTIVATION_STATE);
|
||||||
|
// Wait for edited offer to be removed from offer-book, edited, and re-published.
|
||||||
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED GBP OFFER:\n{}", formatOfferTable(singletonList(editedOffer), "GBP"));
|
||||||
|
assertEquals(scaledUpFiatOfferPrice.apply(new BigDecimal(fixedPriceAsString)), editedOffer.getPrice());
|
||||||
|
assertFalse(editedOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(0.00, editedOffer.getMarketPriceMargin());
|
||||||
|
assertEquals(0, editedOffer.getTriggerPrice());
|
||||||
|
assertFalse(editedOffer.getIsActivated());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(13)
|
||||||
|
public void testChangeFixedPricedBsqOfferToPriceMarginBasedOfferShouldThrowException() {
|
||||||
|
createBsqPaymentAccounts();
|
||||||
|
OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||||
|
BSQ,
|
||||||
|
100_000_000L,
|
||||||
|
100_000_000L,
|
||||||
|
"0.00005", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
||||||
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
alicesBsqAcct.getId(),
|
||||||
|
BSQ);
|
||||||
|
log.info("ORIGINAL BSQ OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "BSQ"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
"0.00",
|
||||||
|
true,
|
||||||
|
0.1,
|
||||||
|
0,
|
||||||
|
ACTIVATE_OFFER,
|
||||||
|
MKT_PRICE_MARGIN_ONLY));
|
||||||
|
String expectedExceptionMessage = format("UNKNOWN: cannot set mkt price margin or"
|
||||||
|
+ " trigger price on fixed price altcoin offer with id '%s'",
|
||||||
|
originalOffer.getId());
|
||||||
|
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(14)
|
||||||
|
public void testEditTriggerPriceOnFixedPriceBsqOfferShouldThrowException() {
|
||||||
|
createBsqPaymentAccounts();
|
||||||
|
OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||||
|
BSQ,
|
||||||
|
100_000_000L,
|
||||||
|
100_000_000L,
|
||||||
|
"0.00005", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
||||||
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
alicesBsqAcct.getId(),
|
||||||
|
BSQ);
|
||||||
|
log.info("ORIGINAL BSQ OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "BSQ"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
var newTriggerPriceAsLong = calcPriceAsLong.apply(0.00005, 0.00);
|
||||||
|
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
"0.00",
|
||||||
|
false,
|
||||||
|
0.1,
|
||||||
|
newTriggerPriceAsLong,
|
||||||
|
ACTIVATE_OFFER,
|
||||||
|
TRIGGER_PRICE_ONLY));
|
||||||
|
String expectedExceptionMessage = format("UNKNOWN: cannot set mkt price margin or"
|
||||||
|
+ " trigger price on fixed price altcoin offer with id '%s'",
|
||||||
|
originalOffer.getId());
|
||||||
|
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(15)
|
||||||
|
public void testEditFixedPriceOnBsqOffer() {
|
||||||
|
createBsqPaymentAccounts();
|
||||||
|
String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
||||||
|
final OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||||
|
BSQ,
|
||||||
|
100_000_000L,
|
||||||
|
100_000_000L,
|
||||||
|
fixedPriceAsString,
|
||||||
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
alicesBsqAcct.getId(),
|
||||||
|
BSQ);
|
||||||
|
log.info("ORIGINAL BSQ OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "BSQ"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
String newFixedPriceAsString = "0.00003111";
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
newFixedPriceAsString,
|
||||||
|
false,
|
||||||
|
0.0,
|
||||||
|
0,
|
||||||
|
ACTIVATE_OFFER,
|
||||||
|
FIXED_PRICE_ONLY);
|
||||||
|
// Wait for edited offer to be edited and removed from offer-book.
|
||||||
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED BSQ OFFER:\n{}", formatOfferTable(singletonList(editedOffer), BSQ));
|
||||||
|
assertEquals(scaledUpAltcoinOfferPrice.apply(newFixedPriceAsString), editedOffer.getPrice());
|
||||||
|
assertTrue(editedOffer.getIsActivated());
|
||||||
|
assertFalse(editedOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(0.00, editedOffer.getMarketPriceMargin());
|
||||||
|
assertEquals(0, editedOffer.getTriggerPrice());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(16)
|
||||||
|
public void testDisableBsqOffer() {
|
||||||
|
createBsqPaymentAccounts();
|
||||||
|
String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
||||||
|
final OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||||
|
BSQ,
|
||||||
|
100_000_000L,
|
||||||
|
100_000_000L,
|
||||||
|
fixedPriceAsString,
|
||||||
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
alicesBsqAcct.getId(),
|
||||||
|
BSQ);
|
||||||
|
log.info("ORIGINAL BSQ OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "BSQ"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
fixedPriceAsString,
|
||||||
|
false,
|
||||||
|
0.0,
|
||||||
|
0,
|
||||||
|
DEACTIVATE_OFFER,
|
||||||
|
ACTIVATION_STATE_ONLY);
|
||||||
|
// Wait for edited offer to be removed from offer-book.
|
||||||
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED BSQ OFFER:\n{}", formatOfferTable(singletonList(editedOffer), BSQ));
|
||||||
|
assertFalse(editedOffer.getIsActivated());
|
||||||
|
assertEquals(scaledUpAltcoinOfferPrice.apply(fixedPriceAsString), editedOffer.getPrice());
|
||||||
|
assertFalse(editedOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(0.00, editedOffer.getMarketPriceMargin());
|
||||||
|
assertEquals(0, editedOffer.getTriggerPrice());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(17)
|
||||||
|
public void testEditFixedPriceAndDisableBsqOffer() {
|
||||||
|
createBsqPaymentAccounts();
|
||||||
|
String fixedPriceAsString = "0.00005"; // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
||||||
|
final OfferInfo originalOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||||
|
BSQ,
|
||||||
|
100_000_000L,
|
||||||
|
100_000_000L,
|
||||||
|
fixedPriceAsString,
|
||||||
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
alicesBsqAcct.getId(),
|
||||||
|
BSQ);
|
||||||
|
log.info("ORIGINAL BSQ OFFER:\n{}", formatOfferTable(singletonList(originalOffer), "BSQ"));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
String newFixedPriceAsString = "0.000045";
|
||||||
|
aliceClient.editOffer(originalOffer.getId(),
|
||||||
|
newFixedPriceAsString,
|
||||||
|
false,
|
||||||
|
0.0,
|
||||||
|
0,
|
||||||
|
DEACTIVATE_OFFER,
|
||||||
|
FIXED_PRICE_AND_ACTIVATION_STATE);
|
||||||
|
// Wait for edited offer to be edited and removed from offer-book.
|
||||||
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
OfferInfo editedOffer = aliceClient.getMyOffer(originalOffer.getId());
|
||||||
|
log.info("EDITED BSQ OFFER:\n{}", formatOfferTable(singletonList(editedOffer), BSQ));
|
||||||
|
assertFalse(editedOffer.getIsActivated());
|
||||||
|
assertEquals(scaledUpAltcoinOfferPrice.apply(newFixedPriceAsString), editedOffer.getPrice());
|
||||||
|
assertFalse(editedOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(0.00, editedOffer.getMarketPriceMargin());
|
||||||
|
assertEquals(0, editedOffer.getTriggerPrice());
|
||||||
|
}
|
||||||
|
|
||||||
|
private OfferInfo createMktPricedOfferForEdit(String direction,
|
||||||
|
String currencyCode,
|
||||||
|
String paymentAccountId,
|
||||||
|
double marketPriceMargin,
|
||||||
|
long triggerPrice) {
|
||||||
|
return aliceClient.createMarketBasedPricedOffer(direction,
|
||||||
|
currencyCode,
|
||||||
|
AMOUNT,
|
||||||
|
AMOUNT,
|
||||||
|
marketPriceMargin,
|
||||||
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
paymentAccountId,
|
||||||
|
BSQ,
|
||||||
|
triggerPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OfferInfo createFixedPricedOfferForEdit(String direction,
|
||||||
|
String currencyCode,
|
||||||
|
String paymentAccountId,
|
||||||
|
String priceAsString) {
|
||||||
|
return aliceClient.createFixedPricedOffer(direction,
|
||||||
|
currencyCode,
|
||||||
|
AMOUNT,
|
||||||
|
AMOUNT,
|
||||||
|
priceAsString,
|
||||||
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
paymentAccountId,
|
||||||
|
BSQ);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSanityCheck(OfferInfo originalOffer, OfferInfo editedOffer) {
|
||||||
|
// Assert some of the immutable offer fields are unchanged.
|
||||||
|
assertEquals(originalOffer.getDirection(), editedOffer.getDirection());
|
||||||
|
assertEquals(originalOffer.getAmount(), editedOffer.getAmount());
|
||||||
|
assertEquals(originalOffer.getMinAmount(), editedOffer.getMinAmount());
|
||||||
|
assertEquals(originalOffer.getTxFee(), editedOffer.getTxFee());
|
||||||
|
assertEquals(originalOffer.getMakerFee(), editedOffer.getMakerFee());
|
||||||
|
assertEquals(originalOffer.getPaymentAccountId(), editedOffer.getPaymentAccountId());
|
||||||
|
assertEquals(originalOffer.getDate(), editedOffer.getDate());
|
||||||
|
if (originalOffer.getDirection().equals(BUY.name()))
|
||||||
|
assertEquals(originalOffer.getBuyerSecurityDeposit(), editedOffer.getBuyerSecurityDeposit());
|
||||||
|
else
|
||||||
|
assertEquals(originalOffer.getSellerSecurityDeposit(), editedOffer.getSellerSecurityDeposit());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PaymentAccount getOrCreatePaymentAccount(String countryCode) {
|
||||||
|
if (paymentAcctCache.containsKey(countryCode)) {
|
||||||
|
return paymentAcctCache.get(countryCode);
|
||||||
|
} else {
|
||||||
|
PaymentAccount paymentAcct = createDummyF2FAccount(aliceClient, countryCode);
|
||||||
|
paymentAcctCache.put(countryCode, paymentAcct);
|
||||||
|
return paymentAcct;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void clearPaymentAcctCache() {
|
||||||
|
paymentAcctCache.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,7 +72,8 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
0.00,
|
0.00,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
alicesUsdAccount.getId(),
|
alicesUsdAccount.getId(),
|
||||||
TRADE_FEE_CURRENCY_CODE);
|
TRADE_FEE_CURRENCY_CODE,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
var offerId = alicesOffer.getId();
|
var offerId = alicesOffer.getId();
|
||||||
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,8 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
|
||||||
0.00,
|
0.00,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
alicesPaymentAccount.getId(),
|
alicesPaymentAccount.getId(),
|
||||||
TRADE_FEE_CURRENCY_CODE);
|
TRADE_FEE_CURRENCY_CODE,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
var offerId = alicesOffer.getId();
|
var offerId = alicesOffer.getId();
|
||||||
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,8 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
0.00,
|
0.00,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
alicesUsdAccount.getId(),
|
alicesUsdAccount.getId(),
|
||||||
TRADE_FEE_CURRENCY_CODE);
|
TRADE_FEE_CURRENCY_CODE,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
var offerId = alicesOffer.getId();
|
var offerId = alicesOffer.getId();
|
||||||
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.apitest.scenario;
|
||||||
|
|
||||||
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.OfferInfo;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestInfo;
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledIf;
|
||||||
|
|
||||||
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
|
import static bisq.cli.CurrencyFormat.formatPrice;
|
||||||
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
|
import static java.lang.System.getenv;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import static protobuf.OfferPayload.Direction.BUY;
|
||||||
|
import static protobuf.OfferPayload.Direction.SELL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to verify trigger based, automatic offer deactivation works.
|
||||||
|
* Disabled by default.
|
||||||
|
* Set ENV or IDE-ENV LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED=true to run.
|
||||||
|
*/
|
||||||
|
@EnabledIf("envLongRunningTestEnabled")
|
||||||
|
@Slf4j
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
public class LongRunningOfferDeactivationTest extends AbstractOfferTest {
|
||||||
|
|
||||||
|
private static final int MAX_ITERATIONS = 500;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
public void testSellOfferAutoDisable(final TestInfo testInfo) {
|
||||||
|
PaymentAccount paymentAcct = createDummyF2FAccount(aliceClient, "US");
|
||||||
|
double mktPriceAsDouble = aliceClient.getBtcPrice("USD");
|
||||||
|
long triggerPrice = calcPriceAsLong.apply(mktPriceAsDouble, -50.0000);
|
||||||
|
log.info("Current USD mkt price = {} Trigger Price = {}", mktPriceAsDouble, formatPrice(triggerPrice));
|
||||||
|
OfferInfo offer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||||
|
"USD",
|
||||||
|
1_000_000,
|
||||||
|
1_000_000,
|
||||||
|
0.00,
|
||||||
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
paymentAcct.getId(),
|
||||||
|
BTC,
|
||||||
|
triggerPrice);
|
||||||
|
log.info("SELL offer {} created with margin based price {}.",
|
||||||
|
offer.getId(),
|
||||||
|
formatPrice(offer.getPrice()));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
|
||||||
|
offer = aliceClient.getMyOffer(offer.getId()); // Offer has trigger price now.
|
||||||
|
log.info("SELL offer should be automatically disabled when mkt price falls below {}.",
|
||||||
|
formatPrice(offer.getTriggerPrice()));
|
||||||
|
|
||||||
|
int numIterations = 0;
|
||||||
|
while (++numIterations < MAX_ITERATIONS) {
|
||||||
|
offer = aliceClient.getMyOffer(offer.getId());
|
||||||
|
|
||||||
|
var mktPrice = aliceClient.getBtcPrice("USD");
|
||||||
|
if (offer.getIsActivated()) {
|
||||||
|
log.info("Offer still enabled at mkt price {} > {} trigger price",
|
||||||
|
mktPrice,
|
||||||
|
formatPrice(offer.getTriggerPrice()));
|
||||||
|
sleep(1000 * 60); // 60s
|
||||||
|
} else {
|
||||||
|
log.info("Successful test completion after offer disabled at mkt price {} < {} trigger price.",
|
||||||
|
mktPrice,
|
||||||
|
formatPrice(offer.getTriggerPrice()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (numIterations == MAX_ITERATIONS)
|
||||||
|
fail("Offer never disabled");
|
||||||
|
|
||||||
|
genBtcBlocksThenWait(1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
public void testBuyOfferAutoDisable(final TestInfo testInfo) {
|
||||||
|
PaymentAccount paymentAcct = createDummyF2FAccount(aliceClient, "US");
|
||||||
|
double mktPriceAsDouble = aliceClient.getBtcPrice("USD");
|
||||||
|
long triggerPrice = calcPriceAsLong.apply(mktPriceAsDouble, 50.0000);
|
||||||
|
log.info("Current USD mkt price = {} Trigger Price = {}", mktPriceAsDouble, formatPrice(triggerPrice));
|
||||||
|
OfferInfo offer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||||
|
"USD",
|
||||||
|
1_000_000,
|
||||||
|
1_000_000,
|
||||||
|
0.00,
|
||||||
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
paymentAcct.getId(),
|
||||||
|
BTC,
|
||||||
|
triggerPrice);
|
||||||
|
log.info("BUY offer {} created with margin based price {}.",
|
||||||
|
offer.getId(),
|
||||||
|
formatPrice(offer.getPrice()));
|
||||||
|
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||||
|
|
||||||
|
offer = aliceClient.getMyOffer(offer.getId()); // Offer has trigger price now.
|
||||||
|
log.info("BUY offer should be automatically disabled when mkt price rises above {}.",
|
||||||
|
formatPrice(offer.getTriggerPrice()));
|
||||||
|
|
||||||
|
int numIterations = 0;
|
||||||
|
while (++numIterations < MAX_ITERATIONS) {
|
||||||
|
offer = aliceClient.getMyOffer(offer.getId());
|
||||||
|
|
||||||
|
var mktPrice = aliceClient.getBtcPrice("USD");
|
||||||
|
if (offer.getIsActivated()) {
|
||||||
|
log.info("Offer still enabled at mkt price {} < {} trigger price",
|
||||||
|
mktPrice,
|
||||||
|
formatPrice(offer.getTriggerPrice()));
|
||||||
|
sleep(1000 * 60); // 60s
|
||||||
|
} else {
|
||||||
|
log.info("Successful test completion after offer disabled at mkt price {} > {} trigger price.",
|
||||||
|
mktPrice,
|
||||||
|
formatPrice(offer.getTriggerPrice()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (numIterations == MAX_ITERATIONS)
|
||||||
|
fail("Offer never disabled");
|
||||||
|
|
||||||
|
genBtcBlocksThenWait(1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static boolean envLongRunningTestEnabled() {
|
||||||
|
String envName = "LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED";
|
||||||
|
String envX = getenv(envName);
|
||||||
|
if (envX != null) {
|
||||||
|
log.info("Enabled, found {}.", envName);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.info("Skipped, no environment variable {} defined.", envName);
|
||||||
|
log.info("To enable on Mac OS or Linux:"
|
||||||
|
+ "\tIf running in terminal, export LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED=true in bash shell."
|
||||||
|
+ "\tIf running in Intellij, set LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED=true in launcher's Environment variables field.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import bisq.apitest.method.offer.CancelOfferTest;
|
||||||
import bisq.apitest.method.offer.CreateBSQOffersTest;
|
import bisq.apitest.method.offer.CreateBSQOffersTest;
|
||||||
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
||||||
import bisq.apitest.method.offer.CreateOfferUsingMarketPriceMarginTest;
|
import bisq.apitest.method.offer.CreateOfferUsingMarketPriceMarginTest;
|
||||||
|
import bisq.apitest.method.offer.EditOfferTest;
|
||||||
import bisq.apitest.method.offer.ValidateCreateOfferTest;
|
import bisq.apitest.method.offer.ValidateCreateOfferTest;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -71,11 +72,12 @@ public class OfferTest extends AbstractOfferTest {
|
||||||
test.testCreateNZDBTCBuyOfferMinus2PctPriceMargin();
|
test.testCreateNZDBTCBuyOfferMinus2PctPriceMargin();
|
||||||
test.testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin();
|
test.testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin();
|
||||||
test.testCreateBRLBTCSellOffer6Point55PctPriceMargin();
|
test.testCreateBRLBTCSellOffer6Point55PctPriceMargin();
|
||||||
|
test.testCreateUSDBTCBuyOfferWithTriggerPrice();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(5)
|
@Order(5)
|
||||||
public void testCreateBSQOffersTest() {
|
public void testCreateBSQOffers() {
|
||||||
CreateBSQOffersTest test = new CreateBSQOffersTest();
|
CreateBSQOffersTest test = new CreateBSQOffersTest();
|
||||||
CreateBSQOffersTest.createBsqPaymentAccounts();
|
CreateBSQOffersTest.createBsqPaymentAccounts();
|
||||||
test.testCreateBuy1BTCFor20KBSQOffer();
|
test.testCreateBuy1BTCFor20KBSQOffer();
|
||||||
|
@ -85,4 +87,30 @@ public class OfferTest extends AbstractOfferTest {
|
||||||
test.testGetAllMyBsqOffers();
|
test.testGetAllMyBsqOffers();
|
||||||
test.testGetAvailableBsqOffers();
|
test.testGetAvailableBsqOffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
|
public void testEditOffer() {
|
||||||
|
EditOfferTest test = new EditOfferTest();
|
||||||
|
// Edit fiat offer tests
|
||||||
|
test.testOfferDisableAndEnable();
|
||||||
|
test.testEditTriggerPrice();
|
||||||
|
test.testSetTriggerPriceToNegativeValueShouldThrowException();
|
||||||
|
test.testEditMktPriceMargin();
|
||||||
|
test.testEditFixedPrice();
|
||||||
|
test.testEditFixedPriceAndDeactivation();
|
||||||
|
test.testEditMktPriceMarginAndDeactivation();
|
||||||
|
test.testEditMktPriceMarginAndTriggerPriceAndDeactivation();
|
||||||
|
test.testEditingFixedPriceInMktPriceMarginBasedOfferShouldThrowException();
|
||||||
|
test.testEditingTriggerPriceInFixedPriceOfferShouldThrowException();
|
||||||
|
test.testChangeFixedPriceOfferToPriceMarginBasedOfferWithTriggerPrice();
|
||||||
|
test.testChangePriceMarginBasedOfferToFixedPriceOfferAndDeactivateIt();
|
||||||
|
test.testChangeFixedPriceOfferToPriceMarginBasedOfferWithTriggerPrice();
|
||||||
|
// Edit bsq offer tests
|
||||||
|
test.testChangeFixedPricedBsqOfferToPriceMarginBasedOfferShouldThrowException();
|
||||||
|
test.testEditTriggerPriceOnFixedPriceBsqOfferShouldThrowException();
|
||||||
|
test.testEditFixedPriceOnBsqOffer();
|
||||||
|
test.testDisableBsqOffer();
|
||||||
|
test.testEditFixedPriceAndDisableBsqOffer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,11 +39,6 @@ import bisq.cli.GrpcClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience GrpcClient wrapper for bots using gRPC services.
|
* Convenience GrpcClient wrapper for bots using gRPC services.
|
||||||
*
|
|
||||||
* TODO Consider if the duplication smell is bad enough to force a BotClient user
|
|
||||||
* to use the GrpcClient instead (and delete this class). But right now, I think it is
|
|
||||||
* OK because moving some of the non-gRPC related methods to GrpcClient is even smellier.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"JavaDoc", "unused"})
|
@SuppressWarnings({"JavaDoc", "unused"})
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -134,7 +129,8 @@ public class BotClient {
|
||||||
long minAmountInSatoshis,
|
long minAmountInSatoshis,
|
||||||
double priceMarginAsPercent,
|
double priceMarginAsPercent,
|
||||||
double securityDepositAsPercent,
|
double securityDepositAsPercent,
|
||||||
String feeCurrency) {
|
String feeCurrency,
|
||||||
|
long triggerPrice) {
|
||||||
return grpcClient.createMarketBasedPricedOffer(direction,
|
return grpcClient.createMarketBasedPricedOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amountInSatoshis,
|
amountInSatoshis,
|
||||||
|
@ -142,7 +138,8 @@ public class BotClient {
|
||||||
priceMarginAsPercent,
|
priceMarginAsPercent,
|
||||||
securityDepositAsPercent,
|
securityDepositAsPercent,
|
||||||
paymentAccount.getId(),
|
paymentAccount.getId(),
|
||||||
feeCurrency);
|
feeCurrency,
|
||||||
|
triggerPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -33,7 +33,7 @@ import java.util.function.Supplier;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static bisq.cli.CurrencyFormat.formatMarketPrice;
|
import static bisq.cli.CurrencyFormat.formatInternalFiatPrice;
|
||||||
import static bisq.cli.CurrencyFormat.formatSatoshis;
|
import static bisq.cli.CurrencyFormat.formatSatoshis;
|
||||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
|
@ -128,7 +128,8 @@ public class RandomOffer {
|
||||||
minAmount,
|
minAmount,
|
||||||
priceMargin,
|
priceMargin,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
feeCurrency);
|
feeCurrency,
|
||||||
|
0 /*no trigger price*/);
|
||||||
} else {
|
} else {
|
||||||
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
||||||
direction,
|
direction,
|
||||||
|
@ -167,11 +168,11 @@ public class RandomOffer {
|
||||||
log.info(description);
|
log.info(description);
|
||||||
if (useMarketBasedPrice) {
|
if (useMarketBasedPrice) {
|
||||||
log.info("Offer Price Margin = {}%", priceMargin);
|
log.info("Offer Price Margin = {}%", priceMargin);
|
||||||
log.info("Expected Offer Price = {} {}", formatMarketPrice(Double.parseDouble(fixedOfferPrice)), currencyCode);
|
log.info("Expected Offer Price = {} {}", formatInternalFiatPrice(Double.parseDouble(fixedOfferPrice)), currencyCode);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
log.info("Fixed Offer Price = {} {}", fixedOfferPrice, currencyCode);
|
log.info("Fixed Offer Price = {} {}", fixedOfferPrice, currencyCode);
|
||||||
}
|
}
|
||||||
log.info("Current Market Price = {} {}", formatMarketPrice(currentMarketPrice), currencyCode);
|
log.info("Current Market Price = {} {}", formatInternalFiatPrice(currentMarketPrice), currencyCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,10 +39,7 @@ import java.util.List;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static bisq.cli.CurrencyFormat.formatMarketPrice;
|
import static bisq.cli.CurrencyFormat.*;
|
||||||
import static bisq.cli.CurrencyFormat.formatTxFeeRateInfo;
|
|
||||||
import static bisq.cli.CurrencyFormat.toSatoshis;
|
|
||||||
import static bisq.cli.CurrencyFormat.toSecurityDepositAsPct;
|
|
||||||
import static bisq.cli.Method.*;
|
import static bisq.cli.Method.*;
|
||||||
import static bisq.cli.TableFormat.*;
|
import static bisq.cli.TableFormat.*;
|
||||||
import static bisq.cli.opts.OptLabel.*;
|
import static bisq.cli.opts.OptLabel.*;
|
||||||
|
@ -59,6 +56,7 @@ import bisq.cli.opts.CancelOfferOptionParser;
|
||||||
import bisq.cli.opts.CreateCryptoCurrencyPaymentAcctOptionParser;
|
import bisq.cli.opts.CreateCryptoCurrencyPaymentAcctOptionParser;
|
||||||
import bisq.cli.opts.CreateOfferOptionParser;
|
import bisq.cli.opts.CreateOfferOptionParser;
|
||||||
import bisq.cli.opts.CreatePaymentAcctOptionParser;
|
import bisq.cli.opts.CreatePaymentAcctOptionParser;
|
||||||
|
import bisq.cli.opts.EditOfferOptionParser;
|
||||||
import bisq.cli.opts.GetAddressBalanceOptionParser;
|
import bisq.cli.opts.GetAddressBalanceOptionParser;
|
||||||
import bisq.cli.opts.GetBTCMarketPriceOptionParser;
|
import bisq.cli.opts.GetBTCMarketPriceOptionParser;
|
||||||
import bisq.cli.opts.GetBalanceOptionParser;
|
import bisq.cli.opts.GetBalanceOptionParser;
|
||||||
|
@ -200,7 +198,7 @@ public class CliMain {
|
||||||
}
|
}
|
||||||
var currencyCode = opts.getCurrencyCode();
|
var currencyCode = opts.getCurrencyCode();
|
||||||
var price = client.getBtcPrice(currencyCode);
|
var price = client.getBtcPrice(currencyCode);
|
||||||
out.println(formatMarketPrice(price));
|
out.println(formatInternalFiatPrice(price));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case getfundingaddresses: {
|
case getfundingaddresses: {
|
||||||
|
@ -337,6 +335,7 @@ public class CliMain {
|
||||||
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
|
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
|
||||||
var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit());
|
var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit());
|
||||||
var makerFeeCurrencyCode = opts.getMakerFeeCurrencyCode();
|
var makerFeeCurrencyCode = opts.getMakerFeeCurrencyCode();
|
||||||
|
var triggerPrice = 0; // Cannot be defined until offer is in book.
|
||||||
var offer = client.createOffer(direction,
|
var offer = client.createOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
|
@ -346,10 +345,34 @@ public class CliMain {
|
||||||
marketPriceMargin.doubleValue(),
|
marketPriceMargin.doubleValue(),
|
||||||
securityDeposit,
|
securityDeposit,
|
||||||
paymentAcctId,
|
paymentAcctId,
|
||||||
makerFeeCurrencyCode);
|
makerFeeCurrencyCode,
|
||||||
|
triggerPrice);
|
||||||
out.println(formatOfferTable(singletonList(offer), currencyCode));
|
out.println(formatOfferTable(singletonList(offer), currencyCode));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case editoffer: {
|
||||||
|
var opts = new EditOfferOptionParser(args).parse();
|
||||||
|
if (opts.isForHelp()) {
|
||||||
|
out.println(client.getMethodHelp(method));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var offerId = opts.getOfferId();
|
||||||
|
var fixedPrice = opts.getFixedPrice();
|
||||||
|
var isUsingMktPriceMargin = opts.isUsingMktPriceMargin();
|
||||||
|
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
|
||||||
|
var triggerPrice = toInternalFiatPrice(opts.getTriggerPriceAsBigDecimal());
|
||||||
|
var enable = opts.getEnableAsSignedInt();
|
||||||
|
var editOfferType = opts.getOfferEditType();
|
||||||
|
client.editOffer(offerId,
|
||||||
|
fixedPrice,
|
||||||
|
isUsingMktPriceMargin,
|
||||||
|
marketPriceMargin.doubleValue(),
|
||||||
|
triggerPrice,
|
||||||
|
enable,
|
||||||
|
editOfferType);
|
||||||
|
out.println("offer has been edited");
|
||||||
|
return;
|
||||||
|
}
|
||||||
case canceloffer: {
|
case canceloffer: {
|
||||||
var opts = new CancelOfferOptionParser(args).parse();
|
var opts = new CancelOfferOptionParser(args).parse();
|
||||||
if (opts.isForHelp()) {
|
if (opts.isForHelp()) {
|
||||||
|
@ -486,7 +509,7 @@ public class CliMain {
|
||||||
}
|
}
|
||||||
var tradeId = opts.getTradeId();
|
var tradeId = opts.getTradeId();
|
||||||
var address = opts.getAddress();
|
var address = opts.getAddress();
|
||||||
// Multi-word memos must be double quoted.
|
// Multi-word memos must be double-quoted.
|
||||||
var memo = opts.getMemo();
|
var memo = opts.getMemo();
|
||||||
client.withdrawFunds(tradeId, address, memo);
|
client.withdrawFunds(tradeId, address, memo);
|
||||||
out.printf("trade %s funds sent to btc address %s%n", tradeId, address);
|
out.printf("trade %s funds sent to btc address %s%n", tradeId, address);
|
||||||
|
@ -754,6 +777,13 @@ public class CliMain {
|
||||||
stream.format(rowFormat, "", "--fixed-price=<price> | --market-price=margin=<percent> \\", "");
|
stream.format(rowFormat, "", "--fixed-price=<price> | --market-price=margin=<percent> \\", "");
|
||||||
stream.format(rowFormat, "", "--security-deposit=<percent> \\", "");
|
stream.format(rowFormat, "", "--security-deposit=<percent> \\", "");
|
||||||
stream.format(rowFormat, "", "[--fee-currency=<bsq|btc>]", "");
|
stream.format(rowFormat, "", "[--fee-currency=<bsq|btc>]", "");
|
||||||
|
stream.format(rowFormat, "", "[--trigger-price=<price>]", "");
|
||||||
|
stream.println();
|
||||||
|
stream.format(rowFormat, editoffer.name(), "--offer-id=<offer-id> \\", "Edit offer with id");
|
||||||
|
stream.format(rowFormat, "", "[--fixed-price=<price>] \\", "");
|
||||||
|
stream.format(rowFormat, "", "[--market-price=margin=<percent>] \\", "");
|
||||||
|
stream.format(rowFormat, "", "[--trigger-price=<price>] \\", "");
|
||||||
|
stream.format(rowFormat, "", "[--enabled=<true|false>]", "");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, canceloffer.name(), "--offer-id=<offer-id>", "Cancel offer with id");
|
stream.format(rowFormat, canceloffer.name(), "--offer-id=<offer-id>", "Cancel offer with id");
|
||||||
stream.println();
|
stream.println();
|
||||||
|
|
|
@ -46,6 +46,7 @@ class ColumnHeaderConstants {
|
||||||
static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date (UTC)", 20, ' ');
|
static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date (UTC)", 20, ' ');
|
||||||
static final String COL_HEADER_CURRENCY = "Currency";
|
static final String COL_HEADER_CURRENCY = "Currency";
|
||||||
static final String COL_HEADER_DIRECTION = "Buy/Sell";
|
static final String COL_HEADER_DIRECTION = "Buy/Sell";
|
||||||
|
static final String COL_HEADER_ENABLED = "Enabled";
|
||||||
static final String COL_HEADER_NAME = "Name";
|
static final String COL_HEADER_NAME = "Name";
|
||||||
static final String COL_HEADER_PAYMENT_METHOD = "Payment Method";
|
static final String COL_HEADER_PAYMENT_METHOD = "Payment Method";
|
||||||
static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC";
|
static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC";
|
||||||
|
@ -64,7 +65,7 @@ class ColumnHeaderConstants {
|
||||||
static final String COL_HEADER_TRADE_TX_FEE = padEnd("Tx Fee(BTC)", 12, ' ');
|
static final String COL_HEADER_TRADE_TX_FEE = padEnd("Tx Fee(BTC)", 12, ' ');
|
||||||
static final String COL_HEADER_TRADE_MAKER_FEE = padEnd("Maker Fee(%-3s)", 12, ' '); // "Maker Fee(%-3s)";
|
static final String COL_HEADER_TRADE_MAKER_FEE = padEnd("Maker Fee(%-3s)", 12, ' '); // "Maker Fee(%-3s)";
|
||||||
static final String COL_HEADER_TRADE_TAKER_FEE = padEnd("Taker Fee(%-3s)", 12, ' '); // "Taker Fee(%-3s)";
|
static final String COL_HEADER_TRADE_TAKER_FEE = padEnd("Taker Fee(%-3s)", 12, ' '); // "Taker Fee(%-3s)";
|
||||||
|
static final String COL_HEADER_TRIGGER_PRICE = "Trigger Price(%-3s)";
|
||||||
static final String COL_HEADER_TX_ID = "Tx ID";
|
static final String COL_HEADER_TX_ID = "Tx ID";
|
||||||
static final String COL_HEADER_TX_INPUT_SUM = "Tx Inputs (BTC)";
|
static final String COL_HEADER_TX_INPUT_SUM = "Tx Inputs (BTC)";
|
||||||
static final String COL_HEADER_TX_OUTPUT_SUM = "Tx Outputs (BTC)";
|
static final String COL_HEADER_TX_OUTPUT_SUM = "Tx Outputs (BTC)";
|
||||||
|
|
|
@ -22,6 +22,7 @@ import bisq.proto.grpc.TxFeeRateInfo;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.DecimalFormatSymbols;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
@ -35,15 +36,22 @@ import static java.math.RoundingMode.UNNECESSARY;
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public class CurrencyFormat {
|
public class CurrencyFormat {
|
||||||
|
|
||||||
private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
|
// Use the US locale for all DecimalFormat objects.
|
||||||
|
private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US);
|
||||||
|
|
||||||
|
// Formats numbers in US locale, human friendly style.
|
||||||
|
private static final NumberFormat FRIENDLY_NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||||
|
|
||||||
|
// Formats numbers for internal use, i.e., grpc request parameters.
|
||||||
|
private static final DecimalFormat INTERNAL_FIAT_DECIMAL_FORMAT = new DecimalFormat("##############0.0000");
|
||||||
|
|
||||||
static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100_000_000);
|
static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100_000_000);
|
||||||
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
|
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000", DECIMAL_FORMAT_SYMBOLS);
|
||||||
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,###,##0");
|
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,###,##0", DECIMAL_FORMAT_SYMBOLS);
|
||||||
|
|
||||||
static final BigDecimal BSQ_SATOSHI_DIVISOR = new BigDecimal(100);
|
static final BigDecimal BSQ_SATOSHI_DIVISOR = new BigDecimal(100);
|
||||||
static final DecimalFormat BSQ_FORMAT = new DecimalFormat("###,###,###,##0.00");
|
static final DecimalFormat BSQ_FORMAT = new DecimalFormat("###,###,###,##0.00", DECIMAL_FORMAT_SYMBOLS);
|
||||||
static final DecimalFormat SEND_BSQ_FORMAT = new DecimalFormat("###########0.00");
|
static final DecimalFormat SEND_BSQ_FORMAT = new DecimalFormat("###########0.00", DECIMAL_FORMAT_SYMBOLS);
|
||||||
|
|
||||||
static final BigDecimal SECURITY_DEPOSIT_MULTIPLICAND = new BigDecimal("0.01");
|
static final BigDecimal SECURITY_DEPOSIT_MULTIPLICAND = new BigDecimal("0.01");
|
||||||
|
|
||||||
|
@ -58,10 +66,9 @@ public class CurrencyFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatBsqAmount(long bsqSats) {
|
public static String formatBsqAmount(long bsqSats) {
|
||||||
// BSQ sats = trade.getOffer().getVolume()
|
FRIENDLY_NUMBER_FORMAT.setMinimumFractionDigits(2);
|
||||||
NUMBER_FORMAT.setMinimumFractionDigits(2);
|
FRIENDLY_NUMBER_FORMAT.setMaximumFractionDigits(2);
|
||||||
NUMBER_FORMAT.setMaximumFractionDigits(2);
|
FRIENDLY_NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
||||||
NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
|
||||||
return SEND_BSQ_FORMAT.format((double) bsqSats / SATOSHI_DIVISOR.doubleValue());
|
return SEND_BSQ_FORMAT.format((double) bsqSats / SATOSHI_DIVISOR.doubleValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,38 +102,48 @@ public class CurrencyFormat {
|
||||||
: formatCryptoCurrencyOfferVolume(volume);
|
: formatCryptoCurrencyOfferVolume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatMarketPrice(double price) {
|
public static String formatInternalFiatPrice(BigDecimal price) {
|
||||||
NUMBER_FORMAT.setMinimumFractionDigits(4);
|
INTERNAL_FIAT_DECIMAL_FORMAT.setMinimumFractionDigits(4);
|
||||||
NUMBER_FORMAT.setMaximumFractionDigits(4);
|
INTERNAL_FIAT_DECIMAL_FORMAT.setMaximumFractionDigits(4);
|
||||||
return NUMBER_FORMAT.format(price);
|
return INTERNAL_FIAT_DECIMAL_FORMAT.format(price);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatInternalFiatPrice(double price) {
|
||||||
|
FRIENDLY_NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||||
|
FRIENDLY_NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||||
|
return FRIENDLY_NUMBER_FORMAT.format(price);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatPrice(long price) {
|
public static String formatPrice(long price) {
|
||||||
NUMBER_FORMAT.setMinimumFractionDigits(4);
|
FRIENDLY_NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||||
NUMBER_FORMAT.setMaximumFractionDigits(4);
|
FRIENDLY_NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||||
NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
FRIENDLY_NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
||||||
return NUMBER_FORMAT.format((double) price / 10_000);
|
return FRIENDLY_NUMBER_FORMAT.format((double) price / 10_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatCryptoCurrencyPrice(long price) {
|
public static String formatCryptoCurrencyPrice(long price) {
|
||||||
NUMBER_FORMAT.setMinimumFractionDigits(8);
|
FRIENDLY_NUMBER_FORMAT.setMinimumFractionDigits(8);
|
||||||
NUMBER_FORMAT.setMaximumFractionDigits(8);
|
FRIENDLY_NUMBER_FORMAT.setMaximumFractionDigits(8);
|
||||||
NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
FRIENDLY_NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
||||||
return NUMBER_FORMAT.format((double) price / SATOSHI_DIVISOR.doubleValue());
|
return FRIENDLY_NUMBER_FORMAT.format((double) price / SATOSHI_DIVISOR.doubleValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatOfferVolume(long volume) {
|
public static String formatOfferVolume(long volume) {
|
||||||
NUMBER_FORMAT.setMinimumFractionDigits(0);
|
FRIENDLY_NUMBER_FORMAT.setMinimumFractionDigits(0);
|
||||||
NUMBER_FORMAT.setMaximumFractionDigits(0);
|
FRIENDLY_NUMBER_FORMAT.setMaximumFractionDigits(0);
|
||||||
NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
FRIENDLY_NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
||||||
return NUMBER_FORMAT.format((double) volume / 10_000);
|
return FRIENDLY_NUMBER_FORMAT.format((double) volume / 10_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatCryptoCurrencyOfferVolume(long volume) {
|
public static String formatCryptoCurrencyOfferVolume(long volume) {
|
||||||
NUMBER_FORMAT.setMinimumFractionDigits(2);
|
FRIENDLY_NUMBER_FORMAT.setMinimumFractionDigits(2);
|
||||||
NUMBER_FORMAT.setMaximumFractionDigits(2);
|
FRIENDLY_NUMBER_FORMAT.setMaximumFractionDigits(2);
|
||||||
NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
FRIENDLY_NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
||||||
return NUMBER_FORMAT.format((double) volume / SATOSHI_DIVISOR.doubleValue());
|
return FRIENDLY_NUMBER_FORMAT.format((double) volume / SATOSHI_DIVISOR.doubleValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long toInternalFiatPrice(BigDecimal humanFriendlyFiatPrice) {
|
||||||
|
return humanFriendlyFiatPrice.multiply(new BigDecimal(10_000)).longValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long toSatoshis(String btc) {
|
public static long toSatoshis(String btc) {
|
||||||
|
|
|
@ -21,63 +21,31 @@ import bisq.proto.grpc.AddressBalanceInfo;
|
||||||
import bisq.proto.grpc.BalancesInfo;
|
import bisq.proto.grpc.BalancesInfo;
|
||||||
import bisq.proto.grpc.BsqBalanceInfo;
|
import bisq.proto.grpc.BsqBalanceInfo;
|
||||||
import bisq.proto.grpc.BtcBalanceInfo;
|
import bisq.proto.grpc.BtcBalanceInfo;
|
||||||
import bisq.proto.grpc.CancelOfferRequest;
|
|
||||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
|
||||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
|
||||||
import bisq.proto.grpc.CreateCryptoCurrencyPaymentAccountRequest;
|
|
||||||
import bisq.proto.grpc.CreateOfferRequest;
|
|
||||||
import bisq.proto.grpc.CreatePaymentAccountRequest;
|
|
||||||
import bisq.proto.grpc.GetAddressBalanceRequest;
|
|
||||||
import bisq.proto.grpc.GetBalancesRequest;
|
|
||||||
import bisq.proto.grpc.GetCryptoCurrencyPaymentMethodsRequest;
|
|
||||||
import bisq.proto.grpc.GetFundingAddressesRequest;
|
|
||||||
import bisq.proto.grpc.GetMethodHelpRequest;
|
import bisq.proto.grpc.GetMethodHelpRequest;
|
||||||
import bisq.proto.grpc.GetMyOfferRequest;
|
|
||||||
import bisq.proto.grpc.GetMyOffersRequest;
|
|
||||||
import bisq.proto.grpc.GetOfferRequest;
|
|
||||||
import bisq.proto.grpc.GetOffersRequest;
|
|
||||||
import bisq.proto.grpc.GetPaymentAccountFormRequest;
|
|
||||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
|
||||||
import bisq.proto.grpc.GetPaymentMethodsRequest;
|
|
||||||
import bisq.proto.grpc.GetTradeRequest;
|
|
||||||
import bisq.proto.grpc.GetTransactionRequest;
|
|
||||||
import bisq.proto.grpc.GetTxFeeRateRequest;
|
|
||||||
import bisq.proto.grpc.GetUnusedBsqAddressRequest;
|
|
||||||
import bisq.proto.grpc.GetVersionRequest;
|
import bisq.proto.grpc.GetVersionRequest;
|
||||||
import bisq.proto.grpc.KeepFundsRequest;
|
|
||||||
import bisq.proto.grpc.LockWalletRequest;
|
|
||||||
import bisq.proto.grpc.MarketPriceRequest;
|
|
||||||
import bisq.proto.grpc.OfferInfo;
|
import bisq.proto.grpc.OfferInfo;
|
||||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
|
||||||
import bisq.proto.grpc.SendBsqRequest;
|
|
||||||
import bisq.proto.grpc.SendBtcRequest;
|
|
||||||
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
|
|
||||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
|
||||||
import bisq.proto.grpc.StopRequest;
|
import bisq.proto.grpc.StopRequest;
|
||||||
import bisq.proto.grpc.TakeOfferReply;
|
import bisq.proto.grpc.TakeOfferReply;
|
||||||
import bisq.proto.grpc.TakeOfferRequest;
|
|
||||||
import bisq.proto.grpc.TradeInfo;
|
import bisq.proto.grpc.TradeInfo;
|
||||||
import bisq.proto.grpc.TxFeeRateInfo;
|
import bisq.proto.grpc.TxFeeRateInfo;
|
||||||
import bisq.proto.grpc.TxInfo;
|
import bisq.proto.grpc.TxInfo;
|
||||||
import bisq.proto.grpc.UnlockWalletRequest;
|
|
||||||
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
|
||||||
import bisq.proto.grpc.VerifyBsqSentToAddressRequest;
|
|
||||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
|
||||||
|
|
||||||
import protobuf.PaymentAccount;
|
import protobuf.PaymentAccount;
|
||||||
import protobuf.PaymentMethod;
|
import protobuf.PaymentMethod;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static bisq.cli.CryptoCurrencyUtil.isSupportedCryptoCurrency;
|
import static bisq.proto.grpc.EditOfferRequest.EditType;
|
||||||
import static java.util.Comparator.comparing;
|
|
||||||
import static java.util.stream.Collectors.toList;
|
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import bisq.cli.request.OffersServiceRequest;
|
||||||
|
import bisq.cli.request.PaymentAccountsServiceRequest;
|
||||||
|
import bisq.cli.request.TradesServiceRequest;
|
||||||
|
import bisq.cli.request.WalletsServiceRequest;
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
@ -85,9 +53,19 @@ import static protobuf.OfferPayload.Direction.SELL;
|
||||||
public final class GrpcClient {
|
public final class GrpcClient {
|
||||||
|
|
||||||
private final GrpcStubs grpcStubs;
|
private final GrpcStubs grpcStubs;
|
||||||
|
private final OffersServiceRequest offersServiceRequest;
|
||||||
|
private final TradesServiceRequest tradesServiceRequest;
|
||||||
|
private final WalletsServiceRequest walletsServiceRequest;
|
||||||
|
private final PaymentAccountsServiceRequest paymentAccountsServiceRequest;
|
||||||
|
|
||||||
public GrpcClient(String apiHost, int apiPort, String apiPassword) {
|
public GrpcClient(String apiHost,
|
||||||
|
int apiPort,
|
||||||
|
String apiPassword) {
|
||||||
this.grpcStubs = new GrpcStubs(apiHost, apiPort, apiPassword);
|
this.grpcStubs = new GrpcStubs(apiHost, apiPort, apiPassword);
|
||||||
|
this.offersServiceRequest = new OffersServiceRequest(grpcStubs);
|
||||||
|
this.tradesServiceRequest = new TradesServiceRequest(grpcStubs);
|
||||||
|
this.walletsServiceRequest = new WalletsServiceRequest(grpcStubs);
|
||||||
|
this.paymentAccountsServiceRequest = new PaymentAccountsServiceRequest(grpcStubs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
|
@ -96,108 +74,67 @@ public final class GrpcClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BalancesInfo getBalances() {
|
public BalancesInfo getBalances() {
|
||||||
return getBalances("");
|
return walletsServiceRequest.getBalances();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BsqBalanceInfo getBsqBalances() {
|
public BsqBalanceInfo getBsqBalances() {
|
||||||
return getBalances("BSQ").getBsq();
|
return walletsServiceRequest.getBsqBalances();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BtcBalanceInfo getBtcBalances() {
|
public BtcBalanceInfo getBtcBalances() {
|
||||||
return getBalances("BTC").getBtc();
|
return walletsServiceRequest.getBtcBalances();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BalancesInfo getBalances(String currencyCode) {
|
public BalancesInfo getBalances(String currencyCode) {
|
||||||
var request = GetBalancesRequest.newBuilder()
|
return walletsServiceRequest.getBalances(currencyCode);
|
||||||
.setCurrencyCode(currencyCode)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.getBalances(request).getBalances();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddressBalanceInfo getAddressBalance(String address) {
|
public AddressBalanceInfo getAddressBalance(String address) {
|
||||||
var request = GetAddressBalanceRequest.newBuilder()
|
return walletsServiceRequest.getAddressBalance(address);
|
||||||
.setAddress(address).build();
|
|
||||||
return grpcStubs.walletsService.getAddressBalance(request).getAddressBalanceInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getBtcPrice(String currencyCode) {
|
public double getBtcPrice(String currencyCode) {
|
||||||
var request = MarketPriceRequest.newBuilder()
|
return walletsServiceRequest.getBtcPrice(currencyCode);
|
||||||
.setCurrencyCode(currencyCode)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.priceService.getMarketPrice(request).getPrice();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<AddressBalanceInfo> getFundingAddresses() {
|
public List<AddressBalanceInfo> getFundingAddresses() {
|
||||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
return walletsServiceRequest.getFundingAddresses();
|
||||||
return grpcStubs.walletsService.getFundingAddresses(request).getAddressBalanceInfoList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUnusedBsqAddress() {
|
public String getUnusedBsqAddress() {
|
||||||
var request = GetUnusedBsqAddressRequest.newBuilder().build();
|
return walletsServiceRequest.getUnusedBsqAddress();
|
||||||
return grpcStubs.walletsService.getUnusedBsqAddress(request).getAddress();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUnusedBtcAddress() {
|
public String getUnusedBtcAddress() {
|
||||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
return walletsServiceRequest.getUnusedBtcAddress();
|
||||||
var addressBalances = grpcStubs.walletsService.getFundingAddresses(request)
|
|
||||||
.getAddressBalanceInfoList();
|
|
||||||
//noinspection OptionalGetWithoutIsPresent
|
|
||||||
return addressBalances.stream()
|
|
||||||
.filter(AddressBalanceInfo::getIsAddressUnused)
|
|
||||||
.findFirst()
|
|
||||||
.get()
|
|
||||||
.getAddress();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxInfo sendBsq(String address, String amount, String txFeeRate) {
|
public TxInfo sendBsq(String address, String amount, String txFeeRate) {
|
||||||
var request = SendBsqRequest.newBuilder()
|
return walletsServiceRequest.sendBsq(address, amount, txFeeRate);
|
||||||
.setAddress(address)
|
|
||||||
.setAmount(amount)
|
|
||||||
.setTxFeeRate(txFeeRate)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.sendBsq(request).getTxInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxInfo sendBtc(String address, String amount, String txFeeRate, String memo) {
|
public TxInfo sendBtc(String address, String amount, String txFeeRate, String memo) {
|
||||||
var request = SendBtcRequest.newBuilder()
|
return walletsServiceRequest.sendBtc(address, amount, txFeeRate, memo);
|
||||||
.setAddress(address)
|
|
||||||
.setAmount(amount)
|
|
||||||
.setTxFeeRate(txFeeRate)
|
|
||||||
.setMemo(memo)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.sendBtc(request).getTxInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean verifyBsqSentToAddress(String address, String amount) {
|
public boolean verifyBsqSentToAddress(String address, String amount) {
|
||||||
var request = VerifyBsqSentToAddressRequest.newBuilder()
|
return walletsServiceRequest.verifyBsqSentToAddress(address, amount);
|
||||||
.setAddress(address)
|
|
||||||
.setAmount(amount)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.verifyBsqSentToAddress(request).getIsAmountReceived();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxFeeRateInfo getTxFeeRate() {
|
public TxFeeRateInfo getTxFeeRate() {
|
||||||
var request = GetTxFeeRateRequest.newBuilder().build();
|
return walletsServiceRequest.getTxFeeRate();
|
||||||
return grpcStubs.walletsService.getTxFeeRate(request).getTxFeeRateInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxFeeRateInfo setTxFeeRate(long txFeeRate) {
|
public TxFeeRateInfo setTxFeeRate(long txFeeRate) {
|
||||||
var request = SetTxFeeRatePreferenceRequest.newBuilder()
|
return walletsServiceRequest.setTxFeeRate(txFeeRate);
|
||||||
.setTxFeeRatePreference(txFeeRate)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.setTxFeeRatePreference(request).getTxFeeRateInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxFeeRateInfo unsetTxFeeRate() {
|
public TxFeeRateInfo unsetTxFeeRate() {
|
||||||
var request = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
|
return walletsServiceRequest.unsetTxFeeRate();
|
||||||
return grpcStubs.walletsService.unsetTxFeeRatePreference(request).getTxFeeRateInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxInfo getTransaction(String txId) {
|
public TxInfo getTransaction(String txId) {
|
||||||
var request = GetTransactionRequest.newBuilder()
|
return walletsServiceRequest.getTransaction(txId);
|
||||||
.setTxId(txId)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.getTransaction(request).getTxInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo createFixedPricedOffer(String direction,
|
public OfferInfo createFixedPricedOffer(String direction,
|
||||||
|
@ -208,7 +145,7 @@ public final class GrpcClient {
|
||||||
double securityDeposit,
|
double securityDeposit,
|
||||||
String paymentAcctId,
|
String paymentAcctId,
|
||||||
String makerFeeCurrencyCode) {
|
String makerFeeCurrencyCode) {
|
||||||
return createOffer(direction,
|
return offersServiceRequest.createOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
|
@ -217,7 +154,8 @@ public final class GrpcClient {
|
||||||
0.00,
|
0.00,
|
||||||
securityDeposit,
|
securityDeposit,
|
||||||
paymentAcctId,
|
paymentAcctId,
|
||||||
makerFeeCurrencyCode);
|
makerFeeCurrencyCode,
|
||||||
|
0 /* no trigger price */);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo createMarketBasedPricedOffer(String direction,
|
public OfferInfo createMarketBasedPricedOffer(String direction,
|
||||||
|
@ -227,8 +165,9 @@ public final class GrpcClient {
|
||||||
double marketPriceMargin,
|
double marketPriceMargin,
|
||||||
double securityDeposit,
|
double securityDeposit,
|
||||||
String paymentAcctId,
|
String paymentAcctId,
|
||||||
String makerFeeCurrencyCode) {
|
String makerFeeCurrencyCode,
|
||||||
return createOffer(direction,
|
long triggerPrice) {
|
||||||
|
return offersServiceRequest.createOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
|
@ -237,7 +176,8 @@ public final class GrpcClient {
|
||||||
marketPriceMargin,
|
marketPriceMargin,
|
||||||
securityDeposit,
|
securityDeposit,
|
||||||
paymentAcctId,
|
paymentAcctId,
|
||||||
makerFeeCurrencyCode);
|
makerFeeCurrencyCode,
|
||||||
|
triggerPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo createOffer(String direction,
|
public OfferInfo createOffer(String direction,
|
||||||
|
@ -249,253 +189,192 @@ public final class GrpcClient {
|
||||||
double marketPriceMargin,
|
double marketPriceMargin,
|
||||||
double securityDeposit,
|
double securityDeposit,
|
||||||
String paymentAcctId,
|
String paymentAcctId,
|
||||||
String makerFeeCurrencyCode) {
|
String makerFeeCurrencyCode,
|
||||||
var request = CreateOfferRequest.newBuilder()
|
long triggerPrice) {
|
||||||
.setDirection(direction)
|
return offersServiceRequest.createOffer(direction,
|
||||||
.setCurrencyCode(currencyCode)
|
currencyCode,
|
||||||
.setAmount(amount)
|
amount,
|
||||||
.setMinAmount(minAmount)
|
minAmount,
|
||||||
.setUseMarketBasedPrice(useMarketBasedPrice)
|
useMarketBasedPrice,
|
||||||
.setPrice(fixedPrice)
|
fixedPrice,
|
||||||
.setMarketPriceMargin(marketPriceMargin)
|
marketPriceMargin,
|
||||||
.setBuyerSecurityDeposit(securityDeposit)
|
securityDeposit,
|
||||||
.setPaymentAccountId(paymentAcctId)
|
paymentAcctId,
|
||||||
.setMakerFeeCurrencyCode(makerFeeCurrencyCode)
|
makerFeeCurrencyCode,
|
||||||
.build();
|
triggerPrice);
|
||||||
return grpcStubs.offersService.createOffer(request).getOffer();
|
}
|
||||||
|
|
||||||
|
public void editOfferActivationState(String offerId, int enable) {
|
||||||
|
offersServiceRequest.editOfferActivationState(offerId, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOfferFixedPrice(String offerId, String priceAsString) {
|
||||||
|
offersServiceRequest.editOfferFixedPrice(offerId, priceAsString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOfferPriceMargin(String offerId, double marketPriceMargin) {
|
||||||
|
offersServiceRequest.editOfferPriceMargin(offerId, marketPriceMargin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOfferTriggerPrice(String offerId, long triggerPrice) {
|
||||||
|
offersServiceRequest.editOfferTriggerPrice(offerId, triggerPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOffer(String offerId,
|
||||||
|
String priceAsString,
|
||||||
|
boolean useMarketBasedPrice,
|
||||||
|
double marketPriceMargin,
|
||||||
|
long triggerPrice,
|
||||||
|
int enable,
|
||||||
|
EditType editType) {
|
||||||
|
// Take care when using this method directly:
|
||||||
|
// useMarketBasedPrice = true if margin based offer, false for fixed priced offer
|
||||||
|
// scaledPriceString fmt = ######.####
|
||||||
|
offersServiceRequest.editOffer(offerId,
|
||||||
|
priceAsString,
|
||||||
|
useMarketBasedPrice,
|
||||||
|
marketPriceMargin,
|
||||||
|
triggerPrice,
|
||||||
|
enable,
|
||||||
|
editType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelOffer(String offerId) {
|
public void cancelOffer(String offerId) {
|
||||||
var request = CancelOfferRequest.newBuilder()
|
offersServiceRequest.cancelOffer(offerId);
|
||||||
.setId(offerId)
|
|
||||||
.build();
|
|
||||||
grpcStubs.offersService.cancelOffer(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo getOffer(String offerId) {
|
public OfferInfo getOffer(String offerId) {
|
||||||
var request = GetOfferRequest.newBuilder()
|
return offersServiceRequest.getOffer(offerId);
|
||||||
.setId(offerId)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.offersService.getOffer(request).getOffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo getMyOffer(String offerId) {
|
public OfferInfo getMyOffer(String offerId) {
|
||||||
var request = GetMyOfferRequest.newBuilder()
|
return offersServiceRequest.getMyOffer(offerId);
|
||||||
.setId(offerId)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.offersService.getMyOffer(request).getOffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
||||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
return offersServiceRequest.getOffers(direction, currencyCode);
|
||||||
return getCryptoCurrencyOffers(direction, currencyCode);
|
|
||||||
} else {
|
|
||||||
var request = GetOffersRequest.newBuilder()
|
|
||||||
.setDirection(direction)
|
|
||||||
.setCurrencyCode(currencyCode)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.offersService.getOffers(request).getOffersList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getCryptoCurrencyOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getCryptoCurrencyOffers(String direction, String currencyCode) {
|
||||||
return getOffers(direction, "BTC").stream()
|
return offersServiceRequest.getCryptoCurrencyOffers(direction, currencyCode);
|
||||||
.filter(o -> o.getBaseCurrencyCode().equalsIgnoreCase(currencyCode))
|
|
||||||
.collect(toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getOffersSortedByDate(String currencyCode) {
|
public List<OfferInfo> getOffersSortedByDate(String currencyCode) {
|
||||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
return offersServiceRequest.getOffersSortedByDate(currencyCode);
|
||||||
offers.addAll(getOffers(BUY.name(), currencyCode));
|
|
||||||
offers.addAll(getOffers(SELL.name(), currencyCode));
|
|
||||||
return sortOffersByDate(offers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getOffersSortedByDate(String direction, String currencyCode) {
|
public List<OfferInfo> getOffersSortedByDate(String direction, String currencyCode) {
|
||||||
var offers = getOffers(direction, currencyCode);
|
return offersServiceRequest.getOffersSortedByDate(direction, currencyCode);
|
||||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getBsqOffersSortedByDate() {
|
public List<OfferInfo> getBsqOffersSortedByDate() {
|
||||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
return offersServiceRequest.getBsqOffersSortedByDate();
|
||||||
offers.addAll(getCryptoCurrencyOffers(BUY.name(), "BSQ"));
|
|
||||||
offers.addAll(getCryptoCurrencyOffers(SELL.name(), "BSQ"));
|
|
||||||
return sortOffersByDate(offers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
return offersServiceRequest.getMyOffers(direction, currencyCode);
|
||||||
return getMyCryptoCurrencyOffers(direction, currencyCode);
|
|
||||||
} else {
|
|
||||||
var request = GetMyOffersRequest.newBuilder()
|
|
||||||
.setDirection(direction)
|
|
||||||
.setCurrencyCode(currencyCode)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.offersService.getMyOffers(request).getOffersList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getMyCryptoCurrencyOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getMyCryptoCurrencyOffers(String direction, String currencyCode) {
|
||||||
return getMyOffers(direction, "BTC").stream()
|
return offersServiceRequest.getMyCryptoCurrencyOffers(direction, currencyCode);
|
||||||
.filter(o -> o.getBaseCurrencyCode().equalsIgnoreCase(currencyCode))
|
|
||||||
.collect(toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getMyOffersSortedByDate(String direction, String currencyCode) {
|
public List<OfferInfo> getMyOffersSortedByDate(String direction, String currencyCode) {
|
||||||
var offers = getMyOffers(direction, currencyCode);
|
return offersServiceRequest.getMyOffersSortedByDate(direction, currencyCode);
|
||||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getMyOffersSortedByDate(String currencyCode) {
|
public List<OfferInfo> getMyOffersSortedByDate(String currencyCode) {
|
||||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
return offersServiceRequest.getMyOffersSortedByDate(currencyCode);
|
||||||
offers.addAll(getMyOffers(BUY.name(), currencyCode));
|
|
||||||
offers.addAll(getMyOffers(SELL.name(), currencyCode));
|
|
||||||
return sortOffersByDate(offers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getMyBsqOffersSortedByDate() {
|
public List<OfferInfo> getMyBsqOffersSortedByDate() {
|
||||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
return offersServiceRequest.getMyBsqOffersSortedByDate();
|
||||||
offers.addAll(getMyCryptoCurrencyOffers(BUY.name(), "BSQ"));
|
|
||||||
offers.addAll(getMyCryptoCurrencyOffers(SELL.name(), "BSQ"));
|
|
||||||
return sortOffersByDate(offers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||||
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
return offersServiceRequest.getMostRecentOffer(direction, currencyCode);
|
||||||
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||||
return offerInfoList.stream()
|
return offersServiceRequest.sortOffersByDate(offerInfoList);
|
||||||
.sorted(comparing(OfferInfo::getDate))
|
|
||||||
.collect(toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||||
var request = TakeOfferRequest.newBuilder()
|
return tradesServiceRequest.getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||||
.setOfferId(offerId)
|
|
||||||
.setPaymentAccountId(paymentAccountId)
|
|
||||||
.setTakerFeeCurrencyCode(takerFeeCurrencyCode)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.tradesService.takeOffer(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||||
var reply = getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
|
return tradesServiceRequest.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||||
if (reply.hasTrade())
|
|
||||||
return reply.getTrade();
|
|
||||||
else
|
|
||||||
throw new IllegalStateException(reply.getFailureReason().getDescription());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeInfo getTrade(String tradeId) {
|
public TradeInfo getTrade(String tradeId) {
|
||||||
var request = GetTradeRequest.newBuilder()
|
return tradesServiceRequest.getTrade(tradeId);
|
||||||
.setTradeId(tradeId)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.tradesService.getTrade(request).getTrade();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void confirmPaymentStarted(String tradeId) {
|
public void confirmPaymentStarted(String tradeId) {
|
||||||
var request = ConfirmPaymentStartedRequest.newBuilder()
|
tradesServiceRequest.confirmPaymentStarted(tradeId);
|
||||||
.setTradeId(tradeId)
|
|
||||||
.build();
|
|
||||||
grpcStubs.tradesService.confirmPaymentStarted(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void confirmPaymentReceived(String tradeId) {
|
public void confirmPaymentReceived(String tradeId) {
|
||||||
var request = ConfirmPaymentReceivedRequest.newBuilder()
|
tradesServiceRequest.confirmPaymentReceived(tradeId);
|
||||||
.setTradeId(tradeId)
|
|
||||||
.build();
|
|
||||||
grpcStubs.tradesService.confirmPaymentReceived(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void keepFunds(String tradeId) {
|
public void keepFunds(String tradeId) {
|
||||||
var request = KeepFundsRequest.newBuilder()
|
tradesServiceRequest.keepFunds(tradeId);
|
||||||
.setTradeId(tradeId)
|
|
||||||
.build();
|
|
||||||
grpcStubs.tradesService.keepFunds(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void withdrawFunds(String tradeId, String address, String memo) {
|
public void withdrawFunds(String tradeId, String address, String memo) {
|
||||||
var request = WithdrawFundsRequest.newBuilder()
|
tradesServiceRequest.withdrawFunds(tradeId, address, memo);
|
||||||
.setTradeId(tradeId)
|
|
||||||
.setAddress(address)
|
|
||||||
.setMemo(memo)
|
|
||||||
.build();
|
|
||||||
grpcStubs.tradesService.withdrawFunds(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PaymentMethod> getPaymentMethods() {
|
public List<PaymentMethod> getPaymentMethods() {
|
||||||
var request = GetPaymentMethodsRequest.newBuilder().build();
|
return paymentAccountsServiceRequest.getPaymentMethods();
|
||||||
return grpcStubs.paymentAccountsService.getPaymentMethods(request).getPaymentMethodsList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPaymentAcctFormAsJson(String paymentMethodId) {
|
public String getPaymentAcctFormAsJson(String paymentMethodId) {
|
||||||
var request = GetPaymentAccountFormRequest.newBuilder()
|
return paymentAccountsServiceRequest.getPaymentAcctFormAsJson(paymentMethodId);
|
||||||
.setPaymentMethodId(paymentMethodId)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.paymentAccountsService.getPaymentAccountForm(request).getPaymentAccountFormJson();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaymentAccount createPaymentAccount(String json) {
|
public PaymentAccount createPaymentAccount(String json) {
|
||||||
var request = CreatePaymentAccountRequest.newBuilder()
|
return paymentAccountsServiceRequest.createPaymentAccount(json);
|
||||||
.setPaymentAccountForm(json)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.paymentAccountsService.createPaymentAccount(request).getPaymentAccount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PaymentAccount> getPaymentAccounts() {
|
public List<PaymentAccount> getPaymentAccounts() {
|
||||||
var request = GetPaymentAccountsRequest.newBuilder().build();
|
return paymentAccountsServiceRequest.getPaymentAccounts();
|
||||||
return grpcStubs.paymentAccountsService.getPaymentAccounts(request).getPaymentAccountsList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
String address,
|
String address,
|
||||||
boolean tradeInstant) {
|
boolean tradeInstant) {
|
||||||
var request = CreateCryptoCurrencyPaymentAccountRequest.newBuilder()
|
return paymentAccountsServiceRequest.createCryptoCurrencyPaymentAccount(accountName,
|
||||||
.setAccountName(accountName)
|
currencyCode,
|
||||||
.setCurrencyCode(currencyCode)
|
address,
|
||||||
.setAddress(address)
|
tradeInstant);
|
||||||
.setTradeInstant(tradeInstant)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.paymentAccountsService.createCryptoCurrencyPaymentAccount(request).getPaymentAccount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PaymentMethod> getCryptoPaymentMethods() {
|
public List<PaymentMethod> getCryptoPaymentMethods() {
|
||||||
var request = GetCryptoCurrencyPaymentMethodsRequest.newBuilder().build();
|
return paymentAccountsServiceRequest.getCryptoPaymentMethods();
|
||||||
return grpcStubs.paymentAccountsService.getCryptoCurrencyPaymentMethods(request).getPaymentMethodsList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void lockWallet() {
|
public void lockWallet() {
|
||||||
var request = LockWalletRequest.newBuilder().build();
|
walletsServiceRequest.lockWallet();
|
||||||
grpcStubs.walletsService.lockWallet(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unlockWallet(String walletPassword, long timeout) {
|
public void unlockWallet(String walletPassword, long timeout) {
|
||||||
var request = UnlockWalletRequest.newBuilder()
|
walletsServiceRequest.unlockWallet(walletPassword, timeout);
|
||||||
.setPassword(walletPassword)
|
|
||||||
.setTimeout(timeout).build();
|
|
||||||
grpcStubs.walletsService.unlockWallet(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeWalletPassword(String walletPassword) {
|
public void removeWalletPassword(String walletPassword) {
|
||||||
var request = RemoveWalletPasswordRequest.newBuilder()
|
walletsServiceRequest.removeWalletPassword(walletPassword);
|
||||||
.setPassword(walletPassword).build();
|
|
||||||
grpcStubs.walletsService.removeWalletPassword(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWalletPassword(String walletPassword) {
|
public void setWalletPassword(String walletPassword) {
|
||||||
var request = SetWalletPasswordRequest.newBuilder()
|
walletsServiceRequest.setWalletPassword(walletPassword);
|
||||||
.setPassword(walletPassword).build();
|
|
||||||
grpcStubs.walletsService.setWalletPassword(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWalletPassword(String oldWalletPassword, String newWalletPassword) {
|
public void setWalletPassword(String oldWalletPassword, String newWalletPassword) {
|
||||||
var request = SetWalletPasswordRequest.newBuilder()
|
walletsServiceRequest.setWalletPassword(oldWalletPassword, newWalletPassword);
|
||||||
.setPassword(oldWalletPassword)
|
|
||||||
.setNewPassword(newWalletPassword).build();
|
|
||||||
grpcStubs.walletsService.setWalletPassword(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
|
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
|
||||||
|
|
|
@ -25,6 +25,7 @@ public enum Method {
|
||||||
confirmpaymentreceived,
|
confirmpaymentreceived,
|
||||||
confirmpaymentstarted,
|
confirmpaymentstarted,
|
||||||
createoffer,
|
createoffer,
|
||||||
|
editoffer,
|
||||||
createpaymentacct,
|
createpaymentacct,
|
||||||
createcryptopaymentacct,
|
createcryptopaymentacct,
|
||||||
getaddressbalance,
|
getaddressbalance,
|
||||||
|
|
|
@ -147,58 +147,126 @@ public class TableFormat {
|
||||||
|
|
||||||
public static String formatOfferTable(List<OfferInfo> offers, String currencyCode) {
|
public static String formatOfferTable(List<OfferInfo> offers, String currencyCode) {
|
||||||
if (offers == null || offers.isEmpty())
|
if (offers == null || offers.isEmpty())
|
||||||
throw new IllegalArgumentException(format("%s offers argument is empty", currencyCode.toLowerCase()));
|
throw new IllegalArgumentException(format("%s offer list is empty", currencyCode.toLowerCase()));
|
||||||
|
|
||||||
String baseCurrencyCode = offers.get(0).getBaseCurrencyCode();
|
String baseCurrencyCode = offers.get(0).getBaseCurrencyCode();
|
||||||
|
boolean isMyOffer = offers.get(0).getIsMyOffer();
|
||||||
return baseCurrencyCode.equalsIgnoreCase("BTC")
|
return baseCurrencyCode.equalsIgnoreCase("BTC")
|
||||||
? formatFiatOfferTable(offers, currencyCode)
|
? formatFiatOfferTable(offers, currencyCode, isMyOffer)
|
||||||
: formatCryptoCurrencyOfferTable(offers, baseCurrencyCode);
|
: formatCryptoCurrencyOfferTable(offers, baseCurrencyCode, isMyOffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatFiatOfferTable(List<OfferInfo> offers, String fiatCurrencyCode) {
|
private static String formatFiatOfferTable(List<OfferInfo> offers,
|
||||||
|
String fiatCurrencyCode,
|
||||||
|
boolean isMyOffer) {
|
||||||
// Some column values might be longer than header, so we need to calculate them.
|
// Some column values might be longer than header, so we need to calculate them.
|
||||||
int amountColWith = getLongestAmountColWidth(offers);
|
int amountColWith = getLongestAmountColWidth(offers);
|
||||||
int volumeColWidth = getLongestVolumeColWidth(offers);
|
int volumeColWidth = getLongestVolumeColWidth(offers);
|
||||||
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
|
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
|
||||||
String headersFormat = COL_HEADER_DIRECTION + COL_HEADER_DELIMITER
|
// "Enabled" and "Trigger Price" columns are displayed for my offers only.
|
||||||
+ COL_HEADER_PRICE + COL_HEADER_DELIMITER // includes %s -> fiatCurrencyCode
|
String enabledHeaderFormat = isMyOffer ?
|
||||||
|
COL_HEADER_ENABLED + COL_HEADER_DELIMITER
|
||||||
|
: "";
|
||||||
|
String triggerPriceHeaderFormat = isMyOffer ?
|
||||||
|
// COL_HEADER_TRIGGER_PRICE includes %s -> fiatCurrencyCode
|
||||||
|
COL_HEADER_TRIGGER_PRICE + COL_HEADER_DELIMITER
|
||||||
|
: "";
|
||||||
|
String headersFormat = enabledHeaderFormat
|
||||||
|
+ COL_HEADER_DIRECTION + COL_HEADER_DELIMITER
|
||||||
|
// COL_HEADER_PRICE includes %s -> fiatCurrencyCode
|
||||||
|
+ COL_HEADER_PRICE + COL_HEADER_DELIMITER
|
||||||
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
|
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
|
||||||
// COL_HEADER_VOLUME includes %s -> fiatCurrencyCode
|
// COL_HEADER_VOLUME includes %s -> fiatCurrencyCode
|
||||||
+ padStart(COL_HEADER_VOLUME, volumeColWidth, ' ') + COL_HEADER_DELIMITER
|
+ padStart(COL_HEADER_VOLUME, volumeColWidth, ' ') + COL_HEADER_DELIMITER
|
||||||
|
+ triggerPriceHeaderFormat
|
||||||
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
|
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
|
||||||
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
|
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
|
||||||
+ COL_HEADER_UUID.trim() + "%n";
|
+ COL_HEADER_UUID.trim() + "%n";
|
||||||
String headerLine = format(headersFormat,
|
String headerLine = format(headersFormat,
|
||||||
fiatCurrencyCode.toUpperCase(),
|
fiatCurrencyCode.toUpperCase(),
|
||||||
fiatCurrencyCode.toUpperCase());
|
fiatCurrencyCode.toUpperCase(),
|
||||||
String colDataFormat = "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s"
|
// COL_HEADER_TRIGGER_PRICE includes %s -> fiatCurrencyCode
|
||||||
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s"
|
isMyOffer ? fiatCurrencyCode.toUpperCase() : "");
|
||||||
+ " %" + amountColWith + "s"
|
String colDataFormat = getFiatOfferColDataFormat(isMyOffer,
|
||||||
+ " %" + (volumeColWidth - 1) + "s"
|
amountColWith,
|
||||||
+ " %-" + paymentMethodColWidth + "s"
|
volumeColWidth,
|
||||||
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
|
paymentMethodColWidth);
|
||||||
+ " %-" + COL_HEADER_UUID.length() + "s";
|
return formattedFiatOfferTable(offers, isMyOffer, headerLine, colDataFormat);
|
||||||
return headerLine
|
|
||||||
+ offers.stream()
|
|
||||||
.map(o -> format(colDataFormat,
|
|
||||||
o.getDirection(),
|
|
||||||
formatPrice(o.getPrice()),
|
|
||||||
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
|
||||||
formatVolumeRange(o.getMinVolume(), o.getVolume()),
|
|
||||||
o.getPaymentMethodShortName(),
|
|
||||||
formatTimestamp(o.getDate()),
|
|
||||||
o.getId()))
|
|
||||||
.collect(Collectors.joining("\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatCryptoCurrencyOfferTable(List<OfferInfo> offers, String cryptoCurrencyCode) {
|
private static String formattedFiatOfferTable(List<OfferInfo> offers,
|
||||||
|
boolean isMyOffer,
|
||||||
|
String headerLine,
|
||||||
|
String colDataFormat) {
|
||||||
|
if (isMyOffer) {
|
||||||
|
return headerLine
|
||||||
|
+ offers.stream()
|
||||||
|
.map(o -> format(colDataFormat,
|
||||||
|
formatEnabled(o),
|
||||||
|
o.getDirection(),
|
||||||
|
formatPrice(o.getPrice()),
|
||||||
|
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||||
|
formatVolumeRange(o.getMinVolume(), o.getVolume()),
|
||||||
|
o.getTriggerPrice() == 0 ? "" : formatPrice(o.getTriggerPrice()),
|
||||||
|
o.getPaymentMethodShortName(),
|
||||||
|
formatTimestamp(o.getDate()),
|
||||||
|
o.getId()))
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
} else {
|
||||||
|
return headerLine
|
||||||
|
+ offers.stream()
|
||||||
|
.map(o -> format(colDataFormat,
|
||||||
|
o.getDirection(),
|
||||||
|
formatPrice(o.getPrice()),
|
||||||
|
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||||
|
formatVolumeRange(o.getMinVolume(), o.getVolume()),
|
||||||
|
o.getPaymentMethodShortName(),
|
||||||
|
formatTimestamp(o.getDate()),
|
||||||
|
o.getId()))
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getFiatOfferColDataFormat(boolean isMyOffer,
|
||||||
|
int amountColWith,
|
||||||
|
int volumeColWidth,
|
||||||
|
int paymentMethodColWidth) {
|
||||||
|
if (isMyOffer) {
|
||||||
|
return "%-" + (COL_HEADER_ENABLED.length() + COL_HEADER_DELIMITER.length()) + "s"
|
||||||
|
+ "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s"
|
||||||
|
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s"
|
||||||
|
+ " %" + amountColWith + "s"
|
||||||
|
+ " %" + (volumeColWidth - 1) + "s"
|
||||||
|
+ " %" + (COL_HEADER_TRIGGER_PRICE.length() - 1) + "s"
|
||||||
|
+ " %-" + paymentMethodColWidth + "s"
|
||||||
|
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
|
||||||
|
+ " %-" + COL_HEADER_UUID.length() + "s";
|
||||||
|
} else {
|
||||||
|
return "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s"
|
||||||
|
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s"
|
||||||
|
+ " %" + amountColWith + "s"
|
||||||
|
+ " %" + (volumeColWidth - 1) + "s"
|
||||||
|
+ " %-" + paymentMethodColWidth + "s"
|
||||||
|
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
|
||||||
|
+ " %-" + COL_HEADER_UUID.length() + "s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatCryptoCurrencyOfferTable(List<OfferInfo> offers,
|
||||||
|
String cryptoCurrencyCode,
|
||||||
|
boolean isMyOffer) {
|
||||||
// Some column values might be longer than header, so we need to calculate them.
|
// Some column values might be longer than header, so we need to calculate them.
|
||||||
int directionColWidth = getLongestDirectionColWidth(offers);
|
int directionColWidth = getLongestDirectionColWidth(offers);
|
||||||
int amountColWith = getLongestAmountColWidth(offers);
|
int amountColWith = getLongestAmountColWidth(offers);
|
||||||
int volumeColWidth = getLongestCryptoCurrencyVolumeColWidth(offers);
|
int volumeColWidth = getLongestCryptoCurrencyVolumeColWidth(offers);
|
||||||
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
|
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
|
||||||
|
// "Enabled" column is displayed for my offers only.
|
||||||
|
String enabledHeaderFormat = isMyOffer ?
|
||||||
|
COL_HEADER_ENABLED + COL_HEADER_DELIMITER
|
||||||
|
: "";
|
||||||
// TODO use memoize function to avoid duplicate the formatting done above?
|
// TODO use memoize function to avoid duplicate the formatting done above?
|
||||||
String headersFormat = padEnd(COL_HEADER_DIRECTION, directionColWidth, ' ') + COL_HEADER_DELIMITER
|
String headersFormat = enabledHeaderFormat
|
||||||
|
+ padEnd(COL_HEADER_DIRECTION, directionColWidth, ' ') + COL_HEADER_DELIMITER
|
||||||
+ COL_HEADER_PRICE_OF_ALTCOIN + COL_HEADER_DELIMITER // includes %s -> cryptoCurrencyCode
|
+ COL_HEADER_PRICE_OF_ALTCOIN + COL_HEADER_DELIMITER // includes %s -> cryptoCurrencyCode
|
||||||
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
|
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
|
||||||
// COL_HEADER_VOLUME includes %s -> cryptoCurrencyCode
|
// COL_HEADER_VOLUME includes %s -> cryptoCurrencyCode
|
||||||
|
@ -209,24 +277,59 @@ public class TableFormat {
|
||||||
String headerLine = format(headersFormat,
|
String headerLine = format(headersFormat,
|
||||||
cryptoCurrencyCode.toUpperCase(),
|
cryptoCurrencyCode.toUpperCase(),
|
||||||
cryptoCurrencyCode.toUpperCase());
|
cryptoCurrencyCode.toUpperCase());
|
||||||
String colDataFormat = "%-" + directionColWidth + "s"
|
String colDataFormat;
|
||||||
+ "%" + (COL_HEADER_PRICE_OF_ALTCOIN.length() + 1) + "s"
|
if (isMyOffer) {
|
||||||
+ " %" + amountColWith + "s"
|
colDataFormat = "%-" + (COL_HEADER_ENABLED.length() + COL_HEADER_DELIMITER.length()) + "s"
|
||||||
+ " %" + (volumeColWidth - 1) + "s"
|
+ "%-" + directionColWidth + "s"
|
||||||
+ " %-" + paymentMethodColWidth + "s"
|
+ "%" + (COL_HEADER_PRICE_OF_ALTCOIN.length() + 1) + "s"
|
||||||
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
|
+ " %" + amountColWith + "s"
|
||||||
+ " %-" + COL_HEADER_UUID.length() + "s";
|
+ " %" + (volumeColWidth - 1) + "s"
|
||||||
return headerLine
|
+ " %-" + paymentMethodColWidth + "s"
|
||||||
+ offers.stream()
|
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
|
||||||
.map(o -> format(colDataFormat,
|
+ " %-" + COL_HEADER_UUID.length() + "s";
|
||||||
directionFormat.apply(o),
|
} else {
|
||||||
formatCryptoCurrencyPrice(o.getPrice()),
|
colDataFormat = "%-" + directionColWidth + "s"
|
||||||
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
+ "%" + (COL_HEADER_PRICE_OF_ALTCOIN.length() + 1) + "s"
|
||||||
formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()),
|
+ " %" + amountColWith + "s"
|
||||||
o.getPaymentMethodShortName(),
|
+ " %" + (volumeColWidth - 1) + "s"
|
||||||
formatTimestamp(o.getDate()),
|
+ " %-" + paymentMethodColWidth + "s"
|
||||||
o.getId()))
|
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
|
||||||
.collect(Collectors.joining("\n"));
|
+ " %-" + COL_HEADER_UUID.length() + "s";
|
||||||
|
}
|
||||||
|
if (isMyOffer) {
|
||||||
|
return headerLine
|
||||||
|
+ offers.stream()
|
||||||
|
.map(o -> format(colDataFormat,
|
||||||
|
formatEnabled(o),
|
||||||
|
directionFormat.apply(o),
|
||||||
|
formatCryptoCurrencyPrice(o.getPrice()),
|
||||||
|
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||||
|
formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()),
|
||||||
|
o.getPaymentMethodShortName(),
|
||||||
|
formatTimestamp(o.getDate()),
|
||||||
|
o.getId()))
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
} else {
|
||||||
|
return headerLine
|
||||||
|
+ offers.stream()
|
||||||
|
.map(o -> format(colDataFormat,
|
||||||
|
directionFormat.apply(o),
|
||||||
|
formatCryptoCurrencyPrice(o.getPrice()),
|
||||||
|
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||||
|
formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()),
|
||||||
|
o.getPaymentMethodShortName(),
|
||||||
|
formatTimestamp(o.getDate()),
|
||||||
|
o.getId()))
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String formatEnabled(OfferInfo offerInfo) {
|
||||||
|
if (offerInfo.getIsMyOffer() && offerInfo.getIsMyPendingOffer())
|
||||||
|
return "PENDING";
|
||||||
|
else
|
||||||
|
return offerInfo.getIsActivated() ? "YES" : "NO";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getLongestPaymentMethodColWidth(List<OfferInfo> offers) {
|
private static int getLongestPaymentMethodColWidth(List<OfferInfo> offers) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import joptsimple.OptionSpec;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@ -64,6 +65,9 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||||
return options.has(helpOpt);
|
return options.has(helpOpt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected final Predicate<OptionSpec<String>> valueNotSpecified = (opt) ->
|
||||||
|
!options.hasArgument(opt) || options.valueOf(opt).isEmpty();
|
||||||
|
|
||||||
private final Function<OptionException, String> cliExceptionMessageStyle = (ex) -> {
|
private final Function<OptionException, String> cliExceptionMessageStyle = (ex) -> {
|
||||||
if (ex.getMessage() == null)
|
if (ex.getMessage() == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
281
cli/src/main/java/bisq/cli/opts/EditOfferOptionParser.java
Normal file
281
cli/src/main/java/bisq/cli/opts/EditOfferOptionParser.java
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
/*
|
||||||
|
* 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.EditOfferRequest;
|
||||||
|
|
||||||
|
import joptsimple.OptionSpec;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import static bisq.cli.opts.OptLabel.*;
|
||||||
|
import static bisq.proto.grpc.EditOfferRequest.EditType.*;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
public class EditOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||||
|
|
||||||
|
static int OPT_ENABLE_ON = 1;
|
||||||
|
static int OPT_ENABLE_OFF = 0;
|
||||||
|
static int OPT_ENABLE_IGNORED = -1;
|
||||||
|
|
||||||
|
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to cancel")
|
||||||
|
.withRequiredArg();
|
||||||
|
|
||||||
|
final OptionSpec<String> fixedPriceOpt = parser.accepts(OPT_FIXED_PRICE, "fixed btc price")
|
||||||
|
.withOptionalArg()
|
||||||
|
.defaultsTo("0");
|
||||||
|
|
||||||
|
final OptionSpec<String> mktPriceMarginOpt = parser.accepts(OPT_MKT_PRICE_MARGIN,
|
||||||
|
"market btc price margin (%)")
|
||||||
|
.withOptionalArg()
|
||||||
|
.defaultsTo("0.00");
|
||||||
|
|
||||||
|
final OptionSpec<String> triggerPriceOpt = parser.accepts(OPT_TRIGGER_PRICE,
|
||||||
|
"trigger price (applies to mkt price margin based offers)")
|
||||||
|
.withOptionalArg()
|
||||||
|
.defaultsTo("0");
|
||||||
|
|
||||||
|
// The 'enable' string opt is optional, and can be empty (meaning do not change
|
||||||
|
// activation state). For this reason, a boolean type is not used (can only be
|
||||||
|
// true or false).
|
||||||
|
final OptionSpec<String> enableOpt = parser.accepts(OPT_ENABLE,
|
||||||
|
"enable or disable offer")
|
||||||
|
.withOptionalArg()
|
||||||
|
.ofType(String.class);
|
||||||
|
|
||||||
|
private EditOfferRequest.EditType offerEditType;
|
||||||
|
|
||||||
|
public EditOfferOptionParser(String[] args) {
|
||||||
|
super(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditOfferOptionParser parse() {
|
||||||
|
super.parse();
|
||||||
|
|
||||||
|
// Short circuit opt validation if user just wants help.
|
||||||
|
if (options.has(helpOpt))
|
||||||
|
return this;
|
||||||
|
|
||||||
|
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
|
||||||
|
throw new IllegalArgumentException("no offer id specified");
|
||||||
|
|
||||||
|
boolean hasNoEditDetails = !options.has(fixedPriceOpt)
|
||||||
|
&& !options.has(mktPriceMarginOpt)
|
||||||
|
&& !options.has(triggerPriceOpt)
|
||||||
|
&& !options.has(enableOpt);
|
||||||
|
if (hasNoEditDetails)
|
||||||
|
throw new IllegalArgumentException("no edit details specified");
|
||||||
|
|
||||||
|
if (options.has(enableOpt)) {
|
||||||
|
if (valueNotSpecified.test(enableOpt))
|
||||||
|
throw new IllegalArgumentException("invalid enable value specified, must be true|false");
|
||||||
|
|
||||||
|
var enableOptValue = options.valueOf(enableOpt);
|
||||||
|
if (!enableOptValue.equalsIgnoreCase("true")
|
||||||
|
&& !enableOptValue.equalsIgnoreCase("false"))
|
||||||
|
throw new IllegalArgumentException("invalid enable value specified, must be true|false");
|
||||||
|
|
||||||
|
// A single enable opt is a valid opt combo.
|
||||||
|
boolean enableOptIsOnlyOpt = !options.has(fixedPriceOpt)
|
||||||
|
&& !options.has(mktPriceMarginOpt)
|
||||||
|
&& !options.has(triggerPriceOpt);
|
||||||
|
if (enableOptIsOnlyOpt) {
|
||||||
|
offerEditType = ACTIVATION_STATE_ONLY;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.has(fixedPriceOpt)) {
|
||||||
|
if (valueNotSpecified.test(fixedPriceOpt))
|
||||||
|
throw new IllegalArgumentException("no fixed price specified");
|
||||||
|
|
||||||
|
String fixedPriceAsString = options.valueOf(fixedPriceOpt);
|
||||||
|
verifyStringIsValidDouble(fixedPriceAsString);
|
||||||
|
|
||||||
|
boolean fixedPriceOptIsOnlyOpt = !options.has(mktPriceMarginOpt)
|
||||||
|
&& !options.has(triggerPriceOpt)
|
||||||
|
&& !options.has(enableOpt);
|
||||||
|
if (fixedPriceOptIsOnlyOpt) {
|
||||||
|
offerEditType = FIXED_PRICE_ONLY;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean fixedPriceOptAndEnableOptAreOnlyOpts = options.has(enableOpt)
|
||||||
|
&& !options.has(mktPriceMarginOpt)
|
||||||
|
&& !options.has(triggerPriceOpt);
|
||||||
|
if (fixedPriceOptAndEnableOptAreOnlyOpts) {
|
||||||
|
offerEditType = FIXED_PRICE_AND_ACTIVATION_STATE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.has(mktPriceMarginOpt)) {
|
||||||
|
if (valueNotSpecified.test(mktPriceMarginOpt))
|
||||||
|
throw new IllegalArgumentException("no mkt price margin specified");
|
||||||
|
|
||||||
|
String priceMarginAsString = options.valueOf(mktPriceMarginOpt);
|
||||||
|
if (priceMarginAsString.isEmpty())
|
||||||
|
throw new IllegalArgumentException("no market price margin specified");
|
||||||
|
|
||||||
|
verifyStringIsValidDouble(priceMarginAsString);
|
||||||
|
|
||||||
|
boolean mktPriceMarginOptIsOnlyOpt = !options.has(triggerPriceOpt)
|
||||||
|
&& !options.has(fixedPriceOpt)
|
||||||
|
&& !options.has(enableOpt);
|
||||||
|
if (mktPriceMarginOptIsOnlyOpt) {
|
||||||
|
offerEditType = MKT_PRICE_MARGIN_ONLY;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean mktPriceMarginOptAndEnableOptAreOnlyOpts = options.has(enableOpt)
|
||||||
|
&& !options.has(triggerPriceOpt);
|
||||||
|
if (mktPriceMarginOptAndEnableOptAreOnlyOpts) {
|
||||||
|
offerEditType = MKT_PRICE_MARGIN_AND_ACTIVATION_STATE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.has(triggerPriceOpt)) {
|
||||||
|
if (valueNotSpecified.test(triggerPriceOpt))
|
||||||
|
throw new IllegalArgumentException("no trigger price specified");
|
||||||
|
|
||||||
|
String triggerPriceAsString = options.valueOf(fixedPriceOpt);
|
||||||
|
if (triggerPriceAsString.isEmpty())
|
||||||
|
throw new IllegalArgumentException("trigger price not specified");
|
||||||
|
|
||||||
|
verifyStringIsValidDouble(triggerPriceAsString);
|
||||||
|
|
||||||
|
boolean triggerPriceOptIsOnlyOpt = !options.has(mktPriceMarginOpt)
|
||||||
|
&& !options.has(fixedPriceOpt)
|
||||||
|
&& !options.has(enableOpt);
|
||||||
|
if (triggerPriceOptIsOnlyOpt) {
|
||||||
|
offerEditType = TRIGGER_PRICE_ONLY;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean triggerPriceOptAndEnableOptAreOnlyOpts = !options.has(mktPriceMarginOpt)
|
||||||
|
&& !options.has(fixedPriceOpt)
|
||||||
|
&& options.has(enableOpt);
|
||||||
|
if (triggerPriceOptAndEnableOptAreOnlyOpts) {
|
||||||
|
offerEditType = TRIGGER_PRICE_AND_ACTIVATION_STATE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.has(mktPriceMarginOpt) && options.has(fixedPriceOpt))
|
||||||
|
throw new IllegalArgumentException("cannot specify market price margin and fixed price");
|
||||||
|
|
||||||
|
if (options.has(fixedPriceOpt) && options.has(triggerPriceOpt))
|
||||||
|
throw new IllegalArgumentException("trigger price cannot be set on fixed price offers");
|
||||||
|
|
||||||
|
if (options.has(mktPriceMarginOpt) && options.has(triggerPriceOpt) && !options.has(enableOpt)) {
|
||||||
|
offerEditType = MKT_PRICE_MARGIN_AND_TRIGGER_PRICE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.has(mktPriceMarginOpt) && options.has(triggerPriceOpt) && options.has(enableOpt)) {
|
||||||
|
offerEditType = MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOfferId() {
|
||||||
|
return options.valueOf(offerIdOpt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFixedPrice() {
|
||||||
|
if (offerEditType.equals(FIXED_PRICE_ONLY) || offerEditType.equals(FIXED_PRICE_AND_ACTIVATION_STATE)) {
|
||||||
|
return options.has(fixedPriceOpt) ? options.valueOf(fixedPriceOpt) : "0";
|
||||||
|
} else {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTriggerPrice() {
|
||||||
|
if (offerEditType.equals(TRIGGER_PRICE_ONLY)
|
||||||
|
|| offerEditType.equals(TRIGGER_PRICE_AND_ACTIVATION_STATE)
|
||||||
|
|| offerEditType.equals(MKT_PRICE_MARGIN_AND_TRIGGER_PRICE)
|
||||||
|
|| offerEditType.equals(MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE)) {
|
||||||
|
return options.has(triggerPriceOpt) ? options.valueOf(triggerPriceOpt) : "0";
|
||||||
|
} else {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getTriggerPriceAsBigDecimal() {
|
||||||
|
return new BigDecimal(getTriggerPrice());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMktPriceMargin() {
|
||||||
|
if (offerEditType.equals(MKT_PRICE_MARGIN_ONLY)
|
||||||
|
|| offerEditType.equals(MKT_PRICE_MARGIN_AND_ACTIVATION_STATE)
|
||||||
|
|| offerEditType.equals(MKT_PRICE_MARGIN_AND_TRIGGER_PRICE)
|
||||||
|
|| offerEditType.equals(MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE)) {
|
||||||
|
return isUsingMktPriceMargin() ? options.valueOf(mktPriceMarginOpt) : "0.00";
|
||||||
|
} else {
|
||||||
|
return "0.00";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMktPriceMarginAsBigDecimal() {
|
||||||
|
return new BigDecimal(options.valueOf(mktPriceMarginOpt));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUsingMktPriceMargin() {
|
||||||
|
return !offerEditType.equals(FIXED_PRICE_ONLY)
|
||||||
|
&& !offerEditType.equals(FIXED_PRICE_AND_ACTIVATION_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEnableAsSignedInt() {
|
||||||
|
// Client sends sint32 in grpc request, not a bool that can only be true or false.
|
||||||
|
// If enable = -1, do not change activation state
|
||||||
|
// If enable = 0, set state = AVAILABLE
|
||||||
|
// If enable = 1, set state = DEACTIVATED
|
||||||
|
@Nullable
|
||||||
|
Boolean input = isEnable();
|
||||||
|
return input == null
|
||||||
|
? OPT_ENABLE_IGNORED
|
||||||
|
: input ? OPT_ENABLE_ON : OPT_ENABLE_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Boolean isEnable() {
|
||||||
|
return options.has(enableOpt)
|
||||||
|
? Boolean.valueOf(options.valueOf(enableOpt))
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditOfferRequest.EditType getOfferEditType() {
|
||||||
|
return offerEditType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyStringIsValidDouble(String string) {
|
||||||
|
try {
|
||||||
|
Double.valueOf(string);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
throw new IllegalArgumentException(format("%s is not a number", string));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ public class OptLabel {
|
||||||
public final static String OPT_CURRENCY_CODE = "currency-code";
|
public final static String OPT_CURRENCY_CODE = "currency-code";
|
||||||
public final static String OPT_DIRECTION = "direction";
|
public final static String OPT_DIRECTION = "direction";
|
||||||
public final static String OPT_DISPUTE_AGENT_TYPE = "dispute-agent-type";
|
public final static String OPT_DISPUTE_AGENT_TYPE = "dispute-agent-type";
|
||||||
|
public final static String OPT_ENABLE = "enable";
|
||||||
public final static String OPT_FEE_CURRENCY = "fee-currency";
|
public final static String OPT_FEE_CURRENCY = "fee-currency";
|
||||||
public final static String OPT_FIXED_PRICE = "fixed-price";
|
public final static String OPT_FIXED_PRICE = "fixed-price";
|
||||||
public final static String OPT_HELP = "help";
|
public final static String OPT_HELP = "help";
|
||||||
|
@ -47,6 +48,7 @@ public class OptLabel {
|
||||||
public final static String OPT_TRADE_INSTANT = "trade-instant";
|
public final static String OPT_TRADE_INSTANT = "trade-instant";
|
||||||
public final static String OPT_TIMEOUT = "timeout";
|
public final static String OPT_TIMEOUT = "timeout";
|
||||||
public final static String OPT_TRANSACTION_ID = "transaction-id";
|
public final static String OPT_TRANSACTION_ID = "transaction-id";
|
||||||
|
public final static String OPT_TRIGGER_PRICE = "trigger-price";
|
||||||
public final static String OPT_TX_FEE_RATE = "tx-fee-rate";
|
public final static String OPT_TX_FEE_RATE = "tx-fee-rate";
|
||||||
public final static String OPT_WALLET_PASSWORD = "wallet-password";
|
public final static String OPT_WALLET_PASSWORD = "wallet-password";
|
||||||
public final static String OPT_NEW_WALLET_PASSWORD = "new-wallet-password";
|
public final static String OPT_NEW_WALLET_PASSWORD = "new-wallet-password";
|
||||||
|
|
319
cli/src/main/java/bisq/cli/request/OffersServiceRequest.java
Normal file
319
cli/src/main/java/bisq/cli/request/OffersServiceRequest.java
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
/*
|
||||||
|
* 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.request;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.CancelOfferRequest;
|
||||||
|
import bisq.proto.grpc.CreateOfferRequest;
|
||||||
|
import bisq.proto.grpc.EditOfferRequest;
|
||||||
|
import bisq.proto.grpc.GetMyOfferRequest;
|
||||||
|
import bisq.proto.grpc.GetMyOffersRequest;
|
||||||
|
import bisq.proto.grpc.GetOfferRequest;
|
||||||
|
import bisq.proto.grpc.GetOffersRequest;
|
||||||
|
import bisq.proto.grpc.OfferInfo;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static bisq.proto.grpc.EditOfferRequest.EditType.ACTIVATION_STATE_ONLY;
|
||||||
|
import static bisq.proto.grpc.EditOfferRequest.EditType.FIXED_PRICE_ONLY;
|
||||||
|
import static bisq.proto.grpc.EditOfferRequest.EditType.MKT_PRICE_MARGIN_ONLY;
|
||||||
|
import static bisq.proto.grpc.EditOfferRequest.EditType.TRIGGER_PRICE_ONLY;
|
||||||
|
import static java.util.Comparator.comparing;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
import static protobuf.OfferPayload.Direction.BUY;
|
||||||
|
import static protobuf.OfferPayload.Direction.SELL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.cli.GrpcStubs;
|
||||||
|
|
||||||
|
public class OffersServiceRequest {
|
||||||
|
|
||||||
|
private final Function<Long, String> scaledPriceStringRequestFormat = (price) -> {
|
||||||
|
BigDecimal factor = new BigDecimal(10).pow(4);
|
||||||
|
//noinspection BigDecimalMethodWithoutRoundingCalled
|
||||||
|
return new BigDecimal(price).divide(factor).toPlainString();
|
||||||
|
};
|
||||||
|
|
||||||
|
private final GrpcStubs grpcStubs;
|
||||||
|
|
||||||
|
public OffersServiceRequest(GrpcStubs grpcStubs) {
|
||||||
|
this.grpcStubs = grpcStubs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferInfo createFixedPricedOffer(String direction,
|
||||||
|
String currencyCode,
|
||||||
|
long amount,
|
||||||
|
long minAmount,
|
||||||
|
String fixedPrice,
|
||||||
|
double securityDeposit,
|
||||||
|
String paymentAcctId,
|
||||||
|
String makerFeeCurrencyCode) {
|
||||||
|
return createOffer(direction,
|
||||||
|
currencyCode,
|
||||||
|
amount,
|
||||||
|
minAmount,
|
||||||
|
false,
|
||||||
|
fixedPrice,
|
||||||
|
0.00,
|
||||||
|
securityDeposit,
|
||||||
|
paymentAcctId,
|
||||||
|
makerFeeCurrencyCode,
|
||||||
|
0 /* no trigger price */);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferInfo createMarketBasedPricedOffer(String direction,
|
||||||
|
String currencyCode,
|
||||||
|
long amount,
|
||||||
|
long minAmount,
|
||||||
|
double marketPriceMargin,
|
||||||
|
double securityDeposit,
|
||||||
|
String paymentAcctId,
|
||||||
|
String makerFeeCurrencyCode,
|
||||||
|
long triggerPrice) {
|
||||||
|
return createOffer(direction,
|
||||||
|
currencyCode,
|
||||||
|
amount,
|
||||||
|
minAmount,
|
||||||
|
true,
|
||||||
|
"0",
|
||||||
|
marketPriceMargin,
|
||||||
|
securityDeposit,
|
||||||
|
paymentAcctId,
|
||||||
|
makerFeeCurrencyCode,
|
||||||
|
triggerPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferInfo createOffer(String direction,
|
||||||
|
String currencyCode,
|
||||||
|
long amount,
|
||||||
|
long minAmount,
|
||||||
|
boolean useMarketBasedPrice,
|
||||||
|
String fixedPrice,
|
||||||
|
double marketPriceMargin,
|
||||||
|
double securityDeposit,
|
||||||
|
String paymentAcctId,
|
||||||
|
String makerFeeCurrencyCode,
|
||||||
|
long triggerPrice) {
|
||||||
|
var request = CreateOfferRequest.newBuilder()
|
||||||
|
.setDirection(direction)
|
||||||
|
.setCurrencyCode(currencyCode)
|
||||||
|
.setAmount(amount)
|
||||||
|
.setMinAmount(minAmount)
|
||||||
|
.setUseMarketBasedPrice(useMarketBasedPrice)
|
||||||
|
.setPrice(fixedPrice)
|
||||||
|
.setMarketPriceMargin(marketPriceMargin)
|
||||||
|
.setBuyerSecurityDeposit(securityDeposit)
|
||||||
|
.setPaymentAccountId(paymentAcctId)
|
||||||
|
.setMakerFeeCurrencyCode(makerFeeCurrencyCode)
|
||||||
|
.setTriggerPrice(triggerPrice)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.offersService.createOffer(request).getOffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOfferActivationState(String offerId, int enable) {
|
||||||
|
var offer = getMyOffer(offerId);
|
||||||
|
var scaledPriceString = offer.getUseMarketBasedPrice()
|
||||||
|
? "0.00"
|
||||||
|
: scaledPriceStringRequestFormat.apply(offer.getPrice());
|
||||||
|
editOffer(offerId,
|
||||||
|
scaledPriceString,
|
||||||
|
offer.getUseMarketBasedPrice(),
|
||||||
|
offer.getMarketPriceMargin(),
|
||||||
|
offer.getTriggerPrice(),
|
||||||
|
enable,
|
||||||
|
ACTIVATION_STATE_ONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOfferFixedPrice(String offerId, String rawPriceString) {
|
||||||
|
var offer = getMyOffer(offerId);
|
||||||
|
editOffer(offerId,
|
||||||
|
rawPriceString,
|
||||||
|
false,
|
||||||
|
offer.getMarketPriceMargin(),
|
||||||
|
offer.getTriggerPrice(),
|
||||||
|
offer.getIsActivated() ? 1 : 0,
|
||||||
|
FIXED_PRICE_ONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOfferPriceMargin(String offerId, double marketPriceMargin) {
|
||||||
|
var offer = getMyOffer(offerId);
|
||||||
|
editOffer(offerId,
|
||||||
|
"0.00",
|
||||||
|
true,
|
||||||
|
marketPriceMargin,
|
||||||
|
offer.getTriggerPrice(),
|
||||||
|
offer.getIsActivated() ? 1 : 0,
|
||||||
|
MKT_PRICE_MARGIN_ONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOfferTriggerPrice(String offerId, long triggerPrice) {
|
||||||
|
var offer = getMyOffer(offerId);
|
||||||
|
editOffer(offerId,
|
||||||
|
"0.00",
|
||||||
|
offer.getUseMarketBasedPrice(),
|
||||||
|
offer.getMarketPriceMargin(),
|
||||||
|
triggerPrice,
|
||||||
|
offer.getIsActivated() ? 1 : 0,
|
||||||
|
TRIGGER_PRICE_ONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editOffer(String offerId,
|
||||||
|
String scaledPriceString,
|
||||||
|
boolean useMarketBasedPrice,
|
||||||
|
double marketPriceMargin,
|
||||||
|
long triggerPrice,
|
||||||
|
int enable,
|
||||||
|
EditOfferRequest.EditType editType) {
|
||||||
|
// Take care when using this method directly:
|
||||||
|
// useMarketBasedPrice = true if margin based offer, false for fixed priced offer
|
||||||
|
// scaledPriceString fmt = ######.####
|
||||||
|
var request = EditOfferRequest.newBuilder()
|
||||||
|
.setId(offerId)
|
||||||
|
.setPrice(scaledPriceString)
|
||||||
|
.setUseMarketBasedPrice(useMarketBasedPrice)
|
||||||
|
.setMarketPriceMargin(marketPriceMargin)
|
||||||
|
.setTriggerPrice(triggerPrice)
|
||||||
|
.setEnable(enable)
|
||||||
|
.setEditType(editType)
|
||||||
|
.build();
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
grpcStubs.offersService.editOffer(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelOffer(String offerId) {
|
||||||
|
var request = CancelOfferRequest.newBuilder()
|
||||||
|
.setId(offerId)
|
||||||
|
.build();
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
grpcStubs.offersService.cancelOffer(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferInfo getOffer(String offerId) {
|
||||||
|
var request = GetOfferRequest.newBuilder()
|
||||||
|
.setId(offerId)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.offersService.getOffer(request).getOffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferInfo getMyOffer(String offerId) {
|
||||||
|
var request = GetMyOfferRequest.newBuilder()
|
||||||
|
.setId(offerId)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.offersService.getMyOffer(request).getOffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
||||||
|
if (isSupportedCryptoCurrency(currencyCode)) {
|
||||||
|
return getCryptoCurrencyOffers(direction, currencyCode);
|
||||||
|
} else {
|
||||||
|
var request = GetOffersRequest.newBuilder()
|
||||||
|
.setDirection(direction)
|
||||||
|
.setCurrencyCode(currencyCode)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.offersService.getOffers(request).getOffersList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> getCryptoCurrencyOffers(String direction, String currencyCode) {
|
||||||
|
return getOffers(direction, "BTC").stream()
|
||||||
|
.filter(o -> o.getBaseCurrencyCode().equalsIgnoreCase(currencyCode))
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> getOffersSortedByDate(String currencyCode) {
|
||||||
|
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||||
|
offers.addAll(getOffers(BUY.name(), currencyCode));
|
||||||
|
offers.addAll(getOffers(SELL.name(), currencyCode));
|
||||||
|
return sortOffersByDate(offers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> getOffersSortedByDate(String direction, String currencyCode) {
|
||||||
|
var offers = getOffers(direction, currencyCode);
|
||||||
|
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> getBsqOffersSortedByDate() {
|
||||||
|
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||||
|
offers.addAll(getCryptoCurrencyOffers(BUY.name(), "BSQ"));
|
||||||
|
offers.addAll(getCryptoCurrencyOffers(SELL.name(), "BSQ"));
|
||||||
|
return sortOffersByDate(offers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||||
|
if (isSupportedCryptoCurrency(currencyCode)) {
|
||||||
|
return getMyCryptoCurrencyOffers(direction, currencyCode);
|
||||||
|
} else {
|
||||||
|
var request = GetMyOffersRequest.newBuilder()
|
||||||
|
.setDirection(direction)
|
||||||
|
.setCurrencyCode(currencyCode)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.offersService.getMyOffers(request).getOffersList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> getMyCryptoCurrencyOffers(String direction, String currencyCode) {
|
||||||
|
return getMyOffers(direction, "BTC").stream()
|
||||||
|
.filter(o -> o.getBaseCurrencyCode().equalsIgnoreCase(currencyCode))
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> getMyOffersSortedByDate(String direction, String currencyCode) {
|
||||||
|
var offers = getMyOffers(direction, currencyCode);
|
||||||
|
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> getMyOffersSortedByDate(String currencyCode) {
|
||||||
|
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||||
|
offers.addAll(getMyOffers(BUY.name(), currencyCode));
|
||||||
|
offers.addAll(getMyOffers(SELL.name(), currencyCode));
|
||||||
|
return sortOffersByDate(offers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> getMyBsqOffersSortedByDate() {
|
||||||
|
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||||
|
offers.addAll(getMyCryptoCurrencyOffers(BUY.name(), "BSQ"));
|
||||||
|
offers.addAll(getMyCryptoCurrencyOffers(SELL.name(), "BSQ"));
|
||||||
|
return sortOffersByDate(offers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||||
|
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
||||||
|
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||||
|
return offerInfoList.stream()
|
||||||
|
.sorted(comparing(OfferInfo::getDate))
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isSupportedCryptoCurrency(String currencyCode) {
|
||||||
|
return getSupportedCryptoCurrencies().contains(currencyCode.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> getSupportedCryptoCurrencies() {
|
||||||
|
final List<String> result = new ArrayList<>();
|
||||||
|
result.add("BSQ");
|
||||||
|
result.sort(String::compareTo);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* 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.request;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.CreateCryptoCurrencyPaymentAccountRequest;
|
||||||
|
import bisq.proto.grpc.CreatePaymentAccountRequest;
|
||||||
|
import bisq.proto.grpc.GetCryptoCurrencyPaymentMethodsRequest;
|
||||||
|
import bisq.proto.grpc.GetPaymentAccountFormRequest;
|
||||||
|
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||||
|
import bisq.proto.grpc.GetPaymentMethodsRequest;
|
||||||
|
|
||||||
|
import protobuf.PaymentAccount;
|
||||||
|
import protobuf.PaymentMethod;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.cli.GrpcStubs;
|
||||||
|
|
||||||
|
public class PaymentAccountsServiceRequest {
|
||||||
|
|
||||||
|
private final GrpcStubs grpcStubs;
|
||||||
|
|
||||||
|
public PaymentAccountsServiceRequest(GrpcStubs grpcStubs) {
|
||||||
|
this.grpcStubs = grpcStubs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PaymentMethod> getPaymentMethods() {
|
||||||
|
var request = GetPaymentMethodsRequest.newBuilder().build();
|
||||||
|
return grpcStubs.paymentAccountsService.getPaymentMethods(request).getPaymentMethodsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPaymentAcctFormAsJson(String paymentMethodId) {
|
||||||
|
var request = GetPaymentAccountFormRequest.newBuilder()
|
||||||
|
.setPaymentMethodId(paymentMethodId)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.paymentAccountsService.getPaymentAccountForm(request).getPaymentAccountFormJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaymentAccount createPaymentAccount(String json) {
|
||||||
|
var request = CreatePaymentAccountRequest.newBuilder()
|
||||||
|
.setPaymentAccountForm(json)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.paymentAccountsService.createPaymentAccount(request).getPaymentAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PaymentAccount> getPaymentAccounts() {
|
||||||
|
var request = GetPaymentAccountsRequest.newBuilder().build();
|
||||||
|
return grpcStubs.paymentAccountsService.getPaymentAccounts(request).getPaymentAccountsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||||
|
String currencyCode,
|
||||||
|
String address,
|
||||||
|
boolean tradeInstant) {
|
||||||
|
var request = CreateCryptoCurrencyPaymentAccountRequest.newBuilder()
|
||||||
|
.setAccountName(accountName)
|
||||||
|
.setCurrencyCode(currencyCode)
|
||||||
|
.setAddress(address)
|
||||||
|
.setTradeInstant(tradeInstant)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.paymentAccountsService.createCryptoCurrencyPaymentAccount(request).getPaymentAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PaymentMethod> getCryptoPaymentMethods() {
|
||||||
|
var request = GetCryptoCurrencyPaymentMethodsRequest.newBuilder().build();
|
||||||
|
return grpcStubs.paymentAccountsService.getCryptoCurrencyPaymentMethods(request).getPaymentMethodsList();
|
||||||
|
}
|
||||||
|
}
|
94
cli/src/main/java/bisq/cli/request/TradesServiceRequest.java
Normal file
94
cli/src/main/java/bisq/cli/request/TradesServiceRequest.java
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* 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.request;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||||
|
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||||
|
import bisq.proto.grpc.GetTradeRequest;
|
||||||
|
import bisq.proto.grpc.KeepFundsRequest;
|
||||||
|
import bisq.proto.grpc.TakeOfferReply;
|
||||||
|
import bisq.proto.grpc.TakeOfferRequest;
|
||||||
|
import bisq.proto.grpc.TradeInfo;
|
||||||
|
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.cli.GrpcStubs;
|
||||||
|
|
||||||
|
public class TradesServiceRequest {
|
||||||
|
|
||||||
|
private final GrpcStubs grpcStubs;
|
||||||
|
|
||||||
|
public TradesServiceRequest(GrpcStubs grpcStubs) {
|
||||||
|
this.grpcStubs = grpcStubs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||||
|
var request = TakeOfferRequest.newBuilder()
|
||||||
|
.setOfferId(offerId)
|
||||||
|
.setPaymentAccountId(paymentAccountId)
|
||||||
|
.setTakerFeeCurrencyCode(takerFeeCurrencyCode)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.tradesService.takeOffer(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TradeInfo takeOffer(String offerId, String paymentAccountId, String takerFeeCurrencyCode) {
|
||||||
|
var reply = getTakeOfferReply(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||||
|
if (reply.hasTrade())
|
||||||
|
return reply.getTrade();
|
||||||
|
else
|
||||||
|
throw new IllegalStateException(reply.getFailureReason().getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TradeInfo getTrade(String tradeId) {
|
||||||
|
var request = GetTradeRequest.newBuilder()
|
||||||
|
.setTradeId(tradeId)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.tradesService.getTrade(request).getTrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void confirmPaymentStarted(String tradeId) {
|
||||||
|
var request = ConfirmPaymentStartedRequest.newBuilder()
|
||||||
|
.setTradeId(tradeId)
|
||||||
|
.build();
|
||||||
|
grpcStubs.tradesService.confirmPaymentStarted(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void confirmPaymentReceived(String tradeId) {
|
||||||
|
var request = ConfirmPaymentReceivedRequest.newBuilder()
|
||||||
|
.setTradeId(tradeId)
|
||||||
|
.build();
|
||||||
|
grpcStubs.tradesService.confirmPaymentReceived(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void keepFunds(String tradeId) {
|
||||||
|
var request = KeepFundsRequest.newBuilder()
|
||||||
|
.setTradeId(tradeId)
|
||||||
|
.build();
|
||||||
|
grpcStubs.tradesService.keepFunds(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void withdrawFunds(String tradeId, String address, String memo) {
|
||||||
|
var request = WithdrawFundsRequest.newBuilder()
|
||||||
|
.setTradeId(tradeId)
|
||||||
|
.setAddress(address)
|
||||||
|
.setMemo(memo)
|
||||||
|
.build();
|
||||||
|
grpcStubs.tradesService.withdrawFunds(request);
|
||||||
|
}
|
||||||
|
}
|
192
cli/src/main/java/bisq/cli/request/WalletsServiceRequest.java
Normal file
192
cli/src/main/java/bisq/cli/request/WalletsServiceRequest.java
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
* 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.request;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.AddressBalanceInfo;
|
||||||
|
import bisq.proto.grpc.BalancesInfo;
|
||||||
|
import bisq.proto.grpc.BsqBalanceInfo;
|
||||||
|
import bisq.proto.grpc.BtcBalanceInfo;
|
||||||
|
import bisq.proto.grpc.GetAddressBalanceRequest;
|
||||||
|
import bisq.proto.grpc.GetBalancesRequest;
|
||||||
|
import bisq.proto.grpc.GetFundingAddressesRequest;
|
||||||
|
import bisq.proto.grpc.GetTransactionRequest;
|
||||||
|
import bisq.proto.grpc.GetTxFeeRateRequest;
|
||||||
|
import bisq.proto.grpc.GetUnusedBsqAddressRequest;
|
||||||
|
import bisq.proto.grpc.LockWalletRequest;
|
||||||
|
import bisq.proto.grpc.MarketPriceRequest;
|
||||||
|
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||||
|
import bisq.proto.grpc.SendBsqRequest;
|
||||||
|
import bisq.proto.grpc.SendBtcRequest;
|
||||||
|
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
|
||||||
|
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||||
|
import bisq.proto.grpc.TxFeeRateInfo;
|
||||||
|
import bisq.proto.grpc.TxInfo;
|
||||||
|
import bisq.proto.grpc.UnlockWalletRequest;
|
||||||
|
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
||||||
|
import bisq.proto.grpc.VerifyBsqSentToAddressRequest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.cli.GrpcStubs;
|
||||||
|
|
||||||
|
public class WalletsServiceRequest {
|
||||||
|
|
||||||
|
private final GrpcStubs grpcStubs;
|
||||||
|
|
||||||
|
public WalletsServiceRequest(GrpcStubs grpcStubs) {
|
||||||
|
this.grpcStubs = grpcStubs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BalancesInfo getBalances() {
|
||||||
|
return getBalances("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public BsqBalanceInfo getBsqBalances() {
|
||||||
|
return getBalances("BSQ").getBsq();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BtcBalanceInfo getBtcBalances() {
|
||||||
|
return getBalances("BTC").getBtc();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BalancesInfo getBalances(String currencyCode) {
|
||||||
|
var request = GetBalancesRequest.newBuilder()
|
||||||
|
.setCurrencyCode(currencyCode)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.walletsService.getBalances(request).getBalances();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressBalanceInfo getAddressBalance(String address) {
|
||||||
|
var request = GetAddressBalanceRequest.newBuilder()
|
||||||
|
.setAddress(address).build();
|
||||||
|
return grpcStubs.walletsService.getAddressBalance(request).getAddressBalanceInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getBtcPrice(String currencyCode) {
|
||||||
|
var request = MarketPriceRequest.newBuilder()
|
||||||
|
.setCurrencyCode(currencyCode)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.priceService.getMarketPrice(request).getPrice();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AddressBalanceInfo> getFundingAddresses() {
|
||||||
|
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||||
|
return grpcStubs.walletsService.getFundingAddresses(request).getAddressBalanceInfoList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnusedBsqAddress() {
|
||||||
|
var request = GetUnusedBsqAddressRequest.newBuilder().build();
|
||||||
|
return grpcStubs.walletsService.getUnusedBsqAddress(request).getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnusedBtcAddress() {
|
||||||
|
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||||
|
var addressBalances = grpcStubs.walletsService.getFundingAddresses(request)
|
||||||
|
.getAddressBalanceInfoList();
|
||||||
|
//noinspection OptionalGetWithoutIsPresent
|
||||||
|
return addressBalances.stream()
|
||||||
|
.filter(AddressBalanceInfo::getIsAddressUnused)
|
||||||
|
.findFirst()
|
||||||
|
.get()
|
||||||
|
.getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TxInfo sendBsq(String address, String amount, String txFeeRate) {
|
||||||
|
var request = SendBsqRequest.newBuilder()
|
||||||
|
.setAddress(address)
|
||||||
|
.setAmount(amount)
|
||||||
|
.setTxFeeRate(txFeeRate)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.walletsService.sendBsq(request).getTxInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TxInfo sendBtc(String address, String amount, String txFeeRate, String memo) {
|
||||||
|
var request = SendBtcRequest.newBuilder()
|
||||||
|
.setAddress(address)
|
||||||
|
.setAmount(amount)
|
||||||
|
.setTxFeeRate(txFeeRate)
|
||||||
|
.setMemo(memo)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.walletsService.sendBtc(request).getTxInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean verifyBsqSentToAddress(String address, String amount) {
|
||||||
|
var request = VerifyBsqSentToAddressRequest.newBuilder()
|
||||||
|
.setAddress(address)
|
||||||
|
.setAmount(amount)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.walletsService.verifyBsqSentToAddress(request).getIsAmountReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TxFeeRateInfo getTxFeeRate() {
|
||||||
|
var request = GetTxFeeRateRequest.newBuilder().build();
|
||||||
|
return grpcStubs.walletsService.getTxFeeRate(request).getTxFeeRateInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TxFeeRateInfo setTxFeeRate(long txFeeRate) {
|
||||||
|
var request = SetTxFeeRatePreferenceRequest.newBuilder()
|
||||||
|
.setTxFeeRatePreference(txFeeRate)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.walletsService.setTxFeeRatePreference(request).getTxFeeRateInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TxFeeRateInfo unsetTxFeeRate() {
|
||||||
|
var request = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
|
||||||
|
return grpcStubs.walletsService.unsetTxFeeRatePreference(request).getTxFeeRateInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TxInfo getTransaction(String txId) {
|
||||||
|
var request = GetTransactionRequest.newBuilder()
|
||||||
|
.setTxId(txId)
|
||||||
|
.build();
|
||||||
|
return grpcStubs.walletsService.getTransaction(request).getTxInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void lockWallet() {
|
||||||
|
var request = LockWalletRequest.newBuilder().build();
|
||||||
|
grpcStubs.walletsService.lockWallet(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockWallet(String walletPassword, long timeout) {
|
||||||
|
var request = UnlockWalletRequest.newBuilder()
|
||||||
|
.setPassword(walletPassword)
|
||||||
|
.setTimeout(timeout).build();
|
||||||
|
grpcStubs.walletsService.unlockWallet(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeWalletPassword(String walletPassword) {
|
||||||
|
var request = RemoveWalletPasswordRequest.newBuilder()
|
||||||
|
.setPassword(walletPassword).build();
|
||||||
|
grpcStubs.walletsService.removeWalletPassword(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWalletPassword(String walletPassword) {
|
||||||
|
var request = SetWalletPasswordRequest.newBuilder()
|
||||||
|
.setPassword(walletPassword).build();
|
||||||
|
grpcStubs.walletsService.setWalletPassword(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWalletPassword(String oldWalletPassword, String newWalletPassword) {
|
||||||
|
var request = SetWalletPasswordRequest.newBuilder()
|
||||||
|
.setPassword(oldWalletPassword)
|
||||||
|
.setNewPassword(newWalletPassword).build();
|
||||||
|
grpcStubs.walletsService.setWalletPassword(request);
|
||||||
|
}
|
||||||
|
}
|
346
cli/src/test/java/bisq/cli/opts/EditOfferOptionParserTest.java
Normal file
346
cli/src/test/java/bisq/cli/opts/EditOfferOptionParserTest.java
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
package bisq.cli.opts;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static bisq.cli.Method.editoffer;
|
||||||
|
import static bisq.cli.opts.EditOfferOptionParser.OPT_ENABLE_IGNORED;
|
||||||
|
import static bisq.cli.opts.EditOfferOptionParser.OPT_ENABLE_OFF;
|
||||||
|
import static bisq.cli.opts.EditOfferOptionParser.OPT_ENABLE_ON;
|
||||||
|
import static bisq.cli.opts.OptLabel.*;
|
||||||
|
import static bisq.proto.grpc.EditOfferRequest.EditType.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
// This opt parser test ahs the most thorough coverage,
|
||||||
|
// and is a reference for other opt parser tests.
|
||||||
|
public class EditOfferOptionParserTest {
|
||||||
|
|
||||||
|
private static final String PASSWORD_OPT = "--" + OPT_PASSWORD + "=" + "xyz";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferWithMissingOfferIdOptShouldThrowException() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name()
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("no offer id specified", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferWithoutAnyOptsShouldThrowException() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID"
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("no edit details specified", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferWithEmptyEnableOptValueShouldThrowException() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_ENABLE + "=" // missing opt value
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("invalid enable value specified, must be true|false",
|
||||||
|
exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferWithMissingEnableValueShouldThrowException() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_ENABLE // missing equals sign & opt value
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("invalid enable value specified, must be true|false",
|
||||||
|
exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferWithInvalidEnableValueShouldThrowException() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_ENABLE + "=0"
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("invalid enable value specified, must be true|false",
|
||||||
|
exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferWithMktPriceOptAndFixedPriceOptShouldThrowException() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_MKT_PRICE_MARGIN + "=0.11",
|
||||||
|
"--" + OPT_FIXED_PRICE + "=50000.0000"
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("cannot specify market price margin and fixed price",
|
||||||
|
exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferWithFixedPriceOptAndTriggerPriceOptShouldThrowException() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_FIXED_PRICE + "=50000.0000",
|
||||||
|
"--" + OPT_TRIGGER_PRICE + "=51000.0000"
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("trigger price cannot be set on fixed price offers",
|
||||||
|
exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferActivationStateOnly() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_ENABLE + "=" + "true"
|
||||||
|
};
|
||||||
|
EditOfferOptionParser parser = new EditOfferOptionParser(args).parse();
|
||||||
|
assertEquals(ACTIVATION_STATE_ONLY, parser.getOfferEditType());
|
||||||
|
assertEquals(OPT_ENABLE_ON, parser.getEnableAsSignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferFixedPriceWithoutOptValueShouldThrowException1() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_FIXED_PRICE + "="
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("no fixed price specified",
|
||||||
|
exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferFixedPriceWithoutOptValueShouldThrowException2() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_FIXED_PRICE
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("no fixed price specified",
|
||||||
|
exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferFixedPriceOnly() {
|
||||||
|
String fixedPriceAsString = "50000.0000";
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_FIXED_PRICE + "=" + fixedPriceAsString
|
||||||
|
};
|
||||||
|
EditOfferOptionParser parser = new EditOfferOptionParser(args).parse();
|
||||||
|
assertEquals(FIXED_PRICE_ONLY, parser.getOfferEditType());
|
||||||
|
assertEquals(fixedPriceAsString, parser.getFixedPrice());
|
||||||
|
assertFalse(parser.isUsingMktPriceMargin());
|
||||||
|
assertEquals("0.00", parser.getMktPriceMargin());
|
||||||
|
assertEquals(OPT_ENABLE_IGNORED, parser.getEnableAsSignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferFixedPriceAndActivationStateOnly() {
|
||||||
|
String fixedPriceAsString = "50000.0000";
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_FIXED_PRICE + "=" + fixedPriceAsString,
|
||||||
|
"--" + OPT_ENABLE + "=" + "false"
|
||||||
|
};
|
||||||
|
EditOfferOptionParser parser = new EditOfferOptionParser(args).parse();
|
||||||
|
assertEquals(FIXED_PRICE_AND_ACTIVATION_STATE, parser.getOfferEditType());
|
||||||
|
assertEquals(fixedPriceAsString, parser.getFixedPrice());
|
||||||
|
assertFalse(parser.isUsingMktPriceMargin());
|
||||||
|
assertEquals("0.00", parser.getMktPriceMargin());
|
||||||
|
assertEquals(OPT_ENABLE_OFF, parser.getEnableAsSignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferMktPriceMarginOnly() {
|
||||||
|
String mktPriceMarginAsString = "0.25";
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_MKT_PRICE_MARGIN + "=" + mktPriceMarginAsString
|
||||||
|
};
|
||||||
|
EditOfferOptionParser parser = new EditOfferOptionParser(args).parse();
|
||||||
|
assertEquals(MKT_PRICE_MARGIN_ONLY, parser.getOfferEditType());
|
||||||
|
assertTrue(parser.isUsingMktPriceMargin());
|
||||||
|
assertEquals(mktPriceMarginAsString, parser.getMktPriceMargin());
|
||||||
|
assertEquals("0", parser.getTriggerPrice());
|
||||||
|
assertEquals(OPT_ENABLE_IGNORED, parser.getEnableAsSignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferMktPriceMarginWithoutOptValueShouldThrowException() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_MKT_PRICE_MARGIN
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("no mkt price margin specified",
|
||||||
|
exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditOfferMktPriceMarginAndActivationStateOnly() {
|
||||||
|
String mktPriceMarginAsString = "0.15";
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_MKT_PRICE_MARGIN + "=" + mktPriceMarginAsString,
|
||||||
|
"--" + OPT_ENABLE + "=" + "false"
|
||||||
|
};
|
||||||
|
EditOfferOptionParser parser = new EditOfferOptionParser(args).parse();
|
||||||
|
assertEquals(MKT_PRICE_MARGIN_AND_ACTIVATION_STATE, parser.getOfferEditType());
|
||||||
|
assertTrue(parser.isUsingMktPriceMargin());
|
||||||
|
assertEquals(mktPriceMarginAsString, parser.getMktPriceMargin());
|
||||||
|
assertEquals("0", parser.getTriggerPrice());
|
||||||
|
assertEquals(OPT_ENABLE_OFF, parser.getEnableAsSignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditTriggerPriceOnly() {
|
||||||
|
String triggerPriceAsString = "50000.0000";
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_TRIGGER_PRICE + "=" + triggerPriceAsString
|
||||||
|
};
|
||||||
|
EditOfferOptionParser parser = new EditOfferOptionParser(args).parse();
|
||||||
|
assertEquals(TRIGGER_PRICE_ONLY, parser.getOfferEditType());
|
||||||
|
assertEquals(triggerPriceAsString, parser.getTriggerPrice());
|
||||||
|
assertTrue(parser.isUsingMktPriceMargin());
|
||||||
|
assertEquals("0.00", parser.getMktPriceMargin());
|
||||||
|
assertEquals(OPT_ENABLE_IGNORED, parser.getEnableAsSignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditTriggerPriceWithoutOptValueShouldThrowException1() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_TRIGGER_PRICE + "="
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("no trigger price specified",
|
||||||
|
exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditTriggerPriceWithoutOptValueShouldThrowException2() {
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_TRIGGER_PRICE
|
||||||
|
};
|
||||||
|
Throwable exception = assertThrows(RuntimeException.class, () ->
|
||||||
|
new EditOfferOptionParser(args).parse());
|
||||||
|
assertEquals("no trigger price specified",
|
||||||
|
exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditTriggerPriceAndActivationStateOnly() {
|
||||||
|
String triggerPriceAsString = "50000.0000";
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_TRIGGER_PRICE + "=" + triggerPriceAsString,
|
||||||
|
"--" + OPT_ENABLE + "=" + "true"
|
||||||
|
};
|
||||||
|
EditOfferOptionParser parser = new EditOfferOptionParser(args).parse();
|
||||||
|
assertEquals(TRIGGER_PRICE_AND_ACTIVATION_STATE, parser.getOfferEditType());
|
||||||
|
assertEquals(triggerPriceAsString, parser.getTriggerPrice());
|
||||||
|
assertTrue(parser.isUsingMktPriceMargin());
|
||||||
|
assertEquals("0.00", parser.getMktPriceMargin());
|
||||||
|
assertEquals("0", parser.getFixedPrice());
|
||||||
|
assertEquals(OPT_ENABLE_ON, parser.getEnableAsSignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditMKtPriceMarginAndTriggerPrice() {
|
||||||
|
String mktPriceMarginAsString = "0.25";
|
||||||
|
String triggerPriceAsString = "50000.0000";
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_MKT_PRICE_MARGIN + "=" + mktPriceMarginAsString,
|
||||||
|
"--" + OPT_TRIGGER_PRICE + "=" + triggerPriceAsString
|
||||||
|
};
|
||||||
|
EditOfferOptionParser parser = new EditOfferOptionParser(args).parse();
|
||||||
|
assertEquals(MKT_PRICE_MARGIN_AND_TRIGGER_PRICE, parser.getOfferEditType());
|
||||||
|
assertEquals(triggerPriceAsString, parser.getTriggerPrice());
|
||||||
|
assertTrue(parser.isUsingMktPriceMargin());
|
||||||
|
assertEquals(mktPriceMarginAsString, parser.getMktPriceMargin());
|
||||||
|
assertEquals("0", parser.getFixedPrice());
|
||||||
|
assertEquals(OPT_ENABLE_IGNORED, parser.getEnableAsSignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditMKtPriceMarginAndTriggerPriceAndEnableState() {
|
||||||
|
String mktPriceMarginAsString = "0.25";
|
||||||
|
String triggerPriceAsString = "50000.0000";
|
||||||
|
String[] args = new String[]{
|
||||||
|
PASSWORD_OPT,
|
||||||
|
editoffer.name(),
|
||||||
|
"--" + OPT_OFFER_ID + "=" + "ABC-OFFER-ID",
|
||||||
|
"--" + OPT_MKT_PRICE_MARGIN + "=" + mktPriceMarginAsString,
|
||||||
|
"--" + OPT_TRIGGER_PRICE + "=" + triggerPriceAsString,
|
||||||
|
"--" + OPT_ENABLE + "=" + "FALSE"
|
||||||
|
};
|
||||||
|
EditOfferOptionParser parser = new EditOfferOptionParser(args).parse();
|
||||||
|
assertEquals(MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE, parser.getOfferEditType());
|
||||||
|
assertEquals(triggerPriceAsString, parser.getTriggerPrice());
|
||||||
|
assertTrue(parser.isUsingMktPriceMargin());
|
||||||
|
assertEquals(mktPriceMarginAsString, parser.getMktPriceMargin());
|
||||||
|
assertEquals("0", parser.getFixedPrice());
|
||||||
|
assertEquals(OPT_ENABLE_OFF, parser.getEnableAsSignedInt());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package bisq.cli.opt;
|
package bisq.cli.opts;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -11,13 +11,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import bisq.cli.opts.CancelOfferOptionParser;
|
|
||||||
import bisq.cli.opts.CreateCryptoCurrencyPaymentAcctOptionParser;
|
|
||||||
import bisq.cli.opts.CreateOfferOptionParser;
|
|
||||||
import bisq.cli.opts.CreatePaymentAcctOptionParser;
|
|
||||||
|
|
||||||
|
|
||||||
public class OptionParsersTest {
|
public class OptionParsersTest {
|
||||||
|
|
||||||
private static final String PASSWORD_OPT = "--" + OPT_PASSWORD + "=" + "xyz";
|
private static final String PASSWORD_OPT = "--" + OPT_PASSWORD + "=" + "xyz";
|
||||||
|
@ -178,7 +171,7 @@ public class OptionParsersTest {
|
||||||
new CreatePaymentAcctOptionParser(args).parse());
|
new CreatePaymentAcctOptionParser(args).parse());
|
||||||
if (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0)
|
if (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0)
|
||||||
assertEquals("json payment account form '\\tmp\\milkyway\\solarsystem\\mars' could not be found",
|
assertEquals("json payment account form '\\tmp\\milkyway\\solarsystem\\mars' could not be found",
|
||||||
exception.getMessage());
|
exception.getMessage());
|
||||||
else
|
else
|
||||||
assertEquals("json payment account form '/tmp/milkyway/solarsystem/mars' could not be found",
|
assertEquals("json payment account form '/tmp/milkyway/solarsystem/mars' could not be found",
|
||||||
exception.getMessage());
|
exception.getMessage());
|
|
@ -21,9 +21,7 @@ import bisq.core.api.model.AddressBalanceInfo;
|
||||||
import bisq.core.api.model.BalancesInfo;
|
import bisq.core.api.model.BalancesInfo;
|
||||||
import bisq.core.api.model.TxFeeRateInfo;
|
import bisq.core.api.model.TxFeeRateInfo;
|
||||||
import bisq.core.btc.wallet.TxBroadcaster;
|
import bisq.core.btc.wallet.TxBroadcaster;
|
||||||
import bisq.core.monetary.Price;
|
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferPayload;
|
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.payload.PaymentMethod;
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
|
@ -36,7 +34,6 @@ import bisq.common.config.Config;
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
@ -52,6 +49,8 @@ import java.util.function.Consumer;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import static bisq.proto.grpc.EditOfferRequest.EditType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides high level interface to functionality of core Bisq features.
|
* Provides high level interface to functionality of core Bisq features.
|
||||||
* E.g. useful for different APIs to access data of different domains of Bisq.
|
* E.g. useful for different APIs to access data of different domains of Bisq.
|
||||||
|
@ -122,7 +121,7 @@ public class CoreApi {
|
||||||
return coreOffersService.getOffer(id);
|
return coreOffersService.getOffer(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Offer getMyOffer(String id) {
|
public OpenOffer getMyOffer(String id) {
|
||||||
return coreOffersService.getMyOffer(id);
|
return coreOffersService.getMyOffer(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,14 +129,10 @@ public class CoreApi {
|
||||||
return coreOffersService.getOffers(direction, currencyCode);
|
return coreOffersService.getOffers(direction, currencyCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Offer> getMyOffers(String direction, String currencyCode) {
|
public List<OpenOffer> getMyOffers(String direction, String currencyCode) {
|
||||||
return coreOffersService.getMyOffers(direction, currencyCode);
|
return coreOffersService.getMyOffers(direction, currencyCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenOffer getMyOpenOffer(String id) {
|
|
||||||
return coreOffersService.getMyOpenOffer(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createAnPlaceOffer(String currencyCode,
|
public void createAnPlaceOffer(String currencyCode,
|
||||||
String directionAsString,
|
String directionAsString,
|
||||||
String priceAsString,
|
String priceAsString,
|
||||||
|
@ -164,32 +159,30 @@ public class CoreApi {
|
||||||
resultHandler);
|
resultHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Offer editOffer(String offerId,
|
public void editOffer(String offerId,
|
||||||
String currencyCode,
|
String priceAsString,
|
||||||
OfferPayload.Direction direction,
|
boolean useMarketBasedPrice,
|
||||||
Price price,
|
double marketPriceMargin,
|
||||||
boolean useMarketBasedPrice,
|
long triggerPrice,
|
||||||
double marketPriceMargin,
|
int enable,
|
||||||
Coin amount,
|
EditType editType) {
|
||||||
Coin minAmount,
|
coreOffersService.editOffer(offerId,
|
||||||
double buyerSecurityDeposit,
|
priceAsString,
|
||||||
PaymentAccount paymentAccount) {
|
|
||||||
return coreOffersService.editOffer(offerId,
|
|
||||||
currencyCode,
|
|
||||||
direction,
|
|
||||||
price,
|
|
||||||
useMarketBasedPrice,
|
useMarketBasedPrice,
|
||||||
marketPriceMargin,
|
marketPriceMargin,
|
||||||
amount,
|
triggerPrice,
|
||||||
minAmount,
|
enable,
|
||||||
buyerSecurityDeposit,
|
editType);
|
||||||
paymentAccount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelOffer(String id) {
|
public void cancelOffer(String id) {
|
||||||
coreOffersService.cancelOffer(id);
|
coreOffersService.cancelOffer(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isMyOffer(String id) {
|
||||||
|
return coreOffersService.isMyOffer(id);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// PaymentAccounts
|
// PaymentAccounts
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -20,13 +20,16 @@ package bisq.core.api;
|
||||||
import bisq.core.monetary.Altcoin;
|
import bisq.core.monetary.Altcoin;
|
||||||
import bisq.core.monetary.Price;
|
import bisq.core.monetary.Price;
|
||||||
import bisq.core.offer.CreateOfferService;
|
import bisq.core.offer.CreateOfferService;
|
||||||
|
import bisq.core.offer.MutableOfferPayloadFields;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferBookService;
|
import bisq.core.offer.OfferBookService;
|
||||||
import bisq.core.offer.OfferFilter;
|
import bisq.core.offer.OfferFilter;
|
||||||
|
import bisq.core.offer.OfferPayload;
|
||||||
import bisq.core.offer.OfferUtil;
|
import bisq.core.offer.OfferUtil;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
|
||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.KeyRing;
|
||||||
|
@ -42,6 +45,7 @@ import java.math.BigDecimal;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -52,9 +56,14 @@ import static bisq.common.util.MathUtils.exactMultiply;
|
||||||
import static bisq.common.util.MathUtils.roundDoubleToLong;
|
import static bisq.common.util.MathUtils.roundDoubleToLong;
|
||||||
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
||||||
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
||||||
|
import static bisq.core.offer.Offer.State;
|
||||||
import static bisq.core.offer.OfferPayload.Direction;
|
import static bisq.core.offer.OfferPayload.Direction;
|
||||||
import static bisq.core.offer.OfferPayload.Direction.BUY;
|
import static bisq.core.offer.OfferPayload.Direction.BUY;
|
||||||
|
import static bisq.core.offer.OpenOffer.State.AVAILABLE;
|
||||||
|
import static bisq.core.offer.OpenOffer.State.DEACTIVATED;
|
||||||
import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer;
|
import static bisq.core.payment.PaymentAccountUtil.isPaymentAccountValidForOffer;
|
||||||
|
import static bisq.proto.grpc.EditOfferRequest.EditType;
|
||||||
|
import static bisq.proto.grpc.EditOfferRequest.EditType.*;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static java.util.Comparator.comparing;
|
import static java.util.Comparator.comparing;
|
||||||
|
|
||||||
|
@ -62,8 +71,11 @@ import static java.util.Comparator.comparing;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
class CoreOffersService {
|
class CoreOffersService {
|
||||||
|
|
||||||
private final Supplier<Comparator<Offer>> priceComparator = () -> comparing(Offer::getPrice);
|
private final Supplier<Comparator<Offer>> priceComparator = () ->
|
||||||
private final Supplier<Comparator<Offer>> reversePriceComparator = () -> comparing(Offer::getPrice).reversed();
|
comparing(Offer::getPrice);
|
||||||
|
|
||||||
|
private final Supplier<Comparator<OpenOffer>> openOfferPriceComparator = () ->
|
||||||
|
comparing(openOffer -> openOffer.getOffer().getPrice());
|
||||||
|
|
||||||
private final CoreContext coreContext;
|
private final CoreContext coreContext;
|
||||||
private final KeyRing keyRing;
|
private final KeyRing keyRing;
|
||||||
|
@ -76,6 +88,7 @@ class CoreOffersService {
|
||||||
private final OfferFilter offerFilter;
|
private final OfferFilter offerFilter;
|
||||||
private final OpenOfferManager openOfferManager;
|
private final OpenOfferManager openOfferManager;
|
||||||
private final OfferUtil offerUtil;
|
private final OfferUtil offerUtil;
|
||||||
|
private final PriceFeedService priceFeedService;
|
||||||
private final User user;
|
private final User user;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -87,6 +100,7 @@ class CoreOffersService {
|
||||||
OfferFilter offerFilter,
|
OfferFilter offerFilter,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
OfferUtil offerUtil,
|
OfferUtil offerUtil,
|
||||||
|
PriceFeedService priceFeedService,
|
||||||
User user) {
|
User user) {
|
||||||
this.coreContext = coreContext;
|
this.coreContext = coreContext;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
|
@ -96,6 +110,7 @@ class CoreOffersService {
|
||||||
this.offerFilter = offerFilter;
|
this.offerFilter = offerFilter;
|
||||||
this.openOfferManager = openOfferManager;
|
this.openOfferManager = openOfferManager;
|
||||||
this.offerUtil = offerUtil;
|
this.offerUtil = offerUtil;
|
||||||
|
this.priceFeedService = priceFeedService;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,10 +123,10 @@ class CoreOffersService {
|
||||||
new IllegalStateException(format("offer with id '%s' not found", id)));
|
new IllegalStateException(format("offer with id '%s' not found", id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Offer getMyOffer(String id) {
|
OpenOffer getMyOffer(String id) {
|
||||||
return offerBookService.getOffers().stream()
|
return openOfferManager.getObservableList().stream()
|
||||||
.filter(o -> o.getId().equals(id))
|
.filter(o -> o.getId().equals(id))
|
||||||
.filter(o -> o.isMyOffer(keyRing))
|
.filter(o -> o.getOffer().isMyOffer(keyRing))
|
||||||
.findAny().orElseThrow(() ->
|
.findAny().orElseThrow(() ->
|
||||||
new IllegalStateException(format("offer with id '%s' not found", id)));
|
new IllegalStateException(format("offer with id '%s' not found", id)));
|
||||||
}
|
}
|
||||||
|
@ -125,11 +140,11 @@ class CoreOffersService {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Offer> getMyOffers(String direction, String currencyCode) {
|
List<OpenOffer> getMyOffers(String direction, String currencyCode) {
|
||||||
return offerBookService.getOffers().stream()
|
return openOfferManager.getObservableList().stream()
|
||||||
.filter(o -> o.isMyOffer(keyRing))
|
.filter(o -> o.getOffer().isMyOffer(keyRing))
|
||||||
.filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode))
|
.filter(o -> offerMatchesDirectionAndCurrency(o.getOffer(), direction, currencyCode))
|
||||||
.sorted(priceComparator(direction))
|
.sorted(openOfferPriceComparator(direction))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +152,13 @@ class CoreOffersService {
|
||||||
return openOfferManager.getOpenOfferById(id)
|
return openOfferManager.getOpenOfferById(id)
|
||||||
.filter(open -> open.getOffer().isMyOffer(keyRing))
|
.filter(open -> open.getOffer().isMyOffer(keyRing))
|
||||||
.orElseThrow(() ->
|
.orElseThrow(() ->
|
||||||
new IllegalStateException(format("openoffer with id '%s' not found", id)));
|
new IllegalStateException(format("offer with id '%s' not found", id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isMyOffer(String id) {
|
||||||
|
return openOfferManager.getOpenOfferById(id)
|
||||||
|
.filter(open -> open.getOffer().isMyOffer(keyRing))
|
||||||
|
.isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and place new offer.
|
// Create and place new offer.
|
||||||
|
@ -193,47 +214,67 @@ class CoreOffersService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit a placed offer.
|
// Edit a placed offer.
|
||||||
Offer editOffer(String offerId,
|
void editOffer(String offerId,
|
||||||
String currencyCode,
|
String editedPriceAsString,
|
||||||
Direction direction,
|
boolean editedUseMarketBasedPrice,
|
||||||
Price price,
|
double editedMarketPriceMargin,
|
||||||
boolean useMarketBasedPrice,
|
long editedTriggerPrice,
|
||||||
double marketPriceMargin,
|
int editedEnable,
|
||||||
Coin amount,
|
EditType editType) {
|
||||||
Coin minAmount,
|
OpenOffer openOffer = getMyOpenOffer(offerId);
|
||||||
double buyerSecurityDeposit,
|
new EditOfferValidator(openOffer,
|
||||||
PaymentAccount paymentAccount) {
|
editedPriceAsString,
|
||||||
Coin useDefaultTxFee = Coin.ZERO;
|
editedUseMarketBasedPrice,
|
||||||
return createOfferService.createAndGetOffer(offerId,
|
editedMarketPriceMargin,
|
||||||
direction,
|
editedTriggerPrice,
|
||||||
currencyCode.toUpperCase(),
|
editedEnable,
|
||||||
amount,
|
editType).validate();
|
||||||
minAmount,
|
log.info("Validated 'editoffer' params offerId={}"
|
||||||
price,
|
+ "\n\teditedPriceAsString={}"
|
||||||
useDefaultTxFee,
|
+ "\n\teditedUseMarketBasedPrice={}"
|
||||||
useMarketBasedPrice,
|
+ "\n\teditedMarketPriceMargin={}"
|
||||||
exactMultiply(marketPriceMargin, 0.01),
|
+ "\n\teditedTriggerPrice={}"
|
||||||
buyerSecurityDeposit,
|
+ "\n\teditedEnable={}"
|
||||||
paymentAccount);
|
+ "\n\teditType={}",
|
||||||
|
offerId,
|
||||||
|
editedPriceAsString,
|
||||||
|
editedUseMarketBasedPrice,
|
||||||
|
editedMarketPriceMargin,
|
||||||
|
editedTriggerPrice,
|
||||||
|
editedEnable,
|
||||||
|
editType);
|
||||||
|
OpenOffer.State currentOfferState = openOffer.getState();
|
||||||
|
// Client sent (sint32) editedEnable, not a bool (with default=false).
|
||||||
|
// If editedEnable = -1, do not change current state
|
||||||
|
// If editedEnable = 0, set state = AVAILABLE
|
||||||
|
// If editedEnable = 1, set state = DEACTIVATED
|
||||||
|
OpenOffer.State newOfferState = editedEnable < 0
|
||||||
|
? currentOfferState
|
||||||
|
: editedEnable > 0 ? AVAILABLE : DEACTIVATED;
|
||||||
|
OfferPayload editedPayload = getMergedOfferPayload(openOffer,
|
||||||
|
editedPriceAsString,
|
||||||
|
editedMarketPriceMargin,
|
||||||
|
editType);
|
||||||
|
Offer editedOffer = new Offer(editedPayload);
|
||||||
|
priceFeedService.setCurrencyCode(openOffer.getOffer().getOfferPayload().getCurrencyCode());
|
||||||
|
editedOffer.setPriceFeedService(priceFeedService);
|
||||||
|
editedOffer.setState(State.AVAILABLE);
|
||||||
|
openOfferManager.editOpenOfferStart(openOffer,
|
||||||
|
() -> log.info("EditOpenOfferStart: offer {}", openOffer.getId()),
|
||||||
|
log::error);
|
||||||
|
openOfferManager.editOpenOfferPublish(editedOffer,
|
||||||
|
editedTriggerPrice,
|
||||||
|
newOfferState,
|
||||||
|
() -> log.info("EditOpenOfferPublish: offer {}", openOffer.getId()),
|
||||||
|
log::error);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancelOffer(String id) {
|
void cancelOffer(String id) {
|
||||||
Offer offer = getMyOffer(id);
|
OpenOffer openOffer = getMyOffer(id);
|
||||||
openOfferManager.removeOffer(offer,
|
openOfferManager.removeOffer(openOffer.getOffer(),
|
||||||
() -> {
|
() -> {
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
log::error);
|
||||||
throw new IllegalStateException(errorMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyPaymentAccountIsValidForNewOffer(Offer offer, PaymentAccount paymentAccount) {
|
|
||||||
if (!isPaymentAccountValidForOffer(offer, paymentAccount)) {
|
|
||||||
String error = format("cannot create %s offer with payment account %s",
|
|
||||||
offer.getOfferPayload().getCounterCurrencyCode(),
|
|
||||||
paymentAccount.getId());
|
|
||||||
throw new IllegalStateException(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void placeOffer(Offer offer,
|
private void placeOffer(Offer offer,
|
||||||
|
@ -252,6 +293,55 @@ class CoreOffersService {
|
||||||
throw new IllegalStateException(offer.getErrorMessage());
|
throw new IllegalStateException(offer.getErrorMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OfferPayload getMergedOfferPayload(OpenOffer openOffer,
|
||||||
|
String editedPriceAsString,
|
||||||
|
double editedMarketPriceMargin,
|
||||||
|
EditType editType) {
|
||||||
|
// API supports editing (1) price, OR (2) marketPriceMargin & useMarketBasedPrice
|
||||||
|
// OfferPayload fields. API does not support editing payment acct or currency
|
||||||
|
// code fields. Note: triggerPrice isDeactivated fields are in OpenOffer, not
|
||||||
|
// in OfferPayload.
|
||||||
|
Offer offer = openOffer.getOffer();
|
||||||
|
String currencyCode = offer.getOfferPayload().getCurrencyCode();
|
||||||
|
boolean isEditingPrice = editType.equals(FIXED_PRICE_ONLY) || editType.equals(FIXED_PRICE_AND_ACTIVATION_STATE);
|
||||||
|
Price editedPrice;
|
||||||
|
if (isEditingPrice) {
|
||||||
|
editedPrice = Price.valueOf(currencyCode, priceStringToLong(editedPriceAsString, currencyCode));
|
||||||
|
} else {
|
||||||
|
editedPrice = offer.getPrice();
|
||||||
|
}
|
||||||
|
boolean isUsingMktPriceMargin = editType.equals(MKT_PRICE_MARGIN_ONLY)
|
||||||
|
|| editType.equals(MKT_PRICE_MARGIN_AND_ACTIVATION_STATE)
|
||||||
|
|| editType.equals(TRIGGER_PRICE_ONLY)
|
||||||
|
|| editType.equals(TRIGGER_PRICE_AND_ACTIVATION_STATE)
|
||||||
|
|| editType.equals(MKT_PRICE_MARGIN_AND_TRIGGER_PRICE)
|
||||||
|
|| editType.equals(MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE);
|
||||||
|
MutableOfferPayloadFields mutableOfferPayloadFields = new MutableOfferPayloadFields(
|
||||||
|
Objects.requireNonNull(editedPrice).getValue(),
|
||||||
|
isUsingMktPriceMargin ? exactMultiply(editedMarketPriceMargin, 0.01) : 0.00,
|
||||||
|
isUsingMktPriceMargin,
|
||||||
|
offer.getOfferPayload().getBaseCurrencyCode(),
|
||||||
|
offer.getOfferPayload().getCounterCurrencyCode(),
|
||||||
|
offer.getPaymentMethod().getId(),
|
||||||
|
offer.getMakerPaymentAccountId(),
|
||||||
|
offer.getOfferPayload().getCountryCode(),
|
||||||
|
offer.getOfferPayload().getAcceptedCountryCodes(),
|
||||||
|
offer.getOfferPayload().getBankId(),
|
||||||
|
offer.getOfferPayload().getAcceptedBankIds(),
|
||||||
|
offer.getOfferPayload().getExtraDataMap());
|
||||||
|
log.info("Merging OfferPayload with {}", mutableOfferPayloadFields);
|
||||||
|
return offerUtil.getMergedOfferPayload(openOffer, mutableOfferPayloadFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyPaymentAccountIsValidForNewOffer(Offer offer, PaymentAccount paymentAccount) {
|
||||||
|
if (!isPaymentAccountValidForOffer(offer, paymentAccount)) {
|
||||||
|
String error = format("cannot create %s offer with payment account %s",
|
||||||
|
offer.getOfferPayload().getCounterCurrencyCode(),
|
||||||
|
paymentAccount.getId());
|
||||||
|
throw new IllegalStateException(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean offerMatchesDirectionAndCurrency(Offer offer,
|
private boolean offerMatchesDirectionAndCurrency(Offer offer,
|
||||||
String direction,
|
String direction,
|
||||||
String currencyCode) {
|
String currencyCode) {
|
||||||
|
@ -261,11 +351,19 @@ class CoreOffersService {
|
||||||
return offerOfWantedDirection && offerInWantedCurrency;
|
return offerOfWantedDirection && offerInWantedCurrency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Comparator<OpenOffer> openOfferPriceComparator(String direction) {
|
||||||
|
// A buyer probably wants to see sell orders in price ascending order.
|
||||||
|
// A seller probably wants to see buy orders in price descending order.
|
||||||
|
return direction.equalsIgnoreCase(BUY.name())
|
||||||
|
? openOfferPriceComparator.get().reversed()
|
||||||
|
: openOfferPriceComparator.get();
|
||||||
|
}
|
||||||
|
|
||||||
private Comparator<Offer> priceComparator(String direction) {
|
private Comparator<Offer> priceComparator(String direction) {
|
||||||
// A buyer probably wants to see sell orders in price ascending order.
|
// A buyer probably wants to see sell orders in price ascending order.
|
||||||
// A seller probably wants to see buy orders in price descending order.
|
// A seller probably wants to see buy orders in price descending order.
|
||||||
return direction.equalsIgnoreCase(BUY.name())
|
return direction.equalsIgnoreCase(BUY.name())
|
||||||
? reversePriceComparator.get()
|
? priceComparator.get().reversed()
|
||||||
: priceComparator.get();
|
: priceComparator.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
141
core/src/main/java/bisq/core/api/EditOfferValidator.java
Normal file
141
core/src/main/java/bisq/core/api/EditOfferValidator.java
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
package bisq.core.api;
|
||||||
|
|
||||||
|
import bisq.core.offer.OpenOffer;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.EditOfferRequest;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
class EditOfferValidator {
|
||||||
|
|
||||||
|
private final OpenOffer currentlyOpenOffer;
|
||||||
|
private final String editedPriceAsString;
|
||||||
|
private final boolean editedUseMarketBasedPrice;
|
||||||
|
private final double editedMarketPriceMargin;
|
||||||
|
private final long editedTriggerPrice;
|
||||||
|
private final int editedEnable;
|
||||||
|
private final EditOfferRequest.EditType editType;
|
||||||
|
|
||||||
|
private final boolean isZeroEditedFixedPriceString;
|
||||||
|
private final boolean isZeroEditedTriggerPrice;
|
||||||
|
|
||||||
|
EditOfferValidator(OpenOffer currentlyOpenOffer,
|
||||||
|
String editedPriceAsString,
|
||||||
|
boolean editedUseMarketBasedPrice,
|
||||||
|
double editedMarketPriceMargin,
|
||||||
|
long editedTriggerPrice,
|
||||||
|
int editedEnable,
|
||||||
|
EditOfferRequest.EditType editType) {
|
||||||
|
this.currentlyOpenOffer = currentlyOpenOffer;
|
||||||
|
this.editedPriceAsString = editedPriceAsString;
|
||||||
|
this.editedUseMarketBasedPrice = editedUseMarketBasedPrice;
|
||||||
|
this.editedMarketPriceMargin = editedMarketPriceMargin;
|
||||||
|
this.editedTriggerPrice = editedTriggerPrice;
|
||||||
|
this.editedEnable = editedEnable;
|
||||||
|
this.editType = editType;
|
||||||
|
|
||||||
|
this.isZeroEditedFixedPriceString = new BigDecimal(editedPriceAsString).doubleValue() == 0;
|
||||||
|
this.isZeroEditedTriggerPrice = editedTriggerPrice == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate() {
|
||||||
|
log.info("Verifying 'editoffer' params OK for editType {}", editType);
|
||||||
|
switch (editType) {
|
||||||
|
case ACTIVATION_STATE_ONLY: {
|
||||||
|
validateEditedActivationState();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FIXED_PRICE_ONLY:
|
||||||
|
case FIXED_PRICE_AND_ACTIVATION_STATE: {
|
||||||
|
validateEditedFixedPrice();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MKT_PRICE_MARGIN_ONLY:
|
||||||
|
case MKT_PRICE_MARGIN_AND_ACTIVATION_STATE:
|
||||||
|
case TRIGGER_PRICE_ONLY:
|
||||||
|
case TRIGGER_PRICE_AND_ACTIVATION_STATE:
|
||||||
|
case MKT_PRICE_MARGIN_AND_TRIGGER_PRICE:
|
||||||
|
case MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE: {
|
||||||
|
checkNotAltcoinOffer();
|
||||||
|
validateEditedTriggerPrice();
|
||||||
|
validateEditedMarketPriceMargin();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateEditedActivationState() {
|
||||||
|
if (editedEnable < 0)
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("programmer error: the 'enable' request parameter does not"
|
||||||
|
+ " indicate activation state of offer with id '%s' should be changed.",
|
||||||
|
currentlyOpenOffer.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateEditedFixedPrice() {
|
||||||
|
if (currentlyOpenOffer.getOffer().isUseMarketBasedPrice())
|
||||||
|
log.info("Attempting to change mkt price margin based offer with id '{}' to fixed price offer.",
|
||||||
|
currentlyOpenOffer.getId());
|
||||||
|
|
||||||
|
if (editedUseMarketBasedPrice)
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("programmer error: cannot change fixed price (%s)"
|
||||||
|
+ " in mkt price based offer with id '%s'",
|
||||||
|
editedMarketPriceMargin,
|
||||||
|
currentlyOpenOffer.getId()));
|
||||||
|
|
||||||
|
if (!isZeroEditedTriggerPrice)
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("programmer error: cannot change trigger price (%s)"
|
||||||
|
+ " in offer with id '%s' when changing fixed price",
|
||||||
|
editedTriggerPrice,
|
||||||
|
currentlyOpenOffer.getId()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateEditedMarketPriceMargin() {
|
||||||
|
if (!currentlyOpenOffer.getOffer().isUseMarketBasedPrice())
|
||||||
|
log.info("Attempting to change fixed price offer with id '{}' to mkt price margin based offer.",
|
||||||
|
currentlyOpenOffer.getId());
|
||||||
|
|
||||||
|
if (!isZeroEditedFixedPriceString)
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("programmer error: cannot set fixed price (%s)"
|
||||||
|
+ " in mkt price margin based offer with id '%s'",
|
||||||
|
editedPriceAsString,
|
||||||
|
currentlyOpenOffer.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateEditedTriggerPrice() {
|
||||||
|
if (!currentlyOpenOffer.getOffer().isUseMarketBasedPrice()
|
||||||
|
&& !editedUseMarketBasedPrice
|
||||||
|
&& !isZeroEditedTriggerPrice)
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("programmer error: cannot set a trigger price (%s)"
|
||||||
|
+ " in fixed price offer with id '%s'",
|
||||||
|
editedTriggerPrice,
|
||||||
|
currentlyOpenOffer.getId()));
|
||||||
|
|
||||||
|
if (editedTriggerPrice < 0)
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("programmer error: cannot set trigger price to a negative value"
|
||||||
|
+ " in offer with id '%s'",
|
||||||
|
currentlyOpenOffer.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNotAltcoinOffer() {
|
||||||
|
if (isCryptoCurrency(currentlyOpenOffer.getOffer().getCurrencyCode())) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("cannot set mkt price margin or trigger price on fixed price altcoin offer with id '%s'",
|
||||||
|
currentlyOpenOffer.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
package bisq.core.api.model;
|
package bisq.core.api.model;
|
||||||
|
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
|
import bisq.core.offer.OpenOffer;
|
||||||
|
|
||||||
import bisq.common.Payload;
|
import bisq.common.Payload;
|
||||||
|
|
||||||
|
@ -61,7 +62,9 @@ public class OfferInfo implements Payload {
|
||||||
private final String counterCurrencyCode;
|
private final String counterCurrencyCode;
|
||||||
private final long date;
|
private final long date;
|
||||||
private final String state;
|
private final String state;
|
||||||
|
private final boolean isActivated;
|
||||||
|
private boolean isMyOffer; // Not final -- may be re-set after instantiation.
|
||||||
|
private final boolean isMyPendingOffer;
|
||||||
|
|
||||||
public OfferInfo(OfferInfoBuilder builder) {
|
public OfferInfo(OfferInfoBuilder builder) {
|
||||||
this.id = builder.id;
|
this.id = builder.id;
|
||||||
|
@ -87,20 +90,41 @@ public class OfferInfo implements Payload {
|
||||||
this.counterCurrencyCode = builder.counterCurrencyCode;
|
this.counterCurrencyCode = builder.counterCurrencyCode;
|
||||||
this.date = builder.date;
|
this.date = builder.date;
|
||||||
this.state = builder.state;
|
this.state = builder.state;
|
||||||
|
this.isActivated = builder.isActivated;
|
||||||
|
this.isMyOffer = builder.isMyOffer;
|
||||||
|
this.isMyPendingOffer = builder.isMyPendingOffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow isMyOffer to be set on a new offer's OfferInfo instance.
|
||||||
|
public void setIsMyOffer(boolean isMyOffer) {
|
||||||
|
this.isMyOffer = isMyOffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OfferInfo toOfferInfo(Offer offer) {
|
public static OfferInfo toOfferInfo(Offer offer) {
|
||||||
return getOfferInfoBuilder(offer).build();
|
// Assume the offer is not mine, but isMyOffer can be reset to true, i.e., when
|
||||||
|
// calling TradeInfo toTradeInfo(Trade trade, String role, boolean isMyOffer);
|
||||||
|
return getOfferInfoBuilder(offer, false).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OfferInfo toOfferInfo(Offer offer, long triggerPrice) {
|
public static OfferInfo toPendingOfferInfo(Offer myNewOffer) {
|
||||||
// The Offer does not have a triggerPrice attribute, so we get
|
// Use this to build an OfferInfo instance when a new OpenOffer is being
|
||||||
// the base OfferInfoBuilder, then add the OpenOffer's triggerPrice.
|
// prepared, and no valid OpenOffer state (AVAILABLE, DEACTIVATED) exists.
|
||||||
return getOfferInfoBuilder(offer).withTriggerPrice(triggerPrice).build();
|
// It is needed for the CLI's 'createoffer' output, which has a boolean 'ENABLED'
|
||||||
|
// column that will show a PENDING value when this.isMyPendingOffer = true.
|
||||||
|
return getOfferInfoBuilder(myNewOffer, true)
|
||||||
|
.withIsMyPendingOffer(true)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OfferInfoBuilder getOfferInfoBuilder(Offer offer) {
|
public static OfferInfo toOfferInfo(OpenOffer openOffer) {
|
||||||
|
// An OpenOffer is always my offer.
|
||||||
|
return getOfferInfoBuilder(openOffer.getOffer(), true)
|
||||||
|
.withTriggerPrice(openOffer.getTriggerPrice())
|
||||||
|
.withIsActivated(!openOffer.isDeactivated())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OfferInfoBuilder getOfferInfoBuilder(Offer offer, boolean isMyOffer) {
|
||||||
return new OfferInfoBuilder()
|
return new OfferInfoBuilder()
|
||||||
.withId(offer.getId())
|
.withId(offer.getId())
|
||||||
.withDirection(offer.getDirection().name())
|
.withDirection(offer.getDirection().name())
|
||||||
|
@ -123,7 +147,8 @@ public class OfferInfo implements Payload {
|
||||||
.withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode())
|
.withBaseCurrencyCode(offer.getOfferPayload().getBaseCurrencyCode())
|
||||||
.withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode())
|
.withCounterCurrencyCode(offer.getOfferPayload().getCounterCurrencyCode())
|
||||||
.withDate(offer.getDate().getTime())
|
.withDate(offer.getDate().getTime())
|
||||||
.withState(offer.getState().name());
|
.withState(offer.getState().name())
|
||||||
|
.withIsMyOffer(isMyOffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -156,6 +181,9 @@ public class OfferInfo implements Payload {
|
||||||
.setCounterCurrencyCode(counterCurrencyCode)
|
.setCounterCurrencyCode(counterCurrencyCode)
|
||||||
.setDate(date)
|
.setDate(date)
|
||||||
.setState(state)
|
.setState(state)
|
||||||
|
.setIsActivated(isActivated)
|
||||||
|
.setIsMyOffer(isMyOffer)
|
||||||
|
.setIsMyPendingOffer(isMyPendingOffer)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,6 +213,9 @@ public class OfferInfo implements Payload {
|
||||||
.withCounterCurrencyCode(proto.getCounterCurrencyCode())
|
.withCounterCurrencyCode(proto.getCounterCurrencyCode())
|
||||||
.withDate(proto.getDate())
|
.withDate(proto.getDate())
|
||||||
.withState(proto.getState())
|
.withState(proto.getState())
|
||||||
|
.withIsActivated(proto.getIsActivated())
|
||||||
|
.withIsMyOffer(proto.getIsMyOffer())
|
||||||
|
.withIsMyPendingOffer(proto.getIsMyPendingOffer())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,6 +249,9 @@ public class OfferInfo implements Payload {
|
||||||
private String counterCurrencyCode;
|
private String counterCurrencyCode;
|
||||||
private long date;
|
private long date;
|
||||||
private String state;
|
private String state;
|
||||||
|
private boolean isActivated;
|
||||||
|
private boolean isMyOffer;
|
||||||
|
private boolean isMyPendingOffer;
|
||||||
|
|
||||||
public OfferInfoBuilder withId(String id) {
|
public OfferInfoBuilder withId(String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
@ -334,6 +368,21 @@ public class OfferInfo implements Payload {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OfferInfoBuilder withIsActivated(boolean isActivated) {
|
||||||
|
this.isActivated = isActivated;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferInfoBuilder withIsMyOffer(boolean isMyOffer) {
|
||||||
|
this.isMyOffer = isMyOffer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferInfoBuilder withIsMyPendingOffer(boolean isMyPendingOffer) {
|
||||||
|
this.isMyPendingOffer = isMyPendingOffer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public OfferInfo build() {
|
public OfferInfo build() {
|
||||||
return new OfferInfo(this);
|
return new OfferInfo(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,11 +92,11 @@ public class TradeInfo implements Payload {
|
||||||
this.contract = builder.contract;
|
this.contract = builder.contract;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TradeInfo toTradeInfo(Trade trade) {
|
public static TradeInfo toNewTradeInfo(Trade trade) {
|
||||||
return toTradeInfo(trade, null);
|
return toTradeInfo(trade, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TradeInfo toTradeInfo(Trade trade, String role) {
|
public static TradeInfo toTradeInfo(Trade trade, String role, boolean isMyOffer) {
|
||||||
ContractInfo contractInfo;
|
ContractInfo contractInfo;
|
||||||
if (trade.getContract() != null) {
|
if (trade.getContract() != null) {
|
||||||
Contract contract = trade.getContract();
|
Contract contract = trade.getContract();
|
||||||
|
@ -116,8 +116,10 @@ public class TradeInfo implements Payload {
|
||||||
contractInfo = ContractInfo.emptyContract.get();
|
contractInfo = ContractInfo.emptyContract.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OfferInfo offerInfo = toOfferInfo(trade.getOffer());
|
||||||
|
offerInfo.setIsMyOffer(isMyOffer);
|
||||||
return new TradeInfoBuilder()
|
return new TradeInfoBuilder()
|
||||||
.withOffer(toOfferInfo(trade.getOffer()))
|
.withOffer(offerInfo)
|
||||||
.withTradeId(trade.getId())
|
.withTradeId(trade.getId())
|
||||||
.withShortId(trade.getShortId())
|
.withShortId(trade.getShortId())
|
||||||
.withDate(trade.getDate().getTime())
|
.withDate(trade.getDate().getTime())
|
||||||
|
|
|
@ -32,6 +32,8 @@ import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
|
|
||||||
|
import bisq.network.p2p.storage.P2PDataStorage;
|
||||||
|
|
||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.KeyRing;
|
||||||
import bisq.common.util.MathUtils;
|
import bisq.common.util.MathUtils;
|
||||||
|
|
||||||
|
@ -72,12 +74,12 @@ public class MarketAlerts {
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
offerBookService.addOfferBookChangedListener(new OfferBookService.OfferBookChangedListener() {
|
offerBookService.addOfferBookChangedListener(new OfferBookService.OfferBookChangedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onAdded(Offer offer) {
|
public void onAdded(Offer offer, P2PDataStorage.ByteArray hashOfPayload) {
|
||||||
onOfferAdded(offer);
|
onOfferAdded(offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRemoved(Offer offer) {
|
public void onRemoved(Offer offer, P2PDataStorage.ByteArray hashOfPayload) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
applyFilterOnAllOffers();
|
applyFilterOnAllOffers();
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* 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.offer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of editable OfferPayload fields.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public final class MutableOfferPayloadFields {
|
||||||
|
|
||||||
|
private final long price;
|
||||||
|
private final double marketPriceMargin;
|
||||||
|
private final boolean useMarketBasedPrice;
|
||||||
|
private final String baseCurrencyCode;
|
||||||
|
private final String counterCurrencyCode;
|
||||||
|
private final String paymentMethodId;
|
||||||
|
private final String makerPaymentAccountId;
|
||||||
|
@Nullable
|
||||||
|
private final String countryCode;
|
||||||
|
@Nullable
|
||||||
|
private final List<String> acceptedCountryCodes;
|
||||||
|
@Nullable
|
||||||
|
private final String bankId;
|
||||||
|
@Nullable
|
||||||
|
private final List<String> acceptedBankIds;
|
||||||
|
@Nullable
|
||||||
|
private final Map<String, String> extraDataMap;
|
||||||
|
|
||||||
|
public MutableOfferPayloadFields(OfferPayload offerPayload) {
|
||||||
|
this(offerPayload.getPrice(),
|
||||||
|
offerPayload.getMarketPriceMargin(),
|
||||||
|
offerPayload.isUseMarketBasedPrice(),
|
||||||
|
offerPayload.getBaseCurrencyCode(),
|
||||||
|
offerPayload.getCounterCurrencyCode(),
|
||||||
|
offerPayload.getPaymentMethodId(),
|
||||||
|
offerPayload.getMakerPaymentAccountId(),
|
||||||
|
offerPayload.getCountryCode(),
|
||||||
|
offerPayload.getAcceptedCountryCodes(),
|
||||||
|
offerPayload.getBankId(),
|
||||||
|
offerPayload.getAcceptedBankIds(),
|
||||||
|
offerPayload.getExtraDataMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableOfferPayloadFields(long price,
|
||||||
|
double marketPriceMargin,
|
||||||
|
boolean useMarketBasedPrice,
|
||||||
|
String baseCurrencyCode,
|
||||||
|
String counterCurrencyCode,
|
||||||
|
String paymentMethodId,
|
||||||
|
String makerPaymentAccountId,
|
||||||
|
@Nullable String countryCode,
|
||||||
|
@Nullable List<String> acceptedCountryCodes,
|
||||||
|
@Nullable String bankId,
|
||||||
|
@Nullable List<String> acceptedBankIds,
|
||||||
|
@Nullable Map<String, String> extraDataMap) {
|
||||||
|
this.price = price;
|
||||||
|
this.marketPriceMargin = marketPriceMargin;
|
||||||
|
this.useMarketBasedPrice = useMarketBasedPrice;
|
||||||
|
this.baseCurrencyCode = baseCurrencyCode;
|
||||||
|
this.counterCurrencyCode = counterCurrencyCode;
|
||||||
|
this.paymentMethodId = paymentMethodId;
|
||||||
|
this.makerPaymentAccountId = makerPaymentAccountId;
|
||||||
|
this.countryCode = countryCode;
|
||||||
|
this.acceptedCountryCodes = acceptedCountryCodes;
|
||||||
|
this.bankId = bankId;
|
||||||
|
this.acceptedBankIds = acceptedBankIds;
|
||||||
|
this.extraDataMap = extraDataMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.network.p2p.BootstrapListener;
|
import bisq.network.p2p.BootstrapListener;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
import bisq.network.p2p.storage.HashMapChangedListener;
|
import bisq.network.p2p.storage.HashMapChangedListener;
|
||||||
|
import bisq.network.p2p.storage.P2PDataStorage;
|
||||||
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
|
@ -44,22 +45,23 @@ import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static bisq.network.p2p.storage.P2PDataStorage.get32ByteHashAsByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles storage and retrieval of offers.
|
* Handles storage and retrieval of offers.
|
||||||
* Uses an invalidation flag to only request the full offer map in case there was a change (anyone has added or removed an offer).
|
* Uses an invalidation flag to only request the full offer map in case there was a change (anyone has added or removed an offer).
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class OfferBookService {
|
public class OfferBookService {
|
||||||
private static final Logger log = LoggerFactory.getLogger(OfferBookService.class);
|
|
||||||
|
|
||||||
public interface OfferBookChangedListener {
|
public interface OfferBookChangedListener {
|
||||||
void onAdded(Offer offer);
|
void onAdded(Offer offer, P2PDataStorage.ByteArray hashOfPayload);
|
||||||
|
|
||||||
void onRemoved(Offer offer);
|
void onRemoved(Offer offer, P2PDataStorage.ByteArray hashOfPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
|
@ -92,7 +94,8 @@ public class OfferBookService {
|
||||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
||||||
Offer offer = new Offer(offerPayload);
|
Offer offer = new Offer(offerPayload);
|
||||||
offer.setPriceFeedService(priceFeedService);
|
offer.setPriceFeedService(priceFeedService);
|
||||||
listener.onAdded(offer);
|
P2PDataStorage.ByteArray hashOfPayload = get32ByteHashAsByteArray(offerPayload);
|
||||||
|
listener.onAdded(offer, hashOfPayload);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -104,7 +107,8 @@ public class OfferBookService {
|
||||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
||||||
Offer offer = new Offer(offerPayload);
|
Offer offer = new Offer(offerPayload);
|
||||||
offer.setPriceFeedService(priceFeedService);
|
offer.setPriceFeedService(priceFeedService);
|
||||||
listener.onRemoved(offer);
|
P2PDataStorage.ByteArray hashOfPayload = get32ByteHashAsByteArray(offerPayload);
|
||||||
|
listener.onRemoved(offer, hashOfPayload);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -116,12 +120,12 @@ public class OfferBookService {
|
||||||
public void onUpdatedDataReceived() {
|
public void onUpdatedDataReceived() {
|
||||||
addOfferBookChangedListener(new OfferBookChangedListener() {
|
addOfferBookChangedListener(new OfferBookChangedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onAdded(Offer offer) {
|
public void onAdded(Offer offer, P2PDataStorage.ByteArray hashOfPayload) {
|
||||||
doDumpStatistics();
|
doDumpStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRemoved(Offer offer) {
|
public void onRemoved(Offer offer, P2PDataStorage.ByteArray hashOfPayload) {
|
||||||
doDumpStatistics();
|
doDumpStatistics();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -364,6 +364,53 @@ public class OfferUtil {
|
||||||
Res.get("offerbook.warning.paymentMethodBanned"));
|
Res.get("offerbook.warning.paymentMethodBanned"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns an edited payload: a merge of the original offerPayload and
|
||||||
|
// editedOfferPayload fields. Mutable fields are sourced from
|
||||||
|
// mutableOfferPayloadFields param, e.g., payment account details, price, etc.
|
||||||
|
// Immutable fields are sourced from the original openOffer param.
|
||||||
|
public OfferPayload getMergedOfferPayload(OpenOffer openOffer,
|
||||||
|
MutableOfferPayloadFields mutableOfferPayloadFields) {
|
||||||
|
OfferPayload originalOfferPayload = openOffer.getOffer().getOfferPayload();
|
||||||
|
return new OfferPayload(originalOfferPayload.getId(),
|
||||||
|
originalOfferPayload.getDate(),
|
||||||
|
originalOfferPayload.getOwnerNodeAddress(),
|
||||||
|
originalOfferPayload.getPubKeyRing(),
|
||||||
|
originalOfferPayload.getDirection(),
|
||||||
|
mutableOfferPayloadFields.getPrice(),
|
||||||
|
mutableOfferPayloadFields.getMarketPriceMargin(),
|
||||||
|
mutableOfferPayloadFields.isUseMarketBasedPrice(),
|
||||||
|
originalOfferPayload.getAmount(),
|
||||||
|
originalOfferPayload.getMinAmount(),
|
||||||
|
mutableOfferPayloadFields.getBaseCurrencyCode(),
|
||||||
|
mutableOfferPayloadFields.getCounterCurrencyCode(),
|
||||||
|
originalOfferPayload.getArbitratorNodeAddresses(),
|
||||||
|
originalOfferPayload.getMediatorNodeAddresses(),
|
||||||
|
mutableOfferPayloadFields.getPaymentMethodId(),
|
||||||
|
mutableOfferPayloadFields.getMakerPaymentAccountId(),
|
||||||
|
originalOfferPayload.getOfferFeePaymentTxId(),
|
||||||
|
mutableOfferPayloadFields.getCountryCode(),
|
||||||
|
mutableOfferPayloadFields.getAcceptedCountryCodes(),
|
||||||
|
mutableOfferPayloadFields.getBankId(),
|
||||||
|
mutableOfferPayloadFields.getAcceptedBankIds(),
|
||||||
|
originalOfferPayload.getVersionNr(),
|
||||||
|
originalOfferPayload.getBlockHeightAtOfferCreation(),
|
||||||
|
originalOfferPayload.getTxFee(),
|
||||||
|
originalOfferPayload.getMakerFee(),
|
||||||
|
originalOfferPayload.isCurrencyForMakerFeeBtc(),
|
||||||
|
originalOfferPayload.getBuyerSecurityDeposit(),
|
||||||
|
originalOfferPayload.getSellerSecurityDeposit(),
|
||||||
|
originalOfferPayload.getMaxTradeLimit(),
|
||||||
|
originalOfferPayload.getMaxTradePeriod(),
|
||||||
|
originalOfferPayload.isUseAutoClose(),
|
||||||
|
originalOfferPayload.isUseReOpenAfterAutoClose(),
|
||||||
|
originalOfferPayload.getLowerClosePrice(),
|
||||||
|
originalOfferPayload.getUpperClosePrice(),
|
||||||
|
originalOfferPayload.isPrivateOffer(),
|
||||||
|
originalOfferPayload.getHashOfChallenge(),
|
||||||
|
mutableOfferPayloadFields.getExtraDataMap(),
|
||||||
|
originalOfferPayload.getProtocolVersion());
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
|
private Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
|
||||||
boolean isCurrencyForMakerFeeBtc,
|
boolean isCurrencyForMakerFeeBtc,
|
||||||
String userCurrencyCode,
|
String userCurrencyCode,
|
||||||
|
|
95
core/src/main/resources/help/editoffer-help.txt
Normal file
95
core/src/main/resources/help/editoffer-help.txt
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
editoffer
|
||||||
|
|
||||||
|
NAME
|
||||||
|
----
|
||||||
|
editoffer - edit an existing offer to buy or sell BTC
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
--------
|
||||||
|
editoffer
|
||||||
|
--offer-id=<offer-id>
|
||||||
|
[--market-price-margin=<percent>]
|
||||||
|
[--trigger-price=<btc-price>]
|
||||||
|
[--fixed-price=<btc-price>]
|
||||||
|
[--enabled=<true|false>]
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
-----------
|
||||||
|
Edit an existing offer. Offers can be changed in the following ways:
|
||||||
|
|
||||||
|
Change a fixed-price offer to a market-price-margin based offer.
|
||||||
|
Change a market-price-margin based offer to a fixed-price offer.
|
||||||
|
Change a market-price-margin.
|
||||||
|
Change a fixed-price.
|
||||||
|
Define, change, or remove a market-price-margin based offer's trigger price.
|
||||||
|
Disable an enabled offer.
|
||||||
|
Enable a disabled offer.
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
-------
|
||||||
|
--offer-id
|
||||||
|
The ID of the buy or sell offer to edit.
|
||||||
|
|
||||||
|
--market-price-margin
|
||||||
|
Changes the % above or below market BTC price, e.g., 1.00 (1%).
|
||||||
|
A --fixed-price offer can be changed to a --market-price-margin offer with this option.
|
||||||
|
The --market-price-margin and --trigger-price options can be used in the same editoffer command.
|
||||||
|
The --market-price-margin and --fixed-price options cannot be used in the same editoffer command.
|
||||||
|
|
||||||
|
--fixed-price
|
||||||
|
Changes the fixed BTC price in fiat used to buy or sell BTC, e.g., 34000 (USD).
|
||||||
|
A --market-price-margin offer can be changed to a --fixed-price offer with this option.
|
||||||
|
The --fixed-price and --market-price-margin options cannot be used in the same editoffer command.
|
||||||
|
|
||||||
|
--trigger-price
|
||||||
|
Sets the market price for triggering the de-activation of an offer, or defines trigger-price on an
|
||||||
|
offer that did not have a trigger-price when it was created.
|
||||||
|
A buy BTC offer is de-activated when the market price rises above the trigger-price.
|
||||||
|
A sell BTC offer is de-activated when the market price falls below the trigger-price.
|
||||||
|
Only applies to market-price-margin based offers; a fixed-price offer's trigger-price is ignored.
|
||||||
|
The --fixed-price and --trigger-price options cannot be used in the same editoffer command.
|
||||||
|
|
||||||
|
--enabled
|
||||||
|
If true, enables a disabled offer. Does nothing if offer is already enabled.
|
||||||
|
If false, disabled an enabled offer. Does nothing if offer is already disabled.
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
--------
|
||||||
|
|
||||||
|
To change a fixed-price offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea
|
||||||
|
to a 0.10% market-price-margin based offer:
|
||||||
|
$ ./bisq-cli --password=xyz --port=9998 editoffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--market-price-margin=0.10
|
||||||
|
|
||||||
|
To change a market-price-margin based offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea
|
||||||
|
to a fixed-price offer:
|
||||||
|
$ ./bisq-cli --password=xyz --port=9998 editoffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--fixed-price=50000.0000
|
||||||
|
|
||||||
|
To set or change the trigger-price on a market-price-margin
|
||||||
|
based offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea:
|
||||||
|
$ ./bisq-cli --password=xyz --port=9998 editoffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--trigger-price=50000.0000
|
||||||
|
|
||||||
|
To remove a trigger-price on a market-price-margin
|
||||||
|
based offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea:
|
||||||
|
$ ./bisq-cli --password=xyz --port=9998 editoffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--trigger-price=0
|
||||||
|
|
||||||
|
To change the market-price-margin and trigger-price on an
|
||||||
|
offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea:
|
||||||
|
$ ./bisq-cli --password=xyz --port=9998 editoffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--market-price-margin=0.05 \
|
||||||
|
--trigger-price=50000.0000
|
||||||
|
|
||||||
|
To disable an offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea:
|
||||||
|
$ ./bisq-cli --password=xyz --port=9998 editoffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--enable=false
|
||||||
|
|
||||||
|
To enable a disabled offer with ID 83e8b2e2-51b6-4f39-a748-3ebd29c22aea,
|
||||||
|
and change it from a fixed-price offer to a 0.50% market-price-margin based offer,
|
||||||
|
and set the trigger-price to 50000.0000:
|
||||||
|
$ ./bisq-cli --password=xyz --port=9998 editoffer --offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
|
--market-price-margin=0.50 \
|
||||||
|
--trigger-price=50000.0000 \
|
||||||
|
--enable=true
|
|
@ -26,6 +26,8 @@ import bisq.proto.grpc.CancelOfferReply;
|
||||||
import bisq.proto.grpc.CancelOfferRequest;
|
import bisq.proto.grpc.CancelOfferRequest;
|
||||||
import bisq.proto.grpc.CreateOfferReply;
|
import bisq.proto.grpc.CreateOfferReply;
|
||||||
import bisq.proto.grpc.CreateOfferRequest;
|
import bisq.proto.grpc.CreateOfferRequest;
|
||||||
|
import bisq.proto.grpc.EditOfferReply;
|
||||||
|
import bisq.proto.grpc.EditOfferRequest;
|
||||||
import bisq.proto.grpc.GetMyOfferReply;
|
import bisq.proto.grpc.GetMyOfferReply;
|
||||||
import bisq.proto.grpc.GetMyOfferRequest;
|
import bisq.proto.grpc.GetMyOfferRequest;
|
||||||
import bisq.proto.grpc.GetMyOffersReply;
|
import bisq.proto.grpc.GetMyOffersReply;
|
||||||
|
@ -48,6 +50,7 @@ import java.util.stream.Collectors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static bisq.core.api.model.OfferInfo.toOfferInfo;
|
import static bisq.core.api.model.OfferInfo.toOfferInfo;
|
||||||
|
import static bisq.core.api.model.OfferInfo.toPendingOfferInfo;
|
||||||
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
|
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
|
||||||
import static bisq.proto.grpc.OffersGrpc.*;
|
import static bisq.proto.grpc.OffersGrpc.*;
|
||||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
@ -89,10 +92,9 @@ class GrpcOffersService extends OffersImplBase {
|
||||||
public void getMyOffer(GetMyOfferRequest req,
|
public void getMyOffer(GetMyOfferRequest req,
|
||||||
StreamObserver<GetMyOfferReply> responseObserver) {
|
StreamObserver<GetMyOfferReply> responseObserver) {
|
||||||
try {
|
try {
|
||||||
Offer offer = coreApi.getMyOffer(req.getId());
|
OpenOffer openOffer = coreApi.getMyOffer(req.getId());
|
||||||
OpenOffer openOffer = coreApi.getMyOpenOffer(req.getId());
|
|
||||||
var reply = GetMyOfferReply.newBuilder()
|
var reply = GetMyOfferReply.newBuilder()
|
||||||
.setOffer(toOfferInfo(offer, openOffer.getTriggerPrice()).toProtoMessage())
|
.setOffer(toOfferInfo(openOffer).toProtoMessage())
|
||||||
.build();
|
.build();
|
||||||
responseObserver.onNext(reply);
|
responseObserver.onNext(reply);
|
||||||
responseObserver.onCompleted();
|
responseObserver.onCompleted();
|
||||||
|
@ -125,7 +127,8 @@ class GrpcOffersService extends OffersImplBase {
|
||||||
StreamObserver<GetMyOffersReply> responseObserver) {
|
StreamObserver<GetMyOffersReply> responseObserver) {
|
||||||
try {
|
try {
|
||||||
List<OfferInfo> result = coreApi.getMyOffers(req.getDirection(), req.getCurrencyCode())
|
List<OfferInfo> result = coreApi.getMyOffers(req.getDirection(), req.getCurrencyCode())
|
||||||
.stream().map(OfferInfo::toOfferInfo)
|
.stream()
|
||||||
|
.map(OfferInfo::toOfferInfo)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
var reply = GetMyOffersReply.newBuilder()
|
var reply = GetMyOffersReply.newBuilder()
|
||||||
.addAllOffers(result.stream()
|
.addAllOffers(result.stream()
|
||||||
|
@ -158,7 +161,7 @@ class GrpcOffersService extends OffersImplBase {
|
||||||
offer -> {
|
offer -> {
|
||||||
// This result handling consumer's accept operation will return
|
// This result handling consumer's accept operation will return
|
||||||
// the new offer to the gRPC client after async placement is done.
|
// the new offer to the gRPC client after async placement is done.
|
||||||
OfferInfo offerInfo = toOfferInfo(offer);
|
OfferInfo offerInfo = toPendingOfferInfo(offer);
|
||||||
CreateOfferReply reply = CreateOfferReply.newBuilder()
|
CreateOfferReply reply = CreateOfferReply.newBuilder()
|
||||||
.setOffer(offerInfo.toProtoMessage())
|
.setOffer(offerInfo.toProtoMessage())
|
||||||
.build();
|
.build();
|
||||||
|
@ -170,6 +173,25 @@ class GrpcOffersService extends OffersImplBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void editOffer(EditOfferRequest req,
|
||||||
|
StreamObserver<EditOfferReply> responseObserver) {
|
||||||
|
try {
|
||||||
|
coreApi.editOffer(req.getId(),
|
||||||
|
req.getPrice(),
|
||||||
|
req.getUseMarketBasedPrice(),
|
||||||
|
req.getMarketPriceMargin(),
|
||||||
|
req.getTriggerPrice(),
|
||||||
|
req.getEnable(),
|
||||||
|
req.getEditType());
|
||||||
|
var reply = EditOfferReply.newBuilder().build();
|
||||||
|
responseObserver.onNext(reply);
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
exceptionHandler.handleException(log, cause, responseObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancelOffer(CancelOfferRequest req,
|
public void cancelOffer(CancelOfferRequest req,
|
||||||
StreamObserver<CancelOfferReply> responseObserver) {
|
StreamObserver<CancelOfferReply> responseObserver) {
|
||||||
|
@ -198,6 +220,7 @@ class GrpcOffersService extends OffersImplBase {
|
||||||
put(getGetOffersMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
put(getGetOffersMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
||||||
put(getGetMyOffersMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
put(getGetMyOffersMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
||||||
put(getCreateOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
put(getCreateOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
||||||
|
put(getEditOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
||||||
put(getCancelOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
put(getCancelOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
||||||
}}
|
}}
|
||||||
)));
|
)));
|
||||||
|
|
|
@ -44,6 +44,7 @@ import java.util.Optional;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.api.model.TradeInfo.toTradeInfo;
|
||||||
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
|
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
|
||||||
import static bisq.proto.grpc.TradesGrpc.*;
|
import static bisq.proto.grpc.TradesGrpc.*;
|
||||||
|
@ -72,9 +73,10 @@ class GrpcTradesService extends TradesImplBase {
|
||||||
StreamObserver<GetTradeReply> responseObserver) {
|
StreamObserver<GetTradeReply> responseObserver) {
|
||||||
try {
|
try {
|
||||||
Trade trade = coreApi.getTrade(req.getTradeId());
|
Trade trade = coreApi.getTrade(req.getTradeId());
|
||||||
|
boolean isMyOffer = coreApi.isMyOffer(trade.getOffer().getId());
|
||||||
String role = coreApi.getTradeRole(req.getTradeId());
|
String role = coreApi.getTradeRole(req.getTradeId());
|
||||||
var reply = GetTradeReply.newBuilder()
|
var reply = GetTradeReply.newBuilder()
|
||||||
.setTrade(toTradeInfo(trade, role).toProtoMessage())
|
.setTrade(toTradeInfo(trade, role, isMyOffer).toProtoMessage())
|
||||||
.build();
|
.build();
|
||||||
responseObserver.onNext(reply);
|
responseObserver.onNext(reply);
|
||||||
responseObserver.onCompleted();
|
responseObserver.onCompleted();
|
||||||
|
@ -99,7 +101,7 @@ class GrpcTradesService extends TradesImplBase {
|
||||||
req.getPaymentAccountId(),
|
req.getPaymentAccountId(),
|
||||||
req.getTakerFeeCurrencyCode(),
|
req.getTakerFeeCurrencyCode(),
|
||||||
trade -> {
|
trade -> {
|
||||||
TradeInfo tradeInfo = toTradeInfo(trade);
|
TradeInfo tradeInfo = toNewTradeInfo(trade);
|
||||||
var reply = TakeOfferReply.newBuilder()
|
var reply = TakeOfferReply.newBuilder()
|
||||||
.setTrade(tradeInfo.toProtoMessage())
|
.setTrade(tradeInfo.toProtoMessage())
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -21,8 +21,8 @@ import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferBookService;
|
import bisq.core.offer.OfferBookService;
|
||||||
import bisq.core.offer.OfferRestrictions;
|
import bisq.core.offer.OfferRestrictions;
|
||||||
import bisq.core.trade.TradeManager;
|
|
||||||
|
|
||||||
|
import bisq.network.p2p.storage.P2PDataStorage;
|
||||||
import bisq.network.utils.Utils;
|
import bisq.network.utils.Utils;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
@ -32,6 +32,7 @@ import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -61,13 +62,14 @@ public class OfferBook {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
OfferBook(OfferBookService offerBookService, TradeManager tradeManager, FilterManager filterManager) {
|
OfferBook(OfferBookService offerBookService, FilterManager filterManager) {
|
||||||
this.offerBookService = offerBookService;
|
this.offerBookService = offerBookService;
|
||||||
this.filterManager = filterManager;
|
this.filterManager = filterManager;
|
||||||
|
|
||||||
offerBookService.addOfferBookChangedListener(new OfferBookService.OfferBookChangedListener() {
|
offerBookService.addOfferBookChangedListener(new OfferBookService.OfferBookChangedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onAdded(Offer offer) {
|
public void onAdded(Offer offer, P2PDataStorage.ByteArray hashOfPayload) {
|
||||||
|
printOfferBookListItems("Before onAdded");
|
||||||
// We get onAdded called every time a new ProtectedStorageEntry is received.
|
// We get onAdded called every time a new ProtectedStorageEntry is received.
|
||||||
// Mostly it is the same OfferPayload but the ProtectedStorageEntry is different.
|
// Mostly it is the same OfferPayload but the ProtectedStorageEntry is different.
|
||||||
// We filter here to only add new offers if the same offer (using equals) was not already added and it
|
// We filter here to only add new offers if the same offer (using equals) was not already added and it
|
||||||
|
@ -83,44 +85,111 @@ public class OfferBook {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasSameOffer = offerBookListItems.stream()
|
// Use offer.equals(offer) to see if the OfferBook list contains an exact
|
||||||
.anyMatch(item -> item.getOffer().equals(offer));
|
// match -- offer.equals(offer) includes comparisons of payload, state
|
||||||
|
// and errorMessage.
|
||||||
|
boolean hasSameOffer = offerBookListItems.stream().anyMatch(item -> item.getOffer().equals(offer));
|
||||||
if (!hasSameOffer) {
|
if (!hasSameOffer) {
|
||||||
OfferBookListItem offerBookListItem = new OfferBookListItem(offer);
|
OfferBookListItem newOfferBookListItem = new OfferBookListItem(offer, hashOfPayload);
|
||||||
// We don't use the contains method as the equals method in Offer takes state and errorMessage into account.
|
removeDuplicateItem(newOfferBookListItem);
|
||||||
// If we have an offer with same ID we remove it and add the new offer as it might have a changed state.
|
offerBookListItems.add(newOfferBookListItem); // Add replacement.
|
||||||
Optional<OfferBookListItem> candidateWithSameId = offerBookListItems.stream()
|
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
|
||||||
.filter(item -> item.getOffer().getId().equals(offer.getId()))
|
log.debug("onAdded: Added new offer {}\n"
|
||||||
.findAny();
|
+ "\twith newItem.payloadHash: {}",
|
||||||
if (candidateWithSameId.isPresent()) {
|
offer.getId(),
|
||||||
log.warn("We had an old offer in the list with the same Offer ID. We remove the old one. " +
|
newOfferBookListItem.hashOfPayload == null ? "null" : newOfferBookListItem.hashOfPayload.getHex());
|
||||||
"old offerBookListItem={}, new offerBookListItem={}", candidateWithSameId.get(), offerBookListItem);
|
|
||||||
offerBookListItems.remove(candidateWithSameId.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
offerBookListItems.add(offerBookListItem);
|
|
||||||
} else {
|
} else {
|
||||||
log.debug("We have the exact same offer already in our list and ignore the onAdded call. ID={}", offer.getId());
|
log.debug("We have the exact same offer already in our list and ignore the onAdded call. ID={}", offer.getId());
|
||||||
}
|
}
|
||||||
|
printOfferBookListItems("After onAdded");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRemoved(Offer offer) {
|
public void onRemoved(Offer offer, P2PDataStorage.ByteArray hashOfPayload) {
|
||||||
removeOffer(offer, tradeManager);
|
printOfferBookListItems("Before onRemoved");
|
||||||
|
removeOffer(offer, hashOfPayload);
|
||||||
|
printOfferBookListItems("After onRemoved");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeOffer(Offer offer, TradeManager tradeManager) {
|
private void removeDuplicateItem(OfferBookListItem newOfferBookListItem) {
|
||||||
|
String offerId = newOfferBookListItem.getOffer().getId();
|
||||||
|
// We need to remove any view items with a matching offerId before
|
||||||
|
// a newOfferBookListItem is added to the view.
|
||||||
|
List<OfferBookListItem> duplicateItems = offerBookListItems.stream()
|
||||||
|
.filter(item -> item.getOffer().getId().equals(offerId))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
duplicateItems.forEach(oldOfferItem -> {
|
||||||
|
offerBookListItems.remove(oldOfferItem);
|
||||||
|
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
|
||||||
|
log.debug("onAdded: Removed old offer {}\n"
|
||||||
|
+ "\twith payload hash {} from list.\n"
|
||||||
|
+ "\tThis may make a subsequent onRemoved( {} ) call redundant.",
|
||||||
|
offerId,
|
||||||
|
oldOfferItem.getHashOfPayload() == null ? "null" : oldOfferItem.getHashOfPayload().getHex(),
|
||||||
|
oldOfferItem.getOffer().getId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeOffer(Offer offer, P2PDataStorage.ByteArray hashOfPayload) {
|
||||||
// Update state in case that that offer is used in the take offer screen, so it gets updated correctly
|
// Update state in case that that offer is used in the take offer screen, so it gets updated correctly
|
||||||
offer.setState(Offer.State.REMOVED);
|
offer.setState(Offer.State.REMOVED);
|
||||||
|
|
||||||
offer.cancelAvailabilityRequest();
|
offer.cancelAvailabilityRequest();
|
||||||
// We don't use the contains method as the equals method in Offer takes state and errorMessage into account.
|
|
||||||
Optional<OfferBookListItem> candidateToRemove = offerBookListItems.stream()
|
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
|
||||||
.filter(item -> item.getOffer().getId().equals(offer.getId()))
|
log.debug("onRemoved: id = {}\n"
|
||||||
|
+ "\twith payload-hash = {}",
|
||||||
|
offer.getId(),
|
||||||
|
hashOfPayload == null ? "null" : hashOfPayload.getHex());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the removal candidate in the OfferBook list with matching offerId and payload-hash.
|
||||||
|
Optional<OfferBookListItem> candidateWithMatchingPayloadHash = offerBookListItems.stream()
|
||||||
|
.filter(item -> item.getOffer().getId().equals(offer.getId()) && (
|
||||||
|
item.hashOfPayload == null
|
||||||
|
|| item.hashOfPayload.equals(hashOfPayload))
|
||||||
|
)
|
||||||
.findAny();
|
.findAny();
|
||||||
candidateToRemove.ifPresent(offerBookListItems::remove);
|
|
||||||
|
if (!candidateWithMatchingPayloadHash.isPresent()) {
|
||||||
|
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
|
||||||
|
log.debug("UI view list does not contain offer with id {} and payload-hash {}",
|
||||||
|
offer.getId(),
|
||||||
|
hashOfPayload == null ? "null" : hashOfPayload.getHex());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfferBookListItem candidate = candidateWithMatchingPayloadHash.get();
|
||||||
|
|
||||||
|
// Remove the candidate only if the candidate's offer payload is null (after list
|
||||||
|
// is populated by 'fillOfferBookListItems()'), or the hash matches the onRemoved
|
||||||
|
// hashOfPayload parameter. We may receive add/remove messages out of order
|
||||||
|
// (from api's 'editoffer'), and use the offer payload hash to ensure we do not
|
||||||
|
// remove an edited offer immediately after it was added.
|
||||||
|
if ((candidate.getHashOfPayload() == null || candidate.getHashOfPayload().equals(hashOfPayload))) {
|
||||||
|
// The payload-hash test passed, remove the candidate and print reason.
|
||||||
|
offerBookListItems.remove(candidate);
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
|
||||||
|
log.debug("Candidate.payload-hash: {} is null or == onRemoved.payload-hash: {} ?"
|
||||||
|
+ " Yes, removed old offer",
|
||||||
|
candidate.hashOfPayload == null ? "null" : candidate.hashOfPayload.getHex(),
|
||||||
|
hashOfPayload == null ? "null" : hashOfPayload.getHex());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (log.isDebugEnabled()) { // TODO delete debug stmt in future PR.
|
||||||
|
// Candidate's payload-hash test failed: payload-hash != onRemoved.payload-hash.
|
||||||
|
// Print reason for not removing candidate.
|
||||||
|
log.debug("Candidate.payload-hash: {} == onRemoved.payload-hash: {} ?"
|
||||||
|
+ " No, old offer not removed",
|
||||||
|
candidate.hashOfPayload == null ? "null" : candidate.hashOfPayload.getHex(),
|
||||||
|
hashOfPayload == null ? "null" : hashOfPayload.getHex());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableList<OfferBookListItem> getOfferBookListItems() {
|
public ObservableList<OfferBookListItem> getOfferBookListItems() {
|
||||||
|
@ -133,16 +202,28 @@ public class OfferBook {
|
||||||
// Investigate why....
|
// Investigate why....
|
||||||
offerBookListItems.clear();
|
offerBookListItems.clear();
|
||||||
offerBookListItems.addAll(offerBookService.getOffers().stream()
|
offerBookListItems.addAll(offerBookService.getOffers().stream()
|
||||||
.filter(o -> !filterManager.isOfferIdBanned(o.getId()))
|
.filter(o -> isOfferAllowed(o))
|
||||||
.filter(o -> !OfferRestrictions.requiresNodeAddressUpdate() || Utils.isV3Address(o.getMakerNodeAddress().getHostName()))
|
|
||||||
.map(OfferBookListItem::new)
|
.map(OfferBookListItem::new)
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
log.debug("offerBookListItems.size {}", offerBookListItems.size());
|
log.debug("offerBookListItems.size {}", offerBookListItems.size());
|
||||||
fillOfferCountMaps();
|
fillOfferCountMaps();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
t.printStackTrace();
|
log.error("Error at fillOfferBookListItems: " + t);
|
||||||
log.error("Error at fillOfferBookListItems: " + t.toString());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printOfferBookListItems(String msg) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
if (offerBookListItems.size() == 0) {
|
||||||
|
log.debug("{} -> OfferBookListItems: none", msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder(msg + " -> ").append("OfferBookListItems:").append("\n");
|
||||||
|
offerBookListItems.forEach(i -> stringBuilder.append("\t").append(i.toString()).append("\n"));
|
||||||
|
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
|
||||||
|
log.debug(stringBuilder.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +235,13 @@ public class OfferBook {
|
||||||
return sellOfferCountMap;
|
return sellOfferCountMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isOfferAllowed(Offer offer) {
|
||||||
|
boolean isBanned = filterManager.isOfferIdBanned(offer.getId());
|
||||||
|
boolean isV3NodeAddressCompliant = !OfferRestrictions.requiresNodeAddressUpdate()
|
||||||
|
|| Utils.isV3Address(offer.getMakerNodeAddress().getHostName());
|
||||||
|
return !isBanned && isV3NodeAddressCompliant;
|
||||||
|
}
|
||||||
|
|
||||||
private void fillOfferCountMaps() {
|
private void fillOfferCountMaps() {
|
||||||
buyOfferCountMap.clear();
|
buyOfferCountMap.clear();
|
||||||
sellOfferCountMap.clear();
|
sellOfferCountMap.clear();
|
||||||
|
|
|
@ -27,6 +27,8 @@ import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.payment.payload.PaymentMethod;
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
|
|
||||||
|
import bisq.network.p2p.storage.P2PDataStorage;
|
||||||
|
|
||||||
import de.jensd.fx.glyphs.GlyphIcons;
|
import de.jensd.fx.glyphs.GlyphIcons;
|
||||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
||||||
|
|
||||||
|
@ -40,18 +42,41 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@Slf4j
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class OfferBookListItem {
|
public class OfferBookListItem {
|
||||||
@Getter
|
@Getter
|
||||||
private final Offer offer;
|
private final Offer offer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The protected storage (offer) payload hash helps prevent edited offers from being
|
||||||
|
* mistakenly removed from a UI user's OfferBook list if the API's 'editoffer'
|
||||||
|
* command results in onRemoved(offer) being called after onAdded(offer) on peers.
|
||||||
|
* (Checking the offer-id is not enough.) This msg order problem does not happen
|
||||||
|
* when the UI edits an offer because the remove/add msgs are always sent in separate
|
||||||
|
* envelope bundles. It can happen when the API is used to edit an offer because
|
||||||
|
* the remove/add msgs are received in the same envelope bundle, then processed in
|
||||||
|
* unpredictable order.
|
||||||
|
*
|
||||||
|
* A null value indicates the item's payload hash has not been set by onAdded or
|
||||||
|
* onRemoved since the most recent OfferBook view refresh.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Getter
|
||||||
|
P2PDataStorage.ByteArray hashOfPayload;
|
||||||
|
|
||||||
// We cache the data once created for performance reasons. AccountAgeWitnessService calls can
|
// We cache the data once created for performance reasons. AccountAgeWitnessService calls can
|
||||||
// be a bit expensive.
|
// be a bit expensive.
|
||||||
private WitnessAgeData witnessAgeData;
|
private WitnessAgeData witnessAgeData;
|
||||||
|
|
||||||
public OfferBookListItem(Offer offer) {
|
public OfferBookListItem(Offer offer) {
|
||||||
|
this(offer, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferBookListItem(Offer offer, @Nullable P2PDataStorage.ByteArray hashOfPayload) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
|
this.hashOfPayload = hashOfPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WitnessAgeData getWitnessAgeData(AccountAgeWitnessService accountAgeWitnessService,
|
public WitnessAgeData getWitnessAgeData(AccountAgeWitnessService accountAgeWitnessService,
|
||||||
|
@ -95,6 +120,15 @@ public class OfferBookListItem {
|
||||||
return witnessAgeData;
|
return witnessAgeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "OfferBookListItem{" +
|
||||||
|
"offerId=" + offer.getId() +
|
||||||
|
", hashOfPayload=" + (hashOfPayload == null ? "null" : hashOfPayload.getHex()) +
|
||||||
|
", witnessAgeData=" + (witnessAgeData == null ? "null" : witnessAgeData.displayString) +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
public static class WitnessAgeData implements Comparable<WitnessAgeData> {
|
public static class WitnessAgeData implements Comparable<WitnessAgeData> {
|
||||||
String displayString;
|
String displayString;
|
||||||
|
|
|
@ -298,7 +298,7 @@ class TakeOfferDataModel extends OfferDataModel {
|
||||||
// only local effect. Other trader might see the offer for a few seconds
|
// only local effect. Other trader might see the offer for a few seconds
|
||||||
// still (but cannot take it).
|
// still (but cannot take it).
|
||||||
if (removeOffer) {
|
if (removeOffer) {
|
||||||
offerBook.removeOffer(checkNotNull(offer), tradeManager);
|
offerBook.removeOffer(checkNotNull(offer), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
||||||
|
|
|
@ -28,6 +28,7 @@ import bisq.core.btc.wallet.Restrictions;
|
||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.locale.TradeCurrency;
|
import bisq.core.locale.TradeCurrency;
|
||||||
import bisq.core.offer.CreateOfferService;
|
import bisq.core.offer.CreateOfferService;
|
||||||
|
import bisq.core.offer.MutableOfferPayloadFields;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferPayload;
|
import bisq.core.offer.OfferPayload;
|
||||||
import bisq.core.offer.OfferUtil;
|
import bisq.core.offer.OfferUtil;
|
||||||
|
@ -182,54 +183,12 @@ class EditOfferDataModel extends MutableOfferDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPublishOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
public void onPublishOffer(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
// editedPayload is a merge of the original offerPayload and newOfferPayload
|
MutableOfferPayloadFields mutableOfferPayloadFields =
|
||||||
// fields which are editable are merged in from newOfferPayload (such as payment account details)
|
new MutableOfferPayloadFields(createAndGetOffer().getOfferPayload());
|
||||||
// fields which cannot change (most importantly BTC amount) are sourced from the original offerPayload
|
final OfferPayload editedPayload = offerUtil.getMergedOfferPayload(openOffer, mutableOfferPayloadFields);
|
||||||
final OfferPayload offerPayload = openOffer.getOffer().getOfferPayload();
|
|
||||||
final OfferPayload newOfferPayload = createAndGetOffer().getOfferPayload();
|
|
||||||
final OfferPayload editedPayload = new OfferPayload(offerPayload.getId(),
|
|
||||||
offerPayload.getDate(),
|
|
||||||
offerPayload.getOwnerNodeAddress(),
|
|
||||||
offerPayload.getPubKeyRing(),
|
|
||||||
offerPayload.getDirection(),
|
|
||||||
newOfferPayload.getPrice(),
|
|
||||||
newOfferPayload.getMarketPriceMargin(),
|
|
||||||
newOfferPayload.isUseMarketBasedPrice(),
|
|
||||||
offerPayload.getAmount(),
|
|
||||||
offerPayload.getMinAmount(),
|
|
||||||
newOfferPayload.getBaseCurrencyCode(),
|
|
||||||
newOfferPayload.getCounterCurrencyCode(),
|
|
||||||
offerPayload.getArbitratorNodeAddresses(),
|
|
||||||
offerPayload.getMediatorNodeAddresses(),
|
|
||||||
newOfferPayload.getPaymentMethodId(),
|
|
||||||
newOfferPayload.getMakerPaymentAccountId(),
|
|
||||||
offerPayload.getOfferFeePaymentTxId(),
|
|
||||||
newOfferPayload.getCountryCode(),
|
|
||||||
newOfferPayload.getAcceptedCountryCodes(),
|
|
||||||
newOfferPayload.getBankId(),
|
|
||||||
newOfferPayload.getAcceptedBankIds(),
|
|
||||||
offerPayload.getVersionNr(),
|
|
||||||
offerPayload.getBlockHeightAtOfferCreation(),
|
|
||||||
offerPayload.getTxFee(),
|
|
||||||
offerPayload.getMakerFee(),
|
|
||||||
offerPayload.isCurrencyForMakerFeeBtc(),
|
|
||||||
offerPayload.getBuyerSecurityDeposit(),
|
|
||||||
offerPayload.getSellerSecurityDeposit(),
|
|
||||||
offerPayload.getMaxTradeLimit(),
|
|
||||||
offerPayload.getMaxTradePeriod(),
|
|
||||||
offerPayload.isUseAutoClose(),
|
|
||||||
offerPayload.isUseReOpenAfterAutoClose(),
|
|
||||||
offerPayload.getLowerClosePrice(),
|
|
||||||
offerPayload.getUpperClosePrice(),
|
|
||||||
offerPayload.isPrivateOffer(),
|
|
||||||
offerPayload.getHashOfChallenge(),
|
|
||||||
newOfferPayload.getExtraDataMap(),
|
|
||||||
offerPayload.getProtocolVersion());
|
|
||||||
|
|
||||||
final Offer editedOffer = new Offer(editedPayload);
|
final Offer editedOffer = new Offer(editedPayload);
|
||||||
editedOffer.setPriceFeedService(priceFeedService);
|
editedOffer.setPriceFeedService(priceFeedService);
|
||||||
editedOffer.setState(Offer.State.AVAILABLE);
|
editedOffer.setState(Offer.State.AVAILABLE);
|
||||||
|
|
||||||
openOfferManager.editOpenOfferPublish(editedOffer, triggerPrice, initialState, () -> {
|
openOfferManager.editOpenOfferPublish(editedOffer, triggerPrice, initialState, () -> {
|
||||||
openOffer = null;
|
openOffer = null;
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
|
|
|
@ -9,5 +9,4 @@
|
||||||
<root level="TRACE">
|
<root level="TRACE">
|
||||||
<appender-ref ref="CONSOLE_APPENDER"/>
|
<appender-ref ref="CONSOLE_APPENDER"/>
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -72,6 +72,8 @@ service Offers {
|
||||||
}
|
}
|
||||||
rpc CreateOffer (CreateOfferRequest) returns (CreateOfferReply) {
|
rpc CreateOffer (CreateOfferRequest) returns (CreateOfferReply) {
|
||||||
}
|
}
|
||||||
|
rpc EditOffer (EditOfferRequest) returns (EditOfferReply) {
|
||||||
|
}
|
||||||
rpc CancelOffer (CancelOfferRequest) returns (CancelOfferReply) {
|
rpc CancelOffer (CancelOfferRequest) returns (CancelOfferReply) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,6 +130,35 @@ message CreateOfferReply {
|
||||||
OfferInfo offer = 1;
|
OfferInfo offer = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message EditOfferRequest {
|
||||||
|
string id = 1;
|
||||||
|
string price = 2;
|
||||||
|
bool useMarketBasedPrice = 3;
|
||||||
|
double marketPriceMargin = 4;
|
||||||
|
uint64 triggerPrice = 5;
|
||||||
|
// Send a signed int, not a bool (with default=false).
|
||||||
|
// -1 = do not change activation state
|
||||||
|
// 0 = disable
|
||||||
|
// 1 = enable
|
||||||
|
sint32 enable = 6;
|
||||||
|
// The EditType constricts what offer details can be modified and simplifies param validation.
|
||||||
|
enum EditType {
|
||||||
|
ACTIVATION_STATE_ONLY = 0;
|
||||||
|
FIXED_PRICE_ONLY = 1;
|
||||||
|
FIXED_PRICE_AND_ACTIVATION_STATE = 2;
|
||||||
|
MKT_PRICE_MARGIN_ONLY = 3;
|
||||||
|
MKT_PRICE_MARGIN_AND_ACTIVATION_STATE = 4;
|
||||||
|
TRIGGER_PRICE_ONLY = 5;
|
||||||
|
TRIGGER_PRICE_AND_ACTIVATION_STATE = 6;
|
||||||
|
MKT_PRICE_MARGIN_AND_TRIGGER_PRICE = 7;
|
||||||
|
MKT_PRICE_MARGIN_AND_TRIGGER_PRICE_AND_ACTIVATION_STATE = 8;
|
||||||
|
}
|
||||||
|
EditType editType = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EditOfferReply {
|
||||||
|
}
|
||||||
|
|
||||||
message CancelOfferRequest {
|
message CancelOfferRequest {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
}
|
}
|
||||||
|
@ -159,6 +190,9 @@ message OfferInfo {
|
||||||
string offerFeePaymentTxId = 21;
|
string offerFeePaymentTxId = 21;
|
||||||
uint64 txFee = 22;
|
uint64 txFee = 22;
|
||||||
uint64 makerFee = 23;
|
uint64 makerFee = 23;
|
||||||
|
bool isActivated = 24;
|
||||||
|
bool isMyOffer = 25;
|
||||||
|
bool isMyPendingOffer = 26;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AvailabilityResultWithDescription {
|
message AvailabilityResultWithDescription {
|
||||||
|
|
Loading…
Add table
Reference in a new issue