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.
- meshtensor_cli/__init__.py +22 -0
- meshtensor_cli/cli.py +10742 -0
- meshtensor_cli/doc_generation_helper.py +4 -0
- meshtensor_cli/src/__init__.py +1085 -0
- meshtensor_cli/src/commands/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/axon.py +132 -0
- meshtensor_cli/src/commands/crowd/__init__.py +0 -0
- meshtensor_cli/src/commands/crowd/contribute.py +621 -0
- meshtensor_cli/src/commands/crowd/contributors.py +200 -0
- meshtensor_cli/src/commands/crowd/create.py +783 -0
- meshtensor_cli/src/commands/crowd/dissolve.py +219 -0
- meshtensor_cli/src/commands/crowd/refund.py +233 -0
- meshtensor_cli/src/commands/crowd/update.py +418 -0
- meshtensor_cli/src/commands/crowd/utils.py +124 -0
- meshtensor_cli/src/commands/crowd/view.py +991 -0
- meshtensor_cli/src/commands/governance/__init__.py +0 -0
- meshtensor_cli/src/commands/governance/governance.py +794 -0
- meshtensor_cli/src/commands/liquidity/__init__.py +0 -0
- meshtensor_cli/src/commands/liquidity/liquidity.py +699 -0
- meshtensor_cli/src/commands/liquidity/utils.py +202 -0
- meshtensor_cli/src/commands/proxy.py +700 -0
- meshtensor_cli/src/commands/stake/__init__.py +0 -0
- meshtensor_cli/src/commands/stake/add.py +799 -0
- meshtensor_cli/src/commands/stake/auto_staking.py +306 -0
- meshtensor_cli/src/commands/stake/children_hotkeys.py +865 -0
- meshtensor_cli/src/commands/stake/claim.py +770 -0
- meshtensor_cli/src/commands/stake/list.py +738 -0
- meshtensor_cli/src/commands/stake/move.py +1211 -0
- meshtensor_cli/src/commands/stake/remove.py +1466 -0
- meshtensor_cli/src/commands/stake/wizard.py +323 -0
- meshtensor_cli/src/commands/subnets/__init__.py +0 -0
- meshtensor_cli/src/commands/subnets/mechanisms.py +515 -0
- meshtensor_cli/src/commands/subnets/price.py +733 -0
- meshtensor_cli/src/commands/subnets/subnets.py +2908 -0
- meshtensor_cli/src/commands/sudo.py +1294 -0
- meshtensor_cli/src/commands/tc/__init__.py +0 -0
- meshtensor_cli/src/commands/tc/tc.py +190 -0
- meshtensor_cli/src/commands/treasury/__init__.py +0 -0
- meshtensor_cli/src/commands/treasury/treasury.py +194 -0
- meshtensor_cli/src/commands/view.py +354 -0
- meshtensor_cli/src/commands/wallets.py +2311 -0
- meshtensor_cli/src/commands/weights.py +467 -0
- meshtensor_cli/src/meshtensor/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/balances.py +313 -0
- meshtensor_cli/src/meshtensor/chain_data.py +1263 -0
- meshtensor_cli/src/meshtensor/extrinsics/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/extrinsics/mev_shield.py +174 -0
- meshtensor_cli/src/meshtensor/extrinsics/registration.py +1861 -0
- meshtensor_cli/src/meshtensor/extrinsics/root.py +550 -0
- meshtensor_cli/src/meshtensor/extrinsics/serving.py +255 -0
- meshtensor_cli/src/meshtensor/extrinsics/transfer.py +239 -0
- meshtensor_cli/src/meshtensor/meshtensor_interface.py +2598 -0
- meshtensor_cli/src/meshtensor/minigraph.py +254 -0
- meshtensor_cli/src/meshtensor/networking.py +12 -0
- meshtensor_cli/src/meshtensor/templates/main-filters.j2 +24 -0
- meshtensor_cli/src/meshtensor/templates/main-header.j2 +36 -0
- meshtensor_cli/src/meshtensor/templates/neuron-details.j2 +111 -0
- meshtensor_cli/src/meshtensor/templates/price-multi.j2 +113 -0
- meshtensor_cli/src/meshtensor/templates/price-single.j2 +99 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details-header.j2 +49 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details.j2 +32 -0
- meshtensor_cli/src/meshtensor/templates/subnet-metrics.j2 +57 -0
- meshtensor_cli/src/meshtensor/templates/subnets-table.j2 +28 -0
- meshtensor_cli/src/meshtensor/templates/table.j2 +267 -0
- meshtensor_cli/src/meshtensor/templates/view.css +1058 -0
- meshtensor_cli/src/meshtensor/templates/view.j2 +43 -0
- meshtensor_cli/src/meshtensor/templates/view.js +1053 -0
- meshtensor_cli/src/meshtensor/utils.py +2007 -0
- meshtensor_cli/version.py +23 -0
- meshtensor_cli-9.18.1.dist-info/METADATA +261 -0
- meshtensor_cli-9.18.1.dist-info/RECORD +74 -0
- meshtensor_cli-9.18.1.dist-info/WHEEL +4 -0
- 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."
|