Edit on GitHub

communex.cli.balance

  1import re
  2from typing import Optional, cast
  3
  4import typer
  5from typer import Context
  6
  7from communex._common import IPFS_REGEX, BalanceUnit, format_balance
  8from communex.balance import from_nano, to_nano
  9from communex.cli._common import (
 10    make_custom_context,
 11    print_table_from_plain_dict,
 12)
 13from communex.compat.key import local_key_addresses
 14from communex.errors import ChainTransactionError
 15from communex.faucet.powv2 import solve_for_difficulty_fast
 16
 17balance_app = typer.Typer(no_args_is_help=True)
 18
 19
 20@balance_app.command()
 21def free_balance(
 22    ctx: Context,
 23    key: str,
 24    unit: BalanceUnit = BalanceUnit.joule,
 25    password: Optional[str] = None,
 26):
 27    """
 28    Gets free balance of a key.
 29    """
 30    context = make_custom_context(ctx)
 31    client = context.com_client()
 32
 33    key_address = context.resolve_key_ss58(key, password)
 34
 35    with context.progress_status(
 36        f"Getting free balance of key {key_address}..."
 37    ):
 38        balance = client.get_balance(key_address)
 39
 40    context.output(format_balance(balance, unit))
 41
 42
 43@balance_app.command()
 44def staked_balance(
 45    ctx: Context,
 46    key: str,
 47    unit: BalanceUnit = BalanceUnit.joule,
 48    password: Optional[str] = None,
 49):
 50    """
 51    Gets the balance staked on the key itself.
 52    """
 53    context = make_custom_context(ctx)
 54    client = context.com_client()
 55
 56    key_address = context.resolve_key_ss58(key, password)
 57
 58    with context.progress_status(
 59        f"Getting staked balance of key {key_address}..."
 60    ):
 61        result = sum(client.get_staketo(key=key_address).values())
 62
 63    context.output(format_balance(result, unit))
 64
 65
 66@balance_app.command()
 67def show(
 68    ctx: Context,
 69    key: str,
 70    unit: BalanceUnit = BalanceUnit.joule,
 71    password: Optional[str] = None,
 72):
 73    """
 74    Gets entire balance of a key (free balance + staked balance).
 75    """
 76    context = make_custom_context(ctx)
 77    client = context.com_client()
 78
 79    key_address = context.resolve_key_ss58(key, password)
 80
 81    with context.progress_status(f"Getting value of key {key_address}..."):
 82        staked_balance = sum(client.get_staketo(key=key_address).values())
 83        free_balance = client.get_balance(key_address)
 84        balance_sum = free_balance + staked_balance
 85
 86    print_table_from_plain_dict(
 87        {
 88            "Free": format_balance(free_balance, unit),
 89            "Staked": format_balance(staked_balance, unit),
 90            "Total": format_balance(balance_sum, unit),
 91        },
 92        ["Result", "Amount"],
 93        context.console,
 94    )
 95
 96
 97@balance_app.command()
 98def get_staked(
 99    ctx: Context,
100    key: str,
101    unit: BalanceUnit = BalanceUnit.joule,
102    password: Optional[str] = None,
103):
104    """
105    Gets total stake of a key it delegated across other keys.
106    """
107    context = make_custom_context(ctx)
108    client = context.com_client()
109
110    key_address = context.resolve_key_ss58(key, password)
111
112    with context.progress_status(f"Getting stake of {key_address}..."):
113        result = sum(client.get_staketo(key=key_address).values())
114
115    context.output(format_balance(result, unit))
116
117
118@balance_app.command()
119def transfer(ctx: Context, key: str, amount: float, dest: str):
120    """
121    Transfer amount to destination using key
122    """
123    context = make_custom_context(ctx)
124    client = context.com_client()
125
126    nano_amount = to_nano(amount)
127
128    resolved_key = context.load_key(key, None)
129    resolved_dest = context.resolve_key_ss58(dest, None)
130
131    if not context.confirm(
132        f"Are you sure you want to transfer {amount} tokens to {dest}?"
133    ):
134        raise typer.Abort()
135
136    with context.progress_status(f"Transferring {amount} tokens to {dest}..."):
137        response = client.transfer(
138            key=resolved_key, amount=nano_amount, dest=resolved_dest
139        )
140
141    if response.is_success:
142        context.info(f"Transferred {amount} tokens to {dest}")
143    else:
144        raise ChainTransactionError(response.error_message)  # type: ignore
145
146
147@balance_app.command()
148def transfer_stake(
149    ctx: Context, key: str, amount: float, from_key: str, dest: str
150):
151    """
152    Transfers stake of key from point A to point B
153    """
154    context = make_custom_context(ctx)
155    client = context.com_client()
156
157    nano_amount = to_nano(amount)
158    keypair = context.load_key(key, None)
159    resolved_from = context.resolve_key_ss58(from_key)
160    resolved_dest = context.resolve_key_ss58(dest)
161
162    with context.progress_status(
163        f"Transferring {amount} tokens from {from_key} to {dest}' ..."
164    ):
165        response = client.transfer_stake(
166            key=keypair,
167            amount=nano_amount,
168            from_module_key=resolved_from,
169            dest_module_address=resolved_dest,
170        )
171
172    if response.is_success:
173        context.info(f"Transferred {amount} tokens from {from_key} to {dest}")
174    else:
175        raise ChainTransactionError(response.error_message)  # type: ignore
176
177
178@balance_app.command()
179def stake(
180    ctx: Context,
181    key: str,
182    amount: float,
183    dest: str,
184):
185    """
186    Stake amount to destination using key
187    """
188    context = make_custom_context(ctx)
189    client = context.com_client()
190
191    nano_amount = to_nano(amount)
192    keypair = context.load_key(key, None)
193    resolved_dest = context.resolve_key_ss58(dest, None)
194
195    delegating_message = (
196        "By default you delegate DAO "
197        "voting power to the validator you stake to. "
198        "In case you want to change this, call: "
199        "`comx key power-delegation <key> --disable`."
200    )
201    context.info("INFO: ", style="bold green", end="")  # type: ignore
202    context.info(delegating_message)  # type: ignore
203    with context.progress_status(f"Staking {amount} tokens to {dest}..."):
204        response = client.stake(
205            key=keypair, amount=nano_amount, dest=resolved_dest
206        )
207
208    if response.is_success:
209        context.info(f"Staked {amount} tokens to {dest}")
210    else:
211        raise ChainTransactionError(response.error_message)  # type: ignore
212
213
214@balance_app.command()
215def unstake(ctx: Context, key: str, amount: float, dest: str):
216    """
217    Unstake amount from destination using key
218    """
219    context = make_custom_context(ctx)
220    client = context.com_client()
221
222    nano_amount = to_nano(amount)
223    keypair = context.load_key(key, None)
224    resolved_dest = context.resolve_key_ss58(dest, None)
225
226    with context.progress_status(f"Unstaking {amount} tokens from {dest}'..."):
227        response = client.unstake(
228            key=keypair, amount=nano_amount, dest=resolved_dest
229        )  # TODO: is it right?
230
231    if response.is_success:
232        context.info(f"Unstaked {amount} tokens from {dest}")
233    else:
234        raise ChainTransactionError(response.error_message)  # type: ignore
235
236
237@balance_app.command()
238def run_faucet(
239    ctx: Context,
240    key: str,
241    num_processes: Optional[int] = None,
242    num_executions: int = 1,
243):
244    context = make_custom_context(ctx)
245    use_testnet = ctx.obj.use_testnet
246
247    if not use_testnet:
248        context.error("Faucet only enabled on testnet")
249        raise typer.Exit(code=1)
250
251    resolved_key = context.load_key(key, None)
252
253    client = context.com_client()
254    for _ in range(num_executions):
255        with context.progress_status("Solving PoW..."):
256            solution = solve_for_difficulty_fast(
257                client,
258                resolved_key,
259                client.url,
260                num_processes=num_processes,
261            )
262        with context.progress_status("Sending solution to blockchain"):
263            params = {
264                "block_number": solution.block_number,
265                "nonce": solution.nonce,
266                "work": solution.seal,
267                "key": resolved_key.ss58_address,
268            }
269            client.compose_call(
270                "faucet",
271                params=params,
272                unsigned=True,
273                module="FaucetModule",
274                key=resolved_key.ss58_address,  # type: ignore
275            )
276
277
278@balance_app.command()
279def transfer_dao_funds(
280    ctx: Context,
281    signer_key: str,
282    amount: float,
283    cid_hash: str,
284    dest: str,
285):
286    context = make_custom_context(ctx)
287
288    if not re.match(IPFS_REGEX, cid_hash):
289        context.error(f"CID provided is invalid: {cid_hash}")
290        raise typer.Exit(code=1)
291
292    ipfs_prefix = "ipfs://"
293    cid = ipfs_prefix + cid_hash
294
295    nano_amount = to_nano(amount)
296    keypair = context.load_key(signer_key, None)
297    dest = context.resolve_key_ss58(dest, None)
298
299    client = context.com_client()
300    client.add_transfer_dao_treasury_proposal(keypair, cid, nano_amount, dest)
301
302
303@balance_app.command()
304def bridge(
305    ctx: Context,
306    key: str,
307    amount: float,
308):
309    context = make_custom_context(ctx)
310    client = context.com_client()
311
312    nano_amount = to_nano(amount)
313    keypair = context.load_key(key, None)
314
315    with context.progress_status(f"Bridging {amount} tokens..."):
316        try:
317            client.bridge(keypair, nano_amount)
318        except Exception as e:
319            context.error(f"Failed to bridge {amount} tokens: {e}")
320        else:
321            context.info(f"Bridged {amount}$j successfully")
322
323
324@balance_app.command()
325def bridge_withdraw(
326    ctx: Context,
327    key: str,
328    amount: float,
329):
330    context = make_custom_context(ctx)
331    client = context.com_client()
332
333    nano_amount = to_nano(amount)
334    keypair = context.load_key(key, None)
335
336    with context.progress_status(f"Withdrawing {amount} tokens..."):
337        try:
338            client.bridge_withdraw(keypair, nano_amount)
339        except Exception as e:
340            context.error(f"Failed to withdraw {amount} tokens: {e}")
341        else:
342            context.info(f"Withdrew {amount}$j successfully")
343
344
345@balance_app.command()
346def bridged_balance(
347    ctx: Context,
348    key: Optional[str] = None,
349):
350    context = make_custom_context(ctx)
351    client = context.com_client()
352    with context.progress_status("Getting bridged balance..."):
353        bridge_map = client.query_map(
354            "Bridged", params=[], extract_value=False
355        )["Bridged"]
356        bridge_map = cast(dict[str, int], bridge_map)
357    if key is None:
358        local_keys = local_key_addresses(context.password_manager)
359        local_bridge_map = {
360            address: from_nano(ammount)
361            for address, ammount in bridge_map.items()
362            if address in local_keys.values()
363        }
364    else:
365        key = cast(str, context.resolve_key_ss58(key, None))
366        bridged_amount = from_nano(bridge_map.get(key, 0))
367        local_bridge_map = {key: bridged_amount}
368
369    print_table_from_plain_dict(
370        local_bridge_map, ["Address", "Amount"], context.console
371    )
balance_app = <typer.main.Typer object>
@balance_app.command()
def free_balance( ctx: typer.models.Context, key: str, unit: communex._common.BalanceUnit = <BalanceUnit.joule: 'joule'>, password: Optional[str] = None):
21@balance_app.command()
22def free_balance(
23    ctx: Context,
24    key: str,
25    unit: BalanceUnit = BalanceUnit.joule,
26    password: Optional[str] = None,
27):
28    """
29    Gets free balance of a key.
30    """
31    context = make_custom_context(ctx)
32    client = context.com_client()
33
34    key_address = context.resolve_key_ss58(key, password)
35
36    with context.progress_status(
37        f"Getting free balance of key {key_address}..."
38    ):
39        balance = client.get_balance(key_address)
40
41    context.output(format_balance(balance, unit))

