mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-07-29 01:38:39 -04:00
Savings wallet (WIP)
This commit is contained in:
parent
f07aa9ba6a
commit
091eae4965
19 changed files with 288 additions and 169 deletions
|
@ -36,12 +36,12 @@ public class FeePolicy {
|
|||
// disputed payout tx: 408 bytes
|
||||
|
||||
// We set a fixed fee to make the needed amounts in the trade predictable.
|
||||
// We use 0.0003 BTC (0.12 EUR @ 400 EUR/BTC) which is for our tx sizes about 75-150 satoshi/byte
|
||||
// We use 0.0002 BTC (0.08 EUR @ 400 EUR/BTC) which is for our tx sizes about 50-90 satoshi/byte
|
||||
// We cannot make that user defined as it need to be the same for both users, so we can only change that in
|
||||
// software updates
|
||||
// TODO before Beta we should get a good future proof guess as a change causes incompatible versions
|
||||
public static Coin getFixedTxFeeForTrades() {
|
||||
return Coin.valueOf(30_000);
|
||||
return Coin.valueOf(20_000);
|
||||
}
|
||||
|
||||
// For non trade transactions (withdrawal) we use the default fee calculation
|
||||
|
@ -50,7 +50,7 @@ public class FeePolicy {
|
|||
// The BitcoinJ fee calculation use kb so a tx size < 1kb will still pay the fee for a kb tx.
|
||||
// Our payout tx has about 370 bytes so we get a fee/kb value of about 90 satoshi/byte making it high priority
|
||||
// Other payout transactions (E.g. arbitrators many collected transactions) will go with 30 satoshi/byte if > 1kb
|
||||
private static Coin FEE_PER_KB = Coin.valueOf(30_000); // 0.0003 BTC about 0.12 EUR @ 400 EUR/BTC
|
||||
private static Coin FEE_PER_KB = Coin.valueOf(20_000); // 0.0002 BTC about 0.08 EUR @ 400 EUR/BTC
|
||||
|
||||
public static void setFeePerKb(Coin feePerKb) {
|
||||
FEE_PER_KB = feePerKb;
|
||||
|
@ -84,8 +84,8 @@ public class FeePolicy {
|
|||
|
||||
|
||||
// TODO will be increased once we get higher limits
|
||||
// 0.01 BTC; about 0.4 EUR @ 400 EUR/BTC
|
||||
// 0.02 BTC; about 8 EUR @ 400 EUR/BTC
|
||||
public static Coin getSecurityDeposit() {
|
||||
return Coin.valueOf(1_000_000);
|
||||
return Coin.valueOf(2_000_000);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -333,6 +333,12 @@ public class WalletService {
|
|||
.findAny();
|
||||
}
|
||||
|
||||
public List<AddressEntry> getTradeAddressEntryList() {
|
||||
return getAddressEntryList().stream()
|
||||
.filter(e -> e.getContext().equals(AddressEntry.Context.TRADE))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SavingsAddressEntry
|
||||
|
@ -493,6 +499,10 @@ public class WalletService {
|
|||
return wallet != null ? getBalance(wallet.calculateAllSpendCandidates(), address) : Coin.ZERO;
|
||||
}
|
||||
|
||||
public Coin getBalanceForAddressEntryWithTradeId(String tradeId) {
|
||||
return getBalanceForAddress(getTradeAddressEntry(tradeId).getAddress());
|
||||
}
|
||||
|
||||
private Coin getBalance(List<TransactionOutput> transactionOutputs, Address address) {
|
||||
Coin balance = Coin.ZERO;
|
||||
for (TransactionOutput transactionOutput : transactionOutputs) {
|
||||
|
@ -539,7 +549,8 @@ public class WalletService {
|
|||
Coin fee;
|
||||
try {
|
||||
wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, aesKey));
|
||||
fee = Coin.ZERO;
|
||||
// We use the min fee for now as the mix of savingswallet/trade wallet has some nasty edge cases...
|
||||
fee = FeePolicy.getFixedTxFeeForTrades();
|
||||
} catch (InsufficientMoneyException e) {
|
||||
log.info("The amount to be transferred is not enough to pay the transaction fees of {}. " +
|
||||
"We subtract that fee from the receivers amount to make the transaction possible.");
|
||||
|
@ -556,7 +567,8 @@ public class WalletService {
|
|||
Coin fee;
|
||||
try {
|
||||
wallet.completeTx(getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, null, aesKey));
|
||||
fee = Coin.ZERO;
|
||||
// We use the min fee for now as the mix of savingswallet/trade wallet has some nasty edge cases...
|
||||
fee = FeePolicy.getFixedTxFeeForTrades();
|
||||
} catch (InsufficientMoneyException e) {
|
||||
log.info("The amount to be transferred is not enough to pay the transaction fees of {}. " +
|
||||
"We subtract that fee from the receivers amount to make the transaction possible.");
|
||||
|
|
|
@ -67,18 +67,18 @@ public final class PaymentMethod implements Persistable, Comparable {
|
|||
public static PaymentMethod BLOCK_CHAINS;
|
||||
|
||||
public static final List<PaymentMethod> ALL_VALUES = new ArrayList<>(Arrays.asList(
|
||||
OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY, Coin.parseCoin("0.4")), // tx instant so min. wait time
|
||||
PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, 0, DAY, Coin.parseCoin("0.4")),
|
||||
SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY, Coin.parseCoin("0.3")), // sepa takes 1-3 business days. We use 8 days to include safety for holidays
|
||||
NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 0, 4 * DAY, Coin.parseCoin("0.3")),
|
||||
SAME_BANK = new PaymentMethod(SAME_BANK_ID, 0, 2 * DAY, Coin.parseCoin("0.3")),
|
||||
SPECIFIC_BANKS = new PaymentMethod(SPECIFIC_BANKS_ID, 0, 4 * DAY, Coin.parseCoin("0.3")),
|
||||
SWISH = new PaymentMethod(SWISH_ID, 0, DAY, Coin.parseCoin("0.4")),
|
||||
ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY, Coin.parseCoin("0.4")),
|
||||
OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY, Coin.parseCoin("1")), // tx instant so min. wait time
|
||||
SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY, Coin.parseCoin("0.5")), // sepa takes 1-3 business days. We use 8 days to include safety for holidays
|
||||
NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 0, 4 * DAY, Coin.parseCoin("0.5")),
|
||||
SAME_BANK = new PaymentMethod(SAME_BANK_ID, 0, 2 * DAY, Coin.parseCoin("0.5")),
|
||||
SPECIFIC_BANKS = new PaymentMethod(SPECIFIC_BANKS_ID, 0, 4 * DAY, Coin.parseCoin("0.5")),
|
||||
PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, 0, DAY, Coin.parseCoin("0.75")),
|
||||
SWISH = new PaymentMethod(SWISH_ID, 0, DAY, Coin.parseCoin("0.75")),
|
||||
ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY, Coin.parseCoin("0.75")),
|
||||
/* FED_WIRE = new PaymentMethod(FED_WIRE_ID, 0, DAY, Coin.parseCoin("0.1")),*/
|
||||
/* TRANSFER_WISE = new PaymentMethod(TRANSFER_WISE_ID, 0, DAY, Coin.parseCoin("0.1")),*/
|
||||
/* US_POSTAL_MONEY_ORDER = new PaymentMethod(US_POSTAL_MONEY_ORDER_ID, 0, DAY, Coin.parseCoin("0.1")),*/
|
||||
BLOCK_CHAINS = new PaymentMethod(BLOCK_CHAINS_ID, 0, DAY, Coin.parseCoin("0.5"))
|
||||
BLOCK_CHAINS = new PaymentMethod(BLOCK_CHAINS_ID, 0, DAY, Coin.parseCoin("1"))
|
||||
));
|
||||
|
||||
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package io.bitsquare.trade;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.trade.offer.OpenOfferManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class TradableCollections {
|
||||
private static final Logger log = LoggerFactory.getLogger(TradableCollections.class);
|
||||
|
||||
public static List<AddressEntry> getAddressEntriesForAvailableBalance(OpenOfferManager openOfferManager, TradeManager tradeManager, WalletService walletService) {
|
||||
Set<String> reservedTrades = getNotCompletedTradableItems(openOfferManager, tradeManager).stream()
|
||||
.map(tradable -> tradable.getOffer().getId())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
List<AddressEntry> list = new ArrayList<>();
|
||||
list.addAll(walletService.getAddressEntryList().stream()
|
||||
.filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive())
|
||||
.filter(e -> !reservedTrades.contains(e.getOfferId()))
|
||||
.collect(Collectors.toList()));
|
||||
return list;
|
||||
}
|
||||
|
||||
public static Set<Tradable> getNotCompletedTradableItems(OpenOfferManager openOfferManager, TradeManager tradeManager) {
|
||||
return Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
|
||||
.filter(tradable -> !(tradable instanceof Trade) || ((Trade) tradable).getState().getPhase() != Trade.Phase.PAYOUT_PAID)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
110
core/src/main/java/io/bitsquare/trade/TradableHelper.java
Normal file
110
core/src/main/java/io/bitsquare/trade/TradableHelper.java
Normal file
|
@ -0,0 +1,110 @@
|
|||
package io.bitsquare.trade;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.FeePolicy;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.trade.closed.ClosedTradableManager;
|
||||
import io.bitsquare.trade.failed.FailedTradesManager;
|
||||
import io.bitsquare.trade.offer.Offer;
|
||||
import io.bitsquare.trade.offer.OpenOffer;
|
||||
import io.bitsquare.trade.offer.OpenOfferManager;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class TradableHelper {
|
||||
private static final Logger log = LoggerFactory.getLogger(TradableHelper.class);
|
||||
|
||||
public static List<AddressEntry> getAddressEntriesForAvailableBalance(OpenOfferManager openOfferManager, TradeManager tradeManager, WalletService walletService) {
|
||||
Set<String> reservedTradeIds = getNotCompletedTradableItems(openOfferManager, tradeManager).stream()
|
||||
.map(tradable -> tradable.getOffer().getId())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return walletService.getAddressEntryList().stream()
|
||||
.filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive())
|
||||
.filter(e -> !reservedTradeIds.contains(e.getOfferId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static Set<Tradable> getNotCompletedTradableItems(OpenOfferManager openOfferManager, TradeManager tradeManager) {
|
||||
return Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
|
||||
.filter(tradable -> !(tradable instanceof Trade) || ((Trade) tradable).getState().getPhase() != Trade.Phase.PAYOUT_PAID)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static Coin getBalanceInOpenOffer(OpenOffer openOffer) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
Coin balance = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb());
|
||||
// For the seller we add the trade amount
|
||||
if (offer.getDirection() == Offer.Direction.SELL)
|
||||
balance = balance.add(offer.getAmount());
|
||||
|
||||
return balance;
|
||||
}
|
||||
|
||||
public static Coin getBalanceInTrade(Trade trade, WalletService walletService) {
|
||||
AddressEntry addressEntry = walletService.getTradeAddressEntry(trade.getId());
|
||||
Coin balance = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb());
|
||||
// For the seller we add the trade amount
|
||||
if (trade.getContract() != null &&
|
||||
trade.getTradeAmount() != null &&
|
||||
trade.getContract().getSellerPayoutAddressString().equals(addressEntry.getAddressString()))
|
||||
balance = balance.add(trade.getTradeAmount());
|
||||
return balance;
|
||||
}
|
||||
|
||||
public static Coin getAvailableBalance(AddressEntry addressEntry, WalletService walletService,
|
||||
OpenOfferManager openOfferManager, TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
FailedTradesManager failedTradesManager) {
|
||||
Coin balance;
|
||||
Coin totalBalance = walletService.getBalanceForAddress(addressEntry.getAddress());
|
||||
String id = addressEntry.getOfferId();
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(id);
|
||||
Optional<Trade> tradeOptional = tradeManager.getTradeById(id);
|
||||
Optional<Tradable> closedTradableOptional = closedTradableManager.getTradableById(id);
|
||||
Optional<Trade> failedTradesOptional = failedTradesManager.getTradeById(id);
|
||||
|
||||
if (openOfferOptional.isPresent()) {
|
||||
balance = totalBalance.subtract(TradableHelper.getBalanceInOpenOffer(openOfferOptional.get()));
|
||||
} else if (tradeOptional.isPresent()) {
|
||||
Trade trade = tradeOptional.get();
|
||||
if (trade.getState().getPhase() != Trade.Phase.PAYOUT_PAID)
|
||||
balance = totalBalance.subtract(TradableHelper.getBalanceInTrade(trade, walletService));
|
||||
else
|
||||
balance = totalBalance;
|
||||
} else if (closedTradableOptional.isPresent()) {
|
||||
Tradable tradable = closedTradableOptional.get();
|
||||
Coin balanceInTrade = Coin.ZERO;
|
||||
if (tradable instanceof OpenOffer)
|
||||
balanceInTrade = TradableHelper.getBalanceInOpenOffer((OpenOffer) tradable);
|
||||
else if (tradable instanceof Trade)
|
||||
balanceInTrade = TradableHelper.getBalanceInTrade((Trade) tradable, walletService);
|
||||
balance = totalBalance.subtract(balanceInTrade);
|
||||
} else if (failedTradesOptional.isPresent()) {
|
||||
balance = totalBalance.subtract(TradableHelper.getBalanceInTrade(failedTradesOptional.get(), walletService));
|
||||
} else {
|
||||
balance = totalBalance;
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
||||
public static Coin getReservedBalance(Tradable tradable, WalletService walletService) {
|
||||
AddressEntry addressEntry = walletService.getTradeAddressEntry(tradable.getId());
|
||||
Coin balance = walletService.getBalanceForAddress(addressEntry.getAddress());
|
||||
if (tradable instanceof Trade) {
|
||||
Trade trade = (Trade) tradable;
|
||||
if (trade.getState().getPhase().ordinal() < Trade.Phase.PAYOUT_PAID.ordinal())
|
||||
balance = TradableHelper.getBalanceInTrade(trade, walletService);
|
||||
} else if (tradable instanceof OpenOffer) {
|
||||
balance = TradableHelper.getBalanceInOpenOffer((OpenOffer) tradable);
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
}
|
|
@ -147,7 +147,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
public void closeAllOpenOffers(@Nullable Runnable completeHandler) {
|
||||
openOffers.forEach(openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer()));
|
||||
if (completeHandler != null)
|
||||
UserThread.runAfter(completeHandler::run, openOffers.size() * 200 + 300, TimeUnit.MILLISECONDS);
|
||||
UserThread.runAfter(completeHandler::run, openOffers.size() * 100 + 200, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue