forked from LBRYCommunity/lbry-sdk
customizable address generator support
This commit is contained in:
parent
cfeb7b249b
commit
69ad8e384a
6 changed files with 98 additions and 83 deletions
|
@ -3,10 +3,10 @@ from twisted.trial import unittest
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from torba.coin.bitcoinsegwit import MainNetLedger
|
from torba.coin.bitcoinsegwit import MainNetLedger
|
||||||
from torba.baseaccount import KeyChain, SingleKey
|
from torba.baseaccount import HierarchicalDeterministic, SingleKey
|
||||||
|
|
||||||
|
|
||||||
class TestKeyChainAccount(unittest.TestCase):
|
class TestHierarchicalDeterministicAccount(unittest.TestCase):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -42,7 +42,7 @@ class TestKeyChainAccount(unittest.TestCase):
|
||||||
def test_ensure_address_gap(self):
|
def test_ensure_address_gap(self):
|
||||||
account = self.account
|
account = self.account
|
||||||
|
|
||||||
self.assertIsInstance(account.receiving, KeyChain)
|
self.assertIsInstance(account.receiving, HierarchicalDeterministic)
|
||||||
|
|
||||||
yield account.receiving.generate_keys(4, 7)
|
yield account.receiving.generate_keys(4, 7)
|
||||||
yield account.receiving.generate_keys(0, 3)
|
yield account.receiving.generate_keys(0, 3)
|
||||||
|
@ -95,7 +95,7 @@ class TestKeyChainAccount(unittest.TestCase):
|
||||||
account = self.ledger.account_class.from_seed(
|
account = self.ledger.account_class.from_seed(
|
||||||
self.ledger,
|
self.ledger,
|
||||||
"carbon smart garage balance margin twelve chest sword toast envelope bottom stomach ab"
|
"carbon smart garage balance margin twelve chest sword toast envelope bottom stomach ab"
|
||||||
"sent", "torba", receiving_gap=3, change_gap=2
|
"sent", "torba", {'name': 'deterministic-chain', 'receiving_gap': 3, 'change_gap': 2}
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
account.private_key.extended_key_string(),
|
account.private_key.extended_key_string(),
|
||||||
|
@ -139,11 +139,11 @@ class TestKeyChainAccount(unittest.TestCase):
|
||||||
'public_key':
|
'public_key':
|
||||||
'xpub661MyMwAqRbcF84AR8yfHoMzf4S2ct6mPJtvBtvNeyN9hBHuZ6uGJszkTSn5fQUCdz3XU17eBzFeAUwV6f'
|
'xpub661MyMwAqRbcF84AR8yfHoMzf4S2ct6mPJtvBtvNeyN9hBHuZ6uGJszkTSn5fQUCdz3XU17eBzFeAUwV6f'
|
||||||
'iW44g14WF52fYC5J483wqQ5ZP',
|
'iW44g14WF52fYC5J483wqQ5ZP',
|
||||||
'is_hd': True,
|
'address_generator': {
|
||||||
'receiving_gap': 5,
|
'name': 'deterministic-chain',
|
||||||
'receiving_maximum_uses_per_address': 2,
|
'receiving': {'gap': 5, 'maximum_uses_per_address': 2},
|
||||||
'change_gap': 5,
|
'change': {'gap': 5, 'maximum_uses_per_address': 2}
|
||||||
'change_maximum_uses_per_address': 2
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
account = self.ledger.account_class.from_dict(self.ledger, account_data)
|
account = self.ledger.account_class.from_dict(self.ledger, account_data)
|
||||||
|
@ -166,7 +166,7 @@ class TestSingleKeyAccount(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.ledger = MainNetLedger({'db': MainNetLedger.database_class(':memory:')})
|
self.ledger = MainNetLedger({'db': MainNetLedger.database_class(':memory:')})
|
||||||
yield self.ledger.db.start()
|
yield self.ledger.db.start()
|
||||||
self.account = self.ledger.account_class.generate(self.ledger, u"torba", is_hd=False)
|
self.account = self.ledger.account_class.generate(self.ledger, u"torba", {'name': 'single-address'})
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_generate_account(self):
|
def test_generate_account(self):
|
||||||
|
@ -247,7 +247,7 @@ class TestSingleKeyAccount(unittest.TestCase):
|
||||||
account = self.ledger.account_class.from_seed(
|
account = self.ledger.account_class.from_seed(
|
||||||
self.ledger,
|
self.ledger,
|
||||||
"carbon smart garage balance margin twelve chest sword toast envelope bottom stomach ab"
|
"carbon smart garage balance margin twelve chest sword toast envelope bottom stomach ab"
|
||||||
"sent", "torba", is_hd=False
|
"sent", "torba", {'name': 'single-address'}
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
account.private_key.extended_key_string(),
|
account.private_key.extended_key_string(),
|
||||||
|
@ -291,7 +291,7 @@ class TestSingleKeyAccount(unittest.TestCase):
|
||||||
'public_key':
|
'public_key':
|
||||||
'xpub661MyMwAqRbcF84AR8yfHoMzf4S2ct6mPJtvBtvNeyN9hBHuZ6uGJszkTSn5fQUCdz3XU17eBzFeAUwV6f'
|
'xpub661MyMwAqRbcF84AR8yfHoMzf4S2ct6mPJtvBtvNeyN9hBHuZ6uGJszkTSn5fQUCdz3XU17eBzFeAUwV6f'
|
||||||
'iW44g14WF52fYC5J483wqQ5ZP',
|
'iW44g14WF52fYC5J483wqQ5ZP',
|
||||||
'is_hd': False
|
'address_generator': {'name': 'single-address'}
|
||||||
}
|
}
|
||||||
|
|
||||||
account = self.ledger.account_class.from_dict(self.ledger, account_data)
|
account = self.ledger.account_class.from_dict(self.ledger, account_data)
|
||||||
|
|
|
@ -148,7 +148,7 @@ class TestTransactionSigning(unittest.TestCase):
|
||||||
account = self.ledger.account_class.from_seed(
|
account = self.ledger.account_class.from_seed(
|
||||||
self.ledger,
|
self.ledger,
|
||||||
u"carbon smart garage balance margin twelve chest sword toast envelope bottom stomach ab"
|
u"carbon smart garage balance margin twelve chest sword toast envelope bottom stomach ab"
|
||||||
u"sent", u"torba"
|
u"sent", u"torba", {}
|
||||||
)
|
)
|
||||||
|
|
||||||
yield account.ensure_address_gap()
|
yield account.ensure_address_gap()
|
||||||
|
|
|
@ -44,11 +44,11 @@ class TestWalletCreation(unittest.TestCase):
|
||||||
'public_key':
|
'public_key':
|
||||||
'xpub661MyMwAqRbcF84AR8yfHoMzf4S2ct6mPJtvBtvNeyN9hBHuZ6uGJszkTSn5fQUCdz3XU17eBzFeAUwV6f'
|
'xpub661MyMwAqRbcF84AR8yfHoMzf4S2ct6mPJtvBtvNeyN9hBHuZ6uGJszkTSn5fQUCdz3XU17eBzFeAUwV6f'
|
||||||
'iW44g14WF52fYC5J483wqQ5ZP',
|
'iW44g14WF52fYC5J483wqQ5ZP',
|
||||||
'is_hd': True,
|
'address_generator': {
|
||||||
'receiving_gap': 10,
|
'name': 'deterministic-chain',
|
||||||
'receiving_maximum_uses_per_address': 2,
|
'receiving': {'gap': 17, 'maximum_uses_per_address': 3},
|
||||||
'change_gap': 10,
|
'change': {'gap': 10, 'maximum_uses_per_address': 3}
|
||||||
'change_maximum_uses_per_address': 2,
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import typing
|
import typing
|
||||||
from typing import Sequence
|
from typing import Tuple, Type
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from torba.mnemonic import Mnemonic
|
from torba.mnemonic import Mnemonic
|
||||||
|
@ -10,7 +10,9 @@ if typing.TYPE_CHECKING:
|
||||||
from torba import baseledger
|
from torba import baseledger
|
||||||
|
|
||||||
|
|
||||||
class KeyManager:
|
class AddressManager:
|
||||||
|
|
||||||
|
name: str
|
||||||
|
|
||||||
__slots__ = 'account', 'public_key', 'chain_number'
|
__slots__ = 'account', 'public_key', 'chain_number'
|
||||||
|
|
||||||
|
@ -19,6 +21,15 @@ class KeyManager:
|
||||||
self.public_key = public_key
|
self.public_key = public_key
|
||||||
self.chain_number = chain_number
|
self.chain_number = chain_number
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, account: 'BaseAccount', d: dict) \
|
||||||
|
-> Tuple['AddressManager', 'AddressManager']:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_dict(cls, receiving: 'AddressManager', change: 'AddressManager') -> dict:
|
||||||
|
return {'name': cls.name}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def db(self):
|
def db(self):
|
||||||
return self.account.ledger.db
|
return self.account.ledger.db
|
||||||
|
@ -28,6 +39,9 @@ class KeyManager:
|
||||||
self.account, self.chain_number, limit, max_used_times, order_by
|
self.account, self.chain_number, limit, max_used_times, order_by
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_private_key(self, index: int) -> PrivateKey:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_max_gap(self) -> defer.Deferred:
|
def get_max_gap(self) -> defer.Deferred:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -51,17 +65,38 @@ class KeyManager:
|
||||||
defer.returnValue(addresses[0])
|
defer.returnValue(addresses[0])
|
||||||
|
|
||||||
|
|
||||||
class KeyChain(KeyManager):
|
class HierarchicalDeterministic(AddressManager):
|
||||||
""" Implements simple version of Bitcoin Hierarchical Deterministic key management. """
|
""" Implements simple version of Bitcoin Hierarchical Deterministic key management. """
|
||||||
|
|
||||||
|
name = "deterministic-chain"
|
||||||
|
|
||||||
__slots__ = 'gap', 'maximum_uses_per_address'
|
__slots__ = 'gap', 'maximum_uses_per_address'
|
||||||
|
|
||||||
def __init__(self, account: 'BaseAccount', root_public_key: PubKey,
|
def __init__(self, account: 'BaseAccount', chain: int, gap: int, maximum_uses_per_address: int) -> None:
|
||||||
chain_number: int, gap: int, maximum_uses_per_address: int) -> None:
|
super().__init__(account, account.public_key.child(chain), chain)
|
||||||
super().__init__(account, root_public_key.child(chain_number), chain_number)
|
|
||||||
self.gap = gap
|
self.gap = gap
|
||||||
self.maximum_uses_per_address = maximum_uses_per_address
|
self.maximum_uses_per_address = maximum_uses_per_address
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, account: 'BaseAccount', d: dict) -> Tuple[AddressManager, AddressManager]:
|
||||||
|
return (
|
||||||
|
cls(account, 0, **d.get('receiving', {'gap': 20, 'maximum_uses_per_address': 2})),
|
||||||
|
cls(account, 1, **d.get('change', {'gap': 6, 'maximum_uses_per_address': 2}))
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_dict(cls, receiving: 'HierarchicalDeterministic', change: 'HierarchicalDeterministic') -> dict:
|
||||||
|
d = super().to_dict(receiving, change)
|
||||||
|
d['receiving'] = receiving.to_dict_instance()
|
||||||
|
d['change'] = change.to_dict_instance()
|
||||||
|
return d
|
||||||
|
|
||||||
|
def to_dict_instance(self):
|
||||||
|
return {'gap': self.gap, 'maximum_uses_per_address': self.maximum_uses_per_address}
|
||||||
|
|
||||||
|
def get_private_key(self, index: int) -> PrivateKey:
|
||||||
|
return self.account.private_key.child(self.chain_number).child(index)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def generate_keys(self, start: int, end: int) -> defer.Deferred:
|
def generate_keys(self, start: int, end: int) -> defer.Deferred:
|
||||||
new_keys = []
|
new_keys = []
|
||||||
|
@ -111,11 +146,22 @@ class KeyChain(KeyManager):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SingleKey(KeyManager):
|
class SingleKey(AddressManager):
|
||||||
""" Single Key manager always returns the same address for all operations. """
|
""" Single Key address manager always returns the same address for all operations. """
|
||||||
|
|
||||||
|
name = "single-address"
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, account: 'BaseAccount', d: dict)\
|
||||||
|
-> Tuple[AddressManager, AddressManager]:
|
||||||
|
same_address_manager = cls(account, account.public_key, 0)
|
||||||
|
return same_address_manager, same_address_manager
|
||||||
|
|
||||||
|
def get_private_key(self, index: int) -> PrivateKey:
|
||||||
|
return self.account.private_key
|
||||||
|
|
||||||
def get_max_gap(self) -> defer.Deferred:
|
def get_max_gap(self) -> defer.Deferred:
|
||||||
return defer.succeed(0)
|
return defer.succeed(0)
|
||||||
|
|
||||||
|
@ -138,10 +184,13 @@ class BaseAccount:
|
||||||
mnemonic_class = Mnemonic
|
mnemonic_class = Mnemonic
|
||||||
private_key_class = PrivateKey
|
private_key_class = PrivateKey
|
||||||
public_key_class = PubKey
|
public_key_class = PubKey
|
||||||
|
address_generators = {
|
||||||
|
SingleKey.name: SingleKey,
|
||||||
|
HierarchicalDeterministic.name: HierarchicalDeterministic,
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, ledger: 'baseledger.BaseLedger', name: str, seed: str, encrypted: bool, is_hd: bool,
|
def __init__(self, ledger: 'baseledger.BaseLedger', name: str, seed: str, encrypted: bool,
|
||||||
private_key: PrivateKey, public_key: PubKey, receiving_gap: int = 20, change_gap: int = 6,
|
private_key: PrivateKey, public_key: PubKey, address_generator: dict
|
||||||
receiving_maximum_uses_per_address: int = 2, change_maximum_uses_per_address: int = 2
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.ledger = ledger
|
self.ledger = ledger
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -149,34 +198,26 @@ class BaseAccount:
|
||||||
self.encrypted = encrypted
|
self.encrypted = encrypted
|
||||||
self.private_key = private_key
|
self.private_key = private_key
|
||||||
self.public_key = public_key
|
self.public_key = public_key
|
||||||
if is_hd:
|
generator_name = address_generator.get('name', HierarchicalDeterministic.name)
|
||||||
self.receiving: KeyManager = KeyChain(
|
self.address_generator: Type[AddressManager] = self.address_generators[generator_name]
|
||||||
self, public_key, 0, receiving_gap, receiving_maximum_uses_per_address
|
self.receiving, self.change = self.address_generator.from_dict(self, address_generator)
|
||||||
)
|
self.address_managers = {self.receiving, self.change}
|
||||||
self.change: KeyManager = KeyChain(
|
|
||||||
self, public_key, 1, change_gap, change_maximum_uses_per_address
|
|
||||||
)
|
|
||||||
self.keychains: Sequence[KeyManager] = (self.receiving, self.change)
|
|
||||||
else:
|
|
||||||
self.change = self.receiving = SingleKey(self, public_key, 0)
|
|
||||||
self.keychains = (self.receiving,)
|
|
||||||
ledger.add_account(self)
|
ledger.add_account(self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, ledger: 'baseledger.BaseLedger', password: str, **kwargs):
|
def generate(cls, ledger: 'baseledger.BaseLedger', password: str, address_generator: dict = None):
|
||||||
seed = cls.mnemonic_class().make_seed()
|
seed = cls.mnemonic_class().make_seed()
|
||||||
return cls.from_seed(ledger, seed, password, **kwargs)
|
return cls.from_seed(ledger, seed, password, address_generator or {})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_seed(cls, ledger: 'baseledger.BaseLedger', seed: str, password: str,
|
def from_seed(cls, ledger: 'baseledger.BaseLedger', seed: str, password: str, address_generator: dict):
|
||||||
is_hd: bool = True, **kwargs):
|
|
||||||
private_key = cls.get_private_key_from_seed(ledger, seed, password)
|
private_key = cls.get_private_key_from_seed(ledger, seed, password)
|
||||||
return cls(
|
return cls(
|
||||||
ledger=ledger, name='Account #{}'.format(private_key.public_key.address),
|
ledger=ledger, name='Account #{}'.format(private_key.public_key.address),
|
||||||
seed=seed, encrypted=False, is_hd=is_hd,
|
seed=seed, encrypted=False,
|
||||||
private_key=private_key,
|
private_key=private_key,
|
||||||
public_key=private_key.public_key,
|
public_key=private_key.public_key,
|
||||||
**kwargs
|
address_generator=address_generator
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -193,54 +234,30 @@ class BaseAccount:
|
||||||
else:
|
else:
|
||||||
private_key = d['private_key']
|
private_key = d['private_key']
|
||||||
public_key = from_extended_key_string(ledger, d['public_key'])
|
public_key = from_extended_key_string(ledger, d['public_key'])
|
||||||
|
return cls(
|
||||||
kwargs = dict(
|
|
||||||
ledger=ledger,
|
ledger=ledger,
|
||||||
name=d['name'],
|
name=d['name'],
|
||||||
seed=d['seed'],
|
seed=d['seed'],
|
||||||
encrypted=d['encrypted'],
|
encrypted=d['encrypted'],
|
||||||
private_key=private_key,
|
private_key=private_key,
|
||||||
public_key=public_key,
|
public_key=public_key,
|
||||||
is_hd=False
|
address_generator=d['address_generator']
|
||||||
)
|
)
|
||||||
|
|
||||||
if d['is_hd']:
|
|
||||||
kwargs.update(dict(
|
|
||||||
receiving_gap=d['receiving_gap'],
|
|
||||||
change_gap=d['change_gap'],
|
|
||||||
receiving_maximum_uses_per_address=d['receiving_maximum_uses_per_address'],
|
|
||||||
change_maximum_uses_per_address=d['change_maximum_uses_per_address'],
|
|
||||||
is_hd=True
|
|
||||||
))
|
|
||||||
|
|
||||||
return cls(**kwargs)
|
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
private_key = self.private_key
|
private_key = self.private_key
|
||||||
if not self.encrypted and self.private_key:
|
if not self.encrypted and self.private_key:
|
||||||
private_key = self.private_key.extended_key_string()
|
private_key = self.private_key.extended_key_string()
|
||||||
|
return {
|
||||||
d = {
|
|
||||||
'ledger': self.ledger.get_id(),
|
'ledger': self.ledger.get_id(),
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'seed': self.seed,
|
'seed': self.seed,
|
||||||
'encrypted': self.encrypted,
|
'encrypted': self.encrypted,
|
||||||
'private_key': private_key,
|
'private_key': private_key,
|
||||||
'public_key': self.public_key.extended_key_string(),
|
'public_key': self.public_key.extended_key_string(),
|
||||||
'is_hd': False
|
'address_generator': self.address_generator.to_dict(self.receiving, self.change)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isinstance(self.receiving, KeyChain) and isinstance(self.change, KeyChain):
|
|
||||||
d.update({
|
|
||||||
'receiving_gap': self.receiving.gap,
|
|
||||||
'change_gap': self.change.gap,
|
|
||||||
'receiving_maximum_uses_per_address': self.receiving.maximum_uses_per_address,
|
|
||||||
'change_maximum_uses_per_address': self.change.maximum_uses_per_address,
|
|
||||||
'is_hd': True
|
|
||||||
})
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -258,8 +275,8 @@ class BaseAccount:
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def ensure_address_gap(self):
|
def ensure_address_gap(self):
|
||||||
addresses = []
|
addresses = []
|
||||||
for keychain in self.keychains:
|
for address_manager in self.address_managers:
|
||||||
new_addresses = yield keychain.ensure_address_gap()
|
new_addresses = yield address_manager.ensure_address_gap()
|
||||||
addresses.extend(new_addresses)
|
addresses.extend(new_addresses)
|
||||||
defer.returnValue(addresses)
|
defer.returnValue(addresses)
|
||||||
|
|
||||||
|
@ -273,9 +290,8 @@ class BaseAccount:
|
||||||
|
|
||||||
def get_private_key(self, chain: int, index: int) -> PrivateKey:
|
def get_private_key(self, chain: int, index: int) -> PrivateKey:
|
||||||
assert not self.encrypted, "Cannot get private key on encrypted wallet account."
|
assert not self.encrypted, "Cannot get private key on encrypted wallet account."
|
||||||
if isinstance(self.receiving, SingleKey):
|
address_manager = {0: self.receiving, 1: self.change}[chain]
|
||||||
return self.private_key
|
return address_manager.get_private_key(index)
|
||||||
return self.private_key.child(chain).child(index)
|
|
||||||
|
|
||||||
def get_balance(self, confirmations: int = 6, **constraints):
|
def get_balance(self, confirmations: int = 6, **constraints):
|
||||||
if confirmations > 0:
|
if confirmations > 0:
|
||||||
|
|
|
@ -328,9 +328,8 @@ class BaseTransaction:
|
||||||
self.locktime = stream.read_uint32()
|
self.locktime = stream.read_uint32()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ensure_all_have_same_ledger(
|
def ensure_all_have_same_ledger(cls, funding_accounts: Iterable[BaseAccount],
|
||||||
cls, funding_accounts: Iterable[BaseAccount], change_account: BaseAccount = None)\
|
change_account: BaseAccount = None) -> 'baseledger.BaseLedger':
|
||||||
-> 'baseledger.BaseLedger':
|
|
||||||
ledger = None
|
ledger = None
|
||||||
for account in funding_accounts:
|
for account in funding_accounts:
|
||||||
if ledger is None:
|
if ledger is None:
|
||||||
|
|
|
@ -24,7 +24,7 @@ class Wallet:
|
||||||
self.storage = storage or WalletStorage()
|
self.storage = storage or WalletStorage()
|
||||||
|
|
||||||
def generate_account(self, ledger: 'baseledger.BaseLedger') -> 'baseaccount.BaseAccount':
|
def generate_account(self, ledger: 'baseledger.BaseLedger') -> 'baseaccount.BaseAccount':
|
||||||
account = ledger.account_class.generate(ledger, u'torba')
|
account = ledger.account_class.generate(ledger, u'torba', {})
|
||||||
self.accounts.append(account)
|
self.accounts.append(account)
|
||||||
return account
|
return account
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue