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 extends View> 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 extends View> 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 extends View> 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();