set buyer and seller payout tx fee and amount, fix csv export #720

This commit is contained in:
woodser 2023-12-08 05:45:27 -05:00
parent 846a8634e5
commit 8800d9ea46
18 changed files with 374 additions and 247 deletions

View file

@ -163,9 +163,6 @@ public class CoreDisputesService {
}
applyPayoutAmountsToDisputeResult(payout, winningDispute, winnerDisputeResult, customWinnerAmount);
// create dispute payout tx
trade.getProcessModel().setUnsignedPayoutTx(arbitrationManager.createDisputePayoutTx(trade, winningDispute.getContract(), winnerDisputeResult, false));
// close winning dispute ticket
closeDisputeTicket(arbitrationManager, winningDispute, winnerDisputeResult, () -> {
arbitrationManager.requestPersistence();
@ -180,8 +177,8 @@ public class CoreDisputesService {
if (!loserDisputeOptional.isPresent()) throw new IllegalStateException("could not find peer dispute");
var loserDispute = loserDisputeOptional.get();
var loserDisputeResult = createDisputeResult(loserDispute, winner, reason, summaryNotes, closeDate);
loserDisputeResult.setBuyerPayoutAmount(winnerDisputeResult.getBuyerPayoutAmount());
loserDisputeResult.setSellerPayoutAmount(winnerDisputeResult.getSellerPayoutAmount());
loserDisputeResult.setBuyerPayoutAmountBeforeCost(winnerDisputeResult.getBuyerPayoutAmountBeforeCost());
loserDisputeResult.setSellerPayoutAmountBeforeCost(winnerDisputeResult.getSellerPayoutAmountBeforeCost());
loserDisputeResult.setSubtractFeeFrom(winnerDisputeResult.getSubtractFeeFrom());
closeDisputeTicket(arbitrationManager, loserDispute, loserDisputeResult, () -> {
arbitrationManager.requestPersistence();
@ -217,31 +214,23 @@ public class CoreDisputesService {
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
}
}
@ -263,8 +252,8 @@ public class CoreDisputesService {
currencyCode,
Res.get("disputeSummaryWindow.reason." + reason.name()),
amount,
HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmount(), true),
HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmount(), true),
HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmountBeforeCost(), true),
HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmountBeforeCost(), true),
disputeResult.summaryNotesProperty().get()
);

View file

