Skip to content

Commit

Permalink
Litecoin: adding fee confirmation dialog
Browse files Browse the repository at this point in the history
Will alert the user when a higher fee is about to be
used
  • Loading branch information
hank committed Jan 4, 2014
1 parent 9f63f57 commit b050c13
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 39 deletions.
6 changes: 6 additions & 0 deletions wallet/src/de/schildbach/wallet/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package de.schildbach.wallet;

import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;

import android.os.Environment;
Expand Down Expand Up @@ -77,6 +79,10 @@ public class Constants
public static final int ADDRESS_FORMAT_GROUP_SIZE = 4;
public static final int ADDRESS_FORMAT_LINE_SIZE = 12;

public static final BigInteger CENT = new BigInteger("1000000", 10);
public static final BigInteger MIN_TX_FEE = CENT.divide(new BigInteger("10"));
public static final BigInteger TX_FEE_PER_KB = CENT.divide(new BigInteger("10"));

public static final int BTC_MAX_PRECISION = 8;
public static final int MBTC_MAX_PRECISION = 5;
public static final int LOCAL_PRECISION = 4;
Expand Down
150 changes: 111 additions & 39 deletions wallet/src/de/schildbach/wallet/ui/SendCoinsFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@

package de.schildbach.wallet.ui;

import java.math.BigDecimal;
import java.math.BigInteger;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import android.app.AlertDialog;
import android.util.Log;
import android.widget.Toast;
import com.google.bitcoin.core.InsufficientMoneyException;
import com.google.bitcoin.core.VerificationException;
import org.litecoin.LitecoinWallet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -845,52 +852,111 @@ private void handleGo()
sendRequest.changeAddress = WalletUtils.pickOldestKey(wallet).toAddress(Constants.NETWORK_PARAMETERS);
sendRequest.emptyWallet = amount.equals(wallet.getBalance(BalanceType.AVAILABLE));

new SendCoinsOfflineTask(wallet, backgroundHandler)
{
@Override
protected void onSuccess(final Transaction transaction)
{
sentTransaction = transaction;
// Multi-part transaction creation to properly calculate fee
// Complete the transaction in order to get the final size
try {
wallet.completeTx(sendRequest);
} catch (InsufficientMoneyException e) {
Log.i("wallet_ltc", "Insufficient funds when completing tx");
Toast
.makeText(getActivity(), "Insufficient funds. Please lower the amount and try again.", Toast.LENGTH_LONG)
.show();
return;
}
// Get the size of the transaction
/*
Log.d("Litecoin", "Transaction size is " + sendRequest.tx.getLength());
BigInteger nTransactionFee = Constants.MIN_TX_FEE;
BigInteger nFeePerKB = Constants.TX_FEE_PER_KB;
BigInteger nSizeFee = nFeePerKB.multiply(new BigInteger(Integer.toString(sendRequest.tx.getLength() / 1000)));
BigInteger nPayFee = nTransactionFee.add(nSizeFee);
*/

if(sendRequest.fee.compareTo(Constants.MIN_TX_FEE) > 0)
{
Log.i("LitecoinSendCoins", "Higher fee: " +
sendRequest.fee.toString() + " < " + sendRequest.fee.toString());

new AlertDialog.Builder(SendCoinsFragment.this.getActivity())
.setMessage("Transaction requires fee of " +
new BigDecimal(sendRequest.fee).divide(new BigDecimal("100000000")) + ". Continue?")
.setTitle("Extra fee needed")
.setCancelable(true)
.setNeutralButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
startActivity(new Intent(getActivity(), WalletActivity.class));
getActivity().finish();
}
})
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
// Fees are agreeable
// Process the transaction
// Send Asynchronously
new NormalSendCoinsOfflineTask(wallet, backgroundHandler).commitRequest(sendRequest);
}
})
.show();
} else {
// No fee recalculation necessary
// Process the transaction
// Send Asynchronously
new NormalSendCoinsOfflineTask(wallet, backgroundHandler).commitRequest(sendRequest);
}

state = State.SENDING;
updateView();
}