Gets free balance of a key.

@balance_app.command()
def staked_balance( ctx: typer.models.Context, key: str, unit: communex._common.BalanceUnit = <BalanceUnit.joule: 'joule'>, password: Optional[str] = None):
44@balance_app.command()
45def staked_balance(
46    ctx: Context,
47    key: str,
48    unit: BalanceUnit = BalanceUnit.joule,
49    password: Optional[str] = None,
50):
51    """
52    Gets the balance staked on the key itself.
53    """
54    context = make_custom_context(ctx)
55    client = context.com_client()
56
57    key_address = context.resolve_key_ss58(key, password)
58
59    with context.progress_status(
60        f"Getting staked balance of key {key_address}..."
61    ):
62        result = sum(client.get_staketo(key=key_address).values())
63
64    context.output(format_balance(result, unit))

Gets the balance staked on the key itself.

@balance_app.command()
def show( ctx: typer.models.Context, key: str, unit: communex._common.BalanceUnit = <BalanceUnit.joule: 'joule'>, password: Optional[str] = None):
67@balance_app.command()
68def show(
69    ctx: Context,
70    key: str,
71    unit: BalanceUnit = BalanceUnit.joule,
72    password: Optional[str] = None,
73):
74    """
75    Gets entire balance of a key (free balance + staked balance).
76    """
77    context = make_custom_context(ctx)
78    client = context.com_client()
79
80    key_address = context.resolve_key_ss58(key, password)
81
82    with context.progress_status(f"Getting value of key {key_address}..."):
83        staked_balance = sum(client.get_staketo(key=key_address).values())
84        free_balance = client.get_balance(key_address)
85        balance_sum = free_balance + staked_balance
86
87    print_table_from_plain_dict(
88        {
89            "Free": format_balance(free_balance, unit),
90            "Staked": format_balance(staked_balance, unit),
91            "Total": format_balance(balance_sum, unit),
92        },
93        ["Result", "Amount"],
94        context.console,
95    )

