Skip to content

Commit

Permalink
Merge bf71bbc into c020816
Browse files Browse the repository at this point in the history
  • Loading branch information
Aldin-SXR committed Jun 6, 2021
2 parents c020816 + bf71bbc commit a71a633
Show file tree
Hide file tree
Showing 18 changed files with 957 additions and 63 deletions.
134 changes: 129 additions & 5 deletions android/app/src/main/java/org/electroncash/electroncash3/ColdLoad.kt
Expand Up @@ -2,8 +2,12 @@ package org.electroncash.electroncash3

import android.content.ClipboardManager
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
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.*

Expand All @@ -17,12 +21,13 @@ val libTransaction by lazy { libMod("transaction") }
// Valid transaction quickly show up in transactions.

class ColdLoadDialog : AlertDialogFragment() {

override fun onBuildDialog(builder: AlertDialog.Builder) {
builder.setTitle(R.string.load_transaction)
.setView(R.layout.load)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.qr_code, null)
.setPositiveButton(R.string.send, null)
.setPositiveButton(R.string.OK, null)
}

override fun onShowDialog() {
Expand All @@ -42,23 +47,70 @@ class ColdLoadDialog : AlertDialogFragment() {
}

private fun updateUI() {
val currenttext = etTransaction.text
//checks if text is blank. further validations can be added here
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = currenttext.isNotBlank()
val tx = libTransaction.callAttr("Transaction", etTransaction.text.toString())
updateStatusText(tx)

// Check hex transaction signing status
if (canSign(tx)) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.sign)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
} else if (canBroadcast(tx)) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.send)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
} else {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
}
}

// Receives the result of a QR scan.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)

if (result != null && result.contents != null) {
etTransaction.setText(result.contents)
// Try to decode the QR content as Base43; if that fails, treat it as is
val txHex: String = try {
baseDecode(result.contents, 43)
} catch (e: PyException) {
result.contents
}
etTransaction.setText(txHex)
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}

fun onOK() {
val tx = libTransaction.callAttr("Transaction", etTransaction.text.toString())

// If transaction can be broadcasted, broadcast it.
// Otherwise, prompt for signing. If the transaction hex is invalid,
// the OK button will be disabled, regardless.
try {
if (canBroadcast(tx)) {
broadcastSignedTransaction(tx)
} else {
signLoadedTransaction()
}
} catch (e: ToastException) {
e.show()
}
}

/**
* Sign a loaded transaction.
*/
private fun signLoadedTransaction() {
val dialog = SignPasswordDialog()
dialog.setArguments(Bundle().apply {
putString("tx", etTransaction.text.toString())
})
showDialog(this, dialog)
}

