diff --git a/electrum b/electrum index 15fcb60dc..00ce71cc8 100755 --- a/electrum +++ b/electrum @@ -323,7 +323,8 @@ if __name__ == '__main__': args = [cmd, options.show_all, options.show_balance, options.show_labels] elif cmd in ['payto', 'mktx']: - args = [ 'mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, options.from_addr ] + domain = [options.from_addr] if options.from_addr else None + args = [ 'mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain ] elif cmd == 'help': if len(args) < 2: diff --git a/gui/gui_classic.py b/gui/gui_classic.py index d87d26b3d..8fd703cef 100644 --- a/gui/gui_classic.py +++ b/gui/gui_classic.py @@ -285,8 +285,9 @@ class ElectrumWindow(QMainWindow): self.lite = None self.wallet = wallet self.config = config - self.init_plugins() + self.current_account = self.config.get("current_account", None) + self.init_plugins() self.create_status_bar() self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet'))) @@ -429,7 +430,7 @@ class ElectrumWindow(QMainWindow): text = _("Synchronizing...") icon = QIcon(":icons/status_waiting.png") else: - c, u = self.wallet.get_balance() + c, u = self.wallet.get_account_balance(self.current_account) text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) ) if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() ) text += self.create_quote_text(Decimal(c+u)/100000000) @@ -607,7 +608,7 @@ class ElectrumWindow(QMainWindow): def update_history_tab(self): self.history_list.clear() - for item in self.wallet.get_tx_history(): + for item in self.wallet.get_tx_history(self.current_account): tx_hash, conf, is_mine, value, fee, balance, timestamp = item if conf: try: @@ -666,6 +667,7 @@ class ElectrumWindow(QMainWindow): grid.setColumnMinimumWidth(3,300) grid.setColumnStretch(5,1) + self.payto_e = QLineEdit() grid.addWidget(QLabel(_('Pay to')), 1, 0) grid.addWidget(self.payto_e, 1, 1, 1, 3) @@ -724,8 +726,8 @@ class ElectrumWindow(QMainWindow): self.funds_error = False if self.amount_e.text() == '!': - c, u = self.wallet.get_balance() - inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0 ) + c, u = self.wallet.get_account_balance(self.current_account) + inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account) fee = self.wallet.estimated_fee(inputs) amount = c + u - fee self.amount_e.setText( str( Decimal( amount ) / 100000000 ) ) @@ -737,7 +739,7 @@ class ElectrumWindow(QMainWindow): if not is_fee: fee = None if amount is None: return - inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee ) + inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account ) if not is_fee: self.fee_e.setText( str( Decimal( fee ) / 100000000 ) ) if inputs: @@ -802,7 +804,7 @@ class ElectrumWindow(QMainWindow): return try: - tx = self.wallet.mktx( [(to_address, amount)], password, fee) + tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account) except BaseException, e: self.show_message(str(e)) return @@ -1091,8 +1093,16 @@ class ElectrumWindow(QMainWindow): for i,width in enumerate(self.column_widths['receive'][self.expert_mode]): l.setColumnWidth(i, width) + if self.current_account is None: + account_items = self.wallet.accounts.items() + elif self.current_account != -1: + account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))] + else: + account_items = [] - for k, account in self.wallet.accounts.items(): + print self.current_account + + for k, account in account_items: name = account.get('name',str(k)) c,u = self.wallet.get_account_balance(k) account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] ) @@ -1127,7 +1137,8 @@ class ElectrumWindow(QMainWindow): item.setBackgroundColor(1, QColor('red')) seq_item.addChild(item) - if self.wallet.imported_keys: + + if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1): c,u = self.wallet.get_imported_balance() account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] ) l.addTopLevelItem(account_item) @@ -1183,6 +1194,17 @@ class ElectrumWindow(QMainWindow): console.updateNamespace(methods) return console + def change_account(self,s): + if s == _("All accounts"): + self.current_account = None + else: + accounts = self.wallet.get_accounts() + for k, v in accounts.items(): + if v == s: + self.current_account = k + self.update_history_tab() + self.update_status() + self.update_receive_tab() def create_status_bar(self): self.status_text = "" @@ -1194,6 +1216,14 @@ class ElectrumWindow(QMainWindow): if(update_notification.new_version): sb.addPermanentWidget(update_notification) + accounts = self.wallet.get_accounts() + if len(accounts) > 1: + from_combo = QComboBox() + from_combo.addItems([_("All accounts")] + accounts.values()) + from_combo.setCurrentIndex(0) + self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account) + sb.addPermanentWidget(from_combo) + if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7): sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) ) if self.wallet.seed: diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 41f939593..b7bc66b6f 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -804,6 +804,7 @@ class Transaction: def get_value(self, addresses, prevout_values): # return the balance for that tx + is_relevant = False is_send = False is_pruned = False is_partial = False @@ -813,6 +814,7 @@ class Transaction: addr = item.get('address') if addr in addresses: is_send = True + is_relevant = True key = item['prevout_hash'] + ':%d'%item['prevout_n'] value = prevout_values.get( key ) if value is None: @@ -829,6 +831,7 @@ class Transaction: v_out += value if addr in addresses: v_out_mine += value + is_relevant = True if is_pruned: # some inputs are mine: @@ -850,7 +853,7 @@ class Transaction: # all inputs are mine fee = v_out - v_in - return is_send, v, fee + return is_relevant, is_send, v, fee def as_dict(self): import json diff --git a/lib/commands.py b/lib/commands.py index 85c53ac8c..245820b2b 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -208,7 +208,7 @@ class Commands: return False - def _mktx(self, to_address, amount, fee = None, change_addr = None, from_addr = None): + def _mktx(self, to_address, amount, fee = None, change_addr = None, domain = None): if not is_valid(to_address): raise BaseException("Invalid Bitcoin address", to_address) @@ -217,12 +217,13 @@ class Commands: if not is_valid(change_addr): raise BaseException("Invalid Bitcoin address", change_addr) - if from_addr: - if not is_valid(from_addr): - raise BaseException("invalid Bitcoin address", from_addr) + if domain is not None: + for addr in domain: + if not is_valid(addr): + raise BaseException("invalid Bitcoin address", addr) - if not self.wallet.is_mine(from_addr): - raise BaseException("address not in wallet") + if not self.wallet.is_mine(addr): + raise BaseException("address not in wallet", addr) for k, v in self.wallet.labels.items(): if v == to_address: @@ -234,16 +235,16 @@ class Commands: amount = int(100000000*amount) if fee: fee = int(100000000*fee) - return self.wallet.mktx( [(to_address, amount)], self.password, fee , change_addr, from_addr) + return self.wallet.mktx( [(to_address, amount)], self.password, fee , change_addr, domain) - def mktx(self, to_address, amount, fee = None, change_addr = None, from_addr = None): - tx = self._mktx(to_address, amount, fee, change_addr, from_addr) + def mktx(self, to_address, amount, fee = None, change_addr = None, domain = None): + tx = self._mktx(to_address, amount, fee, change_addr, domain) return tx.as_dict() - def payto(self, to_address, amount, fee = None, change_addr = None, from_addr = None): - tx = self._mktx(to_address, amount, fee, change_addr, from_addr) + def payto(self, to_address, amount, fee = None, change_addr = None, domain = None): + tx = self._mktx(to_address, amount, fee, change_addr, domain) r, h = self.wallet.sendtx( tx ) return h diff --git a/lib/wallet.py b/lib/wallet.py index 755f063cf..07c0f4c6d 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -165,11 +165,10 @@ class Wallet: self.config.set_key('accounts', self.accounts, True) - def addresses(self, include_change = False): - o = self.imported_keys.keys() - for a in self.accounts.values(): - o += a[0] - if include_change: o += a[1] + def addresses(self, include_change = True): + o = self.get_account_addresses(-1, include_change) + for a in self.accounts.keys(): + o += self.get_account_addresses(a, include_change) return o @@ -400,7 +399,7 @@ class Wallet: def fill_addressbook(self): for tx_hash, tx in self.transactions.items(): - is_send, _, _ = self.get_tx_value(tx) + is_relevant, is_send, _, _ = self.get_tx_value(tx) if is_send: for addr, v in tx.outputs: if not self.is_mine(addr) and addr not in self.addressbook: @@ -421,10 +420,9 @@ class Wallet: return flags - def get_tx_value(self, tx, addresses=None): - if addresses is None: addresses = self.addresses(True) - return tx.get_value(addresses, self.prevout_values) - + def get_tx_value(self, tx, account=None): + domain = self.get_account_addresses(account) + return tx.get_value(domain, self.prevout_values) def update_tx_outputs(self, tx_hash): @@ -480,9 +478,25 @@ class Wallet: u += v return c, u - def get_account_addresses(self, a): - ac = self.accounts[a] - return ac[0] + ac[1] + + def get_accounts(self): + accounts = {} + for k, account in self.accounts.items(): + accounts[k] = account.get('name') + if self.imported_keys: + accounts[-1] = 'Imported keys' + return accounts + + def get_account_addresses(self, a, include_change=True): + if a is None: + o = self.addresses(True) + elif a == -1: + o = self.imported_keys.keys() + else: + ac = self.accounts[a] + o = ac[0][:] + if include_change: o += ac[1] + return o def get_imported_balance(self): cc = uu = 0 @@ -493,6 +507,11 @@ class Wallet: return cc, uu def get_account_balance(self, account): + if account is None: + return self.get_balance() + elif account == -1: + return self.get_imported_balance() + conf = unconf = 0 for addr in self.get_account_addresses(account): c, u = self.get_addr_balance(addr) @@ -531,14 +550,13 @@ class Wallet: - def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ): + def choose_tx_inputs( self, amount, fixed_fee, account = None ): """ todo: minimize tx size """ total = 0 fee = self.fee if fixed_fee is None else fixed_fee - + domain = self.get_account_addresses(account) coins = [] prioritized_coins = [] - domain = [from_addr] if from_addr else self.addresses(True) for i in self.frozen_addresses: if i in domain: domain.remove(i) @@ -571,13 +589,17 @@ class Wallet: return fee - def add_tx_change( self, outputs, amount, fee, total, change_addr=None ): + def add_tx_change( self, inputs, outputs, amount, fee, total, change_addr=None, account=0 ): + "add change to a transaction" change_amount = total - ( amount + fee ) if change_amount != 0: - # normally, the update thread should ensure that the last change address is unused if not change_addr: - change_addresses = self.accounts[0][1] - change_addr = change_addresses[-self.gap_limit_for_change] + if not self.use_change or account == -1: + change_addr = inputs[-1]['address'] + else: + if account is None: account = 0 + change_addr = self.accounts[account][1][-self.gap_limit_for_change] + # Insert the change output at a random position in the outputs posn = random.randint(0, len(outputs)) outputs[posn:posn] = [( change_addr, change_amount)] @@ -588,6 +610,7 @@ class Wallet: with self.lock: return self.history.get(address) + def get_status(self, h): if not h: return None if h == ['*']: return '*' @@ -597,10 +620,8 @@ class Wallet: return hashlib.sha256( status ).digest().encode('hex') - def receive_tx_callback(self, tx_hash, tx, tx_height): - if not self.check_new_tx(tx_hash, tx): # may happen due to pruning print_error("received transaction that is no longer referenced in history", tx_hash) @@ -631,7 +652,7 @@ class Wallet: if self.verifier: self.verifier.add(tx_hash, tx_height) - def get_tx_history(self): + def get_tx_history(self, account=None): with self.transaction_lock: history = self.transactions.items() history.sort(key = lambda x: self.verifier.verified_tx.get(x[0]) if self.verifier.verified_tx.get(x[0]) else (1e12,0,0)) @@ -639,21 +660,23 @@ class Wallet: balance = 0 for tx_hash, tx in history: - is_mine, v, fee = self.get_tx_value(tx) + is_relevant, is_mine, v, fee = self.get_tx_value(tx, account) if v is not None: balance += v - c, u = self.get_balance() + + c, u = self.get_account_balance(account) if balance != c+u: - #v_str = format_satoshis( c+u - balance, True, self.num_zeros) result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) ) balance = c + u - balance for tx_hash, tx in history: - conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None) - is_mine, value, fee = self.get_tx_value(tx) + is_relevant, is_mine, value, fee = self.get_tx_value(tx, account) + if not is_relevant: + continue if value is not None: balance += value + conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None) result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) ) return result @@ -670,7 +693,7 @@ class Wallet: tx = self.transactions.get(tx_hash) default_label = '' if tx: - is_mine, _, _ = self.get_tx_value(tx) + is_relevant, is_mine, _, _ = self.get_tx_value(tx) if is_mine: for o in tx.outputs: o_addr, _ = o @@ -705,20 +728,27 @@ class Wallet: return default_label - def mktx(self, outputs, password, fee=None, change_addr=None, from_addr= None): - + def mktx(self, outputs, password, fee=None, change_addr=None, account=None ): + """ + create a transaction + account parameter: + None means use all accounts + -1 means imported keys + 0, 1, etc are seed accounts + """ + for address, x in outputs: assert is_valid(address) amount = sum( map(lambda x:x[1], outputs) ) - inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr ) + + domain = self.get_account_addresses(account) + + inputs, total, fee = self.choose_tx_inputs( amount, fee, domain ) if not inputs: raise ValueError("Not enough funds") - if not self.use_change and not change_addr: - change_addr = inputs[-1]['address'] - print_error( "Sending change to", change_addr ) - outputs = self.add_tx_change(outputs, amount, fee, total, change_addr) + outputs = self.add_tx_change(inputs, outputs, amount, fee, total, change_addr, account) tx = Transaction.from_io(inputs, outputs) @@ -726,7 +756,7 @@ class Wallet: for i in range(len(tx.inputs)): txin = tx.inputs[i] address = txin['address'] - if address in self.imported_keys.keys(): + if address in self.imported_keys.keys(): pk_addresses.append(address) continue account, sequence = self.get_address_index(address)