diff --git a/torba/baseaccount.py b/torba/baseaccount.py index ca30b1c8d..e7f020b2f 100644 --- a/torba/baseaccount.py +++ b/torba/baseaccount.py @@ -5,6 +5,7 @@ from twisted.internet import defer from torba.mnemonic import Mnemonic from torba.bip32 import PrivateKey, PubKey, from_extended_key_string from torba.hash import double_sha256, aes_encrypt, aes_decrypt +from torba.constants import COIN if typing.TYPE_CHECKING: from torba import baseledger @@ -65,15 +66,15 @@ class AddressManager: @defer.inlineCallbacks def get_addresses(self, limit: int = None, only_usable: bool = False) -> defer.Deferred: records = yield self.get_address_records(limit=limit, only_usable=only_usable) - defer.returnValue([r['address'] for r in records]) + return [r['address'] for r in records] @defer.inlineCallbacks def get_or_create_usable_address(self) -> defer.Deferred: addresses = yield self.get_addresses(limit=1, only_usable=True) if addresses: - defer.returnValue(addresses[0]) + return addresses[0] addresses = yield self.ensure_address_gap() - defer.returnValue(addresses[0]) + return addresses[0] class HierarchicalDeterministic(AddressManager): @@ -109,7 +110,7 @@ class HierarchicalDeterministic(AddressManager): yield self.db.add_keys( self.account, self.chain_number, new_keys ) - defer.returnValue([key[1].address for key in new_keys]) + return [key[1].address for key in new_keys] @defer.inlineCallbacks def get_max_gap(self) -> defer.Deferred: @@ -122,7 +123,7 @@ class HierarchicalDeterministic(AddressManager): else: max_gap = max(max_gap, current_gap) current_gap = 0 - defer.returnValue(max_gap) + return max_gap @defer.inlineCallbacks def ensure_address_gap(self) -> defer.Deferred: @@ -136,12 +137,12 @@ class HierarchicalDeterministic(AddressManager): break if existing_gap == self.gap: - defer.returnValue([]) + return [] start = addresses[0]['position']+1 if addresses else 0 end = start + (self.gap - existing_gap) new_keys = yield self.generate_keys(start, end-1) - defer.returnValue(new_keys) + return new_keys def get_address_records(self, limit: int = None, only_usable: bool = False): return self._query_addresses( @@ -179,8 +180,8 @@ class SingleKey(AddressManager): yield self.db.add_keys( self.account, self.chain_number, [(0, self.public_key)] ) - defer.returnValue([self.public_key.address]) - defer.returnValue([]) + return [self.public_key.address] + return [] def get_address_records(self, limit: int = None, only_usable: bool = False) -> defer.Deferred: return self._query_addresses() @@ -201,6 +202,7 @@ class BaseAccount: address_generator: dict) -> None: self.ledger = ledger self.wallet = wallet + self.id = public_key.address self.name = name self.seed = seed self.encrypted = encrypted @@ -271,6 +273,22 @@ class BaseAccount: 'address_generator': self.address_generator.to_dict(self.receiving, self.change) } + @defer.inlineCallbacks + def get_details(self, show_seed=False, **kwargs): + satoshis = yield self.get_balance(**kwargs) + details = { + 'id': self.id, + 'name': self.name, + 'coins': round(satoshis/COIN, 2), + 'satoshis': satoshis, + 'encrypted': self.encrypted, + 'public_key': self.public_key.extended_key_string(), + 'address_generator': self.address_generator.to_dict(self.receiving, self.change) + } + if show_seed: + details['seed'] = self.seed + return details + def decrypt(self, password): assert self.encrypted, "Key is not encrypted." secret = double_sha256(password) @@ -291,12 +309,12 @@ class BaseAccount: for address_manager in self.address_managers: new_addresses = yield address_manager.ensure_address_gap() addresses.extend(new_addresses) - defer.returnValue(addresses) + return addresses @defer.inlineCallbacks def get_addresses(self, limit: int = None, max_used_times: int = None) -> defer.Deferred: records = yield self.get_address_records(limit, max_used_times) - defer.returnValue([r['address'] for r in records]) + return [r['address'] for r in records] def get_address_records(self, limit: int = None, max_used_times: int = None) -> defer.Deferred: return self.ledger.db.get_addresses(self, None, limit, max_used_times) @@ -316,14 +334,17 @@ class BaseAccount: def get_max_gap(self): change_gap = yield self.change.get_max_gap() receiving_gap = yield self.receiving.get_max_gap() - defer.returnValue({ + return { 'max_change_gap': change_gap, 'max_receiving_gap': receiving_gap, - }) + } def get_unspent_outputs(self, **constraints): return self.ledger.db.get_utxos_for_account(self, **constraints) + def get_inputs_outputs(self, **constraints): + return self.ledger.db.get_txios_for_account(self, **constraints) + @defer.inlineCallbacks def fund(self, to_account, amount=None, everything=False, outputs=1, broadcast=False, **constraints): @@ -360,4 +381,4 @@ class BaseAccount: [txi.txo_ref.txo for txi in tx.inputs] ) - defer.returnValue(tx) + return tx diff --git a/torba/basedatabase.py b/torba/basedatabase.py index d86be74d3..8e4463631 100644 --- a/torba/basedatabase.py +++ b/torba/basedatabase.py @@ -86,25 +86,25 @@ class SQLiteMixin: def query_one_value(self, query, params=None, default=None): result = yield self.run_query(query, params) if result: - defer.returnValue(result[0][0] or default) + return result[0][0] or default else: - defer.returnValue(default) + return default @defer.inlineCallbacks def query_dict_value_list(self, query, fields, params=None): result = yield self.run_query(query.format(', '.join(fields)), params) if result: - defer.returnValue([dict(zip(fields, r)) for r in result]) + return [dict(zip(fields, r)) for r in result] else: - defer.returnValue([]) + return [] @defer.inlineCallbacks def query_dict_value(self, query, fields, params=None, default=None): result = yield self.query_dict_value_list(query, fields, params) if result: - defer.returnValue(result[0]) + return result[0] else: - defer.returnValue(default) + return default @staticmethod def execute(t, sql, values): @@ -262,9 +262,9 @@ class BaseDatabase(SQLiteMixin): "SELECT raw, height, is_verified FROM tx WHERE txid = ?", (txid,) ) if result: - defer.returnValue(result[0]) + return result[0] else: - defer.returnValue((None, None, False)) + return None, None, False def get_balance_for_account(self, account, include_reserved=False, **constraints): if not include_reserved: @@ -294,14 +294,34 @@ class BaseDatabase(SQLiteMixin): """+constraints_to_sql(constraints), constraints ) output_class = account.ledger.transaction_class.output_class - defer.returnValue([ + return [ output_class( values[0], output_class.script_class(values[1]), TXRefImmutable.from_id(values[2]), position=values[3] ) for values in utxos - ]) + ] + + @defer.inlineCallbacks + def get_txios_for_account(self, account, **constraints): + constraints['account'] = account.public_key.address + utxos = yield self.run_query( + """ + SELECT amount, script, txid, txo.position + FROM txo JOIN pubkey_address ON pubkey_address.address=txo.address + WHERE account=:account AND txo.is_reserved=0 AND txoid NOT IN (SELECT txoid FROM txi) + """+constraints_to_sql(constraints), constraints + ) + output_class = account.ledger.transaction_class.output_class + return [ + output_class( + values[0], + output_class.script_class(values[1]), + TXRefImmutable.from_id(values[2]), + position=values[3] + ) for values in utxos + ] def add_keys(self, account, chain, keys): sql = ( diff --git a/torba/baseledger.py b/torba/baseledger.py index dd6bad5ec..a498072da 100644 --- a/torba/baseledger.py +++ b/torba/baseledger.py @@ -129,11 +129,8 @@ class BaseLedger(metaclass=LedgerRegistry): def path(self): return os.path.join(self.config['data_path'], self.get_id()) - @defer.inlineCallbacks - def add_account(self, account: baseaccount.BaseAccount) -> defer.Deferred: + def add_account(self, account: baseaccount.BaseAccount): self.accounts.append(account) - if self.network.is_connected: - yield self.update_account(account) @defer.inlineCallbacks def get_transaction(self, txhash): diff --git a/torba/basemanager.py b/torba/basemanager.py index 87422dcf5..198e7ae9c 100644 --- a/torba/basemanager.py +++ b/torba/basemanager.py @@ -43,20 +43,15 @@ class BaseWalletManager: return wallet @defer.inlineCallbacks - def get_balances(self, confirmations=6): - balances = {} - for i, ledger in enumerate(self.ledgers.values()): - ledger_balances = balances[ledger.get_id()] = [] - for j, account in enumerate(ledger.accounts): - satoshis = yield account.get_balance(confirmations) - ledger_balances.append({ - 'account': account.name, - 'coins': round(satoshis/COIN, 2), - 'satoshis': satoshis, - 'is_default_account': i == j == 0, - 'id': account.public_key.address - }) - defer.returnValue(balances) + def get_detailed_accounts(self, confirmations=6, show_seed=False): + ledgers = {} + for i, account in enumerate(self.accounts): + details = yield account.get_details(confirmations=confirmations, show_seed=True) + details['is_default_account'] = i == 0 + ledger_id = account.ledger.get_id() + ledgers.setdefault(ledger_id, []) + ledgers[ledger_id].append(details) + return ledgers @property def default_wallet(self):