|
14 | 14 | from bittensor_wallet import Wallet |
15 | 15 | from bittensor_wallet.bittensor_wallet import Keypair |
16 | 16 | from bittensor_wallet.utils import SS58_FORMAT |
17 | | -from scalecodec import GenericCall |
| 17 | +from scalecodec import GenericCall, ScaleBytes |
18 | 18 | import typer |
19 | 19 | import websockets |
20 | 20 |
|
|
30 | 30 | SubnetState, |
31 | 31 | MetagraphInfo, |
32 | 32 | SimSwapResult, |
| 33 | + CrowdloanData, |
33 | 34 | ) |
34 | 35 | from bittensor_cli.src import DelegatesDetails |
35 | 36 | from bittensor_cli.src.bittensor.balances import Balance, fixed_to_float |
@@ -167,6 +168,44 @@ async def query( |
167 | 168 | else: |
168 | 169 | return result |
169 | 170 |
|
| 171 | + async def _decode_inline_call( |
| 172 | + self, |
| 173 | + call_option: Any, |
| 174 | + block_hash: Optional[str] = None, |
| 175 | + ) -> Optional[dict[str, Any]]: |
| 176 | + """ |
| 177 | + Decode an `Option<BoundedCall>` returned from storage into a structured dictionary. |
| 178 | + """ |
| 179 | + if not call_option or "Inline" not in call_option: |
| 180 | + return None |
| 181 | + inline_bytes = bytes(call_option["Inline"][0][0]) |
| 182 | + call_obj = await self.substrate.create_scale_object( |
| 183 | + "Call", |
| 184 | + data=ScaleBytes(inline_bytes), |
| 185 | + block_hash=block_hash, |
| 186 | + ) |
| 187 | + call_value = call_obj.decode() |
| 188 | + |
| 189 | + if not isinstance(call_value, dict): |
| 190 | + return None |
| 191 | + |
| 192 | + call_args = call_value.get("call_args") or [] |
| 193 | + args_map: dict[str, dict[str, Any]] = {} |
| 194 | + for arg in call_args: |
| 195 | + if isinstance(arg, dict) and arg.get("name"): |
| 196 | + args_map[arg["name"]] = { |
| 197 | + "type": arg.get("type"), |
| 198 | + "value": arg.get("value"), |
| 199 | + } |
| 200 | + |
| 201 | + return { |
| 202 | + "call_index": call_value.get("call_index"), |
| 203 | + "pallet": call_value.get("call_module"), |
| 204 | + "method": call_value.get("call_function"), |
| 205 | + "args": args_map, |
| 206 | + "hash": call_value.get("call_hash"), |
| 207 | + } |
| 208 | + |
170 | 209 | async def get_all_subnet_netuids( |
171 | 210 | self, block_hash: Optional[str] = None |
172 | 211 | ) -> list[int]: |
@@ -1693,6 +1732,101 @@ async def get_scheduled_coldkey_swap( |
1693 | 1732 | keys_pending_swap.append(decode_account_id(ss58)) |
1694 | 1733 | return keys_pending_swap |
1695 | 1734 |
|
| 1735 | + async def get_crowdloans( |
| 1736 | + self, block_hash: Optional[str] = None |
| 1737 | + ) -> list[CrowdloanData]: |
| 1738 | + """Retrieves all crowdloans from the network. |
| 1739 | +
|
| 1740 | + Args: |
| 1741 | + block_hash (Optional[str]): The blockchain block hash at which to perform the query. |
| 1742 | +
|
| 1743 | + Returns: |
| 1744 | + dict[int, CrowdloanData]: A dictionary mapping crowdloan IDs to CrowdloanData objects |
| 1745 | + containing details such as creator, deposit, cap, raised amount, and finalization status. |
| 1746 | +
|
| 1747 | + This function fetches information about all crowdloans |
| 1748 | + """ |
| 1749 | + crowdloans_data = await self.substrate.query_map( |
| 1750 | + module="Crowdloan", |
| 1751 | + storage_function="Crowdloans", |
| 1752 | + block_hash=block_hash, |
| 1753 | + fully_exhaust=True, |
| 1754 | + ) |
| 1755 | + crowdloans = {} |
| 1756 | + async for fund_id, fund_info in crowdloans_data: |
| 1757 | + decoded_call = await self._decode_inline_call( |
| 1758 | + fund_info["call"], |
| 1759 | + block_hash=block_hash, |
| 1760 | + ) |
| 1761 | + info_dict = dict(fund_info.value) |
| 1762 | + info_dict["call_details"] = decoded_call |
| 1763 | + crowdloans[fund_id] = CrowdloanData.from_any(info_dict) |
| 1764 | + |
| 1765 | + return crowdloans |
| 1766 | + |
| 1767 | + async def get_single_crowdloan( |
| 1768 | + self, |
| 1769 | + crowdloan_id: int, |
| 1770 | + block_hash: Optional[str] = None, |
| 1771 | + ) -> Optional[CrowdloanData]: |
| 1772 | + """Retrieves detailed information about a specific crowdloan. |
| 1773 | +
|
| 1774 | + Args: |
| 1775 | + crowdloan_id (int): The unique identifier of the crowdloan to retrieve. |
| 1776 | + block_hash (Optional[str]): The blockchain block hash at which to perform the query. |
| 1777 | +
|
| 1778 | + Returns: |
| 1779 | + Optional[CrowdloanData]: A CrowdloanData object containing the crowdloan's details if found, |
| 1780 | + None if the crowdloan does not exist. |
| 1781 | +
|
| 1782 | + The returned data includes crowdloan details such as funding targets, |
| 1783 | + contribution minimums, timeline, and current funding status |
| 1784 | + """ |
| 1785 | + crowdloan_info = await self.query( |
| 1786 | + module="Crowdloan", |
| 1787 | + storage_function="Crowdloans", |
| 1788 | + params=[crowdloan_id], |
| 1789 | + block_hash=block_hash, |
| 1790 | + ) |
| 1791 | + if crowdloan_info: |
| 1792 | + decoded_call = await self._decode_inline_call( |
| 1793 | + crowdloan_info.get("call"), |
| 1794 | + block_hash=block_hash, |
| 1795 | + ) |
| 1796 | + crowdloan_info["call_details"] = decoded_call |
| 1797 | + return CrowdloanData.from_any(crowdloan_info) |
| 1798 | + return None |
| 1799 | + |
| 1800 | + async def get_crowdloan_contribution( |
| 1801 | + self, |
| 1802 | + crowdloan_id: int, |
| 1803 | + contributor: str, |
| 1804 | + block_hash: Optional[str] = None, |
| 1805 | + ) -> Optional[Balance]: |
| 1806 | + """Retrieves a user's contribution to a specific crowdloan. |
| 1807 | +
|
| 1808 | + Args: |
| 1809 | + crowdloan_id (int): The ID of the crowdloan. |
| 1810 | + contributor (str): The SS58 address of the contributor. |
| 1811 | + block_hash (Optional[str]): The blockchain block hash at which to perform the query. |
| 1812 | +
|
| 1813 | + Returns: |
| 1814 | + Optional[Balance]: The contribution amount as a Balance object if found, None otherwise. |
| 1815 | +
|
| 1816 | + This function queries the Contributions storage to find the amount a specific address |
| 1817 | + has contributed to a given crowdloan. |
| 1818 | + """ |
| 1819 | + contribution = await self.query( |
| 1820 | + module="Crowdloan", |
| 1821 | + storage_function="Contributions", |
| 1822 | + params=[crowdloan_id, contributor], |
| 1823 | + block_hash=block_hash, |
| 1824 | + ) |
| 1825 | + |
| 1826 | + if contribution: |
| 1827 | + return Balance.from_rao(contribution) |
| 1828 | + return None |
| 1829 | + |
1696 | 1830 | async def get_coldkey_swap_schedule_duration( |
1697 | 1831 | self, |
1698 | 1832 | block_hash: Optional[str] = None, |
|
0 commit comments