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