mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-22 19:49:32 +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) {
|
||||
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;
|
||||
if (customWinnerAmount > 0) {
|
||||
payout = DisputePayout.CUSTOM;
|
||||
|
@ -159,13 +160,14 @@ public class CoreDisputesService {
|
|||
} else {
|
||||
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
|
||||
trade.getProcessModel().setUnsignedPayoutTx(arbitrationManager.createDisputePayoutTx(trade, winningDispute.getContract(), disputeResult, false));
|
||||
trade.getProcessModel().setUnsignedPayoutTx(arbitrationManager.createDisputePayoutTx(trade, winningDispute.getContract(), winnerDisputeResult, false));
|
||||
|
||||
// close winning dispute ticket
|
||||
closeDisputeTicket(arbitrationManager, winningDispute, disputeResult, () -> {
|
||||
closeDisputeTicket(arbitrationManager, winningDispute, winnerDisputeResult, () -> {
|
||||
arbitrationManager.requestPersistence();
|
||||
}, (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");
|
||||
var loserDispute = loserDisputeOptional.get();
|
||||
var loserDisputeResult = createDisputeResult(loserDispute, winner, reason, summaryNotes, closeDate);
|
||||
loserDisputeResult.setBuyerPayoutAmount(disputeResult.getBuyerPayoutAmount());
|
||||
loserDisputeResult.setSellerPayoutAmount(disputeResult.getSellerPayoutAmount());
|
||||
loserDisputeResult.setBuyerPayoutAmount(winnerDisputeResult.getBuyerPayoutAmount());
|
||||
loserDisputeResult.setSellerPayoutAmount(winnerDisputeResult.getSellerPayoutAmount());
|
||||
loserDisputeResult.setSubtractFeeFrom(winnerDisputeResult.getSubtractFeeFrom());
|
||||
closeDisputeTicket(arbitrationManager, loserDispute, loserDisputeResult, () -> {
|
||||
arbitrationManager.requestPersistence();
|
||||
}, (errMessage, err) -> {
|
||||
|
@ -239,6 +242,7 @@ public class CoreDisputesService {
|
|||
disputeResult.setBuyerPayoutAmount(BigInteger.valueOf(disputeResult.getWinner() == DisputeResult.Winner.BUYER ? customWinnerAmount : loserAmount));
|
||||
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) {
|
||||
|
|
|
@ -243,7 +243,7 @@ public class CoreOffersService {
|
|||
for (Offer offer2 : offers) {
|
||||
if (offer == offer2) continue;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ class CoreTradesService {
|
|||
takeOfferModel.initModel(offer, paymentAccount, amount, useSavingsWallet);
|
||||
takerFee = takeOfferModel.getTakerFee();
|
||||
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
|
||||
|
|
|
@ -74,6 +74,9 @@ public class OfferInfo implements Payload {
|
|||
private final int protocolVersion;
|
||||
@Nullable
|
||||
private final String arbitratorSigner;
|
||||
@Nullable
|
||||
private final String splitOutputTxHash;
|
||||
private final long splitOutputTxFee;
|
||||
|
||||
public OfferInfo(OfferInfoBuilder builder) {
|
||||
this.id = builder.getId();
|
||||
|
@ -103,6 +106,8 @@ public class OfferInfo implements Payload {
|
|||
this.versionNumber = builder.getVersionNumber();
|
||||
this.protocolVersion = builder.getProtocolVersion();
|
||||
this.arbitratorSigner = builder.getArbitratorSigner();
|
||||
this.splitOutputTxHash = builder.getSplitOutputTxHash();
|
||||
this.splitOutputTxFee = builder.getSplitOutputTxFee();
|
||||
}
|
||||
|
||||
public static OfferInfo toOfferInfo(Offer offer) {
|
||||
|
@ -127,6 +132,8 @@ public class OfferInfo implements Payload {
|
|||
.withTriggerPrice(preciseTriggerPrice)
|
||||
.withState(openOffer.getState().name())
|
||||
.withIsActivated(isActivated)
|
||||
.withSplitOutputTxHash(openOffer.getSplitOutputTxHash())
|
||||
.withSplitOutputTxFee(openOffer.getSplitOutputTxFee())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -199,8 +206,10 @@ public class OfferInfo implements Payload {
|
|||
.setOwnerNodeAddress(ownerNodeAddress)
|
||||
.setPubKeyRing(pubKeyRing)
|
||||
.setVersionNr(versionNumber)
|
||||
.setProtocolVersion(protocolVersion);
|
||||
.setProtocolVersion(protocolVersion)
|
||||
.setSplitOutputTxFee(splitOutputTxFee);
|
||||
Optional.ofNullable(arbitratorSigner).ifPresent(builder::setArbitratorSigner);
|
||||
Optional.ofNullable(splitOutputTxHash).ifPresent(builder::setSplitOutputTxHash);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
@ -234,6 +243,8 @@ public class OfferInfo implements Payload {
|
|||
.withVersionNumber(proto.getVersionNr())
|
||||
.withProtocolVersion(proto.getProtocolVersion())
|
||||
.withArbitratorSigner(proto.getArbitratorSigner())
|
||||
.withSplitOutputTxHash(proto.getSplitOutputTxHash())
|
||||
.withSplitOutputTxFee(proto.getSplitOutputTxFee())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,8 @@ public final class OfferInfoBuilder {
|
|||
private String versionNumber;
|
||||
private int protocolVersion;
|
||||
private String arbitratorSigner;
|
||||
private String splitOutputTxHash;
|
||||
private long splitOutputTxFee;
|
||||
|
||||
public OfferInfoBuilder withId(String id) {
|
||||
this.id = id;
|
||||
|
@ -210,6 +212,16 @@ public final class OfferInfoBuilder {
|
|||
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() {
|
||||
return new OfferInfo(this);
|
||||
}
|
||||
|
|
|
@ -222,7 +222,7 @@ public class OfferUtil {
|
|||
getMaxBuyerSecurityDepositAsPercent());
|
||||
checkArgument(buyerSecurityDeposit >= getMinBuyerSecurityDepositAsPercent(),
|
||||
"securityDeposit must not be less than " +
|
||||
getMinBuyerSecurityDepositAsPercent());
|
||||
getMinBuyerSecurityDepositAsPercent() + " but was " + buyerSecurityDeposit);
|
||||
checkArgument(!filterManager.isCurrencyBanned(currencyCode),
|
||||
Res.get("offerbook.warning.currencyBanned"));
|
||||
checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()),
|
||||
|
|
|
@ -70,6 +70,9 @@ public final class OpenOffer implements Tradable {
|
|||
@Getter
|
||||
@Nullable
|
||||
String splitOutputTxHash;
|
||||
@Getter
|
||||
@Setter
|
||||
long splitOutputTxFee;
|
||||
@Nullable
|
||||
@Setter
|
||||
@Getter
|
||||
|
@ -114,6 +117,7 @@ public final class OpenOffer implements Tradable {
|
|||
this.scheduledAmount = openOffer.scheduledAmount;
|
||||
this.scheduledTxHashes = openOffer.scheduledTxHashes == null ? null : new ArrayList<String>(openOffer.scheduledTxHashes);
|
||||
this.splitOutputTxHash = openOffer.splitOutputTxHash;
|
||||
this.splitOutputTxFee = openOffer.splitOutputTxFee;
|
||||
this.reserveTxHash = openOffer.reserveTxHash;
|
||||
this.reserveTxHex = openOffer.reserveTxHex;
|
||||
this.reserveTxKey = openOffer.reserveTxKey;
|
||||
|
@ -130,6 +134,7 @@ public final class OpenOffer implements Tradable {
|
|||
@Nullable String scheduledAmount,
|
||||
@Nullable List<String> scheduledTxHashes,
|
||||
String splitOutputTxHash,
|
||||
long splitOutputTxFee,
|
||||
@Nullable String reserveTxHash,
|
||||
@Nullable String reserveTxHex,
|
||||
@Nullable String reserveTxKey) {
|
||||
|
@ -139,6 +144,7 @@ public final class OpenOffer implements Tradable {
|
|||
this.reserveExactAmount = reserveExactAmount;
|
||||
this.scheduledTxHashes = scheduledTxHashes;
|
||||
this.splitOutputTxHash = splitOutputTxHash;
|
||||
this.splitOutputTxFee = splitOutputTxFee;
|
||||
this.reserveTxHash = reserveTxHash;
|
||||
this.reserveTxHex = reserveTxHex;
|
||||
this.reserveTxKey = reserveTxKey;
|
||||
|
@ -153,6 +159,7 @@ public final class OpenOffer implements Tradable {
|
|||
.setOffer(offer.toProtoMessage())
|
||||
.setTriggerPrice(triggerPrice)
|
||||
.setState(protobuf.OpenOffer.State.valueOf(state.name()))
|
||||
.setSplitOutputTxFee(splitOutputTxFee)
|
||||
.setReserveExactAmount(reserveExactAmount);
|
||||
|
||||
Optional.ofNullable(scheduledAmount).ifPresent(e -> builder.setScheduledAmount(scheduledAmount));
|
||||
|
@ -173,6 +180,7 @@ public final class OpenOffer implements Tradable {
|
|||
proto.getScheduledAmount(),
|
||||
proto.getScheduledTxHashesList(),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getSplitOutputTxHash()),
|
||||
proto.getSplitOutputTxFee(),
|
||||
proto.getReserveTxHash(),
|
||||
proto.getReserveTxHex(),
|
||||
proto.getReserveTxKey());
|
||||
|
@ -253,6 +261,9 @@ public final class OpenOffer implements Tradable {
|
|||
",\n offer=" + offer +
|
||||
",\n state=" + state +
|
||||
",\n triggerPrice=" + triggerPrice +
|
||||
",\n reserveExactAmount=" + reserveExactAmount +
|
||||
",\n scheduledAmount=" + scheduledAmount +
|
||||
",\n splitOutputTxFee=" + splitOutputTxFee +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1006,8 +1006,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
log.info("Done creating split output tx to fund offer {}", openOffer.getId());
|
||||
|
||||
// schedule txs
|
||||
openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash()));
|
||||
openOffer.setSplitOutputTxHash(splitOutputTx.getHash());
|
||||
openOffer.setSplitOutputTxFee(splitOutputTx.getFee().longValueExact());
|
||||
openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash()));
|
||||
openOffer.setScheduledAmount(openOffer.getOffer().getReserveAmount().toString());
|
||||
openOffer.setState(OpenOffer.State.SCHEDULED);
|
||||
return splitOutputTx;
|
||||
|
|
|
@ -69,6 +69,7 @@ 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;
|
||||
|
@ -869,12 +870,29 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
// add any loss of precision to winner payout
|
||||
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);
|
||||
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);
|
||||
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;
|
||||
try {
|
||||
payoutTx = trade.getWallet().createTx(txConfig);
|
||||
|
|
|
@ -61,12 +61,21 @@ public final class DisputeResult implements NetworkPayload {
|
|||
PEER_WAS_LATE
|
||||
}
|
||||
|
||||
public enum SubtractFeeFrom {
|
||||
BUYER_ONLY,
|
||||
SELLER_ONLY,
|
||||
BUYER_AND_SELLER
|
||||
}
|
||||
|
||||
private final String tradeId;
|
||||
private final int traderId;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Winner winner;
|
||||
private int reasonOrdinal = Reason.OTHER.ordinal();
|
||||
@Setter
|
||||
@Nullable
|
||||
private SubtractFeeFrom subtractFeeFrom;
|
||||
private final BooleanProperty tamperProofEvidenceProperty = new SimpleBooleanProperty();
|
||||
private final BooleanProperty idVerificationProperty = new SimpleBooleanProperty();
|
||||
private final BooleanProperty screenCastProperty = new SimpleBooleanProperty();
|
||||
|
@ -93,6 +102,7 @@ public final class DisputeResult implements NetworkPayload {
|
|||
int traderId,
|
||||
@Nullable Winner winner,
|
||||
int reasonOrdinal,
|
||||
@Nullable SubtractFeeFrom subtractFeeFrom,
|
||||
boolean tamperProofEvidence,
|
||||
boolean idVerification,
|
||||
boolean screenCast,
|
||||
|
@ -107,6 +117,7 @@ public final class DisputeResult implements NetworkPayload {
|
|||
this.traderId = traderId;
|
||||
this.winner = winner;
|
||||
this.reasonOrdinal = reasonOrdinal;
|
||||
this.subtractFeeFrom = subtractFeeFrom;
|
||||
this.tamperProofEvidenceProperty.set(tamperProofEvidence);
|
||||
this.idVerificationProperty.set(idVerification);
|
||||
this.screenCastProperty.set(screenCast);
|
||||
|
@ -129,6 +140,7 @@ public final class DisputeResult implements NetworkPayload {
|
|||
proto.getTraderId(),
|
||||
ProtoUtil.enumFromProto(DisputeResult.Winner.class, proto.getWinner().name()),
|
||||
proto.getReasonOrdinal(),
|
||||
ProtoUtil.enumFromProto(DisputeResult.SubtractFeeFrom.class, proto.getSubtractFeeFrom().name()),
|
||||
proto.getTamperProofEvidence(),
|
||||
proto.getIdVerification(),
|
||||
proto.getScreenCast(),
|
||||
|
@ -158,6 +170,7 @@ public final class DisputeResult implements NetworkPayload {
|
|||
Optional.ofNullable(arbitratorSignature).ifPresent(arbitratorSignature -> builder.setArbitratorSignature(ByteString.copyFrom(arbitratorSignature)));
|
||||
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(subtractFeeFrom).ifPresent(result -> builder.setSubtractFeeFrom(protobuf.DisputeResult.SubtractFeeFrom.valueOf(subtractFeeFrom.name())));
|
||||
Optional.ofNullable(chatMessage).ifPresent(chatMessage ->
|
||||
builder.setChatMessage(chatMessage.toProtoNetworkEnvelope().getChatMessage()));
|
||||
|
||||
|
@ -201,6 +214,7 @@ public final class DisputeResult implements NetworkPayload {
|
|||
}
|
||||
|
||||
public void setBuyerPayoutAmount(BigInteger buyerPayoutAmount) {
|
||||
if (buyerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("buyerPayoutAmount cannot be negative");
|
||||
this.buyerPayoutAmount = buyerPayoutAmount.longValueExact();
|
||||
}
|
||||
|
||||
|
@ -209,6 +223,7 @@ public final class DisputeResult implements NetworkPayload {
|
|||
}
|
||||
|
||||
public void setSellerPayoutAmount(BigInteger sellerPayoutAmount) {
|
||||
if (sellerPayoutAmount.compareTo(BigInteger.ZERO) < 0) throw new IllegalArgumentException("sellerPayoutAmount cannot be negative");
|
||||
this.sellerPayoutAmount = sellerPayoutAmount.longValueExact();
|
||||
}
|
||||
|
||||
|
@ -231,6 +246,7 @@ public final class DisputeResult implements NetworkPayload {
|
|||
",\n traderId=" + traderId +
|
||||
",\n winner=" + winner +
|
||||
",\n reasonOrdinal=" + reasonOrdinal +
|
||||
",\n subtractFeeFrom=" + subtractFeeFrom +
|
||||
",\n tamperProofEvidenceProperty=" + tamperProofEvidenceProperty +
|
||||
",\n idVerificationProperty=" + idVerificationProperty +
|
||||
",\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 expectedLoserAmount = disputeResult.getWinner() == Winner.BUYER ? disputeResult.getSellerPayoutAmount() : disputeResult.getBuyerPayoutAmount();
|
||||
|
||||
// winner pays cost if loser gets nothing, otherwise loser pays cost
|
||||
if (expectedLoserAmount.equals(BigInteger.ZERO)) expectedWinnerAmount = expectedWinnerAmount.subtract(txCost);
|
||||
else expectedLoserAmount = expectedLoserAmount.subtract(txCost);
|
||||
// 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);
|
||||
|
|
|
@ -69,11 +69,13 @@ import monero.common.MoneroError;
|
|||
import monero.common.MoneroRpcConnection;
|
||||
import monero.common.TaskLooper;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.model.MoneroKeyImage;
|
||||
import monero.daemon.model.MoneroTx;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.MoneroWalletRpc;
|
||||
import monero.wallet.model.MoneroDestination;
|
||||
import monero.wallet.model.MoneroMultisigSignResult;
|
||||
import monero.wallet.model.MoneroOutputQuery;
|
||||
import monero.wallet.model.MoneroOutputWallet;
|
||||
import monero.wallet.model.MoneroTxConfig;
|
||||
import monero.wallet.model.MoneroTxQuery;
|
||||
|
@ -1595,6 +1597,24 @@ public abstract class Trade implements Tradable, Model {
|
|||
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() {
|
||||
return Price.valueOf(offer.getCurrencyCode(), price);
|
||||
}
|
||||
|
|
|
@ -815,6 +815,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
|
||||
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);
|
||||
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)
|
||||
// we move the trade to failedTradesManager
|
||||
// we move the trade to FailedTradesManager
|
||||
public void onMoveInvalidTradeToFailedTrades(Trade trade) {
|
||||
removeTrade(trade);
|
||||
failedTradesManager.add(trade);
|
||||
|
|
|
@ -30,15 +30,21 @@ import java.util.Optional;
|
|||
public final class DepositResponse extends TradeMessage implements DirectMessage {
|
||||
private final long currentDate;
|
||||
private final String errorMessage;
|
||||
private final long buyerSecurityDeposit;
|
||||
private final long sellerSecurityDeposit;
|
||||
|
||||
public DepositResponse(String tradeId,
|
||||
String uid,
|
||||
String messageVersion,
|
||||
long currentDate,
|
||||
String errorMessage) {
|
||||
String errorMessage,
|
||||
long buyerSecurityDeposit,
|
||||
long sellerSecurityDeposit) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.currentDate = currentDate;
|
||||
this.errorMessage = errorMessage;
|
||||
this.buyerSecurityDeposit = buyerSecurityDeposit;
|
||||
this.sellerSecurityDeposit = sellerSecurityDeposit;
|
||||
}
|
||||
|
||||
|
||||
|
@ -52,6 +58,8 @@ public final class DepositResponse extends TradeMessage implements DirectMessage
|
|||
.setTradeId(tradeId)
|
||||
.setUid(uid);
|
||||
builder.setCurrentDate(currentDate);
|
||||
builder.setBuyerSecurityDeposit(buyerSecurityDeposit);
|
||||
builder.setSellerSecurityDeposit(sellerSecurityDeposit);
|
||||
Optional.ofNullable(errorMessage).ifPresent(e -> builder.setErrorMessage(errorMessage));
|
||||
|
||||
return getNetworkEnvelopeBuilder().setDepositResponse(builder).build();
|
||||
|
@ -64,7 +72,9 @@ public final class DepositResponse extends TradeMessage implements DirectMessage
|
|||
proto.getUid(),
|
||||
messageVersion,
|
||||
proto.getCurrentDate(),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()));
|
||||
ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()),
|
||||
proto.getBuyerSecurityDeposit(),
|
||||
proto.getSellerSecurityDeposit());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,6 +82,8 @@ public final class DepositResponse extends TradeMessage implements DirectMessage
|
|||
return "DepositResponse {" +
|
||||
",\n currentDate=" + currentDate +
|
||||
",\n errorMessage=" + errorMessage +
|
||||
",\n buyerSecurityDeposit=" + buyerSecurityDeposit +
|
||||
",\n sellerSecurityDeposit=" + sellerSecurityDeposit +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,6 +116,7 @@ public final class TradePeer implements PersistablePayload {
|
|||
private String depositTxHex;
|
||||
@Nullable
|
||||
private String depositTxKey;
|
||||
private long depositTxFee;
|
||||
private long securityDeposit;
|
||||
@Nullable
|
||||
private String updatedMultisigHex;
|
||||
|
@ -126,7 +127,16 @@ public final class TradePeer implements PersistablePayload {
|
|||
public TradePeer() {
|
||||
}
|
||||
|
||||
public BigInteger getDepositTxFee() {
|
||||
return BigInteger.valueOf(depositTxFee);
|
||||
}
|
||||
|
||||
public void setDepositTxFee(BigInteger depositTxFee) {
|
||||
this.depositTxFee = depositTxFee.longValueExact();
|
||||
}
|
||||
|
||||
public BigInteger getSecurityDeposit() {
|
||||
if (depositTxHash == null) return null;
|
||||
return BigInteger.valueOf(securityDeposit);
|
||||
}
|
||||
|
||||
|
@ -164,6 +174,7 @@ public final class TradePeer implements PersistablePayload {
|
|||
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));
|
||||
builder.setDepositsConfirmedMessageAcked(depositsConfirmedMessageAcked);
|
||||
|
@ -206,6 +217,7 @@ public final class TradePeer implements PersistablePayload {
|
|||
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());
|
||||
|
|
|
@ -80,7 +80,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||
boolean isFromTaker = trader == trade.getTaker();
|
||||
boolean isFromBuyer = trader == trade.getBuyer();
|
||||
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();
|
||||
String depositAddress = processModel.getMultisigAddress();
|
||||
|
||||
|
@ -103,6 +103,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||
|
||||
// set deposit info
|
||||
trader.setSecurityDeposit(txResult.second);
|
||||
trader.setDepositTxFee(txResult.first.getFee());
|
||||
trader.setDepositTxHex(request.getDepositTxHex());
|
||||
trader.setDepositTxKey(request.getDepositTxKey());
|
||||
if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey());
|
||||
|
@ -130,7 +131,9 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
null);
|
||||
null,
|
||||
trade.getBuyer().getSecurityDeposit().longValue(),
|
||||
trade.getSeller().getSecurityDeposit().longValue());
|
||||
|
||||
// send deposit response to maker and taker
|
||||
sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response);
|
||||
|
@ -158,7 +161,9 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
t.getMessage());
|
||||
t.getMessage(),
|
||||
trade.getBuyer().getSecurityDeposit().longValue(),
|
||||
trade.getSeller().getSecurityDeposit().longValue());
|
||||
|
||||
// send deposit response to maker and taker
|
||||
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.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.ArbitratorTrade;
|
||||
import haveno.core.trade.BuyerTrade;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.MakerTrade;
|
||||
import haveno.core.trade.Trade;
|
||||
|
@ -31,6 +32,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
@ -91,10 +93,15 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
|||
processModel.setDepositTxXmr(depositTx); // TODO: redundant with trade.getSelf().setDepositTx(), remove?
|
||||
trade.getSelf().setDepositTx(depositTx);
|
||||
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
||||
trade.getSelf().setDepositTxFee(depositTx.getFee());
|
||||
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().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
|
||||
byte[] sig = null;
|
||||
if (trade instanceof MakerTrade) {
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.DepositResponse;
|
||||
|
@ -43,10 +45,17 @@ public class ProcessDepositResponse extends TradeTask {
|
|||
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
|
||||
trade.setStateIfValidTransitionTo(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
|
||||
trade.addInitProgressStep();
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
// update balances
|
||||
trade.getXmrWalletService().updateBalanceListeners();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -53,6 +53,10 @@ public class ProcessInitTradeRequest extends TradeTask {
|
|||
checkNotNull(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
|
||||
TradePeer multisigParticipant;
|
||||
if (trade instanceof ArbitratorTrade) {
|
||||
|
|
|
@ -17,28 +17,24 @@
|
|||
|
||||
package haveno.core.xmr;
|
||||
|
||||
import haveno.core.offer.OfferPayload;
|
||||
import haveno.core.offer.OpenOffer;
|
||||
import haveno.core.offer.OpenOfferManager;
|
||||
import haveno.core.support.dispute.Dispute;
|
||||
import haveno.core.support.dispute.refund.RefundManager;
|
||||
import haveno.core.trade.ClosedTradableManager;
|
||||
import haveno.core.trade.MakerTrade;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.TradeManager;
|
||||
import haveno.core.trade.failed.FailedTradesManager;
|
||||
import haveno.core.xmr.listeners.XmrBalanceListener;
|
||||
import haveno.core.xmr.wallet.XmrWalletService;
|
||||
import haveno.network.p2p.P2PService;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.common.MoneroError;
|
||||
import monero.wallet.model.MoneroOutputQuery;
|
||||
import monero.wallet.model.MoneroOutputWallet;
|
||||
import monero.wallet.model.MoneroTxQuery;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.math.BigInteger;
|
||||
|
@ -77,19 +73,19 @@ public class Balances {
|
|||
}
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
openOfferManager.getObservableList().addListener((ListChangeListener<OpenOffer>) c -> updatedBalances());
|
||||
tradeManager.getObservableList().addListener((ListChangeListener<Trade>) change -> updatedBalances());
|
||||
refundManager.getDisputesAsObservableList().addListener((ListChangeListener<Dispute>) c -> updatedBalances());
|
||||
openOfferManager.getObservableList().addListener((ListChangeListener<OpenOffer>) c -> updateBalances());
|
||||
tradeManager.getObservableList().addListener((ListChangeListener<Trade>) change -> updateBalances());
|
||||
refundManager.getDisputesAsObservableList().addListener((ListChangeListener<Dispute>) c -> updateBalances());
|
||||
xmrWalletService.addBalanceListener(new XmrBalanceListener() {
|
||||
@Override
|
||||
public void onBalanceChanged(BigInteger balance) {
|
||||
updatedBalances();
|
||||
updateBalances();
|
||||
}
|
||||
});
|
||||
updatedBalances();
|
||||
updateBalances();
|
||||
}
|
||||
|
||||
private void updatedBalances() {
|
||||
private void updateBalances() {
|
||||
if (!xmrWalletService.isWalletReady()) return;
|
||||
try {
|
||||
updateAvailableBalance();
|
||||
|
@ -111,7 +107,18 @@ public class Balances {
|
|||
private void updatePendingBalance() {
|
||||
BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.valueOf(0) : xmrWalletService.getWallet().getBalance(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() {
|
||||
|
@ -120,30 +127,21 @@ public class Balances {
|
|||
List<MoneroOutputWallet> frozenOutputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false));
|
||||
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);
|
||||
}
|
||||
|
||||
private void updateReservedTradeBalance() {
|
||||
BigInteger sum = BigInteger.valueOf(0);
|
||||
List<Trade> openTrades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList());
|
||||
for (Trade trade : openTrades) {
|
||||
try {
|
||||
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));
|
||||
List<Trade> trades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList());
|
||||
for (Trade trade : trades) {
|
||||
sum = sum.add(trade.getReservedAmount());
|
||||
}
|
||||
reservedTradeBalance.set(sum);
|
||||
}
|
||||
|
|
|
@ -368,7 +368,7 @@ public class XmrWalletService {
|
|||
return reserveTx;
|
||||
}
|
||||
|
||||
/**s
|
||||
/**
|
||||
* Create the multisig deposit tx and freeze its inputs.
|
||||
*
|
||||
* @param trade the trade to create a deposit tx from
|
||||
|
@ -388,8 +388,8 @@ public class XmrWalletService {
|
|||
Offer offer = trade.getProcessModel().getOffer();
|
||||
String multisigAddress = trade.getProcessModel().getMultisigAddress();
|
||||
BigInteger tradeFee = trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee();
|
||||
BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.valueOf(0) : offer.getAmount();
|
||||
BigInteger securityDeposit = trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit();
|
||||
BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.valueOf(0) : trade.getAmount();
|
||||
BigInteger securityDeposit = trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit(); // TODO: security deposit should be based on trade amount
|
||||
long time = System.currentTimeMillis();
|
||||
log.info("Creating deposit tx with multisig address={}", multisigAddress);
|
||||
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.
|
||||
* 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 sendAmount amount to give peer
|
||||
* @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) {
|
||||
|
||||
// create task to change main wallet password
|
||||
|
@ -1195,6 +1177,25 @@ public class XmrWalletService {
|
|||
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() {
|
||||
xmrAddressEntryList.requestPersistence();
|
||||
}
|
||||
|
@ -1259,7 +1260,7 @@ public class XmrWalletService {
|
|||
@Override
|
||||
public void run() {
|
||||
for (MoneroWalletListenerI listener : walletListeners) listener.onBalancesChanged(newBalance, newUnlockedBalance);
|
||||
notifyBalanceListeners();
|
||||
updateBalanceListeners();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -192,6 +192,7 @@ public class AccountAgeWitnessServiceTest {
|
|||
1,
|
||||
DisputeResult.Winner.BUYER,
|
||||
DisputeResult.Reason.OTHER.ordinal(),
|
||||
DisputeResult.SubtractFeeFrom.BUYER_ONLY,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
|
|
|
@ -216,6 +216,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
disputeResult.setWinner(peersDisputeResult.getWinner());
|
||||
disputeResult.setReason(peersDisputeResult.getReason());
|
||||
disputeResult.setSummaryNotes(peersDisputeResult.summaryNotesProperty().get());
|
||||
disputeResult.setSubtractFeeFrom(peersDisputeResult.getSubtractFeeFrom());
|
||||
|
||||
buyerGetsTradeAmountRadioButton.setDisable(true);
|
||||
buyerGetsAllRadioButton.setDisable(true);
|
||||
|
@ -403,7 +404,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
|
||||
disputeResult.setBuyerPayoutAmount(buyerAmount);
|
||||
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.SELLER);
|
||||
}
|
||||
|
|
|
@ -541,6 +541,8 @@ message OfferInfo {
|
|||
string version_nr = 25;
|
||||
int32 protocol_version = 26;
|
||||
string arbitrator_signer = 27;
|
||||
string split_output_tx_hash = 28;
|
||||
uint64 split_output_tx_fee = 29 [jstype = JS_STRING];
|
||||
}
|
||||
|
||||
message AvailabilityResultWithDescription {
|
||||
|
|
|
@ -281,6 +281,8 @@ message DepositResponse {
|
|||
string uid = 2;
|
||||
int64 current_date = 3;
|
||||
string error_message = 4;
|
||||
int64 buyerSecurityDeposit = 5;
|
||||
int64 sellerSecurityDeposit = 6;
|
||||
}
|
||||
|
||||
message DepositsConfirmedMessage {
|
||||
|
@ -740,6 +742,12 @@ message DisputeResult {
|
|||
PEER_WAS_LATE = 12;
|
||||
}
|
||||
|
||||
enum SubtractFeeFrom {
|
||||
BUYER_ONLY = 0;
|
||||
SELLER_ONLY = 1;
|
||||
BUYER_AND_SELLER = 2;
|
||||
}
|
||||
|
||||
string trade_id = 1;
|
||||
int32 trader_id = 2;
|
||||
Winner winner = 3;
|
||||
|
@ -752,9 +760,10 @@ message DisputeResult {
|
|||
bytes arbitrator_signature = 10;
|
||||
int64 buyer_payout_amount = 11;
|
||||
int64 seller_payout_amount = 12;
|
||||
bytes arbitrator_pub_key = 13;
|
||||
int64 close_date = 14;
|
||||
bool is_loser_publisher = 15;
|
||||
SubtractFeeFrom subtract_fee_from = 13;
|
||||
bytes arbitrator_pub_key = 14;
|
||||
int64 close_date = 15;
|
||||
bool is_loser_publisher = 16;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1374,12 +1383,13 @@ message OpenOffer {
|
|||
State state = 2;
|
||||
int64 trigger_price = 3;
|
||||
bool reserve_exact_amount = 4;
|
||||
repeated string scheduled_tx_hashes = 5;
|
||||
string scheduled_amount = 6; // BigInteger
|
||||
string split_output_tx_hash = 7;
|
||||
string reserve_tx_hash = 8;
|
||||
string reserve_tx_hex = 9;
|
||||
string reserve_tx_key = 10;
|
||||
string split_output_tx_hash = 5;
|
||||
int64 split_output_tx_fee = 6;
|
||||
repeated string scheduled_tx_hashes = 7;
|
||||
string scheduled_amount = 8; // BigInteger
|
||||
string reserve_tx_hash = 9;
|
||||
string reserve_tx_hex = 10;
|
||||
string reserve_tx_key = 11;
|
||||
}
|
||||
|
||||
message Tradable {
|
||||
|
@ -1573,9 +1583,10 @@ message TradePeer {
|
|||
string deposit_tx_hash = 1008;
|
||||
string deposit_tx_hex = 1009;
|
||||
string deposit_tx_key = 1010;
|
||||
int64 security_deposit = 1011;
|
||||
string updated_multisig_hex = 1012;
|
||||
bool deposits_confirmed_message_acked = 1013;
|
||||
int64 deposit_tx_fee = 1011;
|
||||
int64 security_deposit = 1012;
|
||||
string updated_multisig_hex = 1013;
|
||||
bool deposits_confirmed_message_acked = 1014;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
Loading…
Reference in a new issue