diff --git a/lbry/lbry/extras/daemon/Daemon.py b/lbry/lbry/extras/daemon/Daemon.py index ab1259ee4..0a62dae0b 100644 --- a/lbry/lbry/extras/daemon/Daemon.py +++ b/lbry/lbry/extras/daemon/Daemon.py @@ -38,7 +38,7 @@ from lbry.extras.daemon import comment_client from lbry.extras.daemon.undecorated import undecorated from lbry.wallet.transaction import Transaction, Output, Input from lbry.wallet.account import Account as LBCAccount -from lbry.wallet.dewies import dewies_to_lbc, lbc_to_dewies +from lbry.wallet.dewies import dewies_to_lbc, lbc_to_dewies, dict_values_to_lbc from lbry.schema.claim import Claim from lbry.schema.url import URL @@ -1141,6 +1141,31 @@ class Daemon(metaclass=JSONRPCServerType): self.wallet_manager.wallets.remove(wallet) return wallet + @requires("wallet") + async def jsonrpc_wallet_balance(self, wallet_id=None, confirmations=0, reserved_subtotals=False): + """ + Return the balance of a wallet + + Usage: + wallet_balance [--wallet_id=] [--confirmations=] [--reserved_subtotals] + + Options: + --wallet_id= : (str) balance for specific wallet + --confirmations= : (int) Only include transactions with this many + confirmed blocks. + --reserved_subtotals : (bool) Include detailed reserved balances on + claims, tips and supports. + + Returns: + (decimal) amount of lbry credits in wallet + """ + wallet = self.wallet_manager.get_wallet_or_default(wallet_id) + balance = await self.ledger.get_detailed_balance( + accounts=wallet.accounts, confirmations=confirmations, + reserved_subtotals=reserved_subtotals + ) + return dict_values_to_lbc(balance) + ACCOUNT_DOC = """ Create, modify and inspect wallet accounts. """ @@ -1200,7 +1225,10 @@ class Daemon(metaclass=JSONRPCServerType): """ wallet = self.wallet_manager.get_wallet_or_default(wallet_id) account = wallet.get_account_or_default(account_id) - return await account.get_granular_balances(confirmations=confirmations, reserved_subtotals=reserved_subtotals) + balance = await account.get_detailed_balance( + confirmations=confirmations, reserved_subtotals=reserved_subtotals + ) + return dict_values_to_lbc(balance) @requires("wallet") async def jsonrpc_account_add( diff --git a/lbry/lbry/wallet/account.py b/lbry/lbry/wallet/account.py index 623d8dd35..c3e9b08dc 100644 --- a/lbry/lbry/wallet/account.py +++ b/lbry/lbry/wallet/account.py @@ -5,7 +5,6 @@ from hashlib import sha256 from string import hexdigits import ecdsa -from lbry.wallet.dewies import dewies_to_lbc from lbry.wallet.constants import TXO_TYPES from torba.client.baseaccount import BaseAccount, HierarchicalDeterministic @@ -78,7 +77,7 @@ class Account(BaseAccount): constraints.update({'txo_type': 0}) return super().get_balance(confirmations, **constraints) - async def get_granular_balances(self, confirmations=0, reserved_subtotals=False): + async def get_detailed_balance(self, confirmations=0, reserved_subtotals=False): tips_balance, supports_balance, claims_balance = 0, 0, 0 get_total_balance = partial(self.get_balance, confirmations=confirmations, include_claims=True) total = await get_total_balance() @@ -98,13 +97,13 @@ class Account(BaseAccount): confirmations=confirmations, include_claims=True, txo_type__gt=0 ) return { - 'total': dewies_to_lbc(total), - 'available': dewies_to_lbc(total - reserved), - 'reserved': dewies_to_lbc(reserved), + 'total': total, + 'available': total - reserved, + 'reserved': reserved, 'reserved_subtotals': { - 'claims': dewies_to_lbc(claims_balance), - 'supports': dewies_to_lbc(supports_balance), - 'tips': dewies_to_lbc(tips_balance) + 'claims': claims_balance, + 'supports': supports_balance, + 'tips': tips_balance } if reserved_subtotals else None } diff --git a/lbry/lbry/wallet/dewies.py b/lbry/lbry/wallet/dewies.py index 3e70c1da9..319e0b9ad 100644 --- a/lbry/lbry/wallet/dewies.py +++ b/lbry/lbry/wallet/dewies.py @@ -31,3 +31,12 @@ def lbc_to_dewies(lbc: str) -> int: def dewies_to_lbc(dewies) -> str: return satoshis_to_coins(dewies) + + +def dict_values_to_lbc(d): + for key, value in d.items(): + if isinstance(value, int): + d[key] = dewies_to_lbc(value) + elif isinstance(value, dict): + dict_values_to_lbc(value) + return d diff --git a/lbry/lbry/wallet/ledger.py b/lbry/lbry/wallet/ledger.py index cab0574d0..831ee57fc 100644 --- a/lbry/lbry/wallet/ledger.py +++ b/lbry/lbry/wallet/ledger.py @@ -244,6 +244,23 @@ class MainNetLedger(BaseLedger): def get_transaction_history_count(self, **constraints): return self.db.get_transaction_count(**constraints) + @staticmethod + async def get_detailed_balance(accounts, confirmations=0, reserved_subtotals=False): + result = {} + for account in accounts: + balance = await account.get_detailed_balance(confirmations, reserved_subtotals) + if result: + for key, value in balance.items(): + if key == 'reserved_subtotals': + if value is not None: + for subkey, subvalue in value.items(): + result['reserved_subtotals'][subkey] += subvalue + else: + result[key] += value + else: + result = balance + return result + class TestNetLedger(MainNetLedger): network_name = 'testnet' diff --git a/lbry/tests/integration/test_transaction_commands.py b/lbry/tests/integration/test_transaction_commands.py index 6951ba414..2ce482d79 100644 --- a/lbry/tests/integration/test_transaction_commands.py +++ b/lbry/tests/integration/test_transaction_commands.py @@ -38,34 +38,42 @@ class TransactionCommandsTestCase(CommandTestCase): await self.assertBalance(self.account, '11.0') async def test_granular_balances(self): - account_balance = self.daemon.jsonrpc_account_balance + account2 = await self.daemon.jsonrpc_account_create("Tip-er") - self.assertEqual(await account_balance(reserved_subtotals=False), { + account_balance = self.daemon.jsonrpc_account_balance + wallet_balance = self.daemon.jsonrpc_wallet_balance + + expected = { 'total': '10.0', 'available': '10.0', 'reserved': '0.0', 'reserved_subtotals': None - }) + } + self.assertEqual(await account_balance(reserved_subtotals=False), expected) + self.assertEqual(await wallet_balance(reserved_subtotals=False), expected) - self.assertEqual(await account_balance(reserved_subtotals=True), { + expected = { 'total': '10.0', 'available': '10.0', 'reserved': '0.0', 'reserved_subtotals': {'claims': '0.0', 'supports': '0.0', 'tips': '0.0'} - }) + } + self.assertEqual(await account_balance(reserved_subtotals=True), expected) + self.assertEqual(await wallet_balance(reserved_subtotals=True), expected) # claim with update + supporting our own claim stream1 = await self.stream_create('granularity', '3.0') await self.stream_update(self.get_claim_id(stream1), data=b'news', bid='1.0') await self.support_create(self.get_claim_id(stream1), '2.0') - self.assertEqual(await account_balance(reserved_subtotals=True), { + expected = { 'total': '9.977534', 'available': '6.977534', 'reserved': '3.0', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} - }) + } + self.assertEqual(await account_balance(reserved_subtotals=True), expected) + self.assertEqual(await wallet_balance(reserved_subtotals=True), expected) - account2 = await self.daemon.jsonrpc_account_create("Tip-er") address2 = await self.daemon.jsonrpc_address_unused(account2.id) # send lbc to someone else @@ -77,6 +85,12 @@ class TransactionCommandsTestCase(CommandTestCase): 'reserved': '3.0', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} }) + self.assertEqual(await wallet_balance(reserved_subtotals=True), { + 'total': '9.97741', + 'available': '6.97741', + 'reserved': '3.0', + 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} + }) # tip received support1 = await self.support_create( @@ -88,6 +102,12 @@ class TransactionCommandsTestCase(CommandTestCase): 'reserved': '3.3', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.3'} }) + self.assertEqual(await wallet_balance(reserved_subtotals=True), { + 'total': '9.977268', + 'available': '6.677268', + 'reserved': '3.3', + 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.3'} + }) # tip claimed tx = await self.daemon.jsonrpc_support_abandon(txid=support1['txid'], nout=0) @@ -98,6 +118,12 @@ class TransactionCommandsTestCase(CommandTestCase): 'reserved': '3.0', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} }) + self.assertEqual(await wallet_balance(reserved_subtotals=True), { + 'total': '9.977161', + 'available': '6.977161', + 'reserved': '3.0', + 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} + }) stream2 = await self.stream_create( 'granularity-is-cool', '0.1', account_id=account2.id, funding_account_ids=[account2.id] @@ -105,10 +131,17 @@ class TransactionCommandsTestCase(CommandTestCase): # tip another claim await self.support_create( - self.get_claim_id(stream2), '0.2', tip=True, funding_account_ids=[self.account.id]) + self.get_claim_id(stream2), '0.2', tip=True, funding_account_ids=[self.account.id] + ) self.assertEqual(await account_balance(reserved_subtotals=True), { 'total': '9.077157', 'available': '6.077157', 'reserved': '3.0', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} }) + self.assertEqual(await wallet_balance(reserved_subtotals=True), { + 'total': '9.938908', + 'available': '6.638908', + 'reserved': '3.3', + 'reserved_subtotals': {'claims': '1.1', 'supports': '2.0', 'tips': '0.2'} + })