@ -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 class TradeInfo implements Payload {
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 class TradeInfo implements Payload {
.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 class TradeInfo implements Payload {
.setAmount(amount)
.setBuyerSecurityDeposit(buyerSecurityDeposit)
.setSellerSecurityDeposit(sellerSecurityDeposit)
.setBuyerDepositTxFee(buyerDepositTxFee)
.setSellerDepositTxFee(sellerDepositTxFee)
.setBuyerPayoutTxFee(buyerPayoutTxFee)
.setSellerPayoutTxFee(sellerPayoutTxFee)
.setBuyerPayoutAmount(buyerPayoutAmount)
.setSellerPayoutAmount(sellerPayoutAmount)
.setPrice(price)
.setTradeVolume(volume)
.setArbitratorNodeAddress(arbitratorNodeAddress)
@ -241,6 +266,12 @@ public class TradeInfo implements Payload {
.withAmount(proto.getAmount())
.withBuyerSecurityDeposit(proto.getBuyerSecurityDeposit())
.withSellerSecurityDeposit(proto.getSellerSecurityDeposit())
.withBuyerDepositTxFee(proto.getBuyerDepositTxFee())
.withSellerDepositTxFee(proto.getSellerDepositTxFee())
.withBuyerPayoutTxFee(proto.getBuyerPayoutTxFee())
.withSellerPayoutTxFee(proto.getSellerPayoutTxFee())
.withBuyerPayoutAmount(proto.getBuyerPayoutAmount())
.withSellerPayoutAmount(proto.getSellerPayoutAmount())
.withPrice(proto.getPrice())
.withVolume(proto.getTradeVolume())
.withPeriodState(proto.getPeriodState())
@ -278,6 +309,12 @@ public class TradeInfo implements Payload {
", amount='" + amount + '\'' + "\n" +
", buyerSecurityDeposit='" + buyerSecurityDeposit + '\'' + "\n" +
", sellerSecurityDeposit='" + sellerSecurityDeposit + '\'' + "\n" +
", buyerDepositTxFee='" + buyerDepositTxFee + '\'' + "\n" +
", sellerDepositTxFee='" + sellerDepositTxFee + '\'' + "\n" +
", buyerPayoutTxFee='" + buyerPayoutTxFee + '\'' + "\n" +
", sellerPayoutTxFee='" + sellerPayoutTxFee + '\'' + "\n" +
", buyerPayoutAmount='" + buyerPayoutAmount + '\'' + "\n" +
", sellerPayoutAmount='" + sellerPayoutAmount + '\'' + "\n" +
", price='" + price + '\'' + "\n" +
", arbitratorNodeAddress='" + arbitratorNodeAddress + '\'' + "\n" +
", tradePeerNodeAddress='" + tradePeerNodeAddress + '\'' + "\n" +

View file

@ -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 final class TradeInfoV1Builder {
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;

View file

@ -69,7 +69,6 @@ import java.math.BigInteger;
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;
@ -729,23 +728,21 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
dispute.addAndPersistChatMessage(chatMessage);
}
// create dispute payout tx if not published
// create dispute payout tx once per trader if we have their updated multisig hex
TradePeer receiver = trade.getTradePeer(dispute.getTraderPubKeyRing());
if (!trade.isPayoutPublished() && receiver.getUpdatedMultisigHex() != null) {
trade.getProcessModel().setUnsignedPayoutTx(createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false)); // can be null if we don't have receiver's multisig hex
if (!trade.isPayoutPublished() && receiver.getUpdatedMultisigHex() != null && receiver.getUnsignedPayoutTxHex() == null) {
createDisputePayoutTx(trade, dispute.getContract(), disputeResult, true);
}
// create dispute closed message
MoneroTxWallet unsignedPayoutTx = receiver.getUpdatedMultisigHex() == null ? null : trade.getProcessModel().getUnsignedPayoutTx();
String unsignedPayoutTxHex = unsignedPayoutTx == null ? null : unsignedPayoutTx.getTxSet().getMultisigTxHex();
TradePeer receiverPeer = receiver == trade.getBuyer() ? trade.getSeller() : trade.getBuyer();
boolean deferPublishPayout = !exists && unsignedPayoutTxHex != null && receiverPeer.getUpdatedMultisigHex() != null && trade.getDisputeState().ordinal() >= Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG.ordinal();
boolean deferPublishPayout = !exists && receiver.getUnsignedPayoutTxHex() != null && receiverPeer.getUpdatedMultisigHex() != null && trade.getDisputeState().ordinal() >= Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG.ordinal();
DisputeClosedMessage disputeClosedMessage = new DisputeClosedMessage(disputeResult,
p2PService.getAddress(),
UUID.randomUUID().toString(),
getSupportType(),
trade.getSelf().getUpdatedMultisigHex(),
unsignedPayoutTxHex, // include dispute payout tx if arbitrator has their updated multisig info
receiver.getUnsignedPayoutTxHex(), // include dispute payout tx if arbitrator has their updated multisig info
deferPublishPayout); // instruct trader to defer publishing payout tx because peer is expected to publish imminently
// send dispute closed message
@ -812,12 +809,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
}
}
);
// save state
if (unsignedPayoutTx != null) {
trade.setPayoutTx(unsignedPayoutTx);
trade.setPayoutTxHex(unsignedPayoutTx.getTxSet().getMultisigTxHex());
}
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SENT_DISPUTE_CLOSED_MSG);
requestPersistence();
} catch (Exception e) {
@ -829,7 +820,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
public MoneroTxWallet createDisputePayoutTx(Trade trade, Contract contract, DisputeResult disputeResult, boolean skipMultisigImport) {
public MoneroTxWallet createDisputePayoutTx(Trade trade, Contract contract, DisputeResult disputeResult, boolean updateState) {
// import multisig hex
trade.importMultisigHex();
@ -848,42 +839,34 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// 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 +880,14 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
throw new RuntimeException("Loser payout is too small to cover the mining fee");
}
// save updated multisig hex
trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex());
// update trade state
if (updateState) {
trade.getProcessModel().setUnsignedPayoutTx(payoutTx);
trade.setPayoutTx(payoutTx);
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
if (trade.getBuyer().getUpdatedMultisigHex() != null && trade.getBuyer().getUnsignedPayoutTxHex() == null) trade.getBuyer().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
if (trade.getSeller().getUpdatedMultisigHex() != null && trade.getSeller().getUnsignedPayoutTxHex() == null) trade.getSeller().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
}
return payoutTx;
} catch (Exception e) {
trade.syncAndPollWallet();

View file

@ -86,8 +86,8 @@ public final class DisputeResult implements NetworkPayload {
@Setter
@Nullable
private byte[] arbitratorSignature;
private long buyerPayoutAmount;
private long sellerPayoutAmount;
private long buyerPayoutAmountBeforeCost;
private long sellerPayoutAmountBeforeCost;
@Setter
@Nullable
private byte[] arbitratorPubKey;
@ -109,8 +109,8 @@ public final class DisputeResult implements NetworkPayload {
String summaryNotes,
@Nullable ChatMessage chatMessage,
@Nullable byte[] arbitratorSignature,
long buyerPayoutAmount,
long sellerPayoutAmount,
long buyerPayoutAmountBeforeCost,
long sellerPayoutAmountBeforeCost,
@Nullable byte[] arbitratorPubKey,
long closeDate) {
this.tradeId = tradeId;
@ -124,8 +124,8 @@ public final class DisputeResult implements NetworkPayload {
this.summaryNotesProperty.set(summaryNotes);
this.chatMessage = chatMessage;
this.arbitratorSignature = arbitratorSignature;
this.buyerPayoutAmount = buyerPayoutAmount;
this.sellerPayoutAmount = sellerPayoutAmount;
this.buyerPayoutAmountBeforeCost = buyerPayoutAmountBeforeCost;
this.sellerPayoutAmountBeforeCost = sellerPayoutAmountBeforeCost;
this.arbitratorPubKey = arbitratorPubKey;
this.closeDate = closeDate;
}
@ -147,8 +147,8 @@ public final class DisputeResult implements NetworkPayload {
proto.getSummaryNotes(),
proto.getChatMessage() == null ? null : ChatMessage.fromPayloadProto(proto.getChatMessage()),
proto.getArbitratorSignature().toByteArray(),
proto.getBuyerPayoutAmount(),
proto.getSellerPayoutAmount(),
proto.getBuyerPayoutAmountBeforeCost(),
proto.getSellerPayoutAmountBeforeCost(),
proto.getArbitratorPubKey().toByteArray(),
proto.getCloseDate());
}
@ -163,8 +163,8 @@ public final class DisputeResult implements NetworkPayload {
.setIdVerification(idVerificationProperty.get())
.setScreenCast(screenCastProperty.get())
.setSummaryNotes(summaryNotesProperty.get())
.setBuyerPayoutAmount(buyerPayoutAmount)
.setSellerPayoutAmount(sellerPayoutAmount)
.setBuyerPayoutAmountBeforeCost(buyerPayoutAmountBeforeCost)
.setSellerPayoutAmountBeforeCost(sellerPayoutAmountBeforeCost)
.setCloseDate(closeDate);
Optional.ofNullable(arbitratorSignature).ifPresent(arbitratorSignature -> builder.setArbitratorSignature(ByteString.copyFrom(arbitratorSignature)));
@ -213,22 +213,22 @@ public final class DisputeResult implements NetworkPayload {
return summaryNotesProperty;
}
public void setBuyerPayoutAmount(BigInteger buyerPayoutAmount) {
if (buyerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("buyerPayoutAmount cannot be negative");
this.buyerPayoutAmount = buyerPayoutAmount.longValueExact();
public void setBuyerPayoutAmountBeforeCost(BigInteger buyerPayoutAmountBeforeCost) {
if (buyerPayoutAmountBeforeCost.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("buyerPayoutAmountBeforeCost cannot be negative");
this.buyerPayoutAmountBeforeCost = buyerPayoutAmountBeforeCost.longValueExact();
}
public BigInteger getBuyerPayoutAmount() {
return BigInteger.valueOf(buyerPayoutAmount);
public BigInteger getBuyerPayoutAmountBeforeCost() {
return BigInteger.valueOf(buyerPayoutAmountBeforeCost);
}
public void setSellerPayoutAmount(BigInteger sellerPayoutAmount) {
if (sellerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("sellerPayoutAmount cannot be negative");
this.sellerPayoutAmount = sellerPayoutAmount.longValueExact();
public void setSellerPayoutAmountBeforeCost(BigInteger sellerPayoutAmountBeforeCost) {
if (sellerPayoutAmountBeforeCost.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("sellerPayoutAmountBeforeCost cannot be negative");
this.sellerPayoutAmountBeforeCost = sellerPayoutAmountBeforeCost.longValueExact();
}
public BigInteger getSellerPayoutAmount() {
return BigInteger.valueOf(sellerPayoutAmount);
public BigInteger getSellerPayoutAmountBeforeCost() {
return BigInteger.valueOf(sellerPayoutAmountBeforeCost);
}
public void setCloseDate(Date closeDate) {
@ -253,8 +253,8 @@ public final class DisputeResult implements NetworkPayload {
",\n summaryNotesProperty=" + summaryNotesProperty +
",\n chatMessage=" + chatMessage +
",\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) +
",\n buyerPayoutAmount=" + buyerPayoutAmount +
",\n sellerPayoutAmount=" + sellerPayoutAmount +
",\n buyerPayoutAmountBeforeCost=" + buyerPayoutAmountBeforeCost +
",\n sellerPayoutAmountBeforeCost=" + sellerPayoutAmountBeforeCost +
",\n arbitratorPubKey=" + Utilities.bytesAsHexString(arbitratorPubKey) +
",\n closeDate=" + closeDate +
"\n}";

View file

@ -380,42 +380,22 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
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("Dust left in multisig wallet for {} {}: {}", getClass().getSimpleName(), trade.getId(), 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();
// 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 payout amounts
BigInteger[] buyerSellerPayoutTxCost = getBuyerSellerPayoutTxCost(disputeResult, txCost);
BigInteger expectedBuyerAmount = disputeResult.getBuyerPayoutAmountBeforeCost().subtract(buyerSellerPayoutTxCost[0]);
BigInteger expectedSellerAmount = disputeResult.getSellerPayoutAmountBeforeCost().subtract(buyerSellerPayoutTxCost[1]);
if (!expectedBuyerAmount.equals(actualBuyerAmount)) throw new RuntimeException("Unexpected buyer payout: " + expectedBuyerAmount + " vs " + actualBuyerAmount);
if (!expectedSellerAmount.equals(actualSellerAmount)) throw new RuntimeException("Unexpected seller payout: " + expectedSellerAmount + " vs " + actualSellerAmount);
// check wallet's daemon connection
trade.checkDaemonConnection();
@ -431,7 +411,8 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
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();
@ -443,8 +424,9 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
// TODO (monero-project): creating tx will require exchanging updated multisig hex if message needs reprocessed. provide weight with describe_transfer so fee can be estimated?
MoneroTxWallet feeEstimateTx = null;
try {
feeEstimateTx = createDisputePayoutTx(trade, dispute.getContract(), disputeResult, true);
feeEstimateTx = createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false);
} catch (Exception e) {
e.printStackTrace();
log.warn("Could not recreate dispute payout tx to verify fee: " + e.getMessage());
}
if (feeEstimateTx != null) {
@ -453,8 +435,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
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
@ -468,4 +448,26 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
dispute.setDisputePayoutTxId(disputeTxSet.getTxs().get(0).getHash());
return disputeTxSet;
}
public static BigInteger[] getBuyerSellerPayoutTxCost(DisputeResult disputeResult, BigInteger payoutTxCost) {
boolean isBuyerWinner = disputeResult.getWinner() == Winner.BUYER;
BigInteger loserAmount = isBuyerWinner ? disputeResult.getSellerPayoutAmountBeforeCost() : disputeResult.getBuyerPayoutAmountBeforeCost();
if (loserAmount.equals(BigInteger.valueOf(0))) {
BigInteger buyerPayoutTxFee = isBuyerWinner ? payoutTxCost : BigInteger.ZERO;
BigInteger sellerPayoutTxFee = isBuyerWinner ? BigInteger.ZERO : payoutTxCost;
return new BigInteger[] { buyerPayoutTxFee, sellerPayoutTxFee };
} else {
switch (disputeResult.getSubtractFeeFrom()) {
case BUYER_AND_SELLER:
BigInteger payoutTxFeeSplit = payoutTxCost.divide(BigInteger.valueOf(2));
return new BigInteger[] { payoutTxFeeSplit, payoutTxFeeSplit };
case BUYER_ONLY:
return new BigInteger[] { payoutTxCost, BigInteger.ZERO };
case SELLER_ONLY:
return new BigInteger[] { BigInteger.ZERO, payoutTxCost };
default:
throw new RuntimeException("Unsupported subtract fee from: " + disputeResult.getSubtractFeeFrom());
}
}
}
}

View file

@ -188,8 +188,8 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
Trade trade = tradeOptional.get();
if (trade.getDisputeState() == Trade.DisputeState.MEDIATION_REQUESTED ||
trade.getDisputeState() == Trade.DisputeState.MEDIATION_STARTED_BY_PEER) {
trade.getProcessModel().setBuyerPayoutAmountFromMediation(disputeResult.getBuyerPayoutAmount().longValueExact());
trade.getProcessModel().setSellerPayoutAmountFromMediation(disputeResult.getSellerPayoutAmount().longValueExact());
trade.getProcessModel().setBuyerPayoutAmountFromMediation(disputeResult.getBuyerPayoutAmountBeforeCost().longValueExact());
trade.getProcessModel().setSellerPayoutAmountFromMediation(disputeResult.getSellerPayoutAmountBeforeCost().longValueExact());
trade.setDisputeState(Trade.DisputeState.MEDIATION_CLOSED);
@ -222,8 +222,8 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
Optional<Dispute> optionalDispute = findDispute(tradeId);
checkArgument(optionalDispute.isPresent(), "dispute must be present");
DisputeResult disputeResult = optionalDispute.get().getDisputeResultProperty().get();
BigInteger buyerPayoutAmount = disputeResult.getBuyerPayoutAmount();
BigInteger sellerPayoutAmount = disputeResult.getSellerPayoutAmount();
BigInteger buyerPayoutAmount = disputeResult.getBuyerPayoutAmountBeforeCost();
BigInteger sellerPayoutAmount = disputeResult.getSellerPayoutAmountBeforeCost();
ProcessModel processModel = trade.getProcessModel();
processModel.setBuyerPayoutAmountFromMediation(buyerPayoutAmount.longValueExact());
processModel.setSellerPayoutAmountFromMediation(sellerPayoutAmount.longValueExact());

View file

@ -58,6 +58,9 @@ import java.util.concurrent.TimeUnit;
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 class HavenoUtils {
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;
}
}

View file

@ -37,6 +37,8 @@ import haveno.core.payment.payload.PaymentAccountPayload;
import haveno.core.proto.CoreProtoResolver;
import haveno.core.proto.network.CoreNetworkProtoResolver;
import haveno.core.support.dispute.Dispute;
import haveno.core.support.dispute.DisputeResult;
import haveno.core.support.dispute.arbitration.ArbitrationManager;
import haveno.core.support.dispute.mediation.MediationResultState;
import haveno.core.support.dispute.refund.RefundResultState;
import haveno.core.support.messages.ChatMessage;
@ -323,7 +325,6 @@ public abstract class Trade implements Tradable, Model {
@Getter
private final Offer offer;
private final long takerFee;
private final long totalTxFee;
// Added in 1.5.1
@Getter
@ -451,6 +452,7 @@ public abstract class Trade implements Tradable, Model {
@Getter
@Setter
private String payoutTxKey;
private long payoutTxFee;
private Long payoutHeight;
private IdlePayoutSyncer idlePayoutSyncer;
@ -472,7 +474,6 @@ public abstract class Trade implements Tradable, Model {
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 +942,25 @@ public abstract class Trade implements Tradable, Model {
.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();
@ -981,6 +987,7 @@ public abstract class Trade implements Tradable, Model {
if (!sellerPayoutDestination.getAddress().equals(contract.getSellerPayoutAddressString())) throw new IllegalArgumentException("Seller payout address does not match contract");
// verify change address is multisig's primary address
if (!payoutTx.getChangeAmount().equals(BigInteger.ZERO)) log.warn("Dust left in multisig wallet for {} {}: {}", getClass().getSimpleName(), getId(), payoutTx.getChangeAmount());
if (!payoutTx.getChangeAmount().equals(BigInteger.ZERO) && !payoutTx.getChangeAddress().equals(wallet.getPrimaryAddress())) throw new IllegalArgumentException("Change address is not multisig wallet's primary address");
// verify sum of outputs = destination amounts + change amount
@ -988,11 +995,12 @@ public abstract class Trade implements Tradable, Model {
// 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
@ -1025,7 +1033,6 @@ public abstract class Trade implements Tradable, Model {
// 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();
}
@ -1296,9 +1303,47 @@ public abstract class Trade implements Tradable, Model {
public void setPayoutTx(MoneroTxWallet payoutTx) {
this.payoutTx = payoutTx;
payoutTxId = payoutTx.getHash();
if ("".equals(payoutTxId)) payoutTxId = null; // tx hash is empty until signed
if ("".equals(payoutTxId)) payoutTxId = null; // tx id is empty until signed
payoutTxKey = payoutTx.getKey();
payoutTxFee = payoutTx.getFee().longValueExact();
for (Dispute dispute : getDisputes()) dispute.setDisputePayoutTxId(payoutTxId);
// set final payout amounts
if (getDisputeState().isNotDisputed()) {
BigInteger splitTxFee = payoutTx.getFee().divide(BigInteger.valueOf(2));
getBuyer().setPayoutTxFee(splitTxFee);
getSeller().setPayoutTxFee(splitTxFee);
getBuyer().setPayoutAmount(getBuyer().getSecurityDeposit().subtract(getBuyer().getPayoutTxFee()).add(getAmount()));
getSeller().setPayoutAmount(getSeller().getSecurityDeposit().subtract(getSeller().getPayoutTxFee()));
} else if (getDisputeState().isClosed()) {
DisputeResult disputeResult = getDisputeResult();
BigInteger[] buyerSellerPayoutTxFees = ArbitrationManager.getBuyerSellerPayoutTxCost(disputeResult, payoutTx.getFee());
getBuyer().setPayoutTxFee(buyerSellerPayoutTxFees[0]);
getSeller().setPayoutTxFee(buyerSellerPayoutTxFees[1]);
getBuyer().setPayoutAmount(disputeResult.getBuyerPayoutAmountBeforeCost().subtract(getBuyer().getPayoutTxFee()));
getSeller().setPayoutAmount(disputeResult.getSellerPayoutAmountBeforeCost().subtract(getSeller().getPayoutTxFee()));
}
}
public DisputeResult getDisputeResult() {
if (getDisputes().isEmpty()) return null;
return getDisputes().get(getDisputes().size() - 1).getDisputeResultProperty().get();
}
@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) {
@ -1653,15 +1698,7 @@ public abstract class Trade implements Tradable, Model {
@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 +2056,6 @@ public abstract class Trade implements Tradable, Model {
protobuf.Trade.Builder builder = protobuf.Trade.newBuilder()
.setOffer(offer.toProtoMessage())
.setTakerFee(takerFee)
.setTotalTxFee(totalTxFee)
.setTakeOfferDate(takeOfferDate)
.setProcessModel(processModel.toProtoMessage())
.setAmount(amount)
@ -2081,7 +2117,7 @@ public abstract class Trade implements Tradable, Model {
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 +2134,6 @@ public abstract class Trade implements Tradable, Model {
",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' +
",\n counterCurrencyExtraData='" + counterCurrencyExtraData + '\'' +
",\n chatMessages=" + chatMessages +
",\n totalTxFee=" + totalTxFee +
",\n takerFee=" + takerFee +
",\n xmrWalletService=" + xmrWalletService +
",\n stateProperty=" + stateProperty +

View file

@ -136,6 +136,11 @@ public final class TradePeer implements PersistablePayload {
private long depositTxFee;
private long securityDeposit;
@Nullable
@Setter
private String unsignedPayoutTxHex;
private long payoutTxFee;
private long payoutAmount;
@Nullable
private String updatedMultisigHex;
@Getter
@Setter
@ -161,6 +166,22 @@ public final class TradePeer implements PersistablePayload {
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 +212,15 @@ public final class TradePeer implements PersistablePayload {
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(unsignedPayoutTxHex).ifPresent(e -> builder.setUnsignedPayoutTxHex(unsignedPayoutTxHex));
Optional.ofNullable(payoutTxFee).ifPresent(e -> builder.setPayoutTxFee(payoutTxFee));
Optional.ofNullable(payoutAmount).ifPresent(e -> builder.setPayoutAmount(payoutAmount));
builder.setDepositsConfirmedMessageAcked(depositsConfirmedMessageAcked);
builder.setCurrentDate(currentDate);
@ -237,13 +261,16 @@ public final class TradePeer implements PersistablePayload {
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.setUnsignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getUnsignedPayoutTxHex()));
tradePeer.setPayoutTxFee(BigInteger.valueOf(proto.getPayoutTxFee()));
tradePeer.setPayoutAmount(BigInteger.valueOf(proto.getPayoutAmount()));
return tradePeer;
}
}

View file

@ -136,17 +136,17 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
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 +157,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
}
} 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);
}
}
}

View file

@ -48,7 +48,7 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
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 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
// 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();

View file

@ -211,8 +211,8 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
if (applyPeersDisputeResult) {
// If the other peers dispute has been closed we apply the result to ourselves
DisputeResult peersDisputeResult = peersDisputeOptional.get().getDisputeResultProperty().get();
disputeResult.setBuyerPayoutAmount(peersDisputeResult.getBuyerPayoutAmount());
disputeResult.setSellerPayoutAmount(peersDisputeResult.getSellerPayoutAmount());
disputeResult.setBuyerPayoutAmountBeforeCost(peersDisputeResult.getBuyerPayoutAmountBeforeCost());
disputeResult.setSellerPayoutAmountBeforeCost(peersDisputeResult.getSellerPayoutAmountBeforeCost());
disputeResult.setWinner(peersDisputeResult.getWinner());
disputeResult.setReason(peersDisputeResult.getReason());
disputeResult.setSummaryNotes(peersDisputeResult.summaryNotesProperty().get());
@ -402,8 +402,8 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
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);
}
@ -587,8 +587,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
!trade.isPayoutPublished()) {
// create payout tx
MoneroTxWallet payoutTx = arbitrationManager.createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false);
trade.getProcessModel().setUnsignedPayoutTx((MoneroTxWallet) payoutTx);
MoneroTxWallet payoutTx = arbitrationManager.createDisputePayoutTx(trade, dispute.getContract(), disputeResult, true);
// show confirmation
showPayoutTxConfirmation(contract,
@ -709,8 +708,8 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
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 +718,8 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
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));

View file

@ -91,8 +91,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
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 class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
@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 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
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();

View file

@ -679,8 +679,8 @@ public abstract class TradeStepView extends AnchorPane {
DisputeResult disputeResult = optionalDispute.get().getDisputeResultProperty().get();
Contract contract = checkNotNull(trade.getContract(), "contract must not be null");
boolean isMyRoleBuyer = contract.isMyRoleBuyer(model.dataModel.getPubKeyRingProvider().get());
String buyerPayoutAmount = HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmount(), true);
String sellerPayoutAmount = HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmount(), true);
String buyerPayoutAmount = HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmountBeforeCost(), true);
String sellerPayoutAmount = HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmountBeforeCost(), true);
String myPayoutAmount = isMyRoleBuyer ? buyerPayoutAmount : sellerPayoutAmount;
String peersPayoutAmount = isMyRoleBuyer ? sellerPayoutAmount : buyerPayoutAmount;

View file

@ -639,10 +639,9 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
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 buyerPayoutAmount = disputeResult != null ? HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmount(), true) : "";
String sellerPayoutAmount = disputeResult != null ? HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmount(), true) : "";
String winner = disputeResult != null && disputeResult.getWinner() == DisputeResult.Winner.BUYER ? "Buyer" : "Seller";
String buyerPayoutAmount = disputeResult != null ? HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmountBeforeCost(), true) : "";
String sellerPayoutAmount = disputeResult != null ? HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmountBeforeCost(), true) : "";
int index = disputeIndex.incrementAndGet();
String tradeDateString = dateFormatter.format(firstDispute.getTradeDate());

View file

@ -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 {

View file

@ -758,8 +758,8 @@ message DisputeResult {
string summary_notes = 8;
ChatMessage chat_message = 9;
bytes arbitrator_signature = 10;
int64 buyer_payout_amount = 11;
int64 seller_payout_amount = 12;
int64 buyer_payout_amount_before_cost = 11;
int64 seller_payout_amount_before_cost = 12;
SubtractFeeFrom subtract_fee_from = 13;
bytes arbitrator_pub_key = 14;
int64 close_date = 15;
@ -1488,28 +1488,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 {
@ -1572,21 +1571,23 @@ message TradePeer {
PaymentSentMessage payment_sent_message = 23;
PaymentReceivedMessage payment_received_message = 24;
DisputeClosedMessage dispute_closed_message = 25;
string reserve_tx_hash = 1001;
string reserve_tx_hex = 1002;
string reserve_tx_key = 1003;
repeated string reserve_tx_key_images = 1004;
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 reserve_tx_hash = 26;
string reserve_tx_hex = 27;
string reserve_tx_key = 28;
repeated string reserve_tx_key_images = 29;
string prepared_multisig_hex = 30;
string made_multisig_hex = 31;
string exchanged_multisig_hex = 32;
string updated_multisig_hex = 33;
bool deposits_confirmed_message_acked = 34;
string deposit_tx_hash = 35;
string deposit_tx_hex = 36;
string deposit_tx_key = 37;
int64 deposit_tx_fee = 38;
int64 security_deposit = 39;
string unsigned_payout_tx_hex = 40;
int64 payout_tx_fee = 41;
int64 payout_amount = 42;
}
///////////////////////////////////////////////////////////////////////////////////////////