mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 07:07:43 +01:00
Add deposit funds screen
This commit is contained in:
parent
605cf27eaf
commit
fa11cb4a6b
23 changed files with 675 additions and 37 deletions
|
@ -62,6 +62,22 @@ public class Log {
|
|||
logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
|
||||
logbackLogger.setLevel(useDetailedLogging ? Level.TRACE : Level.INFO);
|
||||
logbackLogger.addAppender(appender);
|
||||
|
||||
// log errors in separate file
|
||||
// not working as expected still.... damn logback...
|
||||
/* FileAppender errorAppender = new FileAppender();
|
||||
errorAppender.setEncoder(encoder);
|
||||
errorAppender.setName("Error");
|
||||
errorAppender.setContext(loggerContext);
|
||||
errorAppender.setFile(fileName + "_error.log");
|
||||
LevelFilter levelFilter = new LevelFilter();
|
||||
levelFilter.setLevel(Level.ERROR);
|
||||
levelFilter.setOnMatch(FilterReply.ACCEPT);
|
||||
levelFilter.setOnMismatch(FilterReply.DENY);
|
||||
levelFilter.start();
|
||||
errorAppender.addFilter(levelFilter);
|
||||
errorAppender.start();
|
||||
logbackLogger.addAppender(errorAppender);*/
|
||||
}
|
||||
|
||||
public static void traceCall() {
|
||||
|
|
|
@ -499,7 +499,7 @@ public class DisputeManager {
|
|||
contract.getBuyerPayoutAddressString(),
|
||||
contract.getSellerPayoutAddressString(),
|
||||
disputeResult.getArbitratorAddressAsString(),
|
||||
walletService.getAddressEntryByOfferId(dispute.getTradeId()),
|
||||
walletService.getTradeAddressEntry(dispute.getTradeId()),
|
||||
contract.getBuyerBtcPubKey(),
|
||||
contract.getSellerBtcPubKey(),
|
||||
disputeResult.getArbitratorPubKey()
|
||||
|
|
|
@ -44,8 +44,10 @@ public final class AddressEntry implements Persistable {
|
|||
private static final Logger log = LoggerFactory.getLogger(AddressEntry.class);
|
||||
|
||||
public enum Context {
|
||||
SAVINGS,
|
||||
TRADE,
|
||||
ARBITRATOR
|
||||
ARBITRATOR,
|
||||
DAO
|
||||
}
|
||||
|
||||
// keyPair can be null in case the object is created from deserialization as it is transient.
|
||||
|
|
|
@ -66,9 +66,16 @@ public final class AddressEntryList extends ArrayList<AddressEntry> implements P
|
|||
}
|
||||
}
|
||||
|
||||
public AddressEntry getNewAddressEntry(AddressEntry.Context context, String offerId) {
|
||||
public AddressEntry getNewTradeAddressEntry(String offerId) {
|
||||
log.trace("getNewAddressEntry called with offerId " + offerId);
|
||||
AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context, offerId);
|
||||
AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), AddressEntry.Context.TRADE, offerId);
|
||||
add(addressEntry);
|
||||
storage.queueUpForSave();
|
||||
return addressEntry;
|
||||
}
|
||||
|
||||
public AddressEntry getNewSavingsAddressEntry() {
|
||||
AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), AddressEntry.Context.SAVINGS);
|
||||
add(addressEntry);
|
||||
storage.queueUpForSave();
|
||||
return addressEntry;
|
||||
|
|
|
@ -312,18 +312,28 @@ public class WalletService {
|
|||
return ImmutableList.copyOf(addressEntryList);
|
||||
}
|
||||
|
||||
public List<AddressEntry> getSavingsAddressEntryList() {
|
||||
return getAddressEntryList().stream()
|
||||
.filter(e -> e.getContext().equals(AddressEntry.Context.SAVINGS))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public AddressEntry getArbitratorAddressEntry() {
|
||||
return arbitratorAddressEntry;
|
||||
}
|
||||
|
||||
public AddressEntry getAddressEntryByOfferId(String offerId) {
|
||||
public AddressEntry getTradeAddressEntry(String offerId) {
|
||||
Optional<AddressEntry> addressEntry = getAddressEntryList().stream()
|
||||
.filter(e -> offerId.equals(e.getOfferId()))
|
||||
.findAny();
|
||||
if (addressEntry.isPresent())
|
||||
return addressEntry.get();
|
||||
else
|
||||
return addressEntryList.getNewAddressEntry(AddressEntry.Context.TRADE, offerId);
|
||||
return addressEntryList.getNewTradeAddressEntry(offerId);
|
||||
}
|
||||
|
||||
public AddressEntry getNewSavingsAddressEntry() {
|
||||
return addressEntryList.getNewSavingsAddressEntry();
|
||||
}
|
||||
|
||||
private Optional<AddressEntry> getAddressEntryByAddress(String address) {
|
||||
|
|
|
@ -302,7 +302,7 @@ public class TradeManager {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onWithdrawRequest(String toAddress, KeyParameter aesKey, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) {
|
||||
AddressEntry addressEntry = walletService.getAddressEntryByOfferId(trade.getId());
|
||||
AddressEntry addressEntry = walletService.getTradeAddressEntry(trade.getId());
|
||||
String fromAddress = addressEntry.getAddressString();
|
||||
|
||||
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
|
||||
|
|
|
@ -45,7 +45,7 @@ public class BroadcastCreateOfferFeeTx extends Task<PlaceOfferModel> {
|
|||
try {
|
||||
runInterceptHook();
|
||||
Coin totalsNeeded = FeePolicy.getSecurityDeposit().add(FeePolicy.getCreateOfferFee()).add(FeePolicy.getFixedTxFeeForTrades());
|
||||
AddressEntry addressEntry = model.walletService.getAddressEntryByOfferId(model.offer.getId());
|
||||
AddressEntry addressEntry = model.walletService.getTradeAddressEntry(model.offer.getId());
|
||||
Coin balance = model.walletService.getBalanceForAddress(addressEntry.getAddress());
|
||||
if (balance.compareTo(totalsNeeded) >= 0) {
|
||||
model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback<Transaction>() {
|
||||
|
|
|
@ -44,7 +44,7 @@ public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
|
|||
log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress);
|
||||
Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress);
|
||||
Transaction transaction = model.tradeWalletService.createTradingFeeTx(
|
||||
model.walletService.getAddressEntryByOfferId(model.offer.getId()),
|
||||
model.walletService.getTradeAddressEntry(model.offer.getId()),
|
||||
FeePolicy.getCreateOfferFee(),
|
||||
selectedArbitrator.getBtcAddress());
|
||||
|
||||
|
|
|
@ -180,7 +180,7 @@ public class ProcessModel implements Model, Serializable {
|
|||
}
|
||||
|
||||
public AddressEntry getAddressEntry() {
|
||||
return walletService.getAddressEntryByOfferId(offer.getId());
|
||||
return walletService.getTradeAddressEntry(offer.getId());
|
||||
}
|
||||
|
||||
public byte[] getTradeWalletPubKey() {
|
||||
|
|
|
@ -51,7 +51,7 @@ public class SetupDepositBalanceListener extends TradeTask {
|
|||
runInterceptHook();
|
||||
|
||||
WalletService walletService = processModel.getWalletService();
|
||||
Address address = walletService.getAddressEntryByOfferId(trade.getId()).getAddress();
|
||||
Address address = walletService.getTradeAddressEntry(trade.getId()).getAddress();
|
||||
balanceListener = new BalanceListener(address) {
|
||||
@Override
|
||||
public void onBalanceChanged(Coin balance, Transaction tx) {
|
||||
|
|
|
@ -82,7 +82,7 @@ public class TextFieldWithCopyIcon extends AnchorPane {
|
|||
// Getter/Setter
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private String getText() {
|
||||
public String getText() {
|
||||
return text.get();
|
||||
}
|
||||
|
||||
|
|
|
@ -684,7 +684,7 @@ public class MainViewModel implements ViewModel {
|
|||
|
||||
private void updateReservedBalance() {
|
||||
Coin sum = Coin.valueOf(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
|
||||
.map(tradable -> walletService.getAddressEntryByOfferId(tradable.getId()))
|
||||
.map(tradable -> walletService.getTradeAddressEntry(tradable.getId()))
|
||||
.map(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()))
|
||||
.mapToLong(Coin::getValue)
|
||||
.sum());
|
||||
|
@ -703,7 +703,7 @@ public class MainViewModel implements ViewModel {
|
|||
if (trade.getContract() != null &&
|
||||
trade.getTradeAmount() != null &&
|
||||
trade.getContract().getSellerPayoutAddressString()
|
||||
.equals(walletService.getAddressEntryByOfferId(trade.getId()).getAddressString())) {
|
||||
.equals(walletService.getTradeAddressEntry(trade.getId()).getAddressString())) {
|
||||
balanceInDeposit = balanceInDeposit.add(trade.getTradeAmount());
|
||||
}
|
||||
return balanceInDeposit;
|
||||
|
|
|
@ -16,16 +16,16 @@
|
|||
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.Tab?>
|
||||
<?import javafx.scene.control.TabPane?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<TabPane fx:id="root" fx:controller="io.bitsquare.gui.main.funds.FundsView"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
|
||||
AnchorPane.topAnchor="0.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<Tab fx:id="reservedTab" text="Reserved/Locked funds" closable="false"/>
|
||||
<Tab fx:id="depositTab" text="Deposit funds" closable="false"/>
|
||||
<Tab fx:id="withdrawalTab" text="Available for withdrawal" closable="false"/>
|
||||
<Tab fx:id="reservedTab" text="Reserved/Locked funds" closable="false"/>
|
||||
<Tab fx:id="transactionsTab" text="Transactions" closable="false"/>
|
||||
|
||||
</TabPane>
|
|
@ -17,17 +17,14 @@
|
|||
|
||||
package io.bitsquare.gui.main.funds;
|
||||
|
||||
import io.bitsquare.app.BitsquareApp;
|
||||
import io.bitsquare.common.util.Utilities;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.model.Activatable;
|
||||
import io.bitsquare.gui.common.view.*;
|
||||
import io.bitsquare.gui.main.MainView;
|
||||
import io.bitsquare.gui.main.funds.deposit.DepositView;
|
||||
import io.bitsquare.gui.main.funds.reserved.ReservedView;
|
||||
import io.bitsquare.gui.main.funds.transactions.TransactionsView;
|
||||
import io.bitsquare.gui.main.funds.withdrawal.WithdrawalView;
|
||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||
import io.bitsquare.user.Preferences;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Tab;
|
||||
|
@ -39,7 +36,7 @@ import javax.inject.Inject;
|
|||
public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
|
||||
|
||||
@FXML
|
||||
Tab reservedTab, withdrawalTab, transactionsTab;
|
||||
Tab depositTab, withdrawalTab, reservedTab, transactionsTab;
|
||||
|
||||
private Navigation.Listener navigationListener;
|
||||
private ChangeListener<Tab> tabChangeListener;
|
||||
|
@ -47,13 +44,11 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
|
||||
private final ViewLoader viewLoader;
|
||||
private final Navigation navigation;
|
||||
private final Preferences preferences;
|
||||
|
||||
@Inject
|
||||
public FundsView(CachingViewLoader viewLoader, Navigation navigation, Preferences preferences) {
|
||||
public FundsView(CachingViewLoader viewLoader, Navigation navigation) {
|
||||
this.viewLoader = viewLoader;
|
||||
this.navigation = navigation;
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,10 +59,12 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
};
|
||||
|
||||
tabChangeListener = (ov, oldValue, newValue) -> {
|
||||
if (newValue == reservedTab)
|
||||
navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class);
|
||||
if (newValue == depositTab)
|
||||
navigation.navigateTo(MainView.class, FundsView.class, DepositView.class);
|
||||
else if (newValue == withdrawalTab)
|
||||
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
|
||||
else if (newValue == reservedTab)
|
||||
navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class);
|
||||
else if (newValue == transactionsTab)
|
||||
navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class);
|
||||
};
|
||||
|
@ -78,15 +75,17 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener);
|
||||
navigation.addListener(navigationListener);
|
||||
|
||||
if (root.getSelectionModel().getSelectedItem() == reservedTab)
|
||||
navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class);
|
||||
if (root.getSelectionModel().getSelectedItem() == depositTab)
|
||||
navigation.navigateTo(MainView.class, FundsView.class, DepositView.class);
|
||||
else if (root.getSelectionModel().getSelectedItem() == withdrawalTab)
|
||||
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
|
||||
else if (root.getSelectionModel().getSelectedItem() == reservedTab)
|
||||
navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class);
|
||||
else if (root.getSelectionModel().getSelectedItem() == transactionsTab)
|
||||
navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class);
|
||||
|
||||
String key = "tradeWalletInfoAtFunds";
|
||||
if (!BitsquareApp.DEV_MODE)
|
||||
/* if (!BitsquareApp.DEV_MODE)
|
||||
new Popup().backgroundInfo("Bitsquare does not use a single application wallet, but dedicated wallets for every trade.\n\n" +
|
||||
"Funding of the wallet will be done when needed, for instance when you create or take an offer.\n" +
|
||||
"Withdrawing funds can be done after a trade is completed.\n\n" +
|
||||
|
@ -96,7 +95,7 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
.onAction(() -> Utilities.openWebPage("https://bitsquare.io/faq"))
|
||||
.closeButtonText("I understand")
|
||||
.dontShowAgainId(key, preferences)
|
||||
.show();
|
||||
.show();*/
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -113,10 +112,12 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
|
||||
View view = viewLoader.load(viewClass);
|
||||
|
||||
if (view instanceof ReservedView)
|
||||
currentTab = reservedTab;
|
||||
if (view instanceof DepositView)
|
||||
currentTab = depositTab;
|
||||
else if (view instanceof WithdrawalView)
|
||||
currentTab = withdrawalTab;
|
||||
else if (view instanceof ReservedView)
|
||||
currentTab = reservedTab;
|
||||
else if (view instanceof TransactionsView)
|
||||
currentTab = transactionsTab;
|
||||
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare 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.
|
||||
*
|
||||
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.gui.main.funds.deposit;
|
||||
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.btc.listeners.TxConfidenceListener;
|
||||
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DepositListItem {
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
private final StringProperty balance = new SimpleStringProperty();
|
||||
private final WalletService walletService;
|
||||
private BSFormatter formatter;
|
||||
private final ConfidenceProgressIndicator progressIndicator;
|
||||
private final Tooltip tooltip;
|
||||
|
||||
|
||||
private String balanceString;
|
||||
private String addressString;
|
||||
private String status = "Unused";
|
||||
private TxConfidenceListener txConfidenceListener;
|
||||
|
||||
// public DepositListItem(AddressEntry addressEntry, Transaction transaction, WalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) {
|
||||
public DepositListItem(AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) {
|
||||
this.walletService = walletService;
|
||||
this.formatter = formatter;
|
||||
|
||||
addressString = addressEntry.getAddressString();
|
||||
|
||||
// confidence
|
||||
progressIndicator = new ConfidenceProgressIndicator();
|
||||
progressIndicator.setId("funds-confidence");
|
||||
tooltip = new Tooltip("Not used yet");
|
||||
progressIndicator.setProgress(0);
|
||||
progressIndicator.setPrefHeight(30);
|
||||
progressIndicator.setPrefWidth(30);
|
||||
Tooltip.install(progressIndicator, tooltip);
|
||||
|
||||
final Address address = addressEntry.getAddress();
|
||||
walletService.addBalanceListener(new BalanceListener(address) {
|
||||
@Override
|
||||
public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) {
|
||||
DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin));
|
||||
updateConfidence(walletService.getConfidenceForTxId(tx.getHashAsString()));
|
||||
if (balanceAsCoin.isPositive())
|
||||
status = "Funded";
|
||||
}
|
||||
});
|
||||
|
||||
Coin balanceAsCoin = walletService.getBalanceForAddress(address);
|
||||
balance.set(formatter.formatCoin(balanceAsCoin));
|
||||
if (balanceAsCoin.isPositive())
|
||||
status = "Funded";
|
||||
|
||||
TransactionConfidence transactionConfidence = walletService.getConfidenceForAddress(address);
|
||||
if (transactionConfidence != null) {
|
||||
updateConfidence(transactionConfidence);
|
||||
|
||||
txConfidenceListener = new TxConfidenceListener(transactionConfidence.getTransactionHash().toString()) {
|
||||
@Override
|
||||
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
|
||||
updateConfidence(confidence);
|
||||
}
|
||||
};
|
||||
walletService.addTxConfidenceListener(txConfidenceListener);
|
||||
}
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
walletService.removeTxConfidenceListener(txConfidenceListener);
|
||||
}
|
||||
|
||||
private void updateConfidence(TransactionConfidence confidence) {
|
||||
if (confidence != null) {
|
||||
switch (confidence.getConfidenceType()) {
|
||||
case UNKNOWN:
|
||||
tooltip.setText("Unknown transaction status");
|
||||
progressIndicator.setProgress(0);
|
||||
break;
|
||||
case PENDING:
|
||||
tooltip.setText("Seen by " + confidence.numBroadcastPeers() + " peer(s) / 0 confirmations");
|
||||
progressIndicator.setProgress(-1.0);
|
||||
break;
|
||||
case BUILDING:
|
||||
tooltip.setText("Confirmed in " + confidence.getDepthInBlocks() + " block(s)");
|
||||
progressIndicator.setProgress(Math.min(1, (double) confidence.getDepthInBlocks() / 6.0));
|
||||
break;
|
||||
case DEAD:
|
||||
tooltip.setText("Transaction is invalid.");
|
||||
progressIndicator.setProgress(0);
|
||||
break;
|
||||
}
|
||||
|
||||
progressIndicator.setPrefSize(24, 24);
|
||||
}
|
||||
}
|
||||
|
||||
public ConfidenceProgressIndicator getProgressIndicator() {
|
||||
return progressIndicator;
|
||||
}
|
||||
|
||||
public String getAddressString() {
|
||||
return addressString;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public final StringProperty balanceProperty() {
|
||||
return this.balance;
|
||||
}
|
||||
|
||||
public String getBalance() {
|
||||
return balance.get();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ This file is part of Bitsquare.
|
||||
~
|
||||
~ Bitsquare 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.
|
||||
~
|
||||
~ Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.cell.PropertyValueFactory?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.deposit.DepositView"
|
||||
spacing="10" xmlns:fx="http://javafx.com/fxml">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||
</padding>
|
||||
|
||||
<TableView fx:id="table" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn text="Select" fx:id="selectColumn" minWidth="110" maxWidth="110" sortable="false"/>
|
||||
<TableColumn text="Address" fx:id="addressColumn" minWidth="260"/>
|
||||
<TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="150" maxWidth="150">
|
||||
<cellValueFactory>
|
||||
<PropertyValueFactory property="balance"/>
|
||||
</cellValueFactory>
|
||||
</TableColumn>
|
||||
|
||||
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="150" maxWidth="150"/>
|
||||
<TableColumn text="Status" fx:id="statusColumn" minWidth="150" maxWidth="150"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
|
||||
<GridPane fx:id="gridPane" hgap="5.0" vgap="5.0">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||
</padding>
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="RIGHT" hgrow="NEVER"/>
|
||||
<ColumnConstraints hgrow="ALWAYS"/>
|
||||
<ColumnConstraints hgrow="NEVER"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
||||
</VBox>
|
|
@ -0,0 +1,395 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare 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.
|
||||
*
|
||||
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.gui.main.funds.deposit;
|
||||
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import io.bitsquare.btc.AddressEntry;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import io.bitsquare.common.util.Utilities;
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import io.bitsquare.gui.components.AddressTextField;
|
||||
import io.bitsquare.gui.components.HyperlinkWithIcon;
|
||||
import io.bitsquare.gui.components.TitledGroupBg;
|
||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||
import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
|
||||
import io.bitsquare.gui.main.overlays.windows.QRCodeWindow;
|
||||
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
|
||||
import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import io.bitsquare.gui.util.Layout;
|
||||
import io.bitsquare.gui.util.validation.BtcAddressValidator;
|
||||
import io.bitsquare.trade.TradeManager;
|
||||
import io.bitsquare.trade.closed.ClosedTradableManager;
|
||||
import io.bitsquare.trade.failed.FailedTradesManager;
|
||||
import io.bitsquare.trade.offer.OpenOfferManager;
|
||||
import io.bitsquare.user.Preferences;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import net.glxn.qrgen.QRCode;
|
||||
import net.glxn.qrgen.image.ImageType;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.uri.BitcoinURI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import static io.bitsquare.gui.util.FormBuilder.*;
|
||||
|
||||
@FxmlView
|
||||
public class DepositView extends ActivatableView<VBox, Void> {
|
||||
|
||||
@FXML
|
||||
GridPane gridPane;
|
||||
|
||||
@FXML
|
||||
TableView<DepositListItem> table;
|
||||
@FXML
|
||||
TableColumn<DepositListItem, DepositListItem> selectColumn, addressColumn, balanceColumn, confidenceColumn, statusColumn;
|
||||
private ImageView qrCodeImageView;
|
||||
private int gridRow = 0;
|
||||
private AddressTextField addressTextField;
|
||||
Button generateNewAddressButton;
|
||||
|
||||
private final WalletService walletService;
|
||||
private final TradeManager tradeManager;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
private final FailedTradesManager failedTradesManager;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
private final BSFormatter formatter;
|
||||
private final Preferences preferences;
|
||||
private final BtcAddressValidator btcAddressValidator;
|
||||
private final WalletPasswordWindow walletPasswordWindow;
|
||||
private final OfferDetailsWindow offerDetailsWindow;
|
||||
private final TradeDetailsWindow tradeDetailsWindow;
|
||||
private final ObservableList<DepositListItem> depositAddresses = FXCollections.observableArrayList();
|
||||
private BalanceListener balanceListener;
|
||||
private TitledGroupBg titledGroupBg;
|
||||
private Label addressLabel;
|
||||
private Label qrCodeLabel;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private DepositView(WalletService walletService, TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
FailedTradesManager failedTradesManager, OpenOfferManager openOfferManager,
|
||||
BSFormatter formatter, Preferences preferences,
|
||||
BtcAddressValidator btcAddressValidator, WalletPasswordWindow walletPasswordWindow,
|
||||
OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) {
|
||||
this.walletService = walletService;
|
||||
this.tradeManager = tradeManager;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.failedTradesManager = failedTradesManager;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.formatter = formatter;
|
||||
this.preferences = preferences;
|
||||
this.btcAddressValidator = btcAddressValidator;
|
||||
this.walletPasswordWindow = walletPasswordWindow;
|
||||
this.offerDetailsWindow = offerDetailsWindow;
|
||||
this.tradeDetailsWindow = tradeDetailsWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
table.setPlaceholder(new Label("No deposit addresses are generated yet"));
|
||||
|
||||
setSelectColumnCellFactory();
|
||||
setAddressColumnCellFactory();
|
||||
setStatusColumnCellFactory();
|
||||
setConfidenceColumnCellFactory();
|
||||
|
||||
titledGroupBg = addTitledGroupBg(gridPane, gridRow, 2, "Fund your wallet");
|
||||
|
||||
qrCodeLabel = addLabel(gridPane, gridRow, "QR-Code:", 0);
|
||||
//GridPane.setMargin(qrCodeLabel, new Insets(Layout.FIRST_ROW_DISTANCE - 9, 0, 0, 5));
|
||||
|
||||
qrCodeImageView = new ImageView();
|
||||
qrCodeImageView.setStyle("-fx-cursor: hand;");
|
||||
Tooltip.install(qrCodeImageView, new Tooltip("Open large QR-Code window"));
|
||||
qrCodeImageView.setOnMouseClicked(e -> new QRCodeWindow(getBitcoinURI()).show());
|
||||
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
||||
GridPane.setColumnIndex(qrCodeImageView, 1);
|
||||
GridPane.setMargin(qrCodeImageView, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 0));
|
||||
gridPane.getChildren().add(qrCodeImageView);
|
||||
|
||||
Tuple2<Label, AddressTextField> addressTuple = addLabelAddressTextField(gridPane, ++gridRow, "Address:");
|
||||
addressLabel = addressTuple.first;
|
||||
GridPane.setValignment(addressLabel, VPos.TOP);
|
||||
GridPane.setMargin(addressLabel, new Insets(3, 0, 0, 0));
|
||||
addressTextField = addressTuple.second;
|
||||
|
||||
titledGroupBg.setVisible(false);
|
||||
titledGroupBg.setManaged(false);
|
||||
qrCodeLabel.setVisible(false);
|
||||
qrCodeLabel.setManaged(false);
|
||||
qrCodeImageView.setVisible(false);
|
||||
qrCodeImageView.setManaged(false);
|
||||
addressLabel.setVisible(false);
|
||||
addressLabel.setManaged(false);
|
||||
addressTextField.setVisible(false);
|
||||
addressTextField.setManaged(false);
|
||||
|
||||
generateNewAddressButton = addButton(gridPane, ++gridRow, "Generate new address", -20);
|
||||
|
||||
generateNewAddressButton.setOnAction(event -> {
|
||||
boolean hasUnUsedAddress = walletService.getSavingsAddressEntryList().stream()
|
||||
.filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isZero())
|
||||
.findAny().isPresent();
|
||||
if (hasUnUsedAddress) {
|
||||
new Popup().warning("You have already addresses generated which are still not used.\n" +
|
||||
"Please select in the address table an unused address.").show();
|
||||
} else {
|
||||
AddressEntry newSavingsAddressEntry = walletService.getNewSavingsAddressEntry();
|
||||
fillForm(newSavingsAddressEntry.getAddressString());
|
||||
updateList();
|
||||
}
|
||||
});
|
||||
|
||||
balanceListener = new BalanceListener() {
|
||||
@Override
|
||||
public void onBalanceChanged(Coin balance, Transaction tx) {
|
||||
updateList();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getBitcoinURI() {
|
||||
return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(),
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
updateList();
|
||||
|
||||
walletService.addBalanceListener(balanceListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
depositAddresses.forEach(DepositListItem::cleanup);
|
||||
walletService.removeBalanceListener(balanceListener);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// UI handlers
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
private void fillForm(String address) {
|
||||
titledGroupBg.setVisible(true);
|
||||
titledGroupBg.setManaged(true);
|
||||
qrCodeLabel.setVisible(true);
|
||||
qrCodeLabel.setManaged(true);
|
||||
qrCodeImageView.setVisible(true);
|
||||
qrCodeImageView.setManaged(true);
|
||||
addressLabel.setVisible(true);
|
||||
addressLabel.setManaged(true);
|
||||
addressTextField.setVisible(true);
|
||||
addressTextField.setManaged(true);
|
||||
|
||||
GridPane.setMargin(generateNewAddressButton, new Insets(15, 0, 0, 0));
|
||||
|
||||
addressTextField.setAddress(address);
|
||||
|
||||
final byte[] imageBytes = QRCode
|
||||
.from(getBitcoinURI())
|
||||
.withSize(150, 150) // code has 41 elements 8 px is border with 150 we get 3x scale and min. border
|
||||
.to(ImageType.PNG)
|
||||
.stream()
|
||||
.toByteArray();
|
||||
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
|
||||
qrCodeImageView.setImage(qrImage);
|
||||
|
||||
}
|
||||
|
||||
private void openBlockExplorer(DepositListItem item) {
|
||||
if (item.getAddressString() != null) {
|
||||
try {
|
||||
Utilities.openWebPage(preferences.getBlockChainExplorer().addressUrl + item.getAddressString());
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
new Popup().warning("Opening browser failed. Please check your internet " +
|
||||
"connection.").show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void updateList() {
|
||||
depositAddresses.clear();
|
||||
walletService.getSavingsAddressEntryList().stream()
|
||||
.forEach(e -> depositAddresses.add(new DepositListItem(e, walletService, formatter)));
|
||||
table.setItems(depositAddresses);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ColumnCellFactories
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void setStatusColumnCellFactory() {
|
||||
statusColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||
statusColumn.setCellFactory(new Callback<TableColumn<DepositListItem, DepositListItem>,
|
||||
TableCell<DepositListItem, DepositListItem>>() {
|
||||
|
||||
@Override
|
||||
public TableCell<DepositListItem, DepositListItem> call(TableColumn<DepositListItem,
|
||||
DepositListItem> column) {
|
||||
return new TableCell<DepositListItem, DepositListItem>() {
|
||||
|
||||
@Override
|
||||
public void updateItem(final DepositListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
setGraphic(new Label(item.getStatus()));
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setSelectColumnCellFactory() {
|
||||
selectColumn.setCellValueFactory((addressListItem) ->
|
||||
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||
selectColumn.setCellFactory(
|
||||
new Callback<TableColumn<DepositListItem, DepositListItem>, TableCell<DepositListItem,
|
||||
DepositListItem>>() {
|
||||
|
||||
@Override
|
||||
public TableCell<DepositListItem, DepositListItem> call(TableColumn<DepositListItem,
|
||||
DepositListItem> column) {
|
||||
return new TableCell<DepositListItem, DepositListItem>() {
|
||||
|
||||
Button button;
|
||||
|
||||
@Override
|
||||
public void updateItem(final DepositListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
if (button == null) {
|
||||
button = new Button("Select");
|
||||
button.setOnAction(e -> fillForm(item.getAddressString()));
|
||||
setGraphic(button);
|
||||
}
|
||||
} else {
|
||||
setGraphic(null);
|
||||
if (button != null) {
|
||||
button.setOnAction(null);
|
||||
button = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setAddressColumnCellFactory() {
|
||||
addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||
addressColumn.setCellFactory(
|
||||
new Callback<TableColumn<DepositListItem, DepositListItem>, TableCell<DepositListItem,
|
||||
DepositListItem>>() {
|
||||
|
||||
@Override
|
||||
public TableCell<DepositListItem, DepositListItem> call(TableColumn<DepositListItem,
|
||||
DepositListItem> column) {
|
||||
return new TableCell<DepositListItem, DepositListItem>() {
|
||||
|
||||
private HyperlinkWithIcon field;
|
||||
|
||||
@Override
|
||||
public void updateItem(final DepositListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
String addressString = item.getAddressString();
|
||||
field = new HyperlinkWithIcon(addressString, AwesomeIcon.EXTERNAL_LINK);
|
||||
field.setOnAction(event -> openBlockExplorer(item));
|
||||
field.setTooltip(new Tooltip("Open external blockchain explorer for " +
|
||||
"address: " + addressString));
|
||||
setGraphic(field);
|
||||
} else {
|
||||
setGraphic(null);
|
||||
if (field != null)
|
||||
field.setOnAction(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setConfidenceColumnCellFactory() {
|
||||
confidenceColumn.setCellValueFactory((addressListItem) ->
|
||||
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||
confidenceColumn.setCellFactory(
|
||||
new Callback<TableColumn<DepositListItem, DepositListItem>, TableCell<DepositListItem,
|
||||
DepositListItem>>() {
|
||||
|
||||
@Override
|
||||
public TableCell<DepositListItem, DepositListItem> call(TableColumn<DepositListItem,
|
||||
DepositListItem> column) {
|
||||
return new TableCell<DepositListItem, DepositListItem>() {
|
||||
|
||||
@Override
|
||||
public void updateItem(final DepositListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
setGraphic(item.getProgressIndicator());
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ public class ReservedView extends ActivatableView<VBox, Void> {
|
|||
private void updateList() {
|
||||
reservedAddresses.forEach(ReservedListItem::cleanup);
|
||||
reservedAddresses.setAll(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
|
||||
.map(tradable -> new ReservedListItem(tradable, walletService.getAddressEntryByOfferId(tradable.getOffer().getId()), walletService, formatter))
|
||||
.map(tradable -> new ReservedListItem(tradable, walletService.getTradeAddressEntry(tradable.getOffer().getId()), walletService, formatter))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
reservedAddresses.sort((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate()));
|
||||
|
|
|
@ -127,7 +127,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
|||
this.formatter = formatter;
|
||||
|
||||
offerId = UUID.randomUUID().toString();
|
||||
addressEntry = walletService.getAddressEntryByOfferId(offerId);
|
||||
addressEntry = walletService.getTradeAddressEntry(offerId);
|
||||
offerFeeAsCoin = FeePolicy.getCreateOfferFee();
|
||||
networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades();
|
||||
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
|
||||
|
|
|
@ -734,6 +734,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
|||
qrCodeImageView = new ImageView();
|
||||
qrCodeImageView.setVisible(false);
|
||||
qrCodeImageView.setStyle("-fx-cursor: hand;");
|
||||
Tooltip.install(qrCodeImageView, new Tooltip("Open large QR-Code window"));
|
||||
qrCodeImageView.setOnMouseClicked(e -> new QRCodeWindow(getBitcoinURI()).show());
|
||||
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
||||
GridPane.setColumnIndex(qrCodeImageView, 2);
|
||||
|
|
|
@ -155,7 +155,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
void initWithData(Offer offer) {
|
||||
this.offer = offer;
|
||||
|
||||
addressEntry = walletService.getAddressEntryByOfferId(offer.getId());
|
||||
addressEntry = walletService.getTradeAddressEntry(offer.getId());
|
||||
checkNotNull(addressEntry, "addressEntry must not be null");
|
||||
|
||||
ObservableList<PaymentAccount> possiblePaymentAccounts = getPossiblePaymentAccounts();
|
||||
|
|
|
@ -668,6 +668,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
qrCodeImageView = new ImageView();
|
||||
qrCodeImageView.setVisible(false);
|
||||
qrCodeImageView.setStyle("-fx-cursor: hand;");
|
||||
Tooltip.install(qrCodeImageView, new Tooltip("Open large QR-Code window"));
|
||||
qrCodeImageView.setOnMouseClicked(e -> new QRCodeWindow(getBitcoinURI()).show());
|
||||
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
||||
GridPane.setColumnIndex(qrCodeImageView, 2);
|
||||
|
|
|
@ -159,7 +159,7 @@ public class BuyerStep5View extends TradeStepView {
|
|||
private void reviewWithdrawal() {
|
||||
Coin senderAmount = trade.getPayoutAmount();
|
||||
WalletService walletService = model.dataModel.walletService;
|
||||
AddressEntry fromAddressesEntry = walletService.getAddressEntryByOfferId(trade.getId());
|
||||
AddressEntry fromAddressesEntry = walletService.getTradeAddressEntry(trade.getId());
|
||||
String fromAddresses = fromAddressesEntry.getAddressString();
|
||||
String toAddresses = withdrawAddressTextField.getText();
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue