From 7772391e8cca3fbeaa894989211a79e82412b5b5 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 15:01:30 +0700 Subject: [PATCH 01/20] Remove unnecessary sellersPriceSpec field --- .../take_offer/review/TakeOfferReviewController.java | 7 ++----- .../bisq_easy/take_offer/review/TakeOfferReviewModel.java | 3 --- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java index 73065fe025..7306440d3e 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java @@ -125,14 +125,11 @@ public void init(BisqEasyOffer bisqEasyOffer) { .ifPresent(model::setTakersQuoteSideAmount); } - PriceSpec priceSpec = bisqEasyOffer.getPriceSpec(); - model.setSellersPriceSpec(priceSpec); - Optional priceQuote = PriceUtil.findQuote(marketPriceService, bisqEasyOffer); priceQuote.ifPresent(priceInput::setQuote); applyPriceQuote(priceQuote); - applyPriceDetails(priceSpec, market); + applyPriceDetails(bisqEasyOffer.getPriceSpec(), market); } public void setTakersBaseSideAmount(Monetary amount) { @@ -192,7 +189,7 @@ private void doTakeOffer(BisqEasyOffer bisqEasyOffer, UserIdentity takerIdentity Monetary takersQuoteSideAmount = model.getTakersQuoteSideAmount(); BitcoinPaymentMethodSpec bitcoinPaymentMethodSpec = model.getBitcoinPaymentMethodSpec(); FiatPaymentMethodSpec fiatPaymentMethodSpec = model.getFiatPaymentMethodSpec(); - PriceSpec sellersPriceSpec = model.getSellersPriceSpec(); + PriceSpec sellersPriceSpec = bisqEasyOffer.getPriceSpec(); long marketPrice = model.getMarketPrice(); BisqEasyProtocol bisqEasyProtocol = bisqEasyTradeService.createBisqEasyProtocol(takerIdentity.getIdentity(), bisqEasyOffer, diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewModel.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewModel.java index 49d03a3c86..117c8d940e 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewModel.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewModel.java @@ -22,7 +22,6 @@ import bisq.offer.bisq_easy.BisqEasyOffer; import bisq.offer.payment_method.BitcoinPaymentMethodSpec; import bisq.offer.payment_method.FiatPaymentMethodSpec; -import bisq.offer.price.spec.PriceSpec; import bisq.trade.bisq_easy.BisqEasyTrade; import bisq.user.profile.UserProfile; import javafx.beans.property.ObjectProperty; @@ -50,8 +49,6 @@ class TakeOfferReviewModel implements Model { private Monetary takersBaseSideAmount; @Setter private Monetary takersQuoteSideAmount; - @Setter - private PriceSpec sellersPriceSpec; private final ObjectProperty takeOfferStatus = new SimpleObjectProperty<>(TakeOfferStatus.NOT_STARTED); @Setter private String price; From c39fe6154094f507edef8165e0cdd4467480efff Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 15:10:20 +0700 Subject: [PATCH 02/20] Rename agreedPriceSpec to priceSpec. This was from earlier versions where only the seller was setting the price. --- .../trade_state/TradeStateController.java | 4 ++-- .../review/TakeOfferReviewController.java | 4 ++-- .../bisq/contract/bisq_easy/BisqEasyContract.java | 14 +++++++------- contract/src/main/proto/contract.proto | 2 +- .../bisq/trade/bisq_easy/BisqEasyTradeService.java | 4 ++-- .../messages/BisqEasyTakeOfferRequestHandler.java | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/open_trades/trade_state/TradeStateController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/open_trades/trade_state/TradeStateController.java index 64cd410da1..486b9c9da4 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/open_trades/trade_state/TradeStateController.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/open_trades/trade_state/TradeStateController.java @@ -169,7 +169,7 @@ public void onActivate() { PriceSpecFormatter.getFormattedPriceSpec(bisqEasyTrade.getOffer().getPriceSpec()))); model.getSellerPriceDescriptionApprovalOverlay().set( Res.get("bisqEasy.tradeState.acceptOrRejectSellersPrice.description.sellersPrice", - PriceSpecFormatter.getFormattedPriceSpec(bisqEasyTrade.getContract().getAgreedPriceSpec()))); + PriceSpecFormatter.getFormattedPriceSpec(bisqEasyTrade.getContract().getPriceSpec()))); }); } @@ -526,7 +526,7 @@ && requiresSellerPriceAcceptance() private boolean requiresSellerPriceAcceptance() { PriceSpec buyerPriceSpec = model.getBisqEasyTrade().get().getOffer().getPriceSpec(); - PriceSpec sellerPriceSpec = model.getBisqEasyTrade().get().getContract().getAgreedPriceSpec(); + PriceSpec sellerPriceSpec = model.getBisqEasyTrade().get().getContract().getPriceSpec(); boolean priceSpecChanged = !buyerPriceSpec.equals(sellerPriceSpec); Set validStatesToRejectPrice = Set.of( diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java index 7306440d3e..b8e5ee6669 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java @@ -189,7 +189,7 @@ private void doTakeOffer(BisqEasyOffer bisqEasyOffer, UserIdentity takerIdentity Monetary takersQuoteSideAmount = model.getTakersQuoteSideAmount(); BitcoinPaymentMethodSpec bitcoinPaymentMethodSpec = model.getBitcoinPaymentMethodSpec(); FiatPaymentMethodSpec fiatPaymentMethodSpec = model.getFiatPaymentMethodSpec(); - PriceSpec sellersPriceSpec = bisqEasyOffer.getPriceSpec(); + PriceSpec priceSpec = bisqEasyOffer.getPriceSpec(); long marketPrice = model.getMarketPrice(); BisqEasyProtocol bisqEasyProtocol = bisqEasyTradeService.createBisqEasyProtocol(takerIdentity.getIdentity(), bisqEasyOffer, @@ -198,7 +198,7 @@ private void doTakeOffer(BisqEasyOffer bisqEasyOffer, UserIdentity takerIdentity bitcoinPaymentMethodSpec, fiatPaymentMethodSpec, mediator, - sellersPriceSpec, + priceSpec, marketPrice); BisqEasyTrade bisqEasyTrade = bisqEasyProtocol.getModel(); log.info("Selected mediator for trade {}: {}", bisqEasyTrade.getShortId(), mediator.map(UserProfile::getUserName).orElse("N/A")); diff --git a/contract/src/main/java/bisq/contract/bisq_easy/BisqEasyContract.java b/contract/src/main/java/bisq/contract/bisq_easy/BisqEasyContract.java index 63149d25e9..82b8add9c0 100644 --- a/contract/src/main/java/bisq/contract/bisq_easy/BisqEasyContract.java +++ b/contract/src/main/java/bisq/contract/bisq_easy/BisqEasyContract.java @@ -43,7 +43,7 @@ public final class BisqEasyContract extends TwoPartyContract { private final BitcoinPaymentMethodSpec baseSidePaymentMethodSpec; private final FiatPaymentMethodSpec quoteSidePaymentMethodSpec; private final Optional mediator; - private final PriceSpec agreedPriceSpec; + private final PriceSpec priceSpec; private final long marketPrice; private final long takeOfferDate; @@ -55,7 +55,7 @@ public BisqEasyContract(long takeOfferDate, BitcoinPaymentMethodSpec baseSidePaymentMethodSpec, FiatPaymentMethodSpec quoteSidePaymentMethodSpec, Optional mediator, - PriceSpec agreedPriceSpec, + PriceSpec priceSpec, long marketPrice) { this(takeOfferDate, offer, @@ -66,7 +66,7 @@ public BisqEasyContract(long takeOfferDate, baseSidePaymentMethodSpec, quoteSidePaymentMethodSpec, mediator, - agreedPriceSpec, + priceSpec, marketPrice); } @@ -79,7 +79,7 @@ private BisqEasyContract(long takeOfferDate, BitcoinPaymentMethodSpec baseSidePaymentMethodSpec, FiatPaymentMethodSpec quoteSidePaymentMethodSpec, Optional mediator, - PriceSpec agreedPriceSpec, + PriceSpec priceSpec, long marketPrice) { super(takeOfferDate, offer, protocolType, taker); this.baseSideAmount = baseSideAmount; @@ -87,7 +87,7 @@ private BisqEasyContract(long takeOfferDate, this.baseSidePaymentMethodSpec = baseSidePaymentMethodSpec; this.quoteSidePaymentMethodSpec = quoteSidePaymentMethodSpec; this.mediator = mediator; - this.agreedPriceSpec = agreedPriceSpec; + this.priceSpec = priceSpec; this.marketPrice = marketPrice; this.takeOfferDate = takeOfferDate; @@ -120,7 +120,7 @@ private bisq.contract.protobuf.BisqEasyContract.Builder getBisqEasyContractBuild .setQuoteSideAmount(quoteSideAmount) .setBaseSidePaymentMethodSpec(baseSidePaymentMethodSpec.toProto(serializeForHash)) .setQuoteSidePaymentMethodSpec(quoteSidePaymentMethodSpec.toProto(serializeForHash)) - .setAgreedPriceSpec(agreedPriceSpec.toProto(serializeForHash)) + .setPriceSpec(priceSpec.toProto(serializeForHash)) .setMarketPrice(marketPrice); mediator.ifPresent(mediator -> builder.setMediator(mediator.toProto(serializeForHash))); return builder; @@ -145,7 +145,7 @@ public static BisqEasyContract fromProto(bisq.contract.protobuf.Contract proto) bisqEasyContractProto.hasMediator() ? Optional.of(UserProfile.fromProto(bisqEasyContractProto.getMediator())) : Optional.empty(), - PriceSpec.fromProto(bisqEasyContractProto.getAgreedPriceSpec()), + PriceSpec.fromProto(bisqEasyContractProto.getPriceSpec()), bisqEasyContractProto.getMarketPrice()); } } diff --git a/contract/src/main/proto/contract.proto b/contract/src/main/proto/contract.proto index 46f148c490..8ee495483c 100644 --- a/contract/src/main/proto/contract.proto +++ b/contract/src/main/proto/contract.proto @@ -83,7 +83,7 @@ message BisqEasyContract { offer.PaymentMethodSpec baseSidePaymentMethodSpec = 3; offer.PaymentMethodSpec quoteSidePaymentMethodSpec = 4; optional user.UserProfile mediator = 12; - offer.PriceSpec agreedPriceSpec = 13; + offer.PriceSpec priceSpec = 13; uint64 marketPrice = 14; } diff --git a/trade/src/main/java/bisq/trade/bisq_easy/BisqEasyTradeService.java b/trade/src/main/java/bisq/trade/bisq_easy/BisqEasyTradeService.java index 56222c0c92..ba66e568d8 100644 --- a/trade/src/main/java/bisq/trade/bisq_easy/BisqEasyTradeService.java +++ b/trade/src/main/java/bisq/trade/bisq_easy/BisqEasyTradeService.java @@ -229,7 +229,7 @@ public BisqEasyProtocol createBisqEasyProtocol(Identity takerIdentity, BitcoinPaymentMethodSpec bitcoinPaymentMethodSpec, FiatPaymentMethodSpec fiatPaymentMethodSpec, Optional mediator, - PriceSpec agreedPriceSpec, + PriceSpec priceSpec, long marketPrice) { verifyTradingNotOnHalt(); verifyMinVersionForTrading(); @@ -244,7 +244,7 @@ public BisqEasyProtocol createBisqEasyProtocol(Identity takerIdentity, bitcoinPaymentMethodSpec, fiatPaymentMethodSpec, mediator, - agreedPriceSpec, + priceSpec, marketPrice); boolean isBuyer = bisqEasyOffer.getTakersDirection().isBuy(); NetworkId makerNetworkId = contract.getMaker().getNetworkId(); diff --git a/trade/src/main/java/bisq/trade/bisq_easy/protocol/messages/BisqEasyTakeOfferRequestHandler.java b/trade/src/main/java/bisq/trade/bisq_easy/protocol/messages/BisqEasyTakeOfferRequestHandler.java index 26f4bb8514..5a3d45293e 100644 --- a/trade/src/main/java/bisq/trade/bisq_easy/protocol/messages/BisqEasyTakeOfferRequestHandler.java +++ b/trade/src/main/java/bisq/trade/bisq_easy/protocol/messages/BisqEasyTakeOfferRequestHandler.java @@ -150,7 +150,7 @@ private void validateAmount(BisqEasyOffer takersOffer, BisqEasyContract takersCo Market market = takersOffer.getMarket(); MarketPrice marketPrice = marketPriceService.getMarketPriceByCurrencyMap().get(market); Optional priceQuote = PriceUtil.findQuote(marketPriceService, - takersContract.getAgreedPriceSpec(), market); + takersContract.getPriceSpec(), market); Optional amount = priceQuote.map(quote -> quote.toBaseSideMonetary(Monetary.from(takersContract.getQuoteSideAmount(), market.getQuoteCurrencyCode()))); From bfb627c611033bb0b2bc24d10721733a5273614a Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 15:10:48 +0700 Subject: [PATCH 03/20] Do not include installDist and generateInstallers in buildAll task --- build.gradle.kts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5c309ceec6..4849909c00 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,17 +10,18 @@ tasks.register("buildAll") { doLast { listOf( "build", - ":wallets:build", - ":apps:seed-node-app:build", - ":apps:seed-node-app:installDist", + //":apps:seed-node-app:installDist", + ":apps:desktop:desktop:build", ":apps:desktop:desktop-app:build", - ":apps:desktop:desktop-app:installDist", - ":apps:desktop:desktop-app-launcher:generateInstallers", + ":apps:desktop:desktop-app-launcher:build", + //":apps:desktop:desktop-app:installDist", + // ":apps:desktop:desktop-app-launcher:generateInstallers", ":apps:desktop:webcam-app:build", - ":apps:oracle-node-app:build", ":apps:http-api-app:build", ":apps:node-monitor-web-app:build", -// ":REPLACEME:build", + ":apps:oracle-node-app:build", + ":apps:seed-node-app:build", + ":wallets:build", ).forEach { exec { println("Executing Build: $it") From b486db89364ccc58c6ccec4c22e4480f025a363a Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 15:14:07 +0700 Subject: [PATCH 04/20] Wrap in ArrayList to ensure sorting is supported in case we get an immutable list --- offer/src/main/java/bisq/offer/bisq_easy/BisqEasyOffer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/offer/src/main/java/bisq/offer/bisq_easy/BisqEasyOffer.java b/offer/src/main/java/bisq/offer/bisq_easy/BisqEasyOffer.java index a77a1d568b..378ea66a68 100644 --- a/offer/src/main/java/bisq/offer/bisq_easy/BisqEasyOffer.java +++ b/offer/src/main/java/bisq/offer/bisq_easy/BisqEasyOffer.java @@ -101,7 +101,9 @@ private BisqEasyOffer(String id, baseSidePaymentMethodSpecs, quoteSidePaymentMethodSpecs, offerOptions); - this.supportedLanguageCodes = supportedLanguageCodes; + + // We might get an immutable list, but we need to sort it, so wrap it into an ArrayList + this.supportedLanguageCodes = new ArrayList<>(supportedLanguageCodes); Collections.sort(this.supportedLanguageCodes); verify(); From 01906926ad183c2dfb68ce620e8ebda33d1692ae Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 15:14:34 +0700 Subject: [PATCH 05/20] Add methods --- .../payment_method/PaymentMethodSpecUtil.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/offer/src/main/java/bisq/offer/payment_method/PaymentMethodSpecUtil.java b/offer/src/main/java/bisq/offer/payment_method/PaymentMethodSpecUtil.java index bf83b16a04..0398e287b5 100644 --- a/offer/src/main/java/bisq/offer/payment_method/PaymentMethodSpecUtil.java +++ b/offer/src/main/java/bisq/offer/payment_method/PaymentMethodSpecUtil.java @@ -17,16 +17,22 @@ package bisq.offer.payment_method; -import bisq.account.payment_method.BitcoinPaymentMethod; -import bisq.account.payment_method.BitcoinPaymentRail; -import bisq.account.payment_method.FiatPaymentMethod; -import bisq.account.payment_method.PaymentMethod; +import bisq.account.payment_method.*; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; public class PaymentMethodSpecUtil { + public static BitcoinPaymentMethod getBitcoinPaymentMethod(String paymentMethod) { + return BitcoinPaymentMethodUtil.getPaymentMethod(paymentMethod); + } + + public static FiatPaymentMethod getFiatPaymentMethod(String paymentMethod) { + return FiatPaymentMethodUtil.getPaymentMethod(paymentMethod); + } + + public static List createBitcoinPaymentMethodSpecs(List paymentMethods) { return paymentMethods.stream() .map(BitcoinPaymentMethodSpec::new) From 00a9b99781bfaad2b64f7c75530d6aa040a2fc4f Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 15:15:37 +0700 Subject: [PATCH 06/20] Remove param from "bisqEasy.tradeWizard.review.priceDetails" i18n string as its not expecting one --- .../trade_wizard/review/TradeWizardReviewController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java index 548e872e44..fc1273c093 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java @@ -616,7 +616,7 @@ private void applyPriceDetails(PriceSpec priceSpec, Market market) { percentFromMarketPrice = PriceUtil.findPercentFromMarketPrice(marketPriceService, priceSpec, market); double percent = percentFromMarketPrice.orElse(0d); if ((priceSpec instanceof FloatPriceSpec || priceSpec instanceof MarketPriceSpec) && percent == 0) { - model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails", marketPriceAsString)); + model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails")); } else { String aboveOrBelow = percent > 0 ? Res.get("offer.price.above") : Res.get("offer.price.below"); String percentAsString = percentFromMarketPrice.map(Math::abs).map(PercentageFormatter::formatToPercentWithSymbol) From f87674a162eaebe7f9386af2efe02d05a47d6a27 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 15:18:25 +0700 Subject: [PATCH 07/20] Cleanup --- .../bisq_easy/take_offer/review/TakeOfferReviewController.java | 3 +-- .../trade_wizard/review/TradeWizardReviewController.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java index b8e5ee6669..02bad935d0 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/take_offer/review/TakeOfferReviewController.java @@ -332,8 +332,7 @@ private void applyPriceDetails(PriceSpec priceSpec, Market market) { marketPrice.ifPresent(price -> model.setMarketPrice(price.getPriceQuote().getValue())); Optional marketPriceQuote = marketPrice.map(MarketPrice::getPriceQuote); String marketPriceAsString = marketPriceQuote.map(PriceFormatter::formatWithCode).orElse(Res.get("data.na")); - Optional percentFromMarketPrice; - percentFromMarketPrice = PriceUtil.findPercentFromMarketPrice(marketPriceService, priceSpec, market); + Optional percentFromMarketPrice = PriceUtil.findPercentFromMarketPrice(marketPriceService, priceSpec, market); double percent = percentFromMarketPrice.orElse(0d); if ((priceSpec instanceof FloatPriceSpec || priceSpec instanceof MarketPriceSpec) && percent == 0) { model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails", marketPriceAsString)); diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java index fc1273c093..4fe44c38a6 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/trade_wizard/review/TradeWizardReviewController.java @@ -612,8 +612,7 @@ private void applyPriceDetails(PriceSpec priceSpec, Market market) { marketPrice.ifPresent(price -> model.setMarketPrice(price.getPriceQuote().getValue())); Optional marketPriceQuote = marketPriceService.findMarketPrice(market).map(MarketPrice::getPriceQuote); String marketPriceAsString = marketPriceQuote.map(PriceFormatter::formatWithCode).orElse(Res.get("data.na")); - Optional percentFromMarketPrice; - percentFromMarketPrice = PriceUtil.findPercentFromMarketPrice(marketPriceService, priceSpec, market); + Optional percentFromMarketPrice = PriceUtil.findPercentFromMarketPrice(marketPriceService, priceSpec, market); double percent = percentFromMarketPrice.orElse(0d); if ((priceSpec instanceof FloatPriceSpec || priceSpec instanceof MarketPriceSpec) && percent == 0) { model.setPriceDetails(Res.get("bisqEasy.tradeWizard.review.priceDetails")); From 84638a9859832b1f55f73155fb3b9d99c84c9850 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 16:03:32 +0700 Subject: [PATCH 08/20] Move toDouble to Monetary. Use asDouble in round methods --- common/src/main/java/bisq/common/monetary/Coin.java | 7 +------ common/src/main/java/bisq/common/monetary/Fiat.java | 7 +------ common/src/main/java/bisq/common/monetary/Monetary.java | 5 +++-- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/common/src/main/java/bisq/common/monetary/Coin.java b/common/src/main/java/bisq/common/monetary/Coin.java index 3cd8ab4eec..908ee9868c 100644 --- a/common/src/main/java/bisq/common/monetary/Coin.java +++ b/common/src/main/java/bisq/common/monetary/Coin.java @@ -191,11 +191,6 @@ public Coin divide(long divisor) { return new Coin(this.value / divisor, this.code, this.precision); } - @Override - public double toDouble(long value) { - return MathUtils.roundDouble(BigDecimal.valueOf(value).movePointLeft(precision).doubleValue(), precision); - } - private static int derivePrecision(String code) { if (code.equals("XMR")) return 12; if (code.equals("BSQ")) return 2; @@ -204,7 +199,7 @@ private static int derivePrecision(String code) { public Coin round(int roundPrecision) { //todo (low prio) add tests - double rounded = MathUtils.roundDouble(toDouble(value), roundPrecision); + double rounded = MathUtils.roundDouble(asDouble(), roundPrecision); long shifted = BigDecimal.valueOf(rounded).movePointRight(precision).longValue(); return Coin.fromValue(shifted, code, precision); } diff --git a/common/src/main/java/bisq/common/monetary/Fiat.java b/common/src/main/java/bisq/common/monetary/Fiat.java index e2d24dfad4..4cf96c5f1d 100644 --- a/common/src/main/java/bisq/common/monetary/Fiat.java +++ b/common/src/main/java/bisq/common/monetary/Fiat.java @@ -127,13 +127,8 @@ public Fiat divide(long divisor) { return new Fiat(this.value / divisor, this.code, this.precision); } - @Override - public double toDouble(long value) { - return MathUtils.roundDouble(BigDecimal.valueOf(value).movePointLeft(precision).doubleValue(), precision); - } - public Fiat round(int roundPrecision) { - double rounded = MathUtils.roundDouble(toDouble(value), roundPrecision); + double rounded = MathUtils.roundDouble(asDouble(), roundPrecision); long shifted = BigDecimal.valueOf(rounded).movePointRight(precision).longValue(); return Fiat.fromValue(shifted, code, precision); } diff --git a/common/src/main/java/bisq/common/monetary/Monetary.java b/common/src/main/java/bisq/common/monetary/Monetary.java index 279daebcf8..7fcc8e8de3 100644 --- a/common/src/main/java/bisq/common/monetary/Monetary.java +++ b/common/src/main/java/bisq/common/monetary/Monetary.java @@ -115,8 +115,9 @@ public static Monetary fromProto(bisq.common.protobuf.Monetary proto) { }; } - public abstract double toDouble(long value); - + public double toDouble(long value) { + return MathUtils.roundDouble(BigDecimal.valueOf(value).movePointLeft(precision).doubleValue(), precision); + } public double asDouble() { return toDouble(value); } From b932c52d85a93e414d30beac704a2f9f7e05c6ac Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 16:04:40 +0700 Subject: [PATCH 09/20] Add support for useLowPrecision --- .../offer/amount/OfferAmountFormatter.java | 71 ++++++++++++------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/offer/src/main/java/bisq/offer/amount/OfferAmountFormatter.java b/offer/src/main/java/bisq/offer/amount/OfferAmountFormatter.java index 1877db2bbf..bfc2413d9c 100644 --- a/offer/src/main/java/bisq/offer/amount/OfferAmountFormatter.java +++ b/offer/src/main/java/bisq/offer/amount/OfferAmountFormatter.java @@ -28,7 +28,7 @@ import bisq.presentation.formatters.AmountFormatter; import lombok.extern.slf4j.Slf4j; -import java.util.function.Function; +import java.util.function.BiFunction; @Slf4j public class OfferAmountFormatter { @@ -39,11 +39,11 @@ public class OfferAmountFormatter { // Either min-max or fixed public static String formatBaseAmount(MarketPriceService marketPriceService, Offer offer) { - return formatBaseAmount(marketPriceService, offer.getAmountSpec(), offer.getPriceSpec(), offer.getMarket(), offer.hasAmountRange(), true); + return formatBaseAmount(marketPriceService, offer.getAmountSpec(), offer.getPriceSpec(), offer.getMarket(), offer.hasAmountRange(), true, true); } public static String formatBaseAmount(MarketPriceService marketPriceService, Offer offer, boolean withCode) { - return formatBaseAmount(marketPriceService, offer.getAmountSpec(), offer.getPriceSpec(), offer.getMarket(), offer.hasAmountRange(), withCode); + return formatBaseAmount(marketPriceService, offer.getAmountSpec(), offer.getPriceSpec(), offer.getMarket(), offer.hasAmountRange(), withCode, true); } public static String formatBaseAmount(MarketPriceService marketPriceService, @@ -51,11 +51,12 @@ public static String formatBaseAmount(MarketPriceService marketPriceService, PriceSpec priceSpec, Market market, boolean hasAmountRange, - boolean withCode) { + boolean withCode, + boolean useLowPrecision) { if (hasAmountRange) { - return formatBaseSideRangeAmount(marketPriceService, amountSpec, priceSpec, market, withCode); + return formatBaseSideRangeAmount(marketPriceService, amountSpec, priceSpec, market, withCode, useLowPrecision); } else { - return formatBaseSideFixedAmount(marketPriceService, amountSpec, priceSpec, market, withCode); + return formatBaseSideFixedAmount(marketPriceService, amountSpec, priceSpec, market, withCode, useLowPrecision); } } @@ -67,15 +68,18 @@ public static String formatBaseSideFixedAmount(MarketPriceService marketPriceSer public static String formatBaseSideFixedAmount(MarketPriceService marketPriceService, Offer offer, boolean withCode) { - return formatBaseSideFixedAmount(marketPriceService, offer.getAmountSpec(), offer.getPriceSpec(), offer.getMarket(), withCode); + return formatBaseSideFixedAmount(marketPriceService, offer.getAmountSpec(), offer.getPriceSpec(), offer.getMarket(), withCode, true); } public static String formatBaseSideFixedAmount(MarketPriceService marketPriceService, AmountSpec amountSpec, PriceSpec priceSpec, Market market, - boolean withCode) { - return OfferAmountUtil.findBaseSideFixedAmount(marketPriceService, amountSpec, priceSpec, market).map(getFormatFunction(withCode)).orElse(Res.get("data.na")); + boolean withCode, + boolean useLowPrecision) { + return OfferAmountUtil.findBaseSideFixedAmount(marketPriceService, amountSpec, priceSpec, market) + .map(monetary -> getFormatFunction(withCode).apply(monetary, useLowPrecision)) + .orElse(Res.get("data.na")); } // Min @@ -94,7 +98,9 @@ public static String formatBaseSideMinAmount(MarketPriceService marketPriceServi PriceSpec priceSpec, Market market, boolean withCode) { - return OfferAmountUtil.findBaseSideMinAmount(marketPriceService, amountSpec, priceSpec, market).map(getFormatFunction(withCode)).orElse(Res.get("data.na")); + return OfferAmountUtil.findBaseSideMinAmount(marketPriceService, amountSpec, priceSpec, market) + .map(monetary -> getFormatFunction(withCode).apply(monetary, true)) + .orElse(Res.get("data.na")); } // Max @@ -113,36 +119,43 @@ public static String formatBaseSideMaxAmount(MarketPriceService marketPriceServi PriceSpec priceSpec, Market market, boolean withCode) { - return OfferAmountUtil.findBaseSideMaxAmount(marketPriceService, amountSpec, priceSpec, market).map(getFormatFunction(withCode)).orElse(Res.get("data.na")); + return OfferAmountUtil.findBaseSideMaxAmount(marketPriceService, amountSpec, priceSpec, market) + .map(monetary -> getFormatFunction(withCode).apply(monetary, true)) + .orElse(Res.get("data.na")); } // Max or fixed public static String formatBaseSideMaxOrFixedAmount(MarketPriceService marketPriceService, Offer offer, - boolean withCode) { - return formatBaseSideMaxOrFixedAmount(marketPriceService, offer.getAmountSpec(), offer.getPriceSpec(), offer.getMarket(), withCode); + boolean withCode, + boolean useLowPrecision) { + return formatBaseSideMaxOrFixedAmount(marketPriceService, offer.getAmountSpec(), offer.getPriceSpec(), offer.getMarket(), withCode, useLowPrecision); } public static String formatBaseSideMaxOrFixedAmount(MarketPriceService marketPriceService, AmountSpec amountSpec, PriceSpec priceSpec, Market market, - boolean withCode) { - return OfferAmountUtil.findBaseSideMaxOrFixedAmount(marketPriceService, amountSpec, priceSpec, market).map(getFormatFunction(withCode)).orElse(Res.get("data.na")); + boolean withCode, + boolean useLowPrecision) { + return OfferAmountUtil.findBaseSideMaxOrFixedAmount(marketPriceService, amountSpec, priceSpec, market) + .map(monetary -> getFormatFunction(withCode).apply(monetary, useLowPrecision)) + .orElse(Res.get("data.na")); } // Range (Min - Max) public static String formatBaseSideRangeAmount(MarketPriceService marketPriceService, Offer offer, boolean withCode) { - return formatBaseSideRangeAmount(marketPriceService, offer.getAmountSpec(), offer.getPriceSpec(), offer.getMarket(), withCode); + return formatBaseSideRangeAmount(marketPriceService, offer.getAmountSpec(), offer.getPriceSpec(), offer.getMarket(), withCode, true); } public static String formatBaseSideRangeAmount(MarketPriceService marketPriceService, AmountSpec amountSpec, PriceSpec priceSpec, Market market, - boolean withCode) { + boolean withCode, + boolean useLowPrecision) { return formatBaseSideMinAmount(marketPriceService, amountSpec, priceSpec, market, false) + " - " + formatBaseSideMaxAmount(marketPriceService, amountSpec, priceSpec, market, withCode); } @@ -198,7 +211,9 @@ public static String formatQuoteSideFixedAmount(MarketPriceService marketPriceSe PriceSpec priceSpec, Market market, boolean withCode) { - return OfferAmountUtil.findQuoteSideFixedAmount(marketPriceService, amountSpec, priceSpec, market).map(getFormatFunction(withCode)).orElse(Res.get("data.na")); + return OfferAmountUtil.findQuoteSideFixedAmount(marketPriceService, amountSpec, priceSpec, market) + .map(monetary -> getFormatFunction(withCode).apply(monetary, true)) + .orElse(Res.get("data.na")); } // Min @@ -217,7 +232,9 @@ public static String formatQuoteSideMinAmount(MarketPriceService marketPriceServ PriceSpec priceSpec, Market market, boolean withCode) { - return OfferAmountUtil.findQuoteSideMinAmount(marketPriceService, amountSpec, priceSpec, market).map(getFormatFunction(withCode)).orElse(Res.get("data.na")); + return OfferAmountUtil.findQuoteSideMinAmount(marketPriceService, amountSpec, priceSpec, market) + .map(monetary -> getFormatFunction(withCode).apply(monetary, true)) + .orElse(Res.get("data.na")); } // Min or fixed @@ -236,7 +253,9 @@ public static String formatQuoteSideMinOrFixedAmount(MarketPriceService marketPr PriceSpec priceSpec, Market market, boolean withCode) { - return OfferAmountUtil.findQuoteSideMinOrFixedAmount(marketPriceService, amountSpec, priceSpec, market).map(getFormatFunction(withCode)).orElse(Res.get("data.na")); + return OfferAmountUtil.findQuoteSideMinOrFixedAmount(marketPriceService, amountSpec, priceSpec, market) + .map(monetary -> getFormatFunction(withCode).apply(monetary, true)) + .orElse(Res.get("data.na")); } // Max @@ -255,7 +274,9 @@ public static String formatQuoteSideMaxAmount(MarketPriceService marketPriceServ PriceSpec priceSpec, Market market, boolean withCode) { - return OfferAmountUtil.findQuoteSideMaxAmount(marketPriceService, amountSpec, priceSpec, market).map(getFormatFunction(withCode)).orElse(Res.get("data.na")); + return OfferAmountUtil.findQuoteSideMaxAmount(marketPriceService, amountSpec, priceSpec, market) + .map(monetary -> getFormatFunction(withCode).apply(monetary, true)) + .orElse(Res.get("data.na")); } // Max or fixed @@ -270,7 +291,7 @@ public static String formatQuoteSideMaxOrFixedAmount(MarketPriceService marketPr PriceSpec priceSpec, Market market, boolean withCode) { - return OfferAmountUtil.findQuoteSideMaxOrFixedAmount(marketPriceService, amountSpec, priceSpec, market).map(getFormatFunction(withCode)).orElse(Res.get("data.na")); + return OfferAmountUtil.findQuoteSideMaxOrFixedAmount(marketPriceService, amountSpec, priceSpec, market).map(monetary -> getFormatFunction(withCode).apply(monetary, true)).orElse(Res.get("data.na")); } // Range (Min - Max) @@ -294,7 +315,9 @@ public static String formatQuoteSideRangeAmount(MarketPriceService marketPriceSe // Private /////////////////////////////////////////////////////////////////////////////////////////////////// - private static Function getFormatFunction(boolean withCode) { - return withCode ? AmountFormatter::formatAmountWithCode : AmountFormatter::formatAmount; + private static BiFunction getFormatFunction(boolean withCode) { + BiFunction formatAmount = AmountFormatter::formatAmount; + BiFunction formatAmountWithCode = AmountFormatter::formatAmountWithCode; + return withCode ? formatAmountWithCode : formatAmount; } } From f93ca207f8e859224e721d08e6754602cbec1896 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 16:09:07 +0700 Subject: [PATCH 10/20] Cleanup --- build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4849909c00..0e8c4cae07 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -70,7 +70,6 @@ tasks.register("publishAll") { listOf( ":account:publishToMavenLocal", ":application:publishToMavenLocal", -// ":bisq-easy:publishToMavenLocal", ":bonded-roles:publishToMavenLocal", ":chat:publishToMavenLocal", ":common:publishToMavenLocal", From 4f54c7281eeb3b90aef1bb5f06c83a86e76ba428 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 16:01:55 +0700 Subject: [PATCH 11/20] Add dto package --- .../main/java/bisq/common/monetary/Coin.java | 2 +- .../main/java/bisq/common/monetary/Fiat.java | 2 +- .../java/bisq/common/monetary/PriceQuote.java | 2 +- http-api/build.gradle.kts | 1 + .../src/main/java/bisq/dto/DtoMappings.java | 683 ++++++++++++++++++ .../bisq/dto/OfferListItemDtoFactory.java | 123 ++++ .../protocol_type/TradeProtocolTypeDto.java | 30 + .../bisq/dto/common/currency/MarketDto.java | 24 + .../bisq/dto/common/monetary/CoinDto.java | 38 + .../bisq/dto/common/monetary/FiatDto.java | 38 + .../bisq/dto/common/monetary/MonetaryDto.java | 50 ++ .../dto/common/monetary/PriceQuoteDto.java | 23 + .../network/AddressByTransportTypeMapDto.java | 23 + .../bisq/dto/common/network/AddressDto.java | 21 + .../dto/common/network/TransportTypeDto.java | 24 + .../dto/network/identity/NetworkIdDto.java | 25 + .../java/bisq/dto/offer/DirectionDto.java | 23 + .../dto/offer/amount/spec/AmountSpecDto.java | 42 ++ .../spec/BaseSideFixedAmountSpecDto.java | 34 + .../spec/BaseSideRangeAmountSpecDto.java | 35 + .../offer/amount/spec/FixedAmountSpecDto.java | 32 + .../spec/QuoteSideFixedAmountSpecDto.java | 34 + .../spec/QuoteSideRangeAmountSpecDto.java | 35 + .../offer/amount/spec/RangeAmountSpecDto.java | 34 + .../dto/offer/bisq_easy/BisqEasyOfferDto.java | 44 ++ .../dto/offer/bisq_easy/OfferListItemDto.java | 73 ++ .../dto/offer/options/OfferOptionDto.java | 40 + .../offer/options/ReputationOptionDto.java | 37 + .../offer/options/TradeTermsOptionDto.java | 36 + .../BitcoinPaymentMethodSpecDto.java | 39 + .../FiatPaymentMethodSpecDto.java | 38 + .../payment_method/PaymentMethodSpecDto.java | 55 ++ .../dto/offer/price/spec/FixPriceSpecDto.java | 37 + .../offer/price/spec/FloatPriceSpecDto.java | 36 + .../offer/price/spec/MarketPriceSpecDto.java | 31 + .../dto/offer/price/spec/PriceSpecDto.java | 41 ++ .../bisq/dto/security/keys/KeyPairDto.java | 21 + .../bisq/dto/security/keys/PrivateKeyDto.java | 22 + .../bisq/dto/security/keys/PubKeyDto.java | 21 + .../bisq/dto/security/keys/PublicKeyDto.java | 22 + .../bisq/dto/security/pow/ProofOfWorkDto.java | 29 + .../bisq/dto/user/profile/UserProfileDto.java | 34 + .../user/reputation/ReputationScoreDto.java | 21 + .../bisq/offer/bisq_easy/BisqEasyOffer.java | 2 +- .../java/bisq/user/profile/UserProfile.java | 2 +- 45 files changed, 2054 insertions(+), 5 deletions(-) create mode 100644 http-api/src/main/java/bisq/dto/DtoMappings.java create mode 100644 http-api/src/main/java/bisq/dto/OfferListItemDtoFactory.java create mode 100644 http-api/src/main/java/bisq/dto/account/protocol_type/TradeProtocolTypeDto.java create mode 100644 http-api/src/main/java/bisq/dto/common/currency/MarketDto.java create mode 100644 http-api/src/main/java/bisq/dto/common/monetary/CoinDto.java create mode 100644 http-api/src/main/java/bisq/dto/common/monetary/FiatDto.java create mode 100644 http-api/src/main/java/bisq/dto/common/monetary/MonetaryDto.java create mode 100644 http-api/src/main/java/bisq/dto/common/monetary/PriceQuoteDto.java create mode 100644 http-api/src/main/java/bisq/dto/common/network/AddressByTransportTypeMapDto.java create mode 100644 http-api/src/main/java/bisq/dto/common/network/AddressDto.java create mode 100644 http-api/src/main/java/bisq/dto/common/network/TransportTypeDto.java create mode 100644 http-api/src/main/java/bisq/dto/network/identity/NetworkIdDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/DirectionDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/amount/spec/AmountSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/amount/spec/BaseSideFixedAmountSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/amount/spec/BaseSideRangeAmountSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/amount/spec/FixedAmountSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/amount/spec/QuoteSideFixedAmountSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/amount/spec/QuoteSideRangeAmountSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/amount/spec/RangeAmountSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/bisq_easy/BisqEasyOfferDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/bisq_easy/OfferListItemDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/options/OfferOptionDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/options/ReputationOptionDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/options/TradeTermsOptionDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/payment_method/BitcoinPaymentMethodSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/payment_method/FiatPaymentMethodSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/payment_method/PaymentMethodSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/price/spec/FixPriceSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/price/spec/FloatPriceSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/price/spec/MarketPriceSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/offer/price/spec/PriceSpecDto.java create mode 100644 http-api/src/main/java/bisq/dto/security/keys/KeyPairDto.java create mode 100644 http-api/src/main/java/bisq/dto/security/keys/PrivateKeyDto.java create mode 100644 http-api/src/main/java/bisq/dto/security/keys/PubKeyDto.java create mode 100644 http-api/src/main/java/bisq/dto/security/keys/PublicKeyDto.java create mode 100644 http-api/src/main/java/bisq/dto/security/pow/ProofOfWorkDto.java create mode 100644 http-api/src/main/java/bisq/dto/user/profile/UserProfileDto.java create mode 100644 http-api/src/main/java/bisq/dto/user/reputation/ReputationScoreDto.java diff --git a/common/src/main/java/bisq/common/monetary/Coin.java b/common/src/main/java/bisq/common/monetary/Coin.java index 908ee9868c..7cd94b91bb 100644 --- a/common/src/main/java/bisq/common/monetary/Coin.java +++ b/common/src/main/java/bisq/common/monetary/Coin.java @@ -151,7 +151,7 @@ private Coin(double faceValue, String code, int precision) { super(code + " [crypto]", faceValue, code, precision, code.equals("BSQ") ? 2 : 4); } - private Coin(String id, long value, String code, int precision, int lowPrecision) { + public Coin(String id, long value, String code, int precision, int lowPrecision) { super(id, value, code, precision, lowPrecision); } diff --git a/common/src/main/java/bisq/common/monetary/Fiat.java b/common/src/main/java/bisq/common/monetary/Fiat.java index 4cf96c5f1d..daefad2339 100644 --- a/common/src/main/java/bisq/common/monetary/Fiat.java +++ b/common/src/main/java/bisq/common/monetary/Fiat.java @@ -86,7 +86,7 @@ private Fiat(double faceValue, String code, int precision) { super(code, faceValue, code, precision, 2); } - private Fiat(String id, long value, String code, int precision, int lowPrecision) { + public Fiat(String id, long value, String code, int precision, int lowPrecision) { super(id, value, code, precision, lowPrecision); } diff --git a/common/src/main/java/bisq/common/monetary/PriceQuote.java b/common/src/main/java/bisq/common/monetary/PriceQuote.java index cbf9fc4623..b2e4b94d34 100644 --- a/common/src/main/java/bisq/common/monetary/PriceQuote.java +++ b/common/src/main/java/bisq/common/monetary/PriceQuote.java @@ -56,7 +56,7 @@ public final class PriceQuote implements Comparable, PersistableProt private final int lowPrecision; private final Market market; - private PriceQuote(long value, Monetary baseSideMonetary, Monetary quoteSideMonetary) { + public PriceQuote(long value, Monetary baseSideMonetary, Monetary quoteSideMonetary) { this.value = value; this.baseSideMonetary = baseSideMonetary; this.quoteSideMonetary = quoteSideMonetary; diff --git a/http-api/build.gradle.kts b/http-api/build.gradle.kts index 958727cee6..1a2e91b7eb 100644 --- a/http-api/build.gradle.kts +++ b/http-api/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { implementation("bisq:os-specific") implementation("network:network") + implementation("network:network-identity") implementation("bitcoind:core") implementation("wallets:wallet") diff --git a/http-api/src/main/java/bisq/dto/DtoMappings.java b/http-api/src/main/java/bisq/dto/DtoMappings.java new file mode 100644 index 0000000000..e7f6d36030 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/DtoMappings.java @@ -0,0 +1,683 @@ +/* + * 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 . + */ + +package bisq.dto; + +import bisq.account.payment_method.BitcoinPaymentMethod; +import bisq.account.payment_method.FiatPaymentMethod; +import bisq.account.protocol_type.TradeProtocolType; +import bisq.common.currency.Market; +import bisq.common.encoding.Hex; +import bisq.common.monetary.Coin; +import bisq.common.monetary.Fiat; +import bisq.common.monetary.Monetary; +import bisq.common.monetary.PriceQuote; +import bisq.common.network.Address; +import bisq.common.network.AddressByTransportTypeMap; +import bisq.common.network.TransportType; +import bisq.dto.account.protocol_type.TradeProtocolTypeDto; +import bisq.dto.common.currency.MarketDto; +import bisq.dto.common.monetary.CoinDto; +import bisq.dto.common.monetary.FiatDto; +import bisq.dto.common.monetary.MonetaryDto; +import bisq.dto.common.monetary.PriceQuoteDto; +import bisq.dto.common.network.AddressByTransportTypeMapDto; +import bisq.dto.common.network.AddressDto; +import bisq.dto.common.network.TransportTypeDto; +import bisq.dto.network.identity.NetworkIdDto; +import bisq.dto.offer.DirectionDto; +import bisq.dto.offer.amount.spec.*; +import bisq.dto.offer.bisq_easy.BisqEasyOfferDto; +import bisq.dto.offer.options.OfferOptionDto; +import bisq.dto.offer.options.ReputationOptionDto; +import bisq.dto.offer.options.TradeTermsOptionDto; +import bisq.dto.offer.payment_method.BitcoinPaymentMethodSpecDto; +import bisq.dto.offer.payment_method.FiatPaymentMethodSpecDto; +import bisq.dto.offer.payment_method.PaymentMethodSpecDto; +import bisq.dto.offer.price.spec.FixPriceSpecDto; +import bisq.dto.offer.price.spec.FloatPriceSpecDto; +import bisq.dto.offer.price.spec.MarketPriceSpecDto; +import bisq.dto.offer.price.spec.PriceSpecDto; +import bisq.dto.security.keys.KeyPairDto; +import bisq.dto.security.keys.PrivateKeyDto; +import bisq.dto.security.keys.PubKeyDto; +import bisq.dto.security.keys.PublicKeyDto; +import bisq.dto.security.pow.ProofOfWorkDto; +import bisq.dto.user.profile.UserProfileDto; +import bisq.dto.user.reputation.ReputationScoreDto; +import bisq.network.identity.NetworkId; +import bisq.offer.Direction; +import bisq.offer.amount.spec.*; +import bisq.offer.bisq_easy.BisqEasyOffer; +import bisq.offer.options.OfferOption; +import bisq.offer.options.ReputationOption; +import bisq.offer.options.TradeTermsOption; +import bisq.offer.payment_method.BitcoinPaymentMethodSpec; +import bisq.offer.payment_method.FiatPaymentMethodSpec; +import bisq.offer.payment_method.PaymentMethodSpec; +import bisq.offer.payment_method.PaymentMethodSpecUtil; +import bisq.offer.price.spec.FixPriceSpec; +import bisq.offer.price.spec.FloatPriceSpec; +import bisq.offer.price.spec.MarketPriceSpec; +import bisq.offer.price.spec.PriceSpec; +import bisq.security.DigestUtil; +import bisq.security.keys.KeyGeneration; +import bisq.security.keys.PubKey; +import bisq.security.pow.ProofOfWork; +import bisq.user.profile.UserProfile; +import bisq.user.reputation.ReputationScore; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Base64; +import java.util.stream.Collectors; + +public class DtoMappings { + + // account.protocol_type + + public static class TradeProtocolTypeMapping { + public static TradeProtocolType toPojo(TradeProtocolTypeDto dto) { + return switch (dto) { + case BISQ_EASY -> TradeProtocolType.BISQ_EASY; + case BISQ_MU_SIG -> TradeProtocolType.BISQ_MU_SIG; + case SUBMARINE -> TradeProtocolType.SUBMARINE; + case LIQUID_MU_SIG -> TradeProtocolType.LIQUID_MU_SIG; + case BISQ_LIGHTNING -> TradeProtocolType.BISQ_LIGHTNING; + case LIQUID_SWAP -> TradeProtocolType.LIQUID_SWAP; + case BSQ_SWAP -> TradeProtocolType.BSQ_SWAP; + case LIGHTNING_ESCROW -> TradeProtocolType.LIGHTNING_ESCROW; + case MONERO_SWAP -> TradeProtocolType.MONERO_SWAP; + }; + } + + public static TradeProtocolTypeDto from(TradeProtocolType value) { + return switch (value) { + case BISQ_EASY -> TradeProtocolTypeDto.BISQ_EASY; + case BISQ_MU_SIG -> TradeProtocolTypeDto.BISQ_MU_SIG; + case SUBMARINE -> TradeProtocolTypeDto.SUBMARINE; + case LIQUID_MU_SIG -> TradeProtocolTypeDto.LIQUID_MU_SIG; + case BISQ_LIGHTNING -> TradeProtocolTypeDto.BISQ_LIGHTNING; + case LIQUID_SWAP -> TradeProtocolTypeDto.LIQUID_SWAP; + case BSQ_SWAP -> TradeProtocolTypeDto.BSQ_SWAP; + case LIGHTNING_ESCROW -> TradeProtocolTypeDto.LIGHTNING_ESCROW; + case MONERO_SWAP -> TradeProtocolTypeDto.MONERO_SWAP; + }; + } + } + + + // common.currency + + public static class MarketMapping { + public static Market toPojo(MarketDto dto) { + return new Market(dto.baseCurrencyCode(), dto.quoteCurrencyCode(), dto.baseCurrencyName(), dto.quoteCurrencyName()); + } + + public static MarketDto from(Market value) { + return new MarketDto(value.getBaseCurrencyCode(), value.getQuoteCurrencyCode(), value.getBaseCurrencyName(), value.getQuoteCurrencyName()); + } + } + + + // common.monetary + + public static class CoinMapping { + public static Coin toPojo(CoinDto dto) { + return new Coin(dto.getId(), dto.getValue(), dto.getCode(), dto.getPrecision(), dto.getLowPrecision()); + } + + public static CoinDto from(Coin value) { + return new CoinDto(value.getId(), value.getValue(), value.getCode(), value.getPrecision(), value.getLowPrecision()); + } + } + + public static class FiatMapping { + public static Fiat toPojo(FiatDto dto) { + return new Fiat(dto.getId(), dto.getValue(), dto.getCode(), dto.getPrecision(), dto.getLowPrecision()); + } + + public static FiatDto from(Fiat value) { + return new FiatDto(value.getId(), value.getValue(), value.getCode(), value.getPrecision(), value.getLowPrecision()); + } + } + + + public static class MonetaryMapping { + public static Monetary toPojo(MonetaryDto dto) { + if (dto instanceof FiatDto) { + return FiatMapping.toPojo((FiatDto) dto); + } else { + return CoinMapping.toPojo((CoinDto) dto); + } + } + + public static MonetaryDto from(Monetary value) { + if (value instanceof Fiat) { + return new FiatDto(value.getId(), value.getValue(), value.getCode(), value.getPrecision(), value.getLowPrecision()); + } else { + return new CoinDto(value.getId(), value.getValue(), value.getCode(), value.getPrecision(), value.getLowPrecision()); + } + } + } + + public static class PriceQuoteMapping { + public static PriceQuote toPojo(PriceQuoteDto dto) { + String baseCurrencyCode = dto.market().baseCurrencyCode(); + String quoteCurrencyCode = dto.market().quoteCurrencyCode(); + if (baseCurrencyCode.equals("BTC")) { + Monetary baseSideMonetary = Coin.asBtcFromFaceValue(1); + Monetary quoteSideMonetary = Fiat.from(dto.value(), quoteCurrencyCode); + return new PriceQuote(dto.value(), baseSideMonetary, quoteSideMonetary); + } else { + throw new UnsupportedOperationException("Altcoin price quote mapping is not supported yet"); + } + } + + public static PriceQuoteDto from(PriceQuote value) { + return new PriceQuoteDto(value.getValue(), MarketMapping.from(value.getMarket())); + } + } + + + // common.network + + public static class AddressByTransportTypeMapMapping { + public static AddressByTransportTypeMap toPojo(AddressByTransportTypeMapDto dto) { + return new AddressByTransportTypeMap(dto.map().entrySet().stream().collect(Collectors.toMap(entry -> TransportTypeMapping.toPojo(entry.getKey()), entry -> AddressMapping.toPojo(entry.getValue())))); + } + + public static AddressByTransportTypeMapDto from(AddressByTransportTypeMap map) { + return new AddressByTransportTypeMapDto(map.getMap().entrySet().stream().collect(Collectors.toMap(entry -> TransportTypeMapping.from(entry.getKey()), entry -> AddressMapping.from(entry.getValue())))); + } + } + + public static class AddressMapping { + public static Address toPojo(AddressDto dto) { + return new Address(dto.host(), dto.port()); + } + + public static AddressDto from(Address value) { + return new AddressDto(value.getHost(), value.getPort()); + } + } + + public static class TransportTypeMapping { + public static TransportType toPojo(TransportTypeDto dto) { + if (dto == TransportTypeDto.CLEAR) { + return TransportType.CLEAR; + } else if (dto == TransportTypeDto.TOR) { + return TransportType.TOR; + } else if (dto == TransportTypeDto.I2P) { + return TransportType.I2P; + } else { + throw new IllegalArgumentException("Unsupported enum " + dto); + } + } + + public static TransportTypeDto from(TransportType value) { + if (value == TransportType.CLEAR) { + return TransportTypeDto.CLEAR; + } else if (value == TransportType.TOR) { + return TransportTypeDto.TOR; + } else if (value == TransportType.I2P) { + return TransportTypeDto.I2P; + } else { + throw new IllegalArgumentException("Unsupported enum " + value); + } + } + } + + + // network.identity + + public static class NetworkIdMapping { + public static NetworkId toPojo(NetworkIdDto dto) { + return new NetworkId(AddressByTransportTypeMapMapping.toPojo(dto.addressByTransportTypeMap()), PubKeyMapping.toPojo(dto.pubKey())); + } + + public static NetworkIdDto from(NetworkId value) { + return new NetworkIdDto(AddressByTransportTypeMapMapping.from(value.getAddressByTransportTypeMap()), PubKeyMapping.from(value.getPubKey())); + } + } + + + // offer + + public static class DirectionMapping { + public static Direction toPojo(DirectionDto dto) { + if (dto == DirectionDto.BUY) { + return Direction.BUY; + } else { + return Direction.SELL; + } + } + + public static DirectionDto from(Direction value) { + if (value == Direction.BUY) { + return DirectionDto.BUY; + } else { + return DirectionDto.SELL; + } + } + } + + + // offer.amount.spec + + public static class AmountSpecMapping { + public static AmountSpec toPojo(AmountSpecDto dto) { + if (dto instanceof RangeAmountSpecDto) { + return RangeAmountSpecMapping.toPojo((RangeAmountSpecDto) dto); + } else { + return FixedAmountSpecMapping.toPojo((FixedAmountSpecDto) dto); + } + } + + public static AmountSpecDto from(AmountSpec value) { + if (value instanceof RangeAmountSpec) { + return RangeAmountSpecMapping.from((RangeAmountSpec) value); + } else { + return FixedAmountSpecMapping.from((FixedAmountSpec) value); + } + } + } + + public static class BaseSideFixedAmountSpecMapping { + public static BaseSideFixedAmountSpec toPojo(BaseSideFixedAmountSpecDto dto) { + return new BaseSideFixedAmountSpec(dto.getAmount()); + } + + public static BaseSideFixedAmountSpecDto from(BaseSideFixedAmountSpec value) { + return new BaseSideFixedAmountSpecDto(value.getAmount()); + } + } + + public static class BaseSideRangeAmountSpecMapping { + public static BaseSideRangeAmountSpec toPojo(BaseSideRangeAmountSpecDto dto) { + return new BaseSideRangeAmountSpec(dto.getMinAmount(), dto.getMaxAmount()); + } + + public static BaseSideRangeAmountSpecDto from(BaseSideRangeAmountSpec value) { + return new BaseSideRangeAmountSpecDto(value.getMinAmount(), value.getMaxAmount()); + } + } + + public static class FixedAmountSpecMapping { + public static FixedAmountSpec toPojo(FixedAmountSpecDto dto) { + if (dto instanceof BaseSideFixedAmountSpecDto) { + return BaseSideFixedAmountSpecMapping.toPojo((BaseSideFixedAmountSpecDto) dto); + } else if (dto instanceof QuoteSideFixedAmountSpecDto) { + return QuoteSideFixedAmountSpecMapping.toPojo((QuoteSideFixedAmountSpecDto) dto); + } else { + throw new IllegalArgumentException("Unsupported FixedAmountSpecDto " + dto); + } + } + + public static FixedAmountSpecDto from(FixedAmountSpec value) { + if (value instanceof BaseSideFixedAmountSpec) { + return BaseSideFixedAmountSpecMapping.from((BaseSideFixedAmountSpec) value); + } else if (value instanceof QuoteSideFixedAmountSpec) { + return QuoteSideFixedAmountSpecMapping.from((QuoteSideFixedAmountSpec) value); + } else { + throw new IllegalArgumentException("Unsupported FixedAmountSpec " + value); + } + } + } + + public static class QuoteSideFixedAmountSpecMapping { + public static QuoteSideFixedAmountSpec toPojo(QuoteSideFixedAmountSpecDto dto) { + return new QuoteSideFixedAmountSpec(dto.getAmount()); + } + + public static QuoteSideFixedAmountSpecDto from(QuoteSideFixedAmountSpec value) { + return new QuoteSideFixedAmountSpecDto(value.getAmount()); + } + } + + public static class QuoteSideRangeAmountSpecMapping { + public static QuoteSideRangeAmountSpec toPojo(QuoteSideRangeAmountSpecDto dto) { + return new QuoteSideRangeAmountSpec(dto.getMinAmount(), dto.getMaxAmount()); + } + + public static QuoteSideRangeAmountSpecDto from(QuoteSideRangeAmountSpec value) { + return new QuoteSideRangeAmountSpecDto(value.getMinAmount(), value.getMaxAmount()); + } + } + + public static class RangeAmountSpecMapping { + public static RangeAmountSpec toPojo(RangeAmountSpecDto dto) { + if (dto instanceof BaseSideRangeAmountSpecDto) { + return BaseSideRangeAmountSpecMapping.toPojo((BaseSideRangeAmountSpecDto) dto); + } else if (dto instanceof QuoteSideRangeAmountSpecDto) { + return QuoteSideRangeAmountSpecMapping.toPojo((QuoteSideRangeAmountSpecDto) dto); + } else { + throw new IllegalArgumentException("Unsupported RangeAmountSpecDto " + dto); + } + } + + public static RangeAmountSpecDto from(RangeAmountSpec value) { + if (value instanceof BaseSideRangeAmountSpec) { + return BaseSideRangeAmountSpecMapping.from((BaseSideRangeAmountSpec) value); + } else if (value instanceof QuoteSideRangeAmountSpec) { + return QuoteSideRangeAmountSpecMapping.from((QuoteSideRangeAmountSpec) value); + } else { + throw new IllegalArgumentException("Unsupported RangeAmountSpec " + value); + } + } + } + + + // offer.bisq_easy + + public static class BisqEasyOfferMapping { + public static BisqEasyOffer toPojo(BisqEasyOfferDto dto) { + return new BisqEasyOffer(dto.id(), dto.date(), NetworkIdMapping.toPojo(dto.makerNetworkId()), DirectionMapping.toPojo(dto.direction()), MarketMapping.toPojo(dto.market()), AmountSpecMapping.toPojo(dto.amountSpec()), PriceSpecMapping.toPojo(dto.priceSpec()), dto.protocolTypes().stream().map(TradeProtocolTypeMapping::toPojo).collect(Collectors.toList()), dto.baseSidePaymentMethodSpecs().stream().map(BitcoinPaymentMethodSpecMapping::toPojo).collect(Collectors.toList()), dto.quoteSidePaymentMethodSpecs().stream().map(FiatPaymentMethodSpecMapping::toPojo).collect(Collectors.toList()), dto.offerOptions().stream().map(OfferOptionMapping::toPojo).collect(Collectors.toList()), dto.supportedLanguageCodes()); + } + + public static BisqEasyOfferDto from(BisqEasyOffer value) { + return new BisqEasyOfferDto(value.getId(), value.getDate(), NetworkIdMapping.from(value.getMakerNetworkId()), DirectionMapping.from(value.getDirection()), MarketMapping.from(value.getMarket()), AmountSpecMapping.from(value.getAmountSpec()), PriceSpecMapping.from(value.getPriceSpec()), value.getProtocolTypes().stream().map(TradeProtocolTypeMapping::from).collect(Collectors.toList()), value.getBaseSidePaymentMethodSpecs().stream().map(BitcoinPaymentMethodSpecMapping::from).collect(Collectors.toList()), value.getQuoteSidePaymentMethodSpecs().stream().map(FiatPaymentMethodSpecMapping::from).collect(Collectors.toList()), value.getOfferOptions().stream().map(OfferOptionMapping::from).collect(Collectors.toList()), value.getSupportedLanguageCodes()); + } + } + + + // offer.options + + public static class OfferOptionMapping { + public static OfferOption toPojo(OfferOptionDto dto) { + if (dto instanceof ReputationOptionDto) { + return ReputationOptionMapping.toPojo((ReputationOptionDto) dto); + } else if (dto instanceof TradeTermsOptionDto) { + return TradeTermsOptionMapping.toPojo((TradeTermsOptionDto) dto); + } else { + throw new IllegalArgumentException("Unsupported OfferOptionDto " + dto); + } + } + + public static OfferOptionDto from(OfferOption value) { + if (value instanceof ReputationOption) { + //noinspection deprecation + return new ReputationOptionDto(((ReputationOption) value).getRequiredTotalReputationScore()); + } else if (value instanceof TradeTermsOption) { + return new TradeTermsOptionDto(((TradeTermsOption) value).getMakersTradeTerms()); + } else { + throw new IllegalArgumentException("Unsupported OfferOption " + value); + } + } + } + + public static class ReputationOptionMapping { + public static ReputationOption toPojo(ReputationOptionDto dto) { + //noinspection deprecation + return new ReputationOption(dto.getRequiredTotalReputationScore()); + } + + public static ReputationOptionDto from(ReputationOption value) { + //noinspection deprecation + return new ReputationOptionDto(value.getRequiredTotalReputationScore()); + } + } + + public static class TradeTermsOptionMapping { + public static TradeTermsOption toPojo(TradeTermsOptionDto dto) { + return new TradeTermsOption(dto.getMakersTradeTerms()); + } + + public static TradeTermsOptionDto from(TradeTermsOption value) { + return new TradeTermsOptionDto(value.getMakersTradeTerms()); + } + } + + + // offer.payment_method + + public static class BitcoinPaymentMethodSpecMapping { + public static BitcoinPaymentMethodSpec toPojo(BitcoinPaymentMethodSpecDto dto) { + String paymentMethod = dto.getPaymentMethod(); + BitcoinPaymentMethod method = PaymentMethodSpecUtil.getBitcoinPaymentMethod(paymentMethod); + return new BitcoinPaymentMethodSpec(method, dto.getSaltedMakerAccountId()); + } + + public static BitcoinPaymentMethodSpecDto from(BitcoinPaymentMethodSpec value) { + return new BitcoinPaymentMethodSpecDto(value.getPaymentMethod().getName(), value.getSaltedMakerAccountId()); + } + } + + public static class FiatPaymentMethodSpecMapping { + public static FiatPaymentMethodSpec toPojo(FiatPaymentMethodSpecDto dto) { + String paymentMethod = dto.getPaymentMethod(); + FiatPaymentMethod method = PaymentMethodSpecUtil.getFiatPaymentMethod(paymentMethod); + return new FiatPaymentMethodSpec(method, dto.getSaltedMakerAccountId()); + } + + public static FiatPaymentMethodSpecDto from(FiatPaymentMethodSpec value) { + return new FiatPaymentMethodSpecDto(value.getPaymentMethod().getName(), value.getSaltedMakerAccountId()); + } + } + + public static class PaymentMethodSpecMapping { + public static PaymentMethodSpec toPojo(PaymentMethodSpecDto dto) { + if (dto instanceof FiatPaymentMethodSpecDto) { + return FiatPaymentMethodSpecMapping.toPojo((FiatPaymentMethodSpecDto) dto); + } else if (dto instanceof BitcoinPaymentMethodSpecDto) { + return BitcoinPaymentMethodSpecMapping.toPojo((BitcoinPaymentMethodSpecDto) dto); + } else { + throw new IllegalArgumentException("Unsupported PaymentMethodSpecDto " + dto); + } + } + + public static PaymentMethodSpecDto from(PaymentMethodSpec value) { + if (value instanceof FiatPaymentMethodSpec) { + return FiatPaymentMethodSpecMapping.from((FiatPaymentMethodSpec) value); + } else if (value instanceof BitcoinPaymentMethodSpec) { + return BitcoinPaymentMethodSpecMapping.from((BitcoinPaymentMethodSpec) value); + } else { + throw new IllegalArgumentException("Unsupported PaymentMethodSpec " + value); + } + } + } + + + // offer.price.spec + + public static class MarketPriceSpecMapping { + public static MarketPriceSpec toPojo(MarketPriceSpecDto dto) { + return new MarketPriceSpec(); + } + + public static MarketPriceSpecDto from(MarketPriceSpec value) { + return new MarketPriceSpecDto(); + } + } + + public static class FloatPriceSpecMapping { + public static FloatPriceSpec toPojo(FloatPriceSpecDto dto) { + return new FloatPriceSpec(dto.getPercentage()); + } + + public static FloatPriceSpecDto from(FloatPriceSpec value) { + return new FloatPriceSpecDto(value.getPercentage()); + } + } + + public static class FixPriceSpecMapping { + public static FixPriceSpec toPojo(FixPriceSpecDto dto) { + return new FixPriceSpec(PriceQuoteMapping.toPojo(dto.getPriceQuote())); + } + + public static FixPriceSpecDto from(FixPriceSpec value) { + return new FixPriceSpecDto(PriceQuoteMapping.from(value.getPriceQuote())); + } + } + + public static class PriceSpecMapping { + public static PriceSpec toPojo(PriceSpecDto dto) { + return switch (dto) { + case MarketPriceSpecDto marketPriceSpecDto -> MarketPriceSpecMapping.toPojo(marketPriceSpecDto); + case FixPriceSpecDto fixPriceSpecDto -> FixPriceSpecMapping.toPojo(fixPriceSpecDto); + case FloatPriceSpecDto floatPriceSpecDto -> FloatPriceSpecMapping.toPojo(floatPriceSpecDto); + case null, default -> throw new IllegalArgumentException("Unsupported PriceSpecDto " + dto); + }; + } + + public static PriceSpecDto from(PriceSpec value) { + return switch (value) { + case MarketPriceSpec marketPriceSpec -> MarketPriceSpecMapping.from(marketPriceSpec); + case FixPriceSpec fixPriceSpec -> FixPriceSpecMapping.from(fixPriceSpec); + case FloatPriceSpec floatPriceSpec -> FloatPriceSpecMapping.from(floatPriceSpec); + case null, default -> throw new IllegalArgumentException("Unsupported PriceSpec " + value); + }; + } + } + + + // security.keys + + public static class KeyPairDtoMapping { + public static KeyPair toPojo(KeyPairDto dto) { + PublicKey publicKey = PublicKeyMapping.toPojo(dto.publicKey()); + PrivateKey privateKey = PrivateKeyMapping.toPojo(dto.privateKey()); + return new KeyPair(publicKey, privateKey); + } + + public static KeyPairDto from(KeyPair value) { + PrivateKeyDto privateKeyDto = PrivateKeyMapping.from(value.getPrivate()); + PublicKeyDto publicKeyDto = PublicKeyMapping.from(value.getPublic()); + return new KeyPairDto(publicKeyDto, privateKeyDto); + } + } + + public static class PrivateKeyMapping { + public static PrivateKey toPojo(PrivateKeyDto dto) { + try { + byte[] decoded = Base64.getDecoder().decode(dto.encoded()); + return KeyGeneration.generatePrivate(decoded); + } catch (Exception e) { + throw new RuntimeException("Failed to generate privateKey", e); + } + } + + public static PrivateKeyDto from(PrivateKey value) { + return new PrivateKeyDto(Base64.getEncoder().encodeToString(value.getEncoded())); + } + } + + public static class PubKeyMapping { + public static PubKey toPojo(PubKeyDto dto) { + return new PubKey(PublicKeyMapping.toPojo(dto.publicKey()), dto.keyId()); + } + + public static PubKeyDto from(PubKey value) { + PublicKey publicKey = value.getPublicKey(); + PublicKeyDto publicKeyDto = PublicKeyMapping.from(publicKey); + String keyId = value.getKeyId(); + byte[] hashAsBytes = DigestUtil.hash(publicKey.getEncoded()); + String hash = Base64.getEncoder().encodeToString(hashAsBytes); + String id = Hex.encode(hashAsBytes); + return new PubKeyDto(publicKeyDto, keyId, hash, id); + } + } + + public static class PublicKeyMapping { + public static PublicKey toPojo(PublicKeyDto dto) { + try { + byte[] decoded = Base64.getDecoder().decode(dto.encoded()); + return KeyGeneration.generatePublic(decoded); + } catch (Exception e) { + throw new RuntimeException("Failed to generate publicKey", e); + } + } + + public static PublicKeyDto from(PublicKey value) { + return new PublicKeyDto(Base64.getEncoder().encodeToString(value.getEncoded())); + } + } + + + // security.pow + + public static class ProofOfWorkDtoMapping { + public static ProofOfWork toPojo(ProofOfWorkDto dto) { + return new ProofOfWork( + Base64.getDecoder().decode(dto.payload()), + dto.counter(), + dto.challenge() != null ? Base64.getDecoder().decode(dto.challenge()) : null, + dto.difficulty(), + Base64.getDecoder().decode(dto.solution()), + dto.duration() + ); + } + + public static ProofOfWorkDto from(ProofOfWork value) { + return new ProofOfWorkDto( + Base64.getEncoder().encodeToString(value.getPayload()), + value.getCounter(), + value.getChallenge() != null ? Base64.getEncoder().encodeToString(value.getChallenge()) : null, + value.getDifficulty(), + Base64.getEncoder().encodeToString(value.getSolution()), + value.getDuration() + ); + } + } + + + // user.profile + + public static class UserProfileDtoMapping { + public static UserProfile toPojo(UserProfileDto dto) { + return new UserProfile(dto.version(), + dto.nickName(), + ProofOfWorkDtoMapping.toPojo(dto.proofOfWork()), + dto.avatarVersion(), + NetworkIdMapping.toPojo(dto.networkId()), + dto.terms(), + dto.statement(), + dto.applicationVersion() + ); + } + + public static UserProfileDto from(UserProfile value) { + return new UserProfileDto( + value.getVersion(), + value.getNickName(), + ProofOfWorkDtoMapping.from(value.getProofOfWork()), + value.getAvatarVersion(), + NetworkIdMapping.from(value.getNetworkId()), + value.getTerms(), + value.getStatement(), + value.getApplicationVersion(), + value.getNym(), + value.getUserName(), + value.getPublishDate() + ); + } + } + + + // user.reputation + + public static class ReputationScoreMapping { + public static ReputationScore toPojo(ReputationScoreDto dto) { + return new ReputationScore(dto.totalScore(), dto.fiveSystemScore(), dto.ranking()); + } + + public static ReputationScoreDto from(ReputationScore value) { + return new ReputationScoreDto(value.getTotalScore(), value.getFiveSystemScore(), value.getRanking()); + } + } +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/OfferListItemDtoFactory.java b/http-api/src/main/java/bisq/dto/OfferListItemDtoFactory.java new file mode 100644 index 0000000000..6f5becf4a4 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/OfferListItemDtoFactory.java @@ -0,0 +1,123 @@ +/* + * 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 . + */ + +package bisq.dto; + + +import bisq.account.payment_method.PaymentMethod; +import bisq.bonded_roles.market_price.MarketPriceService; +import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookMessage; +import bisq.common.currency.Market; +import bisq.dto.offer.bisq_easy.BisqEasyOfferDto; +import bisq.dto.offer.bisq_easy.OfferListItemDto; +import bisq.dto.user.reputation.ReputationScoreDto; +import bisq.i18n.Res; +import bisq.offer.Direction; +import bisq.offer.amount.OfferAmountFormatter; +import bisq.offer.amount.spec.AmountSpec; +import bisq.offer.amount.spec.RangeAmountSpec; +import bisq.offer.bisq_easy.BisqEasyOffer; +import bisq.offer.payment_method.PaymentMethodSpecUtil; +import bisq.offer.price.PriceUtil; +import bisq.offer.price.spec.PriceSpec; +import bisq.offer.price.spec.PriceSpecFormatter; +import bisq.presentation.formatters.DateFormatter; +import bisq.presentation.formatters.PriceFormatter; +import bisq.user.identity.UserIdentityService; +import bisq.user.profile.UserProfile; +import bisq.user.profile.UserProfileService; +import bisq.user.reputation.ReputationScore; +import bisq.user.reputation.ReputationService; + +import java.text.DateFormat; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class OfferListItemDtoFactory { + public static OfferListItemDto createOfferListItemDto(UserProfileService userProfileService, + UserIdentityService userIdentityService, + ReputationService reputationService, + MarketPriceService marketPriceService, + BisqEasyOfferbookMessage bisqEasyOfferbookMessage) { + BisqEasyOffer bisqEasyOffer = bisqEasyOfferbookMessage.getBisqEasyOffer().orElseThrow(); + boolean isMyOffer = bisqEasyOfferbookMessage.isMyMessage(userIdentityService); + Direction direction = bisqEasyOffer.getDirection(); + String messageId = bisqEasyOfferbookMessage.getId(); + String offerId = bisqEasyOffer.getId(); + BisqEasyOfferDto bisqEasyOfferDto = DtoMappings.BisqEasyOfferMapping.from(bisqEasyOffer); + String authorUserProfileId = bisqEasyOfferbookMessage.getAuthorUserProfileId(); + Optional senderUserProfile = userProfileService.findUserProfile(authorUserProfileId); + String nym = senderUserProfile.map(UserProfile::getNym).orElse(""); + String userName = senderUserProfile.map(UserProfile::getUserName).orElse(""); + ReputationScoreDto reputationScore = senderUserProfile.flatMap(reputationService::findReputationScore) + .map(DtoMappings.ReputationScoreMapping::from) + .orElse(DtoMappings.ReputationScoreMapping.from(ReputationScore.NONE)); + + // For now, we send also the formatted values as we have not the complex formatters in mobile impl. yet. + // We might need to replicate the formatters anyway later and then those fields could be removed + long date = bisqEasyOfferbookMessage.getDate(); + String formattedDate = DateFormatter.formatDateTime(new Date(date), DateFormat.MEDIUM, DateFormat.SHORT, + true, " " + Res.get("temporal.at") + " "); + AmountSpec amountSpec = bisqEasyOffer.getAmountSpec(); + PriceSpec priceSpec = bisqEasyOffer.getPriceSpec(); + boolean hasAmountRange = amountSpec instanceof RangeAmountSpec; + Market market = bisqEasyOffer.getMarket(); + String formattedQuoteAmount = OfferAmountFormatter.formatQuoteAmount( + marketPriceService, + amountSpec, + priceSpec, + market, + hasAmountRange, + true + ); + String formattedBaseAmount = OfferAmountFormatter.formatBaseAmount( + marketPriceService, + amountSpec, + priceSpec, + market, + hasAmountRange, + true, + false + ); + String formattedPrice = PriceUtil.findQuote(marketPriceService, bisqEasyOffer) + .map(PriceFormatter::format) + .orElse(""); + String formattedPriceSpec = PriceSpecFormatter.getFormattedPriceSpec(priceSpec, true); + List quoteSidePaymentMethods = PaymentMethodSpecUtil.getPaymentMethods(bisqEasyOffer.getQuoteSidePaymentMethodSpecs()) + .stream() + .map(PaymentMethod::getName) + .collect(Collectors.toList()); + List baseSidePaymentMethods = PaymentMethodSpecUtil.getPaymentMethods(bisqEasyOffer.getBaseSidePaymentMethodSpecs()) + .stream() + .map(PaymentMethod::getName) + .collect(Collectors.toList()); + return new OfferListItemDto(bisqEasyOfferDto, + isMyOffer, + nym, + userName, + reputationScore, + formattedDate, + formattedQuoteAmount, + formattedBaseAmount, + formattedPrice, + formattedPriceSpec, + quoteSidePaymentMethods, + baseSidePaymentMethods); + } +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/account/protocol_type/TradeProtocolTypeDto.java b/http-api/src/main/java/bisq/dto/account/protocol_type/TradeProtocolTypeDto.java new file mode 100644 index 0000000000..10656f2be3 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/account/protocol_type/TradeProtocolTypeDto.java @@ -0,0 +1,30 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.dto.account.protocol_type; + +public enum TradeProtocolTypeDto { + BISQ_EASY, + BISQ_MU_SIG, + SUBMARINE, + LIQUID_MU_SIG, + BISQ_LIGHTNING, + LIQUID_SWAP, + BSQ_SWAP, + LIGHTNING_ESCROW, + MONERO_SWAP; +} diff --git a/http-api/src/main/java/bisq/dto/common/currency/MarketDto.java b/http-api/src/main/java/bisq/dto/common/currency/MarketDto.java new file mode 100644 index 0000000000..35fa64a41b --- /dev/null +++ b/http-api/src/main/java/bisq/dto/common/currency/MarketDto.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +package bisq.dto.common.currency; + +public record MarketDto(String baseCurrencyCode, + String quoteCurrencyCode, + String baseCurrencyName, + String quoteCurrencyName) { +} diff --git a/http-api/src/main/java/bisq/dto/common/monetary/CoinDto.java b/http-api/src/main/java/bisq/dto/common/monetary/CoinDto.java new file mode 100644 index 0000000000..a5b2c22a92 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/common/monetary/CoinDto.java @@ -0,0 +1,38 @@ +/* + * 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 . + */ + +package bisq.dto.common.monetary; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@EqualsAndHashCode(callSuper = true) +public final class CoinDto extends MonetaryDto { + @JsonCreator + public CoinDto(@JsonProperty("id") String id, + @JsonProperty("value") long value, + @JsonProperty("code") String code, + @JsonProperty("precision") int precision, + @JsonProperty("lowPrecision") int lowPrecision) { + super(id, value, code, precision, lowPrecision); + } +} diff --git a/http-api/src/main/java/bisq/dto/common/monetary/FiatDto.java b/http-api/src/main/java/bisq/dto/common/monetary/FiatDto.java new file mode 100644 index 0000000000..ac1c0edaf3 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/common/monetary/FiatDto.java @@ -0,0 +1,38 @@ +/* + * 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 . + */ + +package bisq.dto.common.monetary; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@EqualsAndHashCode(callSuper = true) +public class FiatDto extends MonetaryDto { + @JsonCreator + public FiatDto(@JsonProperty("id") String id, + @JsonProperty("value") long value, + @JsonProperty("code") String code, + @JsonProperty("precision") int precision, + @JsonProperty("lowPrecision") int lowPrecision) { + super(id, value, code, precision, lowPrecision); + } +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/common/monetary/MonetaryDto.java b/http-api/src/main/java/bisq/dto/common/monetary/MonetaryDto.java new file mode 100644 index 0000000000..bdbf337832 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/common/monetary/MonetaryDto.java @@ -0,0 +1,50 @@ +/* + * 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 . + */ + +package bisq.dto.common.monetary; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = CoinDto.class, name = "Coin"), + @JsonSubTypes.Type(value = FiatDto.class, name = "Fiat"), +}) +@EqualsAndHashCode +@Getter +public abstract class MonetaryDto { + protected final String id; + protected final long value; + protected final String code; + protected final int precision; + protected final int lowPrecision; + + protected MonetaryDto(String id, long value, String code, int precision, int lowPrecision) { + this.id = id; + this.value = value; + this.code = code; + this.precision = precision; + this.lowPrecision = lowPrecision; + } +} diff --git a/http-api/src/main/java/bisq/dto/common/monetary/PriceQuoteDto.java b/http-api/src/main/java/bisq/dto/common/monetary/PriceQuoteDto.java new file mode 100644 index 0000000000..aa03ed93b6 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/common/monetary/PriceQuoteDto.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +package bisq.dto.common.monetary; + +import bisq.dto.common.currency.MarketDto; + +public record PriceQuoteDto(long value, MarketDto market) { +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/common/network/AddressByTransportTypeMapDto.java b/http-api/src/main/java/bisq/dto/common/network/AddressByTransportTypeMapDto.java new file mode 100644 index 0000000000..1f3b125dae --- /dev/null +++ b/http-api/src/main/java/bisq/dto/common/network/AddressByTransportTypeMapDto.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +package bisq.dto.common.network; + +import java.util.Map; + +public record AddressByTransportTypeMapDto(Map map) { +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/common/network/AddressDto.java b/http-api/src/main/java/bisq/dto/common/network/AddressDto.java new file mode 100644 index 0000000000..0f0803257f --- /dev/null +++ b/http-api/src/main/java/bisq/dto/common/network/AddressDto.java @@ -0,0 +1,21 @@ +/* + * 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 . + */ + +package bisq.dto.common.network; + +public record AddressDto(String host, int port) { +} diff --git a/http-api/src/main/java/bisq/dto/common/network/TransportTypeDto.java b/http-api/src/main/java/bisq/dto/common/network/TransportTypeDto.java new file mode 100644 index 0000000000..afb2bb30b6 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/common/network/TransportTypeDto.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +package bisq.dto.common.network; + +public enum TransportTypeDto { + TOR, + I2P, + CLEAR; +} diff --git a/http-api/src/main/java/bisq/dto/network/identity/NetworkIdDto.java b/http-api/src/main/java/bisq/dto/network/identity/NetworkIdDto.java new file mode 100644 index 0000000000..bf94bf878a --- /dev/null +++ b/http-api/src/main/java/bisq/dto/network/identity/NetworkIdDto.java @@ -0,0 +1,25 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.dto.network.identity; + + +import bisq.dto.common.network.AddressByTransportTypeMapDto; +import bisq.dto.security.keys.PubKeyDto; + +public record NetworkIdDto(AddressByTransportTypeMapDto addressByTransportTypeMap, PubKeyDto pubKey) { +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/offer/DirectionDto.java b/http-api/src/main/java/bisq/dto/offer/DirectionDto.java new file mode 100644 index 0000000000..4d8a9ffde0 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/DirectionDto.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +package bisq.dto.offer; + +public enum DirectionDto { + BUY, + SELL; +} diff --git a/http-api/src/main/java/bisq/dto/offer/amount/spec/AmountSpecDto.java b/http-api/src/main/java/bisq/dto/offer/amount/spec/AmountSpecDto.java new file mode 100644 index 0000000000..443b454aba --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/amount/spec/AmountSpecDto.java @@ -0,0 +1,42 @@ +/* + * 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 . + */ + +package bisq.dto.offer.amount.spec; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = BaseSideFixedAmountSpecDto.class, name = "BaseSideFixedAmountSpec"), + @JsonSubTypes.Type(value = BaseSideRangeAmountSpecDto.class, name = "BaseSideRangeAmountSpec"), + @JsonSubTypes.Type(value = QuoteSideFixedAmountSpecDto.class, name = "QuoteSideFixedAmountSpec"), + @JsonSubTypes.Type(value = QuoteSideRangeAmountSpecDto.class, name = "QuoteSideRangeAmountSpec") +}) +@Getter +@EqualsAndHashCode +public abstract class AmountSpecDto { + protected AmountSpecDto() { + } +} + diff --git a/http-api/src/main/java/bisq/dto/offer/amount/spec/BaseSideFixedAmountSpecDto.java b/http-api/src/main/java/bisq/dto/offer/amount/spec/BaseSideFixedAmountSpecDto.java new file mode 100644 index 0000000000..3cb3c62ce5 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/amount/spec/BaseSideFixedAmountSpecDto.java @@ -0,0 +1,34 @@ +/* + * 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 . + */ + +package bisq.dto.offer.amount.spec; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@EqualsAndHashCode(callSuper = true) +public class BaseSideFixedAmountSpecDto extends FixedAmountSpecDto { + @JsonCreator + public BaseSideFixedAmountSpecDto(@JsonProperty("amount") long amount) { + super(amount); + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/amount/spec/BaseSideRangeAmountSpecDto.java b/http-api/src/main/java/bisq/dto/offer/amount/spec/BaseSideRangeAmountSpecDto.java new file mode 100644 index 0000000000..87f9252f59 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/amount/spec/BaseSideRangeAmountSpecDto.java @@ -0,0 +1,35 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.dto.offer.amount.spec; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@EqualsAndHashCode(callSuper = true) +public class BaseSideRangeAmountSpecDto extends RangeAmountSpecDto { + @JsonCreator + public BaseSideRangeAmountSpecDto(@JsonProperty("minAmount") long minAmount, + @JsonProperty("maxAmount") long maxAmount) { + super(minAmount, maxAmount); + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/amount/spec/FixedAmountSpecDto.java b/http-api/src/main/java/bisq/dto/offer/amount/spec/FixedAmountSpecDto.java new file mode 100644 index 0000000000..7ae7366668 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/amount/spec/FixedAmountSpecDto.java @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +package bisq.dto.offer.amount.spec; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@Getter +@EqualsAndHashCode(callSuper = true) +public abstract class FixedAmountSpecDto extends AmountSpecDto { + public long amount; + + public FixedAmountSpecDto(long amount) { + super(); + this.amount = amount; + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/amount/spec/QuoteSideFixedAmountSpecDto.java b/http-api/src/main/java/bisq/dto/offer/amount/spec/QuoteSideFixedAmountSpecDto.java new file mode 100644 index 0000000000..44ca872380 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/amount/spec/QuoteSideFixedAmountSpecDto.java @@ -0,0 +1,34 @@ +/* + * 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 . + */ + +package bisq.dto.offer.amount.spec; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@EqualsAndHashCode(callSuper = true) +public class QuoteSideFixedAmountSpecDto extends FixedAmountSpecDto { + @JsonCreator + public QuoteSideFixedAmountSpecDto(@JsonProperty("amount") long amount) { + super(amount); + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/amount/spec/QuoteSideRangeAmountSpecDto.java b/http-api/src/main/java/bisq/dto/offer/amount/spec/QuoteSideRangeAmountSpecDto.java new file mode 100644 index 0000000000..c881dec32e --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/amount/spec/QuoteSideRangeAmountSpecDto.java @@ -0,0 +1,35 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.dto.offer.amount.spec; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@EqualsAndHashCode(callSuper = true) +public class QuoteSideRangeAmountSpecDto extends RangeAmountSpecDto { + @JsonCreator + public QuoteSideRangeAmountSpecDto(@JsonProperty("minAmount") long minAmount, + @JsonProperty("maxAmount") long maxAmount) { + super(minAmount, maxAmount); + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/amount/spec/RangeAmountSpecDto.java b/http-api/src/main/java/bisq/dto/offer/amount/spec/RangeAmountSpecDto.java new file mode 100644 index 0000000000..b80975d562 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/amount/spec/RangeAmountSpecDto.java @@ -0,0 +1,34 @@ +/* + * 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 . + */ + +package bisq.dto.offer.amount.spec; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@Getter +@EqualsAndHashCode(callSuper = true) +public abstract class RangeAmountSpecDto extends AmountSpecDto { + protected final long minAmount; + protected final long maxAmount; + + public RangeAmountSpecDto(long minAmount, long maxAmount) { + super(); + this.minAmount = minAmount; + this.maxAmount = maxAmount; + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/bisq_easy/BisqEasyOfferDto.java b/http-api/src/main/java/bisq/dto/offer/bisq_easy/BisqEasyOfferDto.java new file mode 100644 index 0000000000..02007548a9 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/bisq_easy/BisqEasyOfferDto.java @@ -0,0 +1,44 @@ +/* + * 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 . + */ + +package bisq.dto.offer.bisq_easy; + +import bisq.dto.account.protocol_type.TradeProtocolTypeDto; +import bisq.dto.common.currency.MarketDto; +import bisq.dto.network.identity.NetworkIdDto; +import bisq.dto.offer.DirectionDto; +import bisq.dto.offer.amount.spec.AmountSpecDto; +import bisq.dto.offer.options.OfferOptionDto; +import bisq.dto.offer.payment_method.BitcoinPaymentMethodSpecDto; +import bisq.dto.offer.payment_method.FiatPaymentMethodSpecDto; +import bisq.dto.offer.price.spec.PriceSpecDto; + +import java.util.List; + +public record BisqEasyOfferDto(String id, + long date, + NetworkIdDto makerNetworkId, + DirectionDto direction, + MarketDto market, + AmountSpecDto amountSpec, + PriceSpecDto priceSpec, + List protocolTypes, + List baseSidePaymentMethodSpecs, + List quoteSidePaymentMethodSpecs, + List offerOptions, + List supportedLanguageCodes) { +} diff --git a/http-api/src/main/java/bisq/dto/offer/bisq_easy/OfferListItemDto.java b/http-api/src/main/java/bisq/dto/offer/bisq_easy/OfferListItemDto.java new file mode 100644 index 0000000000..63598edf13 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/bisq_easy/OfferListItemDto.java @@ -0,0 +1,73 @@ +/* + * 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 . + */ + +package bisq.dto.offer.bisq_easy; + +import bisq.dto.user.reputation.ReputationScoreDto; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@ToString +@EqualsAndHashCode +public class OfferListItemDto { + private final BisqEasyOfferDto bisqEasyOffer; + @JsonProperty("isMyOffer") + private final boolean isMyOffer; + private final String nym; + private final String userName; + private final ReputationScoreDto reputationScore; + private final String formattedDate; + private final String formattedQuoteAmount; + private final String formattedBaseAmount; + private final String formattedPrice; + private final String formattedPriceSpec; + private final List quoteSidePaymentMethods; + private final List baseSidePaymentMethods; + + @JsonCreator + public OfferListItemDto(BisqEasyOfferDto bisqEasyOffer, + boolean isMyOffer, + String nym, + String userName, + ReputationScoreDto reputationScore, + String formattedDate, + String formattedQuoteAmount, + String formattedBaseAmount, + String formattedPrice, + String formattedPriceSpec, + List quoteSidePaymentMethods, + List baseSidePaymentMethods) { + this.bisqEasyOffer = bisqEasyOffer; + this.isMyOffer = isMyOffer; + this.nym = nym; + this.userName = userName; + this.reputationScore = reputationScore; + this.formattedDate = formattedDate; + this.formattedQuoteAmount = formattedQuoteAmount; + this.formattedBaseAmount = formattedBaseAmount; + this.formattedPrice = formattedPrice; + this.formattedPriceSpec = formattedPriceSpec; + this.quoteSidePaymentMethods = quoteSidePaymentMethods; + this.baseSidePaymentMethods = baseSidePaymentMethods; + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/options/OfferOptionDto.java b/http-api/src/main/java/bisq/dto/offer/options/OfferOptionDto.java new file mode 100644 index 0000000000..677446eace --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/options/OfferOptionDto.java @@ -0,0 +1,40 @@ +/* + * 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 . + */ + +package bisq.dto.offer.options; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.EqualsAndHashCode; +import lombok.Getter; + + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = ReputationOptionDto.class, name = "ReputationOption"), + @JsonSubTypes.Type(value = TradeTermsOptionDto.class, name = "TradeTermsOption"), +}) +@Getter +@EqualsAndHashCode +public abstract class OfferOptionDto { + protected OfferOptionDto() { + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/options/ReputationOptionDto.java b/http-api/src/main/java/bisq/dto/offer/options/ReputationOptionDto.java new file mode 100644 index 0000000000..55ca3efdb8 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/options/ReputationOptionDto.java @@ -0,0 +1,37 @@ +/* + * 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 . + */ + +package bisq.dto.offer.options; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@EqualsAndHashCode(callSuper = true) +public final class ReputationOptionDto extends OfferOptionDto { + @Deprecated(since = "2.1.1") + private final long requiredTotalReputationScore; + + @JsonCreator + public ReputationOptionDto(@JsonProperty("requiredTotalReputationScore") long requiredTotalReputationScore) { + this.requiredTotalReputationScore = requiredTotalReputationScore; + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/options/TradeTermsOptionDto.java b/http-api/src/main/java/bisq/dto/offer/options/TradeTermsOptionDto.java new file mode 100644 index 0000000000..c6301e9f48 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/options/TradeTermsOptionDto.java @@ -0,0 +1,36 @@ +/* + * 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 . + */ + +package bisq.dto.offer.options; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@EqualsAndHashCode(callSuper = true) +public final class TradeTermsOptionDto extends OfferOptionDto { + private final String makersTradeTerms; + + @JsonCreator + public TradeTermsOptionDto(@JsonProperty("makersTradeTerms") String makersTradeTerms) { + this.makersTradeTerms = makersTradeTerms; + } +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/offer/payment_method/BitcoinPaymentMethodSpecDto.java b/http-api/src/main/java/bisq/dto/offer/payment_method/BitcoinPaymentMethodSpecDto.java new file mode 100644 index 0000000000..0660e92d0e --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/payment_method/BitcoinPaymentMethodSpecDto.java @@ -0,0 +1,39 @@ +/* + * 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 . + */ + +package bisq.dto.offer.payment_method; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.util.Optional; + +@Getter +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public final class BitcoinPaymentMethodSpecDto extends PaymentMethodSpecDto { + + @JsonCreator + public BitcoinPaymentMethodSpecDto(@JsonProperty("paymentMethod") String paymentMethod, @JsonProperty("saltedMakerAccountId") Optional saltedMakerAccountId) { + super(paymentMethod, saltedMakerAccountId); + } +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/offer/payment_method/FiatPaymentMethodSpecDto.java b/http-api/src/main/java/bisq/dto/offer/payment_method/FiatPaymentMethodSpecDto.java new file mode 100644 index 0000000000..2ce096fcd0 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/payment_method/FiatPaymentMethodSpecDto.java @@ -0,0 +1,38 @@ +/* + * 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 . + */ + +package bisq.dto.offer.payment_method; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.util.Optional; + +@Getter +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public final class FiatPaymentMethodSpecDto extends PaymentMethodSpecDto { + @JsonCreator + public FiatPaymentMethodSpecDto(@JsonProperty("paymentMethod") String paymentMethod, @JsonProperty("saltedMakerAccountId") Optional saltedMakerAccountId) { + super(paymentMethod, saltedMakerAccountId); + } +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/offer/payment_method/PaymentMethodSpecDto.java b/http-api/src/main/java/bisq/dto/offer/payment_method/PaymentMethodSpecDto.java new file mode 100644 index 0000000000..15fc3c358c --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/payment_method/PaymentMethodSpecDto.java @@ -0,0 +1,55 @@ +/* + * 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 . + */ + +package bisq.dto.offer.payment_method; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.util.Optional; + +@ToString +@Getter +@EqualsAndHashCode + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = FiatPaymentMethodSpecDto.class, name = "FiatPaymentMethodSpec"), + @JsonSubTypes.Type(value = BitcoinPaymentMethodSpecDto.class, name = "BitcoinPaymentMethodSpec"), +}) +@JsonInclude(JsonInclude.Include.NON_NULL) +public abstract class PaymentMethodSpecDto { + protected final Optional saltedMakerAccountId; + protected final String paymentMethod; + + protected PaymentMethodSpecDto(String paymentMethod) { + this(paymentMethod, Optional.empty()); + } + + protected PaymentMethodSpecDto(String paymentMethod, Optional saltedMakerAccountId) { + this.paymentMethod = paymentMethod; + this.saltedMakerAccountId = saltedMakerAccountId; + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/price/spec/FixPriceSpecDto.java b/http-api/src/main/java/bisq/dto/offer/price/spec/FixPriceSpecDto.java new file mode 100644 index 0000000000..5e5e68f480 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/price/spec/FixPriceSpecDto.java @@ -0,0 +1,37 @@ +/* + * 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 . + */ + +package bisq.dto.offer.price.spec; + +import bisq.dto.common.monetary.PriceQuoteDto; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@EqualsAndHashCode(callSuper = true) +public class FixPriceSpecDto extends PriceSpecDto { + private final PriceQuoteDto priceQuote; + + @JsonCreator + public FixPriceSpecDto(@JsonProperty("priceQuote") PriceQuoteDto priceQuote) { + this.priceQuote = priceQuote; + } +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/offer/price/spec/FloatPriceSpecDto.java b/http-api/src/main/java/bisq/dto/offer/price/spec/FloatPriceSpecDto.java new file mode 100644 index 0000000000..a0ee7da649 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/price/spec/FloatPriceSpecDto.java @@ -0,0 +1,36 @@ +/* + * 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 . + */ + +package bisq.dto.offer.price.spec; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@EqualsAndHashCode(callSuper = true) +public class FloatPriceSpecDto extends PriceSpecDto { + private final double percentage; + + @JsonCreator + public FloatPriceSpecDto(@JsonProperty("percentage") double percentage) { + this.percentage = percentage; + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/price/spec/MarketPriceSpecDto.java b/http-api/src/main/java/bisq/dto/offer/price/spec/MarketPriceSpecDto.java new file mode 100644 index 0000000000..382ff00a06 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/price/spec/MarketPriceSpecDto.java @@ -0,0 +1,31 @@ +/* + * 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 . + */ + +package bisq.dto.offer.price.spec; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@EqualsAndHashCode(callSuper = true) +//@JsonIgnoreProperties(ignoreUnknown = true) +public class MarketPriceSpecDto extends PriceSpecDto { + public MarketPriceSpecDto() { + } +} diff --git a/http-api/src/main/java/bisq/dto/offer/price/spec/PriceSpecDto.java b/http-api/src/main/java/bisq/dto/offer/price/spec/PriceSpecDto.java new file mode 100644 index 0000000000..3916d377c2 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/offer/price/spec/PriceSpecDto.java @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +package bisq.dto.offer.price.spec; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode +@Getter +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = FixPriceSpecDto.class, name = "FixPriceSpec"), + @JsonSubTypes.Type(value = FloatPriceSpecDto.class, name = "FloatPriceSpec"), + @JsonSubTypes.Type(value = MarketPriceSpecDto.class, name = "MarketPriceSpec") +}) + +public abstract class PriceSpecDto { + protected PriceSpecDto() { + } +} diff --git a/http-api/src/main/java/bisq/dto/security/keys/KeyPairDto.java b/http-api/src/main/java/bisq/dto/security/keys/KeyPairDto.java new file mode 100644 index 0000000000..44bc46e392 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/security/keys/KeyPairDto.java @@ -0,0 +1,21 @@ +/* + * 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 . + */ + +package bisq.dto.security.keys; + +public record KeyPairDto(PublicKeyDto publicKey, PrivateKeyDto privateKey) { +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/security/keys/PrivateKeyDto.java b/http-api/src/main/java/bisq/dto/security/keys/PrivateKeyDto.java new file mode 100644 index 0000000000..57fb23e1a2 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/security/keys/PrivateKeyDto.java @@ -0,0 +1,22 @@ +/* + * 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 . + */ + +package bisq.dto.security.keys; + + +public record PrivateKeyDto(String encoded) { +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/security/keys/PubKeyDto.java b/http-api/src/main/java/bisq/dto/security/keys/PubKeyDto.java new file mode 100644 index 0000000000..befd24201b --- /dev/null +++ b/http-api/src/main/java/bisq/dto/security/keys/PubKeyDto.java @@ -0,0 +1,21 @@ +/* + * 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 . + */ + +package bisq.dto.security.keys; + +public record PubKeyDto(PublicKeyDto publicKey, String keyId, String hash, String id) { +} diff --git a/http-api/src/main/java/bisq/dto/security/keys/PublicKeyDto.java b/http-api/src/main/java/bisq/dto/security/keys/PublicKeyDto.java new file mode 100644 index 0000000000..da0354e285 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/security/keys/PublicKeyDto.java @@ -0,0 +1,22 @@ +/* + * 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 . + */ + +package bisq.dto.security.keys; + + +public record PublicKeyDto(String encoded) { +} \ No newline at end of file diff --git a/http-api/src/main/java/bisq/dto/security/pow/ProofOfWorkDto.java b/http-api/src/main/java/bisq/dto/security/pow/ProofOfWorkDto.java new file mode 100644 index 0000000000..6d661320c5 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/security/pow/ProofOfWorkDto.java @@ -0,0 +1,29 @@ +/* + * 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 . + */ + +package bisq.dto.security.pow; + +import javax.annotation.Nullable; + +public record ProofOfWorkDto( + String payload, + long counter, + @Nullable String challenge, + double difficulty, + String solution, + long duration) { +} diff --git a/http-api/src/main/java/bisq/dto/user/profile/UserProfileDto.java b/http-api/src/main/java/bisq/dto/user/profile/UserProfileDto.java new file mode 100644 index 0000000000..51df686dca --- /dev/null +++ b/http-api/src/main/java/bisq/dto/user/profile/UserProfileDto.java @@ -0,0 +1,34 @@ +/* + * 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 . + */ + +package bisq.dto.user.profile; + +import bisq.dto.network.identity.NetworkIdDto; +import bisq.dto.security.pow.ProofOfWorkDto; + +public record UserProfileDto(int version, + String nickName, + ProofOfWorkDto proofOfWork, + int avatarVersion, + NetworkIdDto networkId, + String terms, + String statement, + String applicationVersion, + String nym, + String userName, + long publishDate) { +} diff --git a/http-api/src/main/java/bisq/dto/user/reputation/ReputationScoreDto.java b/http-api/src/main/java/bisq/dto/user/reputation/ReputationScoreDto.java new file mode 100644 index 0000000000..cf6dcce0a2 --- /dev/null +++ b/http-api/src/main/java/bisq/dto/user/reputation/ReputationScoreDto.java @@ -0,0 +1,21 @@ +/* + * 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 . + */ + +package bisq.dto.user.reputation; + +public record ReputationScoreDto(long totalScore, double fiveSystemScore, int ranking) { +} diff --git a/offer/src/main/java/bisq/offer/bisq_easy/BisqEasyOffer.java b/offer/src/main/java/bisq/offer/bisq_easy/BisqEasyOffer.java index 378ea66a68..be58de5e2b 100644 --- a/offer/src/main/java/bisq/offer/bisq_easy/BisqEasyOffer.java +++ b/offer/src/main/java/bisq/offer/bisq_easy/BisqEasyOffer.java @@ -78,7 +78,7 @@ public BisqEasyOffer(NetworkId makerNetworkId, ); } - private BisqEasyOffer(String id, + public BisqEasyOffer(String id, long date, NetworkId makerNetworkId, Direction direction, diff --git a/user/src/main/java/bisq/user/profile/UserProfile.java b/user/src/main/java/bisq/user/profile/UserProfile.java index 632e4e1325..547b66a089 100644 --- a/user/src/main/java/bisq/user/profile/UserProfile.java +++ b/user/src/main/java/bisq/user/profile/UserProfile.java @@ -133,7 +133,7 @@ private UserProfile(String nickName, ApplicationVersion.getVersion().getVersionAsString()); } - private UserProfile(int version, + public UserProfile(int version, String nickName, ProofOfWork proofOfWork, int avatarVersion, From 7d93a2010108d55a219d565e3e2e0d6a83b77ec4 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 16:12:25 +0700 Subject: [PATCH 12/20] Use PriceQuoteDto in MarketPriceRestApi instead of long --- .../domain/market_price/MarketPriceResponse.java | 12 +++--------- .../domain/market_price/MarketPriceRestApi.java | 6 ++++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceResponse.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceResponse.java index c500bdf00f..5b36fec67e 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceResponse.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceResponse.java @@ -17,20 +17,14 @@ package bisq.http_api.rest_api.domain.market_price; +import bisq.dto.common.monetary.PriceQuoteDto; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; import java.util.Map; /** * Response DTO for market price quotes. */ -@Getter -public class MarketPriceResponse { - @Schema(description = "Map of currency codes to market price quotes") - private final Map quotes; - - public MarketPriceResponse(Map quotes) { - this.quotes = quotes; - } +public record MarketPriceResponse( + @Schema(description = "Map of currency codes to market price quotes") Map quotes) { } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceRestApi.java index 4635c58176..28b38b199b 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceRestApi.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceRestApi.java @@ -20,6 +20,8 @@ import bisq.bonded_roles.market_price.MarketPrice; import bisq.bonded_roles.market_price.MarketPriceService; import bisq.common.currency.Market; +import bisq.dto.DtoMappings; +import bisq.dto.common.monetary.PriceQuoteDto; import bisq.http_api.rest_api.domain.RestApiBase; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -67,12 +69,12 @@ public MarketPriceRestApi(MarketPriceService marketPriceService) { public Response getQuotes() { try { Map marketPriceByCurrencyMap = marketPriceService.getMarketPriceByCurrencyMap(); - Map result = marketPriceService.getMarketPriceByCurrencyMap() + Map result = marketPriceService.getMarketPriceByCurrencyMap() .entrySet().stream() .filter(entry->entry.getKey().getBaseCurrencyCode().equals("BTC")) // We get altcoin quotes as well .collect(Collectors.toMap( entry -> entry.getKey().getQuoteCurrencyCode(), - entry -> entry.getValue().getPriceQuote().getValue() + entry -> DtoMappings.PriceQuoteMapping.from(entry.getValue().getPriceQuote()) )); if (result.isEmpty()) { From 5d107af17388bd6b5714fdef9dab6626a7833950 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 16:15:08 +0700 Subject: [PATCH 13/20] Use new dto classes --- .../domain/offerbook/OfferListItemDto.java | 99 ------------------- .../domain/offerbook/OfferbookRestApi.java | 96 ++---------------- .../domain/offerbook/ReputationScoreDto.java | 38 ------- 3 files changed, 9 insertions(+), 224 deletions(-) delete mode 100644 http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/OfferListItemDto.java delete mode 100644 http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/ReputationScoreDto.java diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/OfferListItemDto.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/OfferListItemDto.java deleted file mode 100644 index 500befd577..0000000000 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/OfferListItemDto.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 . - */ - -package bisq.http_api.rest_api.domain.offerbook; - -import bisq.offer.Direction; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; -import lombok.ToString; - -import java.util.List; - -@Getter -@ToString -@Schema(name = "OfferListItem", description = "Detailed information about an offer in the offerbook.") -public class OfferListItemDto { - @Schema(description = "Unique identifier for the message.", example = "msg-123456") - private final String messageId; - @Schema(description = "Unique identifier for the offer.", example = "offer-987654") - private final String offerId; - @JsonProperty("isMyMessage") - @Schema(description = "Indicates whether this message belongs to the current user.", example = "true") - private final boolean isMyMessage; - @Schema(description = "Direction of the offer (buy or sell).", implementation = Direction.class) - private final Direction direction; - @Schema(description = "Quote currency code of the offer.", example = "USD") - private final String quoteCurrencyCode; - @Schema(description = "Title of the offer.", example = "Buy 1 BTC at $30,000") - private final String offerTitle; - @Schema(description = "Timestamp of the offer in milliseconds since epoch.", example = "1672531200000") - private final long date; - @Schema(description = "Formatted date string for the offer.", example = "2023-01-01 12:00:00") - private final String formattedDate; - @Schema(description = "Anonymous pseudonym of the user.", example = "Nym123") - private final String nym; - @Schema(description = "Username of the offer's creator.", example = "Alice") - private final String userName; - @Schema(description = "Reputation score of the user who created the offer.", implementation = ReputationScoreDto.class) - private final ReputationScoreDto reputationScore; - @Schema(description = "Formatted amount for the quoted currency.", example = "30,000 USD") - private final String formattedQuoteAmount; - @Schema(description = "Formatted price of the offer.", example = "$30,000 per BTC") - private final String formattedPrice; - @Schema(description = "List of payment methods supported by the quote side.", example = "[\"Bank Transfer\", \"PayPal\"]") - private final List quoteSidePaymentMethods; - @Schema(description = "List of payment methods supported by the base side.", example = "[\"Cash Deposit\"]") - private final List baseSidePaymentMethods; - @Schema(description = "Supported language codes for the offer.", example = "en,es,fr") - private final String supportedLanguageCodes; - - public OfferListItemDto(String messageId, - String offerId, - boolean isMyMessage, - Direction direction, - String quoteCurrencyCode, - String offerTitle, - long date, - String formattedDate, - String nym, - String userName, - ReputationScoreDto reputationScore, - String formattedQuoteAmount, - String formattedPrice, - List quoteSidePaymentMethods, - List baseSidePaymentMethods, - String supportedLanguageCodes) { - this.messageId = messageId; - this.offerId = offerId; - this.isMyMessage = isMyMessage; - this.direction = direction; - this.quoteCurrencyCode = quoteCurrencyCode; - this.offerTitle = offerTitle; - this.date = date; - this.formattedDate = formattedDate; - this.nym = nym; - this.userName = userName; - this.reputationScore = reputationScore; - this.formattedQuoteAmount = formattedQuoteAmount; - this.formattedPrice = formattedPrice; - this.quoteSidePaymentMethods = quoteSidePaymentMethods; - this.baseSidePaymentMethods = baseSidePaymentMethods; - this.supportedLanguageCodes = supportedLanguageCodes; - } -} diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/OfferbookRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/OfferbookRestApi.java index 8177f67229..2ecc65a076 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/OfferbookRestApi.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/OfferbookRestApi.java @@ -17,31 +17,19 @@ package bisq.http_api.rest_api.domain.offerbook; -import bisq.account.payment_method.PaymentMethod; import bisq.bonded_roles.market_price.MarketPriceService; import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannel; import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannelService; import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookMessage; import bisq.common.currency.Market; import bisq.common.currency.MarketRepository; +import bisq.dto.OfferListItemDtoFactory; +import bisq.dto.offer.bisq_easy.OfferListItemDto; import bisq.http_api.rest_api.domain.RestApiBase; -import bisq.common.util.StringUtils; -import bisq.i18n.Res; -import bisq.offer.Direction; -import bisq.offer.amount.OfferAmountFormatter; -import bisq.offer.amount.spec.AmountSpec; -import bisq.offer.amount.spec.RangeAmountSpec; -import bisq.offer.bisq_easy.BisqEasyOffer; -import bisq.offer.payment_method.PaymentMethodSpecUtil; -import bisq.offer.price.spec.PriceSpec; -import bisq.offer.price.spec.PriceSpecFormatter; -import bisq.presentation.formatters.DateFormatter; import bisq.user.UserService; import bisq.user.identity.UserIdentityService; -import bisq.user.profile.UserProfile; import bisq.user.profile.UserProfileService; import bisq.user.reputation.ReputationService; -import com.google.common.base.Joiner; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -52,8 +40,6 @@ import jakarta.ws.rs.core.Response; import lombok.extern.slf4j.Slf4j; -import java.text.DateFormat; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; @@ -187,81 +173,17 @@ private Optional> findOffer(String marketCodes) { .map(channel -> channel.getChatMessages() .stream() .filter(BisqEasyOfferbookMessage::hasBisqEasyOffer) - .map(message -> { - BisqEasyOffer bisqEasyOffer = message.getBisqEasyOffer().orElseThrow(); - long date = message.getDate(); - String formattedDate = DateFormatter.formatDateTime(new Date(date), DateFormat.MEDIUM, DateFormat.SHORT, - true, " " + Res.get("temporal.at") + " "); - String authorUserProfileId = message.getAuthorUserProfileId(); - Optional senderUserProfile = userProfileService.findUserProfile(authorUserProfileId); - String nym = senderUserProfile.map(UserProfile::getNym).orElse(""); - String userName = senderUserProfile.map(UserProfile::getUserName).orElse(""); - - ReputationScoreDto reputationScore = senderUserProfile.flatMap(reputationService::findReputationScore) - .map(score -> new ReputationScoreDto( - score.getTotalScore(), - score.getFiveSystemScore(), - score.getRanking() - )) - .orElse(new ReputationScoreDto(0, 0, 0)); - AmountSpec amountSpec = bisqEasyOffer.getAmountSpec(); - PriceSpec priceSpec = bisqEasyOffer.getPriceSpec(); - boolean hasAmountRange = amountSpec instanceof RangeAmountSpec; - // Market market= bisqEasyOffer.getMarket(); - String formattedQuoteAmount = OfferAmountFormatter.formatQuoteAmount( - marketPriceService, - amountSpec, - priceSpec, - market, - hasAmountRange, - true - ); - String formattedPrice = PriceSpecFormatter.getFormattedPriceSpec(priceSpec, true); - - List quoteSidePaymentMethods = PaymentMethodSpecUtil.getPaymentMethods(bisqEasyOffer.getQuoteSidePaymentMethodSpecs()) - .stream() - .map(PaymentMethod::getName) - .collect(Collectors.toList()); - - List baseSidePaymentMethods = PaymentMethodSpecUtil.getPaymentMethods(bisqEasyOffer.getBaseSidePaymentMethodSpecs()) - .stream() - .map(PaymentMethod::getName) - .collect(Collectors.toList()); - - String supportedLanguageCodes = Joiner.on(",").join(bisqEasyOffer.getSupportedLanguageCodes()); - boolean isMyMessage = message.isMyMessage(userIdentityService); - Direction direction = bisqEasyOffer.getDirection(); - String offerTitle = getOfferTitle(message, direction, isMyMessage); - String messageId = message.getId(); - String offerId = bisqEasyOffer.getId(); - return new OfferListItemDto(messageId, - offerId, - isMyMessage, - direction, - market.getQuoteCurrencyCode(), - offerTitle, - date, - formattedDate, - nym, - userName, - reputationScore, - formattedQuoteAmount, - formattedPrice, - quoteSidePaymentMethods, - baseSidePaymentMethods, - supportedLanguageCodes); - }) + .map(this::createOfferListItemDto) .collect(Collectors.toList()) ) ); } - private String getOfferTitle(BisqEasyOfferbookMessage message, Direction direction, boolean isMyMessage) { - if (isMyMessage) { - String directionString = StringUtils.capitalize(Res.get("offer." + direction.name().toLowerCase())); - return Res.get("bisqEasy.tradeWizard.review.chatMessage.myMessageTitle", directionString); - } else { - return message.getText(); - } + private OfferListItemDto createOfferListItemDto(BisqEasyOfferbookMessage bisqEasyOfferbookMessage) { + return OfferListItemDtoFactory.createOfferListItemDto(userProfileService, + userIdentityService, + reputationService, + marketPriceService, + bisqEasyOfferbookMessage); } } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/ReputationScoreDto.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/ReputationScoreDto.java deleted file mode 100644 index d1184d225e..0000000000 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/offerbook/ReputationScoreDto.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 . - */ - -package bisq.http_api.rest_api.domain.offerbook; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; - -@Getter -@Schema(name = "ReputationScoreDto", description = "User reputation details including total score, 5-star rating, and ranking.") -public class ReputationScoreDto { - @Schema(description = "Total reputation score of the user.", example = "1500") - private final long totalScore; - @Schema(description = "5-star system equivalent score (out of 5).", example = "4.8") - private final double fiveSystemScore; - @Schema(description = "User's ranking among peers.", example = "12") - private final int ranking; - - public ReputationScoreDto(long totalScore, double fiveSystemScore, int ranking) { - this.totalScore = totalScore; - this.fiveSystemScore = fiveSystemScore; - this.ranking = ranking; - } -} From 7e75f43f31d0d7cb58ce4268eea70fce373adf4d Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 16:21:31 +0700 Subject: [PATCH 14/20] Use new UserIdentityPreparation. Rename path and methods --- .../CreateUserIdentityRequest.java | 2 +- ...Data.java => UserIdentityPreparation.java} | 22 ++++++--------- .../user_identity/UserIdentityRestApi.java | 28 ++++++++++++------- 3 files changed, 27 insertions(+), 25 deletions(-) rename http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/{PreparedData.java => UserIdentityPreparation.java} (73%) diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityRequest.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityRequest.java index 56c6187682..239e5e12e2 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityRequest.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityRequest.java @@ -33,5 +33,5 @@ public class CreateUserIdentityRequest { private String statement = ""; @Schema(description = "Prepared data as JSON object", required = true) - private PreparedData preparedData; + private UserIdentityPreparation userIdentityPreparation; } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/PreparedData.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityPreparation.java similarity index 73% rename from http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/PreparedData.java rename to http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityPreparation.java index 6369f44072..57336dc046 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/PreparedData.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityPreparation.java @@ -17,33 +17,27 @@ package bisq.http_api.rest_api.domain.user_identity; -import bisq.security.keys.JsonSerialization; -import bisq.security.pow.ProofOfWork; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import bisq.dto.security.keys.KeyPairDto; +import bisq.dto.security.pow.ProofOfWorkDto; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; -import java.security.KeyPair; - @Getter -@Schema(name = "PreparedData") -public final class PreparedData { - @JsonSerialize(using = JsonSerialization.KeyPair.Serializer.class) - @JsonDeserialize(using = JsonSerialization.KeyPair.Deserializer.class) +@Schema(name = "UserIdentityPreparation") +public final class UserIdentityPreparation { @Schema(description = "Key pair", example = "{ \"privateKey\": \"MIGNAgEAMBAGByqGSM49AgEGBSuBBAAKBHYwdAIBAQQgky6PNO163DColHrGmSNMgY93amwpAO8ZA8/Pb+Xl5magBwYFK4EEAAqhRANCAARyZim9kPgZixR2+ALUs72fO2zzSkeV89w4oQpkRUct5ob4yHRIIwwrggjoCGmNUWqX/pNA18R46vNYTp8NWuSu\", \"publicKey\": \"MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEcmYpvZD4GYsUdvgC1LO9nzts80pHlfPcOKEKZEVHLeaG+Mh0SCMMK4II6AhpjVFql/6TQNfEeOrzWE6fDVrkrg==\" }") - private KeyPair keyPair; + private KeyPairDto keyPair; @Schema(description = "ID", example = "b0edc477ec967379867ae44b1e030fa4f8e68327") private String id; @Schema(description = "Nym", example = "Ravenously-Poignant-Coordinate-695") private String nym; @Schema(description = "User statement", example = "{ \"payload\": \"[-80, -19, -60, 119, -20, -106, 115, 121, -122, 122, -28, 75, 30, 3, 15, -92, -8, -26, -125, 39]\", \"counter\": 93211, \"difficulty\": 65536.0, \"solution\": [0, 0, 0, 0, 0, 1, 108, 27], \"duration\": 19 }") - private ProofOfWork proofOfWork; + private ProofOfWorkDto proofOfWork; - public static PreparedData from(KeyPair keyPair, String id, String nym, ProofOfWork proofOfWork) { - PreparedData dto = new PreparedData(); + public static UserIdentityPreparation from(KeyPairDto keyPair, String id, String nym, ProofOfWorkDto proofOfWork) { + UserIdentityPreparation dto = new UserIdentityPreparation(); dto.keyPair = keyPair; dto.id = id; dto.nym = nym; diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java index 71dde9fb13..8b3f4fda96 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java @@ -18,6 +18,9 @@ package bisq.http_api.rest_api.domain.user_identity; import bisq.common.encoding.Hex; +import bisq.dto.DtoMappings; +import bisq.dto.security.keys.KeyPairDto; +import bisq.dto.security.pow.ProofOfWorkDto; import bisq.http_api.rest_api.domain.RestApiBase; import bisq.security.DigestUtil; import bisq.security.SecurityService; @@ -60,25 +63,27 @@ public UserIdentityRestApi(SecurityService securityService, UserIdentityService } @GET - @Path("/prepared-data") + @Path("/preparation") @Operation( summary = "Generate Prepared Data", description = "Generates a key pair, public key hash, Nym, and proof of work for a new user identity.", responses = { @ApiResponse(responseCode = "201", description = "Prepared data generated successfully", - content = @Content(schema = @Schema(implementation = PreparedData.class))), + content = @Content(schema = @Schema(implementation = UserIdentityPreparation.class))), @ApiResponse(responseCode = "500", description = "Internal server error") } ) - public Response createPreparedData() { + public Response getUserIdentityPreparation() { try { KeyPair keyPair = securityService.getKeyBundleService().generateKeyPair(); byte[] pubKeyHash = DigestUtil.hash(keyPair.getPublic().getEncoded()); String id = Hex.encode(pubKeyHash); ProofOfWork proofOfWork = userIdentityService.mintNymProofOfWork(pubKeyHash); String nym = NymIdGenerator.generate(pubKeyHash, proofOfWork.getSolution()); - PreparedData preparedData = PreparedData.from(keyPair, id, nym, proofOfWork); - return buildResponse(Response.Status.CREATED, preparedData); + KeyPairDto keyPairDto = DtoMappings.KeyPairDtoMapping.from(keyPair); + ProofOfWorkDto proofOfWorkDto = DtoMappings.ProofOfWorkDtoMapping.from(proofOfWork); + UserIdentityPreparation userIdentityPreparation = UserIdentityPreparation.from(keyPairDto, id, nym, proofOfWorkDto); + return buildResponse(Response.Status.CREATED, userIdentityPreparation); } catch (Exception e) { log.error("Error generating prepared data", e); return buildErrorResponse("Could not generate prepared data."); @@ -147,7 +152,7 @@ public Response getSelectedUserProfile() { @POST @Operation( - summary = "Create and Publish User Identity", + summary = "Create User Identity and Publish User Profile", description = "Creates a new user identity and publishes the associated user profile.", requestBody = @RequestBody( description = "Request payload containing user nickname, terms, statement, and prepared data.", @@ -160,16 +165,19 @@ public Response getSelectedUserProfile() { @ApiResponse(responseCode = "500", description = "Internal server error") } ) - public Response createUserIdentityAndPublishUserProfile(CreateUserIdentityRequest request) { + public Response createAndPublishNewUserProfile(CreateUserIdentityRequest request) { try { - PreparedData preparedData = request.getPreparedData(); - KeyPair keyPair = preparedData.getKeyPair(); + UserIdentityPreparation userIdentityPreparation = request.getUserIdentityPreparation(); + KeyPairDto keyPairDto = userIdentityPreparation.getKeyPair(); + KeyPair keyPair = DtoMappings.KeyPairDtoMapping.toPojo(keyPairDto); byte[] pubKeyHash = DigestUtil.hash(keyPair.getPublic().getEncoded()); int avatarVersion = 0; + ProofOfWorkDto proofOfWorkDto = userIdentityPreparation.getProofOfWork(); + ProofOfWork proofOfWork = DtoMappings.ProofOfWorkDtoMapping.toPojo(proofOfWorkDto); UserIdentity userIdentity = userIdentityService.createAndPublishNewUserProfile(request.getNickName(), keyPair, pubKeyHash, - preparedData.getProofOfWork(), + proofOfWork, avatarVersion, request.getTerms(), request.getStatement()).get(); From e07ce39e9b409f3ff0c9af1f6c28dabc071fa9b9 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 16:28:53 +0700 Subject: [PATCH 15/20] Add jackson-datatype-jdk8 dependency --- gradle/libs.versions.toml | 10 ++++++---- .../bisq/http_api/web_socket/WebSocketService.java | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 207d57ad69..baa733f860 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -129,6 +129,8 @@ i2p-router = { module = 'net.i2p:router', version.ref = 'i2p-lib' } jackson-core = { module = 'com.fasterxml.jackson.core:jackson-core', version.ref = 'jackson-lib' } jackson-annotations = { module = 'com.fasterxml.jackson.core:jackson-annotations', version.ref = 'jackson-lib' } jackson-databind = { module = 'com.fasterxml.jackson.core:jackson-databind', version.ref = 'jackson-lib' } +jackson-datatype = { module = 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8', version.ref = 'jackson-lib' } + jakarta-websocket = { module = 'jakarta.websocket:jakarta.websocket-api', version.ref = 'jakarta-lib' } javacv = { module = "org.bytedeco:javacv-platform", version.ref = "javacv" } @@ -177,13 +179,13 @@ glassfish-jersey = ['glassfish-jersey-jdk-http', 'glassfish-jersey-json-jackson' grpc = ['grpc-protobuf', 'grpc-services', 'grpc-stub'] i2p = ['i2p-core', 'i2p-router', 'i2p-streaming'] i2p-v2 = ['i2p-core-v2', 'i2p-streaming-v2'] -jackson = ['jackson-core', 'jackson-annotations', 'jackson-databind'] +jackson = ['jackson-core', 'jackson-annotations', 'jackson-databind', 'jackson-datatype'] springfox-libs = ['springfox-boot-starter', 'springfox-swagger2', 'springfox-swagger-ui'] rest-api-libs = ['swagger-jaxrs2-jakarta', 'glassfish-jersey-jdk-http', 'glassfish-jersey-json-jackson', - 'glassfish-jersey-inject-hk2', - 'glassfish-jaxb-runtime', 'jackson-core', 'jackson-annotations', 'jackson-databind'] + 'glassfish-jersey-inject-hk2', 'glassfish-jaxb-runtime', + 'jackson-core', 'jackson-annotations', 'jackson-databind', 'jackson-datatype'] websocket-libs = ['glassfish-jersey-json-jackson', 'glassfish-jersey-server', 'glassfish-jersey-containers-grizzly', - 'glassfish-grizzly-websockets-server', 'jakarta-websocket', 'jackson-databind', 'swagger-swagger-annotations'] + 'glassfish-grizzly-websockets-server', 'jakarta-websocket', 'jackson-databind', 'jackson-datatype', 'swagger-swagger-annotations'] # Referenced in subproject's build.gradle > plugin block as alias: `alias(libs.plugins.protobuf)` # Note: plugin version constraints are not supported by the java-platform plugin, so cannot be enforced there. However, diff --git a/http-api/src/main/java/bisq/http_api/web_socket/WebSocketService.java b/http-api/src/main/java/bisq/http_api/web_socket/WebSocketService.java index 86a576485a..3fdc020181 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/WebSocketService.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/WebSocketService.java @@ -25,6 +25,7 @@ import bisq.http_api.web_socket.util.GrizzlySwaggerHttpHandler; import bisq.user.UserService; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import jakarta.ws.rs.core.UriBuilder; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -116,6 +117,8 @@ public WebSocketService(Config config, this.config = config; this.restApiResourceConfig = restApiResourceConfig; ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new Jdk8Module()); + //objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); subscriptionService = new SubscriptionService(objectMapper, bondedRolesService, chatService, userService); From 0a90d990143e48fb4ab292ea037c6f9b090268e1 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 16:36:01 +0700 Subject: [PATCH 16/20] Add WebSocketMessage interface and add JsonSubTypes annotations for polymorphism support. Remove className, responseClassName and webSocketEventClassName as it is handled by the polymorphism support in jackson/kotlinx. --- .../rest_api/domain/offer/OfferRestApi.java | 272 ++++++++++++++++++ .../domain/offer/PublishOfferRequest.java | 34 +++ .../domain/offer/PublishOfferResponse.java | 21 ++ .../domain/offer/TakeOfferRequest.java | 24 ++ .../domain/offer/TakeOfferResponse.java | 21 ++ .../http_api/web_socket/WebSocketMessage.java | 42 +++ .../WebSocketRestApiRequest.java | 5 +- .../WebSocketRestApiResponse.java | 9 +- .../web_socket/subscription/Subscriber.java | 5 +- .../subscription/SubscriberRepository.java | 2 +- .../subscription/SubscriptionRequest.java | 11 +- .../subscription/SubscriptionResponse.java | 9 +- .../subscription/SubscriptionService.java | 2 +- .../subscription/WebSocketEvent.java | 12 +- 14 files changed, 431 insertions(+), 38 deletions(-) create mode 100644 http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java create mode 100644 http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferRequest.java create mode 100644 http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferResponse.java create mode 100644 http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferRequest.java create mode 100644 http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferResponse.java create mode 100644 http-api/src/main/java/bisq/http_api/web_socket/WebSocketMessage.java diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java new file mode 100644 index 0000000000..a0c9ed2f6a --- /dev/null +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java @@ -0,0 +1,272 @@ +/* + * 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 . + */ + +package bisq.http_api.rest_api.domain.offer; + +import bisq.account.payment_method.BitcoinPaymentMethod; +import bisq.account.payment_method.BitcoinPaymentMethodUtil; +import bisq.account.payment_method.FiatPaymentMethod; +import bisq.account.payment_method.FiatPaymentMethodUtil; +import bisq.bisq_easy.BisqEasyServiceUtil; +import bisq.bonded_roles.market_price.MarketPriceService; +import bisq.chat.ChatChannelDomain; +import bisq.chat.ChatChannelSelectionService; +import bisq.chat.ChatService; +import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannelService; +import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookMessage; +import bisq.chat.bisqeasy.open_trades.BisqEasyOpenTradeChannelService; +import bisq.common.currency.Market; +import bisq.common.monetary.Monetary; +import bisq.common.util.StringUtils; +import bisq.contract.bisq_easy.BisqEasyContract; +import bisq.dto.DtoMappings; +import bisq.http_api.rest_api.domain.RestApiBase; +import bisq.i18n.Res; +import bisq.offer.Direction; +import bisq.offer.amount.spec.AmountSpec; +import bisq.offer.bisq_easy.BisqEasyOffer; +import bisq.offer.payment_method.BitcoinPaymentMethodSpec; +import bisq.offer.payment_method.FiatPaymentMethodSpec; +import bisq.offer.payment_method.PaymentMethodSpecUtil; +import bisq.offer.price.spec.PriceSpec; +import bisq.support.SupportService; +import bisq.support.mediation.MediationRequestService; +import bisq.trade.TradeService; +import bisq.trade.bisq_easy.BisqEasyTrade; +import bisq.trade.bisq_easy.BisqEasyTradeService; +import bisq.trade.bisq_easy.protocol.BisqEasyProtocol; +import bisq.user.UserService; +import bisq.user.banned.BannedUserService; +import bisq.user.identity.UserIdentity; +import bisq.user.identity.UserIdentityService; +import bisq.user.profile.UserProfile; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkArgument; + +@Slf4j +@Path("/offers") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Bisq Easy Offer API") +public class OfferRestApi extends RestApiBase { + private final BisqEasyOfferbookChannelService bisqEasyOfferbookChannelService; + private final ChatService chatService; + private final MarketPriceService marketPriceService; + private final UserIdentityService userIdentityService; + private final BannedUserService bannedUserService; + private final MediationRequestService mediationRequestService; + private final BisqEasyTradeService bisqEasyTradeService; + private final BisqEasyOpenTradeChannelService bisqEasyOpenTradeChannelService; + + public OfferRestApi(ChatService chatService, + MarketPriceService marketPriceService, + UserService userService, + SupportService supportedService, + TradeService tradeService) { + this.bisqEasyOfferbookChannelService = chatService.getBisqEasyOfferbookChannelService(); + this.chatService = chatService; + this.marketPriceService = marketPriceService; + userIdentityService = userService.getUserIdentityService(); + bannedUserService = userService.getBannedUserService(); + mediationRequestService = supportedService.getMediationRequestService(); + bisqEasyTradeService = tradeService.getBisqEasyTradeService(); + bisqEasyOpenTradeChannelService = chatService.getBisqEasyOpenTradeChannelService(); + } + + // todo use AsyncResponse + @POST + @Operation( + summary = "Take Bisq Easy Offer", + description = "Takes a Bisq Easy Offer.", + requestBody = @RequestBody( + description = "", + content = @Content(schema = @Schema(implementation = PublishOfferRequest.class)) + ), + responses = { + @ApiResponse(responseCode = "201", description = "", + content = @Content(schema = @Schema(example = ""))), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "500", description = "Internal server error") + } + ) + @Path("{offerId}/take") + public Response takeOffer(@PathParam("offerId") String offerId, TakeOfferRequest request) { + try { + UserIdentity takerIdentity = userIdentityService.getSelectedUserIdentity(); + checkArgument(!bannedUserService.isUserProfileBanned(takerIdentity.getUserProfile()), "Taker profile is banned"); + //noinspection OptionalGetWithoutIsPresent + BisqEasyOffer bisqEasyOffer = bisqEasyOfferbookChannelService.getChannels().stream().flatMap(c -> c.getChatMessages().stream()) + .filter(BisqEasyOfferbookMessage::hasBisqEasyOffer) + .map(e -> e.getBisqEasyOffer().get()) + .filter(e -> e.getId().equals(offerId)) + .findFirst() + .orElseThrow(); + checkArgument(!bannedUserService.isNetworkIdBanned(bisqEasyOffer.getMakerNetworkId()), "Maker profile is banned"); + Monetary baseSideAmount = Monetary.from(request.baseSideAmount(), bisqEasyOffer.getMarket().getBaseCurrencyCode()); + Monetary quoteSideAmount = Monetary.from(request.quoteSideAmount(), bisqEasyOffer.getMarket().getBaseCurrencyCode()); + BitcoinPaymentMethod bitcoinPaymentMethod = PaymentMethodSpecUtil.getBitcoinPaymentMethod(request.bitcoinPaymentMethod()); + BitcoinPaymentMethodSpec bitcoinPaymentMethodSpec = new BitcoinPaymentMethodSpec(bitcoinPaymentMethod); + FiatPaymentMethod fiatPaymentMethod = PaymentMethodSpecUtil.getFiatPaymentMethod(request.fiatPaymentMethod()); + FiatPaymentMethodSpec fiatPaymentMethodSpec = new FiatPaymentMethodSpec(fiatPaymentMethod); + Optional mediator = mediationRequestService.selectMediator(bisqEasyOffer.getMakersUserProfileId(), takerIdentity.getId()); + PriceSpec makersPriceSpec = bisqEasyOffer.getPriceSpec(); + long marketPrice = marketPriceService.findMarketPrice(bisqEasyOffer.getMarket()) + .map(e -> e.getPriceQuote().getValue()) + .orElseThrow(); + BisqEasyProtocol bisqEasyProtocol = bisqEasyTradeService.createBisqEasyProtocol(takerIdentity.getIdentity(), + bisqEasyOffer, + baseSideAmount, + quoteSideAmount, + bitcoinPaymentMethodSpec, + fiatPaymentMethodSpec, + mediator, + makersPriceSpec, + marketPrice); + BisqEasyTrade bisqEasyTrade = bisqEasyProtocol.getModel(); + log.info("Selected mediator for trade {}: {}", bisqEasyTrade.getShortId(), mediator.map(UserProfile::getUserName).orElse("N/A")); + + bisqEasyTradeService.takeOffer(bisqEasyTrade); + BisqEasyContract contract = bisqEasyTrade.getContract(); + + String tradeId = bisqEasyTrade.getId(); + // todo set timeout + // asyncResponse.setTimeout(150, TimeUnit.SECONDS); // We have 120 seconds socket timeout, so we should never get triggered here, as the message will be sent as mailbox message + bisqEasyOpenTradeChannelService.sendTakeOfferMessage(tradeId, bisqEasyOffer, contract.getMediator()) + .thenAccept(result -> + { + // In case the user has switched to another market we want to select that market in the offer book + ChatChannelSelectionService chatChannelSelectionService = + chatService.getChatChannelSelectionService(ChatChannelDomain.BISQ_EASY_OFFERBOOK); + bisqEasyOfferbookChannelService.findChannel(contract.getOffer().getMarket()) + .ifPresent(chatChannelSelectionService::selectChannel); + bisqEasyOpenTradeChannelService.findChannelByTradeId(tradeId) + .ifPresent(channel -> { + String taker = userIdentityService.getSelectedUserIdentity().getUserProfile().getUserName(); + String maker = channel.getPeer().getUserName(); + String encoded = Res.encode("bisqEasy.takeOffer.tradeLogMessage", taker, maker); + chatService.getBisqEasyOpenTradeChannelService().sendTradeLogMessage(encoded, channel); + }); + } + ) + .get(); + + String errorMessage = bisqEasyTrade.errorMessageObservable().get(); + checkArgument(errorMessage == null, "An error occurred at taking the offer: " + errorMessage + + ". ErrorStackTrace: " + StringUtils.truncate(bisqEasyTrade.getErrorStackTrace(), 500)); + + String peersErrorMessage = bisqEasyTrade.peersErrorMessageObservable().get(); + checkArgument(peersErrorMessage == null, "An error occurred at the peers side at taking the offer: " + peersErrorMessage + + ". ErrorStackTrace: " + StringUtils.truncate(bisqEasyTrade.getPeersErrorStackTrace(), 500)); + + return buildResponse(Response.Status.OK, new TakeOfferResponse(bisqEasyTrade.getId())); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return buildErrorResponse("Thread was interrupted."); + } catch (IllegalArgumentException e) { + return buildResponse(Response.Status.BAD_REQUEST, "Invalid input: " + e.getMessage()); + } catch (Exception e) { + log.error("Error publishing offer", e); + return buildErrorResponse("An unexpected error occurred."); + } + } + + // todo use AsyncResponse + @POST + @Operation( + summary = "Create and Publish Bisq Easy Offer", + description = "Creates a Bisq Easy Offer and publish it to the network.", + requestBody = @RequestBody( + description = "", + content = @Content(schema = @Schema(implementation = PublishOfferRequest.class)) + ), + responses = { + @ApiResponse(responseCode = "201", description = "", + content = @Content(schema = @Schema(example = ""))), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "500", description = "Internal server error") + } + ) + public Response createOffer(PublishOfferRequest request) { + try { + UserIdentity userIdentity = userIdentityService.getSelectedUserIdentity(); + Direction direction = DtoMappings.DirectionMapping.toPojo(request.direction()); + Market market = DtoMappings.MarketMapping.toPojo(request.market()); + List bitcoinPaymentMethods = request.bitcoinPaymentMethods().stream() + .map(BitcoinPaymentMethodUtil::getPaymentMethod) + .collect(Collectors.toList()); + List fiatPaymentMethods = request.fiatPaymentMethods().stream() + .map(FiatPaymentMethodUtil::getPaymentMethod) + .collect(Collectors.toList()); + AmountSpec amountSpec = DtoMappings.AmountSpecMapping.toPojo(request.amountSpec()); + PriceSpec priceSpec = DtoMappings.PriceSpecMapping.toPojo(request.priceSpec()); + List supportedLanguageCodes = new ArrayList<>(request.supportedLanguageCodes()); + String chatMessageText = BisqEasyServiceUtil.createOfferBookMessageFromPeerPerspective(userIdentity.getNickName(), + marketPriceService, + direction, + market, + bitcoinPaymentMethods, + fiatPaymentMethods, + amountSpec, + priceSpec); + UserProfile userProfile = userIdentity.getUserProfile(); + BisqEasyOffer bisqEasyOffer = new BisqEasyOffer( + userProfile.getNetworkId(), + direction, + market, + amountSpec, + priceSpec, + bitcoinPaymentMethods, + fiatPaymentMethods, + userProfile.getTerms(), + supportedLanguageCodes); + String channelId = bisqEasyOfferbookChannelService.findChannel(market).orElseThrow().getId(); + BisqEasyOfferbookMessage myOfferMessage = new BisqEasyOfferbookMessage(channelId, + userProfile.getId(), + Optional.of(bisqEasyOffer), + Optional.of(chatMessageText), + Optional.empty(), + new Date().getTime(), + false); + bisqEasyOfferbookChannelService.publishChatMessage(myOfferMessage, userIdentity).get(); + return buildResponse(Response.Status.CREATED, new PublishOfferResponse(bisqEasyOffer.getId())); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return buildErrorResponse("Thread was interrupted."); + } catch (IllegalArgumentException e) { + return buildResponse(Response.Status.BAD_REQUEST, "Invalid input: " + e.getMessage()); + } catch (Exception e) { + log.error("Error publishing offer", e); + return buildErrorResponse("An unexpected error occurred."); + } + } +} diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferRequest.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferRequest.java new file mode 100644 index 0000000000..cae0125bc3 --- /dev/null +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferRequest.java @@ -0,0 +1,34 @@ +/* + * 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 . + */ + +package bisq.http_api.rest_api.domain.offer; + +import bisq.dto.common.currency.MarketDto; +import bisq.dto.offer.DirectionDto; +import bisq.dto.offer.amount.spec.AmountSpecDto; +import bisq.dto.offer.price.spec.PriceSpecDto; + +import java.util.Set; + +public record PublishOfferRequest(DirectionDto direction, + MarketDto market, + Set bitcoinPaymentMethods, + Set fiatPaymentMethods, + AmountSpecDto amountSpec, + PriceSpecDto priceSpec, + Set supportedLanguageCodes) { +} diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferResponse.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferResponse.java new file mode 100644 index 0000000000..63d8e55462 --- /dev/null +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferResponse.java @@ -0,0 +1,21 @@ +/* + * 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 . + */ + +package bisq.http_api.rest_api.domain.offer; + +public record PublishOfferResponse(String offerId) { +} diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferRequest.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferRequest.java new file mode 100644 index 0000000000..aae310a95e --- /dev/null +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferRequest.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +package bisq.http_api.rest_api.domain.offer; + +public record TakeOfferRequest(long baseSideAmount, + long quoteSideAmount, + String bitcoinPaymentMethod, + String fiatPaymentMethod) { +} diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferResponse.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferResponse.java new file mode 100644 index 0000000000..84e862c094 --- /dev/null +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferResponse.java @@ -0,0 +1,21 @@ +/* + * 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 . + */ + +package bisq.http_api.rest_api.domain.offer; + +public record TakeOfferResponse(String tradeId) { +} diff --git a/http-api/src/main/java/bisq/http_api/web_socket/WebSocketMessage.java b/http-api/src/main/java/bisq/http_api/web_socket/WebSocketMessage.java new file mode 100644 index 0000000000..5fab38eeef --- /dev/null +++ b/http-api/src/main/java/bisq/http_api/web_socket/WebSocketMessage.java @@ -0,0 +1,42 @@ +/* + * 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 . + */ + +package bisq.http_api.web_socket; + +import bisq.http_api.web_socket.rest_api_proxy.WebSocketRestApiRequest; +import bisq.http_api.web_socket.rest_api_proxy.WebSocketRestApiResponse; +import bisq.http_api.web_socket.subscription.SubscriptionRequest; +import bisq.http_api.web_socket.subscription.SubscriptionResponse; +import bisq.http_api.web_socket.subscription.WebSocketEvent; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = WebSocketRestApiRequest.class, name = "WebSocketRestApiRequest"), + @JsonSubTypes.Type(value = WebSocketRestApiResponse.class, name = "WebSocketRestApiResponse"), + @JsonSubTypes.Type(value = SubscriptionRequest.class, name = "SubscriptionRequest"), + @JsonSubTypes.Type(value = SubscriptionResponse.class, name = "SubscriptionResponse"), + @JsonSubTypes.Type(value = WebSocketEvent.class, name = "WebSocketEvent") +}) +public interface WebSocketMessage { +} + diff --git a/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiRequest.java b/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiRequest.java index 011352bfb9..95ef61d76f 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiRequest.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiRequest.java @@ -17,6 +17,7 @@ package bisq.http_api.web_socket.rest_api_proxy; +import bisq.http_api.web_socket.WebSocketMessage; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.EqualsAndHashCode; @@ -29,15 +30,13 @@ @Getter @EqualsAndHashCode @ToString -public class WebSocketRestApiRequest { +public class WebSocketRestApiRequest implements WebSocketMessage { // Client side full qualified class name for response class required for polymorphism support private String responseClassName; private String requestId; private String path; private String method; private String body; - // Client side full qualified class name set by serialize to support polymorphism - private String className; public static boolean isExpectedJson(String message) { return message.contains("requestId") && diff --git a/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiResponse.java b/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiResponse.java index dd416833e5..efc8324784 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiResponse.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiResponse.java @@ -17,6 +17,7 @@ package bisq.http_api.web_socket.rest_api_proxy; +import bisq.http_api.web_socket.WebSocketMessage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @@ -32,19 +33,15 @@ @Getter @EqualsAndHashCode @ToString -public class WebSocketRestApiResponse { - // Client side full qualified class name required for polymorphism support - private final String className; +public class WebSocketRestApiResponse implements WebSocketMessage { private final String requestId; private final int statusCode; private final String body; @JsonCreator - public WebSocketRestApiResponse(@JsonProperty("className") String className, - @JsonProperty("requestId") String requestId, + public WebSocketRestApiResponse(@JsonProperty("requestId") String requestId, @JsonProperty("statusCode") int statusCode, @JsonProperty("body") String body) { - this.className = className; this.requestId = requestId; this.statusCode = statusCode; this.body = body; diff --git a/http-api/src/main/java/bisq/http_api/web_socket/subscription/Subscriber.java b/http-api/src/main/java/bisq/http_api/web_socket/subscription/Subscriber.java index 7ba2f9c898..bf88566cd3 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/subscription/Subscriber.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/subscription/Subscriber.java @@ -37,20 +37,17 @@ public class Subscriber { private final Optional parameter; private final String subscriberId; private final WebSocket webSocket; - private final String webSocketEventClassName; private final AtomicInteger sequenceNumber = new AtomicInteger(0); // sequenceNumber start with 0 at subscribe time and gets increased at each emitted WebSocketEvent private final ExecutorService executorService; public Subscriber(Topic topic, Optional parameter, String subscriberId, - WebSocket webSocket, - String webSocketEventClassName) { + WebSocket webSocket) { this.topic = topic; this.parameter = parameter; this.subscriberId = subscriberId; this.webSocket = webSocket; - this.webSocketEventClassName = webSocketEventClassName; executorService = ExecutorFactory.newSingleThreadExecutor("Subscriber-" + topic.name() + "-" + subscriberId); } diff --git a/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriberRepository.java b/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriberRepository.java index 94890c918a..d772e035a2 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriberRepository.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriberRepository.java @@ -39,7 +39,7 @@ public void onConnectionClosed(WebSocket webSocket) { public void add(SubscriptionRequest request, WebSocket webSocket) { Topic topic = request.getTopic(); Optional parameter = StringUtils.toOptional(request.getParameter()); - Subscriber subscriber = new Subscriber(topic, parameter, request.getRequestId(), webSocket, request.getWebSocketEventClassName()); + Subscriber subscriber = new Subscriber(topic, parameter, request.getRequestId(), webSocket); synchronized (subscribersByTopicLock) { Set subscribers = subscribersByTopic.computeIfAbsent(topic, key -> new HashSet<>()); subscribers.add(subscriber); diff --git a/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionRequest.java b/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionRequest.java index 86e8b26e72..6311845c8b 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionRequest.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionRequest.java @@ -17,6 +17,7 @@ package bisq.http_api.web_socket.subscription; +import bisq.http_api.web_socket.WebSocketMessage; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.EqualsAndHashCode; @@ -31,20 +32,12 @@ @Getter @EqualsAndHashCode @ToString -public class SubscriptionRequest { - // Client side full qualified class name for response class required for polymorphism support - private String responseClassName; - // Client side full qualified class name for WebSocketEvent class required for polymorphism support - private String webSocketEventClassName; +public class SubscriptionRequest implements WebSocketMessage { private String requestId; private Topic topic; @Nullable private String parameter; - // Client side full qualified class name set by serialize to support polymorphism - private String className; - - public static Optional fromJson(ObjectMapper objectMapper, String json) { try { return Optional.of(objectMapper.readValue(json, SubscriptionRequest.class)); diff --git a/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionResponse.java b/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionResponse.java index fb2973e058..fca4121ff4 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionResponse.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionResponse.java @@ -17,6 +17,7 @@ package bisq.http_api.web_socket.subscription; +import bisq.http_api.web_socket.WebSocketMessage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @@ -34,9 +35,7 @@ @Getter @EqualsAndHashCode @ToString -public class SubscriptionResponse { - // Client side full qualified class name required for polymorphism support - private final String className; +public class SubscriptionResponse implements WebSocketMessage { private final String requestId; @Nullable private final String payload; @@ -44,11 +43,9 @@ public class SubscriptionResponse { private final String errorMessage; @JsonCreator - public SubscriptionResponse(@JsonProperty("className") String className, - @JsonProperty("requestId") String requestId, + public SubscriptionResponse(@JsonProperty("requestId") String requestId, @JsonProperty("payload") @Nullable String payload, @JsonProperty("errorMessage") @Nullable String errorMessage) { - this.className = className; this.requestId = requestId; this.payload = payload; this.errorMessage = errorMessage; diff --git a/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionService.java b/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionService.java index 1a31e995db..f69d20ae1c 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionService.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/subscription/SubscriptionService.java @@ -87,7 +87,7 @@ private void subscribe(SubscriptionRequest request, WebSocket webSocket) { findWebSocketService(request.getTopic()) .flatMap(BaseWebSocketService::getJsonPayload) - .flatMap(json -> new SubscriptionResponse(request.getResponseClassName(), request.getRequestId(), json, null) + .flatMap(json -> new SubscriptionResponse(request.getRequestId(), json, null) .toJson(objectMapper)) .ifPresent(webSocket::send); } diff --git a/http-api/src/main/java/bisq/http_api/web_socket/subscription/WebSocketEvent.java b/http-api/src/main/java/bisq/http_api/web_socket/subscription/WebSocketEvent.java index 0130a7b387..24fcd29e24 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/subscription/WebSocketEvent.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/subscription/WebSocketEvent.java @@ -17,6 +17,7 @@ package bisq.http_api.web_socket.subscription; +import bisq.http_api.web_socket.WebSocketMessage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; @@ -32,9 +33,7 @@ @Getter @EqualsAndHashCode @ToString -public class WebSocketEvent { - // Client side full qualified class name required for polymorphism support - private final String className; +public class WebSocketEvent implements WebSocketMessage { private final Topic topic; private final String subscriberId; private final String payload; @@ -42,13 +41,11 @@ public class WebSocketEvent { private final int sequenceNumber; @JsonCreator - public WebSocketEvent(@JsonProperty("className") String className, - @JsonProperty("topic") Topic topic, + public WebSocketEvent(@JsonProperty("topic") Topic topic, @JsonProperty("subscriberId") String subscriberId, @JsonProperty("payload") String payload, @JsonProperty("modificationType") ModificationType modificationType, @JsonProperty("sequenceNumber") int sequenceNumber) { - this.className = className; this.topic = topic; this.subscriberId = subscriberId; this.payload = payload; @@ -57,14 +54,13 @@ public WebSocketEvent(@JsonProperty("className") String className, } public static Optional toJson(ObjectMapper objectMapper, - String className, Topic topic, String subscriberId, String payload, ModificationType modificationType, int sequenceNumber) { try { - var webSocketEvent = new WebSocketEvent(className, topic, subscriberId, payload, modificationType, sequenceNumber); + var webSocketEvent = new WebSocketEvent(topic, subscriberId, payload, modificationType, sequenceNumber); return Optional.of(objectMapper.writeValueAsString(webSocketEvent)); } catch (JsonProcessingException e) { log.error("Json serialisation failed", e); From 0fc760a62cb24e09e7c7973fe63e145cb7e89cdc Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 17:10:27 +0700 Subject: [PATCH 17/20] Add support for OfferRestApi --- .../DesktopApplicationService.java | 4 +- .../HttpApiApplicationService.java | 4 +- .../java/bisq/http_api/HttpApiService.java | 25 +++- .../rest_api/RestApiResourceConfig.java | 4 + .../user_identity/UserIdentityRestApi.java | 1 + .../WebSocketRestApiResourceConfig.java | 4 +- .../domain/BaseWebSocketService.java | 39 +++++- .../MarketPriceWebSocketService.java | 15 ++- .../offerbook/OffersWebSocketService.java | 111 +++--------------- .../WebSocketRestApiService.java | 10 +- .../http_api/web_socket/util/JsonUtil.java | 9 +- 11 files changed, 107 insertions(+), 119 deletions(-) diff --git a/apps/desktop/desktop-app/src/main/java/bisq/desktop_app/DesktopApplicationService.java b/apps/desktop/desktop-app/src/main/java/bisq/desktop_app/DesktopApplicationService.java index 79841b80d0..ac7219caa7 100644 --- a/apps/desktop/desktop-app/src/main/java/bisq/desktop_app/DesktopApplicationService.java +++ b/apps/desktop/desktop-app/src/main/java/bisq/desktop_app/DesktopApplicationService.java @@ -231,7 +231,9 @@ public DesktopApplicationService(String[] args, ShutDownHandler shutDownHandler) networkService, userService, bondedRolesService, - chatService); + chatService, + supportService, + tradeService); } @Override diff --git a/apps/http-api-app/src/main/java/bisq/http_api_node/HttpApiApplicationService.java b/apps/http-api-app/src/main/java/bisq/http_api_node/HttpApiApplicationService.java index 11be8a4d84..13ac0db91c 100644 --- a/apps/http-api-app/src/main/java/bisq/http_api_node/HttpApiApplicationService.java +++ b/apps/http-api-app/src/main/java/bisq/http_api_node/HttpApiApplicationService.java @@ -169,7 +169,9 @@ public HttpApiApplicationService(String[] args) { networkService, userService, bondedRolesService, - chatService); + chatService, + supportService, + tradeService); } @Override diff --git a/http-api/src/main/java/bisq/http_api/HttpApiService.java b/http-api/src/main/java/bisq/http_api/HttpApiService.java index 127d695751..338b365a3c 100644 --- a/http-api/src/main/java/bisq/http_api/HttpApiService.java +++ b/http-api/src/main/java/bisq/http_api/HttpApiService.java @@ -18,19 +18,22 @@ package bisq.http_api; import bisq.bonded_roles.BondedRolesService; -import bisq.http_api.rest_api.domain.market_price.MarketPriceRestApi; import bisq.chat.ChatService; -import bisq.http_api.rest_api.domain.offerbook.OfferbookRestApi; import bisq.common.application.Service; import bisq.common.util.CompletableFutureUtils; import bisq.http_api.rest_api.RestApiResourceConfig; import bisq.http_api.rest_api.RestApiService; +import bisq.http_api.rest_api.domain.market_price.MarketPriceRestApi; +import bisq.http_api.rest_api.domain.offer.OfferRestApi; +import bisq.http_api.rest_api.domain.offerbook.OfferbookRestApi; +import bisq.http_api.rest_api.domain.user_identity.UserIdentityRestApi; import bisq.http_api.web_socket.WebSocketRestApiResourceConfig; import bisq.http_api.web_socket.WebSocketService; import bisq.network.NetworkService; import bisq.security.SecurityService; +import bisq.support.SupportService; +import bisq.trade.TradeService; import bisq.user.UserService; -import bisq.http_api.rest_api.domain.user_identity.UserIdentityRestApi; import lombok.extern.slf4j.Slf4j; import java.util.Optional; @@ -51,18 +54,29 @@ public HttpApiService(RestApiService.Config restApiConfig, NetworkService networkService, UserService userService, BondedRolesService bondedRolesService, - ChatService chatService) { + ChatService chatService, + SupportService supportedService, + TradeService tradeService) { boolean restApiConfigEnabled = restApiConfig.isEnabled(); boolean webSocketConfigEnabled = webSocketConfig.isEnabled(); if (restApiConfigEnabled || webSocketConfigEnabled) { OfferbookRestApi offerbookRestApi = new OfferbookRestApi(chatService.getBisqEasyOfferbookChannelService(), bondedRolesService.getMarketPriceService(), userService); + OfferRestApi offerRestApi = new OfferRestApi(chatService, + bondedRolesService.getMarketPriceService(), + userService, + supportedService, + tradeService); UserIdentityRestApi userIdentityRestApi = new UserIdentityRestApi(securityService, userService.getUserIdentityService()); MarketPriceRestApi marketPriceRestApi = new MarketPriceRestApi(bondedRolesService.getMarketPriceService()); if (restApiConfigEnabled) { - var restApiResourceConfig = new RestApiResourceConfig(restApiConfig.getRestApiBaseUrl(), offerbookRestApi, userIdentityRestApi, marketPriceRestApi); + var restApiResourceConfig = new RestApiResourceConfig(restApiConfig.getRestApiBaseUrl(), + offerbookRestApi, + offerRestApi, + userIdentityRestApi, + marketPriceRestApi); this.restApiService = Optional.of(new RestApiService(restApiConfig, restApiResourceConfig)); } else { this.restApiService = Optional.empty(); @@ -71,6 +85,7 @@ public HttpApiService(RestApiService.Config restApiConfig, if (webSocketConfigEnabled) { var webSocketResourceConfig = new WebSocketRestApiResourceConfig(webSocketConfig.getRestApiBaseUrl(), offerbookRestApi, + offerRestApi, userIdentityRestApi, marketPriceRestApi); this.webSocketService = Optional.of(new WebSocketService(webSocketConfig, diff --git a/http-api/src/main/java/bisq/http_api/rest_api/RestApiResourceConfig.java b/http-api/src/main/java/bisq/http_api/rest_api/RestApiResourceConfig.java index 5966cbb814..bc59090129 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/RestApiResourceConfig.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/RestApiResourceConfig.java @@ -1,6 +1,7 @@ package bisq.http_api.rest_api; import bisq.http_api.rest_api.domain.market_price.MarketPriceRestApi; +import bisq.http_api.rest_api.domain.offer.OfferRestApi; import bisq.http_api.rest_api.domain.offerbook.OfferbookRestApi; import bisq.http_api.rest_api.domain.user_identity.UserIdentityRestApi; import lombok.Getter; @@ -12,6 +13,7 @@ public class RestApiResourceConfig extends BaseRestApiResourceConfig { public RestApiResourceConfig(String swaggerBaseUrl, OfferbookRestApi offerbookRestApi, + OfferRestApi offerRestApi, UserIdentityRestApi userIdentityRestApi , MarketPriceRestApi marketPriceRestApi) { super(swaggerBaseUrl); @@ -22,6 +24,7 @@ public RestApiResourceConfig(String swaggerBaseUrl, // As we want to pass the dependencies in the constructor, so we need the hack // with AbstractBinder to register resources as classes for Swagger register(OfferbookRestApi.class); + register(OfferRestApi.class); register(UserIdentityRestApi.class); register(MarketPriceRestApi.class); @@ -29,6 +32,7 @@ public RestApiResourceConfig(String swaggerBaseUrl, @Override protected void configure() { bind(offerbookRestApi).to(OfferbookRestApi.class); + bind(offerRestApi).to(OfferRestApi.class); bind(userIdentityRestApi).to(UserIdentityRestApi.class); bind(marketPriceRestApi).to(MarketPriceRestApi.class); } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java index 8b3f4fda96..aba7ccc429 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java @@ -150,6 +150,7 @@ public Response getSelectedUserProfile() { return buildOkResponse(userProfile); } + // todo use AsyncResponse @POST @Operation( summary = "Create User Identity and Publish User Profile", diff --git a/http-api/src/main/java/bisq/http_api/web_socket/WebSocketRestApiResourceConfig.java b/http-api/src/main/java/bisq/http_api/web_socket/WebSocketRestApiResourceConfig.java index fcbd45c743..ad3dc3d208 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/WebSocketRestApiResourceConfig.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/WebSocketRestApiResourceConfig.java @@ -1,6 +1,7 @@ package bisq.http_api.web_socket; import bisq.http_api.rest_api.domain.market_price.MarketPriceRestApi; +import bisq.http_api.rest_api.domain.offer.OfferRestApi; import bisq.http_api.rest_api.domain.offerbook.OfferbookRestApi; import bisq.http_api.rest_api.RestApiResourceConfig; import bisq.http_api.rest_api.domain.user_identity.UserIdentityRestApi; @@ -11,8 +12,9 @@ public class WebSocketRestApiResourceConfig extends RestApiResourceConfig { public WebSocketRestApiResourceConfig(String swaggerBaseUrl, OfferbookRestApi offerbookRestApi, + OfferRestApi offerRestApi, UserIdentityRestApi userIdentityRestApi , MarketPriceRestApi marketPriceRestApi) { - super(swaggerBaseUrl, offerbookRestApi, userIdentityRestApi, marketPriceRestApi); + super(swaggerBaseUrl, offerbookRestApi, offerRestApi, userIdentityRestApi, marketPriceRestApi); } } diff --git a/http-api/src/main/java/bisq/http_api/web_socket/domain/BaseWebSocketService.java b/http-api/src/main/java/bisq/http_api/web_socket/domain/BaseWebSocketService.java index a36cf75919..89c4e12163 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/domain/BaseWebSocketService.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/domain/BaseWebSocketService.java @@ -19,11 +19,17 @@ import bisq.common.application.Service; +import bisq.dto.account.protocol_type.TradeProtocolTypeDto; +import bisq.dto.offer.amount.spec.AmountSpecDto; +import bisq.dto.offer.bisq_easy.OfferListItemDto; +import bisq.dto.offer.options.OfferOptionDto; +import bisq.dto.offer.price.spec.PriceSpecDto; import bisq.http_api.web_socket.subscription.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; +import java.util.List; import java.util.Optional; import java.util.Set; @@ -42,8 +48,40 @@ public BaseWebSocketService(ObjectMapper objectMapper, abstract public Optional getJsonPayload(); + //todo protected Optional toJson(T payload) { try { + if (payload instanceof List list && list.get(0) instanceof OfferListItemDto offerListItemDto) { + try { + PriceSpecDto value = offerListItemDto.getBisqEasyOffer().priceSpec(); + objectMapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + log.error("Json serialisation failed", e); + } + try { + AmountSpecDto value = offerListItemDto.getBisqEasyOffer().amountSpec(); + objectMapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + log.error("Json serialisation failed", e); + } + try { + List value = offerListItemDto.getBisqEasyOffer().offerOptions(); + objectMapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + log.error("Json serialisation failed", e); + } + try { + List value = offerListItemDto.getBisqEasyOffer().protocolTypes(); + objectMapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + log.error("Json serialisation failed", e); //failed + } + try { + objectMapper.writeValueAsString(offerListItemDto.getBisqEasyOffer()); + } catch (JsonProcessingException e) { + log.error("Json serialisation failed", e); //failed + } + } return Optional.of(objectMapper.writeValueAsString(payload)); } catch (JsonProcessingException e) { log.error("Json serialisation failed", e); @@ -73,7 +111,6 @@ protected void send(String json, Subscriber subscriber, ModificationType modificationType) { WebSocketEvent.toJson(objectMapper, - subscriber.getWebSocketEventClassName(), subscriber.getTopic(), subscriber.getSubscriberId(), json, diff --git a/http-api/src/main/java/bisq/http_api/web_socket/domain/market_price/MarketPriceWebSocketService.java b/http-api/src/main/java/bisq/http_api/web_socket/domain/market_price/MarketPriceWebSocketService.java index 24e2e5cf1c..8872a2b2b0 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/domain/market_price/MarketPriceWebSocketService.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/domain/market_price/MarketPriceWebSocketService.java @@ -23,6 +23,8 @@ import bisq.common.currency.Market; import bisq.common.observable.Pin; import bisq.common.observable.map.ObservableHashMap; +import bisq.dto.DtoMappings; +import bisq.dto.common.monetary.PriceQuoteDto; import bisq.http_api.web_socket.domain.SimpleObservableWebSocketService; import bisq.http_api.web_socket.subscription.SubscriberRepository; import bisq.http_api.web_socket.subscription.Topic; @@ -34,7 +36,7 @@ import java.util.stream.Collectors; @Slf4j -public class MarketPriceWebSocketService extends SimpleObservableWebSocketService, Map> { +public class MarketPriceWebSocketService extends SimpleObservableWebSocketService, Map> { private final MarketPriceService marketPriceService; public MarketPriceWebSocketService(ObjectMapper objectMapper, @@ -50,18 +52,23 @@ protected ObservableHashMap getObservable() { } @Override - protected HashMap toPayload(ObservableHashMap observable) { + protected HashMap toPayload(ObservableHashMap observable) { return getObservable() .entrySet().stream() - .filter(entry -> entry.getKey().getBaseCurrencyCode().equals("BTC")) // We get altcoin quotes as well which have BTC as quote currency + .filter(MarketPriceWebSocketService::isBaseCurrencyBtc) .collect(Collectors.toMap( entry -> entry.getKey().getQuoteCurrencyCode(), - entry -> entry.getValue().getPriceQuote().getValue(), + entry -> DtoMappings.PriceQuoteMapping.from(entry.getValue().getPriceQuote()), (existing, replacement) -> existing, HashMap::new )); } + private static boolean isBaseCurrencyBtc(Map.Entry entry) { + // We get altcoin quotes as well which have BTC as quote currency + return entry.getKey().getBaseCurrencyCode().equals("BTC"); + } + @Override protected Pin setupObserver() { return getObservable().addObserver(this::onChange); diff --git a/http-api/src/main/java/bisq/http_api/web_socket/domain/offerbook/OffersWebSocketService.java b/http-api/src/main/java/bisq/http_api/web_socket/domain/offerbook/OffersWebSocketService.java index ea4eb41e6c..6d14d3a09a 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/domain/offerbook/OffersWebSocketService.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/domain/offerbook/OffersWebSocketService.java @@ -17,43 +17,27 @@ package bisq.http_api.web_socket.domain.offerbook; -import bisq.account.payment_method.PaymentMethod; import bisq.bonded_roles.BondedRolesService; import bisq.bonded_roles.market_price.MarketPriceService; import bisq.chat.ChatService; import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannel; import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannelService; import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookMessage; -import bisq.http_api.rest_api.domain.offerbook.OfferListItemDto; -import bisq.http_api.rest_api.domain.offerbook.ReputationScoreDto; -import bisq.common.currency.Market; import bisq.common.observable.Pin; import bisq.common.observable.collection.CollectionObserver; -import bisq.common.util.StringUtils; +import bisq.dto.OfferListItemDtoFactory; +import bisq.dto.offer.bisq_easy.OfferListItemDto; import bisq.http_api.web_socket.domain.BaseWebSocketService; import bisq.http_api.web_socket.subscription.ModificationType; import bisq.http_api.web_socket.subscription.SubscriberRepository; -import bisq.i18n.Res; -import bisq.offer.Direction; -import bisq.offer.amount.OfferAmountFormatter; -import bisq.offer.amount.spec.AmountSpec; -import bisq.offer.amount.spec.RangeAmountSpec; -import bisq.offer.bisq_easy.BisqEasyOffer; -import bisq.offer.payment_method.PaymentMethodSpecUtil; -import bisq.offer.price.spec.PriceSpec; -import bisq.offer.price.spec.PriceSpecFormatter; -import bisq.presentation.formatters.DateFormatter; import bisq.user.UserService; import bisq.user.identity.UserIdentityService; -import bisq.user.profile.UserProfile; import bisq.user.profile.UserProfileService; import bisq.user.reputation.ReputationService; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Joiner; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.NotImplementedException; -import java.text.DateFormat; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -71,10 +55,10 @@ public class OffersWebSocketService extends BaseWebSocketService { private final Set pins = new HashSet<>(); public OffersWebSocketService(ObjectMapper objectMapper, - SubscriberRepository subscriberRepository, - ChatService chatService, - UserService userService, - BondedRolesService bondedRolesService) { + SubscriberRepository subscriberRepository, + ChatService chatService, + UserService userService, + BondedRolesService bondedRolesService) { super(objectMapper, subscriberRepository, OFFERS); bisqEasyOfferbookChannelService = chatService.getBisqEasyOfferbookChannelService(); @@ -127,15 +111,15 @@ public Optional getJsonPayload(Stream channels ArrayList payload = channels .flatMap(channel -> channel.getChatMessages().stream() - .map(this::toOfferListItemDto)) + .map(this::createOfferListItemDto)) .collect(Collectors.toCollection(ArrayList::new)); return toJson(payload); } private void send(String quoteCurrencyCode, - BisqEasyOfferbookMessage message, + BisqEasyOfferbookMessage bisqEasyOfferbookMessage, ModificationType modificationType) { - OfferListItemDto item = toOfferListItemDto(message); + OfferListItemDto item = createOfferListItemDto(bisqEasyOfferbookMessage); // The payload is defined as a list to support batch data delivery at subscribe. ArrayList payload = new ArrayList<>(List.of(item)); toJson(payload).ifPresent(json -> { @@ -145,78 +129,11 @@ private void send(String quoteCurrencyCode, }); } - private OfferListItemDto toOfferListItemDto(BisqEasyOfferbookMessage message) { - BisqEasyOffer bisqEasyOffer = message.getBisqEasyOffer().orElseThrow(); - Market market = bisqEasyOffer.getMarket(); - - long date = message.getDate(); - String formattedDate = DateFormatter.formatDateTime(new Date(date), DateFormat.MEDIUM, DateFormat.SHORT, - true, " " + Res.get("temporal.at") + " "); - String authorUserProfileId = message.getAuthorUserProfileId(); - Optional senderUserProfile = userProfileService.findUserProfile(authorUserProfileId); - String nym = senderUserProfile.map(UserProfile::getNym).orElse(""); - String userName = senderUserProfile.map(UserProfile::getUserName).orElse(""); - - ReputationScoreDto reputationScore = senderUserProfile.flatMap(reputationService::findReputationScore) - .map(score -> new ReputationScoreDto( - score.getTotalScore(), - score.getFiveSystemScore(), - score.getRanking() - )) - .orElse(new ReputationScoreDto(0, 0, 0)); - AmountSpec amountSpec = bisqEasyOffer.getAmountSpec(); - PriceSpec priceSpec = bisqEasyOffer.getPriceSpec(); - boolean hasAmountRange = amountSpec instanceof RangeAmountSpec; - String formattedQuoteAmount = OfferAmountFormatter.formatQuoteAmount( + private OfferListItemDto createOfferListItemDto(BisqEasyOfferbookMessage bisqEasyOfferbookMessage) { + return OfferListItemDtoFactory.createOfferListItemDto(userProfileService, + userIdentityService, + reputationService, marketPriceService, - amountSpec, - priceSpec, - market, - hasAmountRange, - true - ); - String formattedPrice = PriceSpecFormatter.getFormattedPriceSpec(priceSpec, true); - - List quoteSidePaymentMethods = PaymentMethodSpecUtil.getPaymentMethods(bisqEasyOffer.getQuoteSidePaymentMethodSpecs()) - .stream() - .map(PaymentMethod::getName) - .collect(Collectors.toList()); - - List baseSidePaymentMethods = PaymentMethodSpecUtil.getPaymentMethods(bisqEasyOffer.getBaseSidePaymentMethodSpecs()) - .stream() - .map(PaymentMethod::getName) - .collect(Collectors.toList()); - - String supportedLanguageCodes = Joiner.on(",").join(bisqEasyOffer.getSupportedLanguageCodes()); - boolean isMyMessage = message.isMyMessage(userIdentityService); - Direction direction = bisqEasyOffer.getDirection(); - String offerTitle = getOfferTitle(message, direction, isMyMessage); - String messageId = message.getId(); - String offerId = bisqEasyOffer.getId(); - return new OfferListItemDto(messageId, - offerId, - isMyMessage, - direction, - market.getQuoteCurrencyCode(), - offerTitle, - date, - formattedDate, - nym, - userName, - reputationScore, - formattedQuoteAmount, - formattedPrice, - quoteSidePaymentMethods, - baseSidePaymentMethods, - supportedLanguageCodes); - } - - private String getOfferTitle(BisqEasyOfferbookMessage message, Direction direction, boolean isMyMessage) { - if (isMyMessage) { - String directionString = StringUtils.capitalize(Res.get("offer." + direction.name().toLowerCase())); - return Res.get("bisqEasy.tradeWizard.review.chatMessage.myMessageTitle", directionString); - } else { - return message.getText(); - } + bisqEasyOfferbookMessage); } } diff --git a/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiService.java b/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiService.java index bcf9af8e8b..f07da42daa 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiService.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/rest_api_proxy/WebSocketRestApiService.java @@ -20,6 +20,7 @@ import bisq.common.application.Service; import bisq.http_api.web_socket.util.JsonUtil; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.ws.rs.core.Response; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; @@ -76,7 +77,7 @@ private WebSocketRestApiResponse sendToRestApiServer(WebSocketRestApiRequest req String errorMessage = RequestValidation.validateRequest(request); if (errorMessage != null) { log.error(errorMessage); - return new WebSocketRestApiResponse(request.getResponseClassName(), request.getRequestId(), 400, errorMessage); + return new WebSocketRestApiResponse(request.getRequestId(), Response.Status.BAD_REQUEST.getStatusCode(), errorMessage); } String url = restApiAddress + request.getPath(); @@ -91,14 +92,13 @@ private WebSocketRestApiResponse sendToRestApiServer(WebSocketRestApiRequest req HttpRequest httpRequest = requestBuilder.build(); log.info("Send {} httpRequest to {}. httpRequest={} ", method, url, httpRequest); // Blocking send - HttpClient httpClient1 = httpClient.orElseThrow(); - HttpResponse httpResponse = httpClient1.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + HttpResponse httpResponse = httpClient.orElseThrow().send(httpRequest, HttpResponse.BodyHandlers.ofString()); log.info("httpResponse {}", httpResponse); - return new WebSocketRestApiResponse(request.getResponseClassName(), request.getRequestId(), httpResponse.statusCode(), httpResponse.body()); + return new WebSocketRestApiResponse( request.getRequestId(), httpResponse.statusCode(), httpResponse.body()); } catch (Exception e) { errorMessage = String.format("Error at sending a '%s' request to '%s' with body: '%s'. Error: %s", method, url, body, e.getMessage()); log.error(errorMessage, e); - return new WebSocketRestApiResponse(request.getResponseClassName(), request.getRequestId(), 500, errorMessage); + return new WebSocketRestApiResponse(request.getRequestId(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), errorMessage); } } } \ No newline at end of file diff --git a/http-api/src/main/java/bisq/http_api/web_socket/util/JsonUtil.java b/http-api/src/main/java/bisq/http_api/web_socket/util/JsonUtil.java index 7b6b4b7986..92203549eb 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/util/JsonUtil.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/util/JsonUtil.java @@ -21,14 +21,15 @@ import java.util.regex.Pattern; public class JsonUtil { - // We use by convention same class name. We get the className field set by the client. + // We use by convention same class name. We get the type field set by the client. public static boolean hasExpectedJsonClassName(Class clazz, String json) { - String regex = "\"className\":\"[^\"]*\\.([^\"]+)\""; + String regex = "\"type\":\\s*\"([^\"]+)\""; // We use simple name Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(json); if (matcher.find()) { - String className = matcher.group(1); - return clazz.getSimpleName().equals(className); + String type = matcher.group(1); + String simpleName = clazz.getSimpleName(); + return simpleName.equals(type); } else { return false; } From 4c91b90764fb33919822151c1b70e17e65e0fdf7 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 17:38:05 +0700 Subject: [PATCH 18/20] Move take offer to TradeRestApi --- .../java/bisq/http_api/HttpApiService.java | 8 + .../rest_api/RestApiResourceConfig.java | 4 + .../rest_api/domain/offer/OfferRestApi.java | 116 +--------- .../{offer => trade}/TakeOfferRequest.java | 5 +- .../{offer => trade}/TakeOfferResponse.java | 2 +- .../rest_api/domain/trade/TradeRestApi.java | 200 ++++++++++++++++++ .../WebSocketRestApiResourceConfig.java | 6 +- 7 files changed, 224 insertions(+), 117 deletions(-) rename http-api/src/main/java/bisq/http_api/rest_api/domain/{offer => trade}/TakeOfferRequest.java (85%) rename http-api/src/main/java/bisq/http_api/rest_api/domain/{offer => trade}/TakeOfferResponse.java (94%) create mode 100644 http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TradeRestApi.java diff --git a/http-api/src/main/java/bisq/http_api/HttpApiService.java b/http-api/src/main/java/bisq/http_api/HttpApiService.java index 338b365a3c..682ca6b07f 100644 --- a/http-api/src/main/java/bisq/http_api/HttpApiService.java +++ b/http-api/src/main/java/bisq/http_api/HttpApiService.java @@ -26,6 +26,7 @@ import bisq.http_api.rest_api.domain.market_price.MarketPriceRestApi; import bisq.http_api.rest_api.domain.offer.OfferRestApi; import bisq.http_api.rest_api.domain.offerbook.OfferbookRestApi; +import bisq.http_api.rest_api.domain.trade.TradeRestApi; import bisq.http_api.rest_api.domain.user_identity.UserIdentityRestApi; import bisq.http_api.web_socket.WebSocketRestApiResourceConfig; import bisq.http_api.web_socket.WebSocketService; @@ -68,6 +69,11 @@ public HttpApiService(RestApiService.Config restApiConfig, userService, supportedService, tradeService); + TradeRestApi tradeRestApi = new TradeRestApi(chatService, + bondedRolesService.getMarketPriceService(), + userService, + supportedService, + tradeService); UserIdentityRestApi userIdentityRestApi = new UserIdentityRestApi(securityService, userService.getUserIdentityService()); MarketPriceRestApi marketPriceRestApi = new MarketPriceRestApi(bondedRolesService.getMarketPriceService()); @@ -75,6 +81,7 @@ public HttpApiService(RestApiService.Config restApiConfig, var restApiResourceConfig = new RestApiResourceConfig(restApiConfig.getRestApiBaseUrl(), offerbookRestApi, offerRestApi, + tradeRestApi, userIdentityRestApi, marketPriceRestApi); this.restApiService = Optional.of(new RestApiService(restApiConfig, restApiResourceConfig)); @@ -86,6 +93,7 @@ public HttpApiService(RestApiService.Config restApiConfig, var webSocketResourceConfig = new WebSocketRestApiResourceConfig(webSocketConfig.getRestApiBaseUrl(), offerbookRestApi, offerRestApi, + tradeRestApi, userIdentityRestApi, marketPriceRestApi); this.webSocketService = Optional.of(new WebSocketService(webSocketConfig, diff --git a/http-api/src/main/java/bisq/http_api/rest_api/RestApiResourceConfig.java b/http-api/src/main/java/bisq/http_api/rest_api/RestApiResourceConfig.java index bc59090129..24c81f3d57 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/RestApiResourceConfig.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/RestApiResourceConfig.java @@ -3,6 +3,7 @@ import bisq.http_api.rest_api.domain.market_price.MarketPriceRestApi; import bisq.http_api.rest_api.domain.offer.OfferRestApi; import bisq.http_api.rest_api.domain.offerbook.OfferbookRestApi; +import bisq.http_api.rest_api.domain.trade.TradeRestApi; import bisq.http_api.rest_api.domain.user_identity.UserIdentityRestApi; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -14,6 +15,7 @@ public class RestApiResourceConfig extends BaseRestApiResourceConfig { public RestApiResourceConfig(String swaggerBaseUrl, OfferbookRestApi offerbookRestApi, OfferRestApi offerRestApi, + TradeRestApi tradeRestApi, UserIdentityRestApi userIdentityRestApi , MarketPriceRestApi marketPriceRestApi) { super(swaggerBaseUrl); @@ -25,6 +27,7 @@ public RestApiResourceConfig(String swaggerBaseUrl, // with AbstractBinder to register resources as classes for Swagger register(OfferbookRestApi.class); register(OfferRestApi.class); + register(TradeRestApi.class); register(UserIdentityRestApi.class); register(MarketPriceRestApi.class); @@ -33,6 +36,7 @@ public RestApiResourceConfig(String swaggerBaseUrl, protected void configure() { bind(offerbookRestApi).to(OfferbookRestApi.class); bind(offerRestApi).to(OfferRestApi.class); + bind(tradeRestApi).to(TradeRestApi.class); bind(userIdentityRestApi).to(UserIdentityRestApi.class); bind(marketPriceRestApi).to(MarketPriceRestApi.class); } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java index a0c9ed2f6a..6e51780502 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java @@ -23,32 +23,21 @@ import bisq.account.payment_method.FiatPaymentMethodUtil; import bisq.bisq_easy.BisqEasyServiceUtil; import bisq.bonded_roles.market_price.MarketPriceService; -import bisq.chat.ChatChannelDomain; -import bisq.chat.ChatChannelSelectionService; import bisq.chat.ChatService; import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannelService; import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookMessage; import bisq.chat.bisqeasy.open_trades.BisqEasyOpenTradeChannelService; import bisq.common.currency.Market; -import bisq.common.monetary.Monetary; -import bisq.common.util.StringUtils; -import bisq.contract.bisq_easy.BisqEasyContract; import bisq.dto.DtoMappings; import bisq.http_api.rest_api.domain.RestApiBase; -import bisq.i18n.Res; import bisq.offer.Direction; import bisq.offer.amount.spec.AmountSpec; import bisq.offer.bisq_easy.BisqEasyOffer; -import bisq.offer.payment_method.BitcoinPaymentMethodSpec; -import bisq.offer.payment_method.FiatPaymentMethodSpec; -import bisq.offer.payment_method.PaymentMethodSpecUtil; import bisq.offer.price.spec.PriceSpec; import bisq.support.SupportService; import bisq.support.mediation.MediationRequestService; import bisq.trade.TradeService; -import bisq.trade.bisq_easy.BisqEasyTrade; import bisq.trade.bisq_easy.BisqEasyTradeService; -import bisq.trade.bisq_easy.protocol.BisqEasyProtocol; import bisq.user.UserService; import bisq.user.banned.BannedUserService; import bisq.user.identity.UserIdentity; @@ -60,7 +49,10 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.ws.rs.*; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import lombok.extern.slf4j.Slf4j; @@ -71,8 +63,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkArgument; - @Slf4j @Path("/offers") @Produces(MediaType.APPLICATION_JSON) @@ -103,104 +93,6 @@ public OfferRestApi(ChatService chatService, bisqEasyOpenTradeChannelService = chatService.getBisqEasyOpenTradeChannelService(); } - // todo use AsyncResponse - @POST - @Operation( - summary = "Take Bisq Easy Offer", - description = "Takes a Bisq Easy Offer.", - requestBody = @RequestBody( - description = "", - content = @Content(schema = @Schema(implementation = PublishOfferRequest.class)) - ), - responses = { - @ApiResponse(responseCode = "201", description = "", - content = @Content(schema = @Schema(example = ""))), - @ApiResponse(responseCode = "400", description = "Invalid input"), - @ApiResponse(responseCode = "500", description = "Internal server error") - } - ) - @Path("{offerId}/take") - public Response takeOffer(@PathParam("offerId") String offerId, TakeOfferRequest request) { - try { - UserIdentity takerIdentity = userIdentityService.getSelectedUserIdentity(); - checkArgument(!bannedUserService.isUserProfileBanned(takerIdentity.getUserProfile()), "Taker profile is banned"); - //noinspection OptionalGetWithoutIsPresent - BisqEasyOffer bisqEasyOffer = bisqEasyOfferbookChannelService.getChannels().stream().flatMap(c -> c.getChatMessages().stream()) - .filter(BisqEasyOfferbookMessage::hasBisqEasyOffer) - .map(e -> e.getBisqEasyOffer().get()) - .filter(e -> e.getId().equals(offerId)) - .findFirst() - .orElseThrow(); - checkArgument(!bannedUserService.isNetworkIdBanned(bisqEasyOffer.getMakerNetworkId()), "Maker profile is banned"); - Monetary baseSideAmount = Monetary.from(request.baseSideAmount(), bisqEasyOffer.getMarket().getBaseCurrencyCode()); - Monetary quoteSideAmount = Monetary.from(request.quoteSideAmount(), bisqEasyOffer.getMarket().getBaseCurrencyCode()); - BitcoinPaymentMethod bitcoinPaymentMethod = PaymentMethodSpecUtil.getBitcoinPaymentMethod(request.bitcoinPaymentMethod()); - BitcoinPaymentMethodSpec bitcoinPaymentMethodSpec = new BitcoinPaymentMethodSpec(bitcoinPaymentMethod); - FiatPaymentMethod fiatPaymentMethod = PaymentMethodSpecUtil.getFiatPaymentMethod(request.fiatPaymentMethod()); - FiatPaymentMethodSpec fiatPaymentMethodSpec = new FiatPaymentMethodSpec(fiatPaymentMethod); - Optional mediator = mediationRequestService.selectMediator(bisqEasyOffer.getMakersUserProfileId(), takerIdentity.getId()); - PriceSpec makersPriceSpec = bisqEasyOffer.getPriceSpec(); - long marketPrice = marketPriceService.findMarketPrice(bisqEasyOffer.getMarket()) - .map(e -> e.getPriceQuote().getValue()) - .orElseThrow(); - BisqEasyProtocol bisqEasyProtocol = bisqEasyTradeService.createBisqEasyProtocol(takerIdentity.getIdentity(), - bisqEasyOffer, - baseSideAmount, - quoteSideAmount, - bitcoinPaymentMethodSpec, - fiatPaymentMethodSpec, - mediator, - makersPriceSpec, - marketPrice); - BisqEasyTrade bisqEasyTrade = bisqEasyProtocol.getModel(); - log.info("Selected mediator for trade {}: {}", bisqEasyTrade.getShortId(), mediator.map(UserProfile::getUserName).orElse("N/A")); - - bisqEasyTradeService.takeOffer(bisqEasyTrade); - BisqEasyContract contract = bisqEasyTrade.getContract(); - - String tradeId = bisqEasyTrade.getId(); - // todo set timeout - // asyncResponse.setTimeout(150, TimeUnit.SECONDS); // We have 120 seconds socket timeout, so we should never get triggered here, as the message will be sent as mailbox message - bisqEasyOpenTradeChannelService.sendTakeOfferMessage(tradeId, bisqEasyOffer, contract.getMediator()) - .thenAccept(result -> - { - // In case the user has switched to another market we want to select that market in the offer book - ChatChannelSelectionService chatChannelSelectionService = - chatService.getChatChannelSelectionService(ChatChannelDomain.BISQ_EASY_OFFERBOOK); - bisqEasyOfferbookChannelService.findChannel(contract.getOffer().getMarket()) - .ifPresent(chatChannelSelectionService::selectChannel); - bisqEasyOpenTradeChannelService.findChannelByTradeId(tradeId) - .ifPresent(channel -> { - String taker = userIdentityService.getSelectedUserIdentity().getUserProfile().getUserName(); - String maker = channel.getPeer().getUserName(); - String encoded = Res.encode("bisqEasy.takeOffer.tradeLogMessage", taker, maker); - chatService.getBisqEasyOpenTradeChannelService().sendTradeLogMessage(encoded, channel); - }); - } - ) - .get(); - - String errorMessage = bisqEasyTrade.errorMessageObservable().get(); - checkArgument(errorMessage == null, "An error occurred at taking the offer: " + errorMessage + - ". ErrorStackTrace: " + StringUtils.truncate(bisqEasyTrade.getErrorStackTrace(), 500)); - - String peersErrorMessage = bisqEasyTrade.peersErrorMessageObservable().get(); - checkArgument(peersErrorMessage == null, "An error occurred at the peers side at taking the offer: " + peersErrorMessage + - ". ErrorStackTrace: " + StringUtils.truncate(bisqEasyTrade.getPeersErrorStackTrace(), 500)); - - return buildResponse(Response.Status.OK, new TakeOfferResponse(bisqEasyTrade.getId())); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return buildErrorResponse("Thread was interrupted."); - } catch (IllegalArgumentException e) { - return buildResponse(Response.Status.BAD_REQUEST, "Invalid input: " + e.getMessage()); - } catch (Exception e) { - log.error("Error publishing offer", e); - return buildErrorResponse("An unexpected error occurred."); - } - } - - // todo use AsyncResponse @POST @Operation( summary = "Create and Publish Bisq Easy Offer", diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferRequest.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TakeOfferRequest.java similarity index 85% rename from http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferRequest.java rename to http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TakeOfferRequest.java index aae310a95e..78a1b6e5ed 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferRequest.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TakeOfferRequest.java @@ -15,9 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.http_api.rest_api.domain.offer; +package bisq.http_api.rest_api.domain.trade; -public record TakeOfferRequest(long baseSideAmount, +public record TakeOfferRequest(String offerId, + long baseSideAmount, long quoteSideAmount, String bitcoinPaymentMethod, String fiatPaymentMethod) { diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferResponse.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TakeOfferResponse.java similarity index 94% rename from http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferResponse.java rename to http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TakeOfferResponse.java index 84e862c094..071fc27c8a 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/TakeOfferResponse.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TakeOfferResponse.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.http_api.rest_api.domain.offer; +package bisq.http_api.rest_api.domain.trade; public record TakeOfferResponse(String tradeId) { } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TradeRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TradeRestApi.java new file mode 100644 index 0000000000..ada40e67af --- /dev/null +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TradeRestApi.java @@ -0,0 +1,200 @@ +/* + * 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 . + */ + +package bisq.http_api.rest_api.domain.trade; + +import bisq.account.payment_method.BitcoinPaymentMethod; +import bisq.account.payment_method.FiatPaymentMethod; +import bisq.bonded_roles.market_price.MarketPriceService; +import bisq.chat.ChatChannelDomain; +import bisq.chat.ChatChannelSelectionService; +import bisq.chat.ChatService; +import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannelService; +import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookMessage; +import bisq.chat.bisqeasy.open_trades.BisqEasyOpenTradeChannelService; +import bisq.common.monetary.Monetary; +import bisq.common.util.StringUtils; +import bisq.contract.bisq_easy.BisqEasyContract; +import bisq.http_api.rest_api.domain.RestApiBase; +import bisq.http_api.rest_api.domain.offer.PublishOfferRequest; +import bisq.i18n.Res; +import bisq.offer.bisq_easy.BisqEasyOffer; +import bisq.offer.payment_method.BitcoinPaymentMethodSpec; +import bisq.offer.payment_method.FiatPaymentMethodSpec; +import bisq.offer.payment_method.PaymentMethodSpecUtil; +import bisq.offer.price.spec.PriceSpec; +import bisq.support.SupportService; +import bisq.support.mediation.MediationRequestService; +import bisq.trade.TradeService; +import bisq.trade.bisq_easy.BisqEasyTrade; +import bisq.trade.bisq_easy.BisqEasyTradeService; +import bisq.trade.bisq_easy.protocol.BisqEasyProtocol; +import bisq.user.UserService; +import bisq.user.banned.BannedUserService; +import bisq.user.identity.UserIdentity; +import bisq.user.identity.UserIdentityService; +import bisq.user.profile.UserProfile; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import static com.google.common.base.Preconditions.checkArgument; + +@Slf4j +@Path("/trades") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Bisq Easy Trade API") +public class TradeRestApi extends RestApiBase { + private final BisqEasyOfferbookChannelService bisqEasyOfferbookChannelService; + private final ChatService chatService; + private final MarketPriceService marketPriceService; + private final UserIdentityService userIdentityService; + private final BannedUserService bannedUserService; + private final MediationRequestService mediationRequestService; + private final BisqEasyTradeService bisqEasyTradeService; + private final BisqEasyOpenTradeChannelService bisqEasyOpenTradeChannelService; + + public TradeRestApi(ChatService chatService, + MarketPriceService marketPriceService, + UserService userService, + SupportService supportedService, + TradeService tradeService) { + this.bisqEasyOfferbookChannelService = chatService.getBisqEasyOfferbookChannelService(); + this.chatService = chatService; + this.marketPriceService = marketPriceService; + userIdentityService = userService.getUserIdentityService(); + bannedUserService = userService.getBannedUserService(); + mediationRequestService = supportedService.getMediationRequestService(); + bisqEasyTradeService = tradeService.getBisqEasyTradeService(); + bisqEasyOpenTradeChannelService = chatService.getBisqEasyOpenTradeChannelService(); + } + + @POST + @Operation( + summary = "Create a Trade by Taking a Bisq Easy Offer", + description = "Create a Trade by Taking a Bisq Easy Offer.", + requestBody = @RequestBody( + description = "", + content = @Content(schema = @Schema(implementation = PublishOfferRequest.class)) + ), + responses = { + @ApiResponse(responseCode = "201", description = "", + content = @Content(schema = @Schema(example = ""))), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "500", description = "Internal server error") + } + ) + public void takeOffer(TakeOfferRequest request, @Suspended AsyncResponse asyncResponse) { + asyncResponse.setTimeout(150, TimeUnit.SECONDS); // We have 120 seconds socket timeout, so we should never get triggered here, as the message will be sent as mailbox message + asyncResponse.setTimeoutHandler(response -> { + response.resume(buildResponse(Response.Status.SERVICE_UNAVAILABLE, "Request timed out")); + }); + + try { + UserIdentity takerIdentity = userIdentityService.getSelectedUserIdentity(); + checkArgument(!bannedUserService.isUserProfileBanned(takerIdentity.getUserProfile()), "Taker profile is banned"); + //noinspection OptionalGetWithoutIsPresent + BisqEasyOffer bisqEasyOffer = bisqEasyOfferbookChannelService.getChannels().stream().flatMap(c -> c.getChatMessages().stream()) + .filter(BisqEasyOfferbookMessage::hasBisqEasyOffer) + .map(e -> e.getBisqEasyOffer().get()) + .filter(e -> e.getId().equals(request.offerId())) + .findFirst() + .orElseThrow(); + checkArgument(!bannedUserService.isNetworkIdBanned(bisqEasyOffer.getMakerNetworkId()), "Maker profile is banned"); + Monetary baseSideAmount = Monetary.from(request.baseSideAmount(), bisqEasyOffer.getMarket().getBaseCurrencyCode()); + Monetary quoteSideAmount = Monetary.from(request.quoteSideAmount(), bisqEasyOffer.getMarket().getBaseCurrencyCode()); + BitcoinPaymentMethod bitcoinPaymentMethod = PaymentMethodSpecUtil.getBitcoinPaymentMethod(request.bitcoinPaymentMethod()); + BitcoinPaymentMethodSpec bitcoinPaymentMethodSpec = new BitcoinPaymentMethodSpec(bitcoinPaymentMethod); + FiatPaymentMethod fiatPaymentMethod = PaymentMethodSpecUtil.getFiatPaymentMethod(request.fiatPaymentMethod()); + FiatPaymentMethodSpec fiatPaymentMethodSpec = new FiatPaymentMethodSpec(fiatPaymentMethod); + Optional mediator = mediationRequestService.selectMediator(bisqEasyOffer.getMakersUserProfileId(), takerIdentity.getId()); + PriceSpec makersPriceSpec = bisqEasyOffer.getPriceSpec(); + long marketPrice = marketPriceService.findMarketPrice(bisqEasyOffer.getMarket()) + .map(e -> e.getPriceQuote().getValue()) + .orElseThrow(); + BisqEasyProtocol bisqEasyProtocol = bisqEasyTradeService.createBisqEasyProtocol(takerIdentity.getIdentity(), + bisqEasyOffer, + baseSideAmount, + quoteSideAmount, + bitcoinPaymentMethodSpec, + fiatPaymentMethodSpec, + mediator, + makersPriceSpec, + marketPrice); + BisqEasyTrade bisqEasyTrade = bisqEasyProtocol.getModel(); + log.info("Selected mediator for trade {}: {}", bisqEasyTrade.getShortId(), mediator.map(UserProfile::getUserName).orElse("N/A")); + + bisqEasyTradeService.takeOffer(bisqEasyTrade); + BisqEasyContract contract = bisqEasyTrade.getContract(); + + String tradeId = bisqEasyTrade.getId(); + bisqEasyOpenTradeChannelService.sendTakeOfferMessage(tradeId, bisqEasyOffer, contract.getMediator()) + .thenAccept(result -> + { + // In case the user has switched to another market we want to select that market in the offer book + ChatChannelSelectionService chatChannelSelectionService = + chatService.getChatChannelSelectionService(ChatChannelDomain.BISQ_EASY_OFFERBOOK); + bisqEasyOfferbookChannelService.findChannel(contract.getOffer().getMarket()) + .ifPresent(chatChannelSelectionService::selectChannel); + bisqEasyOpenTradeChannelService.findChannelByTradeId(tradeId) + .ifPresent(channel -> { + String taker = userIdentityService.getSelectedUserIdentity().getUserProfile().getUserName(); + String maker = channel.getPeer().getUserName(); + String encoded = Res.encode("bisqEasy.takeOffer.tradeLogMessage", taker, maker); + chatService.getBisqEasyOpenTradeChannelService().sendTradeLogMessage(encoded, channel); + }); + } + ) + .get(); + + // After the take offer is completed we check if errors happened + String errorMessage = bisqEasyTrade.errorMessageObservable().get(); + checkArgument(errorMessage == null, "An error occurred at taking the offer: " + errorMessage + + ". ErrorStackTrace: " + StringUtils.truncate(bisqEasyTrade.getErrorStackTrace(), 500)); + + String peersErrorMessage = bisqEasyTrade.peersErrorMessageObservable().get(); + checkArgument(peersErrorMessage == null, "An error occurred at the peers side at taking the offer: " + peersErrorMessage + + ". ErrorStackTrace: " + StringUtils.truncate(bisqEasyTrade.getPeersErrorStackTrace(), 500)); + + asyncResponse.resume(buildResponse(Response.Status.CREATED, new TakeOfferResponse(bisqEasyTrade.getId()))); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + asyncResponse.resume(buildErrorResponse("Thread was interrupted.")); + } catch (IllegalArgumentException e) { + asyncResponse.resume(buildResponse(Response.Status.BAD_REQUEST, "Invalid input: " + e.getMessage())); + } catch (Exception e) { + log.error("Error publishing offer", e); + asyncResponse.resume(buildErrorResponse("An unexpected error occurred.")); + } + } +} diff --git a/http-api/src/main/java/bisq/http_api/web_socket/WebSocketRestApiResourceConfig.java b/http-api/src/main/java/bisq/http_api/web_socket/WebSocketRestApiResourceConfig.java index ad3dc3d208..087dd3910c 100644 --- a/http-api/src/main/java/bisq/http_api/web_socket/WebSocketRestApiResourceConfig.java +++ b/http-api/src/main/java/bisq/http_api/web_socket/WebSocketRestApiResourceConfig.java @@ -1,9 +1,10 @@ package bisq.http_api.web_socket; +import bisq.http_api.rest_api.RestApiResourceConfig; import bisq.http_api.rest_api.domain.market_price.MarketPriceRestApi; import bisq.http_api.rest_api.domain.offer.OfferRestApi; import bisq.http_api.rest_api.domain.offerbook.OfferbookRestApi; -import bisq.http_api.rest_api.RestApiResourceConfig; +import bisq.http_api.rest_api.domain.trade.TradeRestApi; import bisq.http_api.rest_api.domain.user_identity.UserIdentityRestApi; import jakarta.ws.rs.ApplicationPath; import lombok.extern.slf4j.Slf4j; @@ -13,8 +14,9 @@ public class WebSocketRestApiResourceConfig extends RestApiResourceConfig { public WebSocketRestApiResourceConfig(String swaggerBaseUrl, OfferbookRestApi offerbookRestApi, OfferRestApi offerRestApi, + TradeRestApi tradeRestApi, UserIdentityRestApi userIdentityRestApi , MarketPriceRestApi marketPriceRestApi) { - super(swaggerBaseUrl, offerbookRestApi, offerRestApi, userIdentityRestApi, marketPriceRestApi); + super(swaggerBaseUrl, offerbookRestApi, offerRestApi, tradeRestApi, userIdentityRestApi, marketPriceRestApi); } } From 8d5f84475027d290d1053593d8be172eba1243ee Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 17:44:15 +0700 Subject: [PATCH 19/20] Apply AsyncResponse to createOffer --- .../rest_api/domain/offer/OfferRestApi.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java index 6e51780502..fe7736a959 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java @@ -53,6 +53,8 @@ import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import lombok.extern.slf4j.Slf4j; @@ -61,6 +63,7 @@ import java.util.Date; import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j @@ -108,7 +111,11 @@ public OfferRestApi(ChatService chatService, @ApiResponse(responseCode = "500", description = "Internal server error") } ) - public Response createOffer(PublishOfferRequest request) { + public void createOffer(PublishOfferRequest request, @Suspended AsyncResponse asyncResponse) { + asyncResponse.setTimeout(10, TimeUnit.SECONDS); + asyncResponse.setTimeoutHandler(response -> { + response.resume(buildResponse(Response.Status.SERVICE_UNAVAILABLE, "Request timed out")); + }); try { UserIdentity userIdentity = userIdentityService.getSelectedUserIdentity(); Direction direction = DtoMappings.DirectionMapping.toPojo(request.direction()); @@ -150,15 +157,15 @@ public Response createOffer(PublishOfferRequest request) { new Date().getTime(), false); bisqEasyOfferbookChannelService.publishChatMessage(myOfferMessage, userIdentity).get(); - return buildResponse(Response.Status.CREATED, new PublishOfferResponse(bisqEasyOffer.getId())); + asyncResponse.resume(buildResponse(Response.Status.CREATED, new PublishOfferResponse(bisqEasyOffer.getId()))); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - return buildErrorResponse("Thread was interrupted."); + asyncResponse.resume(buildErrorResponse("Thread was interrupted.")); } catch (IllegalArgumentException e) { - return buildResponse(Response.Status.BAD_REQUEST, "Invalid input: " + e.getMessage()); + asyncResponse.resume(buildResponse(Response.Status.BAD_REQUEST, "Invalid input: " + e.getMessage())); } catch (Exception e) { log.error("Error publishing offer", e); - return buildErrorResponse("An unexpected error occurred."); + asyncResponse.resume(buildErrorResponse("An unexpected error occurred.")); } } } From 4665efca7b3f617ddc121bf85987017d6605d2a7 Mon Sep 17 00:00:00 2001 From: HenrikJannsen Date: Sun, 22 Dec 2024 19:48:07 +0700 Subject: [PATCH 20/20] Rename preparation path to key-material. Use AsyncResponse to getKeyMaterial. Var. refactorings --- .../market_price/MarketPriceRestApi.java | 4 +- ...PriceResponse.java => QuotesResponse.java} | 2 +- ...erRequest.java => CreateOfferRequest.java} | 14 +-- ...Response.java => CreateOfferResponse.java} | 2 +- .../rest_api/domain/offer/OfferRestApi.java | 7 +- .../rest_api/domain/trade/TradeRestApi.java | 5 +- .../CreateUserIdentityRequest.java | 6 +- ...e.java => CreateUserIdentityResponse.java} | 4 +- ...paration.java => KeyMaterialResponse.java} | 10 +- .../user_identity/UserIdentityRestApi.java | 114 ++++++++++-------- 10 files changed, 89 insertions(+), 79 deletions(-) rename http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/{MarketPriceResponse.java => QuotesResponse.java} (96%) rename http-api/src/main/java/bisq/http_api/rest_api/domain/offer/{PublishOfferRequest.java => CreateOfferRequest.java} (68%) rename http-api/src/main/java/bisq/http_api/rest_api/domain/offer/{PublishOfferResponse.java => CreateOfferResponse.java} (93%) rename http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/{UserProfileResponse.java => CreateUserIdentityResponse.java} (89%) rename http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/{UserIdentityPreparation.java => KeyMaterialResponse.java} (75%) diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceRestApi.java index 28b38b199b..c0358680a8 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceRestApi.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceRestApi.java @@ -60,7 +60,7 @@ public MarketPriceRestApi(MarketPriceService marketPriceService) { @ApiResponse( responseCode = "200", description = "Market price quotes retrieved successfully", - content = @Content(schema = @Schema(implementation = MarketPriceResponse.class)) + content = @Content(schema = @Schema(implementation = QuotesResponse.class)) ), @ApiResponse(responseCode = "404", description = "Market price quotes not found"), @ApiResponse(responseCode = "500", description = "Internal server error") @@ -81,7 +81,7 @@ public Response getQuotes() { return buildNotFoundResponse("No market price quotes found."); } - return buildOkResponse(new MarketPriceResponse(result)); + return buildOkResponse(new QuotesResponse(result)); } catch (Exception ex) { log.error("Failed to retrieve market price quotes", ex); return buildErrorResponse("An error occurred while retrieving market prices."); diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceResponse.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/QuotesResponse.java similarity index 96% rename from http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceResponse.java rename to http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/QuotesResponse.java index 5b36fec67e..a192a4bebf 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/MarketPriceResponse.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/market_price/QuotesResponse.java @@ -25,6 +25,6 @@ /** * Response DTO for market price quotes. */ -public record MarketPriceResponse( +public record QuotesResponse( @Schema(description = "Map of currency codes to market price quotes") Map quotes) { } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferRequest.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/CreateOfferRequest.java similarity index 68% rename from http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferRequest.java rename to http-api/src/main/java/bisq/http_api/rest_api/domain/offer/CreateOfferRequest.java index cae0125bc3..5adf13f115 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferRequest.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/CreateOfferRequest.java @@ -24,11 +24,11 @@ import java.util.Set; -public record PublishOfferRequest(DirectionDto direction, - MarketDto market, - Set bitcoinPaymentMethods, - Set fiatPaymentMethods, - AmountSpecDto amountSpec, - PriceSpecDto priceSpec, - Set supportedLanguageCodes) { +public record CreateOfferRequest(DirectionDto direction, + MarketDto market, + Set bitcoinPaymentMethods, + Set fiatPaymentMethods, + AmountSpecDto amountSpec, + PriceSpecDto priceSpec, + Set supportedLanguageCodes) { } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferResponse.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/CreateOfferResponse.java similarity index 93% rename from http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferResponse.java rename to http-api/src/main/java/bisq/http_api/rest_api/domain/offer/CreateOfferResponse.java index 63d8e55462..fac78179f6 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/PublishOfferResponse.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/CreateOfferResponse.java @@ -17,5 +17,5 @@ package bisq.http_api.rest_api.domain.offer; -public record PublishOfferResponse(String offerId) { +public record CreateOfferResponse(String offerId) { } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java index fe7736a959..b0bccda22d 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/offer/OfferRestApi.java @@ -102,7 +102,7 @@ public OfferRestApi(ChatService chatService, description = "Creates a Bisq Easy Offer and publish it to the network.", requestBody = @RequestBody( description = "", - content = @Content(schema = @Schema(implementation = PublishOfferRequest.class)) + content = @Content(schema = @Schema(implementation = CreateOfferRequest.class)) ), responses = { @ApiResponse(responseCode = "201", description = "", @@ -111,7 +111,7 @@ public OfferRestApi(ChatService chatService, @ApiResponse(responseCode = "500", description = "Internal server error") } ) - public void createOffer(PublishOfferRequest request, @Suspended AsyncResponse asyncResponse) { + public void createOffer(CreateOfferRequest request, @Suspended AsyncResponse asyncResponse) { asyncResponse.setTimeout(10, TimeUnit.SECONDS); asyncResponse.setTimeoutHandler(response -> { response.resume(buildResponse(Response.Status.SERVICE_UNAVAILABLE, "Request timed out")); @@ -157,14 +157,13 @@ public void createOffer(PublishOfferRequest request, @Suspended AsyncResponse as new Date().getTime(), false); bisqEasyOfferbookChannelService.publishChatMessage(myOfferMessage, userIdentity).get(); - asyncResponse.resume(buildResponse(Response.Status.CREATED, new PublishOfferResponse(bisqEasyOffer.getId()))); + asyncResponse.resume(buildResponse(Response.Status.CREATED, new CreateOfferResponse(bisqEasyOffer.getId()))); } catch (InterruptedException e) { Thread.currentThread().interrupt(); asyncResponse.resume(buildErrorResponse("Thread was interrupted.")); } catch (IllegalArgumentException e) { asyncResponse.resume(buildResponse(Response.Status.BAD_REQUEST, "Invalid input: " + e.getMessage())); } catch (Exception e) { - log.error("Error publishing offer", e); asyncResponse.resume(buildErrorResponse("An unexpected error occurred.")); } } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TradeRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TradeRestApi.java index ada40e67af..f7bdf9a746 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TradeRestApi.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/trade/TradeRestApi.java @@ -30,7 +30,7 @@ import bisq.common.util.StringUtils; import bisq.contract.bisq_easy.BisqEasyContract; import bisq.http_api.rest_api.domain.RestApiBase; -import bisq.http_api.rest_api.domain.offer.PublishOfferRequest; +import bisq.http_api.rest_api.domain.offer.CreateOfferRequest; import bisq.i18n.Res; import bisq.offer.bisq_easy.BisqEasyOffer; import bisq.offer.payment_method.BitcoinPaymentMethodSpec; @@ -105,7 +105,7 @@ public TradeRestApi(ChatService chatService, description = "Create a Trade by Taking a Bisq Easy Offer.", requestBody = @RequestBody( description = "", - content = @Content(schema = @Schema(implementation = PublishOfferRequest.class)) + content = @Content(schema = @Schema(implementation = CreateOfferRequest.class)) ), responses = { @ApiResponse(responseCode = "201", description = "", @@ -193,7 +193,6 @@ public void takeOffer(TakeOfferRequest request, @Suspended AsyncResponse asyncRe } catch (IllegalArgumentException e) { asyncResponse.resume(buildResponse(Response.Status.BAD_REQUEST, "Invalid input: " + e.getMessage())); } catch (Exception e) { - log.error("Error publishing offer", e); asyncResponse.resume(buildErrorResponse("An unexpected error occurred.")); } } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityRequest.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityRequest.java index 239e5e12e2..d0b5731920 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityRequest.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityRequest.java @@ -21,7 +21,7 @@ import lombok.Data; @Data -@Schema(description = "Request payload for creating a new user identity.") +@Schema(description = "Request key material for creating a new user identity.") public class CreateUserIdentityRequest { @Schema(description = "Nickname for the user", example = "Satoshi", required = true) private String nickName; @@ -32,6 +32,6 @@ public class CreateUserIdentityRequest { @Schema(description = "User statement", example = "I am Satoshi") private String statement = ""; - @Schema(description = "Prepared data as JSON object", required = true) - private UserIdentityPreparation userIdentityPreparation; + @Schema(description = "Key material for creating an user identity", required = true) + private KeyMaterialResponse keyMaterialResponse; } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserProfileResponse.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityResponse.java similarity index 89% rename from http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserProfileResponse.java rename to http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityResponse.java index b0da323a33..8a85cdaef0 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserProfileResponse.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/CreateUserIdentityResponse.java @@ -24,11 +24,11 @@ @Getter @Schema(name = "UserProfileResponse", description = "Response payload containing the user profile ID.") -public class UserProfileResponse { +public class CreateUserIdentityResponse { private final String userProfileId; @JsonCreator - public UserProfileResponse(@JsonProperty("userProfileId") String userProfileId) { + public CreateUserIdentityResponse(@JsonProperty("userProfileId") String userProfileId) { this.userProfileId = userProfileId; } } diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityPreparation.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/KeyMaterialResponse.java similarity index 75% rename from http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityPreparation.java rename to http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/KeyMaterialResponse.java index 57336dc046..e715216cfc 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityPreparation.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/KeyMaterialResponse.java @@ -23,8 +23,8 @@ import lombok.Getter; @Getter -@Schema(name = "UserIdentityPreparation") -public final class UserIdentityPreparation { +@Schema(name = "KeyMaterial") +public final class KeyMaterialResponse { @Schema(description = "Key pair", example = "{ \"privateKey\": \"MIGNAgEAMBAGByqGSM49AgEGBSuBBAAKBHYwdAIBAQQgky6PNO163DColHrGmSNMgY93amwpAO8ZA8/Pb+Xl5magBwYFK4EEAAqhRANCAARyZim9kPgZixR2+ALUs72fO2zzSkeV89w4oQpkRUct5ob4yHRIIwwrggjoCGmNUWqX/pNA18R46vNYTp8NWuSu\", \"publicKey\": \"MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEcmYpvZD4GYsUdvgC1LO9nzts80pHlfPcOKEKZEVHLeaG+Mh0SCMMK4II6AhpjVFql/6TQNfEeOrzWE6fDVrkrg==\" }") private KeyPairDto keyPair; @@ -32,12 +32,10 @@ public final class UserIdentityPreparation { private String id; @Schema(description = "Nym", example = "Ravenously-Poignant-Coordinate-695") private String nym; - @Schema(description = "User statement", - example = "{ \"payload\": \"[-80, -19, -60, 119, -20, -106, 115, 121, -122, 122, -28, 75, 30, 3, 15, -92, -8, -26, -125, 39]\", \"counter\": 93211, \"difficulty\": 65536.0, \"solution\": [0, 0, 0, 0, 0, 1, 108, 27], \"duration\": 19 }") private ProofOfWorkDto proofOfWork; - public static UserIdentityPreparation from(KeyPairDto keyPair, String id, String nym, ProofOfWorkDto proofOfWork) { - UserIdentityPreparation dto = new UserIdentityPreparation(); + public static KeyMaterialResponse from(KeyPairDto keyPair, String id, String nym, ProofOfWorkDto proofOfWork) { + KeyMaterialResponse dto = new KeyMaterialResponse(); dto.keyPair = keyPair; dto.id = id; dto.nym = nym; diff --git a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java index aba7ccc429..75e286175d 100644 --- a/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java +++ b/http-api/src/main/java/bisq/http_api/rest_api/domain/user_identity/UserIdentityRestApi.java @@ -36,6 +36,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.*; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import lombok.extern.slf4j.Slf4j; @@ -44,6 +46,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j @@ -63,17 +66,21 @@ public UserIdentityRestApi(SecurityService securityService, UserIdentityService } @GET - @Path("/preparation") + @Path("/key-material") @Operation( summary = "Generate Prepared Data", description = "Generates a key pair, public key hash, Nym, and proof of work for a new user identity.", responses = { @ApiResponse(responseCode = "201", description = "Prepared data generated successfully", - content = @Content(schema = @Schema(implementation = UserIdentityPreparation.class))), + content = @Content(schema = @Schema(implementation = KeyMaterialResponse.class))), @ApiResponse(responseCode = "500", description = "Internal server error") } ) - public Response getUserIdentityPreparation() { + public void getKeyMaterial(@Suspended AsyncResponse asyncResponse) { + asyncResponse.setTimeout(5, TimeUnit.SECONDS); + asyncResponse.setTimeoutHandler(response -> { + response.resume(buildResponse(Response.Status.SERVICE_UNAVAILABLE, "Request timed out")); + }); try { KeyPair keyPair = securityService.getKeyBundleService().generateKeyPair(); byte[] pubKeyHash = DigestUtil.hash(keyPair.getPublic().getEncoded()); @@ -82,14 +89,65 @@ public Response getUserIdentityPreparation() { String nym = NymIdGenerator.generate(pubKeyHash, proofOfWork.getSolution()); KeyPairDto keyPairDto = DtoMappings.KeyPairDtoMapping.from(keyPair); ProofOfWorkDto proofOfWorkDto = DtoMappings.ProofOfWorkDtoMapping.from(proofOfWork); - UserIdentityPreparation userIdentityPreparation = UserIdentityPreparation.from(keyPairDto, id, nym, proofOfWorkDto); - return buildResponse(Response.Status.CREATED, userIdentityPreparation); + KeyMaterialResponse keyMaterialResponse = KeyMaterialResponse.from(keyPairDto, id, nym, proofOfWorkDto); + asyncResponse.resume(buildResponse(Response.Status.CREATED, keyMaterialResponse)); } catch (Exception e) { log.error("Error generating prepared data", e); - return buildErrorResponse("Could not generate prepared data."); + asyncResponse.resume(buildErrorResponse("Could not generate prepared data.")); } } + + @POST + @Operation( + summary = "Create User Identity and Publish User Profile", + description = "Creates a new user identity and publishes the associated user profile.", + requestBody = @RequestBody( + description = "Request payload containing user nickname, terms, statement, and prepared data.", + content = @Content(schema = @Schema(implementation = CreateUserIdentityRequest.class)) + ), + responses = { + @ApiResponse(responseCode = "201", description = "User identity created successfully", + content = @Content(schema = @Schema(example = "{ \"userProfileId\": \"d22d7b62ef442b5df03378f134bc8f54a2171cba\" }"))), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "500", description = "Internal server error") + } + ) + public void createUserIdentity(CreateUserIdentityRequest request, + @Suspended AsyncResponse asyncResponse) { + asyncResponse.setTimeout(10, TimeUnit.SECONDS); + asyncResponse.setTimeoutHandler(response -> { + response.resume(buildResponse(Response.Status.SERVICE_UNAVAILABLE, "Request timed out")); + }); + + try { + KeyMaterialResponse keyMaterialResponse = request.getKeyMaterialResponse(); + KeyPairDto keyPairDto = keyMaterialResponse.getKeyPair(); + KeyPair keyPair = DtoMappings.KeyPairDtoMapping.toPojo(keyPairDto); + byte[] pubKeyHash = DigestUtil.hash(keyPair.getPublic().getEncoded()); + int avatarVersion = 0; + ProofOfWorkDto proofOfWorkDto = keyMaterialResponse.getProofOfWork(); + ProofOfWork proofOfWork = DtoMappings.ProofOfWorkDtoMapping.toPojo(proofOfWorkDto); + UserIdentity userIdentity = userIdentityService.createAndPublishNewUserProfile(request.getNickName(), + keyPair, + pubKeyHash, + proofOfWork, + avatarVersion, + request.getTerms(), + request.getStatement()).get(); + + asyncResponse.resume(buildResponse(Response.Status.CREATED, new CreateUserIdentityResponse(userIdentity.getId()))); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + asyncResponse.resume(buildErrorResponse("Thread was interrupted.")); + } catch (IllegalArgumentException e) { + asyncResponse.resume(buildResponse(Response.Status.BAD_REQUEST, "Invalid input: " + e.getMessage())); + } catch (Exception e) { + asyncResponse.resume(buildErrorResponse("An unexpected error occurred.")); + } + } + + @GET @Path("/{id}") @Operation( @@ -149,48 +207,4 @@ public Response getSelectedUserProfile() { UserProfile userProfile = selectedUserIdentity.getUserProfile(); return buildOkResponse(userProfile); } - - // todo use AsyncResponse - @POST - @Operation( - summary = "Create User Identity and Publish User Profile", - description = "Creates a new user identity and publishes the associated user profile.", - requestBody = @RequestBody( - description = "Request payload containing user nickname, terms, statement, and prepared data.", - content = @Content(schema = @Schema(implementation = CreateUserIdentityRequest.class)) - ), - responses = { - @ApiResponse(responseCode = "201", description = "User identity created successfully", - content = @Content(schema = @Schema(example = "{ \"userProfileId\": \"d22d7b62ef442b5df03378f134bc8f54a2171cba\" }"))), - @ApiResponse(responseCode = "400", description = "Invalid input"), - @ApiResponse(responseCode = "500", description = "Internal server error") - } - ) - public Response createAndPublishNewUserProfile(CreateUserIdentityRequest request) { - try { - UserIdentityPreparation userIdentityPreparation = request.getUserIdentityPreparation(); - KeyPairDto keyPairDto = userIdentityPreparation.getKeyPair(); - KeyPair keyPair = DtoMappings.KeyPairDtoMapping.toPojo(keyPairDto); - byte[] pubKeyHash = DigestUtil.hash(keyPair.getPublic().getEncoded()); - int avatarVersion = 0; - ProofOfWorkDto proofOfWorkDto = userIdentityPreparation.getProofOfWork(); - ProofOfWork proofOfWork = DtoMappings.ProofOfWorkDtoMapping.toPojo(proofOfWorkDto); - UserIdentity userIdentity = userIdentityService.createAndPublishNewUserProfile(request.getNickName(), - keyPair, - pubKeyHash, - proofOfWork, - avatarVersion, - request.getTerms(), - request.getStatement()).get(); - return buildResponse(Response.Status.CREATED, new UserProfileResponse(userIdentity.getId())); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return buildErrorResponse("Thread was interrupted."); - } catch (IllegalArgumentException e) { - return buildResponse(Response.Status.BAD_REQUEST, "Invalid input: " + e.getMessage()); - } catch (Exception e) { - log.error("Error creating user identity", e); - return buildErrorResponse("An unexpected error occurred."); - } - } }