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 new api method 'sendbtc' #4901

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 49 additions & 4 deletions apitest/src/test/java/bisq/apitest/method/MethodTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
import bisq.proto.grpc.SendBsqRequest;
import bisq.proto.grpc.SendBtcRequest;
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
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 +162,24 @@ 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 SendBtcRequest createSendBtcRequest(String address,
String amount,
String txFeeRate) {
return SendBtcRequest.newBuilder()
.setAddress(address)
.setAmount(amount)
.setTxFeeRate(txFeeRate)
.build();
}

protected final GetFundingAddressesRequest createGetFundingAddressesRequest() {
Expand Down Expand Up @@ -247,9 +265,36 @@ 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
return grpcStubs(bisqAppConfig).walletsService.sendBsq(createSendBsqRequest(address,
amount,
txFeeRate))
.getTxInfo();
}

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

protected final TxInfo sendBtc(BisqAppConfig bisqAppConfig,
String address,
String amount,
String txFeeRate) {
//noinspection ResultOfMethodCallIgnored
grpcStubs(bisqAppConfig).walletsService.sendBsq(createSendBsqRequest(address, amount));
return grpcStubs(bisqAppConfig).walletsService.sendBtc(createSendBtcRequest(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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static bisq.cli.TableFormat.formatBtcBalanceInfoTbl;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;


Expand Down Expand Up @@ -55,10 +56,10 @@ public void testInitialBtcBalances(final TestInfo testInfo) {
// Bob & Alice's regtest Bisq wallets were initialized with 10 BTC.

BtcBalanceInfo alicesBalances = getBtcBalances(alicedaemon);
log.info("{} Alice's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(alicesBalances));
log.debug("{} Alice's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(alicesBalances));

BtcBalanceInfo bobsBalances = getBtcBalances(bobdaemon);
log.info("{} Bob's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(bobsBalances));
log.debug("{} Bob's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(bobsBalances));

assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), alicesBalances.getAvailableBalance());
assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), bobsBalances.getAvailableBalance());
Expand All @@ -75,7 +76,7 @@ public void testFundAlicesBtcWallet(final TestInfo testInfo) {
// New balance is 12.5 BTC
assertEquals(1250000000, btcBalanceInfo.getAvailableBalance());

log.info("{} -> Alice's Funded Address Balance -> \n{}",
log.debug("{} -> Alice's Funded Address Balance -> \n{}",
testName(testInfo),
formatAddressBalanceTbl(singletonList(getAddressBalance(alicedaemon, newAddress))));

Expand All @@ -87,11 +88,43 @@ public void testFundAlicesBtcWallet(final TestInfo testInfo) {
1250000000,
0);
verifyBtcBalances(alicesExpectedBalances, btcBalanceInfo);
log.info("{} -> Alice's BTC Balances After Sending 2.5 BTC -> \n{}",
log.debug("{} -> Alice's BTC Balances After Sending 2.5 BTC -> \n{}",
testName(testInfo),
formatBtcBalanceInfoTbl(btcBalanceInfo));
}

@Test
@Order(3)
public void testAliceSendBTCToBob(TestInfo testInfo) {
String bobsBtcAddress = getUnusedBtcAddress(bobdaemon);
log.debug("Sending most of Alice's BTC to Bob @ {}", bobsBtcAddress);

sendBtc(alicedaemon, bobsBtcAddress, "5.50", "100");
genBtcBlocksThenWait(1, 3000);

BtcBalanceInfo alicesBalances = getBtcBalances(alicedaemon);

log.debug("{} Alice's BTC Balances:\n{}",
testName(testInfo),
formatBtcBalanceInfoTbl(alicesBalances));
bisq.core.api.model.BtcBalanceInfo alicesExpectedBalances =
bisq.core.api.model.BtcBalanceInfo.valueOf(700000000,
0,
700000000,
0);
verifyBtcBalances(alicesExpectedBalances, alicesBalances);

BtcBalanceInfo bobsBalances = getBtcBalances(bobdaemon);
log.debug("{} Bob's BTC Balances:\n{}",
testName(testInfo),
formatBtcBalanceInfoTbl(bobsBalances));
// We cannot (?) predict the exact tx size and calculate how much in tx fees were
// deducted from the 5.5 BTC sent to Bob, but we do know Bob should have something
// between 15.49978000 and 15.49978100 BTC.
assertTrue(bobsBalances.getAvailableBalance() >= 1549978000);
assertTrue(bobsBalances.getAvailableBalance() <= 1549978100);
}

@AfterAll
public static void tearDown() {
tearDownScaffold();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public void testBtcWalletFunding(final TestInfo testInfo) {

btcWalletTest.testInitialBtcBalances(testInfo);
btcWalletTest.testFundAlicesBtcWallet(testInfo);
btcWalletTest.testAliceSendBTCToBob(testInfo);
}

@Test
Expand Down
86 changes: 65 additions & 21 deletions cli/src/main/java/bisq/cli/CliMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@
import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.RemoveWalletPasswordRequest;
import bisq.proto.grpc.SendBsqRequest;
import bisq.proto.grpc.SendBtcRequest;
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 @@ -110,6 +112,7 @@ private enum Method {
getfundingaddresses,
getunusedbsqaddress,
sendbsq,
sendbtc,
gettxfeerate,
settxfeerate,
unsettxfeerate,
Expand Down Expand Up @@ -259,19 +262,46 @@ 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 sendbtc: {
if (nonOptionArgs.size() < 2)
throw new IllegalArgumentException("no btc address specified");

var address = nonOptionArgs.get(1);

if (nonOptionArgs.size() < 3)
throw new IllegalArgumentException("no btc amount specified");

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

var txFeeRate = nonOptionArgs.size() == 4 ? nonOptionArgs.get(3) : "";
if (!txFeeRate.isEmpty())
verifyStringIsValidLong(txFeeRate);

var request = SendBtcRequest.newBuilder()
.setAddress(address)
.setAmount(amount)
.setTxFeeRate(txFeeRate)
.build();
var reply = walletsService.sendBtc(request);
TxInfo txInfo = reply.getTxInfo();
out.printf("%s btc sent to %s in tx %s%n", amount, address, txInfo.getId());
return;
}
case gettxfeerate: {
Expand All @@ -284,13 +314,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 +584,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 +646,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 +706,8 @@ 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, "sendbtc", "address, amount [,tx fee rate (sats/byte)]", "Send BTC");
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
17 changes: 15 additions & 2 deletions core/src/main/java/bisq/core/api/CoreApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@
import bisq.common.handlers.ResultHandler;

import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;

import javax.inject.Inject;
import javax.inject.Singleton;

import com.google.common.util.concurrent.FutureCallback;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -246,8 +249,18 @@ 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 sendBtc(String address,
String amount,
String txFeeRate,
FutureCallback<Transaction> callback) {
walletsService.sendBtc(address, amount, txFeeRate, callback);
}

public void getTxFeeRate(ResultHandler resultHandler) {
Expand Down
Loading