diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 1d8c984649..58825cd451 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -67,6 +67,7 @@ remove_liquidity_extrinsic, toggle_user_liquidity_extrinsic, ) +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.asyncex.move_stake import ( transfer_stake_extrinsic, swap_stake_extrinsic, @@ -98,8 +99,8 @@ ) from bittensor.core.extrinsics.asyncex.serving import ( publish_metadata_extrinsic, + serve_axon_extrinsic, ) -from bittensor.core.extrinsics.asyncex.serving import serve_axon_extrinsic from bittensor.core.extrinsics.asyncex.staking import ( add_stake_extrinsic, add_stake_multiple_extrinsic, @@ -122,10 +123,11 @@ from bittensor.core.extrinsics.utils import get_transfer_fn_params from bittensor.core.metagraph import AsyncMetagraph from bittensor.core.settings import ( - version_as_int, + DEFAULT_MEV_PROTECTION, DEFAULT_PERIOD, - TYPE_REGISTRY, TAO_APP_BLOCK_EXPLORER, + TYPE_REGISTRY, + version_as_int, ) from bittensor.core.types import ( BlockInfo, @@ -148,23 +150,24 @@ ) from bittensor.utils.balance import ( Balance, - fixed_to_float, check_balance_amount, + fixed_to_float, ) from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import ( + LiquidityPosition, calculate_fees, get_fees, - tick_to_price, price_to_tick, - LiquidityPosition, + tick_to_price, ) if TYPE_CHECKING: + from async_substrate_interface import AsyncQueryMapResult from async_substrate_interface.types import ScaleObj from bittensor_wallet import Keypair, Wallet + from bittensor.core.axon import Axon - from async_substrate_interface import AsyncQueryMapResult class AsyncSubtensor(SubtensorMixin): @@ -2949,6 +2952,212 @@ async def get_metagraph_info( return MetagraphInfo.from_dict(query.value) + async def get_mev_shield_current_key( + self, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional[bytes]: + """ + Retrieves the CurrentKey from the MevShield pallet storage. + + The CurrentKey contains the ML-KEM-768 public key that is currently being used for encryption in this block. + This key is rotated from NextKey at the beginning of each block. + + Parameters: + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + The ML-KEM-768 public key as bytes (1184 bytes for ML-KEM-768) + + Note: + If CurrentKey is not set (None in storage), this function returns None. This can happen if no validator has + announced a key yet. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + query = await self.substrate.query( + module="MevShield", + storage_function="CurrentKey", + block_hash=block_hash, + ) + + if query is None: + return None + + public_key_bytes = bytes(next(iter(query))) + + # Validate public_key size for ML-KEM-768 (must be exactly 1184 bytes) + MLKEM768_PUBLIC_KEY_SIZE = 1184 + if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: + raise ValueError( + f"Invalid ML-KEM-768 public key size: {len(public_key_bytes)} bytes. " + f"Expected exactly {MLKEM768_PUBLIC_KEY_SIZE} bytes." + ) + + return public_key_bytes + + async def get_mev_shield_next_key( + self, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional[bytes]: + """ + Retrieves the NextKey from the MevShield pallet storage. + + The NextKey contains the ML-KEM-768 public key that will be used for encryption in the next block. This key is + rotated from NextKey to CurrentKey at the beginning of each block. + + Parameters: + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + The ML-KEM-768 public key as bytes (1184 bytes for ML-KEM-768) + + Note: + If NextKey is not set (None in storage), this function returns None. This can happen if no validator has + announced the next key yet. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + query = await self.substrate.query( + module="MevShield", + storage_function="NextKey", + block_hash=block_hash, + ) + + if query is None: + return None + + public_key_bytes = bytes(next(iter(query))) + + # Validate public_key size for ML-KEM-768 (must be exactly 1184 bytes) + MLKEM768_PUBLIC_KEY_SIZE = 1184 + if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: + raise ValueError( + f"Invalid ML-KEM-768 public key size: {len(public_key_bytes)} bytes. " + f"Expected exactly {MLKEM768_PUBLIC_KEY_SIZE} bytes." + ) + + return public_key_bytes + + async def get_mev_shield_submission( + self, + submission_id: str, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional[dict[str, str | int | bytes]]: + """ + Retrieves Submission from the MevShield pallet storage. + + If submission_id is provided, returns a single submission. If submission_id is None, returns all submissions from + the storage map. + + Parameters: + submission_id: The hash ID of the submission. Can be a hex string with "0x" prefix or bytes. If None, + returns all submissions. + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + If submission_id is provided: A dictionary containing the submission data if found, None otherwise. The + dictionary contains: + - author: The SS58 address of the account that submitted the encrypted extrinsic + - commitment: The blake2_256 hash of the payload_core (as hex string with "0x" prefix) + - ciphertext: The encrypted blob as bytes (format: [u16 kem_len][kem_ct][nonce24][aead_ct]) + - submitted_in: The block number when the submission was created + + If submission_id is None: A dictionary mapping submission IDs (as hex strings) to submission dictionaries. + + Note: + If a specific submission does not exist in storage, this function returns None. If querying all submissions + and none exist, returns an empty dictionary. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + submission_id = ( + submission_id[2:] if submission_id.startswith("0x") else submission_id + ) + submission_id_bytes = bytes.fromhex(submission_id) + + query = await self.substrate.query( + module="MevShield", + storage_function="Submissions", + params=[submission_id_bytes], + block_hash=block_hash, + ) + + if query is None or not isinstance(query, dict): + return None + + autor = decode_account_id(query.get("author")) + commitment = bytes(query.get("commitment")[0]) + ciphertext = bytes(query.get("ciphertext")[0]) + submitted_in = query.get("submitted_in") + + return { + "author": autor, + "commitment": commitment, + "ciphertext": ciphertext, + "submitted_in": submitted_in, + } + + async def get_mev_shield_submissions( + self, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional[dict[str, dict[str, str | int]]]: + """ + Retrieves all encrypted submissions from the MevShield pallet storage. + + This function queries the MevShield.Submissions storage map and returns all pending encrypted submissions that + have been submitted via submit_encrypted but not yet executed via execute_revealed. + + Parameters: + block: The blockchain block number for the query. If None, uses the current block. + block_hash: The hash of the block to retrieve the submissions from. Do not specify if using block or reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + A dictionary mapping wrapper_id (as hex string with "0x" prefix) to submission data dictionaries. Each + submission dictionary contains: + - author: The SS58 address of the account that submitted the encrypted extrinsic + - commitment: The blake2_256 hash of the payload_core as bytes (32 bytes) + - ciphertext: The encrypted blob as bytes (format: [u16 kem_len][kem_ct][nonce24][aead_ct]) + - submitted_in: The block number when the submission was created + + Returns None if no submissions exist in storage at the specified block. + + Note: + Submissions are automatically pruned after KEY_EPOCH_HISTORY blocks (100 blocks) by the pallet's + on_initialize hook. Only submissions that have been submitted but not yet executed will be present in + storage. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + query = await self.substrate.query_map( + module="MevShield", + storage_function="Submissions", + block_hash=block_hash, + ) + + result = {} + async for q in query: + key, value = q + value = value.value + result["0x" + bytes(key[0]).hex()] = { + "author": decode_account_id(value.get("author")), + "commitment": bytes(value.get("commitment")[0]), + "ciphertext": bytes(value.get("ciphertext")[0]), + "submitted_in": value.get("submitted_in"), + } + + return result if result else None + async def get_minimum_required_stake(self): """ Returns the minimum required stake for nominators in the Subtensor network. @@ -5603,10 +5812,13 @@ async def add_stake( safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified @@ -5624,12 +5836,16 @@ async def add_stake( exceed the price tolerance. If false, the entire stake fails if it would exceed the tolerance. rate_tolerance: The maximum allowed price change ratio when staking. For example, 0.005 = 0.5% maximum price increase. Only used when safe_staking is True. + mev_protection: If True, encrypts and submits the staking transaction through the MEV Shield pallet to + protect against front-running and MEV attacks. The transaction remains encrypted in the mempool until + validators decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5648,10 +5864,12 @@ async def add_stake( safe_staking=safe_staking, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def add_liquidity( @@ -5662,10 +5880,13 @@ async def add_liquidity( price_low: Balance, price_high: Balance, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds liquidity to the specified price range. @@ -5677,12 +5898,16 @@ async def add_liquidity( price_low: The lower bound of the price tick range. In TAO. price_high: The upper bound of the price tick range. In TAO. hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5698,10 +5923,12 @@ async def add_liquidity( price_low=price_low, price_high=price_high, hotkey_ss58=hotkey_ss58, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def add_stake_multiple( @@ -5710,10 +5937,13 @@ async def add_stake_multiple( netuids: UIDs, hotkey_ss58s: list[str], amounts: list[Balance], + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds stakes to multiple neurons identified by their hotkey SS58 addresses. @@ -5724,12 +5954,16 @@ async def add_stake_multiple( netuids: List of subnet UIDs. hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. amounts: List of corresponding TAO amounts to bet for each netuid and hotkey. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5743,10 +5977,12 @@ async def add_stake_multiple( netuids=netuids, hotkey_ss58s=hotkey_ss58s, amounts=amounts, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def add_proxy( @@ -5755,10 +5991,13 @@ async def add_proxy( delegate_ss58: str, proxy_type: Union[str, "ProxyType"], delay: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds a proxy relationship. @@ -5773,12 +6012,16 @@ async def add_proxy( proxy_type: The type of proxy permissions (e.g., "Any", "NonTransfer", "Governance", "Staking"). Can be a string or ProxyType enum value. delay: The number of blocks before the proxy can be used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5793,10 +6036,12 @@ async def add_proxy( delegate_ss58=delegate_ss58, proxy_type=proxy_type, delay=delay, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def announce_proxy( @@ -5804,10 +6049,13 @@ async def announce_proxy( wallet: "Wallet", real_account_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Announces a future call that will be executed through a proxy. @@ -5820,12 +6068,16 @@ async def announce_proxy( wallet: Bittensor wallet object (should be the proxy account wallet). real_account_ss58: The SS58 address of the real account on whose behalf the call will be made. call_hash: The hash of the call that will be executed in the future. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5839,20 +6091,25 @@ async def announce_proxy( wallet=wallet, real_account_ss58=real_account_ss58, call_hash=call_hash, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def burned_register( self, wallet: "Wallet", netuid: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers a neuron on the Bittensor network by recycling TAO. This method of registration involves recycling @@ -5861,12 +6118,16 @@ async def burned_register( Parameters: wallet: The wallet associated with the neuron to be registered. netuid: The unique identifier of the subnet. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5876,42 +6137,53 @@ async def burned_register( return await root_register_extrinsic( subtensor=self, wallet=wallet, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) return await burned_register_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def claim_root( self, wallet: "Wallet", netuids: "UIDs", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ): """Claims the root emissions for a coldkey. Parameters: wallet: Bittensor Wallet instance. netuids: The netuids to claim root emissions for. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5920,10 +6192,12 @@ async def claim_root( subtensor=self, wallet=wallet, netuids=netuids, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def commit_weights( @@ -5936,10 +6210,13 @@ async def commit_weights( mechid: int = 0, version_key: int = version_as_int, max_attempts: int = 5, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = 16, raise_error: bool = True, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Commits a hash of the subnet validator's weight vector to the Bittensor blockchain using the provided wallet. @@ -5954,12 +6231,16 @@ async def commit_weights( mechid: The subnet mechanism unique identifier. version_key: Version key for compatibility with the network. max_attempts: The number of maximum attempts to commit weights. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5992,10 +6273,12 @@ async def commit_weights( uids=uids, weights=weights, salt=salt, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + mev_protection=mev_protection, period=period, raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) except Exception as error: return ExtrinsicResponse.from_exception( @@ -6015,10 +6298,13 @@ async def contribute_crowdloan( wallet: "Wallet", crowdloan_id: int, amount: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Contributes funds to an active crowdloan campaign. @@ -6027,12 +6313,16 @@ async def contribute_crowdloan( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to contribute to. amount: Amount to contribute. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6042,10 +6332,12 @@ async def contribute_crowdloan( wallet=wallet, crowdloan_id=crowdloan_id, amount=amount, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def create_crowdloan( @@ -6057,10 +6349,13 @@ async def create_crowdloan( end: int, call: Optional["GenericCall"] = None, target_address: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Creates a new crowdloan campaign on-chain. @@ -6073,12 +6368,16 @@ async def create_crowdloan( end: Block number when the campaign ends. call: Runtime call data (e.g., subtensor::register_leased_network). target_address: SS58 address to transfer funds to on success. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6092,10 +6391,12 @@ async def create_crowdloan( end=end, call=call, target_address=target_address, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def create_pure_proxy( @@ -6104,10 +6405,13 @@ async def create_pure_proxy( proxy_type: Union[str, "ProxyType"], delay: int, index: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Creates a pure proxy account. @@ -6121,12 +6425,16 @@ async def create_pure_proxy( proxy_type: The type of proxy permissions for the pure proxy. Can be a string or ProxyType enum value. delay: The number of blocks before the pure proxy can be used. index: The index to use for generating the pure proxy account address. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6142,20 +6450,25 @@ async def create_pure_proxy( proxy_type=proxy_type, delay=delay, index=index, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def dissolve_crowdloan( self, wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Dissolves a completed or failed crowdloan campaign after all refunds are processed. @@ -6166,12 +6479,16 @@ async def dissolve_crowdloan( Parameters: wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to dissolve. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6186,20 +6503,25 @@ async def dissolve_crowdloan( subtensor=self, wallet=wallet, crowdloan_id=crowdloan_id, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def finalize_crowdloan( self, wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Finalizes a successful crowdloan campaign once the cap has been reached and the end block has passed. @@ -6209,10 +6531,14 @@ async def finalize_crowdloan( Parameters: wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to finalize. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6221,10 +6547,12 @@ async def finalize_crowdloan( subtensor=self, wallet=wallet, crowdloan_id=crowdloan_id, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def kill_pure_proxy( @@ -6237,10 +6565,13 @@ async def kill_pure_proxy( height: int, ext_index: int, force_proxy_type: Optional[Union[str, "ProxyType"]] = ProxyType.Any, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Kills (removes) a pure proxy account. @@ -6269,12 +6600,16 @@ async def kill_pure_proxy( type (or `Any`) with the pure proxy account. Defaults to `ProxyType.Any` for maximum compatibility. If `None`, Substrate will automatically select an available proxy type from the spawner's proxy relationships. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6298,10 +6633,74 @@ async def kill_pure_proxy( height=height, ext_index=ext_index, force_proxy_type=force_proxy_type, + mev_protection=mev_protection, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + + async def mev_submit_encrypted( + self, + wallet: "Wallet", + call: "GenericCall", + signer_keypair: Optional["Keypair"] = None, + *, + period: Optional[int] = DEFAULT_PERIOD, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, + blocks_for_revealed_execution: int = 5, + ) -> ExtrinsicResponse: + """ + Submits an encrypted extrinsic to the MEV Shield pallet. + + This function encrypts a call using ML-KEM-768 + XChaCha20Poly1305 and submits it to the MevShield pallet. The + extrinsic remains encrypted in the transaction pool until it is included in a block and decrypted by validators. + + Parameters: + wallet: The wallet used to sign the extrinsic (must be unlocked, coldkey will be used for signing). + call: The GenericCall object to encrypt and submit. + signer_keypair: The keypair used to sign the inner call. + period: The number of blocks during which the transaction will remain valid after it's submitted. If the + transaction is not included in a block within that number of blocks, it will expire and be rejected. You can + think of it as an expiration date for the transaction. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the DecryptedExecuted event, indicating that validators + have successfully decrypted and executed the inner call. If True, the function will poll subsequent + blocks for the event matching this submission's commitment. + blocks_for_revealed_execution: Maximum number of blocks to poll for the DecryptedExecuted event after + inclusion. The function checks blocks from start_block+1 to start_block + blocks_for_revealed_execution. + Returns immediately if the event is found before the block limit is reached. + + Returns: + ExtrinsicResponse: The result object of the extrinsic execution. + + Raises: + ValueError: If NextKey is not available in storage or encryption fails. + SubstrateRequestException: If the extrinsic fails to be submitted or included. + + Note: + The encryption uses the public key from NextKey storage, which rotates every block. The payload structure is: + payload_core = signer_bytes (32B) + nonce (u32 LE, 4B) + SCALE(call) + plaintext = payload_core + b"\\x01" + signature (64B for sr25519) + commitment = blake2_256(payload_core) + """ + return await submit_encrypted_extrinsic( + subtensor=self, + wallet=wallet, + call=call, + signer_keypair=signer_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + blocks_for_revealed_execution=blocks_for_revealed_execution, ) async def modify_liquidity( @@ -6311,10 +6710,13 @@ async def modify_liquidity( position_id: int, liquidity_delta: Balance, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Modifies liquidity in liquidity position by adding or removing liquidity from it. @@ -6324,12 +6726,16 @@ async def modify_liquidity( position_id: The id of the position record in the pool. liquidity_delta: The amount of liquidity to be added or removed (add if positive or remove if negative). hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6370,10 +6776,12 @@ async def modify_liquidity( position_id=position_id, liquidity_delta=liquidity_delta, hotkey_ss58=hotkey_ss58, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def move_stake( @@ -6385,10 +6793,13 @@ async def move_stake( destination_hotkey_ss58: str, amount: Optional[Balance] = None, move_all_stake: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Moves stake to a different hotkey and/or subnet. @@ -6401,12 +6812,16 @@ async def move_stake( destination_hotkey_ss58: The SS58 address of the destination hotkey. amount: Amount of stake to move. move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6421,19 +6836,24 @@ async def move_stake( destination_hotkey_ss58=destination_hotkey_ss58, amount=amount, move_all_stake=move_all_stake, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def poke_deposit( self, wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adjusts deposits made for proxies and announcements based on current values. @@ -6444,10 +6864,14 @@ async def poke_deposit( Parameters: wallet: Bittensor wallet object (the account whose deposits will be adjusted). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6464,10 +6888,12 @@ async def poke_deposit( return await poke_deposit_extrinsic( subtensor=self, wallet=wallet, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def proxy( @@ -6476,10 +6902,13 @@ async def proxy( real_account_ss58: str, force_proxy_type: Optional[Union[str, "ProxyType"]], call: "GenericCall", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Executes a call on behalf of the real account through a proxy. @@ -6494,12 +6923,16 @@ async def proxy( force_proxy_type: The type of proxy to use for the call. If None, any proxy type can be used. Otherwise, must match one of the allowed proxy types. Can be a string or ProxyType enum value. call: The inner call to be executed on behalf of the real account. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6514,10 +6947,12 @@ async def proxy( real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def proxy_announced( @@ -6527,10 +6962,13 @@ async def proxy_announced( real_account_ss58: str, force_proxy_type: Optional[Union[str, "ProxyType"]], call: "GenericCall", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Executes an announced call on behalf of the real account through a proxy. @@ -6546,12 +6984,16 @@ async def proxy_announced( force_proxy_type: The type of proxy to use for the call. If None, any proxy type can be used. Otherwise, must match one of the allowed proxy types. Can be a string or ProxyType enum value. call: The inner call to be executed on behalf of the real account (must match the announced call_hash). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6567,20 +7009,25 @@ async def proxy_announced( real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def refund_crowdloan( self, wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Refunds contributors from a failed or expired crowdloan campaign. @@ -6591,12 +7038,16 @@ async def refund_crowdloan( Parameters: wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to refund. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6611,10 +7062,12 @@ async def refund_crowdloan( subtensor=self, wallet=wallet, crowdloan_id=crowdloan_id, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def reject_proxy_announcement( @@ -6622,10 +7075,13 @@ async def reject_proxy_announcement( wallet: "Wallet", delegate_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Rejects an announcement made by a proxy delegate. @@ -6638,12 +7094,16 @@ async def reject_proxy_announcement( wallet: Bittensor wallet object (should be the real account wallet). delegate_ss58: The SS58 address of the delegate proxy account whose announcement is being rejected. call_hash: The hash of the call that was announced and is now being rejected. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6656,10 +7116,12 @@ async def reject_proxy_announcement( wallet=wallet, delegate_ss58=delegate_ss58, call_hash=call_hash, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def register( @@ -6674,10 +7136,13 @@ async def register( num_processes: Optional[int] = None, update_interval: Optional[int] = None, log_verbose: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. @@ -6697,12 +7162,16 @@ async def register( num_processes: The number of processes to use to register. update_interval: The number of nonces to solve between updates. log_verbose: If ``true``, the registration process will log more information. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6722,31 +7191,40 @@ async def register( dev_id=dev_id, output_in_place=output_in_place, log_verbose=log_verbose, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def register_subnet( self: "AsyncSubtensor", wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers a new subnetwork on the Bittensor network. Parameters: wallet: The wallet to be used for subnet registration. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6754,10 +7232,12 @@ async def register_subnet( return await register_subnet_extrinsic( subtensor=self, wallet=wallet, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def remove_proxy_announcement( @@ -6765,10 +7245,13 @@ async def remove_proxy_announcement( wallet: "Wallet", real_account_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes an announcement made by a proxy account. @@ -6781,12 +7264,16 @@ async def remove_proxy_announcement( wallet: Bittensor wallet object (should be the proxy account wallet that made the announcement). real_account_ss58: The SS58 address of the real account on whose behalf the call was announced. call_hash: The hash of the call that was announced and is now being removed. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6800,10 +7287,12 @@ async def remove_proxy_announcement( wallet=wallet, real_account_ss58=real_account_ss58, call_hash=call_hash, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def remove_liquidity( @@ -6812,10 +7301,13 @@ async def remove_liquidity( netuid: int, position_id: int, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Remove liquidity and credit balances back to wallet's hotkey stake. @@ -6824,12 +7316,16 @@ async def remove_liquidity( netuid: The UID of the target subnet for which the call is being initiated. position_id: The id of the position record in the pool. hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6845,19 +7341,24 @@ async def remove_liquidity( netuid=netuid, position_id=position_id, hotkey_ss58=hotkey_ss58, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def remove_proxies( self, wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes all proxy relationships for the account in a single transaction. @@ -6869,12 +7370,16 @@ async def remove_proxies( Parameters: wallet: Bittensor wallet object. The account whose proxies will be removed (the delegator). All proxy relationships where wallet.coldkey.ss58_address is the real account will be removed. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6886,10 +7391,12 @@ async def remove_proxies( return await remove_proxies_extrinsic( subtensor=self, wallet=wallet, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def remove_proxy( @@ -6898,10 +7405,13 @@ async def remove_proxy( delegate_ss58: str, proxy_type: Union[str, "ProxyType"], delay: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes a specific proxy relationship. @@ -6915,12 +7425,16 @@ async def remove_proxy( delegate_ss58: The SS58 address of the delegate proxy account to remove. proxy_type: The type of proxy permissions to remove. Can be a string or ProxyType enum value. delay: The number of blocks before the proxy removal takes effect. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6935,10 +7449,12 @@ async def remove_proxy( delegate_ss58=delegate_ss58, proxy_type=proxy_type, delay=delay, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def reveal_weights( @@ -6951,10 +7467,13 @@ async def reveal_weights( mechid: int = 0, max_attempts: int = 5, version_key: int = version_as_int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = 16, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -6969,12 +7488,16 @@ async def reveal_weights( mechid: The subnet mechanism unique identifier. max_attempts: The number of maximum attempts to reveal weights. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7001,10 +7524,12 @@ async def reveal_weights( weights=weights, salt=salt, version_key=version_key, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) except Exception as error: return ExtrinsicResponse.from_exception( @@ -7019,22 +7544,29 @@ async def reveal_weights( async def root_register( self, wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Register neuron by recycling some TAO. Parameters: wallet: The wallet associated with the neuron to be registered. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7043,32 +7575,41 @@ async def root_register( return await root_register_extrinsic( subtensor=self, wallet=wallet, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def root_set_pending_childkey_cooldown( self, wallet: "Wallet", cooldown: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Sets the pending childkey cooldown. Parameters: wallet: bittensor wallet instance. cooldown: the number of blocks to setting pending childkey cooldown. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7079,10 +7620,12 @@ async def root_set_pending_childkey_cooldown( subtensor=self, wallet=wallet, cooldown=cooldown, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def set_auto_stake( @@ -7090,10 +7633,13 @@ async def set_auto_stake( wallet: "Wallet", netuid: int, hotkey_ss58: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism. @@ -7102,12 +7648,16 @@ async def set_auto_stake( netuid: The subnet unique identifier. hotkey_ss58: The SS58 address of the validator's hotkey to which the miner automatically stakes all rewards received from the specified subnet immediately upon receipt. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7120,10 +7670,12 @@ async def set_auto_stake( wallet=wallet, netuid=netuid, hotkey_ss58=hotkey_ss58, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def set_children( @@ -7132,10 +7684,13 @@ async def set_children( hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Allows a coldkey to set children-keys. @@ -7145,12 +7700,16 @@ async def set_children( hotkey_ss58: The `SS58` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7174,10 +7733,12 @@ async def set_children( hotkey_ss58=hotkey_ss58, netuid=netuid, children=children, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def set_delegate_take( @@ -7185,10 +7746,13 @@ async def set_delegate_take( wallet: "Wallet", hotkey_ss58: str, take: float, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Sets the delegate 'take' percentage for a neuron identified by its hotkey. @@ -7198,12 +7762,16 @@ async def set_delegate_take( wallet: bittensor wallet instance. hotkey_ss58: The ``SS58`` address of the neuron's hotkey. take: Percentage reward for the delegate. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7240,10 +7808,12 @@ async def set_delegate_take( hotkey_ss58=hotkey_ss58, take=take_u16, action="increase_take" if current_take_u16 < take_u16 else "decrease_take", + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_finalization=wait_for_finalization, wait_for_inclusion=wait_for_inclusion, + wait_for_revealed_execution=wait_for_revealed_execution, ) if response.success: @@ -7256,10 +7826,13 @@ async def set_root_claim_type( self, wallet: "Wallet", new_root_claim_type: "Literal['Swap', 'Keep'] | RootClaimType | dict", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ): """Sets the root claim type for the coldkey in provided wallet. @@ -7270,12 +7843,16 @@ async def set_root_claim_type( - RootClaimType: RootClaimType.Swap, RootClaimType.Keep - Dict: {"KeepSubnets": {"subnets": [1, 2, 3]}} - Callable: RootClaimType.KeepSubnets([1, 2, 3]) + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7284,10 +7861,12 @@ async def set_root_claim_type( subtensor=self, wallet=wallet, new_root_claim_type=new_root_claim_type, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def set_subnet_identity( @@ -7295,10 +7874,13 @@ async def set_subnet_identity( wallet: "Wallet", netuid: int, subnet_identity: SubnetIdentity, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Sets the identity of a subnet for a specific wallet and network. @@ -7308,12 +7890,16 @@ async def set_subnet_identity( netuid: The unique ID of the network on which the operation takes place. subnet_identity: The identity data of the subnet including attributes like name, GitHub repository, contact, URL, discord, description, and any additional metadata. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7330,10 +7916,12 @@ async def set_subnet_identity( discord=subnet_identity.discord, description=subnet_identity.description, additional=subnet_identity.additional, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def set_weights( @@ -7347,10 +7935,13 @@ async def set_weights( commit_reveal_version: int = 4, max_attempts: int = 5, version_key: int = version_as_int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = 8, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Sets the weight vector for a neuron acting as a validator, specifying the weights assigned to subnet miners @@ -7371,12 +7962,16 @@ async def set_weights( commit_reveal_version: The version of the chain commit-reveal protocol to use. max_attempts: The number of maximum attempts to set weights. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7433,10 +8028,12 @@ async def _blocks_weight_limit() -> bool: block_time=block_time, commit_reveal_version=commit_reveal_version, version_key=version_key, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) except Exception as error: return ExtrinsicResponse.from_exception( @@ -7464,10 +8061,12 @@ async def _blocks_weight_limit() -> bool: uids=uids, weights=weights, version_key=version_key, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) except Exception as error: return ExtrinsicResponse.from_exception( @@ -7486,10 +8085,13 @@ async def serve_axon( netuid: int, axon: "Axon", certificate: Optional[Certificate] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers an ``Axon`` serving endpoint on the Bittensor network for a specific neuron. @@ -7501,12 +8103,16 @@ async def serve_axon( netuid: The unique identifier of the subnetwork. axon: The Axon instance to be registered for serving. certificate: Certificate to use for TLS. If ``None``, no TLS will be used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7519,10 +8125,12 @@ async def serve_axon( netuid=netuid, axon=axon, certificate=certificate, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def set_commitment( @@ -7530,10 +8138,13 @@ async def set_commitment( wallet: "Wallet", netuid: int, data: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Commits arbitrary data to the Bittensor network by publishing metadata. @@ -7545,12 +8156,16 @@ async def set_commitment( wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the data. netuid (int): The unique identifier of the subnetwork. data (str): The data to be committed to the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7570,10 +8185,12 @@ async def set_commitment( netuid=netuid, data_type=f"Raw{len(data)}", data=data.encode(), + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def set_reveal_commitment( @@ -7583,10 +8200,13 @@ async def set_reveal_commitment( data: str, blocks_until_reveal: int = 360, block_time: Union[int, float] = 12, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Commits arbitrary data to the Bittensor network by publishing metadata. @@ -7598,12 +8218,16 @@ async def set_reveal_commitment( blocks_until_reveal: The number of blocks from now after which the data will be revealed. Then number of blocks in one epoch. block_time: The number of seconds between each block. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7624,10 +8248,12 @@ async def set_reveal_commitment( netuid=netuid, data_type="TimelockEncrypted", data=data_, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) response.data = data_ return response @@ -7636,10 +8262,13 @@ async def start_call( self, wallet: "Wallet", netuid: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start @@ -7648,12 +8277,16 @@ async def start_call( Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7662,10 +8295,12 @@ async def start_call( subtensor=self, wallet=wallet, netuid=netuid, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def swap_stake( @@ -7678,10 +8313,13 @@ async def swap_stake( safe_swapping: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Moves stake between subnets while keeping the same coldkey-hotkey pair ownership. @@ -7700,12 +8338,16 @@ async def swap_stake( rate_tolerance: The maximum allowed increase in the price ratio between subnets (origin_price/destination_price). For example, 0.005 = 0.5% maximum increase. Only used when safe_staking is True. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7728,10 +8370,12 @@ async def swap_stake( safe_swapping=safe_swapping, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def toggle_user_liquidity( @@ -7739,10 +8383,13 @@ async def toggle_user_liquidity( wallet: "Wallet", netuid: int, enable: bool, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Allow to toggle user liquidity for specified subnet. @@ -7750,12 +8397,16 @@ async def toggle_user_liquidity( wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. enable: Boolean indicating whether to enable user liquidity. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7767,10 +8418,12 @@ async def toggle_user_liquidity( wallet=wallet, netuid=netuid, enable=enable, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def transfer( @@ -7780,10 +8433,13 @@ async def transfer( amount: Optional[Balance], transfer_all: bool = False, keep_alive: bool = True, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Transfer token of amount to destination. @@ -7794,12 +8450,16 @@ async def transfer( amount: Number of tokens to transfer. `None` is transferring all. transfer_all: Flag to transfer all tokens. keep_alive: Flag to keep the connection alive. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7812,10 +8472,12 @@ async def transfer( amount=amount, transfer_all=transfer_all, keep_alive=keep_alive, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def transfer_stake( @@ -7826,10 +8488,13 @@ async def transfer_stake( origin_netuid: int, destination_netuid: int, amount: Balance, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Transfers stake from one subnet to another while changing the coldkey owner. @@ -7841,12 +8506,16 @@ async def transfer_stake( origin_netuid: The source subnet UID. destination_netuid: The destination subnet UID. amount: Amount to transfer. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7860,10 +8529,12 @@ async def transfer_stake( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def unstake( @@ -7875,10 +8546,13 @@ async def unstake( allow_partial_stake: bool = False, safe_unstaking: bool = False, rate_tolerance: float = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes a specified amount of stake from a single hotkey account. This function is critical for adjusting @@ -7896,12 +8570,16 @@ async def unstake( 0.005 = 0.5% maximum price decrease. Only used when safe_staking is True. safe_unstaking: If true, enables price safety checks to protect against fluctuating prices. The unstake will only execute if the price change doesn't exceed the rate tolerance. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7919,10 +8597,12 @@ async def unstake( allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, safe_unstaking=safe_unstaking, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def unstake_all( @@ -7931,10 +8611,13 @@ async def unstake_all( netuid: int, hotkey_ss58: str, rate_tolerance: Optional[float] = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. @@ -7944,12 +8627,16 @@ async def unstake_all( hotkey_ss58: The SS58 address of the hotkey to unstake from. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -7999,10 +8686,12 @@ async def unstake_all( netuid=netuid, hotkey_ss58=hotkey_ss58, rate_tolerance=rate_tolerance, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def unstake_multiple( @@ -8012,10 +8701,13 @@ async def unstake_multiple( hotkey_ss58s: list[str], amounts: Optional[list[Balance]] = None, unstake_all: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Performs batch unstaking from multiple hotkey accounts, allowing a neuron to reduce its staked amounts @@ -8027,12 +8719,16 @@ async def unstake_multiple( hotkey_ss58s: A list of hotkey `SS58` addresses to unstake from. amounts: The amounts of TAO to unstake from each hotkey. If not provided, unstakes all. unstake_all: If true, unstakes all tokens. If `True` amounts are ignored. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -8047,10 +8743,12 @@ async def unstake_multiple( hotkey_ss58s=hotkey_ss58s, amounts=amounts, unstake_all=unstake_all, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def update_cap_crowdloan( @@ -8058,10 +8756,13 @@ async def update_cap_crowdloan( wallet: "Wallet", crowdloan_id: int, new_cap: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Updates the fundraising cap (maximum total contribution) of a non-finalized crowdloan. @@ -8073,12 +8774,16 @@ async def update_cap_crowdloan( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_cap: The new fundraising cap (in TAO or Balance). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -8093,10 +8798,12 @@ async def update_cap_crowdloan( wallet=wallet, crowdloan_id=crowdloan_id, new_cap=new_cap, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def update_end_crowdloan( @@ -8104,10 +8811,13 @@ async def update_end_crowdloan( wallet: "Wallet", crowdloan_id: int, new_end: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Updates the end block of a non-finalized crowdloan campaign. @@ -8119,12 +8829,16 @@ async def update_end_crowdloan( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_end: The new block number at which the crowdloan will end. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -8140,10 +8854,12 @@ async def update_end_crowdloan( wallet=wallet, crowdloan_id=crowdloan_id, new_end=new_end, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def update_min_contribution_crowdloan( @@ -8151,10 +8867,13 @@ async def update_min_contribution_crowdloan( wallet: "Wallet", crowdloan_id: int, new_min_contribution: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Updates the minimum contribution amount of a non-finalized crowdloan. @@ -8166,12 +8885,16 @@ async def update_min_contribution_crowdloan( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_min_contribution: The new minimum contribution amount (in TAO or Balance). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -8186,20 +8909,25 @@ async def update_min_contribution_crowdloan( wallet=wallet, crowdloan_id=crowdloan_id, new_min_contribution=new_min_contribution, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) async def withdraw_crowdloan( self, wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Withdraws a contribution from an active (not yet finalized or dissolved) crowdloan. @@ -8207,12 +8935,16 @@ async def withdraw_crowdloan( Parameters: wallet: Wallet instance used to sign the transaction (must be unlocked). crowdloan_id: The unique identifier of the crowdloan to withdraw from. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -8225,10 +8957,12 @@ async def withdraw_crowdloan( subtensor=self, wallet=wallet, crowdloan_id=crowdloan_id, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index e8bc2fa222..49d2852254 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -1,6 +1,8 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule, Sudo +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils import float_to_u64 @@ -15,10 +17,13 @@ async def set_children_extrinsic( hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Allows a coldkey to set children-keys. @@ -29,6 +34,9 @@ async def set_children_extrinsic( hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. @@ -67,14 +75,26 @@ async def set_children_extrinsic( ], ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) return response except Exception as error: @@ -85,10 +105,13 @@ async def root_set_pending_childkey_cooldown_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", cooldown: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Allows a root coldkey to set children-keys. @@ -97,6 +120,9 @@ async def root_set_pending_childkey_cooldown_extrinsic( subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). cooldown: The cooldown period in blocks. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. @@ -119,14 +145,26 @@ async def root_set_pending_childkey_cooldown_extrinsic( sudo_call = await Sudo(subtensor).sudo(call=call) - response = await subtensor.sign_and_send_extrinsic( - call=sudo_call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=sudo_call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=sudo_call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) return response except Exception as error: diff --git a/bittensor/core/extrinsics/asyncex/crowdloan.py b/bittensor/core/extrinsics/asyncex/crowdloan.py index a5f0941986..05bbd8c086 100644 --- a/bittensor/core/extrinsics/asyncex/crowdloan.py +++ b/bittensor/core/extrinsics/asyncex/crowdloan.py @@ -1,6 +1,8 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import Crowdloan +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import check_balance_amount @@ -16,10 +18,13 @@ async def contribute_crowdloan_extrinsic( wallet: "Wallet", crowdloan_id: int, amount: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Contributes funds to an active crowdloan campaign. @@ -29,12 +34,16 @@ async def contribute_crowdloan_extrinsic( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to contribute to. amount: Amount to contribute. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -52,14 +61,26 @@ async def contribute_crowdloan_extrinsic( amount=amount.rao, ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -74,10 +95,13 @@ async def create_crowdloan_extrinsic( end: int, call: Optional["GenericCall"] = None, target_address: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Creates a new crowdloan campaign on-chain. @@ -91,12 +115,16 @@ async def create_crowdloan_extrinsic( end: Block number when the campaign ends. call: Runtime call data (e.g., subtensor::register_leased_network). target_address: SS58 address to transfer funds to on success. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -120,14 +148,26 @@ async def create_crowdloan_extrinsic( target_address=target_address, ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -137,10 +177,13 @@ async def dissolve_crowdloan_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Dissolves a completed or failed crowdloan campaign after all refunds are processed. @@ -152,12 +195,16 @@ async def dissolve_crowdloan_extrinsic( subtensor: Active Subtensor connection. wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to dissolve. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -176,14 +223,26 @@ async def dissolve_crowdloan_extrinsic( call = await Crowdloan(subtensor).dissolve(crowdloan_id=crowdloan_id) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -193,10 +252,13 @@ async def finalize_crowdloan_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Finalizes a successful crowdloan campaign once the cap has been reached and the end block has passed. @@ -207,10 +269,14 @@ async def finalize_crowdloan_extrinsic( subtensor: Active Subtensor connection. wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to finalize. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -223,14 +289,26 @@ async def finalize_crowdloan_extrinsic( call = await Crowdloan(subtensor).finalize(crowdloan_id=crowdloan_id) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -240,10 +318,13 @@ async def refund_crowdloan_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Refunds contributors from a failed or expired crowdloan campaign. @@ -255,12 +336,16 @@ async def refund_crowdloan_extrinsic( subtensor: Active Subtensor connection. wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to refund. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -279,14 +364,26 @@ async def refund_crowdloan_extrinsic( call = await Crowdloan(subtensor).refund(crowdloan_id=crowdloan_id) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -297,10 +394,13 @@ async def update_cap_crowdloan_extrinsic( wallet: "Wallet", crowdloan_id: int, new_cap: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Updates the fundraising cap (maximum total contribution) of a non-finalized crowdloan. @@ -313,12 +413,16 @@ async def update_cap_crowdloan_extrinsic( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_cap: The new fundraising cap (in TAO or Balance). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -340,14 +444,26 @@ async def update_cap_crowdloan_extrinsic( crowdloan_id=crowdloan_id, new_cap=new_cap.rao ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -358,10 +474,13 @@ async def update_end_crowdloan_extrinsic( wallet: "Wallet", crowdloan_id: int, new_end: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Updates the end block of a non-finalized crowdloan campaign. @@ -374,12 +493,16 @@ async def update_end_crowdloan_extrinsic( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_end: The new block number at which the crowdloan will end. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -400,14 +523,26 @@ async def update_end_crowdloan_extrinsic( crowdloan_id=crowdloan_id, new_end=new_end ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -418,10 +553,13 @@ async def update_min_contribution_crowdloan_extrinsic( wallet: "Wallet", crowdloan_id: int, new_min_contribution: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Updates the minimum contribution amount of a non-finalized crowdloan. @@ -434,12 +572,16 @@ async def update_min_contribution_crowdloan_extrinsic( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_min_contribution: The new minimum contribution amount (in TAO or Balance). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -461,14 +603,26 @@ async def update_min_contribution_crowdloan_extrinsic( crowdloan_id=crowdloan_id, new_min_contribution=new_min_contribution.rao ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -478,10 +632,13 @@ async def withdraw_crowdloan_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Withdraws a contribution from an active (not yet finalized or dissolved) crowdloan. @@ -490,12 +647,16 @@ async def withdraw_crowdloan_extrinsic( subtensor: Active Subtensor connection. wallet: Wallet instance used to sign the transaction (must be unlocked). crowdloan_id: The unique identifier of the crowdloan to withdraw from. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -512,14 +673,26 @@ async def withdraw_crowdloan_extrinsic( call = await Crowdloan(subtensor).withdraw(crowdloan_id=crowdloan_id) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/liquidity.py b/bittensor/core/extrinsics/asyncex/liquidity.py index b9b6b4e4e9..aa828f2c1a 100644 --- a/bittensor/core/extrinsics/asyncex/liquidity.py +++ b/bittensor/core/extrinsics/asyncex/liquidity.py @@ -1,6 +1,8 @@ from typing import Optional, TYPE_CHECKING +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import Swap +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance from bittensor.utils.liquidity import price_to_tick @@ -18,10 +20,13 @@ async def add_liquidity_extrinsic( price_low: Balance, price_high: Balance, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds liquidity to the specified price range. @@ -34,12 +39,16 @@ async def add_liquidity_extrinsic( price_low: The lower bound of the price tick range. price_high: The upper bound of the price tick range. hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -64,14 +73,26 @@ async def add_liquidity_extrinsic( hotkey=hotkey_ss58 or wallet.hotkey.ss58_address, ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -83,10 +104,13 @@ async def modify_liquidity_extrinsic( position_id: int, liquidity_delta: Balance, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Modifies liquidity in liquidity position by adding or removing liquidity from it. @@ -97,12 +121,16 @@ async def modify_liquidity_extrinsic( position_id: The id of the position record in the pool. liquidity_delta: The amount of liquidity to be added or removed (add if positive or remove if negative). hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -126,14 +154,26 @@ async def modify_liquidity_extrinsic( liquidity_delta=liquidity_delta.rao, ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -144,10 +184,13 @@ async def remove_liquidity_extrinsic( netuid: int, position_id: int, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Remove liquidity and credit balances back to wallet's hotkey stake. @@ -157,12 +200,16 @@ async def remove_liquidity_extrinsic( netuid: The UID of the target subnet for which the call is being initiated. position_id: The id of the position record in the pool. hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -185,14 +232,26 @@ async def remove_liquidity_extrinsic( position_id=position_id, ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -202,10 +261,13 @@ async def toggle_user_liquidity_extrinsic( wallet: "Wallet", netuid: int, enable: bool, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Allow to toggle user liquidity for specified subnet. @@ -214,12 +276,16 @@ async def toggle_user_liquidity_extrinsic( wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. enable: Boolean indicating whether to enable user liquidity. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -235,13 +301,25 @@ async def toggle_user_liquidity_extrinsic( enable=enable, ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/mev_shield.py b/bittensor/core/extrinsics/asyncex/mev_shield.py new file mode 100644 index 0000000000..40feffeb23 --- /dev/null +++ b/bittensor/core/extrinsics/asyncex/mev_shield.py @@ -0,0 +1,224 @@ +"""Module provides async MEV Shield extrinsics.""" + +from typing import TYPE_CHECKING, Optional + +from async_substrate_interface import AsyncExtrinsicReceipt + +from bittensor.core.extrinsics.pallets import MevShield +from bittensor.core.extrinsics.utils import ( + get_event_attributes_by_event_name, + get_mev_commitment_and_ciphertext, + post_process_mev_response, + POST_SUBMIT_MEV_EVENTS, + MEV_SUBMITTED_EVENT, +) +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils.btlogging import logging + +if TYPE_CHECKING: + from bittensor.core.async_subtensor import AsyncSubtensor + from bittensor_wallet import Wallet, Keypair + from scalecodec.types import GenericCall + + +async def find_revealed_extrinsic( + subtensor: "AsyncSubtensor", + event_names: list[str], + event_hash_id: str, + start_block_hash: str, + blocks_ahead: int = 5, +) -> tuple[str, "AsyncExtrinsicReceipt"] | tuple[None, None]: + """ + Searches for an extrinsic containing a specific MEV Shield event in subsequent blocks. + + This function iterates through blocks starting from the specified block hash and searches for extrinsics that + contain a MEV Shield event (DecryptedExecuted or DecryptedRejected) matching the provided wrapper_id and signer. It + checks each extrinsic's triggered events to find the matching event. + + Parameters: + subtensor: The Subtensor instance used for blockchain queries. + event_names: The event identifiers to search for. Typically "DecryptedExecuted" or "DecryptedRejected" for MEV + Shield transactions. + event_hash_id: The wrapper_id (hash of (author, commitment, ciphertext)) to match. This uniquely identifies a + specific MEV Shield submission. + start_block_hash: The hash of the block where the search should begin. Usually the block where submit_encrypted + was included. + blocks_ahead: Maximum number of blocks to search ahead from the start block. Defaults to 5 blocks. The function + will check blocks from start_block + 1 to start_block + blocks_ahead (the start block itself is not checked, + as execute_revealed will be in subsequent blocks). + + Returns: + Tuple with event name and ExtrinsicReceipt object. + """ + start_block_number = await subtensor.substrate.get_block_number(start_block_hash) + + for offset in range(1, blocks_ahead + 1): + current_block_number = start_block_number + offset + + try: + current_block_hash = await subtensor.substrate.get_block_hash( + current_block_number + ) + events = await subtensor.substrate.get_events(current_block_hash) + except Exception as e: + logging.debug( + f"Error getting extrinsics for block `{current_block_number}`: {e}" + ) + continue + + for event_name in event_names: + if event := get_event_attributes_by_event_name(events, event_name): + if event["attributes"]["id"] == event_hash_id: + return event_name, AsyncExtrinsicReceipt( + substrate=subtensor.substrate, + block_hash=current_block_hash, + extrinsic_idx=event["extrinsic_idx"], + ) + + logging.debug(f"No {event_names} event found in block {current_block_number}.") + await subtensor.wait_for_block() + + return None, None + + +async def submit_encrypted_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + call: "GenericCall", + signer_keypair: Optional["Keypair"] = None, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, + blocks_for_revealed_execution: int = 5, +) -> ExtrinsicResponse: + """ + Submits an encrypted extrinsic to the MEV Shield pallet. + + This function encrypts a call using ML-KEM-768 + XChaCha20Poly1305 and submits it to the MevShield pallet. The + extrinsic remains encrypted in the transaction pool until it is included in a block and decrypted by validators. + + Parameters: + subtensor: The Subtensor client instance used for blockchain interaction. + wallet: The wallet used to sign the extrinsic (must be unlocked, coldkey will be used for signing). + call: The GenericCall object to encrypt and submit. + signer_keypair: The Keypair object used for signing the inner call. + period: The number of blocks during which the transaction will remain valid after it's submitted. If the + transaction is not included in a block within that number of blocks, it will expire and be rejected. You can + think of it as an expiration date for the transaction. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the DecryptedExecuted event, indicating that validators have + successfully decrypted and executed the inner call. If True, the function will poll subsequent blocks for + the event matching this submission's commitment. + blocks_for_revealed_execution: Maximum number of blocks to poll for the DecryptedExecuted event after inclusion. + The function checks blocks from start_block + 1 to start_block + blocks_for_revealed_execution. Returns + immediately if the event is found before the block limit is reached. + + Returns: + ExtrinsicResponse: The result object of the extrinsic execution. + + Raises: + ValueError: If NextKey is not available in storage or encryption fails. + SubstrateRequestException: If the extrinsic fails to be submitted or included. + + Note: + The encryption uses the public key from NextKey storage, which rotates every block. The payload structure is: + payload_core = signer_bytes (32B) + key_hash (32B Blake2-256 hash of NextKey) + SCALE(call) + plaintext = payload_core + b"\\x01" + signature (64B for sr25519) + commitment = blake2_256(payload_core) + + The key_hash binds the transaction to the key epoch at submission time and replaces nonce-based replay + protection. + """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if wait_for_revealed_execution and not wait_for_inclusion: + return ExtrinsicResponse.from_exception( + raise_error=raise_error, + error=ValueError( + "`wait_for_inclusion` must be `True` if `wait_for_revealed_execution` is `True`." + ), + ) + + # Use wallet.coldkey as default signer if signer_keypair is not provided + if signer_keypair is None: + signer_keypair = wallet.coldkey + + ml_kem_768_public_key = await subtensor.get_mev_shield_next_key() + if ml_kem_768_public_key is None: + return ExtrinsicResponse.from_exception( + raise_error=raise_error, + error=ValueError("MEV Shield NextKey not available in storage."), + ) + + genesis_hash = await subtensor.get_block_hash(block=0) + + mev_commitment, mev_ciphertext, payload_core, signature = ( + get_mev_commitment_and_ciphertext( + call=call, + signer_keypair=signer_keypair, + genesis_hash=genesis_hash, + ml_kem_768_public_key=ml_kem_768_public_key, + ) + ) + + extrinsic_call = await MevShield(subtensor).submit_encrypted( + commitment=mev_commitment, + ciphertext=mev_ciphertext, + ) + + response = await subtensor.sign_and_send_extrinsic( + wallet=wallet, + call=extrinsic_call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if response.success: + response.data = { + "commitment": mev_commitment, + "ciphertext": mev_ciphertext, + "ml_kem_768_public_key": ml_kem_768_public_key, + "payload_core": payload_core, + "signature": signature, + "submitting_id": extrinsic_call.call_hash, + } + if wait_for_revealed_execution: + triggered_events = await response.extrinsic_receipt.triggered_events + + event_hash_id = get_event_attributes_by_event_name( + events=triggered_events, event_name=MEV_SUBMITTED_EVENT + )["attributes"]["id"] + + reveled_event, reveled_extrinsic = await find_revealed_extrinsic( + subtensor=subtensor, + event_names=POST_SUBMIT_MEV_EVENTS, + event_hash_id=event_hash_id, + start_block_hash=response.extrinsic_receipt.block_hash, + blocks_ahead=blocks_for_revealed_execution, + ) + + post_process_mev_response( + response=response, + revealed_name=reveled_event, + revealed_extrinsic=reveled_extrinsic, + raise_error=raise_error, + ) + + logging.debug("[green]Encrypted extrinsic submitted successfully.[/green]") + else: + logging.error(f"[red]{response.message}[/red]") + + return response + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 60826ae82c..25f7b2db6f 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -1,7 +1,9 @@ import asyncio from typing import Optional, TYPE_CHECKING +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -48,10 +50,13 @@ async def move_stake_extrinsic( destination_hotkey_ss58: str, amount: Optional[Balance] = None, move_all_stake: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Moves stake from one hotkey to another within subnets in the Bittensor network. @@ -65,12 +70,16 @@ async def move_stake_extrinsic( destination_hotkey_ss58: The SS58 address of the destination hotkey. amount: Amount to move. move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -121,14 +130,26 @@ async def move_stake_extrinsic( alpha_amount=amount.rao, ) block_hash_before = await subtensor.get_block_hash() - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: sim_swap = await subtensor.sim_swap( @@ -185,10 +206,13 @@ async def transfer_stake_extrinsic( origin_netuid: int, destination_netuid: int, amount: Balance, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Transfers stake from one coldkey to another in the Bittensor network. @@ -201,12 +225,16 @@ async def transfer_stake_extrinsic( origin_netuid: Network UID of the origin subnet. destination_netuid: Network UID of the destination subnet. amount: The amount of stake to transfer as a `Balance` object. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -251,14 +279,26 @@ async def transfer_stake_extrinsic( alpha_amount=amount.rao, ) block_hash_before = await subtensor.get_block_hash() - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: sim_swap = await subtensor.sim_swap( @@ -309,10 +349,13 @@ async def swap_stake_extrinsic( safe_swapping: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Swaps stake from one subnet to another for a given hotkey in the Bittensor network. @@ -327,6 +370,9 @@ async def swap_stake_extrinsic( safe_swapping: If true, enables price safety checks to protect against price impact. allow_partial_stake: If true, allows partial stake swaps when the full amount would exceed the price tolerance. rate_tolerance: Maximum allowed increase in a price ratio (0.005 = 0.5%). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. @@ -402,14 +448,26 @@ async def swap_stake_extrinsic( ) block_hash_before = await subtensor.get_block_hash() - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: sim_swap = await subtensor.sim_swap( diff --git a/bittensor/core/extrinsics/asyncex/proxy.py b/bittensor/core/extrinsics/asyncex/proxy.py index acf764d0b8..058e272fb4 100644 --- a/bittensor/core/extrinsics/asyncex/proxy.py +++ b/bittensor/core/extrinsics/asyncex/proxy.py @@ -2,8 +2,10 @@ from typing import TYPE_CHECKING, Optional, Union from bittensor.core.chain_data.proxy import ProxyType +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import Proxy from bittensor.core.extrinsics.utils import apply_pure_proxy_data +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.btlogging import logging @@ -20,10 +22,13 @@ async def add_proxy_extrinsic( delegate_ss58: str, proxy_type: Union[str, ProxyType], delay: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds a proxy relationship. @@ -35,12 +40,16 @@ async def add_proxy_extrinsic( proxy_type: The type of proxy permissions (e.g., "Any", "NonTransfer", "Governance", "Staking"). Can be a string or ProxyType enum value. delay: The number of blocks before the proxy can be used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -72,14 +81,26 @@ async def add_proxy_extrinsic( delay=delay, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Proxy added successfully.[/green]") @@ -98,10 +119,13 @@ async def remove_proxy_extrinsic( delegate_ss58: str, proxy_type: Union[str, ProxyType], delay: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes a proxy relationship. @@ -112,12 +136,16 @@ async def remove_proxy_extrinsic( delegate_ss58: The SS58 address of the delegate proxy account to remove. proxy_type: The type of proxy permissions to remove. Can be a string or ProxyType enum value. delay: The number of blocks before the proxy removal takes effect. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -142,14 +170,26 @@ async def remove_proxy_extrinsic( delay=delay, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Proxy removed successfully.[/green]") @@ -165,10 +205,13 @@ async def remove_proxy_extrinsic( async def remove_proxies_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes all proxy relationships for the account. @@ -179,10 +222,14 @@ async def remove_proxies_extrinsic( Parameters: subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object (the account whose proxies will be removed). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -200,14 +247,26 @@ async def remove_proxies_extrinsic( call = await Proxy(subtensor).remove_proxies() - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]All proxies removed successfully.[/green]") @@ -226,10 +285,13 @@ async def create_pure_proxy_extrinsic( proxy_type: Union[str, ProxyType], delay: int, index: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Creates a pure proxy account. @@ -240,12 +302,16 @@ async def create_pure_proxy_extrinsic( proxy_type: The type of proxy permissions for the pure proxy. Can be a string or ProxyType enum value. delay: The number of blocks before the pure proxy can be used. index: The index to use for generating the pure proxy account address. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -270,14 +336,26 @@ async def create_pure_proxy_extrinsic( index=index, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Pure proxy created successfully.[/green]") @@ -325,10 +403,13 @@ async def kill_pure_proxy_extrinsic( height: int, ext_index: int, force_proxy_type: Optional[Union[str, ProxyType]] = ProxyType.Any, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Kills (removes) a pure proxy account. @@ -357,12 +438,16 @@ async def kill_pure_proxy_extrinsic( the pure proxy account should be used. The spawner must have a proxy relationship of this type (or `Any`) with the pure proxy account. Defaults to `ProxyType.Any` for maximum compatibility. If `None`, Substrate will automatically select an available proxy type from the spawner's proxy relationships. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -439,6 +524,7 @@ async def kill_pure_proxy_extrinsic( real_account_ss58=pure_proxy_ss58, force_proxy_type=force_proxy_type, call=kill_pure_call, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -462,10 +548,13 @@ async def proxy_extrinsic( real_account_ss58: str, force_proxy_type: Optional[Union[str, ProxyType]], call: "GenericCall", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Executes a call on behalf of the real account through a proxy. @@ -477,12 +566,16 @@ async def proxy_extrinsic( force_proxy_type: The type of proxy to use for the call. If None, any proxy type can be used. Otherwise, must match one of the allowed proxy types. Can be a string or ProxyType enum value. call: The inner call to be executed on behalf of the real account. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -511,14 +604,26 @@ async def proxy_extrinsic( call=call, ) - response = await subtensor.sign_and_send_extrinsic( - call=proxy_call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=proxy_call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=proxy_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Proxy call executed successfully.[/green]") @@ -538,10 +643,13 @@ async def proxy_announced_extrinsic( real_account_ss58: str, force_proxy_type: Optional[Union[str, ProxyType]], call: "GenericCall", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Executes an announced call on behalf of the real account through a proxy. @@ -557,12 +665,16 @@ async def proxy_announced_extrinsic( force_proxy_type: The type of proxy to use for the call. If None, any proxy type can be used. Otherwise, must match one of the allowed proxy types. Can be a string or ProxyType enum value. call: The inner call to be executed on behalf of the real account (must match the announced call_hash). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -592,14 +704,26 @@ async def proxy_announced_extrinsic( call=call, ) - response = await subtensor.sign_and_send_extrinsic( - call=proxy_call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=proxy_call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=proxy_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Announced proxy call executed successfully.[/green]") @@ -617,10 +741,13 @@ async def announce_extrinsic( wallet: "Wallet", real_account_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Announces a future call that will be executed through a proxy. @@ -630,12 +757,16 @@ async def announce_extrinsic( wallet: Bittensor wallet object (should be the proxy account wallet). real_account_ss58: The SS58 address of the real account on whose behalf the call will be made. call_hash: The hash of the call that will be executed in the future. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -660,14 +791,26 @@ async def announce_extrinsic( call_hash=call_hash, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Proxy call announced successfully.[/green]") @@ -685,10 +828,13 @@ async def reject_announcement_extrinsic( wallet: "Wallet", delegate_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Rejects an announcement made by a proxy delegate. @@ -701,12 +847,16 @@ async def reject_announcement_extrinsic( wallet: Bittensor wallet object (should be the real account wallet). delegate_ss58: The SS58 address of the delegate proxy account whose announcement is being rejected. call_hash: The hash of the call that was announced and is now being rejected. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -731,14 +881,26 @@ async def reject_announcement_extrinsic( call_hash=call_hash, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Announcement rejected successfully.[/green]") @@ -756,10 +918,13 @@ async def remove_announcement_extrinsic( wallet: "Wallet", real_account_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes an announcement made by a proxy account. @@ -772,12 +937,16 @@ async def remove_announcement_extrinsic( wallet: Bittensor wallet object (should be the proxy account wallet that made the announcement). real_account_ss58: The SS58 address of the real account on whose behalf the call was announced. call_hash: The hash of the call that was announced and is now being removed. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -802,14 +971,26 @@ async def remove_announcement_extrinsic( call_hash=call_hash, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Announcement removed successfully.[/green]") @@ -825,10 +1006,13 @@ async def remove_announcement_extrinsic( async def poke_deposit_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adjusts deposits made for proxies and announcements based on current values. @@ -840,10 +1024,14 @@ async def poke_deposit_extrinsic( Parameters: subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object (the account whose deposits will be adjusted). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -866,14 +1054,26 @@ async def poke_deposit_extrinsic( call = await Proxy(subtensor).poke_deposit() - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Deposit poked successfully.[/green]") diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index 152becc436..adcd78c992 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -6,7 +6,9 @@ from typing import Optional, Union, TYPE_CHECKING from bittensor.core.errors import RegistrationError +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.btlogging import logging from bittensor.utils.registration import create_pow_async, log_no_torch_error, torch @@ -20,10 +22,13 @@ async def burned_register_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Registers the wallet to chain by recycling TAO. @@ -31,12 +36,16 @@ async def burned_register_extrinsic( subtensor: Subtensor instance. wallet: Bittensor wallet object. netuid: The ``netuid`` of the subnet to register on. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -84,14 +93,26 @@ async def burned_register_extrinsic( netuid=netuid, hotkey=wallet.hotkey.ss58_address ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) extrinsic_fee = response.extrinsic_fee logging.debug( f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{extrinsic_fee}[/blue]." @@ -140,10 +161,13 @@ async def burned_register_extrinsic( async def register_subnet_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers a new subnetwork on the Bittensor blockchain asynchronously. @@ -151,12 +175,16 @@ async def register_subnet_extrinsic( Parameters: subtensor: The subtensor interface to send the extrinsic. wallet: The wallet to be used for subnet registration. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -182,14 +210,26 @@ async def register_subnet_extrinsic( hotkey=wallet.hotkey.ss58_address ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) if not wait_for_finalization and not wait_for_inclusion: return response @@ -217,10 +257,13 @@ async def register_extrinsic( num_processes: Optional[int] = None, update_interval: Optional[int] = None, log_verbose: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. @@ -239,12 +282,16 @@ async def register_extrinsic( num_processes: The number of processes to use to register. update_interval: The number of nonces to solve between updates. log_verbose: If `True`, the registration process will log more information. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -343,14 +390,26 @@ async def register_extrinsic( nonce=pow_result.nonce, work=[int(byte_) for byte_ in pow_result.seal], ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) if not response.success: # Look error here @@ -404,10 +463,13 @@ async def set_subnet_identity_extrinsic( discord: str, description: str, additional: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Set the identity information for a given subnet. @@ -424,12 +486,16 @@ async def set_subnet_identity_extrinsic( discord: Discord server or contact for the subnet. description: A textual description of the subnet. additional: Any additional metadata or information related to the subnet. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -454,14 +520,26 @@ async def set_subnet_identity_extrinsic( additional=additional, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) if not wait_for_finalization and not wait_for_inclusion: return response diff --git a/bittensor/core/extrinsics/asyncex/root.py b/bittensor/core/extrinsics/asyncex/root.py index 009a01d04e..782569e983 100644 --- a/bittensor/core/extrinsics/asyncex/root.py +++ b/bittensor/core/extrinsics/asyncex/root.py @@ -2,7 +2,9 @@ from typing import Optional, TYPE_CHECKING, Literal from bittensor.core.chain_data import RootClaimType +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance @@ -41,10 +43,13 @@ async def _get_limits(subtensor: "AsyncSubtensor") -> tuple[int, float]: async def root_register_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers the neuron to the root network. @@ -52,12 +57,16 @@ async def root_register_extrinsic( Parameters: subtensor: Subtensor instance to interact with the blockchain. wallet: Bittensor Wallet instance. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -109,14 +118,26 @@ async def root_register_extrinsic( hotkey=wallet.hotkey.ss58_address ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if not response.success: logging.error(f"[red]{response.message}[/red]") @@ -149,10 +170,13 @@ async def set_root_claim_type_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", new_root_claim_type: "Literal['Swap', 'Keep'] | RootClaimType | dict", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Sets the root claim type for the coldkey in provided wallet. @@ -164,12 +188,16 @@ async def set_root_claim_type_extrinsic( - RootClaimType: RootClaimType.Swap, RootClaimType.Keep - Dict: {"KeepSubnets": {"subnets": [1, 2, 3]}} - Callable: RootClaimType.KeepSubnets([1, 2, 3]) + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -186,14 +214,26 @@ async def set_root_claim_type_extrinsic( new_root_claim_type=normalized_type ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -203,10 +243,13 @@ async def claim_root_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuids: "UIDs", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Claims the root emissions for a coldkey. @@ -214,12 +257,16 @@ async def claim_root_extrinsic( subtensor: Subtensor instance to interact with the blockchain. wallet: Bittensor Wallet instance. netuids: The netuids to claim root emissions for. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -232,14 +279,26 @@ async def claim_root_extrinsic( call = await SubtensorModule(subtensor).claim_root(subnets=netuids) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 5a3d3bdbe7..e504e511b4 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -2,8 +2,9 @@ from typing import Optional, Union, TYPE_CHECKING from bittensor.core.errors import MetadataError +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import Commitments, SubtensorModule -from bittensor.core.settings import version_as_int +from bittensor.core.settings import DEFAULT_MEV_PROTECTION, version_as_int from bittensor.core.types import AxonServeCallParams, ExtrinsicResponse from bittensor.utils import ( networking as net, @@ -27,10 +28,13 @@ async def serve_extrinsic( placeholder1: int = 0, placeholder2: int = 0, certificate: Optional[Certificate] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Subscribes a Bittensor endpoint to the subtensor chain. @@ -45,12 +49,16 @@ async def serve_extrinsic( placeholder1: A placeholder for future use. placeholder2: A placeholder for future use. certificate: Certificate to use for TLS. If ``None``, no TLS will be used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -99,15 +107,28 @@ async def serve_extrinsic( ) call = await call_function(**params.as_dict()) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - sign_with="hotkey", - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + signer_keypair=wallet.hotkey, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + sign_with="hotkey", + period=period, + raise_error=raise_error, + ) if response.success: logging.debug( @@ -128,10 +149,13 @@ async def serve_axon_extrinsic( netuid: int, axon: "Axon", certificate: Optional[Certificate] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Serves the axon to the network. @@ -141,12 +165,16 @@ async def serve_axon_extrinsic( netuid: The ``netuid`` being served on. axon: Axon to serve. certificate: Certificate to use for TLS. If ``None``, no TLS will be used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -181,6 +209,7 @@ async def serve_axon_extrinsic( protocol=4, netuid=netuid, certificate=certificate, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -203,11 +232,14 @@ async def publish_metadata_extrinsic( netuid: int, data_type: str, data: Union[bytes, dict], - period: Optional[int] = None, reset_bonds: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, + period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Publishes metadata on the Bittensor network using the specified wallet and network identifier. @@ -222,12 +254,16 @@ async def publish_metadata_extrinsic( data: The actual metadata content to be published. This should be formatted or hashed according to the ``type`` specified. (Note: max ``str`` length is 128 bytes for ``'Raw0-128'``.) reset_bonds: If `True`, the function will reset the bonds for the neuron. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -253,15 +289,28 @@ async def publish_metadata_extrinsic( call = await Commitments(subtensor).set_commitment(netuid=netuid, info=info) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - sign_with=signing_keypair, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + signer_keypair=wallet.hotkey, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + sign_with=signing_keypair, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: return response diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index d687afd346..69d6236199 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -4,8 +4,10 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.errors import BalanceTypeError +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils import format_error_message from bittensor.utils.balance import Balance @@ -25,10 +27,13 @@ async def add_stake_extrinsic( safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified subnet. @@ -44,12 +49,16 @@ async def add_stake_extrinsic( safe_staking: If True, enables price safety checks. allow_partial_stake: If True, allows partial unstaking if price tolerance exceeded. rate_tolerance: Maximum allowed price increase percentage (0.005 = 0.5%). + mev_protection: If True, encrypts and submits the staking transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -84,8 +93,6 @@ async def add_stake_extrinsic( if amount > old_balance - existential_deposit: # If we are staking all, we need to leave at least the existential deposit. amount = old_balance - existential_deposit - else: - amount = amount # Check enough to stake. if amount > old_balance: @@ -134,16 +141,28 @@ async def add_stake_extrinsic( ) block_hash_before = await subtensor.get_block_hash() - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - use_nonce=True, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, + ) if response.success: sim_swap = await subtensor.sim_swap( origin_netuid=0, @@ -201,10 +220,13 @@ async def add_stake_multiple_extrinsic( netuids: UIDs, hotkey_ss58s: list[str], amounts: list[Balance], + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey on subnet with @@ -216,12 +238,16 @@ async def add_stake_multiple_extrinsic( netuids: List of netuids to stake to. hotkey_ss58s: List of hotkeys to stake to. amounts: List of corresponding TAO amounts to bet for each netuid and hotkey. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -338,6 +364,7 @@ async def add_stake_multiple_extrinsic( netuid=netuid, hotkey_ss58=hotkey_ss58, amount=amount, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -422,10 +449,13 @@ async def set_auto_stake_extrinsic( wallet: "Wallet", netuid: int, hotkey_ss58: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism. @@ -435,12 +465,16 @@ async def set_auto_stake_extrinsic( netuid: The subnet unique identifier. hotkey_ss58: The SS58 address of the validator's hotkey to which the miner automatically stakes all rewards received from the specified subnet immediately upon receipt. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -455,14 +489,26 @@ async def set_auto_stake_extrinsic( netuid=netuid, hotkey=hotkey_ss58 ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) if response.success: logging.debug(response.message) diff --git a/bittensor/core/extrinsics/asyncex/start_call.py b/bittensor/core/extrinsics/asyncex/start_call.py index 05d9f542ab..b0e71eb93e 100644 --- a/bittensor/core/extrinsics/asyncex/start_call.py +++ b/bittensor/core/extrinsics/asyncex/start_call.py @@ -1,6 +1,8 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: @@ -12,10 +14,13 @@ async def start_call_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start a @@ -25,12 +30,16 @@ async def start_call_extrinsic( subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -43,14 +52,26 @@ async def start_call_extrinsic( call = await SubtensorModule(subtensor).start_call(netuid=netuid) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/take.py b/bittensor/core/extrinsics/asyncex/take.py index b3b468ba69..78c4d02089 100644 --- a/bittensor/core/extrinsics/asyncex/take.py +++ b/bittensor/core/extrinsics/asyncex/take.py @@ -2,7 +2,9 @@ from bittensor_wallet.bittensor_wallet import Wallet +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: @@ -15,10 +17,13 @@ async def set_take_extrinsic( hotkey_ss58: str, take: int, action: Literal["increase_take", "decrease_take"], + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Sets the delegate 'take' percentage for a neuron identified by its hotkey. @@ -28,12 +33,16 @@ async def set_take_extrinsic( hotkey_ss58: SS58 address of the hotkey to set take for. take: The percentage of rewards that the delegate claims from nominators. action: The call function to use to set the take. Can be either "increase_take" or "decrease_take". + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -55,14 +64,26 @@ async def set_take_extrinsic( else: raise ValueError(f"Invalid action: {action}") - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index cf833aa7e6..9bde182535 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -1,9 +1,14 @@ import asyncio from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import Balances from bittensor.core.extrinsics.utils import get_transfer_fn_params -from bittensor.core.settings import NETWORK_EXPLORER_MAP, DEFAULT_NETWORK +from bittensor.core.settings import ( + DEFAULT_MEV_PROTECTION, + NETWORK_EXPLORER_MAP, + DEFAULT_NETWORK, +) from bittensor.core.types import ExtrinsicResponse from bittensor.utils import ( get_explorer_url_for_network, @@ -24,10 +29,13 @@ async def transfer_extrinsic( amount: Optional[Balance], keep_alive: bool = True, transfer_all: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Transfers funds from this wallet to the destination public key address. @@ -38,12 +46,16 @@ async def transfer_extrinsic( amount: Amount to stake as Bittensor balance. `None` if transferring all. transfer_all: Whether to transfer all funds from this wallet to the destination address. keep_alive: If set, keeps the account alive by keeping the balance above the existential deposit. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -106,14 +118,26 @@ async def transfer_extrinsic( call = await getattr(Balances(subtensor), call_function)(**call_params) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) response.transaction_tao_fee = fee if response.success: diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 29028fdc7f..9ea41f9352 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -4,8 +4,10 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.errors import BalanceTypeError +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.core.types import UIDs from bittensor.utils import format_error_message @@ -26,10 +28,13 @@ async def unstake_extrinsic( allow_partial_stake: bool = False, rate_tolerance: float = 0.005, safe_unstaking: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes stake into the wallet coldkey from the specified hotkey ``uid``. @@ -43,12 +48,16 @@ async def unstake_extrinsic( allow_partial_stake: If true, allows partial unstaking if price tolerance exceeded. safe_unstaking: If true, enables price safety checks. rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -121,16 +130,28 @@ async def unstake_extrinsic( logging.debug(logging_message) block_hash_before = await subtensor.get_block_hash() - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - use_nonce=True, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, + ) if response.success: sim_swap = await subtensor.sim_swap( @@ -189,10 +210,13 @@ async def unstake_all_extrinsic( netuid: int, hotkey_ss58: str, rate_tolerance: Optional[float] = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. @@ -203,12 +227,16 @@ async def unstake_all_extrinsic( hotkey_ss58: The SS58 address of the hotkey to unstake from. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -228,16 +256,28 @@ async def unstake_all_extrinsic( limit_price=limit_price, ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - use_nonce=True, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -251,10 +291,13 @@ async def unstake_multiple_extrinsic( amounts: Optional[list[Balance]] = None, rate_tolerance: Optional[float] = 0.05, unstake_all: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. @@ -267,12 +310,16 @@ async def unstake_multiple_extrinsic( amounts: List of amounts to unstake. If ``None``, unstake all. rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). unstake_all: If true, unstakes all tokens. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -399,6 +446,7 @@ async def unstake_multiple_extrinsic( hotkey_ss58=hotkey_ss58, netuid=netuid, rate_tolerance=rate_tolerance, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -411,6 +459,8 @@ async def unstake_multiple_extrinsic( netuid=netuid, hotkey_ss58=hotkey_ss58, amount=unstaking_balance, + rate_tolerance=rate_tolerance, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 37370642e4..7aa2ad63e0 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -4,8 +4,9 @@ from bittensor_drand import get_encrypted_commit +from bittensor.core.extrinsics.asyncex.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule -from bittensor.core.settings import version_as_int +from bittensor.core.settings import DEFAULT_MEV_PROTECTION, version_as_int from bittensor.core.types import ExtrinsicResponse, Salt, UIDs, Weights from bittensor.utils import get_mechid_storage_index from bittensor.utils.btlogging import logging @@ -29,10 +30,13 @@ async def commit_timelocked_weights_extrinsic( block_time: Union[int, float], commit_reveal_version: int = 4, version_key: int = version_as_int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Commits the weights for a specific sub subnet mechanism on the Bittensor blockchain using the provided wallet. @@ -46,12 +50,16 @@ async def commit_timelocked_weights_extrinsic( block_time: The number of seconds for block duration. commit_reveal_version: The version of the commit-reveal in the chain. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -98,17 +106,30 @@ async def commit_timelocked_weights_extrinsic( commit_reveal_version=commit_reveal_version, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + signer_keypair=wallet.hotkey, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with=signing_keypair, + nonce_key=signing_keypair, + raise_error=raise_error, + ) if response.success: logging.debug(response.message) @@ -134,10 +155,13 @@ async def commit_weights_extrinsic( weights: Weights, salt: Salt, version_key: int = version_as_int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. @@ -150,12 +174,16 @@ async def commit_weights_extrinsic( weights: NumPy array of weight values corresponding to each UID. salt: list of randomly generated integers as salt to generated weighted hash. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -186,17 +214,30 @@ async def commit_weights_extrinsic( commit_hash=commit_hash, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + signer_keypair=wallet.hotkey, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with=signing_keypair, + nonce_key=signing_keypair, + raise_error=raise_error, + ) if response.success: logging.debug(response.message) @@ -218,10 +259,13 @@ async def reveal_weights_extrinsic( weights: Weights, salt: Salt, version_key: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. @@ -235,12 +279,16 @@ async def reveal_weights_extrinsic( weights: List of weight values corresponding to each UID. salt: List of salt values corresponding to the hash function. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -266,17 +314,30 @@ async def reveal_weights_extrinsic( version_key=version_key, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + signer_keypair=wallet.hotkey, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with=signing_keypair, + nonce_key=signing_keypair, + raise_error=raise_error, + ) if response.success: logging.debug(response.message) @@ -297,10 +358,13 @@ async def set_weights_extrinsic( uids: UIDs, weights: Weights, version_key: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. @@ -313,12 +377,16 @@ async def set_weights_extrinsic( uids: List of neuron UIDs for which weights are being revealed. weights: List of weight values corresponding to each UID. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -343,17 +411,30 @@ async def set_weights_extrinsic( version_key=version_key, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - use_nonce=True, - nonce_key=signing_keypair, - sign_with=signing_keypair, - raise_error=raise_error, - ) + if mev_protection: + response = await submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + signer_keypair=wallet.hotkey, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + use_nonce=True, + nonce_key=signing_keypair, + sign_with=signing_keypair, + raise_error=raise_error, + ) if response.success: logging.debug(response.message) diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index 6f5ae76244..5172d2c2f2 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -1,6 +1,8 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule, Sudo +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils import float_to_u64 @@ -15,10 +17,13 @@ def set_children_extrinsic( hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Allows a coldkey to set children-keys. @@ -29,12 +34,16 @@ def set_children_extrinsic( hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -67,14 +76,26 @@ def set_children_extrinsic( ], ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) return response except Exception as error: @@ -85,10 +106,13 @@ def root_set_pending_childkey_cooldown_extrinsic( subtensor: "Subtensor", wallet: "Wallet", cooldown: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Allows a root coldkey to set children-keys. @@ -97,12 +121,16 @@ def root_set_pending_childkey_cooldown_extrinsic( subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). cooldown: The cooldown period in blocks. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -119,14 +147,26 @@ def root_set_pending_childkey_cooldown_extrinsic( sudo_call = Sudo(subtensor).sudo(call=call) - response = subtensor.sign_and_send_extrinsic( - call=sudo_call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=sudo_call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=sudo_call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) return response except Exception as error: diff --git a/bittensor/core/extrinsics/crowdloan.py b/bittensor/core/extrinsics/crowdloan.py index 33c0d35574..2acfda7e08 100644 --- a/bittensor/core/extrinsics/crowdloan.py +++ b/bittensor/core/extrinsics/crowdloan.py @@ -1,6 +1,8 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import Crowdloan +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import check_balance_amount @@ -16,10 +18,13 @@ def contribute_crowdloan_extrinsic( wallet: "Wallet", crowdloan_id: int, amount: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Contributes funds to an active crowdloan campaign. @@ -29,12 +34,16 @@ def contribute_crowdloan_extrinsic( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to contribute to. amount: Amount to contribute. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -51,14 +60,26 @@ def contribute_crowdloan_extrinsic( crowdloan_id=crowdloan_id, amount=amount.rao ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -73,10 +94,13 @@ def create_crowdloan_extrinsic( end: int, call: Optional["GenericCall"] = None, target_address: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Creates a new crowdloan campaign on-chain. @@ -90,12 +114,16 @@ def create_crowdloan_extrinsic( end: Block number when the campaign ends. call: Runtime call data (e.g., subtensor::register_leased_network). target_address: SS58 address to transfer funds to on success. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -119,14 +147,26 @@ def create_crowdloan_extrinsic( target_address=target_address, ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -136,10 +176,13 @@ def dissolve_crowdloan_extrinsic( subtensor: "Subtensor", wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Dissolves a completed or failed crowdloan campaign after all refunds are processed. @@ -151,12 +194,16 @@ def dissolve_crowdloan_extrinsic( subtensor: Active Subtensor connection. wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to dissolve. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -175,14 +222,26 @@ def dissolve_crowdloan_extrinsic( call = Crowdloan(subtensor).dissolve(crowdloan_id=crowdloan_id) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -192,10 +251,13 @@ def finalize_crowdloan_extrinsic( subtensor: "Subtensor", wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Finalizes a successful crowdloan campaign once the cap has been reached and the end block has passed. @@ -206,10 +268,14 @@ def finalize_crowdloan_extrinsic( subtensor: Active Subtensor connection. wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to finalize. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -222,14 +288,26 @@ def finalize_crowdloan_extrinsic( call = Crowdloan(subtensor).finalize(crowdloan_id=crowdloan_id) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -239,10 +317,13 @@ def refund_crowdloan_extrinsic( subtensor: "Subtensor", wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Refunds contributors from a failed or expired crowdloan campaign. @@ -254,12 +335,16 @@ def refund_crowdloan_extrinsic( subtensor: Active Subtensor connection. wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to refund. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -278,14 +363,26 @@ def refund_crowdloan_extrinsic( call = Crowdloan(subtensor).refund(crowdloan_id=crowdloan_id) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -296,10 +393,13 @@ def update_cap_crowdloan_extrinsic( wallet: "Wallet", crowdloan_id: int, new_cap: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Updates the fundraising cap (maximum total contribution) of a non-finalized crowdloan. @@ -312,12 +412,16 @@ def update_cap_crowdloan_extrinsic( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_cap: The new fundraising cap (in TAO or Balance). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -339,14 +443,26 @@ def update_cap_crowdloan_extrinsic( crowdloan_id=crowdloan_id, new_cap=new_cap.rao ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -357,10 +473,13 @@ def update_end_crowdloan_extrinsic( wallet: "Wallet", crowdloan_id: int, new_end: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Updates the end block of a non-finalized crowdloan campaign. @@ -373,12 +492,16 @@ def update_end_crowdloan_extrinsic( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_end: The new block number at which the crowdloan will end. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -399,14 +522,26 @@ def update_end_crowdloan_extrinsic( crowdloan_id=crowdloan_id, new_end=new_end ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -417,10 +552,13 @@ def update_min_contribution_crowdloan_extrinsic( wallet: "Wallet", crowdloan_id: int, new_min_contribution: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Updates the minimum contribution amount of a non-finalized crowdloan. @@ -433,12 +571,16 @@ def update_min_contribution_crowdloan_extrinsic( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_min_contribution: The new minimum contribution amount (in TAO or Balance). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -460,14 +602,26 @@ def update_min_contribution_crowdloan_extrinsic( crowdloan_id=crowdloan_id, new_min_contribution=new_min_contribution.rao ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -477,10 +631,13 @@ def withdraw_crowdloan_extrinsic( subtensor: "Subtensor", wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> "ExtrinsicResponse": """ Withdraws a contribution from an active (not yet finalized or dissolved) crowdloan. @@ -489,12 +646,16 @@ def withdraw_crowdloan_extrinsic( subtensor: Active Subtensor connection. wallet: Wallet instance used to sign the transaction (must be unlocked). crowdloan_id: The unique identifier of the crowdloan to withdraw from. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -511,14 +672,26 @@ def withdraw_crowdloan_extrinsic( call = Crowdloan(subtensor).withdraw(crowdloan_id=crowdloan_id) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/liquidity.py b/bittensor/core/extrinsics/liquidity.py index 043bc6d5a2..3dc8632339 100644 --- a/bittensor/core/extrinsics/liquidity.py +++ b/bittensor/core/extrinsics/liquidity.py @@ -1,6 +1,8 @@ from typing import Optional, TYPE_CHECKING +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import Swap +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance from bittensor.utils.liquidity import price_to_tick @@ -18,10 +20,13 @@ def add_liquidity_extrinsic( price_low: Balance, price_high: Balance, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds liquidity to the specified price range. @@ -34,12 +39,16 @@ def add_liquidity_extrinsic( price_low: The lower bound of the price tick range. price_high: The upper bound of the price tick range. hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -64,14 +73,26 @@ def add_liquidity_extrinsic( hotkey=hotkey_ss58 or wallet.hotkey.ss58_address, ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -83,10 +104,13 @@ def modify_liquidity_extrinsic( position_id: int, liquidity_delta: Balance, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Modifies liquidity in liquidity position by adding or removing liquidity from it. @@ -97,12 +121,16 @@ def modify_liquidity_extrinsic( position_id: The id of the position record in the pool. liquidity_delta: The amount of liquidity to be added or removed (add if positive or remove if negative). hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -126,14 +154,26 @@ def modify_liquidity_extrinsic( liquidity_delta=liquidity_delta.rao, ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -144,10 +184,13 @@ def remove_liquidity_extrinsic( netuid: int, position_id: int, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Remove liquidity and credit balances back to wallet's hotkey stake. @@ -157,12 +200,16 @@ def remove_liquidity_extrinsic( netuid: The UID of the target subnet for which the call is being initiated. position_id: The id of the position record in the pool. hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -185,14 +232,26 @@ def remove_liquidity_extrinsic( position_id=position_id, ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -202,10 +261,13 @@ def toggle_user_liquidity_extrinsic( wallet: "Wallet", netuid: int, enable: bool, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Allow to toggle user liquidity for specified subnet. @@ -214,12 +276,16 @@ def toggle_user_liquidity_extrinsic( wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. enable: Boolean indicating whether to enable user liquidity. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -235,13 +301,25 @@ def toggle_user_liquidity_extrinsic( enable=enable, ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/mev_shield.py b/bittensor/core/extrinsics/mev_shield.py new file mode 100644 index 0000000000..7346da363c --- /dev/null +++ b/bittensor/core/extrinsics/mev_shield.py @@ -0,0 +1,224 @@ +"""Module provides sync MEV Shield extrinsics.""" + +from typing import TYPE_CHECKING, Optional + +from async_substrate_interface import ExtrinsicReceipt + +from bittensor.core.extrinsics.pallets import MevShield +from bittensor.core.extrinsics.utils import ( + get_event_attributes_by_event_name, + get_mev_commitment_and_ciphertext, + post_process_mev_response, + POST_SUBMIT_MEV_EVENTS, + MEV_SUBMITTED_EVENT, +) +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils.btlogging import logging + +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor + from bittensor_wallet import Wallet, Keypair + from scalecodec.types import GenericCall + + +def find_revealed_extrinsic( + subtensor: "Subtensor", + event_names: list[str], + event_hash_id: str, + start_block_hash: str, + blocks_ahead: int = 5, +) -> tuple[str, "ExtrinsicReceipt"] | tuple[None, None]: + """ + Searches for an extrinsic containing a specific MEV Shield event in subsequent blocks. + + This function iterates through blocks starting from the specified block hash and searches for extrinsics that + contain a MEV Shield event (DecryptedExecuted or DecryptedRejected) matching the provided wrapper_id and signer. It + checks each extrinsic's triggered events to find the matching event. + + Parameters: + subtensor: The Subtensor instance used for blockchain queries. + event_names: The event identifiers to search for. Typically "DecryptedExecuted" or "DecryptedRejected" for MEV + Shield transactions. + event_hash_id: The wrapper_id (hash of (author, commitment, ciphertext)) to match. This uniquely identifies a + specific MEV Shield submission. + start_block_hash: The hash of the block where the search should begin. Usually the block where submit_encrypted + was included. + blocks_ahead: Maximum number of blocks to search ahead from the start block. Defaults to 5 blocks. The function + will check blocks from start_block + 1 to start_block + blocks_ahead (the start block itself is not checked, + as execute_revealed will be in subsequent blocks). + + Returns: + Tuple with event name and ExtrinsicReceipt object. + """ + start_block_number = subtensor.substrate.get_block_number(start_block_hash) + + for offset in range(1, blocks_ahead + 1): + current_block_number = start_block_number + offset + + try: + current_block_hash = subtensor.substrate.get_block_hash( + current_block_number + ) + events = subtensor.substrate.get_events(current_block_hash) + except Exception as e: + logging.debug( + f"Error getting extrinsics for block `{current_block_number}`: {e}" + ) + continue + + for event_name in event_names: + if event := get_event_attributes_by_event_name(events, event_name): + if event["attributes"]["id"] == event_hash_id: + return event_name, ExtrinsicReceipt( + substrate=subtensor.substrate, + block_hash=current_block_hash, + extrinsic_idx=event["extrinsic_idx"], + ) + + logging.debug(f"No {event_names} event found in block {current_block_number}.") + subtensor.wait_for_block() + + return None, None + + +def submit_encrypted_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + call: "GenericCall", + signer_keypair: Optional["Keypair"] = None, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, + blocks_for_revealed_execution: int = 5, +) -> ExtrinsicResponse: + """ + Submits an encrypted extrinsic to the MEV Shield pallet. + + This function encrypts a call using ML-KEM-768 + XChaCha20Poly1305 and submits it to the MevShield pallet. The + extrinsic remains encrypted in the transaction pool until it is included in a block and decrypted by validators. + + Parameters: + subtensor: The Subtensor client instance used for blockchain interaction. + wallet: The wallet used to sign the extrinsic (must be unlocked, coldkey will be used for signing). + call: The GenericCall object to encrypt and submit. + signer_keypair: The Keypair object used for signing the inner call. + period: The number of blocks during which the transaction will remain valid after it's submitted. If the + transaction is not included in a block within that number of blocks, it will expire and be rejected. You can + think of it as an expiration date for the transaction. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the DecryptedExecuted event, indicating that validators have + successfully decrypted and executed the inner call. If True, the function will poll subsequent blocks for + the event matching this submission's commitment. + blocks_for_revealed_execution: Maximum number of blocks to poll for the DecryptedExecuted event after inclusion. + The function checks blocks from start_block + 1 to start_block + blocks_for_revealed_execution. Returns + immediately if the event is found before the block limit is reached. + + Returns: + ExtrinsicResponse: The result object of the extrinsic execution. + + Raises: + ValueError: If NextKey is not available in storage or encryption fails. + SubstrateRequestException: If the extrinsic fails to be submitted or included. + + Note: + The encryption uses the public key from NextKey storage, which rotates every block. The payload structure is: + payload_core = signer_bytes (32B) + key_hash (32B Blake2-256 hash of NextKey) + SCALE(call) + plaintext = payload_core + b"\\x01" + signature (64B for sr25519) + commitment = blake2_256(payload_core) + + The key_hash binds the transaction to the key epoch at submission time and replaces nonce-based replay + protection. + """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if wait_for_revealed_execution and not wait_for_inclusion: + return ExtrinsicResponse.from_exception( + raise_error=raise_error, + error=ValueError( + "`wait_for_inclusion` must be `True` if `wait_for_revealed_execution` is `True`." + ), + ) + + # Use wallet.coldkey as default signer if signer_keypair is not provided + if signer_keypair is None: + signer_keypair = wallet.coldkey + + ml_kem_768_public_key = subtensor.get_mev_shield_next_key() + if ml_kem_768_public_key is None: + return ExtrinsicResponse.from_exception( + raise_error=raise_error, + error=ValueError("MEV Shield NextKey not available in storage."), + ) + + genesis_hash = subtensor.get_block_hash(block=0) + + mev_commitment, mev_ciphertext, payload_core, signature = ( + get_mev_commitment_and_ciphertext( + call=call, + signer_keypair=signer_keypair, + genesis_hash=genesis_hash, + ml_kem_768_public_key=ml_kem_768_public_key, + ) + ) + + extrinsic_call = MevShield(subtensor).submit_encrypted( + commitment=mev_commitment, + ciphertext=mev_ciphertext, + ) + + response = subtensor.sign_and_send_extrinsic( + wallet=wallet, + call=extrinsic_call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if response.success: + response.data = { + "commitment": mev_commitment, + "ciphertext": mev_ciphertext, + "ml_kem_768_public_key": ml_kem_768_public_key, + "payload_core": payload_core, + "signature": signature, + "submitting_id": extrinsic_call.call_hash, + } + if wait_for_revealed_execution: + triggered_events = response.extrinsic_receipt.triggered_events + event_hash_id = get_event_attributes_by_event_name( + events=triggered_events, # type: ignore + event_name=MEV_SUBMITTED_EVENT, + )["attributes"]["id"] + + reveled_event, reveled_extrinsic = find_revealed_extrinsic( + subtensor=subtensor, + event_names=POST_SUBMIT_MEV_EVENTS, + event_hash_id=event_hash_id, + start_block_hash=response.extrinsic_receipt.block_hash, + blocks_ahead=blocks_for_revealed_execution, + ) + + post_process_mev_response( + response=response, + revealed_name=reveled_event, + revealed_extrinsic=reveled_extrinsic, + raise_error=raise_error, + ) + + logging.debug("[green]Encrypted extrinsic submitted successfully.[/green]") + else: + logging.error(f"[red]{response.message}[/red]") + + return response + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index 7bc0a86c2a..17ddcdbd73 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -1,6 +1,8 @@ from typing import Optional, TYPE_CHECKING +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -45,10 +47,13 @@ def move_stake_extrinsic( destination_hotkey_ss58: str, amount: Optional[Balance] = None, move_all_stake: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Moves stake to a different hotkey and/or subnet while keeping the same coldkey owner. @@ -62,12 +67,16 @@ def move_stake_extrinsic( destination_hotkey_ss58: The SS58 address of the destination hotkey. amount: Amount to move. move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -117,14 +126,26 @@ def move_stake_extrinsic( alpha_amount=amount.rao, ) block_before = subtensor.block - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: sim_swap = subtensor.sim_swap( @@ -181,10 +202,13 @@ def transfer_stake_extrinsic( origin_netuid: int, destination_netuid: int, amount: Balance, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Transfers stake from one subnet to another while changing the coldkey owner. @@ -197,12 +221,16 @@ def transfer_stake_extrinsic( origin_netuid: Network UID of the origin subnet. destination_netuid: Network UID of the destination subnet. amount: The amount of stake to transfer as a `Balance` object. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -247,14 +275,26 @@ def transfer_stake_extrinsic( alpha_amount=amount.rao, ) block_before = subtensor.block - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: sim_swap = subtensor.sim_swap( @@ -305,10 +345,13 @@ def swap_stake_extrinsic( safe_swapping: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Moves stake between subnets while keeping the same coldkey-hotkey pair ownership. @@ -323,13 +366,16 @@ def swap_stake_extrinsic( safe_swapping: If true, enables price safety checks to protect against price impact. allow_partial_stake: If true, allows partial stake swaps when the full amount would exceed the price tolerance. rate_tolerance: Maximum allowed increase in a price ratio (0.005 = 0.5%). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. - + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -397,14 +443,26 @@ def swap_stake_extrinsic( ) block_before = subtensor.block - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: sim_swap = subtensor.sim_swap( diff --git a/bittensor/core/extrinsics/pallets/__init__.py b/bittensor/core/extrinsics/pallets/__init__.py index facf04b2b8..328eb67c83 100644 --- a/bittensor/core/extrinsics/pallets/__init__.py +++ b/bittensor/core/extrinsics/pallets/__init__.py @@ -2,6 +2,7 @@ from .balances import Balances from .commitments import Commitments from .crowdloan import Crowdloan +from .mev_shield import MevShield from .proxy import Proxy from .subtensor_module import SubtensorModule from .sudo import Sudo @@ -13,6 +14,7 @@ "Balances", "Commitments", "Crowdloan", + "MevShield", "Proxy", "SubtensorModule", "Sudo", diff --git a/bittensor/core/extrinsics/pallets/mev_shield.py b/bittensor/core/extrinsics/pallets/mev_shield.py new file mode 100644 index 0000000000..cef4995bc8 --- /dev/null +++ b/bittensor/core/extrinsics/pallets/mev_shield.py @@ -0,0 +1,59 @@ +from dataclasses import dataclass + +from .base import CallBuilder as _BasePallet, Call + + +@dataclass +class MevShield(_BasePallet): + """Factory class for creating GenericCall objects for MevShield pallet functions. + + This class provides methods to create GenericCall instances for all MevShield pallet extrinsics. + + Works with both sync (Subtensor) and async (AsyncSubtensor) instances. For async operations, pass an AsyncSubtensor + instance and await the result. + + Example: + # Sync usage + call = MevShield(subtensor).submit_encrypted( + commitment="0x1234...", + ciphertext=b"encrypted_data..." + ) + response = subtensor.sign_and_send_extrinsic(call=call, ...) + + # Async usage + call = await MevShield(async_subtensor).submit_encrypted( + commitment="0x1234...", + ciphertext=b"encrypted_data..." + ) + response = await async_subtensor.sign_and_send_extrinsic(call=call, ...) + """ + + def submit_encrypted( + self, + commitment: str, + ciphertext: bytes, + ) -> Call: + """Returns GenericCall instance for MevShield function submit_encrypted. + + This function submits an encrypted extrinsic to the MEV Shield pallet. The extrinsic remains encrypted in the + transaction pool until it is included in a block and decrypted by validators. + + Parameters: + commitment: The blake2_256 hash of the payload_core (signer + nonce + SCALE(call)). Must be a hex string + with "0x" prefix. + ciphertext: The encrypted blob containing the payload and signature. + Format: [u16 kem_len LE][kem_ct][nonce24][aead_ct] + Maximum size: 8192 bytes. + + Returns: + GenericCall instance ready for extrinsic submission. + + Note: + The commitment is used to verify the ciphertext's content at decryption time. The ciphertext is encrypted + using ML-KEM-768 + XChaCha20Poly1305 with the public key from the NextKey storage item, which rotates every + block. + """ + return self.create_composed_call( + commitment=commitment, + ciphertext=ciphertext, + ) diff --git a/bittensor/core/extrinsics/proxy.py b/bittensor/core/extrinsics/proxy.py index 1942201af6..508131d6a5 100644 --- a/bittensor/core/extrinsics/proxy.py +++ b/bittensor/core/extrinsics/proxy.py @@ -1,8 +1,10 @@ from typing import TYPE_CHECKING, Optional, Union from bittensor.core.chain_data.proxy import ProxyType +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import Proxy from bittensor.core.extrinsics.utils import apply_pure_proxy_data +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.btlogging import logging @@ -18,10 +20,13 @@ def add_proxy_extrinsic( delegate_ss58: str, proxy_type: Union[str, ProxyType], delay: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds a proxy relationship. @@ -33,12 +38,16 @@ def add_proxy_extrinsic( proxy_type: The type of proxy permissions (e.g., "Any", "NonTransfer", "Governance", "Staking"). Can be a string or ProxyType enum value. delay: The number of blocks before the proxy can be used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -63,14 +72,26 @@ def add_proxy_extrinsic( delay=delay, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Proxy added successfully.[/green]") @@ -89,10 +110,13 @@ def remove_proxy_extrinsic( delegate_ss58: str, proxy_type: Union[str, ProxyType], delay: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes a proxy relationship. @@ -103,12 +127,16 @@ def remove_proxy_extrinsic( delegate_ss58: The SS58 address of the delegate proxy account to remove. proxy_type: The type of proxy permissions to remove. Can be a string or ProxyType enum value. delay: The number of blocks before the proxy removal takes effect. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -133,14 +161,26 @@ def remove_proxy_extrinsic( delay=delay, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Proxy removed successfully.[/green]") @@ -156,10 +196,13 @@ def remove_proxy_extrinsic( def remove_proxies_extrinsic( subtensor: "Subtensor", wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes all proxy relationships for the account. @@ -170,10 +213,14 @@ def remove_proxies_extrinsic( Parameters: subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object (the account whose proxies will be removed). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -191,14 +238,26 @@ def remove_proxies_extrinsic( call = Proxy(subtensor).remove_proxies() - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]All proxies removed successfully.[/green]") @@ -217,10 +276,13 @@ def create_pure_proxy_extrinsic( proxy_type: Union[str, ProxyType], delay: int, index: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Creates a pure proxy account. @@ -231,12 +293,16 @@ def create_pure_proxy_extrinsic( proxy_type: The type of proxy permissions for the pure proxy. Can be a string or ProxyType enum value. delay: The number of blocks before the pure proxy can be used. index: The index to use for generating the pure proxy account address. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -268,14 +334,26 @@ def create_pure_proxy_extrinsic( index=index, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Pure proxy created successfully.[/green]") @@ -321,10 +399,13 @@ def kill_pure_proxy_extrinsic( height: int, ext_index: int, force_proxy_type: Optional[Union[str, ProxyType]] = ProxyType.Any, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Kills (removes) a pure proxy account. @@ -353,12 +434,16 @@ def kill_pure_proxy_extrinsic( the pure proxy account should be used. The spawner must have a proxy relationship of this type (or `Any`) with the pure proxy account. Defaults to `ProxyType.Any` for maximum compatibility. If `None`, Substrate will automatically select an available proxy type from the spawner's proxy relationships. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -435,6 +520,7 @@ def kill_pure_proxy_extrinsic( real_account_ss58=pure_proxy_ss58, force_proxy_type=force_proxy_type, call=kill_pure_call, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -458,10 +544,13 @@ def proxy_extrinsic( real_account_ss58: str, force_proxy_type: Optional[Union[str, ProxyType]], call: "GenericCall", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Executes a call on behalf of the real account through a proxy. @@ -473,12 +562,16 @@ def proxy_extrinsic( force_proxy_type: The type of proxy to use for the call. If None, any proxy type can be used. Otherwise, must match one of the allowed proxy types. Can be a string or ProxyType enum value. call: The inner call to be executed on behalf of the real account. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -507,14 +600,26 @@ def proxy_extrinsic( call=call, ) - response = subtensor.sign_and_send_extrinsic( - call=proxy_call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=proxy_call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=proxy_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Proxy call executed successfully.[/green]") @@ -534,10 +639,13 @@ def proxy_announced_extrinsic( real_account_ss58: str, force_proxy_type: Optional[Union[str, ProxyType]], call: "GenericCall", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Executes an announced call on behalf of the real account through a proxy. @@ -553,12 +661,16 @@ def proxy_announced_extrinsic( force_proxy_type: The type of proxy to use for the call. If None, any proxy type can be used. Otherwise, must match one of the allowed proxy types. Can be a string or ProxyType enum value. call: The inner call to be executed on behalf of the real account (must match the announced call_hash). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -588,14 +700,26 @@ def proxy_announced_extrinsic( call=call, ) - response = subtensor.sign_and_send_extrinsic( - call=proxy_call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=proxy_call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=proxy_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Announced proxy call executed successfully.[/green]") @@ -613,10 +737,13 @@ def announce_extrinsic( wallet: "Wallet", real_account_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Announces a future call that will be executed through a proxy. @@ -626,12 +753,16 @@ def announce_extrinsic( wallet: Bittensor wallet object (should be the proxy account wallet). real_account_ss58: The SS58 address of the real account on whose behalf the call will be made. call_hash: The hash of the call that will be executed in the future. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -656,14 +787,26 @@ def announce_extrinsic( call_hash=call_hash, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Proxy call announced successfully.[/green]") @@ -681,10 +824,13 @@ def reject_announcement_extrinsic( wallet: "Wallet", delegate_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Rejects an announcement made by a proxy delegate. @@ -697,12 +843,16 @@ def reject_announcement_extrinsic( wallet: Bittensor wallet object (should be the real account wallet). delegate_ss58: The SS58 address of the delegate proxy account whose announcement is being rejected. call_hash: The hash of the call that was announced and is now being rejected. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -727,14 +877,26 @@ def reject_announcement_extrinsic( call_hash=call_hash, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Announcement rejected successfully.[/green]") @@ -752,10 +914,13 @@ def remove_announcement_extrinsic( wallet: "Wallet", real_account_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes an announcement made by a proxy account. @@ -768,12 +933,16 @@ def remove_announcement_extrinsic( wallet: Bittensor wallet object (should be the proxy account wallet that made the announcement). real_account_ss58: The SS58 address of the real account on whose behalf the call was announced. call_hash: The hash of the call that was announced and is now being removed. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -798,14 +967,26 @@ def remove_announcement_extrinsic( call_hash=call_hash, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Announcement removed successfully.[/green]") @@ -821,10 +1002,13 @@ def remove_announcement_extrinsic( def poke_deposit_extrinsic( subtensor: "Subtensor", wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adjusts deposits made for proxies and announcements based on current values. @@ -836,10 +1020,14 @@ def poke_deposit_extrinsic( Parameters: subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object (the account whose deposits will be adjusted). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -862,14 +1050,26 @@ def poke_deposit_extrinsic( call = Proxy(subtensor).poke_deposit() - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: logging.debug("[green]Deposit poked successfully.[/green]") diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index 6474fcdafd..50962fb9c1 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -6,7 +6,9 @@ from typing import Optional, Union, TYPE_CHECKING from bittensor.core.errors import RegistrationError +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.btlogging import logging from bittensor.utils.registration import create_pow, log_no_torch_error, torch @@ -20,10 +22,13 @@ def burned_register_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Registers the wallet to chain by recycling TAO. @@ -31,12 +36,16 @@ def burned_register_extrinsic( subtensor: Subtensor instance. wallet: Bittensor wallet object. netuid: The ``netuid`` of the subnet to register on. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -81,14 +90,26 @@ def burned_register_extrinsic( netuid=netuid, hotkey=wallet.hotkey.ss58_address ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) extrinsic_fee = response.extrinsic_fee logging.debug( f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{extrinsic_fee}[/blue]." @@ -135,10 +156,13 @@ def burned_register_extrinsic( def register_subnet_extrinsic( subtensor: "Subtensor", wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers a new subnetwork on the Bittensor blockchain. @@ -146,12 +170,16 @@ def register_subnet_extrinsic( Parameters: subtensor: The subtensor interface to send the extrinsic. wallet: The wallet to be used for subnet registration. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -177,14 +205,26 @@ def register_subnet_extrinsic( hotkey=wallet.hotkey.ss58_address ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) if not wait_for_finalization and not wait_for_inclusion: return response @@ -212,10 +252,13 @@ def register_extrinsic( num_processes: Optional[int] = None, update_interval: Optional[int] = None, log_verbose: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. @@ -231,12 +274,16 @@ def register_extrinsic( num_processes: The number of processes to use to register. update_interval: The number of nonces to solve between updates. log_verbose: If `True`, the registration process will log more information. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -336,14 +383,26 @@ def register_extrinsic( nonce=pow_result.nonce, work=[int(byte_) for byte_ in pow_result.seal], ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) if not response.success: # Look error here @@ -397,10 +456,13 @@ def set_subnet_identity_extrinsic( discord: str, description: str, additional: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Set the identity information for a given subnet. @@ -417,12 +479,16 @@ def set_subnet_identity_extrinsic( discord: Discord server or contact for the subnet. description: A textual description of the subnet. additional: Any additional metadata or information related to the subnet. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -447,14 +513,26 @@ def set_subnet_identity_extrinsic( additional=additional, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) if not wait_for_finalization and not wait_for_inclusion: return response diff --git a/bittensor/core/extrinsics/root.py b/bittensor/core/extrinsics/root.py index f26a77b607..996136b23f 100644 --- a/bittensor/core/extrinsics/root.py +++ b/bittensor/core/extrinsics/root.py @@ -2,7 +2,9 @@ from typing import Literal, Optional, TYPE_CHECKING from bittensor.core.chain_data import RootClaimType +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance @@ -38,10 +40,13 @@ def _get_limits(subtensor: "Subtensor") -> tuple[int, float]: def root_register_extrinsic( subtensor: "Subtensor", wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers the neuron to the root network. @@ -49,12 +54,16 @@ def root_register_extrinsic( Parameters: subtensor: Subtensor instance to interact with the blockchain. wallet: Bittensor Wallet instance. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -104,14 +113,26 @@ def root_register_extrinsic( hotkey=wallet.hotkey.ss58_address ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) if not response.success: logging.error(f"[red]{response.message}[/red]") @@ -144,10 +165,13 @@ def set_root_claim_type_extrinsic( subtensor: "Subtensor", wallet: "Wallet", new_root_claim_type: "Literal['Swap', 'Keep'] | RootClaimType | dict", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Sets the root claim type for the coldkey in provided wallet. @@ -159,12 +183,16 @@ def set_root_claim_type_extrinsic( - RootClaimType: RootClaimType.Swap, RootClaimType.Keep - Dict: {"KeepSubnets": {"subnets": [1, 2, 3]}} - Callable: RootClaimType.KeepSubnets([1, 2, 3]) + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -181,14 +209,26 @@ def set_root_claim_type_extrinsic( new_root_claim_type=normalized_type ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -198,10 +238,13 @@ def claim_root_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuids: "UIDs", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Claims the root emissions for a coldkey. @@ -209,12 +252,16 @@ def claim_root_extrinsic( subtensor: Subtensor instance to interact with the blockchain. wallet: Bittensor Wallet instance. netuids: The netuids to claim root emissions for. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -227,14 +274,26 @@ def claim_root_extrinsic( call = SubtensorModule(subtensor).claim_root(subnets=netuids) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 673df26780..7ecdafbf01 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -1,8 +1,9 @@ from typing import Optional, Union, TYPE_CHECKING from bittensor.core.errors import MetadataError +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import Commitments, SubtensorModule -from bittensor.core.settings import version_as_int +from bittensor.core.settings import DEFAULT_MEV_PROTECTION, version_as_int from bittensor.core.types import AxonServeCallParams, ExtrinsicResponse from bittensor.utils import ( networking as net, @@ -26,10 +27,13 @@ def serve_extrinsic( placeholder1: int = 0, placeholder2: int = 0, certificate: Optional[Certificate] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Subscribes a Bittensor endpoint to the subtensor chain. @@ -44,12 +48,16 @@ def serve_extrinsic( placeholder1: A placeholder for future use. placeholder2: A placeholder for future use. certificate: Certificate to use for TLS. If ``None``, no TLS will be used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -97,15 +105,27 @@ def serve_extrinsic( ) call = call_function(**params.as_dict()) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - sign_with="hotkey", - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + sign_with="hotkey", + period=period, + raise_error=raise_error, + ) if response.success: logging.debug( @@ -126,10 +146,13 @@ def serve_axon_extrinsic( netuid: int, axon: "Axon", certificate: Optional["Certificate"] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Serves the axon to the network. @@ -139,12 +162,16 @@ def serve_axon_extrinsic( netuid: The ``netuid`` being served on. axon: Axon to serve. certificate: Certificate to use for TLS. If ``None``, no TLS will be used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -177,6 +204,7 @@ def serve_axon_extrinsic( protocol=4, netuid=netuid, certificate=certificate, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -200,10 +228,13 @@ def publish_metadata_extrinsic( data_type: str, data: Union[bytes, dict], reset_bonds: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Publishes metadata on the Bittensor network using the specified wallet and network identifier. @@ -218,12 +249,16 @@ def publish_metadata_extrinsic( data: The actual metadata content to be published. This should be formatted or hashed according to the ``type`` specified. (Note: max ``str`` length is 128 bytes for ``'Raw0-128'``.) reset_bonds: If `True`, the function will reset the bonds for the neuron. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -248,15 +283,27 @@ def publish_metadata_extrinsic( call = Commitments(subtensor).set_commitment(netuid=netuid, info=info) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - sign_with=signing_keypair, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + sign_with=signing_keypair, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) if response.success: return response diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 210b80d648..e53e04aa2a 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -3,8 +3,10 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.errors import BalanceTypeError +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils import format_error_message from bittensor.utils.balance import Balance @@ -24,10 +26,13 @@ def add_stake_extrinsic( safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified subnet. @@ -43,12 +48,16 @@ def add_stake_extrinsic( safe_staking: If True, enables price safety checks. allow_partial_stake: If True, allows partial unstaking if price tolerance exceeded. rate_tolerance: Maximum allowed price increase percentage (0.005 = 0.5%). + mev_protection: If True, encrypts and submits the staking transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -129,16 +138,28 @@ def add_stake_extrinsic( ) block_before = subtensor.block - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - nonce_key="coldkeypub", - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + nonce_key="coldkeypub", + period=period, + raise_error=raise_error, + ) if response.success: sim_swap = subtensor.sim_swap( origin_netuid=0, @@ -194,10 +215,13 @@ def add_stake_multiple_extrinsic( netuids: UIDs, hotkey_ss58s: list[str], amounts: list[Balance], + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey on subnet with @@ -209,12 +233,16 @@ def add_stake_multiple_extrinsic( netuids: List of netuids to stake to. hotkey_ss58s: List of hotkeys to stake to. amounts: List of corresponding TAO amounts to bet for each netuid and hotkey. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -329,10 +357,12 @@ def add_stake_multiple_extrinsic( netuid=netuid, hotkey_ss58=hotkey_ss58, amount=amount, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) data.update({(idx, hotkey_ss58, netuid): response}) @@ -411,10 +441,13 @@ def set_auto_stake_extrinsic( wallet: "Wallet", netuid: int, hotkey_ss58: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism. @@ -424,12 +457,16 @@ def set_auto_stake_extrinsic( netuid: The subnet unique identifier. hotkey_ss58: The SS58 address of the validator's hotkey to which the miner automatically stakes all rewards received from the specified subnet immediately upon receipt. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -444,14 +481,26 @@ def set_auto_stake_extrinsic( netuid=netuid, hotkey=hotkey_ss58 ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) if response.success: logging.debug(response.message) diff --git a/bittensor/core/extrinsics/start_call.py b/bittensor/core/extrinsics/start_call.py index 24c053decf..9872ce2de2 100644 --- a/bittensor/core/extrinsics/start_call.py +++ b/bittensor/core/extrinsics/start_call.py @@ -1,6 +1,8 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: @@ -12,10 +14,13 @@ def start_call_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start a @@ -25,12 +30,16 @@ def start_call_extrinsic( subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -43,14 +52,26 @@ def start_call_extrinsic( call = SubtensorModule(subtensor).start_call(netuid=netuid) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/take.py b/bittensor/core/extrinsics/take.py index 546d3b8046..61484d305c 100644 --- a/bittensor/core/extrinsics/take.py +++ b/bittensor/core/extrinsics/take.py @@ -2,7 +2,9 @@ from bittensor_wallet.bittensor_wallet import Wallet +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: @@ -15,10 +17,13 @@ def set_take_extrinsic( hotkey_ss58: str, take: int, action: Literal["increase_take", "decrease_take"], + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Sets the delegate 'take' percentage for a neuron identified by its hotkey. @@ -28,12 +33,16 @@ def set_take_extrinsic( hotkey_ss58: SS58 address of the hotkey to set take for. take: The percentage of rewards that the delegate claims from nominators. action: The call function to use to set the take. Can be either "increase_take" or "decrease_take". + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -55,14 +64,26 @@ def set_take_extrinsic( else: raise ValueError(f"Invalid action: {action}") - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index 7a1cb921c8..a1f6a5469f 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -1,8 +1,13 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import Balances from bittensor.core.extrinsics.utils import get_transfer_fn_params -from bittensor.core.settings import NETWORK_EXPLORER_MAP, DEFAULT_NETWORK +from bittensor.core.settings import ( + DEFAULT_MEV_PROTECTION, + NETWORK_EXPLORER_MAP, + DEFAULT_NETWORK, +) from bittensor.core.types import ExtrinsicResponse from bittensor.utils import ( get_explorer_url_for_network, @@ -23,10 +28,13 @@ def transfer_extrinsic( amount: Optional[Balance], keep_alive: bool = True, transfer_all: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Transfers funds from this wallet to the destination public key address. @@ -37,12 +45,16 @@ def transfer_extrinsic( amount: Amount to stake as Bittensor balance. `None` if transferring all. transfer_all: Whether to transfer all funds from this wallet to the destination address. keep_alive: If set, keeps the account alive by keeping the balance above the existential deposit. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -101,14 +113,26 @@ def transfer_extrinsic( call = getattr(Balances(subtensor), call_function)(**call_params) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) response.transaction_tao_fee = fee if response.success: diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 4fc091ffe8..0cd13f7e68 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -3,8 +3,10 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.errors import BalanceTypeError +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils import format_error_message from bittensor.utils.balance import Balance @@ -24,10 +26,13 @@ def unstake_extrinsic( allow_partial_stake: bool = False, rate_tolerance: float = 0.005, safe_unstaking: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes stake into the wallet coldkey from the specified hotkey ``uid``. @@ -41,12 +46,16 @@ def unstake_extrinsic( allow_partial_stake: If true, allows partial unstaking if price tolerance exceeded. rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). safe_unstaking: If true, enables price safety checks. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -118,16 +127,28 @@ def unstake_extrinsic( logging.debug(logging_message) block_before = subtensor.block - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - use_nonce=True, - period=period, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, + ) if response.success: sim_swap = subtensor.sim_swap( @@ -184,10 +205,13 @@ def unstake_all_extrinsic( netuid: int, hotkey_ss58: str, rate_tolerance: Optional[float] = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. @@ -198,12 +222,16 @@ def unstake_all_extrinsic( hotkey_ss58: The SS58 address of the hotkey to unstake from. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -223,16 +251,28 @@ def unstake_all_extrinsic( limit_price=limit_price, ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - use_nonce=True, - period=period, - raise_error=raise_error, - ) + if mev_protection: + return submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) @@ -246,10 +286,13 @@ def unstake_multiple_extrinsic( amounts: Optional[list[Balance]] = None, rate_tolerance: Optional[float] = 0.05, unstake_all: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. @@ -262,12 +305,16 @@ def unstake_multiple_extrinsic( amounts: List of amounts to unstake. If ``None``, unstake all. rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). unstake_all: If true, unstakes all tokens. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -393,10 +440,12 @@ def unstake_multiple_extrinsic( hotkey_ss58=hotkey_ss58, netuid=netuid, rate_tolerance=rate_tolerance, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) else: response = unstake_extrinsic( @@ -405,10 +454,12 @@ def unstake_multiple_extrinsic( netuid=netuid, hotkey_ss58=hotkey_ss58, amount=unstaking_balance, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) data.update({(idx, hotkey_ss58, netuid): response}) diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 983c8cbc12..db17f9c359 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -1,7 +1,12 @@ """Module with helper functions for extrinsics.""" +import hashlib +import logging from typing import TYPE_CHECKING, Optional, Union +from bittensor_drand import encrypt_mlkem768, mlkem_kdf_id +from scalecodec import ss58_decode + from bittensor.core.extrinsics.pallets import Sudo from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance @@ -10,6 +15,19 @@ from bittensor_wallet import Wallet from bittensor.core.chain_data import StakeInfo from bittensor.core.subtensor import Subtensor + from scalecodec.types import GenericCall + from bittensor_wallet.keypair import Keypair + from async_substrate_interface import AsyncExtrinsicReceipt, ExtrinsicReceipt + + +MEV_SUBMITTED_EVENT = "mevShield.EncryptedSubmitted" +MEV_EXECUTED_EVENT = "mevShield.DecryptedExecuted" +MEV_UNSUCCESSFUL_EVENTS = [ + "mevShield.DecryptedRejected", + "mevShield.DecryptionFailed", +] + +POST_SUBMIT_MEV_EVENTS = [MEV_EXECUTED_EVENT] + MEV_UNSUCCESSFUL_EVENTS def get_old_stakes( @@ -207,3 +225,162 @@ def apply_pure_proxy_data( raise RuntimeError(message) return response.with_log("warning") + + +def get_mev_commitment_and_ciphertext( + call: "GenericCall", + signer_keypair: "Keypair", + genesis_hash: str, + ml_kem_768_public_key: bytes, +) -> tuple[str, bytes, bytes, bytes]: + """ + Builds MEV Shield payload and encrypts it using ML-KEM-768 + XChaCha20Poly1305. + + This function constructs the payload structure required for MEV Shield encryption and performs the encryption + process. The payload binds the transaction to a specific key epoch using the key_hash, which replaces nonce-based + replay protection. + + Parameters: + call: The GenericCall object representing the inner call to be encrypted and executed. + signer_keypair: The Keypair used for signing the inner call payload. The signer's AccountId32 (32 bytes) is + embedded in the payload_core. + genesis_hash: The genesis block hash as a hex string (with or without "0x" prefix). Used for chain-bound + signature domain separation. + ml_kem_768_public_key: The ML-KEM-768 public key bytes (1184 bytes) from NextKey storage. This key is used for + encryption and its hash binds the transaction to the key epoch. + + Returns: + A tuple containing: + - commitment_hex (str): Hex string of the Blake2-256 hash of payload_core (32 bytes). + - ciphertext (bytes): Encrypted blob containing plaintext. + - payload_core (bytes): Raw payload bytes before encryption. + - signature (bytes): MultiSignature (64 bytes for sr25519). + """ + # Create payload_core: signer (32B) + key_hash (32B Blake2-256 hash) + SCALE(call) + decoded_ss58 = ss58_decode(signer_keypair.ss58_address) + decoded_ss58_cut = ( + decoded_ss58[2:] if decoded_ss58.startswith("0x") else decoded_ss58 + ) + signer_bytes = bytes.fromhex(decoded_ss58_cut) # 32 bytes + + # Compute key_hash = Blake2-256(NextKey_bytes) + # This binds the transaction to the key epoch at submission time + key_hash_bytes = hashlib.blake2b( + ml_kem_768_public_key, digest_size=32 + ).digest() # 32 bytes + + scale_call_bytes = bytes(call.data.data) # SCALE encoded call + mev_shield_version = mlkem_kdf_id() + + # Fix genesis_hash processing + genesis_hash_clean = ( + genesis_hash[2:] if genesis_hash.startswith("0x") else genesis_hash + ) + genesis_hash_bytes = bytes.fromhex(genesis_hash_clean) + + payload_core = signer_bytes + key_hash_bytes + scale_call_bytes + + # Sign payload: coldkey.sign(b"mev-shield:v1" + genesis_hash + payload_core) + message_to_sign = ( + b"mev-shield:" + mev_shield_version + genesis_hash_bytes + payload_core + ) + + signature = signer_keypair.sign(message_to_sign) + + # Create plaintext: payload_core + b"\x01" + signature + plaintext = payload_core + b"\x01" + signature + + # Getting ciphertext (encrypting plaintext using ML-KEM-768) + ciphertext = encrypt_mlkem768(ml_kem_768_public_key, plaintext) + + # Compute commitment: blake2_256(payload_core) + commitment_hash = hashlib.blake2b(payload_core, digest_size=32).digest() + commitment_hex = "0x" + commitment_hash.hex() + + return commitment_hex, ciphertext, payload_core, signature + + +def get_event_attributes_by_event_name(events: list, event_name: str) -> Optional[dict]: + """ + Extracts event data from triggered events by event ID. + + Searches through a list of triggered events and returns the attributes dictionary for the first event matching the + specified event_id. + + Parameters: + events: List of event dictionaries, typically from ExtrinsicReceipt.triggered_events. Each event should have an + "module_id". "event_id" key and an "attributes" key. + event_name: The events identifier to search for (e.g. "mevShield.EncryptedSubmitted", etc). + + Returns: + The attributes dictionary of the matching event, or None if no matching event is found.""" + for event in events: + try: + module_id, event_id = event_name.split(".") + except (ValueError, AttributeError): + logging.debug( + "Invalid event_name. Should be string as `module_id.event_id` e.g. `mevShield.EncryptedSubmitted`." + ) + return None + if ( + event["module_id"].lower() == module_id.lower() + and event["event_id"].lower() == event_id.lower() + ): + return event + return None + + +def post_process_mev_response( + response: "ExtrinsicResponse", + revealed_name: Optional[str], + revealed_extrinsic: Optional["ExtrinsicReceipt | AsyncExtrinsicReceipt"], + raise_error: bool = False, +) -> None: + """ + Post-processes the result of a MEV Shield extrinsic submission by updating the response object based on the revealed + extrinsic execution status. + + This function analyzes the revealed extrinsic (execute_revealed) that was found after the initial encrypted + submission and updates the response object accordingly. It handles cases where the revealed extrinsic was not found, + where it failed (DecryptedRejected or DecryptionFailed events), and propagates errors if requested. + + Parameters: + response: The ExtrinsicResponse object from the initial submit_encrypted call. This object will be modified + in-place with the revealed extrinsic receipt and updated success/error status. + revealed_name: The name of the event found in the revealed extrinsic (e.g., "mevShield.DecryptedExecuted", + "mevShield.DecryptedRejected", "mevShield.DecryptionFailed"). + revealed_extrinsic: The ExtrinsicReceipt or AsyncExtrinsicReceipt object for the execute_revealed transaction, + if found. None if the revealed extrinsic was not found in the expected blocks. This receipt contains the + triggered events and execution details. + raise_error: If True, raises the error immediately if the response contains an error. If False, the error is + stored in response.error but not raised. Defaults to False. + + Returns: + None. The function modifies the response object in-place by setting: + - response.mev_extrinsic_receipt: The revealed extrinsic receipt + - response.success: False if revealed extrinsic not found or failed, otherwise True. + - response.message: Error message describing the failure if failure. + - response.error: RuntimeError with the response.message. + """ + # add revealed extrinsic receipt to response + response.mev_extrinsic_receipt = revealed_extrinsic + + # when main extrinsic is successful but revealed extrinsic is not found in the chain. + if revealed_extrinsic is None: + response.success = False + response.message = "MeV protected extrinsic does not contain related event." + response.error = RuntimeError(response.message) + + # when main extrinsic is successful but revealed extrinsic is not successful. + if revealed_name in MEV_UNSUCCESSFUL_EVENTS: + response.success = False + response.message = ( + f"{revealed_name}: Check `mev_extrinsic_receipt` for details." + ) + response.error = RuntimeError(response.message) + + if response.error: + if raise_error: + raise response.error + else: + response.with_log() diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index cae837a1e8..155d3cac53 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -4,8 +4,9 @@ from bittensor_drand import get_encrypted_commit +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.pallets import SubtensorModule -from bittensor.core.settings import version_as_int +from bittensor.core.settings import DEFAULT_MEV_PROTECTION, version_as_int from bittensor.core.types import ExtrinsicResponse, Salt, UIDs, Weights from bittensor.utils import get_mechid_storage_index from bittensor.utils.btlogging import logging @@ -29,10 +30,13 @@ def commit_timelocked_weights_extrinsic( block_time: Union[int, float], commit_reveal_version: int = 4, version_key: int = version_as_int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Commits the weights for a specific sub subnet mechanism on the Bittensor blockchain using the provided wallet. @@ -47,12 +51,16 @@ def commit_timelocked_weights_extrinsic( block_time: The number of seconds for block duration. commit_reveal_version: The version of the commit-reveal in the chain. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -99,17 +107,29 @@ def commit_timelocked_weights_extrinsic( commit_reveal_version=commit_reveal_version, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with=signing_keypair, + nonce_key=signing_keypair, + raise_error=raise_error, + ) if response.success: logging.debug(response.message) @@ -135,10 +155,13 @@ def commit_weights_extrinsic( weights: Weights, salt: Salt, version_key: int = version_as_int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. @@ -151,12 +174,16 @@ def commit_weights_extrinsic( weights: NumPy array of weight values corresponding to each UID. salt: list of randomly generated integers as salt to generated weighted hash. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -187,17 +214,29 @@ def commit_weights_extrinsic( commit_hash=commit_hash, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with=signing_keypair, + nonce_key=signing_keypair, + raise_error=raise_error, + ) if response.success: logging.debug(response.message) @@ -219,10 +258,13 @@ def reveal_weights_extrinsic( weights: Weights, salt: Salt, version_key: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. @@ -236,12 +278,16 @@ def reveal_weights_extrinsic( weights: List of weight values corresponding to each UID. salt: List of salt values corresponding to the hash function. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -267,17 +313,29 @@ def reveal_weights_extrinsic( version_key=version_key, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with=signing_keypair, + nonce_key=signing_keypair, + raise_error=raise_error, + ) if response.success: logging.debug(response.message) @@ -298,10 +356,13 @@ def set_weights_extrinsic( uids: UIDs, weights: Weights, version_key: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. @@ -314,12 +375,16 @@ def set_weights_extrinsic( uids: List of neuron UIDs for which weights are being revealed. weights: List of weight values corresponding to each UID. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -344,17 +409,29 @@ def set_weights_extrinsic( version_key=version_key, ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - use_nonce=True, - nonce_key=signing_keypair, - sign_with=signing_keypair, - raise_error=raise_error, - ) + if mev_protection: + response = submit_encrypted_extrinsic( + subtensor=subtensor, + wallet=wallet, + call=call, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + else: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + use_nonce=True, + nonce_key=signing_keypair, + sign_with=signing_keypair, + raise_error=raise_error, + ) if response.success: logging.debug(response.message) diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index b9a1c08e07..9b5062e1ad 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -70,6 +70,15 @@ # details https://paritytech.github.io/polkadot-sdk/master/src/sp_runtime/generic/era.rs.html#65-72 DEFAULT_PERIOD = 128 +# Default MEV Shield protection setting for extrinsics. +# When enabled, transactions are encrypted to protect against Miner Extractable Value (MEV) attacks. +DEFAULT_MEV_PROTECTION = os.getenv("BT_MEV_PROTECTION", "").lower() in ( + "1", + "true", + "yes", + "on", +) + # Block Explorers map network to explorer url # Must all be polkadotjs explorer urls NETWORK_EXPLORER_MAP = { diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index f7a223586a..b49ef48eac 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1,7 +1,7 @@ import copy from datetime import datetime, timezone from functools import lru_cache -from typing import TYPE_CHECKING, Any, Iterable, Optional, Union, cast, Literal +from typing import TYPE_CHECKING, Any, Iterable, Literal, Optional, Union, cast import scalecodec from async_substrate_interface.errors import SubstrateRequestException @@ -14,8 +14,8 @@ from bittensor.core.axon import Axon from bittensor.core.chain_data import ( - CrowdloanInfo, CrowdloanConstants, + CrowdloanInfo, DelegatedInfo, DelegateInfo, DynamicInfo, @@ -24,16 +24,16 @@ NeuronInfoLite, ProposalVoteData, ProxyAnnouncementInfo, - ProxyInfo, ProxyConstants, + ProxyInfo, ProxyType, RootClaimType, SelectiveMetagraphIndex, SimSwapResult, StakeInfo, - SubnetInfo, - SubnetIdentity, SubnetHyperparameters, + SubnetIdentity, + SubnetInfo, WeightCommitInfo, decode_account_id, ) @@ -47,8 +47,8 @@ from bittensor.core.config import Config from bittensor.core.errors import ChainError from bittensor.core.extrinsics.children import ( - set_children_extrinsic, root_set_pending_childkey_cooldown_extrinsic, + set_children_extrinsic, ) from bittensor.core.extrinsics.crowdloan import ( contribute_crowdloan_extrinsic, @@ -67,10 +67,11 @@ remove_liquidity_extrinsic, toggle_user_liquidity_extrinsic, ) +from bittensor.core.extrinsics.mev_shield import submit_encrypted_extrinsic from bittensor.core.extrinsics.move_stake import ( - transfer_stake_extrinsic, - swap_stake_extrinsic, move_stake_extrinsic, + swap_stake_extrinsic, + transfer_stake_extrinsic, ) from bittensor.core.extrinsics.proxy import ( add_proxy_extrinsic, @@ -82,8 +83,8 @@ proxy_extrinsic, reject_announcement_extrinsic, remove_announcement_extrinsic, - remove_proxy_extrinsic, remove_proxies_extrinsic, + remove_proxy_extrinsic, ) from bittensor.core.extrinsics.registration import ( burned_register_extrinsic, @@ -122,10 +123,11 @@ ) from bittensor.core.metagraph import Metagraph from bittensor.core.settings import ( - version_as_int, + DEFAULT_MEV_PROTECTION, DEFAULT_PERIOD, TAO_APP_BLOCK_EXPLORER, TYPE_REGISTRY, + version_as_int, ) from bittensor.core.types import ( BlockInfo, @@ -148,22 +150,22 @@ ) from bittensor.utils.balance import ( Balance, - fixed_to_float, FixedPoint, check_balance_amount, + fixed_to_float, ) from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import ( + LiquidityPosition, calculate_fees, get_fees, - tick_to_price, price_to_tick, - LiquidityPosition, + tick_to_price, ) if TYPE_CHECKING: - from bittensor_wallet import Keypair, Wallet from async_substrate_interface.sync_substrate import QueryMapResult + from bittensor_wallet import Keypair, Wallet from scalecodec.types import GenericCall @@ -2233,6 +2235,192 @@ def get_metagraph_info( return MetagraphInfo.from_dict(query.value) + def get_mev_shield_current_key( + self, block: Optional[int] = None + ) -> Optional[bytes]: + """ + Retrieves the CurrentKey from the MevShield pallet storage. + + The CurrentKey contains the ML-KEM-768 public key that is currently being used for encryption in this block. + This key is rotated from NextKey at the beginning of each block. + + Parameters: + block: The blockchain block number at which to perform the query. If None, uses the current block. + + Returns: + The ML-KEM-768 public key as bytes (1184 bytes for ML-KEM-768) + + Note: + If CurrentKey is not set (None in storage), this function returns None. This can happen if no validator has + announced a key yet. + """ + block_hash = self.determine_block_hash(block=block) + query = self.substrate.query( + module="MevShield", + storage_function="CurrentKey", + block_hash=block_hash, + ) + + if query is None: + return None + + public_key_bytes = bytes(next(iter(query))) + + # Validate public_key size for ML-KEM-768 (must be exactly 1184 bytes) + MLKEM768_PUBLIC_KEY_SIZE = 1184 + if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: + raise ValueError( + f"Invalid ML-KEM-768 public key size: {len(public_key_bytes)} bytes. " + f"Expected exactly {MLKEM768_PUBLIC_KEY_SIZE} bytes." + ) + + return public_key_bytes + + def get_mev_shield_next_key(self, block: Optional[int] = None) -> Optional[bytes]: + """ + Retrieves the NextKey from the MevShield pallet storage. + + The NextKey contains the ML-KEM-768 public key that will be used for encryption in the next block. This key is + rotated from NextKey to CurrentKey at the beginning of each block. + + Parameters: + block: The blockchain block number at which to perform the query. If None, uses the current block. + + Returns: + The ML-KEM-768 public key as bytes (1184 bytes for ML-KEM-768) + + Note: + If NextKey is not set (None in storage), this function returns None. This can happen if no validator has + announced the next key yet. + """ + block_hash = self.determine_block_hash(block=block) + query = self.substrate.query( + module="MevShield", + storage_function="NextKey", + block_hash=block_hash, + ) + + if query is None: + return None + + public_key_bytes = bytes(next(iter(query))) + + # Validate public_key size for ML-KEM-768 (must be exactly 1184 bytes) + MLKEM768_PUBLIC_KEY_SIZE = 1184 + if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: + raise ValueError( + f"Invalid ML-KEM-768 public key size: {len(public_key_bytes)} bytes. " + f"Expected exactly {MLKEM768_PUBLIC_KEY_SIZE} bytes." + ) + + return public_key_bytes + + def get_mev_shield_submission( + self, + submission_id: str, + block: Optional[int] = None, + ) -> Optional[dict[str, str | int | bytes]]: + """ + Retrieves Submission from the MevShield pallet storage. + + If submission_id is provided, returns a single submission. If submission_id is None, returns all submissions from + the storage map. + + Parameters: + submission_id: The hash ID of the submission. Can be a hex string with "0x" prefix or bytes. If None, + returns all submissions. + block: The blockchain block number at which to perform the query. If None, uses the current block. + + Returns: + If submission_id is provided: A dictionary containing the submission data if found, None otherwise. The + dictionary contains: + - author: The SS58 address of the account that submitted the encrypted extrinsic + - commitment: The blake2_256 hash of the payload_core (as hex string with "0x" prefix) + - ciphertext: The encrypted blob as bytes (format: [u16 kem_len][kem_ct][nonce24][aead_ct]) + - submitted_in: The block number when the submission was created + + If submission_id is None: A dictionary mapping submission IDs (as hex strings) to submission dictionaries. + + Note: + If a specific submission does not exist in storage, this function returns None. If querying all submissions + and none exist, returns an empty dictionary. + """ + block_hash = self.determine_block_hash(block=block) + submission_id = ( + submission_id[2:] if submission_id.startswith("0x") else submission_id + ) + submission_id_bytes = bytes.fromhex(submission_id) + + query = self.substrate.query( + module="MevShield", + storage_function="Submissions", + params=[submission_id_bytes], + block_hash=block_hash, + ) + + if query is None or not isinstance(query, dict): + return None + + autor = decode_account_id(query.get("author")) + commitment = bytes(query.get("commitment")[0]) + ciphertext = bytes(query.get("ciphertext")[0]) + submitted_in = query.get("submitted_in") + + return { + "author": autor, + "commitment": commitment, + "ciphertext": ciphertext, + "submitted_in": submitted_in, + } + + def get_mev_shield_submissions( + self, + block: Optional[int] = None, + ) -> Optional[dict[str, dict[str, str | int]]]: + """ + Retrieves all encrypted submissions from the MevShield pallet storage. + + This function queries the MevShield.Submissions storage map and returns all pending encrypted submissions that + have been submitted via submit_encrypted but not yet executed via execute_revealed. + + Parameters: + block: The blockchain block number for the query. If None, uses the current block. + + Returns: + A dictionary mapping wrapper_id (as hex string with "0x" prefix) to submission data dictionaries. Each + submission dictionary contains: + - author: The SS58 address of the account that submitted the encrypted extrinsic + - commitment: The blake2_256 hash of the payload_core as bytes (32 bytes) + - ciphertext: The encrypted blob as bytes (format: [u16 kem_len][kem_ct][nonce24][aead_ct]) + - submitted_in: The block number when the submission was created + + Returns None if no submissions exist in storage at the specified block. + + Note: + Submissions are automatically pruned after KEY_EPOCH_HISTORY blocks (100 blocks) by the pallet's + on_initialize hook. Only submissions that have been submitted but not yet executed will be present in + storage. + """ + block_hash = self.determine_block_hash(block=block) + query = self.substrate.query_map( + module="MevShield", + storage_function="Submissions", + block_hash=block_hash, + ) + + result = {} + for q in query: + key, value = q + value = value.value + result["0x" + bytes(key[0]).hex()] = { + "author": decode_account_id(value.get("author")), + "commitment": bytes(value.get("commitment")[0]), + "ciphertext": bytes(value.get("ciphertext")[0]), + "submitted_in": value.get("submitted_in"), + } + + return result if result else None + def get_minimum_required_stake(self) -> Balance: """ Returns the minimum required stake for nominators in the Subtensor network. @@ -4346,10 +4534,13 @@ def add_stake( safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified @@ -4367,12 +4558,17 @@ def add_stake( exceed the price tolerance. If false, the entire stake fails if it would exceed the tolerance. rate_tolerance: The maximum allowed price change ratio when staking. For example, 0.005 = 0.5% maximum price increase. Only used when safe_staking is True. + mev_protection: If True, encrypts and submits the staking transaction through the MEV Shield pallet to + protect against front-running and MEV attacks. The transaction remains encrypted in the mempool until + validators decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection + used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4391,10 +4587,12 @@ def add_stake( safe_staking=safe_staking, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def add_liquidity( @@ -4405,10 +4603,13 @@ def add_liquidity( price_low: Balance, price_high: Balance, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds liquidity to the specified price range. @@ -4420,12 +4621,16 @@ def add_liquidity( price_low: The lower bound of the price tick range. In TAO. price_high: The upper bound of the price tick range. In TAO. hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4441,10 +4646,12 @@ def add_liquidity( price_low=price_low, price_high=price_high, hotkey_ss58=hotkey_ss58, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def add_stake_multiple( @@ -4453,10 +4660,13 @@ def add_stake_multiple( netuids: UIDs, hotkey_ss58s: list[str], amounts: list[Balance], + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds stakes to multiple neurons identified by their hotkey SS58 addresses. @@ -4467,12 +4677,16 @@ def add_stake_multiple( netuids: List of subnet UIDs. hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. amounts: List of corresponding TAO amounts to bet for each netuid and hotkey. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4486,10 +4700,12 @@ def add_stake_multiple( netuids=netuids, hotkey_ss58s=hotkey_ss58s, amounts=amounts, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def add_proxy( @@ -4498,10 +4714,13 @@ def add_proxy( delegate_ss58: str, proxy_type: Union[str, "ProxyType"], delay: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adds a proxy relationship. @@ -4516,12 +4735,16 @@ def add_proxy( proxy_type: The type of proxy permissions (e.g., "Any", "NonTransfer", "Governance", "Staking"). Can be a string or ProxyType enum value. delay: The number of blocks before the proxy can be used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4536,10 +4759,12 @@ def add_proxy( delegate_ss58=delegate_ss58, proxy_type=proxy_type, delay=delay, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def announce_proxy( @@ -4547,10 +4772,13 @@ def announce_proxy( wallet: "Wallet", real_account_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Announces a future call that will be executed through a proxy. @@ -4563,12 +4791,16 @@ def announce_proxy( wallet: Bittensor wallet object (should be the proxy account wallet). real_account_ss58: The SS58 address of the real account on whose behalf the call will be made. call_hash: The hash of the call that will be executed in the future. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4582,20 +4814,25 @@ def announce_proxy( wallet=wallet, real_account_ss58=real_account_ss58, call_hash=call_hash, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def burned_register( self, wallet: "Wallet", netuid: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers a neuron on the Bittensor network by recycling TAO. This method of registration involves recycling @@ -4604,12 +4841,16 @@ def burned_register( Parameters: wallet: The wallet associated with the neuron to be registered. netuid: The unique identifier of the subnet. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4619,42 +4860,53 @@ def burned_register( return root_register_extrinsic( subtensor=self, wallet=wallet, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) return burned_register_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def claim_root( self, wallet: "Wallet", netuids: "UIDs", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ): """Claims the root emissions for a coldkey. Parameters: wallet: Bittensor Wallet instance. netuids: The netuids to claim root emissions for. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4663,10 +4915,12 @@ def claim_root( subtensor=self, wallet=wallet, netuids=netuids, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def commit_weights( @@ -4679,10 +4933,13 @@ def commit_weights( mechid: int = 0, version_key: int = version_as_int, max_attempts: int = 5, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = 16, raise_error: bool = True, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. @@ -4697,12 +4954,16 @@ def commit_weights( mechid: Subnet mechanism unique identifier. version_key: Version key for compatibility with the network. max_attempts: The number of maximum attempts to commit weights. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4732,10 +4993,12 @@ def commit_weights( uids=uids, weights=weights, salt=salt, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) except Exception as error: return ExtrinsicResponse.from_exception( @@ -4755,10 +5018,13 @@ def contribute_crowdloan( wallet: "Wallet", crowdloan_id: int, amount: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Contributes funds to an active crowdloan campaign. @@ -4767,12 +5033,16 @@ def contribute_crowdloan( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to contribute to. amount: Amount to contribute. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4782,10 +5052,12 @@ def contribute_crowdloan( wallet=wallet, crowdloan_id=crowdloan_id, amount=amount, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def create_crowdloan( @@ -4797,10 +5069,13 @@ def create_crowdloan( end: int, call: Optional["GenericCall"] = None, target_address: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Creates a new crowdloan campaign on-chain. @@ -4813,12 +5088,16 @@ def create_crowdloan( end: Block number when the campaign ends. call: Runtime call data (e.g., subtensor::register_leased_network). target_address: SS58 address to transfer funds to on success. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4832,10 +5111,12 @@ def create_crowdloan( end=end, call=call, target_address=target_address, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def create_pure_proxy( @@ -4844,10 +5125,13 @@ def create_pure_proxy( proxy_type: Union[str, "ProxyType"], delay: int, index: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Creates a pure proxy account. @@ -4861,12 +5145,16 @@ def create_pure_proxy( proxy_type: The type of proxy permissions for the pure proxy. Can be a string or ProxyType enum value. delay: The number of blocks before the pure proxy can be used. index: The index to use for generating the pure proxy account address. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4882,20 +5170,25 @@ def create_pure_proxy( proxy_type=proxy_type, delay=delay, index=index, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def dissolve_crowdloan( self, wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Dissolves a completed or failed crowdloan campaign after all refunds are processed. @@ -4906,12 +5199,16 @@ def dissolve_crowdloan( Parameters: wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to dissolve. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4926,20 +5223,25 @@ def dissolve_crowdloan( subtensor=self, wallet=wallet, crowdloan_id=crowdloan_id, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def finalize_crowdloan( self, wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Finalizes a successful crowdloan campaign once the cap has been reached and the end block has passed. @@ -4949,10 +5251,14 @@ def finalize_crowdloan( Parameters: wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to finalize. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4961,10 +5267,12 @@ def finalize_crowdloan( subtensor=self, wallet=wallet, crowdloan_id=crowdloan_id, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def kill_pure_proxy( @@ -4977,10 +5285,13 @@ def kill_pure_proxy( height: int, ext_index: int, force_proxy_type: Optional[Union[str, "ProxyType"]] = ProxyType.Any, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Kills (removes) a pure proxy account. @@ -5009,12 +5320,16 @@ def kill_pure_proxy( type (or `Any`) with the pure proxy account. Defaults to `ProxyType.Any` for maximum compatibility. If `None`, Substrate will automatically select an available proxy type from the spawner's proxy relationships. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5038,10 +5353,74 @@ def kill_pure_proxy( height=height, ext_index=ext_index, force_proxy_type=force_proxy_type, + mev_protection=mev_protection, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + ) + + def mev_submit_encrypted( + self, + wallet: "Wallet", + call: "GenericCall", + signer_keypair: Optional["Keypair"] = None, + *, + period: Optional[int] = DEFAULT_PERIOD, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, + blocks_for_revealed_execution: int = 5, + ) -> ExtrinsicResponse: + """ + Submits an encrypted extrinsic to the MEV Shield pallet. + + This function encrypts a call using ML-KEM-768 + XChaCha20Poly1305 and submits it to the MevShield pallet. The + extrinsic remains encrypted in the transaction pool until it is included in a block and decrypted by validators. + + Parameters: + wallet: The wallet used to sign the extrinsic (must be unlocked, coldkey will be used for signing). + call: The GenericCall object to encrypt and submit. + signer_keypair: The keypair used to sign the inner call. + period: The number of blocks during which the transaction will remain valid after it's submitted. If the + transaction is not included in a block within that number of blocks, it will expire and be rejected. You can + think of it as an expiration date for the transaction. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the DecryptedExecuted event, indicating that validators + have successfully decrypted and executed the inner call. If True, the function will poll subsequent + blocks for the event matching this submission's commitment. + blocks_for_revealed_execution: Maximum number of blocks to poll for the DecryptedExecuted event after + inclusion. The function checks blocks from start_block+1 to start_block + blocks_for_revealed_execution. + Returns immediately if the event is found before the block limit is reached. + + Returns: + ExtrinsicResponse: The result object of the extrinsic execution. + + Raises: + ValueError: If NextKey is not available in storage or encryption fails. + SubstrateRequestException: If the extrinsic fails to be submitted or included. + + Note: + The encryption uses the public key from NextKey storage, which rotates every block. The payload structure is: + payload_core = signer_bytes (32B) + nonce (u32 LE, 4B) + SCALE(call) + plaintext = payload_core + b"\\x01" + signature (64B for sr25519) + commitment = blake2_256(payload_core) + """ + return submit_encrypted_extrinsic( + subtensor=self, + wallet=wallet, + call=call, + signer_keypair=signer_keypair, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, + blocks_for_revealed_execution=blocks_for_revealed_execution, ) def modify_liquidity( @@ -5051,10 +5430,13 @@ def modify_liquidity( position_id: int, liquidity_delta: Balance, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Modifies liquidity in liquidity position by adding or removing liquidity from it. @@ -5064,12 +5446,16 @@ def modify_liquidity( position_id: The id of the position record in the pool. liquidity_delta: The amount of liquidity to be added or removed (add if positive or remove if negative). hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5110,10 +5496,12 @@ def modify_liquidity( position_id=position_id, liquidity_delta=liquidity_delta, hotkey_ss58=hotkey_ss58, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def move_stake( @@ -5125,10 +5513,13 @@ def move_stake( destination_hotkey_ss58: str, amount: Optional[Balance] = None, move_all_stake: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Moves stake to a different hotkey and/or subnet. @@ -5141,12 +5532,16 @@ def move_stake( destination_hotkey_ss58: The SS58 address of the destination hotkey. amount: Amount of stake to move. move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5161,19 +5556,24 @@ def move_stake( destination_hotkey_ss58=destination_hotkey_ss58, amount=amount, move_all_stake=move_all_stake, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def poke_deposit( self, wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Adjusts deposits made for proxies and announcements based on current values. @@ -5184,10 +5584,14 @@ def poke_deposit( Parameters: wallet: Bittensor wallet object (the account whose deposits will be adjusted). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5204,10 +5608,12 @@ def poke_deposit( return poke_deposit_extrinsic( subtensor=self, wallet=wallet, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def proxy( @@ -5216,10 +5622,13 @@ def proxy( real_account_ss58: str, force_proxy_type: Optional[Union[str, "ProxyType"]], call: "GenericCall", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Executes a call on behalf of the real account through a proxy. @@ -5234,12 +5643,16 @@ def proxy( force_proxy_type: The type of proxy to use for the call. If None, any proxy type can be used. Otherwise, must match one of the allowed proxy types. Can be a string or ProxyType enum value. call: The inner call to be executed on behalf of the real account. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5254,10 +5667,12 @@ def proxy( real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def proxy_announced( @@ -5267,10 +5682,13 @@ def proxy_announced( real_account_ss58: str, force_proxy_type: Optional[Union[str, "ProxyType"]], call: "GenericCall", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Executes an announced call on behalf of the real account through a proxy. @@ -5286,12 +5704,16 @@ def proxy_announced( force_proxy_type: The type of proxy to use for the call. If None, any proxy type can be used. Otherwise, must match one of the allowed proxy types. Can be a string or ProxyType enum value. call: The inner call to be executed on behalf of the real account (must match the announced call_hash). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5307,20 +5729,25 @@ def proxy_announced( real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def refund_crowdloan( self, wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Refunds contributors from a failed or expired crowdloan campaign. @@ -5331,12 +5758,16 @@ def refund_crowdloan( Parameters: wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to refund. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5351,10 +5782,12 @@ def refund_crowdloan( subtensor=self, wallet=wallet, crowdloan_id=crowdloan_id, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def reject_proxy_announcement( @@ -5362,10 +5795,13 @@ def reject_proxy_announcement( wallet: "Wallet", delegate_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Rejects an announcement made by a proxy delegate. @@ -5378,12 +5814,16 @@ def reject_proxy_announcement( wallet: Bittensor wallet object (should be the real account wallet). delegate_ss58: The SS58 address of the delegate proxy account whose announcement is being rejected. call_hash: The hash of the call that was announced and is now being rejected. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5396,10 +5836,12 @@ def reject_proxy_announcement( wallet=wallet, delegate_ss58=delegate_ss58, call_hash=call_hash, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def register( @@ -5414,10 +5856,13 @@ def register( num_processes: Optional[int] = None, update_interval: Optional[int] = None, log_verbose: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. @@ -5437,12 +5882,16 @@ def register( num_processes: The number of processes to use to register. update_interval: The number of nonces to solve between updates. log_verbose: If ``true``, the registration process will log more information. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5462,31 +5911,40 @@ def register( dev_id=dev_id, output_in_place=output_in_place, log_verbose=log_verbose, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def register_subnet( self, wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers a new subnetwork on the Bittensor network. Parameters: wallet: The wallet to be used for subnet registration. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5494,10 +5952,12 @@ def register_subnet( return register_subnet_extrinsic( subtensor=self, wallet=wallet, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def remove_proxy_announcement( @@ -5505,10 +5965,13 @@ def remove_proxy_announcement( wallet: "Wallet", real_account_ss58: str, call_hash: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes an announcement made by a proxy account. @@ -5521,12 +5984,16 @@ def remove_proxy_announcement( wallet: Bittensor wallet object (should be the proxy account wallet that made the announcement). real_account_ss58: The SS58 address of the real account on whose behalf the call was announced. call_hash: The hash of the call that was announced and is now being removed. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5540,10 +6007,12 @@ def remove_proxy_announcement( wallet=wallet, real_account_ss58=real_account_ss58, call_hash=call_hash, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def remove_liquidity( @@ -5552,10 +6021,13 @@ def remove_liquidity( netuid: int, position_id: int, hotkey_ss58: Optional[str] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Remove liquidity and credit balances back to wallet's hotkey stake. @@ -5564,12 +6036,16 @@ def remove_liquidity( netuid: The UID of the target subnet for which the call is being initiated. position_id: The id of the position record in the pool. hotkey_ss58: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5585,19 +6061,24 @@ def remove_liquidity( netuid=netuid, position_id=position_id, hotkey_ss58=hotkey_ss58, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def remove_proxies( self, wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes all proxy relationships for the account in a single transaction. @@ -5609,12 +6090,16 @@ def remove_proxies( Parameters: wallet: Bittensor wallet object. The account whose proxies will be removed (the delegator). All proxy relationships where wallet.coldkey.ss58_address is the real account will be removed. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5626,10 +6111,12 @@ def remove_proxies( return remove_proxies_extrinsic( subtensor=self, wallet=wallet, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def remove_proxy( @@ -5638,10 +6125,13 @@ def remove_proxy( delegate_ss58: str, proxy_type: Union[str, "ProxyType"], delay: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes a specific proxy relationship. @@ -5655,12 +6145,16 @@ def remove_proxy( delegate_ss58: The SS58 address of the delegate proxy account to remove. proxy_type: The type of proxy permissions to remove. Can be a string or ProxyType enum value. delay: The number of blocks before the proxy removal takes effect. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5675,10 +6169,12 @@ def remove_proxy( delegate_ss58=delegate_ss58, proxy_type=proxy_type, delay=delay, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def reveal_weights( @@ -5691,10 +6187,13 @@ def reveal_weights( mechid: int = 0, max_attempts: int = 5, version_key: int = version_as_int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = 16, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -5709,12 +6208,16 @@ def reveal_weights( mechid: The subnet mechanism unique identifier. max_attempts: The number of maximum attempts to reveal weights. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5741,10 +6244,12 @@ def reveal_weights( weights=weights, salt=salt, version_key=version_key, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) except Exception as error: return ExtrinsicResponse.from_exception( @@ -5759,22 +6264,29 @@ def reveal_weights( def root_register( self, wallet: "Wallet", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Register neuron by recycling some TAO. Parameters: wallet (bittensor_wallet.Wallet): Bittensor wallet instance. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5783,32 +6295,41 @@ def root_register( return root_register_extrinsic( subtensor=self, wallet=wallet, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def root_set_pending_childkey_cooldown( self, wallet: "Wallet", cooldown: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Sets the pending childkey cooldown. Parameters: wallet: bittensor wallet instance. cooldown: the number of blocks to setting pending childkey cooldown. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion (bool): Waits for the transaction to be included in a block. wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5819,10 +6340,12 @@ def root_set_pending_childkey_cooldown( subtensor=self, wallet=wallet, cooldown=cooldown, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def set_auto_stake( @@ -5830,10 +6353,13 @@ def set_auto_stake( wallet: "Wallet", netuid: int, hotkey_ss58: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism. @@ -5842,12 +6368,16 @@ def set_auto_stake( netuid: The subnet unique identifier. hotkey_ss58: The SS58 address of the validator's hotkey to which the miner automatically stakes all rewards received from the specified subnet immediately upon receipt. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5860,10 +6390,12 @@ def set_auto_stake( wallet=wallet, netuid=netuid, hotkey_ss58=hotkey_ss58, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def set_children( @@ -5872,10 +6404,13 @@ def set_children( netuid: int, hotkey_ss58: str, children: list[tuple[float, str]], + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Allows a coldkey to set children-keys. @@ -5885,12 +6420,16 @@ def set_children( hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5901,10 +6440,12 @@ def set_children( hotkey_ss58=hotkey_ss58, netuid=netuid, children=children, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def set_delegate_take( @@ -5916,6 +6457,7 @@ def set_delegate_take( wait_for_finalization: bool = True, raise_error: bool = False, period: Optional[int] = DEFAULT_PERIOD, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Sets the delegate 'take' percentage for a neuron identified by its hotkey. @@ -5931,6 +6473,7 @@ def set_delegate_take( raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5971,6 +6514,7 @@ def set_delegate_take( raise_error=raise_error, wait_for_finalization=wait_for_finalization, wait_for_inclusion=wait_for_inclusion, + wait_for_revealed_execution=wait_for_revealed_execution, ) if response.success: @@ -5983,10 +6527,13 @@ def set_root_claim_type( self, wallet: "Wallet", new_root_claim_type: "Literal['Swap', 'Keep'] | RootClaimType | dict", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ): """Sets the root claim type for the coldkey in provided wallet. @@ -5997,12 +6544,16 @@ def set_root_claim_type( - RootClaimType: RootClaimType.Swap, RootClaimType.Keep - Dict: {"KeepSubnets": {"subnets": [1, 2, 3]}} - Callable: RootClaimType.KeepSubnets([1, 2, 3]) + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6011,10 +6562,12 @@ def set_root_claim_type( subtensor=self, wallet=wallet, new_root_claim_type=new_root_claim_type, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def set_subnet_identity( @@ -6022,10 +6575,13 @@ def set_subnet_identity( wallet: "Wallet", netuid: int, subnet_identity: SubnetIdentity, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Sets the identity of a subnet for a specific wallet and network. @@ -6035,12 +6591,16 @@ def set_subnet_identity( netuid: The unique ID of the network on which the operation takes place. subnet_identity: The identity data of the subnet including attributes like name, GitHub repository, contact, URL, discord, description, and any additional metadata. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6057,10 +6617,12 @@ def set_subnet_identity( discord=subnet_identity.discord, description=subnet_identity.description, additional=subnet_identity.additional, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def set_weights( @@ -6074,10 +6636,13 @@ def set_weights( commit_reveal_version: int = 4, max_attempts: int = 5, version_key: int = version_as_int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = 8, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Sets the interneuronal weights for the specified neuron. This process involves specifying the influence or trust @@ -6095,12 +6660,16 @@ def set_weights( commit_reveal_version: The version of the chain commit-reveal protocol to use. max_attempts: The number of maximum attempts to set weights. version_key: Version key for compatibility with the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6153,10 +6722,12 @@ def _blocks_weight_limit() -> bool: block_time=block_time, commit_reveal_version=commit_reveal_version, version_key=version_key, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) except Exception as error: return ExtrinsicResponse.from_exception( @@ -6184,10 +6755,12 @@ def _blocks_weight_limit() -> bool: uids=uids, weights=weights, version_key=version_key, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) except Exception as error: return ExtrinsicResponse.from_exception( @@ -6206,10 +6779,13 @@ def serve_axon( netuid: int, axon: "Axon", certificate: Optional[Certificate] = None, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Registers an ``Axon`` serving endpoint on the Bittensor network for a specific neuron. @@ -6221,12 +6797,16 @@ def serve_axon( netuid: The unique identifier of the subnetwork. axon: The Axon instance to be registered for serving. certificate: Certificate to use for TLS. If ``None``, no TLS will be used. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6239,10 +6819,12 @@ def serve_axon( netuid=netuid, axon=axon, certificate=certificate, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def set_commitment( @@ -6250,10 +6832,13 @@ def set_commitment( wallet: "Wallet", netuid: int, data: str, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Commits arbitrary data to the Bittensor network by publishing metadata. @@ -6266,12 +6851,16 @@ def set_commitment( wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the data. netuid (int): The unique identifier of the subnetwork. data (str): The data to be committed to the network. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6291,10 +6880,12 @@ def set_commitment( netuid=netuid, data_type=f"Raw{len(data)}", data=data.encode(), + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def set_reveal_commitment( @@ -6304,10 +6895,13 @@ def set_reveal_commitment( data: str, blocks_until_reveal: int = 360, block_time: Union[int, float] = 12, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Commits arbitrary data to the Bittensor network by publishing metadata. @@ -6319,12 +6913,16 @@ def set_reveal_commitment( blocks_until_reveal: The number of blocks from now after which the data will be revealed. Then number of blocks in one epoch. block_time: The number of seconds between each block. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6345,10 +6943,12 @@ def set_reveal_commitment( netuid=netuid, data_type="TimelockEncrypted", data=data_, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) response.data = data_ return response @@ -6357,10 +6957,13 @@ def start_call( self, wallet: "Wallet", netuid: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start @@ -6369,12 +6972,16 @@ def start_call( Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6383,10 +6990,12 @@ def start_call( subtensor=self, wallet=wallet, netuid=netuid, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def swap_stake( @@ -6399,10 +7008,13 @@ def swap_stake( safe_swapping: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Moves stake between subnets while keeping the same coldkey-hotkey pair ownership. @@ -6421,12 +7033,16 @@ def swap_stake( rate_tolerance: The maximum allowed increase in the price ratio between subnets (origin_price/destination_price). For example, 0.005 = 0.5% maximum increase. Only used when safe_staking is True. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the inclusion of the transaction. wait_for_finalization: Whether to wait for the finalization of the transaction. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6449,10 +7065,12 @@ def swap_stake( safe_swapping=safe_swapping, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def toggle_user_liquidity( @@ -6460,10 +7078,13 @@ def toggle_user_liquidity( wallet: "Wallet", netuid: int, enable: bool, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Allow to toggle user liquidity for specified subnet. @@ -6471,12 +7092,16 @@ def toggle_user_liquidity( wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. enable: Boolean indicating whether to enable user liquidity. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6488,10 +7113,12 @@ def toggle_user_liquidity( wallet=wallet, netuid=netuid, enable=enable, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def transfer( @@ -6501,10 +7128,13 @@ def transfer( amount: Optional[Balance], transfer_all: bool = False, keep_alive: bool = True, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Transfer token of amount to destination. @@ -6515,12 +7145,16 @@ def transfer( amount: Number of tokens to transfer. `None` is transferring all. transfer_all: Flag to transfer all tokens. keep_alive: Flag to keep the connection alive. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6533,10 +7167,12 @@ def transfer( amount=amount, transfer_all=transfer_all, keep_alive=keep_alive, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def transfer_stake( @@ -6547,10 +7183,13 @@ def transfer_stake( origin_netuid: int, destination_netuid: int, amount: Balance, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Transfers stake from one subnet to another while changing the coldkey owner. @@ -6562,12 +7201,16 @@ def transfer_stake( origin_netuid: The source subnet UID. destination_netuid: The destination subnet UID. amount: Amount to transfer. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6581,10 +7224,12 @@ def transfer_stake( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def unstake( @@ -6596,10 +7241,13 @@ def unstake( allow_partial_stake: bool = False, rate_tolerance: float = 0.005, safe_unstaking: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Removes a specified amount of stake from a single hotkey account. This function is critical for adjusting @@ -6617,12 +7265,16 @@ def unstake( 0.005 = 0.5% maximum price decrease. Only used when safe_staking is True. safe_unstaking: If true, enables price safety checks to protect against fluctuating prices. The unstake will only execute if the price change doesn't exceed the rate tolerance. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6641,10 +7293,12 @@ def unstake( allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, safe_unstaking=safe_unstaking, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def unstake_all( @@ -6653,10 +7307,13 @@ def unstake_all( netuid: int, hotkey_ss58: str, rate_tolerance: Optional[float] = 0.005, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. @@ -6666,12 +7323,16 @@ def unstake_all( hotkey_ss58: The SS58 address of the hotkey to unstake from. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6720,10 +7381,12 @@ def unstake_all( netuid=netuid, hotkey_ss58=hotkey_ss58, rate_tolerance=rate_tolerance, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def unstake_multiple( @@ -6733,10 +7396,13 @@ def unstake_multiple( hotkey_ss58s: list[str], amounts: Optional[list[Balance]] = None, unstake_all: bool = False, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Performs batch unstaking from multiple hotkey accounts, allowing a neuron to reduce its staked amounts @@ -6748,12 +7414,16 @@ def unstake_multiple( hotkey_ss58s: A list of hotkey `SS58` addresses to unstake from. amounts: The amounts of TAO to unstake from each hotkey. If not provided, unstakes all. unstake_all: If true, unstakes all tokens. If `True` amounts are ignored. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6768,10 +7438,12 @@ def unstake_multiple( hotkey_ss58s=hotkey_ss58s, amounts=amounts, unstake_all=unstake_all, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def update_cap_crowdloan( @@ -6779,10 +7451,13 @@ def update_cap_crowdloan( wallet: "Wallet", crowdloan_id: int, new_cap: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Updates the fundraising cap (maximum total contribution) of a non-finalized crowdloan. @@ -6794,12 +7469,16 @@ def update_cap_crowdloan( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_cap: The new fundraising cap (in TAO or Balance). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6814,10 +7493,12 @@ def update_cap_crowdloan( wallet=wallet, crowdloan_id=crowdloan_id, new_cap=new_cap, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def update_end_crowdloan( @@ -6825,10 +7506,13 @@ def update_end_crowdloan( wallet: "Wallet", crowdloan_id: int, new_end: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Updates the end block of a non-finalized crowdloan campaign. @@ -6840,12 +7524,16 @@ def update_end_crowdloan( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_end: The new block number at which the crowdloan will end. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6861,10 +7549,12 @@ def update_end_crowdloan( wallet=wallet, crowdloan_id=crowdloan_id, new_end=new_end, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def update_min_contribution_crowdloan( @@ -6872,10 +7562,13 @@ def update_min_contribution_crowdloan( wallet: "Wallet", crowdloan_id: int, new_min_contribution: "Balance", + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Updates the minimum contribution amount of a non-finalized crowdloan. @@ -6887,12 +7580,16 @@ def update_min_contribution_crowdloan( wallet: Bittensor Wallet instance used to sign the transaction. crowdloan_id: The unique identifier of the crowdloan to update. new_min_contribution: The new minimum contribution amount (in TAO or Balance). + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6907,20 +7604,25 @@ def update_min_contribution_crowdloan( wallet=wallet, crowdloan_id=crowdloan_id, new_min_contribution=new_min_contribution, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) def withdraw_crowdloan( self, wallet: "Wallet", crowdloan_id: int, + *, + mev_protection: bool = DEFAULT_MEV_PROTECTION, period: Optional[int] = DEFAULT_PERIOD, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + wait_for_revealed_execution: bool = True, ) -> ExtrinsicResponse: """ Withdraws a contribution from an active (not yet finalized or dissolved) crowdloan. @@ -6928,12 +7630,16 @@ def withdraw_crowdloan( Parameters: wallet: Wallet instance used to sign the transaction (must be unlocked). crowdloan_id: The unique identifier of the crowdloan to withdraw from. + mev_protection: If True, encrypts and submits the transaction through the MEV Shield pallet to protect + against front-running and MEV attacks. The transaction remains encrypted in the mempool until validators + decrypt and execute it. If False, submits the transaction directly without encryption. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. wait_for_finalization: Whether to wait for finalization of the extrinsic. + wait_for_revealed_execution: Whether to wait for the revealed execution of transaction if mev_protection used. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -6946,8 +7652,10 @@ def withdraw_crowdloan( subtensor=self, wallet=wallet, crowdloan_id=crowdloan_id, + mev_protection=mev_protection, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=wait_for_revealed_execution, ) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index af1e7dd2bc..fa55c53d9e 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -301,6 +301,11 @@ class ExtrinsicResponse: contains the most detailed execution data available, including the block number and hash, triggered events, extrinsic index, execution phase, and other low-level details. This allows deep debugging or post-analysis of on-chain execution. + mev_extrinsic_receipt: The receipt object of the revealed (decrypted and executed) MEV Shield extrinsic. This is + populated when using MEV Shield protection (``with_mev_protection=True``) and contains the execution details + of the second extrinsic that decrypts and executes the originally encrypted call. Contains triggered events + such as ``DecryptedExecuted`` or ``DecryptedRejected``, block information, and other execution metadata. Set + to ``None`` for non-MEV Shield transactions or when the revealed extrinsic receipt is not available. transaction_tao_fee: TAO fee charged by the transaction in TAO (e.g., fee for add_stake), if available. transaction_alpha_fee: Alpha fee charged by the transaction (e.g., fee for transfer_stake), if available. error: Captures the underlying exception if the extrinsic failed, otherwise `None`. @@ -329,10 +334,11 @@ class ExtrinsicResponse: message: Successfully registered subnet extrinsic_function: register_subnet_extrinsic extrinsic: {'account_id': '0xd43593c715fdd31c... + transaction_fee: τ1.0 + extrinsic_receipt: Extrinsic Receipt data of of the submitted extrinsic + mev_extrinsic_receipt: None transaction_tao_fee: τ1.0 transaction_alpha_fee: 1.0β - extrinsic_receipt: Extrinsic Receipt data of of the submitted extrinsic - transaction_fee: τ1.0 error: None data: None @@ -353,6 +359,7 @@ class ExtrinsicResponse: extrinsic: Optional["GenericExtrinsic"] = None extrinsic_fee: Optional["Balance"] = None extrinsic_receipt: Optional["AsyncExtrinsicReceipt | ExtrinsicReceipt"] = None + mev_extrinsic_receipt: Optional["AsyncExtrinsicReceipt | ExtrinsicReceipt"] = None transaction_tao_fee: Optional["Balance"] = None transaction_alpha_fee: Optional["Balance"] = None error: Optional[Exception] = None @@ -375,11 +382,12 @@ def __str__(self): f"\textrinsic_function: {self.extrinsic_function}\n" f"\textrinsic: {self.extrinsic}\n" f"\textrinsic_fee: {self.extrinsic_fee}\n" - f"\textrinsic_receipt: {_extrinsic_receipt}" + f"\textrinsic_receipt: {_extrinsic_receipt}\n" + f"\tmev_extrinsic_receipt: {self.mev_extrinsic_receipt}\n" f"\ttransaction_tao_fee: {self.transaction_tao_fee}\n" f"\ttransaction_alpha_fee: {self.transaction_alpha_fee}\n" + f"\terror: {self.error}\n" f"\tdata: {self.data}\n" - f"\terror: {self.error}" ) def __repr__(self): @@ -530,7 +538,17 @@ def with_log( ExtrinsicResponse instance. """ if self.message: - getattr(logging, level)(self.message) + if level in ["trace", "error"]: + message = f"[red]{self.message}[/red]" + elif level == "info": + message = f"[blue]{self.message}[/blue]" + elif level == "warning": + message = f"[yellow]{self.message}[/yellow]" + elif level == "success": + message = f"[green]{self.message}[/green]" + else: + message = self.message + getattr(logging, level)(message) return self diff --git a/bittensor/extras/subtensor_api/__init__.py b/bittensor/extras/subtensor_api/__init__.py index ee001ebc63..7f538e2ed9 100644 --- a/bittensor/extras/subtensor_api/__init__.py +++ b/bittensor/extras/subtensor_api/__init__.py @@ -8,6 +8,7 @@ from .delegates import Delegates as _Delegates from .extrinsics import Extrinsics as _Extrinsics from .metagraphs import Metagraphs as _Metagraphs +from .mev_shield import MevShield as _MevShield from .neurons import Neurons as _Neurons from .proxy import Proxy as _Proxy from .queries import Queries as _Queries @@ -230,6 +231,11 @@ def metagraphs(self): """Property to access metagraphs methods.""" return _Metagraphs(self.inner_subtensor) + @property + def mev_shield(self): + """Property to access MEV Shield methods.""" + return _MevShield(self.inner_subtensor) + @property def neurons(self): """Property to access neurons methods.""" diff --git a/bittensor/extras/subtensor_api/mev_shield.py b/bittensor/extras/subtensor_api/mev_shield.py new file mode 100644 index 0000000000..8484e1a766 --- /dev/null +++ b/bittensor/extras/subtensor_api/mev_shield.py @@ -0,0 +1,18 @@ +from typing import Union + +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor +from bittensor.core.subtensor import Subtensor as _Subtensor + + +class MevShield: + """Class for managing MEV Shield operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + # Storage queries + self.get_mev_shield_current_key = subtensor.get_mev_shield_current_key + self.get_mev_shield_next_key = subtensor.get_mev_shield_next_key + self.get_mev_shield_submission = subtensor.get_mev_shield_submission + self.get_mev_shield_submissions = subtensor.get_mev_shield_submissions + + # Extrinsics + self.mev_submit_encrypted = subtensor.mev_submit_encrypted diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index f4dd89902d..fa26b0b745 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -100,6 +100,7 @@ UnknownSynapseError, UnstakeError, ) +from bittensor.core.extrinsics import pallets from bittensor.core.metagraph import Metagraph from bittensor.core.settings import BLOCKTIME from bittensor.core.stream import StreamingSynapse @@ -229,6 +230,7 @@ "tao", "rao", "logging", + "pallets", "MockSubtensor", "SubnetsAPI", "trace", diff --git a/pyproject.toml b/pyproject.toml index b6e6a387fe..3f95468c9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,9 +32,9 @@ dependencies = [ "pydantic>=2.3,<3", "scalecodec==1.2.12", "uvicorn", - "bittensor-drand>=1.0.0,<2.0.0", + "bittensor-drand>=1.2.0,<2.0.0", "bittensor-wallet>=4.0.0,<5.0", - "async-substrate-interface>=1.5.12" + "async-substrate-interface>=1.5.13" ] [project.optional-dependencies] diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 7783a55f2a..89b66c3368 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -67,7 +67,7 @@ def local_chain(request): # passed env variable to control node mod (non-/fast-blocks) fast_blocks = "False" if (os.getenv("FAST_BLOCKS") == "0") is True else "True" - params = f"{fast_blocks}" if args is None else f"{fast_blocks} {args} " + params = f"{fast_blocks}" if args is None else f"{args}" if shutil.which("docker") and not os.getenv("USE_DOCKER") == "0": yield from docker_runner(params) diff --git a/tests/e2e_tests/test_mev_shield.py b/tests/e2e_tests/test_mev_shield.py new file mode 100644 index 0000000000..d54934599d --- /dev/null +++ b/tests/e2e_tests/test_mev_shield.py @@ -0,0 +1,191 @@ +"""E2E tests for MEV Shield functionality.""" + +import pytest + +from bittensor.core.extrinsics import pallets +from bittensor.utils.balance import Balance +from bittensor.utils.btlogging import logging +from tests.e2e_tests.utils import ( + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_NEURON, + REGISTER_SUBNET, + SUDO_SET_TEMPO, + NETUID, + AdminUtils, +) + +TEMPO_TO_SET = 3 + + +@pytest.mark.parametrize("local_chain", [False], indirect=True) +def test_mev_shield_happy_path( + subtensor, alice_wallet, bob_wallet, charlie_wallet, dave_wallet, local_chain +): + """Tests MEV Shield functionality with add_stake inner call. + + This test verifies the complete MEV Shield flow: encrypting a transaction, submitting it, and verifying that + validators decrypt and execute it correctly. The test covers two scenarios: + - using default signer (wallet.coldkey) + - explicit signer keypair + + Steps: + - Register a subnet through Bob and activate it + - Register Charlie's neuron on the subnet + - Wait until the third epoch (MEV Shield logic requires at least 3 epochs with fast blocks) + - For each signer scenario (None/default and explicit dave_wallet.coldkey): + - Get the current stake before the transaction + - Create an add_stake_limit call for Charlie's hotkey + - Submit the call encrypted through MEV Shield using mev_submit_encrypted + - Wait for validators to decrypt and execute the transaction (3 blocks) + - Verify that the stake has increased after execution + """ + bob_sn = TestSubnet(subtensor) + bob_sn.execute_steps( + [ + REGISTER_SUBNET(bob_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + ACTIVATE_SUBNET(bob_wallet), + REGISTER_NEURON(charlie_wallet), + ] + ) + + if subtensor.chain.is_fast_blocks(): + # MeV Res logic works not from before third epoch with fast blocks, so we need to wait for it + next_epoch_start_block = subtensor.subnets.get_next_epoch_start_block( + bob_sn.netuid + ) + subtensor.wait_for_block( + next_epoch_start_block + subtensor.subnets.tempo(bob_sn.netuid) * 2 + ) + + for signer in [None, dave_wallet.coldkey]: + stake_before = subtensor.staking.get_stake( + coldkey_ss58=signer.ss58_address + if signer is not None + else alice_wallet.coldkey.ss58_address, + hotkey_ss58=charlie_wallet.hotkey.ss58_address, + netuid=bob_sn.netuid, + ) + logging.console.info(f"Stake before: {stake_before}") + + subnet_price = subtensor.subnets.get_subnet_price(2) + limit_price = (subnet_price * 2).rao + + call = pallets.SubtensorModule(subtensor).add_stake_limit( + netuid=bob_sn.netuid, + hotkey=charlie_wallet.hotkey.ss58_address, + amount_staked=Balance.from_tao(5).rao, + allow_partial=True, + limit_price=limit_price, + ) + + response = subtensor.mev_shield.mev_submit_encrypted( + wallet=alice_wallet, + call=call, + signer_keypair=signer, + raise_error=True, + ) + + assert response.success, response.message + assert response.mev_extrinsic_receipt is not None, ( + "No revealed extrinsic receipt." + ) + + stake_after = subtensor.staking.get_stake( + coldkey_ss58=signer.ss58_address + if signer is not None + else alice_wallet.coldkey.ss58_address, + hotkey_ss58=charlie_wallet.hotkey.ss58_address, + netuid=bob_sn.netuid, + ) + logging.console.info(f"Stake after: {stake_after}") + assert stake_after > stake_before + + +@pytest.mark.parametrize("local_chain", [False], indirect=True) +@pytest.mark.asyncio +async def test_mev_shield_happy_path_async( + async_subtensor, alice_wallet, bob_wallet, charlie_wallet, dave_wallet, local_chain +): + """Async tests MEV Shield functionality with add_stake inner call. + + This test verifies the complete MEV Shield flow: encrypting a transaction, submitting it, and verifying that + validators decrypt and execute it correctly. The test covers two scenarios: + - using default signer (wallet.coldkey) + - explicit signer keypair + + Steps: + - Register a subnet through Bob and activate it + - Register Charlie's neuron on the subnet + - Wait until the third epoch (MEV Shield logic requires at least 3 epochs with fast blocks) + - For each signer scenario (None/default and explicit dave_wallet.coldkey): + - Get the current stake before the transaction + - Create an add_stake_limit call for Charlie's hotkey + - Submit the call encrypted through MEV Shield using mev_submit_encrypted + - Wait for validators to decrypt and execute the transaction (3 blocks) + - Verify that the stake has increased after execution + """ + + bob_sn = TestSubnet(async_subtensor) + await bob_sn.async_execute_steps( + [ + REGISTER_SUBNET(bob_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + ACTIVATE_SUBNET(bob_wallet), + REGISTER_NEURON(charlie_wallet), + ] + ) + + if await async_subtensor.chain.is_fast_blocks(): + # MeV Res logic works not from before third epoch with fast blocks, so we need to wait for it + next_epoch_start_block = ( + await async_subtensor.subnets.get_next_epoch_start_block(bob_sn.netuid) + ) + await async_subtensor.wait_for_block( + next_epoch_start_block + + await async_subtensor.subnets.tempo(bob_sn.netuid) * 2 + ) + + for signer in [None, dave_wallet.coldkey]: + stake_before = await async_subtensor.staking.get_stake( + coldkey_ss58=signer.ss58_address + if signer is not None + else alice_wallet.coldkey.ss58_address, + hotkey_ss58=charlie_wallet.hotkey.ss58_address, + netuid=bob_sn.netuid, + ) + logging.console.info(f"Stake before: {stake_before}") + + subnet_price = await async_subtensor.subnets.get_subnet_price(2) + limit_price = (subnet_price * 2).rao + + call = await pallets.SubtensorModule(async_subtensor).add_stake_limit( + netuid=bob_sn.netuid, + hotkey=charlie_wallet.hotkey.ss58_address, + amount_staked=Balance.from_tao(5).rao, + allow_partial=True, + limit_price=limit_price, + ) + + response = await async_subtensor.mev_shield.mev_submit_encrypted( + wallet=alice_wallet, + call=call, + signer_keypair=signer, + raise_error=True, + ) + + assert response.success, response.message + assert response.mev_extrinsic_receipt is not None, ( + "No revealed extrinsic receipt." + ) + + stake_after = await async_subtensor.staking.get_stake( + coldkey_ss58=signer.ss58_address + if signer is not None + else alice_wallet.coldkey.ss58_address, + hotkey_ss58=charlie_wallet.hotkey.ss58_address, + netuid=bob_sn.netuid, + ) + logging.console.info(f"Stake after: {stake_after}") + assert stake_after > stake_before diff --git a/tests/unit_tests/extrinsics/asyncex/test_proxy.py b/tests/unit_tests/extrinsics/asyncex/test_proxy.py index fd79ca3615..d5287ec407 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_proxy.py +++ b/tests/unit_tests/extrinsics/asyncex/test_proxy.py @@ -1,6 +1,7 @@ import pytest from bittensor.core.extrinsics.asyncex import proxy +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from scalecodec.types import GenericCall from bittensor_wallet import Wallet @@ -253,6 +254,7 @@ async def test_kill_pure_proxy_extrinsic(subtensor, mocker): real_account_ss58=pure_proxy_ss58, force_proxy_type=proxy.ProxyType.Any, call=mocked_pallet_call.return_value, + mev_protection=DEFAULT_MEV_PROTECTION, period=None, raise_error=False, wait_for_inclusion=True, diff --git a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py index 6d698ef8c1..9d1176a942 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py @@ -1,6 +1,7 @@ import pytest from bittensor.core.extrinsics.asyncex import unstaking +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance @@ -157,9 +158,11 @@ async def test_unstake_multiple_extrinsic_some_unstake_is_happy(fake_wallet, moc mocked_unstake_extrinsic.assert_awaited_once_with( subtensor=fake_subtensor, wallet=fake_wallet, - amount=Balance.from_tao(1.1, sn_5), - hotkey_ss58=hotkey_ss58s[0], netuid=fake_netuids[0], + hotkey_ss58=hotkey_ss58s[0], + amount=Balance.from_tao(1.1, sn_5), + rate_tolerance=0.05, + mev_protection=DEFAULT_MEV_PROTECTION, period=None, raise_error=False, wait_for_inclusion=wait_for_inclusion, diff --git a/tests/unit_tests/extrinsics/test_crowdloan.py b/tests/unit_tests/extrinsics/test_crowdloan.py index 49bdb592c7..2427b2b920 100644 --- a/tests/unit_tests/extrinsics/test_crowdloan.py +++ b/tests/unit_tests/extrinsics/test_crowdloan.py @@ -2,6 +2,7 @@ from scalecodec.types import GenericCall import pytest from bittensor.core.extrinsics import crowdloan +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance @@ -26,6 +27,7 @@ def test_contribute_crowdloan_extrinsic(subtensor, mocker): wallet=faked_wallet, crowdloan_id=fake_crowdloan_id, amount=fake_amount, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Assertions @@ -75,6 +77,7 @@ def test_create_crowdloan_extrinsic(subtensor, mocker): end=fake_end, call=fake_call, target_address=fake_target_address, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Assertions @@ -128,6 +131,7 @@ def test_same_params_extrinsics(subtensor, mocker, extrinsic, subtensor_function subtensor=subtensor, wallet=faked_wallet, crowdloan_id=fake_crowdloan_id, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Assertions @@ -165,6 +169,7 @@ def test_update_cap_crowdloan_extrinsic(subtensor, mocker): wallet=faked_wallet, crowdloan_id=fake_crowdloan_id, new_cap=fake_new_cap, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Assertions @@ -204,6 +209,7 @@ def test_update_end_crowdloan_extrinsic(subtensor, mocker): wallet=faked_wallet, crowdloan_id=fake_crowdloan_id, new_end=fake_new_end, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Assertions @@ -247,6 +253,7 @@ def test_update_min_contribution_crowdloan_extrinsic(subtensor, mocker): wallet=faked_wallet, crowdloan_id=fake_crowdloan_id, new_min_contribution=fake_new_min_contribution, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Assertions diff --git a/tests/unit_tests/extrinsics/test_proxy.py b/tests/unit_tests/extrinsics/test_proxy.py index 5313538b45..b718d86d65 100644 --- a/tests/unit_tests/extrinsics/test_proxy.py +++ b/tests/unit_tests/extrinsics/test_proxy.py @@ -2,6 +2,7 @@ from scalecodec.types import GenericCall from bittensor.core.extrinsics import proxy +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse @@ -26,6 +27,7 @@ def test_add_proxy_extrinsic(subtensor, mocker): delegate_ss58=delegate_ss58, proxy_type=proxy_type, delay=delay, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -67,6 +69,7 @@ def test_remove_proxy_extrinsic(subtensor, mocker): delegate_ss58=delegate_ss58, proxy_type=proxy_type, delay=delay, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -101,6 +104,7 @@ def test_remove_proxies_extrinsic(subtensor, mocker): response = proxy.remove_proxies_extrinsic( subtensor=subtensor, wallet=wallet, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -160,6 +164,7 @@ def test_create_pure_proxy_extrinsic(subtensor, mocker): proxy_type=proxy_type, delay=delay, index=index, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -216,6 +221,7 @@ def test_kill_pure_proxy_extrinsic(subtensor, mocker): index=index, height=height, ext_index=ext_index, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -233,6 +239,7 @@ def test_kill_pure_proxy_extrinsic(subtensor, mocker): real_account_ss58=pure_proxy_ss58, force_proxy_type=proxy.ProxyType.Any, call=mocked_pallet_call.return_value, + mev_protection=DEFAULT_MEV_PROTECTION, period=None, raise_error=False, wait_for_inclusion=True, @@ -262,6 +269,7 @@ def test_proxy_extrinsic(subtensor, mocker): real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -302,6 +310,7 @@ def test_proxy_extrinsic_with_none_force_proxy_type(subtensor, mocker): real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -344,6 +353,7 @@ def test_proxy_announced_extrinsic(subtensor, mocker): real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -387,6 +397,7 @@ def test_proxy_announced_extrinsic_with_none_force_proxy_type(subtensor, mocker) real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -425,6 +436,7 @@ def test_announce_extrinsic(subtensor, mocker): wallet=wallet, real_account_ss58=real_account_ss58, call_hash=call_hash, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -461,6 +473,7 @@ def test_reject_announcement_extrinsic(subtensor, mocker): wallet=wallet, delegate_ss58=delegate_ss58, call_hash=call_hash, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -497,6 +510,7 @@ def test_remove_announcement_extrinsic(subtensor, mocker): wallet=wallet, real_account_ss58=real_account_ss58, call_hash=call_hash, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -529,6 +543,7 @@ def test_poke_deposit_extrinsic(subtensor, mocker): response = proxy.poke_deposit_extrinsic( subtensor=subtensor, wallet=wallet, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py index adeac230b4..413f6155e4 100644 --- a/tests/unit_tests/extrinsics/test_root.py +++ b/tests/unit_tests/extrinsics/test_root.py @@ -97,6 +97,7 @@ def test_root_register_extrinsic( wallet=mock_wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=True, ) # Assert assert result.success == expected_result diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 427609cf85..bfb6dea58c 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -111,6 +111,7 @@ def test_serve_extrinsic_happy_path( placeholder2=placeholder2, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=True, ) # Assert @@ -170,6 +171,7 @@ def test_serve_extrinsic_edge_cases( placeholder2=placeholder2, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=True, ) # Assert @@ -228,6 +230,7 @@ def test_serve_extrinsic_error_cases( placeholder2=placeholder2, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=True, ) # Assert @@ -308,6 +311,7 @@ def test_serve_axon_extrinsic( axon=mock_axon, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=True, ) # Assert @@ -365,6 +369,7 @@ def test_publish_metadata( data=data, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=True, ) # Assert assert result.success is True, f"Test ID: {test_id}" diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index 8696677320..77ced51faf 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -1,6 +1,7 @@ import pytest from bittensor.core.extrinsics import staking +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.utils.balance import Balance from bittensor.core.types import ExtrinsicResponse @@ -37,8 +38,10 @@ def test_add_stake_extrinsic(mocker): hotkey_ss58=hotkey_ss58, netuid=fake_netuid, amount=amount, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=True, ) # Asserts @@ -95,6 +98,7 @@ def test_add_stake_multiple_extrinsic(subtensor, mocker, fake_wallet): netuids=netuids, hotkey_ss58s=hotkey_ss58s, amounts=amounts, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, raise_error=True, @@ -137,6 +141,7 @@ def test_set_auto_stake_extrinsic( wallet=fake_wallet, hotkey_ss58=hotkey_ss58, netuid=netuid, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index d582d785e9..e6f560c388 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -1,5 +1,6 @@ import pytest from bittensor.core.extrinsics import transfer +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.utils.balance import Balance from bittensor.core.types import ExtrinsicResponse @@ -47,6 +48,7 @@ def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): destination_ss58=fake_destination, amount=fake_amount, transfer_all=False, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=True, wait_for_finalization=True, keep_alive=True, @@ -118,6 +120,7 @@ def test_transfer_extrinsic_call_successful_with_failed_response( destination_ss58=fake_destination, amount=fake_amount, transfer_all=False, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=True, wait_for_finalization=True, keep_alive=True, @@ -183,6 +186,7 @@ def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, mocker) destination_ss58=fake_destination, amount=fake_amount, transfer_all=False, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=True, wait_for_finalization=True, keep_alive=True, @@ -224,6 +228,7 @@ def test_transfer_extrinsic_invalid_destination(subtensor, fake_wallet, mocker): destination_ss58=fake_destination, amount=fake_amount, transfer_all=False, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=True, wait_for_finalization=True, keep_alive=True, @@ -255,6 +260,7 @@ def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocker): destination_ss58=fake_destination, amount=fake_amount, transfer_all=False, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=True, wait_for_finalization=True, keep_alive=True, diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 92f1192954..f5c6f578a9 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -1,4 +1,5 @@ from bittensor.core.extrinsics import unstaking +from bittensor.core.settings import DEFAULT_MEV_PROTECTION from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance @@ -33,8 +34,10 @@ def test_unstake_extrinsic(fake_wallet, mocker): hotkey_ss58=hotkey_ss58, netuid=fake_netuid, amount=amount, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=True, ) # Asserts @@ -79,6 +82,7 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): wallet=fake_wallet, hotkey_ss58=hotkey, netuid=fake_netuid, + mev_protection=DEFAULT_MEV_PROTECTION, ) # Asserts @@ -139,8 +143,10 @@ def test_unstake_multiple_extrinsic(subtensor, fake_wallet, mocker): hotkey_ss58s=hotkey_ss58s, netuids=fake_netuids, amounts=amounts, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=True, ) # Asserts @@ -158,8 +164,10 @@ def test_unstake_multiple_extrinsic(subtensor, fake_wallet, mocker): netuid=sn_5, hotkey_ss58="hotkey1", amount=Balance.from_tao(1.1, sn_5), + mev_protection=DEFAULT_MEV_PROTECTION, period=None, raise_error=False, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + wait_for_revealed_execution=True, ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 300f8c6d84..714cfc5c18 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2,7 +2,7 @@ import unittest.mock as mock import pytest -from async_substrate_interface.types import ScaleObj, Runtime +from async_substrate_interface.types import Runtime, ScaleObj from bittensor_wallet import Wallet from scalecodec import GenericCall @@ -10,15 +10,15 @@ from bittensor.core import async_subtensor, settings from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.core.chain_data import ( - proposal_vote_data, ChainIdentity, NeuronInfo, - StakeInfo, SelectiveMetagraphIndex, + StakeInfo, + proposal_vote_data, ) from bittensor.core.errors import BalanceTypeError +from bittensor.core.settings import DEFAULT_MEV_PROTECTION, DEFAULT_PERIOD from bittensor.core.types import ExtrinsicResponse -from bittensor.core.settings import DEFAULT_PERIOD from bittensor.utils import U64_MAX, get_function_name from bittensor.utils.balance import Balance @@ -1737,7 +1737,7 @@ async def fake_error_message(): assert result == (False, mocked_format_error_message.return_value) assert result.extrinsic_function == get_function_name() assert result.extrinsic == fake_extrinsic - assert result.extrinsic_fee == None + assert result.extrinsic_fee is None assert result.error is fake_error assert result.data is None @@ -1828,7 +1828,7 @@ async def test_sign_and_send_extrinsic_substrate_request_exception( assert result == (False, str(fake_exception)) assert result.extrinsic_function == get_function_name() assert result.extrinsic == fake_extrinsic - assert result.extrinsic_fee == None + assert result.extrinsic_fee is None assert result.error == fake_exception assert result.data is None @@ -2592,11 +2592,13 @@ async def test_transfer_success(subtensor, fake_wallet, mocker): destination_ss58=fake_destination, amount=fake_amount, transfer_all=fake_transfer_all, - wait_for_inclusion=True, - wait_for_finalization=False, keep_alive=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=False, + wait_for_revealed_execution=True, ) assert result == mocked_transfer_extrinsic.return_value @@ -2628,10 +2630,12 @@ async def test_register_success(subtensor, fake_wallet, mocker): subtensor=subtensor, tpb=256, update_interval=None, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_finalization=True, wait_for_inclusion=True, + wait_for_revealed_execution=True, ) assert result == mocked_register_extrinsic.return_value @@ -2666,10 +2670,12 @@ async def test_set_children(subtensor, fake_wallet, mocker): hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, children=fake_children, - wait_for_finalization=True, - wait_for_inclusion=True, - raise_error=False, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_set_children_extrinsic.return_value @@ -2795,10 +2801,12 @@ async def test_set_subnet_identity(mocker, subtensor, fake_wallet): discord=fake_subnet_identity.discord, description=fake_subnet_identity.description, additional=fake_subnet_identity.additional, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, - wait_for_finalization=True, wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -2902,10 +2910,12 @@ async def test_start_call(subtensor, mocker): subtensor=subtensor, wallet=wallet_name, netuid=netuid, - wait_for_inclusion=True, - wait_for_finalization=False, + mev_protection=False, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=False, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -3397,10 +3407,12 @@ async def test_unstake_all(subtensor, fake_wallet, mocker): hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, rate_tolerance=0.005, - wait_for_inclusion=True, - wait_for_finalization=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == fake_unstake_all_extrinsic.return_value @@ -3623,10 +3635,12 @@ async def test_add_liquidity(subtensor, fake_wallet, mocker): price_low=Balance.from_tao(180).rao, price_high=Balance.from_tao(130).rao, hotkey_ss58=None, - wait_for_inclusion=True, - wait_for_finalization=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -3657,10 +3671,12 @@ async def test_modify_liquidity(subtensor, fake_wallet, mocker): position_id=position_id, liquidity_delta=Balance.from_tao(150), hotkey_ss58=None, - wait_for_inclusion=True, - wait_for_finalization=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -3689,10 +3705,12 @@ async def test_remove_liquidity(subtensor, fake_wallet, mocker): netuid=netuid, position_id=position_id, hotkey_ss58=None, - wait_for_inclusion=True, - wait_for_finalization=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -3720,10 +3738,12 @@ async def test_toggle_user_liquidity(subtensor, fake_wallet, mocker): wallet=fake_wallet, netuid=netuid, enable=enable, - wait_for_inclusion=True, - wait_for_finalization=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -4213,10 +4233,12 @@ async def test_set_auto_stake(subtensor, mocker): wallet=wallet, netuid=netuid, hotkey_ss58=hotkey, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -4329,10 +4351,12 @@ async def test_contribute_crowdloan(mocker, subtensor): wallet=wallet, crowdloan_id=crowdloan_id, amount=amount, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4374,10 +4398,12 @@ async def test_create_crowdloan(mocker, subtensor): end=end, call=call, target_address=target_address, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4413,10 +4439,12 @@ async def test_crowdloan_methods_with_crowdloan_id_parameter( subtensor=subtensor, wallet=wallet, crowdloan_id=crowdloan_id, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4446,10 +4474,12 @@ async def test_update_cap_crowdloan(mocker, subtensor): wallet=wallet, crowdloan_id=crowdloan_id, new_cap=new_cap, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4479,10 +4509,12 @@ async def test_update_end_crowdloan(mocker, subtensor): wallet=wallet, crowdloan_id=crowdloan_id, new_end=new_end, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4512,10 +4544,12 @@ async def test_update_min_contribution_crowdloan(mocker, subtensor): wallet=wallet, crowdloan_id=crowdloan_id, new_min_contribution=new_min_contribution, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4956,10 +4990,12 @@ async def test_claim_root(mocker, subtensor): subtensor=subtensor, wallet=wallet, netuids=netuids, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_claim_root_extrinsic.return_value @@ -4984,10 +5020,12 @@ async def test_set_root_claim_type(mocker, subtensor): subtensor=subtensor, wallet=faked_wallet, new_root_claim_type=fake_new_root_claim_type, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_set_root_claim_type_extrinsic.return_value @@ -5314,10 +5352,12 @@ async def test_add_proxy(mocker, subtensor): delegate_ss58=delegate_ss58, proxy_type=proxy_type, delay=delay, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_add_proxy_extrinsic.return_value @@ -5346,10 +5386,12 @@ async def test_announce_proxy(mocker, subtensor): wallet=wallet, real_account_ss58=real_account_ss58, call_hash=call_hash, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_announce_extrinsic.return_value @@ -5381,10 +5423,12 @@ async def test_create_pure_proxy(mocker, subtensor): proxy_type=proxy_type, delay=delay, index=index, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_create_pure_proxy_extrinsic.return_value @@ -5426,10 +5470,12 @@ async def test_kill_pure_proxy(mocker, subtensor): height=height, ext_index=ext_index, force_proxy_type=async_subtensor.ProxyType.Any, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_kill_pure_proxy_extrinsic.return_value @@ -5450,10 +5496,12 @@ async def test_poke_deposit(mocker, subtensor): mocked_poke_deposit_extrinsic.assert_awaited_once_with( subtensor=subtensor, wallet=wallet, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_poke_deposit_extrinsic.return_value @@ -5483,10 +5531,12 @@ async def test_proxy(mocker, subtensor): real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_proxy_extrinsic.return_value @@ -5521,10 +5571,12 @@ async def test_proxy_announced(mocker, subtensor): real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_proxy_announced_extrinsic.return_value @@ -5553,10 +5605,12 @@ async def test_reject_proxy_announcement(mocker, subtensor): wallet=wallet, delegate_ss58=delegate_ss58, call_hash=call_hash, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_reject_announcement_extrinsic.return_value @@ -5585,10 +5639,12 @@ async def test_remove_proxy_announcement(mocker, subtensor): wallet=wallet, real_account_ss58=real_account_ss58, call_hash=call_hash, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_remove_announcement_extrinsic.return_value @@ -5609,10 +5665,12 @@ async def test_remove_proxies(mocker, subtensor): mocked_remove_proxies_extrinsic.assert_awaited_once_with( subtensor=subtensor, wallet=wallet, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_remove_proxies_extrinsic.return_value @@ -5644,10 +5702,12 @@ async def test_remove_proxy(mocker, subtensor): delegate_ss58=delegate_ss58, proxy_type=proxy_type, delay=delay, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_remove_proxy_extrinsic.return_value @@ -5797,3 +5857,463 @@ async def test_get_stake_info_for_coldkeys_success(subtensor, mocker): mocked_stake_info_list_from_dicts.assert_has_calls( [mocker.call([stake_info_dict_1]), mocker.call([stake_info_dict_2])] ) + + +@pytest.mark.asyncio +async def test_get_mev_shield_current_key_success(subtensor, mocker): + """Test get_mev_shield_current_key returns correct key when found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + fake_public_key_bytes = b"\x00" * 1184 # ML-KEM-768 public key size + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query = mocker.AsyncMock() + mocked_query.return_value = iter([fake_public_key_bytes]) + subtensor.substrate.query = mocked_query + + # Call + result = await subtensor.get_mev_shield_current_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query.assert_awaited_once_with( + module="MevShield", + storage_function="CurrentKey", + block_hash=fake_block_hash, + ) + assert result == fake_public_key_bytes + + +@pytest.mark.asyncio +async def test_get_mev_shield_current_key_none(subtensor, mocker): + """Test get_mev_shield_current_key returns None when key not found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query = mocker.AsyncMock(return_value=None) + subtensor.substrate.query = mocked_query + + # Call + result = await subtensor.get_mev_shield_current_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query.assert_awaited_once_with( + module="MevShield", + storage_function="CurrentKey", + block_hash=fake_block_hash, + ) + assert result is None + + +@pytest.mark.asyncio +async def test_get_mev_shield_current_key_invalid_size(subtensor, mocker): + """Test get_mev_shield_current_key raises ValueError for invalid key size.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + fake_public_key_bytes = b"\x00" * 1000 # Invalid size + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query = mocker.AsyncMock() + mocked_query.return_value = iter([fake_public_key_bytes]) + subtensor.substrate.query = mocked_query + + # Call & Assert + with pytest.raises(ValueError, match="Invalid ML-KEM-768 public key size"): + await subtensor.get_mev_shield_current_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query.assert_awaited_once_with( + module="MevShield", + storage_function="CurrentKey", + block_hash=fake_block_hash, + ) + + +@pytest.mark.asyncio +async def test_get_mev_shield_next_key_success(subtensor, mocker): + """Test get_mev_shield_next_key returns correct key when found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + fake_public_key_bytes = b"\x00" * 1184 # ML-KEM-768 public key size + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query = mocker.AsyncMock() + mocked_query.return_value = iter([fake_public_key_bytes]) + subtensor.substrate.query = mocked_query + + # Call + result = await subtensor.get_mev_shield_next_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query.assert_awaited_once_with( + module="MevShield", + storage_function="NextKey", + block_hash=fake_block_hash, + ) + assert result == fake_public_key_bytes + + +@pytest.mark.asyncio +async def test_get_mev_shield_next_key_none(subtensor, mocker): + """Test get_mev_shield_next_key returns None when key not found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query = mocker.AsyncMock(return_value=None) + subtensor.substrate.query = mocked_query + + # Call + result = await subtensor.get_mev_shield_next_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query.assert_awaited_once_with( + module="MevShield", + storage_function="NextKey", + block_hash=fake_block_hash, + ) + assert result is None + + +@pytest.mark.asyncio +async def test_get_mev_shield_next_key_invalid_size(subtensor, mocker): + """Test get_mev_shield_next_key raises ValueError for invalid key size.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + fake_public_key_bytes = b"\x00" * 1000 # Invalid size + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query = mocker.AsyncMock() + mocked_query.return_value = iter([fake_public_key_bytes]) + subtensor.substrate.query = mocked_query + + # Call & Assert + with pytest.raises(ValueError, match="Invalid ML-KEM-768 public key size"): + await subtensor.get_mev_shield_next_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query.assert_awaited_once_with( + module="MevShield", + storage_function="NextKey", + block_hash=fake_block_hash, + ) + + +@pytest.mark.asyncio +async def test_get_mev_shield_submission_success(subtensor, mocker): + """Test get_mev_shield_submission returns correct submission when found.""" + # Prep + fake_submission_id = "0x1234567890abcdef" + fake_block = 123 + fake_block_hash = "0x123abc" + fake_author = b"\x01" * 32 + fake_commitment = b"\x02" * 32 + fake_ciphertext = b"\x03" * 100 + fake_submitted_in = 100 + + fake_query_result = { + "author": [fake_author], + "commitment": [fake_commitment], + "ciphertext": [fake_ciphertext], + "submitted_in": fake_submitted_in, + } + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query = mocker.AsyncMock(return_value=fake_query_result) + subtensor.substrate.query = mocked_query + mocked_decode_account_id = mocker.patch.object( + async_subtensor, + "decode_account_id", + return_value="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + ) + + # Call + result = await subtensor.get_mev_shield_submission( + submission_id=fake_submission_id, block=fake_block + ) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query.assert_awaited_once_with( + module="MevShield", + storage_function="Submissions", + params=[bytes.fromhex("1234567890abcdef")], + block_hash=fake_block_hash, + ) + mocked_decode_account_id.assert_called_once_with([fake_author]) + assert result == { + "author": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "commitment": fake_commitment, + "ciphertext": fake_ciphertext, + "submitted_in": fake_submitted_in, + } + + +@pytest.mark.asyncio +async def test_get_mev_shield_submission_without_0x_prefix(subtensor, mocker): + """Test get_mev_shield_submission handles submission_id without 0x prefix.""" + # Prep + fake_submission_id = "1234567890abcdef" + fake_block = 123 + fake_block_hash = "0x123abc" + fake_query_result = { + "author": [b"\x01" * 32], + "commitment": [b"\x02" * 32], + "ciphertext": [b"\x03" * 100], + "submitted_in": 100, + } + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query = mocker.AsyncMock(return_value=fake_query_result) + subtensor.substrate.query = mocked_query + mocked_decode_account_id = mocker.patch.object( + async_subtensor, + "decode_account_id", + return_value="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + ) + + # Call + result = await subtensor.get_mev_shield_submission( + submission_id=fake_submission_id, block=fake_block + ) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query.assert_awaited_once_with( + module="MevShield", + storage_function="Submissions", + params=[bytes.fromhex("1234567890abcdef")], + block_hash=fake_block_hash, + ) + mocked_decode_account_id.assert_called_once_with([b"\x01" * 32]) + assert result is not None + + +@pytest.mark.asyncio +async def test_get_mev_shield_submission_none(subtensor, mocker): + """Test get_mev_shield_submission returns None when submission not found.""" + # Prep + fake_submission_id = "0x1234567890abcdef" + fake_block = 123 + fake_block_hash = "0x123abc" + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query = mocker.AsyncMock(return_value=None) + subtensor.substrate.query = mocked_query + + # Call + result = await subtensor.get_mev_shield_submission( + submission_id=fake_submission_id, block=fake_block + ) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query.assert_awaited_once_with( + module="MevShield", + storage_function="Submissions", + params=[bytes.fromhex("1234567890abcdef")], + block_hash=fake_block_hash, + ) + assert result is None + + +@pytest.mark.asyncio +async def test_get_mev_shield_submissions_success(subtensor, mocker): + """Test get_mev_shield_submissions returns all submissions when found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + fake_submission_id_1 = b"\x01" * 32 + fake_submission_id_2 = b"\x02" * 32 + fake_author_1 = b"\x03" * 32 + fake_author_2 = b"\x04" * 32 + fake_commitment_1 = b"\x05" * 32 + fake_commitment_2 = b"\x06" * 32 + fake_ciphertext_1 = b"\x07" * 100 + fake_ciphertext_2 = b"\x08" * 100 + + fake_query_result = mocker.AsyncMock() + fake_query_result.__aiter__.return_value = iter( + [ + ( + [fake_submission_id_1], + mocker.MagicMock( + value={ + "author": [fake_author_1], + "commitment": [fake_commitment_1], + "ciphertext": [fake_ciphertext_1], + "submitted_in": 100, + } + ), + ), + ( + [fake_submission_id_2], + mocker.MagicMock( + value={ + "author": [fake_author_2], + "commitment": [fake_commitment_2], + "ciphertext": [fake_ciphertext_2], + "submitted_in": 101, + } + ), + ), + ] + ) + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query_map = mocker.AsyncMock(return_value=fake_query_result) + subtensor.substrate.query_map = mocked_query_map + mocked_decode_account_id = mocker.patch.object( + async_subtensor, + "decode_account_id", + side_effect=[ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + ], + ) + + # Call + result = await subtensor.get_mev_shield_submissions(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query_map.assert_awaited_once_with( + module="MevShield", + storage_function="Submissions", + block_hash=fake_block_hash, + ) + assert result is not None + assert len(result) == 2 + assert "0x" + fake_submission_id_1.hex() in result + assert "0x" + fake_submission_id_2.hex() in result + assert result["0x" + fake_submission_id_1.hex()]["submitted_in"] == 100 + assert result["0x" + fake_submission_id_2.hex()]["submitted_in"] == 101 + # Verify decode_account_id was called for both submissions + assert mocked_decode_account_id.call_count == 2 + + +@pytest.mark.asyncio +async def test_get_mev_shield_submissions_none(subtensor, mocker): + """Test get_mev_shield_submissions returns None when no submissions found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + + fake_query_result = mocker.AsyncMock() + fake_query_result.__aiter__.return_value = iter([]) + + mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) + mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) + mocked_query_map = mocker.AsyncMock(return_value=fake_query_result) + subtensor.substrate.query_map = mocked_query_map + + # Call + result = await subtensor.get_mev_shield_submissions(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query_map.assert_awaited_once_with( + module="MevShield", + storage_function="Submissions", + block_hash=fake_block_hash, + ) + assert result is None + + +@pytest.mark.asyncio +async def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): + """Test mev_submit_encrypted calls submit_encrypted_extrinsic correctly.""" + # Prep + fake_call = mocker.Mock(spec=GenericCall) + fake_signer_keypair = mocker.Mock() + fake_period = 128 + fake_raise_error = False + fake_wait_for_inclusion = True + fake_wait_for_finalization = True + fake_wait_for_revealed_execution = True + fake_blocks_for_revealed_execution = 5 + + mocked_submit_encrypted_extrinsic = mocker.AsyncMock() + mocker.patch.object( + async_subtensor, "submit_encrypted_extrinsic", mocked_submit_encrypted_extrinsic + ) + + # Call + result = await subtensor.mev_submit_encrypted( + wallet=fake_wallet, + call=fake_call, + signer_keypair=fake_signer_keypair, + period=fake_period, + raise_error=fake_raise_error, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + wait_for_revealed_execution=fake_wait_for_revealed_execution, + blocks_for_revealed_execution=fake_blocks_for_revealed_execution, + ) + + # Asserts + mocked_submit_encrypted_extrinsic.assert_awaited_once_with( + subtensor=subtensor, + wallet=fake_wallet, + call=fake_call, + signer_keypair=fake_signer_keypair, + period=fake_period, + raise_error=fake_raise_error, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + wait_for_revealed_execution=fake_wait_for_revealed_execution, + blocks_for_revealed_execution=fake_blocks_for_revealed_execution, + ) + assert result == mocked_submit_encrypted_extrinsic.return_value + + +@pytest.mark.asyncio +async def test_mev_submit_encrypted_default_params(subtensor, fake_wallet, mocker): + """Test mev_submit_encrypted with default parameters.""" + # Prep + fake_call = mocker.Mock(spec=GenericCall) + + mocked_submit_encrypted_extrinsic = mocker.AsyncMock() + mocker.patch.object( + async_subtensor, "submit_encrypted_extrinsic", mocked_submit_encrypted_extrinsic + ) + + # Call + result = await subtensor.mev_submit_encrypted(wallet=fake_wallet, call=fake_call) + + # Asserts + mocked_submit_encrypted_extrinsic.assert_awaited_once_with( + subtensor=subtensor, + wallet=fake_wallet, + call=fake_call, + signer_keypair=None, + period=DEFAULT_PERIOD, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, + blocks_for_revealed_execution=5, + ) + assert result == mocked_submit_encrypted_extrinsic.return_value diff --git a/tests/unit_tests/test_stream.py b/tests/unit_tests/test_stream.py index 04a38cb7c0..be333d59b3 100644 --- a/tests/unit_tests/test_stream.py +++ b/tests/unit_tests/test_stream.py @@ -68,6 +68,7 @@ class TestBTStreamingResponseModel: def test_bt_streaming_response_model_creation(self): """Test BTStreamingResponseModel initialization.""" + async def mock_token_streamer(send: Send): await send({"type": "http.response.body", "body": b"test"}) @@ -76,6 +77,7 @@ async def mock_token_streamer(send: Send): def test_bt_streaming_response_model_validation(self): """Test that BTStreamingResponseModel validates token_streamer type.""" + # Should accept callable async def valid_streamer(send: Send): pass @@ -94,6 +96,7 @@ class TestBTStreamingResponse: def test_bt_streaming_response_creation(self): """Test BTStreamingResponse initialization.""" + async def mock_token_streamer(send: Send): await send({"type": "http.response.body", "body": b"test"}) @@ -106,6 +109,7 @@ async def mock_token_streamer(send: Send): def test_bt_streaming_response_without_synapse(self): """Test BTStreamingResponse initialization without synapse.""" + async def mock_token_streamer(send: Send): pass @@ -132,12 +136,14 @@ async def mock_token_streamer(send: Send): # Verify send was called with correct structure assert send_mock.call_count == 3 - + # First call: start response with headers first_call = send_mock.call_args_list[0][0][0] assert first_call["type"] == "http.response.start" assert first_call["status"] == 200 - assert any(h == (b"content-type", b"text/event-stream") for h in first_call["headers"]) + assert any( + h == (b"content-type", b"text/event-stream") for h in first_call["headers"] + ) # Second call: token streamer assert call_order == ["token_streamer"] @@ -169,6 +175,7 @@ async def test_streamer(send: Send): @pytest.mark.asyncio async def test_streaming_response_headers(self): """Verify content-type headers for event-streaming.""" + async def mock_streamer(send: Send): pass @@ -181,12 +188,13 @@ async def mock_streamer(send: Send): # Check that headers include text/event-stream headers_call = send_mock.call_args_list[0][0][0] headers = headers_call["headers"] - + assert (b"content-type", b"text/event-stream") in headers @pytest.mark.asyncio async def test_asgi_interface_compatibility(self): """Test ASGI scope/receive/send interface.""" + async def mock_streamer(send: Send): await send({"type": "http.response.body", "body": b"test"}) @@ -506,7 +514,7 @@ async def simple_streamer(send: Send): # Check headers in first call headers_call = send_mock.call_args_list[0][0][0] headers = dict(headers_call["headers"]) - + # Verify event-stream header is present assert headers.get(b"content-type") == b"text/event-stream" @@ -521,7 +529,7 @@ async def streamer2(send: Send): await send({"type": "http.response.body", "body": b"stream2"}) synapse = ConcreteStreamingSynapse() - + response1 = synapse.create_streaming_response(streamer1) response2 = synapse.create_streaming_response(streamer2) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 4e6ca74220..bbf2d8a69c 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1,12 +1,12 @@ import argparse import datetime import unittest.mock as mock -from unittest.mock import MagicMock, ANY +from unittest.mock import ANY, MagicMock import pytest import websockets from async_substrate_interface import sync_substrate -from async_substrate_interface.types import ScaleObj, Runtime +from async_substrate_interface.types import Runtime, ScaleObj from bittensor_wallet import Wallet from scalecodec import GenericCall @@ -15,16 +15,19 @@ from bittensor.core import subtensor as subtensor_module from bittensor.core.async_subtensor import AsyncSubtensor, logging from bittensor.core.axon import Axon -from bittensor.core.chain_data import SubnetHyperparameters, SelectiveMetagraphIndex -from bittensor.core.settings import version_as_int, DEFAULT_PERIOD +from bittensor.core.chain_data import SelectiveMetagraphIndex, SubnetHyperparameters +from bittensor.core.settings import ( + DEFAULT_MEV_PROTECTION, + DEFAULT_PERIOD, + version_as_int, +) from bittensor.core.subtensor import Subtensor -from bittensor.core.types import AxonServeCallParams -from bittensor.core.types import ExtrinsicResponse +from bittensor.core.types import AxonServeCallParams, ExtrinsicResponse from bittensor.utils import ( Certificate, + determine_chain_endpoint_and_network, u16_normalized_float, u64_normalized_float, - determine_chain_endpoint_and_network, ) from bittensor.utils.balance import Balance @@ -1180,10 +1183,12 @@ def test_serve_axon(subtensor, mocker): netuid=fake_netuid, axon=fake_axon, certificate=fake_certificate, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=fake_wait_for_inclusion, wait_for_finalization=fake_wait_for_finalization, + wait_for_revealed_execution=True, ) assert result == mocked_serve_axon_extrinsic.return_value @@ -1220,10 +1225,12 @@ def test_commit(subtensor, fake_wallet, mocker): netuid=fake_netuid, data_type=f"Raw{len(fake_data)}", data=fake_data.encode(), + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result is mocked_publish_metadata.return_value @@ -1278,11 +1285,13 @@ def test_transfer(subtensor, fake_wallet, mocker): destination_ss58=fake_dest, amount=fake_amount, transfer_all=False, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=fake_wait_for_inclusion, wait_for_finalization=fake_wait_for_finalization, keep_alive=True, period=DEFAULT_PERIOD, raise_error=False, + wait_for_revealed_execution=True, ) assert result == mocked_transfer_extrinsic.return_value @@ -1813,11 +1822,13 @@ def test_reveal_weights(subtensor, fake_wallet, mocker): version_key=version_as_int, weights=weights, salt=salt, + mev_protection=DEFAULT_MEV_PROTECTION, period=16, raise_error=False, wait_for_inclusion=False, wait_for_finalization=False, mechid=0, + wait_for_revealed_execution=True, ) @@ -2647,6 +2658,8 @@ def test_add_stake_success(mocker, fake_wallet, subtensor): rate_tolerance=0.005, period=DEFAULT_PERIOD, raise_error=False, + mev_protection=DEFAULT_MEV_PROTECTION, + wait_for_revealed_execution=True, ) assert result == mock_add_stake_extrinsic.return_value @@ -2682,7 +2695,7 @@ def test_add_stake_with_safe_staking(mocker, fake_wallet, subtensor): wallet=fake_wallet, hotkey_ss58=fake_hotkey_ss58, netuid=14, - amount=fake_amount.rao, + amount=fake_amount, wait_for_inclusion=True, wait_for_finalization=False, safe_staking=True, @@ -2690,6 +2703,8 @@ def test_add_stake_with_safe_staking(mocker, fake_wallet, subtensor): rate_tolerance=fake_rate_tolerance, period=DEFAULT_PERIOD, raise_error=False, + mev_protection=DEFAULT_MEV_PROTECTION, + wait_for_revealed_execution=True, ) assert result == mock_add_stake_extrinsic.return_value @@ -2721,10 +2736,12 @@ def test_add_stake_multiple_success(mocker, fake_wallet, subtensor): hotkey_ss58s=fake_hotkey_ss58, netuids=[1], amounts=fake_amount, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=True, wait_for_finalization=False, period=DEFAULT_PERIOD, raise_error=False, + wait_for_revealed_execution=True, ) assert result == mock_add_stake_multiple_extrinsic.return_value @@ -2761,10 +2778,12 @@ def test_unstake_success(mocker, subtensor, fake_wallet): safe_unstaking=False, allow_partial_stake=False, rate_tolerance=0.005, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, + raise_error=False, wait_for_inclusion=True, wait_for_finalization=False, - raise_error=False, + wait_for_revealed_execution=True, ) assert result == mock_unstake_extrinsic.return_value @@ -2801,10 +2820,12 @@ def test_unstake_with_safe_unstaking(mocker, subtensor, fake_wallet): safe_unstaking=True, allow_partial_stake=True, rate_tolerance=fake_rate_tolerance, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=False, + wait_for_revealed_execution=True, ) assert result == mock_unstake_extrinsic.return_value @@ -2848,8 +2869,10 @@ def test_swap_stake_success(mocker, subtensor, fake_wallet): safe_swapping=False, allow_partial_stake=False, rate_tolerance=0.005, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_revealed_execution=True, ) assert result == mock_swap_stake_extrinsic.return_value @@ -2894,8 +2917,10 @@ def test_swap_stake_with_safe_staking(mocker, subtensor, fake_wallet): safe_swapping=True, allow_partial_stake=True, rate_tolerance=fake_rate_tolerance, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_revealed_execution=True, ) assert result == mock_swap_stake_extrinsic.return_value @@ -2929,9 +2954,11 @@ def test_unstake_multiple_success(mocker, subtensor, fake_wallet): amounts=fake_amounts, wait_for_inclusion=True, wait_for_finalization=False, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, unstake_all=False, raise_error=False, + wait_for_revealed_execution=True, ) assert result == mock_unstake_multiple_extrinsic.return_value @@ -2980,12 +3007,14 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): weights=fake_weights, commit_reveal_version=4, version_key=subtensor_module.version_as_int, + mev_protection=DEFAULT_MEV_PROTECTION, wait_for_inclusion=fake_wait_for_inclusion, wait_for_finalization=fake_wait_for_finalization, block_time=12.0, period=8, raise_error=False, mechid=0, + wait_for_revealed_execution=True, ) assert result == mocked_commit_timelocked_mechanism_weights_extrinsic.return_value @@ -3042,10 +3071,12 @@ def test_set_subnet_identity(mocker, subtensor, fake_wallet): discord=fake_subnet_identity.discord, description=fake_subnet_identity.description, additional=fake_subnet_identity.additional, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_finalization=True, wait_for_inclusion=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -3141,10 +3172,12 @@ def test_start_call(subtensor, mocker): subtensor=subtensor, wallet=wallet_name, netuid=netuid, - wait_for_inclusion=True, - wait_for_finalization=False, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=False, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -3701,10 +3734,12 @@ def test_set_children(subtensor, fake_wallet, mocker): hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=fake_netuid, children=fake_children, - wait_for_finalization=True, - wait_for_inclusion=True, - raise_error=False, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_set_children_extrinsic.return_value @@ -3729,10 +3764,12 @@ def test_unstake_all(subtensor, fake_wallet, mocker): hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, rate_tolerance=0.005, - wait_for_inclusion=True, - wait_for_finalization=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == fake_unstake_all_extrinsic.return_value @@ -3879,10 +3916,12 @@ def test_add_liquidity(subtensor, fake_wallet, mocker): price_low=Balance.from_tao(180).rao, price_high=Balance.from_tao(130).rao, hotkey_ss58=None, - wait_for_inclusion=True, - wait_for_finalization=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -3912,10 +3951,12 @@ def test_modify_liquidity(subtensor, fake_wallet, mocker): position_id=position_id, liquidity_delta=Balance.from_tao(150), hotkey_ss58=None, - wait_for_inclusion=True, - wait_for_finalization=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -3943,10 +3984,12 @@ def test_remove_liquidity(subtensor, fake_wallet, mocker): netuid=netuid, position_id=position_id, hotkey_ss58=None, - wait_for_inclusion=True, - wait_for_finalization=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -3973,10 +4016,12 @@ def test_toggle_user_liquidity(subtensor, fake_wallet, mocker): wallet=fake_wallet, netuid=netuid, enable=enable, - wait_for_inclusion=True, - wait_for_finalization=True, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -4428,10 +4473,12 @@ def test_set_auto_stake(subtensor, mocker): wallet=wallet, netuid=netuid, hotkey_ss58=hotkey, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert result == mocked_extrinsic.return_value @@ -4509,10 +4556,12 @@ def test_contribute_crowdloan(mocker, subtensor): wallet=wallet, crowdloan_id=crowdloan_id, amount=amount, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4553,10 +4602,12 @@ def test_create_crowdloan(mocker, subtensor): end=end, call=call, target_address=target_address, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4591,10 +4642,12 @@ def test_crowdloan_methods_with_crowdloan_id_parameter( subtensor=subtensor, wallet=wallet, crowdloan_id=crowdloan_id, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4623,10 +4676,12 @@ def test_update_cap_crowdloan(mocker, subtensor): wallet=wallet, crowdloan_id=crowdloan_id, new_cap=new_cap, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4655,10 +4710,12 @@ def test_update_end_crowdloan(mocker, subtensor): wallet=wallet, crowdloan_id=crowdloan_id, new_end=new_end, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -4687,10 +4744,12 @@ def test_update_min_contribution_crowdloan(mocker, subtensor): wallet=wallet, crowdloan_id=crowdloan_id, new_min_contribution=new_min_contribution, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_extrinsic.return_value @@ -5094,10 +5153,12 @@ def test_claim_root(mocker, subtensor): subtensor=subtensor, wallet=wallet, netuids=netuids, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_claim_root_extrinsic.return_value @@ -5121,10 +5182,12 @@ def test_set_root_claim_type(mocker, subtensor): subtensor=subtensor, wallet=faked_wallet, new_root_claim_type=fake_new_root_claim_type, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_set_root_claim_type_extrinsic.return_value @@ -5444,10 +5507,12 @@ def test_add_proxy(mocker, subtensor): delegate_ss58=delegate_ss58, proxy_type=proxy_type, delay=delay, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_add_proxy_extrinsic.return_value @@ -5475,10 +5540,12 @@ def test_announce_proxy(mocker, subtensor): wallet=wallet, real_account_ss58=real_account_ss58, call_hash=call_hash, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_announce_extrinsic.return_value @@ -5509,10 +5576,12 @@ def test_create_pure_proxy(mocker, subtensor): proxy_type=proxy_type, delay=delay, index=index, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_create_pure_proxy_extrinsic.return_value @@ -5553,10 +5622,12 @@ def test_kill_pure_proxy(mocker, subtensor): height=height, ext_index=ext_index, force_proxy_type=subtensor_module.ProxyType.Any, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_kill_pure_proxy_extrinsic.return_value @@ -5576,10 +5647,12 @@ def test_poke_deposit(mocker, subtensor): mocked_poke_deposit_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=wallet, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_poke_deposit_extrinsic.return_value @@ -5608,10 +5681,12 @@ def test_proxy(mocker, subtensor): real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_proxy_extrinsic.return_value @@ -5645,10 +5720,12 @@ def test_proxy_announced(mocker, subtensor): real_account_ss58=real_account_ss58, force_proxy_type=force_proxy_type, call=call, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_proxy_announced_extrinsic.return_value @@ -5676,10 +5753,12 @@ def test_reject_proxy_announcement(mocker, subtensor): wallet=wallet, delegate_ss58=delegate_ss58, call_hash=call_hash, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_reject_announcement_extrinsic.return_value @@ -5707,10 +5786,12 @@ def test_remove_proxy_announcement(mocker, subtensor): wallet=wallet, real_account_ss58=real_account_ss58, call_hash=call_hash, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_remove_announcement_extrinsic.return_value @@ -5730,10 +5811,12 @@ def test_remove_proxies(mocker, subtensor): mocked_remove_proxies_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=wallet, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_remove_proxies_extrinsic.return_value @@ -5764,10 +5847,12 @@ def test_remove_proxy(mocker, subtensor): delegate_ss58=delegate_ss58, proxy_type=proxy_type, delay=delay, + mev_protection=DEFAULT_MEV_PROTECTION, period=DEFAULT_PERIOD, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + wait_for_revealed_execution=True, ) assert response == mocked_remove_proxy_extrinsic.return_value @@ -5894,3 +5979,456 @@ def test_get_stake_info_for_coldkeys_success(subtensor, mocker): mocked_stake_info_list_from_dicts.assert_has_calls( [mocker.call([stake_info_dict_1]), mocker.call([stake_info_dict_2])] ) + + +def test_get_mev_shield_current_key_success(subtensor, mocker): + """Test get_mev_shield_current_key returns correct key when found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + fake_public_key_bytes = b"\x00" * 1184 # ML-KEM-768 public key size + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query = mocker.patch.object(subtensor.substrate, "query") + mocked_query.return_value = iter([fake_public_key_bytes]) + + # Call + result = subtensor.get_mev_shield_current_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query.assert_called_once_with( + module="MevShield", + storage_function="CurrentKey", + block_hash=fake_block_hash, + ) + assert result == fake_public_key_bytes + + +def test_get_mev_shield_current_key_none(subtensor, mocker): + """Test get_mev_shield_current_key returns None when key not found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query = mocker.patch.object(subtensor.substrate, "query", return_value=None) + + # Call + result = subtensor.get_mev_shield_current_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query.assert_called_once_with( + module="MevShield", + storage_function="CurrentKey", + block_hash=fake_block_hash, + ) + assert result is None + + +def test_get_mev_shield_current_key_invalid_size(subtensor, mocker): + """Test get_mev_shield_current_key raises ValueError for invalid key size.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + fake_public_key_bytes = b"\x00" * 1000 # Invalid size + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query = mocker.patch.object(subtensor.substrate, "query") + mocked_query.return_value = iter([fake_public_key_bytes]) + + # Call & Assert + with pytest.raises(ValueError, match="Invalid ML-KEM-768 public key size"): + subtensor.get_mev_shield_current_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query.assert_called_once_with( + module="MevShield", + storage_function="CurrentKey", + block_hash=fake_block_hash, + ) + + +def test_get_mev_shield_next_key_success(subtensor, mocker): + """Test get_mev_shield_next_key returns correct key when found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + fake_public_key_bytes = b"\x00" * 1184 # ML-KEM-768 public key size + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query = mocker.patch.object(subtensor.substrate, "query") + mocked_query.return_value = iter([fake_public_key_bytes]) + + # Call + result = subtensor.get_mev_shield_next_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query.assert_called_once_with( + module="MevShield", + storage_function="NextKey", + block_hash=fake_block_hash, + ) + assert result == fake_public_key_bytes + + +def test_get_mev_shield_next_key_none(subtensor, mocker): + """Test get_mev_shield_next_key returns None when key not found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query = mocker.patch.object(subtensor.substrate, "query", return_value=None) + + # Call + result = subtensor.get_mev_shield_next_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query.assert_called_once_with( + module="MevShield", + storage_function="NextKey", + block_hash=fake_block_hash, + ) + assert result is None + + +def test_get_mev_shield_next_key_invalid_size(subtensor, mocker): + """Test get_mev_shield_next_key raises ValueError for invalid key size.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + fake_public_key_bytes = b"\x00" * 1000 # Invalid size + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query = mocker.patch.object(subtensor.substrate, "query") + mocked_query.return_value = iter([fake_public_key_bytes]) + + # Call & Assert + with pytest.raises(ValueError, match="Invalid ML-KEM-768 public key size"): + subtensor.get_mev_shield_next_key(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query.assert_called_once_with( + module="MevShield", + storage_function="NextKey", + block_hash=fake_block_hash, + ) + + +def test_get_mev_shield_submission_success(subtensor, mocker): + """Test get_mev_shield_submission returns correct submission when found.""" + # Prep + fake_submission_id = "0x1234567890abcdef" + fake_block = 123 + fake_block_hash = "0x123abc" + fake_author = b"\x01" * 32 + fake_commitment = b"\x02" * 32 + fake_ciphertext = b"\x03" * 100 + fake_submitted_in = 100 + + fake_query_result = { + "author": [fake_author], + "commitment": [fake_commitment], + "ciphertext": [fake_ciphertext], + "submitted_in": fake_submitted_in, + } + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query = mocker.patch.object( + subtensor.substrate, "query", return_value=fake_query_result + ) + mocked_decode_account_id = mocker.patch.object( + subtensor_module, + "decode_account_id", + return_value="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + ) + + # Call + result = subtensor.get_mev_shield_submission( + submission_id=fake_submission_id, block=fake_block + ) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query.assert_called_once_with( + module="MevShield", + storage_function="Submissions", + params=[bytes.fromhex("1234567890abcdef")], + block_hash=fake_block_hash, + ) + mocked_decode_account_id.assert_called_once_with([fake_author]) + assert result == { + "author": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "commitment": fake_commitment, + "ciphertext": fake_ciphertext, + "submitted_in": fake_submitted_in, + } + + +def test_get_mev_shield_submission_without_0x_prefix(subtensor, mocker): + """Test get_mev_shield_submission handles submission_id without 0x prefix.""" + # Prep + fake_submission_id = "1234567890abcdef" + fake_block = 123 + fake_block_hash = "0x123abc" + fake_query_result = { + "author": [b"\x01" * 32], + "commitment": [b"\x02" * 32], + "ciphertext": [b"\x03" * 100], + "submitted_in": 100, + } + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query = mocker.patch.object( + subtensor.substrate, "query", return_value=fake_query_result + ) + mocked_decode_account_id = mocker.patch.object( + subtensor_module, + "decode_account_id", + return_value="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + ) + + # Call + result = subtensor.get_mev_shield_submission( + submission_id=fake_submission_id, block=fake_block + ) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query.assert_called_once_with( + module="MevShield", + storage_function="Submissions", + params=[bytes.fromhex("1234567890abcdef")], + block_hash=fake_block_hash, + ) + mocked_decode_account_id.assert_called_once_with([b"\x01" * 32]) + assert result is not None + + +def test_get_mev_shield_submission_none(subtensor, mocker): + """Test get_mev_shield_submission returns None when submission not found.""" + # Prep + fake_submission_id = "0x1234567890abcdef" + fake_block = 123 + fake_block_hash = "0x123abc" + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query = mocker.patch.object(subtensor.substrate, "query", return_value=None) + + # Call + result = subtensor.get_mev_shield_submission( + submission_id=fake_submission_id, block=fake_block + ) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query.assert_called_once_with( + module="MevShield", + storage_function="Submissions", + params=[bytes.fromhex("1234567890abcdef")], + block_hash=fake_block_hash, + ) + assert result is None + + +def test_get_mev_shield_submissions_success(subtensor, mocker): + """Test get_mev_shield_submissions returns all submissions when found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + fake_submission_id_1 = b"\x01" * 32 + fake_submission_id_2 = b"\x02" * 32 + fake_author_1 = b"\x03" * 32 + fake_author_2 = b"\x04" * 32 + fake_commitment_1 = b"\x05" * 32 + fake_commitment_2 = b"\x06" * 32 + fake_ciphertext_1 = b"\x07" * 100 + fake_ciphertext_2 = b"\x08" * 100 + + fake_query_result = mocker.MagicMock() + fake_query_result.__iter__.return_value = iter( + [ + ( + [fake_submission_id_1], + mocker.MagicMock( + value={ + "author": [fake_author_1], + "commitment": [fake_commitment_1], + "ciphertext": [fake_ciphertext_1], + "submitted_in": 100, + } + ), + ), + ( + [fake_submission_id_2], + mocker.MagicMock( + value={ + "author": [fake_author_2], + "commitment": [fake_commitment_2], + "ciphertext": [fake_ciphertext_2], + "submitted_in": 101, + } + ), + ), + ] + ) + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query_map = mocker.patch.object( + subtensor.substrate, "query_map", return_value=fake_query_result + ) + mocked_decode_account_id = mocker.patch.object( + subtensor_module, + "decode_account_id", + side_effect=[ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + ], + ) + + # Call + result = subtensor.get_mev_shield_submissions(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query_map.assert_called_once_with( + module="MevShield", + storage_function="Submissions", + block_hash=fake_block_hash, + ) + assert result is not None + assert len(result) == 2 + assert "0x" + fake_submission_id_1.hex() in result + assert "0x" + fake_submission_id_2.hex() in result + assert result["0x" + fake_submission_id_1.hex()]["submitted_in"] == 100 + assert result["0x" + fake_submission_id_2.hex()]["submitted_in"] == 101 + # Verify decode_account_id was called for both submissions + assert mocked_decode_account_id.call_count == 2 + + +def test_get_mev_shield_submissions_none(subtensor, mocker): + """Test get_mev_shield_submissions returns None when no submissions found.""" + # Prep + fake_block = 123 + fake_block_hash = "0x123abc" + + fake_query_result = mocker.MagicMock() + fake_query_result.__iter__.return_value = iter([]) + + mocked_determine_block_hash = mocker.patch.object( + subtensor, "determine_block_hash", return_value=fake_block_hash + ) + mocked_query_map = mocker.patch.object( + subtensor.substrate, "query_map", return_value=fake_query_result + ) + + # Call + result = subtensor.get_mev_shield_submissions(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=fake_block) + mocked_query_map.assert_called_once_with( + module="MevShield", + storage_function="Submissions", + block_hash=fake_block_hash, + ) + assert result is None + + +def test_mev_submit_encrypted_success(subtensor, fake_wallet, mocker): + """Test mev_submit_encrypted calls submit_encrypted_extrinsic correctly.""" + # Prep + fake_call = mocker.Mock(spec=GenericCall) + fake_signer_keypair = mocker.Mock() + fake_period = 128 + fake_raise_error = False + fake_wait_for_inclusion = True + fake_wait_for_finalization = True + fake_wait_for_revealed_execution = True + fake_blocks_for_revealed_execution = 5 + + mocked_submit_encrypted_extrinsic = mocker.patch.object( + subtensor_module, "submit_encrypted_extrinsic" + ) + + # Call + result = subtensor.mev_submit_encrypted( + wallet=fake_wallet, + call=fake_call, + signer_keypair=fake_signer_keypair, + period=fake_period, + raise_error=fake_raise_error, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + wait_for_revealed_execution=fake_wait_for_revealed_execution, + blocks_for_revealed_execution=fake_blocks_for_revealed_execution, + ) + + # Asserts + mocked_submit_encrypted_extrinsic.assert_called_once_with( + subtensor=subtensor, + wallet=fake_wallet, + call=fake_call, + signer_keypair=fake_signer_keypair, + period=fake_period, + raise_error=fake_raise_error, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, + wait_for_revealed_execution=fake_wait_for_revealed_execution, + blocks_for_revealed_execution=fake_blocks_for_revealed_execution, + ) + assert result == mocked_submit_encrypted_extrinsic.return_value + + +def test_mev_submit_encrypted_default_params(subtensor, fake_wallet, mocker): + """Test mev_submit_encrypted with default parameters.""" + # Prep + fake_call = mocker.Mock(spec=GenericCall) + + mocked_submit_encrypted_extrinsic = mocker.patch.object( + subtensor_module, "submit_encrypted_extrinsic" + ) + + # Call + result = subtensor.mev_submit_encrypted(wallet=fake_wallet, call=fake_call) + + # Asserts + mocked_submit_encrypted_extrinsic.assert_called_once_with( + subtensor=subtensor, + wallet=fake_wallet, + call=fake_call, + signer_keypair=None, + period=DEFAULT_PERIOD, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + wait_for_revealed_execution=True, + blocks_for_revealed_execution=5, + ) + assert result == mocked_submit_encrypted_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor_api.py b/tests/unit_tests/test_subtensor_api.py index 7985959aba..606bd76ca3 100644 --- a/tests/unit_tests/test_subtensor_api.py +++ b/tests/unit_tests/test_subtensor_api.py @@ -41,6 +41,9 @@ def test_properties_methods_comparable(other_class: "Subtensor" = None): stakes_methods = [m for m in dir(subtensor_api.staking) if not m.startswith("_")] subnets_methods = [m for m in dir(subtensor_api.subnets) if not m.startswith("_")] wallets_methods = [m for m in dir(subtensor_api.wallets) if not m.startswith("_")] + mev_shield_methods = [ + m for m in dir(subtensor_api.mev_shield) if not m.startswith("_") + ] all_subtensor_api_methods = ( subtensor_api_methods @@ -56,6 +59,7 @@ def test_properties_methods_comparable(other_class: "Subtensor" = None): + stakes_methods + subnets_methods + wallets_methods + + mev_shield_methods ) # Assertions