From 4e27168ed5e9ea4e87a01a7b5f693b82e402e5bd Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 24 Feb 2026 12:41:55 -0800 Subject: [PATCH 1/4] bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0cfe2953..506233e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name = "bittensor-cli" -version = "9.18.1" +version = "9.19.0" description = "Bittensor CLI" readme = "README.md" authors = [ From d232129009f58b6fd1843e10796f53f7a6c6ace9 Mon Sep 17 00:00:00 2001 From: ibraheem-latent Date: Tue, 24 Feb 2026 12:45:13 -0800 Subject: [PATCH 2/4] update typing --- bittensor_cli/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 295232c3..3a5080d5 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -32,7 +32,7 @@ from rich.prompt import FloatPrompt, Prompt, IntPrompt from rich.table import Column, Table from rich.tree import Tree -from typing_extensions import Annotated +from typing import Annotated from yaml import safe_dump, safe_load from bittensor_cli.src import ( From fd060b363c86c7da354672029ff0bf5af327b702 Mon Sep 17 00:00:00 2001 From: Ibraheem <165814940+ibraheem-abe@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:20:55 -0800 Subject: [PATCH 3/4] Merge pull request #828 from opentensor/feat/updated-mev-shield-v2 Feat: Updated MevShield mechanics --- .../src/bittensor/extrinsics/mev_shield.py | 46 ++----------------- .../src/bittensor/subtensor_interface.py | 5 ++ bittensor_cli/src/commands/stake/add.py | 5 -- bittensor_cli/src/commands/stake/move.py | 7 --- bittensor_cli/src/commands/stake/remove.py | 7 --- bittensor_cli/src/commands/subnets/subnets.py | 3 -- pyproject.toml | 4 +- 7 files changed, 11 insertions(+), 66 deletions(-) diff --git a/bittensor_cli/src/bittensor/extrinsics/mev_shield.py b/bittensor_cli/src/bittensor/extrinsics/mev_shield.py index 4eabbd2b..81750f2a 100644 --- a/bittensor_cli/src/bittensor/extrinsics/mev_shield.py +++ b/bittensor_cli/src/bittensor/extrinsics/mev_shield.py @@ -1,4 +1,3 @@ -import hashlib from typing import TYPE_CHECKING, Optional from async_substrate_interface import AsyncExtrinsicReceipt @@ -37,18 +36,15 @@ async def encrypt_extrinsic( plaintext = bytes(signed_extrinsic.data.data) # Encrypt using ML-KEM-768 - ciphertext = encrypt_mlkem768(ml_kem_768_public_key, plaintext) - - # Commitment: blake2_256(payload_core) - commitment_hash = hashlib.blake2b(plaintext, digest_size=32).digest() - commitment_hex = "0x" + commitment_hash.hex() + ciphertext = encrypt_mlkem768( + ml_kem_768_public_key, plaintext, include_key_hash=True + ) # Create the MevShield.submit_encrypted call encrypted_call = await subtensor.substrate.compose_call( call_module="MevShield", call_function="submit_encrypted", call_params={ - "commitment": commitment_hex, "ciphertext": ciphertext, }, ) @@ -56,29 +52,9 @@ async def encrypt_extrinsic( return encrypted_call -async def extract_mev_shield_id(response: "AsyncExtrinsicReceipt") -> Optional[str]: - """ - Extract the MEV Shield wrapper ID from an extrinsic response. - - After submitting a MEV Shield encrypted call, the EncryptedSubmitted event - contains the wrapper ID needed to track execution. - - Args: - response: The extrinsic receipt from submit_extrinsic. - - Returns: - The wrapper ID (hex string) or None if not found. - """ - for event in await response.triggered_events: - if event["event_id"] == "EncryptedSubmitted": - return event["attributes"]["id"] - return None - - async def wait_for_extrinsic_by_hash( subtensor: "SubtensorInterface", extrinsic_hash: str, - shield_id: str, submit_block_hash: str, timeout_blocks: int = 2, status=None, @@ -112,7 +88,7 @@ async def _noop(_): return True starting_block = await subtensor.substrate.get_block_number(submit_block_hash) - current_block = starting_block + 1 + current_block = starting_block while current_block - starting_block <= timeout_blocks: if status: @@ -137,20 +113,6 @@ async def _noop(_): result_idx = idx break - # Failure: Decryption failed - call = extrinsic.value.get("call", {}) - if ( - call.get("call_module") == "MevShield" - and call.get("call_function") == "mark_decryption_failed" - ): - call_args = call.get("call_args", []) - for arg in call_args: - if arg.get("name") == "id" and arg.get("value") == shield_id: - result_idx = idx - break - if result_idx is not None: - break - if result_idx is not None: receipt = AsyncExtrinsicReceipt( substrate=subtensor.substrate, diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 57b4a627..f7e8a44a 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1289,6 +1289,11 @@ async def create_signed(call_to_sign, n): return False, format_error_message(await response.error_message), None except SubstrateRequestException as e: err_msg = format_error_message(e) + if mev_protection and "'result': 'invalid'" in str(e).lower(): + err_msg = ( + f"MEV Shield extrinsic rejected as invalid. " + f"This usually means the MEV Shield NextKey changed between fetching and submission." + ) if proxy and "Invalid Transaction" in err_msg: extrinsic_fee, signer_balance = await asyncio.gather( self.get_extrinsic_fee( diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index 2379a62a..9ffc885a 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -11,7 +11,6 @@ from bittensor_cli.src import COLOR_PALETTE from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.extrinsics.mev_shield import ( - extract_mev_shield_id, wait_for_extrinsic_by_hash, ) from bittensor_cli.src.bittensor.utils import ( @@ -166,11 +165,9 @@ async def safe_stake_extrinsic( else: if mev_protection: inner_hash = err_msg - mev_shield_id = await extract_mev_shield_id(response) mev_success, mev_error, response = await wait_for_extrinsic_by_hash( subtensor=subtensor, extrinsic_hash=inner_hash, - shield_id=mev_shield_id, submit_block_hash=response.block_hash, status=status_, ) @@ -258,11 +255,9 @@ async def stake_extrinsic( else: if mev_protection: inner_hash = err_msg - mev_shield_id = await extract_mev_shield_id(response) mev_success, mev_error, response = await wait_for_extrinsic_by_hash( subtensor=subtensor, extrinsic_hash=inner_hash, - shield_id=mev_shield_id, submit_block_hash=response.block_hash, status=status_, ) diff --git a/bittensor_cli/src/commands/stake/move.py b/bittensor_cli/src/commands/stake/move.py index 402bd4f2..f835f732 100644 --- a/bittensor_cli/src/commands/stake/move.py +++ b/bittensor_cli/src/commands/stake/move.py @@ -10,7 +10,6 @@ from bittensor_cli.src import COLOR_PALETTE from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.extrinsics.mev_shield import ( - extract_mev_shield_id, wait_for_extrinsic_by_hash, ) from bittensor_cli.src.bittensor.utils import ( @@ -709,11 +708,9 @@ async def move_stake( if success_: if mev_protection: inner_hash = err_msg - mev_shield_id = await extract_mev_shield_id(response) mev_success, mev_error, response = await wait_for_extrinsic_by_hash( subtensor=subtensor, extrinsic_hash=inner_hash, - shield_id=mev_shield_id, submit_block_hash=response.block_hash, status=status, ) @@ -927,11 +924,9 @@ async def transfer_stake( if success_: if mev_protection: inner_hash = err_msg - mev_shield_id = await extract_mev_shield_id(response) mev_success, mev_error, response = await wait_for_extrinsic_by_hash( subtensor=subtensor, extrinsic_hash=inner_hash, - shield_id=mev_shield_id, submit_block_hash=response.block_hash, status=status, ) @@ -1165,11 +1160,9 @@ async def swap_stake( if success_: if mev_protection: inner_hash = err_msg - mev_shield_id = await extract_mev_shield_id(response) mev_success, mev_error, response = await wait_for_extrinsic_by_hash( subtensor=subtensor, extrinsic_hash=inner_hash, - shield_id=mev_shield_id, submit_block_hash=response.block_hash, status=status, ) diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index bb8faceb..5736bf75 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -11,7 +11,6 @@ from bittensor_cli.src import COLOR_PALETTE from bittensor_cli.src.bittensor.extrinsics.mev_shield import ( - extract_mev_shield_id, wait_for_extrinsic_by_hash, ) from bittensor_cli.src.bittensor.balances import Balance @@ -649,11 +648,9 @@ async def _unstake_extrinsic( if success: if mev_protection: inner_hash = err_msg - mev_shield_id = await extract_mev_shield_id(response) mev_success, mev_error, response = await wait_for_extrinsic_by_hash( subtensor=subtensor, extrinsic_hash=inner_hash, - shield_id=mev_shield_id, submit_block_hash=response.block_hash, status=status, ) @@ -764,11 +761,9 @@ async def _safe_unstake_extrinsic( if success: if mev_protection: inner_hash = err_msg - mev_shield_id = await extract_mev_shield_id(response) mev_success, mev_error, response = await wait_for_extrinsic_by_hash( subtensor=subtensor, extrinsic_hash=inner_hash, - shield_id=mev_shield_id, submit_block_hash=response.block_hash, status=status, ) @@ -894,11 +889,9 @@ async def _unstake_all_extrinsic( if mev_protection: inner_hash = err_msg - mev_shield_id = await extract_mev_shield_id(response) mev_success, mev_error, response = await wait_for_extrinsic_by_hash( subtensor=subtensor, extrinsic_hash=inner_hash, - shield_id=mev_shield_id, submit_block_hash=response.block_hash, status=status, ) diff --git a/bittensor_cli/src/commands/subnets/subnets.py b/bittensor_cli/src/commands/subnets/subnets.py index 57a24710..9afc3dc2 100644 --- a/bittensor_cli/src/commands/subnets/subnets.py +++ b/bittensor_cli/src/commands/subnets/subnets.py @@ -20,7 +20,6 @@ ) from bittensor_cli.src.bittensor.extrinsics.root import root_register_extrinsic from bittensor_cli.src.bittensor.extrinsics.mev_shield import ( - extract_mev_shield_id, wait_for_extrinsic_by_hash, ) from rich.live import Live @@ -271,11 +270,9 @@ async def _find_event_attributes_in_extrinsic_receipt( # Check for MEV shield execution if mev_protection: inner_hash = err_msg - mev_shield_id = await extract_mev_shield_id(response) mev_success, mev_error, response = await wait_for_extrinsic_by_hash( subtensor=subtensor, extrinsic_hash=inner_hash, - shield_id=mev_shield_id, submit_block_hash=response.block_hash, status=status, ) diff --git a/pyproject.toml b/pyproject.toml index 506233e9..bc270d0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,10 +30,10 @@ classifiers = [ ] dependencies = [ "wheel", - "async-substrate-interface>=1.6.0", + "async-substrate-interface>=1.6.2", "aiohttp~=3.13", "backoff~=2.2.1", - "bittensor-drand>=1.2.0", + "bittensor-drand>=1.3.0", "GitPython>=3.0.0", "netaddr~=1.3.0", "numpy>=2.0.1,<3.0.0", From 53229d9610684c5f8b47295c1e23565f24b85792 Mon Sep 17 00:00:00 2001 From: Ibraheem <165814940+ibraheem-abe@users.noreply.github.com> Date: Thu, 19 Feb 2026 17:13:02 -0800 Subject: [PATCH 4/4] Merge pull request #829 from opentensor/update/python-3.9-eol Update: Python 3.9 End of Life --- .github/workflows/e2e-subtensor-tests.yml | 2 +- .github/workflows/ruff-formatter.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- pyproject.toml | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yml b/.github/workflows/e2e-subtensor-tests.yml index fcd7ddf5..b1527f85 100644 --- a/.github/workflows/e2e-subtensor-tests.yml +++ b/.github/workflows/e2e-subtensor-tests.yml @@ -163,7 +163,7 @@ jobs: os: - ubuntu-latest test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }} - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - name: Check-out repository uses: actions/checkout@v4 diff --git a/.github/workflows/ruff-formatter.yml b/.github/workflows/ruff-formatter.yml index 93166c30..52e7e359 100644 --- a/.github/workflows/ruff-formatter.yml +++ b/.github/workflows/ruff-formatter.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9.13"] + python-version: ["3.10"] steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 4e5d34e4..4e96f29b 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -27,7 +27,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - name: Check-out repository uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index bc270d0b..743c035d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,12 +12,11 @@ authors = [ ] license = "MIT" scripts = { btcli = "bittensor_cli.cli:main" } -requires-python = ">=3.9" +requires-python = ">=3.10" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: End Users/Desktop", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12",