integration of TomP2P for payment process (part 1), fund screen, setup redesign, orders screen

This commit is contained in:
Manfred Karrer 2014-05-15 12:31:41 +02:00
parent 6459184ce9
commit c2db60be67
76 changed files with 4622 additions and 1515 deletions

View file

@ -7,9 +7,7 @@ This is just a first very basic GUI prototype with mock data.
There is only the trade process for Sell BTC and the role of the offer taker modelled yet.
The project use Java 8 and Maven.
We use bitcoinj library as a submodule. To get the project with the submodule included use:
git clone --recursive git://github.com/bitsquare/bitsquare
We use the bitcoinj library and TomP2P for DHT and messaging.
### Implemented (prototype level):
* Screen for orderbook with filtering mock offers by amount, price and order type (buy, sell)
@ -22,12 +20,12 @@ git clone --recursive git://github.com/bitsquare/bitsquare
* Pay in to MS fund
* Payout from MS fund
* TomP2P as messaging lib integrated and basic use cases in msg screen implemented: orderbook, add order, remove order, find peer, chat with peer
* Payment process until wait for bank transfer implemented with messaging
### Next steps:
* Implement messaging with TomP2P for registration, orderbook and payment process
* Payment process after wait for bank transfer implemented with messaging
* Arbitrator integration
* Other trade variants (Buy BTC taker, Sell BTC offerer, Sell BTC offerer)
* Verify registration and fee payments tx and get them from the blockchain
* ...

View file

@ -2,11 +2,11 @@
- settings
low prio:
- tx confirm. not working correct and reliable
- add settings after setup
- start with orderbook and open registration when user interacts with orderbook (take offer, create offer)
- settings screen
- return to setup when unregistered, change/add bank accounts from settings
- BigInteger for all btc values
- formatting
- validation

@ -1 +0,0 @@
Subproject commit eda6dccf6dc015df613a19f1fc0b5c3c98546185

43
pom.xml
View file

@ -11,6 +11,7 @@
<description>A P2P Fiat-Bitcoin Exchange</description>
<url>https://www.bitsquare.io</url>
<!--
<parent>
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
@ -18,6 +19,8 @@
<relativePath>libs/bitcoinj/pom.xml</relativePath>
</parent>
-->
<licenses>
<license>
<name>GNU AFFERO GENERAL PUBLIC LICENSE</name>
@ -97,13 +100,18 @@
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.google</groupId>
<artifactId>bitcoinj</artifactId>
<version>0.12-SNAPSHOT</version>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>net.tomp2p</groupId>
<artifactId>TomP2P</artifactId>
<version>4.4</version>
</dependency>
<dependency>
@ -112,20 +120,13 @@
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.7</version>
</dependency>
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
@ -146,12 +147,6 @@
<version>16.0.1</version>
</dependency>
<dependency>
<groupId>com.aquafx-project</groupId>
<artifactId>aquafx</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
@ -177,9 +172,9 @@
</dependency>
<dependency>
<groupId>net.tomp2p</groupId>
<artifactId>TomP2P</artifactId>
<version>4.4</version>
<groupId>com.madgag.spongycastle</groupId>
<artifactId>core</artifactId>
<version>1.50.0.0</version>
</dependency>
</dependencies>
@ -187,11 +182,11 @@
<reporting>
<plugins>
<!-- Generate cross-referenced HTML source code listing -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>2.1</version>
</plugin>
<!-- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>2.1</version>
</plugin> -->
</plugins>
</reporting>

View file

@ -1,5 +1,6 @@
package io.bitsquare;
import com.google.bitcoin.core.ECKey;
import com.google.bitcoin.core.Utils;
import com.google.inject.Guice;
import com.google.inject.Injector;
@ -14,6 +15,7 @@ import io.bitsquare.settings.Settings;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.Arbitrator;
import io.bitsquare.user.User;
import io.bitsquare.util.DSAKeyUtil;
import io.bitsquare.util.MockData;
import javafx.application.Application;
import javafx.scene.Parent;
@ -22,21 +24,23 @@ import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.util.Locale;
import java.util.UUID;
public class BitSquare extends Application
{
private static final Logger log = LoggerFactory.getLogger(BitSquare.class);
public static String ID = "";
private WalletFacade walletFacade;
private MessageFacade messageFacade;
public static void main(String[] args)
{
log.debug("Startup: main");
if (args.length > 0)
WalletFacade.WALLET_PREFIX = args[0];
else
WalletFacade.WALLET_PREFIX = "bitsquare";
ID = args[0];
launch(args);
}
@ -44,9 +48,12 @@ public class BitSquare extends Application
@Override
public void start(Stage stage) throws Exception
{
log.debug("Startup: start");
final Injector injector = Guice.createInjector(new BitSquareModule());
walletFacade = injector.getInstance(WalletFacade.class);
messageFacade = injector.getInstance(MessageFacade.class);
log.debug("Startup: messageFacade, walletFacade inited");
// apply stored data
final User user = injector.getInstance(User.class);
@ -59,7 +66,11 @@ public class BitSquare extends Application
settings.updateFromStorage((Settings) storage.read(settings.getClass().getName()));
stage.setTitle("BitSquare (" + WalletFacade.WALLET_PREFIX + ")");
if (ID.length() > 0)
stage.setTitle("BitSquare (" + ID + ")");
else
stage.setTitle("BitSquare");
GuiceFXMLLoader.setInjector(injector);
final GuiceFXMLLoader loader = new GuiceFXMLLoader(getClass().getResource("/io/bitsquare/gui/MainView.fxml"), Localisation.getResourceBundle());
@ -71,12 +82,13 @@ public class BitSquare extends Application
final String global = getClass().getResource("/io/bitsquare/gui/global.css").toExternalForm();
scene.getStylesheets().setAll(global);
stage.setMinWidth(740);
stage.setMinWidth(800);
stage.setMinHeight(400);
stage.setWidth(800);
stage.setHeight(600);
stage.show();
log.debug("Startup: stage displayed");
}
@Override
@ -104,20 +116,22 @@ public class BitSquare extends Application
//settings.addAcceptedCountryLocale(Locale.getDefault());
settings.addAcceptedCountryLocale(MockData.getLocales().get(0));
settings.addAcceptedCountryLocale(new Locale("en", "US"));
settings.addAcceptedCountryLocale(new Locale("de", "DE"));
settings.addAcceptedCountryLocale(new Locale("es", "ES"));
settings.getAcceptedArbitrators().clear();
settings.addAcceptedArbitrator(new Arbitrator("uid_1", "Charlie Boom", UUID.randomUUID().toString(),
UUID.randomUUID().toString(), "http://www.arbit.io/Charly_Boom", 0.1, 10, Utils.toNanoCoins("0.01")));
settings.addAcceptedArbitrator(new Arbitrator("uid_2", "Tom Shang", UUID.randomUUID().toString(),
UUID.randomUUID().toString(), "http://www.arbit.io/Tom_Shang", 0, 1, Utils.toNanoCoins("0.001")));
settings.addAcceptedArbitrator(new Arbitrator("uid_3", "Edward Snow", UUID.randomUUID().toString(),
UUID.randomUUID().toString(), "http://www.arbit.io/Edward_Swow", 0.2, 5, Utils.toNanoCoins("0.05")));
settings.addAcceptedArbitrator(new Arbitrator("uid_4", "Julian Sander", UUID.randomUUID().toString(),
UUID.randomUUID().toString(), "http://www.arbit.io/Julian_Sander", 0, 20, Utils.toNanoCoins("0.1")));
settings.setMinCollateral(0.01);
settings.setMaxCollateral(0.1);
settings.getAcceptedArbitrators().clear();
settings.addAcceptedArbitrator(new Arbitrator("uid_1", "Charlie Boom", Utils.bytesToHexString(new ECKey().getPubKey()),
getMessagePubKey(), "http://www.arbit.io/Charly_Boom", 1, 10, Utils.toNanoCoins("0.01")));
settings.addAcceptedArbitrator(new Arbitrator("uid_2", "Tom Shang", Utils.bytesToHexString(new ECKey().getPubKey()),
getMessagePubKey(), "http://www.arbit.io/Tom_Shang", 0, 1, Utils.toNanoCoins("0.001")));
settings.addAcceptedArbitrator(new Arbitrator("uid_3", "Edward Snow", Utils.bytesToHexString(new ECKey().getPubKey()),
getMessagePubKey(), "http://www.arbit.io/Edward_Swow", 2, 5, Utils.toNanoCoins("0.05")));
settings.addAcceptedArbitrator(new Arbitrator("uid_4", "Julian Sander", Utils.bytesToHexString(new ECKey().getPubKey()),
getMessagePubKey(), "http://www.arbit.io/Julian_Sander", 0, 20, Utils.toNanoCoins("0.1")));
settings.setMinCollateral(1);
settings.setMaxCollateral(10);
storage.write(settings.getClass().getName(), settings);
@ -125,6 +139,22 @@ public class BitSquare extends Application
}
}
private String getMessagePubKey()
{
try
{
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
keyGen.initialize(1024);
KeyPair generatedKeyPair = keyGen.genKeyPair();
PublicKey pubKey = generatedKeyPair.getPublic();
return DSAKeyUtil.getHexStringFromPublicKey(pubKey);
} catch (Exception e2)
{
return null;
}
}
private void initMockUser(Storage storage, User user)
{
user.getBankAccounts().clear();
@ -149,7 +179,7 @@ public class BitSquare extends Application
);
user.addBankAccount(bankAccount2);
user.setAccountID(UUID.randomUUID().toString());
user.setAccountID(Utils.bytesToHexString(new ECKey().getPubKey()));
storage.write(user.getClass().getName(), user);
}

View file

@ -1,24 +1,30 @@
package io.bitsquare.msg;
package io.bitsquare;
import net.tomp2p.p2p.Peer;
import net.tomp2p.p2p.PeerMaker;
import net.tomp2p.peers.Number160;
public class BootstrapMasterPeer
public class RelayNode
{
private static Peer masterPeer = null;
public static Number160 ID = Number160.createHash(1);
public static void main(String[] args) throws Exception
{
INSTANCE(5000);
if (args.length == 1)
INSTANCE(new Integer(args[0]));
else
INSTANCE(5000);
}
public static Peer INSTANCE(int port) throws Exception
{
if (masterPeer == null)
{
masterPeer = new PeerMaker(ID).setPorts(port).makeAndListen();
// masterPeer = new PeerMaker(ID).setPorts(port).setBagSize(100).makeAndListen(); // setBagSize cause sync problems...
masterPeer.getBroadcastRPC().getConnectionBean().getConnectionReservation().reserve(10).awaitUninterruptibly();
}
return masterPeer;
}
}

View file

@ -8,7 +8,6 @@ public class BankAccount implements Serializable
{
private static final long serialVersionUID = 1792577576443221268L;
private static final long VERSION = 1;
private BankAccountType bankAccountType;
private String accountPrimaryID;
@ -80,17 +79,18 @@ public class BankAccount implements Serializable
return accountTitle;
}
// Changes of that structure must be reflected in VERSION updates
public String getStringifiedBankAccount()
@Override
public String toString()
{
return "{" +
"type=" + bankAccountType +
", primaryID='" + accountPrimaryID + '\'' +
", secondaryID='" + accountSecondaryID + '\'' +
", holderName='" + accountHolderName + '\'' +
", currency='" + currency.getCurrencyCode() + '\'' +
", country='" + countryLocale.getCountry() + '\'' +
", v='" + VERSION + '\'' +
return "BankAccount{" +
"bankAccountType=" + bankAccountType +
", accountPrimaryID='" + accountPrimaryID + '\'' +
", accountSecondaryID='" + accountSecondaryID + '\'' +
", accountHolderName='" + accountHolderName + '\'' +
", countryLocale=" + countryLocale +
", currency=" + currency +
", uid='" + uid + '\'' +
", accountTitle='" + accountTitle + '\'' +
'}';
}

View file

@ -7,6 +7,11 @@ public class BankAccountType implements Serializable
private static final long serialVersionUID = -8772708150197835288L;
public static enum BankAccountTypeEnum
{
SEPA, WIRE, INTERNATIONAL, OK_PAY, NET_TELLER, PERFECT_MONEY, OTHER
}
private BankAccountTypeEnum type;
private String primaryIDName;
private String secondaryIDName;
@ -40,8 +45,4 @@ public class BankAccountType implements Serializable
return type.toString();
}
public static enum BankAccountTypeEnum
{
SEPA, WIRE, INTERNATIONAL, OK_PAY, NET_TELLER, PERFECT_MONEY, OTHER
}
}

View file

@ -7,6 +7,8 @@ import com.google.bitcoin.store.UnreadableWalletException;
import com.google.bitcoin.store.WalletProtobufSerializer;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import io.bitsquare.BitSquare;
import io.bitsquare.util.Utilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -39,7 +41,7 @@ public class AccountRegistrationWallet extends Wallet implements WalletEventList
this.chain = chain;
this.peerGroup = peerGroup;
walletFile = new File(".", "bitsquare_account_reg" + ".wallet");
walletFile = new File(Utilities.getRootDir() + "account_reg_" + BitSquare.ID + ".wallet");
if (walletFile.exists())
{
FileInputStream walletStream = null;
@ -74,7 +76,10 @@ public class AccountRegistrationWallet extends Wallet implements WalletEventList
{
e.printStackTrace();
}
autosaveToFile(walletFile, 1, TimeUnit.SECONDS, null);
allowSpendingUnconfirmedTransactions();
}
void shutDown()
@ -166,7 +171,7 @@ public class AccountRegistrationWallet extends Wallet implements WalletEventList
for (WalletFacade.WalletListener walletListener : walletListeners)
walletListener.onCoinsReceived(newBalance);
log.info("onCoinsReceived");
// log.info("onCoinsReceived");
}
@Override
@ -175,7 +180,7 @@ public class AccountRegistrationWallet extends Wallet implements WalletEventList
for (WalletFacade.WalletListener walletListener : walletListeners)
walletListener.onConfidenceChanged(tx.getConfidence().numBroadcastPeers(), WalletUtil.getConfDepthInBlocks(this));
log.info("onTransactionConfidenceChanged " + tx.getConfidence().toString());
// log.info("onTransactionConfidenceChanged " + tx.getConfidence().toString());
}
@Override
@ -193,7 +198,7 @@ public class AccountRegistrationWallet extends Wallet implements WalletEventList
@Override
public void onWalletChanged(Wallet wallet)
{
log.info("onWalletChanged");
// log.info("onWalletChanged");
}
@Override

View file

@ -16,11 +16,19 @@ public class BlockChainFacade
}
public boolean verifyEmbeddedData(String address)
//TODO
public boolean isAccountBlackListed(String accountID, BankAccount bankAccount)
{
return false;
}
//TODO
public boolean verifyAccountRegistration()
{
return true;
// tx id 76982adc582657b2eb68f3e43341596a68aadc4ef6b9590e88e93387d4d5d1f9
// address: mjbxLbuVpU1cNXLJbrJZyirYwweoRPVVTj
return true;
/*
if (findAddressInBlockChain(address) && isFeePayed(address))
return getDataForTxWithAddress(address) != null;
@ -49,14 +57,14 @@ public class BlockChainFacade
return true;
}
public boolean isAccountIDBlacklisted(String accountID)
private boolean isAccountIDBlacklisted(String accountID)
{
// TODO
// check if accountID is on blacklist
return false;
}
public boolean isBankAccountBlacklisted(BankAccount bankAccount)
private boolean isBankAccountBlacklisted(BankAccount bankAccount)
{
// TODO
// check if accountID is on blacklist

View file

@ -3,6 +3,8 @@ package io.bitsquare.btc;
import com.google.bitcoin.core.Utils;
import io.bitsquare.gui.util.Converter;
import io.bitsquare.gui.util.Formatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.text.DecimalFormat;
@ -10,6 +12,8 @@ import java.util.Locale;
public class BtcFormatter
{
private static final Logger log = LoggerFactory.getLogger(BtcFormatter.class);
public static BigInteger BTC = new BigInteger("100000000");
public static BigInteger mBTC = new BigInteger("100000");
@ -30,10 +34,13 @@ public class BtcFormatter
{
// only "." as decimal sep supported by Utils.toNanoCoins
DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance(Locale.ENGLISH);
decimalFormat.setMaximumFractionDigits(10);
decimalFormat.setMinimumFractionDigits(10);
String stringValue = decimalFormat.format(value);
return Utils.toNanoCoins(stringValue);
} catch (Exception e)
{
log.warn("Exception at doubleValueToSatoshis " + e.getMessage());
return BigInteger.ZERO;
}
}

View file

@ -1,11 +1,14 @@
package io.bitsquare.btc;
import com.google.bitcoin.core.Transaction;
import java.math.BigInteger;
public class Fees
{
// min dust value lead to exception at for non standard to address pay scripts, so we use a value >= 7860 instead
public static BigInteger MS_TX_FEE = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; // Transaction.REFERENCE_DEFAULT_MIN_TX_FEE = BigInteger.valueOf(10000)
public static BigInteger ACCOUNT_REGISTRATION_FEE = BigInteger.valueOf(7860);// Utils.toNanoCoins("0.001");
public static BigInteger OFFER_CREATION_FEE = BigInteger.valueOf(7860); // //Transaction.MIN_NONDUST_OUTPUT; // Utils.toNanoCoins("0.001");
public static BigInteger OFFER_CREATION_FEE = BigInteger.valueOf(7860); // Transaction.MIN_NONDUST_OUTPUT; // Utils.toNanoCoins("0.001");
public static BigInteger OFFER_TAKER_FEE = BigInteger.valueOf(7860);
}

View file

@ -13,6 +13,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject;
import io.bitsquare.BitSquare;
import io.bitsquare.crypto.CryptoFacade;
import io.bitsquare.gui.util.Popups;
import javafx.application.Platform;
@ -35,16 +36,14 @@ public class WalletFacade implements WalletEventListener
{
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";
public static String WALLET_PREFIX;
public static String WALLET_PREFIX = BitSquare.ID;
// for testing trade process between offerer and taker
//public static String WALLET_PREFIX = "offerer"; // offerer
//public static String WALLET_PREFIX = "taker"; // offerer
//public static String WALLET_PREFIX = "bitsquare";
private static final Logger log = LoggerFactory.getLogger(WalletFacade.class);
private NetworkParameters params;
@ -56,6 +55,8 @@ public class WalletFacade implements WalletEventListener
private List<DownloadListener> downloadListeners = new ArrayList<>();
private List<WalletListener> walletListeners = new ArrayList<>();
private Wallet wallet;
@ -107,7 +108,7 @@ public class WalletFacade implements WalletEventListener
wallet = walletAppKit.wallet();
//wallet.allowSpendingUnconfirmedTransactions();
wallet.allowSpendingUnconfirmedTransactions();
walletAppKit.peerGroup().setMaxConnections(20);
wallet.addEventListener(this);
@ -168,7 +169,7 @@ public class WalletFacade implements WalletEventListener
return wallet.getBalance(Wallet.BalanceType.ESTIMATED);
}
public String getAddress()
public String getAddressAsString()
{
return wallet.getKeys().get(0).toAddress(params).toString();
}
@ -189,6 +190,18 @@ public class WalletFacade implements WalletEventListener
return tx.getHashAsString();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Trade process
///////////////////////////////////////////////////////////////////////////////////////////
public int getNumOfPeersSeenTx(String txID)
{
// TODO check from blockchain
// will be async
return 3;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Account registration
///////////////////////////////////////////////////////////////////////////////////////////
@ -200,7 +213,7 @@ public class WalletFacade implements WalletEventListener
public String getAccountRegistrationPubKey()
{
return Utils.bytesToHexString(getAccountRegistrationWallet().getKey().getPubKey());
return Utils.bytesToHexString(getAccountRegistrationKey().getPubKey());
}
public BigInteger getAccountRegistrationBalance()
@ -213,6 +226,11 @@ public class WalletFacade implements WalletEventListener
getAccountRegistrationWallet().saveToBlockchain(cryptoFacade.getEmbeddedAccountRegistrationData(getAccountRegistrationWallet().getKey(), stringifiedBankAccounts));
}
public ECKey getAccountRegistrationKey()
{
return getAccountRegistrationWallet().getKey();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
///////////////////////////////////////////////////////////////////////////////////////////
@ -227,12 +245,11 @@ public class WalletFacade implements WalletEventListener
return WalletUtil.getConfDepthInBlocks(getAccountRegistrationWallet());
}
public ECKey getAccountKey()
public Wallet getWallet()
{
return getAccountRegistrationWallet().getKey();
return wallet;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: WalletEventListener
///////////////////////////////////////////////////////////////////////////////////////////
@ -243,7 +260,7 @@ public class WalletFacade implements WalletEventListener
for (WalletListener walletListener : walletListeners)
walletListener.onCoinsReceived(newBalance);
log.info("onCoinsReceived");
// log.info("onCoinsReceived");
}
@Override
@ -252,13 +269,13 @@ public class WalletFacade implements WalletEventListener
for (WalletListener walletListener : walletListeners)
walletListener.onConfidenceChanged(tx.getConfidence().numBroadcastPeers(), WalletUtil.getConfDepthInBlocks(wallet));
log.info("onTransactionConfidenceChanged " + tx.getConfidence().toString());
// log.info("onTransactionConfidenceChanged " + tx.getConfidence().toString());
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance)
{
log.info("onCoinsSent");
// log.info("onCoinsSent");
}
@Override
@ -270,7 +287,7 @@ public class WalletFacade implements WalletEventListener
@Override
public void onWalletChanged(Wallet wallet)
{
log.info("onWalletChanged");
// log.info("onWalletChanged");
}
@Override
@ -285,12 +302,11 @@ public class WalletFacade implements WalletEventListener
log.info("onScriptsAdded");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
private AccountRegistrationWallet getAccountRegistrationWallet()
public AccountRegistrationWallet getAccountRegistrationWallet()
{
if (accountRegistrationWallet == null)
accountRegistrationWallet = new AccountRegistrationWallet(params, walletAppKit.chain(), walletAppKit.peerGroup());
@ -329,25 +345,25 @@ public class WalletFacade implements WalletEventListener
String arbitratorPubKey = "";
// 1 offerer creates MS TX and pay in
/* Transaction tx1 = offererCreatesMSTxAndAddPayment(offererAmount, offererPubKey, takerPubKey, arbitratorPubKey);
Transaction tx1 = offererCreatesMSTxAndAddPayment(offererAmount, offererPubKey, takerPubKey, arbitratorPubKey);
tx1AsHex = Utils.bytesToHexString(tx1.bitcoinSerialize());
*/
tx1AsHex = "01000000014378dfcd19add18eb6f118a1e35ced127ff23c9dc5034eee1cda5b9caeb814f0000000006b4830450221008e599dd7bb7223c7b036869198b14f08009f9bc117709d23c249d0bdd6b483be022047be181f467782ea277b36890feb2f6de3ceddcedf8730a9f505bac36b3b015b01210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7ffffffff0240420f00000000004852210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b65680053aeb077e605000000001976a9149fc3d8e0371b6eab89a8c3c015839f9e493ccf6588ac00000000";
// 2. taker pay in and sign
/* Transaction tx2 = takerAddPaymentAndSign(takerAmount, msOutputAmount, offererPubKey, takerPubKey, arbitratorPubKey, tx1AsHex);
/* Transaction tx2 = takerAddPaymentAndSign(takerAmount, msOutputAmount, offererPubKey, takerPubKey, arbitratorPubKey, tx1AsHex);
tx2AsHex = Utils.bytesToHexString(tx2.bitcoinSerialize());
tx2ScriptSigAsHex = Utils.bytesToHexString(tx2.getInput(1).getScriptBytes());
tx2ConnOutAsHex = Utils.bytesToHexString(tx2.getInput(1).getConnectedOutput().getParentTransaction().bitcoinSerialize());
*/
*/
tx2AsHex = "01000000024378dfcd19add18eb6f118a1e35ced127ff23c9dc5034eee1cda5b9caeb814f0000000006b4830450221008e599dd7bb7223c7b036869198b14f08009f9bc117709d23c249d0bdd6b483be022047be181f467782ea277b36890feb2f6de3ceddcedf8730a9f505bac36b3b015b01210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7ffffffffa58b22a93a0fcf99ba48aa3b96d842284b2b3d24f72d045cc192ea8a6b89435c010000006a47304402207f4beeb1a86432be0b4c3d4f4db7416b52b66c84383d1980d39e21d547a1762f02200405d0d4b80d1094e3a08cb39ef6f1161be163026d417af08d54c5a1cfdbbbeb01210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568ffffffff03c0c62d00000000004852210352f2e34760514099f90b03aab91239466924c3b06047d3cf0e011f26ef96ceb7210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b65680053aeb077e605000000001976a9149fc3d8e0371b6eab89a8c3c015839f9e493ccf6588ac7035d705000000001976a914e5175c1f71c28218306d4a27c8cec0269dddbbde88ac00000000";
tx2ScriptSigAsHex = "47304402207f4beeb1a86432be0b4c3d4f4db7416b52b66c84383d1980d39e21d547a1762f02200405d0d4b80d1094e3a08cb39ef6f1161be163026d417af08d54c5a1cfdbbbeb01210207cf5fb65d6923d5d41db21ceac9567a0fc3eb92c6137f274018381ced7b6568";
tx2ConnOutAsHex = "01000000014378dfcd19add18eb6f118a1e35ced127ff23c9dc5034eee1cda5b9caeb814f0010000006a473044022011431387fc19b093b26a6d2371995c828179aae68e94ad5804e5d0986a6b471302206abc2b698375620e65fc9970b7781da0af2179d1bdc4ebc82a13e285359a3ce7012103c7b9e9ef657705522c85b8429bb2b42c04f0fd4a09e0605cd7dd62ffecb57944ffffffff02c0ce823e000000001976a9142d1b4347ae850805f3badbb4b2949674f46c4ccd88ac00e1f505000000001976a914e5175c1f71c28218306d4a27c8cec0269dddbbde88ac00000000";
// 3. offerer sign and send
Transaction tx3 = offererSignAndSendTx(tx1AsHex, tx2AsHex, tx2ConnOutAsHex, tx2ScriptSigAsHex);
Transaction tx3 = offererSignAndSendTx(tx1AsHex, tx2AsHex, tx2ConnOutAsHex, tx2ScriptSigAsHex, null);
log.info(tx3.toString()); // tx has 453 Bytes
@ -421,8 +437,13 @@ public class WalletFacade implements WalletEventListener
}
public String getMultiSigPubKeyAsHex()
{
return Utils.bytesToHexString(wallet.getKeys().get(0).getPubKey());
}
// deposit 1. offerer
private Transaction offererCreatesMSTxAndAddPayment(BigInteger offererAmount, String offererPubKey, String takerPubKey, String arbitratorPubKey) throws InsufficientMoneyException
public Transaction offererCreatesMSTxAndAddPayment(BigInteger offererAmount, String offererPubKey, String takerPubKey, String arbitratorPubKey) throws InsufficientMoneyException
{
// use that to use the convenient api for getting the best coin selection and fee calculation
// TODO should be constructed manually
@ -494,13 +515,15 @@ public class WalletFacade implements WalletEventListener
public Transaction offererSignAndSendTx(String tx1AsHex,
String tx2AsHex,
String tx2ConnOutAsHex,
String tx2ScriptSigAsHex) throws Exception
String tx2ScriptSigAsHex,
FutureCallback<Transaction> callback) throws Exception
{
log.info("offererSignAndSendTx start");
Transaction tx = new Transaction(params);
Transaction tx1 = new Transaction(params, Utils.parseAsHexOrBase58(tx1AsHex));
Transaction tx1ConnOut = wallet.getTransaction(tx1.getInput(0).getOutpoint().getHash());
TransactionOutPoint tx1OutPoint = new TransactionOutPoint(params, 0, tx1ConnOut);
TransactionOutPoint tx1OutPoint = new TransactionOutPoint(params, 1, tx1ConnOut);
TransactionInput tx1Input = new TransactionInput(params, tx, tx1.getInput(0).getScriptBytes(), tx1OutPoint);
tx1Input.setParent(tx);
tx.addInput(tx1Input);
@ -529,10 +552,13 @@ public class WalletFacade implements WalletEventListener
else
throw new ScriptException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey);
log.info("offererSignAndSendTx check correctlySpends input 0");
input.getScriptSig().correctlySpends(tx, 0, scriptPubKey, false);
input = tx.getInput(1);
scriptPubKey = input.getConnectedOutput().getScriptPubKey();
log.info("offererSignAndSendTx check correctlySpends input 1");
input.getScriptSig().correctlySpends(tx, 1, scriptPubKey, false);
/*
@ -542,26 +568,28 @@ public class WalletFacade implements WalletEventListener
OUT[1] offerer change
OUT[2] taker change
*/
log.info("offererSignAndSendTx broadcastTransaction verify ");
tx.verify();
log.info("offererSignAndSendTx broadcastTransaction pre ");
ListenableFuture<Transaction> broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(tx);
FutureCallback callback = new FutureCallback<Transaction>()
log.info("offererSignAndSendTx broadcastTransaction post");
FutureCallback<Transaction> localCallback = new FutureCallback<Transaction>()
{
@Override
public void onSuccess(Transaction transaction)
{
log.info("sendResult onSuccess:" + transaction.toString());
log.info("offererSignAndSendTx onSuccess" + transaction.toString());
}
@Override
public void onFailure(Throwable t)
{
log.warn("sendResult onFailure:" + t.toString());
log.info("offererSignAndSendTx onFailure" + t.toString());
Popups.openErrorPopup("Fee payment failed", "Fee payment failed. " + t.toString());
}
};
Futures.addCallback(broadcastComplete, localCallback);
Futures.addCallback(broadcastComplete, callback);
return tx;
@ -628,7 +656,7 @@ public class WalletFacade implements WalletEventListener
ListenableFuture<Transaction> broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(tx);
FutureCallback callback = new FutureCallback<Transaction>()
FutureCallback<Transaction> callback = new FutureCallback<Transaction>()
{
@Override
public void onSuccess(Transaction transaction)
@ -648,7 +676,6 @@ public class WalletFacade implements WalletEventListener
return tx;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Inner classes
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -6,7 +6,6 @@ import com.google.common.base.Charsets;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.security.SignatureException;
import java.util.UUID;
@ -58,7 +57,7 @@ public class CryptoFacade
public boolean verifyHash(String hashAsHexStringToVerify, String msg, String sig)
{
String hashAsHexString = Hex.toHexString(createHash(msg, sig));
String hashAsHexString = Utils.bytesToHexString(createHash(msg, sig));
return hashAsHexString.equals(hashAsHexStringToVerify);
}

View file

@ -4,6 +4,7 @@ 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;
@ -17,10 +18,10 @@ import io.bitsquare.msg.MessageFacade;
import io.bitsquare.settings.Settings;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.Trading;
import io.bitsquare.trade.orderbook.MockOrderBook;
import io.bitsquare.trade.orderbook.OrderBook;
import io.bitsquare.trade.orderbook.OrderBookFilter;
import io.bitsquare.user.User;
import io.bitsquare.util.Utilities;
import java.io.File;
@ -31,7 +32,7 @@ public class BitSquareModule extends AbstractModule
protected void configure()
{
bind(User.class).asEagerSingleton();
bind(OrderBook.class).to(MockOrderBook.class).asEagerSingleton();
bind(OrderBook.class).asEagerSingleton();
bind(Storage.class).asEagerSingleton();
bind(Settings.class).asEagerSingleton();
bind(OrderBookFilter.class).asEagerSingleton();
@ -44,6 +45,7 @@ public class BitSquareModule extends AbstractModule
bind(Trading.class).asEagerSingleton();
//bind(String.class).annotatedWith(Names.named("networkType")).toInstance(WalletFacade.MAIN_NET);
// bind(String.class).annotatedWith(Names.named("networkType")).toInstance(WalletFacade.REG_TEST_NET);
bind(String.class).annotatedWith(Names.named("networkType")).toInstance(WalletFacade.TEST_NET);
bind(NetworkParameters.class).toProvider(NetworkParametersProvider.class).asEagerSingleton();
bind(WalletAppKit.class).toProvider(WalletAppKitProvider.class).asEagerSingleton();
@ -62,7 +64,7 @@ class WalletAppKitProvider implements Provider<WalletAppKit>
public WalletAppKit get()
{
return new WalletAppKit(networkParameters, new File("."), WalletFacade.WALLET_PREFIX);
return new WalletAppKit(networkParameters, new File(Utilities.getRootDir()), WalletFacade.WALLET_PREFIX);
}
}
@ -88,6 +90,9 @@ class NetworkParametersProvider implements Provider<NetworkParameters>
case WalletFacade.TEST_NET:
result = TestNet3Params.get();
break;
case WalletFacade.REG_TEST_NET:
result = RegTestParams.get();
break;
}
return result;
}

