diff --git a/core/src/main/java/haveno/core/api/CoreDisputesService.java b/core/src/main/java/haveno/core/api/CoreDisputesService.java index 0cb12417adb..2be6b692ea4 100644 --- a/core/src/main/java/haveno/core/api/CoreDisputesService.java +++ b/core/src/main/java/haveno/core/api/CoreDisputesService.java @@ -180,6 +180,10 @@ public void resolveDispute(String tradeId, DisputeResult.Winner winner, DisputeR if (!loserDisputeOptional.isPresent()) throw new IllegalStateException("could not find peer dispute"); var loserDispute = loserDisputeOptional.get(); var loserDisputeResult = createDisputeResult(loserDispute, winner, reason, summaryNotes, closeDate); + loserDisputeResult.setBuyerPayoutAmountBeforeCost(winnerDisputeResult.getBuyerPayoutAmountBeforeCost()); + loserDisputeResult.setSellerPayoutAmountBeforeCost(winnerDisputeResult.getSellerPayoutAmountBeforeCost()); + loserDisputeResult.setBuyerPayoutTxFee(winnerDisputeResult.getBuyerPayoutTxFee()); + loserDisputeResult.setSellerPayoutTxFee(winnerDisputeResult.getSellerPayoutTxFee()); loserDisputeResult.setBuyerPayoutAmount(winnerDisputeResult.getBuyerPayoutAmount()); loserDisputeResult.setSellerPayoutAmount(winnerDisputeResult.getSellerPayoutAmount()); loserDisputeResult.setSubtractFeeFrom(winnerDisputeResult.getSubtractFeeFrom()); @@ -217,31 +221,23 @@ public void applyPayoutAmountsToDisputeResult(DisputePayout payout, Dispute disp BigInteger tradeAmount = contract.getTradeAmount(); disputeResult.setSubtractFeeFrom(DisputeResult.SubtractFeeFrom.BUYER_AND_SELLER); if (payout == DisputePayout.BUYER_GETS_TRADE_AMOUNT) { - disputeResult.setBuyerPayoutAmount(tradeAmount.add(buyerSecurityDeposit)); - disputeResult.setSellerPayoutAmount(sellerSecurityDeposit); + disputeResult.setBuyerPayoutAmountBeforeCost(tradeAmount.add(buyerSecurityDeposit)); + disputeResult.setSellerPayoutAmountBeforeCost(sellerSecurityDeposit); } else if (payout == DisputePayout.BUYER_GETS_ALL) { - disputeResult.setBuyerPayoutAmount(tradeAmount - .add(buyerSecurityDeposit) - .add(sellerSecurityDeposit)); // TODO (woodser): apply min payout to incentivize loser? (see post v1.1.7) - disputeResult.setSellerPayoutAmount(BigInteger.valueOf(0)); + disputeResult.setBuyerPayoutAmountBeforeCost(tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit)); // TODO (woodser): apply min payout to incentivize loser? (see post v1.1.7) + disputeResult.setSellerPayoutAmountBeforeCost(BigInteger.valueOf(0)); } else if (payout == DisputePayout.SELLER_GETS_TRADE_AMOUNT) { - disputeResult.setBuyerPayoutAmount(buyerSecurityDeposit); - disputeResult.setSellerPayoutAmount(tradeAmount.add(sellerSecurityDeposit)); + disputeResult.setBuyerPayoutAmountBeforeCost(buyerSecurityDeposit); + disputeResult.setSellerPayoutAmountBeforeCost(tradeAmount.add(sellerSecurityDeposit)); } else if (payout == DisputePayout.SELLER_GETS_ALL) { - disputeResult.setBuyerPayoutAmount(BigInteger.valueOf(0)); - disputeResult.setSellerPayoutAmount(tradeAmount - .add(sellerSecurityDeposit) - .add(buyerSecurityDeposit)); + disputeResult.setBuyerPayoutAmountBeforeCost(BigInteger.valueOf(0)); + disputeResult.setSellerPayoutAmountBeforeCost(tradeAmount.add(sellerSecurityDeposit).add(buyerSecurityDeposit)); } else if (payout == DisputePayout.CUSTOM) { - if (customWinnerAmount > trade.getWallet().getBalance().longValueExact()) { - throw new RuntimeException("Winner payout is more than the trade wallet's balance"); - } + if (customWinnerAmount > trade.getWallet().getBalance().longValueExact()) throw new RuntimeException("Winner payout is more than the trade wallet's balance"); long loserAmount = tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit).subtract(BigInteger.valueOf(customWinnerAmount)).longValueExact(); - if (loserAmount < 0) { - throw new RuntimeException("Loser payout cannot be negative"); - } - disputeResult.setBuyerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? customWinnerAmount : loserAmount)); - disputeResult.setSellerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? loserAmount : customWinnerAmount)); + if (loserAmount < 0) throw new RuntimeException("Loser payout cannot be negative"); + disputeResult.setBuyerPayoutAmountBeforeCost(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? customWinnerAmount : loserAmount)); + disputeResult.setSellerPayoutAmountBeforeCost(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? loserAmount : customWinnerAmount)); disputeResult.setSubtractFeeFrom(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? SubtractFeeFrom.SELLER_ONLY : SubtractFeeFrom.BUYER_ONLY); // winner gets exact amount, loser pays mining fee } } diff --git a/core/src/main/java/haveno/core/api/model/TradeInfo.java b/core/src/main/java/haveno/core/api/model/TradeInfo.java index c4486aeda55..e80fed58a44 100644 --- a/core/src/main/java/haveno/core/api/model/TradeInfo.java +++ b/core/src/main/java/haveno/core/api/model/TradeInfo.java @@ -71,6 +71,12 @@ public class TradeInfo implements Payload { private final long amount; private final long buyerSecurityDeposit; private final long sellerSecurityDeposit; + private final long buyerDepositTxFee; + private final long sellerDepositTxFee; + private final long buyerPayoutTxFee; + private final long sellerPayoutTxFee; + private final long buyerPayoutAmount; + private final long sellerPayoutAmount; private final String price; private final String volume; private final String arbitratorNodeAddress; @@ -105,6 +111,12 @@ public TradeInfo(TradeInfoV1Builder builder) { this.amount = builder.getAmount(); this.buyerSecurityDeposit = builder.getBuyerSecurityDeposit(); this.sellerSecurityDeposit = builder.getSellerSecurityDeposit(); + this.buyerDepositTxFee = builder.getBuyerDepositTxFee(); + this.sellerDepositTxFee = builder.getSellerDepositTxFee(); + this.buyerPayoutTxFee = builder.getBuyerPayoutTxFee(); + this.sellerPayoutTxFee = builder.getSellerPayoutTxFee(); + this.buyerPayoutAmount = builder.getBuyerPayoutAmount(); + this.sellerPayoutAmount = builder.getSellerPayoutAmount(); this.price = builder.getPrice(); this.volume = builder.getVolume(); this.arbitratorNodeAddress = builder.getArbitratorNodeAddress(); @@ -161,6 +173,13 @@ public static TradeInfo toTradeInfo(Trade trade, String role) { .withAmount(trade.getAmount().longValueExact()) .withBuyerSecurityDeposit(trade.getBuyer().getSecurityDeposit() == null ? -1 : trade.getBuyer().getSecurityDeposit().longValueExact()) .withSellerSecurityDeposit(trade.getSeller().getSecurityDeposit() == null ? -1 : trade.getSeller().getSecurityDeposit().longValueExact()) + .withBuyerDepositTxFee(trade.getBuyer().getDepositTxFee() == null ? -1 : trade.getBuyer().getDepositTxFee().longValueExact()) + .withSellerDepositTxFee(trade.getSeller().getDepositTxFee() == null ? -1 : trade.getSeller().getDepositTxFee().longValueExact()) + .withBuyerPayoutTxFee(trade.getBuyer().getPayoutTxFee() == null ? -1 : trade.getBuyer().getPayoutTxFee().longValueExact()) + .withSellerPayoutTxFee(trade.getSeller().getPayoutTxFee() == null ? -1 : trade.getSeller().getPayoutTxFee().longValueExact()) + .withBuyerPayoutAmount(trade.getBuyer().getPayoutAmount() == null ? -1 : trade.getBuyer().getPayoutAmount().longValueExact()) + .withSellerPayoutAmount(trade.getSeller().getPayoutAmount() == null ? -1 : trade.getSeller().getPayoutAmount().longValueExact()) + .withTotalTxFee(trade.getTotalTxFee().longValueExact()) .withPrice(toPreciseTradePrice.apply(trade)) .withVolume(toRoundedVolume.apply(trade)) .withArbitratorNodeAddress(toArbitratorNodeAddress.apply(trade)) @@ -204,6 +223,12 @@ public haveno.proto.grpc.TradeInfo toProtoMessage() { .setAmount(amount) .setBuyerSecurityDeposit(buyerSecurityDeposit) .setSellerSecurityDeposit(sellerSecurityDeposit) + .setBuyerDepositTxFee(buyerDepositTxFee) + .setSellerDepositTxFee(sellerDepositTxFee) + .setBuyerPayoutTxFee(buyerPayoutTxFee) + .setSellerPayoutTxFee(sellerPayoutTxFee) + .setBuyerPayoutAmount(buyerPayoutAmount) + .setSellerPayoutAmount(sellerPayoutAmount) .setPrice(price) .setTradeVolume(volume) .setArbitratorNodeAddress(arbitratorNodeAddress) diff --git a/core/src/main/java/haveno/core/api/model/builder/TradeInfoV1Builder.java b/core/src/main/java/haveno/core/api/model/builder/TradeInfoV1Builder.java index 7987061806b..68d3a57e8f6 100644 --- a/core/src/main/java/haveno/core/api/model/builder/TradeInfoV1Builder.java +++ b/core/src/main/java/haveno/core/api/model/builder/TradeInfoV1Builder.java @@ -41,6 +41,12 @@ public final class TradeInfoV1Builder { private long takerFee; private long buyerSecurityDeposit; private long sellerSecurityDeposit; + private long buyerDepositTxFee; + private long sellerDepositTxFee; + private long buyerPayoutTxFee; + private long sellerPayoutTxFee; + private long buyerPayoutAmount; + private long sellerPayoutAmount; private String makerDepositTxId; private String takerDepositTxId; private String payoutTxId; @@ -117,6 +123,36 @@ public TradeInfoV1Builder withSellerSecurityDeposit(long sellerSecurityDeposit) return this; } + public TradeInfoV1Builder withBuyerDepositTxFee(long buyerDepositTxFee) { + this.buyerDepositTxFee = buyerDepositTxFee; + return this; + } + + public TradeInfoV1Builder withSellerDepositTxFee(long sellerDepositTxFee) { + this.sellerDepositTxFee = sellerDepositTxFee; + return this; + } + + public TradeInfoV1Builder withBuyerPayoutTxFee(long buyerPayoutTxFee) { + this.buyerPayoutTxFee = buyerPayoutTxFee; + return this; + } + + public TradeInfoV1Builder withSellerPayoutTxFee(long sellerPayoutTxFee) { + this.sellerPayoutTxFee = sellerPayoutTxFee; + return this; + } + + public TradeInfoV1Builder withBuyerPayoutAmount(long buyerPayoutAmount) { + this.buyerPayoutAmount = buyerPayoutAmount; + return this; + } + + public TradeInfoV1Builder withSellerPayoutAmount(long sellerPayoutAmount) { + this.sellerPayoutAmount = sellerPayoutAmount; + return this; + } + public TradeInfoV1Builder withMakerDepositTxId(String makerDepositTxId) { this.makerDepositTxId = makerDepositTxId; return this; diff --git a/core/src/main/java/haveno/core/support/dispute/DisputeManager.java b/core/src/main/java/haveno/core/support/dispute/DisputeManager.java index 6a1f69c9579..17df3f95af3 100644 --- a/core/src/main/java/haveno/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/haveno/core/support/dispute/DisputeManager.java @@ -69,7 +69,6 @@ import java.security.KeyPair; import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Optional; @@ -848,42 +847,34 @@ public MoneroTxWallet createDisputePayoutTx(Trade trade, Contract contract, Disp // trade wallet must be synced if (trade.getWallet().isMultisigImportNeeded()) throw new RuntimeException("Arbitrator's wallet needs updated multisig hex to create payout tx which means a trader must have already broadcast the payout tx for trade " + trade.getId()); - // collect winner and loser payout address and amounts - String winnerPayoutAddress = disputeResult.getWinner() == Winner.BUYER ? - (contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString()) : - (contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString()); - String loserPayoutAddress = winnerPayoutAddress.equals(contract.getMakerPayoutAddressString()) ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString(); - BigInteger winnerPayoutAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount(); - BigInteger loserPayoutAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount(); - - // check sufficient balance - if (winnerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new RuntimeException("Winner payout cannot be negative"); - if (loserPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new RuntimeException("Loser payout cannot be negative"); - if (winnerPayoutAmount.add(loserPayoutAmount).compareTo(trade.getWallet().getUnlockedBalance()) > 0) { - throw new RuntimeException("The payout amounts are more than the wallet's unlocked balance, unlocked balance=" + trade.getWallet().getUnlockedBalance() + " vs " + winnerPayoutAmount + " + " + loserPayoutAmount + " = " + (winnerPayoutAmount.add(loserPayoutAmount))); + // check amounts + if (disputeResult.getBuyerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) < 0) throw new RuntimeException("Buyer payout cannot be negative"); + if (disputeResult.getSellerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) < 0) throw new RuntimeException("Seller payout cannot be negative"); + if (disputeResult.getBuyerPayoutAmountBeforeCost().add(disputeResult.getSellerPayoutAmountBeforeCost()).compareTo(trade.getWallet().getUnlockedBalance()) > 0) { + throw new RuntimeException("The payout amounts are more than the wallet's unlocked balance, unlocked balance=" + trade.getWallet().getUnlockedBalance() + " vs " + disputeResult.getBuyerPayoutAmountBeforeCost() + " + " + disputeResult.getSellerPayoutAmountBeforeCost() + " = " + (disputeResult.getBuyerPayoutAmountBeforeCost().add(disputeResult.getSellerPayoutAmountBeforeCost()))); } - // add any loss of precision to winner payout - winnerPayoutAmount = winnerPayoutAmount.add(trade.getWallet().getUnlockedBalance().subtract(winnerPayoutAmount.add(loserPayoutAmount))); - // create dispute payout tx config MoneroTxConfig txConfig = new MoneroTxConfig().setAccountIndex(0); + String buyerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getMakerPayoutAddressString() : contract.getTakerPayoutAddressString(); + String sellerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString(); txConfig.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY); - if (winnerPayoutAmount.compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(winnerPayoutAddress, winnerPayoutAmount); - if (loserPayoutAmount.compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(loserPayoutAddress, loserPayoutAmount); + if (disputeResult.getBuyerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(buyerPayoutAddress, disputeResult.getBuyerPayoutAmountBeforeCost()); + if (disputeResult.getSellerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(sellerPayoutAddress, disputeResult.getSellerPayoutAmountBeforeCost()); // configure who pays mining fee + BigInteger loserPayoutAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmountBeforeCost() : disputeResult.getBuyerPayoutAmountBeforeCost(); if (loserPayoutAmount.equals(BigInteger.ZERO)) txConfig.setSubtractFeeFrom(0); // winner pays fee if loser gets 0 else { switch (disputeResult.getSubtractFeeFrom()) { case BUYER_AND_SELLER: - txConfig.setSubtractFeeFrom(Arrays.asList(0, 1)); + txConfig.setSubtractFeeFrom(0, 1); break; case BUYER_ONLY: - txConfig.setSubtractFeeFrom(disputeResult.getWinner() == Winner.BUYER ? 0 : 1); + txConfig.setSubtractFeeFrom(0); break; case SELLER_ONLY: - txConfig.setSubtractFeeFrom(disputeResult.getWinner() == Winner.SELLER ? 0 : 1); + txConfig.setSubtractFeeFrom(1); break; } } @@ -897,8 +888,19 @@ public MoneroTxWallet createDisputePayoutTx(Trade trade, Contract contract, Disp throw new RuntimeException("Loser payout is too small to cover the mining fee"); } - // save updated multisig hex + // update trade state + BigInteger[] buyerSellerPayoutTxCosts = getBuyerSellerPayoutTxCost(disputeResult, payoutTx.getFee()); + trade.getBuyer().setPayoutTxFee(buyerSellerPayoutTxCosts[0]); + trade.getSeller().setPayoutTxFee(buyerSellerPayoutTxCosts[1]); + if (disputeResult.getBuyerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) > 0) trade.getBuyer().setPayoutAmount(HavenoUtils.getDestination(buyerPayoutAddress, payoutTx).getAmount()); + if (disputeResult.getSellerPayoutAmountBeforeCost().compareTo(BigInteger.ZERO) > 0) trade.getSeller().setPayoutAmount(HavenoUtils.getDestination(sellerPayoutAddress, payoutTx).getAmount()); trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex()); + + // update dispute result + disputeResult.setBuyerPayoutTxFee(trade.getBuyer().getPayoutTxFee()); + disputeResult.setSellerPayoutTxFee(trade.getSeller().getPayoutTxFee()); + disputeResult.setBuyerPayoutAmount(trade.getBuyer().getPayoutAmount()); + disputeResult.setSellerPayoutAmount(trade.getSeller().getPayoutAmount()); return payoutTx; } catch (Exception e) { trade.syncAndPollWallet(); @@ -908,6 +910,28 @@ public MoneroTxWallet createDisputePayoutTx(Trade trade, Contract contract, Disp return null; // can be null if already published or we don't have receiver's multisig hex } + public static BigInteger[] getBuyerSellerPayoutTxCost(DisputeResult disputeResult, BigInteger payoutTxFee) { + boolean isBuyerWinner = disputeResult.getWinner() == Winner.BUYER; + BigInteger loserAmount = isBuyerWinner ? disputeResult.getSellerPayoutAmountBeforeCost() : disputeResult.getBuyerPayoutAmountBeforeCost(); + if (loserAmount.equals(BigInteger.valueOf(0))) { + BigInteger buyerPayoutTxFee = isBuyerWinner ? payoutTxFee : BigInteger.ZERO; + BigInteger sellerPayoutTxFee = isBuyerWinner ? BigInteger.ZERO : payoutTxFee; + return new BigInteger[] { buyerPayoutTxFee, sellerPayoutTxFee }; + } else { + switch (disputeResult.getSubtractFeeFrom()) { + case BUYER_AND_SELLER: + BigInteger payoutTxFeeSplit = payoutTxFee.divide(BigInteger.valueOf(2)); + return new BigInteger[] { payoutTxFeeSplit, payoutTxFeeSplit }; + case BUYER_ONLY: + return new BigInteger[] { payoutTxFee, BigInteger.ZERO }; + case SELLER_ONLY: + return new BigInteger[] { BigInteger.ZERO, payoutTxFee }; + default: + throw new RuntimeException("Unrecognized subtract fee from: " + disputeResult.getSubtractFeeFrom()); + } + } + } + private Tuple2 getNodeAddressPubKeyRingTuple(Dispute dispute) { PubKeyRing receiverPubKeyRing = null; NodeAddress peerNodeAddress = null; diff --git a/core/src/main/java/haveno/core/support/dispute/DisputeResult.java b/core/src/main/java/haveno/core/support/dispute/DisputeResult.java index 7929b2b32e9..150c1b9aa10 100644 --- a/core/src/main/java/haveno/core/support/dispute/DisputeResult.java +++ b/core/src/main/java/haveno/core/support/dispute/DisputeResult.java @@ -86,6 +86,10 @@ public enum SubtractFeeFrom { @Setter @Nullable private byte[] arbitratorSignature; + private long buyerPayoutAmountBeforeCost; + private long sellerPayoutAmountBeforeCost; + private long buyerPayoutTxFee; + private long sellerPayoutTxFee; private long buyerPayoutAmount; private long sellerPayoutAmount; @Setter @@ -109,6 +113,10 @@ public DisputeResult(String tradeId, String summaryNotes, @Nullable ChatMessage chatMessage, @Nullable byte[] arbitratorSignature, + long buyerPayoutAmountBeforeCost, + long sellerPayoutAmountBeforeCost, + long buyerPayoutTxFee, + long sellerPayoutTxFee, long buyerPayoutAmount, long sellerPayoutAmount, @Nullable byte[] arbitratorPubKey, @@ -124,6 +132,10 @@ public DisputeResult(String tradeId, this.summaryNotesProperty.set(summaryNotes); this.chatMessage = chatMessage; this.arbitratorSignature = arbitratorSignature; + this.buyerPayoutAmountBeforeCost = buyerPayoutAmountBeforeCost; + this.sellerPayoutAmountBeforeCost = sellerPayoutAmountBeforeCost; + this.buyerPayoutTxFee = buyerPayoutTxFee; + this.sellerPayoutTxFee = sellerPayoutTxFee; this.buyerPayoutAmount = buyerPayoutAmount; this.sellerPayoutAmount = sellerPayoutAmount; this.arbitratorPubKey = arbitratorPubKey; @@ -147,6 +159,10 @@ public static DisputeResult fromProto(protobuf.DisputeResult proto) { proto.getSummaryNotes(), proto.getChatMessage() == null ? null : ChatMessage.fromPayloadProto(proto.getChatMessage()), proto.getArbitratorSignature().toByteArray(), + proto.getBuyerPayoutAmountBeforeCost(), + proto.getSellerPayoutAmountBeforeCost(), + proto.getBuyerPayoutTxFee(), + proto.getSellerPayoutTxFee(), proto.getBuyerPayoutAmount(), proto.getSellerPayoutAmount(), proto.getArbitratorPubKey().toByteArray(), @@ -163,6 +179,10 @@ public protobuf.DisputeResult toProtoMessage() { .setIdVerification(idVerificationProperty.get()) .setScreenCast(screenCastProperty.get()) .setSummaryNotes(summaryNotesProperty.get()) + .setBuyerPayoutAmountBeforeCost(buyerPayoutAmountBeforeCost) + .setSellerPayoutAmountBeforeCost(sellerPayoutAmountBeforeCost) + .setBuyerPayoutTxFee(buyerPayoutTxFee) + .setSellerPayoutTxFee(sellerPayoutTxFee) .setBuyerPayoutAmount(buyerPayoutAmount) .setSellerPayoutAmount(sellerPayoutAmount) .setCloseDate(closeDate); @@ -213,6 +233,42 @@ public StringProperty summaryNotesProperty() { return summaryNotesProperty; } + public void setBuyerPayoutAmountBeforeCost(BigInteger buyerPayoutAmountBeforeCost) { + if (buyerPayoutAmountBeforeCost.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("buyerPayoutAmountBeforeCost cannot be negative"); + this.buyerPayoutAmountBeforeCost = buyerPayoutAmountBeforeCost.longValueExact(); + } + + public BigInteger getBuyerPayoutAmountBeforeCost() { + return BigInteger.valueOf(buyerPayoutAmountBeforeCost); + } + + public void setSellerPayoutAmountBeforeCost(BigInteger sellerPayoutAmountBeforeCost) { + if (sellerPayoutAmountBeforeCost.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("sellerPayoutAmountBeforeCost cannot be negative"); + this.sellerPayoutAmountBeforeCost = sellerPayoutAmountBeforeCost.longValueExact(); + } + + public BigInteger getSellerPayoutAmountBeforeCost() { + return BigInteger.valueOf(sellerPayoutAmountBeforeCost); + } + + public void setBuyerPayoutTxFee(BigInteger buyerPayoutTxFee) { + if (buyerPayoutTxFee.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("buyerPayoutTxFee cannot be negative"); + this.buyerPayoutTxFee = buyerPayoutTxFee.longValueExact(); + } + + public BigInteger getBuyerPayoutTxFee() { + return BigInteger.valueOf(buyerPayoutTxFee); + } + + public void setSellerPayoutTxFee(BigInteger sellerPayoutTxFee) { + if (sellerPayoutTxFee.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("sellerPayoutTxFee cannot be negative"); + this.sellerPayoutTxFee = sellerPayoutTxFee.longValueExact(); + } + + public BigInteger getSellerPayoutTxFee() { + return BigInteger.valueOf(sellerPayoutTxFee); + } + public void setBuyerPayoutAmount(BigInteger buyerPayoutAmount) { if (buyerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("buyerPayoutAmount cannot be negative"); this.buyerPayoutAmount = buyerPayoutAmount.longValueExact(); @@ -253,6 +309,10 @@ public String toString() { ",\n summaryNotesProperty=" + summaryNotesProperty + ",\n chatMessage=" + chatMessage + ",\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) + + ",\n buyerPayoutAmountBeforeCost=" + buyerPayoutAmountBeforeCost + + ",\n sellerPayoutAmountBeforeCost=" + sellerPayoutAmountBeforeCost + + ",\n buyerPayoutTxFee=" + buyerPayoutTxFee + + ",\n sellerPayoutTxFee=" + sellerPayoutTxFee + ",\n buyerPayoutAmount=" + buyerPayoutAmount + ",\n sellerPayoutAmount=" + sellerPayoutAmount + ",\n arbitratorPubKey=" + Utilities.bytesAsHexString(arbitratorPubKey) + diff --git a/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java index 57908663d60..134537d8a05 100644 --- a/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java @@ -34,7 +34,6 @@ import haveno.core.support.dispute.Dispute; import haveno.core.support.dispute.DisputeManager; import haveno.core.support.dispute.DisputeResult; -import haveno.core.support.dispute.DisputeResult.Winner; import haveno.core.support.dispute.DisputeSummaryVerification; import haveno.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import haveno.core.support.dispute.messages.DisputeClosedMessage; @@ -294,6 +293,12 @@ private void handleDisputeClosedMessage(DisputeClosedMessage disputeClosedMessag if (trade.isPayoutPublished()) { tradeManager.closeDisputedTrade(trade.getId(), Trade.DisputeState.DISPUTE_CLOSED); } + + // set payout amounts + trade.getBuyer().setPayoutTxFee(disputeResult.getBuyerPayoutTxFee()); + trade.getSeller().setPayoutTxFee(disputeResult.getSellerPayoutTxFee()); + trade.getBuyer().setPayoutAmount(disputeResult.getBuyerPayoutAmount()); + trade.getSeller().setPayoutAmount(disputeResult.getSellerPayoutAmount()); // We use the chatMessage as we only persist those not the DisputeClosedMessage. // If we would use the DisputeClosedMessage we could not lookup for the msg when we receive the AckMessage. @@ -358,6 +363,10 @@ private MoneroTxSet signAndPublishDisputePayoutTx(Trade trade) { if (disputeTxSet.getTxs() == null || disputeTxSet.getTxs().size() != 1) throw new RuntimeException("Bad arbitrator-signed payout tx"); // TODO (woodser): nack MoneroTxWallet arbitratorSignedPayoutTx = disputeTxSet.getTxs().get(0); + // verify payout tx fee + BigInteger payoutTxFee = disputeResult.getBuyerPayoutTxFee().add(disputeResult.getSellerPayoutTxFee()); + if (!arbitratorSignedPayoutTx.getFee().equals(payoutTxFee)) throw new RuntimeException("Unexpected payout tx fee, expected=" + payoutTxFee + ", actual=" + arbitratorSignedPayoutTx.getFee() + ", tradeId=" + trade.getId() + ", disputeId=" + dispute.getId() + ", disputeResult=" + disputeResult); + // verify payout tx has 1 or 2 destinations int numDestinations = arbitratorSignedPayoutTx.getOutgoingTransfer() == null || arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations() == null ? 0 : arbitratorSignedPayoutTx.getOutgoingTransfer().getDestinations().size(); if (numDestinations != 1 && numDestinations != 2) throw new RuntimeException("Buyer-signed payout tx does not have 1 or 2 destinations"); @@ -380,42 +389,26 @@ private MoneroTxSet signAndPublishDisputePayoutTx(Trade trade) { if (!arbitratorSignedPayoutTx.getOutputSum().equals(destinationSum.add(arbitratorSignedPayoutTx.getChangeAmount()))) throw new RuntimeException("Sum of outputs != destination amounts + change amount"); // get actual payout amounts - BigInteger actualWinnerAmount = disputeResult.getWinner() == Winner.BUYER ? buyerPayoutDestination.getAmount() : sellerPayoutDestination.getAmount(); - BigInteger actualLoserAmount = numDestinations == 1 ? BigInteger.ZERO : disputeResult.getWinner() == Winner.BUYER ? sellerPayoutDestination.getAmount() : buyerPayoutDestination.getAmount(); + BigInteger actualBuyerAmount = buyerPayoutDestination == null ? BigInteger.ZERO : buyerPayoutDestination.getAmount(); + BigInteger actualSellerAmount = sellerPayoutDestination == null ? BigInteger.ZERO : sellerPayoutDestination.getAmount(); // verify payouts sum to unlocked balance within loss of precision due to conversion to centineros - BigInteger txCost = arbitratorSignedPayoutTx.getFee().add(arbitratorSignedPayoutTx.getChangeAmount()); // fee + lost dust change - if (trade.getWallet().getUnlockedBalance().subtract(actualWinnerAmount.add(actualLoserAmount).add(txCost)).compareTo(BigInteger.valueOf(0)) > 0) { - throw new RuntimeException("The dispute payout amounts do not sum to the wallet's unlocked balance while verifying the dispute payout tx, unlocked balance=" + trade.getWallet().getUnlockedBalance() + " vs sum payout amount=" + actualWinnerAmount.add(actualLoserAmount) + ", winner payout=" + actualWinnerAmount + ", loser payout=" + actualLoserAmount); + BigInteger txCost = arbitratorSignedPayoutTx.getFee().add(arbitratorSignedPayoutTx.getChangeAmount()); // cost = fee + lost dust change + if (!arbitratorSignedPayoutTx.getChangeAmount().equals(BigInteger.ZERO)) log.warn("Leaving dust in multisig wallet: " + arbitratorSignedPayoutTx.getChangeAmount()); + if (trade.getWallet().getUnlockedBalance().subtract(actualBuyerAmount.add(actualSellerAmount).add(txCost)).compareTo(BigInteger.valueOf(0)) > 0) { + throw new RuntimeException("The dispute payout amounts do not sum to the wallet's unlocked balance while verifying the dispute payout tx, unlocked balance=" + trade.getWallet().getUnlockedBalance() + " vs sum payout amount=" + actualBuyerAmount.add(actualSellerAmount) + ", buyer payout=" + actualBuyerAmount + ", seller payout=" + actualSellerAmount); } - // get expected payout amounts - BigInteger expectedWinnerAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount(); - BigInteger expectedLoserAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount(); + // get actual payout tx costs + BigInteger[] buyerSellerPayoutTxCosts = getBuyerSellerPayoutTxCost(disputeResult, txCost); + BigInteger actualBuyerPayoutTxCost = buyerSellerPayoutTxCosts[0]; + BigInteger actualSellerPayoutTxCost = buyerSellerPayoutTxCosts[1]; - // subtract mining fee from expected payouts - if (expectedLoserAmount.equals(BigInteger.ZERO)) expectedWinnerAmount = expectedWinnerAmount.subtract(txCost); // winner pays fee if loser gets 0 - else { - switch (disputeResult.getSubtractFeeFrom()) { - case BUYER_AND_SELLER: - BigInteger txCostSplit = txCost.divide(BigInteger.valueOf(2)); - expectedWinnerAmount = expectedWinnerAmount.subtract(txCostSplit); - expectedLoserAmount = expectedLoserAmount.subtract(txCostSplit); - break; - case BUYER_ONLY: - expectedWinnerAmount = expectedWinnerAmount.subtract(disputeResult.getWinner() == Winner.BUYER ? txCost : BigInteger.ZERO); - expectedLoserAmount = expectedLoserAmount.subtract(disputeResult.getWinner() == Winner.BUYER ? BigInteger.ZERO : txCost); - break; - case SELLER_ONLY: - expectedWinnerAmount = expectedWinnerAmount.subtract(disputeResult.getWinner() == Winner.BUYER ? BigInteger.ZERO : txCost); - expectedLoserAmount = expectedLoserAmount.subtract(disputeResult.getWinner() == Winner.BUYER ? txCost : BigInteger.ZERO); - break; - } - } - - // verify winner and loser payout amounts - if (!expectedWinnerAmount.equals(actualWinnerAmount)) throw new RuntimeException("Unexpected winner payout: " + expectedWinnerAmount + " vs " + actualWinnerAmount); - if (!expectedLoserAmount.equals(actualLoserAmount)) throw new RuntimeException("Unexpected loser payout: " + expectedLoserAmount + " vs " + actualLoserAmount); + // verify buyer and seller payout amounts + if (!disputeResult.getBuyerPayoutTxFee().equals(actualBuyerPayoutTxCost)) throw new RuntimeException("Unexpected buyer payout tx fee, expected=" + disputeResult.getBuyerPayoutTxFee() + ", actual=" + actualBuyerPayoutTxCost); + if (!disputeResult.getSellerPayoutTxFee().equals(actualSellerPayoutTxCost)) throw new RuntimeException("Unexpected seller payout tx fee, expected=" + disputeResult.getSellerPayoutTxFee() + ", actual=" + actualSellerPayoutTxCost); + if (!disputeResult.getBuyerPayoutAmount().equals(actualBuyerAmount)) throw new RuntimeException("Unexpected buyer payout amount, expected=" + disputeResult.getBuyerPayoutAmount() + ", actual=" + actualBuyerAmount); + if (!disputeResult.getSellerPayoutAmount().equals(actualSellerAmount)) throw new RuntimeException("Unexpected seller payout amount, expected=" + disputeResult.getSellerPayoutAmount() + ", actual=" + actualSellerAmount); // check wallet's daemon connection trade.checkDaemonConnection(); @@ -431,7 +424,8 @@ private MoneroTxSet signAndPublishDisputePayoutTx(Trade trade) { boolean signed = trade.getPayoutTxHex() != null && !nonSignedDisputePayoutTxHexes.contains(trade.getPayoutTxHex()); // sign arbitrator-signed payout tx - if (!signed) { + if (signed) disputeTxSet.setMultisigTxHex(trade.getPayoutTxHex()); + else { MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(unsignedPayoutTxHex); if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing arbitrator-signed payout tx"); String signedMultisigTxHex = result.getSignedMultisigTxHex(); @@ -445,6 +439,7 @@ private MoneroTxSet signAndPublishDisputePayoutTx(Trade trade) { try { feeEstimateTx = createDisputePayoutTx(trade, dispute.getContract(), disputeResult, true); } catch (Exception e) { + e.printStackTrace(); log.warn("Could not recreate dispute payout tx to verify fee: " + e.getMessage()); } if (feeEstimateTx != null) { @@ -453,8 +448,6 @@ private MoneroTxSet signAndPublishDisputePayoutTx(Trade trade) { if (feeDiff > XmrWalletService.MINER_FEE_TOLERANCE) throw new IllegalArgumentException("Miner fee is not within " + (XmrWalletService.MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + arbitratorSignedPayoutTx.getFee()); log.info("Payout tx fee {} is within tolerance, diff %={}", arbitratorSignedPayoutTx.getFee(), feeDiff); } - } else { - disputeTxSet.setMultisigTxHex(trade.getPayoutTxHex()); } // submit fully signed payout tx to the network diff --git a/core/src/main/java/haveno/core/trade/HavenoUtils.java b/core/src/main/java/haveno/core/trade/HavenoUtils.java index 9991c66ecf3..246d9f2ae91 100644 --- a/core/src/main/java/haveno/core/trade/HavenoUtils.java +++ b/core/src/main/java/haveno/core/trade/HavenoUtils.java @@ -58,6 +58,9 @@ import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import monero.common.MoneroRpcConnection; +import monero.wallet.model.MoneroDestination; +import monero.wallet.model.MoneroTxWallet; + import org.bitcoinj.core.Coin; /** @@ -543,4 +546,12 @@ public static boolean connectionConfigsEqual(MoneroRpcConnection c1, MoneroRpcCo if (c1 == null) return false; return c1.equals(c2); // equality considers uri, username, and password } + + // TODO: move to monero-java MoneroTxWallet + public static MoneroDestination getDestination(String address, MoneroTxWallet tx) { + for (MoneroDestination destination : tx.getOutgoingTransfer().getDestinations()) { + if (address.equals(destination.getAddress())) return destination; + } + return null; + } } diff --git a/core/src/main/java/haveno/core/trade/Trade.java b/core/src/main/java/haveno/core/trade/Trade.java index e1538895705..743871c8ef6 100644 --- a/core/src/main/java/haveno/core/trade/Trade.java +++ b/core/src/main/java/haveno/core/trade/Trade.java @@ -323,7 +323,6 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt @Getter private final Offer offer; private final long takerFee; - private final long totalTxFee; // Added in 1.5.1 @Getter @@ -451,6 +450,7 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt @Getter @Setter private String payoutTxKey; + private long payoutTxFee; private Long payoutHeight; private IdlePayoutSyncer idlePayoutSyncer; @@ -472,7 +472,6 @@ protected Trade(Offer offer, this.offer = offer; this.amount = tradeAmount.longValueExact(); this.takerFee = takerFee.longValueExact(); - this.totalTxFee = 0l; // TODO: sum tx fees this.price = tradePrice; this.xmrWalletService = xmrWalletService; this.processModel = processModel; @@ -941,20 +940,25 @@ public MoneroTxWallet createPayoutTx() { .setRelay(false) .setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY)); - // save updated multisig hex + // update state + BigInteger payoutTxFeeSplit = payoutTx.getFee().divide(BigInteger.valueOf(2)); + getBuyer().setPayoutTxFee(payoutTxFeeSplit); + getBuyer().setPayoutAmount(HavenoUtils.getDestination(buyerPayoutAddress, payoutTx).getAmount()); + getSeller().setPayoutTxFee(payoutTxFeeSplit); + getSeller().setPayoutAmount(HavenoUtils.getDestination(sellerPayoutAddress, payoutTx).getAmount()); getSelf().setUpdatedMultisigHex(multisigWallet.exportMultisigHex()); return payoutTx; } /** - * Verify a payout tx. + * Process a payout tx. * * @param payoutTxHex is the payout tx hex to verify * @param sign signs the payout tx if true * @param publish publishes the signed payout tx if true */ - public void verifyPayoutTx(String payoutTxHex, boolean sign, boolean publish) { - log.info("Verifying payout tx"); + public void processPayoutTx(String payoutTxHex, boolean sign, boolean publish) { + log.info("Processing payout tx for {} {}", getClass().getSimpleName(), getId()); // gather relevant info MoneroWallet wallet = getWallet(); @@ -988,11 +992,12 @@ public void verifyPayoutTx(String payoutTxHex, boolean sign, boolean publish) { // verify buyer destination amount is deposit amount + this amount - 1/2 tx costs BigInteger txCost = payoutTx.getFee().add(payoutTx.getChangeAmount()); - BigInteger expectedBuyerPayout = buyerDepositAmount.add(tradeAmount).subtract(txCost.divide(BigInteger.valueOf(2))); + BigInteger txCostSplit = txCost.divide(BigInteger.valueOf(2)); + BigInteger expectedBuyerPayout = buyerDepositAmount.add(tradeAmount).subtract(txCostSplit); if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new IllegalArgumentException("Buyer destination amount is not deposit amount + trade amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout); // verify seller destination amount is deposit amount - this amount - 1/2 tx costs - BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCost.divide(BigInteger.valueOf(2))); + BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCostSplit); if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new IllegalArgumentException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout); // check wallet connection @@ -1022,10 +1027,13 @@ public void verifyPayoutTx(String payoutTxHex, boolean sign, boolean publish) { // update trade state setPayoutTx(payoutTx); setPayoutTxHex(payoutTxHex); + getBuyer().setPayoutTxFee(txCostSplit); + getBuyer().setPayoutAmount(expectedBuyerPayout); + getSeller().setPayoutTxFee(txCostSplit); + getSeller().setPayoutAmount(expectedSellerPayout); // submit payout tx if (publish) { - //if (true) throw new RuntimeException("Let's pretend there's an error last second submitting tx to daemon, so we need to resubmit payout hex"); wallet.submitMultisigTxHex(payoutTxHex); pollWallet(); } @@ -1298,9 +1306,26 @@ public void setPayoutTx(MoneroTxWallet payoutTx) { payoutTxId = payoutTx.getHash(); if ("".equals(payoutTxId)) payoutTxId = null; // tx hash is empty until signed payoutTxKey = payoutTx.getKey(); + payoutTxFee = payoutTx.getFee().longValueExact(); for (Dispute dispute : getDisputes()) dispute.setDisputePayoutTxId(payoutTxId); } + @Nullable + public MoneroTx getPayoutTx() { + if (payoutTx == null) { + payoutTx = payoutTxId == null ? null : (this instanceof ArbitratorTrade) ? xmrWalletService.getTxWithCache(payoutTxId) : xmrWalletService.getWallet().getTx(payoutTxId); + } + return payoutTx; + } + + public void setPayoutTxFee(BigInteger payoutTxFee) { + this.payoutTxFee = payoutTxFee.longValueExact(); + } + + public BigInteger getPayoutTxFee() { + return BigInteger.valueOf(payoutTxFee); + } + public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; errorMessageProperty.set(errorMessage); @@ -1653,15 +1678,7 @@ public BigInteger getSellerSecurityDepositBeforeMiningFee() { @Override public BigInteger getTotalTxFee() { - return BigInteger.valueOf(totalTxFee); - } - - @Nullable - public MoneroTx getPayoutTx() { - if (payoutTx == null) { - payoutTx = payoutTxId == null ? null : (this instanceof ArbitratorTrade) ? xmrWalletService.getTxWithCache(payoutTxId) : xmrWalletService.getWallet().getTx(payoutTxId); - } - return payoutTx; + return getSelf().getDepositTxFee().add(getSelf().getPayoutTxFee()); // sum my tx fees } public boolean hasErrorMessage() { @@ -2019,7 +2036,6 @@ public Message toProtoMessage() { protobuf.Trade.Builder builder = protobuf.Trade.newBuilder() .setOffer(offer.toProtoMessage()) .setTakerFee(takerFee) - .setTotalTxFee(totalTxFee) .setTakeOfferDate(takeOfferDate) .setProcessModel(processModel.toProtoMessage()) .setAmount(amount) @@ -2081,7 +2097,7 @@ public String toString() { return "Trade{" + "\n offer=" + offer + ",\n takerFee=" + takerFee + - ",\n totalTxFee=" + totalTxFee + + ",\n totalTxFee=" + getTotalTxFee() + ",\n takeOfferDate=" + takeOfferDate + ",\n processModel=" + processModel + ",\n payoutTxId='" + payoutTxId + '\'' + @@ -2098,7 +2114,6 @@ public String toString() { ",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' + ",\n counterCurrencyExtraData='" + counterCurrencyExtraData + '\'' + ",\n chatMessages=" + chatMessages + - ",\n totalTxFee=" + totalTxFee + ",\n takerFee=" + takerFee + ",\n xmrWalletService=" + xmrWalletService + ",\n stateProperty=" + stateProperty + diff --git a/core/src/main/java/haveno/core/trade/messages/PaymentReceivedMessage.java b/core/src/main/java/haveno/core/trade/messages/PaymentReceivedMessage.java index 1c34c9da637..57735dca917 100644 --- a/core/src/main/java/haveno/core/trade/messages/PaymentReceivedMessage.java +++ b/core/src/main/java/haveno/core/trade/messages/PaymentReceivedMessage.java @@ -48,6 +48,10 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage { private final SignedWitness buyerSignedWitness; @Nullable private final PaymentSentMessage paymentSentMessage; + private final long buyerPayoutTxFee; + private final long sellerPayoutTxFee; + private final long buyerPayoutAmount; + private final long sellerPayoutAmount; @Setter @Nullable private byte[] sellerSignature; @@ -61,7 +65,11 @@ public PaymentReceivedMessage(String tradeId, boolean deferPublishPayout, AccountAgeWitness buyerAccountAgeWitness, @Nullable SignedWitness buyerSignedWitness, - @Nullable PaymentSentMessage paymentSentMessage) { + @Nullable PaymentSentMessage paymentSentMessage, + long buyerPayoutTxFee, + long sellerPayoutTxFee, + long buyerPayoutAmount, + long sellerPayoutAmount) { this(tradeId, senderNodeAddress, uid, @@ -72,7 +80,11 @@ public PaymentReceivedMessage(String tradeId, deferPublishPayout, buyerAccountAgeWitness, buyerSignedWitness, - paymentSentMessage); + paymentSentMessage, + buyerPayoutTxFee, + sellerPayoutTxFee, + buyerPayoutAmount, + sellerPayoutAmount); } @@ -90,7 +102,11 @@ private PaymentReceivedMessage(String tradeId, boolean deferPublishPayout, AccountAgeWitness buyerAccountAgeWitness, @Nullable SignedWitness buyerSignedWitness, - PaymentSentMessage paymentSentMessage) { + PaymentSentMessage paymentSentMessage, + long buyerPayoutTxFee, + long sellerPayoutTxFee, + long buyerPayoutAmount, + long sellerPayoutAmount) { super(messageVersion, tradeId, uid); this.senderNodeAddress = senderNodeAddress; this.unsignedPayoutTxHex = unsignedPayoutTxHex; @@ -100,6 +116,10 @@ private PaymentReceivedMessage(String tradeId, this.paymentSentMessage = paymentSentMessage; this.buyerAccountAgeWitness = buyerAccountAgeWitness; this.buyerSignedWitness = buyerSignedWitness; + this.buyerPayoutTxFee = buyerPayoutTxFee; + this.sellerPayoutTxFee = sellerPayoutTxFee; + this.buyerPayoutAmount = buyerPayoutAmount; + this.sellerPayoutAmount = sellerPayoutAmount; } @Override @@ -108,7 +128,11 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { .setTradeId(tradeId) .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setUid(uid) - .setDeferPublishPayout(deferPublishPayout); + .setDeferPublishPayout(deferPublishPayout) + .setBuyerPayoutTxFee(buyerPayoutTxFee) + .setSellerPayoutTxFee(sellerPayoutTxFee) + .setBuyerPayoutAmount(buyerPayoutAmount) + .setSellerPayoutAmount(sellerPayoutAmount); Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex)); Optional.ofNullable(unsignedPayoutTxHex).ifPresent(e -> builder.setUnsignedPayoutTxHex(unsignedPayoutTxHex)); Optional.ofNullable(signedPayoutTxHex).ifPresent(e -> builder.setSignedPayoutTxHex(signedPayoutTxHex)); @@ -138,7 +162,11 @@ public static PaymentReceivedMessage fromProto(protobuf.PaymentReceivedMessage p proto.getDeferPublishPayout(), buyerAccountAgeWitness, buyerSignedWitness, - proto.hasPaymentSentMessage() ? PaymentSentMessage.fromProto(proto.getPaymentSentMessage(), messageVersion) : null); + proto.hasPaymentSentMessage() ? PaymentSentMessage.fromProto(proto.getPaymentSentMessage(), messageVersion) : null, + proto.getBuyerPayoutTxFee(), + proto.getSellerPayoutTxFee(), + proto.getBuyerPayoutAmount(), + proto.getSellerPayoutAmount()); message.setSellerSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getSellerSignature())); return message; } @@ -154,6 +182,10 @@ public String toString() { ",\n deferPublishPayout=" + deferPublishPayout + ",\n paymentSentMessage=" + paymentSentMessage + ",\n sellerSignature=" + sellerSignature + + ",\n buyerPayoutTxFee=" + buyerPayoutTxFee + + ",\n sellerPayoutTxFee=" + sellerPayoutTxFee + + ",\n buyerPayoutAmount=" + buyerPayoutAmount + + ",\n sellerPayoutAmount=" + sellerPayoutAmount + "\n} " + super.toString(); } } diff --git a/core/src/main/java/haveno/core/trade/protocol/TradePeer.java b/core/src/main/java/haveno/core/trade/protocol/TradePeer.java index 7a3818311d7..665b8fb211d 100644 --- a/core/src/main/java/haveno/core/trade/protocol/TradePeer.java +++ b/core/src/main/java/haveno/core/trade/protocol/TradePeer.java @@ -135,6 +135,8 @@ public final class TradePeer implements PersistablePayload { private String depositTxKey; private long depositTxFee; private long securityDeposit; + private long payoutTxFee; + private long payoutAmount; @Nullable private String updatedMultisigHex; @Getter @@ -161,6 +163,22 @@ public void setSecurityDeposit(BigInteger securityDeposit) { this.securityDeposit = securityDeposit.longValueExact(); } + public BigInteger getPayoutTxFee() { + return BigInteger.valueOf(payoutTxFee); + } + + public void setPayoutTxFee(BigInteger payoutTxFee) { + this.payoutTxFee = payoutTxFee.longValueExact(); + } + + public BigInteger getPayoutAmount() { + return BigInteger.valueOf(payoutAmount); + } + + public void setPayoutAmount(BigInteger payoutAmount) { + this.payoutAmount = payoutAmount.longValueExact(); + } + @Override public Message toProtoMessage() { final protobuf.TradePeer.Builder builder = protobuf.TradePeer.newBuilder(); @@ -191,12 +209,14 @@ public Message toProtoMessage() { Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex)); Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex)); Optional.ofNullable(exchangedMultisigHex).ifPresent(e -> builder.setExchangedMultisigHex(exchangedMultisigHex)); + Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex)); Optional.ofNullable(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash)); Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex)); Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey)); Optional.ofNullable(depositTxFee).ifPresent(e -> builder.setDepositTxFee(depositTxFee)); Optional.ofNullable(securityDeposit).ifPresent(e -> builder.setSecurityDeposit(securityDeposit)); - Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex)); + Optional.ofNullable(payoutTxFee).ifPresent(e -> builder.setPayoutTxFee(payoutTxFee)); + Optional.ofNullable(payoutAmount).ifPresent(e -> builder.setPayoutAmount(payoutAmount)); builder.setDepositsConfirmedMessageAcked(depositsConfirmedMessageAcked); builder.setCurrentDate(currentDate); @@ -237,13 +257,15 @@ public static TradePeer fromProto(protobuf.TradePeer proto, CoreProtoResolver co tradePeer.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex())); tradePeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex())); tradePeer.setExchangedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getExchangedMultisigHex())); + tradePeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex())); + tradePeer.setDepositsConfirmedMessageAcked(proto.getDepositsConfirmedMessageAcked()); tradePeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash())); tradePeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex())); tradePeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey())); tradePeer.setDepositTxFee(BigInteger.valueOf(proto.getDepositTxFee())); tradePeer.setSecurityDeposit(BigInteger.valueOf(proto.getSecurityDeposit())); - tradePeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex())); - tradePeer.setDepositsConfirmedMessageAcked(proto.getDepositsConfirmedMessageAcked()); + tradePeer.setPayoutTxFee(BigInteger.valueOf(proto.getPayoutTxFee())); + tradePeer.setPayoutAmount(BigInteger.valueOf(proto.getPayoutAmount())); return tradePeer; } } diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java index 79cd9890af0..c77fc7f3852 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java @@ -34,6 +34,8 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import java.math.BigInteger; + @Slf4j public class ProcessPaymentReceivedMessage extends TradeTask { public ProcessPaymentReceivedMessage(TaskRunner taskHandler, Trade trade) { @@ -97,7 +99,11 @@ protected void run() { processModel.getAccountAgeWitnessService().publishOwnSignedWitness(signedWitness); } - // complete + // update state + trade.getBuyer().setPayoutTxFee(BigInteger.valueOf(message.getBuyerPayoutTxFee())); + trade.getBuyer().setPayoutAmount(BigInteger.valueOf(message.getBuyerPayoutAmount())); + trade.getSeller().setPayoutTxFee(BigInteger.valueOf(message.getSellerPayoutTxFee())); + trade.getSeller().setPayoutAmount(BigInteger.valueOf(message.getSellerPayoutAmount())); trade.advanceState(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG); // arbitrator auto completes when payout published trade.requestPersistence(); complete(); @@ -136,17 +142,17 @@ private void processPayoutTx(PaymentReceivedMessage message) { if (!trade.isPayoutPublished()) { if (isSigned) { log.info("{} {} publishing signed payout tx from seller", trade.getClass().getSimpleName(), trade.getId()); - trade.verifyPayoutTx(message.getSignedPayoutTxHex(), false, true); + trade.processPayoutTx(message.getSignedPayoutTxHex(), false, true); } else { try { PaymentSentMessage paymentSentMessage = (trade.isArbitrator() ? trade.getBuyer() : trade.getArbitrator()).getPaymentSentMessage(); if (paymentSentMessage == null) throw new RuntimeException("Process model does not have payment sent message for " + trade.getClass().getSimpleName() + " " + trade.getId()); if (StringUtils.equals(trade.getPayoutTxHex(), paymentSentMessage.getPayoutTxHex())) { // unsigned log.info("{} {} verifying, signing, and publishing payout tx", trade.getClass().getSimpleName(), trade.getId()); - trade.verifyPayoutTx(message.getUnsignedPayoutTxHex(), true, true); + trade.processPayoutTx(message.getUnsignedPayoutTxHex(), true, true); } else { log.info("{} {} re-verifying and publishing payout tx", trade.getClass().getSimpleName(), trade.getId()); - trade.verifyPayoutTx(trade.getPayoutTxHex(), false, true); + trade.processPayoutTx(trade.getPayoutTxHex(), false, true); } } catch (Exception e) { trade.syncAndPollWallet(); @@ -157,7 +163,7 @@ private void processPayoutTx(PaymentReceivedMessage message) { } } else { log.info("Payout tx already published for {} {}", trade.getClass().getSimpleName(), trade.getId()); - if (message.getSignedPayoutTxHex() != null && !trade.isPayoutConfirmed()) trade.verifyPayoutTx(message.getSignedPayoutTxHex(), false, true); + if (message.getSignedPayoutTxHex() != null && !trade.isPayoutConfirmed()) trade.processPayoutTx(message.getSignedPayoutTxHex(), false, true); } } } diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java b/core/src/main/java/haveno/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java index 5183a24b4be..a4d8bb728a7 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java @@ -48,7 +48,7 @@ protected void run() { if (trade.getPayoutTxHex() != null) { try { log.info("Seller verifying, signing, and publishing payout tx for trade {}", trade.getId()); - trade.verifyPayoutTx(trade.getPayoutTxHex(), true, true); + trade.processPayoutTx(trade.getPayoutTxHex(), true, true); } catch (Exception e) { log.warn("Error verifying, signing, and publishing payout tx for trade {}: {}. Creating unsigned payout tx", trade.getId(), e.getMessage()); createUnsignedPayoutTx(); @@ -60,7 +60,7 @@ protected void run() { // republish payout tx from previous message log.info("Seller re-verifying and publishing payout tx for trade {}", trade.getId()); - trade.verifyPayoutTx(trade.getArbitrator().getPaymentReceivedMessage().getSignedPayoutTxHex(), false, true); + trade.processPayoutTx(trade.getArbitrator().getPaymentReceivedMessage().getSignedPayoutTxHex(), false, true); } processModel.getTradeManager().requestPersistence(); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java b/core/src/main/java/haveno/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java index 78d8537bbaa..02c7ee58bbc 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/SellerSendPaymentReceivedMessage.java @@ -93,7 +93,11 @@ protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) { trade.getState().ordinal() >= Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG.ordinal(), // informs to expect payout trade.getTradePeer().getAccountAgeWitness(), signedWitness, - getReceiver() == trade.getArbitrator() ? trade.getBuyer().getPaymentSentMessage() : null // buyer already has payment sent message + getReceiver() == trade.getArbitrator() ? trade.getBuyer().getPaymentSentMessage() : null, // buyer already has payment sent message + trade.getBuyer().getPayoutTxFee().longValueExact(), + trade.getSeller().getPayoutTxFee().longValueExact(), + trade.getBuyer().getPayoutAmount().longValueExact(), + trade.getSeller().getPayoutAmount().longValueExact() ); // sign message diff --git a/core/src/test/java/haveno/core/account/witness/AccountAgeWitnessServiceTest.java b/core/src/test/java/haveno/core/account/witness/AccountAgeWitnessServiceTest.java index ba4acc9b13d..d022be60118 100644 --- a/core/src/test/java/haveno/core/account/witness/AccountAgeWitnessServiceTest.java +++ b/core/src/test/java/haveno/core/account/witness/AccountAgeWitnessServiceTest.java @@ -201,6 +201,10 @@ public void testArbitratorSignWitness() { null, 100000, 0, + 0, + 0, + 0, + 0, null, now - 1)); diff --git a/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java index 1c4ab0e634a..728e004b71d 100644 --- a/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -211,8 +211,12 @@ private void addContent() { if (applyPeersDisputeResult) { // If the other peers dispute has been closed we apply the result to ourselves DisputeResult peersDisputeResult = peersDisputeOptional.get().getDisputeResultProperty().get(); + disputeResult.setBuyerPayoutAmountBeforeCost(peersDisputeResult.getBuyerPayoutAmountBeforeCost()); + disputeResult.setSellerPayoutAmountBeforeCost(peersDisputeResult.getSellerPayoutAmountBeforeCost()); disputeResult.setBuyerPayoutAmount(peersDisputeResult.getBuyerPayoutAmount()); disputeResult.setSellerPayoutAmount(peersDisputeResult.getSellerPayoutAmount()); + disputeResult.setBuyerPayoutTxFee(peersDisputeResult.getBuyerPayoutTxFee()); + disputeResult.setSellerPayoutTxFee(peersDisputeResult.getSellerPayoutTxFee()); disputeResult.setWinner(peersDisputeResult.getWinner()); disputeResult.setReason(peersDisputeResult.getReason()); disputeResult.setSummaryNotes(peersDisputeResult.summaryNotesProperty().get()); @@ -402,8 +406,8 @@ private void applyCustomAmounts(InputTextField inputTextField, boolean oldFocusV buyerPayoutAmountInputTextField.setText(formattedCounterPartAmount); } - disputeResult.setBuyerPayoutAmount(buyerAmount); - disputeResult.setSellerPayoutAmount(sellerAmount); + disputeResult.setBuyerPayoutAmountBeforeCost(buyerAmount); + disputeResult.setSellerPayoutAmountBeforeCost(sellerAmount); disputeResult.setWinner(buyerAmount.compareTo(sellerAmount) > 0 ? DisputeResult.Winner.BUYER : DisputeResult.Winner.SELLER); // TODO: UI should allow selection of receiver of exact custom amount, otherwise defaulting to bigger receiver. could extend API to specify who pays payout tx fee: buyer, seller, or both disputeResult.setSubtractFeeFrom(buyerAmount.compareTo(sellerAmount) > 0 ? DisputeResult.SubtractFeeFrom.SELLER_ONLY : DisputeResult.SubtractFeeFrom.BUYER_ONLY); } @@ -709,8 +713,8 @@ private void applyPayoutAmountsToDisputeResult(Toggle selectedTradeAmountToggle) throw new IllegalStateException("Unknown radio button"); } disputesService.applyPayoutAmountsToDisputeResult(payout, dispute, disputeResult, -1); - buyerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmount())); - sellerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmount())); + buyerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmountBeforeCost())); + sellerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmountBeforeCost())); } private void applyTradeAmountRadioButtonStates() { @@ -719,8 +723,8 @@ private void applyTradeAmountRadioButtonStates() { BigInteger sellerSecurityDeposit = trade.getSeller().getSecurityDeposit(); BigInteger tradeAmount = contract.getTradeAmount(); - BigInteger buyerPayoutAmount = disputeResult.getBuyerPayoutAmount(); - BigInteger sellerPayoutAmount = disputeResult.getSellerPayoutAmount(); + BigInteger buyerPayoutAmount = disputeResult.getBuyerPayoutAmountBeforeCost(); + BigInteger sellerPayoutAmount = disputeResult.getSellerPayoutAmountBeforeCost(); buyerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(buyerPayoutAmount)); sellerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(sellerPayoutAmount)); diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/closedtrades/ClosedTradesView.java b/desktop/src/main/java/haveno/desktop/main/portfolio/closedtrades/ClosedTradesView.java index e6fb43f516e..ce05b83187c 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/closedtrades/ClosedTradesView.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/closedtrades/ClosedTradesView.java @@ -91,8 +91,7 @@ private enum ColumnNames { VOLUME(Res.get("shared.amount")), VOLUME_CURRENCY(Res.get("shared.currency")), TX_FEE(Res.get("shared.txFee")), - TRADE_FEE_BTC(Res.get("shared.tradeFee") + " BTC"), - TRADE_FEE_BSQ(Res.get("shared.tradeFee") + " BSQ"), + TRADE_FEE(Res.get("shared.tradeFee")), BUYER_SEC(Res.get("shared.buyerSecurityDeposit")), SELLER_SEC(Res.get("shared.sellerSecurityDeposit")), OFFER_TYPE(Res.get("shared.offerType")), @@ -158,7 +157,7 @@ public ClosedTradesView(ClosedTradesViewModel model, @Override public void initialize() { widthListener = (observable, oldValue, newValue) -> onWidthChange((double) newValue); - tradeFeeColumn.setGraphic(new AutoTooltipLabel(ColumnNames.TRADE_FEE_BTC.toString().replace(" BTC", ""))); + tradeFeeColumn.setGraphic(new AutoTooltipLabel(ColumnNames.TRADE_FEE.toString().replace(" BTC", ""))); buyerSecurityDepositColumn.setGraphic(new AutoTooltipLabel(ColumnNames.BUYER_SEC.toString())); sellerSecurityDepositColumn.setGraphic(new AutoTooltipLabel(ColumnNames.SELLER_SEC.toString())); priceColumn.setGraphic(new AutoTooltipLabel(ColumnNames.PRICE.toString())); @@ -275,11 +274,9 @@ protected void activate() { columns[ColumnNames.VOLUME_CURRENCY.ordinal()] = item.getVolumeCurrencyAsString(); columns[ColumnNames.TX_FEE.ordinal()] = item.getTxFeeAsString(); if (model.dataModel.isCurrencyForTradeFeeBtc(item.getTradable())) { - columns[ColumnNames.TRADE_FEE_BTC.ordinal()] = item.getTradeFeeAsString(false); - columns[ColumnNames.TRADE_FEE_BSQ.ordinal()] = ""; + columns[ColumnNames.TRADE_FEE.ordinal()] = item.getTradeFeeAsString(false); } else { - columns[ColumnNames.TRADE_FEE_BTC.ordinal()] = ""; - columns[ColumnNames.TRADE_FEE_BSQ.ordinal()] = item.getTradeFeeAsString(false); + columns[ColumnNames.TRADE_FEE.ordinal()] = ""; } columns[ColumnNames.BUYER_SEC.ordinal()] = item.getBuyerSecurityDepositAsString(); columns[ColumnNames.SELLER_SEC.ordinal()] = item.getSellerSecurityDepositAsString(); diff --git a/desktop/src/main/java/haveno/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/haveno/desktop/main/support/dispute/DisputeView.java index 4117041e2b1..70e629d7d93 100644 --- a/desktop/src/main/java/haveno/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/haveno/desktop/main/support/dispute/DisputeView.java @@ -639,8 +639,7 @@ private void showCompactReport() { String sellersRole = contract.isBuyerMakerAndSellerTaker() ? "Seller as taker" : "Seller as maker"; String opener = firstDispute.isDisputeOpenerIsBuyer() ? buyersRole : sellersRole; DisputeResult disputeResult = firstDispute.getDisputeResultProperty().get(); - String winner = disputeResult != null && - disputeResult.getWinner() == DisputeResult.Winner.BUYER ? "Buyer" : "Seller"; + String winner = disputeResult != null && disputeResult.getWinner() == DisputeResult.Winner.BUYER ? "Buyer" : "Seller"; String buyerPayoutAmount = disputeResult != null ? HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmount(), true) : ""; String sellerPayoutAmount = disputeResult != null ? HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmount(), true) : ""; diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 20506eb2efe..dc7ab4c3ea6 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -831,33 +831,38 @@ message TradeInfo { uint64 date = 4; string role = 5; uint64 taker_fee = 6 [jstype = JS_STRING]; - string taker_fee_tx_id = 7; - string payout_tx_id = 8; - uint64 amount = 9 [jstype = JS_STRING]; - uint64 buyer_security_deposit = 10 [jstype = JS_STRING]; - uint64 seller_security_deposit = 11 [jstype = JS_STRING]; - string price = 12; - string arbitrator_node_address = 13; - string trade_peer_node_address = 14; - string state = 15; - string phase = 16; - string period_state = 17; - string payout_state = 18; - string dispute_state = 19; - bool is_deposits_published = 20; - bool is_deposits_confirmed = 21; - bool is_deposits_unlocked = 22; - bool is_payment_sent = 23; - bool is_payment_received = 24; - bool is_payout_published = 25; - bool is_payout_confirmed = 26; - bool is_payout_unlocked = 27; - bool is_completed = 28; - string contract_as_json = 29; - ContractInfo contract = 30; - string trade_volume = 31; - string maker_deposit_tx_id = 32; - string taker_deposit_tx_id = 33; + uint64 amount = 7 [jstype = JS_STRING]; + uint64 buyer_security_deposit = 8 [jstype = JS_STRING]; + uint64 seller_security_deposit = 9 [jstype = JS_STRING]; + uint64 buyer_deposit_tx_fee = 10 [jstype = JS_STRING]; + uint64 seller_deposit_tx_fee = 11 [jstype = JS_STRING]; + uint64 buyer_payout_tx_fee = 12 [jstype = JS_STRING]; + uint64 seller_payout_tx_fee = 13 [jstype = JS_STRING]; + uint64 buyer_payout_amount = 14 [jstype = JS_STRING]; + uint64 seller_payout_amount = 15 [jstype = JS_STRING]; + string price = 16; + string arbitrator_node_address = 17; + string trade_peer_node_address = 18; + string state = 19; + string phase = 20; + string period_state = 21; + string payout_state = 22; + string dispute_state = 23; + bool is_deposits_published = 24; + bool is_deposits_confirmed = 25; + bool is_deposits_unlocked = 26; + bool is_payment_sent = 27; + bool is_payment_received = 28; + bool is_payout_published = 29; + bool is_payout_confirmed = 30; + bool is_payout_unlocked = 31; + bool is_completed = 32; + string contract_as_json = 33; + ContractInfo contract = 34; + string trade_volume = 35; + string maker_deposit_tx_id = 36; + string taker_deposit_tx_id = 37; + string payout_tx_id = 38; } message ContractInfo { diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 6420672da98..378c35f3e41 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -319,6 +319,10 @@ message PaymentReceivedMessage { SignedWitness buyer_signed_witness = 9; PaymentSentMessage payment_sent_message = 10; bytes seller_signature = 11; + int64 buyer_payout_tx_fee = 12; + int64 seller_payout_tx_fee = 13; + int64 buyer_payout_amount = 14; + int64 seller_payout_amount = 15; } message MediatedPayoutTxPublishedMessage { @@ -758,12 +762,16 @@ message DisputeResult { string summary_notes = 8; ChatMessage chat_message = 9; bytes arbitrator_signature = 10; - int64 buyer_payout_amount = 11; - int64 seller_payout_amount = 12; - SubtractFeeFrom subtract_fee_from = 13; - bytes arbitrator_pub_key = 14; - int64 close_date = 15; - bool is_loser_publisher = 16; + int64 buyer_payout_amount_before_cost = 11; + int64 seller_payout_amount_before_cost = 12; + int64 buyer_payout_tx_fee = 13; + int64 seller_payout_tx_fee = 14; + int64 buyer_payout_amount = 15; + int64 seller_payout_amount = 16; + SubtractFeeFrom subtract_fee_from = 17; + bytes arbitrator_pub_key = 18; + int64 close_date = 19; + bool is_loser_publisher = 20; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -1488,28 +1496,27 @@ message Trade { string payout_tx_key = 5; int64 amount = 6; int64 taker_fee = 8; - int64 total_tx_fee = 9; - int64 take_offer_date = 10; - int64 price = 11; - State state = 12; - PayoutState payout_state = 13; - DisputeState dispute_state = 14; - TradePeriodState period_state = 15; - Contract contract = 16; - string contract_as_json = 17; - bytes contract_hash = 18; - NodeAddress arbitrator_node_address = 19; - NodeAddress mediator_node_address = 20; - string error_message = 21; - string counter_currency_tx_id = 22; - repeated ChatMessage chat_message = 23; - MediationResultState mediation_result_state = 24; - int64 lock_time = 25; - int64 start_time = 26; - NodeAddress refund_agent_node_address = 27; - RefundResultState refund_result_state = 28; - string counter_currency_extra_data = 29; - string uid = 30; + int64 take_offer_date = 9; + int64 price = 10; + State state = 11; + PayoutState payout_state = 12; + DisputeState dispute_state = 13; + TradePeriodState period_state = 14; + Contract contract = 15; + string contract_as_json = 16; + bytes contract_hash = 17; + NodeAddress arbitrator_node_address = 18; + NodeAddress mediator_node_address = 19; + string error_message = 20; + string counter_currency_tx_id = 21; + repeated ChatMessage chat_message = 22; + MediationResultState mediation_result_state = 23; + int64 lock_time = 24; + int64 start_time = 25; + NodeAddress refund_agent_node_address = 26; + RefundResultState refund_result_state = 27; + string counter_currency_extra_data = 28; + string uid = 29; } message BuyerAsMakerTrade { @@ -1580,13 +1587,15 @@ message TradePeer { string prepared_multisig_hex = 1005; string made_multisig_hex = 1006; string exchanged_multisig_hex = 1007; - string deposit_tx_hash = 1008; - string deposit_tx_hex = 1009; - string deposit_tx_key = 1010; - int64 deposit_tx_fee = 1011; - int64 security_deposit = 1012; - string updated_multisig_hex = 1013; - bool deposits_confirmed_message_acked = 1014; + string updated_multisig_hex = 1008; + bool deposits_confirmed_message_acked = 1009; + string deposit_tx_hash = 1010; + string deposit_tx_hex = 1011; + string deposit_tx_key = 1012; + int64 deposit_tx_fee = 1013; + int64 security_deposit = 1014; + int64 payout_tx_fee = 1015; + int64 payout_amount = 1016; } ///////////////////////////////////////////////////////////////////////////////////////////