Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional txFeeRate parameter to api sendbsq #4900

Closed
wants to merge 8 commits into from
27 changes: 23 additions & 4 deletions apitest/src/test/java/bisq/apitest/method/MethodTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.TakeOfferRequest;
import bisq.proto.grpc.TradeInfo;
import bisq.proto.grpc.TxInfo;
import bisq.proto.grpc.UnlockWalletRequest;
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.WithdrawFundsRequest;
Expand Down Expand Up @@ -160,8 +161,14 @@ protected final GetUnusedBsqAddressRequest createGetUnusedBsqAddressRequest() {
return GetUnusedBsqAddressRequest.newBuilder().build();
}

protected final SendBsqRequest createSendBsqRequest(String address, String amount) {
return SendBsqRequest.newBuilder().setAddress(address).setAmount(amount).build();
protected final SendBsqRequest createSendBsqRequest(String address,
String amount,
String txFeeRate) {
return SendBsqRequest.newBuilder()
.setAddress(address)
.setAmount(amount)
.setTxFeeRate(txFeeRate)
.build();
}

protected final GetFundingAddressesRequest createGetFundingAddressesRequest() {
Expand Down Expand Up @@ -247,9 +254,21 @@ protected final String getUnusedBsqAddress(BisqAppConfig bisqAppConfig) {
return grpcStubs(bisqAppConfig).walletsService.getUnusedBsqAddress(createGetUnusedBsqAddressRequest()).getAddress();
}

protected final void sendBsq(BisqAppConfig bisqAppConfig, String address, String amount) {
protected final TxInfo sendBsq(BisqAppConfig bisqAppConfig,
String address,
String amount) {
return sendBsq(bisqAppConfig, address, amount, "");
}

protected final TxInfo sendBsq(BisqAppConfig bisqAppConfig,
String address,
String amount,
String txFeeRate) {
//noinspection ResultOfMethodCallIgnored
grpcStubs(bisqAppConfig).walletsService.sendBsq(createSendBsqRequest(address, amount));
return grpcStubs(bisqAppConfig).walletsService.sendBsq(createSendBsqRequest(address,
amount,
txFeeRate))
.getTxInfo();
}

protected final String getUnusedBtcAddress(BisqAppConfig bisqAppConfig) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public void testInitialBsqBalances(final TestInfo testInfo) {
@Order(3)
public void testSendBsqAndCheckBalancesBeforeGeneratingBtcBlock(final TestInfo testInfo) {
String bobsBsqAddress = getUnusedBsqAddress(bobdaemon);
sendBsq(alicedaemon, bobsBsqAddress, SEND_BSQ_AMOUNT);
sendBsq(alicedaemon, bobsBsqAddress, SEND_BSQ_AMOUNT, "100");
sleep(2000);

BsqBalanceInfo alicesBsqBalances = getBsqBalances(alicedaemon);
Expand Down
57 changes: 36 additions & 21 deletions cli/src/main/java/bisq/cli/CliMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.SetWalletPasswordRequest;
import bisq.proto.grpc.TakeOfferRequest;
import bisq.proto.grpc.TxInfo;
import bisq.proto.grpc.UnlockWalletRequest;
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
import bisq.proto.grpc.WithdrawFundsRequest;
Expand Down Expand Up @@ -259,19 +260,20 @@ public static void run(String[] args) {
throw new IllegalArgumentException("no bsq amount specified");

var amount = nonOptionArgs.get(2);
verifyStringIsValidDecimal(amount);

try {
Double.parseDouble(amount);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(format("'%s' is not a number", amount));
}
var txFeeRate = nonOptionArgs.size() == 4 ? nonOptionArgs.get(3) : "";
if (!txFeeRate.isEmpty())
verifyStringIsValidLong(txFeeRate);

var request = SendBsqRequest.newBuilder()
.setAddress(address)
.setAmount(amount)
.setTxFeeRate(txFeeRate)
.build();
walletsService.sendBsq(request);
out.printf("%s BSQ sent to %s%n", amount, address);
var reply = walletsService.sendBsq(request);
TxInfo txInfo = reply.getTxInfo();
out.printf("%s bsq sent to %s in tx %s%n", amount, address, txInfo.getId());
return;
}
case gettxfeerate: {
Expand All @@ -284,13 +286,7 @@ public static void run(String[] args) {
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no tx fee rate specified");

long txFeeRate;
try {
txFeeRate = Long.parseLong(nonOptionArgs.get(2));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(format("'%s' is not a number", nonOptionArgs.get(2)));
}

var txFeeRate = toLong(nonOptionArgs.get(2));
var request = SetTxFeeRatePreferenceRequest.newBuilder()
.setTxFeeRatePreference(txFeeRate)
.build();
Expand Down Expand Up @@ -560,12 +556,7 @@ public static void run(String[] args) {
if (nonOptionArgs.size() < 3)
throw new IllegalArgumentException("no unlock timeout specified");

long timeout;
try {
timeout = Long.parseLong(nonOptionArgs.get(2));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(format("'%s' is not a number", nonOptionArgs.get(2)));
}
var timeout = toLong(nonOptionArgs.get(2));
var request = UnlockWalletRequest.newBuilder()
.setPassword(nonOptionArgs.get(1))
.setTimeout(timeout).build();
Expand Down Expand Up @@ -627,6 +618,30 @@ private static Method getMethodFromCmd(String methodName) {
return Method.valueOf(methodName.toLowerCase());
}

private static void verifyStringIsValidDecimal(String param) {
try {
Double.parseDouble(param);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException(format("'%s' is not a number", param));
}
}

private static void verifyStringIsValidLong(String param) {
try {
Long.parseLong(param);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException(format("'%s' is not a number", param));
}
}

private static long toLong(String param) {
try {
return Long.parseLong(param);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException(format("'%s' is not a number", param));
}
}

private static File saveFileToDisk(String prefix,
@SuppressWarnings("SameParameterValue") String suffix,
String text) {
Expand Down Expand Up @@ -663,7 +678,7 @@ private static void printHelp(OptionParser parser, PrintStream stream) {
stream.format(rowFormat, "getaddressbalance", "address", "Get server wallet address balance");
stream.format(rowFormat, "getfundingaddresses", "", "Get BTC funding addresses");
stream.format(rowFormat, "getunusedbsqaddress", "", "Get unused BSQ address");
stream.format(rowFormat, "sendbsq", "address, amount", "Send BSQ");
stream.format(rowFormat, "sendbsq", "address, amount [,tx fee rate (sats/byte)]", "Send BSQ");
stream.format(rowFormat, "gettxfeerate", "", "Get current tx fee rate in sats/byte");
stream.format(rowFormat, "settxfeerate", "satoshis (per byte)", "Set custom tx fee rate in sats/byte");
stream.format(rowFormat, "unsettxfeerate", "", "Unset custom tx fee rate");
Expand Down
7 changes: 5 additions & 2 deletions core/src/main/java/bisq/core/api/CoreApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,11 @@ public String getUnusedBsqAddress() {
return walletsService.getUnusedBsqAddress();
}

public void sendBsq(String address, String amount, TxBroadcaster.Callback callback) {
walletsService.sendBsq(address, amount, callback);
public void sendBsq(String address,
String amount,
String txFeeRate,
TxBroadcaster.Callback callback) {
walletsService.sendBsq(address, amount, txFeeRate, callback);
}

public void getTxFeeRate(ResultHandler resultHandler) {
Expand Down
18 changes: 16 additions & 2 deletions core/src/main/java/bisq/core/api/CoreWalletsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,27 @@ String getUnusedBsqAddress() {

void sendBsq(String address,
String amount,
String txFeeRate,
TxBroadcaster.Callback callback) {
try {
LegacyAddress legacyAddress = getValidBsqLegacyAddress(address);
Coin receiverAmount = getValidBsqTransferAmount(amount);
BsqTransferModel model = bsqTransferService.getBsqTransferModel(legacyAddress, receiverAmount);

// A non txFeeRate String value overrides the fee service and custom fee.
Coin txFeePerVbyte = txFeeRate.isEmpty()
? btcWalletService.getTxFeeForWithdrawalPerVbyte()
: Coin.valueOf(Long.parseLong(txFeeRate));

BsqTransferModel model = bsqTransferService.getBsqTransferModel(legacyAddress,
receiverAmount,
txFeePerVbyte);
log.info("Sending {} BSQ to {} with tx fee rate {} sats/byte).",
amount,
address,
txFeePerVbyte.value);
bsqTransferService.sendFunds(model, callback);
} catch (InsufficientMoneyException
} catch (NumberFormatException
| InsufficientMoneyException
| BsqChangeBelowDustException
| TransactionVerificationException
| WalletException ex) {
Expand Down
84 changes: 84 additions & 0 deletions core/src/main/java/bisq/core/api/model/TxInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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.api.model;

import bisq.common.Payload;

import org.bitcoinj.core.Transaction;

import lombok.EqualsAndHashCode;
import lombok.Getter;

@EqualsAndHashCode
@Getter
public class TxInfo implements Payload {

private final String id;
private final long outputSum;
private final long fee;
private final int size;

public TxInfo(String id, long outputSum, long fee, int size) {
this.id = id;
this.outputSum = outputSum;
this.fee = fee;
this.size = size;
}

public static TxInfo toTxInfo(Transaction transaction) {
if (transaction == null)
throw new IllegalStateException("server created a null transaction");

return new TxInfo(transaction.getTxId().toString(),
transaction.getOutputSum().value,
transaction.getFee().value,
transaction.getMessageSize());
}

//////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
//////////////////////////////////////////////////////////////////////////////////////

@Override
public bisq.proto.grpc.TxInfo toProtoMessage() {
return bisq.proto.grpc.TxInfo.newBuilder()
.setId(id)
.setOutputSum(outputSum)
.setFee(fee)
.setSize(size)
.build();
}

@SuppressWarnings("unused")
public static TxInfo fromProto(bisq.proto.grpc.TxInfo proto) {
return new TxInfo(proto.getId(),
proto.getOutputSum(),
proto.getFee(),
proto.getSize());
}

@Override
public String toString() {
return "TxInfo{" + "\n" +
" id='" + id + '\'' + "\n" +
", outputSum=" + outputSum + " sats" + "\n" +
", fee=" + fee + " sats" + "\n" +
", size=" + size + " bytes" + "\n" +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ public BsqTransferService(WalletsManager walletsManager,
}

public BsqTransferModel getBsqTransferModel(LegacyAddress address,
Coin receiverAmount)
Coin receiverAmount,
Coin txFeePerVbyte)
throws TransactionVerificationException,
WalletException,
BsqChangeBelowDustException,
InsufficientMoneyException {

Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(address.toString(), receiverAmount);
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, true);
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, txFeePerVbyte);
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);

return new BsqTransferModel(address,
Expand Down
Loading