View file

@ -1,5 +1,7 @@
package io.bitsquare.gui;
import com.google.bitcoin.core.*;
import com.google.bitcoin.script.Script;
import com.google.inject.Inject;
import io.bitsquare.bank.BankAccount;
import io.bitsquare.btc.BtcFormatter;
@ -11,7 +13,9 @@ import io.bitsquare.gui.setup.SetupController;
import io.bitsquare.gui.util.Icons;
import io.bitsquare.gui.util.Localisation;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.TradeMessage;
import io.bitsquare.trade.Direction;
import io.bitsquare.trade.Trading;
import io.bitsquare.user.User;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
@ -26,12 +30,15 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.util.StringConverter;
import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.ResourceBundle;
public class MainController implements Initializable, NavigationController, WalletFacade.DownloadListener
@ -41,6 +48,7 @@ public class MainController implements Initializable, NavigationController, Wall
private User user;
private WalletFacade walletFacade;
private MessageFacade messageFacade;
private Trading trading;
private ChildController childController;
private ToggleGroup toggleGroup;
private ToggleButton prevToggleButton;
@ -48,6 +56,9 @@ public class MainController implements Initializable, NavigationController, Wall
private Pane setupView;
private SetupController setupController;
private NetworkSyncPane networkSyncPane;
private ToggleButton buyButton, sellButton, homeButton, msgButton, ordersButton, historyButton, fundsButton, settingsButton;
private Pane msgButtonHolder, buyButtonHolder, sellButtonHolder, ordersButtonButtonHolder;
private TextField balanceTextField;
@FXML
public Pane contentPane;
@ -64,11 +75,12 @@ public class MainController implements Initializable, NavigationController, Wall
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public MainController(User user, WalletFacade walletFacade, MessageFacade messageFacade)
public MainController(User user, WalletFacade walletFacade, MessageFacade messageFacade, Trading trading)
{
this.user = user;
this.walletFacade = walletFacade;
this.messageFacade = messageFacade;
this.trading = trading;
}
@ -88,19 +100,50 @@ public class MainController implements Initializable, NavigationController, Wall
walletFacade.addDownloadListener(this);
walletFacade.initWallet();
buildNavigation();
if (user.getAccountID() == null)
{
buildSetupView();
anchorPane.setOpacity(0);
anchorPane.setVisible(false);
setupController.setNetworkSyncPane(networkSyncPane);
rootContainer.getChildren().add(setupView);
}
else
{
buildNavigation();
sellButton.fire();
// ordersButton.fire();
// homeButton.fire();
// msgButton.fire();
}
AnchorPane.setBottomAnchor(networkSyncPane, 0.0);
AnchorPane.setLeftAnchor(networkSyncPane, 0.0);
messageFacade.addTakeOfferRequestListener((tradingMessage, sender) -> showTakeOfferRequest(tradingMessage, sender));
}
private void showTakeOfferRequest(final TradeMessage tradeMessage, PeerAddress sender)
{
trading.createOffererPaymentProtocol(tradeMessage, sender);
try
{
ImageView newTradeRequestIcon = Icons.getIconImageView(Icons.MSG_ALERT);
Button alertButton = new Button("", newTradeRequestIcon);
alertButton.setId("nav-alert-button");
alertButton.relocate(36, 19);
Tooltip.install(alertButton, new Tooltip("Someone accepted your offer"));
alertButton.setOnAction((e) -> {
ordersButton.fire();
});
ordersButtonButtonHolder.getChildren().add(alertButton);
} catch (NullPointerException e)
{
log.warn("showTakeOfferRequest failed because of a NullPointerException");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: NavigationController
@ -113,12 +156,12 @@ public class MainController implements Initializable, NavigationController, Wall
{
anchorPane.getChildren().add(networkSyncPane);
anchorPane.setOpacity(1);
anchorPane.setVisible(true);
rootContainer.getChildren().remove(setupView);
setupView = null;
setupController = null;
return null;
buildNavigation();
}
if (childController != null)
@ -181,21 +224,31 @@ public class MainController implements Initializable, NavigationController, Wall
{
toggleGroup = new ToggleGroup();
ToggleButton homeButton = addNavButton(leftNavPane, "Overview", Icons.HOME, Icons.HOME, NavigationController.HOME);
ToggleButton buyButton = addNavButton(leftNavPane, "Buy BTC", Icons.NAV_BUY, Icons.NAV_BUY_ACTIVE, NavigationController.MARKET, Direction.BUY);
ToggleButton sellButton = addNavButton(leftNavPane, "Sell BTC", Icons.NAV_SELL, Icons.NAV_SELL_ACTIVE, NavigationController.MARKET, Direction.SELL);
addNavButton(leftNavPane, "Orders", Icons.ORDERS, Icons.ORDERS, NavigationController.ORDERS);
addNavButton(leftNavPane, "History", Icons.HISTORY, Icons.HISTORY, NavigationController.HISTORY);
addNavButton(leftNavPane, "Funds", Icons.FUNDS, Icons.FUNDS, NavigationController.FUNDS);
ToggleButton msgButton = addNavButton(leftNavPane, "Message", Icons.MSG, Icons.MSG, NavigationController.MSG);
homeButton = addNavButton(leftNavPane, "Overview", Icons.HOME, Icons.HOME, NavigationController.HOME);
buyButtonHolder = new Pane();
buyButton = addNavButton(buyButtonHolder, "Buy BTC", Icons.NAV_BUY, Icons.NAV_BUY_ACTIVE, NavigationController.MARKET, Direction.BUY);
leftNavPane.getChildren().add(buyButtonHolder);
sellButtonHolder = new Pane();
sellButton = addNavButton(sellButtonHolder, "Sell BTC", Icons.NAV_SELL, Icons.NAV_SELL_ACTIVE, NavigationController.MARKET, Direction.SELL);
leftNavPane.getChildren().add(sellButtonHolder);
ordersButtonButtonHolder = new Pane();
ordersButton = addNavButton(ordersButtonButtonHolder, "Orders", Icons.ORDERS, Icons.ORDERS, NavigationController.ORDERS);
leftNavPane.getChildren().add(ordersButtonButtonHolder);
historyButton = addNavButton(leftNavPane, "History", Icons.HISTORY, Icons.HISTORY, NavigationController.HISTORY);
fundsButton = addNavButton(leftNavPane, "Funds", Icons.FUNDS, Icons.FUNDS, NavigationController.FUNDS);
msgButtonHolder = new Pane();
msgButton = addNavButton(msgButtonHolder, "Message", Icons.MSG, Icons.MSG, NavigationController.MSG);
leftNavPane.getChildren().add(msgButtonHolder);
addBalanceInfo(rightNavPane);
addAccountComboBox(rightNavPane);
addNavButton(rightNavPane, "Settings", Icons.SETTINGS, Icons.SETTINGS, NavigationController.SETTINGS);
//sellButton.fire();
//homeButton.fire();
msgButton.fire();
settingsButton = addNavButton(rightNavPane, "Settings", Icons.SETTINGS, Icons.SETTINGS, NavigationController.SETTINGS);
}
private ToggleButton addNavButton(Pane parent, String title, String iconId, String iconIdActivated, String navTarget)
@ -250,18 +303,19 @@ public class MainController implements Initializable, NavigationController, Wall
private TextField addBalanceInfo(Pane parent)
{
TextField balanceLabel = new TextField();
balanceLabel.setEditable(false);
balanceLabel.setMouseTransparent(true);
balanceLabel.setPrefWidth(90);
balanceLabel.setId("nav-balance-label");
balanceLabel.setText(BtcFormatter.formatSatoshis(walletFacade.getBalance(), false));
balanceTextField = new TextField();
balanceTextField.setEditable(false);
balanceTextField.setMouseTransparent(true);
balanceTextField.setPrefWidth(90);
balanceTextField.setId("nav-balance-label");
balanceTextField.setText(BtcFormatter.formatSatoshis(walletFacade.getBalance(), false));
Label balanceCurrencyLabel = new Label("BTC");
balanceCurrencyLabel.setPadding(new Insets(6, 0, 0, 0));
HBox hBox = new HBox();
hBox.setSpacing(2);
hBox.getChildren().setAll(balanceLabel, balanceCurrencyLabel);
hBox.getChildren().setAll(balanceTextField, balanceCurrencyLabel);
VBox vBox = new VBox();
vBox.setPadding(new Insets(12, 0, 0, 0));
@ -274,7 +328,47 @@ public class MainController implements Initializable, NavigationController, Wall
vBox.getChildren().setAll(hBox, titleLabel);
parent.getChildren().add(vBox);
return balanceLabel;
balanceTextField.setText(Utils.bitcoinValueToFriendlyString(walletFacade.getBalance()));
walletFacade.getWallet().addEventListener(new WalletEventListener()
{
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance)
{
balanceTextField.setText(Utils.bitcoinValueToFriendlyString(newBalance));
}
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx)
{
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance)
{
}
@Override
public void onReorganize(Wallet wallet)
{
}
@Override
public void onWalletChanged(Wallet wallet)
{
}
@Override
public void onKeysAdded(Wallet wallet, List<ECKey> keys)
{
}
@Override
public void onScriptsAdded(Wallet wallet, List<Script> scripts)
{
}
});
return balanceTextField;
}
private void addAccountComboBox(Pane parent)
@ -322,4 +416,5 @@ public class MainController implements Initializable, NavigationController, Wall
}
}
}

View file

@ -13,8 +13,9 @@ public interface NavigationController
public static final String SETTINGS = "/io/bitsquare/gui/settings/SettingsView.fxml";
public static final String ORDER_BOOK = "/io/bitsquare/gui/market/orderbook/OrderBookView.fxml";
public static final String TRADE = "/io/bitsquare/gui/market/trade/TradeView.fxml";
public static final String CREATE_OFFER = "/io/bitsquare/gui/market/offer/CreateOfferView.fxml";
public static final String TAKER_TRADE = "/io/bitsquare/gui/market/trade/TakerTradeView.fxml";
public static final String OFFERER_TRADE = "/io/bitsquare/gui/orders/OffererTradeView.fxml";
public static final String CREATE_OFFER = "/io/bitsquare/gui/market/createOffer/CreateOfferView.fxml";
ChildController navigateToView(String fxmlView, String title);
}

View file

@ -1,6 +1,6 @@
package io.bitsquare.gui.components.processbar;
import io.bitsquare.util.Utils;
import io.bitsquare.util.Utilities;
import javafx.animation.AnimationTimer;
import javafx.scene.control.Button;
import javafx.scene.control.Control;
@ -65,7 +65,7 @@ public class ProcessStepsBuilder
// TODO
// mock simulate network delay
Utils.setTimeout(100, (AnimationTimer animationTimer) -> {
Utilities.setTimeout(100, (AnimationTimer animationTimer) -> {
next();
return null;
});

View file

@ -1,39 +1,43 @@
package io.bitsquare.gui.funds;
import com.google.bitcoin.core.*;
import com.google.bitcoin.script.Script;
import com.google.inject.Inject;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.btc.BtcFormatter;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.gui.ChildController;
import io.bitsquare.gui.NavigationController;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.Pane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
public class FundsController implements Initializable, ChildController
{
private NavigationController navigationController;
private static final Logger log = LoggerFactory.getLogger(FundsController.class);
private WalletFacade walletFacade;
@FXML
public Pane rootContainer;
private TextField tradingAccountTextField, balanceTextField;
@FXML
public TextField addressLabel;
private Label copyIcon, confirmationLabel;
@FXML
public TextField balanceLabel;
@FXML
public Label copyIcon;
private ProgressIndicator progressIndicator;
@Inject
public FundsController(WalletFacade walletFacade)
@ -46,24 +50,65 @@ public class FundsController implements Initializable, ChildController
@Override
public void initialize(URL url, ResourceBundle rb)
{
String tradingAccountAddress = walletFacade.getAddressAsString();
tradingAccountTextField.setText(tradingAccountAddress);
copyIcon.setId("copy-icon");
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
Tooltip.install(copyIcon, new Tooltip("Copy address to clipboard"));
copyIcon.setOnMouseClicked(e -> {
Clipboard clipboard = Clipboard.getSystemClipboard();
ClipboardContent content = new ClipboardContent();
content.putString(addressLabel.getText());
content.putString(tradingAccountAddress);
clipboard.setContent(content);
});
updateBalance(walletFacade.getBalance());
walletFacade.getWallet().addEventListener(new WalletEventListener()
{
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance)
{
updateBalance(newBalance);
}
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx)
{
updateConfidence(tx);
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance)
{
}
@Override
public void onReorganize(Wallet wallet)
{
}
@Override
public void onWalletChanged(Wallet wallet)
{
}
@Override
public void onKeysAdded(Wallet wallet, List<ECKey> keys)
{
}
@Override
public void onScriptsAdded(Wallet wallet, List<Script> scripts)
{
}
});
}
@Override
public void setNavigationController(NavigationController navigationController)
{
this.navigationController = navigationController;
addressLabel.setText(walletFacade.getAddress());
balanceLabel.setText(BtcFormatter.formatSatoshis(walletFacade.getBalance(), false));
}
@Override
@ -72,6 +117,72 @@ public class FundsController implements Initializable, ChildController
}
private void updateBalance(BigInteger balance)
{
if (balance.compareTo(BigInteger.ZERO) == 0)
{
confirmationLabel.setText("");
progressIndicator.setOpacity(0);
progressIndicator.setProgress(0);
}
else
{
progressIndicator.setOpacity(1);
progressIndicator.setProgress(-1);
Set<Transaction> transactions = walletFacade.getWallet().getTransactions(false);
Transaction latestTransaction = null;
for (Iterator<Transaction> iterator = transactions.iterator(); iterator.hasNext(); )
{
Transaction transaction = iterator.next();
if (latestTransaction != null)
{
if (transaction.getUpdateTime().compareTo(latestTransaction.getUpdateTime()) > 0)
{
latestTransaction = transaction;
}
}
else
{
latestTransaction = transaction;
}
}
if (latestTransaction != null)
{
updateConfidence(latestTransaction);
}
}
balanceTextField.setText(Utils.bitcoinValueToFriendlyString(balance));
}
private void updateConfidence(Transaction tx)
{
TransactionConfidence confidence = tx.getConfidence();
double progressIndicatorSize = 50;
switch (confidence.getConfidenceType())
{
case UNKNOWN:
confirmationLabel.setText("");
progressIndicator.setProgress(0);
break;
case PENDING:
confirmationLabel.setText("Seen by " + confidence.numBroadcastPeers() + " peer(s) / 0 confirmations");
progressIndicator.setProgress(-1.0);
progressIndicatorSize = 20;
break;
case BUILDING:
confirmationLabel.setText("Confirmed in " + confidence.getDepthInBlocks() + " block(s)");
progressIndicator.setProgress(Math.min(1, (double) confidence.getDepthInBlocks() / 6.0));
break;
case DEAD:
confirmationLabel.setText("Transaction is invalid.");
break;
}
progressIndicator.setMaxHeight(progressIndicatorSize);
progressIndicator.setPrefHeight(progressIndicatorSize);
progressIndicator.setMaxWidth(progressIndicatorSize);
progressIndicator.setPrefWidth(progressIndicatorSize);
}
}

View file

@ -1,38 +1,57 @@
<?import io.bitsquare.gui.components.HSpacer?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:controller="io.bitsquare.gui.funds.FundsController"
xmlns:fx="http://javafx.com/fxml" fx:id="rootContainer">
<AnchorPane fx:controller="io.bitsquare.gui.funds.FundsController" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
xmlns:fx="http://javafx.com/fxml">
<children>
<VBox spacing="20" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label text="Just one generic address by now for dev...." AnchorPane.topAnchor="10" AnchorPane.leftAnchor="10"/>
<Label id="headline-label" text="Trading account wallet"/>
<HBox spacing="5" AnchorPane.topAnchor="40" AnchorPane.leftAnchor="10">
<Label text="Funds address:">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="5.0"/>
</padding>
</Label>
<TextField fx:id="addressLabel" prefWidth="300" editable="false"/>
<GridPane hgap="5.0" vgap="5.0">
<Label fx:id="copyIcon">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="4.0"/>
</padding>
</Label>
<HSpacer prefWidth="10"/>
<children>
<Label text="Trading account address:"/>
<TextField fx:id="tradingAccountTextField" editable="false" GridPane.columnIndex="1"/>
<Label fx:id="copyIcon" minWidth="10" GridPane.columnIndex="2">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="-1.0"/>
</padding>
<tooltip>
<Tooltip text="Copy address to clipboard"/>
</tooltip>
</Label>
<Label text="Balance:">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="5.0"/>
</padding>
</Label>
<TextField fx:id="balanceLabel" prefWidth="100" editable="false"/>
<Label text="BTC">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="5.0"/>
</padding>
</Label>
</HBox>
<Label text="Balance:" GridPane.rowIndex="1"/>
<TextField fx:id="balanceTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<ProgressIndicator fx:id="progressIndicator" id="confidence-progress-indicator" GridPane.columnIndex="2" GridPane.halignment="LEFT"
GridPane.rowIndex="1" GridPane.rowSpan="2" GridPane.valignment="TOP">
<GridPane.margin>
<Insets top="2.0"/>
</GridPane.margin>
</ProgressIndicator>
<Label fx:id="confirmationLabel" text="Checking confirmations..." GridPane.columnIndex="3" GridPane.rowIndex="1"/>
<Label text="dummy for layout progressIndicator" visible="false" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
</children>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="ALWAYS"/>
<ColumnConstraints hgrow="SOMETIMES" prefWidth="20.0"/>
<ColumnConstraints hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="SOMETIMES"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
</rowConstraints>
</GridPane>
</children>
</VBox>
</children>
</AnchorPane>

View file

@ -15,6 +15,18 @@
-fx-font-size: 14;
}
#online-label {
-fx-fill: green;
}
#offline-label {
-fx-fill: red;
}
#confidence-progress-indicator {
-fx-fill: null;
}
/* main nav */
#nav-button {
@ -22,6 +34,12 @@
-fx-alignment: center;
}
#nav-alert-button {
-fx-background-color: transparent;
-fx-cursor: hand;
-fx-border-style: none;
}
#nav-button-label {
-fx-font-size: 10;
-fx-text-alignment: center;

View file

@ -1,6 +1,14 @@
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Pane?>
<Pane fx:controller="io.bitsquare.gui.history.HistoryController"
xmlns:fx="http://javafx.com/fxml">
<Label text="Histroy"/>
</Pane>
<?import javafx.scene.layout.*?>
<AnchorPane fx:controller="io.bitsquare.gui.history.HistoryController" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
xmlns:fx="http://javafx.com/fxml">
<children>
<VBox spacing="20" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label id="headline-label" text="Trade history"/>
</children>
</VBox>
</children>
</AnchorPane>

View file

@ -1,6 +1,14 @@
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="rootContainer" fx:controller="io.bitsquare.gui.home.HomeController" spacing="10"
xmlns:fx="http://javafx.com/fxml">
<Label text="Overview"/>
</VBox>
<?import javafx.scene.layout.*?>
<AnchorPane fx:controller="io.bitsquare.gui.home.HomeController" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
xmlns:fx="http://javafx.com/fxml">
<children>
<VBox spacing="20" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label id="headline-label" text="Overview"/>
</children>
</VBox>
</children>
</AnchorPane>

View file

