/*
* Copyright 2014 Christopher Mann
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.uni_bonn.bit;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.GlazedListsSwing;
import org.bitcoinj.core.*;
import org.bitcoinj.params.RegTestParams;
import com.google.zxing.WriterException;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.uiDesigner.core.Spacer;
import de.uni_bonn.bit.wallet_protocol.IWalletProtocol;
import javax.swing.*;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.NumberFormatter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.text.DecimalFormat;
import java.util.List;
import org.apache.log4j.Logger;
/**
* This dialog is used to create a new transaction and to sign it with two-party signature protocol. In this dialog, the
* user enters the target address and the amount of Bitcoins to send. After the transaction has been created, the dialog
* displays a QR code which can be scanned with the phone wallet to start the two-party signature protocol.
*/
public class TransactionDialog extends JDialog {
private JPanel contentPane;
private JButton buttonOK;
private JTextField addressTextField;
private JButton generateTransactionButton;
private JTextPane infoTextPane;
private JLabel qrCodeLabel;
private JFormattedTextField amountTextField;
private JTable tblOutputs;
private JScrollPane tblOutputsPane;
private Wallet wallet;
private TransactionBroadcaster broadcaster;
private ProtocolServer server;
public TransactionDialog(Wallet wallet, TransactionBroadcaster broadcaster) {
setContentPane(contentPane);
setModal(true);
infoTextPane.setBackground(contentPane.getBackground());
tblOutputsPane.setVisible(false);
setTitle("Send Bitcoins");
pack();
this.wallet = wallet;
this.broadcaster = broadcaster;
amountTextField.setValue(new Double(0));
amountTextField.setFormatterFactory(new DefaultFormatterFactory(new NumberFormatter(new DecimalFormat("0.00"))));
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
onClose();
}
});
generateTransactionButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
generateTransactionButton_Clicked(e);
}
});
buttonOK.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
onClose();
}
});
}
private void onClose() {
if (server != null) {
server.close();
}
dispose();
}
public void generateTransactionButton_Clicked(ActionEvent e) {
try {
//Close the old server, so the port is free again
if (server != null) {
server.close();
}
KeyShareWalletExtension walletEx = ((KeyShareWalletExtension) wallet.addOrGetExistingExtension(new KeyShareWalletExtension()));
Address receiverAddress = new Address(TransactionHelper.netParams, addressTextField.getText());
Double amountDouble = (Double) amountTextField.getValue();
Coin amount = Coin.valueOf(Math.round(amountDouble * 100000000));
Wallet.SendRequest sendRequest = Wallet.SendRequest.to(receiverAddress, amount);
sendRequest.changeAddress = walletEx.getAddress();
sendRequest.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG;
wallet.completeTx(sendRequest);
tblOutputs.setModel(GlazedListsSwing.eventTableModel(createOutputsList(sendRequest.tx),
new String[]{"address", "amount"},
new String[]{"Address", "BTC"},
new boolean[]{false, false}));
//hack to ensure correct column sizes. Real columns sizes are proportions of the preferred width.
tblOutputs.getColumnModel().getColumn(0).setPreferredWidth(800);
tblOutputs.getColumnModel().getColumn(1).setPreferredWidth(200);
tblOutputsPane.setVisible(true);
List<String> ipAddresses = IPAddressHelper.getAllUsableIPAddresses();
WalletProtocolImpl walletProtocolImpl = new WalletProtocolImpl(sendRequest.tx, new MyWalletProtocolListener(),
walletEx.getPrivateKey(), walletEx.getOtherPublicKey(), walletEx.getPkpDesktop(),
walletEx.getPkpPhone(), walletEx.getDesktopBCParameters(), walletEx.getPhoneBCParameters());
server = new ProtocolServer(IWalletProtocol.class, walletProtocolImpl);
qrCodeLabel.setIcon(new ImageIcon(QRCodeHelper.CreateQRCodeForTLSSetup(
ipAddresses, server.getPublicKey()
)));
} catch (WriterException we) {
qrCodeLabel.setIcon(null);
infoTextPane.setText("Exception while creating the QR code:\n" + we.getMessage());
} catch (AddressFormatException afe) {
qrCodeLabel.setIcon(null);
infoTextPane.setText("The receiver address is incorrect:\n" + afe.getMessage());
} catch (IllegalArgumentException iae) {
qrCodeLabel.setIcon(null);
infoTextPane.setText("Something went wrong when creating the transaction:\n" + iae.getMessage());
} catch (InsufficientMoneyException ime) {
qrCodeLabel.setIcon(null);
infoTextPane.setText("Insufficient funds to complete this transaction:\n" + ime.getMessage());
} catch (UnknownHostException e1) {
e1.printStackTrace();
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
} catch (InvalidKeySpecException e1) {
e1.printStackTrace();
}
}
{
// GUI initializer generated by IntelliJ IDEA GUI Designer
// >>> IMPORTANT!! <<<
// DO NOT EDIT OR ADD ANY CODE HERE!
$$$setupUI$$$();
}
/**
* Method generated by IntelliJ IDEA GUI Designer
* >>> IMPORTANT!! <<<
* DO NOT edit this method OR call it in your code!
*
* @noinspection ALL
*/
private void $$$setupUI$$$() {
contentPane = new JPanel();
contentPane.setLayout(new GridLayoutManager(7, 1, new Insets(10, 10, 10, 10), -1, -1));
contentPane.setMinimumSize(new Dimension(400, 450));
contentPane.setPreferredSize(new Dimension(400, 450));
final JPanel panel1 = new JPanel();
panel1.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1));
contentPane.add(panel1, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
final Spacer spacer1 = new Spacer();
panel1.add(spacer1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
final JPanel panel2 = new JPanel();
panel2.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
panel1.add(panel2, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
buttonOK = new JButton();
buttonOK.setActionCommand("");
buttonOK.setText("Close");
panel2.add(buttonOK, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
final JPanel panel3 = new JPanel();
panel3.setLayout(new GridLayoutManager(2, 2, new Insets(0, 0, 0, 0), -1, -1));
contentPane.add(panel3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
final JLabel label1 = new JLabel();
label1.setText("Address:");
panel3.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
addressTextField = new JTextField();
addressTextField.setText("myaxzyLuJjfivCSrCwWEDaAMiyk7XkXXoX");
panel3.add(addressTextField, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
final JLabel label2 = new JLabel();
label2.setText("Amount:");
panel3.add(label2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
amountTextField = new JFormattedTextField();
amountTextField.setText("");
panel3.add(amountTextField, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
final JPanel panel4 = new JPanel();
panel4.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1));
contentPane.add(panel4, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
generateTransactionButton = new JButton();
generateTransactionButton.setText("Generate Transaction");
panel4.add(generateTransactionButton, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
final Spacer spacer2 = new Spacer();
panel4.add(spacer2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
final JPanel panel5 = new JPanel();
panel5.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
contentPane.add(panel5, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, new Dimension(-1, 200), null, null, 0, false));
qrCodeLabel = new JLabel();
qrCodeLabel.setText("");
panel5.add(qrCodeLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
final Spacer spacer3 = new Spacer();
contentPane.add(spacer3, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
infoTextPane = new JTextPane();
infoTextPane.setEditable(false);
infoTextPane.setText("Please enter receiver address and amount and click on Generate Transaction");
contentPane.add(infoTextPane, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_NORTH, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(150, 30), null, 0, false));
tblOutputsPane = new JScrollPane();
tblOutputsPane.setVisible(true);
contentPane.add(tblOutputsPane, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(-1, 70), null, 0, false));
tblOutputs = new JTable();
tblOutputs.setVisible(true);
tblOutputsPane.setViewportView(tblOutputs);
}
/**
* @noinspection ALL
*/
public JComponent $$$getRootComponent$$$() {
return contentPane;
}
private class MyWalletProtocolListener implements WalletProtocolImpl.WalletProtocolListener {
@Override
public void protocolCompleted(final Transaction transaction) {
Logger.getLogger(TransactionDialog.class).info("Final Transaction stats: #inputs=" + transaction.getInputs().size() + " size=" + transaction.bitcoinSerialize().length);
broadcaster.broadcastTransaction(transaction);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
infoTextPane.setText("Transaction signing protocol successfully completed.");
}
});
}
@Override
public void protocolFailed(Exception exception) {
}
}
public static class AddressAmountPair {
private String address, amount;
public AddressAmountPair(String address, String amount) {
this.address = address;
this.amount = amount;
}
public String getAddress() {
return address;
}
public String getAmount() {
return amount;
}
}
private EventList<AddressAmountPair> createOutputsList(Transaction transaction) {
KeyShareWalletExtension walletExtension = (KeyShareWalletExtension) wallet.addOrGetExistingExtension(new KeyShareWalletExtension());
EventList<AddressAmountPair> result = new BasicEventList<>();
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
String addressString = transactionOutput.getScriptPubKey().getToAddress(RegTestParams.get()).toString();
if (addressString.equals(walletExtension.getAddressAsString())) {
addressString = "Change ("
+ addressString.substring(0, 4)
+ "..."
+ addressString.substring(addressString.length() - 4, addressString.length())
+ ")";
}
result.add(new AddressAmountPair(addressString,
transactionOutput.getValue().toFriendlyString())
);
}
result.add(new AddressAmountPair("Miner fee",
TransactionHelper.computeOverpay(transaction).toFriendlyString()));
return result;
}
}