Merge remote-tracking branch 'remotes/upstream/master' into update-install-script

This commit is contained in:
Devin Bileck 2018-10-24 23:49:46 -07:00
commit eb879ef495
No known key found for this signature in database
GPG key ID: C86D829C2399D073
54 changed files with 785 additions and 158 deletions

View file

@ -120,6 +120,7 @@ public class Version {
//TODO move to consensus area
public static final byte COMPENSATION_REQUEST = (byte) 0x01;
public static final byte REIMBURSEMENT_REQUEST = (byte) 0x01;
public static final byte PROPOSAL = (byte) 0x01;
public static final byte BLIND_VOTE = (byte) 0x01;
public static final byte VOTE_REVEAL = (byte) 0x01;

View file

@ -1360,10 +1360,11 @@ enum TxType {
PAY_TRADE_FEE = 6;
PROPOSAL = 7;
COMPENSATION_REQUEST = 8;
BLIND_VOTE = 9;
VOTE_REVEAL = 10;
LOCKUP = 11;
UNLOCK = 12;
REIMBURSEMENT_REQUEST = 9;
BLIND_VOTE = 10;
VOTE_REVEAL = 11;
LOCKUP = 12;
UNLOCK = 13;
}
message TxInput {
@ -1403,16 +1404,17 @@ enum TxOutputType {
BTC_OUTPUT = 4;
PROPOSAL_OP_RETURN_OUTPUT = 5;
COMP_REQ_OP_RETURN_OUTPUT = 6;
CONFISCATE_BOND_OP_RETURN_OUTPUT = 7;
ISSUANCE_CANDIDATE_OUTPUT = 8;
BLIND_VOTE_LOCK_STAKE_OUTPUT = 9;
BLIND_VOTE_OP_RETURN_OUTPUT = 10;
VOTE_REVEAL_UNLOCK_STAKE_OUTPUT = 11;
VOTE_REVEAL_OP_RETURN_OUTPUT = 12;
LOCKUP_OUTPUT = 13;
LOCKUP_OP_RETURN_OUTPUT = 14;
UNLOCK_OUTPUT = 15;
INVALID_OUTPUT = 16;
REIMBURSEMENT_OP_RETURN_OUTPUT = 7;
CONFISCATE_BOND_OP_RETURN_OUTPUT = 8;
ISSUANCE_CANDIDATE_OUTPUT = 9;
BLIND_VOTE_LOCK_STAKE_OUTPUT = 10;
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;
}
message SpentInfo {
@ -1474,6 +1476,7 @@ message Issuance {
int32 chain_height = 2;
int64 amount = 3;
string pub_key = 4;
string issuance_type = 5;
}
message Proposal {
@ -1484,11 +1487,12 @@ message Proposal {
string tx_id = 5;
oneof message {
CompensationProposal compensation_proposal = 6;
ChangeParamProposal change_param_proposal = 7;
BondedRoleProposal bonded_role_proposal = 8;
ConfiscateBondProposal confiscate_bond_proposal = 9;
GenericProposal generic_proposal = 10;
RemoveAssetProposal remove_asset_proposal = 11;
ReimbursementProposal reimbursement_proposal = 7;
ChangeParamProposal change_param_proposal = 8;
BondedRoleProposal bonded_role_proposal = 9;
ConfiscateBondProposal confiscate_bond_proposal = 10;
GenericProposal generic_proposal = 11;
RemoveAssetProposal remove_asset_proposal = 12;
}
}
@ -1497,6 +1501,11 @@ message CompensationProposal {
string bsq_address = 2;
}
message ReimbursementProposal {
int64 requested_bsq = 1;
string bsq_address = 2;
}
message ChangeParamProposal {
string param = 1; // name of enum
int64 param_value = 2;

View file

@ -146,11 +146,28 @@ public class BtcWalletService extends WalletService {
///////////////////////////////////////////////////////////////////////////////////////////
// CompensationRequest tx
// Proposal txs
///////////////////////////////////////////////////////////////////////////////////////////
public Transaction completePreparedCompensationRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData) throws
TransactionVerificationException, WalletException, InsufficientMoneyException {
public Transaction completePreparedProposalTx(Transaction preparedBurnFeeTx, byte[] opReturnData)
throws WalletException, InsufficientMoneyException, TransactionVerificationException {
return completePreparedProposalTx(preparedBurnFeeTx, opReturnData, null, null);
}
public Transaction completePreparedReimbursementRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData)
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress);
}
public Transaction completePreparedCompensationRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData)
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress);
}
private Transaction completePreparedProposalTx(Transaction feeTx, byte[] opReturnData,
@Nullable Coin issuanceAmount, @Nullable Address issuanceAddress)
throws TransactionVerificationException, WalletException, InsufficientMoneyException {
// (BsqFee)tx has following structure:
// inputs [1-n] BSQ inputs (fee)
@ -160,7 +177,7 @@ public class BtcWalletService extends WalletService {
// inputs [1-n] BSQ inputs for request fee
// inputs [1-n] BTC inputs for BSQ issuance and miner fee
// outputs [1] Mandatory BSQ request fee change output (>= 546 Satoshi)
// outputs [1] Potentially BSQ issuance output (>= 546 Satoshi)
// outputs [1] Potentially BSQ issuance output (>= 546 Satoshi) - in case of a issuance tx, otherwise that output does not exist
// outputs [0-1] BTC change output from issuance and miner fee inputs (>= 546 Satoshi)
// outputs [1] OP_RETURN with opReturnData and amount 0
// mining fee: BTC mining fee + burned BSQ fee
@ -174,9 +191,11 @@ public class BtcWalletService extends WalletService {
// BSQ change outputs from BSQ fee inputs.
feeTx.getOutputs().forEach(preparedTx::addOutput);
// BSQ issuance output
preparedTx.addOutput(issuanceAmount, issuanceAddress);
// For generic proposals there is no issuance output, for compensation and reimburse requests there is
if (issuanceAmount != null && issuanceAddress != null) {
// BSQ issuance output
preparedTx.addOutput(issuanceAmount, issuanceAddress);
}
// safety check counter to avoid endless loops
int counter = 0;
@ -204,8 +223,8 @@ public class BtcWalletService extends WalletService {
}
Transaction tx = new Transaction(params);
preparedBsqTxInputs.stream().forEach(tx::addInput);
preparedBsqTxOutputs.stream().forEach(tx::addOutput);
preparedBsqTxInputs.forEach(tx::addInput);
preparedBsqTxOutputs.forEach(tx::addOutput);
SendRequest sendRequest = SendRequest.forTx(tx);
sendRequest.shuffleOutputs = false;
@ -228,7 +247,7 @@ public class BtcWalletService extends WalletService {
numInputs = resultTx.getInputs().size();
txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length;
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
// calculated fee must be inside of a tolerance range with tx fee
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
}
@ -244,17 +263,6 @@ public class BtcWalletService extends WalletService {
return resultTx;
}
//TODO Similar like completePreparedCompensationRequestTx but without second output for BSQ issuance
public Transaction completePreparedProposalTx(Transaction preparedBurnFeeTx, byte[] opReturnData) {
try {
//TODO dummy
return completePreparedCompensationRequestTx(Coin.valueOf(10000), getFreshAddressEntry().getAddress(),
preparedBurnFeeTx, opReturnData);
} catch (TransactionVerificationException | InsufficientMoneyException | WalletException e) {
e.printStackTrace();
}
throw new RuntimeException("completePreparedGenericProposalTx not implemented yet.");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Blind vote tx

View file

@ -42,6 +42,8 @@ import bisq.core.dao.governance.proposal.compensation.CompensationProposalServic
import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposalService;
import bisq.core.dao.governance.proposal.generic.GenericProposalService;
import bisq.core.dao.governance.proposal.param.ChangeParamProposalService;
import bisq.core.dao.governance.proposal.reimbursement.ReimbursementConsensus;
import bisq.core.dao.governance.proposal.reimbursement.ReimbursementProposalService;
import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposalService;
import bisq.core.dao.governance.proposal.role.BondedRoleProposalService;
import bisq.core.dao.governance.role.BondedRole;
@ -54,7 +56,7 @@ import bisq.core.dao.state.blockchain.Tx;
import bisq.core.dao.state.blockchain.TxOutput;
import bisq.core.dao.state.blockchain.TxOutputKey;
import bisq.core.dao.state.blockchain.TxType;
import bisq.core.dao.state.governance.Issuance;
import bisq.core.dao.state.governance.IssuanceType;
import bisq.core.dao.state.governance.Param;
import bisq.core.dao.state.period.DaoPhase;
import bisq.core.dao.state.period.PeriodService;
@ -104,6 +106,7 @@ public class DaoFacade implements DaoSetupService {
private final MyBlindVoteListService myBlindVoteListService;
private final MyVoteListService myVoteListService;
private final CompensationProposalService compensationProposalService;
private final ReimbursementProposalService reimbursementProposalService;
private final ChangeParamProposalService changeParamProposalService;
private final ConfiscateBondProposalService confiscateBondProposalService;
private final BondedRoleProposalService bondedRoleProposalService;
@ -126,6 +129,7 @@ public class DaoFacade implements DaoSetupService {
MyBlindVoteListService myBlindVoteListService,
MyVoteListService myVoteListService,
CompensationProposalService compensationProposalService,
ReimbursementProposalService reimbursementProposalService,
ChangeParamProposalService changeParamProposalService,
ConfiscateBondProposalService confiscateBondProposalService,
BondedRoleProposalService bondedRoleProposalService,
@ -144,6 +148,7 @@ public class DaoFacade implements DaoSetupService {
this.myBlindVoteListService = myBlindVoteListService;
this.myVoteListService = myVoteListService;
this.compensationProposalService = compensationProposalService;
this.reimbursementProposalService = reimbursementProposalService;
this.changeParamProposalService = changeParamProposalService;
this.confiscateBondProposalService = confiscateBondProposalService;
this.bondedRoleProposalService = bondedRoleProposalService;
@ -223,6 +228,15 @@ public class DaoFacade implements DaoSetupService {
requestedBsq);
}
public ProposalWithTransaction getReimbursementProposalWithTransaction(String name,
String link,
Coin requestedBsq)
throws ValidationException, InsufficientMoneyException, TxException {
return reimbursementProposalService.createProposalWithTransaction(name,
link,
requestedBsq);
}
public ProposalWithTransaction getParamProposalWithTransaction(String name,
String link,
Param param,
@ -517,8 +531,8 @@ public class DaoFacade implements DaoSetupService {
return daoStateService.getGenesisTotalSupply();
}
public Set<Issuance> getIssuanceSet() {
return daoStateService.getIssuanceSet();
public int getNumIssuanceTransactions(IssuanceType issuanceType) {
return daoStateService.getIssuanceSet(issuanceType).size();
}
public Set<Tx> getFeeTxs() {
@ -541,8 +555,8 @@ public class DaoFacade implements DaoSetupService {
return daoStateService.getTotalBurntFee();
}
public long getTotalIssuedAmountFromCompRequests() {
return daoStateService.getTotalIssuedAmount();
public long getTotalIssuedAmount(IssuanceType issuanceType) {
return daoStateService.getTotalIssuedAmount(issuanceType);
}
public long getBlockTime(int issuanceBlockHeight) {
@ -553,8 +567,8 @@ public class DaoFacade implements DaoSetupService {
return daoStateService.getIssuanceBlockHeight(txId);
}
public boolean isIssuanceTx(String txId) {
return daoStateService.isIssuanceTx(txId);
public boolean isIssuanceTx(String txId, IssuanceType issuanceType) {
return daoStateService.isIssuanceTx(txId, issuanceType);
}
public boolean hasTxBurntFee(String hashAsString) {
@ -605,6 +619,14 @@ public class DaoFacade implements DaoSetupService {
return CompensationConsensus.getMaxCompensationRequestAmount(daoStateService, periodService.getChainHeight());
}
public Coin getMinReimbursementRequestAmount() {
return ReimbursementConsensus.getMinReimbursementRequestAmount(daoStateService, periodService.getChainHeight());
}
public Coin getMaxReimbursementRequestAmount() {
return ReimbursementConsensus.getMaxReimbursementRequestAmount(daoStateService, periodService.getChainHeight());
}
public long getPramValue(Param param) {
return daoStateService.getParamValue(param, periodService.getChainHeight());
}

View file

@ -41,6 +41,8 @@ import bisq.core.dao.governance.proposal.generic.GenericProposalService;
import bisq.core.dao.governance.proposal.generic.GenericProposalValidator;
import bisq.core.dao.governance.proposal.param.ChangeParamProposalService;
import bisq.core.dao.governance.proposal.param.ChangeParamValidator;
import bisq.core.dao.governance.proposal.reimbursement.ReimbursementProposalService;
import bisq.core.dao.governance.proposal.reimbursement.ReimbursementValidator;
import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposalService;
import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetValidator;
import bisq.core.dao.governance.proposal.role.BondedRoleProposalService;
@ -131,6 +133,9 @@ public class DaoModule extends AppModule {
bind(CompensationValidator.class).in(Singleton.class);
bind(CompensationProposalService.class).in(Singleton.class);
bind(ReimbursementValidator.class).in(Singleton.class);
bind(ReimbursementProposalService.class).in(Singleton.class);
bind(ChangeParamValidator.class).in(Singleton.class);
bind(ChangeParamProposalService.class).in(Singleton.class);

View file

@ -41,6 +41,7 @@ import bisq.core.dao.governance.proposal.compensation.CompensationProposal;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.blockchain.Block;
import bisq.core.dao.state.governance.IssuanceType;
import bisq.core.dao.state.period.DaoPhase;
import bisq.core.dao.state.period.PeriodService;
@ -83,6 +84,8 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Publishes blind vote tx and blind vote payload to p2p network.
* Maintains myBlindVoteList for own blind votes. Triggers republishing of my blind votes at startup during blind
@ -278,8 +281,10 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
.filter(txId -> periodService.isTxInPastCycle(txId, periodService.getChainHeight()))
.collect(Collectors.toSet());
return new MeritList(daoStateService.getIssuanceSet().stream()
return new MeritList(daoStateService.getIssuanceSet(IssuanceType.COMPENSATION).stream()
.map(issuance -> {
checkArgument(issuance.getIssuanceType() == IssuanceType.COMPENSATION,
"IssuanceType must be COMPENSATION for MeritList");
// We check if it is our proposal
if (!myCompensationProposalTxIs.contains(issuance.getTxId()))
return null;

View file

@ -21,6 +21,7 @@ import bisq.core.dao.governance.voteresult.VoteResultException;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.blockchain.Tx;
import bisq.core.dao.state.governance.Issuance;
import bisq.core.dao.state.governance.IssuanceType;
import bisq.common.crypto.Encryption;
import bisq.common.util.Utilities;
@ -68,8 +69,11 @@ public class MeritConsensus {
.filter(merit -> isSignatureValid(merit.getSignature(), merit.getIssuance().getPubKey(), blindVoteTxId))
.mapToLong(merit -> {
try {
return getWeightedMeritAmount(merit.getIssuance().getAmount(),
merit.getIssuance().getChainHeight(),
Issuance issuance = merit.getIssuance();
checkArgument(issuance.getIssuanceType() == IssuanceType.COMPENSATION,
"issuance must be of type COMPENSATION");
return getWeightedMeritAmount(issuance.getAmount(),
issuance.getChainHeight(),
txChainHeight,
BLOCKS_PER_YEAR);
} catch (Throwable t) {
@ -148,6 +152,7 @@ public class MeritConsensus {
.mapToLong(merit -> {
try {
Issuance issuance = merit.getIssuance();
checkArgument(issuance.getIssuanceType() == IssuanceType.COMPENSATION, "issuance must be of type COMPENSATION");
int issuanceHeight = issuance.getChainHeight();
checkArgument(issuanceHeight <= currentChainHeight,
"issuanceHeight must not be larger as currentChainHeight");

View file

@ -0,0 +1,31 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package bisq.core.dao.governance.proposal;
import org.bitcoinj.core.Coin;
/**
* Marker interface for proposals which can lead to new BSQ issuance
*/
public interface IssuanceProposal {
Coin getRequestedBsq();
String getBsqAddress();
String getTxId();
}

View file

@ -22,6 +22,7 @@ import bisq.core.dao.governance.proposal.compensation.CompensationProposal;
import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposal;
import bisq.core.dao.governance.proposal.generic.GenericProposal;
import bisq.core.dao.governance.proposal.param.ChangeParamProposal;
import bisq.core.dao.governance.proposal.reimbursement.ReimbursementProposal;
import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposal;
import bisq.core.dao.governance.proposal.role.BondedRoleProposal;
import bisq.core.dao.state.blockchain.TxType;
@ -93,6 +94,8 @@ public abstract class Proposal implements PersistablePayload, NetworkPayload, Co
switch (proto.getMessageCase()) {
case COMPENSATION_PROPOSAL:
return CompensationProposal.fromProto(proto);
case REIMBURSEMENT_PROPOSAL:
return ReimbursementProposal.fromProto(proto);
case CHANGE_PARAM_PROPOSAL:
return ChangeParamProposal.fromProto(proto);
case BONDED_ROLE_PROPOSAL:

View file

@ -21,6 +21,7 @@ import bisq.core.locale.Res;
public enum ProposalType {
COMPENSATION_REQUEST,
REIMBURSEMENT_REQUEST,
CHANGE_PARAM,
BONDED_ROLE,
CONFISCATE_BOND,

View file

@ -19,6 +19,7 @@ package bisq.core.dao.governance.proposal;
import bisq.core.dao.exceptions.ValidationException;
import bisq.core.dao.governance.proposal.compensation.CompensationProposal;
import bisq.core.dao.governance.proposal.reimbursement.ReimbursementProposal;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.blockchain.Tx;
import bisq.core.dao.state.blockchain.TxType;
@ -112,7 +113,12 @@ public class ProposalValidator {
}
if (proposal instanceof CompensationProposal) {
if (optionalTx.get().getTxType() != TxType.COMPENSATION_REQUEST) {
log.error("TxType is not PROPOSAL. proposal.getTxId()={}", proposal.getTxId());
log.error("TxType is not a COMPENSATION_REQUEST. proposal.getTxId()={}", proposal.getTxId());
return false;
}
} else if (proposal instanceof ReimbursementProposal) {
if (optionalTx.get().getTxType() != TxType.REIMBURSEMENT_REQUEST) {
log.error("TxType is not a REIMBURSEMENT_REQUEST. proposal.getTxId()={}", proposal.getTxId());
return false;
}
} else {

View file

@ -18,6 +18,7 @@
package bisq.core.dao.governance.proposal.compensation;
import bisq.core.app.BisqEnvironment;
import bisq.core.dao.governance.proposal.IssuanceProposal;
import bisq.core.dao.governance.proposal.Proposal;
import bisq.core.dao.governance.proposal.ProposalType;
import bisq.core.dao.state.blockchain.TxType;
@ -43,7 +44,7 @@ import javax.annotation.concurrent.Immutable;
@Slf4j
@EqualsAndHashCode(callSuper = true)
@Value
public final class CompensationProposal extends Proposal {
public final class CompensationProposal extends Proposal implements IssuanceProposal {
private final long requestedBsq;
private final String bsqAddress;

View file

@ -91,16 +91,20 @@ public class ChangeParamValidator extends ProposalValidator {
break;
case COMPENSATION_REQUEST_MIN_AMOUNT:
case REIMBURSEMENT_MIN_AMOUNT:
if (paramValue < Restrictions.getMinNonDustOutput().value)
throw new ChangeParamValidationException(Res.get("validation.amountBelowDust", Restrictions.getMinNonDustOutput().value));
checkMinMax(param, paramValue, 100, -50);
break;
case COMPENSATION_REQUEST_MAX_AMOUNT:
case REIMBURSEMENT_MAX_AMOUNT:
checkMinMax(param, paramValue, 100, -50);
break;
case QUORUM_COMP_REQUEST:
break;
case QUORUM_REIMBURSEMENT:
break;
case QUORUM_CHANGE_PARAM:
break;
case QUORUM_ROLE:
@ -114,6 +118,8 @@ public class ChangeParamValidator extends ProposalValidator {
case THRESHOLD_COMP_REQUEST:
break;
case THRESHOLD_REIMBURSEMENT:
break;
case THRESHOLD_CHANGE_PARAM:
break;
case THRESHOLD_ROLE:

View file

@ -0,0 +1,37 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package bisq.core.dao.governance.proposal.reimbursement;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.governance.Param;
import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ReimbursementConsensus {
public static Coin getMinReimbursementRequestAmount(DaoStateService daoStateService, int chainHeight) {
return Coin.valueOf(daoStateService.getParamValue(Param.REIMBURSEMENT_MIN_AMOUNT, chainHeight));
}
public static Coin getMaxReimbursementRequestAmount(DaoStateService daoStateService, int chainHeight) {
return Coin.valueOf(daoStateService.getParamValue(Param.REIMBURSEMENT_MAX_AMOUNT, chainHeight));
}
}

View file

@ -0,0 +1,159 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package bisq.core.dao.governance.proposal.reimbursement;
import bisq.core.app.BisqEnvironment;
import bisq.core.dao.governance.proposal.IssuanceProposal;
import bisq.core.dao.governance.proposal.Proposal;
import bisq.core.dao.governance.proposal.ProposalType;
import bisq.core.dao.state.blockchain.TxType;
import bisq.core.dao.state.governance.Param;
import bisq.common.app.Version;
import io.bisq.generated.protobuffer.PB;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import java.util.Date;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.concurrent.Immutable;
@Immutable
@Slf4j
@EqualsAndHashCode(callSuper = true)
@Value
public final class ReimbursementProposal extends Proposal implements IssuanceProposal {
private final long requestedBsq;
private final String bsqAddress;
ReimbursementProposal(String name,
String link,
Coin requestedBsq,
String bsqAddress) {
this(name,
link,
bsqAddress,
requestedBsq.value,
Version.REIMBURSEMENT_REQUEST,
new Date().getTime(),
"");
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private ReimbursementProposal(String name,
String link,
String bsqAddress,
long requestedBsq,
byte version,
long creationDate,
String txId) {
super(name,
link,
version,
creationDate,
txId);
this.requestedBsq = requestedBsq;
this.bsqAddress = bsqAddress;
}
@Override
public PB.Proposal.Builder getProposalBuilder() {
final PB.ReimbursementProposal.Builder builder = PB.ReimbursementProposal.newBuilder()
.setBsqAddress(bsqAddress)
.setRequestedBsq(requestedBsq);
return super.getProposalBuilder().setReimbursementProposal(builder);
}
public static ReimbursementProposal fromProto(PB.Proposal proto) {
final PB.ReimbursementProposal proposalProto = proto.getReimbursementProposal();
return new ReimbursementProposal(proto.getName(),
proto.getLink(),
proposalProto.getBsqAddress(),
proposalProto.getRequestedBsq(),
(byte) proto.getVersion(),
proto.getCreationDate(),
proto.getTxId());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public Coin getRequestedBsq() {
return Coin.valueOf(requestedBsq);
}
public Address getAddress() throws AddressFormatException {
// Remove leading 'B'
String underlyingBtcAddress = bsqAddress.substring(1, bsqAddress.length());
return Address.fromBase58(BisqEnvironment.getParameters(), underlyingBtcAddress);
}
@Override
public ProposalType getType() {
return ProposalType.REIMBURSEMENT_REQUEST;
}
@Override
public Param getQuorumParam() {
return Param.QUORUM_REIMBURSEMENT;
}
@Override
public Param getThresholdParam() {
return Param.THRESHOLD_REIMBURSEMENT;
}
@Override
public TxType getTxType() {
return TxType.REIMBURSEMENT_REQUEST;
}
@Override
public Proposal cloneProposalAndAddTxId(String txId) {
return new ReimbursementProposal(getName(),
getLink(),
getBsqAddress(),
getRequestedBsq().value,
getVersion(),
getCreationDate().getTime(),
txId);
}
@Override
public String toString() {
return "ReimbursementProposal{" +
"\n requestedBsq=" + requestedBsq +
",\n bsqAddress='" + bsqAddress + '\'' +
"\n} " + super.toString();
}
}

View file

@ -0,0 +1,99 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package bisq.core.dao.governance.proposal.reimbursement;
import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.exceptions.ValidationException;
import bisq.core.dao.governance.proposal.BaseProposalService;
import bisq.core.dao.governance.proposal.Proposal;
import bisq.core.dao.governance.proposal.ProposalConsensus;
import bisq.core.dao.governance.proposal.ProposalWithTransaction;
import bisq.core.dao.governance.proposal.TxException;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.blockchain.OpReturnType;
import bisq.common.app.Version;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
/**
* Creates the ReimbursementProposal and the transaction.
*/
@Slf4j
public class ReimbursementProposalService extends BaseProposalService<ReimbursementProposal> {
private Coin requestedBsq;
private String bsqAddress;
@Inject
public ReimbursementProposalService(BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
DaoStateService daoStateService,
ReimbursementValidator proposalValidator) {
super(bsqWalletService,
btcWalletService,
daoStateService,
proposalValidator);
}
public ProposalWithTransaction createProposalWithTransaction(String name,
String link,
Coin requestedBsq)
throws ValidationException, InsufficientMoneyException, TxException {
this.requestedBsq = requestedBsq;
this.bsqAddress = bsqWalletService.getUnusedBsqAddressAsString();
return super.createProposalWithTransaction(name, link);
}
@Override
protected ReimbursementProposal createProposalWithoutTxId() {
return new ReimbursementProposal(
name,
link,
requestedBsq,
bsqAddress);
}
@Override
protected byte[] getOpReturnData(byte[] hashOfPayload) {
return ProposalConsensus.getOpReturnData(hashOfPayload,
OpReturnType.REIMBURSEMENT_REQUEST.getType(),
Version.REIMBURSEMENT_REQUEST);
}
@Override
protected Transaction completeTx(Transaction preparedBurnFeeTx, byte[] opReturnData, Proposal proposal)
throws WalletException, InsufficientMoneyException, TransactionVerificationException {
ReimbursementProposal reimbursementProposal = (ReimbursementProposal) proposal;
return btcWalletService.completePreparedReimbursementRequestTx(
reimbursementProposal.getRequestedBsq(),
reimbursementProposal.getAddress(),
preparedBurnFeeTx,
opReturnData);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package bisq.core.dao.governance.proposal.reimbursement;
import bisq.core.dao.exceptions.ValidationException;
import bisq.core.dao.governance.proposal.Proposal;
import bisq.core.dao.governance.proposal.ProposalValidator;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.period.PeriodService;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.commons.lang3.Validate.notEmpty;
@Slf4j
public class ReimbursementValidator extends ProposalValidator {
@Inject
public ReimbursementValidator(DaoStateService daoStateService, PeriodService periodService) {
super(daoStateService, periodService);
}
@Override
public void validateDataFields(Proposal proposal) throws ValidationException {
try {
super.validateDataFields(proposal);
ReimbursementProposal reimbursementProposal = (ReimbursementProposal) proposal;
String bsqAddress = reimbursementProposal.getBsqAddress();
notEmpty(bsqAddress, "bsqAddress must not be empty");
checkArgument(bsqAddress.substring(0, 1).equals("B"), "bsqAddress must start with B");
reimbursementProposal.getAddress(); // throws AddressFormatException if wrong address
Coin requestedBsq = reimbursementProposal.getRequestedBsq();
Coin maxReimbursementRequestAmount = ReimbursementConsensus.getMaxReimbursementRequestAmount(daoStateService, periodService.getChainHeight());
checkArgument(requestedBsq.compareTo(maxReimbursementRequestAmount) <= 0,
"Requested BSQ must not exceed " + (maxReimbursementRequestAmount.value / 100L) + " BSQ");
Coin minReimbursementRequestAmount = ReimbursementConsensus.getMinReimbursementRequestAmount(daoStateService, periodService.getChainHeight());
checkArgument(requestedBsq.compareTo(minReimbursementRequestAmount) >= 0,
"Requested BSQ must not be less than " + (minReimbursementRequestAmount.value / 100L) + " BSQ");
} catch (Throwable throwable) {
throw new ValidationException(throwable);
}
}
}

View file

@ -30,9 +30,9 @@ import bisq.core.dao.governance.blindvote.VoteWithProposalTxId;
import bisq.core.dao.governance.blindvote.VoteWithProposalTxIdList;
import bisq.core.dao.governance.merit.MeritConsensus;
import bisq.core.dao.governance.merit.MeritList;
import bisq.core.dao.governance.proposal.IssuanceProposal;
import bisq.core.dao.governance.proposal.Proposal;
import bisq.core.dao.governance.proposal.ProposalListPresentation;
import bisq.core.dao.governance.proposal.compensation.CompensationProposal;
import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposal;
import bisq.core.dao.governance.proposal.param.ChangeParamProposal;
import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposal;
@ -564,8 +564,8 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
private void applyIssuance(Set<EvaluatedProposal> acceptedEvaluatedProposals, int chainHeight) {
acceptedEvaluatedProposals.stream()
.map(EvaluatedProposal::getProposal)
.filter(proposal -> proposal instanceof CompensationProposal)
.forEach(proposal -> issuanceService.issueBsq((CompensationProposal) proposal, chainHeight));
.filter(proposal -> proposal instanceof IssuanceProposal)
.forEach(proposal -> issuanceService.issueBsq((IssuanceProposal) proposal, chainHeight));
}
private void applyParamChange(Set<EvaluatedProposal> acceptedEvaluatedProposals, int chainHeight) {

View file

@ -17,12 +17,15 @@
package bisq.core.dao.governance.voteresult.issuance;
import bisq.core.dao.governance.proposal.IssuanceProposal;
import bisq.core.dao.governance.proposal.compensation.CompensationProposal;
import bisq.core.dao.governance.proposal.reimbursement.ReimbursementProposal;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.blockchain.Tx;
import bisq.core.dao.state.blockchain.TxInput;
import bisq.core.dao.state.blockchain.TxOutput;
import bisq.core.dao.state.governance.Issuance;
import bisq.core.dao.state.governance.IssuanceType;
import bisq.core.dao.state.period.DaoPhase;
import bisq.core.dao.state.period.PeriodService;
@ -32,6 +35,8 @@ import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
//TODO case that user misses reveal phase not impl. yet
@Slf4j
@ -50,42 +55,51 @@ public class IssuanceService {
this.periodService = periodService;
}
public void issueBsq(CompensationProposal compensationProposal, int chainHeight) {
public void issueBsq(IssuanceProposal issuanceProposal, int chainHeight) {
daoStateService.getIssuanceCandidateTxOutputs().stream()
.filter(txOutput -> isValid(txOutput, compensationProposal, periodService, chainHeight))
.filter(txOutput -> isValid(txOutput, issuanceProposal, periodService, chainHeight))
.forEach(txOutput -> {
IssuanceType issuanceType = IssuanceType.UNDEFINED;
if (issuanceProposal instanceof CompensationProposal) {
issuanceType = IssuanceType.COMPENSATION;
} else if (issuanceProposal instanceof ReimbursementProposal) {
issuanceType = IssuanceType.REIMBURSEMENT;
}
checkArgument(issuanceType != IssuanceType.UNDEFINED, "issuanceType must not be undefined");
// We don't check atm if the output is unspent. We cannot use the bsqWallet as that would not
// reflect our current block state (could have been spent at later block which is valid and
// bsqWallet would show that spent state). We would need to support a spent status for the outputs
// which are interpreted as BTC (as a not yet accepted comp. request).
Optional<Tx> optionalTx = daoStateService.getTx(compensationProposal.getTxId());
Optional<Tx> optionalTx = daoStateService.getTx(issuanceProposal.getTxId());
if (optionalTx.isPresent()) {
long amount = compensationProposal.getRequestedBsq().value;
long amount = issuanceProposal.getRequestedBsq().value;
Tx tx = optionalTx.get();
// We use key from first input
TxInput txInput = tx.getTxInputs().get(0);
String pubKey = txInput.getPubKey();
Issuance issuance = new Issuance(tx.getId(), chainHeight, amount, pubKey);
Issuance issuance = new Issuance(tx.getId(), chainHeight, amount, pubKey, issuanceType);
daoStateService.addIssuance(issuance);
daoStateService.addUnspentTxOutput(txOutput);
StringBuilder sb = new StringBuilder();
sb.append("\n################################################################################\n");
sb.append("We issued new BSQ to tx with ID ").append(txOutput.getTxId())
.append("\nIssued BSQ: ").append(compensationProposal.getRequestedBsq())
.append("\nIssued BSQ: ").append(issuanceProposal.getRequestedBsq())
.append("\nIssuance type: ").append(issuanceType.name())
.append("\n################################################################################\n");
log.info(sb.toString());
} else {
//TODO throw exception
log.error("Tx for compensation request not found. txId={}", compensationProposal.getTxId());
log.error("Tx for compensation request not found. txId={}", issuanceProposal.getTxId());
}
});
}
private boolean isValid(TxOutput txOutput, CompensationProposal compensationProposal, PeriodService periodService, int chainHeight) {
return txOutput.getTxId().equals(compensationProposal.getTxId())
&& compensationProposal.getRequestedBsq().value == txOutput.getValue()
&& compensationProposal.getBsqAddress().substring(1).equals(txOutput.getAddress())
private boolean isValid(TxOutput txOutput, IssuanceProposal issuanceProposal, PeriodService periodService, int chainHeight) {
return txOutput.getTxId().equals(issuanceProposal.getTxId())
&& issuanceProposal.getRequestedBsq().value == txOutput.getValue()
&& issuanceProposal.getBsqAddress().substring(1).equals(txOutput.getAddress())
&& periodService.isTxInPhaseAndCycle(txOutput.getTxId(), DaoPhase.Phase.PROPOSAL, chainHeight);
}
}

View file

@ -27,6 +27,7 @@ enum JsonTxOutputType {
BTC_OUTPUT("BTC"),
PROPOSAL_OP_RETURN_OUTPUT("Proposal opReturn"),
COMP_REQ_OP_RETURN_OUTPUT("Compensation request opReturn"),
REIMBURSEMENT_OP_RETURN_OUTPUT("Reimbursement request opReturn"),
CONFISCATE_BOND_OP_RETURN_OUTPUT("Confiscate bond opReturn"),
ISSUANCE_CANDIDATE_OUTPUT("Issuance candidate"),
BLIND_VOTE_LOCK_STAKE_OUTPUT("Blind vote lock stake"),

View file

@ -29,6 +29,7 @@ enum JsonTxType {
PAY_TRADE_FEE("Pay trade fee"),
PROPOSAL("Proposal"),
COMPENSATION_REQUEST("Compensation request"),
REIMBURSEMENT_REQUEST("Reimbursement request"),
BLIND_VOTE("Blind vote"),
VOTE_REVEAL("Vote reveal"),
LOCKUP("Lockup"),

View file

@ -90,6 +90,11 @@ class OpReturnParser {
return TxOutputType.COMP_REQ_OP_RETURN_OUTPUT;
else
break;
case REIMBURSEMENT_REQUEST:
if (ProposalConsensus.hasOpReturnDataValidLength(opReturnData))
return TxOutputType.REIMBURSEMENT_OP_RETURN_OUTPUT;
else
break;
case BLIND_VOTE:
if (BlindVoteConsensus.hasOpReturnDataValidLength(opReturnData))
return TxOutputType.BLIND_VOTE_OP_RETURN_OUTPUT;

View file

@ -83,6 +83,7 @@ public class TxInputParser {
case BTC_OUTPUT:
case PROPOSAL_OP_RETURN_OUTPUT:
case COMP_REQ_OP_RETURN_OUTPUT:
case REIMBURSEMENT_OP_RETURN_OUTPUT:
case CONFISCATE_BOND_OP_RETURN_OUTPUT:
case ISSUANCE_CANDIDATE_OUTPUT:
break;

View file

@ -205,7 +205,8 @@ public class TxOutputParser {
if (availableInputValue > 0 &&
index == 1 &&
optionalOpReturnType.isPresent() &&
optionalOpReturnType.get() == OpReturnType.COMPENSATION_REQUEST) {
(optionalOpReturnType.get() == OpReturnType.COMPENSATION_REQUEST ||
optionalOpReturnType.get() == OpReturnType.REIMBURSEMENT_REQUEST)) {
optionalIssuanceCandidate = Optional.of(txOutput);
} else {
txOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
@ -225,6 +226,8 @@ public class TxOutputParser {
return Optional.of(OpReturnType.PROPOSAL);
case COMP_REQ_OP_RETURN_OUTPUT:
return Optional.of(OpReturnType.COMPENSATION_REQUEST);
case REIMBURSEMENT_OP_RETURN_OUTPUT:
return Optional.of(OpReturnType.REIMBURSEMENT_REQUEST);
case BLIND_VOTE_OP_RETURN_OUTPUT:
return Optional.of(OpReturnType.BLIND_VOTE);
case VOTE_REVEAL_OP_RETURN_OUTPUT:

View file

@ -195,7 +195,8 @@ public class TxParser {
processProposal(blockHeight, tempTx, bsqFee);
break;
case COMPENSATION_REQUEST:
processCompensationRequest(blockHeight, tempTx, bsqFee);
case REIMBURSEMENT_REQUEST:
processIssuance(blockHeight, tempTx, bsqFee);
break;
case BLIND_VOTE:
processBlindVote(blockHeight, tempTx, bsqFee);
@ -211,7 +212,7 @@ public class TxParser {
// We need to check if any tempTxOutput is available and if so and the OpReturn data is invalid we
// set the output to a BTC output. We must not use `if else` cases here!
if (opReturnType != OpReturnType.COMPENSATION_REQUEST) {
if (opReturnType != OpReturnType.COMPENSATION_REQUEST && opReturnType != OpReturnType.REIMBURSEMENT_REQUEST) {
txOutputParser.getOptionalIssuanceCandidate().ifPresent(tempTxOutput -> tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT));
}
@ -235,7 +236,7 @@ public class TxParser {
}
}
private void processCompensationRequest(int blockHeight, TempTx tempTx, long bsqFee) {
private void processIssuance(int blockHeight, TempTx tempTx, long bsqFee) {
boolean isFeeAndPhaseValid = isFeeAndPhaseValid(blockHeight, bsqFee, DaoPhase.Phase.PROPOSAL, Param.PROPOSAL_FEE);
Optional<TempTxOutput> optionalIssuanceCandidate = txOutputParser.getOptionalIssuanceCandidate();
if (isFeeAndPhaseValid) {
@ -400,21 +401,24 @@ public class TxParser {
case PROPOSAL:
return TxType.PROPOSAL;
case COMPENSATION_REQUEST:
case REIMBURSEMENT_REQUEST:
boolean hasCorrectNumOutputs = tempTx.getTempTxOutputs().size() >= 3;
if (!hasCorrectNumOutputs) {
log.warn("Compensation request tx need to have at least 3 outputs");
log.warn("Compensation/reimbursement request tx need to have at least 3 outputs");
return TxType.INVALID;
}
TempTxOutput issuanceTxOutput = tempTx.getTempTxOutputs().get(1);
boolean hasIssuanceOutput = issuanceTxOutput.getTxOutputType() == TxOutputType.ISSUANCE_CANDIDATE_OUTPUT;
if (!hasIssuanceOutput) {
log.warn("Compensation request txOutput type of output at index 1 need to be ISSUANCE_CANDIDATE_OUTPUT. " +
log.warn("Compensation/reimbursement request txOutput type of output at index 1 need to be ISSUANCE_CANDIDATE_OUTPUT. " +
"TxOutputType={}", issuanceTxOutput.getTxOutputType());
return TxType.INVALID;
}
return TxType.COMPENSATION_REQUEST;
return opReturnType == OpReturnType.COMPENSATION_REQUEST ?
TxType.COMPENSATION_REQUEST :
TxType.REIMBURSEMENT_REQUEST;
case BLIND_VOTE:
return TxType.BLIND_VOTE;
case VOTE_REVEAL:

View file

@ -32,6 +32,7 @@ import bisq.core.dao.state.blockchain.TxOutputType;
import bisq.core.dao.state.blockchain.TxType;
import bisq.core.dao.state.governance.ConfiscateBond;
import bisq.core.dao.state.governance.Issuance;
import bisq.core.dao.state.governance.IssuanceType;
import bisq.core.dao.state.governance.Param;
import bisq.core.dao.state.governance.ParamChange;
import bisq.core.dao.state.period.Cycle;
@ -407,6 +408,7 @@ public class DaoStateService implements DaoSetupService {
return false;
case PROPOSAL_OP_RETURN_OUTPUT:
case COMP_REQ_OP_RETURN_OUTPUT:
case REIMBURSEMENT_OP_RETURN_OUTPUT:
case ISSUANCE_CANDIDATE_OUTPUT:
return true;
case BLIND_VOTE_LOCK_STAKE_OUTPUT:
@ -451,6 +453,7 @@ public class DaoStateService implements DaoSetupService {
return false;
case PROPOSAL_OP_RETURN_OUTPUT:
case COMP_REQ_OP_RETURN_OUTPUT:
case REIMBURSEMENT_OP_RETURN_OUTPUT:
return true;
case ISSUANCE_CANDIDATE_OUTPUT:
return isIssuanceTx(txOutput.getTxId());
@ -502,31 +505,42 @@ public class DaoStateService implements DaoSetupService {
daoState.getIssuanceMap().put(issuance.getTxId(), issuance);
}
public Set<Issuance> getIssuanceSet() {
return new HashSet<>(daoState.getIssuanceMap().values());
public Set<Issuance> getIssuanceSet(IssuanceType issuanceType) {
return daoState.getIssuanceMap().values().stream()
.filter(issuance -> issuance.getIssuanceType() == issuanceType)
.collect(Collectors.toSet());
}
public Optional<Issuance> getIssuance(String txId, IssuanceType issuanceType) {
return daoState.getIssuanceMap().values().stream()
.filter(issuance -> issuance.getTxId().equals(txId))
.filter(issuance -> issuance.getIssuanceType() == issuanceType)
.findAny();
}
public Optional<Issuance> getIssuance(String txId) {
if (daoState.getIssuanceMap().containsKey(txId))
return Optional.of(daoState.getIssuanceMap().get(txId));
else
return Optional.empty();
return daoState.getIssuanceMap().values().stream()
.filter(issuance -> issuance.getTxId().equals(txId))
.findAny();
}
//TODO rename acceptedIssuanceTx
public boolean isIssuanceTx(String txId) {
return getIssuance(txId).isPresent();
}
public boolean isIssuanceTx(String txId, IssuanceType issuanceType) {
return getIssuance(txId, issuanceType).isPresent();
}
public int getIssuanceBlockHeight(String txId) {
return getIssuance(txId)
.map(Issuance::getChainHeight)
.orElse(0);
}
public long getTotalIssuedAmount() {
public long getTotalIssuedAmount(IssuanceType issuanceType) {
return getIssuanceCandidateTxOutputs().stream()
.filter(txOutput -> isIssuanceTx(txOutput.getTxId()))
.filter(txOutput -> isIssuanceTx(txOutput.getTxId(), issuanceType))
.mapToLong(TxOutput::getValue)
.sum();
}

View file

@ -30,9 +30,10 @@ public enum OpReturnType {
//TODO add undefined ?
PROPOSAL((byte) 0x10),
COMPENSATION_REQUEST((byte) 0x11),
BLIND_VOTE((byte) 0x12),
VOTE_REVEAL((byte) 0x13),
LOCKUP((byte) 0x14);
REIMBURSEMENT_REQUEST((byte) 0x12),
BLIND_VOTE((byte) 0x13),
VOTE_REVEAL((byte) 0x14),
LOCKUP((byte) 0x15);
@Getter
private byte type;

View file

@ -28,6 +28,7 @@ public enum TxOutputType {
BTC_OUTPUT,
PROPOSAL_OP_RETURN_OUTPUT,
COMP_REQ_OP_RETURN_OUTPUT,
REIMBURSEMENT_OP_RETURN_OUTPUT,
CONFISCATE_BOND_OP_RETURN_OUTPUT,
ISSUANCE_CANDIDATE_OUTPUT,
BLIND_VOTE_LOCK_STAKE_OUTPUT,

View file

@ -34,6 +34,7 @@ public enum TxType {
PAY_TRADE_FEE(false, true),
PROPOSAL(true, true),
COMPENSATION_REQUEST(true, true),
REIMBURSEMENT_REQUEST(true, true),
BLIND_VOTE(true, true),
VOTE_REVEAL(true, false),
LOCKUP(true, false),

View file

@ -17,13 +17,12 @@
package bisq.core.dao.state.governance;
import bisq.common.proto.ProtoUtil;
import bisq.common.proto.network.NetworkPayload;
import bisq.common.proto.persistable.PersistablePayload;
import io.bisq.generated.protobuffer.PB;
import javax.inject.Inject;
import java.util.Optional;
import lombok.Value;
@ -45,12 +44,14 @@ public class Issuance implements PersistablePayload, NetworkPayload {
@Nullable
private final String pubKey; // sig key as hex of first input in issuance tx
@Inject
public Issuance(String txId, int chainHeight, long amount, @Nullable String pubKey) {
private final IssuanceType issuanceType;
public Issuance(String txId, int chainHeight, long amount, @Nullable String pubKey, IssuanceType issuanceType) {
this.txId = txId;
this.chainHeight = chainHeight;
this.amount = amount;
this.pubKey = pubKey;
this.issuanceType = issuanceType;
}
@ -62,7 +63,8 @@ public class Issuance implements PersistablePayload, NetworkPayload {
final PB.Issuance.Builder builder = PB.Issuance.newBuilder()
.setTxId(txId)
.setChainHeight(chainHeight)
.setAmount(amount);
.setAmount(amount)
.setIssuanceType(issuanceType.name());
Optional.ofNullable(pubKey).ifPresent(e -> builder.setPubKey(pubKey));
@ -73,7 +75,8 @@ public class Issuance implements PersistablePayload, NetworkPayload {
return new Issuance(proto.getTxId(),
proto.getChainHeight(),
proto.getAmount(),
proto.getPubKey().isEmpty() ? null : proto.getPubKey());
proto.getPubKey().isEmpty() ? null : proto.getPubKey(),
proto.getIssuanceType().isEmpty() ? IssuanceType.UNDEFINED : ProtoUtil.enumFromProto(IssuanceType.class, proto.getIssuanceType()));
}
@Override
@ -83,6 +86,7 @@ public class Issuance implements PersistablePayload, NetworkPayload {
",\n chainHeight=" + chainHeight +
",\n amount=" + amount +
",\n pubKey='" + pubKey + '\'' +
",\n issuanceType='" + issuanceType + '\'' +
"\n}";
}
}

View file

@ -0,0 +1,24 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package bisq.core.dao.state.governance;
public enum IssuanceType {
UNDEFINED,
COMPENSATION,
REIMBURSEMENT
}

View file

@ -67,12 +67,15 @@ public enum Param {
// As BSQ based validation values can change over time if BSQ value rise we need to support that in the Params as well
COMPENSATION_REQUEST_MIN_AMOUNT(1_000), // 10 BSQ
COMPENSATION_REQUEST_MAX_AMOUNT(10_000_000), // 100 000 BSQ
REIMBURSEMENT_MIN_AMOUNT(1_000), // 10 BSQ
REIMBURSEMENT_MAX_AMOUNT(1_000_000), // 10 000 BSQ
// Quorum required for voting result to be valid.
// Quorum is the min. amount of total BSQ (earned+stake) which was used for voting on a request.
// E.g. If only 2000 BSQ was used on a vote but 10 000 is required the result is invalid even if the voters voted
// 100% for acceptance. This should prevent that changes can be done with low stakeholder participation.
QUORUM_COMP_REQUEST(2_000_000), // 20 000 BSQ
QUORUM_REIMBURSEMENT(2_000_000), // 20 000 BSQ
QUORUM_CHANGE_PARAM(10_000_000), // 100 000 BSQ
QUORUM_ROLE(5_000_000), // 50 000 BSQ
QUORUM_CONFISCATION(20_000_000), // 200 000 BSQ
@ -85,6 +88,7 @@ public enum Param {
// The result must be larger than the threshold. A 50% vote result for a threshold with 50% is not sufficient,
// it requires min. 50.01%.
THRESHOLD_COMP_REQUEST(5_000), // 50%
THRESHOLD_REIMBURSEMENT(5_000), // 50%
THRESHOLD_CHANGE_PARAM(7_500), // 75% That might change the THRESHOLD_CHANGE_PARAM and QUORUM_CHANGE_PARAM as well. So we have to be careful here!
THRESHOLD_ROLE(5_000), // 50%
THRESHOLD_CONFISCATION(8_500), // 85% Confiscation is considered an exceptional case and need very high consensus among the stakeholders.

View file

@ -134,9 +134,12 @@ public class BsqFormatter extends BSFormatter {
case BLIND_VOTE_FEE:
case COMPENSATION_REQUEST_MIN_AMOUNT:
case COMPENSATION_REQUEST_MAX_AMOUNT:
case REIMBURSEMENT_MIN_AMOUNT:
case REIMBURSEMENT_MAX_AMOUNT:
return formatCoinWithCode(Coin.valueOf(value));
case QUORUM_COMP_REQUEST:
case QUORUM_REIMBURSEMENT:
case QUORUM_CHANGE_PARAM:
case QUORUM_ROLE:
case QUORUM_CONFISCATION:
@ -145,6 +148,7 @@ public class BsqFormatter extends BSFormatter {
return formatCoinWithCode(Coin.valueOf(value));
case THRESHOLD_COMP_REQUEST:
case THRESHOLD_REIMBURSEMENT:
case THRESHOLD_CHANGE_PARAM:
case THRESHOLD_ROLE:
case THRESHOLD_CONFISCATION:
@ -183,10 +187,13 @@ public class BsqFormatter extends BSFormatter {
case BLIND_VOTE_FEE:
case COMPENSATION_REQUEST_MIN_AMOUNT:
case COMPENSATION_REQUEST_MAX_AMOUNT:
case REIMBURSEMENT_MIN_AMOUNT:
case REIMBURSEMENT_MAX_AMOUNT:
return parseToCoin(inputValue).value;
case QUORUM_COMP_REQUEST:
case QUORUM_REIMBURSEMENT:
case QUORUM_CHANGE_PARAM:
case QUORUM_ROLE:
case QUORUM_CONFISCATION:
@ -196,6 +203,7 @@ public class BsqFormatter extends BSFormatter {
case THRESHOLD_COMP_REQUEST:
case THRESHOLD_REIMBURSEMENT:
case THRESHOLD_CHANGE_PARAM:
case THRESHOLD_ROLE:
case THRESHOLD_CONFISCATION:

View file

@ -746,7 +746,9 @@ funds.tx.noTxAvailable=No transactions available
funds.tx.revert=Revert
funds.tx.txSent=Transaction successfully sent to a new address in the local Bisq wallet.
funds.tx.direction.self=Sent to yourself
funds.tx.proposal=Proposal
funds.tx.proposalTxFee=Miner fee for proposal
funds.tx.reimbursementRequestTxFee=Reimbursement request
funds.tx.compensationRequestTxFee=Compensation request
####################################################################
@ -1228,12 +1230,18 @@ dao.param.BLIND_VOTE_FEE=Voting fee in BSQ
dao.param.COMPENSATION_REQUEST_MIN_AMOUNT=Compensation request min. BSQ amount
# suppress inspection "UnusedProperty"
dao.param.COMPENSATION_REQUEST_MAX_AMOUNT=Compensation request max. BSQ amount
# suppress inspection "UnusedProperty"
dao.param.REIMBURSEMENT_MIN_AMOUNT=Reimbursement request min. BSQ amount
# suppress inspection "UnusedProperty"
dao.param.REIMBURSEMENT_MAX_AMOUNT=Reimbursement request max. BSQ amount
# suppress inspection "UnusedProperty"
dao.param.QUORUM_GENERIC=Required quorum in BSQ for generic proposal
# suppress inspection "UnusedProperty"
dao.param.QUORUM_COMP_REQUEST=Required quorum in BSQ for compensation request
# suppress inspection "UnusedProperty"
dao.param.QUORUM_REIMBURSEMENT=Required quorum in BSQ for reimbursement request
# suppress inspection "UnusedProperty"
dao.param.QUORUM_CHANGE_PARAM=Required quorum in BSQ for changing a parameter
# suppress inspection "UnusedProperty"
dao.param.QUORUM_REMOVE_ASSET=Required quorum in BSQ for removing an asset
@ -1249,6 +1257,8 @@ dao.param.THRESHOLD_GENERIC=Required threshold in % for generic proposal
# suppress inspection "UnusedProperty"
dao.param.THRESHOLD_COMP_REQUEST=Required threshold in % for compensation request
# suppress inspection "UnusedProperty"
dao.param.THRESHOLD_REIMBURSEMENT=Required threshold in % for reimbursement request
# suppress inspection "UnusedProperty"
dao.param.THRESHOLD_CHANGE_PARAM=Required threshold in % for changing a parameter
# suppress inspection "UnusedProperty"
dao.param.THRESHOLD_REMOVE_ASSET=Required threshold in % for removing an asset
@ -1377,6 +1387,8 @@ dao.phase.separatedPhaseBar.RESULT=Vote result
# suppress inspection "UnusedProperty"
dao.proposal.type.COMPENSATION_REQUEST=Compensation request
# suppress inspection "UnusedProperty"
dao.proposal.type.REIMBURSEMENT_REQUEST=Reimbursement request
# suppress inspection "UnusedProperty"
dao.proposal.type.BONDED_ROLE=Proposal for a bonded role
# suppress inspection "UnusedProperty"
dao.proposal.type.REMOVE_ASSET=Proposal for removing an asset
@ -1390,6 +1402,8 @@ dao.proposal.type.CONFISCATE_BOND=Proposal for confiscating a bond
# suppress inspection "UnusedProperty"
dao.proposal.type.short.COMPENSATION_REQUEST=Compensation request
# suppress inspection "UnusedProperty"
dao.proposal.type.short.REIMBURSEMENT_REQUEST=Reimbursement request
# suppress inspection "UnusedProperty"
dao.proposal.type.short.BONDED_ROLE=Bonded role
# suppress inspection "UnusedProperty"
dao.proposal.type.short.REMOVE_ASSET=Removing an altcoin
@ -1476,6 +1490,7 @@ dao.wallet.dashboard.genesisBlockHeight=Genesis block height:
dao.wallet.dashboard.genesisTxId=Genesis transaction ID:
dao.wallet.dashboard.genesisIssueAmount=BSQ issued at genesis transaction:
dao.wallet.dashboard.compRequestIssueAmount=BSQ issued for compensation requests:
dao.wallet.dashboard.reimbursementAmount=BSQ issued for reimbursement requests:
dao.wallet.dashboard.availableAmount=Total available BSQ:
dao.wallet.dashboard.burntAmount=Burned BSQ (fees):
dao.wallet.dashboard.totalLockedUpAmount=Locked up in bonds:
@ -1483,7 +1498,8 @@ dao.wallet.dashboard.totalUnlockingAmount=Unlocking BSQ from bonds:
dao.wallet.dashboard.totalUnlockedAmount=Unlocked BSQ from bonds:
dao.wallet.dashboard.allTx=No. of all BSQ transactions:
dao.wallet.dashboard.utxo=No. of all unspent transaction outputs:
dao.wallet.dashboard.issuanceTx=No. of all issuance transactions:
dao.wallet.dashboard.compensationIssuanceTx=No. of all compensation request issuance transactions:
dao.wallet.dashboard.reimbursementIssuanceTx=No. of all reimbursement request issuance transactions:
dao.wallet.dashboard.burntTx=No. of all fee payments transactions:
dao.wallet.dashboard.price=Latest BSQ/BTC trade price (in Bisq):
dao.wallet.dashboard.marketCap=Market capitalisation (based on trade price):
@ -1528,6 +1544,8 @@ dao.tx.type.enum.PAY_TRADE_FEE=Trading fee
# suppress inspection "UnusedProperty"
dao.tx.type.enum.COMPENSATION_REQUEST=Fee for compensation request
# suppress inspection "UnusedProperty"
dao.tx.type.enum.REIMBURSEMENT_REQUEST=Fee for reimbursement request
# suppress inspection "UnusedProperty"
dao.tx.type.enum.PROPOSAL=Fee for proposal
# suppress inspection "UnusedProperty"
dao.tx.type.enum.BLIND_VOTE=Fee for blind vote
@ -1538,10 +1556,12 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=Compensation request/issuance
dao.tx.issuance.tooltip=Compensation request which led to an issuance of new BSQ.\n\
dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\n\
Issuance date: {0}
dao.tx.issuanceFromReimbursement=Reimbursement request/issuance
dao.tx.issuanceFromReimbursement.tooltip=Reimbursement request which led to an issuance of new BSQ.\n\
Issuance date: {0}
dao.proposal.create.missingFunds=You don''t have sufficient funds for creating the proposal.\n\
Missing: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=Keine Transaktionen verfügbar
funds.tx.revert=Umkehren
funds.tx.txSent=Transaktion erfolgreich zu einer neuen Adresse in der lokalen Bisq-Brieftasche gesendet.
funds.tx.direction.self=An Sie selbst senden
funds.tx.proposal=Vorschlag
funds.tx.proposalTxFee=Vorschlag
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Sperre Kopplung
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Entsperre Kopplung
dao.tx.issuance=Entlohnungsanfrage/ausgabe
dao.tx.issuance.tooltip=Entlohnungsanfrage, die zur Ausgabe neuere BSQ führte.\nAusgabedatum: {0}
dao.tx.issuanceFromCompReq=Entlohnungsanfrage/ausgabe
dao.tx.issuanceFromCompReq.tooltip=Entlohnungsanfrage, die zur Ausgabe neuere BSQ führte.\nAusgabedatum: {0}
dao.proposal.create.missingFunds=Sie haben nicht genügend Gelder um den Vorschlag zu erstellen.\nFehlend: {0}
dao.feeTx.confirm=Bestätige {0} Transaktion

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=Δεν υπάρχουν διαθέσιμες συναλλ
funds.tx.revert=Revert
funds.tx.txSent=Η συναλλαγή απεστάλη επιτυχώς σε νέα διεύθυνση στο τοπικό πορτοφόλι Bisq.
funds.tx.direction.self=Αποστολή στον εαυτό σου
funds.tx.proposal=Πρόταση
funds.tx.proposalTxFee=Πρόταση
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=Αίτημα/έκδοση αποζημίωσης
dao.tx.issuance.tooltip=Αίτημα αποζημίωσης το οποίο οδήγησε σε έκδοση νέων BSQ.\nΗμερομηνία έκδοσης: {0}
dao.tx.issuanceFromCompReq=Αίτημα/έκδοση αποζημίωσης
dao.tx.issuanceFromCompReq.tooltip=Αίτημα αποζημίωσης το οποίο οδήγησε σε έκδοση νέων BSQ.\nΗμερομηνία έκδοσης: {0}
dao.proposal.create.missingFunds=Δεν έχεις επαρκή κεφάλαια για τη δημιουργία της πρότασης.\nΥπολείπονται: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=Sin transacciones disponibles
funds.tx.revert=Revertir
funds.tx.txSent=La transacción se ha enviado exitosamente a una nueva dirección en la billetera Bisq local.
funds.tx.direction.self=Enviado a usted mismo
funds.tx.proposal=Propuesta
funds.tx.proposalTxFee=Propuesta
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=Solicitud/emisión de compensación
dao.tx.issuance.tooltip=Solicitud de compensación que lleva a emitir nuevos BSQ.\nFecha de emisión: {0}
dao.tx.issuanceFromCompReq=Solicitud/emisión de compensación
dao.tx.issuanceFromCompReq.tooltip=Solicitud de compensación que lleva a emitir nuevos BSQ.\nFecha de emisión: {0}
dao.proposal.create.missingFunds=No tiene suficientes fondos para crear la propuesta.\nFaltan: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=هیچ تراکنشی موجود نیست
funds.tx.revert=عودت
funds.tx.txSent=تراکنش به طور موفقیت آمیز به یک آدرس جدید در کیف پول محلی Bisq ارسال شد.
funds.tx.direction.self=ارسال شده به خودتان
funds.tx.proposal=پیشنهاد
funds.tx.proposalTxFee=پیشنهاد
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=درخواست/صدور خسارت
dao.tx.issuance.tooltip=درخواست خسارت که منجر به یک صدور BSQ جدید می شود.\nتاریخ صدور: {0}
dao.tx.issuanceFromCompReq=درخواست/صدور خسارت
dao.tx.issuanceFromCompReq.tooltip=درخواست خسارت که منجر به یک صدور BSQ جدید می شود.\nتاریخ صدور: {0}
dao.proposal.create.missingFunds=شما وجوه کافی برای ایجاد پیشنهاد را ندارید.\nمقدار مورد نیاز: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -709,7 +709,7 @@ funds.tx.noTxAvailable=Aucune transaction disponible
funds.tx.revert=Défaire
funds.tx.txSent=Transaction envoyée avec succès à la nouvelle adresse dans le portefeuille local bisq.
funds.tx.direction.self=Sent to yourself
funds.tx.proposal=Proposition
funds.tx.proposalTxFee=Proposition
####################################################################
@ -1076,8 +1076,8 @@ dao.tx.type.enum.BLIND_VOTE=Fee for blind vote
dao.tx.type.enum.VOTE_REVEAL=Vote reveal
dao.tx.type.enum.LOCK_UP=Lock bonds
dao.tx.type.enum.UN_LOCK=Unlock bonds
dao.tx.issuance=Compensation request/issuance
dao.tx.issuance.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.proposal.create.confirm.info=Proposal fee: {0}\nMining fee: {1} ({2} satoshis/byte)\nTransaction size: {3} Kb\n\nAre you sure you want to publish the proposal?
dao.proposal.create.missingFunds=You don''t have sufficient funds for creating the proposal.\nMissing: {0}

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=Nincs hozzáférhető tranzakció
funds.tx.revert=Visszaszállás
funds.tx.txSent=Tranzakció sikeresen elküldve egy új címre a helyi Bisq pénztárcában.
funds.tx.direction.self=Küld saját magadnak.
funds.tx.proposal=Kártérítési kérelem
funds.tx.proposalTxFee=Kártérítési kérelem
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=Compensation request/issuance
dao.tx.issuance.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.proposal.create.missingFunds=Nem rendelkezik elegendő összegekkel a kártérítési kérelem létrehozásához.\nHiányzó: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=Sem transações disponíveis
funds.tx.revert=Reverter
funds.tx.txSent=Transação enviada com sucesso para um novo endereço em sua carteira Bisq local.
funds.tx.direction.self=Enviar para você mesmo
funds.tx.proposal=Proposta
funds.tx.proposalTxFee=Proposta
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=Compensation request/issuance
dao.tx.issuance.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.proposal.create.missingFunds=You don''t have sufficient funds for creating the proposal.\nMissing: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=Nicio tranzacție disponibilă
funds.tx.revert=Revenire
funds.tx.txSent=Tranzacția a fost virată cu succes la o nouă adresă în portofelul Bisq local.
funds.tx.direction.self=Trimite-ți ție
funds.tx.proposal=Solicitare de despăgubire
funds.tx.proposalTxFee=Solicitare de despăgubire
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=Compensation request/issuance
dao.tx.issuance.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.proposal.create.missingFunds=Nu ai suficiente fonduri pentru crearea solicitării de despăgubire.\nLipsesc: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=Транзакции недоступны
funds.tx.revert=Возврат
funds.tx.txSent=Транзакция успешно отправлена на новый адрес локального кошелька Bisq.
funds.tx.direction.self=Транзакция внутри кошелька
funds.tx.proposal=Предложение
funds.tx.proposalTxFee=Предложение
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Запереть гарантийный депозит
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Отпереть гарантийный депозит
dao.tx.issuance=Запрос/выдача компенсации
dao.tx.issuance.tooltip=Запрос компенсации, который привел к выпуску новых BSQ.\nДата выпуска: {0}
dao.tx.issuanceFromCompReq=Запрос/выдача компенсации
dao.tx.issuanceFromCompReq.tooltip=Запрос компенсации, который привел к выпуску новых BSQ.\nДата выпуска: {0}
dao.proposal.create.missingFunds=У Вас недостаточно средств для создания предложения.\nНехватает: {0}
dao.feeTx.confirm=Подтвердить транзакцию {0}

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=Nema dostupnih transakcija
funds.tx.revert=Vrati
funds.tx.txSent=Transakcija uspešno poslata na novu adresu u lokalnom Bisq novčaniku
funds.tx.direction.self=Transakcija unutar novčanika
funds.tx.proposal=Proposal
funds.tx.proposalTxFee=Proposal
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=Compensation request/issuance
dao.tx.issuance.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.proposal.create.missingFunds=You don''t have sufficient funds for creating the proposal.\nMissing: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=ไม่มีธุรกรรมใด ๆ
funds.tx.revert=กลับสู่สภาพเดิม
funds.tx.txSent=ธุรกรรมถูกส่งสำเร็จไปยังที่อยู่ใหม่ใน Bisq wallet ท้องถิ่นแล้ว
funds.tx.direction.self=ส่งถึงตัวคุณเอง
funds.tx.proposal=คำขอ
funds.tx.proposalTxFee=คำขอ
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=ค่าชดเชย คำขอ/การออก
dao.tx.issuance.tooltip=คำขอค่าชดเชยซึ่งนำไปสู่การออก BSQ ใหม่\nวันที่ออก: {0}
dao.tx.issuanceFromCompReq=ค่าชดเชย คำขอ/การออก
dao.tx.issuanceFromCompReq.tooltip=คำขอค่าชดเชยซึ่งนำไปสู่การออก BSQ ใหม่\nวันที่ออก: {0}
dao.proposal.create.missingFunds=คุณไม่มีเงินเพียงพอสำหรับการสร้างข้อเสนอ\nขาดไป: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=Không có giao dịch nào
funds.tx.revert=Khôi phục
funds.tx.txSent=GIao dịch đã gửi thành công tới địa chỉ mới trong ví Bisq nội bộ.
funds.tx.direction.self=Gửi cho chính bạn
funds.tx.proposal=Đề xuất
funds.tx.proposalTxFee=Đề xuất
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=Yêu cầu bồi thường/ban hành
dao.tx.issuance.tooltip=Yêu cầu bồi thường dẫn đến ban hành BSQ mới.\nNgày ban hành: {0}
dao.tx.issuanceFromCompReq=Yêu cầu bồi thường/ban hành
dao.tx.issuanceFromCompReq.tooltip=Yêu cầu bồi thường dẫn đến ban hành BSQ mới.\nNgày ban hành: {0}
dao.proposal.create.missingFunds=Bạn không có đủ tiền để tạo đề xuất.\nThiếu: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -701,7 +701,7 @@ funds.tx.noTxAvailable=没有可用交易
funds.tx.revert=还原
funds.tx.txSent=交易成功发送到本地Bisq钱包中的新地址。
funds.tx.direction.self=内部钱包交易
funds.tx.proposal=Proposal
funds.tx.proposalTxFee=Proposal
####################################################################
@ -1356,8 +1356,8 @@ dao.tx.type.enum.LOCKUP=Lock up bond
# suppress inspection "UnusedProperty"
dao.tx.type.enum.UNLOCK=Unlock bond
dao.tx.issuance=补偿请求/发行
dao.tx.issuance.tooltip=导致新BSQ发行的补偿请求\n发行日期: {0}
dao.tx.issuanceFromCompReq=补偿请求/发行
dao.tx.issuanceFromCompReq.tooltip=导致新BSQ发行的补偿请求\n发行日期: {0}
dao.proposal.create.missingFunds=You don''t have sufficient funds for creating the proposal.\nMissing: {0}
dao.feeTx.confirm=Confirm {0} transaction

View file

@ -35,6 +35,7 @@ import bisq.core.dao.governance.proposal.compensation.CompensationProposal;
import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposal;
import bisq.core.dao.governance.proposal.generic.GenericProposal;
import bisq.core.dao.governance.proposal.param.ChangeParamProposal;
import bisq.core.dao.governance.proposal.reimbursement.ReimbursementProposal;
import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposal;
import bisq.core.dao.governance.proposal.role.BondedRoleProposal;
import bisq.core.dao.governance.role.BondedRole;
@ -163,6 +164,7 @@ public class ProposalDisplay {
switch (proposalType) {
case COMPENSATION_REQUEST:
case REIMBURSEMENT_REQUEST:
break;
case CHANGE_PARAM:
titledGroupBgRowSpan = 6;
@ -203,14 +205,21 @@ public class ProposalDisplay {
int comboBoxValueTextFieldIndex = -1;
switch (proposalType) {
case COMPENSATION_REQUEST:
case REIMBURSEMENT_REQUEST:
requestedBsqTextField = addLabelInputTextField(gridPane, ++gridRow,
Res.get("dao.proposal.display.requestedBsq")).second;
BsqValidator bsqValidator = new BsqValidator(bsqFormatter);
bsqValidator.setMinValue(daoFacade.getMinCompensationRequestAmount());
bsqValidator.setMaxValue(daoFacade.getMaxCompensationRequestAmount());
checkNotNull(requestedBsqTextField, "requestedBsqTextField must not be null");
requestedBsqTextField.setValidator(bsqValidator);
inputControls.add(requestedBsqTextField);
BsqValidator bsqValidator = new BsqValidator(bsqFormatter);
if (proposalType == ProposalType.COMPENSATION_REQUEST) {
bsqValidator.setMinValue(daoFacade.getMinCompensationRequestAmount());
bsqValidator.setMaxValue(daoFacade.getMaxCompensationRequestAmount());
} else if (proposalType == ProposalType.REIMBURSEMENT_REQUEST) {
bsqValidator.setMinValue(daoFacade.getMinReimbursementRequestAmount());
bsqValidator.setMaxValue(daoFacade.getMaxReimbursementRequestAmount());
}
requestedBsqTextField.setValidator(bsqValidator);
break;
case CHANGE_PARAM:
checkNotNull(gridPane, "gridPane must not be null");
@ -445,6 +454,10 @@ public class ProposalDisplay {
CompensationProposal compensationProposal = (CompensationProposal) proposal;
checkNotNull(requestedBsqTextField, "requestedBsqTextField must not be null");
requestedBsqTextField.setText(bsqFormatter.formatCoinWithCode(compensationProposal.getRequestedBsq()));
} else if (proposal instanceof ReimbursementProposal) {
ReimbursementProposal reimbursementProposal = (ReimbursementProposal) proposal;
checkNotNull(requestedBsqTextField, "requestedBsqTextField must not be null");
requestedBsqTextField.setText(bsqFormatter.formatCoinWithCode(reimbursementProposal.getRequestedBsq()));
} else if (proposal instanceof ChangeParamProposal) {
ChangeParamProposal changeParamProposal = (ChangeParamProposal) proposal;
checkNotNull(paramComboBox, "paramComboBox must not be null");

View file

@ -263,6 +263,12 @@ public class MakeProposalView extends ActivatableView<GridPane, Void> implements
return daoFacade.getCompensationProposalWithTransaction(proposalDisplay.nameTextField.getText(),
proposalDisplay.linkInputTextField.getText(),
bsqFormatter.parseToCoin(proposalDisplay.requestedBsqTextField.getText()));
case REIMBURSEMENT_REQUEST:
checkNotNull(proposalDisplay.requestedBsqTextField,
"proposalDisplay.requestedBsqTextField must not be null");
return daoFacade.getReimbursementProposalWithTransaction(proposalDisplay.nameTextField.getText(),
proposalDisplay.linkInputTextField.getText(),
bsqFormatter.parseToCoin(proposalDisplay.requestedBsqTextField.getText()));
case CHANGE_PARAM:
checkNotNull(proposalDisplay.paramComboBox,
"proposalDisplay.paramComboBox must no tbe null");

View file

@ -17,7 +17,7 @@
package bisq.desktop.main.dao.governance.result;
import bisq.core.dao.governance.proposal.compensation.CompensationProposal;
import bisq.core.dao.governance.proposal.IssuanceProposal;
import bisq.core.dao.governance.voteresult.EvaluatedProposal;
import bisq.core.dao.state.DaoStateService;
import bisq.core.locale.Res;
@ -75,8 +75,8 @@ public class CycleListItem {
public String getIssuance() {
long totalIssuance = resultsOfCycle.getEvaluatedProposals().stream()
.filter(EvaluatedProposal::isAccepted)
.filter(e -> e.getProposal() instanceof CompensationProposal)
.map(e -> (CompensationProposal) e.getProposal())
.filter(e -> e.getProposal() instanceof IssuanceProposal)
.map(e -> (IssuanceProposal) e.getProposal())
.mapToLong(e -> e.getRequestedBsq().value)
.sum();
return bsqFormatter.formatCoinWithCode(Coin.valueOf(totalIssuance));

View file

@ -25,6 +25,7 @@ import bisq.core.dao.governance.proposal.Proposal;
import bisq.core.dao.governance.proposal.compensation.CompensationProposal;
import bisq.core.dao.governance.proposal.confiscatebond.ConfiscateBondProposal;
import bisq.core.dao.governance.proposal.param.ChangeParamProposal;
import bisq.core.dao.governance.proposal.reimbursement.ReimbursementProposal;
import bisq.core.dao.governance.proposal.removeAsset.RemoveAssetProposal;
import bisq.core.dao.governance.proposal.role.BondedRoleProposal;
import bisq.core.dao.governance.role.BondedRole;
@ -110,6 +111,10 @@ public class ProposalListItem {
CompensationProposal compensationProposal = (CompensationProposal) proposal;
Coin requestedBsq = evaluatedProposal.isAccepted() ? compensationProposal.getRequestedBsq() : Coin.ZERO;
return bsqFormatter.formatCoinWithCode(requestedBsq);
case REIMBURSEMENT_REQUEST:
ReimbursementProposal reimbursementProposal = (ReimbursementProposal) proposal;
requestedBsq = evaluatedProposal.isAccepted() ? reimbursementProposal.getRequestedBsq() : Coin.ZERO;
return bsqFormatter.formatCoinWithCode(requestedBsq);
case CHANGE_PARAM:
ChangeParamProposal changeParamProposal = (ChangeParamProposal) proposal;
return changeParamProposal.getParam().getDisplayString();

View file

@ -26,6 +26,7 @@ import bisq.desktop.util.Layout;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.blockchain.Block;
import bisq.core.dao.state.governance.IssuanceType;
import bisq.core.locale.Res;
import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Price;
@ -60,10 +61,10 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
private final BsqFormatter bsqFormatter;
private int gridRow = 0;
private TextField genesisIssueAmountTextField, compRequestIssueAmountTextField, availableAmountTextField,
private TextField genesisIssueAmountTextField, compRequestIssueAmountTextField, reimbursementAmountTextField, availableAmountTextField,
burntAmountTextField, totalLockedUpAmountTextField, totalUnlockingAmountTextField,
totalUnlockedAmountTextField, allTxTextField, burntTxTextField, utxoTextField, issuanceTxTextField,
priceTextField, marketCapTextField;
totalUnlockedAmountTextField, allTxTextField, burntTxTextField, utxoTextField, compensationIssuanceTxTextField,
reimbursementIssuanceTxTextField, priceTextField, marketCapTextField;
private ChangeListener<Number> priceChangeListener;
private HyperlinkWithIcon hyperlinkWithIcon;
@ -89,10 +90,11 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
public void initialize() {
gridRow = bsqBalanceUtil.addGroup(root, gridRow);
addTitledGroupBg(root, ++gridRow, 4, Res.get("dao.wallet.dashboard.distribution"), Layout.GROUP_DISTANCE);
addTitledGroupBg(root, ++gridRow, 5, Res.get("dao.wallet.dashboard.distribution"), Layout.GROUP_DISTANCE);
genesisIssueAmountTextField = addLabelTextField(root, gridRow, Res.get("dao.wallet.dashboard.genesisIssueAmount"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
compRequestIssueAmountTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.compRequestIssueAmount")).second;
reimbursementAmountTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.reimbursementAmount")).second;
burntAmountTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.burntAmount")).second;
availableAmountTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.availableAmount")).second;
@ -107,7 +109,7 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
marketCapTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.marketCap")).second;
addTitledGroupBg(root, ++gridRow, 6, Res.get("dao.wallet.dashboard.txDetails"), Layout.GROUP_DISTANCE);
addTitledGroupBg(root, ++gridRow, 7, Res.get("dao.wallet.dashboard.txDetails"), Layout.GROUP_DISTANCE);
addLabelTextField(root, gridRow, Res.get("dao.wallet.dashboard.genesisBlockHeight"),
String.valueOf(daoFacade.getGenesisBlockHeight()), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
hyperlinkWithIcon = addLabelHyperlinkWithIcon(root, ++gridRow, Res.get("dao.wallet.dashboard.genesisTxId"),
@ -115,7 +117,8 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", daoFacade.getGenesisTxId())));
allTxTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.allTx")).second;
utxoTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.utxo")).second;
issuanceTxTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.issuanceTx")).second;
compensationIssuanceTxTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.compensationIssuanceTx")).second;
reimbursementIssuanceTxTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.reimbursementIssuanceTx")).second;
burntTxTextField = addLabelTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.burntTx")).second;
priceChangeListener = (observable, oldValue, newValue) -> updatePrice();
@ -166,8 +169,10 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
Coin issuedAmountFromGenesis = daoFacade.getGenesisTotalSupply();
genesisIssueAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(issuedAmountFromGenesis));
Coin issuedAmountFromCompRequests = Coin.valueOf(daoFacade.getTotalIssuedAmountFromCompRequests());
Coin issuedAmountFromCompRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.COMPENSATION));
compRequestIssueAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(issuedAmountFromCompRequests));
Coin issuedAmountFromReimbursementRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT));
reimbursementAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(issuedAmountFromReimbursementRequests));
Coin burntFee = Coin.valueOf(daoFacade.getTotalBurntFee());
Coin totalLockedUpAmount = Coin.valueOf(daoFacade.getTotalLockupAmount());
@ -182,7 +187,8 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
totalUnlockedAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalUnlockedAmount));
allTxTextField.setText(String.valueOf(daoFacade.getTxs().size()));
utxoTextField.setText(String.valueOf(daoFacade.getUnspentTxOutputs().size()));
issuanceTxTextField.setText(String.valueOf(daoFacade.getIssuanceSet().size()));
compensationIssuanceTxTextField.setText(String.valueOf(daoFacade.getNumIssuanceTransactions(IssuanceType.COMPENSATION)));
reimbursementIssuanceTxTextField.setText(String.valueOf(daoFacade.getNumIssuanceTransactions(IssuanceType.REIMBURSEMENT)));
burntTxTextField.setText(String.valueOf(daoFacade.getFeeTxs().size()));
}

View file

@ -35,6 +35,7 @@ import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.blockchain.Block;
import bisq.core.dao.state.blockchain.TxType;
import bisq.core.dao.state.governance.IssuanceType;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.BsqFormatter;
@ -369,11 +370,19 @@ public class BsqTxView extends ActivatableView<GridPane, Void> implements BsqBal
Label label;
if (item.getConfirmations() > 0 && txType.ordinal() > TxType.INVALID.ordinal()) {
if (txType == TxType.COMPENSATION_REQUEST &&
daoFacade.isIssuanceTx(item.getTxId())) {
daoFacade.isIssuanceTx(item.getTxId(), IssuanceType.COMPENSATION)) {
if (field != null)
field.setOnAction(null);
labelString = Res.get("dao.tx.issuance");
labelString = Res.get("dao.tx.issuanceFromCompReq");
label = new AutoTooltipLabel(labelString);
setGraphic(label);
} else if (txType == TxType.REIMBURSEMENT_REQUEST &&
daoFacade.isIssuanceTx(item.getTxId(), IssuanceType.REIMBURSEMENT)) {
if (field != null)
field.setOnAction(null);
labelString = Res.get("dao.tx.issuanceFromReimbursement");
label = new AutoTooltipLabel(labelString);
setGraphic(label);
} else if (item.isBurnedBsqTx() || item.getAmount().isZero()) {
@ -535,13 +544,27 @@ public class BsqTxView extends ActivatableView<GridPane, Void> implements BsqBal
case PROPOSAL:
case COMPENSATION_REQUEST:
String txId = item.getTxId();
if (daoFacade.isIssuanceTx(txId)) {
if (daoFacade.isIssuanceTx(txId, IssuanceType.COMPENSATION)) {
awesomeIcon = AwesomeIcon.MONEY;
style = "dao-tx-type-issuance-icon";
int issuanceBlockHeight = daoFacade.getIssuanceBlockHeight(txId);
long blockTime = daoFacade.getBlockTime(issuanceBlockHeight);
String formattedDate = bsqFormatter.formatDateTime(new Date(blockTime));
toolTipText = Res.get("dao.tx.issuance.tooltip", formattedDate);
toolTipText = Res.get("dao.tx.issuanceFromCompReq.tooltip", formattedDate);
} else {
awesomeIcon = AwesomeIcon.FILE_TEXT;
style = "dao-tx-type-proposal-fee-icon";
}
break;
case REIMBURSEMENT_REQUEST:
txId = item.getTxId();
if (daoFacade.isIssuanceTx(txId, IssuanceType.REIMBURSEMENT)) {
awesomeIcon = AwesomeIcon.MONEY;
style = "dao-tx-type-issuance-icon";
int issuanceBlockHeight = daoFacade.getIssuanceBlockHeight(txId);
long blockTime = daoFacade.getBlockTime(issuanceBlockHeight);
String formattedDate = bsqFormatter.formatDateTime(new Date(blockTime));
toolTipText = Res.get("dao.tx.issuanceFromReimbursement.tooltip", formattedDate);
} else {
awesomeIcon = AwesomeIcon.FILE_TEXT;
style = "dao-tx-type-proposal-fee-icon";

View file

@ -132,10 +132,15 @@ class TransactionsListItem {
outgoing = false;
txFeeForBsqPayment = true;
//
final Optional<TxType> txTypeOptional = daoFacade.getOptionalTxType(txId);
if (txTypeOptional.isPresent() && txTypeOptional.get().equals(TxType.COMPENSATION_REQUEST))
details = Res.get("funds.tx.proposal");
Optional<TxType> txTypeOptional = daoFacade.getOptionalTxType(txId);
if (txTypeOptional.isPresent()) {
if (txTypeOptional.get().equals(TxType.COMPENSATION_REQUEST))
details = Res.get("funds.tx.compensationRequestTxFee");
else if (txTypeOptional.get().equals(TxType.REIMBURSEMENT_REQUEST))
details = Res.get("funds.tx.reimbursementRequestTxFee");
else
details = Res.get("funds.tx.proposalTxFee");
}
} else {
outgoing = true;
}