Gets entire balance of a key (free balance + staked balance).

@balance_app.command()
def get_staked( ctx: typer.models.Context, key: str, unit: communex._common.BalanceUnit = <BalanceUnit.joule: 'joule'>, password: Optional[str] = None):
 98@balance_app.command()
 99def get_staked(
100    ctx: Context,
101    key: str,
102    unit: BalanceUnit = BalanceUnit.joule,
103    password: Optional[str] = None,
104):
105    """
106    Gets total stake of a key it delegated across other keys.
107    """
108    context = make_custom_context(ctx)
109    client = context.com_client()
110
111    key_address = context.resolve_key_ss58(key, password)
112
113    with context.progress_status(f"Getting stake of {key_address}..."):
114        result = sum(client.get_staketo(key=key_address).values())
115
116    context.output(format_balance(result, unit))

Gets total stake of a key it delegated across other keys.

@balance_app.command()
def transfer(ctx: typer.models.Context, key: str, amount: float, dest: str):
119@balance_app.command()
120def transfer(ctx: Context, key: str, amount: float, dest: str):
121    """
122    Transfer amount to destination using key
123    """
124    context = make_custom_context(ctx)
125    client = context.com_client()
126
127    nano_amount = to_nano(amount)
128
129    resolved_key = context.load_key(key, None)
130    resolved_dest = context.resolve_key_ss58(dest, None)
131
132    if not context.confirm(
133        f"Are you sure you want to transfer {amount} tokens to {dest}?"
134    ):
135        raise typer.Abort()
136
137    with context.progress_status(f"Transferring {amount} tokens to {dest}..."):
138        response = client.transfer(
139            key=resolved_key, amount=nano_amount, dest=resolved_dest
140        )
141
142    if response.is_success:
143        context.info(f"Transferred {amount} tokens to {dest}")
144    else:
145        raise ChainTransactionError(response.error_message)  # type: ignore

