lbry-sdk/torba/manager.py
2018-06-03 20:06:55 -04:00

125 lines
4.1 KiB
Python

import functools
from typing import List, Dict, Type
from twisted.internet import defer
from torba.account import AccountsView
from torba.basecoin import CoinRegistry
from torba.baseledger import BaseLedger
from torba.basetransaction import NULL_HASH
from torba.coinselection import CoinSelector
from torba.constants import COIN
from torba.wallet import Wallet, WalletStorage
class WalletManager:
def __init__(self, wallets=None, ledgers=None):
self.wallets = wallets or [] # type: List[Wallet]
self.ledgers = ledgers or {} # type: Dict[Type[BaseLedger],BaseLedger]
self.running = False
@classmethod
def from_config(cls, config):
wallets = []
manager = cls(wallets)
for coin_id, ledger_config in config.get('ledgers', {}).items():
manager.get_or_create_ledger(coin_id, ledger_config)
for wallet_path in config.get('wallets', []):
wallet_storage = WalletStorage(wallet_path)
wallet = Wallet.from_storage(wallet_storage, manager)
wallets.append(wallet)
return manager
def get_or_create_ledger(self, coin_id, ledger_config=None):
coin_class = CoinRegistry.get_coin_class(coin_id)
ledger_class = coin_class.ledger_class
ledger = self.ledgers.get(ledger_class)
if ledger is None:
ledger = ledger_class(self.get_accounts_view(coin_class), ledger_config or {})
self.ledgers[ledger_class] = ledger
return ledger
@property
def default_wallet(self):
for wallet in self.wallets:
return wallet
@property
def default_account(self):
for wallet in self.wallets:
return wallet.default_account
def get_accounts(self, coin_class):
for wallet in self.wallets:
for account in wallet.accounts:
if account.coin.__class__ is coin_class:
yield account
def get_accounts_view(self, coin_class):
return AccountsView(
functools.partial(self.get_accounts, coin_class)
)
def create_wallet(self, path, coin_class):
storage = WalletStorage(path)
wallet = Wallet.from_storage(storage, self)
self.wallets.append(wallet)
self.create_account(wallet, coin_class)
return wallet
def create_account(self, wallet, coin_class):
ledger = self.get_or_create_ledger(coin_class.get_id())
return wallet.generate_account(ledger)
@defer.inlineCallbacks
def start_ledgers(self):
self.running = True
yield defer.DeferredList([
l.start() for l in self.ledgers.values()
])
@defer.inlineCallbacks
def stop_ledgers(self):
yield defer.DeferredList([
l.stop() for l in self.ledgers.values()
])
self.running = False
def send_amount_to_address(self, amount, address):
amount = int(amount * COIN)
account = self.default_account
coin = account.coin
ledger = coin.ledger
tx_class = ledger.transaction_class
in_class, out_class = tx_class.input_class, tx_class.output_class
estimators = [
txo.get_estimator(coin) for txo in account.get_unspent_utxos()
]
cost_of_output = coin.get_input_output_fee(
out_class.pay_pubkey_hash(COIN, NULL_HASH)
)
selector = CoinSelector(estimators, amount, cost_of_output)
spendables = selector.select()
if not spendables:
raise ValueError('Not enough funds to cover this transaction.')
outputs = [
out_class.pay_pubkey_hash(amount, coin.address_to_hash160(address))
]
spent_sum = sum(s.effective_amount for s in spendables)
if spent_sum > amount:
change_address = account.get_least_used_change_address()
change_hash160 = coin.address_to_hash160(change_address)
outputs.append(out_class.pay_pubkey_hash(spent_sum - amount, change_hash160))
tx = tx_class() \
.add_inputs([s.txi for s in spendables]) \
.add_outputs(outputs) \
.sign(account)
return tx