meshtensor-cli 9.18.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. meshtensor_cli/__init__.py +22 -0
  2. meshtensor_cli/cli.py +10742 -0
  3. meshtensor_cli/doc_generation_helper.py +4 -0
  4. meshtensor_cli/src/__init__.py +1085 -0
  5. meshtensor_cli/src/commands/__init__.py +0 -0
  6. meshtensor_cli/src/commands/axon/__init__.py +0 -0
  7. meshtensor_cli/src/commands/axon/axon.py +132 -0
  8. meshtensor_cli/src/commands/crowd/__init__.py +0 -0
  9. meshtensor_cli/src/commands/crowd/contribute.py +621 -0
  10. meshtensor_cli/src/commands/crowd/contributors.py +200 -0
  11. meshtensor_cli/src/commands/crowd/create.py +783 -0
  12. meshtensor_cli/src/commands/crowd/dissolve.py +219 -0
  13. meshtensor_cli/src/commands/crowd/refund.py +233 -0
  14. meshtensor_cli/src/commands/crowd/update.py +418 -0
  15. meshtensor_cli/src/commands/crowd/utils.py +124 -0
  16. meshtensor_cli/src/commands/crowd/view.py +991 -0
  17. meshtensor_cli/src/commands/governance/__init__.py +0 -0
  18. meshtensor_cli/src/commands/governance/governance.py +794 -0
  19. meshtensor_cli/src/commands/liquidity/__init__.py +0 -0
  20. meshtensor_cli/src/commands/liquidity/liquidity.py +699 -0
  21. meshtensor_cli/src/commands/liquidity/utils.py +202 -0
  22. meshtensor_cli/src/commands/proxy.py +700 -0
  23. meshtensor_cli/src/commands/stake/__init__.py +0 -0
  24. meshtensor_cli/src/commands/stake/add.py +799 -0
  25. meshtensor_cli/src/commands/stake/auto_staking.py +306 -0
  26. meshtensor_cli/src/commands/stake/children_hotkeys.py +865 -0
  27. meshtensor_cli/src/commands/stake/claim.py +770 -0
  28. meshtensor_cli/src/commands/stake/list.py +738 -0
  29. meshtensor_cli/src/commands/stake/move.py +1211 -0
  30. meshtensor_cli/src/commands/stake/remove.py +1466 -0
  31. meshtensor_cli/src/commands/stake/wizard.py +323 -0
  32. meshtensor_cli/src/commands/subnets/__init__.py +0 -0
  33. meshtensor_cli/src/commands/subnets/mechanisms.py +515 -0
  34. meshtensor_cli/src/commands/subnets/price.py +733 -0
  35. meshtensor_cli/src/commands/subnets/subnets.py +2908 -0
  36. meshtensor_cli/src/commands/sudo.py +1294 -0
  37. meshtensor_cli/src/commands/tc/__init__.py +0 -0
  38. meshtensor_cli/src/commands/tc/tc.py +190 -0
  39. meshtensor_cli/src/commands/treasury/__init__.py +0 -0
  40. meshtensor_cli/src/commands/treasury/treasury.py +194 -0
  41. meshtensor_cli/src/commands/view.py +354 -0
  42. meshtensor_cli/src/commands/wallets.py +2311 -0
  43. meshtensor_cli/src/commands/weights.py +467 -0
  44. meshtensor_cli/src/meshtensor/__init__.py +0 -0
  45. meshtensor_cli/src/meshtensor/balances.py +313 -0
  46. meshtensor_cli/src/meshtensor/chain_data.py +1263 -0
  47. meshtensor_cli/src/meshtensor/extrinsics/__init__.py +0 -0
  48. meshtensor_cli/src/meshtensor/extrinsics/mev_shield.py +174 -0
  49. meshtensor_cli/src/meshtensor/extrinsics/registration.py +1861 -0
  50. meshtensor_cli/src/meshtensor/extrinsics/root.py +550 -0
  51. meshtensor_cli/src/meshtensor/extrinsics/serving.py +255 -0
  52. meshtensor_cli/src/meshtensor/extrinsics/transfer.py +239 -0
  53. meshtensor_cli/src/meshtensor/meshtensor_interface.py +2598 -0
  54. meshtensor_cli/src/meshtensor/minigraph.py +254 -0
  55. meshtensor_cli/src/meshtensor/networking.py +12 -0
  56. meshtensor_cli/src/meshtensor/templates/main-filters.j2 +24 -0
  57. meshtensor_cli/src/meshtensor/templates/main-header.j2 +36 -0
  58. meshtensor_cli/src/meshtensor/templates/neuron-details.j2 +111 -0
  59. meshtensor_cli/src/meshtensor/templates/price-multi.j2 +113 -0
  60. meshtensor_cli/src/meshtensor/templates/price-single.j2 +99 -0
  61. meshtensor_cli/src/meshtensor/templates/subnet-details-header.j2 +49 -0
  62. meshtensor_cli/src/meshtensor/templates/subnet-details.j2 +32 -0
  63. meshtensor_cli/src/meshtensor/templates/subnet-metrics.j2 +57 -0
  64. meshtensor_cli/src/meshtensor/templates/subnets-table.j2 +28 -0
  65. meshtensor_cli/src/meshtensor/templates/table.j2 +267 -0
  66. meshtensor_cli/src/meshtensor/templates/view.css +1058 -0
  67. meshtensor_cli/src/meshtensor/templates/view.j2 +43 -0
  68. meshtensor_cli/src/meshtensor/templates/view.js +1053 -0
  69. meshtensor_cli/src/meshtensor/utils.py +2007 -0
  70. meshtensor_cli/version.py +23 -0
  71. meshtensor_cli-9.18.1.dist-info/METADATA +261 -0
  72. meshtensor_cli-9.18.1.dist-info/RECORD +74 -0
  73. meshtensor_cli-9.18.1.dist-info/WHEEL +4 -0
  74. meshtensor_cli-9.18.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,418 @@
