mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-23 04:29:22 +00:00
fix incorrect deposit amount for range trades
improve display of reserved and pending balances by adjusting support subtracting fee from buyer and/or seller on dispute resolution validate trade amount is within offer amount expose maker's split output tx fee expose security deposit received from buyer and seller
This commit is contained in:
parent
0294062312
commit
05e2d925f0
25 changed files with 267 additions and 91 deletions
|
@ -146,9 +146,10 @@ public class CoreDisputesService {
|
||||||
|
|
||||||
synchronized (trade) {
|
synchronized (trade) {
|
||||||
try {
|
try {
|
||||||
var closeDate = new Date();
|
|
||||||
var disputeResult = createDisputeResult(winningDispute, winner, reason, summaryNotes, closeDate);
|
|
||||||
|
|
||||||
|
// create dispute result
|
||||||
|
var closeDate = new Date();
|
||||||
|
var winnerDisputeResult = createDisputeResult(winningDispute, winner, reason, summaryNotes, closeDate);
|
||||||
DisputePayout payout;
|
DisputePayout payout;
|
||||||
if (customWinnerAmount > 0) {
|
if (customWinnerAmount > 0) {
|
||||||
payout = DisputePayout.CUSTOM;
|
payout = DisputePayout.CUSTOM;
|
||||||
|
@ -159,13 +160,14 @@ public class CoreDisputesService {
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Unexpected DisputeResult.Winner: " + winner);
|
throw new IllegalStateException("Unexpected DisputeResult.Winner: " + winner);
|
||||||
}
|
}
|
||||||
applyPayoutAmountsToDisputeResult(payout, winningDispute, disputeResult, customWinnerAmount);
|
applyPayoutAmountsToDisputeResult(payout, winningDispute, winnerDisputeResult, customWinnerAmount);
|
||||||
|
winnerDisputeResult.setSubtractFeeFrom(customWinnerAmount == 0 ? DisputeResult.SubtractFeeFrom.BUYER_AND_SELLER : winner == DisputeResult.Winner.BUYER ? DisputeResult.SubtractFeeFrom.SELLER_ONLY : DisputeResult.SubtractFeeFrom.BUYER_ONLY);
|
||||||
|
|
||||||
// create dispute payout tx
|
// create dispute payout tx
|
||||||
trade.getProcessModel().setUnsignedPayoutTx(arbitrationManager.createDisputePayoutTx(trade, winningDispute.getContract(), disputeResult, false));
|
trade.getProcessModel().setUnsignedPayoutTx(arbitrationManager.createDisputePayoutTx(trade, winningDispute.getContract(), winnerDisputeResult, false));
|
||||||
|
|
||||||
// close winning dispute ticket
|
// close winning dispute ticket
|
||||||
closeDisputeTicket(arbitrationManager, winningDispute, disputeResult, () -> {
|
closeDisputeTicket(arbitrationManager, winningDispute, winnerDisputeResult, () -> {
|
||||||
arbitrationManager.requestPersistence();
|
arbitrationManager.requestPersistence();
|
||||||
}, (errMessage, err) -> {
|
}, (errMessage, err) -> {
|
||||||
throw new IllegalStateException(errMessage, err);
|
throw new IllegalStateException(errMessage, err);
|
||||||
|
@ -178,8 +180,9 @@ public class CoreDisputesService {
|
||||||
if (!loserDisputeOptional.isPresent()) throw new IllegalStateException("could not find peer dispute");
|
if (!loserDisputeOptional.isPresent()) throw new IllegalStateException("could not find peer dispute");
|
||||||
var loserDispute = loserDisputeOptional.get();
|
var loserDispute = loserDisputeOptional.get();
|
||||||
var loserDisputeResult = createDisputeResult(loserDispute, winner, reason, summaryNotes, closeDate);
|
var loserDisputeResult = createDisputeResult(loserDispute, winner, reason, summaryNotes, closeDate);
|
||||||
loserDisputeResult.setBuyerPayoutAmount(disputeResult.getBuyerPayoutAmount());
|
loserDisputeResult.setBuyerPayoutAmount(winnerDisputeResult.getBuyerPayoutAmount());
|
||||||
loserDisputeResult.setSellerPayoutAmount(disputeResult.getSellerPayoutAmount());
|
loserDisputeResult.setSellerPayoutAmount(winnerDisputeResult.getSellerPayoutAmount());
|
||||||
|
loserDisputeResult.setSubtractFeeFrom(winnerDisputeResult.getSubtractFeeFrom());
|
||||||
closeDisputeTicket(arbitrationManager, loserDispute, loserDisputeResult, () -> {
|
closeDisputeTicket(arbitrationManager, loserDispute, loserDisputeResult, () -> {
|
||||||
arbitrationManager.requestPersistence();
|
arbitrationManager.requestPersistence();
|
||||||
}, (errMessage, err) -> {
|
}, (errMessage, err) -> {
|
||||||
|
@ -239,6 +242,7 @@ public class CoreDisputesService {
|
||||||
disputeResult.setBuyerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? customWinnerAmount : loserAmount));
|
disputeResult.setBuyerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? customWinnerAmount : loserAmount));
|
||||||
disputeResult.setSellerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? loserAmount : customWinnerAmount));
|
disputeResult.setSellerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? loserAmount : customWinnerAmount));
|
||||||
}
|
}
|
||||||
|
disputeResult.setSubtractFeeFrom(DisputeResult.SubtractFeeFrom.BUYER_AND_SELLER); // TODO: can extend UI to specify who pays mining fee
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeDisputeTicket(DisputeManager disputeManager, Dispute dispute, DisputeResult disputeResult, ResultHandler resultHandler, FaultHandler faultHandler) {
|
public void closeDisputeTicket(DisputeManager disputeManager, Dispute dispute, DisputeResult disputeResult, ResultHandler resultHandler, FaultHandler faultHandler) {
|
||||||
|
|
|
@ -243,7 +243,7 @@ public class CoreOffersService {
|
||||||
for (Offer offer2 : offers) {
|
for (Offer offer2 : offers) {
|
||||||
if (offer == offer2) continue;
|
if (offer == offer2) continue;
|
||||||
if (offer2.getOfferPayload().getReserveTxKeyImages().contains(keyImage)) {
|
if (offer2.getOfferPayload().getReserveTxKeyImages().contains(keyImage)) {
|
||||||
log.warn("Key image {} belongs to multiple offers, seen in offer {}", keyImage, offer2.getId());
|
log.warn("Key image {} belongs to multiple offers, seen in offer {} and {}", keyImage, offer.getId(), offer2.getId());
|
||||||
duplicateFundedOffers.add(offer2);
|
duplicateFundedOffers.add(offer2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ class CoreTradesService {
|
||||||
takeOfferModel.initModel(offer, paymentAccount, amount, useSavingsWallet);
|
takeOfferModel.initModel(offer, paymentAccount, amount, useSavingsWallet);
|
||||||
takerFee = takeOfferModel.getTakerFee();
|
takerFee = takeOfferModel.getTakerFee();
|
||||||
fundsNeededForTrade = takeOfferModel.getFundsNeededForTrade();
|
fundsNeededForTrade = takeOfferModel.getFundsNeededForTrade();
|
||||||
log.info("Initiating take {} offer, {}", offer.isBuyOffer() ? "buy" : "sell", takeOfferModel);
|
log.debug("Initiating take {} offer, {}", offer.isBuyOffer() ? "buy" : "sell", takeOfferModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// take offer
|
// take offer
|
||||||
|
|
|
@ -74,6 +74,9 @@ public class OfferInfo implements Payload {
|
||||||
private final int protocolVersion;
|
private final int protocolVersion;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String arbitratorSigner;
|
private final String arbitratorSigner;
|
||||||
|
@Nullable
|
||||||
|
private final String splitOutputTxHash;
|
||||||
|
private final long splitOutputTxFee;
|
||||||
|
|
||||||
public OfferInfo(OfferInfoBuilder builder) {
|
public OfferInfo(OfferInfoBuilder builder) {
|
||||||
this.id = builder.getId();
|
this.id = builder.getId();
|
||||||
|
@ -103,6 +106,8 @@ public class OfferInfo implements Payload {
|
||||||
this.versionNumber = builder.getVersionNumber();
|
this.versionNumber = builder.getVersionNumber();
|
||||||
this.protocolVersion = builder.getProtocolVersion();
|
this.protocolVersion = builder.getProtocolVersion();
|
||||||
this.arbitratorSigner = builder.getArbitratorSigner();
|
this.arbitratorSigner = builder.getArbitratorSigner();
|
||||||
|
this.splitOutputTxHash = builder.getSplitOutputTxHash();
|
||||||
|
this.splitOutputTxFee = builder.getSplitOutputTxFee();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OfferInfo toOfferInfo(Offer offer) {
|
public static OfferInfo toOfferInfo(Offer offer) {
|
||||||
|
@ -127,6 +132,8 @@ public class OfferInfo implements Payload {
|
||||||
.withTriggerPrice(preciseTriggerPrice)
|
.withTriggerPrice(preciseTriggerPrice)
|
||||||
.withState(openOffer.getState().name())
|
.withState(openOffer.getState().name())
|
||||||
.withIsActivated(isActivated)
|
.withIsActivated(isActivated)
|
||||||
|
.withSplitOutputTxHash(openOffer.getSplitOutputTxHash())
|
||||||
|
.withSplitOutputTxFee(openOffer.getSplitOutputTxFee())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,8 +206,10 @@ public class OfferInfo implements Payload {
|
||||||
.setOwnerNodeAddress(ownerNodeAddress)
|
.setOwnerNodeAddress(ownerNodeAddress)
|
||||||
.setPubKeyRing(pubKeyRing)
|
.setPubKeyRing(pubKeyRing)
|
||||||
.setVersionNr(versionNumber)
|
.setVersionNr(versionNumber)
|
||||||
.setProtocolVersion(protocolVersion);
|
.setProtocolVersion(protocolVersion)
|
||||||
|
.setSplitOutputTxFee(splitOutputTxFee);
|
||||||
Optional.ofNullable(arbitratorSigner).ifPresent(builder::setArbitratorSigner);
|
Optional.ofNullable(arbitratorSigner).ifPresent(builder::setArbitratorSigner);
|
||||||
|
Optional.ofNullable(splitOutputTxHash).ifPresent(builder::setSplitOutputTxHash);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,6 +243,8 @@ public class OfferInfo implements Payload {
|
||||||
.withVersionNumber(proto.getVersionNr())
|
.withVersionNumber(proto.getVersionNr())
|
||||||
.withProtocolVersion(proto.getProtocolVersion())
|
.withProtocolVersion(proto.getProtocolVersion())
|
||||||
.withArbitratorSigner(proto.getArbitratorSigner())
|
.withArbitratorSigner(proto.getArbitratorSigner())
|
||||||
|
.withSplitOutputTxHash(proto.getSplitOutputTxHash())
|
||||||
|
.withSplitOutputTxFee(proto.getSplitOutputTxFee())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,8 @@ public final class OfferInfoBuilder {
|
||||||
private String versionNumber;
|
private String versionNumber;
|
||||||
private int protocolVersion;
|
private int protocolVersion;
|
||||||
private String arbitratorSigner;
|
private String arbitratorSigner;
|
||||||
|
private String splitOutputTxHash;
|
||||||
|
private long splitOutputTxFee;
|
||||||
|
|
||||||
public OfferInfoBuilder withId(String id) {
|
public OfferInfoBuilder withId(String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
@ -209,6 +211,16 @@ public final class OfferInfoBuilder {
|
||||||
this.arbitratorSigner = arbitratorSigner;
|
this.arbitratorSigner = arbitratorSigner;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OfferInfoBuilder withSplitOutputTxHash(String splitOutputTxHash) {
|
||||||
|
this.splitOutputTxHash = splitOutputTxHash;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferInfoBuilder withSplitOutputTxFee(long splitOutputTxFee) {
|
||||||
|
this.splitOutputTxFee = splitOutputTxFee;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public OfferInfo build() {
|
public OfferInfo build() {
|
||||||
return new OfferInfo(this);
|
return new OfferInfo(this);
|
||||||
|
|
|
@ -222,7 +222,7 @@ public class OfferUtil {
|
||||||
getMaxBuyerSecurityDepositAsPercent());
|
getMaxBuyerSecurityDepositAsPercent());
|
||||||
checkArgument(buyerSecurityDeposit >= getMinBuyerSecurityDepositAsPercent(),
|
checkArgument(buyerSecurityDeposit >= getMinBuyerSecurityDepositAsPercent(),
|
||||||
"securityDeposit must not be less than " +
|
"securityDeposit must not be less than " +
|
||||||
getMinBuyerSecurityDepositAsPercent());
|
getMinBuyerSecurityDepositAsPercent() + " but was " + buyerSecurityDeposit);
|
||||||
checkArgument(!filterManager.isCurrencyBanned(currencyCode),
|
checkArgument(!filterManager.isCurrencyBanned(currencyCode),
|
||||||
Res.get("offerbook.warning.currencyBanned"));
|
Res.get("offerbook.warning.currencyBanned"));
|
||||||
checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()),
|
checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()),
|
||||||
|
|
|
@ -70,6 +70,9 @@ public final class OpenOffer implements Tradable {
|
||||||
@Getter
|
@Getter
|
||||||
@Nullable
|
@Nullable
|
||||||
String splitOutputTxHash;
|
String splitOutputTxHash;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
long splitOutputTxFee;
|
||||||
@Nullable
|
@Nullable
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -114,6 +117,7 @@ public final class OpenOffer implements Tradable {
|
||||||
this.scheduledAmount = openOffer.scheduledAmount;
|
this.scheduledAmount = openOffer.scheduledAmount;
|
||||||
this.scheduledTxHashes = openOffer.scheduledTxHashes == null ? null : new ArrayList<String>(openOffer.scheduledTxHashes);
|
this.scheduledTxHashes = openOffer.scheduledTxHashes == null ? null : new ArrayList<String>(openOffer.scheduledTxHashes);
|
||||||
this.splitOutputTxHash = openOffer.splitOutputTxHash;
|
this.splitOutputTxHash = openOffer.splitOutputTxHash;
|
||||||
|
this.splitOutputTxFee = openOffer.splitOutputTxFee;
|
||||||
this.reserveTxHash = openOffer.reserveTxHash;
|
this.reserveTxHash = openOffer.reserveTxHash;
|
||||||
this.reserveTxHex = openOffer.reserveTxHex;
|
this.reserveTxHex = openOffer.reserveTxHex;
|
||||||
this.reserveTxKey = openOffer.reserveTxKey;
|
this.reserveTxKey = openOffer.reserveTxKey;
|
||||||
|
@ -130,6 +134,7 @@ public final class OpenOffer implements Tradable {
|
||||||
@Nullable String scheduledAmount,
|
@Nullable String scheduledAmount,
|
||||||
@Nullable List<String> scheduledTxHashes,
|
@Nullable List<String> scheduledTxHashes,
|
||||||
String splitOutputTxHash,
|
String splitOutputTxHash,
|
||||||
|
long splitOutputTxFee,
|
||||||
@Nullable String reserveTxHash,
|
@Nullable String reserveTxHash,
|
||||||
@Nullable String reserveTxHex,
|
@Nullable String reserveTxHex,
|
||||||
@Nullable String reserveTxKey) {
|
@Nullable String reserveTxKey) {
|
||||||
|
@ -139,6 +144,7 @@ public final class OpenOffer implements Tradable {
|
||||||
this.reserveExactAmount = reserveExactAmount;
|
this.reserveExactAmount = reserveExactAmount;
|
||||||
this.scheduledTxHashes = scheduledTxHashes;
|
this.scheduledTxHashes = scheduledTxHashes;
|
||||||
this.splitOutputTxHash = splitOutputTxHash;
|
this.splitOutputTxHash = splitOutputTxHash;
|
||||||
|
this.splitOutputTxFee = splitOutputTxFee;
|
||||||
this.reserveTxHash = reserveTxHash;
|
this.reserveTxHash = reserveTxHash;
|
||||||
this.reserveTxHex = reserveTxHex;
|
this.reserveTxHex = reserveTxHex;
|
||||||
this.reserveTxKey = reserveTxKey;
|
this.reserveTxKey = reserveTxKey;
|
||||||
|
@ -153,6 +159,7 @@ public final class OpenOffer implements Tradable {
|
||||||
.setOffer(offer.toProtoMessage())
|
.setOffer(offer.toProtoMessage())
|
||||||
.setTriggerPrice(triggerPrice)
|
.setTriggerPrice(triggerPrice)
|
||||||
.setState(protobuf.OpenOffer.State.valueOf(state.name()))
|
.setState(protobuf.OpenOffer.State.valueOf(state.name()))
|
||||||
|
.setSplitOutputTxFee(splitOutputTxFee)
|
||||||
.setReserveExactAmount(reserveExactAmount);
|
.setReserveExactAmount(reserveExactAmount);
|
||||||
|
|
||||||
Optional.ofNullable(scheduledAmount).ifPresent(e -> builder.setScheduledAmount(scheduledAmount));
|
Optional.ofNullable(scheduledAmount).ifPresent(e -> builder.setScheduledAmount(scheduledAmount));
|
||||||
|
@ -173,6 +180,7 @@ public final class OpenOffer implements Tradable {
|
||||||
proto.getScheduledAmount(),
|
proto.getScheduledAmount(),
|
||||||
proto.getScheduledTxHashesList(),
|
proto.getScheduledTxHashesList(),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getSplitOutputTxHash()),
|
ProtoUtil.stringOrNullFromProto(proto.getSplitOutputTxHash()),
|
||||||
|
proto.getSplitOutputTxFee(),
|
||||||
proto.getReserveTxHash(),
|
proto.getReserveTxHash(),
|
||||||
proto.getReserveTxHex(),
|
proto.getReserveTxHex(),
|
||||||
proto.getReserveTxKey());
|
proto.getReserveTxKey());
|
||||||
|
@ -253,6 +261,9 @@ public final class OpenOffer implements Tradable {
|
||||||
",\n offer=" + offer +
|
",\n offer=" + offer +
|
||||||
",\n state=" + state +
|
",\n state=" + state +
|
||||||
",\n triggerPrice=" + triggerPrice +
|
",\n triggerPrice=" + triggerPrice +
|
||||||
|
",\n reserveExactAmount=" + reserveExactAmount +
|
||||||
|
",\n scheduledAmount=" + scheduledAmount +
|
||||||
|
",\n splitOutputTxFee=" + splitOutputTxFee +
|
||||||
"\n}";
|
"\n}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1006,8 +1006,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
log.info("Done creating split output tx to fund offer {}", openOffer.getId());
|
log.info("Done creating split output tx to fund offer {}", openOffer.getId());
|
||||||
|
|
||||||
// schedule txs
|
// schedule txs
|
||||||
openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash()));
|
|
||||||
openOffer.setSplitOutputTxHash(splitOutputTx.getHash());
|
openOffer.setSplitOutputTxHash(splitOutputTx.getHash());
|
||||||
|
openOffer.setSplitOutputTxFee(splitOutputTx.getFee().longValueExact());
|
||||||
|
openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash()));
|
||||||
openOffer.setScheduledAmount(openOffer.getOffer().getReserveAmount().toString());
|
openOffer.setScheduledAmount(openOffer.getOffer().getReserveAmount().toString());
|
||||||
openOffer.setState(OpenOffer.State.SCHEDULED);
|
openOffer.setState(OpenOffer.State.SCHEDULED);
|
||||||
return splitOutputTx;
|
return splitOutputTx;
|
||||||
|
|
|
@ -69,6 +69,7 @@ import java.math.BigInteger;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -869,12 +870,29 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
// add any loss of precision to winner payout
|
// add any loss of precision to winner payout
|
||||||
winnerPayoutAmount = winnerPayoutAmount.add(trade.getWallet().getUnlockedBalance().subtract(winnerPayoutAmount.add(loserPayoutAmount)));
|
winnerPayoutAmount = winnerPayoutAmount.add(trade.getWallet().getUnlockedBalance().subtract(winnerPayoutAmount.add(loserPayoutAmount)));
|
||||||
|
|
||||||
// create dispute payout tx
|
// create dispute payout tx config
|
||||||
MoneroTxConfig txConfig = new MoneroTxConfig().setAccountIndex(0);
|
MoneroTxConfig txConfig = new MoneroTxConfig().setAccountIndex(0);
|
||||||
|
txConfig.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY);
|
||||||
if (winnerPayoutAmount.compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(winnerPayoutAddress, winnerPayoutAmount);
|
if (winnerPayoutAmount.compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(winnerPayoutAddress, winnerPayoutAmount);
|
||||||
if (loserPayoutAmount.compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(loserPayoutAddress, loserPayoutAmount);
|
if (loserPayoutAmount.compareTo(BigInteger.ZERO) > 0) txConfig.addDestination(loserPayoutAddress, loserPayoutAmount);
|
||||||
txConfig.setSubtractFeeFrom(loserPayoutAmount.equals(BigInteger.ZERO) ? 0 : txConfig.getDestinations().size() - 1); // winner only pays fee if loser gets 0
|
|
||||||
txConfig.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY);
|
// configure who pays mining fee
|
||||||
|
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));
|
||||||
|
break;
|
||||||
|
case BUYER_ONLY:
|
||||||
|
txConfig.setSubtractFeeFrom(disputeResult.getWinner() == Winner.BUYER ? 0 : 1);
|
||||||
|
break;
|
||||||
|
case SELLER_ONLY:
|
||||||
|
txConfig.setSubtractFeeFrom(disputeResult.getWinner() == Winner.SELLER ? 0 : 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create dispute payout tx
|
||||||
MoneroTxWallet payoutTx = null;
|
MoneroTxWallet payoutTx = null;
|
||||||
try {
|
try {
|
||||||
payoutTx = trade.getWallet().createTx(txConfig);
|
payoutTx = trade.getWallet().createTx(txConfig);
|
||||||
|
|
|
@ -61,12 +61,21 @@ public final class DisputeResult implements NetworkPayload {
|
||||||
PEER_WAS_LATE
|
PEER_WAS_LATE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SubtractFeeFrom {
|
||||||
|
BUYER_ONLY,
|
||||||
|
SELLER_ONLY,
|
||||||
|
BUYER_AND_SELLER
|
||||||
|
}
|
||||||
|
|
||||||
private final String tradeId;
|
private final String tradeId;
|
||||||
private final int traderId;
|
private final int traderId;
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
private Winner winner;
|
private Winner winner;
|
||||||
private int reasonOrdinal = Reason.OTHER.ordinal();
|
private int reasonOrdinal = Reason.OTHER.ordinal();
|
||||||
|
@Setter
|
||||||
|
@Nullable
|
||||||
|
private SubtractFeeFrom subtractFeeFrom;
|
||||||
private final BooleanProperty tamperProofEvidenceProperty = new SimpleBooleanProperty();
|
private final BooleanProperty tamperProofEvidenceProperty = new SimpleBooleanProperty();
|
||||||
private final BooleanProperty idVerificationProperty = new SimpleBooleanProperty();
|
private final BooleanProperty idVerificationProperty = new SimpleBooleanProperty();
|
||||||
private final BooleanProperty screenCastProperty = new SimpleBooleanProperty();
|
private final BooleanProperty screenCastProperty = new SimpleBooleanProperty();
|
||||||
|
@ -93,6 +102,7 @@ public final class DisputeResult implements NetworkPayload {
|
||||||
int traderId,
|
int traderId,
|
||||||
@Nullable Winner winner,
|
@Nullable Winner winner,
|
||||||
int reasonOrdinal,
|
int reasonOrdinal,
|
||||||
|
@Nullable SubtractFeeFrom subtractFeeFrom,
|
||||||
boolean tamperProofEvidence,
|
boolean tamperProofEvidence,
|
||||||
boolean idVerification,
|
boolean idVerification,
|
||||||
boolean screenCast,
|
boolean screenCast,
|
||||||
|
@ -107,6 +117,7 @@ public final class DisputeResult implements NetworkPayload {
|
||||||
this.traderId = traderId;
|
this.traderId = traderId;
|
||||||
this.winner = winner;
|
this.winner = winner;
|
||||||
this.reasonOrdinal = reasonOrdinal;
|
this.reasonOrdinal = reasonOrdinal;
|
||||||
|
this.subtractFeeFrom = subtractFeeFrom;
|
||||||
this.tamperProofEvidenceProperty.set(tamperProofEvidence);
|
this.tamperProofEvidenceProperty.set(tamperProofEvidence);
|
||||||
this.idVerificationProperty.set(idVerification);
|
this.idVerificationProperty.set(idVerification);
|
||||||
this.screenCastProperty.set(screenCast);
|
this.screenCastProperty.set(screenCast);
|
||||||
|
@ -129,6 +140,7 @@ public final class DisputeResult implements NetworkPayload {
|
||||||
proto.getTraderId(),
|
proto.getTraderId(),
|
||||||
ProtoUtil.enumFromProto(DisputeResult.Winner.class, proto.getWinner().name()),
|
ProtoUtil.enumFromProto(DisputeResult.Winner.class, proto.getWinner().name()),
|
||||||
proto.getReasonOrdinal(),
|
proto.getReasonOrdinal(),
|
||||||
|
ProtoUtil.enumFromProto(DisputeResult.SubtractFeeFrom.class, proto.getSubtractFeeFrom().name()),
|
||||||
proto.getTamperProofEvidence(),
|
proto.getTamperProofEvidence(),
|
||||||
proto.getIdVerification(),
|
proto.getIdVerification(),
|
||||||
proto.getScreenCast(),
|
proto.getScreenCast(),
|
||||||
|
@ -158,6 +170,7 @@ public final class DisputeResult implements NetworkPayload {
|
||||||
Optional.ofNullable(arbitratorSignature).ifPresent(arbitratorSignature -> builder.setArbitratorSignature(ByteString.copyFrom(arbitratorSignature)));
|
Optional.ofNullable(arbitratorSignature).ifPresent(arbitratorSignature -> builder.setArbitratorSignature(ByteString.copyFrom(arbitratorSignature)));
|
||||||
Optional.ofNullable(arbitratorPubKey).ifPresent(arbitratorPubKey -> builder.setArbitratorPubKey(ByteString.copyFrom(arbitratorPubKey)));
|
Optional.ofNullable(arbitratorPubKey).ifPresent(arbitratorPubKey -> builder.setArbitratorPubKey(ByteString.copyFrom(arbitratorPubKey)));
|
||||||
Optional.ofNullable(winner).ifPresent(result -> builder.setWinner(protobuf.DisputeResult.Winner.valueOf(winner.name())));
|
Optional.ofNullable(winner).ifPresent(result -> builder.setWinner(protobuf.DisputeResult.Winner.valueOf(winner.name())));
|
||||||
|
Optional.ofNullable(subtractFeeFrom).ifPresent(result -> builder.setSubtractFeeFrom(protobuf.DisputeResult.SubtractFeeFrom.valueOf(subtractFeeFrom.name())));
|
||||||
Optional.ofNullable(chatMessage).ifPresent(chatMessage ->
|
Optional.ofNullable(chatMessage).ifPresent(chatMessage ->
|
||||||
builder.setChatMessage(chatMessage.toProtoNetworkEnvelope().getChatMessage()));
|
builder.setChatMessage(chatMessage.toProtoNetworkEnvelope().getChatMessage()));
|
||||||
|
|
||||||
|
@ -201,6 +214,7 @@ public final class DisputeResult implements NetworkPayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBuyerPayoutAmount(BigInteger buyerPayoutAmount) {
|
public void setBuyerPayoutAmount(BigInteger buyerPayoutAmount) {
|
||||||
|
if (buyerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("buyerPayoutAmount cannot be negative");
|
||||||
this.buyerPayoutAmount = buyerPayoutAmount.longValueExact();
|
this.buyerPayoutAmount = buyerPayoutAmount.longValueExact();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +223,7 @@ public final class DisputeResult implements NetworkPayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSellerPayoutAmount(BigInteger sellerPayoutAmount) {
|
public void setSellerPayoutAmount(BigInteger sellerPayoutAmount) {
|
||||||
|
if (sellerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("sellerPayoutAmount cannot be negative");
|
||||||
this.sellerPayoutAmount = sellerPayoutAmount.longValueExact();
|
this.sellerPayoutAmount = sellerPayoutAmount.longValueExact();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,6 +246,7 @@ public final class DisputeResult implements NetworkPayload {
|
||||||
",\n traderId=" + traderId +
|
",\n traderId=" + traderId +
|
||||||
",\n winner=" + winner +
|
",\n winner=" + winner +
|
||||||
",\n reasonOrdinal=" + reasonOrdinal +
|
",\n reasonOrdinal=" + reasonOrdinal +
|
||||||
|
",\n subtractFeeFrom=" + subtractFeeFrom +
|
||||||
",\n tamperProofEvidenceProperty=" + tamperProofEvidenceProperty +
|
",\n tamperProofEvidenceProperty=" + tamperProofEvidenceProperty +
|
||||||
",\n idVerificationProperty=" + idVerificationProperty +
|
",\n idVerificationProperty=" + idVerificationProperty +
|
||||||
",\n screenCastProperty=" + screenCastProperty +
|
",\n screenCastProperty=" + screenCastProperty +
|
||||||
|
|
|
@ -390,9 +390,25 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
BigInteger expectedWinnerAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount();
|
BigInteger expectedWinnerAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getBuyerPayoutAmount() : disputeResult.getSellerPayoutAmount();
|
||||||
BigInteger expectedLoserAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount();
|
BigInteger expectedLoserAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount();
|
||||||
|
|
||||||
// winner pays cost if loser gets nothing, otherwise loser pays cost
|
// subtract mining fee from expected payouts
|
||||||
if (expectedLoserAmount.equals(BigInteger.ZERO)) expectedWinnerAmount = expectedWinnerAmount.subtract(txCost);
|
if (expectedLoserAmount.equals(BigInteger.ZERO)) expectedWinnerAmount = expectedWinnerAmount.subtract(txCost); // winner pays fee if loser gets 0
|
||||||
else expectedLoserAmount = expectedLoserAmount.subtract(txCost);
|
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
|
// verify winner and loser payout amounts
|
||||||
if (!expectedWinnerAmount.equals(actualWinnerAmount)) throw new RuntimeException("Unexpected winner payout: " + expectedWinnerAmount + " vs " + actualWinnerAmount);
|
if (!expectedWinnerAmount.equals(actualWinnerAmount)) throw new RuntimeException("Unexpected winner payout: " + expectedWinnerAmount + " vs " + actualWinnerAmount);
|
||||||
|
|
|
@ -69,11 +69,13 @@ import monero.common.MoneroError;
|
||||||
import monero.common.MoneroRpcConnection;
|
import monero.common.MoneroRpcConnection;
|
||||||
import monero.common.TaskLooper;
|
import monero.common.TaskLooper;
|
||||||
import monero.daemon.MoneroDaemon;
|
import monero.daemon.MoneroDaemon;
|
||||||
|
import monero.daemon.model.MoneroKeyImage;
|
||||||
import monero.daemon.model.MoneroTx;
|
import monero.daemon.model.MoneroTx;
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
import monero.wallet.MoneroWalletRpc;
|
import monero.wallet.MoneroWalletRpc;
|
||||||
import monero.wallet.model.MoneroDestination;
|
import monero.wallet.model.MoneroDestination;
|
||||||
import monero.wallet.model.MoneroMultisigSignResult;
|
import monero.wallet.model.MoneroMultisigSignResult;
|
||||||
|
import monero.wallet.model.MoneroOutputQuery;
|
||||||
import monero.wallet.model.MoneroOutputWallet;
|
import monero.wallet.model.MoneroOutputWallet;
|
||||||
import monero.wallet.model.MoneroTxConfig;
|
import monero.wallet.model.MoneroTxConfig;
|
||||||
import monero.wallet.model.MoneroTxQuery;
|
import monero.wallet.model.MoneroTxQuery;
|
||||||
|
@ -1595,6 +1597,24 @@ public abstract class Trade implements Tradable, Model {
|
||||||
return offer.getShortId();
|
return offer.getShortId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigInteger getFrozenAmount() {
|
||||||
|
BigInteger sum = BigInteger.valueOf(0);
|
||||||
|
for (String keyImage : getSelf().getReserveTxKeyImages()) {
|
||||||
|
List<MoneroOutputWallet> outputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false).setKeyImage(new MoneroKeyImage(keyImage))); // TODO: will this check tx pool? avoid
|
||||||
|
if (!outputs.isEmpty()) sum = sum.add(outputs.get(0).getAmount());
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getReservedAmount() {
|
||||||
|
if (!isDepositsPublished() || isPayoutPublished()) return BigInteger.valueOf(0);
|
||||||
|
if (isArbitrator()) {
|
||||||
|
return getAmount().add(getBuyer().getSecurityDeposit()).add(getSeller().getSecurityDeposit()); // arbitrator reserved balance is sum of amounts sent to multisig
|
||||||
|
} else {
|
||||||
|
return isBuyer() ? getBuyer().getSecurityDeposit() : getAmount().add(getSeller().getSecurityDeposit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Price getPrice() {
|
public Price getPrice() {
|
||||||
return Price.valueOf(offer.getCurrencyCode(), price);
|
return Price.valueOf(offer.getCurrencyCode(), price);
|
||||||
}
|
}
|
||||||
|
|
|
@ -815,6 +815,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
|
|
||||||
checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId()));
|
checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId()));
|
||||||
|
|
||||||
|
// validate inputs
|
||||||
|
if (amount.compareTo(offer.getAmount()) > 0) throw new RuntimeException("Trade amount exceeds offer amount");
|
||||||
|
if (amount.compareTo(offer.getMinAmount()) < 0) throw new RuntimeException("Trade amount is less than minimum offer amount");
|
||||||
|
|
||||||
OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser, paymentAccountId, amount);
|
OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser, paymentAccountId, amount);
|
||||||
offer.checkOfferAvailability(model,
|
offer.checkOfferAvailability(model,
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -998,7 +1002,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// If trade is in already in critical state (if taker role: taker fee; both roles: after deposit published)
|
// If trade is in already in critical state (if taker role: taker fee; both roles: after deposit published)
|
||||||
// we move the trade to failedTradesManager
|
// we move the trade to FailedTradesManager
|
||||||
public void onMoveInvalidTradeToFailedTrades(Trade trade) {
|
public void onMoveInvalidTradeToFailedTrades(Trade trade) {
|
||||||
removeTrade(trade);
|
removeTrade(trade);
|
||||||
failedTradesManager.add(trade);
|
failedTradesManager.add(trade);
|
||||||
|
|
|
@ -30,15 +30,21 @@ import java.util.Optional;
|
||||||
public final class DepositResponse extends TradeMessage implements DirectMessage {
|
public final class DepositResponse extends TradeMessage implements DirectMessage {
|
||||||
private final long currentDate;
|
private final long currentDate;
|
||||||
private final String errorMessage;
|
private final String errorMessage;
|
||||||
|
private final long buyerSecurityDeposit;
|
||||||
|
private final long sellerSecurityDeposit;
|
||||||
|
|
||||||
public DepositResponse(String tradeId,
|
public DepositResponse(String tradeId,
|
||||||
String uid,
|
String uid,
|
||||||
String messageVersion,
|
String messageVersion,
|
||||||
long currentDate,
|
long currentDate,
|
||||||
String errorMessage) {
|
String errorMessage,
|
||||||
|
long buyerSecurityDeposit,
|
||||||
|
long sellerSecurityDeposit) {
|
||||||
super(messageVersion, tradeId, uid);
|
super(messageVersion, tradeId, uid);
|
||||||
this.currentDate = currentDate;
|
this.currentDate = currentDate;
|
||||||
this.errorMessage = errorMessage;
|
this.errorMessage = errorMessage;
|
||||||
|
this.buyerSecurityDeposit = buyerSecurityDeposit;
|
||||||
|
this.sellerSecurityDeposit = sellerSecurityDeposit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +58,8 @@ public final class DepositResponse extends TradeMessage implements DirectMessage
|
||||||
.setTradeId(tradeId)
|
.setTradeId(tradeId)
|
||||||
.setUid(uid);
|
.setUid(uid);
|
||||||
builder.setCurrentDate(currentDate);
|
builder.setCurrentDate(currentDate);
|
||||||
|
builder.setBuyerSecurityDeposit(buyerSecurityDeposit);
|
||||||
|
builder.setSellerSecurityDeposit(sellerSecurityDeposit);
|
||||||
Optional.ofNullable(errorMessage).ifPresent(e -> builder.setErrorMessage(errorMessage));
|
Optional.ofNullable(errorMessage).ifPresent(e -> builder.setErrorMessage(errorMessage));
|
||||||
|
|
||||||
return getNetworkEnvelopeBuilder().setDepositResponse(builder).build();
|
return getNetworkEnvelopeBuilder().setDepositResponse(builder).build();
|
||||||
|
@ -64,7 +72,9 @@ public final class DepositResponse extends TradeMessage implements DirectMessage
|
||||||
proto.getUid(),
|
proto.getUid(),
|
||||||
messageVersion,
|
messageVersion,
|
||||||
proto.getCurrentDate(),
|
proto.getCurrentDate(),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()));
|
ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()),
|
||||||
|
proto.getBuyerSecurityDeposit(),
|
||||||
|
proto.getSellerSecurityDeposit());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,6 +82,8 @@ public final class DepositResponse extends TradeMessage implements DirectMessage
|
||||||
return "DepositResponse {" +
|
return "DepositResponse {" +
|
||||||
",\n currentDate=" + currentDate +
|
",\n currentDate=" + currentDate +
|
||||||
",\n errorMessage=" + errorMessage +
|
",\n errorMessage=" + errorMessage +
|
||||||
|
",\n buyerSecurityDeposit=" + buyerSecurityDeposit +
|
||||||
|
",\n sellerSecurityDeposit=" + sellerSecurityDeposit +
|
||||||
"\n} " + super.toString();
|
"\n} " + super.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,7 @@ public final class TradePeer implements PersistablePayload {
|
||||||
private String depositTxHex;
|
private String depositTxHex;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String depositTxKey;
|
private String depositTxKey;
|
||||||
|
private long depositTxFee;
|
||||||
private long securityDeposit;
|
private long securityDeposit;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String updatedMultisigHex;
|
private String updatedMultisigHex;
|
||||||
|
@ -126,7 +127,16 @@ public final class TradePeer implements PersistablePayload {
|
||||||
public TradePeer() {
|
public TradePeer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigInteger getDepositTxFee() {
|
||||||
|
return BigInteger.valueOf(depositTxFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDepositTxFee(BigInteger depositTxFee) {
|
||||||
|
this.depositTxFee = depositTxFee.longValueExact();
|
||||||
|
}
|
||||||
|
|
||||||
public BigInteger getSecurityDeposit() {
|
public BigInteger getSecurityDeposit() {
|
||||||
|
if (depositTxHash == null) return null;
|
||||||
return BigInteger.valueOf(securityDeposit);
|
return BigInteger.valueOf(securityDeposit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,6 +174,7 @@ public final class TradePeer implements PersistablePayload {
|
||||||
Optional.ofNullable(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash));
|
Optional.ofNullable(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash));
|
||||||
Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex));
|
Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex));
|
||||||
Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey));
|
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(securityDeposit).ifPresent(e -> builder.setSecurityDeposit(securityDeposit));
|
||||||
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
||||||
builder.setDepositsConfirmedMessageAcked(depositsConfirmedMessageAcked);
|
builder.setDepositsConfirmedMessageAcked(depositsConfirmedMessageAcked);
|
||||||
|
@ -206,6 +217,7 @@ public final class TradePeer implements PersistablePayload {
|
||||||
tradePeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash()));
|
tradePeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash()));
|
||||||
tradePeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex()));
|
tradePeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex()));
|
||||||
tradePeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
|
tradePeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
|
||||||
|
tradePeer.setDepositTxFee(BigInteger.valueOf(proto.getDepositTxFee()));
|
||||||
tradePeer.setSecurityDeposit(BigInteger.valueOf(proto.getSecurityDeposit()));
|
tradePeer.setSecurityDeposit(BigInteger.valueOf(proto.getSecurityDeposit()));
|
||||||
tradePeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
tradePeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
||||||
tradePeer.setDepositsConfirmedMessageAcked(proto.getDepositsConfirmedMessageAcked());
|
tradePeer.setDepositsConfirmedMessageAcked(proto.getDepositsConfirmedMessageAcked());
|
||||||
|
|
|
@ -80,7 +80,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||||
boolean isFromTaker = trader == trade.getTaker();
|
boolean isFromTaker = trader == trade.getTaker();
|
||||||
boolean isFromBuyer = trader == trade.getBuyer();
|
boolean isFromBuyer = trader == trade.getBuyer();
|
||||||
BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee();
|
BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee();
|
||||||
BigInteger sendAmount = isFromBuyer ? BigInteger.valueOf(0) : offer.getAmount();
|
BigInteger sendAmount = isFromBuyer ? BigInteger.valueOf(0) : trade.getAmount();
|
||||||
BigInteger securityDeposit = isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit();
|
BigInteger securityDeposit = isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit();
|
||||||
String depositAddress = processModel.getMultisigAddress();
|
String depositAddress = processModel.getMultisigAddress();
|
||||||
|
|
||||||
|
@ -103,6 +103,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||||
|
|
||||||
// set deposit info
|
// set deposit info
|
||||||
trader.setSecurityDeposit(txResult.second);
|
trader.setSecurityDeposit(txResult.second);
|
||||||
|
trader.setDepositTxFee(txResult.first.getFee());
|
||||||
trader.setDepositTxHex(request.getDepositTxHex());
|
trader.setDepositTxHex(request.getDepositTxHex());
|
||||||
trader.setDepositTxKey(request.getDepositTxKey());
|
trader.setDepositTxKey(request.getDepositTxKey());
|
||||||
if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey());
|
if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey());
|
||||||
|
@ -130,7 +131,9 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
Version.getP2PMessageVersion(),
|
Version.getP2PMessageVersion(),
|
||||||
new Date().getTime(),
|
new Date().getTime(),
|
||||||
null);
|
null,
|
||||||
|
trade.getBuyer().getSecurityDeposit().longValue(),
|
||||||
|
trade.getSeller().getSecurityDeposit().longValue());
|
||||||
|
|
||||||
// send deposit response to maker and taker
|
// send deposit response to maker and taker
|
||||||
sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response);
|
sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response);
|
||||||
|
@ -158,7 +161,9 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
Version.getP2PMessageVersion(),
|
Version.getP2PMessageVersion(),
|
||||||
new Date().getTime(),
|
new Date().getTime(),
|
||||||
t.getMessage());
|
t.getMessage(),
|
||||||
|
trade.getBuyer().getSecurityDeposit().longValue(),
|
||||||
|
trade.getSeller().getSecurityDeposit().longValue());
|
||||||
|
|
||||||
// send deposit response to maker and taker
|
// send deposit response to maker and taker
|
||||||
sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response);
|
sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response);
|
||||||
|
|
|
@ -20,6 +20,7 @@ package haveno.core.trade.protocol.tasks;
|
||||||
import haveno.common.app.Version;
|
import haveno.common.app.Version;
|
||||||
import haveno.common.taskrunner.TaskRunner;
|
import haveno.common.taskrunner.TaskRunner;
|
||||||
import haveno.core.trade.ArbitratorTrade;
|
import haveno.core.trade.ArbitratorTrade;
|
||||||
|
import haveno.core.trade.BuyerTrade;
|
||||||
import haveno.core.trade.HavenoUtils;
|
import haveno.core.trade.HavenoUtils;
|
||||||
import haveno.core.trade.MakerTrade;
|
import haveno.core.trade.MakerTrade;
|
||||||
import haveno.core.trade.Trade;
|
import haveno.core.trade.Trade;
|
||||||
|
@ -31,6 +32,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.daemon.model.MoneroOutput;
|
import monero.daemon.model.MoneroOutput;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -91,10 +93,15 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
||||||
processModel.setDepositTxXmr(depositTx); // TODO: redundant with trade.getSelf().setDepositTx(), remove?
|
processModel.setDepositTxXmr(depositTx); // TODO: redundant with trade.getSelf().setDepositTx(), remove?
|
||||||
trade.getSelf().setDepositTx(depositTx);
|
trade.getSelf().setDepositTx(depositTx);
|
||||||
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
||||||
|
trade.getSelf().setDepositTxFee(depositTx.getFee());
|
||||||
trade.getSelf().setReserveTxKeyImages(reservedKeyImages);
|
trade.getSelf().setReserveTxKeyImages(reservedKeyImages);
|
||||||
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address?
|
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address?
|
||||||
trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId()));
|
trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId()));
|
||||||
|
|
||||||
|
// TODO: security deposit should be based on trade amount, not max offer amount
|
||||||
|
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getOffer().getBuyerSecurityDeposit() : trade.getOffer().getSellerSecurityDeposit();
|
||||||
|
trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee()));
|
||||||
|
|
||||||
// maker signs deposit hash nonce to avoid challenge protocol
|
// maker signs deposit hash nonce to avoid challenge protocol
|
||||||
byte[] sig = null;
|
byte[] sig = null;
|
||||||
if (trade instanceof MakerTrade) {
|
if (trade instanceof MakerTrade) {
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
package haveno.core.trade.protocol.tasks;
|
package haveno.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
import haveno.common.taskrunner.TaskRunner;
|
import haveno.common.taskrunner.TaskRunner;
|
||||||
import haveno.core.trade.Trade;
|
import haveno.core.trade.Trade;
|
||||||
import haveno.core.trade.messages.DepositResponse;
|
import haveno.core.trade.messages.DepositResponse;
|
||||||
|
@ -43,10 +45,17 @@ public class ProcessDepositResponse extends TradeTask {
|
||||||
throw new RuntimeException(message.getErrorMessage());
|
throw new RuntimeException(message.getErrorMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// record security deposits
|
||||||
|
trade.getBuyer().setSecurityDeposit(BigInteger.valueOf(message.getBuyerSecurityDeposit()));
|
||||||
|
trade.getSeller().setSecurityDeposit(BigInteger.valueOf(message.getSellerSecurityDeposit()));
|
||||||
|
|
||||||
// set success state
|
// set success state
|
||||||
trade.setStateIfValidTransitionTo(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
|
trade.setStateIfValidTransitionTo(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
|
||||||
trade.addInitProgressStep();
|
trade.addInitProgressStep();
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
|
||||||
|
// update balances
|
||||||
|
trade.getXmrWalletService().updateBalanceListeners();
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
failed(t);
|
failed(t);
|
||||||
|
|
|
@ -53,6 +53,10 @@ public class ProcessInitTradeRequest extends TradeTask {
|
||||||
checkNotNull(request);
|
checkNotNull(request);
|
||||||
checkTradeId(processModel.getOfferId(), request);
|
checkTradeId(processModel.getOfferId(), request);
|
||||||
|
|
||||||
|
// validate inputs
|
||||||
|
if (trade.getAmount().compareTo(trade.getOffer().getAmount()) > 0) throw new RuntimeException("Trade amount exceeds offer amount");
|
||||||
|
if (trade.getAmount().compareTo(trade.getOffer().getMinAmount()) < 0) throw new RuntimeException("Trade amount is less than minimum offer amount");
|
||||||
|
|
||||||
// handle request as arbitrator
|
// handle request as arbitrator
|
||||||
TradePeer multisigParticipant;
|
TradePeer multisigParticipant;
|
||||||
if (trade instanceof ArbitratorTrade) {
|
if (trade instanceof ArbitratorTrade) {
|
||||||
|
|
|
@ -17,28 +17,24 @@
|
||||||
|
|
||||||
package haveno.core.xmr;
|
package haveno.core.xmr;
|
||||||
|
|
||||||
import haveno.core.offer.OfferPayload;
|
|
||||||
import haveno.core.offer.OpenOffer;
|
import haveno.core.offer.OpenOffer;
|
||||||
import haveno.core.offer.OpenOfferManager;
|
import haveno.core.offer.OpenOfferManager;
|
||||||
import haveno.core.support.dispute.Dispute;
|
import haveno.core.support.dispute.Dispute;
|
||||||
import haveno.core.support.dispute.refund.RefundManager;
|
import haveno.core.support.dispute.refund.RefundManager;
|
||||||
import haveno.core.trade.ClosedTradableManager;
|
import haveno.core.trade.ClosedTradableManager;
|
||||||
|
import haveno.core.trade.MakerTrade;
|
||||||
import haveno.core.trade.Trade;
|
import haveno.core.trade.Trade;
|
||||||
import haveno.core.trade.TradeManager;
|
import haveno.core.trade.TradeManager;
|
||||||
import haveno.core.trade.failed.FailedTradesManager;
|
import haveno.core.trade.failed.FailedTradesManager;
|
||||||
import haveno.core.xmr.listeners.XmrBalanceListener;
|
import haveno.core.xmr.listeners.XmrBalanceListener;
|
||||||
import haveno.core.xmr.wallet.XmrWalletService;
|
import haveno.core.xmr.wallet.XmrWalletService;
|
||||||
import haveno.network.p2p.P2PService;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.common.MoneroError;
|
|
||||||
import monero.wallet.model.MoneroOutputQuery;
|
import monero.wallet.model.MoneroOutputQuery;
|
||||||
import monero.wallet.model.MoneroOutputWallet;
|
import monero.wallet.model.MoneroOutputWallet;
|
||||||
import monero.wallet.model.MoneroTxQuery;
|
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
@ -77,19 +73,19 @@ public class Balances {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
openOfferManager.getObservableList().addListener((ListChangeListener<OpenOffer>) c -> updatedBalances());
|
openOfferManager.getObservableList().addListener((ListChangeListener<OpenOffer>) c -> updateBalances());
|
||||||
tradeManager.getObservableList().addListener((ListChangeListener<Trade>) change -> updatedBalances());
|
tradeManager.getObservableList().addListener((ListChangeListener<Trade>) change -> updateBalances());
|
||||||
refundManager.getDisputesAsObservableList().addListener((ListChangeListener<Dispute>) c -> updatedBalances());
|
refundManager.getDisputesAsObservableList().addListener((ListChangeListener<Dispute>) c -> updateBalances());
|
||||||
xmrWalletService.addBalanceListener(new XmrBalanceListener() {
|
xmrWalletService.addBalanceListener(new XmrBalanceListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onBalanceChanged(BigInteger balance) {
|
public void onBalanceChanged(BigInteger balance) {
|
||||||
updatedBalances();
|
updateBalances();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
updatedBalances();
|
updateBalances();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatedBalances() {
|
private void updateBalances() {
|
||||||
if (!xmrWalletService.isWalletReady()) return;
|
if (!xmrWalletService.isWalletReady()) return;
|
||||||
try {
|
try {
|
||||||
updateAvailableBalance();
|
updateAvailableBalance();
|
||||||
|
@ -111,7 +107,18 @@ public class Balances {
|
||||||
private void updatePendingBalance() {
|
private void updatePendingBalance() {
|
||||||
BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.valueOf(0) : xmrWalletService.getWallet().getBalance(0);
|
BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.valueOf(0) : xmrWalletService.getWallet().getBalance(0);
|
||||||
BigInteger unlockedBalance = xmrWalletService.getWallet() == null ? BigInteger.valueOf(0) : xmrWalletService.getWallet().getUnlockedBalance(0);
|
BigInteger unlockedBalance = xmrWalletService.getWallet() == null ? BigInteger.valueOf(0) : xmrWalletService.getWallet().getUnlockedBalance(0);
|
||||||
pendingBalance.set(balance.subtract(unlockedBalance));
|
BigInteger pendingBalanceSum = balance.subtract(unlockedBalance);
|
||||||
|
|
||||||
|
// add frozen trade balances - reserved amounts
|
||||||
|
List<Trade> trades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList());
|
||||||
|
for (Trade trade : trades) {
|
||||||
|
if (trade.getFrozenAmount().equals(new BigInteger("0"))) continue;
|
||||||
|
BigInteger tradeFee = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee();
|
||||||
|
pendingBalanceSum = pendingBalanceSum.add(trade.getFrozenAmount()).subtract(trade.getReservedAmount()).subtract(tradeFee).subtract(trade.getSelf().getDepositTxFee());
|
||||||
|
}
|
||||||
|
|
||||||
|
// add frozen offer balances
|
||||||
|
pendingBalance.set(pendingBalanceSum);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateReservedOfferBalance() {
|
private void updateReservedOfferBalance() {
|
||||||
|
@ -120,30 +127,21 @@ public class Balances {
|
||||||
List<MoneroOutputWallet> frozenOutputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false));
|
List<MoneroOutputWallet> frozenOutputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false));
|
||||||
for (MoneroOutputWallet frozenOutput : frozenOutputs) sum = sum.add(frozenOutput.getAmount());
|
for (MoneroOutputWallet frozenOutput : frozenOutputs) sum = sum.add(frozenOutput.getAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// subtract frozen trade balances
|
||||||
|
List<Trade> trades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList());
|
||||||
|
for (Trade trade : trades) {
|
||||||
|
sum = sum.subtract(trade.getFrozenAmount());
|
||||||
|
}
|
||||||
|
|
||||||
reservedOfferBalance.set(sum);
|
reservedOfferBalance.set(sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateReservedTradeBalance() {
|
private void updateReservedTradeBalance() {
|
||||||
BigInteger sum = BigInteger.valueOf(0);
|
BigInteger sum = BigInteger.valueOf(0);
|
||||||
List<Trade> openTrades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList());
|
List<Trade> trades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList());
|
||||||
for (Trade trade : openTrades) {
|
for (Trade trade : trades) {
|
||||||
try {
|
sum = sum.add(trade.getReservedAmount());
|
||||||
List<MoneroTxWallet> depositTxs = xmrWalletService.getWallet().getTxs(new MoneroTxQuery()
|
|
||||||
.setHash(trade.getSelf().getDepositTxHash())
|
|
||||||
.setInTxPool(false)); // don't check pool
|
|
||||||
if (depositTxs.size() != 1 || !depositTxs.get(0).isConfirmed()) continue; // outputs are frozen until confirmed by arbitrator's broadcast
|
|
||||||
} catch (MoneroError e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (trade.getContract() == null) continue;
|
|
||||||
Long reservedAmt;
|
|
||||||
OfferPayload offerPayload = trade.getContract().getOfferPayload();
|
|
||||||
if (trade.getArbitratorNodeAddress().equals(P2PService.getMyNodeAddress())) { // TODO (woodser): this only works if node address does not change
|
|
||||||
reservedAmt = offerPayload.getAmount() + offerPayload.getBuyerSecurityDeposit() + offerPayload.getSellerSecurityDeposit(); // arbitrator reserved balance is sum of amounts sent to multisig
|
|
||||||
} else {
|
|
||||||
reservedAmt = trade.getContract().isMyRoleBuyer(tradeManager.getKeyRing().getPubKeyRing()) ? offerPayload.getBuyerSecurityDeposit() : offerPayload.getAmount() + offerPayload.getSellerSecurityDeposit();
|
|
||||||
}
|
|
||||||
sum = sum.add(BigInteger.valueOf(reservedAmt));
|
|
||||||
}
|
}
|
||||||
reservedTradeBalance.set(sum);
|
reservedTradeBalance.set(sum);
|
||||||
}
|
}
|
||||||
|
|
|
@ -368,7 +368,7 @@ public class XmrWalletService {
|
||||||
return reserveTx;
|
return reserveTx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**s
|
/**
|
||||||
* Create the multisig deposit tx and freeze its inputs.
|
* Create the multisig deposit tx and freeze its inputs.
|
||||||
*
|
*
|
||||||
* @param trade the trade to create a deposit tx from
|
* @param trade the trade to create a deposit tx from
|
||||||
|
@ -388,8 +388,8 @@ public class XmrWalletService {
|
||||||
Offer offer = trade.getProcessModel().getOffer();
|
Offer offer = trade.getProcessModel().getOffer();
|
||||||
String multisigAddress = trade.getProcessModel().getMultisigAddress();
|
String multisigAddress = trade.getProcessModel().getMultisigAddress();
|
||||||
BigInteger tradeFee = trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee();
|
BigInteger tradeFee = trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee();
|
||||||
BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.valueOf(0) : offer.getAmount();
|
BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.valueOf(0) : trade.getAmount();
|
||||||
BigInteger securityDeposit = trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit();
|
BigInteger securityDeposit = trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit(); // TODO: security deposit should be based on trade amount
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
log.info("Creating deposit tx with multisig address={}", multisigAddress);
|
log.info("Creating deposit tx with multisig address={}", multisigAddress);
|
||||||
MoneroTxWallet depositTx = createTradeTx(tradeFee, sendAmount, securityDeposit, multisigAddress, reserveExactAmount, preferredSubaddressIndex);
|
MoneroTxWallet depositTx = createTradeTx(tradeFee, sendAmount, securityDeposit, multisigAddress, reserveExactAmount, preferredSubaddressIndex);
|
||||||
|
@ -467,6 +467,7 @@ public class XmrWalletService {
|
||||||
* Checks double spends, trade fee, deposit amount and destination, and miner fee.
|
* Checks double spends, trade fee, deposit amount and destination, and miner fee.
|
||||||
* The transaction is submitted to the pool then flushed without relaying.
|
* The transaction is submitted to the pool then flushed without relaying.
|
||||||
*
|
*
|
||||||
|
* @param offerId id of offer to verify trade tx
|
||||||
* @param tradeFee trade fee
|
* @param tradeFee trade fee
|
||||||
* @param sendAmount amount to give peer
|
* @param sendAmount amount to give peer
|
||||||
* @param securityDeposit security deposit amount
|
* @param securityDeposit security deposit amount
|
||||||
|
@ -876,25 +877,6 @@ public class XmrWalletService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyBalanceListeners() {
|
|
||||||
for (XmrBalanceListener balanceListener : balanceListeners) {
|
|
||||||
BigInteger balance;
|
|
||||||
if (balanceListener.getSubaddressIndex() != null && balanceListener.getSubaddressIndex() != 0) balance = getBalanceForSubaddress(balanceListener.getSubaddressIndex());
|
|
||||||
else balance = getAvailableBalance();
|
|
||||||
UserThread.execute(new Runnable() { // TODO (woodser): don't execute on UserThread
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
balanceListener.onBalanceChanged(balance);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Failed to notify balance listener of change");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void changeWalletPasswords(String oldPassword, String newPassword) {
|
private void changeWalletPasswords(String oldPassword, String newPassword) {
|
||||||
|
|
||||||
// create task to change main wallet password
|
// create task to change main wallet password
|
||||||
|
@ -1195,6 +1177,25 @@ public class XmrWalletService {
|
||||||
balanceListeners.remove(listener);
|
balanceListeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateBalanceListeners() {
|
||||||
|
for (XmrBalanceListener balanceListener : balanceListeners) {
|
||||||
|
BigInteger balance;
|
||||||
|
if (balanceListener.getSubaddressIndex() != null && balanceListener.getSubaddressIndex() != 0) balance = getBalanceForSubaddress(balanceListener.getSubaddressIndex());
|
||||||
|
else balance = getAvailableBalance();
|
||||||
|
UserThread.execute(new Runnable() { // TODO (woodser): don't execute on UserThread
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
balanceListener.onBalanceChanged(balance);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to notify balance listener of change");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void saveAddressEntryList() {
|
public void saveAddressEntryList() {
|
||||||
xmrAddressEntryList.requestPersistence();
|
xmrAddressEntryList.requestPersistence();
|
||||||
}
|
}
|
||||||
|
@ -1259,7 +1260,7 @@ public class XmrWalletService {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
for (MoneroWalletListenerI listener : walletListeners) listener.onBalancesChanged(newBalance, newUnlockedBalance);
|
for (MoneroWalletListenerI listener : walletListeners) listener.onBalancesChanged(newBalance, newUnlockedBalance);
|
||||||
notifyBalanceListeners();
|
updateBalanceListeners();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,6 +192,7 @@ public class AccountAgeWitnessServiceTest {
|
||||||
1,
|
1,
|
||||||
DisputeResult.Winner.BUYER,
|
DisputeResult.Winner.BUYER,
|
||||||
DisputeResult.Reason.OTHER.ordinal(),
|
DisputeResult.Reason.OTHER.ordinal(),
|
||||||
|
DisputeResult.SubtractFeeFrom.BUYER_ONLY,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -216,6 +216,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
||||||
disputeResult.setWinner(peersDisputeResult.getWinner());
|
disputeResult.setWinner(peersDisputeResult.getWinner());
|
||||||
disputeResult.setReason(peersDisputeResult.getReason());
|
disputeResult.setReason(peersDisputeResult.getReason());
|
||||||
disputeResult.setSummaryNotes(peersDisputeResult.summaryNotesProperty().get());
|
disputeResult.setSummaryNotes(peersDisputeResult.summaryNotesProperty().get());
|
||||||
|
disputeResult.setSubtractFeeFrom(peersDisputeResult.getSubtractFeeFrom());
|
||||||
|
|
||||||
buyerGetsTradeAmountRadioButton.setDisable(true);
|
buyerGetsTradeAmountRadioButton.setDisable(true);
|
||||||
buyerGetsAllRadioButton.setDisable(true);
|
buyerGetsAllRadioButton.setDisable(true);
|
||||||
|
@ -403,7 +404,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
||||||
|
|
||||||
disputeResult.setBuyerPayoutAmount(buyerAmount);
|
disputeResult.setBuyerPayoutAmount(buyerAmount);
|
||||||
disputeResult.setSellerPayoutAmount(sellerAmount);
|
disputeResult.setSellerPayoutAmount(sellerAmount);
|
||||||
disputeResult.setWinner(buyerAmount.compareTo(sellerAmount) > 0 ?
|
disputeResult.setWinner(buyerAmount.compareTo(sellerAmount) > 0 ? // 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.Winner.BUYER :
|
DisputeResult.Winner.BUYER :
|
||||||
DisputeResult.Winner.SELLER);
|
DisputeResult.Winner.SELLER);
|
||||||
}
|
}
|
||||||
|
|
|
@ -541,6 +541,8 @@ message OfferInfo {
|
||||||
string version_nr = 25;
|
string version_nr = 25;
|
||||||
int32 protocol_version = 26;
|
int32 protocol_version = 26;
|
||||||
string arbitrator_signer = 27;
|
string arbitrator_signer = 27;
|
||||||
|
string split_output_tx_hash = 28;
|
||||||
|
uint64 split_output_tx_fee = 29 [jstype = JS_STRING];
|
||||||
}
|
}
|
||||||
|
|
||||||
message AvailabilityResultWithDescription {
|
message AvailabilityResultWithDescription {
|
||||||
|
|
|
@ -281,6 +281,8 @@ message DepositResponse {
|
||||||
string uid = 2;
|
string uid = 2;
|
||||||
int64 current_date = 3;
|
int64 current_date = 3;
|
||||||
string error_message = 4;
|
string error_message = 4;
|
||||||
|
int64 buyerSecurityDeposit = 5;
|
||||||
|
int64 sellerSecurityDeposit = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DepositsConfirmedMessage {
|
message DepositsConfirmedMessage {
|
||||||
|
@ -740,6 +742,12 @@ message DisputeResult {
|
||||||
PEER_WAS_LATE = 12;
|
PEER_WAS_LATE = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SubtractFeeFrom {
|
||||||
|
BUYER_ONLY = 0;
|
||||||
|
SELLER_ONLY = 1;
|
||||||
|
BUYER_AND_SELLER = 2;
|
||||||
|
}
|
||||||
|
|
||||||
string trade_id = 1;
|
string trade_id = 1;
|
||||||
int32 trader_id = 2;
|
int32 trader_id = 2;
|
||||||
Winner winner = 3;
|
Winner winner = 3;
|
||||||
|
@ -752,9 +760,10 @@ message DisputeResult {
|
||||||
bytes arbitrator_signature = 10;
|
bytes arbitrator_signature = 10;
|
||||||
int64 buyer_payout_amount = 11;
|
int64 buyer_payout_amount = 11;
|
||||||
int64 seller_payout_amount = 12;
|
int64 seller_payout_amount = 12;
|
||||||
bytes arbitrator_pub_key = 13;
|
SubtractFeeFrom subtract_fee_from = 13;
|
||||||
int64 close_date = 14;
|
bytes arbitrator_pub_key = 14;
|
||||||
bool is_loser_publisher = 15;
|
int64 close_date = 15;
|
||||||
|
bool is_loser_publisher = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1374,12 +1383,13 @@ message OpenOffer {
|
||||||
State state = 2;
|
State state = 2;
|
||||||
int64 trigger_price = 3;
|
int64 trigger_price = 3;
|
||||||
bool reserve_exact_amount = 4;
|
bool reserve_exact_amount = 4;
|
||||||
repeated string scheduled_tx_hashes = 5;
|
string split_output_tx_hash = 5;
|
||||||
string scheduled_amount = 6; // BigInteger
|
int64 split_output_tx_fee = 6;
|
||||||
string split_output_tx_hash = 7;
|
repeated string scheduled_tx_hashes = 7;
|
||||||
string reserve_tx_hash = 8;
|
string scheduled_amount = 8; // BigInteger
|
||||||
string reserve_tx_hex = 9;
|
string reserve_tx_hash = 9;
|
||||||
string reserve_tx_key = 10;
|
string reserve_tx_hex = 10;
|
||||||
|
string reserve_tx_key = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Tradable {
|
message Tradable {
|
||||||
|
@ -1573,9 +1583,10 @@ message TradePeer {
|
||||||
string deposit_tx_hash = 1008;
|
string deposit_tx_hash = 1008;
|
||||||
string deposit_tx_hex = 1009;
|
string deposit_tx_hex = 1009;
|
||||||
string deposit_tx_key = 1010;
|
string deposit_tx_key = 1010;
|
||||||
int64 security_deposit = 1011;
|
int64 deposit_tx_fee = 1011;
|
||||||
string updated_multisig_hex = 1012;
|
int64 security_deposit = 1012;
|
||||||
bool deposits_confirmed_message_acked = 1013;
|
string updated_multisig_hex = 1013;
|
||||||
|
bool deposits_confirmed_message_acked = 1014;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
Loading…
Reference in a new issue