Transfer amount to destination using key

@balance_app.command()
def transfer_stake( ctx: typer.models.Context, key: str, amount: float, from_key: str, dest: str):
148@balance_app.command()
149def transfer_stake(
150    ctx: Context, key: str, amount: float, from_key: str, dest: str
151):
152    """
153    Transfers stake of key from point A to point B
154    """
155    context = make_custom_context(ctx)
156    client = context.com_client()
157
158    nano_amount = to_nano(amount)
159    keypair = context.load_key(key, None)
160    resolved_from = context.resolve_key_ss58(from_key)
161    resolved_dest = context.resolve_key_ss58(dest)
162
163    with context.progress_status(
164        f"Transferring {amount} tokens from {from_key} to {dest}' ..."
165    ):
166        response = client.transfer_stake(
167            key=keypair,
168            amount=nano_amount,
169            from_module_key=resolved_from,
170            dest_module_address=resolved_dest,
171        )
172
173    if response.is_success:
174        context.info(f"Transferred {amount} tokens from {from_key} to {dest}")
175    else:
176        raise ChainTransactionError(response.error_message)  # type: ignore

Transfers stake of key from point A to point B

@balance_app.command()
def stake(ctx: typer.models.Context, key: str, amount: float, dest: str):
179@balance_app.command()
180def stake(
181    ctx: Context,
182    key: str,
183    amount: float,
184    dest: str,
185):
186    """
187    Stake amount to destination using key
188    """
189    context = make_custom_context(ctx)
190    client = context.com_client()
191
192    nano_amount = to_nano(amount)
193    keypair = context.load_key(key, None)
194    resolved_dest = context.resolve_key_ss58(dest, None)
195
196    delegating_message = (
197        "By default you delegate DAO "
198        "voting power to the validator you stake to. "
199        "In case you want to change this, call: "
200        "`comx key power-delegation <key> --disable`."
201    )
202    context.info("INFO: ", style="bold green", end="")  # type: ignore
203    context.info(delegating_message)  # type: ignore
204    with context.progress_status(f"Staking {amount} tokens to {dest}..."):
205        response = client.stake(
206            key=keypair, amount=nano_amount, dest=resolved_dest
207        )
208
209    if response.is_success:
210        context.info(f"Staked {amount} tokens to {dest}")
211    else:
212        raise ChainTransactionError(response.error_message)  # type: ignore