sentTransaction.getConfidence().addEventListener(sentTransactionConfidenceListener);
class NormalSendCoinsOfflineTask extends SendCoinsOfflineTask {

if (bluetoothAdapter != null && bluetoothAdapter.isEnabled() && bluetoothMac != null && bluetoothEnableView.isChecked())
{
new SendBluetoothTask(bluetoothAdapter, backgroundHandler)
{
@Override
protected void onResult(final boolean ack)
{
bluetoothAck = ack;
public NormalSendCoinsOfflineTask(@Nonnull Wallet wallet, @Nonnull Handler backgroundHandler) {
super(wallet, backgroundHandler);
}

if (state == State.SENDING)
state = State.SENT;
@Override
protected void onSuccess(@Nonnull final Transaction transaction)
{
sentTransaction = transaction;

updateView();
}
}.send(bluetoothMac, transaction); // send asynchronously
}
state = State.SENDING;
updateView();

application.broadcastTransaction(sentTransaction);
sentTransaction.getConfidence().addEventListener(sentTransactionConfidenceListener);

final Intent result = new Intent();
BitcoinIntegration.transactionHashToResult(result, sentTransaction.getHashAsString());
activity.setResult(Activity.RESULT_OK, result);
}
if (bluetoothAdapter != null && bluetoothAdapter.isEnabled() && bluetoothMac != null && bluetoothEnableView.isChecked())
{
new SendBluetoothTask(bluetoothAdapter, backgroundHandler)
{
@Override
protected void onResult(final boolean ack)
{
bluetoothAck = ack;

@Override
protected void onFailure()
{
state = State.FAILED;
updateView();
if (state == State.SENDING)
state = State.SENT;

activity.longToast(R.string.send_coins_error_msg);
}
}.sendCoinsOffline(sendRequest); // send asynchronously
}
updateView();
}
}.send(bluetoothMac, transaction); // send asynchronously
}

application.broadcastTransaction(sentTransaction);

final Intent result = new Intent();
BitcoinIntegration.transactionHashToResult(result, sentTransaction.getHashAsString());
activity.setResult(Activity.RESULT_OK, result);
}

@Override
protected void onFailure()
{
state = State.FAILED;
updateView();

activity.longToast(R.string.send_coins_error_msg);
}
}

private void handleScan()
{
Expand Down Expand Up @@ -940,8 +1006,14 @@ public CharSequence convertToString(final Cursor cursor)
@Override
public Cursor runQueryOnBackgroundThread(final CharSequence constraint)
{
final Cursor cursor = activity.managedQuery(AddressBookProvider.contentUri(activity.getPackageName()), null,
AddressBookProvider.SELECTION_QUERY, new String[] { constraint.toString() }, null);
final Cursor cursor;
try {
cursor = activity.managedQuery(AddressBookProvider.contentUri(activity.getPackageName()),
null, AddressBookProvider.SELECTION_QUERY, new String[] { constraint.toString() }, null);
} catch(NullPointerException e) {
Log.i("wallet_ltc", "NULL Pointer exception when doing address book completion");
return null;
}
return cursor;
}
}
Expand Down
22 changes: 22 additions & 0 deletions wallet/src/de/schildbach/wallet/ui/SendCoinsOfflineTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,28 @@ public void run()
});
}

public final void commitRequest(@Nonnull final SendRequest sendRequest)
{
backgroundHandler.post(new Runnable()
{
@Override
public void run()
{
final Transaction transaction; // can take long
wallet.commitTx(sendRequest.tx);

callbackHandler.post(new Runnable()
{
@Override
public void run()
{
onSuccess(sendRequest.tx);
}
});
}
});
}

protected abstract void onSuccess(@Nonnull Transaction transaction);

protected abstract void onFailure();
Expand Down

2 comments on commit b050c13

@wtogami
Copy link
Member

@wtogami wtogami commented on b050c13 Jan 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work!

bitcoin/bitcoin#2651 (comment)
Could you make the dialog more informative? Display the To address, amount to send and fee similar to this.
And always ask the user to confirm the send. That way they have a chance to avoid mistakes and fees are never a surprise.

The top box would be better with simply "Confirm Send?"

@hank
Copy link
Member Author

@hank hank commented on b050c13 Jan 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this idea - I never liked how hitting send was a point of no return without confirming anything. I can make that happen rather easily.

Please sign in to comment.