2018-05-25 08:03:25 +02:00
|
|
|
import functools
|
|
|
|
from typing import List, Dict, Type
|
|
|
|
from twisted.internet import defer
|
|
|
|
|
2018-06-12 16:02:04 +02:00
|
|
|
from torba.baseledger import BaseLedger, LedgerRegistry
|
2018-06-08 05:47:46 +02:00
|
|
|
from torba.basetransaction import BaseTransaction, NULL_HASH
|
2018-06-04 02:06:55 +02:00
|
|
|
from torba.coinselection import CoinSelector
|
|
|
|
from torba.constants import COIN
|
2018-05-25 08:03:25 +02:00
|
|
|
from torba.wallet import Wallet, WalletStorage
|
|
|
|
|
|
|
|
|
2018-06-11 15:33:32 +02:00
|
|
|
class WalletManager(object):
|
2018-05-25 08:03:25 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2018-06-12 16:02:04 +02:00
|
|
|
def get_or_create_ledger(self, ledger_id, ledger_config=None):
|
|
|
|
ledger_class = LedgerRegistry.get_ledger_class(ledger_id)
|
2018-05-25 08:03:25 +02:00
|
|
|
ledger = self.ledgers.get(ledger_class)
|
|
|
|
if ledger is None:
|
2018-06-11 15:33:32 +02:00
|
|
|
ledger = self.create_ledger(ledger_class, ledger_config or {})
|
2018-05-25 08:03:25 +02:00
|
|
|
self.ledgers[ledger_class] = ledger
|
|
|
|
return ledger
|
|
|
|
|
2018-06-11 15:33:32 +02:00
|
|
|
def create_ledger(self, ledger_class, config):
|
|
|
|
return ledger_class(config)
|
2018-06-08 05:47:46 +02:00
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
|
|
|
def get_balance(self):
|
|
|
|
balances = {}
|
|
|
|
for ledger in self.ledgers:
|
|
|
|
for account in self.get_accounts(ledger.coin_class):
|
|
|
|
balances.setdefault(ledger.coin_class.name, 0)
|
|
|
|
balances[ledger.coin_class.name] += yield account.get_balance()
|
|
|
|
defer.returnValue(balances)
|
|
|
|
|
2018-05-25 08:03:25 +02:00
|
|
|
@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
|
2018-06-08 05:47:46 +02:00
|
|
|
def start(self):
|
2018-05-25 08:03:25 +02:00
|
|
|
self.running = True
|
|
|
|
yield defer.DeferredList([
|
|
|
|
l.start() for l in self.ledgers.values()
|
|
|
|
])
|
|
|
|
|
|
|
|
@defer.inlineCallbacks
|
2018-06-08 05:47:46 +02:00
|
|
|
def stop(self):
|
2018-05-25 08:03:25 +02:00
|
|
|
yield defer.DeferredList([
|
|
|
|
l.stop() for l in self.ledgers.values()
|
|
|
|
])
|
|
|
|
self.running = False
|
2018-06-04 02:06:55 +02:00
|
|
|
|
|
|
|
def send_amount_to_address(self, amount, address):
|
|
|
|
amount = int(amount * COIN)
|
|
|
|
|
|
|
|
account = self.default_account
|
2018-06-12 16:02:04 +02:00
|
|
|
ledger = account.ledger
|
2018-06-08 05:47:46 +02:00
|
|
|
tx_class = ledger.transaction_class # type: BaseTransaction
|
2018-06-04 02:06:55 +02:00
|
|
|
in_class, out_class = tx_class.input_class, tx_class.output_class
|
|
|
|
|
|
|
|
estimators = [
|
2018-06-12 16:02:04 +02:00
|
|
|
txo.get_estimator(ledger) for txo in account.get_unspent_utxos()
|
2018-06-04 02:06:55 +02:00
|
|
|
]
|
2018-06-08 05:47:46 +02:00
|
|
|
tx_class.create()
|
2018-06-04 02:06:55 +02:00
|
|
|
|
2018-06-12 16:02:04 +02:00
|
|
|
cost_of_output = ledger.get_input_output_fee(
|
2018-06-04 02:06:55 +02:00
|
|
|
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
|