1
+ import asyncio
2
+ import json
3
+ from typing import Optional, Union
4
+
5
+ from meshtensor_wallet import Wallet
6
+ from rich.prompt import IntPrompt, FloatPrompt
7
+ from rich.table import Table, Column, box
8
+
9
+ from meshtensor_cli.src import COLORS
10
+ from meshtensor_cli.src.meshtensor.balances import Balance
11
+ from meshtensor_cli.src.meshtensor.meshtensor_interface import MeshtensorInterface
12
+ from meshtensor_cli.src.meshtensor.utils import (
13
+ blocks_to_duration,
14
+ confirm_action,
15
+ console,
16
+ json_console,
17
+ print_error,
18
+ unlock_key,
19
+ print_extrinsic_id,
20
+ )
21
+ from meshtensor_cli.src.commands.crowd.view import show_crowdloan_details
22
+ from meshtensor_cli.src.commands.crowd.utils import get_constant
23
+
24
+
25
+ async def update_crowdloan(
26
+ meshtensor: MeshtensorInterface,
27
+ wallet: Wallet,
28
+ proxy: Optional[str],
29
+ crowdloan_id: int,
30
+ min_contribution: Optional[Balance] = None,
31
+ end: Optional[int] = None,
32
+ cap: Optional[Balance] = None,
33
+ wait_for_inclusion: bool = True,
34
+ wait_for_finalization: bool = False,
35
+ prompt: bool = True,
36
+ decline: bool = False,
37
+ quiet: bool = False,
38
+ json_output: bool = False,
39
+ ) -> tuple[bool, str]:
40
+ """Update parameters of a non-finalized crowdloan.
41
+
42
+ Args:
43
+ meshtensor: MeshtensorInterface object for chain interaction
44
+ wallet: Wallet object containing coldkey (must be creator)
45
+ proxy: Optional proxy to use for this extrinsic submissions
46
+ crowdloan_id: ID of the crowdloan to update
47
+ min_contribution: New minimum contribution in MESH (None to prompt)
48
+ end: New end block (None to prompt)
49
+ cap: New cap in MESH (None to prompt)
50
+ wait_for_inclusion: Wait for transaction inclusion
51
+ wait_for_finalization: Wait for transaction finalization
52
+ prompt: Whether to prompt for values
53
+ json_output: Whether to output JSON or human-readable
54
+
55
+ Returns:
56
+ tuple[bool, str]: Success status and message
57
+ """
58
+
59
+ block_hash = await meshtensor.substrate.get_chain_head()
60
+ crowdloan, current_block = await asyncio.gather(
61
+ meshtensor.get_single_crowdloan(crowdloan_id, block_hash=block_hash),
62
+ meshtensor.substrate.get_block_number(block_hash=block_hash),
63
+ )
64
+
65
+ runtime = await meshtensor.substrate.init_runtime(block_hash=block_hash)
66
+ absolute_min_meshlet, min_duration, max_duration = await asyncio.gather(
67
+ get_constant(meshtensor, "AbsoluteMinimumContribution", runtime=runtime),
68
+ get_constant(meshtensor, "MinimumBlockDuration", runtime=runtime),
69
+ get_constant(meshtensor, "MaximumBlockDuration", runtime=runtime),
70
+ )
71
+ absolute_min = Balance.from_meshlet(absolute_min_meshlet)
72
+
73
+ if not crowdloan:
74
+ error_msg = f"Crowdloan #{crowdloan_id} not found."
75
+ if json_output:
76
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
77
+ else:
78
+ print_error(f"[red]{error_msg}[/red]")
79
+ return False, error_msg
80
+
81
+ if crowdloan.finalized:
82
+ error_msg = (
83
+ f"Crowdloan #{crowdloan_id} is already finalized and cannot be updated."
84
+ )
85
+ if json_output:
86
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
87
+ else:
88
+ print_error(f"[red]{error_msg}[/red]")
89
+ return False, f"Crowdloan #{crowdloan_id} is already finalized."
90
+
91
+ creator_address = wallet.coldkeypub.ss58_address
92
+ if creator_address != crowdloan.creator:
93
+ error_msg = "Only the creator can update this crowdloan."
94
+ if json_output:
95
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
96
+ else:
97
+ print_error(
98
+ f"[red]Only the creator can update this crowdloan.[/red]\n"
99
+ f"Creator: [blue]{crowdloan.creator}[/blue]\n"
100
+ f"Your address: [blue]{creator_address}[/blue]"
101
+ )
102
+ return False, error_msg
103
+
104
+ await show_crowdloan_details(
105
+ meshtensor=meshtensor,
106
+ crowdloan_id=crowdloan_id,
107
+ wallet=wallet,
108
+ verbose=False,
109
+ crowdloan=crowdloan,
110
+ current_block=current_block,
111
+ )
112
+
113
+ if all(x is None for x in [min_contribution, end, cap]) and prompt:
114
+ console.print(
115
+ f"\n[bold cyan]What would you like to update for Crowdloan #{crowdloan_id}?[/bold cyan]\n"
116
+ )
117
+ time_left = blocks_to_duration(crowdloan.end - current_block)
118
+ choice = IntPrompt.ask(
119
+ f"[cyan][1][/cyan] Minimum Contribution (current: [yellow]{crowdloan.min_contribution}[/yellow])\n"
120
+ f"[cyan][2][/cyan] End Block (current: [yellow]block {crowdloan.end:,}[/yellow], {time_left} remaining)\n"
121
+ f"[cyan][3][/cyan] Cap (current: [yellow]{crowdloan.cap}[/yellow])\n"
122
+ f"[cyan][4][/cyan] Cancel\n\n"
123
+ f"Enter your choice",
124
+ choices=["1", "2", "3", "4"],
125
+ default=4,
126
+ )
127
+
128
+ if choice == 4:
129
+ if json_output:
130
+ json_console.print(
131
+ json.dumps({"success": False, "error": "Update cancelled by user."})
132
+ )
133
+ else:
134
+ console.print("[yellow]Update cancelled.[/yellow]")
135
+ return False, "Update cancelled by user."
136
+
137
+ if choice == 1:
138
+ console.print(
139
+ f"\n[cyan]Update Minimum Contribution[/cyan]"
140
+ f"\n • Current: [yellow]{crowdloan.min_contribution}[/yellow]"
141
+ f"\n • Absolute minimum: [dim]{absolute_min}[/dim]\n"
142
+ )
143
+
144
+ while True:
145
+ new_value = FloatPrompt.ask(
146
+ "Enter new minimum contribution (MESH)",
147
+ default=float(crowdloan.min_contribution.tao),
148
+ )
149
+ candidate = Balance.from_tao(new_value)
150
+ if candidate.meshlet < absolute_min.meshlet:
151
+ print_error(
152
+ f"[red]Minimum contribution must be at least {absolute_min}. Try again.[/red]"
153
+ )
154
+ continue
155
+ min_contribution = candidate
156
+ break
157
+
158
+ elif choice == 2:
159
+ min_end_block = current_block + min_duration
160
+ max_end_block = current_block + max_duration
161
+ duration_remaining = blocks_to_duration(crowdloan.end - current_block)
162
+ console.print(
163
+ f"\n[cyan]Update End Block[/cyan]"
164
+ f"\n • Current: [yellow]block {crowdloan.end:,}[/yellow] ({duration_remaining} remaining)"
165
+ f"\n • Current block: [dim]{current_block:,}[/dim]"
166
+ f"\n • Valid range: [dim]{min_end_block:,} - {max_end_block:,}[/dim]"
167
+ f"\n • Duration range: [dim]{blocks_to_duration(min_duration)} - {blocks_to_duration(max_duration)}[/dim]\n"
168
+ )
169
+
170
+ while True:
171
+ candidate_end = IntPrompt.ask(
172
+ "Enter new end block",
173
+ default=crowdloan.end,
174
+ )
175
+
176
+ if candidate_end <= current_block:
177
+ print_error(
178
+ f"[red]End block must be after current block ({current_block:,}). Try again.[/red]"
179
+ )
180
+ continue
181
+
182
+ duration = candidate_end - current_block
183
+ if duration < min_duration:
184
+ duration_range = f"[dim]{min_end_block} - {blocks_to_duration(min_duration)}[/dim]"
185
+ print_error(
186
+ f"[red]Duration is too short. Minimum: {duration_range}. Try again.[/red]"
187
+ )
188
+ continue
189
+ if duration > max_duration:
190
+ duration_range = f"[dim]{max_end_block} - {blocks_to_duration(max_duration)}[/dim]"
191
+ print_error(
192
+ f"[red]Duration is too long. Maximum: {duration_range}. Try again.[/red]"
193
+ )
194
+ continue
195
+
196
+ end = candidate_end
197
+ break
198
+
199
+ elif choice == 3:
200
+ console.print(
201
+ f"\n[cyan]Update Cap[/cyan]"
202
+ f"\n • Current cap: [yellow]{crowdloan.cap}[/yellow]"
203
+ f"\n • Already raised: [green]{crowdloan.raised}[/green]"
204
+ f"\n • Remaining to raise: [dim]{(crowdloan.cap.meshlet - crowdloan.raised.meshlet) / 1e9:.9f} MESH[/dim]"
205
+ f"\n • New cap must be >= raised amount\n"
206
+ )
207
+
208
+ while True:
209
+ new_value = FloatPrompt.ask(
210
+ "Enter new cap (MESH)",
211
+ default=float(crowdloan.cap.tao),
212
+ )
213
+ candidate_cap = Balance.from_tao(new_value)
214
+ if candidate_cap.meshlet < crowdloan.raised.meshlet:
215
+ print_error(
216
+ f"[red]Cap must be >= amount already raised ({crowdloan.raised}). Try again.[/red]"
217
+ )
218
+ continue
219
+ cap = candidate_cap
220
+ break
221
+
222
+ value: Optional[Union[Balance, int]] = None
223
+ call_function: Optional[str] = None
224
+ param_name: Optional[str] = None
225
+ update_type: Optional[str] = None
226
+
227
+ if min_contribution is not None:
228
+ value = min_contribution
229
+ call_function = "update_min_contribution"
230
+ param_name = "new_min_contribution"
231
+ update_type = "Minimum Contribution"
232
+ elif cap is not None:
233
+ value = cap
234
+ call_function = "update_cap"
235
+ param_name = "new_cap"
236
+ update_type = "Cap"
237
+ elif end is not None:
238
+ value = end
239
+ call_function = "update_end"
240
+ param_name = "new_end"
241
+ update_type = "End Block"
242
+
243
+ if call_function is None or value is None or param_name is None:
244
+ error_msg = "No update parameter specified."
245
+ if json_output:
246
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
247
+ else:
248
+ print_error(f"[red]{error_msg}[/red]")
249
+ return False, error_msg
250
+
251
+ # Validation
252
+ if call_function == "update_min_contribution":
253
+ if value.meshlet < absolute_min.meshlet:
254
+ error_msg = f"Minimum contribution must be at least {absolute_min}."
255
+ if json_output:
256
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
257
+ else:
258
+ print_error(
259
+ f"[red]Minimum contribution ({value}) must be at least {absolute_min}.[/red]"
260
+ )
261
+ return False, error_msg
262
+
263
+ elif call_function == "update_end":
264
+ if value <= current_block:
265
+ error_msg = "End block must be in the future."
266
+ if json_output:
267
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
268
+ else:
269
+ print_error(
270
+ f"[red]End block ({value:,}) must be after current block ({current_block:,}).[/red]"
271
+ )
272
+ return False, error_msg
273
+
274
+ block_duration = value - current_block
275
+ if block_duration < min_duration:
276
+ error_msg = "Block duration too short."
277
+ if json_output:
278
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
279
+ else:
280
+ print_error(
281
+ f"[red]Duration ({blocks_to_duration(block_duration)}) is too short. "
282
+ f"Minimum: [dim]{min_end_block} - {blocks_to_duration(min_duration)}[/dim][/red]"
283
+ )
284
+ return False, error_msg
285
+
286
+ if block_duration > max_duration:
287
+ error_msg = "Block duration too long."
288
+ if json_output:
289
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
290
+ else:
291
+ print_error(
292
+ f"[red]Duration ({blocks_to_duration(block_duration)}) is too long. "
293
+ f"Maximum: [dim]{max_end_block} - {blocks_to_duration(max_duration)}[/dim][/red]"
294
+ )
295
+ return False, error_msg
296
+
297
+ elif call_function == "update_cap":
298
+ if value < crowdloan.raised:
299
+ error_msg = "Cap must be >= raised amount."
300
+ if json_output:
301
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
302
+ else:
303
+ print_error(
304
+ f"[red]New cap ({value}) must be at least the amount already raised ({crowdloan.raised}).[/red]"
305
+ )
306
+ return False, error_msg
307
+
308
+ # Update summary
309
+ table = Table(
310
+ Column("[bold white]Parameter", style=COLORS.G.SUBHEAD),
311
+ Column("[bold white]Current Value", style=COLORS.G.TEMPO),
312
+ Column("[bold white]New Value", style=COLORS.G.TEMPO),
313
+ title="\n[bold cyan]Update Summary[/bold cyan]",
314
+ show_footer=False,
315
+ width=None,
316
+ pad_edge=False,
317
+ box=box.SIMPLE,
318
+ show_edge=True,
319
+ border_style="bright_black",
320
+ )
321
+
322
+ if call_function == "update_min_contribution":
323
+ table.add_row(
324
+ "Minimum Contribution", str(crowdloan.min_contribution), str(value)
325
+ )
326
+ elif call_function == "update_end":
327
+ table.add_row(
328
+ "End Block",
329
+ f"{crowdloan.end:,} ({blocks_to_duration(crowdloan.end - current_block)} remaining)",
330
+ f"{value:,} ({blocks_to_duration(value - current_block)} remaining)",
331
+ )
332
+ elif call_function == "update_cap":
333
+ table.add_row("Cap", str(crowdloan.cap), str(value))
334
+
335
+ console.print(table)
336
+
337
+ if prompt and not confirm_action(
338
+ f"\n[bold]Proceed with updating {update_type}?[/bold]",
339
+ default=False,
340
+ decline=decline,
341
+ quiet=quiet,
342
+ ):
343
+ if json_output:
344
+ json_console.print(
345
+ json.dumps({"success": False, "error": "Update cancelled by user."})
346
+ )
347
+ else:
348
+ console.print("[yellow]Update cancelled.[/yellow]")
349
+ return False, "Update cancelled by user."
350
+
351
+ unlock_status = unlock_key(wallet)
352
+ if not unlock_status.success:
353
+ if json_output:
354
+ json_console.print(
355
+ json.dumps({"success": False, "error": unlock_status.message})
356
+ )
357
+ else:
358
+ print_error(f"[red]{unlock_status.message}[/red]")
359
+ return False, unlock_status.message
360
+
361
+ if call_function != "update_end":
362
+ value = value.meshlet
363
+
364
+ with console.status(
365
+ ":satellite: Submitting update transaction...", spinner="aesthetic"
366
+ ):
367
+ call = await meshtensor.substrate.compose_call(
368
+ call_module="Crowdloan",
369
+ call_function=call_function,
370
+ call_params={"crowdloan_id": crowdloan_id, param_name: value},
371
+ )
372
+
373
+ (
374
+ success,
375
+ error_message,
376
+ extrinsic_receipt,
377
+ ) = await meshtensor.sign_and_send_extrinsic(
378
+ call=call,
379
+ wallet=wallet,
380
+ proxy=proxy,
381
+ wait_for_inclusion=wait_for_inclusion,
382
+ wait_for_finalization=wait_for_finalization,
383
+ )
384
+
385
+ if not success:
386
+ if json_output:
387
+ json_console.print(
388
+ json.dumps(
389
+ {
390
+ "success": False,
391
+ "error": error_message or f"Failed to update {update_type}.",
392
+ }
393
+ )
394
+ )
395
+ else:
396
+ print_error(f"[red]Failed to update {update_type}.[/red]\n{error_message}")
397
+ return False, error_message
398
+
399
+ if json_output:
400
+ extrinsic_id = await extrinsic_receipt.get_extrinsic_identifier()
401
+ output_dict = {
402
+ "success": True,
403
+ "error": None,
404
+ "extrinsic_identifier": extrinsic_id,
405
+ "data": {
406
+ "crowdloan_id": crowdloan_id,
407
+ "update_type": update_type,
408
+ },
409
+ }
410
+ json_console.print(json.dumps(output_dict))
411
+ else:
412
+ console.print(
413
+ f"[green]{update_type} updated successfully![/green]\n"
414
+ f"Crowdloan #{crowdloan_id} has been updated."
415
+ )
416
+ await print_extrinsic_id(extrinsic_receipt)
417
+
418
+ return True, f"{update_type} updated successfully."
@@ -0,0 +1,124 @@
1
+ import json
2
+ from typing import Optional
3
+
4
+ from async_substrate_interface.types import Runtime
5
+ from rich.prompt import Prompt
6
+
7
+ from meshtensor_cli.src.meshtensor.meshtensor_interface import MeshtensorInterface
8
+ from meshtensor_cli.src.meshtensor.utils import console, json_console, print_error
9
+
10
+
11
+ async def prompt_custom_call_params(
12
+ meshtensor: MeshtensorInterface,
13
+ json_output: bool = False,
14
+ ) -> tuple[bool, Optional[str], Optional[str], Optional[str], Optional[str]]:
15
+ """
16
+ Prompt user for custom call parameters (pallet, method, and JSON args)
17
+ and validate that the call can be composed.
18
+
19
+ Args:
20
+ meshtensor: MeshtensorInterface instance for call validation
21
+ json_output: Whether to output errors as JSON
22
+
23
+ Returns:
24
+ Tuple of (success, pallet_name, method_name, args_json, error_msg)
25
+ On success: (True, pallet, method, args, None)
26
+ On failure: (False, None, None, None, error_msg)
27
+ """
28
+ if not json_output:
29
+ console.print(
30
+ "\n[bold cyan]Custom Call Parameters[/bold cyan]\n"
31
+ "[dim]You'll need to provide a pallet (module) name, method name, and optional JSON arguments.\n\n"
32
+ "[yellow]Examples:[/yellow]\n"
33
+ " • Pallet: [cyan]MeshtensorModule[/cyan], [cyan]Balances[/cyan], [cyan]System[/cyan]\n"
34
+ " • Method: [cyan]transfer_allow_death[/cyan], [cyan]transfer_keep_alive[/cyan], [cyan]transfer_all[/cyan]\n"
35
+ ' • Args: [cyan]{"dest": "5D...", "value": 1000000000}[/cyan] or [cyan]{}[/cyan] for empty\n'
36
+ )
37
+
38
+ pallet = Prompt.ask("Enter pallet name")
39
+ if not pallet.strip():
40
+ if json_output:
41
+ json_console.print(
42
+ json.dumps({"success": False, "error": "Pallet name cannot be empty."})
43
+ )
44
+ else:
45
+ print_error("[red]Pallet name cannot be empty.[/red]")
46
+ return await prompt_custom_call_params(meshtensor, json_output)
47
+
48
+ method = Prompt.ask("Enter method name")
49
+ if not method.strip():
50
+ if json_output:
51
+ json_console.print(
52
+ json.dumps({"success": False, "error": "Method name cannot be empty."})
53
+ )
54
+ else:
55
+ print_error("[red]Method name cannot be empty.[/red]")
56
+ return await prompt_custom_call_params(meshtensor, json_output)
57
+
58
+ args_input = Prompt.ask(
59
+ "Enter custom call arguments as JSON [dim](or press Enter for empty: {})[/dim]",
60
+ default="{}",
61
+ )
62
+
63
+ try:
64
+ call_params = json.loads(args_input)
65
+ except json.JSONDecodeError as e:
66
+ error_msg = f"Invalid JSON: {e}"
67
+ if json_output:
68
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
69
+ else:
70
+ print_error(f"[red]{error_msg}[/red]")
71
+ print_error(
72
+ '[yellow]Please try again. Example: {"param1": "value", "param2": 123}[/yellow]'
73
+ )
74
+ return await prompt_custom_call_params(meshtensor, json_output)
75
+
76
+ call, error_msg = await meshtensor.compose_custom_crowdloan_call(
77
+ pallet_name=pallet,
78
+ method_name=method,
79
+ call_params=call_params,
80
+ )
81
+ if call is None:
82
+ if json_output:
83
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
84
+ else:
85
+ print_error(f"[red]Failed to compose call: {error_msg}[/red]")
86
+ console.print(
87
+ "[yellow]Please check:\n"
88
+ " • Pallet name exists in runtime\n"
89
+ " • Method name exists in the pallet\n"
90
+ " • Arguments match the method's expected parameters[/yellow]\n"
91
+ )
92
+ return await prompt_custom_call_params(meshtensor, json_output)
93
+
94
+ return True, pallet, method, args_input, None
95
+
96
+
97
+ async def get_constant(
98
+ meshtensor: MeshtensorInterface,
99
+ constant_name: str,
100
+ runtime: Optional[Runtime] = None,
101
+ block_hash: Optional[str] = None,
102
+ ) -> int:
103
+ """
104
+ Get a constant from the Crowdloan pallet.
105
+
106
+ Args:
107
+ meshtensor: MeshtensorInterface object for chain interaction
108
+ constant_name: Name of the constant to get
109
+ runtime: Runtime object
110
+ block_hash: Block hash
111
+
112
+ Returns:
113
+ The value of the constant
114
+ """
115
+
116
+ runtime = runtime or await meshtensor.substrate.init_runtime(block_hash=block_hash)
117
+
118
+ result = await meshtensor.substrate.get_constant(
119
+ module_name="Crowdloan",
120
+ constant_name=constant_name,
121
+ block_hash=block_hash,
122
+ runtime=runtime,
123
+ )
124
+ return getattr(result, "value", result)