new fund command and automatic account creation
This commit is contained in:
parent
4165881d5f
commit
6a5d88a0d5
5 changed files with 115 additions and 96 deletions
|
@ -67,16 +67,9 @@ def normalize_value(x, key=None):
|
||||||
return True
|
return True
|
||||||
if x.lower() == 'false':
|
if x.lower() == 'false':
|
||||||
return False
|
return False
|
||||||
if '.' in x:
|
if x.isdigit():
|
||||||
try:
|
|
||||||
return float(x)
|
|
||||||
except ValueError:
|
|
||||||
# not a float
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
return int(x)
|
return int(x)
|
||||||
except ValueError:
|
return x
|
||||||
return x
|
|
||||||
|
|
||||||
|
|
||||||
def remove_brackets(key):
|
def remove_brackets(key):
|
||||||
|
|
|
@ -12,6 +12,7 @@ from twisted.web import server
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from torba.constants import COIN
|
from torba.constants import COIN
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ from lbrynet.dht.error import TimeoutError
|
||||||
from lbrynet.core.Peer import Peer
|
from lbrynet.core.Peer import Peer
|
||||||
from lbrynet.core.SinglePeerDownloader import SinglePeerDownloader
|
from lbrynet.core.SinglePeerDownloader import SinglePeerDownloader
|
||||||
from lbrynet.core.client.StandaloneBlobDownloader import StandaloneBlobDownloader
|
from lbrynet.core.client.StandaloneBlobDownloader import StandaloneBlobDownloader
|
||||||
from lbrynet.wallet.account import Account as LBRYAccount
|
from lbrynet.wallet.account import Account as LBCAccount
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
requires = AuthJSONRPCServer.requires
|
requires = AuthJSONRPCServer.requires
|
||||||
|
@ -805,7 +806,6 @@ class Daemon(AuthJSONRPCServer):
|
||||||
log.info("Get version info: " + json.dumps(platform_info))
|
log.info("Get version info: " + json.dumps(platform_info))
|
||||||
return self._render_response(platform_info)
|
return self._render_response(platform_info)
|
||||||
|
|
||||||
# @AuthJSONRPCServer.deprecated() # deprecated actually disables the call
|
|
||||||
def jsonrpc_report_bug(self, message=None):
|
def jsonrpc_report_bug(self, message=None):
|
||||||
"""
|
"""
|
||||||
Report a bug to slack
|
Report a bug to slack
|
||||||
|
@ -2369,36 +2369,6 @@ class Daemon(AuthJSONRPCServer):
|
||||||
d.addCallback(lambda address: self._render_response(address))
|
d.addCallback(lambda address: self._render_response(address))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
|
|
||||||
@AuthJSONRPCServer.deprecated("wallet_send")
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def jsonrpc_send_amount_to_address(self, amount, address):
|
|
||||||
"""
|
|
||||||
Queue a payment of credits to an address
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
send_amount_to_address (<amount> | --amount=<amount>) (<address> | --address=<address>)
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--amount=<amount> : (float) amount to send
|
|
||||||
--address=<address> : (str) address to send credits to
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(bool) true if payment successfully scheduled
|
|
||||||
"""
|
|
||||||
|
|
||||||
if amount < 0:
|
|
||||||
raise NegativeFundsError()
|
|
||||||
elif not amount:
|
|
||||||
raise NullFundsError()
|
|
||||||
|
|
||||||
reserved_points = self.wallet.reserve_points(address, amount)
|
|
||||||
if reserved_points is None:
|
|
||||||
raise InsufficientFundsError()
|
|
||||||
yield self.wallet.send_points_to_address(reserved_points, amount)
|
|
||||||
self.analytics_manager.send_credits_sent()
|
|
||||||
defer.returnValue(True)
|
|
||||||
|
|
||||||
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
|
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def jsonrpc_wallet_send(self, amount, address=None, claim_id=None):
|
def jsonrpc_wallet_send(self, amount, address=None, claim_id=None):
|
||||||
|
@ -2978,27 +2948,6 @@ class Daemon(AuthJSONRPCServer):
|
||||||
|
|
||||||
return self._blob_availability(blob_hash, search_timeout, blob_timeout)
|
return self._blob_availability(blob_hash, search_timeout, blob_timeout)
|
||||||
|
|
||||||
@requires(UPNP_COMPONENT, WALLET_COMPONENT, DHT_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
|
|
||||||
@AuthJSONRPCServer.deprecated("stream_availability")
|
|
||||||
def jsonrpc_get_availability(self, uri, sd_timeout=None, peer_timeout=None):
|
|
||||||
"""
|
|
||||||
Get stream availability for lbry uri
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
get_availability (<uri> | --uri=<uri>) [<sd_timeout> | --sd_timeout=<sd_timeout>]
|
|
||||||
[<peer_timeout> | --peer_timeout=<peer_timeout>]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--uri=<uri> : (str) check availability for this uri
|
|
||||||
--sd_timeout=<sd_timeout> : (int) sd blob download timeout
|
|
||||||
--peer_timeout=<peer_timeout> : (int) how long to look for peers
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(float) Peers per blob / total blobs
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.jsonrpc_stream_availability(uri, peer_timeout, sd_timeout)
|
|
||||||
|
|
||||||
@requires(UPNP_COMPONENT, WALLET_COMPONENT, DHT_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
|
@requires(UPNP_COMPONENT, WALLET_COMPONENT, DHT_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def jsonrpc_stream_availability(self, uri, search_timeout=None, blob_timeout=None):
|
def jsonrpc_stream_availability(self, uri, search_timeout=None, blob_timeout=None):
|
||||||
|
@ -3102,39 +3051,22 @@ class Daemon(AuthJSONRPCServer):
|
||||||
response['head_blob_availability'].get('is_available')
|
response['head_blob_availability'].get('is_available')
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
#######################
|
||||||
def jsonrpc_cli_test_command(self, pos_arg, pos_args=[], pos_arg2=None, pos_arg3=None,
|
# New Wallet Commands #
|
||||||
a_arg=False, b_arg=False):
|
#######################
|
||||||
"""
|
# TODO:
|
||||||
This command is only for testing the CLI argument parsing
|
# Delete this after all commands have been migrated
|
||||||
Usage:
|
# and refactored.
|
||||||
cli_test_command [--a_arg] [--b_arg] (<pos_arg> | --pos_arg=<pos_arg>)
|
|
||||||
[<pos_args>...] [--pos_arg2=<pos_arg2>]
|
|
||||||
[--pos_arg3=<pos_arg3>]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--a_arg : (bool) a arg
|
|
||||||
--b_arg : (bool) b arg
|
|
||||||
--pos_arg=<pos_arg> : (int) pos arg
|
|
||||||
--pos_args=<pos_args> : (int) pos args
|
|
||||||
--pos_arg2=<pos_arg2> : (int) pos arg 2
|
|
||||||
--pos_arg3=<pos_arg3> : (int) pos arg 3
|
|
||||||
Returns:
|
|
||||||
pos args
|
|
||||||
"""
|
|
||||||
out = (pos_arg, pos_args, pos_arg2, pos_arg3, a_arg, b_arg)
|
|
||||||
response = yield self._render_response(out)
|
|
||||||
defer.returnValue(response)
|
|
||||||
|
|
||||||
@requires("wallet")
|
@requires("wallet")
|
||||||
def jsonrpc_account_balance(self, account_name=None, confirmations=6,
|
def jsonrpc_balance(self, account_name=None, confirmations=6, include_reserved=False,
|
||||||
include_reserved=False, include_claims=False):
|
include_claims=False):
|
||||||
"""
|
"""
|
||||||
Return the balance of an individual account or all of the accounts.
|
Return the balance of an individual account or all of the accounts.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
account_balance [<account_name>] [--confirmations=<confirmations>]
|
balance [<account_name>] [--confirmations=<confirmations>]
|
||||||
[--include_reserved] [--include_claims]
|
[--include_reserved] [--include_claims]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--account=<account_name> : (str) If provided only the balance for this
|
--account=<account_name> : (str) If provided only the balance for this
|
||||||
|
@ -3150,7 +3082,7 @@ class Daemon(AuthJSONRPCServer):
|
||||||
if account_name:
|
if account_name:
|
||||||
for account in self.wallet.accounts:
|
for account in self.wallet.accounts:
|
||||||
if account.name == account_name:
|
if account.name == account_name:
|
||||||
if include_claims and not isinstance(account, LBRYAccount):
|
if include_claims and not isinstance(account, LBCAccount):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"'--include-claims' requires specifying an LBC ledger account. "
|
"'--include-claims' requires specifying an LBC ledger account. "
|
||||||
"Found '{}', but it's an {} ledger account."
|
"Found '{}', but it's an {} ledger account."
|
||||||
|
@ -3170,7 +3102,7 @@ class Daemon(AuthJSONRPCServer):
|
||||||
return self.wallet.get_balances(confirmations)
|
return self.wallet.get_balances(confirmations)
|
||||||
|
|
||||||
@requires("wallet")
|
@requires("wallet")
|
||||||
def jsonrpc_account_max_gap(self, account_name):
|
def jsonrpc_max_address_gap(self, account_name):
|
||||||
"""
|
"""
|
||||||
Finds ranges of consecutive addresses that are unused and returns the length
|
Finds ranges of consecutive addresses that are unused and returns the length
|
||||||
of the longest such range: for change and receiving address chains. This is
|
of the longest such range: for change and receiving address chains. This is
|
||||||
|
@ -3178,7 +3110,7 @@ class Daemon(AuthJSONRPCServer):
|
||||||
account settings.
|
account settings.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
account_max_gap <account_name>
|
max_address_gap <account_name>
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--account=<account_name> : (str) account for which to get max gaps
|
--account=<account_name> : (str) account for which to get max gaps
|
||||||
|
@ -3186,10 +3118,89 @@ class Daemon(AuthJSONRPCServer):
|
||||||
Returns:
|
Returns:
|
||||||
(map) maximum gap for change and receiving addresses
|
(map) maximum gap for change and receiving addresses
|
||||||
"""
|
"""
|
||||||
|
return self.get_account_or_error('account', account_name).get_max_gap()
|
||||||
|
|
||||||
|
@requires("wallet")
|
||||||
|
def jsonrpc_fund(self, to_account, from_account, amount=0,
|
||||||
|
everything=False, outputs=1, broadcast=False):
|
||||||
|
"""
|
||||||
|
Transfer some amount (or --everything) to an account from another
|
||||||
|
account (can be the same account). Decimal amounts are interpreted
|
||||||
|
as LBC and non-decimal amounts are interpreted as dewies. You can
|
||||||
|
also spread the transfer across a number of --outputs (cannot be
|
||||||
|
used together with --everything).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
transfer (<to_account> | --to_account=<to_account>)
|
||||||
|
(<from_account> | --from_account=<from_account>)
|
||||||
|
(<amount> | --amount=<amount> | --everything)
|
||||||
|
[<outputs> | --outputs=<outputs>]
|
||||||
|
[--broadcast]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--to_account=<to_account> : (str) send to this account
|
||||||
|
--from_account=<from_account> : (str) spend from this account
|
||||||
|
--amount=<amount> : (str) the amount to transfer (lbc or dewies)
|
||||||
|
--everything : (bool) transfer everything (excluding claims), default: false.
|
||||||
|
--outputs=<outputs> : (int) split payment across many outputs, default: 1.
|
||||||
|
--broadcast : (bool) actually broadcast the transaction, default: false.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(map) maximum gap for change and receiving addresses
|
||||||
|
|
||||||
|
"""
|
||||||
|
to_account = self.get_account_or_error('to_account', to_account)
|
||||||
|
from_account = self.get_account_or_error('from_account', from_account)
|
||||||
|
amount = self.get_dewies_or_error('amount', amount) if amount else None
|
||||||
|
if not isinstance(outputs, int):
|
||||||
|
raise ValueError("--outputs must be an integer.")
|
||||||
|
if everything and outputs > 1:
|
||||||
|
raise ValueError("Using --everything along with --outputs is not supported.")
|
||||||
|
return from_account.fund(
|
||||||
|
to_account=to_account, amount=amount, everything=everything,
|
||||||
|
outputs=outputs, broadcast=broadcast
|
||||||
|
).addCallback(lambda tx: self.tx_to_json(tx, from_account.ledger))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def tx_to_json(tx, ledger):
|
||||||
|
return {
|
||||||
|
'txid': tx.id,
|
||||||
|
'inputs': [
|
||||||
|
{'amount': txi.amount, 'address': txi.txo_ref.txo.get_address(ledger)}
|
||||||
|
for txi in tx.inputs
|
||||||
|
],
|
||||||
|
'outputs': [
|
||||||
|
{'amount': txo.amount, 'address': txo.get_address(ledger)}
|
||||||
|
for txo in tx.outputs
|
||||||
|
],
|
||||||
|
'total_input': tx.input_sum,
|
||||||
|
'total_output': tx.input_sum,
|
||||||
|
'total_fee': tx.fee,
|
||||||
|
'xhex': hexlify(tx.raw).decode(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_account_or_error(self, argument: str, account_name: str, lbc_only=False):
|
||||||
for account in self.wallet.accounts:
|
for account in self.wallet.accounts:
|
||||||
if account.name == account_name:
|
if account.name == account_name:
|
||||||
return account.get_max_gap()
|
if lbc_only and not isinstance(account, LBCAccount):
|
||||||
raise Exception("Couldn't find an account named: '{}'.".format(account_name))
|
raise ValueError(
|
||||||
|
"Found '{}', but it's an {} ledger account. "
|
||||||
|
"'{}' requires specifying an LBC ledger account."
|
||||||
|
.format(account_name, account.ledger.symbol, argument)
|
||||||
|
)
|
||||||
|
return account
|
||||||
|
raise ValueError("Couldn't find an account named: '{}'.".format(account_name))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_dewies_or_error(argument: str, amount: Union[str, int]):
|
||||||
|
if isinstance(amount, str):
|
||||||
|
if '.' in amount:
|
||||||
|
return int(Decimal(amount) * COIN)
|
||||||
|
elif amount.isdigit():
|
||||||
|
return int(amount)
|
||||||
|
elif isinstance(amount, int):
|
||||||
|
return amount
|
||||||
|
raise ValueError("Invalid value for '{}' argument: {}".format(argument, amount))
|
||||||
|
|
||||||
|
|
||||||
def loggly_time_string(dt):
|
def loggly_time_string(dt):
|
||||||
|
|
|
@ -101,6 +101,12 @@ class Account(BaseAccount):
|
||||||
})
|
})
|
||||||
defer.returnValue(channels)
|
defer.returnValue(channels)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_private_key_from_seed(cls, ledger: 'baseledger.BaseLedger', seed: str, password: str):
|
||||||
|
return super().get_private_key_from_seed(
|
||||||
|
ledger, seed, password or 'lbryum'
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, ledger, d: dict) -> 'Account':
|
def from_dict(cls, ledger, d: dict) -> 'Account':
|
||||||
account = super().from_dict(ledger, d)
|
account = super().from_dict(ledger, d)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from torba.basemanager import BaseWalletManager
|
from torba.basemanager import BaseWalletManager
|
||||||
|
@ -13,6 +14,8 @@ from .account import generate_certificate
|
||||||
from .transaction import Transaction
|
from .transaction import Transaction
|
||||||
from .database import WalletDatabase
|
from .database import WalletDatabase
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BackwardsCompatibleNetwork:
|
class BackwardsCompatibleNetwork:
|
||||||
def __init__(self, manager):
|
def __init__(self, manager):
|
||||||
|
@ -107,10 +110,16 @@ class LbryWalletManager(BaseWalletManager):
|
||||||
with open(wallet_file_path, 'w') as f:
|
with open(wallet_file_path, 'w') as f:
|
||||||
f.write(json_data)
|
f.write(json_data)
|
||||||
|
|
||||||
return cls.from_config({
|
manager = cls.from_config({
|
||||||
'ledgers': {ledger_id: ledger_config},
|
'ledgers': {ledger_id: ledger_config},
|
||||||
'wallets': [wallet_file_path]
|
'wallets': [wallet_file_path]
|
||||||
})
|
})
|
||||||
|
if manager.default_account is None:
|
||||||
|
ledger = manager.get_or_create_ledger('lbc_mainnet')
|
||||||
|
log.info('Wallet at %s is empty, generating a default account.', wallet_file_path)
|
||||||
|
manager.default_wallet.generate_account(ledger)
|
||||||
|
manager.default_wallet.save()
|
||||||
|
return manager
|
||||||
|
|
||||||
def get_best_blockhash(self):
|
def get_best_blockhash(self):
|
||||||
return defer.succeed('')
|
return defer.succeed('')
|
||||||
|
|
|
@ -11,7 +11,7 @@ class CLITest(unittest.TestCase):
|
||||||
|
|
||||||
def test_guess_type(self):
|
def test_guess_type(self):
|
||||||
self.assertEqual('0.3.8', normalize_value('0.3.8'))
|
self.assertEqual('0.3.8', normalize_value('0.3.8'))
|
||||||
self.assertEqual(0.3, normalize_value('0.3'))
|
self.assertEqual('0.3', normalize_value('0.3'))
|
||||||
self.assertEqual(3, normalize_value('3'))
|
self.assertEqual(3, normalize_value('3'))
|
||||||
self.assertEqual(3, normalize_value(3))
|
self.assertEqual(3, normalize_value(3))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue