diff --git a/api-schema/src/main/java/org/stellar/anchor/api/callback/GetRateResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/callback/GetRateResponse.java index 48b43015a6..0720ef5f36 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/callback/GetRateResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/callback/GetRateResponse.java @@ -5,7 +5,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.stellar.anchor.api.sep.sep38.RateFee; +import org.stellar.anchor.api.shared.FeeDetails; import reactor.util.annotation.Nullable; /** @@ -37,7 +37,7 @@ public GetRateResponse(Rate rate) { * @return a GET /rate response with price, total_price, sell_amount, buy_amount and fee. */ public static GetRateResponse indicativePrice( - String price, String sellAmount, String buyAmount, RateFee fee) { + String price, String sellAmount, String buyAmount, FeeDetails fee) { Rate rate = Rate.builder().price(price).sellAmount(sellAmount).buyAmount(buyAmount).fee(fee).build(); return new GetRateResponse(rate); @@ -60,6 +60,6 @@ public static class Rate { @Nullable Instant expiresAt; - @Nullable RateFee fee; + @Nullable FeeDetails fee; } } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/GetQuoteResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/GetQuoteResponse.java index da3a703f97..b51e486123 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/GetQuoteResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/GetQuoteResponse.java @@ -6,7 +6,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; -import org.stellar.anchor.api.sep.sep38.RateFee; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.StellarId; @Data @@ -51,5 +51,5 @@ public class GetQuoteResponse { @SerializedName("created_at") Instant createdAt; - RateFee fee; + FeeDetails fee; } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java index 40a26b7ff3..97a720a594 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java @@ -42,8 +42,12 @@ public class PlatformTransactionData { Amount amountOut; @SerializedName("amount_fee") + @Deprecated // ANCHOR-636 Amount amountFee; + @SerializedName("fee_details") + FeeDetails feeDetails; + @SerializedName("quote_id") String quoteId; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyAmountsUpdatedRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyAmountsUpdatedRequest.java index 5e2963153f..34cced4a01 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyAmountsUpdatedRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyAmountsUpdatedRequest.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; +import org.stellar.anchor.api.shared.FeeDetails; @Data @SuperBuilder @@ -18,6 +19,9 @@ public class NotifyAmountsUpdatedRequest extends RpcMethodParamsRequest { private AmountRequest amountOut; @SerializedName("amount_fee") - @NotNull + @Deprecated // ANCHOR-636 private AmountRequest amountFee; + + @SerializedName("fee_details") + private FeeDetails feeDetails; } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyInteractiveFlowCompletedRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyInteractiveFlowCompletedRequest.java index 14c43eba76..d6b6d9cc5c 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyInteractiveFlowCompletedRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyInteractiveFlowCompletedRequest.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; +import org.stellar.anchor.api.shared.FeeDetails; @Data @SuperBuilder @@ -21,10 +22,13 @@ public class NotifyInteractiveFlowCompletedRequest extends RpcMethodParamsReques @SerializedName("amount_out") private AmountAssetRequest amountOut; - @NotNull @SerializedName("amount_fee") + @Deprecated // ANCHOR-636 private AmountAssetRequest amountFee; + @SerializedName("fee_details") + private FeeDetails feeDetails; + @SerializedName("amount_expected") private AmountRequest amountExpected; } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyOffchainFundsReceivedRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyOffchainFundsReceivedRequest.java index 9e14e958de..c4ba538f3d 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyOffchainFundsReceivedRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyOffchainFundsReceivedRequest.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; +import org.stellar.anchor.api.shared.FeeDetails; @Data @SuperBuilder @@ -26,5 +27,9 @@ public class NotifyOffchainFundsReceivedRequest extends RpcMethodParamsRequest { private AmountRequest amountOut; @SerializedName("amount_fee") + @Deprecated // ANCHOR-636 private AmountRequest amountFee; + + @SerializedName("fee_details") + private FeeDetails feeDetails; } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyOnchainFundsReceivedRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyOnchainFundsReceivedRequest.java index 8a26ff389e..0115589db7 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyOnchainFundsReceivedRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/NotifyOnchainFundsReceivedRequest.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; +import org.stellar.anchor.api.shared.FeeDetails; @Data @SuperBuilder @@ -24,5 +25,9 @@ public class NotifyOnchainFundsReceivedRequest extends RpcMethodParamsRequest { private AmountRequest amountOut; @SerializedName("amount_fee") + @Deprecated // ANCHOR-636 private AmountRequest amountFee; + + @SerializedName("fee_details") + private FeeDetails feeDetails; } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/RequestOffchainFundsRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/RequestOffchainFundsRequest.java index 9417086799..5257c9f5d3 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/RequestOffchainFundsRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/RequestOffchainFundsRequest.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.InstructionField; @Data @@ -21,8 +22,12 @@ public class RequestOffchainFundsRequest extends RpcMethodParamsRequest { private AmountAssetRequest amountOut; @SerializedName("amount_fee") + @Deprecated // ANCHOR-636 private AmountAssetRequest amountFee; + @SerializedName("fee_details") + private FeeDetails feeDetails; + @SerializedName("amount_expected") private AmountRequest amountExpected; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/RequestOnchainFundsRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/RequestOnchainFundsRequest.java index a8c6e57317..dcd6630393 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/RequestOnchainFundsRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/rpc/method/RequestOnchainFundsRequest.java @@ -5,6 +5,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; +import org.stellar.anchor.api.shared.FeeDetails; @Data @SuperBuilder @@ -19,8 +20,12 @@ public class RequestOnchainFundsRequest extends RpcMethodParamsRequest { private AmountAssetRequest amountOut; @SerializedName("amount_fee") + @Deprecated // ANCHOR-636 private AmountAssetRequest amountFee; + @SerializedName("fee_details") + private FeeDetails feeDetails; + @SerializedName("amount_expected") private AmountRequest amountExpected; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java index d77380d665..593b0d2600 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java @@ -3,6 +3,7 @@ import com.google.gson.annotations.SerializedName; import java.time.Instant; import lombok.Data; +import org.stellar.anchor.api.shared.FeeDetails; /** Base class of transaction responses for withdraw and deposit. */ @Data @@ -32,11 +33,16 @@ public class TransactionResponse { String amountOutAsset; @SerializedName("amount_fee") + @Deprecated // ANCHOR-636 String amountFee; @SerializedName("amount_fee_asset") + @Deprecated // ANCHOR-636 String amountFeeAsset; + @SerializedName("fee_details") + FeeDetails feeDetails; + @SerializedName("started_at") Instant startedAt; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/GetPriceResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/GetPriceResponse.java index 271bae545a..c4be86319f 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/GetPriceResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/GetPriceResponse.java @@ -3,6 +3,7 @@ import com.google.gson.annotations.SerializedName; import lombok.Builder; import lombok.Data; +import org.stellar.anchor.api.shared.FeeDetails; /** * The response body of the GET /price endpoint of SEP-38. @@ -25,5 +26,5 @@ public class GetPriceResponse { @SerializedName("buy_amount") String buyAmount; - RateFee fee; + FeeDetails fee; } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/Sep38QuoteResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/Sep38QuoteResponse.java index b62c6d5bf6..e4fd3a71c4 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/Sep38QuoteResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/Sep38QuoteResponse.java @@ -4,6 +4,7 @@ import java.time.Instant; import lombok.Builder; import lombok.Data; +import org.stellar.anchor.api.shared.FeeDetails; /** * The response body of the POST /quote endpoint of SEP-38. @@ -37,5 +38,5 @@ public class Sep38QuoteResponse { @SerializedName("buy_amount") String buyAmount; - RateFee fee; + FeeDetails fee; } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep6/Sep6TransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep6/Sep6TransactionResponse.java index e0e031b409..306e415ff8 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep6/Sep6TransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep6/Sep6TransactionResponse.java @@ -5,6 +5,7 @@ import java.util.Map; import lombok.Builder; import lombok.Data; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.InstructionField; import org.stellar.anchor.api.shared.Refunds; @@ -36,11 +37,16 @@ public class Sep6TransactionResponse { String amountOutAsset; @SerializedName("amount_fee") + @Deprecated // ANCHOR-636 String amountFee; @SerializedName("amount_fee_asset") + @Deprecated // ANCHOR-636 String amountFeeAsset; + @SerializedName("fee_details") + FeeDetails feeDetails; + @SerializedName("quote_id") String quoteId; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/RateFeeDetail.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/FeeDescription.java similarity index 66% rename from api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/RateFeeDetail.java rename to api-schema/src/main/java/org/stellar/anchor/api/shared/FeeDescription.java index 21d995938b..ad6b33f825 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/RateFeeDetail.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/FeeDescription.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.api.sep.sep38; +package org.stellar.anchor.api.shared; import lombok.AllArgsConstructor; import lombok.Data; @@ -7,12 +7,12 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class RateFeeDetail { +public class FeeDescription { String name; String description; String amount; - public RateFeeDetail(String name, String amount) { + public FeeDescription(String name, String amount) { this.name = name; this.amount = amount; } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/RateFee.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/FeeDetails.java similarity index 85% rename from api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/RateFee.java rename to api-schema/src/main/java/org/stellar/anchor/api/shared/FeeDetails.java index 800088f01e..cdf44f17b7 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/RateFee.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/FeeDetails.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.api.sep.sep38; +package org.stellar.anchor.api.shared; import java.math.BigDecimal; import java.math.RoundingMode; @@ -12,17 +12,17 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class RateFee { +public class FeeDetails { String total; String asset; - List details; + List details; - public RateFee(String total, String asset) { + public FeeDetails(String total, String asset) { this.total = total; this.asset = asset; } - public void addFeeDetail(RateFeeDetail feeDetail) { + public void addFeeDetail(FeeDescription feeDetail) { if (feeDetail == null || feeDetail.amount == null) { return; } diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index db8a9070f0..a0013e701c 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -594,8 +594,7 @@ public void validatedAndPopulateQuote( builder.amountInAsset(quote.getSellAsset()); builder.amountOut(quote.getBuyAmount()); builder.amountOutAsset(quote.getBuyAsset()); - builder.amountFee(quote.getFee().getTotal()); - builder.amountFeeAsset(quote.getFee().getAsset()); + builder.feeDetails(quote.getFee()); if (kind.equals(DEPOSIT.toString())) { builder.sourceAsset(quote.getSellAsset()); diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Transaction.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Transaction.java index 314eb7ae59..cbb07ef5c7 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Transaction.java @@ -1,6 +1,7 @@ package org.stellar.anchor.sep24; import org.stellar.anchor.SepTransaction; +import org.stellar.anchor.api.shared.FeeDetails; @SuppressWarnings("unused") public interface Sep24Transaction extends SepTransaction { @@ -205,8 +206,10 @@ public interface Sep24Transaction extends SepTransaction { * * @return amount_fee field of the SEP-24 transaction history. */ + @Deprecated // ANCHOR-636 String getAmountFee(); + @Deprecated // ANCHOR-636 void setAmountFee(String amountFee); /** @@ -216,10 +219,23 @@ public interface Sep24Transaction extends SepTransaction { * * @return amount_fee_asset field of the SEP-24 transaction history. */ + @Deprecated // ANCHOR-636 String getAmountFeeAsset(); + @Deprecated // ANCHOR-636 void setAmountFeeAsset(String amountFeeAsset); + /** + * Description of fee charged by the anchor. The schema for this object is defined in the Fee + * Details Object Schema and matches the one of SEP-38 quote's fee. + * + * @return Unified fields of SEP-24 transaction history complied from amount_fee, + * amount_fee_asset and fee_details + */ + FeeDetails getFeeDetails(); + + void setFeeDetails(FeeDetails feeDetails); + Boolean getRefunded(); void setRefunded(Boolean refunded); diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionBuilder.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionBuilder.java index 3298301a9d..9bf6dc9b8a 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionBuilder.java @@ -1,6 +1,7 @@ package org.stellar.anchor.sep24; import java.time.Instant; +import org.stellar.anchor.api.shared.FeeDetails; @SuppressWarnings("unused") public class Sep24TransactionBuilder { @@ -105,11 +106,6 @@ public Sep24TransactionBuilder amountOut(String amountOut) { return this; } - public Sep24TransactionBuilder amountFee(String amountFee) { - txn.setAmountFee(amountFee); - return this; - } - public Sep24TransactionBuilder amountInAsset(String amountInAsset) { txn.setAmountInAsset(amountInAsset); return this; @@ -120,8 +116,8 @@ public Sep24TransactionBuilder amountOutAsset(String amountOutAsset) { return this; } - public Sep24TransactionBuilder amountFeeAsset(String amountFeeAsset) { - txn.setAmountFeeAsset(amountFeeAsset); + public Sep24TransactionBuilder feeDetails(FeeDetails feeDetails) { + txn.setFeeDetails(feeDetails); return this; } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Helper.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Helper.java index 7bf35895d3..a2df546ae6 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Helper.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Helper.java @@ -8,8 +8,8 @@ public class Sep31Helper { public static boolean allAmountAvailable(Sep31Transaction txn) { return txn.getAmountIn() != null && txn.getAmountInAsset() != null - && txn.getAmountFee() != null - && txn.getAmountFeeAsset() != null + && txn.getFeeDetails() != null + && txn.getFeeDetails().getAsset() != null && txn.getAmountOut() != null && txn.getAmountOutAsset() != null; } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java index acf37d26fb..5d9902e06c 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java @@ -58,6 +58,7 @@ import org.stellar.anchor.api.sep.sep31.Sep31PostTransactionRequest; import org.stellar.anchor.api.sep.sep31.Sep31PostTransactionResponse; import org.stellar.anchor.api.shared.Amount; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.SepDepositInfo; import org.stellar.anchor.api.shared.StellarId; import org.stellar.anchor.asset.AssetService; @@ -187,15 +188,24 @@ public Sep31PostTransactionResponse postTransaction( .memo(sep10Jwt.getAccountMemo()) .build(); - Amount fee = Context.get().getFee(); + Sep38Quote quote = Context.get().getQuote(); + FeeDetails feeDetails; + + if (quote != null) { + feeDetails = quote.getFee(); + } else { + Amount fee = Context.get().getFee(); + + feeDetails = new FeeDetails(fee.getAmount(), fee.getAsset(), null); + } + Instant now = Instant.now(); Sep31Transaction txn = new Sep31TransactionBuilder(sep31TransactionStore) .id(generateSepTransactionId()) .status(SepTransactionStatus.PENDING_SENDER.getStatus()) .statusEta(null) - .amountFee(fee.getAmount()) - .amountFeeAsset(fee.getAsset()) + .feeDetails(feeDetails) .startedAt(now) .updatedAt(now) // this will be overwritten by the sep31TransactionStore#save method. .completedAt(null) diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java index 9b7ec64415..974ffe9621 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java @@ -34,14 +34,22 @@ public interface Sep31Transaction extends SepTransaction { void setAmountOutAsset(String amountOutAsset); + @Deprecated // ANCHOR-636 String getAmountFee(); + @Deprecated // ANCHOR-636 void setAmountFee(String amountFee); + @Deprecated // ANCHOR-636 String getAmountFeeAsset(); + @Deprecated // ANCHOR-636 void setAmountFeeAsset(String amountFeeAsset); + FeeDetails getFeeDetails(); + + void setFeeDetails(FeeDetails feeDetails); + String getStellarAccountId(); void setStellarAccountId(String stellarAccountId); diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java index a69b866396..8a895b9730 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; import org.stellar.anchor.api.sep.operation.Sep31Operation; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.StellarId; import org.stellar.anchor.api.shared.StellarTransaction; @@ -54,13 +55,9 @@ public Sep31TransactionBuilder amountOutAsset(String amountOutAsset) { return this; } - public Sep31TransactionBuilder amountFee(String amountFee) { - txn.setAmountFee(amountFee); - return this; - } + public Sep31TransactionBuilder feeDetails(FeeDetails feeDetails) { + txn.setFeeDetails(feeDetails); - public Sep31TransactionBuilder amountFeeAsset(String amountFeeAsset) { - txn.setAmountFeeAsset(amountFeeAsset); return this; } diff --git a/core/src/main/java/org/stellar/anchor/sep38/Sep38Quote.java b/core/src/main/java/org/stellar/anchor/sep38/Sep38Quote.java index 4715b37380..e35e9bef7d 100644 --- a/core/src/main/java/org/stellar/anchor/sep38/Sep38Quote.java +++ b/core/src/main/java/org/stellar/anchor/sep38/Sep38Quote.java @@ -1,7 +1,7 @@ package org.stellar.anchor.sep38; import java.time.Instant; -import org.stellar.anchor.api.sep.sep38.RateFee; +import org.stellar.anchor.api.shared.FeeDetails; public interface Sep38Quote { String getId(); @@ -65,7 +65,7 @@ public interface Sep38Quote { void setTransactionId(String transactionId); - RateFee getFee(); + FeeDetails getFee(); - void setFee(RateFee fee); + void setFee(FeeDetails fee); } diff --git a/core/src/main/java/org/stellar/anchor/sep38/Sep38QuoteBuilder.java b/core/src/main/java/org/stellar/anchor/sep38/Sep38QuoteBuilder.java index 1888846e5f..8001fb7ee6 100644 --- a/core/src/main/java/org/stellar/anchor/sep38/Sep38QuoteBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep38/Sep38QuoteBuilder.java @@ -1,7 +1,7 @@ package org.stellar.anchor.sep38; import java.time.Instant; -import org.stellar.anchor.api.sep.sep38.RateFee; +import org.stellar.anchor.api.shared.FeeDetails; public class Sep38QuoteBuilder { final Sep38Quote quote; @@ -85,7 +85,7 @@ public Sep38QuoteBuilder transactionId(String transactionId) { return this; } - public Sep38QuoteBuilder fee(RateFee fee) { + public Sep38QuoteBuilder fee(FeeDetails fee) { quote.setFee(fee); return this; } diff --git a/core/src/main/java/org/stellar/anchor/sep6/ExchangeAmountsCalculator.java b/core/src/main/java/org/stellar/anchor/sep6/ExchangeAmountsCalculator.java index 595e2c1768..b0d3b18b74 100644 --- a/core/src/main/java/org/stellar/anchor/sep6/ExchangeAmountsCalculator.java +++ b/core/src/main/java/org/stellar/anchor/sep6/ExchangeAmountsCalculator.java @@ -10,7 +10,7 @@ import org.stellar.anchor.api.exception.BadRequestException; import org.stellar.anchor.api.exception.SepValidationException; import org.stellar.anchor.api.sep.AssetInfo; -import org.stellar.anchor.api.sep.sep38.RateFee; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.sep38.Sep38Quote; import org.stellar.anchor.sep38.Sep38QuoteStore; @@ -36,8 +36,7 @@ public Amounts calculateFromQuote(String quoteId, AssetInfo sellAsset, String se .amountInAsset(quote.getSellAsset()) .amountOut(quote.getBuyAmount()) .amountOutAsset(quote.getBuyAsset()) - .amountFee(quote.getFee().getTotal()) - .amountFeeAsset(quote.getFee().getAsset()) + .feeDetails(quote.getFee()) .build(); } @@ -81,7 +80,7 @@ public Sep38Quote validateQuoteAgainstRequestInfo( sellAmount, quote.getSellAmount())); } - RateFee fee = quote.getFee(); + FeeDetails fee = quote.getFee(); if (fee == null) { throw new SepValidationException("Quote is missing the 'fee' field"); } @@ -97,7 +96,6 @@ public static class Amounts { String amountInAsset; String amountOut; String amountOutAsset; - String amountFee; - String amountFeeAsset; + FeeDetails feeDetails; } } diff --git a/core/src/main/java/org/stellar/anchor/sep6/Sep6Service.java b/core/src/main/java/org/stellar/anchor/sep6/Sep6Service.java index 5d413dd73c..5ac0f4469d 100644 --- a/core/src/main/java/org/stellar/anchor/sep6/Sep6Service.java +++ b/core/src/main/java/org/stellar/anchor/sep6/Sep6Service.java @@ -14,6 +14,7 @@ import org.stellar.anchor.api.sep.SepTransactionStatus; import org.stellar.anchor.api.sep.sep6.*; import org.stellar.anchor.api.sep.sep6.InfoResponse.*; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.auth.Sep10Jwt; import org.stellar.anchor.client.ClientFinder; @@ -197,8 +198,7 @@ public StartDepositResponse depositExchange(Sep10Jwt token, StartDepositExchange .amountInAsset(sellAsset.getSep38AssetName()) .amountOut("0") .amountOutAsset(buyAsset.getSep38AssetName()) - .amountFee("0") - .amountFeeAsset(sellAsset.getSep38AssetName()) + .feeDetails(new FeeDetails("0", sellAsset.getSep38AssetName(), null)) .build(); } @@ -218,8 +218,7 @@ public StartDepositResponse depositExchange(Sep10Jwt token, StartDepositExchange .amountInAsset(amounts.getAmountInAsset()) .amountOut(amounts.getAmountOut()) .amountOutAsset(amounts.getAmountOutAsset()) - .amountFee(amounts.getAmountFee()) - .amountFeeAsset(amounts.getAmountFeeAsset()) + .feeDetails(amounts.feeDetails) .amountExpected(request.getAmount()) .startedAt(Instant.now()) .sep10Account(token.getAccount()) @@ -364,8 +363,7 @@ public StartWithdrawResponse withdrawExchange( .amountInAsset(sellAsset.getSep38AssetName()) .amountOut("0") .amountOutAsset(buyAsset.getSep38AssetName()) - .amountFee("0") - .amountFeeAsset(sellAsset.getSep38AssetName()) + .feeDetails(new FeeDetails("0", sellAsset.getSep38AssetName(), null)) .build(); } @@ -382,8 +380,7 @@ public StartWithdrawResponse withdrawExchange( .amountInAsset(amounts.getAmountInAsset()) .amountOut(amounts.getAmountOut()) .amountOutAsset(amounts.getAmountOutAsset()) - .amountFee(amounts.getAmountFee()) - .amountFeeAsset(amounts.getAmountFeeAsset()) + .feeDetails(amounts.getFeeDetails()) .amountExpected(request.getAmount()) .startedAt(Instant.now()) .sep10Account(token.getAccount()) diff --git a/core/src/main/java/org/stellar/anchor/sep6/Sep6Transaction.java b/core/src/main/java/org/stellar/anchor/sep6/Sep6Transaction.java index dbfaa4638f..f17e4da6a9 100644 --- a/core/src/main/java/org/stellar/anchor/sep6/Sep6Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep6/Sep6Transaction.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; import org.stellar.anchor.SepTransaction; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.InstructionField; import org.stellar.anchor.api.shared.Refunds; @@ -184,8 +185,10 @@ public interface Sep6Transaction extends SepTransaction { * * @return the amount of fee charged by the anchor. */ + @Deprecated // ANCHOR-636 String getAmountFee(); + @Deprecated // ANCHOR-636 void setAmountFee(String amountFee); /** @@ -194,10 +197,22 @@ public interface Sep6Transaction extends SepTransaction { * * @return the asset in which fees are calculated in. */ + @Deprecated // ANCHOR-636 String getAmountFeeAsset(); + @Deprecated // ANCHOR-636 void setAmountFeeAsset(String amountFeeAsset); + void setFeeDetails(FeeDetails feeDetails); + + /** + * Description of fee charged by the anchor. Includes total break down of all fees that were + * charged. + * + * @return Description of fee charged by the anchor. + */ + FeeDetails getFeeDetails(); + /** * The amount requested by the user to deposit or withdraw. * diff --git a/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionBuilder.java b/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionBuilder.java index e6b491f2ad..84cc855196 100644 --- a/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionBuilder.java @@ -3,6 +3,7 @@ import java.time.Instant; import java.util.List; import java.util.Map; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.InstructionField; import org.stellar.anchor.api.shared.Refunds; @@ -98,16 +99,23 @@ public Sep6TransactionBuilder amountOutAsset(String amountOutAsset) { return this; } + @Deprecated // ANCHOR-636 public Sep6TransactionBuilder amountFee(String amountFee) { txn.setAmountFee(amountFee); return this; } + @Deprecated // ANCHOR-636 public Sep6TransactionBuilder amountFeeAsset(String amountFeeAsset) { txn.setAmountFeeAsset(amountFeeAsset); return this; } + public Sep6TransactionBuilder feeDetails(FeeDetails feeDetails) { + txn.setFeeDetails(feeDetails); + return this; + } + public Sep6TransactionBuilder amountExpected(String amountExpected) { txn.setAmountExpected(amountExpected); return this; diff --git a/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionUtils.java b/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionUtils.java index 7692f53f3d..203472cb47 100644 --- a/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionUtils.java +++ b/core/src/main/java/org/stellar/anchor/sep6/Sep6TransactionUtils.java @@ -47,6 +47,7 @@ public static Sep6TransactionResponse fromTxn(Sep6Transaction txn) { .amountOutAsset(txn.getAmountOutAsset()) .amountFee(txn.getAmountFee()) .amountFeeAsset(txn.getAmountFeeAsset()) + .feeDetails(txn.getFeeDetails()) .startedAt(txn.getStartedAt().toString()) .updatedAt(txn.getUpdatedAt().toString()) .completedAt(txn.getCompletedAt() != null ? txn.getCompletedAt().toString() : null) diff --git a/core/src/main/java/org/stellar/anchor/util/TransactionHelper.java b/core/src/main/java/org/stellar/anchor/util/TransactionHelper.java index dd790a150c..e5d57a74a7 100644 --- a/core/src/main/java/org/stellar/anchor/util/TransactionHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/TransactionHelper.java @@ -99,6 +99,7 @@ public static GetTransactionResponse toGetTransactionResponse(Sep31Transaction t .amountIn(new Amount(txn.getAmountIn(), txn.getAmountInAsset())) .amountOut(new Amount(txn.getAmountOut(), txn.getAmountOutAsset())) .amountFee(new Amount(txn.getAmountFee(), txn.getAmountFeeAsset())) + .feeDetails(txn.getFeeDetails()) .quoteId(txn.getQuoteId()) .startedAt(txn.getStartedAt()) .updatedAt(txn.getUpdatedAt()) @@ -134,6 +135,7 @@ public static GetTransactionResponse toGetTransactionResponse( .amountIn(Amount.create(txn.getAmountIn(), amountInAsset)) .amountOut(Amount.create(txn.getAmountOut(), amountOutAsset)) .amountFee(Amount.create(txn.getAmountFee(), amountFeeAsset)) + .feeDetails(txn.getFeeDetails()) .quoteId(txn.getQuoteId()) .startedAt(txn.getStartedAt()) .updatedAt(txn.getUpdatedAt()) @@ -182,6 +184,7 @@ public static GetTransactionResponse toGetTransactionResponse( .amountIn(Amount.create(txn.getAmountIn(), amountInAsset)) .amountOut(Amount.create(txn.getAmountOut(), amountOutAsset)) .amountFee(Amount.create(txn.getAmountFee(), amountFeeAsset)) + .feeDetails(txn.getFeeDetails()) .startedAt(txn.getStartedAt()) .updatedAt(txn.getUpdatedAt()) .completedAt(txn.getCompletedAt()) diff --git a/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Transaction.java b/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Transaction.java index c4f727790b..88af54110c 100644 --- a/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Transaction.java +++ b/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Transaction.java @@ -3,6 +3,8 @@ import java.time.Instant; import java.util.List; import lombok.Data; +import org.stellar.anchor.api.shared.FeeDescription; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.StellarTransaction; @Data @@ -46,4 +48,18 @@ public class PojoSep24Transaction implements Sep24Transaction { String quoteId; String sourceAsset; String destinationAsset; + List feeDetailsList; + + public void setFeeDetails(FeeDetails feeDetails) { + setAmountFee(feeDetails.getTotal()); + setAmountFeeAsset(feeDetails.getAsset()); + setFeeDetailsList(feeDetails.getDetails()); + } + + public FeeDetails getFeeDetails() { + if (getAmountFee() == null) { + return null; + } + return new FeeDetails(getAmountFee(), getAmountFeeAsset(), getFeeDetailsList()); + } } diff --git a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java index 9b4c625c03..b641fa18e7 100644 --- a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java +++ b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java @@ -5,6 +5,8 @@ import java.util.Map; import lombok.Data; import org.stellar.anchor.api.sep.operation.Sep31Operation; +import org.stellar.anchor.api.shared.FeeDescription; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.StellarId; import org.stellar.anchor.api.shared.StellarTransaction; @@ -41,6 +43,20 @@ public class PojoSep31Transaction implements Sep31Transaction { String receiverId; String senderId; StellarId creator; + List feeDetailsList; + + public void setFeeDetails(FeeDetails feeDetails) { + setAmountFee(feeDetails.getTotal()); + setAmountFeeAsset(feeDetails.getAsset()); + setFeeDetailsList(feeDetails.getDetails()); + } + + public FeeDetails getFeeDetails() { + if (getAmountFee() == null) { + return null; + } + return new FeeDetails(getAmountFee(), getAmountFeeAsset(), getFeeDetailsList()); + } @Override public void setRefunds(Sep31Refunds sep31Refunds) { diff --git a/core/src/test/java/org/stellar/anchor/sep38/PojoSep38Quote.java b/core/src/test/java/org/stellar/anchor/sep38/PojoSep38Quote.java index 3e6bb15149..86229db3b9 100644 --- a/core/src/test/java/org/stellar/anchor/sep38/PojoSep38Quote.java +++ b/core/src/test/java/org/stellar/anchor/sep38/PojoSep38Quote.java @@ -3,7 +3,7 @@ import com.google.gson.annotations.SerializedName; import java.time.Instant; import lombok.Data; -import org.stellar.anchor.api.sep.sep38.RateFee; +import org.stellar.anchor.api.shared.FeeDetails; @Data public class PojoSep38Quote implements Sep38Quote { @@ -30,5 +30,5 @@ public class PojoSep38Quote implements Sep38Quote { String creatorMemo; String creatorMemoType; String transactionId; - RateFee fee; + FeeDetails fee; } diff --git a/core/src/test/java/org/stellar/anchor/sep6/PojoSep6Transaction.java b/core/src/test/java/org/stellar/anchor/sep6/PojoSep6Transaction.java index 91fbb5b468..0044191cc6 100644 --- a/core/src/test/java/org/stellar/anchor/sep6/PojoSep6Transaction.java +++ b/core/src/test/java/org/stellar/anchor/sep6/PojoSep6Transaction.java @@ -4,9 +4,7 @@ import java.util.List; import java.util.Map; import lombok.Data; -import org.stellar.anchor.api.shared.InstructionField; -import org.stellar.anchor.api.shared.Refunds; -import org.stellar.anchor.api.shared.StellarTransaction; +import org.stellar.anchor.api.shared.*; @Data public class PojoSep6Transaction implements Sep6Transaction { @@ -52,4 +50,18 @@ public class PojoSep6Transaction implements Sep6Transaction { String requiredCustomerInfoMessage; List requiredCustomerInfoUpdates; Map instructions; + List feeDetailsList; + + public void setFeeDetails(FeeDetails feeDetails) { + setAmountFee(feeDetails.getTotal()); + setAmountFeeAsset(feeDetails.getAsset()); + setFeeDetailsList(feeDetails.getDetails()); + } + + public FeeDetails getFeeDetails() { + if (getAmountFee() == null) { + return null; + } + return new FeeDetails(getAmountFee(), getAmountFeeAsset(), getFeeDetailsList()); + } } diff --git a/core/src/test/kotlin/org/stellar/anchor/model/Sep24TransactionTest.kt b/core/src/test/kotlin/org/stellar/anchor/model/Sep24TransactionTest.kt index f15b4fe05a..4618068362 100644 --- a/core/src/test/kotlin/org/stellar/anchor/model/Sep24TransactionTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/model/Sep24TransactionTest.kt @@ -5,6 +5,7 @@ import io.mockk.mockk import java.time.Instant import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.stellar.anchor.api.shared.FeeDetails import org.stellar.anchor.sep24.PojoSep24Transaction import org.stellar.anchor.sep24.Sep24TransactionBuilder import org.stellar.anchor.sep24.Sep24TransactionStore @@ -22,10 +23,9 @@ internal class Sep24TransactionTest { .completedAt(instantNow) .withdrawAnchorAccount("account") .memo("memo") - .amountFee("20") + .feeDetails(FeeDetails("20", "USDC_Fee")) .amountInAsset("USDC_In") .amountOutAsset("USDC_Out") - .amountFeeAsset("USDC_Fee") .build() assertEquals(txn.transactionId, "txnId") diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt index 33cf9418f0..8af5f3d834 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -30,8 +30,8 @@ import org.stellar.anchor.api.sep.operation.Sep31Operation import org.stellar.anchor.api.sep.sep12.Sep12Status import org.stellar.anchor.api.sep.sep31.* import org.stellar.anchor.api.sep.sep31.Sep31PostTransactionRequest.Sep31TxnFields -import org.stellar.anchor.api.sep.sep38.RateFee import org.stellar.anchor.api.shared.Amount +import org.stellar.anchor.api.shared.FeeDetails import org.stellar.anchor.api.shared.SepDepositInfo import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.DefaultAssetService @@ -746,7 +746,7 @@ class Sep31ServiceTest { quote.totalPrice = "0.008" quote.price = "0.0072" quote.expiresAt = Instant.now() - quote.fee = RateFee("10", stellarUSDC) + quote.fee = FeeDetails("10", stellarUSDC) every { quoteStore.findByQuoteId("my_quote_id") } returns quote val senderId = "d2bd1412-e2f6-4047-ad70-a1a2f133b25c" diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt index 8187d9df58..ac7101058c 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt @@ -14,10 +14,7 @@ import org.stellar.anchor.api.sep.SepTransactionStatus import org.stellar.anchor.api.sep.operation.Sep31Operation import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse.Sep31RefundPayment -import org.stellar.anchor.api.shared.Amount -import org.stellar.anchor.api.shared.StellarId -import org.stellar.anchor.api.shared.StellarPayment -import org.stellar.anchor.api.shared.StellarTransaction +import org.stellar.anchor.api.shared.* class Sep31TransactionTest { companion object { @@ -104,8 +101,7 @@ class Sep31TransactionTest { .amountInAsset(fiatUSD) .amountOut("98.0000000") .amountOutAsset(stellarUSDC) - .amountFee("2.0000") - .amountFeeAsset(fiatUSD) + .feeDetails(FeeDetails("2.0000", fiatUSD)) .stellarAccountId(TEST_ACCOUNT) .stellarMemo(TEST_MEMO) .stellarMemoType("text") diff --git a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt index 6983a6b885..1ccf753ad2 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt @@ -26,6 +26,8 @@ import org.stellar.anchor.api.sep.operation.Sep38Operation import org.stellar.anchor.api.sep.sep38.* import org.stellar.anchor.api.sep.sep38.Sep38Context.SEP31 import org.stellar.anchor.api.sep.sep38.Sep38Context.SEP6 +import org.stellar.anchor.api.shared.FeeDescription +import org.stellar.anchor.api.shared.FeeDetails import org.stellar.anchor.api.shared.StellarId import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.config.SecretConfig @@ -421,12 +423,12 @@ class Sep38ServiceTest { assertEquals("sell_amount exceeds max limit", ex.message) } - private fun mockSellAssetFee(sellAsset: String?): RateFee { + private fun mockSellAssetFee(sellAsset: String?): FeeDetails { assertNotNull(sellAsset) - val rateFee = RateFee("0", sellAsset) - rateFee.addFeeDetail(RateFeeDetail("Sell fee", "1.00")) - return rateFee + val feeDetails = FeeDetails("0", sellAsset) + feeDetails.addFeeDetail(FeeDescription("Sell fee", "1.00")) + return feeDetails } @Test diff --git a/core/src/test/kotlin/org/stellar/anchor/sep6/ExchangeAmountsCalculatorTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep6/ExchangeAmountsCalculatorTest.kt index e887d1543d..568b126095 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep6/ExchangeAmountsCalculatorTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep6/ExchangeAmountsCalculatorTest.kt @@ -13,7 +13,7 @@ import org.stellar.anchor.TestConstants.Companion.TEST_ASSET_SEP38_FORMAT import org.stellar.anchor.TestHelper import org.stellar.anchor.api.exception.BadRequestException import org.stellar.anchor.api.exception.SepValidationException -import org.stellar.anchor.api.sep.sep38.RateFee +import org.stellar.anchor.api.shared.FeeDetails import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.sep38.PojoSep38Quote @@ -44,7 +44,7 @@ class ExchangeAmountsCalculatorTest { buyAsset = "iso4217:USD" buyAmount = "98" fee = - RateFee().apply { + FeeDetails().apply { total = "2" asset = "iso4217:USD" } @@ -62,8 +62,7 @@ class ExchangeAmountsCalculatorTest { .amountInAsset(TEST_ASSET_SEP38_FORMAT) .amountOut("98") .amountOutAsset("iso4217:USD") - .amountFee("2") - .amountFeeAsset("iso4217:USD") + .feeDetails(FeeDetails("2", "iso4217:USD")) .build(), result ) diff --git a/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6ServiceTest.kt index dae20c04fe..35a0d2ba13 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6ServiceTest.kt @@ -24,6 +24,7 @@ import org.stellar.anchor.api.exception.SepNotAuthorizedException import org.stellar.anchor.api.exception.SepValidationException import org.stellar.anchor.api.sep.sep6.* import org.stellar.anchor.api.shared.Amount +import org.stellar.anchor.api.shared.FeeDetails import org.stellar.anchor.api.shared.RefundPayment import org.stellar.anchor.api.shared.Refunds import org.stellar.anchor.asset.AssetService @@ -333,8 +334,7 @@ class Sep6ServiceTest { .amountInAsset(sourceAsset) .amountOut("98") .amountOutAsset(TEST_ASSET_SEP38_FORMAT) - .amountFee("2") - .amountFeeAsset(TEST_ASSET_SEP38_FORMAT) + .feeDetails(FeeDetails("2", TEST_ASSET_SEP38_FORMAT)) .build() val request = @@ -896,8 +896,7 @@ class Sep6ServiceTest { .amountInAsset(TEST_ASSET_SEP38_FORMAT) .amountOut("98") .amountOutAsset(destinationAsset) - .amountFee("2") - .amountFeeAsset(destinationAsset) + .feeDetails(FeeDetails("2", destinationAsset)) .build() val request = diff --git a/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6ServiceTestData.kt b/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6ServiceTestData.kt index 20561c45f3..0b4d90a4fc 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6ServiceTestData.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6ServiceTestData.kt @@ -108,6 +108,9 @@ class Sep6ServiceTestData { "amount_out": "98", "amount_out_asset": "stellar:USDC:GABCD", "amount_fee": "2", + "fee_details": { + "total": "2" + }, "from": "GABCD", "to": "GABCD", "deposit_memo": "some memo", diff --git a/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6TransactionUtilsTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6TransactionUtilsTest.kt index f62bab48ed..bd2e615622 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6TransactionUtilsTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep6/Sep6TransactionUtilsTest.kt @@ -3,7 +3,6 @@ package org.stellar.anchor.sep6 import com.google.gson.Gson import java.time.Instant import java.util.* -import kotlin.test.assertEquals import org.junit.jupiter.api.Test import org.skyscreamer.jsonassert.JSONAssert import org.skyscreamer.jsonassert.JSONCompareMode @@ -33,6 +32,10 @@ class Sep6TransactionUtilsTest { "amount_out_asset": "$TEST_ASSET", "amount_fee": "1.00", "amount_fee_asset": "USD", + "fee_details": { + "total": "1.00", + "asset": "USD" + }, "from": "1234", "to": "$TEST_ASSET_ISSUER_ACCOUNT_ID", "deposit_memo_type": "text", @@ -142,6 +145,7 @@ class Sep6TransactionUtilsTest { amountOutAsset = "USDC" amountFee = "1.00" amountFeeAsset = "USD" + feeDetails = FeeDetails("1.00", "USD", null) amountExpected = "100.00" sep10Account = TEST_ACCOUNT sep10AccountMemo = TEST_MEMO diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateService.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateService.kt index f8d6da1e93..5fe6985543 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateService.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/callbacks/rate/RateService.kt @@ -9,8 +9,8 @@ import java.time.ZonedDateTime import java.util.* import org.stellar.anchor.api.callback.GetRateRequest import org.stellar.anchor.api.callback.GetRateResponse -import org.stellar.anchor.api.sep.sep38.RateFee -import org.stellar.anchor.api.sep.sep38.RateFeeDetail +import org.stellar.anchor.api.shared.FeeDescription +import org.stellar.anchor.api.shared.FeeDetails import org.stellar.reference.callbacks.BadRequestException import org.stellar.reference.callbacks.NotFoundException import org.stellar.reference.dao.QuoteRepository @@ -87,11 +87,11 @@ class RateService(private val quoteRepository: QuoteRepository) { price = getString(finalPrice, 10), totalPrice = getString(finalTotalPrice!!, 10), fee = - org.stellar.reference.model.RateFee( + org.stellar.reference.model.FeeDetails( fee.total, fee.asset, fee.details.map { - org.stellar.reference.model.RateFeeDetail(it.name, it.description, it.amount) + org.stellar.reference.model.FeeDescription(it.name, it.description, it.amount) } ) ) @@ -116,12 +116,12 @@ class RateService(private val quoteRepository: QuoteRepository) { private fun getRate(id: String): GetRateResponse.Rate { val quote = quoteRepository.get(id) ?: throw NotFoundException("Rate with quote id $id not found", id) - val rateFee = RateFee("0", quote.fee?.asset) + val feeDetails = FeeDetails("0", quote.fee?.asset) quote.fee ?.details ?.forEach( - fun(detail: org.stellar.reference.model.RateFeeDetail) { - rateFee.addFeeDetail(RateFeeDetail(detail.name, detail.description, detail.amount)) + fun(detail: org.stellar.reference.model.FeeDescription) { + feeDetails.addFeeDetail(FeeDescription(detail.name, detail.description, detail.amount)) } ) @@ -131,7 +131,7 @@ class RateService(private val quoteRepository: QuoteRepository) { .sellAmount(quote.sellAmount) .buyAmount(quote.buyAmount) .expiresAt(quote.expiresAt) - .fee(rateFee) + .fee(feeDetails) .build() } @@ -214,15 +214,15 @@ class RateService(private val quoteRepository: QuoteRepository) { } } - private fun getFee(sellAsset: String, buyAsset: String): RateFee { - val rateFee = RateFee("0", sellAsset) + private fun getFee(sellAsset: String, buyAsset: String): FeeDetails { + val feeDetails = FeeDetails("0", sellAsset) if (getPrice(sellAsset, buyAsset) == null) { - return rateFee + return feeDetails } - val sellAssetDetail = RateFeeDetail("Sell fee", "Fee related to selling the asset.", "1.00") - rateFee.addFeeDetail(sellAssetDetail) - return rateFee + val sellAssetDetail = FeeDescription("Sell fee", "Fee related to selling the asset.", "1.00") + feeDetails.addFeeDetail(sellAssetDetail) + return feeDetails } } } diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/dao/QuoteRepository.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/dao/QuoteRepository.kt index 6533ce5093..5ac07032b2 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/dao/QuoteRepository.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/dao/QuoteRepository.kt @@ -7,8 +7,8 @@ import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import org.stellar.anchor.util.GsonUtils +import org.stellar.reference.model.FeeDetails import org.stellar.reference.model.Quote -import org.stellar.reference.model.RateFee interface QuoteRepository { fun get(id: String): Quote? @@ -40,7 +40,7 @@ class JdbcQuoteRepository(private val db: Database) : QuoteRepository { clientId = it[Quotes.clientId], fee = it[Quotes.fee]?.let { fee -> - GsonUtils.getInstance().fromJson(fee, RateFee::class.java) + GsonUtils.getInstance().fromJson(fee, FeeDetails::class.java) } ) } diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/model/Quote.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/model/Quote.kt index 817c38705f..a97d3d31a5 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/model/Quote.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/model/Quote.kt @@ -20,11 +20,11 @@ data class Quote( @SerialName("buy_delivery_method") val buyDeliveryMethod: String?, @SerialName("country_code") val countryCode: String?, @SerialName("client_id") val clientId: String?, - val fee: RateFee?, + val fee: FeeDetails?, ) @Serializable -data class RateFee(val total: String?, val asset: String, val details: List?) +data class FeeDetails(val total: String?, val asset: String, val details: List?) @Serializable -data class RateFeeDetail(val name: String, val description: String, val amount: String?) +data class FeeDescription(val name: String, val description: String, val amount: String?) diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep38Quote.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep38Quote.java index d0155417ef..7745be7596 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep38Quote.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep38Quote.java @@ -4,7 +4,7 @@ import java.time.Instant; import javax.persistence.*; import lombok.Data; -import org.stellar.anchor.api.sep.sep38.RateFee; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.sep38.Sep38Quote; @Data @@ -69,5 +69,5 @@ public class JdbcSep38Quote implements Sep38Quote { @Convert(converter = RateFeeConverter.class) @Column(length = 1023) - RateFee fee; + FeeDetails fee; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java index 3e230aa298..30ebe4f00b 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java @@ -11,6 +11,8 @@ import lombok.Getter; import lombok.Setter; import org.hibernate.annotations.Type; +import org.stellar.anchor.api.shared.FeeDescription; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.StellarTransaction; import org.stellar.anchor.util.GsonUtils; @@ -52,6 +54,11 @@ public abstract class JdbcSepTransaction { @Column(name = "amount_fee_asset") String amountFeeAsset; + @SerializedName("fee_details") + @Column(name = "fee_details") + @Type(type = "json") + List feeDetailsList; + @SerializedName("started_at") @Column(name = "started_at") Instant startedAt; @@ -77,4 +84,17 @@ public abstract class JdbcSepTransaction { List stellarTransactions; public abstract String getProtocol(); + + public void setFeeDetails(FeeDetails feeDetails) { + setAmountFee(feeDetails.getTotal()); + setAmountFeeAsset(feeDetails.getAsset()); + setFeeDetailsList(feeDetails.getDetails()); + } + + public FeeDetails getFeeDetails() { + if (getAmountFee() == null) { + return null; + } + return new FeeDetails(getAmountFee(), getAmountFeeAsset(), getFeeDetailsList()); + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/RateFeeConverter.java b/platform/src/main/java/org/stellar/anchor/platform/data/RateFeeConverter.java index b543fde848..8c08b88e8c 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/RateFeeConverter.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/RateFeeConverter.java @@ -5,21 +5,21 @@ import java.lang.reflect.Type; import javax.persistence.AttributeConverter; import javax.persistence.Converter; -import org.stellar.anchor.api.sep.sep38.RateFee; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.util.GsonUtils; @Converter -public class RateFeeConverter implements AttributeConverter { +public class RateFeeConverter implements AttributeConverter { private static final Gson gson = GsonUtils.getInstance(); @Override - public String convertToDatabaseColumn(RateFee priceDetails) { + public String convertToDatabaseColumn(FeeDetails priceDetails) { return gson.toJson(priceDetails); } @Override - public RateFee convertToEntityAttribute(String customerInfoJSON) { - Type type = new TypeToken() {}.getType(); + public FeeDetails convertToEntityAttribute(String customerInfoJSON) { + Type type = new TypeToken() {}.getType(); return gson.fromJson(customerInfoJSON, type); } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/ClientStatusCallbackHandler.java b/platform/src/main/java/org/stellar/anchor/platform/event/ClientStatusCallbackHandler.java index 0f0b15412e..1a7ff15d1a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/event/ClientStatusCallbackHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/event/ClientStatusCallbackHandler.java @@ -150,6 +150,9 @@ private Sep24Transaction fromSep24Txn(GetTransactionResponse txn) { sep24Txn.setAmountFee(txn.getAmountFee().getAmount()); sep24Txn.setAmountFeeAsset(txn.getAmountFee().getAsset()); } + if (txn.getFeeDetails() != null) { + sep24Txn.setFeeDetails(txn.getFeeDetails()); + } sep24Txn.setStartedAt(txn.getStartedAt()); sep24Txn.setCompletedAt(txn.getCompletedAt()); sep24Txn.setExternalTransactionId(txn.getExternalTransactionId()); @@ -198,6 +201,9 @@ private Sep31Transaction fromSep31Txn(GetTransactionResponse txn) { sep24Txn.setAmountFee(txn.getAmountFee().getAmount()); sep24Txn.setAmountFeeAsset(txn.getAmountFee().getAsset()); } + if (txn.getFeeDetails() != null) { + sep24Txn.setFeeDetails(txn.getFeeDetails()); + } sep24Txn.setStartedAt(txn.getStartedAt()); sep24Txn.setCompletedAt(txn.getCompletedAt()); sep24Txn.setExternalTransactionId(txn.getExternalTransactionId()); diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyAmountsUpdatedHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyAmountsUpdatedHandler.java index 1c3ff4ce25..da38d28df1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyAmountsUpdatedHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyAmountsUpdatedHandler.java @@ -55,6 +55,11 @@ protected void validate(JdbcSepTransaction txn, NotifyAmountsUpdatedRequest requ throws InvalidParamsException, InvalidRequestException, BadRequestException { super.validate(txn, request); + if ((request.getAmountFee() == null && request.getFeeDetails() == null) + || (request.getAmountFee() != null && request.getFeeDetails() != null)) { + throw new InvalidParamsException("Either amount_fee or fee_details must be set"); + } + AssetValidationUtils.validateAsset( "amount_out", AmountAssetRequest.builder() @@ -63,14 +68,19 @@ protected void validate(JdbcSepTransaction txn, NotifyAmountsUpdatedRequest requ .build(), true, assetService); - AssetValidationUtils.validateAsset( - "amount_fee", - AmountAssetRequest.builder() - .amount(request.getAmountFee().getAmount()) - .asset(txn.getAmountFeeAsset()) - .build(), - true, - assetService); + if (request.getAmountFee() != null) { + AssetValidationUtils.validateAsset( + "amount_fee", + AmountAssetRequest.builder() + .amount(request.getAmountFee().getAmount()) + .asset(txn.getAmountFeeAsset()) + .build(), + true, + assetService); + } + if (request.getFeeDetails() != null) { + AssetValidationUtils.validateFeeDetails(request.getFeeDetails(), txn, assetService); + } } @Override @@ -112,6 +122,11 @@ protected Set getSupportedStatuses(JdbcSepTransaction txn) protected void updateTransactionWithRpcRequest( JdbcSepTransaction txn, NotifyAmountsUpdatedRequest request) throws InvalidParamsException { txn.setAmountOut(request.getAmountOut().getAmount()); - txn.setAmountFee(request.getAmountFee().getAmount()); + if (request.getAmountFee() != null) { + txn.setAmountFee(request.getAmountFee().getAmount()); + } else { + txn.setAmountFee(request.getFeeDetails().getTotal()); + txn.setFeeDetailsList(request.getFeeDetails().getDetails()); + } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyInteractiveFlowCompletedHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyInteractiveFlowCompletedHandler.java index 8f93606d56..e258055949 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyInteractiveFlowCompletedHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyInteractiveFlowCompletedHandler.java @@ -84,16 +84,30 @@ protected void validate(JdbcSepTransaction txn, NotifyInteractiveFlowCompletedRe break; } - AssetValidationUtils.validateAsset("amount_fee", request.getAmountFee(), true, assetService); + if ((request.getAmountFee() == null && request.getFeeDetails() == null) + || (request.getAmountFee() != null && request.getFeeDetails() != null)) { + throw new InvalidParamsException("Either amount_fee or fee_details must be set"); + } + + if (request.getAmountFee() != null) { + AssetValidationUtils.validateAsset("amount_fee", request.getAmountFee(), true, assetService); + } + if (request.getFeeDetails() != null) { + AssetValidationUtils.validateFeeDetails(request.getFeeDetails(), txn, assetService); + } + String feeAsset = + request.getFeeDetails() != null + ? request.getFeeDetails().getAsset() + : request.getAmountFee().getAsset(); switch (Kind.from(txn24.getKind())) { case DEPOSIT: - if (AssetValidationUtils.isStellarAsset(request.getAmountFee().getAsset())) { - throw new InvalidParamsException("amount_fee.asset should be non-stellar asset"); + if (AssetValidationUtils.isStellarAsset(feeAsset)) { + throw new InvalidParamsException("fee asset should be a non-stellar asset"); } break; case WITHDRAWAL: - if (!AssetValidationUtils.isStellarAsset(request.getAmountFee().getAsset())) { - throw new InvalidParamsException("amount_fee.asset should be stellar asset"); + if (!AssetValidationUtils.isStellarAsset(feeAsset)) { + throw new InvalidParamsException("fee asset should be a stellar asset"); } break; } @@ -139,8 +153,14 @@ protected void updateTransactionWithRpcRequest( txn24.setAmountOut(request.getAmountOut().getAmount()); txn24.setAmountOutAsset(request.getAmountOut().getAsset()); - txn24.setAmountFee(request.getAmountFee().getAmount()); - txn24.setAmountFeeAsset(request.getAmountFee().getAsset()); + if (request.getAmountFee() != null) { + txn24.setAmountFee(request.getAmountFee().getAmount()); + txn24.setAmountFeeAsset(request.getAmountFee().getAsset()); + } else { + txn24.setAmountFee(request.getFeeDetails().getTotal()); + txn24.setAmountFeeAsset(request.getFeeDetails().getAsset()); + txn24.setFeeDetailsList(request.getFeeDetails().getDetails()); + } if (request.getAmountExpected() != null) { txn24.setAmountExpected(request.getAmountExpected().getAmount()); diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOffchainFundsReceivedHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOffchainFundsReceivedHandler.java index e88d6e0ad2..41420f4f1d 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOffchainFundsReceivedHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOffchainFundsReceivedHandler.java @@ -69,19 +69,33 @@ protected void validate(JdbcSepTransaction txn, NotifyOffchainFundsReceivedReque throws InvalidParamsException, InvalidRequestException, BadRequestException { super.validate(txn, request); - if (!((request.getAmountIn() == null + // If none of the accepted combinations of input parameters satisfies -> throw an exception + if (! + // None of the amounts are provided + ((request.getAmountIn() == null && request.getAmountOut() == null - && request.getAmountFee() == null) - || (request.getAmountIn() != null + && request.getAmountFee() == null + && request.getFeeDetails() == null) + || + // All the amounts are provided (allow either amount_fee or fee_details) + (request.getAmountIn() != null && request.getAmountOut() != null - && request.getAmountFee() != null) - || (request.getAmountIn() != null + && (request.getAmountFee() != null || request.getFeeDetails() != null)) + || + // Only amount_in is provided + (request.getAmountIn() != null && request.getAmountOut() == null - && request.getAmountFee() == null))) { + && request.getAmountFee() == null + && request.getFeeDetails() == null))) { throw new InvalidParamsException( "Invalid amounts combination provided: all, none or only amount_in should be set"); } + // In case 2nd predicate in previous IF statement was TRUE + if (request.getAmountFee() != null && request.getFeeDetails() != null) { + throw new InvalidParamsException("Either amount_fee or fee_details should be set"); + } + if (request.getAmountIn() != null) { AssetValidationUtils.validateAsset( "amount_in", @@ -110,6 +124,9 @@ protected void validate(JdbcSepTransaction txn, NotifyOffchainFundsReceivedReque true, assetService); } + if (request.getFeeDetails() != null) { + AssetValidationUtils.validateFeeDetails(request.getFeeDetails(), txn, assetService); + } } @Override @@ -174,6 +191,10 @@ protected void updateTransactionWithRpcRequest( if (request.getAmountFee() != null) { txn.setAmountFee(request.getAmountFee().getAmount()); } + if (request.getFeeDetails() != null) { + txn.setAmountFee(request.getFeeDetails().getTotal()); + txn.setFeeDetailsList(request.getFeeDetails().getDetails()); + } switch (Sep.from(txn.getProtocol())) { case SEP_6: diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandler.java index 44ef318639..1efaf694bf 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandler.java @@ -73,13 +73,15 @@ protected void validate(JdbcSepTransaction txn, NotifyOnchainFundsReceivedReques if (!((request.getAmountIn() == null && request.getAmountOut() == null - && request.getAmountFee() == null) + && request.getAmountFee() == null + && request.getFeeDetails() == null) || (request.getAmountIn() != null && request.getAmountOut() != null - && request.getAmountFee() != null) + && (request.getAmountFee() != null || request.getFeeDetails() != null)) || (request.getAmountIn() != null && request.getAmountOut() == null - && request.getAmountFee() == null))) { + && request.getAmountFee() == null + && request.getFeeDetails() == null))) { throw new InvalidParamsException( "Invalid amounts combination provided: all, none or only amount_in should be set"); } @@ -112,6 +114,9 @@ protected void validate(JdbcSepTransaction txn, NotifyOnchainFundsReceivedReques true, assetService); } + if (request.getFeeDetails() != null) { + AssetValidationUtils.validateFeeDetails(request.getFeeDetails(), txn, assetService); + } } @Override @@ -181,5 +186,9 @@ protected void updateTransactionWithRpcRequest( if (request.getAmountFee() != null) { txn.setAmountFee(request.getAmountFee().getAmount()); } + if (request.getFeeDetails() != null) { + txn.setAmountFee(request.getFeeDetails().getTotal()); + txn.setFeeDetailsList(request.getFeeDetails().getDetails()); + } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOffchainFundsHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOffchainFundsHandler.java index 23ebe5f7bc..b57308cc40 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOffchainFundsHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOffchainFundsHandler.java @@ -55,15 +55,26 @@ protected void validate(JdbcSepTransaction txn, RequestOffchainFundsRequest requ throws InvalidParamsException, InvalidRequestException, BadRequestException { super.validate(txn, request); - if (!((request.getAmountIn() == null + // If none of the accepted combinations of input parameters satisfies -> throw an exception + if (! + // None of the amounts are provided + ((request.getAmountIn() == null && request.getAmountOut() == null && request.getAmountFee() == null + && request.getFeeDetails() == null && request.getAmountExpected() == null) - || (request.getAmountIn() != null + || + // All the amounts are provided (allow either amount_fee or fee_details) + (request.getAmountIn() != null && request.getAmountOut() != null - && request.getAmountFee() != null))) { + && (request.getAmountFee() != null || request.getFeeDetails() != null)))) { throw new InvalidParamsException( - "All or none of the amount_in, amount_out, and amount_fee should be set"); + "All or none of the amount_in, amount_out, and (fee_details or amount_fee) should be set"); + } + + // In case 2nd predicate in previous IF statement was TRUE + if (request.getFeeDetails() != null && request.getAmountFee() != null) { + throw new InvalidParamsException("Either fee_details or amount_fee should be set"); } if (request.getAmountIn() != null) { @@ -84,6 +95,12 @@ protected void validate(JdbcSepTransaction txn, RequestOffchainFundsRequest requ } AssetValidationUtils.validateAsset("amount_fee", request.getAmountFee(), true, assetService); } + if (request.getFeeDetails() != null) { + if (AssetValidationUtils.isStellarAsset(request.getFeeDetails().getAsset())) { + throw new InvalidParamsException("fee_details.asset should be non-stellar asset"); + } + AssetValidationUtils.validateFeeDetails(request.getFeeDetails(), txn, assetService); + } if (request.getAmountExpected() != null) { AssetValidationUtils.validateAsset( "amount_expected", @@ -100,8 +117,10 @@ protected void validate(JdbcSepTransaction txn, RequestOffchainFundsRequest requ if (request.getAmountOut() == null && txn.getAmountOut() == null) { throw new InvalidParamsException("amount_out is required"); } - if (request.getAmountFee() == null && txn.getAmountFee() == null) { - throw new InvalidParamsException("amount_fee is required"); + if (request.getAmountFee() == null + && request.getFeeDetails() == null + && txn.getAmountFee() == null) { + throw new InvalidParamsException("fee_details or amount_fee is required"); } } @@ -160,6 +179,11 @@ protected void updateTransactionWithRpcRequest( txn.setAmountFee(request.getAmountFee().getAmount()); txn.setAmountFeeAsset(request.getAmountFee().getAsset()); } + if (request.getFeeDetails() != null) { + txn.setAmountFee(request.getFeeDetails().getTotal()); + txn.setAmountFeeAsset(request.getFeeDetails().getAsset()); + txn.setFeeDetailsList(request.getFeeDetails().getDetails()); + } switch (Sep.from(txn.getProtocol())) { case SEP_6: JdbcSep6Transaction txn6 = (JdbcSep6Transaction) txn; diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java index c775d49bcd..82fad0844a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java @@ -85,15 +85,22 @@ protected void validate(JdbcSepTransaction txn, RequestOnchainFundsRequest reque throws InvalidParamsException, InvalidRequestException, BadRequestException { super.validate(txn, request); + // If none of the accepted combinations of input parameters satisfies -> throw an exception if (!((request.getAmountIn() == null && request.getAmountOut() == null && request.getAmountFee() == null + && request.getFeeDetails() == null && request.getAmountExpected() == null) || (request.getAmountIn() != null && request.getAmountOut() != null - && request.getAmountFee() != null))) { + && (request.getAmountFee() != null || request.getFeeDetails() != null)))) { throw new InvalidParamsException( - "All or none of the amount_in, amount_out, and amount_fee should be set"); + "All or none of the amount_in, amount_out, and (fee_details or amount_fee) should be set"); + } + + // In case 2nd predicate in previous IF statement was TRUE + if (request.getFeeDetails() != null && request.getAmountFee() != null) { + throw new InvalidParamsException("Either fee_details or amount_fee should be set"); } if (request.getAmountIn() != null) { @@ -114,6 +121,12 @@ protected void validate(JdbcSepTransaction txn, RequestOnchainFundsRequest reque } AssetValidationUtils.validateAsset("amount_fee", request.getAmountFee(), true, assetService); } + if (request.getFeeDetails() != null) { + if (!AssetValidationUtils.isStellarAsset(request.getFeeDetails().getAsset())) { + throw new InvalidParamsException("fee_details.asset should be stellar asset"); + } + AssetValidationUtils.validateFeeDetails(request.getFeeDetails(), txn, assetService); + } if (request.getAmountExpected() != null) { AssetValidationUtils.validateAsset( "amount_expected", @@ -130,8 +143,10 @@ protected void validate(JdbcSepTransaction txn, RequestOnchainFundsRequest reque if (request.getAmountOut() == null && txn.getAmountOut() == null) { throw new InvalidParamsException("amount_out is required"); } - if (request.getAmountFee() == null && txn.getAmountFee() == null) { - throw new InvalidParamsException("amount_fee is required"); + if (request.getAmountFee() == null + && request.getFeeDetails() == null + && txn.getAmountFee() == null) { + throw new InvalidParamsException("fee_details or amount_fee is required"); } boolean canGenerateSep6DepositInfo = @@ -216,6 +231,11 @@ protected void updateTransactionWithRpcRequest( txn.setAmountFee(request.getAmountFee().getAmount()); txn.setAmountFeeAsset(request.getAmountFee().getAsset()); } + if (request.getFeeDetails() != null) { + txn.setAmountFee(request.getFeeDetails().getTotal()); + txn.setAmountFeeAsset(request.getFeeDetails().getAsset()); + txn.setFeeDetailsList(request.getFeeDetails().getDetails()); + } switch (Sep.from(txn.getProtocol())) { case SEP_6: diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java b/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java index 66a84ca743..aed6c0c18f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableSet; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Metrics; +import java.math.BigDecimal; import java.time.Instant; import java.util.Comparator; import java.util.LinkedList; @@ -40,6 +41,7 @@ import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.SepTransactionStatus; import org.stellar.anchor.api.shared.Amount; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.api.shared.SepDepositInfo; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.config.CustodyConfig; @@ -254,6 +256,8 @@ private GetTransactionResponse patchTransaction(PatchTransactionRequest patch) validateAsset("amount_out", patch.getTransaction().getAmountOut()); validateAsset("amount_fee", patch.getTransaction().getAmountFee(), true); + FeeDetails feeDetails = validateAndGetRateFee(patch.getTransaction()); + JdbcSepTransaction txn = queryTransactionById(patch.getTransaction().getId()); if (txn == null) throw new BadRequestException( @@ -290,6 +294,10 @@ private GetTransactionResponse patchTransaction(PatchTransactionRequest patch) custodyService.createTransaction(sep6Transaction); } + if (feeDetails != null) { + sep6Transaction.setFeeDetails(feeDetails); + } + txn6Store.save(sep6Transaction); eventSession.publish( AnchorEvent.builder() @@ -313,6 +321,10 @@ private GetTransactionResponse patchTransaction(PatchTransactionRequest patch) custodyService.createTransaction(sep24Txn); } + if (feeDetails != null) { + sep24Txn.setFeeDetails(feeDetails); + } + txn24Store.save(sep24Txn); eventSession.publish( AnchorEvent.builder() @@ -325,6 +337,11 @@ private GetTransactionResponse patchTransaction(PatchTransactionRequest patch) break; case "31": JdbcSep31Transaction sep31Txn = (JdbcSep31Transaction) txn; + + if (feeDetails != null) { + sep31Txn.setFeeDetails(feeDetails); + } + txn31Store.save(sep31Txn); eventSession.publish( AnchorEvent.builder() @@ -375,6 +392,8 @@ void updateSepTransaction(PlatformTransactionData patch, JdbcSepTransaction txn) .ifPresent(stellarTransaction -> txn.setStellarTransactionId(stellarTransaction.getId())); } + FeeDetails feeDetails = validateAndGetRateFee(patch); + switch (txn.getProtocol()) { case "6": JdbcSep6Transaction sep6Txn = (JdbcSep6Transaction) txn; @@ -383,6 +402,9 @@ void updateSepTransaction(PlatformTransactionData patch, JdbcSepTransaction txn) txnUpdated = updateField(patch, sep6Txn, "requiredCustomerInfoMessage", txnUpdated); txnUpdated = updateField(patch, sep6Txn, "requiredCustomerInfoUpdates", txnUpdated); txnUpdated = updateField(patch, sep6Txn, "instructions", txnUpdated); + if (feeDetails != null) { + sep6Txn.setFeeDetails(feeDetails); + } break; case "24": JdbcSep24Transaction sep24Txn = (JdbcSep24Transaction) txn; @@ -414,6 +436,9 @@ void updateSepTransaction(PlatformTransactionData patch, JdbcSepTransaction txn) txnUpdated = true; } } + if (feeDetails != null) { + sep24Txn.setFeeDetails(feeDetails); + } break; case "31": // update message @@ -438,6 +463,9 @@ void updateSepTransaction(PlatformTransactionData patch, JdbcSepTransaction txn) txnUpdated = true; } } + if (feeDetails != null) { + sep31Txn.setFeeDetails(feeDetails); + } validateQuoteAndAmounts(sep31Txn); break; @@ -449,6 +477,31 @@ void updateSepTransaction(PlatformTransactionData patch, JdbcSepTransaction txn) } } + FeeDetails validateAndGetRateFee(PlatformTransactionData patch) throws BadRequestException { + // If both fee_details and fee is set, validate that they match. If only one set, properly set + // the other one. + FeeDetails feeDetails = patch.getFeeDetails(); + + if (feeDetails != null) { + if (patch.getAmountFee() != null) { + if (new BigDecimal(patch.getAmountFee().getAmount()) + .compareTo(new BigDecimal(patch.getFeeDetails().getTotal())) + != 0) { + throw new BadRequestException( + "amount_fee's amount doesn't match amount from fee_details"); + } + if (!patch.getAmountFee().getAsset().equals(patch.getFeeDetails().getAsset())) { + throw new BadRequestException("amount_fee's asset doesn't match asset from fee_details"); + } + } + } else if (patch.getAmountFee() != null) { + feeDetails = + new FeeDetails(patch.getAmountFee().getAmount(), patch.getAmountFee().getAsset(), null); + } + + return feeDetails; + } + /** * validateIfStatusIsSupported will check if the provided string is a SepTransactionStatus * supported by the PlatformAPI @@ -521,7 +574,7 @@ void validateQuoteAndAmounts(Sep31Transaction txn) throws AnchorException { if (allAmountAvailable(txn) && Objects.equals(txn.getAmountInAsset(), txn.getAmountOutAsset())) if (decimal(txn.getAmountIn()) - .compareTo(decimal(txn.getAmountOut()).add(decimal(txn.getAmountFee()))) + .compareTo(decimal(txn.getAmountOut()).add(decimal(txn.getFeeDetails().getTotal()))) != 0) throw new BadRequestException("amount_in != amount_out + amount_fee"); } else { // with exchange @@ -556,6 +609,14 @@ void validateQuoteAndAmounts(Sep31Transaction txn) throws AnchorException { if (!equalsAsDecimals(txn.getAmountFee(), quote.getFee().getTotal())) { throw new BadRequestException("amount_fee != sum(quote.fee.total)"); } + + if (!Objects.equals(txn.getFeeDetails().getAsset(), quote.getFee().getAsset())) { + throw new BadRequestException("transaction.fee_details.asset != quote.fee.asset"); + } + + if (!equalsAsDecimals(txn.getFeeDetails().getTotal(), quote.getFee().getTotal())) { + throw new BadRequestException("transaction.fee_details.total != quote.fee.total"); + } } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/utils/AssetValidationUtils.java b/platform/src/main/java/org/stellar/anchor/platform/utils/AssetValidationUtils.java index b6724117ad..8ed7a8865a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/utils/AssetValidationUtils.java +++ b/platform/src/main/java/org/stellar/anchor/platform/utils/AssetValidationUtils.java @@ -3,12 +3,19 @@ import static java.util.stream.Collectors.toList; import static org.stellar.anchor.util.MathHelper.decimal; +import java.math.BigDecimal; import java.util.List; +import javax.annotation.Nullable; +import javax.validation.constraints.NotNull; import org.apache.commons.collections.CollectionUtils; import org.stellar.anchor.api.exception.BadRequestException; import org.stellar.anchor.api.rpc.method.AmountAssetRequest; import org.stellar.anchor.api.sep.AssetInfo; +import org.stellar.anchor.api.shared.FeeDescription; +import org.stellar.anchor.api.shared.FeeDetails; import org.stellar.anchor.asset.AssetService; +import org.stellar.anchor.platform.data.JdbcSepTransaction; +import org.stellar.anchor.util.MathHelper; import org.stellar.anchor.util.SepHelper; import org.stellar.anchor.util.StringHelper; @@ -32,6 +39,37 @@ public static void validateAsset( validateAsset(fieldName, amount, false, assetService); } + public static void validateFeeDetails( + @NotNull FeeDetails fee, + @Nullable JdbcSepTransaction jdbcTransaction, + AssetService assetService) + throws BadRequestException { + validateAsset( + "fee_details", new AmountAssetRequest(fee.getTotal(), fee.getAsset()), true, assetService); + + if (jdbcTransaction != null + && jdbcTransaction.getAmountFeeAsset() != null + && !fee.getAsset().equals(jdbcTransaction.getAmountFeeAsset())) { + throw new BadRequestException( + "fee_details.asset is different from expected database asset (" + + jdbcTransaction.getAmountFeeAsset() + + ")"); + } + + if (fee.getDetails() != null) { + BigDecimal sum = + fee.getDetails().stream() + .map(FeeDescription::getAmount) + .map(MathHelper::decimal) + .reduce(decimal(0L), BigDecimal::add); + + if (decimal(fee.getTotal()).compareTo(sum) != 0) { + throw new BadRequestException( + "fee_details.total is not equal to the sum of (fee_details.details.amount)"); + } + } + } + public static void validateAsset( String fieldName, AmountAssetRequest amount, boolean allowZero, AssetService assetService) throws BadRequestException { diff --git a/platform/src/main/resources/db/migration/V14__add_fee_details.sql b/platform/src/main/resources/db/migration/V14__add_fee_details.sql new file mode 100644 index 0000000000..1b8c609837 --- /dev/null +++ b/platform/src/main/resources/db/migration/V14__add_fee_details.sql @@ -0,0 +1,3 @@ +ALTER TABLE sep6_transaction ADD fee_details JSON; +ALTER TABLE sep24_transaction ADD fee_details JSON; +ALTER TABLE sep31_transaction ADD fee_details JSON; diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/DoStellarRefundHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/DoStellarRefundHandlerTest.kt index ae3b3567b7..8b69833517 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/DoStellarRefundHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/DoStellarRefundHandlerTest.kt @@ -54,6 +54,7 @@ import org.stellar.anchor.platform.data.JdbcSep31Refunds import org.stellar.anchor.platform.data.JdbcSep31Transaction import org.stellar.anchor.platform.data.JdbcSep6Transaction import org.stellar.anchor.platform.service.AnchorMetrics.PLATFORM_RPC_TRANSACTION +import org.stellar.anchor.platform.utils.toRate import org.stellar.anchor.platform.validator.RequestValidator import org.stellar.anchor.sep24.Sep24TransactionStore import org.stellar.anchor.sep31.Sep31TransactionStore @@ -454,6 +455,7 @@ class DoStellarRefundHandlerTest { expectedResponse.amountIn = Amount("1.1", STELLAR_USDC) expectedResponse.amountOut = Amount("1", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt expectedResponse.refundMemo = MEMO expectedResponse.refundMemoType = MEMO_TYPE @@ -545,6 +547,7 @@ class DoStellarRefundHandlerTest { expectedResponse.amountIn = Amount("1.1", STELLAR_USDC) expectedResponse.amountOut = Amount("1", FIAT_USD) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.updatedAt = sep31TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) @@ -909,6 +912,7 @@ class DoStellarRefundHandlerTest { expectedResponse.amountIn = Amount("1.1", STELLAR_USDC) expectedResponse.amountOut = Amount("1", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.refundMemo = MEMO diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyAmountsUpdatedHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyAmountsUpdatedHandlerTest.kt index ea40236f86..1553e217e0 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyAmountsUpdatedHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyAmountsUpdatedHandlerTest.kt @@ -39,6 +39,7 @@ import org.stellar.anchor.metrics.MetricsService import org.stellar.anchor.platform.data.JdbcSep24Transaction import org.stellar.anchor.platform.data.JdbcSep6Transaction import org.stellar.anchor.platform.service.AnchorMetrics.PLATFORM_RPC_TRANSACTION +import org.stellar.anchor.platform.utils.toRate import org.stellar.anchor.platform.validator.RequestValidator import org.stellar.anchor.sep24.Sep24TransactionStore import org.stellar.anchor.sep31.Sep31TransactionStore @@ -301,6 +302,7 @@ class NotifyAmountsUpdatedHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt JSONAssert.assertEquals( @@ -437,6 +439,7 @@ class NotifyAmountsUpdatedHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyInteractiveFlowCompletedHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyInteractiveFlowCompletedHandlerTest.kt index ffde49f22f..e563455f0e 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyInteractiveFlowCompletedHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyInteractiveFlowCompletedHandlerTest.kt @@ -35,6 +35,7 @@ import org.stellar.anchor.event.EventService.Session import org.stellar.anchor.metrics.MetricsService import org.stellar.anchor.platform.data.JdbcSep24Transaction import org.stellar.anchor.platform.service.AnchorMetrics.PLATFORM_RPC_TRANSACTION +import org.stellar.anchor.platform.utils.toRate import org.stellar.anchor.platform.validator.RequestValidator import org.stellar.anchor.sep24.Sep24TransactionStore import org.stellar.anchor.sep31.Sep31TransactionStore @@ -218,6 +219,7 @@ class NotifyInteractiveFlowCompletedHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.amountExpected = Amount("1", FIAT_USD) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt @@ -303,6 +305,7 @@ class NotifyInteractiveFlowCompletedHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.amountExpected = Amount("1", FIAT_USD) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt @@ -409,7 +412,7 @@ class NotifyInteractiveFlowCompletedHandlerTest { request.amountFee.asset = STELLAR_USDC ex = assertThrows { handler.handle(request) } - assertEquals("amount_fee.asset should be non-stellar asset", ex.message) + assertEquals("fee asset should be a non-stellar asset", ex.message) request.amountFee.asset = FIAT_USD verify(exactly = 0) { txn6Store.save(any()) } @@ -451,7 +454,7 @@ class NotifyInteractiveFlowCompletedHandlerTest { request.amountFee.asset = FIAT_USD ex = assertThrows { handler.handle(request) } - assertEquals("amount_fee.asset should be stellar asset", ex.message) + assertEquals("fee asset should be a stellar asset", ex.message) request.amountFee.asset = STELLAR_USDC verify(exactly = 0) { txn6Store.save(any()) } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOffchainFundsReceivedHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOffchainFundsReceivedHandlerTest.kt index 8d06824153..df74dbaa56 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOffchainFundsReceivedHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOffchainFundsReceivedHandlerTest.kt @@ -28,6 +28,7 @@ import org.stellar.anchor.api.rpc.method.NotifyOffchainFundsReceivedRequest import org.stellar.anchor.api.sep.SepTransactionStatus.* import org.stellar.anchor.api.shared.Amount import org.stellar.anchor.api.shared.Customers +import org.stellar.anchor.api.shared.FeeDetails import org.stellar.anchor.api.shared.StellarId import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.DefaultAssetService @@ -40,6 +41,7 @@ import org.stellar.anchor.metrics.MetricsService import org.stellar.anchor.platform.data.JdbcSep24Transaction import org.stellar.anchor.platform.data.JdbcSep6Transaction import org.stellar.anchor.platform.service.AnchorMetrics.PLATFORM_RPC_TRANSACTION +import org.stellar.anchor.platform.utils.toRate import org.stellar.anchor.platform.validator.RequestValidator import org.stellar.anchor.sep24.Sep24Transaction import org.stellar.anchor.sep24.Sep24TransactionStore @@ -364,6 +366,7 @@ class NotifyOffchainFundsReceivedHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount(null, FIAT_USD) JSONAssert.assertEquals( @@ -748,6 +751,7 @@ class NotifyOffchainFundsReceivedHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = FeeDetails("0.1", STELLAR_USDC) expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandlerTest.kt index fdd53e0dff..7583bcc42e 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandlerTest.kt @@ -28,10 +28,7 @@ import org.stellar.anchor.api.platform.PlatformTransactionData.Sep.* import org.stellar.anchor.api.rpc.method.AmountRequest import org.stellar.anchor.api.rpc.method.NotifyOnchainFundsReceivedRequest import org.stellar.anchor.api.sep.SepTransactionStatus.* -import org.stellar.anchor.api.shared.Amount -import org.stellar.anchor.api.shared.Customers -import org.stellar.anchor.api.shared.StellarId -import org.stellar.anchor.api.shared.StellarTransaction +import org.stellar.anchor.api.shared.* import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.event.EventService @@ -43,6 +40,7 @@ import org.stellar.anchor.platform.data.JdbcSep24Transaction import org.stellar.anchor.platform.data.JdbcSep31Transaction import org.stellar.anchor.platform.data.JdbcSep6Transaction import org.stellar.anchor.platform.service.AnchorMetrics.PLATFORM_RPC_TRANSACTION +import org.stellar.anchor.platform.utils.toRate import org.stellar.anchor.platform.validator.RequestValidator import org.stellar.anchor.sep24.Sep24TransactionStore import org.stellar.anchor.sep31.Sep31TransactionStore @@ -280,7 +278,7 @@ class NotifyOnchainFundsReceivedHandlerTest { .transactionId(TX_ID) .amountIn(AmountRequest("1")) .amountOut(AmountRequest("0.9")) - .amountFee(AmountRequest("0.1")) + .feeDetails(FeeDetails("0.1", STELLAR_USDC)) .stellarTransactionId(STELLAR_TX_ID) .build() val txn24 = JdbcSep24Transaction() @@ -348,6 +346,7 @@ class NotifyOnchainFundsReceivedHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.stellarTransactions = stellarTransactions @@ -796,6 +795,7 @@ class NotifyOnchainFundsReceivedHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.stellarTransactions = stellarTransactions expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyRefundPendingHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyRefundPendingHandlerTest.kt index beb7ac816e..5fb246817d 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyRefundPendingHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyRefundPendingHandlerTest.kt @@ -38,6 +38,7 @@ import org.stellar.anchor.platform.data.JdbcSep24Refunds import org.stellar.anchor.platform.data.JdbcSep24Transaction import org.stellar.anchor.platform.data.JdbcSep6Transaction import org.stellar.anchor.platform.service.AnchorMetrics.PLATFORM_RPC_TRANSACTION +import org.stellar.anchor.platform.utils.toRate import org.stellar.anchor.platform.validator.RequestValidator import org.stellar.anchor.sep24.Sep24TransactionStore import org.stellar.anchor.sep31.Sep31TransactionStore @@ -343,6 +344,7 @@ class NotifyRefundPendingHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt val refundPayment = RefundPayment() refundPayment.amount = Amount("1", txn24.amountInAsset) @@ -458,6 +460,7 @@ class NotifyRefundPendingHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("2.2", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt val refundPayment = RefundPayment() refundPayment.amount = Amount("1", txn24.amountInAsset) @@ -657,6 +660,7 @@ class NotifyRefundPendingHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) @@ -777,6 +781,7 @@ class NotifyRefundPendingHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("2.2", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyRefundSentHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyRefundSentHandlerTest.kt index 73b28b70f4..6de62025d5 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyRefundSentHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyRefundSentHandlerTest.kt @@ -37,6 +37,7 @@ import org.stellar.anchor.event.EventService.Session import org.stellar.anchor.metrics.MetricsService import org.stellar.anchor.platform.data.* import org.stellar.anchor.platform.service.AnchorMetrics.PLATFORM_RPC_TRANSACTION +import org.stellar.anchor.platform.utils.toRate import org.stellar.anchor.platform.validator.RequestValidator import org.stellar.anchor.sep24.Sep24TransactionStore import org.stellar.anchor.sep31.Sep31TransactionStore @@ -419,6 +420,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("2", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt val refundPayment = RefundPayment() refundPayment.amount = Amount("1", txn24.amountInAsset) @@ -539,6 +541,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("2.2", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt val refundPayment1 = RefundPayment() refundPayment1.amount = Amount("1", txn24.amountInAsset) @@ -659,6 +662,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt val refundPayment = RefundPayment() refundPayment.amount = Amount("1", txn24.amountInAsset) @@ -767,6 +771,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("2", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt val refundPayment = RefundPayment() refundPayment.amount = Amount("1", txn24.amountInAsset) @@ -894,6 +899,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("2", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt val refundPayment1 = RefundPayment() refundPayment1.amount = Amount("1.5", txn24.amountInAsset) @@ -1069,6 +1075,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("2", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) @@ -1202,6 +1209,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("2.2", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) @@ -1334,6 +1342,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) @@ -1452,6 +1461,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("2", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) @@ -1592,6 +1602,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountExpected = Amount(null, FIAT_USD) expectedResponse.amountIn = Amount("2", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = Amount("0.1", FIAT_USD).toRate() expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) @@ -1761,6 +1772,7 @@ class NotifyRefundSentHandlerTest { expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountOut = Amount("1", FIAT_USD) expectedResponse.amountFee = Amount("0", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0", STELLAR_USDC).toRate() expectedResponse.updatedAt = sep31TxnCapture.captured.updatedAt expectedResponse.transferReceivedAt = transferReceivedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOffchainFundsHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOffchainFundsHandlerTest.kt index aa1d20bbab..90335234d1 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOffchainFundsHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOffchainFundsHandlerTest.kt @@ -27,10 +27,7 @@ import org.stellar.anchor.api.rpc.method.AmountAssetRequest import org.stellar.anchor.api.rpc.method.AmountRequest import org.stellar.anchor.api.rpc.method.RequestOffchainFundsRequest import org.stellar.anchor.api.sep.SepTransactionStatus.* -import org.stellar.anchor.api.shared.Amount -import org.stellar.anchor.api.shared.Customers -import org.stellar.anchor.api.shared.InstructionField -import org.stellar.anchor.api.shared.StellarId +import org.stellar.anchor.api.shared.* import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.event.EventService @@ -229,7 +226,7 @@ class RequestOffchainFundsHandlerTest { every { txn24Store.save(capture(sep24TxnCapture)) } returns null val ex = assertThrows { handler.handle(request) } - assertEquals("amount_fee is required", ex.message) + assertEquals("fee_details or amount_fee is required", ex.message) verify(exactly = 0) { txn6Store.save(any()) } verify(exactly = 0) { txn24Store.save(any()) } @@ -257,7 +254,7 @@ class RequestOffchainFundsHandlerTest { val ex = assertThrows { handler.handle(request) } assertEquals( - "All or none of the amount_in, amount_out, and amount_fee should be set", + "All or none of the amount_in, amount_out, and (fee_details or amount_fee) should be set", ex.message ) @@ -274,7 +271,7 @@ class RequestOffchainFundsHandlerTest { .transactionId(TX_ID) .amountIn(AmountAssetRequest("1", FIAT_USD)) .amountOut(AmountAssetRequest("1", STELLAR_USDC)) - .amountFee(AmountAssetRequest("1", FIAT_USD)) + .feeDetails(FeeDetails("1", FIAT_USD, null)) .amountExpected(AmountRequest("1")) .build() val txn24 = JdbcSep24Transaction() @@ -288,10 +285,10 @@ class RequestOffchainFundsHandlerTest { every { txn31Store.findByTransactionId(any()) } returns null every { txn24Store.save(capture(sep24TxnCapture)) } returns null - request.amountFee.amount = "-1" + request.feeDetails.total = "-1" var ex = assertThrows { handler.handle(request) } - assertEquals("amount_fee.amount should be non-negative", ex.message) - request.amountFee.amount = "1" + assertEquals("fee_details.amount should be non-negative", ex.message) + request.feeDetails.total = "1" request.amountExpected.amount = "-1" ex = assertThrows { handler.handle(request) } @@ -310,7 +307,7 @@ class RequestOffchainFundsHandlerTest { .transactionId(TX_ID) .amountIn(AmountAssetRequest("1", FIAT_USD)) .amountOut(AmountAssetRequest("1", STELLAR_USDC)) - .amountFee(AmountAssetRequest("1", FIAT_USD)) + .feeDetails(FeeDetails("1", FIAT_USD, null)) .amountExpected(AmountRequest("1")) .build() val txn24 = JdbcSep24Transaction() @@ -334,9 +331,9 @@ class RequestOffchainFundsHandlerTest { assertEquals("amount_out.asset should be stellar asset", ex.message) request.amountOut.asset = STELLAR_USDC - request.amountFee.asset = STELLAR_USDC + request.feeDetails.asset = STELLAR_USDC ex = assertThrows { handler.handle(request) } - assertEquals("amount_fee.asset should be non-stellar asset", ex.message) + assertEquals("fee_details.asset should be non-stellar asset", ex.message) verify(exactly = 0) { txn6Store.save(any()) } verify(exactly = 0) { txn24Store.save(any()) } @@ -398,7 +395,7 @@ class RequestOffchainFundsHandlerTest { .transactionId(TX_ID) .amountIn(AmountAssetRequest("1", FIAT_USD)) .amountOut(AmountAssetRequest("0.9", STELLAR_USDC)) - .amountFee(AmountAssetRequest("0.1", FIAT_USD)) + .feeDetails(FeeDetails("0.1", FIAT_USD)) .amountExpected(AmountRequest("1")) .build() val txn24 = JdbcSep24Transaction() @@ -450,6 +447,7 @@ class RequestOffchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = FeeDetails("0.1", FIAT_USD, null) expectedResponse.amountExpected = Amount("1", FIAT_USD) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt @@ -484,7 +482,7 @@ class RequestOffchainFundsHandlerTest { .transactionId(TX_ID) .amountIn(AmountAssetRequest("1", FIAT_USD)) .amountOut(AmountAssetRequest("0.9", STELLAR_USDC)) - .amountFee(AmountAssetRequest("0.1", FIAT_USD)) + .feeDetails(FeeDetails("0.1", FIAT_USD, null)) .build() val txn24 = JdbcSep24Transaction() txn24.status = INCOMPLETE.toString() @@ -534,6 +532,7 @@ class RequestOffchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = FeeDetails("0.1", FIAT_USD, null) expectedResponse.amountExpected = Amount("1", FIAT_USD) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt @@ -620,6 +619,7 @@ class RequestOffchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = FeeDetails("0.1", STELLAR_USDC, null) expectedResponse.amountExpected = Amount("1", FIAT_USD) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt @@ -713,7 +713,7 @@ class RequestOffchainFundsHandlerTest { .transactionId(TX_ID) .amountIn(AmountAssetRequest("1", FIAT_USD)) .amountOut(AmountAssetRequest("0.9", STELLAR_USDC)) - .amountFee(AmountAssetRequest("0.1", FIAT_USD)) + .feeDetails(FeeDetails("0.1", FIAT_USD, null)) .amountExpected(AmountRequest("1")) .instructions(mapOf("first_name" to InstructionField.builder().build())) .build() @@ -767,6 +767,7 @@ class RequestOffchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = FeeDetails("0.1", FIAT_USD, null) expectedResponse.amountExpected = Amount("1", FIAT_USD) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) @@ -814,7 +815,7 @@ class RequestOffchainFundsHandlerTest { .transactionId(TX_ID) .amountIn(AmountAssetRequest("1", FIAT_USD)) .amountOut(AmountAssetRequest("0.9", STELLAR_USDC)) - .amountFee(AmountAssetRequest("0.1", FIAT_USD)) + .feeDetails(FeeDetails("0.1", FIAT_USD, null)) .instructions(mapOf("first_name" to InstructionField.builder().build())) .build() val txn6 = JdbcSep6Transaction() @@ -866,6 +867,7 @@ class RequestOffchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", FIAT_USD) + expectedResponse.feeDetails = FeeDetails("0.1", FIAT_USD, null) expectedResponse.amountExpected = Amount("1", FIAT_USD) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) @@ -970,6 +972,7 @@ class RequestOffchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", FIAT_USD) expectedResponse.amountOut = Amount("0.9", STELLAR_USDC) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = FeeDetails("0.1", STELLAR_USDC, null) expectedResponse.amountExpected = Amount("1", FIAT_USD) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.customers = Customers(StellarId(null, null, null), StellarId(null, null, null)) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt index 5de7a54eae..d6316f1613 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt @@ -44,6 +44,7 @@ import org.stellar.anchor.metrics.MetricsService import org.stellar.anchor.platform.data.JdbcSep24Transaction import org.stellar.anchor.platform.data.JdbcSep6Transaction import org.stellar.anchor.platform.service.* +import org.stellar.anchor.platform.utils.toRate import org.stellar.anchor.platform.validator.RequestValidator import org.stellar.anchor.sep24.Sep24Transaction import org.stellar.anchor.sep24.Sep24TransactionStore @@ -235,7 +236,7 @@ class RequestOnchainFundsHandlerTest { every { txn24Store.save(capture(sep24TxnCapture)) } returns null val ex = assertThrows { handler.handle(request) } - assertEquals("amount_fee is required", ex.message) + assertEquals("fee_details or amount_fee is required", ex.message) verify(exactly = 0) { txn6Store.save(any()) } verify(exactly = 0) { txn24Store.save(any()) } @@ -263,7 +264,7 @@ class RequestOnchainFundsHandlerTest { val ex = assertThrows { handler.handle(request) } assertEquals( - "All or none of the amount_in, amount_out, and amount_fee should be set", + "All or none of the amount_in, amount_out, and (fee_details or amount_fee) should be set", ex.message ) @@ -467,6 +468,11 @@ class RequestOnchainFundsHandlerTest { ex = assertThrows { handler.handle(request) } assertEquals("amount_fee.asset should be stellar asset", ex.message) + request.amountFee = null + request.feeDetails = Amount("10", FIAT_USD).toRate() + ex = assertThrows { handler.handle(request) } + assertEquals("fee_details.asset should be stellar asset", ex.message) + verify(exactly = 0) { txn6Store.save(any()) } verify(exactly = 0) { txn24Store.save(any()) } verify(exactly = 0) { txn31Store.save(any()) } @@ -550,7 +556,7 @@ class RequestOnchainFundsHandlerTest { .transactionId(TX_ID) .amountIn(AmountAssetRequest("1", STELLAR_USDC)) .amountOut(AmountAssetRequest("0.9", FIAT_USD)) - .amountFee(AmountAssetRequest("0.1", STELLAR_USDC)) + .feeDetails(Amount("0.1", STELLAR_USDC).toRate()) .amountExpected(AmountRequest("1")) .memo(TEXT_MEMO) .memoType(TEXT_MEMO_TYPE) @@ -614,6 +620,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountOut = Amount("0.9", FIAT_USD) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt expectedResponse.memo = TEXT_MEMO @@ -731,6 +738,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountOut = Amount("0.9", FIAT_USD) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt expectedResponse.memo = TEXT_MEMO_2 @@ -839,6 +847,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountOut = Amount("0.9", FIAT_USD) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt expectedResponse.memo = TEXT_MEMO @@ -939,6 +948,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountOut = Amount("0.9", FIAT_USD) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt expectedResponse.memo = TEXT_MEMO @@ -1043,6 +1053,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountOut = Amount("0.9", FIAT_USD) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep24TxnCapture.captured.updatedAt expectedResponse.memo = TEXT_MEMO @@ -1203,7 +1214,7 @@ class RequestOnchainFundsHandlerTest { .transactionId(TX_ID) .amountIn(AmountAssetRequest("1", STELLAR_USDC)) .amountOut(AmountAssetRequest("0.9", FIAT_USD)) - .amountFee(AmountAssetRequest("0.1", STELLAR_USDC)) + .feeDetails(Amount("0.1", STELLAR_USDC).toRate()) .amountExpected(AmountRequest("1")) .memo(TEXT_MEMO) .memoType(TEXT_MEMO_TYPE) @@ -1274,6 +1285,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountOut = Amount("0.9", FIAT_USD) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.memo = TEXT_MEMO @@ -1390,6 +1402,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountOut = Amount("0.9", FIAT_USD) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.memo = TEXT_MEMO_2 @@ -1487,6 +1500,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountOut = Amount("0.9", FIAT_USD) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = Amount("0.1", STELLAR_USDC).toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.memo = TEXT_MEMO @@ -1589,6 +1603,7 @@ class RequestOnchainFundsHandlerTest { expectedResponse.amountIn = Amount("1", STELLAR_USDC) expectedResponse.amountOut = Amount("0.9", FIAT_USD) expectedResponse.amountFee = Amount("0.1", STELLAR_USDC) + expectedResponse.feeDetails = expectedResponse.amountFee.toRate() expectedResponse.amountExpected = Amount("1", STELLAR_USDC) expectedResponse.updatedAt = sep6TxnCapture.captured.updatedAt expectedResponse.memo = TEXT_MEMO diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt index c4c9b00a1c..9f9f73a9fb 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt @@ -22,10 +22,7 @@ import org.stellar.anchor.api.platform.PatchTransactionRequest import org.stellar.anchor.api.platform.PatchTransactionsRequest import org.stellar.anchor.api.platform.PlatformTransactionData import org.stellar.anchor.api.sep.SepTransactionStatus -import org.stellar.anchor.api.sep.sep38.RateFee -import org.stellar.anchor.api.shared.Amount -import org.stellar.anchor.api.shared.RefundPayment -import org.stellar.anchor.api.shared.Refunds +import org.stellar.anchor.api.shared.* import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.config.CustodyConfig @@ -34,6 +31,7 @@ import org.stellar.anchor.event.EventService import org.stellar.anchor.event.EventService.EventQueue.TRANSACTION import org.stellar.anchor.event.EventService.Session import org.stellar.anchor.platform.data.* +import org.stellar.anchor.platform.utils.toRate import org.stellar.anchor.sep24.Sep24DepositInfoGenerator import org.stellar.anchor.sep24.Sep24Transaction import org.stellar.anchor.sep24.Sep24TransactionStore @@ -635,7 +633,7 @@ class TransactionServiceTest { testSep38Quote.sellAsset = fiatUSD testSep38Quote.buyAmount = "98" testSep38Quote.buyAsset = stellarUSDC - testSep38Quote.fee = RateFee("2", fiatUSD) + testSep38Quote.fee = FeeDetails("2", fiatUSD) every { sep38QuoteStore.findByQuoteId(quoteId) } returns testSep38Quote @@ -1124,4 +1122,26 @@ class TransactionServiceTest { assertInstanceOf(BadRequestException::class.java, ex) assertEquals("Transaction is missing.", ex.message) } + + @Test + fun `validateAndGetRateFee test`() { + val data = PlatformTransactionData() + data.amountFee = Amount("10", "USDC") + + assertEquals(Amount("10", "USDC").toRate(), transactionService.validateAndGetRateFee(data)) + + data.feeDetails = FeeDetails("10", "USDC", listOf(FeeDescription("test", "10"))) + assertEquals( + FeeDetails("10", "USDC", listOf(FeeDescription("test", "10"))), + transactionService.validateAndGetRateFee(data) + ) + + data.feeDetails = Amount("9", "USDC").toRate() + var ex = assertThrows { transactionService.validateAndGetRateFee(data) } + assertEquals("amount_fee's amount doesn't match amount from fee_details", ex.message) + + data.feeDetails = Amount("10", "NOTUSDC").toRate() + ex = assertThrows { transactionService.validateAndGetRateFee(data) } + assertEquals("amount_fee's asset doesn't match asset from fee_details", ex.message) + } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/utils/TestUtil.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/utils/TestUtil.kt new file mode 100644 index 0000000000..22cefad442 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/utils/TestUtil.kt @@ -0,0 +1,8 @@ +package org.stellar.anchor.platform.utils + +import org.stellar.anchor.api.shared.Amount +import org.stellar.anchor.api.shared.FeeDetails + +fun Amount.toRate(): FeeDetails { + return FeeDetails(this.amount, this.asset) +}