Skip to content

Commit 08067b1

Browse files
authored
Merge pull request #776 from circlecrystalin/feat/crownload-enhancements
feat: Add crowdloan contributors command and enhance create/view functionality
2 parents 6042e64 + b1207a4 commit 08067b1

File tree

7 files changed

+966
-38
lines changed

7 files changed

+966
-38
lines changed

bittensor_cli/cli.py

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
view as view_crowdloan,
8787
update as crowd_update,
8888
refund as crowd_refund,
89+
contributors as crowd_contributors,
8990
)
9091
from bittensor_cli.src.commands.liquidity.utils import (
9192
prompt_liquidity,
@@ -1334,6 +1335,9 @@ def __init__(self):
13341335
self.crowd_app.command("info", rich_help_panel=HELP_PANELS["CROWD"]["INFO"])(
13351336
self.crowd_info
13361337
)
1338+
self.crowd_app.command(
1339+
"contributors", rich_help_panel=HELP_PANELS["CROWD"]["INFO"]
1340+
)(self.crowd_contributors)
13371341
self.crowd_app.command(
13381342
"create", rich_help_panel=HELP_PANELS["CROWD"]["INITIATOR"]
13391343
)(self.crowd_create)
@@ -2904,14 +2908,15 @@ def wallet_inspect(
29042908
),
29052909
wallet_name: str = Options.wallet_name,
29062910
wallet_path: str = Options.wallet_path,
2911+
wallet_hotkey: str = Options.wallet_hotkey,
29072912
network: Optional[list[str]] = Options.network,
29082913
netuids: str = Options.netuids,
29092914
quiet: bool = Options.quiet,
29102915
verbose: bool = Options.verbose,
29112916
json_output: bool = Options.json_output,
29122917
):
29132918
"""
2914-
Displays the details of the user's wallet (coldkey) on the Bittensor network.
2919+
Displays the details of the user's wallet pairs (coldkey, hotkey) on the Bittensor network.
29152920
29162921
The output is presented as a table with the below columns:
29172922
@@ -2956,7 +2961,7 @@ def wallet_inspect(
29562961
ask_for = [WO.NAME, WO.PATH] if not all_wallets else [WO.PATH]
29572962
validate = WV.WALLET if not all_wallets else WV.NONE
29582963
wallet = self.wallet_ask(
2959-
wallet_name, wallet_path, None, ask_for=ask_for, validate=validate
2964+
wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate
29602965
)
29612966

29622967
self.initialize_chain(network)
@@ -8745,6 +8750,31 @@ def crowd_list(
87458750
quiet: bool = Options.quiet,
87468751
verbose: bool = Options.verbose,
87478752
json_output: bool = Options.json_output,
8753+
status: Optional[str] = typer.Option(
8754+
None,
8755+
"--status",
8756+
help="Filter by status: active, funded, closed, finalized",
8757+
),
8758+
type_filter: Optional[str] = typer.Option(
8759+
None,
8760+
"--type",
8761+
help="Filter by type: subnet, fundraising",
8762+
),
8763+
sort_by: Optional[str] = typer.Option(
8764+
None,
8765+
"--sort-by",
8766+
help="Sort by: raised, end, contributors, id",
8767+
),
8768+
sort_order: Optional[str] = typer.Option(
8769+
None,
8770+
"--sort-order",
8771+
help="Sort order: asc, desc (default: desc for raised, asc for id)",
8772+
),
8773+
search_creator: Optional[str] = typer.Option(
8774+
None,
8775+
"--search-creator",
8776+
help="Search by creator address or identity name",
8777+
),
87488778
):
87498779
"""
87508780
List crowdloans together with their funding progress and key metadata.
@@ -8754,19 +8784,34 @@ def crowd_list(
87548784
or a general fundraising crowdloan.
87558785
87568786
Use `--verbose` for full-precision amounts and longer addresses.
8787+
Use `--status` to filter by status (active, funded, closed, finalized).
8788+
Use `--type` to filter by type (subnet, fundraising).
8789+
Use `--sort-by` and `--sort-order` to sort results.
8790+
Use `--search-creator` to search by creator address or identity name.
87578791
87588792
EXAMPLES
87598793
87608794
[green]$[/green] btcli crowd list
87618795
87628796
[green]$[/green] btcli crowd list --verbose
8797+
8798+
[green]$[/green] btcli crowd list --status active --type subnet
8799+
8800+
[green]$[/green] btcli crowd list --sort-by raised --sort-order desc
8801+
8802+
[green]$[/green] btcli crowd list --search-creator "5D..."
87638803
"""
87648804
self.verbosity_handler(quiet, verbose, json_output, prompt=False)
87658805
return self._run_command(
87668806
view_crowdloan.list_crowdloans(
87678807
subtensor=self.initialize_chain(network),
87688808
verbose=verbose,
87698809
json_output=json_output,
8810+
status_filter=status,
8811+
type_filter=type_filter,
8812+
sort_by=sort_by,
8813+
sort_order=sort_order,
8814+
search_creator=search_creator,
87708815
)
87718816
)
87728817

@@ -8786,17 +8831,25 @@ def crowd_info(
87868831
quiet: bool = Options.quiet,
87878832
verbose: bool = Options.verbose,
87888833
json_output: bool = Options.json_output,
8834+
show_contributors: bool = typer.Option(
8835+
False,
8836+
"--show-contributors",
8837+
help="Show contributor list with identities.",
8838+
),
87898839
):
87908840
"""
87918841
Display detailed information about a specific crowdloan.
87928842
87938843
Includes funding progress, target account, and call details among other information.
8844+
Use `--show-contributors` to display the list of contributors (default: false).
87948845
87958846
EXAMPLES
87968847
87978848
[green]$[/green] btcli crowd info --id 0
87988849
87998850
[green]$[/green] btcli crowd info --id 1 --verbose
8851+
8852+
[green]$[/green] btcli crowd info --id 0 --show-contributors true
88008853
"""
88018854
self.verbosity_handler(quiet, verbose, json_output, prompt=False)
88028855

@@ -8824,6 +8877,53 @@ def crowd_info(
88248877
wallet=wallet,
88258878
verbose=verbose,
88268879
json_output=json_output,
8880+
show_contributors=show_contributors,
8881+
)
8882+
)
8883+
8884+
def crowd_contributors(
8885+
self,
8886+
crowdloan_id: Optional[int] = typer.Option(
8887+
None,
8888+
"--crowdloan-id",
8889+
"--crowdloan_id",
8890+
"--id",
8891+
help="The ID of the crowdloan to list contributors for",
8892+
),
8893+
network: Optional[list[str]] = Options.network,
8894+
quiet: bool = Options.quiet,
8895+
verbose: bool = Options.verbose,
8896+
json_output: bool = Options.json_output,
8897+
):
8898+
"""
8899+
List all contributors to a specific crowdloan.
8900+
8901+
Shows contributor addresses, contribution amounts, identity names, and percentages.
8902+
Contributors are sorted by contribution amount (highest first).
8903+
8904+
EXAMPLES
8905+
8906+
[green]$[/green] btcli crowd contributors --id 0
8907+
8908+
[green]$[/green] btcli crowd contributors --id 1 --verbose
8909+
8910+
[green]$[/green] btcli crowd contributors --id 2 --json-output
8911+
"""
8912+
self.verbosity_handler(quiet, verbose, json_output, prompt=False)
8913+
8914+
if crowdloan_id is None:
8915+
crowdloan_id = IntPrompt.ask(
8916+
f"Enter the [{COLORS.G.SUBHEAD_MAIN}]crowdloan id[/{COLORS.G.SUBHEAD_MAIN}]",
8917+
default=None,
8918+
show_default=False,
8919+
)
8920+
8921+
return self._run_command(
8922+
crowd_contributors.list_contributors(
8923+
subtensor=self.initialize_chain(network),
8924+
crowdloan_id=crowdloan_id,
8925+
verbose=verbose,
8926+
json_output=json_output,
88278927
)
88288928
)
88298929

@@ -8886,6 +8986,21 @@ def crowd_create(
88868986
help="Block number when subnet lease ends (omit for perpetual lease).",
88878987
min=1,
88888988
),
8989+
custom_call_pallet: Optional[str] = typer.Option(
8990+
None,
8991+
"--custom-call-pallet",
8992+
help="Pallet name for custom Substrate call to attach to crowdloan.",
8993+
),
8994+
custom_call_method: Optional[str] = typer.Option(
8995+
None,
8996+
"--custom-call-method",
8997+
help="Method name for custom Substrate call to attach to crowdloan.",
8998+
),
8999+
custom_call_args: Optional[str] = typer.Option(
9000+
None,
9001+
"--custom-call-args",
9002+
help='JSON string of arguments for custom call (e.g., \'{"arg1": "value1", "arg2": 123}\').',
9003+
),
88899004
prompt: bool = Options.prompt,
88909005
wait_for_inclusion: bool = Options.wait_for_inclusion,
88919006
wait_for_finalization: bool = Options.wait_for_finalization,
@@ -8898,6 +9013,7 @@ def crowd_create(
88989013
Create a crowdloan that can either:
88999014
1. Raise funds for a specific address (general fundraising)
89009015
2. Create a new leased subnet where contributors receive emissions
9016+
3. Attach any custom Substrate call (using --custom-call-pallet, --custom-call-method, --custom-call-args)
89019017
89029018
EXAMPLES
89039019
@@ -8909,6 +9025,9 @@ def crowd_create(
89099025
89109026
Subnet lease ending at block 500000:
89119027
[green]$[/green] btcli crowd create --subnet-lease --emissions-share 25 --lease-end-block 500000
9028+
9029+
Custom call:
9030+
[green]$[/green] btcli crowd create --deposit 10 --cap 1000 --duration 1000 --min-contribution 1 --custom-call-pallet "SomeModule" --custom-call-method "some_method" --custom-call-args '{"param1": "value", "param2": 42}'
89129031
"""
89139032
self.verbosity_handler(quiet, verbose, json_output, prompt)
89149033
proxy = self.is_valid_proxy_name_or_ss58(proxy, announce_only)
@@ -8933,6 +9052,9 @@ def crowd_create(
89339052
subnet_lease=subnet_lease,
89349053
emissions_share=emissions_share,
89359054
lease_end_block=lease_end_block,
9055+
custom_call_pallet=custom_call_pallet,
9056+
custom_call_method=custom_call_method,
9057+
custom_call_args=custom_call_args,
89369058
wait_for_inclusion=wait_for_inclusion,
89379059
wait_for_finalization=wait_for_finalization,
89389060
prompt=prompt,

bittensor_cli/src/bittensor/subtensor_interface.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,6 +1925,43 @@ async def get_crowdloan_contribution(
19251925
return Balance.from_rao(contribution)
19261926
return None
19271927

1928+
async def get_crowdloan_contributors(
1929+
self,
1930+
crowdloan_id: int,
1931+
block_hash: Optional[str] = None,
1932+
) -> dict[str, Balance]:
1933+
"""Retrieves all contributors and their contributions for a specific crowdloan.
1934+
1935+
Args:
1936+
crowdloan_id (int): The ID of the crowdloan.
1937+
block_hash (Optional[str]): The blockchain block hash at which to perform the query.
1938+
1939+
Returns:
1940+
dict[str, Balance]: A dictionary mapping contributor SS58 addresses to their
1941+
contribution amounts as Balance objects.
1942+
1943+
This function queries the Contributions storage map with the crowdloan_id as the first key
1944+
to retrieve all contributors and their contribution amounts.
1945+
"""
1946+
contributors_data = await self.substrate.query_map(
1947+
module="Crowdloan",
1948+
storage_function="Contributions",
1949+
params=[crowdloan_id],
1950+
block_hash=block_hash,
1951+
fully_exhaust=True,
1952+
)
1953+
1954+
contributor_contributions = {}
1955+
async for contributor_key, contribution_amount in contributors_data:
1956+
try:
1957+
contributor_address = decode_account_id(contributor_key[0])
1958+
contribution_balance = Balance.from_rao(contribution_amount.value)
1959+
contributor_contributions[contributor_address] = contribution_balance
1960+
except Exception:
1961+
continue
1962+
1963+
return contributor_contributions
1964+
19281965
async def get_coldkey_swap_schedule_duration(
19291966
self,
19301967
block_hash: Optional[str] = None,
@@ -2501,6 +2538,36 @@ async def get_mev_shield_current_key(
25012538

25022539
return public_key_bytes
25032540

2541+
async def compose_custom_crowdloan_call(
2542+
self,
2543+
pallet_name: str,
2544+
method_name: str,
2545+
call_params: dict,
2546+
block_hash: Optional[str] = None,
2547+
) -> tuple[Optional[GenericCall], Optional[str]]:
2548+
"""
2549+
Compose a custom Substrate call.
2550+
2551+
Args:
2552+
pallet_name: Name of the pallet/module
2553+
method_name: Name of the method/function
2554+
call_params: Dictionary of call parameters
2555+
block_hash: Optional block hash for the query
2556+
2557+
Returns:
2558+
Tuple of (GenericCall or None, error_message or None)
2559+
"""
2560+
try:
2561+
call = await self.substrate.compose_call(
2562+
call_module=pallet_name,
2563+
call_function=method_name,
2564+
call_params=call_params,
2565+
block_hash=block_hash,
2566+
)
2567+
return call, None
2568+
except Exception as e:
2569+
return None, f"Failed to compose call: {str(e)}"
2570+
25042571

25052572
async def best_connection(networks: list[str]):
25062573
"""

0 commit comments

Comments
 (0)