Skip to content

Commit

Permalink
Merge pull request #4711 from ghubstan/10-closetrade-impls
Browse files Browse the repository at this point in the history
Implement api methods 'keepfunds', 'withdrawfunds'
  • Loading branch information
sqrrm authored Oct 31, 2020
2 parents 605f4b3 + a3631a0 commit f3c62d3
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 8 deletions.
120 changes: 112 additions & 8 deletions core/src/main/java/bisq/core/api/CoreTradesService.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,57 @@

package bisq.core.api;

import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.Offer;
import bisq.core.offer.takeoffer.TakeOfferModel;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.TradeUtil;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.protocol.BuyerProtocol;
import bisq.core.trade.protocol.SellerProtocol;
import bisq.core.user.User;
import bisq.core.util.validation.BtcAddressValidator;

import org.bitcoinj.core.Coin;

import javax.inject.Inject;

import java.util.Optional;
import java.util.function.Consumer;

import lombok.extern.slf4j.Slf4j;

import static bisq.core.btc.model.AddressEntry.Context.TRADE_PAYOUT;
import static java.lang.String.format;

@Slf4j
class CoreTradesService {

// Dependencies on core api services in this package must be kept to an absolute
// minimum, but some trading functions require an unlocked wallet's key, so an
// exception is made in this case.
private final CoreWalletsService coreWalletsService;

private final BtcWalletService btcWalletService;
private final ClosedTradableManager closedTradableManager;
private final TakeOfferModel takeOfferModel;
private final TradeManager tradeManager;
private final TradeUtil tradeUtil;
private final User user;

@Inject
public CoreTradesService(TakeOfferModel takeOfferModel,
public CoreTradesService(CoreWalletsService coreWalletsService,
BtcWalletService btcWalletService,
ClosedTradableManager closedTradableManager,
TakeOfferModel takeOfferModel,
TradeManager tradeManager,
TradeUtil tradeUtil,
User user) {
this.coreWalletsService = coreWalletsService;
this.btcWalletService = btcWalletService;
this.closedTradableManager = closedTradableManager;
this.takeOfferModel = takeOfferModel;
this.tradeManager = tradeManager;
this.tradeUtil = tradeUtil;
Expand Down Expand Up @@ -116,26 +137,109 @@ void confirmPaymentReceived(String tradeId) {
}
}

@SuppressWarnings("unused")
void keepFunds(String tradeId) {
log.info("TODO");
verifyTradeIsNotClosed(tradeId);
var trade = getOpenTrade(tradeId).orElseThrow(() ->
new IllegalArgumentException(format("trade with id '%s' not found", tradeId)));
log.info("Keeping funds received from trade {}", tradeId);
tradeManager.onTradeCompleted(trade);
}

@SuppressWarnings("unused")
void withdrawFunds(String tradeId, String address) {
log.info("TODO");
void withdrawFunds(String tradeId, String toAddress) {
// An encrypted wallet must be unlocked for this operation.
verifyTradeIsNotClosed(tradeId);
var trade = getOpenTrade(tradeId).orElseThrow(() ->
new IllegalArgumentException(format("trade with id '%s' not found", tradeId)));

verifyIsValidBTCAddress(toAddress);

var fromAddressEntry = btcWalletService.getOrCreateAddressEntry(trade.getId(), TRADE_PAYOUT);
verifyFundsNotWithdrawn(fromAddressEntry);

var amount = trade.getPayoutAmount();
var fee = getEstimatedTxFee(fromAddressEntry.getAddressString(), toAddress, amount);
var receiverAmount = amount.subtract(fee);

log.info(format("Withdrawing funds received from trade %s:"
+ "%n From %s%n To %s%n Amt %s%n Tx Fee %s%n Receiver Amt %s",
tradeId,
fromAddressEntry.getAddressString(),
toAddress,
amount.toFriendlyString(),
fee.toFriendlyString(),
receiverAmount.toFriendlyString()));

tradeManager.onWithdrawRequest(
toAddress,
amount,
fee,
coreWalletsService.getKey(),
trade,
() -> {
},
(errorMessage, throwable) -> {
log.error(errorMessage, throwable);
throw new IllegalStateException(errorMessage, throwable);
});
}

String getTradeRole(String tradeId) {
return tradeUtil.getRole(getTrade(tradeId));
}

Trade getTrade(String tradeId) {
return tradeManager.getTradeById(tradeId).orElseThrow(() ->
new IllegalArgumentException(format("trade with id '%s' not found", tradeId)));
return getOpenTrade(tradeId).orElseGet(() ->
getClosedTrade(tradeId).orElseThrow(() ->
new IllegalArgumentException(format("trade with id '%s' not found", tradeId))
));
}

private Optional<Trade> getOpenTrade(String tradeId) {
return tradeManager.getTradeById(tradeId);
}

private Optional<Trade> getClosedTrade(String tradeId) {
return closedTradableManager.getTradableById(tradeId).map(value -> (Trade) value);
}

private boolean isFollowingBuyerProtocol(Trade trade) {
return tradeManager.getTradeProtocol(trade) instanceof BuyerProtocol;
}

private Coin getEstimatedTxFee(String fromAddress, String toAddress, Coin amount) {
// TODO This and identical logic should be refactored into TradeUtil.
try {
return btcWalletService.getFeeEstimationTransaction(fromAddress,
toAddress,
amount,
TRADE_PAYOUT).getFee();
} catch (Exception ex) {
log.error("", ex);
throw new IllegalStateException(format("could not estimate tx fee: %s", ex.getMessage()));
}
}

// Throws a RuntimeException trade is already closed.
private void verifyTradeIsNotClosed(String tradeId) {
if (getClosedTrade(tradeId).isPresent())
throw new IllegalArgumentException(format("trade '%s' is already closed", tradeId));
}

// Throws a RuntimeException if address is not valid.
private void verifyIsValidBTCAddress(String address) {
try {
new BtcAddressValidator().validate(address);
} catch (Throwable t) {
log.error("", t);
throw new IllegalArgumentException(format("'%s' is not a valid btc address", address));
}
}

// Throws a RuntimeException if address has a zero balance.
private void verifyFundsNotWithdrawn(AddressEntry fromAddressEntry) {
Coin fromAddressBalance = btcWalletService.getBalanceForAddress(fromAddressEntry.getAddress());
if (fromAddressBalance.isZero())
throw new IllegalStateException(format("funds already withdrawn from address '%s'",
fromAddressEntry.getAddressString()));
}
}
6 changes: 6 additions & 0 deletions core/src/main/java/bisq/core/api/CoreWalletsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ public CoreWalletsService(Balances balances,
this.btcWalletService = btcWalletService;
}

@Nullable
KeyParameter getKey() {
verifyEncryptedWalletIsUnlocked();
return tempAesKey;
}

long getAvailableBalance() {
verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
Expand Down

0 comments on commit f3c62d3

Please sign in to comment.