Skip to content

Commit

Permalink
Android: Showing transaction details in a dialog before signing a loa…
Browse files Browse the repository at this point in the history
…ded transaction.
  • Loading branch information
Aldin-SXR committed Jun 10, 2021
1 parent 5610e65 commit 0663281
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 38 deletions.
Expand Up @@ -100,11 +100,11 @@ class ColdLoadDialog : AlertDialogFragment() {
* Sign a loaded transaction.
*/
private fun signLoadedTransaction() {
val dialog = SignPasswordDialog()
dialog.setArguments(Bundle().apply {
putString("tx", etTransaction.text.toString())
})
showDialog(this, dialog)
val arguments = Bundle().apply {
putString("txHex", etTransaction.text.toString())
}
val dialog = SendDialog()
showDialog(this, dialog.apply { setArguments(arguments) })
}

/**
Expand Down Expand Up @@ -166,29 +166,3 @@ fun canBroadcast(tx: PyObject): Boolean {
false
}
}

/**
* Sign a loaded transaction dialog.
*/
class SignPasswordDialog : PasswordDialog<PyObject>() {
val coldLoadDialog by lazy { targetFragment as ColdLoadDialog }

val tx by lazy { libTransaction.callAttr("Transaction", arguments!!.getString("tx"),
Kwarg("sign_schnorr", signSchnorr())) }
val wallet = daemonModel.wallet!!

override fun onPassword(password: String): PyObject {
wallet.callAttr("sign_transaction", tx, password)

return tx
}

override fun onPostExecute(result: PyObject) {
coldLoadDialog.etTransaction.setText(result.toString())

if (!canBroadcast(result)) {
coldLoadDialog.dismiss()
copyToClipboard(result.toString(), R.string.signed_transaction)
}
}
}
94 changes: 87 additions & 7 deletions android/app/src/main/java/org/electroncash/electroncash3/Send.kt
Expand Up @@ -15,6 +15,7 @@ import com.chaquo.python.Kwarg
import com.chaquo.python.PyException
import com.chaquo.python.PyObject
import com.google.zxing.integration.android.IntentIntegrator
import kotlinx.android.synthetic.main.load.*
import kotlinx.android.synthetic.main.send.*


Expand Down Expand Up @@ -130,6 +131,13 @@ class SendDialog : TaskLauncherDialog<Unit>() {
}
setFeeLabel()

// Check if a transaction hex string has been passed from ColdLoad, and load it.
val txHex = arguments?.getString("txHex")
if (txHex != null) {
val tx = libTransaction.callAttr("Transaction", txHex)
setLoadedTransaction(tx)
}

dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { onOK() }
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { scanQR(this) }
model.tx.observe(this, Observer { onTx(it) })
Expand All @@ -150,8 +158,11 @@ class SendDialog : TaskLauncherDialog<Unit>() {
get() = MIN_FEE + sbFee.progress

fun refreshTx() {
model.tx.refresh(TxArgs(wallet, model.paymentRequest, etAddress.text.toString(),
amountBox.amount, btnMax.isChecked))
// If loading a transaction from ColdLoad, it does not need to be constantly refreshed.
if (arguments?.containsKey("txHex") != true) {
model.tx.refresh(TxArgs(wallet, model.paymentRequest, etAddress.text.toString(),
amountBox.amount, btnMax.isChecked))
}
}

fun onTx(result: TxResult) {
Expand Down Expand Up @@ -286,8 +297,51 @@ class SendDialog : TaskLauncherDialog<Unit>() {
}
}

/**
* Fill in the Send dialog with data from a loaded transaction.
*/
fun setLoadedTransaction(tx: PyObject) {
dialog.setTitle(R.string.sign_the)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.sign)
btnContacts.setImageResource(R.drawable.ic_check_24dp)

btnContacts.isEnabled = false
amountBox.isEditable = false
btnMax.isEnabled = false

val txInfo = daemonModel.wallet!!.callAttr("get_tx_info", tx)

val fee: Int = txInfo["fee"]!!.toInt() / tx.callAttr("estimated_size").toInt()
sbFee.progress = fee - 1
sbFee.isEnabled = false
setFeeLabel(tx)

// Get the list of transaction outputs, add every non-related address to the
// "recipients" array, and add up the total amount that is being sent.
val outputs = tx.callAttr("outputs").asList()
var amount: Long = 0
val recipients: ArrayList<String> = ArrayList()
for (output in outputs) {
val address = output.asList()[1]
if (!daemonModel.wallet!!.callAttr("is_mine", address).toBoolean()) {
amount += output.asList()[2].toLong()
recipients.add(address.toString())
}
}

// If there is only one recipient, their address will be displayed.
// Otherwise, this is a "pay to many" transaction.
if (recipients.size == 1) {
etAddress.setText(recipients[0])
} else {
etAddress.setText(R.string.pay_to_many)
}
etAddress.isFocusable = false
setAmount(amount)
}

fun onOK() {
if (model.tx.isComplete()) {
if (arguments?.containsKey("txHex") == true || model.tx.isComplete()) {
onPostExecute(Unit)
} else {
launchTask()
Expand All @@ -300,11 +354,19 @@ class SendDialog : TaskLauncherDialog<Unit>() {

override fun onPostExecute(result: Unit) {
try {
val txResult = model.tx.value!!
if (txResult.isDummy) throw ToastException(R.string.Invalid_address)
txResult.get() // May throw other ToastExceptions.
// If a transaction has been passed from ColdLoad, it will be used.
// Otherwise, the transaction is built from the fields in the Send dialog.
val txHex = arguments?.getString("txHex")
if (txHex == null) {
val txResult = model.tx.value!!
if (txResult.isDummy) throw ToastException(R.string.Invalid_address)
txResult.get() // May throw other ToastExceptions.
}
showDialog(this, SendPasswordDialog().apply { arguments = Bundle().apply {
putString("description", this@SendDialog.etDescription.text.toString())
if (txHex != null) {
putString("txHex", txHex)
}
}})
} catch (e: ToastException) { e.show() }
}
Expand Down Expand Up @@ -366,7 +428,13 @@ class SendContactsDialog : MenuDialog() {

class SendPasswordDialog : PasswordDialog<Unit>() {
val sendDialog by lazy { targetFragment as SendDialog }
val tx by lazy { sendDialog.model.tx.value!!.get() }
val tx: PyObject by lazy {
if (arguments?.containsKey("txHex") == true) {
libTransaction.callAttr("Transaction", arguments!!.getString("txHex"))
} else {
sendDialog.model.tx.value!!.get()
}
}

override fun onPassword(password: String) {
val wallet = sendDialog.wallet
Expand Down Expand Up @@ -396,6 +464,18 @@ class SendPasswordDialog : PasswordDialog<Unit>() {
} else {
copyToClipboard(tx.toString(), R.string.signed_transaction)
}

// The presence of "txHex" argument means that this dialog had been called from ColdLoad.
// If the transaction cannot be broadcasted after signing, close the ColdLoad dialog.
// Otherwise, put the fully signed string into ColdLoad, making it available for sending.
if (arguments!!.containsKey("txHex")) {
val coldLoadDialog: ColdLoadDialog? = findDialog(activity!!, ColdLoadDialog::class)
if (!canBroadcast(tx)) {
coldLoadDialog!!.dismiss()
} else {
coldLoadDialog!!.etTransaction.setText(tx.toString())
}
}
}
}

Expand Down

0 comments on commit 0663281

Please sign in to comment.