Stake amount to destination using key

@balance_app.command()
def unstake(ctx: typer.models.Context, key: str, amount: float, dest: str):
215@balance_app.command()
216def unstake(ctx: Context, key: str, amount: float, dest: str):
217    """
218    Unstake amount from destination using key
219    """
220    context = make_custom_context(ctx)
221    client = context.com_client()
222
223    nano_amount = to_nano(amount)
224    keypair = context.load_key(key, None)
225    resolved_dest = context.resolve_key_ss58(dest, None)
226
227    with context.progress_status(f"Unstaking {amount} tokens from {dest}'..."):
228        response = client.unstake(
229            key=keypair, amount=nano_amount, dest=resolved_dest
230        )  # TODO: is it right?
231
232    if response.is_success:
233        context.info(f"Unstaked {amount} tokens from {dest}")
234    else:
235        raise ChainTransactionError(response.error_message)  # type: ignore

Unstake amount from destination using key

@balance_app.command()
def run_faucet( ctx: typer.models.Context, key: str, num_processes: Optional[int] = None, num_executions: int = 1):
238@balance_app.command()
239def run_faucet(
240    ctx: Context,
241    key: str,
242    num_processes: Optional[int] = None,
243    num_executions: int = 1,
244):
245    context = make_custom_context(ctx)
246    use_testnet = ctx.obj.use_testnet
247
248    if not use_testnet:
249        context.error("Faucet only enabled on testnet")
250        raise typer.Exit(code=1)
251
252    resolved_key = context.load_key(key, None)
253
254    client = context.com_client()
255    for _ in range(num_executions):
256        with context.progress_status("Solving PoW..."):
257            solution = solve_for_difficulty_fast(
258                client,
259                resolved_key,
260                client.url,
261                num_processes=num_processes,
262            )
263        with context.progress_status("Sending solution to blockchain"):
264            params = {
265                "block_number": solution.block_number,
266                "nonce": solution.nonce,
267                "work": solution.seal,
268                "key": resolved_key.ss58_address,
269            }
270            client.compose_call(
271                "faucet",
272                params=params,
273                unsigned=True,
274                module="FaucetModule",
275                key=resolved_key.ss58_address,  # type: ignore
276            )
@balance_app.command()
def transfer_dao_funds( ctx: typer.models.Context, signer_key: str, amount: float, cid_hash: str, dest: str):
279@balance_app.command()
280def transfer_dao_funds(
281    ctx: Context,
282    signer_key: str,
283    amount: float,
284    cid_hash: str,
285    dest: str,
286):
287    context = make_custom_context(ctx)
288
289    if not re.match(IPFS_REGEX, cid_hash):
290        context.error(f"CID provided is invalid: {cid_hash}")
291        raise typer.Exit(code=1)
292
293    ipfs_prefix = "ipfs://"
294    cid = ipfs_prefix + cid_hash
295
296    nano_amount = to_nano(amount)
297    keypair = context.load_key(signer_key, None)
298    dest = context.resolve_key_ss58(dest, None)
299
300    client = context.com_client()
301    client.add_transfer_dao_treasury_proposal(keypair, cid, nano_amount, dest)
@balance_app.command()
def bridge(ctx: typer.models.Context, key: str, amount: float):
304@balance_app.command()
305def bridge(
306    ctx: Context,
307    key: str,
308    amount: float,
309):
310    context = make_custom_context(ctx)
311    client = context.com_client()
312
313    nano_amount = to_nano(amount)
314    keypair = context.load_key(key, None)
315
316    with context.progress_status(f"Bridging {amount} tokens..."):
317        try:
318            client.bridge(keypair, nano_amount)
319        except Exception as e:
320            context.error(f"Failed to bridge {amount} tokens: {e}")
321        else:
322            context.info(f"Bridged {amount}$j successfully")
@balance_app.command()
def bridge_withdraw(ctx: typer.models.Context, key: str, amount: float):
325@balance_app.command()
326def bridge_withdraw(
327    ctx: Context,
328    key: str,
329    amount: float,
330):
331    context = make_custom_context(ctx)
332    client = context.com_client()
333
334    nano_amount = to_nano(amount)
335    keypair = context.load_key(key, None)
336
337    with context.progress_status(f"Withdrawing {amount} tokens..."):
338        try:
339            client.bridge_withdraw(keypair, nano_amount)
340        except Exception as e:
341            context.error(f"Failed to withdraw {amount} tokens: {e}")
342        else:
343            context.info(f"Withdrew {amount}$j successfully")
@balance_app.command()
def bridged_balance(ctx: typer.models.Context, key: Optional[str] = None):
346@balance_app.command()
347def bridged_balance(
348    ctx: Context,
349    key: Optional[str] = None,
350):
351    context = make_custom_context(ctx)
352    client = context.com_client()
353    with context.progress_status("Getting bridged balance..."):
354        bridge_map = client.query_map(
355            "Bridged", params=[], extract_value=False
356        )["Bridged"]
357        bridge_map = cast(dict[str, int], bridge_map)
358    if key is None:
359        local_keys = local_key_addresses(context.password_manager)
360        local_bridge_map = {
361            address: from_nano(ammount)
362            for address, ammount in bridge_map.items()
363            if address in local_keys.values()
364        }
365    else:
366        key = cast(str, context.resolve_key_ss58(key, None))
367        bridged_amount = from_nano(bridge_map.get(key, 0))
368        local_bridge_map = {key: bridged_amount}
369
370    print_table_from_plain_dict(
371        local_bridge_map, ["Address", "Amount"], context.console
372    )