From 328ecf34fe825d0ad1174469b42d12e46769ba9c Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 15 Apr 2014 01:29:34 +0200 Subject: [PATCH] integrate bitcoinj WalletAppKit --- .gitignore | 2 + pom.xml | 78 ++++++++++--- src/main/java/io/bitsquare/BitSquare.java | 78 ++++++++++--- .../java/io/bitsquare/btc/IWalletFacade.java | 10 ++ .../io/bitsquare/btc/MockWalletFacade.java | 52 --------- .../java/io/bitsquare/btc/WalletFacade.java | 97 ++++++++++++++++ .../java/io/bitsquare/di/BitSquareModule.java | 69 +++++++++++- .../java/io/bitsquare/gui/MainController.java | 105 ++++++++++++++---- src/main/java/io/bitsquare/gui/MainView.fxml | 4 +- .../bitsquare/gui/funds/FundsController.java | 9 +- .../io/bitsquare/gui/funds/FundsView.fxml | 2 +- .../tradeprocess/TradeProcessController.java | 2 +- .../io/bitsquare/trade/TradingFacade.java | 6 +- .../trade/payment/process/PaymentProcess.java | 6 +- src/main/resources/wallet/checkpoints | Bin 0 -> 12885 bytes 15 files changed, 408 insertions(+), 112 deletions(-) delete mode 100644 src/main/java/io/bitsquare/btc/MockWalletFacade.java create mode 100644 src/main/java/io/bitsquare/btc/WalletFacade.java create mode 100644 src/main/resources/wallet/checkpoints diff --git a/.gitignore b/.gitignore index 89362a333a..e733251672 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ /bin .idea bitsquare.iml +.spvchain +.wallet diff --git a/pom.xml b/pom.xml index c878cf8081..24ffeb56ce 100644 --- a/pom.xml +++ b/pom.xml @@ -4,14 +4,21 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - bitsquare + io.bitsquare bitsquare - 0.1 + 0.01-SNAPSHOT BitSquare A P2P Fiat-Bitcoin Exchange https://www.bitsquare.io + + com.google + bitcoinj-parent + 0.12-SNAPSHOT + libs/bitcoinj/pom.xml + + Apache 2 @@ -24,6 +31,31 @@ https://github.com/bitsquare/bitsquare + + + sonatype-oss-snapshot + + https://oss.sonatype.org/content/repositories/snapshots + + + sonatype-oss-snapshot2 + + https://oss.sonatype.org/content/groups/public + + + + bitcoinj-release + + http://distribution.bitcoinj.googlecode.com/git/releases + + + + mvn-adamgent + http://mvn-adamgent.googlecode.com/svn/maven/release + Adam Gent Maven Repository + + + @@ -33,24 +65,27 @@ src/main/resources - org.apache.maven.plugins - maven-jar-plugin - 2.4 + maven-compiler-plugin + 3.1 - - - io.bitsquare.BitSquare - - + 1.8 + 1.8 + + + com.google + bitcoinj + 0.12-SNAPSHOT + + junit junit @@ -60,7 +95,7 @@ org.slf4j - slf4j-api + slf4j-jdk14 1.7.6 @@ -80,8 +115,14 @@ com.google.guava - guava-base - r03 + guava + 16.0.1 + + + + com.aquafx-project + aquafx + 0.1 @@ -97,6 +138,17 @@ 8.0.5 + + de.jensd + fontawesomefx + 8.0.0 + + + net.glxn + qrgen + 1.3 + + diff --git a/src/main/java/io/bitsquare/BitSquare.java b/src/main/java/io/bitsquare/BitSquare.java index 23130450e7..7fe0ea6838 100644 --- a/src/main/java/io/bitsquare/BitSquare.java +++ b/src/main/java/io/bitsquare/BitSquare.java @@ -1,12 +1,18 @@ package io.bitsquare; +import com.google.bitcoin.store.BlockStoreException; +import com.google.bitcoin.utils.BriefLogFormatter; +import com.google.bitcoin.utils.Threading; +import com.google.common.base.Throwables; import com.google.inject.Guice; import com.google.inject.Injector; +import io.bitsquare.btc.IWalletFacade; import io.bitsquare.di.BitSquareModule; import io.bitsquare.di.GuiceFXMLLoader; import io.bitsquare.setup.ISetup; import io.bitsquare.setup.MockSetup; import javafx.application.Application; +import javafx.application.Platform; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; @@ -18,6 +24,7 @@ import java.io.IOException; public class BitSquare extends Application { private static final Logger log = LoggerFactory.getLogger(BitSquare.class); + private IWalletFacade walletFacade; public static void main(String[] args) { @@ -25,27 +32,66 @@ public class BitSquare extends Application } @Override - public void start(Stage stage) + public void start(Stage stage) throws Exception { - Injector injector = Guice.createInjector(new BitSquareModule()); - ISetup setup = injector.getInstance(MockSetup.class); + // Show the crash dialog for any exceptions that we don't handle and that hit the main loop. + //GuiUtils.handleCrashesOnThisThread(); + try + { + init(stage); + } catch (Throwable t) + { + // Nicer message for the case where the block store file is locked. + if (Throwables.getRootCause(t) instanceof BlockStoreException) + { + //GuiUtils.informationalAlert("Already running", "This application is already running and cannot be started twice."); + } + else + { + throw t; + } + } + } + @Override + public void stop() throws Exception + { + walletFacade.terminateWallet(); + + super.stop(); + } + + private void init(Stage stage) throws IOException + { + // Make log output concise. + BriefLogFormatter.init(); + + // Tell bitcoinj to run event handlers on the JavaFX UI thread. This keeps things simple and means + // we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener + // we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in + // a future version. + Threading.USER_THREAD = Platform::runLater; + + final Injector injector = Guice.createInjector(new BitSquareModule()); + walletFacade = injector.getInstance(IWalletFacade.class); + + final ISetup setup = injector.getInstance(MockSetup.class); setup.applyPersistedData(); stage.setTitle("BitSquare"); - GuiceFXMLLoader loader = new GuiceFXMLLoader(injector); - try - { - Parent mainView = loader.load(BitSquare.class.getResourceAsStream("/io/bitsquare/gui/MainView.fxml")); - Scene scene = new Scene(mainView, 800, 600); - scene.getStylesheets().setAll(getClass().getResource("/io/bitsquare/gui/global.css").toExternalForm()); - stage.setScene(scene); - stage.show(); - } catch (IOException e) - { - e.printStackTrace(); - } - } + // main view + final GuiceFXMLLoader loader = new GuiceFXMLLoader(injector); + final Parent mainView = loader.load(BitSquare.class.getResourceAsStream("/io/bitsquare/gui/MainView.fxml")); + final Scene scene = new Scene(mainView, 800, 600); + stage.setScene(scene); + // apply css + final String global = getClass().getResource("/io/bitsquare/gui/global.css").toExternalForm(); + // final String textValidation = getClass().getResource("/wallettemplate/utils/text-validation.css").toExternalForm(); + //scene.getStylesheets().setAll(global, textValidation); + scene.getStylesheets().setAll(global); + + stage.show(); + } } diff --git a/src/main/java/io/bitsquare/btc/IWalletFacade.java b/src/main/java/io/bitsquare/btc/IWalletFacade.java index 3981da16bb..3c36732e3d 100644 --- a/src/main/java/io/bitsquare/btc/IWalletFacade.java +++ b/src/main/java/io/bitsquare/btc/IWalletFacade.java @@ -1,9 +1,19 @@ package io.bitsquare.btc; +import com.google.bitcoin.core.PeerEventListener; + import java.math.BigInteger; public interface IWalletFacade { + public static final String MAIN_NET = "MAIN_NET"; + public static final String TEST_NET = "TEST_NET"; + public static final String REG_TEST_NET = "REG_TEST_NET"; + + void initWallet(PeerEventListener peerEventListener); + + void terminateWallet(); + BigInteger getBalance(); boolean pay(BigInteger satoshisToPay, String destinationAddress); diff --git a/src/main/java/io/bitsquare/btc/MockWalletFacade.java b/src/main/java/io/bitsquare/btc/MockWalletFacade.java deleted file mode 100644 index 3443a2bdbe..0000000000 --- a/src/main/java/io/bitsquare/btc/MockWalletFacade.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.bitsquare.btc; - - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigInteger; -import java.util.UUID; - -/** - * Gateway to wallet - */ -public class MockWalletFacade implements IWalletFacade -{ - private static final Logger log = LoggerFactory.getLogger(MockWalletFacade.class); - - private BigInteger balance; - - public MockWalletFacade() - { - balance = new BigInteger("100000000"); - } - - @Override - public BigInteger getBalance() - { - return balance; - } - - @Override - public boolean pay(BigInteger satoshisToPay, String destinationAddress) - { - if (getBalance().subtract(satoshisToPay).longValue() > 0) - { - log.info("Pay " + satoshisToPay.toString() + " Satoshis to " + destinationAddress); - return true; - } - else - { - log.warn("Not enough funds in wallet for paying " + satoshisToPay.toString() + " Satoshis."); - return false; - } - - } - - @Override - public KeyPair createNewAddress() - { - //MOCK - return new KeyPair(UUID.randomUUID().toString(), UUID.randomUUID().toString()); - } -} diff --git a/src/main/java/io/bitsquare/btc/WalletFacade.java b/src/main/java/io/bitsquare/btc/WalletFacade.java new file mode 100644 index 0000000000..853b0f30ce --- /dev/null +++ b/src/main/java/io/bitsquare/btc/WalletFacade.java @@ -0,0 +1,97 @@ +package io.bitsquare.btc; + +import com.google.bitcoin.core.NetworkParameters; +import com.google.bitcoin.core.PeerEventListener; +import com.google.bitcoin.kits.WalletAppKit; +import com.google.bitcoin.params.MainNetParams; +import com.google.bitcoin.params.RegTestParams; +import com.google.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.UUID; + +public class WalletFacade implements IWalletFacade +{ + private static final Logger log = LoggerFactory.getLogger(WalletFacade.class); + private NetworkParameters networkParameters; + private WalletAppKit walletAppKit; + + private BigInteger balance; + + @Inject + public WalletFacade(NetworkParameters networkParameters, WalletAppKit walletAppKit) + { + this.networkParameters = networkParameters; + this.walletAppKit = walletAppKit; + + balance = new BigInteger("100000000"); + } + + public void initWallet(PeerEventListener peerEventListener) + { + if (networkParameters == RegTestParams.get()) + { + walletAppKit.connectToLocalHost(); // You should run a regtest mode bitcoind locally. + } + else if (networkParameters == MainNetParams.get()) + { + // Checkpoints are block headers that ship inside our app: for a new user, we pick the last header + // in the checkpoints file and then download the rest from the network. It makes things much faster. + // Checkpoint files are made using the BuildCheckpoints tool and usually we have to download the + // last months worth or more (takes a few seconds). + walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints")); + } + + // Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen + // or progress widget to keep the user engaged whilst we initialise, but we don't. + walletAppKit.setDownloadListener(peerEventListener) + .setBlockingStartup(false) + .setUserAgent("BitSquare", "1.0"); + walletAppKit.startAsync(); + walletAppKit.awaitRunning(); + // Don't make the user wait for confirmations for now, as the intention is they're sending it their own money! + walletAppKit.wallet().allowSpendingUnconfirmedTransactions(); + walletAppKit.peerGroup().setMaxConnections(11); + + log.info(walletAppKit.wallet().toString()); + } + + public void terminateWallet() + { + walletAppKit.stopAsync(); + walletAppKit.awaitTerminated(); + } + + + //MOCK... + @Override + public BigInteger getBalance() + { + return balance; + } + + @Override + public boolean pay(BigInteger satoshisToPay, String destinationAddress) + { + if (getBalance().subtract(satoshisToPay).longValue() > 0) + { + log.info("Pay " + satoshisToPay.toString() + " Satoshis to " + destinationAddress); + return true; + } + else + { + log.warn("Not enough funds in wallet for paying " + satoshisToPay.toString() + " Satoshis."); + return false; + } + + } + + @Override + public KeyPair createNewAddress() + { + //MOCK + return new KeyPair(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + } +} diff --git a/src/main/java/io/bitsquare/di/BitSquareModule.java b/src/main/java/io/bitsquare/di/BitSquareModule.java index 4190481eb1..683689ffeb 100644 --- a/src/main/java/io/bitsquare/di/BitSquareModule.java +++ b/src/main/java/io/bitsquare/di/BitSquareModule.java @@ -1,10 +1,19 @@ package io.bitsquare.di; +import com.google.bitcoin.core.NetworkParameters; +import com.google.bitcoin.kits.WalletAppKit; +import com.google.bitcoin.params.MainNetParams; +import com.google.bitcoin.params.RegTestParams; +import com.google.bitcoin.params.TestNet3Params; import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.name.Named; +import com.google.inject.name.Names; import io.bitsquare.btc.BlockChainFacade; import io.bitsquare.btc.IWalletFacade; -import io.bitsquare.btc.MockWalletFacade; +import io.bitsquare.btc.WalletFacade; import io.bitsquare.crypto.ICryptoFacade; import io.bitsquare.crypto.MockCryptoFacade; import io.bitsquare.msg.IMessageFacade; @@ -21,8 +30,12 @@ import io.bitsquare.trade.orderbook.MockOrderBook; import io.bitsquare.trade.orderbook.OrderBookFilter; import io.bitsquare.user.User; +import java.io.File; + public class BitSquareModule extends AbstractModule { + + @Override protected void configure() { @@ -35,10 +48,62 @@ public class BitSquareModule extends AbstractModule bind(OrderBookFilterSettings.class).asEagerSingleton(); bind(ICryptoFacade.class).to(MockCryptoFacade.class).asEagerSingleton(); - bind(IWalletFacade.class).to(MockWalletFacade.class).asEagerSingleton(); + bind(IWalletFacade.class).to(WalletFacade.class).asEagerSingleton(); bind(BlockChainFacade.class).asEagerSingleton(); bind(IMessageFacade.class).to(MessageFacade.class).asEagerSingleton(); bind(TradingFacade.class).asEagerSingleton(); + + bind(String.class).annotatedWith(Names.named("networkType")).toInstance(IWalletFacade.MAIN_NET); + bind(NetworkParameters.class).toProvider(NetworkParametersProvider.class).asEagerSingleton(); + bind(WalletAppKit.class).toProvider(WalletAppKitProvider.class).asEagerSingleton(); + } + + +} + +class WalletAppKitProvider implements Provider +{ + private NetworkParameters networkParameters; + + @Inject + public WalletAppKitProvider(NetworkParameters networkParameters) + { + this.networkParameters = networkParameters; + } + + public WalletAppKit get() + { + return new WalletAppKit(networkParameters, new File("."), "bitsquare"); + } +} + +class NetworkParametersProvider implements Provider +{ + private String networkType; + + @Inject + public NetworkParametersProvider(@Named("networkType") String networkType) + { + this.networkType = networkType; + } + + public NetworkParameters get() + { + NetworkParameters result = null; + + switch (networkType) + { + case IWalletFacade.MAIN_NET: + result = MainNetParams.get(); + break; + case IWalletFacade.TEST_NET: + result = TestNet3Params.get(); + break; + case IWalletFacade.REG_TEST_NET: + result = RegTestParams.get(); + break; + } + return result; } } \ No newline at end of file diff --git a/src/main/java/io/bitsquare/gui/MainController.java b/src/main/java/io/bitsquare/gui/MainController.java index 7125b4b684..ff373ad3bd 100644 --- a/src/main/java/io/bitsquare/gui/MainController.java +++ b/src/main/java/io/bitsquare/gui/MainController.java @@ -1,13 +1,18 @@ package io.bitsquare.gui; +import com.google.bitcoin.core.DownloadListener; import com.google.inject.Inject; import io.bitsquare.BitSquare; +import io.bitsquare.btc.IWalletFacade; import io.bitsquare.di.GuiceFXMLLoader; import io.bitsquare.gui.trade.TradeController; import io.bitsquare.gui.util.Icons; import io.bitsquare.settings.Settings; import io.bitsquare.trade.Direction; import io.bitsquare.trade.orderbook.OrderBookFilter; +import javafx.animation.FadeTransition; +import javafx.animation.Interpolator; +import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; @@ -15,61 +20,59 @@ import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Node; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Label; -import javafx.scene.control.ToggleButton; -import javafx.scene.control.ToggleGroup; +import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; +import javafx.util.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.URL; import java.util.Currency; +import java.util.Date; import java.util.ResourceBundle; public class MainController implements Initializable, INavigationController { + private static final Logger log = LoggerFactory.getLogger(MainController.class); + private Settings settings; private OrderBookFilter orderBookFilter; + private IWalletFacade walletFacade; private IChildController childController; private ToggleGroup toggleGroup; private ToggleButton prevToggleButton; private Image prevToggleButtonIcon; + public ProgressBar networkSyncProgressBar; + public Label networkSyncInfoLabel; @FXML public Pane contentPane; - public HBox leftNavPane, rightNavPane; + public HBox leftNavPane, rightNavPane, footerContainer; @Inject - public MainController(Settings settings, OrderBookFilter orderBookFilter) + public MainController(Settings settings, OrderBookFilter orderBookFilter, IWalletFacade walletFacade) { this.settings = settings; this.orderBookFilter = orderBookFilter; + this.walletFacade = walletFacade; } @Override public void initialize(URL url, ResourceBundle rb) { - toggleGroup = new ToggleGroup(); + buildNavigation(); - ToggleButton homeButton = addNavButton(leftNavPane, "Overview", Icons.HOME, Icons.HOME, INavigationController.HOME); - ToggleButton buyButton = addNavButton(leftNavPane, "Buy BTC", Icons.NAV_BUY, Icons.NAV_BUY_ACTIVE, INavigationController.TRADE, Direction.BUY); - ToggleButton sellButton = addNavButton(leftNavPane, "Sell BTC", Icons.NAV_SELL, Icons.NAV_SELL_ACTIVE, INavigationController.TRADE, Direction.SELL); - addNavButton(leftNavPane, "Orders", Icons.ORDERS, Icons.ORDERS, INavigationController.ORDERS); - addNavButton(leftNavPane, "History", Icons.HISTORY, Icons.HISTORY, INavigationController.HISTORY); - addNavButton(leftNavPane, "Funds", Icons.FUNDS, Icons.FUNDS, INavigationController.FUNDS); - addNavButton(leftNavPane, "Message", Icons.MSG, Icons.MSG, INavigationController.MSG); - addCurrencyComboBox(); - addNavButton(rightNavPane, "Settings", Icons.SETTINGS, Icons.SETTINGS, INavigationController.SETTINGS); + buildFooter(); - - sellButton.fire(); - //homeButton.fire(); + walletFacade.initWallet(new ProgressBarUpdater()); } + @Override public IChildController navigateToView(String fxmlView, String title) { @@ -99,6 +102,34 @@ public class MainController implements Initializable, INavigationController return childController; } + private void buildNavigation() + { + toggleGroup = new ToggleGroup(); + + ToggleButton homeButton = addNavButton(leftNavPane, "Overview", Icons.HOME, Icons.HOME, INavigationController.HOME); + ToggleButton buyButton = addNavButton(leftNavPane, "Buy BTC", Icons.NAV_BUY, Icons.NAV_BUY_ACTIVE, INavigationController.TRADE, Direction.BUY); + ToggleButton sellButton = addNavButton(leftNavPane, "Sell BTC", Icons.NAV_SELL, Icons.NAV_SELL_ACTIVE, INavigationController.TRADE, Direction.SELL); + addNavButton(leftNavPane, "Orders", Icons.ORDERS, Icons.ORDERS, INavigationController.ORDERS); + addNavButton(leftNavPane, "History", Icons.HISTORY, Icons.HISTORY, INavigationController.HISTORY); + addNavButton(leftNavPane, "Funds", Icons.FUNDS, Icons.FUNDS, INavigationController.FUNDS); + addNavButton(leftNavPane, "Message", Icons.MSG, Icons.MSG, INavigationController.MSG); + addCurrencyComboBox(); + addNavButton(rightNavPane, "Settings", Icons.SETTINGS, Icons.SETTINGS, INavigationController.SETTINGS); + + sellButton.fire(); + //homeButton.fire(); + } + + private void buildFooter() + { + networkSyncInfoLabel = new Label(); + networkSyncInfoLabel.setText("Synchronize with network..."); + networkSyncProgressBar = new ProgressBar(); + networkSyncProgressBar.setPrefWidth(200); + networkSyncProgressBar.setProgress(-1); + footerContainer.getChildren().addAll(networkSyncProgressBar, networkSyncInfoLabel); + } + private ToggleButton addNavButton(Pane parent, String title, String iconId, String iconIdActivated, String navTarget) { return addNavButton(parent, title, iconId, iconIdActivated, navTarget, null); @@ -162,4 +193,40 @@ public class MainController implements Initializable, INavigationController holder.getChildren().add(currencyComboBox); rightNavPane.getChildren().add(holder); } + + private void setProgress(double percent) + { + networkSyncProgressBar.setProgress(percent / 100.0); + networkSyncInfoLabel.setText("Synchronize with network: " + (int) percent + "%"); + } + + private void doneDownload() + { + networkSyncInfoLabel.setText("Sync with network: Done"); + + FadeTransition fade = new FadeTransition(Duration.millis(700), footerContainer); + fade.setToValue(0.0); + fade.setCycleCount(1); + fade.setInterpolator(Interpolator.EASE_BOTH); + fade.play(); + fade.setOnFinished(e -> footerContainer.getChildren().clear()); + } + + private class ProgressBarUpdater extends DownloadListener + { + @Override + protected void progress(double percent, int blocksSoFar, Date date) + { + super.progress(percent, blocksSoFar, date); + Platform.runLater(() -> MainController.this.setProgress(percent)); + } + + @Override + protected void doneDownload() + { + super.doneDownload(); + Platform.runLater(MainController.this::doneDownload); + } + + } } \ No newline at end of file diff --git a/src/main/java/io/bitsquare/gui/MainView.fxml b/src/main/java/io/bitsquare/gui/MainView.fxml index f3b9269c29..7de5014ed5 100644 --- a/src/main/java/io/bitsquare/gui/MainView.fxml +++ b/src/main/java/io/bitsquare/gui/MainView.fxml @@ -8,7 +8,9 @@ - + diff --git a/src/main/java/io/bitsquare/gui/funds/FundsController.java b/src/main/java/io/bitsquare/gui/funds/FundsController.java index b061a88ad9..feab2b0c72 100644 --- a/src/main/java/io/bitsquare/gui/funds/FundsController.java +++ b/src/main/java/io/bitsquare/gui/funds/FundsController.java @@ -2,19 +2,25 @@ package io.bitsquare.gui.funds; import io.bitsquare.gui.IChildController; import io.bitsquare.gui.INavigationController; +import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.scene.layout.Pane; import java.net.URL; import java.util.ResourceBundle; + public class FundsController implements Initializable, IChildController { + private INavigationController navigationController; + @FXML + public Pane rootContainer; + @Override public void initialize(URL url, ResourceBundle rb) { - } @Override @@ -22,5 +28,6 @@ public class FundsController implements Initializable, IChildController { this.navigationController = navigationController; } + } diff --git a/src/main/java/io/bitsquare/gui/funds/FundsView.fxml b/src/main/java/io/bitsquare/gui/funds/FundsView.fxml index 9fcfeed4e1..3eda0873fd 100644 --- a/src/main/java/io/bitsquare/gui/funds/FundsView.fxml +++ b/src/main/java/io/bitsquare/gui/funds/FundsView.fxml @@ -1,6 +1,6 @@ + xmlns:fx="http://javafx.com/fxml" fx:id="rootContainer"> \ No newline at end of file diff --git a/src/main/java/io/bitsquare/gui/trade/tradeprocess/TradeProcessController.java b/src/main/java/io/bitsquare/gui/trade/tradeprocess/TradeProcessController.java index 0e0691cdc5..e5fddc2ade 100644 --- a/src/main/java/io/bitsquare/gui/trade/tradeprocess/TradeProcessController.java +++ b/src/main/java/io/bitsquare/gui/trade/tradeprocess/TradeProcessController.java @@ -199,7 +199,7 @@ public class TradeProcessController implements Initializable, IChildController private void onBankTransferInited() { infoLabel.setText("Bank transfer has been inited.\nCheck your bank account and continue when you have received the money.\n"); - nextButton.setText("Money received on Bank account"); + nextButton.setText("I have received the bank transfer"); nextButton.setOnAction(e -> releaseBTC()); vBox.getChildren().add(nextButton); } diff --git a/src/main/java/io/bitsquare/trade/TradingFacade.java b/src/main/java/io/bitsquare/trade/TradingFacade.java index 11ad75d20c..639344e020 100644 --- a/src/main/java/io/bitsquare/trade/TradingFacade.java +++ b/src/main/java/io/bitsquare/trade/TradingFacade.java @@ -2,8 +2,8 @@ package io.bitsquare.trade; import com.google.inject.Inject; import io.bitsquare.btc.BlockChainFacade; +import io.bitsquare.btc.IWalletFacade; import io.bitsquare.btc.KeyPair; -import io.bitsquare.btc.MockWalletFacade; import io.bitsquare.crypto.ICryptoFacade; import io.bitsquare.msg.IMessageFacade; import io.bitsquare.msg.Message; @@ -29,7 +29,7 @@ public class TradingFacade private User user; private IMessageFacade messageFacade; private BlockChainFacade blockChainFacade; - private MockWalletFacade walletFacade; + private IWalletFacade walletFacade; private ICryptoFacade cryptoFacade; private Settings settings; @@ -38,7 +38,7 @@ public class TradingFacade Settings settings, IMessageFacade messageFacade, BlockChainFacade blockChainFacade, - MockWalletFacade walletFacade, + IWalletFacade walletFacade, ICryptoFacade cryptoFacade) { this.user = user; diff --git a/src/main/java/io/bitsquare/trade/payment/process/PaymentProcess.java b/src/main/java/io/bitsquare/trade/payment/process/PaymentProcess.java index accf29fc3b..5181160ed6 100644 --- a/src/main/java/io/bitsquare/trade/payment/process/PaymentProcess.java +++ b/src/main/java/io/bitsquare/trade/payment/process/PaymentProcess.java @@ -2,7 +2,7 @@ package io.bitsquare.trade.payment.process; import com.google.inject.Inject; import io.bitsquare.btc.BlockChainFacade; -import io.bitsquare.btc.MockWalletFacade; +import io.bitsquare.btc.IWalletFacade; import io.bitsquare.msg.IMessageFacade; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +26,7 @@ public class PaymentProcess protected String takerOutputPayment; protected String multiSigAddress; - private MockWalletFacade wallet; + private IWalletFacade wallet; public PaymentProcess() { @@ -39,7 +39,7 @@ public class PaymentProcess } @Inject - public void setWallet(MockWalletFacade wallet) + public void setWallet(IWalletFacade wallet) { this.wallet = wallet; } diff --git a/src/main/resources/wallet/checkpoints b/src/main/resources/wallet/checkpoints new file mode 100644 index 0000000000000000000000000000000000000000..bc1b2e50747ff3b6ce60e55ae8a499e8e1759bea GIT binary patch literal 12885 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 z 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 B2O|Ig literal 0 HcmV?d00001