diff --git a/common/src/main/java/bisq/common/app/Version.java b/common/src/main/java/bisq/common/app/Version.java index 22fbaa81db9..b005b0ee7c9 100644 --- a/common/src/main/java/bisq/common/app/Version.java +++ b/common/src/main/java/bisq/common/app/Version.java @@ -125,4 +125,6 @@ public static void printVersion() { public static final byte BLIND_VOTE = (byte) 0x01; public static final byte VOTE_REVEAL = (byte) 0x01; public static final byte LOCKUP = (byte) 0x01; + public static final byte ASSET_LISTING_FEE = (byte) 0x01; + public static final byte PROOF_OF_BURN = (byte) 0x01; } diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto index 798dfc51ee9..ae720d65591 100644 --- a/common/src/main/proto/pb.proto +++ b/common/src/main/proto/pb.proto @@ -941,9 +941,8 @@ message PersistableEnvelope { MyVoteList my_vote_list = 21; MyBlindVoteList my_blind_vote_list = 22; MeritList merit_list = 23; - RemovedAssetList removed_asset_list = 24; - DaoStateStore dao_state_store = 25; - MyReputationList my_reputation_list = 26; + DaoStateStore dao_state_store = 24; + MyReputationList my_reputation_list = 25; } } @@ -1370,6 +1369,8 @@ enum TxType { VOTE_REVEAL = 11; LOCKUP = 12; UNLOCK = 13; + ASSET_LISTING_FEE = 14; + PROOF_OF_BURN = 15; } message TxInput { @@ -1416,10 +1417,12 @@ enum TxOutputType { BLIND_VOTE_OP_RETURN_OUTPUT = 11; VOTE_REVEAL_UNLOCK_STAKE_OUTPUT = 12; VOTE_REVEAL_OP_RETURN_OUTPUT = 13; - LOCKUP_OUTPUT = 14; - LOCKUP_OP_RETURN_OUTPUT = 15; - UNLOCK_OUTPUT = 16; - INVALID_OUTPUT = 17; + ASSET_LISTING_FEE_OP_RETURN_OUTPUT = 14; + PROOF_OF_BURN_OP_RETURN_OUTPUT = 15; + LOCKUP_OUTPUT = 16; + LOCKUP_OP_RETURN_OUTPUT = 17; + UNLOCK_OUTPUT = 18; + INVALID_OUTPUT = 19; } message SpentInfo { @@ -1531,14 +1534,6 @@ message RemoveAssetProposal { string ticker_symbol = 1; } -message RemovedAsset { - string ticker_symbol = 1; - string remove_reason = 2; -} -message RemovedAssetList { - repeated RemovedAsset removed_asset = 1; -} - message Role { string uid = 1; string name = 2; diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 08ae08ac16c..a279939a33e 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -30,6 +30,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletsManager; import bisq.core.dao.DaoSetup; +import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.voteresult.VoteResultException; import bisq.core.dao.governance.voteresult.VoteResultService; import bisq.core.filter.FilterManager; @@ -152,6 +153,7 @@ public interface BisqSetupCompleteListener { private final MarketAlerts marketAlerts; private final VoteResultService voteResultService; private final AssetTradeActivityCheck tradeActivityCheck; + private final AssetService assetService; private final BSFormatter formatter; @Setter @Nullable @@ -226,6 +228,7 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, MarketAlerts marketAlerts, VoteResultService voteResultService, AssetTradeActivityCheck tradeActivityCheck, + AssetService assetService, BSFormatter formatter) { @@ -263,6 +266,7 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, this.marketAlerts = marketAlerts; this.voteResultService = voteResultService; this.tradeActivityCheck = tradeActivityCheck; + this.assetService = assetService; this.formatter = formatter; } @@ -630,6 +634,8 @@ public void onBalanceChanged(Coin balance, Transaction tx) { tradeStatisticsManager.onAllServicesInitialized(); tradeActivityCheck.onAllServicesInitialized(); + assetService.onAllServicesInitialized(); + accountAgeWitnessService.onAllServicesInitialized(); priceFeedService.setCurrencyCodeOnInit(); diff --git a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java index 5195ab90e84..82cd737f018 100644 --- a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java +++ b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java @@ -19,7 +19,6 @@ import bisq.core.dao.DaoOptionKeys; import bisq.core.dao.DaoSetup; -import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.ballot.BallotListService; import bisq.core.dao.governance.blindvote.MyBlindVoteListService; import bisq.core.dao.governance.bond.reputation.MyReputationListService; @@ -56,7 +55,6 @@ public AppSetupWithP2PAndDAO(EncryptionService encryptionService, MyBlindVoteListService myBlindVoteListService, MyProposalListService myProposalListService, MyReputationListService myReputationListService, - AssetService assetService, @Named(DaoOptionKeys.DAO_ACTIVATED) boolean daoActivated) { super(encryptionService, keyRing, @@ -74,7 +72,6 @@ public AppSetupWithP2PAndDAO(EncryptionService encryptionService, persistedDataHosts.add(myBlindVoteListService); persistedDataHosts.add(myProposalListService); persistedDataHosts.add(myReputationListService); - persistedDataHosts.add(assetService); } } diff --git a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java index cff029e41cd..25c8be245fe 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -144,17 +144,19 @@ String getWalletAsString(boolean includePrivKeys) { // Public Methods /////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////// - // Proposal txs + // Burn BSQ txs (some proposal txs, asset listing fee tx, proof of burn tx) /////////////////////////////////////////////////////////////////////////////////////////// - - public Transaction completePreparedProposalTx(Transaction preparedBurnFeeTx, byte[] opReturnData) + public Transaction completePreparedBurnBsqTx(Transaction preparedBurnFeeTx, byte[] opReturnData) throws WalletException, InsufficientMoneyException, TransactionVerificationException { return completePreparedProposalTx(preparedBurnFeeTx, opReturnData, null, null); } + /////////////////////////////////////////////////////////////////////////////////////////// + // Proposal txs + /////////////////////////////////////////////////////////////////////////////////////////// + public Transaction completePreparedReimbursementRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData) throws TransactionVerificationException, WalletException, InsufficientMoneyException { return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress); diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index 5ea20900cfc..7d3b4fef8ca 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -182,10 +182,6 @@ public DaoFacade(MyProposalListService myProposalListService, @Override public void addListeners() { - } - - @Override - public void start() { daoStateService.addBsqStateListener(new DaoStateListener() { @Override public void onNewBlockHeight(int blockHeight) { @@ -203,6 +199,10 @@ public void onParseBlockChainComplete() { }); } + @Override + public void start() { + } + public void addBsqStateListener(DaoStateListener listener) { daoStateService.addBsqStateListener(listener); diff --git a/core/src/main/java/bisq/core/dao/DaoSetup.java b/core/src/main/java/bisq/core/dao/DaoSetup.java index b418d1d535e..74bf5e1dd83 100644 --- a/core/src/main/java/bisq/core/dao/DaoSetup.java +++ b/core/src/main/java/bisq/core/dao/DaoSetup.java @@ -17,6 +17,7 @@ package bisq.core.dao; +import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.ballot.BallotListService; import bisq.core.dao.governance.blindvote.BlindVoteListService; import bisq.core.dao.governance.blindvote.MyBlindVoteListService; @@ -64,6 +65,7 @@ public DaoSetup(BsqNodeProvider bsqNodeProvider, BondedRolesRepository bondedRolesRepository, MyReputationListService myReputationListService, MyBondedReputationRepository myBondedReputationRepository, + AssetService assetService, DaoFacade daoFacade, ExportJsonFilesService exportJsonFilesService) { @@ -83,6 +85,7 @@ public DaoSetup(BsqNodeProvider bsqNodeProvider, daoSetupServices.add(bondedRolesRepository); daoSetupServices.add(myReputationListService); daoSetupServices.add(myBondedReputationRepository); + daoSetupServices.add(assetService); daoSetupServices.add(daoFacade); daoSetupServices.add(exportJsonFilesService); daoSetupServices.add(bsqNodeProvider.getBsqNode()); diff --git a/core/src/main/java/bisq/core/dao/governance/asset/AssetConsensus.java b/core/src/main/java/bisq/core/dao/governance/asset/AssetConsensus.java new file mode 100644 index 00000000000..f144f00eb3e --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/asset/AssetConsensus.java @@ -0,0 +1,65 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.dao.governance.asset; + +import bisq.core.dao.governance.param.Param; +import bisq.core.dao.state.DaoStateService; +import bisq.core.dao.state.model.blockchain.OpReturnType; + +import bisq.common.app.Version; +import bisq.common.crypto.Hash; + +import org.bitcoinj.core.Coin; + +import com.google.common.base.Charsets; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AssetConsensus { + public static Coin getFeePerDay(DaoStateService daoStateService, int chainHeight) { + return daoStateService.getParamValueAsCoin(Param.ASSET_LISTING_FEE_PER_DAY, chainHeight); + } + + public static byte[] getHash(StatefulAsset statefulAsset) { + String stringInput = "AssetListingFee-" + statefulAsset.getTickerSymbol(); + final byte[] bytes = stringInput.getBytes(Charsets.UTF_8); + return Hash.getSha256Ripemd160hash(bytes); + } + + public static byte[] getOpReturnData(byte[] hash) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + outputStream.write(OpReturnType.ASSET_LISTING_FEE.getType()); + outputStream.write(Version.ASSET_LISTING_FEE); + outputStream.write(hash); + return outputStream.toByteArray(); + } catch (IOException e) { + // Not expected to happen ever + e.printStackTrace(); + log.error(e.toString()); + return new byte[0]; + } + } + + public static boolean hasOpReturnDataValidLength(byte[] opReturnData) { + return opReturnData.length == 22; + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java b/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java index 1ac69f94c1a..41653405a87 100644 --- a/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java +++ b/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java @@ -17,25 +17,82 @@ package bisq.core.dao.governance.asset; -import bisq.core.app.BisqEnvironment; +import bisq.core.btc.exceptions.TransactionVerificationException; +import bisq.core.btc.exceptions.TxBroadcastException; +import bisq.core.btc.exceptions.WalletException; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.TxBroadcaster; +import bisq.core.btc.wallet.WalletsManager; +import bisq.core.dao.DaoSetupService; +import bisq.core.dao.governance.param.Param; +import bisq.core.dao.governance.proposal.TxException; +import bisq.core.dao.state.DaoStateListener; +import bisq.core.dao.state.DaoStateService; +import bisq.core.dao.state.model.blockchain.BaseTx; +import bisq.core.dao.state.model.blockchain.Block; +import bisq.core.dao.state.model.blockchain.Tx; +import bisq.core.dao.state.model.governance.RemoveAssetProposal; import bisq.core.locale.CurrencyUtil; +import bisq.core.trade.statistics.TradeStatistics2; +import bisq.core.trade.statistics.TradeStatisticsManager; -import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; +import bisq.common.Timer; +import bisq.common.UserThread; +import bisq.common.app.DevEnv; +import bisq.common.handlers.ErrorMessageHandler; +import bisq.common.handlers.ResultHandler; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; +import org.bitcoinj.core.Transaction; import javax.inject.Inject; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; + +import javafx.collections.SetChangeListener; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + +import static com.google.common.base.Preconditions.checkArgument; + @Slf4j -public class AssetService implements PersistedDataHost { - private final Storage storage; +public class AssetService implements DaoSetupService, DaoStateListener { + private static final long DEFAULT_LOOK_BACK_PERIOD = 120; // 120 days + + private final BsqWalletService bsqWalletService; + private final BtcWalletService btcWalletService; + private final WalletsManager walletsManager; + private final TradeStatisticsManager tradeStatisticsManager; + private final DaoStateService daoStateService; + + @Getter + private IntegerProperty updateFlag = new SimpleIntegerProperty(0); @Getter - private final RemovedAssetsList removedAssetsList = new RemovedAssetsList(); + private final List statefulAssets = new ArrayList<>(); + private Map> tradeStatsByTickerSymbol; + private long bsqFeePerDay; + private long minVolumeInBtc; + private Timer timer; /////////////////////////////////////////////////////////////////////////////////////////// @@ -43,73 +100,253 @@ public class AssetService implements PersistedDataHost { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public AssetService(Storage storage) { - this.storage = storage; + public AssetService(BsqWalletService bsqWalletService, + BtcWalletService btcWalletService, + WalletsManager walletsManager, + TradeStatisticsManager tradeStatisticsManager, + DaoStateService daoStateService) { + this.bsqWalletService = bsqWalletService; + this.btcWalletService = btcWalletService; + this.walletsManager = walletsManager; + this.tradeStatisticsManager = tradeStatisticsManager; + this.daoStateService = daoStateService; } + public void onAllServicesInitialized() { + tradeStatsByTickerSymbol = getTradeStatsByTickerSymbol(); + tradeStatisticsManager.getObservableTradeStatisticsSet().addListener((SetChangeListener) change -> { + // At startup if a user has downloaded the app long after the release he might receive a lots of trade statistic + // objects from the seed node. We don't want to trigger the expensive getTradeStatsByTickerSymbol call in + // between so we delay 20 sec. to be sure to call it after the data has been processed. + // To use a listener would be better but that requires bigger effort at the p2p lib side. + if (timer == null) + timer = UserThread.runAfter(() -> { + tradeStatsByTickerSymbol = getTradeStatsByTickerSymbol(); + updateList(); + timer = null; + }, 20); + }); + } /////////////////////////////////////////////////////////////////////////////////////////// - // PersistedDataHost + // DaoSetupService /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void readPersisted() { - if (BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq()) { - RemovedAssetsList persisted = storage.initAndGetPersisted(removedAssetsList, 100); - if (persisted != null) { - removedAssetsList.clear(); - removedAssetsList.addAll(persisted.getList()); + public void addListeners() { + daoStateService.addBsqStateListener(this); + } + + @Override + public void start() { + statefulAssets.clear(); + statefulAssets.addAll(CurrencyUtil.getSortedAssetStream() + .filter(asset -> !asset.getTickerSymbol().equals("BSQ")) + .map(StatefulAsset::new) + .collect(Collectors.toList())); + } + + public void updateList() { + if (tradeStatsByTickerSymbol == null) + return; + + statefulAssets.forEach(statefulAsset -> { + AssetState assetState; + if (wasAssetRemovedByVoting(statefulAsset.getTickerSymbol())) { + assetState = AssetState.REMOVED_BY_VOTING; + } else { + statefulAsset.setFeePayments(getFeePayments(statefulAsset)); + + long lookBackPeriodInDays = getLookBackPeriodInDays(statefulAsset); + statefulAsset.setLookBackPeriodInDays(lookBackPeriodInDays); + long tradeVolume = getTradeVolume(statefulAsset, lookBackPeriodInDays); + statefulAsset.setTradeVolume(tradeVolume); + if (isInTrialPeriod(statefulAsset)) { + assetState = AssetState.IN_TRIAL_PERIOD; + } else if (tradeVolume >= minVolumeInBtc) { + assetState = AssetState.ACTIVELY_TRADED; + } else { + assetState = AssetState.DE_LISTED; + } } + statefulAsset.setAssetState(assetState); + }); + + updateFlag.set(updateFlag.get() + 1); + } + + private Map> getTradeStatsByTickerSymbol() { + Map> map = new HashMap<>(); + tradeStatisticsManager.getObservableTradeStatisticsSet().stream() + .filter(e -> CurrencyUtil.isCryptoCurrency(e.getBaseCurrency())) + .forEach(e -> { + map.putIfAbsent(e.getBaseCurrency(), new ArrayList<>()); + map.get(e.getBaseCurrency()).add(e); + }); + return map; + } + + private boolean isInTrialPeriod(StatefulAsset statefulAsset) { + for (FeePayment feePayment : statefulAsset.getFeePayments()) { + Optional passedDays = feePayment.getPassedDays(daoStateService); + if (passedDays.isPresent()) { + long daysCoveredByFee = feePayment.daysCoveredByFee(bsqFeePerDay); + if (daysCoveredByFee >= passedDays.get()) { + return true; + } + } + } + return false; + } + + private long getTradeVolume(StatefulAsset statefulAsset, long lookBackPeriodInDays) { + String tickerSymbol = statefulAsset.getTickerSymbol(); + if (tradeStatsByTickerSymbol.containsKey(tickerSymbol)) { + List tradeStatisticsForAsset = tradeStatsByTickerSymbol.get(tickerSymbol); + return getTradeVolume(tradeStatisticsForAsset, lookBackPeriodInDays); + } else { + return 0; } } + @NotNull + private Long getLookBackPeriodInDays(StatefulAsset statefulAsset) { + return statefulAsset.getLastFeePayment() + .map(feePayment -> feePayment.daysCoveredByFee(bsqFeePerDay)) + .orElse(DEFAULT_LOOK_BACK_PERIOD); + } + + private long getTradeVolume(List tradeStatisticsForAsset, long lookBackPeriodInDays) { + // We cannot use blocks as the block height is not in the TradeStatistics2 object and the lookup for all the + // deposit txs would be too expensive. + long lookBackPeriodInMs = TimeUnit.DAYS.toMillis(lookBackPeriodInDays); + AtomicLong accumulatedTradeAmount = new AtomicLong(0); + long now = new Date().getTime(); + tradeStatisticsForAsset.forEach(stat -> { + long timePassed = now - stat.getTradeDate().getTime(); + if (timePassed < lookBackPeriodInMs) { + accumulatedTradeAmount.addAndGet(stat.getTradeAmount().value); + } + }); + return accumulatedTradeAmount.get(); + } + + private List getFeePayments(StatefulAsset statefulAsset) { + return getFeeTxs(statefulAsset).stream() + .map(tx -> { + String txId = tx.getId(); + long burntFee = tx.getBurntFee(); + return new FeePayment(txId, burntFee); + }) + .collect(Collectors.toList()); + } + + private List getFeeTxs(StatefulAsset statefulAsset) { + return daoStateService.getAssetListingFeeOpReturnTxOutputs().stream() + .filter(txOutput -> { + byte[] hash = AssetConsensus.getHash(statefulAsset); + byte[] opReturnData = AssetConsensus.getOpReturnData(hash); + return Arrays.equals(opReturnData, txOutput.getOpReturnData()); + }) + .map(txOutput -> daoStateService.getTx(txOutput.getTxId()).orElse(null)) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(BaseTx::getTime)) + .collect(Collectors.toList()); + } + /////////////////////////////////////////////////////////////////////////////////////////// - // API + // DaoStateListener /////////////////////////////////////////////////////////////////////////////////////////// - public void addToRemovedAssetsListByVoting(String tickerSymbol) { - log.info("Asset '{}' was removed by DAO voting", CurrencyUtil.getNameAndCode(tickerSymbol)); - removedAssetsList.add(new RemovedAsset(tickerSymbol, RemoveReason.VOTING)); - persist(); + @Override + public void onNewBlockHeight(int blockHeight) { } - public boolean hasPaidBSQFee(String tickerSymbol) { - //TODO - return false; + @Override + public void onParseTxsComplete(Block block) { + int chainHeight = daoStateService.getChainHeight(); + bsqFeePerDay = daoStateService.getParamValueAsCoin(Param.ASSET_LISTING_FEE_PER_DAY, chainHeight).value; + minVolumeInBtc = daoStateService.getParamValueAsCoin(Param.ASSET_MIN_VOLUME, chainHeight).value; + updateList(); + + } + + @Override + public void onParseBlockChainComplete() { } - public boolean isAssetRemoved(String tickerSymbol) { - boolean isRemoved = removedAssetsList.getList().stream() - .anyMatch(removedAsset -> removedAsset.getTickerSymbol().equals(tickerSymbol)); + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public boolean wasAssetRemovedByVoting(String tickerSymbol) { + boolean isRemoved = getRemoveAssetProposalStream() + .anyMatch(proposal -> proposal.getTickerSymbol().equals(tickerSymbol)); if (isRemoved) log.info("Asset '{}' was removed", CurrencyUtil.getNameAndCode(tickerSymbol)); return isRemoved; } - public boolean isAssetRemovedByVoting1(String tickerSymbol) { - boolean isRemoved = getRemovedAssetsByRemoveReason(RemoveReason.VOTING).stream() - .anyMatch(removedAsset -> removedAsset.getTickerSymbol().equals(tickerSymbol)); - if (isRemoved) - log.info("Asset '{}' was removed by DAO voting", CurrencyUtil.getNameAndCode(tickerSymbol)); - - return isRemoved; + public boolean isActive(String tickerSymbol) { + return DevEnv.isDaoActivated() ? findAsset(tickerSymbol).map(StatefulAsset::isActive).orElse(false) : true; } + private Optional findAsset(String tickerSymbol) { + return statefulAssets.stream().filter(e -> e.getTickerSymbol().equals(tickerSymbol)).findAny(); + } /////////////////////////////////////////////////////////////////////////////////////////// // Private /////////////////////////////////////////////////////////////////////////////////////////// - private List getRemovedAssetsByRemoveReason(RemoveReason removeReason) { - return removedAssetsList.getList().stream() - .filter(e -> e.getRemoveReason() == removeReason) - .collect(Collectors.toList()); + private Stream getRemoveAssetProposalStream() { + return daoStateService.getEvaluatedProposalList().stream() + .filter(evaluatedProposal -> evaluatedProposal.getProposal() instanceof RemoveAssetProposal) + .map(e -> ((RemoveAssetProposal) e.getProposal())); } - private void persist() { - storage.queueUpForSave(20); + + public Transaction payFee(StatefulAsset statefulAsset, long listingFee) throws InsufficientMoneyException, TxException { + checkArgument(!statefulAsset.wasRemovedByVoting(), "Asset must not have been removed"); + checkArgument(listingFee >= getFeePerDay().value, "Fee must not be less then listing fee for 1 day."); + checkArgument(listingFee % 100 == 0, "Fee must be a multiple of 1 BSQ (100 satoshi)."); + try { + // We create a prepared Bsq Tx for the listing fee. + final Transaction preparedBurnFeeTx = bsqWalletService.getPreparedBurnFeeTx(Coin.valueOf(listingFee)); + byte[] hash = AssetConsensus.getHash(statefulAsset); + byte[] opReturnData = AssetConsensus.getOpReturnData(hash); + // We add the BTC inputs for the miner fee. + final Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData); + // We sign the BSQ inputs of the final tx. + Transaction transaction = bsqWalletService.signTx(txWithBtcFee); + log.info("Asset listing fee tx: " + transaction); + return transaction; + } catch (WalletException | TransactionVerificationException e) { + throw new TxException(e); + } + } + + public Coin getFeePerDay() { + return AssetConsensus.getFeePerDay(daoStateService, daoStateService.getChainHeight()); + } + + // Broadcast tx and publish proposal to P2P network + public void publishTransaction(StatefulAsset statefulAsset, Transaction transaction, long listingFee, ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { + walletsManager.publishAndCommitBsqTx(transaction, new TxBroadcaster.Callback() { + @Override + public void onSuccess(Transaction transaction) { + log.info("Asset listing fee tx has been published. TxId={}", transaction.getHashAsString()); + resultHandler.handleResult(); + } + + @Override + public void onFailure(TxBroadcastException exception) { + errorMessageHandler.handleErrorMessage(exception.getMessage()); + } + }); } } diff --git a/core/src/main/java/bisq/core/dao/governance/asset/RemoveReason.java b/core/src/main/java/bisq/core/dao/governance/asset/AssetState.java similarity index 77% rename from core/src/main/java/bisq/core/dao/governance/asset/RemoveReason.java rename to core/src/main/java/bisq/core/dao/governance/asset/AssetState.java index 81635722b10..24fed67670a 100644 --- a/core/src/main/java/bisq/core/dao/governance/asset/RemoveReason.java +++ b/core/src/main/java/bisq/core/dao/governance/asset/AssetState.java @@ -17,7 +17,13 @@ package bisq.core.dao.governance.asset; -public enum RemoveReason { - VOTING, - INACTIVITY +/** + * Maintain translation stings ("dao.assetState.*") + */ +public enum AssetState { + UNDEFINED, + IN_TRIAL_PERIOD, + ACTIVELY_TRADED, + DE_LISTED, + REMOVED_BY_VOTING // Was removed by voting } diff --git a/core/src/main/java/bisq/core/dao/governance/asset/FeePayment.java b/core/src/main/java/bisq/core/dao/governance/asset/FeePayment.java new file mode 100644 index 00000000000..9bfa435d577 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/asset/FeePayment.java @@ -0,0 +1,58 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.dao.governance.asset; + +import bisq.core.dao.state.DaoStateService; +import bisq.core.dao.state.model.blockchain.Tx; + +import java.util.Optional; + +import lombok.Value; + +@Value +public class FeePayment { + private final String txId; + private final long fee; + + FeePayment(String txId, long fee) { + this.txId = txId; + this.fee = fee; + } + + public long daysCoveredByFee(long bsqFeePerDay) { + return fee / bsqFeePerDay; + } + + public Optional getPassedDays(DaoStateService daoStateService) { + Optional optionalTx = daoStateService.getTx(txId); + if (optionalTx.isPresent()) { + int passedBlocks = daoStateService.getChainHeight() - optionalTx.get().getBlockHeight(); + return Optional.of(passedBlocks / 144); + } else { + return Optional.empty(); + } + } + + @Override + public String toString() { + return "FeePayment{" + + "\n txId='" + txId + '\'' + + ",\n fee=" + fee + + "\n}"; + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/asset/RemovedAsset.java b/core/src/main/java/bisq/core/dao/governance/asset/RemovedAsset.java deleted file mode 100644 index 122693c2b59..00000000000 --- a/core/src/main/java/bisq/core/dao/governance/asset/RemovedAsset.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.dao.governance.asset; - -import bisq.common.proto.ProtoUtil; -import bisq.common.proto.persistable.PersistablePayload; - -import io.bisq.generated.protobuffer.PB; - -import java.util.Objects; - -import lombok.Value; - -@Value -public class RemovedAsset implements PersistablePayload { - private final String tickerSymbol; - private final RemoveReason removeReason; - - RemovedAsset(String tickerSymbol, RemoveReason removeReason) { - this.tickerSymbol = tickerSymbol; - this.removeReason = removeReason; - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // PROTO BUFFER - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public PB.RemovedAsset toProtoMessage() { - PB.RemovedAsset.Builder builder = PB.RemovedAsset.newBuilder() - .setTickerSymbol(tickerSymbol) - .setRemoveReason(removeReason.name()); - return builder.build(); - } - - public static RemovedAsset fromProto(PB.RemovedAsset proto) { - return new RemovedAsset(proto.getTickerSymbol(), - ProtoUtil.enumFromProto(RemoveReason.class, proto.getRemoveReason())); - } - - // Enums must not be used directly for hashCode or equals as it delivers the Object.hashCode (internal address)! - // The equals and hashCode methods cannot be overwritten in Enums. - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof RemovedAsset)) return false; - if (!super.equals(o)) return false; - RemovedAsset that = (RemovedAsset) o; - return Objects.equals(tickerSymbol, that.tickerSymbol) && - removeReason.name().equals(that.removeReason.name()); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), tickerSymbol, removeReason.name()); - } -} diff --git a/core/src/main/java/bisq/core/dao/governance/asset/RemovedAssetsList.java b/core/src/main/java/bisq/core/dao/governance/asset/RemovedAssetsList.java deleted file mode 100644 index 9c655886cea..00000000000 --- a/core/src/main/java/bisq/core/dao/governance/asset/RemovedAssetsList.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.dao.governance.asset; - -import bisq.core.dao.governance.ConsensusCritical; - -import bisq.common.proto.persistable.PersistableList; - -import io.bisq.generated.protobuffer.PB; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import lombok.EqualsAndHashCode; - -/** - * PersistableEnvelope wrapper for list of removedAssets. - */ -@EqualsAndHashCode(callSuper = true) -public class RemovedAssetsList extends PersistableList implements ConsensusCritical { - - public RemovedAssetsList(List list) { - super(list); - } - - public RemovedAssetsList() { - super(); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // PROTO BUFFER - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public PB.PersistableEnvelope toProtoMessage() { - return PB.PersistableEnvelope.newBuilder().setRemovedAssetList(getBuilder()).build(); - } - - public PB.RemovedAssetList.Builder getBuilder() { - return PB.RemovedAssetList.newBuilder() - .addAllRemovedAsset(getList().stream() - .map(RemovedAsset::toProtoMessage) - .collect(Collectors.toList())); - } - - public static RemovedAssetsList fromProto(PB.RemovedAssetList proto) { - return new RemovedAssetsList(new ArrayList<>(proto.getRemovedAssetList().stream() - .map(RemovedAsset::fromProto) - .collect(Collectors.toList()))); - } - - @Override - public String toString() { - return "List of tickerSymbols in RemovedAssetList: " + getList().stream() - .map(RemovedAsset::getTickerSymbol) - .collect(Collectors.toList()); - } -} diff --git a/core/src/main/java/bisq/core/dao/governance/asset/StatefulAsset.java b/core/src/main/java/bisq/core/dao/governance/asset/StatefulAsset.java new file mode 100644 index 00000000000..dca2a1650b2 --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/asset/StatefulAsset.java @@ -0,0 +1,107 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.dao.governance.asset; + +import bisq.core.locale.CurrencyUtil; + +import bisq.asset.Asset; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +public class StatefulAsset { + private final Asset asset; + @Setter + private AssetState assetState = AssetState.UNDEFINED; + private List feePayments = new ArrayList<>(); + @Setter + private long tradeVolume; + @Setter + private long lookBackPeriodInDays; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + public StatefulAsset(Asset asset) { + this.asset = asset; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public String getNameAndCode() { + return CurrencyUtil.getNameAndCode(getTickerSymbol()); + } + + public String getTickerSymbol() { + return asset.getTickerSymbol(); + } + + public void setFeePayments(List feePayments) { + this.feePayments = feePayments; + } + + public Optional getLastFeePayment() { + return feePayments.isEmpty() ? Optional.empty() : Optional.of(feePayments.get(feePayments.size() - 1)); + } + + public long getTotalFeesPaid() { + return feePayments.stream().mapToLong(FeePayment::getFee).sum(); + } + + public long getFeeOfTrialPeriod() { + return getLastFeePayment() + .map(FeePayment::getFee) + .filter(e -> assetState == AssetState.IN_TRIAL_PERIOD) + .orElse(0L); + } + + public boolean isActive() { + return !wasRemovedByVoting() && !isDeListed(); + } + + public boolean wasRemovedByVoting() { + return assetState == AssetState.REMOVED_BY_VOTING; + } + + public boolean isDeListed() { + return assetState == AssetState.DE_LISTED; + } + + + @Override + public String toString() { + return "StatefulAsset{" + + "\n asset=" + asset + + ",\n assetState=" + assetState + + ",\n feePayments=" + feePayments + + ",\n tradeVolume=" + tradeVolume + + ",\n lookBackPeriodInDays=" + lookBackPeriodInDays + + "\n}"; + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/param/Param.java b/core/src/main/java/bisq/core/dao/governance/param/Param.java index 550ebd2e6e9..4b4952bac14 100644 --- a/core/src/main/java/bisq/core/dao/governance/param/Param.java +++ b/core/src/main/java/bisq/core/dao/governance/param/Param.java @@ -105,7 +105,10 @@ public enum Param { "2N4mVTpUZAnhm9phnxB7VrHB4aBhnWrcUrV", // testnet ParamType.ADDRESS), - //TODO add asset listing params (nr. of trades, volume, time, fee which defines listing state) + // Fee for activating an asset or re-listing after deactivation due lack of trade activity. Fee per day of trial period without activity checks. + ASSET_LISTING_FEE_PER_DAY("1", ParamType.BSQ, 10, 10), + // Min required trade volume to not get de-listed. Check starts after trial period and use trial period afterwards to look back for trade activity. + ASSET_MIN_VOLUME("0.01", ParamType.BTC, 10, 10), // TODO for dev testing we use short periods... // Period phase ("11 blocks atm) diff --git a/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnConsensus.java b/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnConsensus.java new file mode 100644 index 00000000000..1870f419fab --- /dev/null +++ b/core/src/main/java/bisq/core/dao/governance/proofofburn/ProofOfBurnConsensus.java @@ -0,0 +1,53 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.dao.governance.proofofburn; + +import bisq.core.dao.state.model.blockchain.OpReturnType; + +import bisq.common.app.Version; +import bisq.common.crypto.Hash; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ProofOfBurnConsensus { + public static byte[] getHash(byte[] bytes) { + return Hash.getSha256Ripemd160hash(bytes); + } + + public static byte[] getOpReturnData(byte[] hash) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + outputStream.write(OpReturnType.PROOF_OF_BURN.getType()); + outputStream.write(Version.PROOF_OF_BURN); + outputStream.write(hash); + return outputStream.toByteArray(); + } catch (IOException e) { + // Not expected to happen ever + e.printStackTrace(); + log.error(e.toString()); + return new byte[0]; + } + } + + public static boolean hasOpReturnDataValidLength(byte[] opReturnData) { + return opReturnData.length == 22; + } +} diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java b/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java index dbecf46283e..26b187bd288 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java @@ -112,6 +112,6 @@ protected byte[] getOpReturnData(byte[] hashOfPayload) { protected Transaction completeTx(Transaction preparedBurnFeeTx, byte[] opReturnData, Proposal proposal) throws WalletException, InsufficientMoneyException, TransactionVerificationException { - return btcWalletService.completePreparedProposalTx(preparedBurnFeeTx, opReturnData); + return btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData); } } diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java index b0fad9b88ae..f647105f6df 100644 --- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java @@ -18,14 +18,12 @@ package bisq.core.dao.governance.voteresult; import bisq.core.dao.DaoSetupService; -import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.ballot.BallotListService; import bisq.core.dao.governance.blindvote.BlindVote; import bisq.core.dao.governance.blindvote.BlindVoteConsensus; import bisq.core.dao.governance.blindvote.BlindVoteListService; import bisq.core.dao.governance.blindvote.VoteWithProposalTxId; import bisq.core.dao.governance.blindvote.VoteWithProposalTxIdList; -import bisq.core.dao.governance.bond.role.BondedRolesRepository; import bisq.core.dao.governance.merit.MeritConsensus; import bisq.core.dao.governance.period.PeriodService; import bisq.core.dao.governance.proposal.IssuanceProposal; @@ -102,9 +100,7 @@ public class VoteResultService implements DaoStateListener, DaoSetupService { private final PeriodService periodService; private final BallotListService ballotListService; private final BlindVoteListService blindVoteListService; - private final BondedRolesRepository bondedRolesRepository; private final IssuanceService issuanceService; - private final AssetService assetService; private final MissingDataRequestService missingDataRequestService; @Getter private final ObservableList voteResultExceptions = FXCollections.observableArrayList(); @@ -121,9 +117,7 @@ public VoteResultService(VoteRevealService voteRevealService, PeriodService periodService, BallotListService ballotListService, BlindVoteListService blindVoteListService, - BondedRolesRepository bondedRolesRepository, IssuanceService issuanceService, - AssetService assetService, MissingDataRequestService missingDataRequestService) { this.voteRevealService = voteRevealService; this.proposalListPresentation = proposalListPresentation; @@ -131,9 +125,7 @@ public VoteResultService(VoteRevealService voteRevealService, this.periodService = periodService; this.ballotListService = ballotListService; this.blindVoteListService = blindVoteListService; - this.bondedRolesRepository = bondedRolesRepository; this.issuanceService = issuanceService; - this.assetService = assetService; this.missingDataRequestService = missingDataRequestService; } @@ -677,8 +669,6 @@ private void applyRemoveAsset(Set acceptedEvaluatedProposals, if (evaluatedProposal.getProposal() instanceof RemoveAssetProposal) { RemoveAssetProposal removeAssetProposal = (RemoveAssetProposal) evaluatedProposal.getProposal(); String tickerSymbol = removeAssetProposal.getTickerSymbol(); - assetService.addToRemovedAssetsListByVoting(tickerSymbol); - StringBuilder sb = new StringBuilder(); sb.append("\n################################################################################\n"); sb.append("We removed an asset. ProposalTxId=").append(removeAssetProposal.getTxId()) diff --git a/core/src/main/java/bisq/core/dao/node/explorer/JsonTxOutputType.java b/core/src/main/java/bisq/core/dao/node/explorer/JsonTxOutputType.java index 15bfab0f2c4..5b21bd5bcb3 100644 --- a/core/src/main/java/bisq/core/dao/node/explorer/JsonTxOutputType.java +++ b/core/src/main/java/bisq/core/dao/node/explorer/JsonTxOutputType.java @@ -34,6 +34,8 @@ enum JsonTxOutputType { BLIND_VOTE_OP_RETURN_OUTPUT("Blind vote opReturn"), VOTE_REVEAL_UNLOCK_STAKE_OUTPUT("Vote reveal unlock stake"), VOTE_REVEAL_OP_RETURN_OUTPUT("Vote reveal opReturn"), + ASSET_LISTING_FEE_OP_RETURN_OUTPUT("Asset listing fee OpReturn"), + PROOF_OF_BURN_OP_RETURN_OUTPUT("Proof of burn opReturn"), LOCKUP_OUTPUT("Lockup"), LOCKUP_OP_RETURN_OUTPUT("Lockup opReturn"), UNLOCK_OUTPUT("Unlock"), diff --git a/core/src/main/java/bisq/core/dao/node/explorer/JsonTxType.java b/core/src/main/java/bisq/core/dao/node/explorer/JsonTxType.java index f0a47960b83..b58c2b6723e 100644 --- a/core/src/main/java/bisq/core/dao/node/explorer/JsonTxType.java +++ b/core/src/main/java/bisq/core/dao/node/explorer/JsonTxType.java @@ -33,7 +33,9 @@ enum JsonTxType { BLIND_VOTE("Blind vote"), VOTE_REVEAL("Vote reveal"), LOCKUP("Lockup"), - UNLOCK("Unlock"); + UNLOCK("Unlock"), + ASSET_LISTING_FEE("Asset listing fee"), + PROOF_OF_BURN("Proof of burn"); @Getter private String displayString; diff --git a/core/src/main/java/bisq/core/dao/node/parser/OpReturnParser.java b/core/src/main/java/bisq/core/dao/node/parser/OpReturnParser.java index 75355c5fd56..6c56b0672d6 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/OpReturnParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/OpReturnParser.java @@ -17,9 +17,11 @@ package bisq.core.dao.node.parser; +import bisq.core.dao.governance.asset.AssetConsensus; import bisq.core.dao.governance.blindvote.BlindVoteConsensus; import bisq.core.dao.governance.bond.BondConsensus; import bisq.core.dao.governance.bond.lockup.LockupReason; +import bisq.core.dao.governance.proofofburn.ProofOfBurnConsensus; import bisq.core.dao.governance.proposal.ProposalConsensus; import bisq.core.dao.governance.voteresult.VoteResultConsensus; import bisq.core.dao.node.parser.exceptions.InvalidParsingConditionException; @@ -119,6 +121,16 @@ static TxOutputType getTxOutputType(TempTxOutput tempTxOutput) { } else { break; } + case ASSET_LISTING_FEE: + if (AssetConsensus.hasOpReturnDataValidLength(opReturnData)) + return TxOutputType.ASSET_LISTING_FEE_OP_RETURN_OUTPUT; + else + break; + case PROOF_OF_BURN: + if (ProofOfBurnConsensus.hasOpReturnDataValidLength(opReturnData)) + return TxOutputType.PROOF_OF_BURN_OP_RETURN_OUTPUT; + else + break; default: throw new InvalidParsingConditionException("We must have a defined opReturnType as it was checked earlier in the caller."); } diff --git a/core/src/main/java/bisq/core/dao/node/parser/TxInputParser.java b/core/src/main/java/bisq/core/dao/node/parser/TxInputParser.java index c842f9efc39..6f4b111bd9a 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/TxInputParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/TxInputParser.java @@ -100,6 +100,8 @@ void process(TxOutputKey txOutputKey, int blockHeight, String txId, int inputInd case BLIND_VOTE_OP_RETURN_OUTPUT: case VOTE_REVEAL_UNLOCK_STAKE_OUTPUT: case VOTE_REVEAL_OP_RETURN_OUTPUT: + case ASSET_LISTING_FEE_OP_RETURN_OUTPUT: + case PROOF_OF_BURN_OP_RETURN_OUTPUT: break; case LOCKUP_OUTPUT: // A LOCKUP BSQ txOutput is spent to a corresponding UNLOCK diff --git a/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java b/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java index ed93be47ff2..68bf31d1e56 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java @@ -240,6 +240,10 @@ static Optional getMappedOpReturnType(TxOutputType outputType) { return Optional.of(OpReturnType.VOTE_REVEAL); case LOCKUP_OP_RETURN_OUTPUT: return Optional.of(OpReturnType.LOCKUP); + case ASSET_LISTING_FEE_OP_RETURN_OUTPUT: + return Optional.of(OpReturnType.ASSET_LISTING_FEE); + case PROOF_OF_BURN_OP_RETURN_OUTPUT: + return Optional.of(OpReturnType.PROOF_OF_BURN); default: return Optional.empty(); } diff --git a/core/src/main/java/bisq/core/dao/node/parser/TxParser.java b/core/src/main/java/bisq/core/dao/node/parser/TxParser.java index 39eb276f791..8badd3d168f 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/TxParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/TxParser.java @@ -203,6 +203,8 @@ private void applyTxTypeAndTxOutputType(int blockHeight, TempTx tempTx, long bsq processVoteReveal(blockHeight, tempTx); break; case LOCKUP: + case ASSET_LISTING_FEE: + case PROOF_OF_BURN: // do nothing break; } @@ -422,6 +424,10 @@ static TxType evaluateTxTypeFromOpReturnType(TempTx tempTx, OpReturnType opRetur return TxType.VOTE_REVEAL; case LOCKUP: return TxType.LOCKUP; + case ASSET_LISTING_FEE: + return TxType.ASSET_LISTING_FEE; + case PROOF_OF_BURN: + return TxType.PROOF_OF_BURN; default: log.warn("We got a BSQ tx with an unknown OP_RETURN. tx={}, opReturnType={}", tempTx, opReturnType); return TxType.INVALID; diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateService.java b/core/src/main/java/bisq/core/dao/state/DaoStateService.java index acee24eb0cc..fb20bd81a87 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateService.java @@ -418,6 +418,9 @@ public boolean isTxOutputSpendable(TxOutputKey key) { case VOTE_REVEAL_UNLOCK_STAKE_OUTPUT: case VOTE_REVEAL_OP_RETURN_OUTPUT: return true; + case ASSET_LISTING_FEE_OP_RETURN_OUTPUT: + case PROOF_OF_BURN_OP_RETURN_OUTPUT: + return false; case LOCKUP_OUTPUT: return false; case LOCKUP_OP_RETURN_OUTPUT: @@ -462,6 +465,8 @@ public boolean isBsqTxOutputType(TxOutput txOutput) { case BLIND_VOTE_OP_RETURN_OUTPUT: case VOTE_REVEAL_UNLOCK_STAKE_OUTPUT: case VOTE_REVEAL_OP_RETURN_OUTPUT: + case ASSET_LISTING_FEE_OP_RETURN_OUTPUT: + case PROOF_OF_BURN_OP_RETURN_OUTPUT: case LOCKUP_OUTPUT: case LOCKUP_OP_RETURN_OUTPUT: case UNLOCK_OUTPUT: @@ -885,6 +890,15 @@ public List getDecryptedBallotsWithMeritsList() { } + /////////////////////////////////////////////////////////////////////////////////////////// + // Asset listing fee + /////////////////////////////////////////////////////////////////////////////////////////// + + public Set getAssetListingFeeOpReturnTxOutputs() { + return getTxOutputsByTxOutputType(TxOutputType.ASSET_LISTING_FEE_OP_RETURN_OUTPUT); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Listeners /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/dao/state/model/blockchain/OpReturnType.java b/core/src/main/java/bisq/core/dao/state/model/blockchain/OpReturnType.java index d693f3c56bf..2c758b7ef5f 100644 --- a/core/src/main/java/bisq/core/dao/state/model/blockchain/OpReturnType.java +++ b/core/src/main/java/bisq/core/dao/state/model/blockchain/OpReturnType.java @@ -37,7 +37,9 @@ public enum OpReturnType implements ImmutableDaoStateModel { REIMBURSEMENT_REQUEST((byte) 0x12), BLIND_VOTE((byte) 0x13), VOTE_REVEAL((byte) 0x14), - LOCKUP((byte) 0x15); + LOCKUP((byte) 0x15), + ASSET_LISTING_FEE((byte) 0x16), + PROOF_OF_BURN((byte) 0x17); @Getter private byte type; diff --git a/core/src/main/java/bisq/core/dao/state/model/blockchain/TxOutputType.java b/core/src/main/java/bisq/core/dao/state/model/blockchain/TxOutputType.java index 81532ed0540..6ddb51bea73 100644 --- a/core/src/main/java/bisq/core/dao/state/model/blockchain/TxOutputType.java +++ b/core/src/main/java/bisq/core/dao/state/model/blockchain/TxOutputType.java @@ -40,6 +40,8 @@ public enum TxOutputType implements ImmutableDaoStateModel { BLIND_VOTE_OP_RETURN_OUTPUT, VOTE_REVEAL_UNLOCK_STAKE_OUTPUT, VOTE_REVEAL_OP_RETURN_OUTPUT, + ASSET_LISTING_FEE_OP_RETURN_OUTPUT, + PROOF_OF_BURN_OP_RETURN_OUTPUT, LOCKUP_OUTPUT, LOCKUP_OP_RETURN_OUTPUT, UNLOCK_OUTPUT, diff --git a/core/src/main/java/bisq/core/dao/state/model/blockchain/TxType.java b/core/src/main/java/bisq/core/dao/state/model/blockchain/TxType.java index cb6d7a0e3a4..96d0ef00fbf 100644 --- a/core/src/main/java/bisq/core/dao/state/model/blockchain/TxType.java +++ b/core/src/main/java/bisq/core/dao/state/model/blockchain/TxType.java @@ -42,7 +42,9 @@ public enum TxType implements ImmutableDaoStateModel { BLIND_VOTE(true, true), VOTE_REVEAL(true, false), LOCKUP(true, false), - UNLOCK(true, false); + UNLOCK(true, false), + ASSET_LISTING_FEE(true, true), + PROOF_OF_BURN(true, true); /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/locale/CurrencyUtil.java b/core/src/main/java/bisq/core/locale/CurrencyUtil.java index a235ab973f5..959e28d2cdd 100644 --- a/core/src/main/java/bisq/core/locale/CurrencyUtil.java +++ b/core/src/main/java/bisq/core/locale/CurrencyUtil.java @@ -21,6 +21,12 @@ import bisq.core.btc.BaseCurrencyNetwork; import bisq.core.dao.governance.asset.AssetService; +import bisq.asset.Asset; +import bisq.asset.AssetRegistry; +import bisq.asset.Coin; +import bisq.asset.Token; +import bisq.asset.coins.BSQ; + import bisq.common.app.DevEnv; import java.util.ArrayList; @@ -32,20 +38,12 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkArgument; - - -import bisq.asset.Asset; -import bisq.asset.AssetRegistry; -import bisq.asset.Coin; -import bisq.asset.Token; -import bisq.asset.coins.BSQ; - @Slf4j public class CurrencyUtil { @@ -53,7 +51,6 @@ public static void setup() { setBaseCurrencyCode(BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode()); } - @Getter private static final AssetRegistry assetRegistry = new AssetRegistry(); private static String baseCurrencyCode = "BTC"; @@ -110,24 +107,17 @@ public static List getAllSortedCryptoCurrencies() { } private static List createAllSortedCryptoCurrenciesList() { - List result = assetRegistry.stream() - .filter(CurrencyUtil::assetIsNotBaseCurrency) - .filter(asset -> isNotBsqOrBsqTradingActivated(asset, BisqEnvironment.getBaseCurrencyNetwork(), DevEnv.isDaoTradingActivated())) - .filter(asset -> assetMatchesNetworkIfMainnet(asset, BisqEnvironment.getBaseCurrencyNetwork())) + return getSortedAssetStream() .map(CurrencyUtil::assetToCryptoCurrency) - .sorted(TradeCurrency::compareTo) .collect(Collectors.toList()); + } - // Util for printing all altcoins for adding to FAQ page - /* StringBuilder sb = new StringBuilder(); - result.stream().forEach(e -> sb.append("
  • “") - .append(e.getCode()) - .append("”, “") - .append(e.getName()) - .append("”
  • ") - .append("\n")); - log.info(sb.toString());*/ - return result; + public static Stream getSortedAssetStream() { + return assetRegistry.stream() + .filter(CurrencyUtil::assetIsNotBaseCurrency) + .filter(asset -> isNotBsqOrBsqTradingActivated(asset, BisqEnvironment.getBaseCurrencyNetwork(), DevEnv.isDaoTradingActivated())) + .filter(asset -> assetMatchesNetworkIfMainnet(asset, BisqEnvironment.getBaseCurrencyNetwork())) + .sorted(Comparator.comparing(Asset::getName)); } public static List getMainCryptoCurrencies() { @@ -488,9 +478,9 @@ public static Optional findAsset(String tickerSymbol, BaseCurrencyNetwork } // Excludes all assets which got removed by DAO voting - public static List getWhiteListedSortedCryptoCurrencies(AssetService assetService) { + public static List getActiveSortedCryptoCurrencies(AssetService assetService) { return getAllSortedCryptoCurrencies().stream() - .filter(e -> !assetService.isAssetRemoved(e.getCode())) + .filter(e -> assetService.isActive(e.getCode())) .collect(Collectors.toList()); } } diff --git a/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java index 38cac92d482..eff237354ae 100644 --- a/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java @@ -136,7 +136,7 @@ private void republishAllFiatAccounts() { } private void addToMap(AccountAgeWitness accountAgeWitness) { - log.debug("addToMap hash=" + Utilities.bytesAsHexString(accountAgeWitness.getHash())); + //log.debug("addToMap hash=" + Utilities.bytesAsHexString(accountAgeWitness.getHash())); if (!accountAgeWitnessMap.containsKey(accountAgeWitness.getHashAsByteArray())) accountAgeWitnessMap.put(accountAgeWitness.getHashAsByteArray(), accountAgeWitness); } diff --git a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java index 4f6ed3065e7..b4b1cc3ecf4 100644 --- a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java @@ -20,7 +20,6 @@ import bisq.core.arbitration.DisputeList; import bisq.core.btc.model.AddressEntryList; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.dao.governance.asset.RemovedAssetsList; import bisq.core.dao.governance.blindvote.MyBlindVoteList; import bisq.core.dao.governance.blindvote.storage.BlindVoteStore; import bisq.core.dao.governance.bond.reputation.MyReputationList; @@ -129,8 +128,6 @@ public PersistableEnvelope fromProto(PB.PersistableEnvelope proto) { return MyBlindVoteList.fromProto(proto.getMyBlindVoteList()); case MERIT_LIST: return MeritList.fromProto(proto.getMeritList()); - case REMOVED_ASSET_LIST: - return RemovedAssetsList.fromProto(proto.getRemovedAssetList()); case DAO_STATE_STORE: return DaoStateStore.fromProto(proto.getDaoStateStore()); case MY_REPUTATION_LIST: diff --git a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java index a48b1f44547..ced7dccd155 100644 --- a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java +++ b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java @@ -20,7 +20,6 @@ import bisq.core.arbitration.DisputeManager; import bisq.core.btc.model.AddressEntryList; import bisq.core.dao.DaoOptionKeys; -import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.ballot.BallotListService; import bisq.core.dao.governance.blindvote.MyBlindVoteListService; import bisq.core.dao.governance.bond.reputation.MyReputationListService; @@ -68,7 +67,6 @@ public static List getPersistedDataHosts(Injector injector) { persistedDataHosts.add(injector.getInstance(MyVoteListService.class)); persistedDataHosts.add(injector.getInstance(MyProposalListService.class)); persistedDataHosts.add(injector.getInstance(MyReputationListService.class)); - persistedDataHosts.add(injector.getInstance(AssetService.class)); } return persistedDataHosts; } diff --git a/core/src/main/java/bisq/core/trade/statistics/AssetTradeActivityCheck.java b/core/src/main/java/bisq/core/trade/statistics/AssetTradeActivityCheck.java index 8127260c748..85b35493507 100644 --- a/core/src/main/java/bisq/core/trade/statistics/AssetTradeActivityCheck.java +++ b/core/src/main/java/bisq/core/trade/statistics/AssetTradeActivityCheck.java @@ -44,6 +44,10 @@ import lombok.extern.slf4j.Slf4j; +/** + * This can be removed once the AssetService is activated with the DAO. + * At the moment it is only used for printing out trade statistics. + */ @Slf4j public class AssetTradeActivityCheck { private final AssetService assetService; @@ -75,7 +79,7 @@ public void onAllServicesInitialized() { StringBuilder sufficientlyTraded = new StringBuilder("\nSufficiently traded assets:"); StringBuilder insufficientlyTraded = new StringBuilder("\nInsufficiently traded assets:"); StringBuilder notTraded = new StringBuilder("\nNot traded assets:"); - List whiteListedSortedCryptoCurrencies = CurrencyUtil.getWhiteListedSortedCryptoCurrencies(assetService); + List whiteListedSortedCryptoCurrencies = CurrencyUtil.getActiveSortedCryptoCurrencies(assetService); Set assetsToRemove = new HashSet<>(whiteListedSortedCryptoCurrencies); whiteListedSortedCryptoCurrencies.forEach(e -> { String code = e.getCode(); @@ -99,7 +103,7 @@ public void onAllServicesInitialized() { .append(numTrades); } - if (!isWarmingUp(code) && !hasPaidBSQFee(code)) { + if (!isWarmingUp(code) /*&& !hasPaidBSQFee(code)*/) { if (isInTradeStatMap) { if (tradeAmount >= minTradeAmount || numTrades >= minNumOfTrades) { assetsToRemove.remove(e); @@ -136,10 +140,6 @@ public void onAllServicesInitialized() { log.debug(result); } - private boolean hasPaidBSQFee(String code) { - return assetService.hasPaidBSQFee(code); - } - private boolean isWarmingUp(String code) { Set newlyAdded = new HashSet<>(); diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java index a1632ab249d..f736ce8c7a6 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java @@ -177,8 +177,9 @@ private void dump() { // Need a more scalable solution later when we get more volume. // The flag will only be activated by dedicated nodes, so it should not be too critical for the moment, but needs to // get improved. Maybe a LevelDB like DB...? Could be impl. in a headless version only. - List list = observableTradeStatisticsSet.stream().map(TradeStatisticsForJson::new).collect(Collectors.toList()); - list.sort((o1, o2) -> (o1.tradeDate < o2.tradeDate ? 1 : (o1.tradeDate == o2.tradeDate ? 0 : -1))); + List list = observableTradeStatisticsSet.stream().map(TradeStatisticsForJson::new) + .sorted((o1, o2) -> (Long.compare(o2.tradeDate, o1.tradeDate))) + .collect(Collectors.toList()); TradeStatisticsForJson[] array = new TradeStatisticsForJson[list.size()]; list.toArray(array); jsonFileManager.writeToDisc(Utilities.objectToJson(array), "trade_statistics"); diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 0319281f35c..b9682cae7a3 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1163,6 +1163,7 @@ account.notifications.priceAlert.warning.lowerPriceTooHigh=The lower price must dao.tab.bsqWallet=BSQ wallet dao.tab.proposals=Governance dao.tab.bonding=Bonding +dao.tab.proofOfBurn=Asset listing fee/Proof of burn dao.paidWithBsq=paid with BSQ dao.availableBsqBalance=Available @@ -1273,6 +1274,11 @@ dao.param.THRESHOLD_ROLE=Required threshold in % for bonded role requests # suppress inspection "UnusedProperty" dao.param.RECIPIENT_BTC_ADDRESS=Recipient BTC address +# suppress inspection "UnusedProperty" +dao.param.ASSET_LISTING_FEE=Asset listing fee +# suppress inspection "UnusedProperty" +dao.param.ASSET_MIN_VOLUME=Min. trade volume + dao.param.currentValue=Current value: {0} dao.param.blocks={0} blocks @@ -1411,6 +1417,34 @@ dao.bond.bondedRoleType.MEDIATOR=Mediator # suppress inspection "UnusedProperty" dao.bond.bondedRoleType.ARBITRATOR=Arbitrator +dao.burnBsq.assetFee=Asset listing fee +dao.burnBsq.menuItem.assetFee=Asset listing fee +dao.burnBsq.menuItem.proofOfBurn=Proof of burn +dao.burnBsq.header=Fee for asset listing +dao.burnBsq.selectAsset=Select Asset +dao.burnBsq.fee=Fee +dao.burnBsq.trialPeriod=Trial period +dao.burnBsq.payFee=Pay fee +dao.burnBsq.allAssets=All assets +dao.burnBsq.assets.nameAndCode=Asset name +dao.burnBsq.assets.state=State +dao.burnBsq.assets.tradeVolume=Trade volume +dao.burnBsq.assets.lookBackPeriod=Verification period +dao.burnBsq.assets.trialFee=Fee for trial period +dao.burnBsq.assets.totalFee=Total fees paid +dao.burnBsq.assets.days={0} days + +# suppress inspection "UnusedProperty" +dao.assetState.UNDEFINED=Undefined +# suppress inspection "UnusedProperty" +dao.assetState.IN_TRIAL_PERIOD=In trial period +# suppress inspection "UnusedProperty" +dao.assetState.ACTIVELY_TRADED=Actively traded +# suppress inspection "UnusedProperty" +dao.assetState.DE_LISTED=De-listed due inactivity +# suppress inspection "UnusedProperty" +dao.assetState.REMOVED_BY_VOTING=Removed by voting + # suppress inspection "UnusedProperty" dao.phase.UNDEFINED=Undefined @@ -1610,6 +1644,10 @@ dao.tx.type.enum.VOTE_REVEAL=Vote reveal dao.tx.type.enum.LOCKUP=Lock up bond # suppress inspection "UnusedProperty" dao.tx.type.enum.UNLOCK=Unlock bond +# suppress inspection "UnusedProperty" +dao.tx.type.enum.ASSET_LISTING_FEE=Asset listing fee +# suppress inspection "UnusedProperty" +dao.tx.type.enum.PROOF_OF_BURN=Proof of burn dao.tx.issuanceFromCompReq=Compensation request/issuance dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\n\ diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/CryptoCurrencyForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/CryptoCurrencyForm.java index cda09319663..b76b0ca38eb 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/CryptoCurrencyForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/CryptoCurrencyForm.java @@ -162,7 +162,7 @@ protected void addTradeCurrencyComboBox() { currencyComboBox.setPromptText(""); }); - currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getWhiteListedSortedCryptoCurrencies(assetService))); + currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getActiveSortedCryptoCurrencies(assetService))); currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 15)); currencyComboBox.setConverter(new StringConverter() { @Override diff --git a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java index 9cfdf26d737..d19677e11e7 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java @@ -26,6 +26,7 @@ import bisq.desktop.common.view.ViewLoader; import bisq.desktop.main.MainView; import bisq.desktop.main.dao.bonding.BondingView; +import bisq.desktop.main.dao.burnbsq.BurnBsqView; import bisq.desktop.main.dao.governance.GovernanceView; import bisq.desktop.main.dao.wallet.BsqWalletView; import bisq.desktop.main.dao.wallet.dashboard.BsqDashboardView; @@ -48,12 +49,7 @@ public class DaoView extends ActivatableViewAndModel { @FXML - private - Tab bsqWalletTab; - @FXML - private Tab proposalsTab; - @FXML - private Tab bondingTab; + private Tab bsqWalletTab, proposalsTab, bondingTab, burnBsqTab; private Navigation.Listener navigationListener; private ChangeListener tabChangeListener; @@ -74,16 +70,19 @@ public void initialize() { bsqWalletTab = new Tab(Res.get("dao.tab.bsqWallet").toUpperCase()); proposalsTab = new Tab(Res.get("dao.tab.proposals").toUpperCase()); bondingTab = new Tab(Res.get("dao.tab.bonding").toUpperCase()); + burnBsqTab = new Tab(Res.get("dao.tab.proofOfBurn").toUpperCase()); bsqWalletTab.setClosable(false); proposalsTab.setClosable(false); bondingTab.setClosable(false); + burnBsqTab.setClosable(false); - root.getTabs().addAll(bsqWalletTab, proposalsTab, bondingTab); + root.getTabs().addAll(bsqWalletTab, proposalsTab, bondingTab, burnBsqTab); if (!BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq() || !DevEnv.isDaoPhase2Activated()) { bondingTab.setDisable(true); proposalsTab.setDisable(true); + burnBsqTab.setDisable(true); } navigationListener = viewPath -> { @@ -106,6 +105,8 @@ public void initialize() { navigation.navigateTo(MainView.class, DaoView.class, GovernanceView.class); } else if (newValue == bondingTab) { navigation.navigateTo(MainView.class, DaoView.class, BondingView.class); + } else if (newValue == burnBsqTab) { + navigation.navigateTo(MainView.class, DaoView.class, BurnBsqView.class); } }; } @@ -123,6 +124,8 @@ else if (selectedItem == proposalsTab) navigation.navigateTo(MainView.class, DaoView.class, GovernanceView.class); else if (selectedItem == bondingTab) navigation.navigateTo(MainView.class, DaoView.class, BondingView.class); + else if (selectedItem == burnBsqTab) + navigation.navigateTo(MainView.class, DaoView.class, BurnBsqView.class); } } @@ -141,6 +144,8 @@ private void loadView(Class viewClass) { selectedTab = proposalsTab; } else if (view instanceof BondingView) { selectedTab = bondingTab; + } else if (view instanceof BurnBsqView) { + selectedTab = burnBsqTab; } selectedTab.setContent(view.getRoot()); diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/BurnBsqView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/BurnBsqView.fxml new file mode 100644 index 00000000000..92b77529244 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/BurnBsqView.fxml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/BurnBsqView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/BurnBsqView.java new file mode 100644 index 00000000000..380fa4d47f0 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/BurnBsqView.java @@ -0,0 +1,125 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.dao.burnbsq; + +import bisq.desktop.Navigation; +import bisq.desktop.common.view.ActivatableViewAndModel; +import bisq.desktop.common.view.CachingViewLoader; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.common.view.View; +import bisq.desktop.common.view.ViewLoader; +import bisq.desktop.common.view.ViewPath; +import bisq.desktop.components.MenuItem; +import bisq.desktop.main.MainView; +import bisq.desktop.main.dao.DaoView; +import bisq.desktop.main.dao.burnbsq.assetfee.AssetFeeView; +import bisq.desktop.main.dao.burnbsq.reputation.ProofOfBurnView; + +import bisq.core.locale.Res; + +import javax.inject.Inject; + +import javafx.fxml.FXML; + +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; + +import java.util.Arrays; +import java.util.List; + +@FxmlView +public class BurnBsqView extends ActivatableViewAndModel { + + private final ViewLoader viewLoader; + private final Navigation navigation; + + private MenuItem assetFee, proofOfBurn; + private Navigation.Listener listener; + + @FXML + private VBox leftVBox; + @FXML + private AnchorPane content; + + private Class selectedViewClass; + private ToggleGroup toggleGroup; + + @Inject + private BurnBsqView(CachingViewLoader viewLoader, Navigation navigation) { + this.viewLoader = viewLoader; + this.navigation = navigation; + } + + @Override + public void initialize() { + listener = viewPath -> { + if (viewPath.size() != 4 || viewPath.indexOf(BurnBsqView.class) != 2) + return; + + selectedViewClass = viewPath.tip(); + loadView(selectedViewClass); + }; + + toggleGroup = new ToggleGroup(); + final List> baseNavPath = Arrays.asList(MainView.class, DaoView.class, BurnBsqView.class); + assetFee = new MenuItem(navigation, toggleGroup, Res.get("dao.burnBsq.menuItem.assetFee"), + AssetFeeView.class, baseNavPath); + proofOfBurn = new MenuItem(navigation, toggleGroup, Res.get("dao.burnBsq.menuItem.proofOfBurn"), + ProofOfBurnView.class, baseNavPath); + + leftVBox.getChildren().addAll(assetFee, proofOfBurn); + } + + @Override + protected void activate() { + assetFee.activate(); + proofOfBurn.activate(); + + navigation.addListener(listener); + ViewPath viewPath = navigation.getCurrentPath(); + if (viewPath.size() == 3 && viewPath.indexOf(BurnBsqView.class) == 2 || + viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) { + if (selectedViewClass == null) + selectedViewClass = AssetFeeView.class; + + loadView(selectedViewClass); + + } else if (viewPath.size() == 4 && viewPath.indexOf(BurnBsqView.class) == 2) { + selectedViewClass = viewPath.get(3); + loadView(selectedViewClass); + } + } + + @SuppressWarnings("Duplicates") + @Override + protected void deactivate() { + navigation.removeListener(listener); + + assetFee.deactivate(); + proofOfBurn.deactivate(); + } + + private void loadView(Class viewClass) { + View view = viewLoader.load(viewClass); + content.getChildren().setAll(view.getRoot()); + + if (view instanceof AssetFeeView) toggleGroup.selectToggle(assetFee); + else if (view instanceof ProofOfBurnView) toggleGroup.selectToggle(proofOfBurn); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.fxml new file mode 100644 index 00000000000..bbb0b8ae4db --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.fxml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java new file mode 100644 index 00000000000..a387cd42369 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java @@ -0,0 +1,417 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.dao.burnbsq.assetfee; + +import bisq.desktop.common.view.ActivatableView; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutoTooltipTableColumn; +import bisq.desktop.components.InputTextField; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.FormBuilder; +import bisq.desktop.util.GUIUtil; +import bisq.desktop.util.Layout; +import bisq.desktop.util.validation.BsqValidator; + +import bisq.core.btc.listeners.BsqBalanceListener; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.dao.governance.asset.AssetService; +import bisq.core.dao.governance.asset.StatefulAsset; +import bisq.core.dao.governance.proposal.TxException; +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.Res; +import bisq.core.util.BSFormatter; +import bisq.core.util.BsqFormatter; + +import bisq.common.app.DevEnv; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; +import org.bitcoinj.core.Transaction; + +import javax.inject.Inject; + +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; + +import javafx.beans.InvalidationListener; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ChangeListener; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; + +import javafx.util.Callback; +import javafx.util.StringConverter; + +import java.util.Comparator; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +import static bisq.desktop.util.FormBuilder.addButtonAfterGroup; +import static bisq.desktop.util.FormBuilder.addInputTextField; +import static bisq.desktop.util.FormBuilder.addTitledGroupBg; + +@FxmlView +public class AssetFeeView extends ActivatableView implements BsqBalanceListener { + private ComboBox assetComboBox; + private InputTextField feeAmountInputTextField; + private TextField trialPeriodTextField; + private Button payFeeButton; + private TableView tableView; + + private final BsqFormatter bsqFormatter; + private final BsqWalletService bsqWalletService; + private final BsqValidator bsqValidator; + private final AssetService assetService; + private BSFormatter btcFormatter; + + private final ObservableList observableList = FXCollections.observableArrayList(); + private final SortedList sortedList = new SortedList<>(observableList); + + private int gridRow = 0; + + private ChangeListener amountFocusOutListener; + private ChangeListener amountInputTextFieldListener; + @Nullable + private StatefulAsset selectedAsset; + private InvalidationListener updateListener; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + private AssetFeeView(BsqFormatter bsqFormatter, + BsqWalletService bsqWalletService, + BsqValidator bsqValidator, + AssetService assetService, + BSFormatter btcFormatter) { + this.bsqFormatter = bsqFormatter; + this.bsqWalletService = bsqWalletService; + this.bsqValidator = bsqValidator; + this.assetService = assetService; + this.btcFormatter = btcFormatter; + } + + @Override + public void initialize() { + addTitledGroupBg(root, gridRow, 3, Res.get("dao.burnBsq.header")); + + assetComboBox = FormBuilder.addComboBox(root, gridRow, + Res.get("dao.burnBsq.selectAsset"), Layout.FIRST_ROW_DISTANCE); + assetComboBox.setConverter(new StringConverter<>() { + @Override + public String toString(StatefulAsset statefulAsset) { + return CurrencyUtil.getNameAndCode(statefulAsset.getAsset().getTickerSymbol()); + } + + @Override + public StatefulAsset fromString(String string) { + return null; + } + }); + + feeAmountInputTextField = addInputTextField(root, ++gridRow, Res.get("dao.burnBsq.fee")); + feeAmountInputTextField.setValidator(bsqValidator); + + trialPeriodTextField = FormBuilder.addTopLabelTextField(root, ++gridRow, Res.get("dao.burnBsq.trialPeriod")).second; + + payFeeButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.burnBsq.payFee")); + + tableView = FormBuilder.addTableViewWithHeader(root, ++gridRow, Res.get("dao.burnBsq.allAssets"), 20); + createColumns(); + tableView.setItems(sortedList); + + createListeners(); + } + + @Override + protected void activate() { + assetComboBox.setOnAction(e -> { + selectedAsset = assetComboBox.getSelectionModel().getSelectedItem(); + }); + + feeAmountInputTextField.textProperty().addListener(amountInputTextFieldListener); + feeAmountInputTextField.focusedProperty().addListener(amountFocusOutListener); + + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + + assetService.getUpdateFlag().addListener(updateListener); + bsqWalletService.addBsqBalanceListener(this); + + payFeeButton.setOnAction((event) -> { + Coin listingFee = getListingFee(); + try { + Transaction transaction = assetService.payFee(selectedAsset, listingFee.value); + Coin miningFee = transaction.getFee(); + int txSize = transaction.bitcoinSerialize().length; + + if (!DevEnv.isDevMode()) { + GUIUtil.showBsqFeeInfoPopup(listingFee, miningFee, txSize, bsqFormatter, btcFormatter, + Res.get("dao.burnBsq.assetFee"), () -> doPublishFeeTx(transaction, listingFee)); + } else { + doPublishFeeTx(transaction, listingFee); + } + } catch (InsufficientMoneyException | TxException e) { + e.printStackTrace(); + new Popup<>().error(e.toString()).show(); + } + }); + + updateList(); + updateButtonState(); + + feeAmountInputTextField.resetValidation(); + } + + @Override + protected void deactivate() { + assetComboBox.setOnAction(null); + + feeAmountInputTextField.textProperty().removeListener(amountInputTextFieldListener); + feeAmountInputTextField.focusedProperty().removeListener(amountFocusOutListener); + + assetService.getUpdateFlag().removeListener(updateListener); + bsqWalletService.removeBsqBalanceListener(this); + + sortedList.comparatorProperty().unbind(); + + payFeeButton.setOnAction(null); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // BsqBalanceListener + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onUpdateBalances(Coin confirmedBalance, + Coin availableNonBsqBalance, + Coin pendingBalance, + Coin lockedForVotingBalance, + Coin lockupBondsBalance, + Coin unlockingBondsBalance) { + bsqValidator.setAvailableBalance(confirmedBalance); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void createListeners() { + amountFocusOutListener = (observable, oldValue, newValue) -> { + if (!newValue) { + updateButtonState(); + } + }; + + amountInputTextFieldListener = (observable, oldValue, newValue) -> { + long days = getListingFee().value / assetService.getFeePerDay().value; + trialPeriodTextField.setText(Res.get("dao.burnBsq.assets.days", days)); + updateButtonState(); + }; + + updateListener = observable -> updateList(); + } + + private void updateList() { + // Here we exclude the assets which have been removed by voting. Paying a fee would not change the state. + ObservableList nonRemovedStatefulAssets = FXCollections.observableArrayList(assetService.getStatefulAssets().stream() + .filter(e -> !e.wasRemovedByVoting()) + .collect(Collectors.toList())); + assetComboBox.setItems(nonRemovedStatefulAssets); + + // In the table we want to show all. + observableList.setAll(assetService.getStatefulAssets().stream() + .map(statefulAsset -> new AssetListItem(statefulAsset, bsqFormatter)) + /*.sorted(Comparator.comparing(AssetListItem::getNameAndCode))*/ + .collect(Collectors.toList())); + GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 10); + } + + private void updateButtonState() { + boolean isValid = bsqValidator.validate(feeAmountInputTextField.getText()).isValid && + selectedAsset != null; + payFeeButton.setDisable(!isValid); + } + + private Coin getListingFee() { + return bsqFormatter.parseToCoin(feeAmountInputTextField.getText()); + } + + private void doPublishFeeTx(Transaction transaction, Coin listingFee) { + assetService.publishTransaction(selectedAsset, transaction, listingFee.value, + () -> { + assetComboBox.getSelectionModel().clearSelection(); + if (!DevEnv.isDevMode()) + new Popup<>().confirmation(Res.get("dao.tx.published.success")).show(); + }, + errorMessage -> new Popup<>().warning(errorMessage).show()); + + feeAmountInputTextField.clear(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Table columns + /////////////////////////////////////////////////////////////////////////////////////////// + + private void createColumns() { + TableColumn column; + + column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.nameAndCode")); + column.setMinWidth(120); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory(new Callback<>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final AssetListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + setText(item.getNameAndCode()); + } else + setText(""); + } + }; + } + }); + tableView.getColumns().add(column); + column.setComparator(Comparator.comparing(AssetListItem::getNameAndCode)); + + column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.state")); + column.setMinWidth(120); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory(new Callback<>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final AssetListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + setText(item.getAssetStateString()); + } else + setText(""); + } + }; + } + }); + tableView.getColumns().add(column); + column.setComparator(Comparator.comparing(AssetListItem::getAssetStateString)); + + column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.tradeVolume")); + column.setMinWidth(120); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory(new Callback<>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final AssetListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + setText(item.getTradedVolumeAsString()); + } else + setText(""); + } + }; + } + }); + tableView.getColumns().add(column); + column.setComparator(Comparator.comparing(AssetListItem::getTradedVolume)); + + column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.lookBackPeriod")); + column.setMinWidth(120); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory(new Callback<>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final AssetListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + setText(item.getLookBackPeriodInDays()); + } else + setText(""); + } + }; + } + }); + tableView.getColumns().add(column); + column.setComparator(Comparator.comparing(AssetListItem::getLookBackPeriodInDays)); + + column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.trialFee")); + column.setMinWidth(120); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory(new Callback<>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final AssetListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + setText(item.getFeeOfTrialPeriodAsString()); + } else + setText(""); + } + }; + } + }); + tableView.getColumns().add(column); + column.setComparator(Comparator.comparing(AssetListItem::getFeeOfTrialPeriod)); + + column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.totalFee")); + column.setMinWidth(120); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory(new Callback<>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final AssetListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + setText(item.getTotalFeesPaidAsString()); + } else + setText(""); + } + }; + } + }); + tableView.getColumns().add(column); + column.setComparator(Comparator.comparing(AssetListItem::getTotalFeesPaid)); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetListItem.java new file mode 100644 index 00000000000..4e3a4063645 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetListItem.java @@ -0,0 +1,57 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.dao.burnbsq.assetfee; + +import bisq.core.dao.governance.asset.StatefulAsset; +import bisq.core.locale.Res; +import bisq.core.util.BsqFormatter; + +import lombok.Value; + +@Value +class AssetListItem { + private final StatefulAsset statefulAsset; + private final String tickerSymbol; + private final String assetStateString; + private final int trialPeriodInBlocks; + private final String nameAndCode; + private final long totalFeesPaid; + private final String totalFeesPaidAsString; + private final long feeOfTrialPeriod; + private final String feeOfTrialPeriodAsString; + private final String tradedVolumeAsString; + private final String lookBackPeriodInDays; + private final long tradedVolume; + + AssetListItem(StatefulAsset statefulAsset, + BsqFormatter bsqFormatter) { + this.statefulAsset = statefulAsset; + + tickerSymbol = statefulAsset.getTickerSymbol(); + nameAndCode = statefulAsset.getNameAndCode(); + assetStateString = Res.get("dao.assetState." + statefulAsset.getAssetState()); + feeOfTrialPeriod = statefulAsset.getFeeOfTrialPeriod(); + feeOfTrialPeriodAsString = bsqFormatter.formatCoinWithCode(feeOfTrialPeriod); + totalFeesPaid = statefulAsset.getTotalFeesPaid(); + totalFeesPaidAsString = bsqFormatter.formatCoinWithCode(totalFeesPaid); + trialPeriodInBlocks = (int) totalFeesPaid * 144; + tradedVolume = statefulAsset.getTradeVolume(); + tradedVolumeAsString = bsqFormatter.formatBTCWithCode(tradedVolume); + lookBackPeriodInDays = Res.get("dao.burnBsq.assets.days", statefulAsset.getLookBackPeriodInDays()); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/reputation/ProofOfBurnListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/reputation/ProofOfBurnListItem.java new file mode 100644 index 00000000000..35e6a09a7b5 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/reputation/ProofOfBurnListItem.java @@ -0,0 +1,29 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.dao.burnbsq.reputation; + +import bisq.core.util.BsqFormatter; + +import lombok.Value; + +@Value +class ProofOfBurnListItem { + + ProofOfBurnListItem(BsqFormatter bsqFormatter) { + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/reputation/ProofOfBurnView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/reputation/ProofOfBurnView.fxml new file mode 100644 index 00000000000..3f8a173e4e1 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/reputation/ProofOfBurnView.fxml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/reputation/ProofOfBurnView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/reputation/ProofOfBurnView.java new file mode 100644 index 00000000000..7a5f7fa8f69 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/reputation/ProofOfBurnView.java @@ -0,0 +1,101 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.dao.burnbsq.reputation; + +import bisq.desktop.common.view.ActivatableView; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.util.validation.BsqValidator; + +import bisq.core.btc.listeners.BsqBalanceListener; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.dao.DaoFacade; +import bisq.core.user.Preferences; +import bisq.core.util.BsqFormatter; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; + +import javafx.scene.layout.GridPane; + +@FxmlView +public class ProofOfBurnView extends ActivatableView implements BsqBalanceListener { + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + private ProofOfBurnView(BsqFormatter bsqFormatter, + BsqWalletService bsqWalletService, + BsqValidator bsqValidator, + DaoFacade daoFacade, + Preferences preferences) { + } + + @Override + public void initialize() { + + } + + @Override + protected void activate() { + + updateList(); + } + + @Override + protected void deactivate() { + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // BsqBalanceListener + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onUpdateBalances(Coin confirmedBalance, + Coin availableNonBsqBalance, + Coin pendingBalance, + Coin lockedForVotingBalance, + Coin lockupBondsBalance, + Coin unlockingBondsBalance) { + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void createListeners() { + } + + private void updateList() { + } + + private void updateButtonState() { + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Table columns + /////////////////////////////////////////////////////////////////////////////////////////// + + private void createColumns() { + + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java index ad732028f1d..f3b8a9f0c5c 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java @@ -338,10 +338,8 @@ public Bond fromString(String string) { Res.get("dao.proposal.display.assetComboBox.label")); comboBoxValueTextFieldIndex = gridRow; checkNotNull(assetComboBox, "assetComboBox must not be null"); - List assetList = CurrencyUtil.getAssetRegistry().stream() + List assetList = CurrencyUtil.getSortedAssetStream() .filter(e -> !e.getTickerSymbol().equals("BSQ")) - .filter(e -> !e.getTickerSymbol().equals("BTC")) - .filter(e -> CurrencyUtil.assetMatchesNetwork(e, BaseCurrencyNetwork.BTC_MAINNET)) .collect(Collectors.toList()); assetComboBox.setItems(FXCollections.observableArrayList(assetList)); assetComboBox.setConverter(new StringConverter<>() { diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java index 6b984142688..80c68c17aa8 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java @@ -124,7 +124,6 @@ private MakeProposalView(DaoFacade daoFacade, this.bsqFormatter = bsqFormatter; } - @Override public void initialize() { gridRow = phasesView.addGroup(root, gridRow); diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxView.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxView.java index 331adc466f0..a16a9f3d3a4 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/tx/BsqTxView.java @@ -589,6 +589,14 @@ public void updateItem(final BsqTxListItem item, boolean empty) { awesomeIcon = AwesomeIcon.UNLOCK; style = "dao-tx-type-unlock-icon"; break; + case ASSET_LISTING_FEE: + awesomeIcon = AwesomeIcon.FILE_TEXT; + style = "dao-tx-type-proposal-fee-icon"; + break; + case PROOF_OF_BURN: + awesomeIcon = AwesomeIcon.FILE_TEXT; + style = "dao-tx-type-proposal-fee-icon"; + break; default: awesomeIcon = AwesomeIcon.QUESTION_SIGN; style = "dao-tx-type-unverified-icon"; diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 146e7b33524..dbe0e5ad9b3 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -183,7 +183,7 @@ public void initialize() { @Override protected void activate() { // We want to have it updated in case an asset got removed - allCryptoCurrencies = FXCollections.observableArrayList(CurrencyUtil.getWhiteListedSortedCryptoCurrencies(assetService)); + allCryptoCurrencies = FXCollections.observableArrayList(CurrencyUtil.getActiveSortedCryptoCurrencies(assetService)); allCryptoCurrencies.removeAll(cryptoCurrencies); activateGeneralOptions();