URI: 
       tMerge pull request #4596 from SomberNight/txoutput_namedtuple - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 194ee395e70ad44d19732acd69fe8eec2693f968
   DIR parent f64062b6f13dfc340fedcdfdf24156ae21414c1c
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Thu,  2 Aug 2018 12:49:51 +0200
       
       Merge pull request #4596 from SomberNight/txoutput_namedtuple
       
       ttransaction: introduce TxOutput namedtuple
       Diffstat:
         M electrum/address_synchronizer.py    |      13 ++++++-------
         M electrum/coinchooser.py             |       4 ++--
         M electrum/commands.py                |       6 +++---
         M electrum/gui/kivy/main_window.py    |       3 ++-
         M electrum/gui/kivy/uix/dialogs/__in… |       6 +++---
         M electrum/gui/kivy/uix/screens.py    |       3 ++-
         M electrum/gui/qt/main_window.py      |      12 ++++++------
         M electrum/gui/qt/paytoedit.py        |      13 +++++++------
         M electrum/gui/stdio.py               |       4 +++-
         M electrum/gui/text.py                |       4 +++-
         M electrum/paymentrequest.py          |       7 ++++---
         M electrum/plugins/digitalbitbox/dig… |       6 +++---
         M electrum/plugins/hw_wallet/plugin.… |      12 ++++++------
         M electrum/plugins/keepkey/keepkey.py |       5 +++--
         M electrum/plugins/ledger/ledger.py   |      10 +++++-----
         M electrum/plugins/safe_t/safe_t.py   |       5 +++--
         M electrum/plugins/trezor/trezor.py   |       5 +++--
         M electrum/plugins/trustedcoin/trust… |       9 +++++----
         M electrum/tests/test_wallet_vertica… |      45 ++++++++++++++++---------------
         M electrum/transaction.py             |      26 +++++++++++++++-----------
         M electrum/wallet.py                  |      33 ++++++++++++++-----------------
       
       21 files changed, 122 insertions(+), 109 deletions(-)
       ---
   DIR diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py
       t@@ -28,7 +28,7 @@ from collections import defaultdict
        from . import bitcoin
        from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY
        from .util import PrintError, profiler, bfh, VerifiedTxInfo, TxMinedStatus
       -from .transaction import Transaction
       +from .transaction import Transaction, TxOutput
        from .synchronizer import Synchronizer
        from .verifier import SPV
        from .blockchain import hash_header
       t@@ -112,12 +112,11 @@ class AddressSynchronizer(PrintError):
                            return addr
                return None
        
       -    def get_txout_address(self, txo):
       -        _type, x, v = txo
       -        if _type == TYPE_ADDRESS:
       -            addr = x
       -        elif _type == TYPE_PUBKEY:
       -            addr = bitcoin.public_key_to_p2pkh(bfh(x))
       +    def get_txout_address(self, txo: TxOutput):
       +        if txo.type == TYPE_ADDRESS:
       +            addr = txo.address
       +        elif txo.type == TYPE_PUBKEY:
       +            addr = bitcoin.public_key_to_p2pkh(bfh(txo.address))
                else:
                    addr = None
                return addr
   DIR diff --git a/electrum/coinchooser.py b/electrum/coinchooser.py
       t@@ -26,7 +26,7 @@ from collections import defaultdict, namedtuple
        from math import floor, log10
        
        from .bitcoin import sha256, COIN, TYPE_ADDRESS, is_address
       -from .transaction import Transaction
       +from .transaction import Transaction, TxOutput
        from .util import NotEnoughFunds, PrintError
        
        
       t@@ -178,7 +178,7 @@ class CoinChooserBase(PrintError):
                # size of the change output, add it to the transaction.
                dust = sum(amount for amount in amounts if amount < dust_threshold)
                amounts = [amount for amount in amounts if amount >= dust_threshold]
       -        change = [(TYPE_ADDRESS, addr, amount)
       +        change = [TxOutput(TYPE_ADDRESS, addr, amount)
                          for addr, amount in zip(change_addrs, amounts)]
                self.print_error('change:', change)
                if dust:
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -38,7 +38,7 @@ from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_enc
        from . import bitcoin
        from .bitcoin import is_address,  hash_160, COIN, TYPE_ADDRESS
        from .i18n import _
       -from .transaction import Transaction, multisig_script
       +from .transaction import Transaction, multisig_script, TxOutput
        from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
        from .plugin import run_hook
        
       t@@ -226,7 +226,7 @@ class Commands:
                        txin['signatures'] = [None]
                        txin['num_sig'] = 1
        
       -        outputs = [(TYPE_ADDRESS, x['address'], int(x['value'])) for x in outputs]
       +        outputs = [TxOutput(TYPE_ADDRESS, x['address'], int(x['value'])) for x in outputs]
                tx = Transaction.from_io(inputs, outputs, locktime=locktime)
                tx.sign(keypairs)
                return tx.as_dict()
       t@@ -415,7 +415,7 @@ class Commands:
                for address, amount in outputs:
                    address = self._resolver(address)
                    amount = satoshis(amount)
       -            final_outputs.append((TYPE_ADDRESS, address, amount))
       +            final_outputs.append(TxOutput(TYPE_ADDRESS, address, amount))
        
                coins = self.wallet.get_spendable_coins(domain, self.config)
                tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)
   DIR diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
       t@@ -713,13 +713,14 @@ class ElectrumWindow(App):
                self.fiat_balance = self.fx.format_amount(c+u+x) + ' [size=22dp]%s[/size]'% self.fx.ccy
        
            def get_max_amount(self):
       +        from electrum.transaction import TxOutput
                if run_hook('abort_send', self):
                    return ''
                inputs = self.wallet.get_spendable_coins(None, self.electrum_config)
                if not inputs:
                    return ''
                addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
       -        outputs = [(TYPE_ADDRESS, addr, '!')]
       +        outputs = [TxOutput(TYPE_ADDRESS, addr, '!')]
                try:
                    tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
                except NoDynamicFeeEstimates as e:
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/__init__.py b/electrum/gui/kivy/uix/dialogs/__init__.py
       t@@ -206,9 +206,9 @@ class OutputList(RecycleView):
        
            def update(self, outputs):
                res = []
       -        for (type, address, amount) in outputs:
       -            value = self.app.format_amount_and_units(amount)
       -            res.append({'address': address, 'value': value})
       +        for o in outputs:
       +            value = self.app.format_amount_and_units(o.value)
       +            res.append({'address': o.address, 'value': value})
                self.data = res
        
        
   DIR diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py
       t@@ -20,6 +20,7 @@ from kivy.utils import platform
        
        from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat
        from electrum import bitcoin
       +from electrum.transaction import TxOutput
        from electrum.util import timestamp_to_datetime
        from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
        from electrum.plugin import run_hook
       t@@ -256,7 +257,7 @@ class SendScreen(CScreen):
                    except:
                        self.app.show_error(_('Invalid amount') + ':\n' + self.screen.amount)
                        return
       -            outputs = [(bitcoin.TYPE_ADDRESS, address, amount)]
       +            outputs = [TxOutput(bitcoin.TYPE_ADDRESS, address, amount)]
                message = self.screen.message
                amount = sum(map(lambda x:x[2], outputs))
                if self.app.electrum_config.get('use_rbf'):
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -50,7 +50,7 @@ from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
                                   export_meta, import_meta, bh2u, bfh, InvalidPassword,
                                   base_units, base_units_list, base_unit_name_to_decimal_point,
                                   decimal_point_to_base_unit_name, quantize_feerate)
       -from electrum.transaction import Transaction
       +from electrum.transaction import Transaction, TxOutput
        from electrum.address_synchronizer import AddTransactionException
        from electrum.wallet import Multisig_Wallet, CannotBumpFee
        
       t@@ -1306,7 +1306,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    outputs = self.payto_e.get_outputs(self.is_max)
                    if not outputs:
                        _type, addr = self.get_payto_or_dummy()
       -                outputs = [(_type, addr, amount)]
       +                outputs = [TxOutput(_type, addr, amount)]
                    is_sweep = bool(self.tx_external_keypairs)
                    make_tx = lambda fee_est: \
                        self.wallet.make_unsigned_transaction(
       t@@ -1485,14 +1485,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    self.show_error(_('No outputs'))
                    return
        
       -        for _type, addr, amount in outputs:
       -            if addr is None:
       +        for o in outputs:
       +            if o.address is None:
                        self.show_error(_('Bitcoin Address is None'))
                        return
       -            if _type == TYPE_ADDRESS and not bitcoin.is_address(addr):
       +            if o.type == TYPE_ADDRESS and not bitcoin.is_address(o.address):
                        self.show_error(_('Invalid Bitcoin Address'))
                        return
       -            if amount is None:
       +            if o.value is None:
                        self.show_error(_('Invalid Amount'))
                        return
        
   DIR diff --git a/electrum/gui/qt/paytoedit.py b/electrum/gui/qt/paytoedit.py
       t@@ -29,6 +29,7 @@ from decimal import Decimal
        
        from electrum import bitcoin
        from electrum.util import bfh
       +from electrum.transaction import TxOutput
        
        from .qrtextedit import ScanQRTextEdit
        from .completion_text_edit import CompletionTextEdit
       t@@ -77,7 +78,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
                x, y = line.split(',')
                out_type, out = self.parse_output(x)
                amount = self.parse_amount(y)
       -        return out_type, out, amount
       +        return TxOutput(out_type, out, amount)
        
            def parse_output(self, x):
                try:
       t@@ -139,16 +140,16 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
                is_max = False
                for i, line in enumerate(lines):
                    try:
       -                _type, to_address, amount = self.parse_address_and_amount(line)
       +                output = self.parse_address_and_amount(line)
                    except:
                        self.errors.append((i, line.strip()))
                        continue
        
       -            outputs.append((_type, to_address, amount))
       -            if amount == '!':
       +            outputs.append(output)
       +            if output.value == '!':
                        is_max = True
                    else:
       -                total += amount
       +                total += output.value
        
                self.win.is_max = is_max
                self.outputs = outputs
       t@@ -174,7 +175,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
                        amount = self.amount_edit.get_amount()
        
                    _type, addr = self.payto_address
       -            self.outputs = [(_type, addr, amount)]
       +            self.outputs = [TxOutput(_type, addr, amount)]
        
                return self.outputs[:]
        
   DIR diff --git a/electrum/gui/stdio.py b/electrum/gui/stdio.py
       t@@ -4,6 +4,7 @@ _ = lambda x:x
        from electrum import WalletStorage, Wallet
        from electrum.util import format_satoshis, set_verbosity
        from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
       +from electrum.transaction import TxOutput
        import getpass, datetime
        
        # minimal fdisk like gui for console usage
       t@@ -189,7 +190,8 @@ class ElectrumGui:
                    if c == "n": return
        
                try:
       -            tx = self.wallet.mktx([(TYPE_ADDRESS, self.str_recipient, amount)], password, self.config, fee)
       +            tx = self.wallet.mktx([TxOutput(TYPE_ADDRESS, self.str_recipient, amount)],
       +                                  password, self.config, fee)
                except Exception as e:
                    print(str(e))
                    return
   DIR diff --git a/electrum/gui/text.py b/electrum/gui/text.py
       t@@ -6,6 +6,7 @@ import getpass
        import electrum
        from electrum.util import format_satoshis, set_verbosity
        from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
       +from electrum.transaction import TxOutput
        from .. import Wallet, WalletStorage
        
        _ = lambda x:x
       t@@ -340,7 +341,8 @@ class ElectrumGui:
                else:
                    password = None
                try:
       -            tx = self.wallet.mktx([(TYPE_ADDRESS, self.str_recipient, amount)], password, self.config, fee)
       +            tx = self.wallet.mktx([TxOutput(TYPE_ADDRESS, self.str_recipient, amount)],
       +                                  password, self.config, fee)
                except Exception as e:
                    self.show_message(str(e))
                    return
   DIR diff --git a/electrum/paymentrequest.py b/electrum/paymentrequest.py
       t@@ -42,6 +42,7 @@ from .util import print_error, bh2u, bfh
        from .util import export_meta, import_meta
        
        from .bitcoin import TYPE_ADDRESS
       +from .transaction import TxOutput
        
        REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
        ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}
       t@@ -123,7 +124,7 @@ class PaymentRequest:
                self.outputs = []
                for o in self.details.outputs:
                    addr = transaction.get_address_from_output_script(o.script)[1]
       -            self.outputs.append((TYPE_ADDRESS, addr, o.amount))
       +            self.outputs.append(TxOutput(TYPE_ADDRESS, addr, o.amount))
                self.memo = self.details.memo
                self.payment_url = self.details.payment_url
        
       t@@ -225,8 +226,8 @@ class PaymentRequest:
        
            def get_address(self):
                o = self.outputs[0]
       -        assert o[0] == TYPE_ADDRESS
       -        return o[1]
       +        assert o.type == TYPE_ADDRESS
       +        return o.address
        
            def get_requestor(self):
                return self.requestor if self.requestor else self.get_address()
   DIR diff --git a/electrum/plugins/digitalbitbox/digitalbitbox.py b/electrum/plugins/digitalbitbox/digitalbitbox.py
       t@@ -534,9 +534,9 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
                            self.give_error("No matching x_key for sign_transaction") # should never happen
        
                    # Build pubkeyarray from outputs
       -            for _type, address, amount in tx.outputs():
       -                assert _type == TYPE_ADDRESS
       -                info = tx.output_info.get(address)
       +            for o in tx.outputs():
       +                assert o.type == TYPE_ADDRESS
       +                info = tx.output_info.get(o.address)
                        if info is not None:
                            index, xpubs, m = info
                            changePath = self.get_derivation() + "/%d/%d" % index
   DIR diff --git a/electrum/plugins/hw_wallet/plugin.py b/electrum/plugins/hw_wallet/plugin.py
       t@@ -28,7 +28,7 @@ from electrum.plugin import BasePlugin, hook
        from electrum.i18n import _
        from electrum.bitcoin import is_address, TYPE_SCRIPT
        from electrum.util import bfh
       -from electrum.transaction import opcodes
       +from electrum.transaction import opcodes, TxOutput
        
        
        class HW_PluginBase(BasePlugin):
       t@@ -91,13 +91,13 @@ def is_any_tx_output_on_change_branch(tx):
            return False
        
        
       -def trezor_validate_op_return_output_and_get_data(_type, address, amount):
       -    if _type != TYPE_SCRIPT:
       -        raise Exception("Unexpected output type: {}".format(_type))
       -    script = bfh(address)
       +def trezor_validate_op_return_output_and_get_data(output: TxOutput) -> bytes:
       +    if output.type != TYPE_SCRIPT:
       +        raise Exception("Unexpected output type: {}".format(output.type))
       +    script = bfh(output.address)
            if not (script[0] == opcodes.OP_RETURN and
                    script[1] == len(script) - 2 and script[1] <= 75):
                raise Exception(_("Only OP_RETURN scripts, with one constant push, are supported."))
       -    if amount != 0:
       +    if output.value != 0:
                raise Exception(_("Amount for OP_RETURN output must be zero."))
            return script[2:]
   DIR diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py
       t@@ -382,7 +382,7 @@ class KeepKeyPlugin(HW_PluginBase):
                    txoutputtype.amount = amount
                    if _type == TYPE_SCRIPT:
                        txoutputtype.script_type = self.types.PAYTOOPRETURN
       -                txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(_type, address, amount)
       +                txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
                    elif _type == TYPE_ADDRESS:
                        if is_segwit_address(address):
                            txoutputtype.script_type = self.types.PAYTOWITNESS
       t@@ -401,7 +401,8 @@ class KeepKeyPlugin(HW_PluginBase):
                has_change = False
                any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
        
       -        for _type, address, amount in tx.outputs():
       +        for o in tx.outputs():
       +            _type, address, amount = o.type, o.address, o.value
                    use_create_by_derivation = False
        
                    info = tx.output_info.get(address)
   DIR diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py
       t@@ -394,9 +394,9 @@ class Ledger_KeyStore(Hardware_KeyStore):
                            self.give_error("Transaction with more than 2 outputs not supported")
                    has_change = False
                    any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
       -            for _type, address, amount in tx.outputs():
       -                assert _type == TYPE_ADDRESS
       -                info = tx.output_info.get(address)
       +            for o in tx.outputs():
       +                assert o.type == TYPE_ADDRESS
       +                info = tx.output_info.get(o.address)
                        if (info is not None) and len(tx.outputs()) > 1 \
                                and not has_change:
                            index, xpubs, m = info
       t@@ -407,9 +407,9 @@ class Ledger_KeyStore(Hardware_KeyStore):
                                changePath = self.get_derivation()[2:] + "/%d/%d"%index
                                has_change = True
                            else:
       -                        output = address
       +                        output = o.address
                        else:
       -                    output = address
       +                    output = o.address
        
                self.handler.show_message(_("Confirm Transaction on your Ledger device..."))
                try:
   DIR diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py
       t@@ -453,7 +453,7 @@ class SafeTPlugin(HW_PluginBase):
                    txoutputtype.amount = amount
                    if _type == TYPE_SCRIPT:
                        txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN
       -                txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(_type, address, amount)
       +                txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
                    elif _type == TYPE_ADDRESS:
                        txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS
                        txoutputtype.address = address
       t@@ -463,7 +463,8 @@ class SafeTPlugin(HW_PluginBase):
                has_change = False
                any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
        
       -        for _type, address, amount in tx.outputs():
       +        for o in tx.outputs():
       +            _type, address, amount = o.type, o.address, o.value
                    use_create_by_derivation = False
        
                    info = tx.output_info.get(address)
   DIR diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py
       t@@ -464,7 +464,7 @@ class TrezorPlugin(HW_PluginBase):
                    txoutputtype.amount = amount
                    if _type == TYPE_SCRIPT:
                        txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN
       -                txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(_type, address, amount)
       +                txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
                    elif _type == TYPE_ADDRESS:
                        txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS
                        txoutputtype.address = address
       t@@ -474,7 +474,8 @@ class TrezorPlugin(HW_PluginBase):
                has_change = False
                any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
        
       -        for _type, address, amount in tx.outputs():
       +        for o in tx.outputs():
       +            _type, address, amount = o.type, o.address, o.value
                    use_create_by_derivation = False
        
                    info = tx.output_info.get(address)
   DIR diff --git a/electrum/plugins/trustedcoin/trustedcoin.py b/electrum/plugins/trustedcoin/trustedcoin.py
       t@@ -33,6 +33,7 @@ from urllib.parse import quote
        
        from electrum import bitcoin, ecc, constants, keystore, version
        from electrum.bitcoin import *
       +from electrum.transaction import TxOutput
        from electrum.mnemonic import Mnemonic
        from electrum.wallet import Multisig_Wallet, Deterministic_Wallet
        from electrum.i18n import _
       t@@ -273,7 +274,7 @@ class Wallet_2fa(Multisig_Wallet):
                fee = self.extra_fee(config) if not is_sweep else 0
                if fee:
                    address = self.billing_info['billing_address']
       -            fee_output = (TYPE_ADDRESS, address, fee)
       +            fee_output = TxOutput(TYPE_ADDRESS, address, fee)
                    try:
                        tx = mk_tx(outputs + [fee_output])
                    except NotEnoughFunds:
       t@@ -395,9 +396,9 @@ class TrustedCoinPlugin(BasePlugin):
            def get_tx_extra_fee(self, wallet, tx):
                if type(wallet) != Wallet_2fa:
                    return
       -        for _type, addr, amount in tx.outputs():
       -            if _type == TYPE_ADDRESS and wallet.is_billing_address(addr):
       -                return addr, amount
       +        for o in tx.outputs():
       +            if o.type == TYPE_ADDRESS and wallet.is_billing_address(o.address):
       +                return o.address, o.value
        
            def finish_requesting(func):
                def f(self, *args, **kwargs):
   DIR diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py
       t@@ -10,6 +10,7 @@ from electrum import SimpleConfig
        from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT
        from electrum.wallet import sweep, Multisig_Wallet, Standard_Wallet, Imported_Wallet
        from electrum.util import bfh, bh2u
       +from electrum.transaction import TxOutput
        
        from electrum.plugins.trustedcoin import trustedcoin
        
       t@@ -532,7 +533,7 @@ class TestWalletSending(TestCaseForTestnet):
                wallet1.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # wallet1 -> wallet2
       -        outputs = [(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 250000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 250000)]
                tx = wallet1.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
        
                self.assertTrue(tx.is_complete())
       t@@ -552,7 +553,7 @@ class TestWalletSending(TestCaseForTestnet):
                wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
        
                # wallet2 -> wallet1
       -        outputs = [(bitcoin.TYPE_ADDRESS, wallet1.get_receiving_address(), 100000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1.get_receiving_address(), 100000)]
                tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
        
                self.assertTrue(tx.is_complete())
       t@@ -605,7 +606,7 @@ class TestWalletSending(TestCaseForTestnet):
                wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # wallet1 -> wallet2
       -        outputs = [(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 370000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 370000)]
                tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx = Transaction(tx.serialize())  # simulates moving partial txn between cosigners
                self.assertFalse(tx.is_complete())
       t@@ -628,7 +629,7 @@ class TestWalletSending(TestCaseForTestnet):
                wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
        
                # wallet2 -> wallet1
       -        outputs = [(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)]
                tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
        
                self.assertTrue(tx.is_complete())
       t@@ -696,7 +697,7 @@ class TestWalletSending(TestCaseForTestnet):
                wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # wallet1 -> wallet2
       -        outputs = [(bitcoin.TYPE_ADDRESS, wallet2a.get_receiving_address(), 165000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2a.get_receiving_address(), 165000)]
                tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                txid = tx.txid()
                tx = Transaction(tx.serialize())  # simulates moving partial txn between cosigners
       t@@ -722,7 +723,7 @@ class TestWalletSending(TestCaseForTestnet):
                wallet2a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
        
                # wallet2 -> wallet1
       -        outputs = [(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)]
                tx = wallet2a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                txid = tx.txid()
                tx = Transaction(tx.serialize())  # simulates moving partial txn between cosigners
       t@@ -776,7 +777,7 @@ class TestWalletSending(TestCaseForTestnet):
                wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # wallet1 -> wallet2
       -        outputs = [(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 1000000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 1000000)]
                tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
        
                self.assertTrue(tx.is_complete())
       t@@ -796,7 +797,7 @@ class TestWalletSending(TestCaseForTestnet):
                wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
        
                # wallet2 -> wallet1
       -        outputs = [(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 300000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 300000)]
                tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
        
                self.assertTrue(tx.is_complete())
       t@@ -832,7 +833,7 @@ class TestWalletSending(TestCaseForTestnet):
                wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, '2N1VTMMFb91SH9SNRAkT7z8otP5eZEct4KL', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2N1VTMMFb91SH9SNRAkT7z8otP5eZEct4KL', 2500000)]
                coins = wallet.get_spendable_coins(domain=None, config=self.config)
                tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000)
                tx.set_rbf(True)
       t@@ -918,7 +919,7 @@ class TestWalletSending(TestCaseForTestnet):
                wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, '2N1VTMMFb91SH9SNRAkT7z8otP5eZEct4KL', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2N1VTMMFb91SH9SNRAkT7z8otP5eZEct4KL', 2500000)]
                coins = wallet.get_spendable_coins(domain=None, config=self.config)
                tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000)
                tx.set_rbf(True)
       t@@ -1048,7 +1049,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325340
       t@@ -1088,7 +1089,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325341
       t@@ -1129,7 +1130,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325341
       t@@ -1165,7 +1166,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325340
       t@@ -1199,7 +1200,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325340
       t@@ -1233,7 +1234,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325340
       t@@ -1270,7 +1271,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325340
       t@@ -1307,7 +1308,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325340
       t@@ -1344,7 +1345,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325340
       t@@ -1393,7 +1394,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, '2MuCQQHJNnrXzQzuqfUCfAwAjPqpyEHbgue', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2MuCQQHJNnrXzQzuqfUCfAwAjPqpyEHbgue', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325503
       t@@ -1450,7 +1451,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, '2N8CtJRwxb2GCaiWWdSHLZHHLoZy53CCyxf', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2N8CtJRwxb2GCaiWWdSHLZHHLoZy53CCyxf', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325504
       t@@ -1509,7 +1510,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
        
                # create unsigned tx
       -        outputs = [(bitcoin.TYPE_ADDRESS, '2MyoZVy8T1t94yLmyKu8DP1SmbWvnxbkwRA', 2500000)]
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2MyoZVy8T1t94yLmyKu8DP1SmbWvnxbkwRA', 2500000)]
                tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
                tx.set_rbf(True)
                tx.locktime = 1325505
   DIR diff --git a/electrum/transaction.py b/electrum/transaction.py
       t@@ -27,7 +27,7 @@
        
        # Note: The deserialization code originally comes from ABE.
        
       -from typing import Sequence, Union
       +from typing import Sequence, Union, NamedTuple
        
        from .util import print_error, profiler
        
       t@@ -59,6 +59,10 @@ class NotRecognizedRedeemScript(Exception):
            pass
        
        
       +TxOutput = NamedTuple("TxOutput", [('type', int), ('address', str), ('value', Union[int, str])])
       +# ^ value is str when the output is set to max: '!'
       +
       +
        class BCDataStream(object):
            def __init__(self):
                self.input = None
       t@@ -721,7 +725,7 @@ class Transaction:
                    return
                d = deserialize(self.raw, force_full_parse)
                self._inputs = d['inputs']
       -        self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']]
       +        self._outputs = [TxOutput(x['type'], x['address'], x['value']) for x in d['outputs']]
                self.locktime = d['lockTime']
                self.version = d['version']
                self.is_partial_originally = d['partial']
       t@@ -1180,17 +1184,17 @@ class Transaction:
        
            def get_outputs(self):
                """convert pubkeys to addresses"""
       -        o = []
       -        for type, x, v in self.outputs():
       -            if type == TYPE_ADDRESS:
       -                addr = x
       -            elif type == TYPE_PUBKEY:
       +        outputs = []
       +        for o in self.outputs():
       +            if o.type == TYPE_ADDRESS:
       +                addr = o.address
       +            elif o.type == TYPE_PUBKEY:
                        # TODO do we really want this conversion? it's not really that address after all
       -                addr = bitcoin.public_key_to_p2pkh(bfh(x))
       +                addr = bitcoin.public_key_to_p2pkh(bfh(o.address))
                    else:
       -                addr = 'SCRIPT ' + x
       -            o.append((addr,v))      # consider using yield (addr, v)
       -        return o
       +                addr = 'SCRIPT ' + o.address
       +            outputs.append((addr, o.value))      # consider using yield (addr, v)
       +        return outputs
        
            def get_output_addresses(self):
                return [addr for addr, val in self.get_outputs()]
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -51,7 +51,7 @@ from .keystore import load_keystore, Hardware_KeyStore
        from .storage import multisig_type, STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW
        
        from . import transaction, bitcoin, coinchooser, paymentrequest, contacts
       -from .transaction import Transaction
       +from .transaction import Transaction, TxOutput
        from .plugin import run_hook
        from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
                                           TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED)
       t@@ -133,7 +133,7 @@ def sweep(privkeys, network, config, recipient, fee=None, imax=100):
            inputs, keypairs = sweep_preparations(privkeys, network, imax)
            total = sum(i.get('value') for i in inputs)
            if fee is None:
       -        outputs = [(TYPE_ADDRESS, recipient, total)]
       +        outputs = [TxOutput(TYPE_ADDRESS, recipient, total)]
                tx = Transaction.from_io(inputs, outputs)
                fee = config.estimate_fee(tx.estimated_size())
            if total - fee < 0:
       t@@ -141,7 +141,7 @@ def sweep(privkeys, network, config, recipient, fee=None, imax=100):
            if total - fee < dust_threshold(network):
                raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network)))
        
       -    outputs = [(TYPE_ADDRESS, recipient, total - fee)]
       +    outputs = [TxOutput(TYPE_ADDRESS, recipient, total - fee)]
            locktime = network.get_local_height()
        
            tx = Transaction.from_io(inputs, outputs, locktime=locktime)
       t@@ -538,11 +538,10 @@ class Abstract_Wallet(AddressSynchronizer):
                # check outputs
                i_max = None
                for i, o in enumerate(outputs):
       -            _type, data, value = o
       -            if _type == TYPE_ADDRESS:
       -                if not is_address(data):
       -                    raise Exception("Invalid bitcoin address: {}".format(data))
       -            if value == '!':
       +            if o.type == TYPE_ADDRESS:
       +                if not is_address(o.address):
       +                    raise Exception("Invalid bitcoin address: {}".format(o.address))
       +            if o.value == '!':
                        if i_max is not None:
                            raise Exception("More than one output set to spend max")
                        i_max = i
       t@@ -593,14 +592,13 @@ class Abstract_Wallet(AddressSynchronizer):
                else:
                    # FIXME?? this might spend inputs with negative effective value...
                    sendable = sum(map(lambda x:x['value'], inputs))
       -            _type, data, value = outputs[i_max]
       -            outputs[i_max] = (_type, data, 0)
       +            outputs[i_max] = outputs[i_max]._replace(value=0)
                    tx = Transaction.from_io(inputs, outputs[:])
                    fee = fee_estimator(tx.estimated_size())
                    amount = sendable - tx.output_value() - fee
                    if amount < 0:
                        raise NotEnoughFunds()
       -            outputs[i_max] = (_type, data, amount)
       +            outputs[i_max] = outputs[i_max]._replace(value=amount)
                    tx = Transaction.from_io(inputs, outputs[:])
        
                # Sort the inputs and outputs deterministically
       t@@ -694,14 +692,13 @@ class Abstract_Wallet(AddressSynchronizer):
                s = sorted(s, key=lambda x: x[2])
                for o in s:
                    i = outputs.index(o)
       -            otype, address, value = o
       -            if value - delta >= self.dust_threshold():
       -                outputs[i] = otype, address, value - delta
       +            if o.value - delta >= self.dust_threshold():
       +                outputs[i] = o._replace(value=o.value-delta)
                        delta = 0
                        break
                    else:
                        del outputs[i]
       -                delta -= value
       +                delta -= o.value
                        if delta > 0:
                            continue
                if delta > 0:
       t@@ -714,8 +711,8 @@ class Abstract_Wallet(AddressSynchronizer):
            def cpfp(self, tx, fee):
                txid = tx.txid()
                for i, o in enumerate(tx.outputs()):
       -            otype, address, value = o
       -            if otype == TYPE_ADDRESS and self.is_mine(address):
       +            address, value = o.address, o.value
       +            if o.type == TYPE_ADDRESS and self.is_mine(address):
                        break
                else:
                    return
       t@@ -725,7 +722,7 @@ class Abstract_Wallet(AddressSynchronizer):
                    return
                self.add_input_info(item)
                inputs = [item]
       -        outputs = [(TYPE_ADDRESS, address, value - fee)]
       +        outputs = [TxOutput(TYPE_ADDRESS, address, value - fee)]
                locktime = self.get_local_height()
                # note: no need to call tx.BIP_LI01_sort() here - single input/output
                return Transaction.from_io(inputs, outputs, locktime=locktime)