package org.ripple.power.ui.btc;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
import org.ripple.power.txns.btc.Address;
import org.ripple.power.txns.btc.AddressFormatException;
import org.ripple.power.txns.btc.BTCLoader;
import org.ripple.power.txns.btc.BlockStoreException;
import org.ripple.power.txns.btc.ECException;
import org.ripple.power.txns.btc.InventoryItem;
import org.ripple.power.txns.btc.InventoryMessage;
import org.ripple.power.txns.btc.Message;
import org.ripple.power.txns.btc.ScriptException;
import org.ripple.power.txns.btc.SignedInput;
import org.ripple.power.txns.btc.Transaction;
import org.ripple.power.txns.btc.TransactionOutput;
import org.ripple.power.txns.btc.VerificationException;
import org.ripple.power.ui.UIRes;
import org.ripple.power.ui.view.ButtonPane;
import org.ripple.power.ui.view.log.ErrorLog;
public class SendDialog extends JDialog implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private final JComboBox<Object> addressField;
private final JTextField amountField;
private final JTextField feeField;
private Address sendAddress;
private BigInteger sendAmount;
private BigInteger sendFee;
public SendDialog(JDialog parent) {
super(parent, "Send Coins", Dialog.ModalityType.DOCUMENT_MODAL);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
if (BTCLoader.addresses.isEmpty()) {
addressField = new JComboBox<>();
} else {
String[] addrList = new String[BTCLoader.addresses.size()];
int index = 0;
for (Address addr : BTCLoader.addresses) {
addrList[index++] = addr.getLabel();
}
addressField = new JComboBox<Object>(addrList);
}
addressField.setEditable(true);
addressField.setSelectedIndex(-1);
addressField.setPreferredSize(new Dimension(340, 25));
JPanel addressPane = new JPanel();
addressPane.add(new JLabel("Address ", JLabel.RIGHT));
addressPane.add(addressField);
amountField = new JTextField("", 15);
JPanel amountPane = new JPanel();
amountPane.add(new JLabel("Amount ", JLabel.RIGHT));
amountPane.add(amountField);
feeField = new JTextField("0.0001", 10);
JPanel feePane = new JPanel();
feePane.add(new JLabel("Fee ", JLabel.RIGHT));
feePane.add(feeField);
JPanel buttonPane = new ButtonPane(this, 10, new String[] { "Send",
"send" }, new String[] { "Done", "done" });
JPanel contentPane = new JPanel();
contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
contentPane.setOpaque(true);
contentPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
contentPane.add(addressPane);
contentPane.add(Box.createVerticalStrut(15));
contentPane.add(amountPane);
contentPane.add(Box.createVerticalStrut(15));
contentPane.add(feePane);
contentPane.add(Box.createVerticalStrut(15));
contentPane.add(buttonPane);
setContentPane(contentPane);
}
public static void showDialog(JDialog parent) {
try {
JDialog dialog = new SendDialog(parent);
dialog.pack();
dialog.setLocationRelativeTo(parent);
dialog.setVisible(true);
} catch (Exception exc) {
ErrorLog.get().logException("Exception while displaying dialog", exc);
}
}
@Override
public void actionPerformed(ActionEvent ae) {
try {
String action = ae.getActionCommand();
switch (action) {
case "send":
if (checkFields()) {
String confirmText = String.format(
"Do you want to send %s BTC?",
BTCLoader.satoshiToString(sendAmount));
if (UIRes.showConfirmMessage(this, "Send Coins",
confirmText, "YES", "NO") == 0) {
sendCoins();
}
}
break;
case "done":
setVisible(false);
dispose();
break;
}
} catch (NumberFormatException exc) {
UIRes.showErrorMessage(this, "Error",
"Invalid numeric value entered");
} catch (AddressFormatException exc) {
UIRes.showErrorMessage(this, "Error", "Send address is not valid");
} catch (BlockStoreException exc) {
ErrorLog.get().logException("Unable to process send request", exc);
} catch (Exception exc) {
ErrorLog.get().logException("Exception while processing action event",
exc);
}
}
private boolean checkFields() throws AddressFormatException,
NumberFormatException {
String sendString = (String) addressField.getSelectedItem();
if (sendString == null) {
UIRes.showErrorMessage(this, "Error",
"You must enter a send address");
return false;
}
int index = addressField.getSelectedIndex();
if (index < 0) {
sendAddress = new Address(sendString);
} else {
sendAddress = BTCLoader.addresses.get(index);
}
String amountString = amountField.getText();
if (amountString.isEmpty()) {
UIRes.showErrorMessage(this, "Error",
"You must enter the amount to send");
return false;
}
sendAmount = BTCLoader.stringToSatoshi(amountString);
if (sendAmount.compareTo(BTCLoader.DUST_TRANSACTION) < 0) {
UIRes.showErrorMessage(this, "ERROR", String.format(
"The minimum amount you can send is %s BTC",
BTCLoader.satoshiToString(BTCLoader.DUST_TRANSACTION)));
return false;
}
String feeString = feeField.getText();
if (feeString.isEmpty()) {
UIRes.showErrorMessage(this, "Enter",
"You must enter a transaction fee");
return false;
}
sendFee = BTCLoader.stringToSatoshi(feeString);
if (sendFee.compareTo(BTCLoader.MIN_TX_FEE) < 0) {
UIRes.showErrorMessage(this, "Error", String.format(
"The minimun transaction fee is %s BTC",
BTCLoader.satoshiToString(BTCLoader.MIN_TX_FEE)));
return false;
}
return true;
}
private void sendCoins() throws BlockStoreException {
List<SignedInput> inputList = BuildInputList.buildSignedInputs();
Transaction tx = null;
for (;;) {
BigInteger totalAmount = sendAmount.add(sendFee);
List<SignedInput> inputs = new ArrayList<SignedInput>(
inputList.size());
for (SignedInput input : inputList) {
inputs.add(input);
totalAmount = totalAmount.subtract(input.getValue());
if (totalAmount.signum() <= 0) {
break;
}
}
if (totalAmount.signum() > 0) {
UIRes.showErrorMessage(this, "Error",
"There are not enough confirmed coins available");
break;
}
List<TransactionOutput> outputs = new ArrayList<>(2);
outputs.add(new TransactionOutput(0, sendAmount, sendAddress));
BigInteger change = totalAmount.negate();
if (change.compareTo(BTCLoader.DUST_TRANSACTION) > 0) {
outputs.add(new TransactionOutput(1, change,
BTCLoader.changeKey.toAddress()));
}
try {
tx = new Transaction(inputs, outputs);
} catch (ECException | ScriptException | VerificationException exc) {
throw new BlockStoreException("Unable to create transaction",
exc);
}
int length = tx.getBytes().length;
BigInteger minFee = BigInteger.valueOf(length / 1000 + 1).multiply(
BTCLoader.MIN_TX_FEE);
if (minFee.compareTo(sendFee) <= 0) {
break;
}
sendFee = minFee;
tx = null;
}
if (tx != null) {
BTCLoader.databaseHandler.processTransaction(tx);
List<InventoryItem> invList = new ArrayList<InventoryItem>(1);
invList.add(new InventoryItem(InventoryItem.INV_TX, tx.getHash()));
Message invMsg = InventoryMessage.buildInventoryMessage(null,
invList);
BTCLoader.networkHandler.broadcastMessage(invMsg);
UIRes.showInfoMessage(
this,
"Transaction Broadcast",
String.format("Transaction broadcast to peer nodes\n%s",
tx.getHash()));
}
}
}