/**
* Broadcast a signed transaction.
*/
private fun broadcastSignedTransaction(tx: PyObject) {
try {
if (!daemonModel.isConnected()) {
throw ToastException(R.string.not_connected)
Expand All @@ -69,4 +121,76 @@ class ColdLoadDialog : AlertDialogFragment() {
dismiss()
} catch (e: ToastException) { e.show() }
}


/**
* Check if a loaded transaction is signed.
* Displays the signing status below the raw TX field.
* (signed, partially signed, empty or invalid)
*/
private fun updateStatusText(tx: PyObject) {
try {
if (etTransaction.text.isBlank()) {
idTxStatus.setText(R.string.empty)
} else {
// Check if the transaction can be processed by this wallet or not
val txInfo = daemonModel.wallet!!.callAttr("get_tx_info", tx)

if (txInfo["amount"] == null && txInfo["status"].toString() != "Signed") {
idTxStatus.setText(R.string.transaction_unrelated)
} else {
idTxStatus.setText(txInfo["status"].toString())
}
}
} catch (e: PyException) {
idTxStatus.setText(R.string.invalid)
}
}
}

/* Check if the wallet can sign the transaction */
fun canSign(tx: PyObject): Boolean {
return try {
!tx.callAttr("is_complete").toBoolean() and
daemonModel.wallet!!.callAttr("can_sign", tx).toBoolean()
} catch (e: PyException) {
false
}
}

/* Check if the transaction is ready to be broadcasted */
fun canBroadcast(tx: PyObject): Boolean {
return try {
tx.callAttr("is_complete").toBoolean()
} catch (e: PyException) {
false
}
}

/**
* Sign a loaded transaction dialog.
*/
class SignPasswordDialog : PasswordDialog<Unit>() {
val coldLoadDialog by lazy { targetFragment as ColdLoadDialog }
// Schnorr signing is supported by standard and imported private key wallets.
val signSchnorr = daemonModel.walletType in listOf("standard", "imported_privkey")

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

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

postToUiThread {
coldLoadDialog.etTransaction.setText(tx.toString())
}
}

override fun onPostExecute(result: Unit) {
if (!canBroadcast(tx)) {
coldLoadDialog.dismiss()
copyToClipboard(tx.toString(), R.string.signed_transaction)
}
}
}
Expand Up @@ -32,8 +32,14 @@ class DaemonModel(val config: PyObject) {
val walletName: String?
get() {
val wallet = this.wallet
return if (wallet == null) null else wallet.callAttr("basename").toString()
return wallet?.callAttr("basename")?.toString()
}
val walletType: String?
get() {
return if (wallet == null) null else commands.callAttr("get", "wallet_type").toString()
}
val scriptType: String?
get() = wallet?.get("txin_type").toString()

lateinit var watchdog: Runnable

Expand Down
45 changes: 42 additions & 3 deletions android/app/src/main/java/org/electroncash/electroncash3/Main.kt
Expand Up @@ -27,10 +27,13 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.observe
import com.chaquo.python.Kwarg
import kotlinx.android.synthetic.main.main.*
import kotlinx.android.synthetic.main.show_master_key.walletMasterKey
import kotlinx.android.synthetic.main.wallet_export.*
import kotlinx.android.synthetic.main.wallet_information.*
import kotlinx.android.synthetic.main.wallet_open.*
import kotlinx.android.synthetic.main.wallet_rename.*
import java.io.File
import java.lang.NullPointerException
import kotlin.reflect.KClass


Expand Down Expand Up @@ -231,6 +234,7 @@ class MainActivity : AppCompatActivity(R.layout.main) {
}
R.id.menuChangePassword -> showDialog(this, PasswordChangeDialog())
R.id.menuShowSeed -> { showDialog(this, SeedPasswordDialog()) }
R.id.menuWalletInformation -> { showDialog(this, WalletInformationDialog()) }
R.id.menuExportSigned -> {
try {
showDialog(this, SendDialog().apply {
Expand Down Expand Up @@ -654,15 +658,50 @@ class SeedPasswordDialog : PasswordDialog<SeedResult>() {
}
}


class SeedDialog : AlertDialogFragment() {
override fun onBuildDialog(builder: AlertDialog.Builder) {
builder.setTitle(R.string.Wallet_seed)
.setView(R.layout.wallet_new_2)
.setPositiveButton(android.R.string.ok, null)
.setView(R.layout.wallet_new_2)
.setPositiveButton(android.R.string.ok, null)
}

override fun onShowDialog() {
setupSeedDialog(this)
}
}

class WalletInformationDialog : AlertDialogFragment() {
override fun onBuildDialog(builder: AlertDialog.Builder) {
builder.setView(R.layout.wallet_information)
.setPositiveButton(android.R.string.ok, null)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

fabCopyMasterKey2.setOnClickListener {
val textToCopy = walletMasterKey.text
copyToClipboard(textToCopy, R.string.Master_public)
}
}

override fun onShowDialog() {
super.onShowDialog()

// Imported wallets do not have a master public key.
val masterKey = daemonModel.commands.callAttr("getmpk")?.toString()
if (masterKey != null) {
walletMasterKey.setText(masterKey)
walletMasterKey.setFocusable(false)
} else {
tvMasterPublicKey.setVisibility(View.GONE)
walletMasterKey.setVisibility(View.GONE)
// Using View.INVISIBLE on the 'Copy' button to preserve layout.
(fabCopyMasterKey2 as View).setVisibility(View.INVISIBLE)
}

idWalletName.setText(daemonModel.walletName)
idWalletType.setText(daemonModel.walletType)
idScriptType.setText(daemonModel.scriptType)
}
}

0 comments on commit a71a633

Please sign in to comment.