@ -0,0 +1,355 @@
package io.bitsquare.gui.market.createOffer;
import com.google.bitcoin.core.*;
import com.google.bitcoin.script.Script;
import com.google.common.util.concurrent.FutureCallback;
import com.google.inject.Inject;
import io.bitsquare.btc.BtcFormatter;
import io.bitsquare.btc.Fees;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.gui.ChildController;
import io.bitsquare.gui.NavigationController;
import io.bitsquare.gui.util.Converter;
import io.bitsquare.gui.util.Formatter;
import io.bitsquare.gui.util.Localisation;
import io.bitsquare.gui.util.Popups;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.settings.Settings;
import io.bitsquare.trade.Direction;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.Trading;
import io.bitsquare.trade.orderbook.OrderBookFilter;
import io.bitsquare.user.Arbitrator;
import io.bitsquare.user.User;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
public class CreateOfferController implements Initializable, ChildController
{
private static final Logger log = LoggerFactory.getLogger(CreateOfferController.class);
private NavigationController navigationController;
private Trading trading;
private WalletFacade walletFacade;
private MessageFacade messageFacade;
private Settings settings;
private User user;
private Direction direction;
private Offer offer;
@FXML
private AnchorPane rootContainer;
@FXML
private Label buyLabel, placeOfferTitle, confirmationLabel, txTitleLabel;
@FXML
private TextField volumeTextField, amountTextField, priceTextField;
@FXML
private Button placeOfferButton, closeButton;
@FXML
private TextField collateralTextField, minAmountTextField, bankAccountTypeTextField, bankAccountCurrencyTextField, bankAccountCountyTextField,
acceptedCountriesTextField, acceptedLanguagesTextField, feeLabel, txTextField;
@FXML
private ProgressIndicator progressIndicator;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public CreateOfferController(Trading trading, WalletFacade walletFacade, MessageFacade messageFacade, Settings settings, User user)
{
this.trading = trading;
this.walletFacade = walletFacade;
this.messageFacade = messageFacade;
this.settings = settings;
this.user = user;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public methods
///////////////////////////////////////////////////////////////////////////////////////////
public void setOrderBookFilter(OrderBookFilter orderBookFilter)
{
direction = orderBookFilter.getDirection();
amountTextField.setText(Formatter.formatPrice(orderBookFilter.getAmount()));
minAmountTextField.setText(Formatter.formatPrice(orderBookFilter.getAmount()));
priceTextField.setText(Formatter.formatPrice(orderBookFilter.getPrice()));
buyLabel.setText(Formatter.formatDirection(direction, false) + ":");
collateralTextField.setText(Formatter.formatVolume(settings.getMinCollateral()));
updateVolume();
//TODO
amountTextField.setText("0,01");
minAmountTextField.setText("0,001");
priceTextField.setText("500");
updateVolume();
amountTextField.textProperty().addListener(new ChangeListener<String>()
{
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
updateVolume();
}
});
priceTextField.textProperty().addListener(new ChangeListener<String>()
{
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
updateVolume();
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: Initializable
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialize(URL url, ResourceBundle rb)
{
bankAccountTypeTextField.setText(Localisation.get(user.getCurrentBankAccount().getBankAccountType().getType().toString()));
bankAccountCurrencyTextField.setText(user.getCurrentBankAccount().getCurrency().getCurrencyCode());
bankAccountCountyTextField.setText(user.getCurrentBankAccount().getCountryLocale().getDisplayCountry());
acceptedCountriesTextField.setText(Formatter.countryLocalesToString(settings.getAcceptedCountryLocales()));
acceptedLanguagesTextField.setText(Formatter.languageLocalesToString(settings.getAcceptedLanguageLocales()));
feeLabel.setText(Utils.bitcoinValueToFriendlyString(Fees.OFFER_CREATION_FEE));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: ChildController
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void setNavigationController(NavigationController navigationController)
{
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI Handlers
///////////////////////////////////////////////////////////////////////////////////////////
public void onPlaceOffer(ActionEvent actionEvent)
{
if (!inputValid())
{
Popups.openWarningPopup("Invalid input", "Your input is invalid");
return;
}
int collateral = (int) (Converter.stringToDouble(collateralTextField.getText()));
Arbitrator arbitrator = settings.getRandomArbitrator(collateral, getAmountAsBI());
if (arbitrator == null)
{
Popups.openWarningPopup("No arbitrator available", "No arbitrator from your arbitrator list does match the collateral and amount value.");
return;
}
log.debug("create offer pubkey " + user.getMessagePubKeyAsHex());
offer = new Offer(user.getMessagePubKeyAsHex(),
direction,
Converter.stringToDouble(priceTextField.getText()),
BtcFormatter.stringValueToSatoshis(amountTextField.getText()),
BtcFormatter.stringValueToSatoshis(minAmountTextField.getText()),
user.getCurrentBankAccount().getBankAccountType().getType(),
user.getCurrentBankAccount().getCurrency(),
user.getCurrentBankAccount().getCountryLocale(),
user.getCurrentBankAccount().getUid(),
arbitrator,
collateral,
settings.getAcceptedCountryLocales(),
settings.getAcceptedLanguageLocales());
FutureCallback callback = new FutureCallback<Transaction>()
{
@Override
public void onSuccess(Transaction transaction)
{
log.info("sendResult onSuccess:" + transaction.toString());
offer.setOfferFeePaymentTxID(transaction.getHashAsString());
setupSuccessScreen(transaction);
placeOfferTitle.setText("Transaction sent:");
}
@Override
public void onFailure(Throwable t)
{
log.warn("sendResult onFailure:" + t.toString());
Popups.openErrorPopup("Fee payment failed", "Fee payment failed. " + t.toString());
placeOfferButton.setDisable(false);
}
};
try
{
trading.placeNewOffer(offer, callback);
messageFacade.addOffer(offer);
placeOfferButton.setDisable(true);
} catch (InsufficientMoneyException e1)
{
Popups.openErrorPopup("Not enough money available", "There is not enough money available. Please pay in first to your wallet. " + e1.getMessage());
} catch (IOException e1)
{
Popups.openErrorPopup("Could not publish offer", "Could not publish offer. " + e1.getMessage());
}
}
public void onClose(ActionEvent actionEvent)
{
TabPane tabPane = ((TabPane) (rootContainer.getParent().getParent()));
tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem());
navigationController.navigateToView(NavigationController.ORDER_BOOK, "Orderbook");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
private void setupSuccessScreen(Transaction newTransaction)
{
placeOfferButton.setVisible(false);
progressIndicator.setVisible(true);
confirmationLabel.setVisible(true);
txTitleLabel.setVisible(true);
txTextField.setVisible(true);
closeButton.setVisible(true);
txTextField.setText(newTransaction.getHashAsString());
updateConfidence(newTransaction);
walletFacade.getWallet().addEventListener(new WalletEventListener()
{
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance)
{
}
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx)
{
updateConfidence(newTransaction);
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance)
{
}
@Override
public void onReorganize(Wallet wallet)
{
}
@Override
public void onWalletChanged(Wallet wallet)
{
}
@Override
public void onKeysAdded(Wallet wallet, List<ECKey> keys)
{
}
@Override
public void onScriptsAdded(Wallet wallet, List<Script> scripts)
{
}
});
}
private void updateConfidence(Transaction tx)
{
TransactionConfidence confidence = tx.getConfidence();
double progressIndicatorSize = 20;
switch (confidence.getConfidenceType())
{
case UNKNOWN:
confirmationLabel.setText("");
progressIndicator.setProgress(0);
progressIndicatorSize = 50;
break;
case PENDING:
confirmationLabel.setText("Seen by " + confidence.numBroadcastPeers() + " peer(s) / 0 confirmations");
progressIndicator.setProgress(-1.0);
break;
case BUILDING:
confirmationLabel.setText("Confirmed in " + confidence.getDepthInBlocks() + " block(s)");
progressIndicator.setProgress(Math.min(1, (double) confidence.getDepthInBlocks() / 6.0));
progressIndicatorSize = 50;
break;
case DEAD:
confirmationLabel.setText("Transaction is invalid.");
break;
}
progressIndicator.setMaxHeight(progressIndicatorSize);
progressIndicator.setPrefHeight(progressIndicatorSize);
progressIndicator.setMaxWidth(progressIndicatorSize);
progressIndicator.setPrefWidth(progressIndicatorSize);
}
private void updateVolume()
{
volumeTextField.setText(Formatter.formatVolume(getVolume()));
}
private double getVolume()
{
double amountAsDouble = Converter.stringToDouble(amountTextField.getText());
double priceAsDouble = Converter.stringToDouble(priceTextField.getText());
return amountAsDouble * priceAsDouble;
}
private BigInteger getAmountAsBI()
{
return BtcFormatter.stringValueToSatoshis(amountTextField.getText());
}
private boolean inputValid()
{
double priceAsDouble = Converter.stringToDouble(priceTextField.getText());
double minAmountAsDouble = Converter.stringToDouble(minAmountTextField.getText());
double amountAsDouble = Converter.stringToDouble(amountTextField.getText());
double collateralAsDouble = Converter.stringToDouble(collateralTextField.getText());
return priceAsDouble > 0 &&
amountAsDouble > 0 &&
minAmountAsDouble > 0 &&
minAmountAsDouble <= amountAsDouble &&
collateralAsDouble >= settings.getMinCollateral() &&
collateralAsDouble <= settings.getMaxCollateral();
}
}

View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="rootContainer" prefHeight="500" prefWidth="800" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="io.bitsquare.gui.market.createOffer.CreateOfferController">
<children>
<GridPane hgap="5.0" vgap="5.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<children>
<Label id="form-header-text" text="Create new offer" GridPane.rowIndex="0"/>
<Label fx:id="buyLabel" GridPane.rowIndex="1"/>
<HBox spacing="5" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="NEVER" alignment="CENTER_LEFT">
<TextField fx:id="amountTextField" prefWidth="70.0" alignment="CENTER_RIGHT"/>
<Label text="BTC for:"/>
<TextField fx:id="priceTextField" prefWidth="70.0" alignment="CENTER_RIGHT"/>
<Label text="EUR ="/>
<TextField fx:id="volumeTextField" prefWidth="70.0" alignment="CENTER_RIGHT" editable="false"/>
<Label text="EUR in total"/>
</HBox>
<Label text="Min. Amount:" GridPane.rowIndex="2"/>
<TextField fx:id="minAmountTextField" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<Label text="Collateral (%):" GridPane.rowIndex="3"/>
<TextField fx:id="collateralTextField" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<Label id="form-header-text" text="Offer details" GridPane.rowIndex="4">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</Label>
<Label text="Bank account type:" GridPane.rowIndex="5"/>
<TextField fx:id="bankAccountTypeTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
<Label text="Bank account currency:" GridPane.rowIndex="6"/>
<TextField fx:id="bankAccountCurrencyTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="6"/>
<Label text="Bank account county:" GridPane.rowIndex="7"/>
<TextField fx:id="bankAccountCountyTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="7"/>
<Label text="Accepted countries:" GridPane.rowIndex="8"/>
<TextField fx:id="acceptedCountriesTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="8"/>
<Label text="Accepted languages:" GridPane.rowIndex="9"/>
<TextField fx:id="acceptedLanguagesTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="9"/>
<Label id="form-header-text" fx:id="placeOfferTitle" text="Place offer:" GridPane.rowIndex="10">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</Label>
<Label text="Offer fee:" GridPane.rowIndex="11"/>
<TextField fx:id="feeLabel" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="11"/>
<Button fx:id="placeOfferButton" defaultButton="true" onAction="#onPlaceOffer" text="Place offer" GridPane.columnIndex="1" GridPane.rowIndex="12"/>
<Label fx:id="txTitleLabel" text="Transaction ID:" visible="false" GridPane.rowIndex="12"/>
<TextField fx:id="txTextField" visible="false" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="12"/>
<ProgressIndicator fx:id="progressIndicator" id="confidence-progress-indicator" visible="false" progress="0" GridPane.columnIndex="2" GridPane.halignment="LEFT"
GridPane.rowIndex="12"
GridPane.rowSpan="2" GridPane.valignment="TOP">
<GridPane.margin>
<Insets top="2.0"/>
</GridPane.margin>
</ProgressIndicator>
<Label fx:id="confirmationLabel" visible="false" text="Checking confirmations..." GridPane.columnIndex="3" GridPane.rowIndex="12"/>
<Button fx:id="closeButton" visible="false" defaultButton="true" onAction="#onClose" text="Close" GridPane.columnIndex="1" GridPane.rowIndex="13"/>
</children>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="400"/>
<ColumnConstraints hgrow="SOMETIMES" prefWidth="20.0" minWidth="20"/>
<ColumnConstraints hgrow="SOMETIMES"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
</rowConstraints>
<opaqueInsets>
<Insets/>
</opaqueInsets>
</GridPane>
</children>
</AnchorPane>

View file

@ -1,299 +0,0 @@
package io.bitsquare.gui.market.offer;
import com.google.bitcoin.core.InsufficientMoneyException;
import com.google.bitcoin.core.Transaction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.inject.Inject;
import io.bitsquare.btc.BtcFormatter;
import io.bitsquare.btc.Fees;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.gui.ChildController;
import io.bitsquare.gui.NavigationController;
import io.bitsquare.gui.components.ConfirmationComponent;
import io.bitsquare.gui.util.*;
import io.bitsquare.settings.Settings;
import io.bitsquare.trade.Direction;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.Trading;
import io.bitsquare.trade.orderbook.OrderBookFilter;
import io.bitsquare.user.Arbitrator;
import io.bitsquare.user.User;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.net.URL;
import java.util.ResourceBundle;
public class CreateOfferController implements Initializable, ChildController, WalletFacade.WalletListener
{
private static final Logger log = LoggerFactory.getLogger(CreateOfferController.class);
private NavigationController navigationController;
private Trading trading;
private WalletFacade walletFacade;
private Settings settings;
private User user;
private Direction direction;
private Offer offer;
private int gridRow;
private Button placeOfferButton;
private TextField collateralTextField, minAmountTextField;
@FXML
private AnchorPane holderPane;
@FXML
private GridPane formGridPane;
@FXML
public Label buyLabel;
@FXML
public TextField volumeTextField, amountTextField, priceTextField;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public CreateOfferController(Trading trading, WalletFacade walletFacade, Settings settings, User user)
{
this.trading = trading;
this.walletFacade = walletFacade;
this.settings = settings;
this.user = user;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public methods
///////////////////////////////////////////////////////////////////////////////////////////
public void setOrderBookFilter(OrderBookFilter orderBookFilter)
{
direction = orderBookFilter.getDirection();
amountTextField.setText(Formatter.formatPrice(orderBookFilter.getAmount()));
minAmountTextField.setText(Formatter.formatPrice(orderBookFilter.getAmount()));
priceTextField.setText(Formatter.formatPrice(orderBookFilter.getPrice()));
buyLabel.setText(Formatter.formatDirection(direction, false) + ":");
collateralTextField.setText(Formatter.formatVolume(settings.getMinCollateral()));
updateVolume();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: Initializable
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialize(URL url, ResourceBundle rb)
{
walletFacade.addRegistrationWalletListener(this);
buildScreen();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: ChildController
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void setNavigationController(NavigationController navigationController)
{
this.navigationController = navigationController;
}
@Override
public void cleanup()
{
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: WalletFacade.WalletListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onConfidenceChanged(int numBroadcastPeers, int depthInBlocks)
{
log.info("onConfidenceChanged " + numBroadcastPeers + " / " + depthInBlocks);
}
@Override
public void onCoinsReceived(BigInteger newBalance)
{
log.info("onCoinsReceived " + newBalance);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
private void buildScreen()
{
gridRow = 1;
minAmountTextField = FormBuilder.addTextField(formGridPane, "Min. Amount:", String.valueOf(settings.getMaxCollateral()), ++gridRow, true, true);
collateralTextField = FormBuilder.addTextField(formGridPane, "Collateral (%):", String.valueOf(settings.getMaxCollateral() * 100), ++gridRow, true, true);
FormBuilder.addVSpacer(formGridPane, ++gridRow);
FormBuilder.addHeaderLabel(formGridPane, "Offer details:", ++gridRow);
FormBuilder.addTextField(formGridPane, "Bank account type:", Localisation.get(user.getCurrentBankAccount().getBankAccountType().getType().toString()), ++gridRow);
FormBuilder.addTextField(formGridPane, "Bank account currency:", user.getCurrentBankAccount().getCurrency().getCurrencyCode(), ++gridRow);
FormBuilder.addTextField(formGridPane, "Bank account county:", user.getCurrentBankAccount().getCountryLocale().getDisplayCountry(), ++gridRow);
FormBuilder.addTextField(formGridPane, "Accepted countries:", Formatter.countryLocalesToString(settings.getAcceptedCountryLocales()), ++gridRow);
FormBuilder.addTextField(formGridPane, "Accepted languages:", Formatter.languageLocalesToString(settings.getAcceptedLanguageLocales()), ++gridRow);
FormBuilder.addVSpacer(formGridPane, ++gridRow);
Label placeOfferTitle = FormBuilder.addHeaderLabel(formGridPane, "Place offer:", ++gridRow);
TextField feeLabel = FormBuilder.addTextField(formGridPane, "Offer fee:", BtcFormatter.formatSatoshis(Fees.OFFER_CREATION_FEE, true), ++gridRow);
feeLabel.setMouseTransparent(true);
placeOfferButton = new Button("Place offer");
formGridPane.add(placeOfferButton, 1, ++gridRow);
placeOfferButton.setDefaultButton(true);
// handlers
amountTextField.textProperty().addListener(new ChangeListener<String>()
{
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
updateVolume();
}
});
priceTextField.textProperty().addListener(new ChangeListener<String>()
{
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
updateVolume();
}
});
placeOfferButton.setOnAction(e -> {
if (!inputValid())
{
Popups.openWarningPopup("Invalid input", "Your input is invalid");
return;
}
double collateralAsDouble = Converter.stringToDouble(collateralTextField.getText()) / 100;
Arbitrator arbitrator = settings.getRandomArbitrator(collateralAsDouble, getAmountAsBI());
if (arbitrator == null)
{
Popups.openWarningPopup("No arbitrator available", "No arbitrator from your arbitrator list does match the collateral and amount value.");
return;
}
offer = new Offer(user.getAccountID(),
user.getMessageID(),
direction,
Converter.stringToDouble(priceTextField.getText()),
BtcFormatter.stringValueToSatoshis(amountTextField.getText()),
BtcFormatter.stringValueToSatoshis(minAmountTextField.getText()),
user.getCurrentBankAccount().getBankAccountType().getType(),
user.getCurrentBankAccount().getCurrency(),
user.getCurrentBankAccount().getCountryLocale(),
arbitrator,
collateralAsDouble,
settings.getAcceptedCountryLocales(),
settings.getAcceptedLanguageLocales());
FutureCallback callback = new FutureCallback<Transaction>()
{
@Override
public void onSuccess(Transaction transaction)
{
log.info("sendResult onSuccess:" + transaction.toString());
offer.setOfferPaymentTxID(transaction.getHashAsString());
buildConfirmationView(transaction.getHashAsString());
placeOfferTitle.setText("Transaction sent:");
formGridPane.getChildren().remove(placeOfferButton);
}
@Override
public void onFailure(Throwable t)
{
log.warn("sendResult onFailure:" + t.toString());
Popups.openErrorPopup("Fee payment failed", "Fee payment failed. " + t.toString());
}
};
try
{
trading.placeNewOffer(offer, callback);
} catch (InsufficientMoneyException e1)
{
Popups.openErrorPopup("Not enough money available", "There is not enough money available. Please pay in first to your wallet.");
}
});
}
private void buildConfirmationView(String txID)
{
FormBuilder.addTextField(formGridPane, "Transaction ID:", txID, ++gridRow, false, true);
new ConfirmationComponent(walletFacade, formGridPane, ++gridRow);
Button closeButton = new Button("Close");
formGridPane.add(closeButton, 1, ++gridRow);
closeButton.setDefaultButton(true);
closeButton.setOnAction(e -> {
TabPane tabPane = ((TabPane) (holderPane.getParent().getParent()));
tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem());
navigationController.navigateToView(NavigationController.ORDER_BOOK, "Orderbook");
});
}
private boolean inputValid()
{
double priceAsDouble = Converter.stringToDouble(priceTextField.getText());
double minAmountAsDouble = Converter.stringToDouble(minAmountTextField.getText());
double amountAsDouble = Converter.stringToDouble(amountTextField.getText());
double collateralAsDouble = Converter.stringToDouble(collateralTextField.getText());
return priceAsDouble > 0 &&
amountAsDouble > 0 &&
minAmountAsDouble > 0 &&
minAmountAsDouble <= amountAsDouble &&
collateralAsDouble >= settings.getMinCollateral() &&
collateralAsDouble <= settings.getMaxCollateral();
}
private void updateVolume()
{
volumeTextField.setText(Formatter.formatVolume(getVolume()));
}
private double getVolume()
{
double amountAsDouble = Converter.stringToDouble(amountTextField.getText());
double priceAsDouble = Converter.stringToDouble(priceTextField.getText());
return amountAsDouble * priceAsDouble;
}
private BigInteger getAmountAsBI()
{
return BtcFormatter.stringValueToSatoshis(amountTextField.getText());
}
}

View file

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="holderPane" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="io.bitsquare.gui.market.offer.CreateOfferController">
<VBox spacing="10" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="5.0"
AnchorPane.bottomAnchor="10.0">
<GridPane fx:id="formGridPane" vgap="5" hgap="5">
<Label id="form-header-text" text="Create new offer:" GridPane.rowIndex="0"
GridPane.columnIndex="0"/>
<Label fx:id="buyLabel" text="Buy" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
<HBox GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="NEVER" spacing="5"
alignment="CENTER_RIGHT">
<TextField fx:id="amountTextField" prefWidth="70.0" alignment="CENTER_RIGHT"/>
<Label text="BTC for:"/>
<TextField fx:id="priceTextField" prefWidth="70.0" alignment="CENTER_RIGHT"/>
<Label text="EUR ="/>
<TextField fx:id="volumeTextField" prefWidth="70.0" alignment="CENTER_RIGHT" editable="false"
mouseTransparent="true"/>
<Label text="EUR in total"/>
</HBox>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
</padding>
<columnConstraints>
<ColumnConstraints halignment="RIGHT"/>
<ColumnConstraints halignment="LEFT"/>
<ColumnConstraints halignment="LEFT"/>
</columnConstraints>
</GridPane>
</VBox>
</AnchorPane>

View file

@ -5,16 +5,20 @@ import io.bitsquare.bank.BankAccountType;
import io.bitsquare.btc.BtcFormatter;
import io.bitsquare.gui.ChildController;
import io.bitsquare.gui.NavigationController;
import io.bitsquare.gui.market.offer.CreateOfferController;
import io.bitsquare.gui.market.trade.TradeController;
import io.bitsquare.gui.market.createOffer.CreateOfferController;
import io.bitsquare.gui.market.trade.TakerTradeController;
import io.bitsquare.gui.util.Converter;
import io.bitsquare.gui.util.Formatter;
import io.bitsquare.gui.util.Icons;
import io.bitsquare.gui.util.Localisation;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.trade.Direction;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.orderbook.OrderBook;
import io.bitsquare.trade.orderbook.OrderBookFilter;
import io.bitsquare.user.User;
import io.bitsquare.util.Utilities;
import javafx.animation.AnimationTimer;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
@ -31,6 +35,7 @@ import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URL;
import java.text.DecimalFormat;
@ -47,7 +52,8 @@ public class OrderBookController implements Initializable, ChildController
private SortedList<OrderBookListItem> offerList;
private final OrderBookFilter orderBookFilter;
private User user;
private MessageFacade messageFacade;
private AnimationTimer pollingTimer;
private Image buyIcon = Icons.getIconImage(Icons.BUY);
private Image sellIcon = Icons.getIconImage(Icons.SELL);
@ -67,17 +73,30 @@ public class OrderBookController implements Initializable, ChildController
@FXML
public Button createOfferButton;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public OrderBookController(OrderBook orderBook, OrderBookFilter orderBookFilter, User user)
public OrderBookController(OrderBook orderBook, OrderBookFilter orderBookFilter, User user, MessageFacade messageFacade)
{
this.orderBook = orderBook;
this.orderBookFilter = orderBookFilter;
this.user = user;
this.messageFacade = messageFacade;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: Initializable
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialize(URL url, ResourceBundle rb)
{
orderBook.init();
// setup table
setCountryColumnCellFactory();
setBankAccountTypeColumnCellFactory();
@ -88,6 +107,8 @@ public class OrderBookController implements Initializable, ChildController
orderBookTable.getSortOrder().add(priceColumn);
orderBookTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
orderBook.loadOffers();
// handlers
amount.textProperty().addListener(new ChangeListener<String>()
{
@ -109,20 +130,21 @@ public class OrderBookController implements Initializable, ChildController
}
});
orderBookFilter.getChangedProperty().addListener(new ChangeListener<Boolean>()
orderBookFilter.getDirectionChangedProperty().addListener(new ChangeListener<Boolean>()
{
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
{
updateOfferList();
applyOffers();
}
});
user.getChangedProperty().addListener(new ChangeListener<Boolean>()
user.getBankAccountChangedProperty().addListener(new ChangeListener<Boolean>()
{
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
{
updateOfferList();
orderBook.loadOffers();
}
});
@ -130,8 +152,16 @@ public class OrderBookController implements Initializable, ChildController
ChildController nextController = navigationController.navigateToView(NavigationController.CREATE_OFFER, "Create offer");
((CreateOfferController) nextController).setOrderBookFilter(orderBookFilter);
});
//TODO do polling until broadcast works
setupPolling();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: ChildController
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void setNavigationController(NavigationController navigationController)
{
@ -141,12 +171,24 @@ public class OrderBookController implements Initializable, ChildController
@Override
public void cleanup()
{
orderBook.cleanup();
orderBookTable.setItems(null);
orderBookTable.getSortOrder().clear();
offerList.comparatorProperty().unbind();
if (pollingTimer != null)
{
pollingTimer.stop();
pollingTimer = null;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public methods
///////////////////////////////////////////////////////////////////////////////////////////
public void setDirection(Direction direction)
{
orderBookTable.getSelectionModel().clearSelection();
@ -155,21 +197,36 @@ public class OrderBookController implements Initializable, ChildController
}
private void openTradeTab(OrderBookListItem orderBookListItem)
{
String title = orderBookListItem.getOffer().getDirection() == Direction.BUY ? "Trade: Sell Bitcoin" : "Trade: Buy Bitcoin";
TradeController tradeController = (TradeController) navigationController.navigateToView(NavigationController.TRADE, title);
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
BigInteger requestedAmount = orderBookListItem.getOffer().getAmount();
private void takeOffer(Offer offer)
{
String title = offer.getDirection() == Direction.BUY ? "Trade: Sell Bitcoin" : "Trade: Buy Bitcoin";
TakerTradeController takerTradeController = (TakerTradeController) navigationController.navigateToView(NavigationController.TAKER_TRADE, title);
BigInteger requestedAmount = offer.getAmount();
if (!amount.getText().equals(""))
requestedAmount = BtcFormatter.stringValueToSatoshis(amount.getText());
tradeController.initWithData(orderBookListItem.getOffer(), requestedAmount);
takerTradeController.initWithData(offer, requestedAmount);
}
private void updateOfferList()
private void removeOffer(Offer offer)
{
orderBook.updateFilter(orderBookFilter);
try
{
orderBook.removeOffer(offer);
} catch (IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
private void applyOffers()
{
orderBook.applyFilter(orderBookFilter);
priceColumn.setSortType((orderBookFilter.getDirection() == Direction.BUY) ? TableColumn.SortType.ASCENDING : TableColumn.SortType.DESCENDING);
orderBookTable.sort();
@ -178,6 +235,34 @@ public class OrderBookController implements Initializable, ChildController
createOfferButton.setDefaultButton(orderBookTable.getItems().size() == 0);
}
private void setupPolling()
{
pollingTimer = Utilities.setInterval(1000, (AnimationTimer animationTimer) -> {
try
{
messageFacade.getDirtyFlag(user.getCurrentBankAccount().getCurrency());
} catch (IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
return null;
});
messageFacade.getIsDirtyProperty().addListener(new ChangeListener<Boolean>()
{
@Override
public void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldValue, Boolean newValue)
{
//log.info("getIsDirtyProperty changed " + oldValue + "/" + newValue);
orderBook.loadOffers();
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table columns
///////////////////////////////////////////////////////////////////////////////////////////
private void setDirectionColumnCellFactory()
{
directionColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper(offer.getValue()));
@ -205,22 +290,35 @@ public class OrderBookController implements Initializable, ChildController
{
String title;
Image icon;
if (orderBookListItem.getOffer().getDirection() == Direction.SELL)
Offer offer = orderBookListItem.getOffer();
if (offer.getMessagePubKeyAsHex().equals(user.getMessagePubKeyAsHex()))
{
icon = buyIcon;
title = Formatter.formatDirection(Direction.BUY, true);
icon = Icons.getIconImage(Icons.REMOVE);
title = "Remove";
button.setOnAction(event -> removeOffer(orderBookListItem.getOffer()));
}
else
{
icon = sellIcon;
title = Formatter.formatDirection(Direction.SELL, true);
if (offer.getDirection() == Direction.SELL)
{
icon = buyIcon;
title = Formatter.formatDirection(Direction.BUY, true);
}
else
{
icon = sellIcon;
title = Formatter.formatDirection(Direction.SELL, true);
}
button.setDefaultButton(getIndex() == 0);
button.setOnAction(event -> takeOffer(orderBookListItem.getOffer()));
}
iconView.setImage(icon);
button.setText(title);
setGraphic(button);
button.setDefaultButton(getIndex() == 0);
button.setOnAction(event -> openTradeTab(orderBookListItem));
}
else
{
@ -305,6 +403,11 @@ public class OrderBookController implements Initializable, ChildController
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private double textInputToNumber(String oldValue, String newValue)
{
//TODO use regex.... or custom textfield component
@ -330,24 +433,5 @@ public class OrderBookController implements Initializable, ChildController
double p = textInputToNumber(price.getText(), price.getText());
volume.setText(Formatter.formatPrice(a * p));
}
// the scrollbar width is not handled correctly from the layout initially
/* private void forceTableLayoutUpdate()
{
final List<OrderBookListItem> items = orderBookTable.getItems();
if (items == null || items.size() == 0) return;
final OrderBookListItem item = orderBookTable.getItems().get(0);
items.remove(0);
Platform.runLater(new Runnable()
{
@Override
public void run()
{
items.add(0, item);
}
});
} */
}

View file

@ -1,5 +1,6 @@
package io.bitsquare.gui.market.orderbook;
import io.bitsquare.btc.BtcFormatter;
import io.bitsquare.gui.util.Formatter;
import io.bitsquare.trade.Offer;
import javafx.beans.property.SimpleStringProperty;
@ -10,19 +11,22 @@ import javafx.beans.property.StringProperty;
*/
public class OrderBookListItem
{
private final StringProperty price = new SimpleStringProperty();
private final StringProperty amount = new SimpleStringProperty();
private final StringProperty volume = new SimpleStringProperty();
protected final StringProperty price = new SimpleStringProperty();
protected final StringProperty amount = new SimpleStringProperty();
protected final StringProperty volume = new SimpleStringProperty();
private Offer offer;
protected Offer offer;
public OrderBookListItem(Offer offer)
{
this.offer = offer;
double amountAsBtcDouble = BtcFormatter.satoshiToBTC(offer.getAmount());
double minAmountAsBtcDouble = BtcFormatter.satoshiToBTC(offer.getMinAmount());
this.price.set(Formatter.formatPrice(offer.getPrice()));
this.amount.set(Formatter.formatAmountWithMinAmount(offer.getAmount().doubleValue(), offer.getMinAmount().doubleValue()));
this.amount.set(Formatter.formatAmountWithMinAmount(amountAsBtcDouble, minAmountAsBtcDouble));
this.volume.set(Formatter.formatVolumeWithMinVolume(offer.getVolume(), offer.getMinVolume()));
}

View file

@ -1,8 +1,5 @@
package io.bitsquare.gui.market.trade;
import com.google.bitcoin.core.InsufficientMoneyException;
import com.google.bitcoin.core.Transaction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.inject.Inject;
import io.bitsquare.btc.BlockChainFacade;
import io.bitsquare.btc.BtcFormatter;
@ -16,12 +13,17 @@ import io.bitsquare.gui.util.Converter;
import io.bitsquare.gui.util.FormBuilder;
import io.bitsquare.gui.util.Formatter;
import io.bitsquare.gui.util.Popups;
import io.bitsquare.trade.*;
import io.bitsquare.util.Utils;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.trade.Direction;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.Trading;
import io.bitsquare.trade.taker.TakerPaymentProtocol;
import io.bitsquare.trade.taker.TakerPaymentProtocolListener;
import io.bitsquare.util.Utilities;
import javafx.animation.AnimationTimer;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
@ -35,17 +37,16 @@ import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
public class TradeController implements Initializable, ChildController, WalletFacade.WalletListener
public class TakerTradeController implements Initializable, ChildController, WalletFacade.WalletListener
{
private static final Logger log = LoggerFactory.getLogger(TradeController.class);
private static final int SIM_DELAY = 2000;
private static final Logger log = LoggerFactory.getLogger(TakerTradeController.class);
private Trading trading;
private WalletFacade walletFacade;
private BlockChainFacade blockChainFacade;
private MessageFacade messageFacade;
private Offer offer;
private Trade trade;
private Contract contract;
private BigInteger requestedAmount;
private boolean offererIsOnline;
private int row;
@ -53,10 +54,14 @@ public class TradeController implements Initializable, ChildController, WalletFa
private List<ProcessStepItem> processStepItems = new ArrayList();
private NavigationController navigationController;
private TextField amountTextField, totalToPayLabel, totalLabel, collateralTextField;
private TextField amountTextField, totalToPayLabel, totalLabel, collateralTextField, isOnlineTextField;
private Label statusTextField, infoLabel;
private Button nextButton;
private ProgressBar progressBar;
private AnimationTimer checkOnlineStatusTimer;
private Pane isOnlineCheckerHolder;
TakerPaymentProtocol takerPaymentProtocol;
private Label headerLabel;
@FXML
private AnchorPane rootContainer;
@ -71,11 +76,12 @@ public class TradeController implements Initializable, ChildController, WalletFa
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TradeController(Trading trading, WalletFacade walletFacade, BlockChainFacade blockChainFacade)
public TakerTradeController(Trading trading, WalletFacade walletFacade, BlockChainFacade blockChainFacade, MessageFacade messageFacade)
{
this.trading = trading;
this.walletFacade = walletFacade;
this.blockChainFacade = blockChainFacade;
this.messageFacade = messageFacade;
}
@ -88,9 +94,9 @@ public class TradeController implements Initializable, ChildController, WalletFa
this.offer = offer;
this.requestedAmount = requestedAmount.compareTo(BigInteger.ZERO) > 0 ? requestedAmount : offer.getAmount();
trade = trading.createTrade(offer);
trade.setTradeAmount(requestedAmount);
contract = trading.createContract(trade);
// trade = trading.createTrade(offer);
//trade.setTradeAmount(requestedAmount);
//contract = trading.createContract(trade);
processStepItems.add(new ProcessStepItem(takerIsSelling() ? "Sell BTC" : "Buy BTC"));
processStepItems.add(new ProcessStepItem("Bank transfer"));
@ -125,7 +131,13 @@ public class TradeController implements Initializable, ChildController, WalletFa
@Override
public void cleanup()
{
if (checkOnlineStatusTimer != null)
{
checkOnlineStatusTimer.stop();
checkOnlineStatusTimer = null;
}
walletFacade.removeRegistrationWalletListener(this);
}
@ -136,15 +148,20 @@ public class TradeController implements Initializable, ChildController, WalletFa
@Override
public void onConfidenceChanged(int numBroadcastPeers, int depthInBlocks)
{
log.info("onConfidenceChanged " + numBroadcastPeers + " / " + depthInBlocks);
//log.info("onConfidenceChanged " + numBroadcastPeers + " / " + depthInBlocks);
}
@Override
public void onCoinsReceived(BigInteger newBalance)
{
log.info("onCoinsReceived " + newBalance);
//log.info("onCoinsReceived " + newBalance);
}
//TODO
public void onPingPeerResult(boolean success)
{
setIsOnlineStatus(success);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
@ -173,24 +190,29 @@ public class TradeController implements Initializable, ChildController, WalletFa
FormBuilder.addTextField(gridPane, "Offer fee (BTC):", BtcFormatter.formatSatoshis(Fees.OFFER_TAKER_FEE, false), ++row);
totalToPayLabel = FormBuilder.addTextField(gridPane, "Total to pay (BTC):", getTotalToPay(), ++row);
isOnlineTextField = FormBuilder.addTextField(gridPane, "Online status:", "Checking offerers online status...", ++row);
ProgressIndicator isOnlineChecker = new ProgressIndicator();
isOnlineChecker.setPrefSize(20, 20);
isOnlineChecker.setLayoutY(3);
isOnlineCheckerHolder = new Pane();
isOnlineCheckerHolder.getChildren().addAll(isOnlineChecker);
gridPane.add(isOnlineCheckerHolder, 2, row);
messageFacade.pingPeer(offer.getMessagePubKeyAsHex());
checkOnlineStatusTimer = Utilities.setTimeout(5000, (AnimationTimer animationTimer) -> {
setIsOnlineStatus(false);
return null;
});
nextButton = FormBuilder.addButton(gridPane, "Take offer and pay", ++row);
nextButton.setDefaultButton(true);
nextButton.setOnAction(e -> initTrade());
nextButton.setOnAction(e -> takeOffer());
// details
FormBuilder.addVSpacer(gridPane, ++row);
FormBuilder.addHeaderLabel(gridPane, "Offerer details:", ++row);
TextField isOnlineTextField = FormBuilder.addTextField(gridPane, "Online status:", "Checking offerers online status...", ++row);
ProgressIndicator isOnlineChecker = new ProgressIndicator();
isOnlineChecker.setPrefSize(20, 20);
isOnlineChecker.setLayoutY(3);
Pane isOnlineCheckerHolder = new Pane();
isOnlineCheckerHolder.getChildren().addAll(isOnlineChecker);
gridPane.add(isOnlineCheckerHolder, 2, row);
checkIfOffererIsOnline(isOnlineCheckerHolder, isOnlineTextField);
FormBuilder.addTextField(gridPane, "Bank account type:", offer.getBankAccountTypeEnum().toString(), ++row);
FormBuilder.addTextField(gridPane, "Bank account country:", offer.getBankAccountCountryLocale().getDisplayCountry(), ++row);
FormBuilder.addTextField(gridPane, "Country:", offer.getBankAccountCountryLocale().getDisplayCountry(), ++row);
FormBuilder.addTextField(gridPane, "Arbitrator:", offer.getArbitrator().getName(), ++row);
Label arbitratorLink = new Label(offer.getArbitrator().getUrl());
arbitratorLink.setId("label-url");
@ -198,23 +220,18 @@ public class TradeController implements Initializable, ChildController, WalletFa
arbitratorLink.setOnMouseClicked(e -> {
try
{
Utils.openURL(offer.getArbitrator().getUrl());
Utilities.openURL(offer.getArbitrator().getUrl());
} catch (Exception e1)
{
log.warn(e1.toString());
}
});
FormBuilder.addVSpacer(gridPane, ++row);
FormBuilder.addHeaderLabel(gridPane, "More details:", ++row);
FormBuilder.addTextField(gridPane, "Offer ID:", offer.getUid().toString(), ++row);
FormBuilder.addTextField(gridPane, "Account ID:", offer.getAccountID(), ++row);
FormBuilder.addTextField(gridPane, "Messaging ID:", offer.getMessageID(), ++row);
FormBuilder.addTextField(gridPane, "Supported languages:", Formatter.languageLocalesToString(offer.getAcceptedLanguageLocales()), ++row);
FormBuilder.addTextField(gridPane, "Supported countries:", Formatter.countryLocalesToString(offer.getAcceptedCountryLocales()), ++row);
}
private void initTrade()
private void takeOffer()
{
if (!tradeAmountValid())
{
@ -222,7 +239,7 @@ public class TradeController implements Initializable, ChildController, WalletFa
return;
}
if (!blockChainFacade.verifyEmbeddedData(offer.getAccountID()))
/* if (!blockChainFacade.verifyAccountRegistration(offer.getAccountID()))
{
Popups.openErrorPopup("Offerers account ID not valid", "Offerers registration tx is not found in blockchain or does not match the requirements.");
return;
@ -232,7 +249,7 @@ public class TradeController implements Initializable, ChildController, WalletFa
{
Popups.openErrorPopup("Offerers account ID is blacklisted", "Offerers account ID is blacklisted.");
return;
}
} */
amountTextField.setEditable(false);
@ -258,101 +275,80 @@ public class TradeController implements Initializable, ChildController, WalletFa
progressIndicatorHolder.getChildren().addAll(progressIndicator);
gridPane.add(progressIndicatorHolder, 1, row);
trade = trading.createTrade(offer);
trade.setTradeAmount(BtcFormatter.stringValueToSatoshis(amountTextField.getText()));
trading.sendTakeOfferRequest(trade);
Utils.setTimeout(SIM_DELAY, (AnimationTimer animationTimer) -> {
onTakeOfferRequestConfirmed();
progressBar.setProgress(1.0 / 3.0);
return null;
});
}
private void onTakeOfferRequestConfirmed()
{
FutureCallback callback = new FutureCallback<Transaction>()
takerPaymentProtocol = trading.addTakerPaymentProtocol(trade, new TakerPaymentProtocolListener()
{
@Override
public void onSuccess(Transaction transaction)
public void onProgress(double progress)
{
log.info("sendResult onSuccess:" + transaction.toString());
trade.setTakeOfferFeeTxID(transaction.getHashAsString());
progressBar.setProgress(progress);
statusTextField.setText("Offer fee payed. Send offerer payment transaction ID for confirmation.");
Utils.setTimeout(SIM_DELAY, (AnimationTimer animationTimer) -> {
onOfferFeePaymentConfirmed();
progressBar.setProgress(2.0 / 3.0);
return null;
});
/*switch (state)
{
case FOUND_PEER_ADDRESS:
statusTextField.setText("Peer found.");
break;
case SEND_TAKE_OFFER_REQUEST_ARRIVED:
statusTextField.setText("Take offer request successfully sent to peer.");
break;
case SEND_TAKE_OFFER_REQUEST_ACCEPTED:
statusTextField.setText("Take offer request accepted by peer.");
break;
case SEND_TAKE_OFFER_REQUEST_REJECTED:
statusTextField.setText("Take offer request rejected by peer.");
break;
case INSUFFICIENT_MONEY_FOR_OFFER_FEE:
Popups.openErrorPopup("Not enough money available", "There is not enough money available. Please pay in first to your wallet.");
break;
case OFFER_FEE_PAYED:
statusTextField.setText("Offer fee payed. Send offerer payment transaction ID for confirmation.");
break;
} */
}
@Override
public void onFailure(Throwable t)
public void onFailure(String failureMessage)
{
log.warn("sendResult onFailure:" + t.toString());
Popups.openErrorPopup("Fee payment failed", "Fee payment failed. " + t.toString());
log.warn(failureMessage);
}
};
try
{
trading.payOfferFee(trade, callback);
} catch (InsufficientMoneyException e)
{
Popups.openErrorPopup("Not enough money available", "There is not enough money available. Please pay in first to your wallet.");
}
}
@Override
public void onDepositTxPublished(String depositTxID)
{
buildDepositPublishedScreen(depositTxID);
}
private void onOfferFeePaymentConfirmed()
{
trading.requestOffererDetailData();
statusTextField.setText("Request bank account details from offerer.");
Utils.setTimeout(SIM_DELAY, (AnimationTimer animationTimer) -> {
onUserDetailsReceived();
progressBar.setProgress(1.0);
return null;
@Override
public void onBankTransferInited()
{
buildBankTransferInitedScreen();
}
});
takerPaymentProtocol.takeOffer();
}
private void onUserDetailsReceived()
private void buildDepositPublishedScreen(String depositTxID)
{
if (!blockChainFacade.verifyEmbeddedData(offer.getAccountID()))
{
Popups.openErrorPopup("Offerers bank account is blacklisted", "Offerers bank account is blacklisted.");
return;
}
trading.signContract(contract);
trading.payToDepositTx(trade);
buildWaitBankTransfer();
}
private void buildWaitBankTransfer()
{
processStepBar.next();
gridPane.getChildren().clear();
row = -1;
FormBuilder.addHeaderLabel(gridPane, "Bank transfer", ++row, 0);
infoLabel = FormBuilder.addLabel(gridPane, "Status:", "Wait for Bank transfer.", ++row);
Utils.setTimeout(SIM_DELAY, (AnimationTimer animationTimer) -> {
onBankTransferInited();
return null;
});
headerLabel = FormBuilder.addHeaderLabel(gridPane, "Deposit transaction published", ++row, 0);
infoLabel = FormBuilder.addLabel(gridPane, "Status:", "Deposit transaction published by offerer.\nAs soon as the offerer starts the \nBank transfer, you will get informed.", ++row);
FormBuilder.addTextField(gridPane, "Transaction ID:", depositTxID, ++row, false, true);
}
private void onBankTransferInited()
private void buildBankTransferInitedScreen()
{
row = 1;
infoLabel.setText("Bank transfer has been inited.");
Label label = FormBuilder.addLabel(gridPane, "", "Check your bank account and continue when you have received the money.", ++row);
GridPane.setColumnSpan(label, 2);
processStepBar.next();
headerLabel.setText("Bank transfer inited");
infoLabel.setText("Check your bank account and continue \nwhen you have received the money.");
log.info("#### grid " + gridPane.getChildren().size());
gridPane.add(nextButton, 1, ++row);
nextButton.setText("I have received the bank transfer");
nextButton.setText("I have received the money at my bank");
nextButton.setOnAction(e -> releaseBTC());
}
@ -405,16 +401,20 @@ public class TradeController implements Initializable, ChildController, WalletFa
return offer.getDirection() == Direction.BUY;
}
private void checkIfOffererIsOnline(Node isOnlineChecker, TextField isOnlineTextField)
private void setIsOnlineStatus(boolean isOnline)
{
// mock
Utils.setTimeout(3000, (AnimationTimer animationTimer) -> {
offererIsOnline = Math.random() > 0.3 ? true : false;
isOnlineTextField.setText(offererIsOnline ? "Online" : "Offline");
gridPane.getChildren().remove(isOnlineChecker);
return null;
});
if (checkOnlineStatusTimer != null)
{
checkOnlineStatusTimer.stop();
checkOnlineStatusTimer = null;
}
offererIsOnline = isOnline;
isOnlineTextField.setText(offererIsOnline ? "Online" : "Offline");
gridPane.getChildren().remove(isOnlineCheckerHolder);
isOnlineTextField.setId(isOnline ? "online-label" : "offline-label");
isOnlineTextField.layout();
}
private void applyVolume()
@ -448,7 +448,10 @@ public class TradeController implements Initializable, ChildController, WalletFa
private BigInteger getCollateralInSatoshis()
{
return BtcFormatter.doubleValueToSatoshis(Converter.stringToDouble(amountTextField.getText()) * offer.getCollateral());
double amount = Converter.stringToDouble(amountTextField.getText());
double resultDouble = amount * (double) offer.getCollateral() / 100.0;
BigInteger result = BtcFormatter.doubleValueToSatoshis(resultDouble);
return result;
}
private BigInteger getAmountInSatoshis()

View file

@ -5,13 +5,13 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="rootContainer" fx:controller="io.bitsquare.gui.market.trade.TradeController"
<AnchorPane fx:id="rootContainer" fx:controller="io.bitsquare.gui.market.trade.TakerTradeController"
xmlns:fx="http://javafx.com/fxml">
<ScrollPane fitToWidth="true" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0"
AnchorPane.bottomAnchor="0">
<content>
<VBox fx:id="vBox" spacing="10">
<VBox spacing="10">
<padding>
<Insets left="10" right="10" top="10" bottom="10"/>
</padding>

View file

@ -1,11 +1,12 @@
package io.bitsquare.gui.msg;
import com.google.inject.Inject;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.BitSquare;
import io.bitsquare.gui.ChildController;
import io.bitsquare.gui.NavigationController;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.MessageListener;
import io.bitsquare.msg.listeners.OrderBookListener;
import io.bitsquare.msg.listeners.PingPeerListener;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@ -26,7 +27,7 @@ import java.util.Arrays;
import java.util.Map;
import java.util.ResourceBundle;
public class MsgController implements Initializable, ChildController, MessageListener
public class MsgController implements Initializable, ChildController, OrderBookListener, PingPeerListener
{
private static final Logger log = LoggerFactory.getLogger(MsgController.class);
@ -69,8 +70,8 @@ public class MsgController implements Initializable, ChildController, MessageLis
@Override
public void initialize(URL url, ResourceBundle rb)
{
myID = WalletFacade.WALLET_PREFIX;
otherID = WalletFacade.WALLET_PREFIX.equals("taker") ? "offerer" : "taker";
myID = BitSquare.ID;
otherID = BitSquare.ID.equals("taker") ? "offerer" : "taker";
messageFacade.addMessageListener(this);
@ -94,14 +95,14 @@ public class MsgController implements Initializable, ChildController, MessageLis
// Interface implementation: MessageListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
/* @Override
public void onMessage(Object message)
{
sendButton.setDisable(!messageFacade.isOtherPeerDefined());
if (message instanceof String)
chatTextArea.appendText("\n" + otherID + ": " + message);
}
} */
@Override
public void onPing()
@ -110,12 +111,12 @@ public class MsgController implements Initializable, ChildController, MessageLis
}
@Override
public void onOfferPublished(boolean success)
public void onOfferAdded(Data offerData, boolean success)
{
if (success)
getOffers();
else
log.warn("onOfferPublished returned false");
log.warn("onOfferAdded returned false");
}
@Override
@ -145,7 +146,7 @@ public class MsgController implements Initializable, ChildController, MessageLis
@Override
public void onOfferRemoved(boolean success)
public void onOfferRemoved(Data offerData, boolean success)
{
if (success)
getOffers();
@ -153,7 +154,7 @@ public class MsgController implements Initializable, ChildController, MessageLis
log.warn("onOfferRemoved failed");
}
@Override
/* @Override
public void onResponseFromSend(Object response)
{
String msg = (response instanceof String) ? (String) response : null;
@ -168,14 +169,14 @@ public class MsgController implements Initializable, ChildController, MessageLis
public void onSendFailed()
{
offerTable.getSelectionModel().clearSelection();
}
} */
@Override
public void onPeerFound()
public void onPingPeerResult(boolean success)
{
sendButton.setDisable(!messageFacade.isOtherPeerDefined());
/* sendButton.setDisable(!messageFacade.isOtherPeerDefined());
if (pingPending)
sendChatMsg(MessageFacade.PING);
sendChatMsg(MessageFacade.PING); */
}
@ -201,14 +202,14 @@ public class MsgController implements Initializable, ChildController, MessageLis
@FXML
public void publishOffer(ActionEvent actionEvent)
{
OfferListItem offerListItem = new OfferListItem(offerDataTextField.getText(), messageFacade.getPubKeyAsHex(), currencyTextField.getText());
/* OfferListItem offerListItem = new OfferListItem(offerDataTextField.getText(), messageFacade.getPubKeyAsHex(), currencyTextField.getText());
try
{
messageFacade.publishOffer(currencyTextField.getText(), offerListItem);
messageFacade.addOffer(currencyTextField.getText(), offerListItem);
} catch (IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
} */
}
@FXML
@ -232,14 +233,14 @@ public class MsgController implements Initializable, ChildController, MessageLis
private void inviteForChat(OfferListItem item, int index)
{
selectedIndex = index;
messageFacade.findPeer(item.getPubKey());
// messageFacade.pingPeer(item.getPubKey());
pingPending = true;
}
private void sendChatMsg(String msg)
{
messageFacade.sendMessage(msg);
// messageFacade.sendMessage(msg);
chatTextArea.appendText("\n" + myID + ": " + msg);
chatInputField.setText("");
@ -252,13 +253,13 @@ public class MsgController implements Initializable, ChildController, MessageLis
private void removeOffer(OfferListItem offer)
{
try
/* try
{
messageFacade.removeOffer(currencyTextField.getText(), offer);
messageFacade.removeOffer(offer);
} catch (IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
} */
}

View file

@ -1,29 +1,111 @@
package io.bitsquare.gui.orders;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionConfidence;
import com.google.inject.Inject;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.bank.BankAccount;
import io.bitsquare.bank.BankAccountType;
import io.bitsquare.gui.ChildController;
import io.bitsquare.gui.NavigationController;
import io.bitsquare.gui.util.Icons;
import io.bitsquare.gui.util.Localisation;
import io.bitsquare.trade.Direction;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.Trading;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.HBox;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.*;
public class OrdersController implements Initializable, ChildController
{
private static final Logger log = LoggerFactory.getLogger(OrdersController.class);
private Trading trading;
private Trade currentTrade;
private Image buyIcon = Icons.getIconImage(Icons.BUY);
private Image sellIcon = Icons.getIconImage(Icons.SELL);
@FXML
private TableView openTradesTable;
@FXML
private TableColumn<String, TradesTableItem> directionColumn, countryColumn, bankAccountTypeColumn, priceColumn, amountColumn, volumeColumn, statusColumn, selectColumn;
@FXML
private ProgressIndicator progressIndicator;
@FXML
private Label confidenceLabel, txIDCopyIcon, holderNameCopyIcon, primaryBankAccountIDCopyIcon, secondaryBankAccountIDCopyIcon;
@FXML
private TextField txIDTextField, bankAccountTypeTextField, holderNameTextField, primaryBankAccountIDTextField, secondaryBankAccountIDTextField;
@FXML
private Button bankTransferInitedButton;
private NavigationController navigationController;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public OrdersController(Trading trading)
{
this.trading = trading;
}
@Override
public void initialize(URL url, ResourceBundle rb)
{
Map<String, Trade> trades = trading.getTrades();
List<Trade> tradeList = new ArrayList<>(trades.values());
ObservableList<TradesTableItem> tradeItems = FXCollections.observableArrayList();
for (Iterator<Trade> iterator = tradeList.iterator(); iterator.hasNext(); )
{
Trade trade = iterator.next();
tradeItems.add(new TradesTableItem(trade));
}
setCountryColumnCellFactory();
setBankAccountTypeColumnCellFactory();
setDirectionColumnCellFactory();
setSelectColumnCellFactory();
openTradesTable.setItems(tradeItems);
openTradesTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
openTradesTable.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue instanceof TradesTableItem)
{
TradesTableItem tradesTableItem = (TradesTableItem) newValue;
fillData(tradesTableItem.getTrade());
}
});
initCopyIcons();
}
@Override
public void setNavigationController(NavigationController navigationController)
{
this.navigationController = navigationController;
}
@Override
@ -32,5 +114,284 @@ public class OrdersController implements Initializable, ChildController
}
public void bankTransferInited(ActionEvent actionEvent)
{
trading.onBankTransferInited(currentTrade.getUid());
}
private void updateTx(Trade trade)
{
Transaction transaction = trade.getDepositTransaction();
String txID = "";
if (transaction != null)
{
txID = transaction.getHashAsString();
transaction.getConfidence().addEventListener(new TransactionConfidence.Listener()
{
@Override
public void onConfidenceChanged(Transaction tx, ChangeReason reason)
{
updateConfidence(tx);
}
});
}
else
{
updateConfidence(transaction);
}
txIDTextField.setText(txID);
}
private void updateConfidence(Transaction tx)
{
TransactionConfidence confidence = tx.getConfidence();
switch (confidence.getConfidenceType())
{
case UNKNOWN:
confidenceLabel.setText("");
progressIndicator.setProgress(0);
break;
case PENDING:
confidenceLabel.setText("Seen by " + confidence.numBroadcastPeers() + " peer(s)");
progressIndicator.setProgress(-1);
break;
case BUILDING:
bankTransferInitedButton.setOpacity(1);
confidenceLabel.setText("Confirmed in " + confidence.getDepthInBlocks() + " block(s)");
progressIndicator.setProgress(Math.min(1, (double) confidence.getDepthInBlocks() / 6.0));
break;
case DEAD:
confidenceLabel.setText("Transaction is invalid.");
break;
}
}
private void fillData(Trade trade)
{
currentTrade = trade;
Transaction transaction = trade.getDepositTransaction();
if (transaction == null)
{
trade.getDepositTxChangedProperty().addListener(new ChangeListener<Boolean>()
{
@Override
public void changed(ObservableValue<? extends Boolean> observableValue, Boolean aBoolean, Boolean aBoolean2)
{
updateTx(trade);
}
});
}
else
{
updateTx(trade);
}
// back details
BankAccount bankAccount = trade.getContract().getTakerBankAccount();
bankAccountTypeTextField.setText(bankAccount.getBankAccountType().getType().toString());
holderNameTextField.setText(bankAccount.getAccountHolderName());
primaryBankAccountIDTextField.setText(bankAccount.getAccountPrimaryID());
secondaryBankAccountIDTextField.setText(bankAccount.getAccountSecondaryID());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table columns
///////////////////////////////////////////////////////////////////////////////////////////
private void setCountryColumnCellFactory()
{
countryColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper(offer.getValue()));
countryColumn.setCellFactory(new Callback<TableColumn<String, TradesTableItem>, TableCell<String, TradesTableItem>>()
{
@Override
public TableCell<String, TradesTableItem> call(TableColumn<String, TradesTableItem> directionColumn)
{
return new TableCell<String, TradesTableItem>()
{
final HBox hBox = new HBox();
{
hBox.setSpacing(3);
hBox.setAlignment(Pos.CENTER);
setGraphic(hBox);
}
@Override
public void updateItem(final TradesTableItem orderBookListItem, boolean empty)
{
super.updateItem(orderBookListItem, empty);
hBox.getChildren().clear();
if (orderBookListItem != null)
{
Locale countryLocale = orderBookListItem.getTrade().getOffer().getBankAccountCountryLocale();
try
{
hBox.getChildren().add(Icons.getIconImageView("/images/countries/" + countryLocale.getCountry().toLowerCase() + ".png"));
} catch (Exception e)
{
log.warn("Country icon not found: " + "/images/countries/" + countryLocale.getCountry().toLowerCase() + ".png country name: " + countryLocale.getDisplayCountry());
}
Tooltip.install(this, new Tooltip(countryLocale.getDisplayCountry()));
}
}
};
}
});
}
private void setBankAccountTypeColumnCellFactory()
{
bankAccountTypeColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper(offer.getValue()));
bankAccountTypeColumn.setCellFactory(new Callback<TableColumn<String, TradesTableItem>, TableCell<String, TradesTableItem>>()
{
@Override
public TableCell<String, TradesTableItem> call(TableColumn<String, TradesTableItem> directionColumn)
{
return new TableCell<String, TradesTableItem>()
{
@Override
public void updateItem(final TradesTableItem orderBookListItem, boolean empty)
{
super.updateItem(orderBookListItem, empty);
if (orderBookListItem != null)
{
BankAccountType.BankAccountTypeEnum bankAccountTypeEnum = orderBookListItem.getTrade().getOffer().getBankAccountTypeEnum();
setText(Localisation.get(bankAccountTypeEnum.toString()));
}
else
{
setText("");
}
}
};
}
});
}
private void setDirectionColumnCellFactory()
{
directionColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper(offer.getValue()));
directionColumn.setCellFactory(new Callback<TableColumn<String, TradesTableItem>, TableCell<String, TradesTableItem>>()
{
@Override
public TableCell<String, TradesTableItem> call(TableColumn<String, TradesTableItem> directionColumn)
{
return new TableCell<String, TradesTableItem>()
{
final ImageView iconView = new ImageView();
final Button button = new Button();
{
button.setGraphic(iconView);
button.setMinWidth(70);
}
@Override
public void updateItem(final TradesTableItem orderBookListItem, boolean empty)
{
super.updateItem(orderBookListItem, empty);
if (orderBookListItem != null)
{
String title;
Image icon;
Offer offer = orderBookListItem.getTrade().getOffer();
if (offer.getDirection() == Direction.SELL)
{
icon = buyIcon;
title = io.bitsquare.gui.util.Formatter.formatDirection(Direction.BUY, true);
}
else
{
icon = sellIcon;
title = io.bitsquare.gui.util.Formatter.formatDirection(Direction.SELL, true);
}
button.setDisable(true);
iconView.setImage(icon);
button.setText(title);
setGraphic(button);
}
else
{
setGraphic(null);
}
}
};
}
});
}
private void setSelectColumnCellFactory()
{
selectColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper(offer.getValue()));
selectColumn.setCellFactory(new Callback<TableColumn<String, TradesTableItem>, TableCell<String, TradesTableItem>>()
{
@Override
public TableCell<String, TradesTableItem> call(TableColumn<String, TradesTableItem> directionColumn)
{
return new TableCell<String, TradesTableItem>()
{
final Button button = new Button("Select");
@Override
public void updateItem(final TradesTableItem orderBookListItem, boolean empty)
{
super.updateItem(orderBookListItem, empty);
if (orderBookListItem != null)
{
setGraphic(button);
}
else
{
setGraphic(null);
}
}
};
}
});
}
private void initCopyIcons()
{
AwesomeDude.setIcon(txIDCopyIcon, AwesomeIcon.COPY);
txIDCopyIcon.setOnMouseClicked(e -> {
Clipboard clipboard = Clipboard.getSystemClipboard();
ClipboardContent content = new ClipboardContent();
content.putString(txIDTextField.getText());
clipboard.setContent(content);
});
AwesomeDude.setIcon(holderNameCopyIcon, AwesomeIcon.COPY);
holderNameCopyIcon.setOnMouseClicked(e -> {
Clipboard clipboard = Clipboard.getSystemClipboard();
ClipboardContent content = new ClipboardContent();
content.putString(holderNameCopyIcon.getText());
clipboard.setContent(content);
});
AwesomeDude.setIcon(primaryBankAccountIDCopyIcon, AwesomeIcon.COPY);
primaryBankAccountIDCopyIcon.setOnMouseClicked(e -> {
Clipboard clipboard = Clipboard.getSystemClipboard();
ClipboardContent content = new ClipboardContent();
content.putString(primaryBankAccountIDCopyIcon.getText());
clipboard.setContent(content);
});
AwesomeDude.setIcon(secondaryBankAccountIDCopyIcon, AwesomeIcon.COPY);
secondaryBankAccountIDCopyIcon.setOnMouseClicked(e -> {
Clipboard clipboard = Clipboard.getSystemClipboard();
ClipboardContent content = new ClipboardContent();
content.putString(secondaryBankAccountIDCopyIcon.getText());
clipboard.setContent(content);
});
}
}

View file

@ -1,6 +1,148 @@
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Pane?>
<Pane fx:controller="io.bitsquare.gui.orders.OrdersController"
xmlns:fx="http://javafx.com/fxml">
<Label text="Orders"/>
</Pane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.layout.*?>
<VBox prefHeight="540" prefWidth="800" spacing="10" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.bitsquare.gui.orders.OrdersController">
<children>
<Label id="headline-label" text="Open trades">
<VBox.margin>
<Insets left="10.0" top="10.0"/>
</VBox.margin>
</Label>
<TableView fx:id="openTradesTable" id="orderbook-table" prefHeight="150.0">
<columns>
<TableColumn fx:id="amountColumn" minWidth="120" text="Amount (Min.)">
<cellValueFactory>
<PropertyValueFactory property="amount"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="priceColumn" minWidth="70" text="Price">
<cellValueFactory>
<PropertyValueFactory property="price"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="volumeColumn" minWidth="130" text="Volume (Min.)">
<cellValueFactory>
<PropertyValueFactory property="volume"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="countryColumn" minWidth="60" text="Country"/>
<TableColumn fx:id="bankAccountTypeColumn" minWidth="140" text="Bank transfer type"/>
<TableColumn fx:id="directionColumn" minWidth="80" sortable="false" text="Offer type"/>
<TableColumn fx:id="statusColumn" minWidth="80" text="Status">
<cellValueFactory>
<PropertyValueFactory property="status"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="selectColumn" minWidth="60" sortable="false" text=""/>
</columns>
<VBox.margin>
<Insets left="10.0" right="10.0"/>
</VBox.margin>
</TableView>
<Label text="After you received 1 blockchain confirmation you are safe to start the bank transfer.">
<VBox.margin>
<Insets bottom="10.0" left="10.0" top="10.0"/>
</VBox.margin>
</Label>
<GridPane hgap="5.0" vgap="5.0">
<children>
<!-- row 0 -->
<Label id="headline-label" text="Deposit transaction" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="LEFT"
GridPane.rowIndex="0"/>
<!-- row 1 -->
<Label text="Transaction ID:" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<TextField fx:id="txIDTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<Label id="copy-icon" fx:id="txIDCopyIcon" minWidth="10" GridPane.columnIndex="2" GridPane.rowIndex="1">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="-1.0"/>
</padding>
<tooltip>
<Tooltip text="Copy address to clipboard"/>
</tooltip>
</Label>
<ProgressIndicator fx:id="progressIndicator" prefHeight="20" prefWidth="20" GridPane.columnIndex="3" GridPane.rowIndex="1"/>
<Label fx:id="confidenceLabel" text="Checking confirmations..." GridPane.columnIndex="4" GridPane.rowIndex="1"/>
<!-- row 2 -->
<Label id="headline-label" text="Bank details" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="LEFT"
GridPane.rowIndex="2"/>
<!-- row 3 -->
<Label text="Bank account type:" GridPane.columnIndex="0" GridPane.rowIndex="3"/>
<TextField fx:id="bankAccountTypeTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<!-- row 4 -->
<Label text="Holder name:" GridPane.columnIndex="0" GridPane.rowIndex="4"/>
<TextField fx:id="holderNameTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<Label id="copy-icon" fx:id="holderNameCopyIcon" minWidth="10" GridPane.columnIndex="2" GridPane.rowIndex="4">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="-1.0"/>
</padding>
<tooltip>
<Tooltip text="Copy address to clipboard"/>
</tooltip>
</Label>
<!-- row 5 -->
<Label text="Primary bank account ID:" GridPane.columnIndex="0" GridPane.rowIndex="5"/>
<TextField fx:id="primaryBankAccountIDTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
<Label id="copy-icon" fx:id="primaryBankAccountIDCopyIcon" minWidth="10" GridPane.columnIndex="2" GridPane.rowIndex="5">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="-1.0"/>
</padding>
<tooltip>
<Tooltip text="Copy address to clipboard"/>
</tooltip>
</Label>
<!-- row 6 -->
<Label text="Secondary bank account ID:" GridPane.columnIndex="0" GridPane.rowIndex="6"/>
<TextField fx:id="secondaryBankAccountIDTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="6"/>
<Label id="copy-icon" fx:id="secondaryBankAccountIDCopyIcon" minWidth="10" GridPane.columnIndex="2" GridPane.rowIndex="6">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="-1.0"/>
</padding>
<tooltip>
<Tooltip text="Copy address to clipboard"/>
</tooltip>
</Label>
<!-- row 7 -->
<Button fx:id="bankTransferInitedButton" defaultButton="true" opacity="0.5" onAction="#bankTransferInited" text="Bank transfer inited"
GridPane.columnIndex="1" GridPane.rowIndex="7"/>
</children>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="400"/>
<ColumnConstraints fillWidth="false" halignment="LEFT" hgrow="NEVER"/>
<ColumnConstraints fillWidth="false" halignment="LEFT" hgrow="SOMETIMES"/>
<ColumnConstraints fillWidth="false" halignment="LEFT" hgrow="SOMETIMES"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints/>
</rowConstraints>
<VBox.margin>
<Insets left="10.0" right="10.0"/>
</VBox.margin>
</GridPane>
</children>
</VBox>

View file

@ -0,0 +1,26 @@
package io.bitsquare.gui.orders;
import io.bitsquare.gui.market.orderbook.OrderBookListItem;
import io.bitsquare.trade.Trade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TradesTableItem extends OrderBookListItem
{
private static final Logger log = LoggerFactory.getLogger(TradesTableItem.class);
private Trade trade;
public TradesTableItem(Trade trade)
{
super(trade.getOffer());
this.trade = trade;
}
public Trade getTrade()
{
return trade;
}
}

View file

@ -1,6 +1,14 @@
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Pane?>
<Pane fx:controller="io.bitsquare.gui.settings.SettingsController"
xmlns:fx="http://javafx.com/fxml">
<Label text="Settings"/>
</Pane>
<?import javafx.scene.layout.*?>
<AnchorPane fx:controller="io.bitsquare.gui.settings.SettingsController" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
xmlns:fx="http://javafx.com/fxml">
<children>
<VBox spacing="20" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label id="headline-label" text="Settings"/>
</children>
</VBox>
</children>
</AnchorPane>

View file

@ -1,35 +1,32 @@
package io.bitsquare.gui.setup;
import com.google.bitcoin.core.InsufficientMoneyException;
import com.google.bitcoin.core.*;
import com.google.bitcoin.script.Script;
import com.google.inject.Inject;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.bank.BankAccount;
import io.bitsquare.bank.BankAccountType;
import io.bitsquare.btc.BtcFormatter;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.gui.ChildController;
import io.bitsquare.gui.NavigationController;
import io.bitsquare.gui.components.ConfirmationComponent;
import io.bitsquare.gui.components.NetworkSyncPane;
import io.bitsquare.gui.components.processbar.ProcessStepBar;
import io.bitsquare.gui.components.processbar.ProcessStepItem;
import io.bitsquare.gui.util.FormBuilder;
import io.bitsquare.gui.util.Localisation;
import io.bitsquare.gui.util.Popups;
import io.bitsquare.gui.util.Verification;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.User;
import io.bitsquare.util.Utils;
import io.bitsquare.util.DSAKeyUtil;
import io.bitsquare.util.Utilities;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -38,34 +35,36 @@ import java.math.BigInteger;
import java.net.URL;
import java.util.*;
public class SetupController implements Initializable, ChildController, WalletFacade.WalletListener
public class SetupController implements Initializable, ChildController
{
private static final Logger log = LoggerFactory.getLogger(SetupController.class);
private final User user;
private final WalletFacade walletFacade;
private NavigationController navigationController;
private MessageFacade messageFacade;
private final Storage storage;
private final List<ProcessStepItem> processStepItems = new ArrayList<>();
private NavigationController navigationController;
private TextField balanceLabel, accountTitle, accountHolderName, accountPrimaryID, accountSecondaryID;
private ComboBox<Locale> countryComboBox;
private ComboBox<BankAccountType> bankTransferTypeComboBox;
private ComboBox<Currency> currencyComboBox;
private Button addBankAccountButton;
@FXML
private AnchorPane rootContainer;
@FXML
private Label infoLabel;
private TitledPane payRegistrationFeePane, addBankAccountPane, settingsPane;
@FXML
private ProcessStepBar<String> processStepBar;
private Label payRegFeeInfoLabel, addBankAccountInfoLabel, copyIcon, confirmationLabel;
@FXML
private GridPane gridPane;
private TextField registrationAddressTextField, balanceTextField, accountTitle, accountHolderName, accountPrimaryID, accountSecondaryID;
@FXML
private Button nextButton, skipButton;
private Button createAccountButton, addBankAccountButton;
@FXML
private VBox vBox;
private Accordion accordion;
@FXML
private ComboBox<Locale> countryComboBox;
@FXML
private ComboBox<BankAccountType> bankAccountTypesComboBox;
@FXML
private ComboBox<Currency> currencyComboBox;
@FXML
private ProgressIndicator progressIndicator;
///////////////////////////////////////////////////////////////////////////////////////////
@ -100,17 +99,13 @@ public class SetupController implements Initializable, ChildController, WalletFa
@Override
public void initialize(URL url, ResourceBundle rb)
{
processStepItems.add(new ProcessStepItem("Fund registration fee"));
processStepItems.add(new ProcessStepItem("Add Bank account"));
processStepItems.add(new ProcessStepItem("Complete"));
processStepBar.setProcessStepItems(processStepItems);
setupRegistrationScreen();
setupBankAccountScreen();
setupSettingsScreen();
walletFacade.addRegistrationWalletListener(this);
buildStep0();
accordion.setExpandedPane(payRegistrationFeePane);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: ChildController
///////////////////////////////////////////////////////////////////////////////////////////
@ -128,84 +123,146 @@ public class SetupController implements Initializable, ChildController, WalletFa
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: WalletFacade.WalletListener
// Button handlers
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onConfidenceChanged(int numBroadcastPeers, int depthInBlocks)
public void onPaymentDone(ActionEvent actionEvent)
{
updateCreateAccountButton();
log.info("onConfidenceChanged " + numBroadcastPeers + " / " + depthInBlocks);
accordion.setExpandedPane(addBankAccountPane);
}
@Override
public void onCoinsReceived(BigInteger newBalance)
public void onSkipPayment(ActionEvent actionEvent)
{
updateCreateAccountButton();
balanceLabel.setText(BtcFormatter.formatSatoshis(walletFacade.getAccountRegistrationBalance(), true));
log.info("onCoinsReceived " + newBalance);
accordion.setExpandedPane(addBankAccountPane);
}
public void onAddBankAccount(ActionEvent actionEvent)
{
addBankAccount();
storage.write(user.getClass().getName(), user);
if (verifyBankAccountData())
{
bankAccountTypesComboBox.getSelectionModel().clearSelection();
accountPrimaryID.setText("");
accountPrimaryID.setPromptText("");
accountSecondaryID.setText("");
accountSecondaryID.setPromptText("");
}
}
public void onCreateAccount(ActionEvent actionEvent)
{
addBankAccount();
if (user.getBankAccounts().size() > 0)
{
try
{
walletFacade.sendRegistrationTx(user.getStringifiedBankAccounts());
user.setAccountID(walletFacade.getAccountRegistrationAddress().toString());
user.setMessagePubKeyAsHex(DSAKeyUtil.getHexStringFromPublicKey(messageFacade.getPubKey()));
storage.write(user.getClass().getName(), user);
accordion.setExpandedPane(settingsPane);
} catch (InsufficientMoneyException e1)
{
Popups.openErrorPopup("Not enough money available", "There is not enough money available. Please pay in first to your wallet.");
}
}
}
public void onSkipBankAccountSetup(ActionEvent actionEvent)
{
accordion.setExpandedPane(settingsPane);
}
public void onClose(ActionEvent actionEvent)
{
navigationController.navigateToView(NavigationController.MARKET, "");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
// Screens setup
///////////////////////////////////////////////////////////////////////////////////////////
private void buildStep0()
private void setupRegistrationScreen()
{
infoLabel.setText("You need to pay 0.01 BTC to the registration address.\n\n" +
payRegFeeInfoLabel.setText("You need to pay 0.01 BTC to the registration address.\n\n" +
"That payment will be used to create a unique account connected with your bank account number.\n" +
"The privacy of your bank account number will be protected and only revealed to your trading partners.\n" +
"The payment will be spent to miners and is needed to store data into the blockchain.\n" +
"Your trading account will be the source for your reputation in the trading platform.\n\n" +
"You need at least 1 confirmation for doing the registration payment.");
int row = -1;
String registrationAddress = walletFacade.getAccountRegistrationAddress().toString();
registrationAddressTextField.setText(registrationAddress);
TextField addressLabel = FormBuilder.addTextField(gridPane, "Registration address:", walletFacade.getAccountRegistrationAddress().toString(), ++row, false, true);
Label copyIcon = new Label("");
gridPane.add(copyIcon, 2, row);
copyIcon.setId("copy-icon");
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
Tooltip.install(copyIcon, new Tooltip("Copy address to clipboard"));
balanceLabel = FormBuilder.addTextField(gridPane, "Balance:", BtcFormatter.formatSatoshis(walletFacade.getAccountRegistrationBalance(), true), ++row);
new ConfirmationComponent(walletFacade, gridPane, ++row);
nextButton.setText("Payment done");
updateCreateAccountButton();
skipButton.setText("Register later");
// handlers
copyIcon.setOnMouseClicked(e -> {
Clipboard clipboard = Clipboard.getSystemClipboard();
ClipboardContent content = new ClipboardContent();
content.putString(addressLabel.getText());
content.putString(registrationAddress);
clipboard.setContent(content);
});
nextButton.setOnAction(e -> {
processStepBar.next();
buildStep1();
});
updateBalance(walletFacade.getAccountRegistrationBalance());
skipButton.setOnAction(e -> close());
walletFacade.getAccountRegistrationWallet().addEventListener(new WalletEventListener()
{
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance)
{
updateBalance(newBalance);
}
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx)
{
updateConfidence(tx);
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance)
{
}
@Override
public void onReorganize(Wallet wallet)
{
}
@Override
public void onWalletChanged(Wallet wallet)
{
}
@Override
public void onKeysAdded(Wallet wallet, List<ECKey> keys)
{
}
@Override
public void onScriptsAdded(Wallet wallet, List<Script> scripts)
{
}
});
}
private void buildStep1()
private void setupBankAccountScreen()
{
infoLabel.setText("Add at least one Bank account to your trading account.\n" +
addBankAccountInfoLabel.setText("Add at least one Bank account to your trading account.\n" +
"That data will be stored in the blockchain in a way that your privacy is protected.\n" +
"Only your trading partners will be able to read those data, so your privacy will be protected.");
gridPane.getChildren().clear();
int row = -1;
bankTransferTypeComboBox = FormBuilder.addBankAccountComboBox(gridPane, "Bank account type:", Utils.getAllBankAccountTypes(), ++row);
bankTransferTypeComboBox.setConverter(new StringConverter<BankAccountType>()
bankAccountTypesComboBox.setItems(FXCollections.observableArrayList(Utilities.getAllBankAccountTypes()));
currencyComboBox.setItems(FXCollections.observableArrayList(Utilities.getAllCurrencies()));
countryComboBox.setItems(FXCollections.observableArrayList(Utilities.getAllLocales()));
bankAccountTypesComboBox.setConverter(new StringConverter<BankAccountType>()
{
@Override
public String toString(BankAccountType bankAccountType)
@ -220,14 +277,6 @@ public class SetupController implements Initializable, ChildController, WalletFa
}
});
bankTransferTypeComboBox.setPromptText("Select bank account type");
accountTitle = FormBuilder.addInputField(gridPane, "Bank account title:", "", ++row);
accountHolderName = FormBuilder.addInputField(gridPane, "Bank account holder name:", "", ++row);
accountPrimaryID = FormBuilder.addInputField(gridPane, "Bank account primary ID", "", ++row);
accountSecondaryID = FormBuilder.addInputField(gridPane, "Bank account secondary ID:", "", ++row);
currencyComboBox = FormBuilder.addCurrencyComboBox(gridPane, "Currency used for bank account:", Utils.getAllCurrencies(), ++row);
currencyComboBox.setPromptText("Select currency");
currencyComboBox.setConverter(new StringConverter<Currency>()
{
@Override
@ -243,8 +292,6 @@ public class SetupController implements Initializable, ChildController, WalletFa
}
});
countryComboBox = FormBuilder.addLocalesComboBox(gridPane, "Country of bank account:", Utils.getAllLocales(), ++row);
countryComboBox.setPromptText("Select country");
countryComboBox.setConverter(new StringConverter<Locale>()
{
@Override
@ -260,21 +307,7 @@ public class SetupController implements Initializable, ChildController, WalletFa
}
});
addBankAccountButton = new Button("Add other Bank account");
gridPane.add(addBankAccountButton, 1, ++row);
nextButton.setText("Create account");
checkCreateAccountButtonState();
skipButton.setText("Register later");
// handlers
accountTitle.textProperty().addListener((ov, oldValue, newValue) -> checkCreateAccountButtonState());
accountHolderName.textProperty().addListener((ov, oldValue, newValue) -> checkCreateAccountButtonState());
accountPrimaryID.textProperty().addListener((ov, oldValue, newValue) -> checkCreateAccountButtonState());
accountSecondaryID.textProperty().addListener((ov, oldValue, newValue) -> checkCreateAccountButtonState());
bankTransferTypeComboBox.valueProperty().addListener((ov, oldValue, newValue) -> {
bankAccountTypesComboBox.valueProperty().addListener((ov, oldValue, newValue) -> {
if (newValue != null && newValue instanceof BankAccountType)
{
accountPrimaryID.setText("");
@ -289,132 +322,94 @@ public class SetupController implements Initializable, ChildController, WalletFa
currencyComboBox.valueProperty().addListener((ov, oldValue, newValue) -> checkCreateAccountButtonState());
countryComboBox.valueProperty().addListener((ov, oldValue, newValue) -> checkCreateAccountButtonState());
addBankAccountButton.setOnAction(e -> {
addBankAccount();
storage.write(user.getClass().getName(), user);
if (verifyBankAccountData())
{
bankTransferTypeComboBox.getSelectionModel().clearSelection();
accountPrimaryID.setText("");
accountPrimaryID.setPromptText("");
accountSecondaryID.setText("");
accountSecondaryID.setPromptText("");
}
});
checkCreateAccountButtonState();
// handlers
accountTitle.textProperty().addListener((ov, oldValue, newValue) -> checkCreateAccountButtonState());
accountHolderName.textProperty().addListener((ov, oldValue, newValue) -> checkCreateAccountButtonState());
accountPrimaryID.textProperty().addListener((ov, oldValue, newValue) -> checkCreateAccountButtonState());
accountSecondaryID.textProperty().addListener((ov, oldValue, newValue) -> checkCreateAccountButtonState());
nextButton.setOnAction(e -> {
addBankAccount();
if (user.getBankAccounts().size() > 0)
{
try
{
walletFacade.sendRegistrationTx(user.getStringifiedBankAccounts());
user.setAccountID(walletFacade.getAccountRegistrationAddress().toString());
user.setMessageID(walletFacade.getAccountRegistrationPubKey());
//user.setMessageID(messageFacade.getPubKey());
storage.write(user.getClass().getName(), user);
processStepBar.next();
buildStep2();
} catch (InsufficientMoneyException e1)
{
Popups.openErrorPopup("Not enough money available", "There is not enough money available. Please pay in first to your wallet.");
}
}
});
skipButton.setOnAction(e -> close());
//todo
bankAccountTypesComboBox.getSelectionModel().select(0);
currencyComboBox.getSelectionModel().select(0);
countryComboBox.getSelectionModel().select(0);
accountHolderName.setText("dummy accountHolderName");
accountTitle.setText("dummy accountTitle");
accountPrimaryID.setText("dummy accountPrimaryID");
accountSecondaryID.setText("dummy accountSecondaryID");
}
private void buildStep2()
private void setupSettingsScreen()
{
vBox.getChildren().remove(infoLabel);
vBox.getChildren().remove(nextButton);
vBox.getChildren().remove(skipButton);
//TODO
}
gridPane.getChildren().clear();
int row = -1;
FormBuilder.addHeaderLabel(gridPane, "Registration complete", ++row);
FormBuilder.addTextField(gridPane, "Registration address:", walletFacade.getAccountRegistrationAddress().toString(), ++row);
FormBuilder.addTextField(gridPane, "Balance:", BtcFormatter.formatSatoshis(walletFacade.getAccountRegistrationBalance(), true), ++row);
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
Button closeButton = FormBuilder.addButton(gridPane, "Close", ++row);
closeButton.setDefaultButton(true);
closeButton.setOnAction(e -> close());
FormBuilder.addVSpacer(gridPane, ++row);
FormBuilder.addHeaderLabel(gridPane, "Summary", ++row);
Label info = new Label("You have saved following bank accounts with your trading account to the blockchain:");
gridPane.add(info, 0, ++row);
GridPane.setColumnSpan(info, 3);
FormBuilder.addVSpacer(gridPane, ++row);
List<BankAccount> bankAccounts = user.getBankAccounts();
Iterator<BankAccount> iterator = bankAccounts.iterator();
int index = 0;
while (iterator.hasNext())
private void updateBalance(BigInteger balance)
{
if (balance.compareTo(BigInteger.ZERO) == 0)
{
FormBuilder.addHeaderLabel(gridPane, "Bank account " + (index + 1), ++row);
BankAccount bankAccount = iterator.next();
// need to get updated row from subroutine
row = buildBankAccountDetails(bankAccount, ++row);
FormBuilder.addVSpacer(gridPane, ++row);
index++;
confirmationLabel.setText("");
progressIndicator.setOpacity(0);
progressIndicator.setProgress(0);
}
else
{
progressIndicator.setOpacity(1);
progressIndicator.setProgress(-1);
Set<Transaction> transactions = walletFacade.getAccountRegistrationWallet().getTransactions(false);
for (Iterator<Transaction> iterator = transactions.iterator(); iterator.hasNext(); )
{
Transaction transaction = iterator.next();
updateConfidence(transaction);
break;
}
}
balanceTextField.setText(Utils.bitcoinValueToFriendlyString(balance));
}
private void close()
private void updateConfidence(Transaction tx)
{
walletFacade.removeRegistrationWalletListener(this);
navigationController.navigateToView(NavigationController.HOME, "");
TransactionConfidence confidence = tx.getConfidence();
double progressIndicatorSize = 50;
switch (confidence.getConfidenceType())
{
case UNKNOWN:
confirmationLabel.setText("");
progressIndicator.setProgress(0);
break;
case PENDING:
confirmationLabel.setText("Seen by " + confidence.numBroadcastPeers() + " peer(s) / 0 confirmations");
progressIndicator.setProgress(-1.0);
progressIndicatorSize = 20;
break;
case BUILDING:
confirmationLabel.setText("Confirmed in " + confidence.getDepthInBlocks() + " block(s)");
progressIndicator.setProgress(Math.min(1, (double) confidence.getDepthInBlocks() / 6.0));
break;
case DEAD:
confirmationLabel.setText("Transaction is invalid.");
break;
}
progressIndicator.setMaxHeight(progressIndicatorSize);
progressIndicator.setPrefHeight(progressIndicatorSize);
progressIndicator.setMaxWidth(progressIndicatorSize);
progressIndicator.setPrefWidth(progressIndicatorSize);
}
// util
private int buildBankAccountDetails(BankAccount bankAccount, int row)
{
FormBuilder.addTextField(gridPane, "Bank account holder name:", bankAccount.getAccountHolderName(), ++row);
FormBuilder.addTextField(gridPane, "Bank account type", bankAccount.getBankAccountType().toString(), ++row);
FormBuilder.addTextField(gridPane, "Bank account primary ID", bankAccount.getAccountPrimaryID(), ++row);
FormBuilder.addTextField(gridPane, "Bank account secondary ID:", bankAccount.getAccountSecondaryID(), ++row);
return row;
}
// TODO need checks per bankTransferType
private boolean verifyBankAccountData()
{
boolean accountIDsByBankTransferTypeValid = Verification.verifyAccountIDsByBankTransferType(bankTransferTypeComboBox.getSelectionModel().getSelectedItem(),
accountPrimaryID.getText(),
accountSecondaryID.getText());
return bankTransferTypeComboBox.getSelectionModel().getSelectedItem() != null
&& countryComboBox.getSelectionModel().getSelectedItem() != null
&& currencyComboBox.getSelectionModel().getSelectedItem() != null
&& accountTitle.getText().length() > 0
&& accountHolderName.getText().length() > 0
&& accountPrimaryID.getText().length() > 0
&& accountSecondaryID.getText().length() > 0
&& accountIDsByBankTransferTypeValid;
}
private void updateCreateAccountButton()
{
boolean funded = walletFacade.getAccountRegistrationBalance().compareTo(BigInteger.ZERO) > 0;
nextButton.setDisable(!funded || walletFacade.getRegConfDepthInBlocks() == 0);
}
private void addBankAccount()
{
if (verifyBankAccountData())
{
BankAccount bankAccount = new BankAccount(
bankTransferTypeComboBox.getSelectionModel().getSelectedItem(),
bankAccountTypesComboBox.getSelectionModel().getSelectedItem(),
currencyComboBox.getSelectionModel().getSelectedItem(),
countryComboBox.getSelectionModel().getSelectedItem(),
accountTitle.getText(),
@ -427,8 +422,25 @@ public class SetupController implements Initializable, ChildController, WalletFa
private void checkCreateAccountButtonState()
{
nextButton.setDisable(!verifyBankAccountData());
createAccountButton.setDisable(!verifyBankAccountData());
addBankAccountButton.setDisable(!verifyBankAccountData());
}
private boolean verifyBankAccountData()
{
boolean accountIDsByBankTransferTypeValid = Verification.verifyAccountIDsByBankTransferType(bankAccountTypesComboBox.getSelectionModel().getSelectedItem(),
accountPrimaryID.getText(),
accountSecondaryID.getText());
return bankAccountTypesComboBox.getSelectionModel().getSelectedItem() != null
&& countryComboBox.getSelectionModel().getSelectedItem() != null
&& currencyComboBox.getSelectionModel().getSelectedItem() != null
&& accountTitle.getText().length() > 0
&& accountHolderName.getText().length() > 0
&& accountPrimaryID.getText().length() > 0
&& accountSecondaryID.getText().length() > 0
&& accountIDsByBankTransferTypeValid;
}
}

View file

@ -1,29 +1,160 @@
<?import io.bitsquare.gui.components.processbar.ProcessStepBar?>
<?import io.bitsquare.gui.components.VSpacer?>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="rootContainer" fx:controller="io.bitsquare.gui.setup.SetupController"
xmlns:fx="http://javafx.com/fxml">
<ScrollPane fitToWidth="true" AnchorPane.leftAnchor="10" AnchorPane.rightAnchor="10" AnchorPane.topAnchor="10"
AnchorPane.bottomAnchor="30">
<VBox fx:id="vBox" spacing="10">
<Label text="Setup trading account" id="headline-label"/>
<ProcessStepBar fx:id="processStepBar"/>
<VSpacer prefHeight="10"/>
<Label fx:id="infoLabel"/>
<GridPane fx:id="gridPane" vgap="5" hgap="5">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
</padding>
<columnConstraints>
<ColumnConstraints halignment="RIGHT"/>
<ColumnConstraints halignment="LEFT" prefWidth="320"/>
<ColumnConstraints halignment="LEFT"/>
</columnConstraints>
</GridPane>
<Button fx:id="nextButton" defaultButton="true"/>
<Button fx:id="skipButton"/>
</VBox>
</ScrollPane>
</AnchorPane>
<AnchorPane fx:id="rootContainer" fx:controller="io.bitsquare.gui.setup.SetupController" prefWidth="800" prefHeight="600" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label id="headline-label" text="Setup trading account" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="10.0"/>
<Accordion fx:id="accordion" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<panes>
<TitledPane fx:id="payRegistrationFeePane" text="Pay registration fee">
<content>
<AnchorPane>
<children>
<VBox spacing="20" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label fx:id="payRegFeeInfoLabel"/>
<GridPane hgap="5.0" vgap="5.0">
<children>
<Label text="Registration address:"/>
<TextField fx:id="registrationAddressTextField" editable="false" GridPane.columnIndex="1"/>
<Label fx:id="copyIcon" minWidth="10" GridPane.columnIndex="2">
<padding>
<Insets bottom="0.0" left="0.0" right="0.0" top="-1.0"/>
</padding>
<tooltip>
<Tooltip text="Copy address to clipboard"/>
</tooltip>
</Label>
<Label text="Balance:" GridPane.rowIndex="1"/>
<TextField fx:id="balanceTextField" editable="false" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<ProgressIndicator fx:id="progressIndicator" id="confidence-progress-indicator" GridPane.columnIndex="2" GridPane.halignment="LEFT"
GridPane.rowIndex="1" GridPane.rowSpan="2" GridPane.valignment="TOP">
<GridPane.margin>
<Insets top="2.0"/>
</GridPane.margin>
</ProgressIndicator>
<Label fx:id="confirmationLabel" text="Checking confirmations..." GridPane.columnIndex="3" GridPane.rowIndex="1"/>
<Button defaultButton="true" onAction="#onPaymentDone" text="Payment done" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<Button onAction="#onSkipPayment" text="Skip and pay later" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
</children>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="ALWAYS"/>
<ColumnConstraints hgrow="SOMETIMES" prefWidth="20.0"/>
<ColumnConstraints hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="SOMETIMES"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
</rowConstraints>
</GridPane>
</children>
</VBox>
</children>
</AnchorPane>
</content>
</TitledPane>
<TitledPane fx:id="addBankAccountPane" animated="true" text="Add Bank account">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<VBox spacing="20" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
AnchorPane.topAnchor="0.0">
<children>
<Label fx:id="addBankAccountInfoLabel"/>
<GridPane hgap="5.0" vgap="5.0">
<children>
<Label text="Bank account type:"/>
<ComboBox fx:id="bankAccountTypesComboBox" promptText="Select bank account type" GridPane.columnIndex="1"/>
<Label text="Bank account title:" GridPane.rowIndex="1"/>
<TextField fx:id="accountTitle" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<Label text="Bank account holder name" GridPane.rowIndex="2"/>
<TextField fx:id="accountHolderName" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<Label text="Bank account primary ID" GridPane.rowIndex="3"/>
<TextField fx:id="accountPrimaryID" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<Label text="Bank account secondary ID" GridPane.rowIndex="4"/>
<TextField fx:id="accountSecondaryID" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<Label text="Currency used for bank account:" GridPane.rowIndex="5"/>
<ComboBox fx:id="currencyComboBox" promptText="Select currency" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
<Label text="Country of bank account" GridPane.rowIndex="6"/>
<ComboBox fx:id="countryComboBox" promptText="Select country" GridPane.columnIndex="1" GridPane.rowIndex="6"/>
<Button fx:id="addBankAccountButton" onAction="#onAddBankAccount" text="Add another bank account" GridPane.columnIndex="1" GridPane.rowIndex="7"/>
<Button fx:id="createAccountButton" defaultButton="true" onAction="#onCreateAccount" text="Create account" GridPane.columnIndex="1"
GridPane.rowIndex="8"/>
<Button onAction="#onSkipBankAccountSetup" text="Skip and register later" GridPane.columnIndex="1" GridPane.rowIndex="9"/>
</children>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="ALWAYS"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
</rowConstraints>
</GridPane>
</children>
</VBox>
</children>
</AnchorPane>
</content>
</TitledPane>
<TitledPane fx:id="settingsPane" animated="true" text="Set up preferences">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<VBox spacing="20" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label text="Set up preferences... TODO "/>
<Button defaultButton="true" onAction="#onClose" text="Done" GridPane.columnIndex="1" GridPane.rowIndex="8"/>
</children>
</VBox>
</children>
</AnchorPane>
</content>
</TitledPane>
</panes>
</Accordion>
</children>
</AnchorPane>

View file

@ -13,9 +13,12 @@ public class Converter
public static double stringToDouble(String input)
{
if (input == null || input.equals(""))
return 0;
try
{
DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance(Locale.getDefault());
Object d = decimalFormat.getDecimalFormatSymbols();
return decimalFormat.parse(input).doubleValue();
} catch (ParseException e)
{

View file

@ -16,11 +16,11 @@ public class Icons
public static final String MSG = "/images/nav/msg.png";
public static final String SETTINGS = "/images/nav/settings.png";
public static final String MSG_ALERT = "/images/nav/alertRound.png";
public static final String BUY = "/images/buy.png";
public static final String SELL = "/images/sell.png";
public static final String REMOVE = "/images/remove_minus_9.png";
public static final String ADD = "/images/list.png";
public static final String REFRESH = "/images/refresh.png";
public static final String REMOVE = "/images/removeOffer.png";
public static final String PROGRESS_0_ICON_FILE = "/images/tx/circleProgress0.png";
public static final String PROGRESS_1_ICON_FILE = "/images/tx/circleProgress1.png";

View file

@ -1,56 +0,0 @@
package io.bitsquare.msg;
import io.bitsquare.trade.Contract;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.Trade;
import io.bitsquare.util.Utils;
public class Message
{
public final static String BROADCAST_NEW_OFFER = "BROADCAST_NEW_OFFER";
public final static String REQUEST_TAKE_OFFER = "REQUEST_TAKE_OFFER";
public final static String OFFER_ACCEPTED = "OFFER_ACCEPTED";
public final static String REQUEST_OFFER_FEE_PAYMENT_CONFIRM = "REQUEST_OFFER_FEE_PAYMENT_CONFIRM";
public final static String SEND_SIGNED_CONTRACT = "SEND_SIGNED_CONTRACT";
private String type;
private Object payload;
public Message(String type, String msg)
{
this.type = type;
this.payload = msg;
}
public Message(String type, Trade trade)
{
this.type = type;
this.payload = trade;
}
public Message(String type, Offer offer)
{
this.type = type;
this.payload = offer;
}
public Message(String type, Contract contract)
{
this.type = type;
this.payload = contract;
}
public String getType()
{
return type;
}
public String toString()
{
return type + ": " + Utils.objectToJson(payload);
}
}

View file

@ -1,9 +1,16 @@
package io.bitsquare.msg;
import com.google.bitcoin.core.Utils;
import com.google.inject.Inject;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.BitSquare;
import io.bitsquare.msg.listeners.*;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.offerer.OffererPaymentProtocol;
import io.bitsquare.trade.taker.TakerPaymentProtocol;
import io.bitsquare.util.DSAKeyUtil;
import io.bitsquare.util.Utilities;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import net.tomp2p.connection.Bindings;
import net.tomp2p.connection.PeerConnection;
import net.tomp2p.futures.*;
@ -21,10 +28,7 @@ import java.io.File;
import java.io.IOException;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.*;
/**
* That facade delivers messaging functionality from the TomP2P library
@ -38,15 +42,26 @@ public class MessageFacade
public static final String PING = "ping";
public static final String PONG = "pong";
private static final int MASTER_PEER_PORT = 5000;
private static String MASTER_PEER_IP = "127.0.0.1";
private static String MASTER_PEER_IP = "192.168.1.33";
private Peer myPeerInstance;
private Peer myPeer;
private int port;
private KeyPair keyPair;
private Peer masterPeer;
private PeerAddress otherPeerAddress;
private PeerConnection peerConnection;
private List<MessageListener> messageListeners = new ArrayList<>();
private List<OrderBookListener> orderBookListeners = new ArrayList<>();
private List<TakeOfferRequestListener> takeOfferRequestListeners = new ArrayList<>();
// //TODO change to map (key: offerID) instead of list (offererPaymentProtocols, takerPaymentProtocols)
private List<TakerPaymentProtocol> takerPaymentProtocols = new ArrayList<>();
private List<OffererPaymentProtocol> offererPaymentProtocols = new ArrayList<>();
private List<PingPeerListener> pingPeerListeners = new ArrayList<>();
private Long lastTimeStamp = -3L;
private BooleanProperty isDirty = new SimpleBooleanProperty(false);
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -55,15 +70,15 @@ public class MessageFacade
@Inject
public MessageFacade()
{
try
/* try
{
masterPeer = BootstrapMasterPeer.INSTANCE(MASTER_PEER_PORT);
} catch (Exception e)
{
if (masterPeer != null)
masterPeer.shutdown();
log.info("masterPeer already instantiated by another app. " + e.getMessage());
}
System.err.println("masterPeer already instantiated by another app. " + e.getMessage());
} */
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -72,35 +87,31 @@ public class MessageFacade
public void init()
{
String keyName = WalletFacade.WALLET_PREFIX;
String keyName = BitSquare.ID;
port = Bindings.MAX_PORT - Math.abs(new Random().nextInt()) % (Bindings.MAX_PORT - Bindings.MIN_DYN_PORT);
if (WalletFacade.WALLET_PREFIX.equals("taker"))
if (BitSquare.ID.equals("taker"))
port = 4501;
else if (WalletFacade.WALLET_PREFIX.equals("offerer"))
else if (BitSquare.ID.equals("offerer"))
port = 4500;
try
{
createMyPeerInstance(keyName, port);
setupStorage();
//setupStorage();
//TODO save periodically or get informed if network address changes
saveMyAddressToDHT();
setupReplyHandler();
} catch (IOException e)
{
shutDown();
log.error("Error at setup peer" + e.getMessage());
log.error("Error at setup myPeerInstance" + e.getMessage());
}
//log.info("myPeerInstance knows: " + myPeerInstance.getPeerBean().getPeerMap().getAll());
}
public void shutDown()
{
if (peerConnection != null)
peerConnection.close();
if (myPeerInstance != null)
myPeerInstance.shutdown();
if (myPeer != null)
myPeer.shutdown();
if (masterPeer != null)
masterPeer.shutdown();
@ -112,36 +123,40 @@ public class MessageFacade
///////////////////////////////////////////////////////////////////////////////////////////
//TODO use Offer and do proper serialisation here
public void publishOffer(String currency, Object offerObject) throws IOException
public void addOffer(Offer offer) throws IOException
{
Number160 locationKey = Number160.createHash(currency);
Data offerData = new Data(offerObject);
offerData.setTTLSeconds(5);
FutureDHT putFuture = myPeerInstance.add(locationKey).setData(offerData).start();
putFuture.addListener(new BaseFutureAdapter<BaseFuture>()
Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode());
Number160 contentKey = Number160.createHash(offer.getUid());
final Data offerData = new Data(offer);
//offerData.setTTLSeconds(5);
final FutureDHT addFuture = myPeer.put(locationKey).setData(contentKey, offerData).start();
addFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
Platform.runLater(() -> onOfferPublished(future.isSuccess()));
Platform.runLater(() -> onOfferAdded(offerData, future.isSuccess(), locationKey));
}
});
}
private void onOfferPublished(boolean success)
private void onOfferAdded(Data offerData, boolean success, Number160 locationKey)
{
for (MessageListener messageListener : messageListeners)
messageListener.onOfferPublished(success);
setDirty(locationKey);
for (OrderBookListener orderBookListener : orderBookListeners)
orderBookListener.onOfferAdded(offerData, success);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Get offers
///////////////////////////////////////////////////////////////////////////////////////////
public void getOffers(String currency)
{
Number160 locationKey = Number160.createHash(currency);
final FutureDHT getOffersFuture = myPeerInstance.get(locationKey).setAll().start();
final Number160 locationKey = Number160.createHash(currency);
final FutureDHT getOffersFuture = myPeer.get(locationKey).setAll().start();
getOffersFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
@ -155,41 +170,130 @@ public class MessageFacade
private void onOffersReceived(Map<Number160, Data> dataMap, boolean success)
{
for (MessageListener messageListener : messageListeners)
messageListener.onOffersReceived(dataMap, success);
for (OrderBookListener orderBookListener : orderBookListeners)
orderBookListener.onOffersReceived(dataMap, success);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Remove offer
///////////////////////////////////////////////////////////////////////////////////////////
public void removeOffer(String currency, Object offerObject) throws IOException
public void removeOffer(Offer offer) throws IOException
{
Data offerData = new Data(offerObject);
Number160 locationKey = Number160.createHash(currency);
Number160 contentKey = offerData.getHash();
FutureDHT putFuture = myPeerInstance.remove(locationKey).setContentKey(contentKey).start();
putFuture.addListener(new BaseFutureAdapter<BaseFuture>()
Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode());
Number160 contentKey = Number160.createHash(offer.getUid());
FutureDHT removeFuture = myPeer.remove(locationKey).setReturnResults().setContentKey(contentKey).start();
removeFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
Platform.runLater(() -> onOfferRemoved(future.isSuccess()));
Data data = removeFuture.getData();
Platform.runLater(() -> onOfferRemoved(data, future.isSuccess(), locationKey));
}
});
}
private void onOfferRemoved(boolean success)
private void onOfferRemoved(Data data, boolean success, Number160 locationKey)
{
for (MessageListener messageListener : messageListeners)
messageListener.onOfferRemoved(success);
setDirty(locationKey);
for (OrderBookListener orderBookListener : orderBookListeners)
orderBookListener.onOfferRemoved(data, success);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Check dirty flag for a location key
///////////////////////////////////////////////////////////////////////////////////////////
public BooleanProperty getIsDirtyProperty()
{
return isDirty;
}
public void getDirtyFlag(Currency currency) throws IOException
{
Number160 locationKey = Number160.createHash(currency.getCurrencyCode());
FutureDHT getFuture = myPeer.get(getDirtyLocationKey(locationKey)).start();
getFuture.addListener(new BaseFutureListener<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
Data data = getFuture.getData();
if (data != null)
{
Object object = data.getObject();
if (object instanceof Long)
{
final long lastTimeStamp = (Long) object;
//System.out.println("getDirtyFlag " + lastTimeStamp);
Platform.runLater(() -> onGetDirtyFlag(lastTimeStamp));
}
}
}
@Override
public void exceptionCaught(Throwable t) throws Exception
{
System.out.println("getFuture exceptionCaught " + System.currentTimeMillis());
}
});
}
private void onGetDirtyFlag(long timeStamp)
{
// TODO don't get updates at first run....
if (lastTimeStamp != timeStamp)
{
isDirty.setValue(!isDirty.get());
}
if (lastTimeStamp > 0)
lastTimeStamp = timeStamp;
else
lastTimeStamp++;
}
private Number160 getDirtyLocationKey(Number160 locationKey) throws IOException
{
return Number160.createHash(locationKey.toString() + "Dirty");
}
private void setDirty(Number160 locationKey)
{
// we don't want to get an update from dirty for own changes, so update the lastTimeStamp to omit a change trigger
lastTimeStamp = System.currentTimeMillis();
FutureDHT putFuture = null;
try
{
putFuture = myPeer.put(getDirtyLocationKey(locationKey)).setData(new Data(lastTimeStamp)).start();
} catch (IOException e)
{
log.warn("Error at writing dirty flag (timeStamp) " + e.getMessage());
}
putFuture.addListener(new BaseFutureListener<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
//System.out.println("operationComplete");
}
@Override
public void exceptionCaught(Throwable t) throws Exception
{
System.err.println("exceptionCaught");
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Send message
///////////////////////////////////////////////////////////////////////////////////////////
public boolean sendMessage(String message)
/* public boolean sendMessage(Object message)
{
boolean result = false;
if (otherPeerAddress != null)
@ -197,10 +301,10 @@ public class MessageFacade
if (peerConnection != null)
peerConnection.close();
peerConnection = myPeerInstance.createPeerConnection(otherPeerAddress, 20);
peerConnection = myPeer.createPeerConnection(otherPeerAddress, 20);
if (!peerConnection.isClosed())
{
FutureResponse sendFuture = myPeerInstance.sendDirect(peerConnection).setObject(message).start();
FutureResponse sendFuture = myPeer.sendDirect(peerConnection).setObject(message).start();
sendFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
@ -222,8 +326,8 @@ public class MessageFacade
}
}
return result;
}
} */
/*
private void onResponseFromSend(Object response)
{
for (MessageListener messageListener : messageListeners)
@ -235,34 +339,195 @@ public class MessageFacade
for (MessageListener messageListener : messageListeners)
messageListener.onSendFailed();
}
*/
///////////////////////////////////////////////////////////////////////////////////////////
// Find peer
// Find peer address
///////////////////////////////////////////////////////////////////////////////////////////
public void findPeer(String pubKeyAsHex)
public void getPeerAddress(final String pubKeyAsHex, AddressLookupListener listener)
{
final FutureDHT getPeerAddressFuture = myPeerInstance.get(getPubKeyHash(pubKeyAsHex)).start();
final Number160 location = Number160.createHash(pubKeyAsHex);
final FutureDHT getPeerAddressFuture = myPeer.get(location).start();
getPeerAddressFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture baseFuture) throws Exception
{
final PeerAddress peerAddress = (PeerAddress) getPeerAddressFuture.getData().getObject();
Platform.runLater(() -> onPeerFound(peerAddress));
if (baseFuture.isSuccess() && getPeerAddressFuture.getData() != null)
{
final PeerAddress peerAddress = (PeerAddress) getPeerAddressFuture.getData().getObject();
Platform.runLater(() -> onAddressFound(peerAddress, listener));
}
else
{
Platform.runLater(() -> onGetPeerAddressFailed(listener));
}
}
});
}
private void onPeerFound(PeerAddress peerAddress)
private void onAddressFound(final PeerAddress peerAddress, AddressLookupListener listener)
{
if (!peerAddress.equals(myPeerInstance.getPeerAddress()))
listener.onResult(peerAddress);
}
private void onGetPeerAddressFailed(AddressLookupListener listener)
{
listener.onFailed();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Trade process
///////////////////////////////////////////////////////////////////////////////////////////
public void sendTradeMessage(final PeerAddress peerAddress, final TradeMessage tradeMessage, TradeMessageListener listener)
{
final PeerConnection peerConnection = myPeer.createPeerConnection(peerAddress, 10);
final FutureResponse sendFuture = myPeer.sendDirect(peerConnection).setObject(tradeMessage).start();
sendFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
otherPeerAddress = peerAddress;
for (MessageListener messageListener : messageListeners)
messageListener.onPeerFound();
@Override
public void operationComplete(BaseFuture baseFuture) throws Exception
{
if (sendFuture.isSuccess())
{
Platform.runLater(() -> onSendTradingMessageResult(listener));
}
else
{
Platform.runLater(() -> onSendTradingMessageFailed(listener));
}
}
}
);
}
private void onSendTradingMessageResult(TradeMessageListener listener)
{
listener.onResult();
}
private void onSendTradingMessageFailed(TradeMessageListener listener)
{
listener.onFailed();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Process incoming tradingMessage
///////////////////////////////////////////////////////////////////////////////////////////
private void processTradingMessage(TradeMessage tradeMessage, PeerAddress sender)
{
//TODO change to map (key: offerID) instead of list (offererPaymentProtocols, takerPaymentProtocols)
log.info("processTradingMessage " + tradeMessage.getType().toString());
switch (tradeMessage.getType())
{
case REQUEST_TAKE_OFFER:
// That is used to initiate the OffererPaymentProtocol and to show incoming requests in the view
for (TakeOfferRequestListener takeOfferRequestListener : takeOfferRequestListeners)
takeOfferRequestListener.onTakeOfferRequested(tradeMessage, sender);
break;
case ACCEPT_TAKE_OFFER_REQUEST:
for (TakerPaymentProtocol takeOfferTradeListener : takerPaymentProtocols)
takeOfferTradeListener.onTakeOfferRequestAccepted();
break;
case REJECT_TAKE_OFFER_REQUEST:
for (TakerPaymentProtocol takeOfferTradeListener : takerPaymentProtocols)
takeOfferTradeListener.onTakeOfferRequestRejected();
break;
case TAKE_OFFER_FEE_PAYED:
for (OffererPaymentProtocol offererPaymentProtocol : offererPaymentProtocols)
offererPaymentProtocol.onTakeOfferFeePayed(tradeMessage);
break;
case REQUEST_TAKER_DEPOSIT_PAYMENT:
for (TakerPaymentProtocol takeOfferTradeListener : takerPaymentProtocols)
takeOfferTradeListener.onTakerDepositPaymentRequested(tradeMessage);
break;
case REQUEST_OFFERER_DEPOSIT_PUBLICATION:
for (OffererPaymentProtocol offererPaymentProtocol : offererPaymentProtocols)
offererPaymentProtocol.onDepositTxReadyForPublication(tradeMessage);
break;
case DEPOSIT_TX_PUBLISHED:
for (TakerPaymentProtocol takeOfferTradeListener : takerPaymentProtocols)
takeOfferTradeListener.onDepositTxPublished(tradeMessage);
break;
case BANK_TX_INITED:
for (TakerPaymentProtocol takeOfferTradeListener : takerPaymentProtocols)
takeOfferTradeListener.onBankTransferInited();
break;
default:
log.info("default");
break;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Ping peer
///////////////////////////////////////////////////////////////////////////////////////////
//TODO not working anymore...
public void pingPeer(String publicKeyAsHex)
{
Number160 location = Number160.createHash(publicKeyAsHex);
final FutureDHT getPeerAddressFuture = myPeer.get(location).start();
getPeerAddressFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture baseFuture) throws Exception
{
final Data data = getPeerAddressFuture.getData();
if (data != null && data.getObject() instanceof PeerAddress)
{
final PeerAddress peerAddress = (PeerAddress) data.getObject();
Platform.runLater(() -> onAddressFoundPingPeer(peerAddress));
}
}
});
}
private void onAddressFoundPingPeer(PeerAddress peerAddress)
{
try
{
final PeerConnection peerConnection = myPeer.createPeerConnection(peerAddress, 10);
if (!peerConnection.isClosed())
{
FutureResponse sendFuture = myPeer.sendDirect(peerConnection).setObject(PING).start();
sendFuture.addListener(new BaseFutureAdapter<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture baseFuture) throws Exception
{
if (sendFuture.isSuccess())
{
final String pong = (String) sendFuture.getObject();
if (pong != null)
Platform.runLater(() -> onResponseFromPing(pong.equals(PONG)));
}
else
{
peerConnection.close();
Platform.runLater(() -> onResponseFromPing(false));
}
}
}
);
}
} catch (Exception e)
{
// ClosedChannelException can happen, check out if there is a better way to ping a myPeerInstance for online status
}
}
private void onResponseFromPing(boolean success)
{
for (PingPeerListener pingPeerListener : pingPeerListeners)
pingPeerListener.onPingPeerResult(success);
}
@ -270,15 +535,6 @@ public class MessageFacade
// Misc
///////////////////////////////////////////////////////////////////////////////////////////
public boolean isOtherPeerDefined()
{
return otherPeerAddress != null;
}
public String getPubKeyAsHex()
{
return Utils.bytesToHexString(keyPair.getPublic().getEncoded());
}
public PublicKey getPubKey()
{
@ -289,14 +545,54 @@ public class MessageFacade
// Event Listeners
///////////////////////////////////////////////////////////////////////////////////////////
public void addMessageListener(MessageListener listener)
public void addMessageListener(OrderBookListener listener)
{
messageListeners.add(listener);
orderBookListeners.add(listener);
}
public void removeMessageListener(MessageListener listener)
public void removeMessageListener(OrderBookListener listener)
{
messageListeners.remove(listener);
orderBookListeners.remove(listener);
}
public void addTakeOfferRequestListener(TakeOfferRequestListener listener)
{
takeOfferRequestListeners.add(listener);
}
public void removeTakeOfferRequestListener(TakeOfferRequestListener listener)
{
takeOfferRequestListeners.remove(listener);
}
public void addTakerPaymentProtocol(TakerPaymentProtocol listener)
{
takerPaymentProtocols.add(listener);
}
public void removeTakerPaymentProtocol(TakerPaymentProtocol listener)
{
takerPaymentProtocols.remove(listener);
}
public void addOffererPaymentProtocol(OffererPaymentProtocol listener)
{
offererPaymentProtocols.add(listener);
}
public void removeOffererPaymentProtocol(OffererPaymentProtocol listener)
{
offererPaymentProtocols.remove(listener);
}
public void addPingPeerListener(PingPeerListener listener)
{
pingPeerListeners.add(listener);
}
public void removePingPeerListener(PingPeerListener listener)
{
pingPeerListeners.remove(listener);
}
@ -306,43 +602,59 @@ public class MessageFacade
private void createMyPeerInstance(String keyName, int port) throws IOException
{
keyPair = MsgKeyUtil.getKeyPair(keyName);
myPeerInstance = new PeerMaker(keyPair).setPorts(port).makeAndListen();
//TODO use list of multiple master bootstrap peers
/*PeerAddress bootstrapServerPeerAddress = new PeerAddress(BootstrapMasterPeer.ID, new InetSocketAddress(InetAddress.getByName(MASTER_PEER_IP), port));
FutureBootstrap futureBootstrap = myPeerInstance.bootstrap().setPeerAddress(bootstrapServerPeerAddress).start();
*/
FutureBootstrap futureBootstrap = myPeerInstance.bootstrap().setBroadcast().setPorts(MASTER_PEER_PORT).start();
if (futureBootstrap != null)
keyPair = DSAKeyUtil.getKeyPair(keyName);
myPeer = new PeerMaker(keyPair).setPorts(port).makeAndListen();
final FutureBootstrap futureBootstrap = myPeer.bootstrap().setBroadcast().setPorts(MASTER_PEER_PORT).start();
// futureBootstrap.awaitUninterruptibly();
futureBootstrap.addListener(new BaseFutureAdapter<BaseFuture>()
{
futureBootstrap.awaitUninterruptibly();
if (futureBootstrap.getBootstrapTo() != null)
@Override
public void operationComplete(BaseFuture future) throws Exception
{
PeerAddress peerAddress = futureBootstrap.getBootstrapTo().iterator().next();
myPeerInstance.discover().setPeerAddress(peerAddress).start().awaitUninterruptibly();
if (futureBootstrap.getBootstrapTo() != null)
{
PeerAddress masterPeerAddress = futureBootstrap.getBootstrapTo().iterator().next();
final FutureDiscover futureDiscover = myPeer.discover().setPeerAddress(masterPeerAddress).start();
//futureDiscover.awaitUninterruptibly();
futureDiscover.addListener(new BaseFutureListener<BaseFuture>()
{
@Override
public void operationComplete(BaseFuture future) throws Exception
{
//System.out.println("operationComplete");
}
@Override
public void exceptionCaught(Throwable t) throws Exception
{
System.err.println("exceptionCaught");
}
});
}
}
}
});
}
private void setupStorage() throws IOException
{
//TODO WalletFacade.WALLET_PREFIX just temp...
String dirPath = io.bitsquare.util.Utils.getRootDir() + "tomP2P_" + WalletFacade.WALLET_PREFIX;
//TODO BitSquare.ID just temp...
String dirPath = Utilities.getRootDir() + "tomP2P_" + BitSquare.ID;
File dirFile = new File(dirPath);
boolean success = true;
if (!dirFile.exists())
success = dirFile.mkdir();
if (success)
myPeerInstance.getPeerBean().setStorage(new StorageDisk(dirPath));
myPeer.getPeerBean().setStorage(new StorageDisk(dirPath));
else
log.warn("Unable to create directory " + dirPath);
}
private void saveMyAddressToDHT() throws IOException
{
myPeerInstance.put(getPubKeyHash(getPubKeyAsHex())).setData(new Data(myPeerInstance.getPeerAddress())).start();
Number160 location = Number160.createHash(DSAKeyUtil.getHexStringFromPublicKey(getPubKey()));
//log.debug("saveMyAddressToDHT location "+location.toString());
myPeer.put(location).setData(new Data(myPeer.getPeerAddress())).start();
}
@ -352,47 +664,32 @@ public class MessageFacade
private void setupReplyHandler()
{
myPeerInstance.setObjectDataReply(new ObjectDataReply()
myPeer.setObjectDataReply(new ObjectDataReply()
{
@Override
public Object reply(PeerAddress sender, Object request) throws Exception
{
String reply = null;
if (!sender.equals(myPeerInstance.getPeerAddress()))
if (!sender.equals(myPeer.getPeerAddress()))
{
otherPeerAddress = sender;
Platform.runLater(() -> onMessage(request));
if (request.equals(PING))
{
Platform.runLater(() -> onPing());
}
Platform.runLater(() -> onMessage(request, sender));
}
return reply;
return null;
}
});
}
private void onMessage(Object message)
private void onMessage(Object request, PeerAddress sender)
{
for (MessageListener messageListener : messageListeners)
messageListener.onMessage(message);
if (request instanceof TradeMessage)
{
processTradingMessage((TradeMessage) request, sender);
}
/* else
{
for (OrderBookListener orderBookListener : orderBookListeners)
orderBookListener.onMessage(request);
} */
}
private void onPing()
{
for (MessageListener messageListener : messageListeners)
messageListener.onPing();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private Number160 getPubKeyHash(String pubKeyAsHex)
{
return net.tomp2p.utils.Utils.makeSHAHash(pubKeyAsHex);
}
}

View file

@ -1,26 +0,0 @@
package io.bitsquare.msg;
import net.tomp2p.peers.Number160;
import net.tomp2p.storage.Data;
import java.util.EventListener;
import java.util.Map;
public interface MessageListener extends EventListener
{
void onMessage(Object message);
void onPing();
void onOfferPublished(boolean success);
void onSendFailed();
void onResponseFromSend(Object response);
void onPeerFound();
void onOffersReceived(Map<Number160, Data> dataMap, boolean success);
void onOfferRemoved(boolean success);
}

View file

@ -0,0 +1,181 @@
package io.bitsquare.msg;
import io.bitsquare.bank.BankAccount;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.UUID;
public class TradeMessage implements Serializable
{
private static final long serialVersionUID = 7916445031849763995L;
private String uid;
private String takerMessagePubKey;
private String signedTakerDepositTxAsHex;
private String txScriptSigAsHex;
private String txConnOutAsHex;
private String contractAsJson;
private String takerContractSignature;
private TradeMessageType type;
private String depositTxID;
private BigInteger tradeAmount;
private String takeOfferFeeTxID;
private String takerMultiSigPubKey;
private String offerUID;
private BankAccount bankAccount;
private String accountID;
private String offererPubKey;
private String preparedOffererDepositTxAsHex;
public TradeMessage(TradeMessageType type, String offerUID)
{
this.offerUID = offerUID;
this.type = type;
uid = UUID.randomUUID().toString();
}
public TradeMessage(TradeMessageType type, String offerUID, BigInteger tradeAmount, String takeOfferFeeTxID, String takerMultiSigPubKey)
{
this.offerUID = offerUID;
this.type = type;
this.tradeAmount = tradeAmount;
this.takeOfferFeeTxID = takeOfferFeeTxID;
this.takerMultiSigPubKey = takerMultiSigPubKey;
uid = UUID.randomUUID().toString();
}
public TradeMessage(TradeMessageType type, String offerUID, BankAccount bankAccount, String accountID, String offererPubKey, String preparedOffererDepositTxAsHex)
{
this.offerUID = offerUID;
this.type = type;
this.bankAccount = bankAccount;
this.accountID = accountID;
this.offererPubKey = offererPubKey;
this.preparedOffererDepositTxAsHex = preparedOffererDepositTxAsHex;
uid = UUID.randomUUID().toString();
}
public TradeMessage(TradeMessageType type, String offerUID,
BankAccount bankAccount,
String accountID,
String takerMessagePubKey,
String signedTakerDepositTxAsHex,
String txScriptSigAsHex,
String txConnOutAsHex,
String contractAsJson,
String takerContractSignature)
{
this.offerUID = offerUID;
this.type = type;
this.bankAccount = bankAccount;
this.accountID = accountID;
this.takerMessagePubKey = takerMessagePubKey;
this.signedTakerDepositTxAsHex = signedTakerDepositTxAsHex;
this.txScriptSigAsHex = txScriptSigAsHex;
this.txConnOutAsHex = txConnOutAsHex;
this.contractAsJson = contractAsJson;
this.takerContractSignature = takerContractSignature;
uid = UUID.randomUUID().toString();
}
public TradeMessage(TradeMessageType type, String offerUID, String depositTxID)
{
this.offerUID = offerUID;
this.type = type;
this.depositTxID = depositTxID;
uid = UUID.randomUUID().toString();
}
public String getUid()
{
return uid;
}
public TradeMessageType getType()
{
return type;
}
public String getTakeOfferFeeTxID()
{
return takeOfferFeeTxID;
}
public String getOfferUID()
{
return offerUID;
}
public BankAccount getBankAccount()
{
return bankAccount;
}
public String getAccountID()
{
return accountID;
}
public String getTakerMultiSigPubKey()
{
return takerMultiSigPubKey;
}
public String getPreparedOffererDepositTxAsHex()
{
return preparedOffererDepositTxAsHex;
}
public BigInteger getTradeAmount()
{
return tradeAmount;
}
public String getTakerMessagePubKey()
{
return takerMessagePubKey;
}
public String getSignedTakerDepositTxAsHex()
{
return signedTakerDepositTxAsHex;
}
public String getContractAsJson()
{
return contractAsJson;
}
public String getTakerContractSignature()
{
return takerContractSignature;
}
public String getTxScriptSigAsHex()
{
return txScriptSigAsHex;
}
public String getTxConnOutAsHex()
{
return txConnOutAsHex;
}
public String getDepositTxID()
{
return depositTxID;
}
public String getOffererPubKey()
{
return offererPubKey;
}
}

View file

@ -0,0 +1,13 @@
package io.bitsquare.msg;
public enum TradeMessageType
{
REQUEST_TAKE_OFFER,
ACCEPT_TAKE_OFFER_REQUEST,
REJECT_TAKE_OFFER_REQUEST,
TAKE_OFFER_FEE_PAYED,
REQUEST_TAKER_DEPOSIT_PAYMENT,
REQUEST_OFFERER_DEPOSIT_PUBLICATION,
DEPOSIT_TX_PUBLISHED,
BANK_TX_INITED
}

View file

@ -0,0 +1,10 @@
package io.bitsquare.msg.listeners;
import net.tomp2p.peers.PeerAddress;
public interface AddressLookupListener
{
void onResult(PeerAddress peerAddress);
void onFailed();
}

View file

@ -0,0 +1,16 @@
package io.bitsquare.msg.listeners;
import net.tomp2p.peers.Number160;
import net.tomp2p.storage.Data;
import java.util.Map;
public interface OrderBookListener
{
void onOfferAdded(Data offerData, boolean success);
void onOffersReceived(Map<Number160, Data> dataMap, boolean success);
void onOfferRemoved(Data data, boolean success);
}

View file

@ -0,0 +1,8 @@
package io.bitsquare.msg.listeners;
public interface PingPeerListener
{
void onPing();
void onPingPeerResult(boolean success);
}

View file

@ -0,0 +1,9 @@
package io.bitsquare.msg.listeners;
import io.bitsquare.msg.TradeMessage;
import net.tomp2p.peers.PeerAddress;
public interface TakeOfferRequestListener
{
void onTakeOfferRequested(TradeMessage tradeMessage, PeerAddress sender);
}

View file

@ -0,0 +1,8 @@
package io.bitsquare.msg.listeners;
public interface TradeMessageListener
{
void onFailed();
void onResult();
}

View file

@ -16,8 +16,8 @@ public class Settings implements Serializable
private List<Locale> acceptedLanguageLocales = new ArrayList<>();
private List<Locale> acceptedCountryLocales = new ArrayList<>();
private List<Arbitrator> acceptedArbitrators = new ArrayList<>();
private double maxCollateral;
private double minCollateral;
private int maxCollateral;
private int minCollateral;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -63,12 +63,12 @@ public class Settings implements Serializable
acceptedArbitrators.add(arbitrator);
}
public void setMaxCollateral(double maxCollateral)
public void setMaxCollateral(int maxCollateral)
{
this.maxCollateral = maxCollateral;
}
public void setMinCollateral(double minCollateral)
public void setMinCollateral(int minCollateral)
{
this.minCollateral = minCollateral;
}
@ -92,26 +92,27 @@ public class Settings implements Serializable
return acceptedCountryLocales;
}
public Arbitrator getRandomArbitrator(double collateral, BigInteger amount)
//TODO
public Arbitrator getRandomArbitrator(int collateral, BigInteger amount)
{
List<Arbitrator> candidates = new ArrayList<>();
for (Arbitrator arbitrator : acceptedArbitrators)
{
if (arbitrator.getArbitrationFeePercent() >= collateral &&
arbitrator.getMinArbitrationFee().compareTo(amount) < 0)
{
candidates.add(arbitrator);
}
/*if (arbitrator.getArbitrationFeePercent() >= collateral &&
arbitrator.getMinArbitrationAmount().compareTo(amount) < 0)
{ */
candidates.add(arbitrator);
// }
}
return candidates.size() > 0 ? candidates.get((int) (Math.random() * candidates.size())) : null;
}
public double getMaxCollateral()
public int getMaxCollateral()
{
return maxCollateral;
}
public double getMinCollateral()
public int getMinCollateral()
{
return minCollateral;
}

View file

@ -1,6 +1,7 @@
package io.bitsquare.storage;
import io.bitsquare.util.Utils;
import io.bitsquare.BitSquare;
import io.bitsquare.util.Utilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -16,7 +17,7 @@ public class Storage
private static final Logger log = LoggerFactory.getLogger(Storage.class);
//TODO save in users preferences location
private final String preferencesFileName = "preferences.ser";
private final String preferencesFileName = "pref_" + BitSquare.ID + ".ser";
private final String storageFile;
private Map<String, Object> dict;
@ -27,7 +28,7 @@ public class Storage
public Storage()
{
storageFile = Utils.getRootDir() + preferencesFileName;
storageFile = Utilities.getRootDir() + preferencesFileName;
dict = readDataVO();
if (dict == null)

View file

@ -1,61 +1,107 @@
package io.bitsquare.trade;
import io.bitsquare.user.User;
import io.bitsquare.bank.BankAccount;
import java.util.UUID;
import java.io.Serializable;
import java.math.BigInteger;
public class Contract
public class Contract implements Serializable
{
private User taker;
private Trade trade;
private String takerPubKey;
private String offererPubKey;
private static final long serialVersionUID = 71472356206100158L;
public Contract(User taker, Trade trade, String takerPubKey)
private Offer offer;
private String takeOfferFeeTxID;
private BigInteger tradeAmount;
private String offererAccountID;
private String takerAccountID;
private BankAccount offererBankAccount;
private BankAccount takerBankAccount;
private String offererMessagePubKeyAsHex;
private String takerMessagePubKeyAsHex;
public Contract(Offer offer,
BigInteger tradeAmount,
String takeOfferFeeTxID,
String offererAccountID,
String takerAccountID,
BankAccount offererBankAccount,
BankAccount takerBankAccount,
String offererMessagePubKeyAsHex,
String takerMessagePubKeyAsHex)
{
this.taker = taker;
this.trade = trade;
this.takerPubKey = takerPubKey;
this.offer = offer;
this.tradeAmount = tradeAmount;
this.takeOfferFeeTxID = takeOfferFeeTxID;
this.offererAccountID = offererAccountID;
this.takerAccountID = takerAccountID;
this.offererBankAccount = offererBankAccount;
this.takerBankAccount = takerBankAccount;
this.offererMessagePubKeyAsHex = offererMessagePubKeyAsHex;
this.takerMessagePubKeyAsHex = takerMessagePubKeyAsHex;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setOffererPubKey(String offererPubKey)
{
this.offererPubKey = offererPubKey;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public UUID getUid()
public Offer getOffer()
{
return trade.getUid();
return offer;
}
public User getTaker()
public String getTakeOfferFeeTxID()
{
return taker;
return takeOfferFeeTxID;
}
public String getTakerPubKey()
public BigInteger getTradeAmount()
{
return takerPubKey;
return tradeAmount;
}
public Trade getTrade()
public String getOffererAccountID()
{
return trade;
return offererAccountID;
}
public String getOffererPubKey()
public String getTakerAccountID()
{
return offererPubKey;
return takerAccountID;
}
public BankAccount getOffererBankAccount()
{
return offererBankAccount;
}
public BankAccount getTakerBankAccount()
{
return takerBankAccount;
}
public String getTakerMessagePubKeyAsHex()
{
return takerMessagePubKeyAsHex;
}
@Override
public String toString()
{
return "Contract{" +
"offer=" + offer +
", takeOfferFeeTxID='" + takeOfferFeeTxID + '\'' +
", tradeAmount=" + tradeAmount +
", offererAccountID='" + offererAccountID + '\'' +
", takerAccountID='" + takerAccountID + '\'' +
", offererBankAccount=" + offererBankAccount +
", takerBankAccount=" + takerBankAccount +
", offererMessagePubKeyAsHex='" + offererMessagePubKeyAsHex + '\'' +
", takerMessagePubKeyAsHex='" + takerMessagePubKeyAsHex + '\'' +
'}';
}
public String getOffererMessagePubKeyAsHex()
{
return offererMessagePubKeyAsHex;
}
}

View file

@ -1,34 +1,38 @@
package io.bitsquare.trade;
import io.bitsquare.bank.BankAccountType;
import io.bitsquare.btc.BtcFormatter;
import io.bitsquare.user.Arbitrator;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Currency;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
public class Offer
public class Offer implements Serializable
{
private static final long serialVersionUID = -971164804305475826L;
// key attributes for lookup
private Direction direction;
private Currency currency;
private UUID uid;
private String uid;
private double price;
private BigInteger amount;
private BigInteger minAmount;
private String accountID;
private String messageID;
private String messagePubKeyAsHex;
private BankAccountType.BankAccountTypeEnum bankAccountTypeEnum;
private Locale bankAccountCountryLocale;
private double collateral;
private int collateral;
private List<Locale> acceptedCountryLocales;
private List<Locale> acceptedLanguageLocales;
private String offerPaymentTxID;
private String offerFeePaymentTxID;
private String bankAccountUID;
private Arbitrator arbitrator;
@ -36,8 +40,7 @@ public class Offer
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public Offer(String accountID,
String messageID,
public Offer(String messagePubKeyAsHex,
Direction direction,
double price,
BigInteger amount,
@ -45,13 +48,13 @@ public class Offer
BankAccountType.BankAccountTypeEnum bankAccountTypeEnum,
Currency currency,
Locale bankAccountCountryLocale,
String bankAccountUID,
Arbitrator arbitrator,
double collateral,
int collateral,
List<Locale> acceptedCountryLocales,
List<Locale> acceptedLanguageLocales)
{
this.accountID = accountID;
this.messageID = messageID;
this.messagePubKeyAsHex = messagePubKeyAsHex;
this.direction = direction;
this.price = price;
this.amount = amount;
@ -59,12 +62,13 @@ public class Offer
this.bankAccountTypeEnum = bankAccountTypeEnum;
this.currency = currency;
this.bankAccountCountryLocale = bankAccountCountryLocale;
this.bankAccountUID = bankAccountUID;
this.arbitrator = arbitrator;
this.collateral = collateral;
this.acceptedCountryLocales = acceptedCountryLocales;
this.acceptedLanguageLocales = acceptedLanguageLocales;
uid = UUID.randomUUID();
this.uid = UUID.randomUUID().toString();
}
@ -72,9 +76,9 @@ public class Offer
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setOfferPaymentTxID(String offerPaymentTxID)
public void setOfferFeePaymentTxID(String offerFeePaymentTxID)
{
this.offerPaymentTxID = offerPaymentTxID;
this.offerFeePaymentTxID = offerFeePaymentTxID;
}
@ -82,17 +86,12 @@ public class Offer
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public String getAccountID()
public String getMessagePubKeyAsHex()
{
return accountID;
return messagePubKeyAsHex;
}
public String getMessageID()
{
return messageID;
}
public UUID getUid()
public String getUid()
{
return uid;
}
@ -144,17 +143,17 @@ public class Offer
public double getVolume()
{
return price * amount.doubleValue();
return price * BtcFormatter.satoshiToBTC(amount);
}
public double getMinVolume()
{
return price * minAmount.doubleValue();
return price * BtcFormatter.satoshiToBTC(minAmount);
}
public String getOfferPaymentTxID()
public String getOfferFeePaymentTxID()
{
return offerPaymentTxID;
return offerFeePaymentTxID;
}
public Arbitrator getArbitrator()
@ -162,8 +161,35 @@ public class Offer
return arbitrator;
}
public double getCollateral()
public int getCollateral()
{
return collateral;
}
public String getBankAccountUID()
{
return bankAccountUID;
}
@Override
public String toString()
{
return "Offer{" +
"direction=" + direction +
", currency=" + currency +
", uid='" + uid + '\'' +
", price=" + price +
", amount=" + amount +
", minAmount=" + minAmount +
", messagePubKey=" + messagePubKeyAsHex.hashCode() +
", bankAccountTypeEnum=" + bankAccountTypeEnum +
", bankAccountCountryLocale=" + bankAccountCountryLocale +
", collateral=" + collateral +
", acceptedCountryLocales=" + acceptedCountryLocales +
", acceptedLanguageLocales=" + acceptedLanguageLocales +
", offerFeePaymentTxID='" + offerFeePaymentTxID + '\'' +
", bankAccountUID='" + bankAccountUID + '\'' +
", arbitrator=" + arbitrator +
'}';
}
}

View file

@ -1,81 +1,131 @@
package io.bitsquare.trade;
import java.math.BigInteger;
import java.util.UUID;
import com.google.bitcoin.core.Transaction;
import javafx.beans.property.SimpleBooleanProperty;
public class Trade
import java.io.Serializable;
import java.math.BigInteger;
public class Trade implements Serializable
{
private static final long serialVersionUID = -8275323072940974077L;
transient private final SimpleBooleanProperty depositTxChangedProperty = new SimpleBooleanProperty();
private Offer offer;
private boolean takeOfferRequested;
private boolean takeOfferAccepted;
private BigInteger requestedAmount;
private boolean takeOfferFeePaymentConfirmed;
private String jsonRepresentation;
private String signature;
private String takeOfferFeeTxID;
private BigInteger tradeAmount;
private Contract contract;
private String contractAsJson;
private String takerSignature;
private Transaction depositTransaction;
public Trade(Offer offer)
{
this.offer = offer;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setTakeOfferFeeTxID(String takeOfferFeeTxID)
{
this.takeOfferFeeTxID = takeOfferFeeTxID;
}
public void setTradeAmount(BigInteger tradeAmount)
{
this.tradeAmount = tradeAmount;
}
public void setContract(Contract contract)
{
this.contract = contract;
}
public void setContractAsJson(String contractAsJson)
{
this.contractAsJson = contractAsJson;
}
public void setContractTakerSignature(String takerSignature)
{
this.takerSignature = takerSignature;
}
public void setDepositTransaction(Transaction depositTransaction)
{
this.depositTransaction = depositTransaction;
depositTxChangedProperty.set(!depositTxChangedProperty.get());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public String getUid()
{
return offer.getUid();
}
public Offer getOffer()
{
return offer;
}
public UUID getUid()
public String getTakeOfferFeeTxID()
{
return offer.getUid();
return takeOfferFeeTxID;
}
public void setJsonRepresentation(String jsonRepresentation)
public BigInteger getTradeAmount()
{
this.jsonRepresentation = jsonRepresentation;
return tradeAmount;
}
public void setSignature(String signature)
public Contract getContract()
{
this.signature = signature;
return contract;
}
public boolean isTakeOfferRequested()
public String getContractAsJson()
{
return takeOfferRequested;
return contractAsJson;
}
public void setTakeOfferRequested(boolean takeOfferRequested)
public String getTakerSignature()
{
this.takeOfferRequested = takeOfferRequested;
return takerSignature;
}
public boolean isTakeOfferAccepted()
public Transaction getDepositTransaction()
{
return takeOfferAccepted;
return depositTransaction;
}
public void setTakeOfferAccepted(boolean takeOfferAccepted)
public SimpleBooleanProperty getDepositTxChangedProperty()
{
this.takeOfferAccepted = takeOfferAccepted;
return depositTxChangedProperty;
}
///////////////////////////////////////////////////////////////////////////////////////////
// toString
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public String toString()
{
return "Trade{" +
"offer=" + offer +
", takeOfferFeeTxID='" + takeOfferFeeTxID + '\'' +
", tradeAmount=" + tradeAmount +
", contract=" + contract +
", contractAsJson='" + contractAsJson + '\'' +
", takerSignature='" + takerSignature + '\'' +
'}';
}
public BigInteger getRequestedAmount()
{
return requestedAmount;
}
public void setTradeAmount(BigInteger requestedAmount)
{
this.requestedAmount = requestedAmount;
}
public void setTakeOfferFeePaymentConfirmed(boolean takeOfferFeePaymentConfirmed)
{
this.takeOfferFeePaymentConfirmed = takeOfferFeePaymentConfirmed;
}
public void setTakeOfferFeeTxID(String takeOfferFeeTxID)
{
this.takeOfferFeeTxID = takeOfferFeeTxID;
}
}

View file

@ -2,35 +2,46 @@ package io.bitsquare.trade;
import com.google.bitcoin.core.InsufficientMoneyException;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionConfidence;
import com.google.common.util.concurrent.FutureCallback;
import com.google.inject.Inject;
import io.bitsquare.btc.BlockChainFacade;
import io.bitsquare.btc.Fees;
import io.bitsquare.btc.KeyPair;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.crypto.CryptoFacade;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.TradeMessage;
import io.bitsquare.settings.Settings;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.offerer.OffererPaymentProtocol;
import io.bitsquare.trade.offerer.OffererPaymentProtocolListener;
import io.bitsquare.trade.taker.TakerPaymentProtocol;
import io.bitsquare.trade.taker.TakerPaymentProtocolListener;
import io.bitsquare.user.User;
import io.bitsquare.util.Utils;
import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.UUID;
import java.util.Map;
/**
* Represents trade domain. Keeps complexity of process apart from view controller
*/
//TODO use scheduler/process pattern with tasks for every async job
public class Trading
{
private static final Logger log = LoggerFactory.getLogger(Trading.class);
private final HashMap<String, Offer> offers = new HashMap<>();
private final HashMap<String, Trade> trades = new HashMap<>();
private final HashMap<String, Contract> contracts = new HashMap<>();
private Map<String, Offer> myOffers = new HashMap<>();
private Map<String, Trade> trades = new HashMap<>();
private final Map<String, TakerPaymentProtocol> takerPaymentProtocols = new HashMap<>();
private final Map<String, OffererPaymentProtocol> offererPaymentProtocols = new HashMap<>();
private final String storageKey;
private User user;
private Storage storage;
private MessageFacade messageFacade;
private BlockChainFacade blockChainFacade;
private WalletFacade walletFacade;
@ -45,6 +56,7 @@ public class Trading
@Inject
public Trading(User user,
Settings settings,
Storage storage,
MessageFacade messageFacade,
BlockChainFacade blockChainFacade,
WalletFacade walletFacade,
@ -52,10 +64,21 @@ public class Trading
{
this.user = user;
this.settings = settings;
this.storage = storage;
this.messageFacade = messageFacade;
this.blockChainFacade = blockChainFacade;
this.walletFacade = walletFacade;
this.cryptoFacade = cryptoFacade;
storageKey = this.getClass().getName();
Object offersObject = storage.read(storageKey + ".offers");
if (offersObject != null && offersObject instanceof HashMap)
myOffers = (Map) offersObject;
Object tradesObject = storage.read(storageKey + ".trades");
if (tradesObject != null && tradesObject instanceof HashMap)
trades = (Map) tradesObject;
}
@ -63,79 +86,117 @@ public class Trading
// Public Methods
///////////////////////////////////////////////////////////////////////////////////////////
public void cleanup()
{
}
///////////////////////////////////////////////////////////////////////////////////////////
// Offer, trade, contract
///////////////////////////////////////////////////////////////////////////////////////////
public void placeNewOffer(Offer offer, FutureCallback<Transaction> callback) throws InsufficientMoneyException
{
log.info("place New Offer");
offers.put(offer.getUid().toString(), offer);
walletFacade.payFee(Fees.OFFER_CREATION_FEE, callback);
if (myOffers.containsKey(offer.getUid()))
throw new IllegalStateException("offers contains already a offer with the ID " + offer.getUid());
// messageFacade.broadcast(new Message(Message.BROADCAST_NEW_OFFER, offer));
myOffers.put(offer.getUid(), offer);
storage.write(storageKey + ".offers", myOffers);
walletFacade.payFee(Fees.OFFER_CREATION_FEE, callback);
}
public void removeOffer(Offer offer) throws IOException
{
myOffers.remove(offer.getUid());
storage.write(storageKey + ".offers", myOffers);
}
public Trade createTrade(Offer offer)
{
log.info("create New Trade");
/* if (trades.containsKey(offer.getUid()))
throw new IllegalStateException("trades contains already a trade with the ID " + offer.getUid()); */
Trade trade = new Trade(offer);
trades.put(trade.getUid().toString(), trade);
trades.put(offer.getUid(), trade);
storage.write(storageKey + ".trades", trades);
return trade;
}
public Contract createContract(Trade trade)
public void removeTrade(Trade trade) throws IOException
{
log.info("create new contract");
KeyPair address = new KeyPair(UUID.randomUUID().toString(), UUID.randomUUID().toString());
//TODO
Contract contract = new Contract(user, trade, address.getPubKey());
contracts.put(trade.getUid().toString(), contract);
return contract;
trades.remove(trade.getUid());
storage.write(storageKey + ".trades", trades);
}
public TakerPaymentProtocol addTakerPaymentProtocol(Trade trade, TakerPaymentProtocolListener listener)
{
TakerPaymentProtocol takerPaymentProtocol = new TakerPaymentProtocol(trade, listener, messageFacade, walletFacade, blockChainFacade, cryptoFacade, user);
takerPaymentProtocols.put(trade.getUid(), takerPaymentProtocol);
return takerPaymentProtocol;
}
public OffererPaymentProtocol addOffererPaymentProtocol(Trade trade, OffererPaymentProtocolListener listener)
{
OffererPaymentProtocol offererPaymentProtocol = new OffererPaymentProtocol(trade, listener, messageFacade, walletFacade, blockChainFacade, cryptoFacade, user);
offererPaymentProtocols.put(trade.getUid(), offererPaymentProtocol);
return offererPaymentProtocol;
}
public void createOffererPaymentProtocol(TradeMessage tradeMessage, PeerAddress sender)
{
Offer offer = myOffers.get(tradeMessage.getOfferUID());
Trade trade = createTrade(offer);
OffererPaymentProtocol offererPaymentProtocol = addOffererPaymentProtocol(trade, new OffererPaymentProtocolListener()
{
@Override
public void onProgress(double progress)
{
//log.debug("onProgress " + progress);
}
@Override
public void onFailure(String failureMessage)
{
log.warn(failureMessage);
}
@Override
public void onDepositTxPublished(String depositTxID)
{
log.debug("trading onDepositTxPublished " + depositTxID);
}
@Override
public void onDepositTxConfirmedUpdate(TransactionConfidence confidence)
{
log.debug("trading onDepositTxConfirmedUpdate");
}
@Override
public void onDepositTxConfirmedInBlockchain()
{
log.debug("trading onDepositTxConfirmedInBlockchain");
}
});
// the handler was not called there because the obejct was not created when the event occurred (and therefor no listener)
// will probably created earlier, so let it for the moment like that....
offererPaymentProtocol.onTakeOfferRequested(sender);
}
// trade process
// 1
public void sendTakeOfferRequest(Trade trade)
public void onBankTransferInited(String tradeUID)
{
log.info("Taker asks offerer to take his offer");
//messageFacade.send(new Message(Message.REQUEST_TAKE_OFFER, trade), trade.getOffer().getOfferer().getMessageID());
offererPaymentProtocols.get(tradeUID).bankTransferInited();
}
// 2
public void payOfferFee(Trade trade, FutureCallback<Transaction> callback) throws InsufficientMoneyException
{
log.info("Pay offer fee");
walletFacade.payFee(Fees.OFFER_TAKER_FEE, callback);
///////////////////////////////////////////////////////////////////////////////////////////
// Trade process
///////////////////////////////////////////////////////////////////////////////////////////
log.info("Taker asks offerer for confirmation for his fee payment.");
// messageFacade.send(new Message(Message.REQUEST_OFFER_FEE_PAYMENT_CONFIRM, trade), trade.getOffer().getOfferer().getMessageID());
}
// 3
public void requestOffererDetailData()
{
log.info("Request offerer detail data");
}
// 4
public void signContract(Contract contract)
{
log.info("sign Contract");
String contractAsJson = Utils.objectToJson(contract);
contract.getTrade().setJsonRepresentation(contractAsJson);
contract.getTrade().setSignature(cryptoFacade.signContract(walletFacade.getAccountKey(), contractAsJson));
}
// 5
public void payToDepositTx(Trade trade)
{
//walletFacade.takerAddPaymentAndSign();
// messageFacade.send(new Message(Message.REQUEST_OFFER_FEE_PAYMENT_CONFIRM, trade), trade.getOffer().getOfferer().getMessageID());
}
// 6
public void releaseBTC(Trade trade)
{
@ -151,9 +212,13 @@ public class Trading
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public HashMap<String, Trade> getTrades()
public Map<String, Trade> getTrades()
{
return trades;
}
public Offer getOffer(String offerUID)
{
return myOffers.get(offerUID.toString());
}
}

View file

@ -0,0 +1,565 @@
package io.bitsquare.trade.offerer;
import com.google.bitcoin.core.InsufficientMoneyException;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionConfidence;
import com.google.bitcoin.core.Utils;
import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.bank.BankAccount;
import io.bitsquare.btc.BlockChainFacade;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.crypto.CryptoFacade;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.TradeMessage;
import io.bitsquare.msg.TradeMessageType;
import io.bitsquare.msg.listeners.TradeMessageListener;
import io.bitsquare.trade.Contract;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.Trade;
import io.bitsquare.user.User;
import io.bitsquare.util.Utilities;
import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.math.BigInteger;
import static com.google.common.base.Preconditions.checkNotNull;
public class OffererPaymentProtocol
{
private static final Logger log = LoggerFactory.getLogger(OffererPaymentProtocol.class);
private Trade trade;
private Offer offer;
private Contract contract;
private OffererPaymentProtocolListener offererPaymentProtocolListener;
private MessageFacade messageFacade;
private WalletFacade walletFacade;
private BlockChainFacade blockChainFacade;
private CryptoFacade cryptoFacade;
private User user;
private PeerAddress peerAddress;
private boolean isTakeOfferRequested;
private int numberOfSteps = 20;//TODO
private int currentStep = 0;
private String preparedOffererDepositTxAsHex;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public OffererPaymentProtocol(Trade trade,
OffererPaymentProtocolListener offererPaymentProtocolListener,
MessageFacade messageFacade,
WalletFacade walletFacade,
BlockChainFacade blockChainFacade,
CryptoFacade cryptoFacade,
User user)
{
checkNotNull(trade);
checkNotNull(messageFacade);
checkNotNull(walletFacade);
checkNotNull(blockChainFacade);
checkNotNull(cryptoFacade);
checkNotNull(user);
this.trade = trade;
this.offererPaymentProtocolListener = offererPaymentProtocolListener;
this.messageFacade = messageFacade;
this.walletFacade = walletFacade;
this.blockChainFacade = blockChainFacade;
this.cryptoFacade = cryptoFacade;
this.user = user;
offer = trade.getOffer();
messageFacade.addOffererPaymentProtocol(this);
log.debug("0 Constr");
}
//************************************************************************************************
// 1.1-1.2 Takers tasks, we start when we get the take offer request
//************************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Step 1.3
///////////////////////////////////////////////////////////////////////////////////////////
// We got a take offer request and check if the offer is not already reserved for another user.
// If the offer is free we send an accept message.
public void onTakeOfferRequested(PeerAddress sender)
{
log.debug("1.3 onTakeOfferRequested");
peerAddress = sender;
// The offer must be still available.
// TODO check also if offer is still in our offer list, if we canceled recently there might be inconsistency
if (!isTakeOfferRequested)
{
log.debug("1.3 offer not yet requested");
TradeMessageListener listener = new TradeMessageListener()
{
@Override
public void onResult()
{
log.debug("1.3 onTakeOfferRequested ACCEPT_TAKE_OFFER_REQUEST onResult");
// The accept message has arrived at the peer
// We set requested flag and remove the offer from the orderbook
offererPaymentProtocolListener.onProgress(getProgress());
isTakeOfferRequested = true;
try
{
messageFacade.removeOffer(offer);
} catch (IOException e)
{
offererPaymentProtocolListener.onFailure("removeOffer failed " + e.getMessage());
}
// It's the takers turn again, so we are in wait mode....
}
@Override
public void onFailed()
{
log.warn("1.3 onTakeOfferRequested ACCEPT_TAKE_OFFER_REQUEST onFailed");
offererPaymentProtocolListener.onFailure("onTakeOfferRequested onSendTradingMessageFailed");
}
};
offererPaymentProtocolListener.onProgress(getProgress());
// 1.3a Send accept take offer message
TradeMessage tradeMessage = new TradeMessage(TradeMessageType.ACCEPT_TAKE_OFFER_REQUEST, trade.getUid());
messageFacade.sendTradeMessage(peerAddress, tradeMessage, listener);
}
else
{
log.debug("1.3 offer already requested REJECT_TAKE_OFFER_REQUEST");
TradeMessage tradeMessage = new TradeMessage(TradeMessageType.REJECT_TAKE_OFFER_REQUEST, trade.getUid());
TradeMessageListener listener = new TradeMessageListener()
{
@Override
public void onResult()
{
log.debug("1.3 isTakeOfferRequested REJECT_TAKE_OFFER_REQUEST onResult");
// no more steps are needed, as we are not interested in that trade
}
@Override
public void onFailed()
{
log.warn("1.3 isTakeOfferRequested REJECT_TAKE_OFFER_REQUEST onFailed");
// we can ignore that as we are not interested in that trade
}
};
// Offerer reject take offer because it's not available anymore
messageFacade.sendTradeMessage(peerAddress, tradeMessage, listener);
}
}
//************************************************************************************************
// 1.4, 2.1 - 2.2 Takers task, we wait until the next incoming message
//************************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.3
///////////////////////////////////////////////////////////////////////////////////////////
public void onTakeOfferFeePayed(TradeMessage requestTradeMessage)
{
log.debug("2.3 onTakeOfferFeePayed");
trade.setTakeOfferFeeTxID(requestTradeMessage.getTakeOfferFeeTxID());
trade.setTradeAmount(requestTradeMessage.getTradeAmount());
verifyTakeOfferFeePayment(requestTradeMessage);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.4
///////////////////////////////////////////////////////////////////////////////////////////
private void verifyTakeOfferFeePayment(TradeMessage requestTradeMessage)
{
log.debug("2.4 verifyTakeOfferFeePayment");
//TODO just dummy now, will be async
int numOfPeersSeenTx = walletFacade.getNumOfPeersSeenTx(requestTradeMessage.getTakeOfferFeeTxID());
if (numOfPeersSeenTx > 2)
{
createDepositTx(requestTradeMessage);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.5
///////////////////////////////////////////////////////////////////////////////////////////
private void createDepositTx(TradeMessage requestTradeMessage)
{
checkNotNull(requestTradeMessage);
log.debug("2.5 createDepositTx");
BigInteger collateralAmount = trade.getTradeAmount().multiply(BigInteger.valueOf(offer.getCollateral())).divide(BigInteger.valueOf(100));
String offererPubKey = walletFacade.getMultiSigPubKeyAsHex();
String takerPubKey = requestTradeMessage.getTakerMultiSigPubKey();
String arbitratorPubKey = offer.getArbitrator().getPubKey();
checkNotNull(requestTradeMessage.getTakerMultiSigPubKey());
checkNotNull(offererPubKey);
checkNotNull(takerPubKey);
checkNotNull(arbitratorPubKey);
log.debug("2.5 offererCreatesMSTxAndAddPayment");
log.debug("collateralAmount " + collateralAmount);
log.debug("offerer pubkey " + offererPubKey);
log.debug("taker pubkey " + takerPubKey);
log.debug("arbitrator pubkey " + arbitratorPubKey);
try
{
Transaction tx = walletFacade.offererCreatesMSTxAndAddPayment(collateralAmount, offererPubKey, takerPubKey, arbitratorPubKey);
preparedOffererDepositTxAsHex = Utils.bytesToHexString(tx.bitcoinSerialize());
log.debug("2.5 deposit tx created: " + tx);
log.debug("2.5 deposit txAsHex: " + preparedOffererDepositTxAsHex);
sendDepositTxAndDataForContract(preparedOffererDepositTxAsHex, offererPubKey);
} catch (InsufficientMoneyException e)
{
log.warn("2.5 InsufficientMoneyException " + e.getMessage());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.6
///////////////////////////////////////////////////////////////////////////////////////////
private void sendDepositTxAndDataForContract(String preparedOffererDepositTxAsHex, String offererPubKey)
{
log.debug("2.6 sendDepositTxAndDataForContract");
// Send all the requested data
TradeMessageListener listener = new TradeMessageListener()
{
@Override
public void onResult()
{
log.debug("2.6 sendDepositTxAndDataForContract onResult");
// Message arrived at taker
offererPaymentProtocolListener.onProgress(getProgress());
// We wait until we get the signed tx back
}
@Override
public void onFailed()
{
log.debug("2.6 sendDepositTxAndDataForContract onFailed");
offererPaymentProtocolListener.onFailure("sendDepositTxAndDataForContract onSendTradingMessageFailed");
}
};
offererPaymentProtocolListener.onProgress(getProgress());
BankAccount bankAccount = user.getBankAccount(offer.getBankAccountUID());
String accountID = user.getAccountID();
checkNotNull(trade.getUid());
checkNotNull(bankAccount);
checkNotNull(accountID);
checkNotNull(preparedOffererDepositTxAsHex);
TradeMessage tradeMessage = new TradeMessage(TradeMessageType.REQUEST_TAKER_DEPOSIT_PAYMENT, trade.getUid(), bankAccount, accountID, offererPubKey, preparedOffererDepositTxAsHex);
log.debug("2.6 sendTradingMessage");
messageFacade.sendTradeMessage(peerAddress, tradeMessage, listener);
}
//************************************************************************************************
// 2.7 - 2.11 Takers task, we wait until the next incoming message
//************************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.1 Incoming msg from taker
///////////////////////////////////////////////////////////////////////////////////////////
public void onDepositTxReadyForPublication(TradeMessage requestTradeMessage)
{
log.debug("3.1 onDepositTxReadyForPublication");
verifyTaker(requestTradeMessage);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.2 Verify offerers account registration and against the blacklist
///////////////////////////////////////////////////////////////////////////////////////////
private void verifyTaker(TradeMessage requestTradeMessage)
{
log.debug("3.2 verifyTaker");
log.debug("3.2.1 verifyAccountRegistration");
if (blockChainFacade.verifyAccountRegistration())
{
log.debug("3.2.2 isAccountBlackListed");
if (!blockChainFacade.isAccountBlackListed(requestTradeMessage.getAccountID(), requestTradeMessage.getBankAccount()))
{
verifyAndSignContract(requestTradeMessage);
}
else
{
offererPaymentProtocolListener.onFailure("Taker is blacklisted.");
}
}
else
{
offererPaymentProtocolListener.onFailure("Takers account registration is invalid.");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.3 Verify and sign the contract
///////////////////////////////////////////////////////////////////////////////////////////
private void verifyAndSignContract(TradeMessage requestTradeMessage)
{
contract = new Contract(offer,
trade.getTradeAmount(),
trade.getTakeOfferFeeTxID(),
user.getAccountID(),
requestTradeMessage.getAccountID(),
user.getCurrentBankAccount(),
requestTradeMessage.getBankAccount(),
offer.getMessagePubKeyAsHex(),
requestTradeMessage.getTakerMessagePubKey());
log.debug("3.3 offerer contract created: " + contract.toString());
String contractAsJson = Utilities.objectToJson(contract);
log.debug("3.3 contractAsJson: " + contractAsJson);
log.debug("3.3 requestTradingMessage.getContractAsJson(): " + requestTradeMessage.getContractAsJson());
// TODO generic json creates too complex object, at least the PublicKeys need to be removed
boolean isEqual = contractAsJson.equals(requestTradeMessage.getContractAsJson());
log.debug("3.3 does json match?: " + isEqual);
/* if (contractAsJson.equals(requestTradingMessage.getContractAsJson()))
{ */
String signature = cryptoFacade.signContract(walletFacade.getAccountRegistrationKey(), contractAsJson);
trade.setContract(contract);
trade.setContractAsJson(contractAsJson);
trade.setContractTakerSignature(signature);
log.debug("3.3 signature: " + signature);
signAndPublishDepositTx(requestTradeMessage);
/* }
else
{
log.error("3.3 verifyContract failed");
} */
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.4 Sign and publish the deposit tx
///////////////////////////////////////////////////////////////////////////////////////////
private void signAndPublishDepositTx(TradeMessage requestTradeMessage)
{
log.debug("3.4 signAndPublishDepositTx");
String signedTakerDepositTxAsHex = requestTradeMessage.getSignedTakerDepositTxAsHex();
String txConnOutAsHex = requestTradeMessage.getTxConnOutAsHex();
String txScriptSigAsHex = requestTradeMessage.getTxScriptSigAsHex();
FutureCallback<Transaction> callback = new FutureCallback<Transaction>()
{
@Override
public void onSuccess(Transaction transaction)
{
log.info("3.4 signAndPublishDepositTx offererSignAndSendTx onSuccess:" + transaction.toString());
}
@Override
public void onFailure(Throwable t)
{
log.error("3.4 signAndPublishDepositTx offererSignAndSendTx onFailure:" + t.getMessage());
}
};
try
{
log.debug("3.4 offererSignAndSendTx");
Transaction transaction = walletFacade.offererSignAndSendTx(preparedOffererDepositTxAsHex, signedTakerDepositTxAsHex, txConnOutAsHex, txScriptSigAsHex, callback);
String txID = transaction.getHashAsString();
trade.setDepositTransaction(transaction);
log.debug("3.4 deposit tx published: " + transaction);
log.debug("3.4 deposit txID: " + txID);
sendDepositTxIdToTaker(transaction);
} catch (Exception e)
{
log.error("3.4 error at walletFacade.offererSignAndSendTx: " + e.getMessage());
e.getStackTrace();// Could not understand form of connected output script: RETURN
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.5 Send tx id of published deposit tx to taker
///////////////////////////////////////////////////////////////////////////////////////////
private void sendDepositTxIdToTaker(Transaction transaction)
{
log.debug("3.5 sendDepositTxIdToTaker");
TradeMessageListener listener = new TradeMessageListener()
{
@Override
public void onResult()
{
log.debug("3.5 sendDepositTxIdToTaker DEPOSIT_TX_PUBLISHED onResult");
offererPaymentProtocolListener.onProgress(getProgress());
}
@Override
public void onFailed()
{
log.warn("3.5 sendDepositTxIdToTaker DEPOSIT_TX_PUBLISHED onFailed");
offererPaymentProtocolListener.onFailure("sendDepositTxAndDataForContract onSendTradingMessageFailed");
}
};
TradeMessage tradeMessage = new TradeMessage(TradeMessageType.DEPOSIT_TX_PUBLISHED, trade.getUid(), transaction.getHashAsString());
log.debug("3.5 sendTradingMessage");
messageFacade.sendTradeMessage(peerAddress, tradeMessage, listener);
// wait for at least 1 confirmation, then pay Fiat
offererPaymentProtocolListener.onDepositTxPublished(tradeMessage.getDepositTxID());
setupListenerForBlockChainConfirmation(transaction);
}
//************************************************************************************************
// 3.6 Taker got informed, but no action from his side required.
//************************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.7 We setup a listener for block chain confirmation
///////////////////////////////////////////////////////////////////////////////////////////
private void setupListenerForBlockChainConfirmation(Transaction transaction)
{
log.debug("3.7 setupListenerForBlockChainConfirmation");
// for testing
// TODO listeners mut be done with blockchain not wallet
onDepositTxConfirmedInBlockchain();
transaction.getConfidence().addEventListener(new TransactionConfidence.Listener()
{
@Override
public void onConfidenceChanged(Transaction tx, ChangeReason reason)
{
log.info("onConfidenceChanged reason = " + reason);
log.info("onConfidenceChanged confidence = " + tx.getConfidence().toString());
if (reason == ChangeReason.SEEN_PEERS)
{
updateConfirmation(tx.getConfidence());
log.debug("### confidence.numBroadcastPeers() " + tx.getConfidence().numBroadcastPeers());
//todo just for testing now, dont like to wait so long...
if (tx.getConfidence().numBroadcastPeers() > 3)
{
onDepositTxConfirmedInBlockchain();
transaction.getConfidence().removeEventListener(this);
}
}
if (reason == ChangeReason.TYPE && tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
{
onDepositTxConfirmedInBlockchain();
transaction.getConfidence().removeEventListener(this);
}
}
}
);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.8 We check if the block chain confirmation is >= 1
///////////////////////////////////////////////////////////////////////////////////////////
private void updateConfirmation(TransactionConfidence confidence)
{
log.debug("3.8 updateConfirmation " + confidence.toString());
offererPaymentProtocolListener.onDepositTxConfirmedUpdate(confidence);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.9 Blockchain confirmation received, so tell user he should start bank transfer
///////////////////////////////////////////////////////////////////////////////////////////
private void onDepositTxConfirmedInBlockchain()
{
log.debug("3.9 readyForBankTransfer");
offererPaymentProtocolListener.onDepositTxConfirmedInBlockchain();
}
//************************************************************************************************
// Offerer need to start bank tx, after he done it he call the next step
//************************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.10 User clicked the "bank transfer inited" button, so we tell the peer that we started the bank tx
///////////////////////////////////////////////////////////////////////////////////////////
public void bankTransferInited()
{
log.debug("3.10 bankTransferInited");
TradeMessageListener listener = new TradeMessageListener()
{
@Override
public void onResult()
{
log.debug("3.10 bankTransferInited BANK_TX_INITED onResult");
log.debug("########## LAST STEP OFFERER FOR FIRST PART");
offererPaymentProtocolListener.onProgress(getProgress());
}
@Override
public void onFailed()
{
log.warn("3.10 bankTransferInited BANK_TX_INITED onFailed");
offererPaymentProtocolListener.onFailure("bankTransferInited BANK_TX_INITED");
}
};
TradeMessage tradeMessage = new TradeMessage(TradeMessageType.BANK_TX_INITED, trade.getUid());
log.debug("3.10 sendTradingMessage BANK_TX_INITED");
messageFacade.sendTradeMessage(peerAddress, tradeMessage, listener);
}
//************************************************************************************************
// We wait until taker has received the money on his bank account, that might take a while.
//************************************************************************************************
private double getProgress()
{
currentStep++;
return (double) currentStep / (double) numberOfSteps;
}
}

View file

@ -0,0 +1,16 @@
package io.bitsquare.trade.offerer;
import com.google.bitcoin.core.TransactionConfidence;
public interface OffererPaymentProtocolListener
{
void onProgress(double progress);
void onFailure(String failureMessage);
void onDepositTxPublished(String depositTxID);
void onDepositTxConfirmedInBlockchain();
void onDepositTxConfirmedUpdate(TransactionConfidence confidence);
}

View file

@ -1,63 +0,0 @@
package io.bitsquare.trade.orderbook;
import com.google.inject.Inject;
import io.bitsquare.btc.BtcFormatter;
import io.bitsquare.gui.market.orderbook.OrderBookListItem;
import io.bitsquare.gui.util.Converter;
import io.bitsquare.gui.util.Formatter;
import io.bitsquare.settings.Settings;
import io.bitsquare.trade.Direction;
import io.bitsquare.trade.Offer;
import io.bitsquare.user.User;
import io.bitsquare.util.MockData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;
public class MockOrderBook extends OrderBook
{
private static final Logger log = LoggerFactory.getLogger(MockOrderBook.class);
@Inject
public MockOrderBook(Settings settings, User user)
{
super(settings, user);
for (int i = 0; i < 1000; i++)
{
allOffers.add(new OrderBookListItem(getOffer()));
}
}
private Offer getOffer()
{
double amount = Math.random() * 10 + 0.1;
amount = Converter.stringToDouble(Formatter.formatAmount(amount));
double minAmount = Math.random() * amount;
minAmount = Converter.stringToDouble(Formatter.formatAmount(minAmount));
Direction direction = Direction.BUY;
double price = 500 + Math.random() * 50;
if (Math.random() > 0.5)
{
direction = Direction.SELL;
price = 500 - Math.random() * 50;
}
double collateral = 0.1;// Math.random() * 20 + 0.1;
Offer offer = new Offer("mjbxLbuVpU1cNXLJbrJZyirYwweoRPVVTj",
UUID.randomUUID().toString(),
direction,
price,
BtcFormatter.doubleValueToSatoshis(amount),
BtcFormatter.doubleValueToSatoshis(minAmount),
MockData.getRandomBankTransferTypeEnums().get(0),
MockData.getRandomCurrencies().get(0),
MockData.getRandomLocales().get(0),
MockData.getRandomArbitrators().get(0),
collateral,
MockData.getRandomLocales(),
MockData.getRandomLocales());
return offer;
}
}

View file

@ -3,28 +3,37 @@ package io.bitsquare.trade.orderbook;
import com.google.inject.Inject;
import io.bitsquare.bank.BankAccount;
import io.bitsquare.gui.market.orderbook.OrderBookListItem;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.listeners.OrderBookListener;
import io.bitsquare.settings.Settings;
import io.bitsquare.trade.Direction;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.Trading;
import io.bitsquare.user.Arbitrator;
import io.bitsquare.user.User;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import net.tomp2p.peers.Number160;
import net.tomp2p.storage.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
public class OrderBook
public class OrderBook implements OrderBookListener
{
private static final Logger log = LoggerFactory.getLogger(OrderBook.class);
private Settings settings;
private User user;
private MessageFacade messageFacade;
private Trading trading;
protected ObservableList<OrderBookListItem> allOffers = FXCollections.observableArrayList();
private FilteredList<OrderBookListItem> filteredList = new FilteredList<>(allOffers);
@ -37,10 +46,12 @@ public class OrderBook
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public OrderBook(Settings settings, User user)
public OrderBook(Settings settings, User user, MessageFacade messageFacade, Trading trading)
{
this.settings = settings;
this.user = user;
this.messageFacade = messageFacade;
this.trading = trading;
}
@ -48,7 +59,29 @@ public class OrderBook
// Public API
///////////////////////////////////////////////////////////////////////////////////////////
public void updateFilter(OrderBookFilter orderBookFilter)
public void init()
{
messageFacade.addMessageListener(this);
}
public void cleanup()
{
messageFacade.removeMessageListener(this);
}
public void loadOffers()
{
messageFacade.getOffers(user.getCurrentBankAccount().getCurrency().getCurrencyCode());
}
public void removeOffer(Offer offer) throws IOException
{
trading.removeOffer(offer);
messageFacade.removeOffer(offer);
}
public void applyFilter(OrderBookFilter orderBookFilter)
{
filteredList.setPredicate(new Predicate<OrderBookListItem>()
{
@ -73,7 +106,7 @@ public class OrderBook
// One of the supported languages from the settings must match one of the offer languages (n to n)
boolean languageResult = languagesInList(settings.getAcceptedLanguageLocales(), offer.getAcceptedLanguageLocales());
// Apply updateFilter only if there is a valid value set
// Apply applyFilter only if there is a valid value set
// The requested amount must be lower or equal then the offer amount
boolean amountResult = true;
if (orderBookFilter.getAmount() > 0)
@ -82,7 +115,7 @@ public class OrderBook
// The requested trade direction must be opposite of the offerList trade direction
boolean directionResult = !orderBookFilter.getDirection().equals(offer.getDirection());
// Apply updateFilter only if there is a valid value set
// Apply applyFilter only if there is a valid value set
boolean priceResult = true;
if (orderBookFilter.getPrice() > 0)
{
@ -104,7 +137,7 @@ public class OrderBook
&& priceResult
&& arbitratorResult;
/*
/*
log.debug("result = " + result +
", currencyResult = " + currencyResult +
", countryResult = " + countryResult +
@ -131,12 +164,93 @@ public class OrderBook
", offer.getPrice() = " + offer.getPrice());
log.debug("offer.getArbitrator() = " + offer.getArbitrator() +
", settings.getAcceptedArbitrators() = " + settings.getAcceptedArbitrators());
*/
*/
return result;
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Interface implementation: MessageListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onOfferAdded(Data offerData, boolean success)
{
try
{
Object offerDataObject = offerData.getObject();
if (offerDataObject instanceof Offer && offerDataObject != null)
{
Offer offer = (Offer) offerDataObject;
allOffers.add(new OrderBookListItem(offer));
}
} catch (ClassNotFoundException | IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
@Override
public void onOffersReceived(Map<Number160, Data> dataMap, boolean success)
{
if (success && dataMap != null)
{
allOffers.clear();
for (Data offerData : dataMap.values())
{
try
{
Object offerDataObject = offerData.getObject();
if (offerDataObject instanceof Offer && offerDataObject != null)
{
Offer offer = (Offer) offerDataObject;
OrderBookListItem orderBookListItem = new OrderBookListItem(offer);
allOffers.add(orderBookListItem);
}
} catch (ClassNotFoundException | IOException e)
{
e.printStackTrace();
}
}
}
else
{
allOffers.clear();
}
}
@Override
public void onOfferRemoved(Data offerData, boolean success)
{
if (success)
{
try
{
Object offerDataObject = offerData.getObject();
if (offerDataObject instanceof Offer && offerDataObject != null)
{
Offer offer = (Offer) offerDataObject;
allOffers.removeIf(new Predicate<OrderBookListItem>()
{
@Override
public boolean test(OrderBookListItem orderBookListItem)
{
return orderBookListItem.getOffer().getUid().equals(offer.getUid());
}
});
}
} catch (ClassNotFoundException | IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
else
{
log.warn("onOfferRemoved failed");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
@ -147,6 +261,7 @@ public class OrderBook
return offerList;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private Methods
///////////////////////////////////////////////////////////////////////////////////////////
@ -182,7 +297,7 @@ public class OrderBook
{
try
{
if (arbitrator.getUID().equals(arbitratorToMatch.getUID()))
if (arbitrator.getUid().equals(arbitratorToMatch.getUid()))
return true;
} catch (Exception e)
{
@ -192,4 +307,6 @@ public class OrderBook
}
return false;
}
}

View file

@ -5,7 +5,7 @@ import javafx.beans.property.SimpleBooleanProperty;
public class OrderBookFilter
{
transient private final SimpleBooleanProperty changedProperty = new SimpleBooleanProperty();
transient private final SimpleBooleanProperty directionChangedProperty = new SimpleBooleanProperty();
private double price;
private double amount;
@ -19,19 +19,17 @@ public class OrderBookFilter
public void setAmount(double amount)
{
this.amount = amount;
triggerChange();
}
public void setPrice(double price)
{
this.price = price;
triggerChange();
}
public void setDirection(Direction direction)
{
this.direction = direction;
triggerChange();
directionChangedProperty.set(!directionChangedProperty.get());
}
@ -54,19 +52,10 @@ public class OrderBookFilter
return price;
}
public SimpleBooleanProperty getChangedProperty()
public SimpleBooleanProperty getDirectionChangedProperty()
{
return changedProperty;
return directionChangedProperty;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private Methods
///////////////////////////////////////////////////////////////////////////////////////////
private void triggerChange()
{
changedProperty.set(!changedProperty.get());
}
}

View file

@ -0,0 +1,501 @@
package io.bitsquare.trade.taker;
import com.google.bitcoin.core.AddressFormatException;
import com.google.bitcoin.core.InsufficientMoneyException;
import com.google.bitcoin.core.Transaction;
import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.bank.BankAccount;
import io.bitsquare.btc.BlockChainFacade;
import io.bitsquare.btc.Fees;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.crypto.CryptoFacade;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.TradeMessage;
import io.bitsquare.msg.TradeMessageType;
import io.bitsquare.msg.listeners.AddressLookupListener;
import io.bitsquare.msg.listeners.TradeMessageListener;
import io.bitsquare.trade.Contract;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.Trade;
import io.bitsquare.user.User;
import io.bitsquare.util.Utilities;
import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.util.concurrent.ExecutionException;
import static com.google.common.base.Preconditions.checkNotNull;
public class TakerPaymentProtocol
{
private static final Logger log = LoggerFactory.getLogger(TakerPaymentProtocol.class);
private Trade trade;
private Offer offer;
private Contract contract;
private TakerPaymentProtocolListener takerPaymentProtocolListener;
private MessageFacade messageFacade;
private WalletFacade walletFacade;
private BlockChainFacade blockChainFacade;
private CryptoFacade cryptoFacade;
private User user;
private PeerAddress peerAddress;
private boolean isTakeOfferRequested;
private int numberOfSteps = 6;//TODO
private int currentStep = 0;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public TakerPaymentProtocol(Trade trade,
TakerPaymentProtocolListener takerPaymentProtocolListener,
MessageFacade messageFacade,
WalletFacade walletFacade,
BlockChainFacade blockChainFacade,
CryptoFacade cryptoFacade,
User user)
{
this.trade = trade;
this.takerPaymentProtocolListener = takerPaymentProtocolListener;
this.messageFacade = messageFacade;
this.walletFacade = walletFacade;
this.blockChainFacade = blockChainFacade;
this.cryptoFacade = cryptoFacade;
this.user = user;
offer = trade.getOffer();
messageFacade.addTakerPaymentProtocol(this);
}
public void takeOffer()
{
log.debug("1 takeOffer");
findPeerAddress();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 1.1
///////////////////////////////////////////////////////////////////////////////////////////
private void findPeerAddress()
{
log.debug("1.1 findPeerAddress");
AddressLookupListener addressLookupListener = new AddressLookupListener()
{
@Override
public void onResult(PeerAddress address)
{
log.debug("1.1 findPeerAddress onResult");
// We got the peer address
peerAddress = address;
takerPaymentProtocolListener.onProgress(getProgress());
// next
requestTakeOffer();
}
@Override
public void onFailed()
{
log.debug("1.1 findPeerAddress onFailed");
takerPaymentProtocolListener.onFailure("onGetPeerAddressFailed");
}
};
takerPaymentProtocolListener.onProgress(getProgress());
// Request the peers address from the DHT
messageFacade.getPeerAddress(offer.getMessagePubKeyAsHex(), addressLookupListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 1.2
///////////////////////////////////////////////////////////////////////////////////////////
private void requestTakeOffer()
{
log.debug("1.2 requestTakeOffer");
TradeMessageListener listener = new TradeMessageListener()
{
@Override
public void onResult()
{
log.debug("1.2 requestTakeOffer onResult");
// Our message has arrived
// We don't know yet if the offerer has accepted
// the take request (if offer is not already reserved for another user)
// We await for an incoming message from the offerer with the accept msg
takerPaymentProtocolListener.onProgress(getProgress());
// Wait for message from offerer...
}
@Override
public void onFailed()
{
log.debug("1.2 requestTakeOffer onFailed");
takerPaymentProtocolListener.onFailure("sendTakeOfferRequest onSendTradingMessageFailed");
}
};
takerPaymentProtocolListener.onProgress(getProgress());
// Send the take offer request
TradeMessage tradeMessage = new TradeMessage(TradeMessageType.REQUEST_TAKE_OFFER, trade.getUid());
messageFacade.sendTradeMessage(peerAddress, tradeMessage, listener);
}
//************************************************************************************************
// 1.3. Offerers tasks, we are in waiting mode
//************************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Step 1.4
///////////////////////////////////////////////////////////////////////////////////////////
// 1.4a offerer has accepted the take offer request. Move on to step 2.
public void onTakeOfferRequestAccepted()
{
log.debug("1.4a onTakeOfferRequestAccepted");
takerPaymentProtocolListener.onProgress(getProgress());
payOfferFee(trade);
}
// 1.4b Offerer has rejected the take offer request. The UI controller will handle the case.
public void onTakeOfferRequestRejected()
{
log.debug("1.4b onTakeOfferRequestRejected");
takerPaymentProtocolListener.onProgress(getProgress());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.1
///////////////////////////////////////////////////////////////////////////////////////////
private void payOfferFee(Trade trade)
{
log.debug("2.1 payOfferFee");
FutureCallback<Transaction> callback = new FutureCallback<Transaction>()
{
@Override
public void onSuccess(Transaction transaction)
{
log.debug("2.1 payOfferFee onSuccess");
log.info("sendResult onSuccess txid:" + transaction.getHashAsString());
// Offer fee payed successfully.
trade.setTakeOfferFeeTxID(transaction.getHashAsString());
takerPaymentProtocolListener.onProgress(getProgress());
// move on
sendTakerOfferFeeTxID(transaction.getHashAsString());
}
@Override
public void onFailure(Throwable t)
{
log.debug("2.1 payOfferFee onFailure");
takerPaymentProtocolListener.onFailure("payOfferFee onFailure " + t.getMessage());
}
};
try
{
// Pay the offer fee
takerPaymentProtocolListener.onProgress(getProgress());
walletFacade.payFee(Fees.OFFER_TAKER_FEE, callback);
} catch (InsufficientMoneyException e)
{
takerPaymentProtocolListener.onProgress(getProgress());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.2
///////////////////////////////////////////////////////////////////////////////////////////
// Request peers account details. At that moment private data (bank account nr.) of the offerer get visible to the taker.
// We send also the tx id of the fee payment, so the offerer can confirm that the payment is seen in the network
// 0 block confirmation is acceptable for the fee to not block the process
// The offerer will wait until a minimum of peers seen the tx before sending his data.
// We also get the multisig tx delivered
private void sendTakerOfferFeeTxID(String takeOfferFeeTxID)
{
log.debug("2.2 sendTakerOfferFeeTxID");
TradeMessageListener listener = new TradeMessageListener()
{
@Override
public void onResult()
{
log.debug("2.2 sendTakerOfferFeeTxID onResult");
// Message has arrived
takerPaymentProtocolListener.onProgress(getProgress());
// We wait until the offerer send us the data
}
@Override
public void onFailed()
{
log.debug("2.2 sendTakerOfferFeeTxID onFailed");
takerPaymentProtocolListener.onFailure("requestAccountDetails onSendTradingMessageFailed");
}
};
takerPaymentProtocolListener.onProgress(getProgress());
// 2.3. send request for the account details and send fee tx id so offerer can verify that the fee has been paid.
TradeMessage tradeMessage = new TradeMessage(TradeMessageType.TAKE_OFFER_FEE_PAYED,
trade.getUid(),
trade.getTradeAmount(),
takeOfferFeeTxID,
walletFacade.getMultiSigPubKeyAsHex());
messageFacade.sendTradeMessage(peerAddress, tradeMessage, listener);
}
//************************************************************************************************
// 2.3 - 2.6 Offerers tasks, we are in waiting mode
//************************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.7 Incoming msg from offerer
///////////////////////////////////////////////////////////////////////////////////////////
public void onTakerDepositPaymentRequested(TradeMessage requestTradeMessage)
{
log.debug("2.7 onTakerDepositPaymentRequested");
verifyOfferer(requestTradeMessage);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.8 Verify offerers account registration and against the blacklist
///////////////////////////////////////////////////////////////////////////////////////////
private void verifyOfferer(TradeMessage requestTradeMessage)
{
log.debug("2.8 verifyOfferer");
log.debug("2.8.1 verifyAccountRegistration");
if (blockChainFacade.verifyAccountRegistration())
{
log.debug("2.8.2 isAccountBlackListed");
if (!blockChainFacade.isAccountBlackListed(requestTradeMessage.getAccountID(), requestTradeMessage.getBankAccount()))
{
createAndSignContract(requestTradeMessage);
}
else
{
takerPaymentProtocolListener.onFailure("Offerer is blacklisted.");
}
}
else
{
takerPaymentProtocolListener.onFailure("Offerers account registration is invalid.");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.9 Create and sign the contract
///////////////////////////////////////////////////////////////////////////////////////////
private void createAndSignContract(TradeMessage requestTradeMessage)
{
log.debug("2.9 createAndSignContract");
checkNotNull(offer);
checkNotNull(trade.getTradeAmount());
checkNotNull(trade.getTakeOfferFeeTxID());
checkNotNull(requestTradeMessage.getAccountID());
checkNotNull(user.getAccountID());
checkNotNull(requestTradeMessage.getBankAccount());
checkNotNull(user.getCurrentBankAccount());
checkNotNull(user.getMessagePubKeyAsHex());
contract = new Contract(offer,
trade.getTradeAmount(),
trade.getTakeOfferFeeTxID(),
requestTradeMessage.getAccountID(),
user.getAccountID(),
requestTradeMessage.getBankAccount(),
user.getCurrentBankAccount(),
offer.getMessagePubKeyAsHex(),
user.getMessagePubKeyAsHex()
);
log.debug("2.9 contract created: " + contract.toString());
String contractAsJson = Utilities.objectToJson(contract);
String signature = cryptoFacade.signContract(walletFacade.getAccountRegistrationKey(), contractAsJson);
//log.debug("2.9 contractAsJson: " + contractAsJson);
log.debug("2.9 contract signature: " + signature);
trade.setContract(contract);
trade.setContractAsJson(contractAsJson);
trade.setContractTakerSignature(signature);
payDeposit(requestTradeMessage);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.10 Pay in the funds to the deposit tx and sign it
///////////////////////////////////////////////////////////////////////////////////////////
private void payDeposit(TradeMessage requestTradeMessage)
{
log.debug("2.10 payDeposit");
BigInteger collateralAmount = trade.getTradeAmount().multiply(BigInteger.valueOf(offer.getCollateral())).divide(BigInteger.valueOf(100));
BigInteger takerAmount = trade.getTradeAmount().add(collateralAmount);
BigInteger msOutputAmount = trade.getTradeAmount().add(collateralAmount).add(collateralAmount);
String offererPubKey = requestTradeMessage.getOffererPubKey();
String takerPubKey = walletFacade.getMultiSigPubKeyAsHex();
String arbitratorPubKey = offer.getArbitrator().getPubKey();
String preparedOffererDepositTxAsHex = requestTradeMessage.getPreparedOffererDepositTxAsHex();
checkNotNull(takerAmount);
checkNotNull(msOutputAmount);
checkNotNull(offererPubKey);
checkNotNull(takerPubKey);
checkNotNull(arbitratorPubKey);
checkNotNull(preparedOffererDepositTxAsHex);
log.debug("2.10 offererCreatesMSTxAndAddPayment");
log.debug("takerAmount " + takerAmount);
log.debug("msOutputAmount " + msOutputAmount);
log.debug("offerer pubkey " + offererPubKey);
log.debug("taker pubkey " + takerPubKey);
log.debug("arbitrator pubkey " + arbitratorPubKey);
log.debug("preparedOffererDepositTxAsHex " + preparedOffererDepositTxAsHex);
try
{
Transaction signedTakerDepositTx = walletFacade.takerAddPaymentAndSign(takerAmount, msOutputAmount, offererPubKey, takerPubKey, arbitratorPubKey, preparedOffererDepositTxAsHex);
log.debug("2.10 deposit tx created: " + signedTakerDepositTx);
sendSignedTakerDepositTxAsHex(signedTakerDepositTx);
} catch (InterruptedException | AddressFormatException | ExecutionException | InsufficientMoneyException e)
{
log.error("2.10 error at walletFacade.takerAddPaymentAndSign: " + e.getMessage());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Step 2.11 Send the tx to the offerer
///////////////////////////////////////////////////////////////////////////////////////////
private void sendSignedTakerDepositTxAsHex(Transaction signedTakerDepositTx)
{
log.debug("2.11 sendSignedTakerDepositTxAsHex");
TradeMessageListener listener = new TradeMessageListener()
{
@Override
public void onResult()
{
log.debug("2.11 sendSignedTakerDepositTxAsHex REQUEST_TAKER_DEPOSIT_PAYMENT onResult");
// Message arrived at taker
takerPaymentProtocolListener.onProgress(getProgress());
}
@Override
public void onFailed()
{
log.debug("2.11 sendSignedTakerDepositTxAsHex REQUEST_TAKER_DEPOSIT_PAYMENT onFailed");
takerPaymentProtocolListener.onFailure("sendSignedTakerDepositTxAsHex REQUEST_TAKER_DEPOSIT_PAYMENT onFailed");
}
};
takerPaymentProtocolListener.onProgress(getProgress());
BankAccount bankAccount = user.getBankAccount(offer.getBankAccountUID());
String accountID = user.getAccountID();
String messagePubKey = user.getMessagePubKeyAsHex();
String contractAsJson = trade.getContractAsJson();
String signature = trade.getTakerSignature();
String signedTakerDepositTxAsHex = com.google.bitcoin.core.Utils.bytesToHexString(signedTakerDepositTx.bitcoinSerialize());
String txScriptSigAsHex = com.google.bitcoin.core.Utils.bytesToHexString(signedTakerDepositTx.getInput(1).getScriptBytes());
String txConnOutAsHex = com.google.bitcoin.core.Utils.bytesToHexString(signedTakerDepositTx.getInput(1).getConnectedOutput().getParentTransaction().bitcoinSerialize());
log.debug("2.10 deposit txAsHex: " + signedTakerDepositTxAsHex);
log.debug("2.10 txScriptSigAsHex: " + txScriptSigAsHex);
log.debug("2.10 txConnOutAsHex: " + txConnOutAsHex);
TradeMessage tradeMessage = new TradeMessage(TradeMessageType.REQUEST_OFFERER_DEPOSIT_PUBLICATION,
trade.getUid(),
bankAccount,
accountID,
messagePubKey,
signedTakerDepositTxAsHex,
txScriptSigAsHex,
txConnOutAsHex,
contractAsJson,
signature);
log.debug("2.11 sendTradingMessage");
messageFacade.sendTradeMessage(peerAddress, tradeMessage, listener);
}
//************************************************************************************************
// 3.1 - 3.5 Offerers tasks, we are in waiting mode
//************************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.6 Incoming msg from offerer
///////////////////////////////////////////////////////////////////////////////////////////
public void onDepositTxPublished(TradeMessage tradeMessage)
{
log.debug("3.6 DepositTxID received: " + tradeMessage.getDepositTxID());
takerPaymentProtocolListener.onProgress(getProgress());
takerPaymentProtocolListener.onDepositTxPublished(tradeMessage.getDepositTxID());
}
//************************************************************************************************
// 3.7-3.10 Offerers tasks, we are in waiting mode
//************************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
// Step 3.11 Incoming msg from offerer
///////////////////////////////////////////////////////////////////////////////////////////
public void onBankTransferInited()
{
log.debug("3.11 Bank transfer inited msg received");
log.debug("########## LAST STEP TAKER FOR FIRST PART");
takerPaymentProtocolListener.onBankTransferInited();
}
//************************************************************************************************
// Taker will check periodically his bank account until he received the money. That might take a while...
//************************************************************************************************
private double getProgress()
{
currentStep++;
return (double) currentStep / (double) numberOfSteps;
}
}

View file

@ -0,0 +1,12 @@
package io.bitsquare.trade.taker;
public interface TakerPaymentProtocolListener
{
void onProgress(double progress);
void onFailure(String failureMessage);
void onDepositTxPublished(String depositTxID);
void onBankTransferInited();
}

View file

@ -9,25 +9,23 @@ public class Arbitrator implements Serializable
private String name;
private String pubKey;
private String messageID;
private String messagePubKeyAsHex;
private String url;
private double baseFeePercent;
private double arbitrationFeePercent;
private BigInteger minArbitrationFee;
private int baseFeePercent; // in percent int 1 for 1%
private int arbitrationFeePercent;
private BigInteger minArbitrationAmount; // in Satoshis
private String uid;
public Arbitrator(String uid, String name, String pubKey, String messageID, String url, double baseFeePercent, double arbitrationFeePercent, BigInteger minArbitrationFee)
public Arbitrator(String uid, String name, String pubKey, String messagePubKeyAsHex, String url, int baseFeePercent, int arbitrationFeePercent, BigInteger minArbitrationAmount)
{
this.uid = uid;
this.name = name;
this.pubKey = pubKey;
this.messageID = messageID;
this.messagePubKeyAsHex = messagePubKeyAsHex;
this.url = url;
this.baseFeePercent = baseFeePercent;
this.arbitrationFeePercent = arbitrationFeePercent;
this.minArbitrationFee = minArbitrationFee;
this.minArbitrationAmount = minArbitrationAmount;
}
@ -45,9 +43,9 @@ public class Arbitrator implements Serializable
return pubKey;
}
public String getMessageID()
public String getMessagePubKeyAsHex()
{
return messageID;
return messagePubKeyAsHex;
}
public String getUrl()
@ -55,23 +53,38 @@ public class Arbitrator implements Serializable
return url;
}
public BigInteger getMinArbitrationFee()
public BigInteger getMinArbitrationAmount()
{
return minArbitrationFee;
return minArbitrationAmount;
}
public double getBaseFeePercent()
public int getBaseFeePercent()
{
return baseFeePercent;
}
public double getArbitrationFeePercent()
public int getArbitrationFeePercent()
{
return arbitrationFeePercent;
}
public Object getUID()
public Object getUid()
{
return uid;
}
@Override
public String toString()
{
return "Arbitrator{" +
"name='" + name + '\'' +
", pubKey='" + pubKey + '\'' +
", messagePubKeyAsHex='" + messagePubKeyAsHex + '\'' +
", url='" + url + '\'' +
", baseFeePercent=" + baseFeePercent +
", arbitrationFeePercent=" + arbitrationFeePercent +
", minArbitrationAmount=" + minArbitrationAmount +
", uid='" + uid + '\'' +
'}';
}
}

View file

@ -12,15 +12,14 @@ public class User implements Serializable
{
private static final long serialVersionUID = 7409078808248518638L;
transient private final SimpleBooleanProperty changedProperty = new SimpleBooleanProperty();
transient private final SimpleBooleanProperty bankAccountChangedProperty = new SimpleBooleanProperty();
private String accountID;
private String messageID;
private String messagePubKeyAsHex;
private boolean isOnline;
private List<BankAccount> bankAccounts = new ArrayList<>();
private BankAccount currentBankAccount = null;
public User()
{
}
@ -35,7 +34,7 @@ public class User implements Serializable
if (savedUser != null)
{
accountID = savedUser.getAccountID();
messageID = savedUser.getMessageID();
messagePubKeyAsHex = savedUser.getMessagePubKeyAsHex();
isOnline = savedUser.getIsOnline();
bankAccounts = savedUser.getBankAccounts();
currentBankAccount = savedUser.getCurrentBankAccount();
@ -53,9 +52,9 @@ public class User implements Serializable
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setMessageID(String messageID)
public void setMessagePubKeyAsHex(String messageID)
{
this.messageID = messageID;
this.messagePubKeyAsHex = messageID;
}
public void setAccountID(String accountID)
@ -71,7 +70,7 @@ public class User implements Serializable
public void setCurrentBankAccount(BankAccount currentBankAccount)
{
this.currentBankAccount = currentBankAccount;
triggerChange();
bankAccountChangedProperty.set(!bankAccountChangedProperty.get());
}
public void setIsOnline(boolean isOnline)
@ -90,7 +89,7 @@ public class User implements Serializable
for (Iterator<BankAccount> iterator = getBankAccounts().iterator(); iterator.hasNext(); )
{
BankAccount bankAccount = iterator.next();
bankAccountUIDs += bankAccount.getStringifiedBankAccount();
bankAccountUIDs += bankAccount.toString();
if (iterator.hasNext())
bankAccountUIDs += ", ";
@ -98,9 +97,9 @@ public class User implements Serializable
return bankAccountUIDs;
}
public String getMessageID()
public String getMessagePubKeyAsHex()
{
return messageID;
return messagePubKeyAsHex;
}
public String getAccountID()
@ -118,23 +117,37 @@ public class User implements Serializable
return currentBankAccount;
}
public BankAccount getBankAccount(String bankAccountUID)
{
for (Iterator<BankAccount> iterator = bankAccounts.iterator(); iterator.hasNext(); )
{
BankAccount bankAccount = iterator.next();
if (bankAccount.getUid().equals(bankAccountUID))
return bankAccount;
}
return null;
}
public boolean getIsOnline()
{
return isOnline;
}
public SimpleBooleanProperty getChangedProperty()
public SimpleBooleanProperty getBankAccountChangedProperty()
{
return changedProperty;
return bankAccountChangedProperty;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private Methods
///////////////////////////////////////////////////////////////////////////////////////////
private void triggerChange()
@Override
public String toString()
{
changedProperty.set(!changedProperty.get());
return "User{" +
"bankAccountChangedProperty=" + bankAccountChangedProperty +
", accountID='" + accountID + '\'' +
", messagePubKeyAsHex='" + messagePubKeyAsHex + '\'' +
", isOnline=" + isOnline +
", bankAccounts=" + bankAccounts +
", currentBankAccount=" + currentBankAccount +
'}';
}
}

View file

@ -1,6 +1,6 @@
package io.bitsquare.msg;
package io.bitsquare.util;
import io.bitsquare.util.Utils;
import com.google.bitcoin.core.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -13,10 +13,10 @@ import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class MsgKeyUtil
public class DSAKeyUtil
{
private static final Logger log = LoggerFactory.getLogger(MsgKeyUtil.class);
private static final String baseDir = Utils.getRootDir();
private static final Logger log = LoggerFactory.getLogger(DSAKeyUtil.class);
private static final String baseDir = Utilities.getRootDir();
public static KeyPair getKeyPair()
{
@ -28,6 +28,27 @@ public class MsgKeyUtil
return getKeyPair("public_" + keyName + ".key", "private_" + keyName + ".key");
}
public static String getHexStringFromPublicKey(PublicKey publicKey)
{
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
return Utils.bytesToHexString(x509EncodedKeySpec.getEncoded());
}
public static PublicKey getPublicKeyFromHexString(String publicKeyAsHex)
{
byte[] bytes = Utils.parseAsHexOrBase58(publicKeyAsHex);
try
{
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
return keyFactory.generatePublic(new X509EncodedKeySpec(bytes));
} catch (NoSuchAlgorithmException | InvalidKeySpecException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
return null;
}
public static KeyPair getKeyPair(String pubKeyPath, String privKeyPath)
{
try
@ -40,7 +61,6 @@ public class MsgKeyUtil
try
{
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
keyGen.initialize(1024);
KeyPair generatedKeyPair = keyGen.genKeyPair();
@ -56,7 +76,6 @@ public class MsgKeyUtil
}
}
private static void dumpKeyPair(KeyPair keyPair)
{
PublicKey pub = keyPair.getPublic();
@ -76,21 +95,20 @@ public class MsgKeyUtil
return result;
}
public static void saveKeyPair(String pubKeyPath, String privKeyPath, KeyPair keyPair) throws IOException
{
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// Store Public Key.
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
publicKey.getEncoded());
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
FileOutputStream fos = new FileOutputStream(baseDir + pubKeyPath);
fos.write(x509EncodedKeySpec.getEncoded());
fos.close();
// Store Private Key.
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
privateKey.getEncoded());
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
fos = new FileOutputStream(baseDir + privKeyPath);
fos.write(pkcs8EncodedKeySpec.getEncoded());
fos.close();

View file

@ -1,8 +1,12 @@
package io.bitsquare.util;
import com.google.bitcoin.core.ECKey;
import io.bitsquare.bank.BankAccountType;
import io.bitsquare.user.Arbitrator;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.util.*;
public class MockData
@ -52,17 +56,32 @@ public class MockData
public static List<Arbitrator> getArbitrators()
{
List<Arbitrator> list = new ArrayList<>();
list.add(new Arbitrator("uid_1", "Charlie Boom", UUID.randomUUID().toString(),
UUID.randomUUID().toString(), "http://www.arbit.io/Charly_Boom", 0.1, 10, com.google.bitcoin.core.Utils.toNanoCoins("0.01")));
list.add(new Arbitrator("uid_2", "Tom Shang", UUID.randomUUID().toString(),
UUID.randomUUID().toString(), "http://www.arbit.io/Tom_Shang", 0, 1, com.google.bitcoin.core.Utils.toNanoCoins("0.001")));
list.add(new Arbitrator("uid_3", "Edward Snow", UUID.randomUUID().toString(),
UUID.randomUUID().toString(), "http://www.arbit.io/Edward_Swow", 0.2, 5, com.google.bitcoin.core.Utils.toNanoCoins("0.05")));
list.add(new Arbitrator("uid_4", "Julian Sander", UUID.randomUUID().toString(),
UUID.randomUUID().toString(), "http://www.arbit.io/Julian_Sander", 0, 20, com.google.bitcoin.core.Utils.toNanoCoins("0.1")));
list.add(new Arbitrator("uid_1", "Charlie Boom", com.google.bitcoin.core.Utils.bytesToHexString(new ECKey().getPubKey()),
getMessagePubKey(), "http://www.arbit.io/Charly_Boom", 1, 10, com.google.bitcoin.core.Utils.toNanoCoins("0.01")));
list.add(new Arbitrator("uid_2", "Tom Shang", com.google.bitcoin.core.Utils.bytesToHexString(new ECKey().getPubKey()),
getMessagePubKey(), "http://www.arbit.io/Tom_Shang", 0, 1, com.google.bitcoin.core.Utils.toNanoCoins("0.001")));
list.add(new Arbitrator("uid_3", "Edward Snow", com.google.bitcoin.core.Utils.bytesToHexString(new ECKey().getPubKey()),
getMessagePubKey(), "http://www.arbit.io/Edward_Swow", 2, 5, com.google.bitcoin.core.Utils.toNanoCoins("0.05")));
list.add(new Arbitrator("uid_4", "Julian Sander", com.google.bitcoin.core.Utils.bytesToHexString(new ECKey().getPubKey()),
getMessagePubKey(), "http://www.arbit.io/Julian_Sander", 0, 20, com.google.bitcoin.core.Utils.toNanoCoins("0.1")));
return list;
}
private static String getMessagePubKey()
{
try
{
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
keyGen.initialize(1024);
KeyPair generatedKeyPair = keyGen.genKeyPair();
PublicKey pubKey = generatedKeyPair.getPublic();
return DSAKeyUtil.getHexStringFromPublicKey(pubKey);
} catch (Exception e2)
{
return null;
}
}
public static List<Arbitrator> getRandomArbitrators()
{
return randomizeList(getArbitrators());
@ -71,12 +90,12 @@ public class MockData
public static List<BankAccountType.BankAccountTypeEnum> getBankTransferTypeEnums()
{
return Utils.getAllBankAccountTypeEnums();
return Utilities.getAllBankAccountTypeEnums();
}
public static List<BankAccountType.BankAccountTypeEnum> getRandomBankTransferTypeEnums()
{
return randomizeList(Utils.getAllBankAccountTypeEnums());
return randomizeList(Utilities.getAllBankAccountTypeEnums());
}
public static List randomizeList(List list)

View file

@ -17,25 +17,25 @@ import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
public class Utils
public class Utilities
{
private static final Logger log = LoggerFactory.getLogger(Utils.class);
private static final Logger log = LoggerFactory.getLogger(Utilities.class);
public static String getRootDir()
{
return Utils.class.getProtectionDomain().getCodeSource().getLocation().getFile() + "/";
return Utilities.class.getProtectionDomain().getCodeSource().getLocation().getFile() + "/../";
}
public static String objectToJson(Object object)
{
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).setPrettyPrinting().create();
return gson.toJson(object);
}
public static <T> T jsonToObject(String jsonString, Class<T> classOfT)
{
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).setPrettyPrinting().create();
return gson.fromJson(jsonString, classOfT);
}
@ -228,23 +228,16 @@ public class Utils
return bankTransferTypes;
}
/**
* @param delay in milliseconds
* @param callback
* @usage Utils.setTimeout(1000, (AnimationTimer animationTimer) -> {
* doSomething();
* return null;
* });
*/
public static void setTimeout(int delay, Function<AnimationTimer, Void> callback)
public static AnimationTimer setTimeout(int delay, Function<AnimationTimer, Void> callback)
{
long startTime = System.currentTimeMillis();
AnimationTimer animationTimer = new AnimationTimer()
{
long lastTimeStamp = System.currentTimeMillis();
@Override
public void handle(long arg0)
{
if (System.currentTimeMillis() > delay + startTime)
if (System.currentTimeMillis() > delay + lastTimeStamp)
{
callback.apply(this);
this.stop();
@ -252,5 +245,26 @@ public class Utils
}
};
animationTimer.start();
return animationTimer;
}
public static AnimationTimer setInterval(int delay, Function<AnimationTimer, Void> callback)
{
AnimationTimer animationTimer = new AnimationTimer()
{
long lastTimeStamp = System.currentTimeMillis();
@Override
public void handle(long arg0)
{
if (System.currentTimeMillis() > delay + lastTimeStamp)
{
lastTimeStamp = System.currentTimeMillis();
callback.apply(this);
}
}
};
animationTimer.start();
return animationTimer;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

Before

Width:  |  Height:  |  Size: 901 B

After

Width:  |  Height:  |  Size: 901 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 B

View file

@ -13,10 +13,10 @@
<appender-ref ref="CONSOLE_APPENDER"/>
</root>
<logger name="io.bitsquare" level="INFO"/>
<logger name="io.bitsquare" level="DEBUG"/>
<logger name="com.google.bitcoin" level="ERROR"/>
<logger name="net.tomp2p" level="INFO"/>
<logger name="net.tomp2p" level="ERROR"/>
<logger name="com.google.bitcoin.core.Peer" level="ERROR" additivity="false"/>