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,621 @@
1
+ import json
2
+ from typing import Optional
3
+
4
+ from async_substrate_interface.utils.cache import asyncio
5
+ from meshtensor_wallet import Wallet
6
+ from rich import box
7
+ from rich.prompt import FloatPrompt
8
+ from rich.table import Column, Table
9
+
10
+ from meshtensor_cli.src import COLORS
11
+ from meshtensor_cli.src.meshtensor.balances import Balance
12
+ from meshtensor_cli.src.meshtensor.meshtensor_interface import MeshtensorInterface
13
+ from meshtensor_cli.src.meshtensor.utils import (
14
+ confirm_action,
15
+ console,
16
+ json_console,
17
+ print_error,
18
+ print_extrinsic_id,
19
+ print_success,
20
+ unlock_key,
21
+ )
22
+ from meshtensor_cli.src.commands.crowd.view import show_crowdloan_details
23
+ from meshtensor_cli.src.meshtensor.chain_data import CrowdloanData
24
+
25
+
26
+ def validate_for_contribution(
27
+ crowdloan: CrowdloanData,
28
+ crowdloan_id: int,
29
+ current_block: int,
30
+ ) -> tuple[bool, Optional[str]]:
31
+ """Validate if a crowdloan can accept contributions.
32
+
33
+ Args:
34
+ crowdloan: The crowdloan data object
35
+ crowdloan_id: The ID of the crowdloan
36
+ current_block: Current blockchain block number
37
+
38
+ Returns:
39
+ tuple[bool, Optional[str]]: (is_valid, error_message)
40
+ - If valid: (True, None)
41
+ - If invalid: (False, error_message)
42
+ """
43
+ if crowdloan.finalized:
44
+ return False, f"Crowdloan #{crowdloan_id} is already finalized."
45
+
46
+ if current_block >= crowdloan.end:
47
+ return False, f"Crowdloan #{crowdloan_id} has ended."
48
+
49
+ if crowdloan.raised >= crowdloan.cap:
50
+ return False, f"Crowdloan #{crowdloan_id} has reached its cap."
51
+
52
+ return True, None
53
+
54
+
55
+ async def contribute_to_crowdloan(
56
+ meshtensor: MeshtensorInterface,
57
+ wallet: Wallet,
58
+ proxy: Optional[str],
59
+ crowdloan_id: int,
60
+ amount: Optional[float],
61
+ prompt: bool,
62
+ decline: bool = False,
63
+ quiet: bool = False,
64
+ wait_for_inclusion: bool = True,
65
+ wait_for_finalization: bool = False,
66
+ json_output: bool = False,
67
+ ) -> tuple[bool, str]:
68
+ """Contribute MESH to an active crowdloan.
69
+
70
+ Args:
71
+ meshtensor: MeshtensorInterface object for chain interaction
72
+ wallet: Wallet object containing coldkey for contribution
73
+ proxy: Optional proxy to use for this extrinsic
74
+ crowdloan_id: ID of the crowdloan to contribute to
75
+ amount: Amount to contribute in MESH (None to prompt)
76
+ prompt: Whether to prompt for confirmation
77
+ wait_for_inclusion: Wait for transaction inclusion
78
+ wait_for_finalization: Wait for transaction finalization
79
+ json_output: Whether to output JSON output or human-readable text
80
+
81
+ Returns:
82
+ tuple[bool, str]: Success status and message
83
+ """
84
+ crowdloan, current_block = await asyncio.gather(
85
+ meshtensor.get_single_crowdloan(crowdloan_id),
86
+ meshtensor.substrate.get_block_number(None),
87
+ )
88
+ if not crowdloan:
89
+ error_msg = f"Crowdloan #{crowdloan_id} not found."
90
+ if json_output:
91
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
92
+ else:
93
+ print_error(error_msg)
94
+ return False, error_msg
95
+
96
+ is_valid, error_message = validate_for_contribution(
97
+ crowdloan, crowdloan_id, current_block
98
+ )
99
+ if not is_valid:
100
+ if json_output:
101
+ json_console.print(json.dumps({"success": False, "error": error_message}))
102
+ else:
103
+ print_error(error_message)
104
+ return False, error_message
105
+
106
+ contributor_address = proxy or wallet.coldkeypub.ss58_address
107
+ current_contribution, user_balance, _ = await asyncio.gather(
108
+ meshtensor.get_crowdloan_contribution(crowdloan_id, contributor_address),
109
+ meshtensor.get_balance(contributor_address),
110
+ show_crowdloan_details(
111
+ meshtensor=meshtensor,
112
+ crowdloan_id=crowdloan_id,
113
+ wallet=wallet,
114
+ verbose=False,
115
+ crowdloan=crowdloan,
116
+ current_block=current_block,
117
+ ),
118
+ )
119
+
120
+ if amount is None:
121
+ left_to_raise = crowdloan.cap - crowdloan.raised
122
+ max_contribution = min(user_balance, left_to_raise)
123
+
124
+ console.print(
125
+ f"\n[bold cyan]Contribution Options:[/bold cyan]\n"
126
+ f" Your Balance: {user_balance}\n"
127
+ f" Maximum You Can Contribute: [{COLORS.S.AMOUNT}]{max_contribution}[/{COLORS.S.AMOUNT}]"
128
+ )
129
+ amount = FloatPrompt.ask(
130
+ f"\nEnter contribution amount in {Balance.unit}",
131
+ default=float(crowdloan.min_contribution.tao),
132
+ )
133
+
134
+ contribution_amount = Balance.from_tao(amount)
135
+ if contribution_amount < crowdloan.min_contribution:
136
+ error_msg = f"Contribution amount ({contribution_amount}) is below minimum ({crowdloan.min_contribution})."
137
+ if json_output:
138
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
139
+ else:
140
+ print_error(error_msg)
141
+ return False, "Contribution below minimum requirement."
142
+
143
+ if contribution_amount > user_balance:
144
+ error_msg = f"Insufficient balance. You have {user_balance} but trying to contribute {contribution_amount}."
145
+ if json_output:
146
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
147
+ else:
148
+ print_error(error_msg)
149
+ return False, "Insufficient balance."
150
+
151
+ # Auto-adjustment
152
+ left_to_raise = crowdloan.cap - crowdloan.raised
153
+ actual_contribution = contribution_amount
154
+ will_be_adjusted = False
155
+
156
+ if contribution_amount > left_to_raise:
157
+ actual_contribution = left_to_raise
158
+ will_be_adjusted = True
159
+
160
+ # Extrinsic fee
161
+ call = await meshtensor.substrate.compose_call(
162
+ call_module="Crowdloan",
163
+ call_function="contribute",
164
+ call_params={
165
+ "crowdloan_id": crowdloan_id,
166
+ "amount": contribution_amount.meshlet,
167
+ },
168
+ )
169
+ extrinsic_fee = await meshtensor.get_extrinsic_fee(
170
+ call, wallet.coldkeypub, proxy=proxy
171
+ )
172
+ if proxy:
173
+ updated_balance = user_balance - actual_contribution
174
+ else:
175
+ updated_balance = user_balance - actual_contribution - extrinsic_fee
176
+
177
+ table = Table(
178
+ Column("[bold white]Field", style=COLORS.G.SUBHEAD),
179
+ Column("[bold white]Value", style=COLORS.G.TEMPO),
180
+ title="\n[bold cyan]Contribution Summary[/bold cyan]",
181
+ show_footer=False,
182
+ width=None,
183
+ pad_edge=False,
184
+ box=box.SIMPLE,
185
+ show_edge=True,
186
+ border_style="bright_black",
187
+ )
188
+
189
+ table.add_row("Crowdloan ID", str(crowdloan_id))
190
+ table.add_row("Creator", crowdloan.creator)
191
+ table.add_row(
192
+ "Current Progress",
193
+ f"{crowdloan.raised} / {crowdloan.cap} ({(crowdloan.raised.tao / crowdloan.cap.tao * 100):.2f}%)",
194
+ )
195
+
196
+ if current_contribution:
197
+ table.add_row("Your Current Contribution", str(current_contribution))
198
+ table.add_row("New Contribution", str(actual_contribution))
199
+ table.add_row(
200
+ "Total After Contribution",
201
+ f"[{COLORS.S.AMOUNT}]{Balance.from_meshlet(current_contribution.meshlet + actual_contribution.meshlet)}[/{COLORS.S.AMOUNT}]",
202
+ )
203
+ else:
204
+ table.add_row(
205
+ "Contribution Amount",
206
+ f"[{COLORS.S.AMOUNT}]{actual_contribution}[/{COLORS.S.AMOUNT}]",
207
+ )
208
+
209
+ if will_be_adjusted:
210
+ table.add_row(
211
+ "Note",
212
+ f"[yellow]Amount adjusted from {contribution_amount} to {actual_contribution} (cap limit)[/yellow]",
213
+ )
214
+
215
+ table.add_row("Transaction Fee", str(extrinsic_fee))
216
+ table.add_row(
217
+ "Balance After",
218
+ f"[blue]{user_balance}[/blue] → [{COLORS.S.AMOUNT}]{updated_balance}[/{COLORS.S.AMOUNT}]",
219
+ )
220
+ console.print(table)
221
+
222
+ if will_be_adjusted:
223
+ console.print(
224
+ f"\n[yellow] Your contribution will be automatically adjusted to {actual_contribution} "
225
+ f"because the crowdloan only needs {left_to_raise} more to reach its cap.[/yellow]"
226
+ )
227
+
228
+ if prompt:
229
+ if not confirm_action(
230
+ "\nProceed with contribution?", decline=decline, quiet=quiet
231
+ ):
232
+ if json_output:
233
+ json_console.print(
234
+ json.dumps(
235
+ {"success": False, "error": "Contribution cancelled by user."}
236
+ )
237
+ )
238
+ else:
239
+ console.print("[yellow]Contribution cancelled.[/yellow]")
240
+ return False, "Contribution cancelled by user."
241
+
242
+ unlock_status = unlock_key(wallet)
243
+ if not unlock_status.success:
244
+ if json_output:
245
+ json_console.print(
246
+ json.dumps({"success": False, "error": unlock_status.message})
247
+ )
248
+ else:
249
+ print_error(unlock_status.message)
250
+ return False, unlock_status.message
251
+
252
+ with console.status(f"\n:satellite: Contributing to crowdloan #{crowdloan_id}..."):
253
+ (
254
+ success,
255
+ error_message,
256
+ extrinsic_receipt,
257
+ ) = await meshtensor.sign_and_send_extrinsic(
258
+ call=call,
259
+ wallet=wallet,
260
+ proxy=proxy,
261
+ wait_for_inclusion=wait_for_inclusion,
262
+ wait_for_finalization=wait_for_finalization,
263
+ )
264
+
265
+ if not success:
266
+ if json_output:
267
+ json_console.print(
268
+ json.dumps(
269
+ {
270
+ "success": False,
271
+ "error": error_message or "Failed to contribute.",
272
+ }
273
+ )
274
+ )
275
+ else:
276
+ print_error(f"Failed to contribute: {error_message}")
277
+ return False, error_message or "Failed to contribute."
278
+
279
+ new_balance, new_contribution, updated_crowdloan = await asyncio.gather(
280
+ meshtensor.get_balance(contributor_address),
281
+ meshtensor.get_crowdloan_contribution(crowdloan_id, contributor_address),
282
+ meshtensor.get_single_crowdloan(crowdloan_id),
283
+ )
284
+
285
+ if json_output:
286
+ extrinsic_id = await extrinsic_receipt.get_extrinsic_identifier()
287
+ output_dict = {
288
+ "success": True,
289
+ "error": None,
290
+ "extrinsic_identifier": extrinsic_id,
291
+ "data": {
292
+ "crowdloan_id": crowdloan_id,
293
+ "contributor": contributor_address,
294
+ "contribution_amount": actual_contribution.tao,
295
+ "previous_contribution": current_contribution.tao
296
+ if current_contribution
297
+ else 0.0,
298
+ "total_contribution": new_contribution.tao if new_contribution else 0.0,
299
+ "balance": {
300
+ "before": user_balance.tao,
301
+ "after": new_balance.tao,
302
+ "fee": extrinsic_fee.tao,
303
+ },
304
+ "crowdloan": {
305
+ "raised_before": crowdloan.raised.tao,
306
+ "raised_after": updated_crowdloan.raised.tao
307
+ if updated_crowdloan
308
+ else crowdloan.raised.tao,
309
+ "cap": crowdloan.cap.tao,
310
+ "percentage": (
311
+ updated_crowdloan.raised.tao / updated_crowdloan.cap.tao * 100
312
+ )
313
+ if updated_crowdloan
314
+ else 0.0,
315
+ },
316
+ "adjusted": will_be_adjusted,
317
+ "cap_reached": updated_crowdloan.raised >= updated_crowdloan.cap
318
+ if updated_crowdloan
319
+ else False,
320
+ },
321
+ }
322
+ json_console.print(json.dumps(output_dict))
323
+ else:
324
+ console.print(
325
+ f"\n[dark_sea_green3]Successfully contributed to crowdloan #{crowdloan_id}![/dark_sea_green3]"
326
+ )
327
+
328
+ console.print(
329
+ f"Balance:\n [blue]{user_balance}[/blue] → "
330
+ f"[{COLORS.S.AMOUNT}]{new_balance}[/{COLORS.S.AMOUNT}]"
331
+ )
332
+
333
+ if new_contribution:
334
+ if current_contribution:
335
+ console.print(
336
+ f"Your Contribution:\n [blue]{current_contribution}[/blue] → "
337
+ f"[{COLORS.S.AMOUNT}]{new_contribution}[/{COLORS.S.AMOUNT}]"
338
+ )
339
+ else:
340
+ console.print(
341
+ f"Your Contribution: [{COLORS.S.AMOUNT}]{new_contribution}[/{COLORS.S.AMOUNT}]"
342
+ )
343
+
344
+ if updated_crowdloan:
345
+ console.print(
346
+ f"Crowdloan Progress:\n [blue]{crowdloan.raised}[/blue] → "
347
+ f"[{COLORS.S.AMOUNT}]{updated_crowdloan.raised}[/{COLORS.S.AMOUNT}] / {updated_crowdloan.cap}"
348
+ )
349
+
350
+ if updated_crowdloan.raised >= updated_crowdloan.cap:
351
+ console.print(
352
+ "\n[bold green]🎉 Crowdloan has reached its funding cap![/bold green]"
353
+ )
354
+
355
+ await print_extrinsic_id(extrinsic_receipt)
356
+
357
+ return True, "Successfully contributed to crowdloan."
358
+
359
+
360
+ async def withdraw_from_crowdloan(
361
+ meshtensor: MeshtensorInterface,
362
+ wallet: Wallet,
363
+ proxy: Optional[str],
364
+ crowdloan_id: int,
365
+ wait_for_inclusion: bool,
366
+ wait_for_finalization: bool,
367
+ prompt: bool,
368
+ decline: bool = False,
369
+ quiet: bool = False,
370
+ json_output: bool = False,
371
+ ) -> tuple[bool, str]:
372
+ """
373
+ Withdraw contributions from a non-finalized crowdloan.
374
+
375
+ Non-creators can withdraw their full contribution.
376
+ Creators can only withdraw amounts above their initial deposit.
377
+
378
+ Args:
379
+ meshtensor: MeshtensorInterface instance for blockchain interaction
380
+ wallet: Wallet instance containing the user's keys
381
+ proxy: Optional proxy to use with this extrinsic submission.
382
+ crowdloan_id: The ID of the crowdloan to withdraw from
383
+ wait_for_inclusion: Whether to wait for transaction inclusion
384
+ wait_for_finalization: Whether to wait for transaction finalization
385
+ prompt: Whether to prompt for user confirmation
386
+ json_output: Whether to output the results as JSON or human-readable
387
+
388
+ Returns:
389
+ Tuple of (success, message) indicating the result
390
+ """
391
+
392
+ crowdloan, current_block = await asyncio.gather(
393
+ meshtensor.get_single_crowdloan(crowdloan_id),
394
+ meshtensor.substrate.get_block_number(None),
395
+ )
396
+
397
+ if not crowdloan:
398
+ error_msg = f"Crowdloan #{crowdloan_id} does not exist."
399
+ if json_output:
400
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
401
+ else:
402
+ print_error(error_msg)
403
+ return False, error_msg
404
+
405
+ if crowdloan.finalized:
406
+ error_msg = f"Crowdloan #{crowdloan_id} is already finalized. Withdrawals are not allowed."
407
+ if json_output:
408
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
409
+ else:
410
+ print_error(error_msg)
411
+ return False, "Cannot withdraw from finalized crowdloan."
412
+
413
+ contributor_address = proxy or wallet.coldkeypub.ss58_address
414
+ user_contribution, user_balance = await asyncio.gather(
415
+ meshtensor.get_crowdloan_contribution(
416
+ crowdloan_id,
417
+ contributor_address,
418
+ ),
419
+ meshtensor.get_balance(contributor_address),
420
+ )
421
+
422
+ if user_contribution == Balance.from_tao(0):
423
+ error_msg = (
424
+ f"You have no contribution to withdraw from crowdloan #{crowdloan_id}."
425
+ )
426
+ if json_output:
427
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
428
+ else:
429
+ print_error(error_msg)
430
+ return False, "No contribution to withdraw."
431
+
432
+ is_creator = wallet.coldkeypub.ss58_address == crowdloan.creator
433
+ if is_creator:
434
+ withdrawable = user_contribution - crowdloan.deposit
435
+ if withdrawable <= 0:
436
+ error_msg = f"As the creator, you cannot withdraw your deposit of {crowdloan.deposit}. Only contributions above the deposit can be withdrawn."
437
+ if json_output:
438
+ json_console.print(json.dumps({"success": False, "error": error_msg}))
439
+ else:
440
+ print_error(error_msg)
441
+ return False, "Creator cannot withdraw deposit amount."
442
+ remaining_contribution = crowdloan.deposit
443
+ else:
444
+ withdrawable = user_contribution
445
+ remaining_contribution = Balance.from_tao(0)
446
+
447
+ call = await meshtensor.substrate.compose_call(
448
+ call_module="Crowdloan",
449
+ call_function="withdraw",
450
+ call_params={
451
+ "crowdloan_id": crowdloan_id,
452
+ },
453
+ )
454
+ extrinsic_fee = await meshtensor.get_extrinsic_fee(
455
+ call, wallet.coldkeypub, proxy=proxy
456
+ )
457
+ await show_crowdloan_details(
458
+ meshtensor=meshtensor,
459
+ crowdloan_id=crowdloan_id,
460
+ wallet=wallet,
461
+ verbose=False,
462
+ crowdloan=crowdloan,
463
+ current_block=current_block,
464
+ )
465
+
466
+ if prompt:
467
+ if proxy:
468
+ new_balance = user_balance + withdrawable
469
+ else:
470
+ new_balance = user_balance + withdrawable - extrinsic_fee
471
+ new_raised = crowdloan.raised - withdrawable
472
+ table = Table(
473
+ Column("[bold white]Field", style=COLORS.G.SUBHEAD),
474
+ Column("[bold white]Value", style=COLORS.G.TEMPO),
475
+ title="\n[bold cyan]Withdrawal Summary[/bold cyan]",
476
+ show_footer=False,
477
+ show_header=False,
478
+ width=None,
479
+ pad_edge=False,
480
+ box=box.SIMPLE,
481
+ show_edge=True,
482
+ border_style="bright_black",
483
+ )
484
+
485
+ table.add_row("Crowdloan ID", str(crowdloan_id))
486
+
487
+ if is_creator:
488
+ table.add_row("Role", "[yellow]Creator[/yellow]")
489
+ table.add_row("Current Contribution", str(user_contribution))
490
+ table.add_row("Deposit (Locked)", f"[yellow]{crowdloan.deposit}[/yellow]")
491
+ table.add_row(
492
+ "Withdrawable Amount",
493
+ f"[{COLORS.S.AMOUNT}]{withdrawable}[/{COLORS.S.AMOUNT}]",
494
+ )
495
+ table.add_row(
496
+ "Remaining After Withdrawal",
497
+ f"[yellow]{remaining_contribution}[/yellow] (deposit)",
498
+ )
499
+ else:
500
+ table.add_row("Current Contribution", str(user_contribution))
501
+ table.add_row(
502
+ "Withdrawal Amount",
503
+ f"[{COLORS.S.AMOUNT}]{withdrawable}[/{COLORS.S.AMOUNT}]",
504
+ )
505
+
506
+ table.add_row("Transaction Fee", str(extrinsic_fee))
507
+ table.add_row(
508
+ "Balance After",
509
+ f"[blue]{user_balance}[/blue] → [{COLORS.S.AMOUNT}]{new_balance}[/{COLORS.S.AMOUNT}]",
510
+ )
511
+
512
+ table.add_row(
513
+ "Crowdloan Total After",
514
+ f"[blue]{crowdloan.raised}[/blue] → [{COLORS.S.AMOUNT}]{new_raised}[/{COLORS.S.AMOUNT}]",
515
+ )
516
+
517
+ console.print(table)
518
+
519
+ if not confirm_action(
520
+ "\nProceed with withdrawal?", decline=decline, quiet=quiet
521
+ ):
522
+ if json_output:
523
+ json_console.print(
524
+ json.dumps(
525
+ {"success": False, "error": "Withdrawal cancelled by user."}
526
+ )
527
+ )
528
+ else:
529
+ console.print("[yellow]Withdrawal cancelled.[/yellow]")
530
+ return False, "Withdrawal cancelled by user."
531
+
532
+ unlock_status = unlock_key(wallet)
533
+ if not unlock_status.success:
534
+ if json_output:
535
+ json_console.print(
536
+ json.dumps({"success": False, "error": unlock_status.message})
537
+ )
538
+ else:
539
+ print_error(unlock_status.message)
540
+ return False, unlock_status.message
541
+
542
+ with console.status(f"\n:satellite: Withdrawing from crowdloan #{crowdloan_id}..."):
543
+ (
544
+ success,
545
+ error_message,
546
+ extrinsic_receipt,
547
+ ) = await meshtensor.sign_and_send_extrinsic(
548
+ call=call,
549
+ wallet=wallet,
550
+ proxy=proxy,
551
+ wait_for_inclusion=wait_for_inclusion,
552
+ wait_for_finalization=wait_for_finalization,
553
+ )
554
+
555
+ if not success:
556
+ if json_output:
557
+ json_console.print(
558
+ json.dumps(
559
+ {
560
+ "success": False,
561
+ "error": error_message or "Failed to withdraw from crowdloan.",
562
+ }
563
+ )
564
+ )
565
+ else:
566
+ print_error(f"Failed to withdraw: {error_message or 'Unknown error'}")
567
+ return False, error_message or "Failed to withdraw from crowdloan."
568
+
569
+ new_balance, updated_contribution, updated_crowdloan = await asyncio.gather(
570
+ meshtensor.get_balance(contributor_address),
571
+ meshtensor.get_crowdloan_contribution(crowdloan_id, contributor_address),
572
+ meshtensor.get_single_crowdloan(crowdloan_id),
573
+ )
574
+
575
+ if json_output:
576
+ extrinsic_id = await extrinsic_receipt.get_extrinsic_identifier()
577
+ output_dict = {
578
+ "success": True,
579
+ "error": None,
580
+ "extrinsic_identifier": extrinsic_id,
581
+ "data": {
582
+ "crowdloan_id": crowdloan_id,
583
+ "is_creator": is_creator,
584
+ "withdrawal_amount": withdrawable.tao,
585
+ "previous_contribution": user_contribution.tao,
586
+ "remaining_contribution": updated_contribution.tao
587
+ if updated_contribution
588
+ else 0.0,
589
+ "deposit_locked": crowdloan.deposit.tao if is_creator else None,
590
+ "balance": {
591
+ "before": user_balance.tao,
592
+ "after": new_balance.tao,
593
+ "fee": extrinsic_fee.tao,
594
+ },
595
+ "crowdloan": {
596
+ "raised_before": crowdloan.raised.tao,
597
+ "raised_after": updated_crowdloan.raised.tao
598
+ if updated_crowdloan
599
+ else (crowdloan.raised.tao - withdrawable.tao),
600
+ },
601
+ },
602
+ }
603
+ json_console.print(json.dumps(output_dict))
604
+ else:
605
+ print_success(f"Successfully withdrew from crowdloan #{crowdloan_id}!\n")
606
+
607
+ console.print(
608
+ f"Amount Withdrawn: [{COLORS.S.AMOUNT}]{withdrawable}[/{COLORS.S.AMOUNT}]\n"
609
+ f"Balance:\n [blue]{user_balance}[/blue] → [{COLORS.S.AMOUNT}]{new_balance}[/{COLORS.S.AMOUNT}]"
610
+ f"Crowdloan raised before: [{COLORS.S.AMOUNT}]{crowdloan.raised}[/{COLORS.S.AMOUNT}]"
611
+ f"Crowdloan raised after: [{COLORS.S.AMOUNT}]{updated_crowdloan.raised}[/{COLORS.S.AMOUNT}]"
612
+ )
613
+
614
+ if is_creator and updated_contribution:
615
+ console.print(
616
+ f"Remaining Contribution: [{COLORS.S.AMOUNT}]{updated_contribution}[/{COLORS.S.AMOUNT}] (deposit locked)"
617
+ )
618
+
619
+ await print_extrinsic_id(extrinsic_receipt)
620
+
621
+ return True, "Successfully withdrew from crowdloan."