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,991 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
from meshtensor_wallet import Wallet
|
|
6
|
+
from rich import box
|
|
7
|
+
from rich.table import Column, Table
|
|
8
|
+
|
|
9
|
+
from meshtensor_cli.src import COLORS
|
|
10
|
+
from meshtensor_cli.src.meshtensor.balances import Balance
|
|
11
|
+
from meshtensor_cli.src.meshtensor.chain_data import CrowdloanData
|
|
12
|
+
from meshtensor_cli.src.meshtensor.meshtensor_interface import MeshtensorInterface
|
|
13
|
+
from meshtensor_cli.src.meshtensor.utils import (
|
|
14
|
+
blocks_to_duration,
|
|
15
|
+
console,
|
|
16
|
+
json_console,
|
|
17
|
+
print_error,
|
|
18
|
+
millify_tao,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _shorten(account: Optional[str]) -> str:
|
|
23
|
+
if not account:
|
|
24
|
+
return "-"
|
|
25
|
+
return f"{account[:6]}…{account[-6:]}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _status(loan: CrowdloanData, current_block: int) -> str:
|
|
29
|
+
if loan.finalized:
|
|
30
|
+
return "Finalized"
|
|
31
|
+
if loan.raised >= loan.cap:
|
|
32
|
+
return "Funded"
|
|
33
|
+
if current_block >= loan.end:
|
|
34
|
+
return "Closed"
|
|
35
|
+
return "Active"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _time_remaining(loan: CrowdloanData, current_block: int) -> str:
|
|
39
|
+
diff = loan.end - current_block
|
|
40
|
+
if diff > 0:
|
|
41
|
+
return blocks_to_duration(diff)
|
|
42
|
+
if diff == 0:
|
|
43
|
+
return "due"
|
|
44
|
+
return f"Closed {blocks_to_duration(abs(diff))} ago"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_loan_type(loan: CrowdloanData) -> str:
|
|
48
|
+
"""Determine if a loan is subnet leasing or fundraising."""
|
|
49
|
+
if loan.call_details:
|
|
50
|
+
pallet = loan.call_details.get("pallet", "")
|
|
51
|
+
method = loan.call_details.get("method", "")
|
|
52
|
+
if pallet == "MeshtensorModule" and method == "register_leased_network":
|
|
53
|
+
return "subnet"
|
|
54
|
+
# If has_call is True, it likely indicates a subnet loan
|
|
55
|
+
# (subnet loans have calls attached, fundraising loans typically don't)
|
|
56
|
+
if loan.has_call:
|
|
57
|
+
return "subnet"
|
|
58
|
+
# Default to fundraising if no call attached
|
|
59
|
+
return "fundraising"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def list_crowdloans(
|
|
63
|
+
meshtensor: MeshtensorInterface,
|
|
64
|
+
verbose: bool = False,
|
|
65
|
+
json_output: bool = False,
|
|
66
|
+
status_filter: Optional[str] = None,
|
|
67
|
+
type_filter: Optional[str] = None,
|
|
68
|
+
sort_by: Optional[str] = None,
|
|
69
|
+
sort_order: Optional[str] = None,
|
|
70
|
+
search_creator: Optional[str] = None,
|
|
71
|
+
) -> bool:
|
|
72
|
+
"""List all crowdloans in a tabular format or JSON output.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
meshtensor: MeshtensorInterface object for chain interaction
|
|
76
|
+
verbose: Show full addresses and precise amounts
|
|
77
|
+
json_output: Output as JSON
|
|
78
|
+
status_filter: Filter by status (active, funded, closed, finalized)
|
|
79
|
+
type_filter: Filter by type (subnet, fundraising)
|
|
80
|
+
sort_by: Sort by field (raised, end, contributors, id)
|
|
81
|
+
sort_order: Sort order (asc, desc)
|
|
82
|
+
search_creator: Search by creator address or identity name
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
current_block, loans, all_identities = await asyncio.gather(
|
|
86
|
+
meshtensor.substrate.get_block_number(None),
|
|
87
|
+
meshtensor.get_crowdloans(),
|
|
88
|
+
meshtensor.query_all_identities(),
|
|
89
|
+
)
|
|
90
|
+
if not loans:
|
|
91
|
+
if json_output:
|
|
92
|
+
json_console.print(
|
|
93
|
+
json.dumps(
|
|
94
|
+
{
|
|
95
|
+
"success": True,
|
|
96
|
+
"error": None,
|
|
97
|
+
"data": {
|
|
98
|
+
"crowdloans": [],
|
|
99
|
+
"total_count": 0,
|
|
100
|
+
"total_raised": 0,
|
|
101
|
+
"total_cap": 0,
|
|
102
|
+
"total_contributors": 0,
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
console.print("[yellow]No crowdloans found.[/yellow]")
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
# Build identity map from all identities
|
|
112
|
+
identity_map = {}
|
|
113
|
+
addresses_to_check = set()
|
|
114
|
+
for loan in loans.values():
|
|
115
|
+
addresses_to_check.add(loan.creator)
|
|
116
|
+
if loan.target_address:
|
|
117
|
+
addresses_to_check.add(loan.target_address)
|
|
118
|
+
|
|
119
|
+
for address in addresses_to_check:
|
|
120
|
+
identity = all_identities.get(address)
|
|
121
|
+
if identity:
|
|
122
|
+
identity_name = identity.get("name") or identity.get("display")
|
|
123
|
+
if identity_name:
|
|
124
|
+
identity_map[address] = identity_name
|
|
125
|
+
|
|
126
|
+
# Apply filters
|
|
127
|
+
filtered_loans = {}
|
|
128
|
+
for loan_id, loan in loans.items():
|
|
129
|
+
# Filter by status
|
|
130
|
+
if status_filter:
|
|
131
|
+
loan_status = _status(loan, current_block)
|
|
132
|
+
if loan_status.lower() != status_filter.lower():
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
# Filter by type
|
|
136
|
+
if type_filter:
|
|
137
|
+
loan_type = _get_loan_type(loan)
|
|
138
|
+
if loan_type.lower() != type_filter.lower():
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
# Filter by creator search
|
|
142
|
+
if search_creator:
|
|
143
|
+
search_term = search_creator.lower()
|
|
144
|
+
creator_match = loan.creator.lower().find(search_term) != -1
|
|
145
|
+
identity_match = False
|
|
146
|
+
if loan.creator in identity_map:
|
|
147
|
+
identity_name = identity_map[loan.creator].lower()
|
|
148
|
+
identity_match = identity_name.find(search_term) != -1
|
|
149
|
+
if not creator_match and not identity_match:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
filtered_loans[loan_id] = loan
|
|
153
|
+
|
|
154
|
+
if not filtered_loans:
|
|
155
|
+
if json_output:
|
|
156
|
+
json_console.print(
|
|
157
|
+
json.dumps(
|
|
158
|
+
{
|
|
159
|
+
"success": True,
|
|
160
|
+
"error": None,
|
|
161
|
+
"data": {
|
|
162
|
+
"crowdloans": [],
|
|
163
|
+
"total_count": 0,
|
|
164
|
+
"total_raised": 0,
|
|
165
|
+
"total_cap": 0,
|
|
166
|
+
"total_contributors": 0,
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
else:
|
|
172
|
+
console.print("[yellow]No crowdloans found matching the filters.[/yellow]")
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
total_raised = sum(loan.raised.tao for loan in filtered_loans.values())
|
|
176
|
+
total_cap = sum(loan.cap.tao for loan in filtered_loans.values())
|
|
177
|
+
total_loans = len(filtered_loans)
|
|
178
|
+
total_contributors = sum(
|
|
179
|
+
loan.contributors_count for loan in filtered_loans.values()
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
funding_percentage = (total_raised / total_cap * 100) if total_cap > 0 else 0
|
|
183
|
+
percentage_color = "dark_sea_green" if funding_percentage < 100 else "red"
|
|
184
|
+
formatted_percentage = (
|
|
185
|
+
f"[{percentage_color}]{funding_percentage:.2f}%[/{percentage_color}]"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if json_output:
|
|
189
|
+
crowdloans_list = []
|
|
190
|
+
for loan_id, loan in filtered_loans.items():
|
|
191
|
+
status = _status(loan, current_block)
|
|
192
|
+
time_remaining = _time_remaining(loan, current_block)
|
|
193
|
+
|
|
194
|
+
call_info = None
|
|
195
|
+
if loan.call_details:
|
|
196
|
+
pallet = loan.call_details.get("pallet", "")
|
|
197
|
+
method = loan.call_details.get("method", "")
|
|
198
|
+
if pallet == "MeshtensorModule" and method == "register_leased_network":
|
|
199
|
+
call_info = "Subnet Leasing"
|
|
200
|
+
else:
|
|
201
|
+
call_info = (
|
|
202
|
+
f"{pallet}.{method}"
|
|
203
|
+
if pallet and method
|
|
204
|
+
else method or pallet or "Unknown"
|
|
205
|
+
)
|
|
206
|
+
elif loan.has_call:
|
|
207
|
+
call_info = "Unknown"
|
|
208
|
+
|
|
209
|
+
crowdloan_data = {
|
|
210
|
+
"id": loan_id,
|
|
211
|
+
"status": status,
|
|
212
|
+
"raised": loan.raised.tao,
|
|
213
|
+
"cap": loan.cap.tao,
|
|
214
|
+
"deposit": loan.deposit.tao,
|
|
215
|
+
"min_contribution": loan.min_contribution.tao,
|
|
216
|
+
"end_block": loan.end,
|
|
217
|
+
"time_remaining": time_remaining,
|
|
218
|
+
"contributors_count": loan.contributors_count,
|
|
219
|
+
"creator": loan.creator,
|
|
220
|
+
"creator_identity": identity_map.get(loan.creator),
|
|
221
|
+
"target_address": loan.target_address,
|
|
222
|
+
"target_identity": identity_map.get(loan.target_address)
|
|
223
|
+
if loan.target_address
|
|
224
|
+
else None,
|
|
225
|
+
"funds_account": loan.funds_account,
|
|
226
|
+
"call": call_info,
|
|
227
|
+
"finalized": loan.finalized,
|
|
228
|
+
}
|
|
229
|
+
crowdloans_list.append(crowdloan_data)
|
|
230
|
+
|
|
231
|
+
# Apply sorting
|
|
232
|
+
if sort_by:
|
|
233
|
+
reverse_order = True
|
|
234
|
+
if sort_order:
|
|
235
|
+
reverse_order = sort_order.lower() == "desc"
|
|
236
|
+
elif sort_by.lower() == "id":
|
|
237
|
+
reverse_order = False
|
|
238
|
+
|
|
239
|
+
if sort_by.lower() == "raised":
|
|
240
|
+
crowdloans_list.sort(key=lambda x: x["raised"], reverse=reverse_order)
|
|
241
|
+
elif sort_by.lower() == "end":
|
|
242
|
+
crowdloans_list.sort(
|
|
243
|
+
key=lambda x: x["end_block"], reverse=reverse_order
|
|
244
|
+
)
|
|
245
|
+
elif sort_by.lower() == "contributors":
|
|
246
|
+
crowdloans_list.sort(
|
|
247
|
+
key=lambda x: x["contributors_count"], reverse=reverse_order
|
|
248
|
+
)
|
|
249
|
+
elif sort_by.lower() == "id":
|
|
250
|
+
crowdloans_list.sort(key=lambda x: x["id"], reverse=reverse_order)
|
|
251
|
+
else:
|
|
252
|
+
# Default sorting: Active first, then by raised amount descending
|
|
253
|
+
crowdloans_list.sort(
|
|
254
|
+
key=lambda x: (
|
|
255
|
+
x["status"] != "Active",
|
|
256
|
+
-x["raised"],
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
output_dict = {
|
|
261
|
+
"success": True,
|
|
262
|
+
"error": None,
|
|
263
|
+
"data": {
|
|
264
|
+
"crowdloans": crowdloans_list,
|
|
265
|
+
"total_count": total_loans,
|
|
266
|
+
"total_raised": total_raised,
|
|
267
|
+
"total_cap": total_cap,
|
|
268
|
+
"total_contributors": total_contributors,
|
|
269
|
+
"funding_percentage": funding_percentage,
|
|
270
|
+
"current_block": current_block,
|
|
271
|
+
"network": meshtensor.network,
|
|
272
|
+
},
|
|
273
|
+
}
|
|
274
|
+
json_console.print(json.dumps(output_dict))
|
|
275
|
+
return True
|
|
276
|
+
|
|
277
|
+
if not verbose:
|
|
278
|
+
funding_string = f"τ {millify_tao(total_raised)}/{millify_tao(total_cap)} ({formatted_percentage})"
|
|
279
|
+
else:
|
|
280
|
+
funding_string = (
|
|
281
|
+
f"τ {total_raised:.1f}/{total_cap:.1f} ({formatted_percentage})"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
table = Table(
|
|
285
|
+
title=f"\n[{COLORS.G.HEADER}]Crowdloans"
|
|
286
|
+
f"\nNetwork: [{COLORS.G.SUBHEAD}]{meshtensor.network}\n\n",
|
|
287
|
+
show_footer=True,
|
|
288
|
+
show_edge=False,
|
|
289
|
+
header_style="bold white",
|
|
290
|
+
border_style="bright_black",
|
|
291
|
+
style="bold",
|
|
292
|
+
title_justify="center",
|
|
293
|
+
show_lines=False,
|
|
294
|
+
pad_edge=True,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
table.add_column(
|
|
298
|
+
"[bold white]ID", style="grey89", justify="center", footer=str(total_loans)
|
|
299
|
+
)
|
|
300
|
+
table.add_column("[bold white]Status", style="cyan", justify="center")
|
|
301
|
+
table.add_column(
|
|
302
|
+
f"[bold white]Raised / Cap\n({Balance.get_unit(0)})",
|
|
303
|
+
style="dark_sea_green2",
|
|
304
|
+
justify="left",
|
|
305
|
+
footer=funding_string,
|
|
306
|
+
)
|
|
307
|
+
table.add_column(
|
|
308
|
+
f"[bold white]Deposit\n({Balance.get_unit(0)})",
|
|
309
|
+
style="steel_blue3",
|
|
310
|
+
justify="left",
|
|
311
|
+
)
|
|
312
|
+
table.add_column(
|
|
313
|
+
f"[bold white]Min Contribution\n({Balance.get_unit(0)})",
|
|
314
|
+
style=COLORS.P.EMISSION,
|
|
315
|
+
justify="left",
|
|
316
|
+
)
|
|
317
|
+
table.add_column("[bold white]Ends (Block)", style=COLORS.S.MESH, justify="left")
|
|
318
|
+
table.add_column(
|
|
319
|
+
"[bold white]Time Remaining",
|
|
320
|
+
style=COLORS.S.ALPHA,
|
|
321
|
+
justify="left",
|
|
322
|
+
)
|
|
323
|
+
table.add_column(
|
|
324
|
+
"[bold white]Contributors",
|
|
325
|
+
style=COLORS.P.ALPHA_IN,
|
|
326
|
+
justify="center",
|
|
327
|
+
footer=str(total_contributors),
|
|
328
|
+
)
|
|
329
|
+
table.add_column(
|
|
330
|
+
"[bold white]Creator",
|
|
331
|
+
style=COLORS.G.TEMPO,
|
|
332
|
+
justify="left",
|
|
333
|
+
overflow="fold",
|
|
334
|
+
)
|
|
335
|
+
table.add_column(
|
|
336
|
+
"[bold white]Target",
|
|
337
|
+
style=COLORS.G.SUBHEAD_EX_1,
|
|
338
|
+
justify="center",
|
|
339
|
+
)
|
|
340
|
+
table.add_column(
|
|
341
|
+
"[bold white]Funds Account",
|
|
342
|
+
style=COLORS.G.SUBHEAD_EX_2,
|
|
343
|
+
justify="left",
|
|
344
|
+
overflow="fold",
|
|
345
|
+
)
|
|
346
|
+
table.add_column("[bold white]Call", style="grey89", justify="center")
|
|
347
|
+
|
|
348
|
+
# Apply sorting for table display
|
|
349
|
+
if sort_by:
|
|
350
|
+
reverse_order = True
|
|
351
|
+
if sort_order:
|
|
352
|
+
reverse_order = sort_order.lower() == "desc"
|
|
353
|
+
elif sort_by.lower() == "id":
|
|
354
|
+
reverse_order = False
|
|
355
|
+
|
|
356
|
+
if sort_by.lower() == "raised":
|
|
357
|
+
sorted_loans = sorted(
|
|
358
|
+
filtered_loans.items(),
|
|
359
|
+
key=lambda x: x[1].raised.tao,
|
|
360
|
+
reverse=reverse_order,
|
|
361
|
+
)
|
|
362
|
+
elif sort_by.lower() == "end":
|
|
363
|
+
sorted_loans = sorted(
|
|
364
|
+
filtered_loans.items(),
|
|
365
|
+
key=lambda x: x[1].end,
|
|
366
|
+
reverse=reverse_order,
|
|
367
|
+
)
|
|
368
|
+
elif sort_by.lower() == "contributors":
|
|
369
|
+
sorted_loans = sorted(
|
|
370
|
+
filtered_loans.items(),
|
|
371
|
+
key=lambda x: x[1].contributors_count,
|
|
372
|
+
reverse=reverse_order,
|
|
373
|
+
)
|
|
374
|
+
elif sort_by.lower() == "id":
|
|
375
|
+
sorted_loans = sorted(
|
|
376
|
+
filtered_loans.items(),
|
|
377
|
+
key=lambda x: x[0],
|
|
378
|
+
reverse=reverse_order,
|
|
379
|
+
)
|
|
380
|
+
else:
|
|
381
|
+
# Default sorting
|
|
382
|
+
sorted_loans = sorted(
|
|
383
|
+
filtered_loans.items(),
|
|
384
|
+
key=lambda x: (
|
|
385
|
+
_status(x[1], current_block) != "Active",
|
|
386
|
+
-x[1].raised.tao,
|
|
387
|
+
),
|
|
388
|
+
)
|
|
389
|
+
else:
|
|
390
|
+
# Default sorting: Active loans first, then by raised amount (descending)
|
|
391
|
+
sorted_loans = sorted(
|
|
392
|
+
filtered_loans.items(),
|
|
393
|
+
key=lambda x: (
|
|
394
|
+
_status(x[1], current_block) != "Active", # Active loans first
|
|
395
|
+
-x[1].raised.tao, # Then by raised amount (descending)
|
|
396
|
+
),
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
for loan_id, loan in sorted_loans:
|
|
400
|
+
status = _status(loan, current_block)
|
|
401
|
+
time_label = _time_remaining(loan, current_block)
|
|
402
|
+
|
|
403
|
+
raised_cell = (
|
|
404
|
+
f"τ {loan.raised.tao:,.4f} / τ {loan.cap.tao:,.4f}"
|
|
405
|
+
if verbose
|
|
406
|
+
else f"τ {millify_tao(loan.raised.tao)} / τ {millify_tao(loan.cap.tao)}"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
deposit_cell = (
|
|
410
|
+
f"τ {loan.deposit.tao:,.4f}"
|
|
411
|
+
if verbose
|
|
412
|
+
else f"τ {millify_tao(loan.deposit.tao)}"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
min_contrib_cell = (
|
|
416
|
+
f"τ {loan.min_contribution.tao:,.4f}"
|
|
417
|
+
if verbose
|
|
418
|
+
else f"τ {millify_tao(loan.min_contribution.tao)}"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
status_color_map = {
|
|
422
|
+
"Finalized": COLORS.G.SUCCESS,
|
|
423
|
+
"Funded": COLORS.P.EMISSION,
|
|
424
|
+
"Closed": COLORS.G.SYM,
|
|
425
|
+
"Active": COLORS.G.HINT,
|
|
426
|
+
}
|
|
427
|
+
status_color = status_color_map.get(status, "white")
|
|
428
|
+
status_cell = f"[{status_color}]{status}[/{status_color}]"
|
|
429
|
+
|
|
430
|
+
if "Closed" in time_label:
|
|
431
|
+
time_cell = f"[{COLORS.G.SYM}]{time_label}[/{COLORS.G.SYM}]"
|
|
432
|
+
elif time_label == "due":
|
|
433
|
+
time_cell = f"[red]{time_label}[/red]"
|
|
434
|
+
else:
|
|
435
|
+
time_cell = time_label
|
|
436
|
+
|
|
437
|
+
# Format creator cell
|
|
438
|
+
creator_identity = identity_map.get(loan.creator)
|
|
439
|
+
address_display = loan.creator if verbose else _shorten(loan.creator)
|
|
440
|
+
creator_cell = (
|
|
441
|
+
f"{creator_identity} ({address_display})"
|
|
442
|
+
if creator_identity
|
|
443
|
+
else address_display
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Format target cell
|
|
447
|
+
if loan.target_address:
|
|
448
|
+
target_identity = identity_map.get(loan.target_address)
|
|
449
|
+
address_display = (
|
|
450
|
+
loan.target_address if verbose else _shorten(loan.target_address)
|
|
451
|
+
)
|
|
452
|
+
target_cell = (
|
|
453
|
+
f"{target_identity} ({address_display})"
|
|
454
|
+
if target_identity
|
|
455
|
+
else address_display
|
|
456
|
+
)
|
|
457
|
+
else:
|
|
458
|
+
target_cell = (
|
|
459
|
+
f"[{COLORS.G.SUBHEAD_MAIN}]Not specified[/{COLORS.G.SUBHEAD_MAIN}]"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
funds_account_cell = (
|
|
463
|
+
loan.funds_account if verbose else _shorten(loan.funds_account)
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
if loan.call_details:
|
|
467
|
+
pallet = loan.call_details.get("pallet", "")
|
|
468
|
+
method = loan.call_details.get("method", "")
|
|
469
|
+
|
|
470
|
+
if pallet == "MeshtensorModule" and method == "register_leased_network":
|
|
471
|
+
call_label = "[magenta]Subnet Leasing[/magenta]"
|
|
472
|
+
else:
|
|
473
|
+
call_label = (
|
|
474
|
+
f"{pallet}.{method}"
|
|
475
|
+
if pallet and method
|
|
476
|
+
else method or pallet or "Unknown"
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
call_cell = call_label
|
|
480
|
+
elif loan.has_call:
|
|
481
|
+
call_cell = f"[{COLORS.G.SYM}]Unknown[/{COLORS.G.SYM}]"
|
|
482
|
+
else:
|
|
483
|
+
call_cell = "-"
|
|
484
|
+
|
|
485
|
+
table.add_row(
|
|
486
|
+
str(loan_id),
|
|
487
|
+
status_cell,
|
|
488
|
+
raised_cell,
|
|
489
|
+
deposit_cell,
|
|
490
|
+
min_contrib_cell,
|
|
491
|
+
str(loan.end),
|
|
492
|
+
time_cell,
|
|
493
|
+
str(loan.contributors_count),
|
|
494
|
+
creator_cell,
|
|
495
|
+
target_cell,
|
|
496
|
+
funds_account_cell,
|
|
497
|
+
call_cell,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
console.print(table)
|
|
501
|
+
|
|
502
|
+
return True
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
async def show_crowdloan_details(
|
|
506
|
+
meshtensor: MeshtensorInterface,
|
|
507
|
+
crowdloan_id: int,
|
|
508
|
+
crowdloan: Optional[CrowdloanData] = None,
|
|
509
|
+
current_block: Optional[int] = None,
|
|
510
|
+
wallet: Optional[Wallet] = None,
|
|
511
|
+
verbose: bool = False,
|
|
512
|
+
json_output: bool = False,
|
|
513
|
+
show_contributors: bool = False,
|
|
514
|
+
) -> tuple[bool, str]:
|
|
515
|
+
"""Display detailed information about a specific crowdloan."""
|
|
516
|
+
|
|
517
|
+
if not crowdloan or not current_block:
|
|
518
|
+
current_block, crowdloan, all_identities = await asyncio.gather(
|
|
519
|
+
meshtensor.substrate.get_block_number(None),
|
|
520
|
+
meshtensor.get_single_crowdloan(crowdloan_id),
|
|
521
|
+
meshtensor.query_all_identities(),
|
|
522
|
+
)
|
|
523
|
+
else:
|
|
524
|
+
all_identities = await meshtensor.query_all_identities()
|
|
525
|
+
|
|
526
|
+
if not crowdloan:
|
|
527
|
+
error_msg = f"Crowdloan #{crowdloan_id} not found."
|
|
528
|
+
if json_output:
|
|
529
|
+
json_console.print(json.dumps({"success": False, "error": error_msg}))
|
|
530
|
+
else:
|
|
531
|
+
print_error(f"[red]{error_msg}[/red]")
|
|
532
|
+
return False, error_msg
|
|
533
|
+
|
|
534
|
+
user_contribution = None
|
|
535
|
+
if wallet and wallet.coldkeypub:
|
|
536
|
+
user_contribution = await meshtensor.get_crowdloan_contribution(
|
|
537
|
+
crowdloan_id, wallet.coldkeypub.ss58_address
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
# Build identity map from all identities
|
|
541
|
+
identity_map = {}
|
|
542
|
+
addresses_to_check = [crowdloan.creator]
|
|
543
|
+
if crowdloan.target_address:
|
|
544
|
+
addresses_to_check.append(crowdloan.target_address)
|
|
545
|
+
|
|
546
|
+
for address in addresses_to_check:
|
|
547
|
+
identity = all_identities.get(address)
|
|
548
|
+
if identity:
|
|
549
|
+
identity_name = identity.get("name") or identity.get("display")
|
|
550
|
+
if identity_name:
|
|
551
|
+
identity_map[address] = identity_name
|
|
552
|
+
|
|
553
|
+
status = _status(crowdloan, current_block)
|
|
554
|
+
status_color_map = {
|
|
555
|
+
"Finalized": COLORS.G.SUCCESS,
|
|
556
|
+
"Funded": COLORS.P.EMISSION,
|
|
557
|
+
"Closed": COLORS.G.SYM,
|
|
558
|
+
"Active": COLORS.G.HINT,
|
|
559
|
+
}
|
|
560
|
+
status_color = status_color_map.get(status, "white")
|
|
561
|
+
|
|
562
|
+
if json_output:
|
|
563
|
+
time_remaining = _time_remaining(crowdloan, current_block)
|
|
564
|
+
|
|
565
|
+
avg_contribution = None
|
|
566
|
+
if crowdloan.contributors_count > 0:
|
|
567
|
+
net_contributions = crowdloan.raised.tao - crowdloan.deposit.tao
|
|
568
|
+
avg_contribution = (
|
|
569
|
+
net_contributions / (crowdloan.contributors_count - 1)
|
|
570
|
+
if crowdloan.contributors_count > 1
|
|
571
|
+
else crowdloan.deposit.tao
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
call_info = None
|
|
575
|
+
if crowdloan.has_call and crowdloan.call_details:
|
|
576
|
+
pallet = crowdloan.call_details.get("pallet", "Unknown")
|
|
577
|
+
method = crowdloan.call_details.get("method", "Unknown")
|
|
578
|
+
args = crowdloan.call_details.get("args", {})
|
|
579
|
+
|
|
580
|
+
if pallet == "MeshtensorModule" and method == "register_leased_network":
|
|
581
|
+
call_info = {
|
|
582
|
+
"type": "Subnet Leasing",
|
|
583
|
+
"pallet": pallet,
|
|
584
|
+
"method": method,
|
|
585
|
+
"emissions_share": args.get("emissions_share", {}).get("value"),
|
|
586
|
+
"end_block": args.get("end_block", {}).get("value"),
|
|
587
|
+
}
|
|
588
|
+
else:
|
|
589
|
+
call_info = {"pallet": pallet, "method": method, "args": args}
|
|
590
|
+
|
|
591
|
+
user_contribution_info = None
|
|
592
|
+
if user_contribution:
|
|
593
|
+
is_creator = (
|
|
594
|
+
wallet
|
|
595
|
+
and wallet.coldkeypub
|
|
596
|
+
and wallet.coldkeypub.ss58_address == crowdloan.creator
|
|
597
|
+
)
|
|
598
|
+
withdrawable_amount = None
|
|
599
|
+
|
|
600
|
+
if status == "Active" and not crowdloan.finalized:
|
|
601
|
+
if is_creator and user_contribution.tao > crowdloan.deposit.tao:
|
|
602
|
+
withdrawable_amount = user_contribution.tao - crowdloan.deposit.tao
|
|
603
|
+
elif not is_creator:
|
|
604
|
+
withdrawable_amount = user_contribution.tao
|
|
605
|
+
|
|
606
|
+
user_contribution_info = {
|
|
607
|
+
"amount": user_contribution.tao,
|
|
608
|
+
"is_creator": is_creator,
|
|
609
|
+
"withdrawable": withdrawable_amount,
|
|
610
|
+
"refundable": status == "Closed",
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
output_dict = {
|
|
614
|
+
"success": True,
|
|
615
|
+
"error": None,
|
|
616
|
+
"data": {
|
|
617
|
+
"crowdloan_id": crowdloan_id,
|
|
618
|
+
"status": status,
|
|
619
|
+
"finalized": crowdloan.finalized,
|
|
620
|
+
"creator": crowdloan.creator,
|
|
621
|
+
"creator_identity": identity_map.get(crowdloan.creator),
|
|
622
|
+
"funds_account": crowdloan.funds_account,
|
|
623
|
+
"raised": crowdloan.raised.tao,
|
|
624
|
+
"cap": crowdloan.cap.tao,
|
|
625
|
+
"raised_percentage": (crowdloan.raised.tao / crowdloan.cap.tao * 100)
|
|
626
|
+
if crowdloan.cap.tao > 0
|
|
627
|
+
else 0,
|
|
628
|
+
"deposit": crowdloan.deposit.tao,
|
|
629
|
+
"min_contribution": crowdloan.min_contribution.tao,
|
|
630
|
+
"end_block": crowdloan.end,
|
|
631
|
+
"current_block": current_block,
|
|
632
|
+
"time_remaining": time_remaining,
|
|
633
|
+
"contributors_count": crowdloan.contributors_count,
|
|
634
|
+
"average_contribution": avg_contribution,
|
|
635
|
+
"target_address": crowdloan.target_address,
|
|
636
|
+
"target_identity": identity_map.get(crowdloan.target_address)
|
|
637
|
+
if crowdloan.target_address
|
|
638
|
+
else None,
|
|
639
|
+
"has_call": crowdloan.has_call,
|
|
640
|
+
"call_details": call_info,
|
|
641
|
+
"user_contribution": user_contribution_info,
|
|
642
|
+
"network": meshtensor.network,
|
|
643
|
+
},
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
# Add contributors list if requested
|
|
647
|
+
if show_contributors:
|
|
648
|
+
contributor_contributions = await meshtensor.get_crowdloan_contributors(
|
|
649
|
+
crowdloan_id
|
|
650
|
+
)
|
|
651
|
+
contributors_list = list(contributor_contributions.keys())
|
|
652
|
+
if contributors_list:
|
|
653
|
+
contributors_json = []
|
|
654
|
+
total_contributed = Balance.from_tao(0)
|
|
655
|
+
for (
|
|
656
|
+
contributor_address,
|
|
657
|
+
contribution_amount,
|
|
658
|
+
) in contributor_contributions.items():
|
|
659
|
+
total_contributed += contribution_amount
|
|
660
|
+
|
|
661
|
+
contributor_data = []
|
|
662
|
+
for contributor_address in contributors_list:
|
|
663
|
+
contribution_amount = contributor_contributions[contributor_address]
|
|
664
|
+
identity = all_identities.get(contributor_address)
|
|
665
|
+
identity_name = None
|
|
666
|
+
if identity:
|
|
667
|
+
identity_name = identity.get("name") or identity.get("display")
|
|
668
|
+
contributor_data.append(
|
|
669
|
+
{
|
|
670
|
+
"address": contributor_address,
|
|
671
|
+
"identity": identity_name,
|
|
672
|
+
"contribution": contribution_amount,
|
|
673
|
+
}
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
contributor_data.sort(key=lambda x: x["contribution"].meshlet, reverse=True)
|
|
677
|
+
|
|
678
|
+
for rank, data in enumerate(contributor_data, start=1):
|
|
679
|
+
percentage = (
|
|
680
|
+
(data["contribution"].meshlet / total_contributed.meshlet * 100)
|
|
681
|
+
if total_contributed.meshlet > 0
|
|
682
|
+
else 0
|
|
683
|
+
)
|
|
684
|
+
contributors_json.append(
|
|
685
|
+
{
|
|
686
|
+
"rank": rank,
|
|
687
|
+
"address": data["address"],
|
|
688
|
+
"identity": data["identity"],
|
|
689
|
+
"contribution_tao": data["contribution"].tao,
|
|
690
|
+
"contribution_meshlet": data["contribution"].meshlet,
|
|
691
|
+
"percentage": percentage,
|
|
692
|
+
}
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
output_dict["data"]["contributors"] = contributors_json
|
|
696
|
+
|
|
697
|
+
json_console.print(json.dumps(output_dict))
|
|
698
|
+
return True, f"Displayed info for crowdloan #{crowdloan_id}"
|
|
699
|
+
|
|
700
|
+
table = Table(
|
|
701
|
+
Column(
|
|
702
|
+
"Field",
|
|
703
|
+
style=COLORS.G.SUBHEAD,
|
|
704
|
+
min_width=20,
|
|
705
|
+
no_wrap=True,
|
|
706
|
+
),
|
|
707
|
+
Column("Value", style=COLORS.G.TEMPO),
|
|
708
|
+
title=f"\n[underline][{COLORS.G.HEADER}]CROWDLOAN #{crowdloan_id}[/underline][/{COLORS.G.HEADER}] - [{status_color} underline]{status.upper()}[/{status_color} underline]",
|
|
709
|
+
show_header=False,
|
|
710
|
+
show_footer=False,
|
|
711
|
+
width=None,
|
|
712
|
+
pad_edge=False,
|
|
713
|
+
box=box.SIMPLE,
|
|
714
|
+
show_edge=True,
|
|
715
|
+
border_style="bright_black",
|
|
716
|
+
expand=False,
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
# OVERVIEW Section
|
|
720
|
+
table.add_row("[cyan underline]OVERVIEW[/cyan underline]", "")
|
|
721
|
+
table.add_section()
|
|
722
|
+
|
|
723
|
+
status_detail = ""
|
|
724
|
+
if status == "Active":
|
|
725
|
+
status_detail = " [dim](accepting contributions)[/dim]"
|
|
726
|
+
elif status == "Funded":
|
|
727
|
+
status_detail = " [yellow](awaiting finalization)[/yellow]"
|
|
728
|
+
elif status == "Closed":
|
|
729
|
+
status_detail = " [dim](failed to reach cap)[/dim]"
|
|
730
|
+
elif status == "Finalized":
|
|
731
|
+
status_detail = " [green](successfully completed)[/green]"
|
|
732
|
+
|
|
733
|
+
table.add_row("Status", f"[{status_color}]{status}[/{status_color}]{status_detail}")
|
|
734
|
+
|
|
735
|
+
# Display creator
|
|
736
|
+
creator_identity = identity_map.get(crowdloan.creator)
|
|
737
|
+
address_display = crowdloan.creator if verbose else _shorten(crowdloan.creator)
|
|
738
|
+
creator_display = (
|
|
739
|
+
f"{creator_identity} ({address_display})"
|
|
740
|
+
if creator_identity
|
|
741
|
+
else address_display
|
|
742
|
+
)
|
|
743
|
+
table.add_row(
|
|
744
|
+
"Creator",
|
|
745
|
+
f"[{COLORS.G.TEMPO}]{creator_display}[/{COLORS.G.TEMPO}]",
|
|
746
|
+
)
|
|
747
|
+
table.add_row(
|
|
748
|
+
"Funds Account",
|
|
749
|
+
f"[{COLORS.G.SUBHEAD_EX_2}]{crowdloan.funds_account}[/{COLORS.G.SUBHEAD_EX_2}]",
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
# FUNDING PROGRESS Section
|
|
753
|
+
table.add_section()
|
|
754
|
+
table.add_row("[cyan underline]FUNDING PROGRESS[/cyan underline]", "")
|
|
755
|
+
table.add_section()
|
|
756
|
+
|
|
757
|
+
raised_pct = (
|
|
758
|
+
(crowdloan.raised.tao / crowdloan.cap.tao * 100) if crowdloan.cap.tao > 0 else 0
|
|
759
|
+
)
|
|
760
|
+
progress_filled = int(raised_pct / 100 * 16)
|
|
761
|
+
progress_empty = 16 - progress_filled
|
|
762
|
+
progress_bar = f"[dark_sea_green]{'█' * progress_filled}[/dark_sea_green][grey35]{'░' * progress_empty}[/grey35]"
|
|
763
|
+
|
|
764
|
+
if verbose:
|
|
765
|
+
raised_str = f"τ {crowdloan.raised.tao:,.4f} / τ {crowdloan.cap.tao:,.4f}"
|
|
766
|
+
deposit_str = f"τ {crowdloan.deposit.tao:,.4f}"
|
|
767
|
+
min_contrib_str = f"τ {crowdloan.min_contribution.tao:,.4f}"
|
|
768
|
+
else:
|
|
769
|
+
raised_str = f"τ {millify_tao(crowdloan.raised.tao)} / τ {millify_tao(crowdloan.cap.tao)}"
|
|
770
|
+
deposit_str = f"τ {millify_tao(crowdloan.deposit.tao)}"
|
|
771
|
+
min_contrib_str = f"τ {millify_tao(crowdloan.min_contribution.tao)}"
|
|
772
|
+
|
|
773
|
+
table.add_row("Raised/Cap", raised_str)
|
|
774
|
+
table.add_row(
|
|
775
|
+
"Progress", f"{progress_bar} [dark_sea_green]{raised_pct:.2f}%[/dark_sea_green]"
|
|
776
|
+
)
|
|
777
|
+
table.add_row("Deposit", deposit_str)
|
|
778
|
+
table.add_row("Min Contribution", min_contrib_str)
|
|
779
|
+
|
|
780
|
+
# TIMELINE Section
|
|
781
|
+
table.add_section()
|
|
782
|
+
table.add_row("[cyan underline]TIMELINE[/cyan underline]", "")
|
|
783
|
+
table.add_section()
|
|
784
|
+
|
|
785
|
+
time_label = _time_remaining(crowdloan, current_block)
|
|
786
|
+
if "Closed" in time_label:
|
|
787
|
+
time_display = f"[{COLORS.G.SYM}]{time_label}[/{COLORS.G.SYM}]"
|
|
788
|
+
elif time_label == "due":
|
|
789
|
+
time_display = "[red]Due now[/red]"
|
|
790
|
+
else:
|
|
791
|
+
time_display = f"[{COLORS.S.ALPHA}]{time_label}[/{COLORS.S.ALPHA}]"
|
|
792
|
+
|
|
793
|
+
table.add_row("Ends at Block", f"{crowdloan.end}")
|
|
794
|
+
table.add_row("Current Block", f"{current_block}")
|
|
795
|
+
table.add_row("Time Remaining", time_display)
|
|
796
|
+
|
|
797
|
+
# PARTICIPATION Section
|
|
798
|
+
table.add_section()
|
|
799
|
+
table.add_row("[cyan underline]PARTICIPATION[/cyan underline]", "")
|
|
800
|
+
table.add_section()
|
|
801
|
+
|
|
802
|
+
table.add_row("Contributors", f"{crowdloan.contributors_count}")
|
|
803
|
+
|
|
804
|
+
if crowdloan.contributors_count > 0:
|
|
805
|
+
net_contributions = crowdloan.raised.tao - crowdloan.deposit.tao
|
|
806
|
+
avg_contribution = (
|
|
807
|
+
net_contributions / (crowdloan.contributors_count - 1)
|
|
808
|
+
if crowdloan.contributors_count > 1
|
|
809
|
+
else crowdloan.deposit.tao
|
|
810
|
+
)
|
|
811
|
+
if verbose:
|
|
812
|
+
avg_contrib_str = f"τ {avg_contribution:,.4f}"
|
|
813
|
+
else:
|
|
814
|
+
avg_contrib_str = f"τ {millify_tao(avg_contribution)}"
|
|
815
|
+
table.add_row("Avg Contribution", avg_contrib_str)
|
|
816
|
+
|
|
817
|
+
if user_contribution:
|
|
818
|
+
is_creator = wallet.coldkeypub.ss58_address == crowdloan.creator
|
|
819
|
+
if verbose:
|
|
820
|
+
user_contrib_str = f"τ {user_contribution.tao:,.4f}"
|
|
821
|
+
else:
|
|
822
|
+
user_contrib_str = f"τ {millify_tao(user_contribution.tao)}"
|
|
823
|
+
|
|
824
|
+
contrib_status = ""
|
|
825
|
+
if status == "Active" and not crowdloan.finalized:
|
|
826
|
+
if is_creator and user_contribution.tao > crowdloan.deposit.tao:
|
|
827
|
+
withdrawable = user_contribution.tao - crowdloan.deposit.tao
|
|
828
|
+
if verbose:
|
|
829
|
+
withdrawable_str = f"{withdrawable:,.4f}"
|
|
830
|
+
else:
|
|
831
|
+
withdrawable_str = f"{millify_tao(withdrawable)}"
|
|
832
|
+
contrib_status = (
|
|
833
|
+
f" [yellow](τ {withdrawable_str} withdrawable)[/yellow]"
|
|
834
|
+
)
|
|
835
|
+
elif not is_creator:
|
|
836
|
+
contrib_status = " [yellow](withdrawable)[/yellow]"
|
|
837
|
+
elif status == "Closed":
|
|
838
|
+
contrib_status = " [green](refundable)[/green]"
|
|
839
|
+
|
|
840
|
+
your_contrib_value = f"{user_contrib_str}{contrib_status}"
|
|
841
|
+
if is_creator:
|
|
842
|
+
your_contrib_value += " [dim](You are the creator)[/dim]"
|
|
843
|
+
table.add_row("Your Contribution", your_contrib_value)
|
|
844
|
+
|
|
845
|
+
# TARGET Section
|
|
846
|
+
table.add_section()
|
|
847
|
+
table.add_row("[cyan underline]TARGET[/cyan underline]", "")
|
|
848
|
+
table.add_section()
|
|
849
|
+
|
|
850
|
+
if crowdloan.target_address:
|
|
851
|
+
target_identity = identity_map.get(crowdloan.target_address)
|
|
852
|
+
address_display = (
|
|
853
|
+
crowdloan.target_address if verbose else _shorten(crowdloan.target_address)
|
|
854
|
+
)
|
|
855
|
+
target_display = (
|
|
856
|
+
f"{target_identity} ({address_display})"
|
|
857
|
+
if target_identity
|
|
858
|
+
else address_display
|
|
859
|
+
)
|
|
860
|
+
else:
|
|
861
|
+
target_display = (
|
|
862
|
+
f"[{COLORS.G.SUBHEAD_MAIN}]Not specified[/{COLORS.G.SUBHEAD_MAIN}]"
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
table.add_row("Address", target_display)
|
|
866
|
+
|
|
867
|
+
table.add_section()
|
|
868
|
+
table.add_row("[cyan underline]CALL DETAILS[/cyan underline]", "")
|
|
869
|
+
table.add_section()
|
|
870
|
+
|
|
871
|
+
has_call_display = (
|
|
872
|
+
f"[{COLORS.G.SUCCESS}]Yes[/{COLORS.G.SUCCESS}]"
|
|
873
|
+
if crowdloan.has_call
|
|
874
|
+
else f"[{COLORS.G.SYM}]No[/{COLORS.G.SYM}]"
|
|
875
|
+
)
|
|
876
|
+
table.add_row("Has Call", has_call_display)
|
|
877
|
+
|
|
878
|
+
if crowdloan.has_call and crowdloan.call_details:
|
|
879
|
+
pallet = crowdloan.call_details.get("pallet", "Unknown")
|
|
880
|
+
method = crowdloan.call_details.get("method", "Unknown")
|
|
881
|
+
args = crowdloan.call_details.get("args", {})
|
|
882
|
+
|
|
883
|
+
if pallet == "MeshtensorModule" and method == "register_leased_network":
|
|
884
|
+
table.add_row("Type", "[magenta]Subnet Leasing[/magenta]")
|
|
885
|
+
emissions_share = args.get("emissions_share", {}).get("value")
|
|
886
|
+
if emissions_share is not None:
|
|
887
|
+
table.add_row("Emissions Share", f"[cyan]{emissions_share}%[/cyan]")
|
|
888
|
+
|
|
889
|
+
end_block = args.get("end_block", {}).get("value")
|
|
890
|
+
if end_block:
|
|
891
|
+
table.add_row("Lease Ends", f"Block {end_block}")
|
|
892
|
+
else:
|
|
893
|
+
table.add_row("Lease Duration", "[green]Perpetual[/green]")
|
|
894
|
+
else:
|
|
895
|
+
table.add_row("Pallet", pallet)
|
|
896
|
+
table.add_row("Method", method)
|
|
897
|
+
if args:
|
|
898
|
+
for arg_name, arg_data in args.items():
|
|
899
|
+
if isinstance(arg_data, dict):
|
|
900
|
+
display_value = arg_data.get("value")
|
|
901
|
+
arg_type = arg_data.get("type")
|
|
902
|
+
else:
|
|
903
|
+
display_value = arg_data
|
|
904
|
+
arg_type = None
|
|
905
|
+
|
|
906
|
+
if arg_type:
|
|
907
|
+
table.add_row(
|
|
908
|
+
f"{arg_name} [{arg_type}]",
|
|
909
|
+
str(display_value),
|
|
910
|
+
)
|
|
911
|
+
else:
|
|
912
|
+
table.add_row(arg_name, str(display_value))
|
|
913
|
+
|
|
914
|
+
# CONTRIBUTORS Section (if requested)
|
|
915
|
+
if show_contributors:
|
|
916
|
+
table.add_section()
|
|
917
|
+
table.add_row("[cyan underline]CONTRIBUTORS[/cyan underline]", "")
|
|
918
|
+
table.add_section()
|
|
919
|
+
|
|
920
|
+
# Fetch contributors
|
|
921
|
+
contributor_contributions = await meshtensor.get_crowdloan_contributors(
|
|
922
|
+
crowdloan_id
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
if contributor_contributions:
|
|
926
|
+
contributors_list = list(contributor_contributions.keys())
|
|
927
|
+
contributor_data = []
|
|
928
|
+
total_contributed = Balance.from_tao(0)
|
|
929
|
+
|
|
930
|
+
for contributor_address in contributors_list:
|
|
931
|
+
contribution_amount = contributor_contributions[contributor_address]
|
|
932
|
+
total_contributed += contribution_amount
|
|
933
|
+
identity = all_identities.get(contributor_address)
|
|
934
|
+
identity_name = None
|
|
935
|
+
if identity:
|
|
936
|
+
identity_name = identity.get("name") or identity.get("display")
|
|
937
|
+
|
|
938
|
+
contributor_data.append(
|
|
939
|
+
{
|
|
940
|
+
"address": contributor_address,
|
|
941
|
+
"identity": identity_name,
|
|
942
|
+
"contribution": contribution_amount,
|
|
943
|
+
}
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
# Sort by contribution amount (descending)
|
|
947
|
+
contributor_data.sort(key=lambda x: x["contribution"].meshlet, reverse=True)
|
|
948
|
+
|
|
949
|
+
# Display contributors in table
|
|
950
|
+
for rank, data in enumerate(contributor_data[:10], start=1): # Show top 10
|
|
951
|
+
address_display = (
|
|
952
|
+
data["address"] if verbose else _shorten(data["address"])
|
|
953
|
+
)
|
|
954
|
+
identity_display = (
|
|
955
|
+
data["identity"] if data["identity"] else "[dim]-[/dim]"
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
if data["identity"]:
|
|
959
|
+
if verbose:
|
|
960
|
+
contributor_display = f"{identity_display} ({address_display})"
|
|
961
|
+
else:
|
|
962
|
+
contributor_display = f"{identity_display} ({address_display})"
|
|
963
|
+
else:
|
|
964
|
+
contributor_display = address_display
|
|
965
|
+
|
|
966
|
+
if verbose:
|
|
967
|
+
contribution_display = f"τ {data['contribution'].tao:,.4f}"
|
|
968
|
+
else:
|
|
969
|
+
contribution_display = f"τ {millify_tao(data['contribution'].tao)}"
|
|
970
|
+
|
|
971
|
+
percentage = (
|
|
972
|
+
(data["contribution"].meshlet / total_contributed.meshlet * 100)
|
|
973
|
+
if total_contributed.meshlet > 0
|
|
974
|
+
else 0
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
table.add_row(
|
|
978
|
+
f"#{rank}",
|
|
979
|
+
f"{contributor_display:<70} - {contribution_display} ({percentage:.2f}%)",
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
if len(contributor_data) > 10:
|
|
983
|
+
table.add_row(
|
|
984
|
+
"",
|
|
985
|
+
f"[dim]... and {len(contributor_data) - 10} more contributors[/dim]",
|
|
986
|
+
)
|
|
987
|
+
else:
|
|
988
|
+
table.add_row("", "[dim]No contributors yet[/dim]")
|
|
989
|
+
|
|
990
|
+
console.print(table)
|
|
991
|
+
return True, f"Displayed info for crowdloan #{crowdloan_id}"
|